Project structure:

DarkVPN_server/
├── static/
│   ├── css/
│   │   └── admin_panel.css
│   ├── js/
│   │   └── admin_panel.js
├── templates/
│   ├── admin_login.html
│   └── admin_panel.html
├── auth_server.py
├── database.py
├── requirements.txt
└── users.db


Project files:

auth_server.py
```py
# auth_server.py

import os
from flask import Flask, request, jsonify, session, render_template, redirect, url_for
from flask_socketio import SocketIO, emit
from datetime import datetime, timedelta
import uuid
import threading
from flask_cors import CORS
import logging

# Load environment variables (optional)
from dotenv import load_dotenv

load_dotenv()

from database import Database

app = Flask(__name__)
app.secret_key = os.getenv('SECRET_KEY', 'default_secret_key')
CORS(app)

socketio = SocketIO(app, cors_allowed_origins="*", async_mode='threading')

# Set up logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

db = Database()
admin_password = os.getenv('ADMIN_PASSWORD', 'viktortop4ik')
latest_extension_version = '1.0'

# Interval for checking user activity (seconds)
CHECK_INTERVAL = 10
TIMEOUT = 30  # Time without heartbeat before considering user disconnected


def check_user_activity():
    while True:
        socketio.sleep(CHECK_INTERVAL)
        now = datetime.utcnow()
        users = db.get_all_users()
        for user in users:
            if user['last_seen']:
                elapsed = (now - user['last_seen']).total_seconds()
                if elapsed > TIMEOUT and user['is_connected']:
                    user['is_connected'] = False
                    db.update_user(user)
                    emit_user_update(user)
                    logger.info(f"User {user['key']} disconnected due to timeout (last_seen: {user['last_seen']}).")


# Start background task for checking user activity
socketio.start_background_task(target=check_user_activity)


# API Endpoint for heartbeat
@app.route('/api/heartbeat', methods=['POST'])
def api_heartbeat():
    data = request.get_json()
    user_key = data.get('userKey')
    version = data.get('version')
    current_url = data.get('currentUrl')
    client_ip = request.remote_addr
    is_connected = data.get('isConnected', False)

    logger.info(
        f"Heartbeat received from userKey: {user_key}, IP: {client_ip}, URL: {current_url}, Connected: {is_connected}")

    if version != latest_extension_version:
        logger.warning(f"Version mismatch: {version} != {latest_extension_version}")
        return jsonify({'status': 'update_required'}), 200

    user = db.get_user_by_key(user_key)

    if not user:
        logger.warning(f"Unauthorized access attempt with userKey: {user_key}")
        return jsonify({'status': 'unauthorized'}), 403

    # Update user info
    user['last_seen'] = datetime.utcnow()
    user['ip'] = client_ip
    user['current_url'] = current_url
    user['is_connected'] = is_connected

    response_data = get_user_info(user)

    if not is_connected:
        # User is not connected, send update and don't provide proxy details
        db.update_user(user)
        emit_user_update(user)
        logger.info(f"User {user_key} is disconnected.")
        return jsonify({'status': 'disconnected', 'user_data': response_data}), 200

    if user['is_banned'] or user['is_stopped']:
        user['is_connected'] = False
        db.update_user(user)
        emit_user_update(user)
        logger.info(f"User {user_key} is {'banned' if user['is_banned'] else 'stopped'}.")
        return jsonify({'status': 'unauthorized', 'user_data': response_data}), 403

    if user['subscription_expires'] < datetime.utcnow():
        user['is_connected'] = False
        db.update_user(user)
        emit_user_update(user)
        logger.info(f"Subscription expired for user {user_key}.")
        return jsonify({'status': 'subscription_expired', 'user_data': response_data}), 403

    # User is authorized and connected, provide proxy details
    proxy_details = {
        'host': '185.106.94.81',
        'port': 44590,
        'scheme': 'socks5'
    }

    db.update_user(user)
    emit_user_update(user)
    logger.info(f"Proxy details sent to user {user_key}.")

    return jsonify({'status': 'ok', 'proxy': proxy_details, 'user_data': response_data}), 200

def get_user_info(user):
    if not user:
        return jsonify({'status': 'error', 'message': 'User not found'}), 404

    days_remaining = (user['subscription_expires'] - datetime.utcnow()).days
    subscription_expired = user['subscription_expires'] < datetime.utcnow()

    # Always include isSubscriptionExpired, defaulting to False
    response_data = {
        'status': 'ok',
        'username': user['username'],
        'daysRemaining': max(0, days_remaining),  # Ensure daysRemaining is not negative
        'isSubscriptionExpired': subscription_expired
    }
    return response_data


# LEGACY # LEGACY # LEGACY # LEGACY # LEGACY # LEGACY
@app.route('/api/user_info', methods=['POST'])
def api_user_info():
    data = request.get_json()
    user_key = data.get('userKey')

    user = db.get_user_by_key(user_key)
    response_data = get_user_info(user)

    return jsonify(response_data, 200)
# LEGACY # LEGACY # LEGACY # LEGACY # LEGACY # LEGACY


# Function to send user updates to admin clients
def emit_user_update(user):
    socketio.emit('user_update', {
        'key': user['key'],
        'username': user['username'],
        'ip': user['ip'] or 'N/A',
        'current_url': user['current_url'] or 'N/A',
        'is_connected': user['is_connected'],
        'subscription_expires': user['subscription_expires'].isoformat(),
        'is_banned': user['is_banned'],
        'is_stopped': user['is_stopped']
    }, namespace='/')


# Admin login
@app.route('/admin', methods=['GET', 'POST'])
def admin_login():
    if 'admin_logged_in' in session:
        return redirect(url_for('admin_panel'))

    if request.method == 'POST':
        password = request.form.get('password')
        if password == admin_password:
            session['admin_logged_in'] = True
            logger.info("Admin logged in successfully.")
            return redirect(url_for('admin_panel'))
        else:
            logger.warning("Invalid admin password attempt.")
            return render_template('admin_login.html', error='Неверный пароль')

    return render_template('admin_login.html')


# Admin panel
@app.route('/admin/panel')
def admin_panel():
    if 'admin_logged_in' not in session:
        return redirect(url_for('admin_login'))

    return render_template('admin_panel.html')


# Admin route to add a new user
@app.route('/admin/add_user', methods=['POST'])
def admin_add_user():
    print("add_user")
    if 'admin_logged_in' not in session:
        return jsonify({'status': 'error', 'message': 'Unauthorized'}), 401

    try:
        data = request.get_json()
        username = data.get('username')
        days = int(data.get('days', 0))

        if not username or days <= 0:
            logger.error("Invalid data for adding user.")
            return jsonify({'status': 'error', 'message': 'Invalid data'}), 400

        user_key = str(uuid.uuid4())
        subscription_expires = datetime.utcnow() + timedelta(days=days)
        print(0)
        user = db.add_user(username, user_key, subscription_expires)
        print(1)
        emit_user_update(user)
        print(2)
        logger.info(f"New user added: {username} with key: {user_key}")

        return jsonify({'status': 'ok', 'userKey': user_key}), 200

    except (ValueError, TypeError) as e:
        logger.error(f"Error adding user: {e}")
        return jsonify({'status': 'error', 'message': 'Invalid input'}), 400


# Admin actions with users
@app.route('/admin/action', methods=['POST'])
def admin_action():
    if 'admin_logged_in' not in session:
        return jsonify({'status': 'error', 'message': 'Unauthorized'}), 401

    try:
        data = request.get_json()
        action = data.get('action')
        user_key = data.get('userKey')

        if not action or not user_key:
            logger.error("Missing action or userKey in admin_action.")
            return jsonify({'status': 'error', 'message': 'Invalid data'}), 400

        user = db.get_user_by_key(user_key)
        if not user:
            logger.warning(f"User not found: {user_key}")
            return jsonify({'status': 'error', 'message': 'User not found'}), 404

        if action == 'ban':
            user['is_banned'] = not user['is_banned']
            db.update_user(user)
            logger.info(f"User {user_key} ban status toggled to {user['is_banned']}.")
        elif action == 'stop':
            user['is_stopped'] = not user['is_stopped']
            db.update_user(user)
            logger.info(f"User {user_key} stop status toggled to {user['is_stopped']}.")
        elif action == 'renew':
            try:
                days = int(data.get('days'))  # Get days as an integer
            except (ValueError, TypeError):
                logger.error("Invalid days value for renewal.")
                return jsonify({'status': 'error', 'message': 'Invalid days value'}), 400

            if days == 0:  # Check if days is zero, then set expiration to now!!
                user['subscription_expires'] = datetime.utcnow()  # Set expiration to now
                db.update_user(user)
                logger.info(f"User {user_key} subscription set to expired.")
                emit_user_update(user)
                return jsonify({'status': 'ok'}), 200

            if days > 0:
                if user['subscription_expires'] < datetime.utcnow():
                    user['subscription_expires'] = datetime.utcnow() + timedelta(days=days)
                else:
                    user['subscription_expires'] += timedelta(days=days)
                logger.info(f"User {user_key} subscription renewed by {days} days.")

            elif days < 0:  # Handle negative days
                user['subscription_expires'] -= timedelta(days=abs(days))  # Subtract days
                logger.info(f"User {user_key} subscription reduced by {abs(days)} days.")

            db.update_user(user)  # Update user in the database
        elif action == 'delete':
            db.delete_user(user_key)
            logger.info(f"User {user_key} deleted.")
            socketio.emit('user_deleted', {'key': user_key}, namespace='/')
        else:
            logger.error(f"Invalid action: {action}")
            return jsonify({'status': 'error', 'message': 'Invalid action'}), 400

        if action != 'delete':
            emit_user_update(user)
        return jsonify({'status': 'ok'}), 200

    except (ValueError, TypeError) as e:
        logger.error(f"Error in admin_action: {e}")
        return jsonify({'status': 'error', 'message': 'Invalid input'}), 400


# SocketIO connection event for admin panel
@socketio.on('connect')
def handle_connect():
    if 'admin_logged_in' in session:
        logger.info("Admin client connected via SocketIO.")
        # Send all current users to connected admin client
        users = db.get_all_users()
        for user in users:
            emit_user_update(user)
    else:
        logger.warning("Unauthorized SocketIO connection attempt.")
        emit('error', {'message': 'Unauthorized'}, namespace='/')
        # Close the connection for unauthorized users
        return False  # Interrupt the connection


# Route to log out
@app.route('/admin/logout')
def admin_logout():
    session.pop('admin_logged_in', None)
    logger.info("Admin logged out.")
    return redirect(url_for('admin_login'))


# Run the application
if __name__ == '__main__':
    socketio.run(app, host='0.0.0.0', port=5011, allow_unsafe_werkzeug=True)

```


database.py
```py
# database.py

import sqlite3
import threading
from datetime import datetime

class Database:
    def __init__(self, db_file='users.db'):
        self.db_file = db_file
        self.lock = threading.Lock()
        self.create_table()

    def create_table(self):
        with self.lock:
            conn = sqlite3.connect(self.db_file)
            conn.row_factory = sqlite3.Row
            cursor = conn.cursor()
            cursor.execute('''
                CREATE TABLE IF NOT EXISTS users (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    username TEXT,
                    key TEXT UNIQUE,
                    subscription_expires DATETIME,
                    is_banned INTEGER,
                    is_stopped INTEGER,
                    is_connected INTEGER,
                    last_seen DATETIME,
                    ip TEXT,
                    current_url TEXT
                )
            ''')
            conn.commit()
            conn.close()

    def add_user(self, username, user_key, subscription_expires):
        with self.lock:
            conn = sqlite3.connect(self.db_file)
            conn.row_factory = sqlite3.Row
            cursor = conn.cursor()
            cursor.execute('''
                INSERT INTO users (username, key, subscription_expires, is_banned, is_stopped, is_connected, last_seen, ip, current_url)
                VALUES (?, ?, ?, 0, 0, 0, NULL, NULL, NULL)
            ''', (username, user_key, subscription_expires.isoformat()))
            conn.commit()
            conn.close()
        return self.get_user_by_key(user_key)

    def get_user_by_key(self, user_key):
        with self.lock:
            conn = sqlite3.connect(self.db_file)
            conn.row_factory = sqlite3.Row
            cursor = conn.cursor()
            cursor.execute('SELECT * FROM users WHERE key=?', (user_key,))
            row = cursor.fetchone()
            conn.close()
            if row:
                user = dict(row)
                user['subscription_expires'] = datetime.fromisoformat(user['subscription_expires'])
                if user['last_seen']:
                    user['last_seen'] = datetime.fromisoformat(user['last_seen'])
                else:
                    user['last_seen'] = None
                user['is_banned'] = bool(user['is_banned'])
                user['is_stopped'] = bool(user['is_stopped'])
                user['is_connected'] = bool(user['is_connected'])
                return user
            else:
                return None

    def update_user(self, user):
        with self.lock:
            conn = sqlite3.connect(self.db_file)
            conn.row_factory = sqlite3.Row
            cursor = conn.cursor()
            cursor.execute('''
                UPDATE users SET 
                    username=?, 
                    subscription_expires=?, 
                    is_banned=?, 
                    is_stopped=?, 
                    is_connected=?, 
                    last_seen=?, 
                    ip=?, 
                    current_url=?
                WHERE key=?
            ''', (user['username'], user['subscription_expires'].isoformat(), int(user['is_banned']), int(user['is_stopped']), int(user['is_connected']), user['last_seen'].isoformat() if user['last_seen'] else None, user['ip'], user['current_url'], user['key']))
            conn.commit()
            conn.close()

    def delete_user(self, user_key):
        with self.lock:
            conn = sqlite3.connect(self.db_file)
            cursor = conn.cursor()
            cursor.execute('DELETE FROM users WHERE key=?', (user_key,))
            conn.commit()
            conn.close()

    def get_all_users(self):
        with self.lock:
            conn = sqlite3.connect(self.db_file)
            conn.row_factory = sqlite3.Row
            cursor = conn.cursor()
            cursor.execute('SELECT * FROM users')
            rows = cursor.fetchall()
            conn.close()
            users = []
            for row in rows:
                user = dict(row)
                user['subscription_expires'] = datetime.fromisoformat(user['subscription_expires'])
                if user['last_seen']:
                    user['last_seen'] = datetime.fromisoformat(user['last_seen'])
                else:
                    user['last_seen'] = None
                user['is_banned'] = bool(user['is_banned'])
                user['is_stopped'] = bool(user['is_stopped'])
                user['is_connected'] = bool(user['is_connected'])
                users.append(user)
            return users

    def close(self):
        pass  # No shared connection to close
```


requirements.txt
```txt
flask
flask-socketio
python-dotenv
flask-cors
```


static/js/admin_panel.js
```js
/* static/js/admin_panel.js */
const socket = io();

const userGrid = document.getElementById('user-grid');
const addUserBtn = document.getElementById('add-user-btn');

// Modals
const createUserModal = document.getElementById('create-user-modal');
const userKeyModal = document.getElementById('user-key-modal');
const renewModal = document.getElementById('renew-modal');

let currentUserKey = null; // For actions like renew

// Event listener for "Add New User" button
addUserBtn.addEventListener('click', function() {
    createUserModal.style.display = 'block';
});

// Event listener for creating a new user
document.getElementById('create-user-confirm').addEventListener('click', function() {
    const username = document.getElementById('new-username').value.trim();
    const days = parseInt(document.getElementById('new-user-days').value);

    if (!username || isNaN(days) || days <= 0) {
        alert('Пожалуйста, введите корректное имя пользователя и длительность подписки.');
        return;
    }

    fetch('/admin/add_user', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ username: username, days: days })
    })
    .then(response => response.json())
    .then(data => {
        if (data.status === 'ok') {
            // Закрыть модальное окно создания пользователя
            createUserModal.style.display = 'none';
            // Открыть модальное окно с ключом пользователя
            document.getElementById('user-key-display').value = data.userKey;
            userKeyModal.style.display = 'block';
            // Добавить зелёный эффект на плитке пользователя
            flashUserTile(data.userKey, 'success');
        } else {
            alert('Ошибка при создании пользователя: ' + data.message);
            if (data.userKey) {
                flashUserTile(data.userKey, 'error');
            }
        }
    })
    .catch(error => {
        console.error('Ошибка:', error);
        alert('Произошла ошибка при отправке запроса.');
        if (data && data.userKey) {
            flashUserTile(data.userKey, 'error');
        }
    });
});

// Event listener for copying the user key
document.getElementById('copy-user-key').addEventListener('click', function() {
    const userKeyInput = document.getElementById('user-key-display');
    userKeyInput.select();
    userKeyInput.setSelectionRange(0, 99999); // For mobile devices

    document.execCommand('copy');
});

// Close modals when clicking on the close buttons
document.querySelectorAll('.modal-close').forEach(function(btn) {
    btn.addEventListener('click', function() {
        this.parentElement.parentElement.style.display = 'none';
    });
});

// Handle clicks outside the modal content to close the modal
window.onclick = function(event) {
    if (event.target === createUserModal) {
        createUserModal.style.display = 'none';
    } else if (event.target === userKeyModal) {
        userKeyModal.style.display = 'none';
    } else if (event.target === renewModal) {
        renewModal.style.display = 'none';
    }
};

// Socket.IO event for user updates
socket.on('user_update', function(data) {
    let userTile = document.getElementById('user-' + data.key);
    if (!userTile) {
        userTile = document.createElement('div');
        userTile.id = 'user-' + data.key;
        userTile.className = 'user-tile';
        userGrid.appendChild(userTile);
    }

    // Определение класса статуса пользователя
    let statusClass = '';
    if (data.is_banned) {
        statusClass = 'banned';
    } else if (data.is_stopped) {
        statusClass = 'stopped';
    } else if (new Date(data.subscription_expires) < new Date()) {
        statusClass = 'expired';
    } else if (data.is_connected) {
        statusClass = 'connected';
    } else {
        statusClass = 'disconnected';
    }

    userTile.className = 'user-tile ' + statusClass;

    // Условие для отображения Current URL или Last URL
    let urlLabel = data.is_connected ? 'Current URL' : 'Last URL';
    let urlValue = data.current_url;

    userTile.innerHTML = `
        <h3>${data.username}</h3>
        <p>IP: <span class="user-ip">${data.ip || 'N/A'}</span></p>
        <p>Connected: <span class="user-connected">${data.is_connected}</span></p>
        <p>Expires: <span class="user-expires">${formatDate(data.subscription_expires)}</span></p>
        <p>${urlLabel}: <span class="user-url">${urlValue}</span></p>
        <div class="user-actions">
            <button class="ban-button" onclick="banUser('${data.key}')">${data.is_banned ? 'Unban' : 'Ban'}</button>
            <button class="stop-button" onclick="stopUser('${data.key}')">${data.is_stopped ? 'Unstop' : 'Stop'}</button>
            <button class="copy-button" onclick="copyUserKey('${data.key}')">Copy Key</button>
            <button class="renew-button" onclick="openRenewModal('${data.key}', '${data.username}')">Renew</button>
            <button class="delete-button" onclick="deleteUser('${data.key}')">Delete</button>
        </div>
    `;
});

// Handle 'user_deleted' event
socket.on('user_deleted', function(data) {
    let userTile = document.getElementById('user-' + data.key);
    if (userTile) {
        userTile.remove();
    }
});

// Function to format date
function formatDate(dateString) {
    const date = new Date(dateString);
    return date.toLocaleString();
}

// Functions for user actions
function banUser(userKey) {
    fetch('/admin/action', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ action: 'ban', userKey: userKey })
    })
    .then(response => response.json())
    .then(data => {
        if (data.status === 'ok') {
            flashUserTile(userKey, 'success');
        } else {
            alert('Ошибка при бане пользователя: ' + data.message);
            flashUserTile(userKey, 'error');
        }
    })
    .catch(error => {
        console.error('Ошибка:', error);
        alert('Произошла ошибка при отправке запроса.');
        flashUserTile(userKey, 'error');
    });
}

function stopUser(userKey) {
    fetch('/admin/action', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ action: 'stop', userKey: userKey })
    })
    .then(response => response.json())
    .then(data => {
        if (data.status === 'ok') {
            flashUserTile(userKey, 'success');
        } else {
            alert('Ошибка при остановке пользователя: ' + data.message);
            flashUserTile(userKey, 'error');
        }
    })
    .catch(error => {
        console.error('Ошибка:', error);
        alert('Произошла ошибка при отправке запроса.');
        flashUserTile(userKey, 'error');
    });
}

function deleteUser(userKey) {
    if (confirm('Вы уверены, что хотите удалить этого пользователя? Это действие необратимо.')) {
        fetch('/admin/action', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ action: 'delete', userKey: userKey })
        })
        .then(response => response.json())
        .then(data => {
            if (data.status === 'ok') {
                flashUserTile(userKey, 'success');
                // Дополнительно можно удалить плитку пользователя из DOM
                let userTile = document.getElementById('user-' + userKey);
                if (userTile) {
                    userTile.remove();
                }
            } else {
                alert('Ошибка при удалении пользователя: ' + data.message);
                flashUserTile(userKey, 'error');
            }
        })
        .catch(error => {
            console.error('Ошибка:', error);
            alert('Произошла ошибка при отправке запроса.');
            flashUserTile(userKey, 'error');
        });
    }
}

function copyUserKey(userKey) {
    const tempInput = document.createElement('input');
    tempInput.value = userKey;
    document.body.appendChild(tempInput);
    tempInput.select();
    tempInput.setSelectionRange(0, 99999); // For mobile devices
    document.execCommand('copy');
    document.body.removeChild(tempInput);
}

function openRenewModal(userKey, username) {
    currentUserKey = userKey;
    renewModal.style.display = 'block';
    document.getElementById('renew-user-info').textContent = `User: ${username}`;
    document.getElementById('renew-days').value = '';
}

/**
 * Добавляет временную анимацию к плитке пользователя.
 * @param {string} userKey - Уникальный ключ пользователя.
 * @param {string} status - 'success' или 'error'.
 */
function flashUserTile(userKey, status) {
    const userTile = document.getElementById('user-' + userKey);
    if (!userTile) return;

    if (status === 'success') {
        userTile.classList.add('success-flash');
    } else if (status === 'error') {
        userTile.classList.add('error-flash');
    }

    // Удаление класса после 1 секунды для возврата к исходному состоянию
    setTimeout(() => {
        userTile.classList.remove('success-flash');
        userTile.classList.remove('error-flash');
    }, 100); // Время должно соответствовать CSS переходу
}

// Renew subscription
document.getElementById('renew-confirm').addEventListener('click', function() {
    const days = parseInt(document.getElementById('renew-days').value);
    if (isNaN(days)) {
        alert('Пожалуйста, введите корректное количество дней.');
        return;
    }
    fetch('/admin/action', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ action: 'renew', userKey: currentUserKey, days: days })
    })
    .then(response => response.json())
    .then(data => {
        if (data.status === 'ok') {
            renewModal.style.display = 'none';
            flashUserTile(currentUserKey, 'success'); // Добавить зелёный эффект
        } else {
            alert('Ошибка при продлении подписки: ' + data.message);
            flashUserTile(currentUserKey, 'error'); // Добавить красный эффект
        }
    })
    .catch(error => {
        console.error('Ошибка:', error);
        alert('Произошла ошибка при отправке запроса.');
        flashUserTile(currentUserKey, 'error'); // Добавить красный эффект
    });
});
```


templates/admin_login.html
```html
<!-- templates/admin_login.html -->
<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <title>Admin Login</title>
    <style>
        /* Styles for login page */
        body {
            font-family: Arial, sans-serif;
            background-color: #34495e;
            display: flex;
            align-items: center;
            justify-content: center;
            height: 100vh;
            margin: 0;
        }

        .login-container {
            background-color: white;
            padding: 30px 40px;
            border-radius: 5px;
            box-shadow: 0 0 15px rgba(0,0,0,0.2);
            text-align: center;
        }

        .login-container h2 {
            margin-top: 0;
            margin-bottom: 20px;
            color: #2c3e50;
        }

        .login-container input {
            width: 100%;
            padding: 12px;
            margin-bottom: 20px;
            box-sizing: border-box;
            font-size: 16px;
        }

        .login-container button {
            width: 100%;
            padding: 12px;
            background-color: #2980b9;
            color: white;
            border: none;
            font-size: 16px;
            cursor: pointer;
            border-radius: 3px;
        }

        .login-container button:hover {
            background-color: #3498db;
        }

        .error {
            color: red;
            margin-bottom: 15px;
        }

    </style>
</head>
<body>
    <div class="login-container">
        <h2>Admin Login</h2>
        {% if error %}
        <p class="error">{{ error }}</p>
        {% endif %}
        <form method="post">
            <!-- Add CSRF token here if using -->
            <input type="password" name="password" placeholder="Введите секретный ключ" required>
            <button type="submit">Вход</button>
        </form>
    </div>
</body>
</html>
```


templates/admin_panel.html
```html
<!-- templates/admin_panel.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Admin Panel</title>
    <!-- Include CSS -->
    <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/admin_panel.css') }}">
    <!-- Include Google Fonts -->
    <link rel="preconnect" href="https://fonts.gstatic.com">
    <link href="https://fonts.googleapis.com/css2?family=Roboto&display=swap" rel="stylesheet">
    <!-- Include Socket.IO library -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/3.1.3/socket.io.min.js"></script>
</head>
<body>
    <!-- Sidebar -->
    <div class="sidebar">
        <h2>Admin Panel</h2>
        <button id="add-user-btn">Add New User</button>
    </div>

    <!-- Main content -->
    <div class="main-content">
        <h2>Users</h2>
        <div id="user-grid">
            <!-- User tiles will be dynamically added here -->
        </div>
    </div>

    <!-- User Creation Modal -->
    <div id="create-user-modal" class="modal">
        <div class="modal-content">
            <button class="modal-close">&times;</button>
            <h3>Add New User</h3>
            <input type="text" id="new-username" placeholder="Username">
            <input type="number" id="new-user-days" placeholder="Subscription Duration (days)">
            <button id="create-user-confirm">Create</button>
        </div>
    </div>

    <!-- Display User Key Modal -->
    <div id="user-key-modal" class="modal">
        <div class="modal-content">
            <button class="modal-close">&times;</button>
            <h3>User Key</h3>
            <input type="text" id="user-key-display" readonly>
            <button id="copy-user-key">Copy</button>
        </div>
    </div>

    <!-- Modal for Renew Subscription -->
    <div id="renew-modal" class="modal">
        <div class="modal-content">
            <button class="modal-close">&times;</button>
            <h3>Renew Subscription</h3>
            <p id="renew-user-info"></p>
            <input type="number" id="renew-days" placeholder="Days to renew">
            <button id="renew-confirm">Renew</button>
        </div>
    </div>

    <!-- Include JS -->
    <script src="{{ url_for('static', filename='js/admin_panel.js') }}"></script>
</body>
</html>
```



Это серверная часть моего проекта.
Можешь пожалуйста ее посмотреть и помочь мне.

Мне нужно поместить его в докер контейнер и запустить его на удаленном Linux-сервере, чтобы там продолжало все работать.
Мой хостинг - aeza.

Помоги пожалуйста