import os
import subprocess
import uuid
import yaml
import paramiko
from flask import Flask, jsonify, request, render_template, send_from_directory
from flask_cors import CORS
from flask_jwt_extended import (
    JWTManager, create_access_token,
    jwt_required, get_jwt_identity,
    verify_jwt_in_request
)
from passlib.hash import pbkdf2_sha256
from logging.handlers import RotatingFileHandler
import json
from datetime import datetime, timedelta
import random
import logging
import string
import user_agents
import traceback
from werkzeug.exceptions import BadRequest, NotFound, HTTPException
from apscheduler.schedulers.background import BackgroundScheduler
from jinja2 import Template
import posixpath
from functools import wraps

# Load environment variables for sensitive configurations
from dotenv import load_dotenv
load_dotenv()  # Loads variables from a .env file

# SSH Configuration
SSH_CONFIG = {
    'hostname': os.getenv('SSH_HOSTNAME', 'your_server_ip'),  # Replace with your server IP or hostname
    'port': int(os.getenv('SSH_PORT', 22)),
    'username': os.getenv('SSH_USERNAME', 'root'),
    'password': os.getenv('SSH_PASSWORD'),  # Ensure this is set
    'key_filepath': os.getenv('SSH_KEY_FILEPATH')  # Optional: path to SSH private key
}

# Load the server IP from the environment variable
SERVER_IP = os.getenv('SERVER_IP', 'your_server_ip')  # Replace with your server IP

app = Flask(
    __name__,
    static_folder='static',
    static_url_path='/static',  # Ensure static files are served from /static
    template_folder='templates'
)
CORS(app)

# JWT Configuration
app.config['JWT_SECRET_KEY'] = os.getenv('JWT_SECRET_KEY', 'your_default_jwt_secret_key')  # Set this in environment
app.config['JWT_ACCESS_TOKEN_EXPIRES'] = timedelta(hours=1)
jwt = JWTManager(app)

# Server Plans
SERVER_PLANS = [
    {
        "key": "basic",
        "name": "Базовый",
        "cpu": 2,
        "ram": 4,
        "disk": "50GB",
        "price": 500
    },
    {
        "key": "advanced",
        "name": "Продвинутый",
        "cpu": 4,
        "ram": 8,
        "disk": "100GB",
        "price": 1000
    },
    {
        "key": "premium",
        "name": "Премиум",
        "cpu": 8,
        "ram": 16,
        "disk": "200GB",
        "price": 2000
    }
]

OS_OPTIONS = {
    'win2020': 'Windows Server 2020',
    'win2022': 'Windows Server 2022',
    'win2025': 'Windows Server 2025',
    'win10': 'Windows 10',
    'win11': 'Windows 11'
}

# Define Remote Containers Directory
REMOTE_CONTAINERS_DIR = "/root"

# Initialize IP Pool
IP_POOL_START = [192, 168, 1, 100]  # Starting IP address
current_ip = IP_POOL_START.copy()

DB_LOCK_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'db.json.lock')

# Log directories (local)
LOGS_DIR = 'logs'
if not os.path.exists(LOGS_DIR):
    os.makedirs(LOGS_DIR)

# Helper functions
def generate_unique_ip():
    global current_ip
    if SERVER_IP:
        return SERVER_IP  # Return the fixed IP from env variable

    ip = '.'.join(map(str, current_ip))
    current_ip[3] += 1
    if current_ip[3] > 254:
        current_ip[3] = 1
        current_ip[2] += 1
        if current_ip[2] > 254:
            current_ip[2] = 1
            current_ip[1] += 1
            if current_ip[1] > 254:
                current_ip[1] = 1
                current_ip[0] += 1
                if current_ip[0] > 254:
                    raise Exception("IP Pool Exhausted")
    return ip

def get_remote_server_dir(container_name):
    safe_container_name = ''.join(c for c in container_name if c.isalnum() or c in ('_', '-'))
    return posixpath.join(REMOTE_CONTAINERS_DIR, safe_container_name)

@app.route('/')
def home():
    return render_template('index.html')

@app.route('/admin')
def admin_page():
    return render_template('admin.html')

# Serve favicon
@app.route('/favicon.ico')
def favicon():
    return send_from_directory(os.path.join(app.root_path, 'static'),
                               'favicon.ico', mimetype='image/vnd.microsoft.icon')

def setup_logging():
    formatter = logging.Formatter(
        '[%(asctime)s] %(levelname)s in %(module)s: %(message)s'
    )
    file_handler = RotatingFileHandler(
        os.path.join(LOGS_DIR, 'app.log'),
        maxBytes=1024 * 1024,
        backupCount=10
    )
    file_handler.setFormatter(formatter)
    file_handler.setLevel(logging.INFO)

    error_file_handler = RotatingFileHandler(
        os.path.join(LOGS_DIR, 'error.log'),
        maxBytes=1024 * 1024,
        backupCount=10
    )
    error_file_handler.setFormatter(formatter)
    error_file_handler.setLevel(logging.ERROR)

    app.logger.addHandler(file_handler)
    app.logger.addHandler(error_file_handler)
    app.logger.setLevel(logging.INFO)

    @app.before_request
    def log_request_info():
        app.logger.info(f'Request: {request.method} {request.url} from {request.remote_addr}')
        app.logger.debug(f'Headers: {dict(request.headers)}')

# Error Handlers

from werkzeug.exceptions import HTTPException, NotFound

@app.errorhandler(NotFound)
def handle_404(e):
    app.logger.error(f'404 Error: {request.url} not found.')
    return render_template('404.html'), 404

@app.errorhandler(Exception)
def handle_exception(e):
    if isinstance(e, HTTPException):
        return e
    app.logger.error(f'Unhandled Exception: {str(e)}')
    app.logger.error(traceback.format_exc())
    return jsonify({'error': 'An internal server error occurred'}), 500

def load_db():
    try:
        db_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'db.json')
        if not os.path.exists(db_path):
            with open(db_path, 'w', encoding='utf-8') as f:
                json.dump({'users': {}, 'logs': []}, f, indent=2)
            app.logger.info('Initialized new db.json with default structure.')

        with open(db_path, 'r', encoding='utf-8') as f:
            content = f.read().strip()
            app.logger.debug(f'Content of db.json: {content}')  # Debug log
            if not content:
                app.logger.warning('db.json is empty. Initializing with default structure.')
                db = {'users': {}, 'logs': []}
            else:
                db = json.loads(content)

        db.setdefault('logs', [])
        db.setdefault('users', {})

        # Clean and validate user data
        cleaned_users = {}
        for username, user in db['users'].items():
            if isinstance(user, dict):
                user.setdefault('role', 'user')
                cleaned_users[username] = user
            else:
                app.logger.warning(f"Skipping invalid user entry: {username} is not a dictionary.")

        db['users'] = cleaned_users

        # Ensure at least one admin exists
        if not any(user.get('role') == 'admin' for user in db['users'].values()):
            default_admin_username = os.getenv('DEFAULT_ADMIN_USERNAME', 'admin')
            default_admin_password = os.getenv('DEFAULT_ADMIN_PASSWORD', 'adminpass')
            if default_admin_username not in db['users']:
                hashed_password = pbkdf2_sha256.hash(default_admin_password)
                db['users'][default_admin_username] = {
                    'password': hashed_password,
                    'servers': [],
                    'balance': 0,
                    'tickets': [],
                    'logs': [],
                    'created_at': datetime.now().isoformat(),
                    'role': 'admin'
                }
                app.logger.info(f"Default admin user '{default_admin_username}' created.")

        save_db(db)
        return db
    except json.JSONDecodeError as e:
        app.logger.error(f'JSON Decode Error in load_db: {e}')
        raise
    except Exception as e:
        app.logger.error(f'Database load error: {str(e)}')
        app.logger.error(traceback.format_exc())
        raise

def save_db(db):
    db_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'db.json')
    with open(db_path, 'w', encoding='utf-8') as f:
        json.dump(db, f, indent=2)

def log_activity(username, action, user_agent_string):
    try:
        db = load_db()
        ua = user_agents.parse(user_agent_string)
        log_entry = {
            'timestamp': datetime.now().isoformat(),
            'username': username,
            'action': action,
            'browser': ua.browser.family,
            'os': ua.os.family,
            'device': ua.device.family,
            'ip': request.remote_addr
        }
        db['logs'].append(log_entry)
        save_db(db)
    except Exception as e:
        app.logger.error(f'Error logging activity: {str(e)}')
        app.logger.error(traceback.format_exc())

def generate_password(length=12):
    return ''.join(random.choices(string.ascii_letters + string.digits, k=length))

def connect_via_ssh():
    ssh = paramiko.SSHClient()
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    connect_params = {
        'hostname': SSH_CONFIG['hostname'],
        'port': SSH_CONFIG.get('port', 22),
        'username': SSH_CONFIG['username']
    }
    if SSH_CONFIG.get('key_filepath'):
        connect_params['key_filename'] = SSH_CONFIG['key_filepath']
    elif SSH_CONFIG.get('password'):
        connect_params['password'] = SSH_CONFIG['password']
    else:
        raise ValueError("Either 'key_filepath' or 'password' must be provided in SSH_CONFIG.")

    try:
        ssh.connect(**connect_params)
        app.logger.info(f"SSH connection established to {SSH_CONFIG['hostname']}")
    except Exception as e:
        app.logger.error(f'SSH connection failed: {e}')
        return None
    return ssh

def execute_ssh_command(command, ssh_client, remote_path=None):
    try:
        if remote_path:
            command = f"cd {remote_path} && {command}"

        app.logger.info(f"Executing SSH Command: {command}")
        stdin, stdout, stderr = ssh_client.exec_command(command)
        exit_status = stdout.channel.recv_exit_status()
        stdout_str = stdout.read().decode().strip()
        stderr_str = stderr.read().decode().strip()
        return stdout_str, stderr_str, exit_status
    except Exception as e:
        app.logger.error(f'SSH command execution error: {e}')
        return None, str(e), -1

def create_docker_compose_content(container_name, version, plan_details, rdp_username, rdp_password, vnc_password, public_port_8006, public_port_rdp_tcp, public_port_rdp_udp, public_port_vnc):
    docker_compose_template = """
version: '3'
services:
  windows:
    image: dockurr/windows
    container_name: {{ container_name }}
    environment:
      VERSION: "{{ version }}"
      DISK_SIZE: "{{ disk_size }}"
      RAM_SIZE: "{{ ram_size }}"
      CPU_CORES: "{{ cpu_cores }}"
      USERNAME: "{{ username }}"
      PASSWORD: "{{ password }}"
      VNC_PASSWORD: "{{ vnc_password }}"
      KEYBOARD: "us"
    devices:
      - /dev/kvm
    cap_add:
      - NET_ADMIN
    ports:
      - "{{ public_port_8006 }}:8006/tcp"
      - "{{ public_port_rdp_tcp }}:3389/tcp"
      - "{{ public_port_rdp_udp }}:3389/udp"
      - "{{ public_port_vnc }}:5900/tcp"
    stop_grace_period: 2m
    restart: unless-stopped
    """
    template = Template(docker_compose_template)
    return template.render(
        container_name=container_name,
        version=version,
        disk_size=plan_details['disk'],
        ram_size=plan_details['ram'],
        cpu_cores=plan_details['cpu'],
        username=rdp_username,
        password=rdp_password,
        vnc_password=vnc_password,
        public_port_8006=public_port_8006,
        public_port_rdp_tcp=public_port_rdp_tcp,
        public_port_rdp_udp=public_port_rdp_udp,
        public_port_vnc=public_port_vnc
    )

def is_valid_username(username):
    import re
    pattern = re.compile(r'^[A-Za-z0-9_]{3,30}$')
    return bool(pattern.match(username))

# Admin Decorator
def admin_required(fn):
    @wraps(fn)
    def wrapper(*args, **kwargs):
        verify_jwt_in_request()
        current_user = get_jwt_identity()
        db = load_db()
        user_data = db['users'].get(current_user)
        if not user_data or user_data.get('role') != 'admin':
            app.logger.warning(f'Unauthorized admin access attempt by user: {current_user}')
            return jsonify({'error': 'Admin access required'}), 403
        return fn(*args, **kwargs)
    return wrapper

@app.route('/api/login', methods=['POST'])
def login():
    try:
        app.logger.info('Processing login request')
        db = load_db()
        data = request.get_json()

        if not data:
            app.logger.warning('No JSON data in login request')
            return jsonify({'error': 'No JSON data provided'}), 400

        username = data.get('username')
        password = data.get('password')

        app.logger.info(f'Login attempt for user: {username}')

        if not username or not password:
            app.logger.warning('Missing username or password in login request')
            return jsonify({'error': 'Username and password are required'}), 400

        user_data = db['users'].get(username)
        if not user_data:
            app.logger.warning(f'Login failed: User {username} not found')
            return jsonify({'error': 'Invalid credentials'}), 401

        if password == user_data['password'] or pbkdf2_sha256.verify(password, user_data['password']):
            app.logger.info(f'Login successful for user: {username}')
            access_token = create_access_token(identity=username)
            try:
                log_activity(username, 'login', request.headers.get('User-Agent', ''))
            except Exception as e:
                app.logger.error(f'Failed to log login activity: {str(e)}')
            return jsonify({
                'token': access_token,
                'username': username,
                'balance': user_data.get('balance', 0),
                'role': user_data.get('role', 'user')  # Include role in response
            }), 200

        app.logger.warning(f'Login failed: Invalid password for user {username}')
        return jsonify({'error': 'Invalid credentials'}), 401

    except Exception as e:
        app.logger.error(f'Login error: {str(e)}')
        app.logger.error(traceback.format_exc())
        return jsonify({'error': 'An internal server error occurred'}), 500

@app.route('/api/register', methods=['POST'])
def register():
    try:
        app.logger.info('Processing registration request')
        db = load_db()
        data = request.get_json()

        if not data:
            app.logger.warning('No JSON data in registration request')
            return jsonify({'error': 'No JSON data provided'}), 400

        username = data.get('username')
        password = data.get('password')

        app.logger.info(f'Registration attempt for username: {username}')

        if not username or not password:
            app.logger.warning('Missing username or password in registration request')
            return jsonify({'error': 'Username and password are required'}), 400

        if not is_valid_username(username):
            app.logger.warning(f'Invalid username attempted: {username}')
            return jsonify({'error': 'Invalid username. Please use only letters, numbers, and underscores. Length between 3-30 characters.'}), 400

        if username in db['users']:
            app.logger.warning(f'Registration failed: Username {username} already exists')
            return jsonify({'error': 'Username already taken'}), 400

        hashed_password = pbkdf2_sha256.hash(password)

        db['users'][username] = {
            'password': hashed_password,
            'servers': [],
            'balance': 0,
            'tickets': [],
            'logs': [],
            'created_at': datetime.now().isoformat(),
            'role': 'user'  # Assign 'user' role by default
        }

        save_db(db)
        app.logger.info(f'Registration successful for user: {username}')
        log_activity(username, 'register', request.headers.get('User-Agent', ''))
        return jsonify({'message': 'Registration successful'}), 201

    except Exception as e:
        app.logger.error(f'Registration error: {str(e)}')
        app.logger.error(traceback.format_exc())
        return jsonify({'error': 'An internal server error occurred'}), 500

@app.route('/api/servers', methods=['GET', 'POST'])
@jwt_required()
def manage_servers():
    current_user = get_jwt_identity()
    db = load_db()

    user_data = db['users'].get(current_user)
    if not user_data:
        app.logger.warning(f'User {current_user} not found when accessing /api/servers')
        return jsonify({'error': 'User not found'}), 404

    if request.method == 'GET':
        servers = user_data.get('servers', [])
        return jsonify(servers), 200

    elif request.method == 'POST':
        data = request.get_json()

        if not data or 'plan' not in data or 'os' not in data or 'name' not in data:
            app.logger.warning('Invalid request data for server creation')
            return jsonify({'error': 'Invalid request data'}), 400

        plan_key = data['plan']
        os_selected = data['os']

        selected_plan = next((p for p in SERVER_PLANS if p['key'] == plan_key), None)

        if not selected_plan:
            app.logger.error(f"Invalid plan selected: {plan_key}")
            return jsonify({'error': 'Invalid plan selected'}), 400

        if user_data.get('balance', 0) < selected_plan['price']:
            app.logger.error(f"User {current_user} has insufficient funds.")
            return jsonify({'error': 'Insufficient funds'}), 400

        if os_selected not in OS_OPTIONS:
            app.logger.error(f"Invalid operating system selected: {os_selected}")
            return jsonify({'error': 'Invalid operating system selected'}), 400

        # Check if a server with the same name exists
        existing_server = next((s for s in user_data['servers'] if s['name'] == data['name']), None)
        if existing_server:
            return jsonify({'error': 'Server with this name already exists'}), 400

        # Generate a unique server ID
        new_server_id = max([s['id'] for s in user_data['servers']], default=0) + 1
        container_name = f"server_{new_server_id}"

        rdp_password = generate_password()
        vnc_password = generate_password()
        public_port_8006 = random.randint(10000, 60000)
        public_port_rdp_tcp = random.randint(10000, 60000)
        public_port_rdp_udp = public_port_rdp_tcp
        public_port_vnc = random.randint(5900, 6000)

        docker_compose_content = create_docker_compose_content(
            container_name, os_selected, selected_plan,
            data.get('rdp_username', 'admin'), rdp_password,
            vnc_password,  # Pass VNC password
            public_port_8006, public_port_rdp_tcp, public_port_rdp_udp,
            public_port_vnc  # Pass VNC port
        )

        ssh_client = connect_via_ssh()
        if not ssh_client:
            return jsonify({'error': 'Failed to establish SSH connection'}), 500

        remote_path = get_remote_server_dir(container_name)

        try:
            mkdir_command = f"mkdir -p {remote_path}"
            app.logger.info(f"Creating remote directory: {remote_path}")
            stdout_str, stderr_str, exit_status = execute_ssh_command(mkdir_command, ssh_client)
            if exit_status != 0:
                app.logger.error(f"Failed to create remote directory {remote_path}: {stderr_str}")
                raise Exception(f"Failed to create remote directory: {stderr_str}")
            app.logger.info(f"Remote directory created: {remote_path}")

            docker_compose_command = f"""cat <<EOF > {remote_path}/docker-compose.yml
{docker_compose_content}
EOF
"""
            app.logger.info(f"Creating docker-compose.yml on remote server for {container_name}")
            stdout_str, stderr_str, exit_status = execute_ssh_command(docker_compose_command, ssh_client)
            if exit_status != 0:
                app.logger.error(f"Error creating docker-compose.yml on remote server: {stderr_str}")
                raise Exception(f"Failed to create docker-compose.yml: {stderr_str}")
            app.logger.info(f"docker-compose.yml created successfully at {remote_path}")

            validate_command = f"docker-compose -f {remote_path}/docker-compose.yml config"
            validate_stdout, validate_stderr, validate_exit_status = execute_ssh_command(validate_command, ssh_client)
            if validate_exit_status != 0:
                app.logger.error(f'Docker Compose Validation Error: {validate_stderr}')
                raise Exception(f'Docker Compose validation failed: {validate_stderr}')
            else:
                app.logger.info("Docker Compose configuration is valid.")

            deploy_command = f"docker-compose -f {remote_path}/docker-compose.yml up -d"
            app.logger.info(f"Deploying server {container_name} on remote server")
            stdout_str, stderr_str, deploy_exit_status = execute_ssh_command(deploy_command, ssh_client)
            if deploy_exit_status != 0:
                app.logger.error(f'Docker Compose Deployment Error: {stderr_str}')
                raise Exception(f'Docker Compose deployment failed: {stderr_str}')
            else:
                app.logger.info(f"Server {container_name} deployed successfully.")
        except Exception as e:
            app.logger.error(f'Error during SSH operation: {e}')
            return jsonify({'error': f'Failed to deploy server: {str(e)}'}), 500
        finally:
            ssh_client.close()

        new_server = {
            'id': new_server_id,
            'name': data['name'],
            'plan': {
                'key': selected_plan['key'],
                'name': selected_plan['name'],
                'cpu': selected_plan['cpu'],
                'ram': selected_plan['ram'],
                'disk': selected_plan['disk'],
                'price': selected_plan['price']
            },
            'os': OS_OPTIONS[os_selected],
            'status': 'active',
            'ip': generate_unique_ip(),
            'created_at': datetime.now().isoformat(),
            'container_name': container_name,
            'ports': {
                '8006/tcp': public_port_8006,
                '3389/tcp': public_port_rdp_tcp,
                '3389/udp': public_port_rdp_udp,
                '5900/tcp': public_port_vnc
            },
            'vnc_password': vnc_password,
            'vnc_address': f"http://{SERVER_IP}:{public_port_vnc}"  # Assuming SERVER_IP is the public IP
        }

        user_data['balance'] -= selected_plan['price']
        user_data['servers'].append(new_server)
        save_db(db)
        log_activity(current_user, 'create_server', request.headers.get('User-Agent', ''))
        return jsonify(new_server), 201

def monthly_billing_check():
    app.logger.info("Checking for monthly billing...")
    try:
        db = load_db()
        for username, user in db['users'].items():
            for server in user.get('servers', []):
                if server.get("next_payment_due"):
                    due_date = datetime.fromisoformat(server["next_payment_due"])
                    if datetime.now() >= due_date:
                        handle_billing_cycle(user, server)
        save_db(db)
    except Exception as e:
        app.logger.error(f"Error during monthly billing check: {e}")
        app.logger.error(traceback.format_exc())

def handle_billing_cycle(user, server):
    plan = server["plan"]
    plan_price = plan["price"]

    if user.get("balance", 0) >= plan_price:
        user["balance"] -= plan_price
        server["next_payment_due"] = (datetime.now() + timedelta(days=30)).isoformat()
        app.logger.info(f"Successfully billed user for server '{server['name']}'. Next payment due on {server['next_payment_due']}.")
    else:
        server["status"] = "Заморожен"
        server["freeze_reason"] = "Недостаточно средств."
        server["freeze_date"] = datetime.now().isoformat()
        app.logger.info(f"Server '{server['name']}' frozen due to insufficient funds.")

def check_frozen_servers():
    app.logger.info("Checking for frozen servers...")
    try:
        db = load_db()
        for username, user in db['users'].items():
            servers_to_remove = []
            for server in user.get('servers', []):
                if server["status"] == "Заморожен":
                    freeze_date = datetime.fromisoformat(server["freeze_date"])
                    if datetime.now() > freeze_date + timedelta(days=3):
                        ssh_client = connect_via_ssh()
                        if not ssh_client:
                            app.logger.error(f"Failed to establish SSH connection to delete frozen server '{server['name']}'.")
                            continue
                        try:
                            execute_command = f"docker-compose -f {get_remote_server_dir(server['container_name'])}/docker-compose.yml kill && docker-compose -f {get_remote_server_dir(server['container_name'])}/docker-compose.yml rm -f"
                            app.logger.info(f"Deleting frozen server '{server['name']}' from remote server.")
                            stdout_str, stderr_str, exit_status = execute_ssh_command(execute_command, ssh_client)
                            if exit_status != 0:
                                app.logger.error(f"Failed to delete server '{server['name']}': {stderr_str}")
                                continue
                            servers_to_remove.append(server)
                            app.logger.info(f"Server '{server['name']}' deleted after being frozen for over 3 days.")
                        finally:
                            ssh_client.close()
            for server in servers_to_remove:
                user['servers'].remove(server)
        save_db(db)
    except Exception as e:
        app.logger.error(f"Error during frozen server check: {e}")
        app.logger.error(traceback.format_exc())

def initialize_scheduler():
    scheduler = BackgroundScheduler()
    scheduler.add_job(func=monthly_billing_check, trigger="interval", days=30)
    scheduler.add_job(func=check_frozen_servers, trigger="interval", hours=24)
    scheduler.start()
    app.logger.info("Scheduler started for billing and frozen server checks.")

@app.route('/api/servers/<int:server_id>', methods=['GET'])
@jwt_required()
def get_server(server_id):
    try:
        current_user = get_jwt_identity()
        db = load_db()
        user_data = db['users'].get(current_user)

        if not user_data:
            app.logger.warning(f'User {current_user} not found when accessing /api/servers/{server_id}')
            return jsonify({'error': 'User not found'}), 404

        server = next((s for s in user_data['servers'] if s['id'] == server_id), None)
        if not server:
            app.logger.warning(f'Server with ID {server_id} not found for user {current_user}')
            return jsonify({'error': 'Server not found'}), 404

        plan = server.get('plan', {})
        plan_key = plan.get('key')
        plan_name = plan.get('name', 'Unknown')

        if not plan_key:
            app.logger.error(f"Server {server_id} has a plan without a 'key'. Cannot process request.")
            return jsonify({'error': "Server's plan information is incomplete. Please contact support."}), 500

        formatted_server = {
            'id': server['id'],
            'name': server['name'],
            'plan': {
                'key': plan_key,
                'name': plan_name,
                'cpu': plan.get('cpu'),
                'ram': plan.get('ram'),
                'disk': plan.get('disk'),
                'price': plan.get('price')
            },
            'os': server['os'],
            'status': server['status'],
            'ip': server['ip'],
            'created_at': server['created_at'],
            'container_name': server['container_name'],
            'ports': server['ports'],
            'vnc_address': server.get('vnc_address'),
            'vnc_password': server.get('vnc_password')
        }

        return jsonify(formatted_server), 200

    except Exception as e:
        app.logger.error(f'Error in get_server endpoint: {str(e)}')
        app.logger.error(traceback.format_exc())
        return jsonify({'error': 'An internal server error occurred'}), 500

@app.route('/api/servers/<int:server_id>/toggle', methods=['POST'])
@jwt_required()
def toggle_server(server_id):
    try:
        current_user = get_jwt_identity()
        db = load_db()

        user_data = db['users'].get(current_user)
        if not user_data:
            return jsonify({'error': 'User not found'}), 404

        server = next((s for s in user_data['servers'] if s['id'] == server_id), None)
        if not server:
            return jsonify({'error': 'Server not found'}), 404

        container_name = server.get('container_name')
        if not container_name:
            app.logger.error('Container name not found for this server')
            return jsonify({'error': 'Container name not found for this server. Please contact support.'}), 400

        server_dir = get_remote_server_dir(container_name)
        if server['status'] == 'active':
            cmd = f"docker-compose -f {server_dir}/docker-compose.yml stop"
            new_status = 'frozen'
        else:
            cmd = f"docker-compose -f {server_dir}/docker-compose.yml start"
            new_status = 'active'

        ssh_client = connect_via_ssh()
        if not ssh_client:
            return jsonify({'error': 'Failed to establish SSH connection'}), 500

        try:
            stdout_str, stderr_str, exit_status = execute_ssh_command(cmd, ssh_client)
            if exit_status != 0:
                app.logger.error(f'SSH command error: {stderr_str}')
                return jsonify({'error': f'Failed to toggle server status: {stderr_str}'}), 500

            server['status'] = new_status
            save_db(db)
            log_activity(current_user, f'toggle_server_{server_id}', request.headers.get('User-Agent', ''))
            return jsonify({'message': f"Server status changed to {server['status']}"}), 200

        except Exception as e:
            app.logger.error(f'Error during SSH operation: {e}')
            return jsonify({'error': f'Failed to toggle server status: {str(e)}'}), 500
        finally:
            ssh_client.close()

    except Exception as e:
        app.logger.error(f'Unhandled exception in toggle_server: {str(e)}')
        app.logger.error(traceback.format_exc())
        return jsonify({'error': 'An internal server error occurred'}), 500

@app.route('/api/servers/<int:server_id>', methods=['DELETE'])
@jwt_required()
def delete_server(server_id):
    try:
        current_user = get_jwt_identity()
        db = load_db()
        user_data = db['users'].get(current_user)

        if not user_data:
            return jsonify({'error': 'User not found'}), 404

        server = next((s for s in user_data['servers'] if s['id'] == server_id), None)
        if not server:
            return jsonify({'error': 'Server not found'}), 404

        container_name = server.get('container_name')
        if not container_name:
            return jsonify({'error': 'Container name not found for this server'}), 400

        server_dir = get_remote_server_dir(container_name)
        # Добавляем команды docker volume prune -f и docker system prune -f
        commands = [
            f"docker-compose -f {server_dir}/docker-compose.yml down",
            f"rm -rf {server_dir}",
            "docker volume prune -f",  # Удаление неиспользуемых томов
            "docker system prune -f"    # Дополнительная очистка Docker
        ]
        full_command = ' && '.join(commands)

        ssh_client = connect_via_ssh()
        if not ssh_client:
            app.logger.error('Failed to establish SSH connection for deleting server.')
            return jsonify({'error': 'Failed to establish SSH connection'}), 500

        try:
            stdout_str, stderr_str, exit_status = execute_ssh_command(full_command, ssh_client)

            if exit_status != 0:
                app.logger.error(f'SSH command error: {stderr_str}')
                return jsonify({'error': f'Failed to delete server: {stderr_str}'}), 500

            user_data['servers'].remove(server)
            save_db(db)
            log_activity(current_user, f'delete_server_{server_id}', request.headers.get('User-Agent', ''))
            app.logger.info(f"Server {server_id} deleted successfully for user {current_user}.")
            return jsonify({'message': 'Server deleted successfully'}), 200

        except Exception as e:
            app.logger.error(f'Error during SSH operation: {e}')
            return jsonify({'error': f'Failed to delete server: {str(e)}'}), 500
        finally:
            ssh_client.close()

    except Exception as e:
        app.logger.error(f'Unhandled exception in delete_server: {str(e)}')
        app.logger.error(traceback.format_exc())
        return jsonify({'error': 'An internal server error occurred'}), 500

@app.route('/api/servers/<int:server_id>', methods=['PUT'])
@jwt_required()
def update_server(server_id):
    try:
        current_user = get_jwt_identity()
        db = load_db()
        user_data = db['users'].get(current_user)

        if not user_data:
            return jsonify({'error': 'User not found'}), 404

        server = next((s for s in user_data['servers'] if s['id'] == server_id), None)
        if not server:
            return jsonify({'error': 'Server not found'}), 404

        data = request.get_json()
        if not data:
            return jsonify({'error': 'No data provided'}), 400

        new_name = data.get('name')
        new_os = data.get('os')

        if new_name:
            server['name'] = new_name
            app.logger.info(f"Server ID {server_id} name updated to {new_name} by user {current_user}")

        if new_os:
            if new_os not in OS_OPTIONS:
                app.logger.error(f"Invalid operating system selected: {new_os}")
                return jsonify({'error': 'Invalid operating system selected'}), 400
                
            container_name = server.get('container_name')
            server_dir = get_remote_server_dir(container_name)
            docker_compose_path = posixpath.join(server_dir, 'docker-compose.yml')

            docker_compose_content = create_docker_compose_content(
                container_name, new_os, server['plan'],
                server.get('username', 'admin'), server.get('password', generate_password()),
                server.get('vnc_password', generate_password()),
                server['ports']['8006/tcp'], server['ports']['3389/tcp'],
                server['ports']['3389/udp'], server['ports']['5900/tcp']
            )

            ssh_client = connect_via_ssh()
            if not ssh_client:
                app.logger.error('Failed to establish SSH connection for updating server.')
                return jsonify({'error': 'Failed to establish SSH connection'}), 500

            try:
                docker_compose_command = f"""cat <<EOF > {docker_compose_path}
{docker_compose_content}
EOF
"""
                app.logger.info(f"Updating docker-compose.yml on remote server for {container_name}")
                stdout_str, stderr_str, exit_status = execute_ssh_command(docker_compose_command, ssh_client)
                if exit_status != 0:
                    app.logger.error(f"Error updating docker-compose.yml on remote server: {stderr_str}")
                    raise Exception(f"Failed to update docker-compose.yml: {stderr_str}")
                app.logger.info(f"docker-compose.yml updated successfully at {docker_compose_path}")

                restart_command = f"docker-compose -f {docker_compose_path} up -d"
                app.logger.info(f"Restarting server {container_name} to apply OS changes")
                stdout_str, stderr_str, exit_status = execute_ssh_command(restart_command, ssh_client)
                if exit_status != 0:
                    app.logger.error(f"Error restarting server on remote: {stderr_str}")
                    raise Exception(f"Failed to restart server: {stderr_str}")
                app.logger.info(f"Server {container_name} restarted successfully with new OS")

                server['os'] = OS_OPTIONS[new_os]

            except Exception as e:
                app.logger.error(f'Error during SSH operation: {e}')
                return jsonify({'error': f'Failed to update server: {str(e)}'}), 500
            finally:
                ssh_client.close()

        save_db(db)
        log_activity(current_user, f'update_server_{server_id}', request.headers.get('User-Agent', ''))
        return jsonify({'message': 'Server updated successfully', 'server': server}), 200
    except Exception as e:
        app.logger.error(f'Error in update_server endpoint: {e}')
        app.logger.error(traceback.format_exc())
        return jsonify({'error': 'An internal server error occurred'}), 500

@app.route('/api/userinfo', methods=['GET', 'PUT'])
@jwt_required()
def userinfo():
    try:
        current_user = get_jwt_identity()
        db = load_db()
        user_data = db['users'].get(current_user)

        if not user_data:
            app.logger.warning(f'User {current_user} not found when accessing /api/userinfo')
            return jsonify({'error': 'User not found'}), 404

        if request.method == 'GET':
            return jsonify({'username': current_user, 'balance': user_data.get('balance', 0)}), 200

        elif request.method == 'PUT':
            data = request.get_json()
            if not data:
                return jsonify({'error': 'No data provided'}), 400

            current_password = data.get('current_password')
            new_username = data.get('new_username')
            new_password = data.get('new_password')

            if not current_password:
                return jsonify({'error': 'Current password is required'}), 400

            if not (current_password == user_data['password'] or pbkdf2_sha256.verify(current_password, user_data['password'])):
                return jsonify({'error': 'Current password is incorrect'}), 401

            if new_username:
                if not is_valid_username(new_username):
                    return jsonify({'error': 'Invalid new username. Please use only letters, numbers, and underscores. Length between 3-30 characters.'}), 400
                if new_username in db['users']:
                    return jsonify({'error': 'Username already taken'}), 400
                db['users'][new_username] = db['users'].pop(current_user)
                current_user = new_username
                app.logger.info(f"User changed username to {new_username}")

            if new_password:
                db['users'][current_user]['password'] = pbkdf2_sha256.hash(new_password)
                app.logger.info(f"User {current_user} updated their password")

            save_db(db)
            log_activity(current_user, 'update_userinfo', request.headers.get('User-Agent', ''))
            return jsonify({'message': 'User information updated successfully', 'username': current_user}), 200

    except Exception as e:
        app.logger.error(f'Error in userinfo endpoint: {e}')
        app.logger.error(traceback.format_exc())
        return jsonify({'error': 'An internal server error occurred'}), 500

@app.route('/api/tickets', methods=['GET', 'POST'])
@jwt_required()
def tickets():
    try:
        current_user = get_jwt_identity()
        db = load_db()

        if request.method == 'GET':
            user_data = db['users'].get(current_user)
            if not user_data:
                return jsonify({'error': 'User not found'}), 404

            return jsonify(user_data.get('tickets', [])), 200

        elif request.method == 'POST':
            data = request.get_json()
            if not data or 'title' not in data or 'description' not in data:
                return jsonify({'error': 'Invalid request data'}), 400

            user_data = db['users'].get(current_user)
            if not user_data:
                return jsonify({'error': 'User not found'}), 404

            new_ticket = {
                'id': len(user_data['tickets']) + 1,
                'title': data['title'],
                'description': data.get('description', ''),
                'status': 'open',
                'created_at': datetime.now().isoformat(),
                'messages': []
            }

            user_data['tickets'].append(new_ticket)
            save_db(db)
            log_activity(current_user, 'create_ticket', request.headers.get('User-Agent', ''))
            return jsonify(new_ticket), 201

    except Exception as e:
        app.logger.error(f'Error in tickets endpoint: {e}')
        app.logger.error(traceback.format_exc())
        return jsonify({'error': 'An internal server error occurred'}), 500

@app.route('/api/tickets/<int:ticket_id>', methods=['GET'])
@jwt_required()
def get_ticket(ticket_id):
    try:
        current_user = get_jwt_identity()
        db = load_db()
        user_data = db['users'].get(current_user)

        if not user_data:
            return jsonify({'error': 'User not found'}), 404

        ticket = next((t for t in user_data['tickets'] if t['id'] == ticket_id), None)
        if not ticket:
            return jsonify({'error': 'Ticket not found'}), 404

        return jsonify(ticket), 200

    except Exception as e:
        app.logger.error(f'Error in get_ticket endpoint: {e}')
        app.logger.error(traceback.format_exc())
        return jsonify({'error': 'An internal server error occurred'}), 500

@app.route('/api/tickets/<int:ticket_id>/reply', methods=['POST'])
@jwt_required()
def reply_ticket(ticket_id):
    try:
        current_user = get_jwt_identity()
        db = load_db()

        user_data = db['users'].get(current_user)
        if not user_data:
            return jsonify({'error': 'User not found'}), 404

        ticket = next((t for t in user_data['tickets'] if t['id'] == ticket_id), None)
        if not ticket:
            return jsonify({'error': 'Ticket not found'}), 404

        data = request.get_json()
        if not data or 'message' not in data:
            return jsonify({'error': 'Message is required'}), 400

        message = {
            'content': data['message'],
            'sender': current_user,
            'timestamp': datetime.now().isoformat()
        }

        ticket['messages'].append(message)
        save_db(db)
        log_activity(current_user, f'reply_ticket_{ticket_id}', request.headers.get('User-Agent', ''))
        return jsonify(message), 201

    except Exception as e:
        app.logger.error(f'Error in reply_ticket endpoint: {e}')
        app.logger.error(traceback.format_exc())
        return jsonify({'error': 'An internal server error occurred'}), 500

# Admin Routes

@app.route('/api/admin/users', methods=['GET'])
@admin_required
def get_all_users():
    try:
        db = load_db()
        users = [
            {
                'username': username,
                'balance': user.get('balance', 0),
                'servers_count': len(user.get('servers', [])),
                'tickets_count': len(user.get('tickets', [])),
                'created_at': user.get('created_at'),
                'role': user.get('role', 'user')
            }
            for username, user in db['users'].items()
        ]
        app.logger.info(f'Admin accessed all users list.')
        return jsonify(users), 200
    except Exception as e:
        app.logger.error(f'Error in get_all_users: {e}')
        app.logger.error(traceback.format_exc())
        return jsonify({'error': 'An internal server error occurred'}), 500

@app.route('/api/admin/users/<string:username>', methods=['GET'])
@admin_required
def get_user_details(username):
    try:
        db = load_db()
        user = db['users'].get(username)
        if not user:
            app.logger.warning(f'Admin tried to access non-existent user: {username}')
            return jsonify({'error': 'User not found'}), 404

        user_details = {
            'username': username,
            'balance': user.get('balance', 0),
            'servers': user.get('servers', []),
            'tickets': user.get('tickets', []),
            'created_at': user.get('created_at'),
            'role': user.get('role', 'user')
        }

        app.logger.info(f'Admin accessed details for user: {username}')
        return jsonify(user_details), 200
    except Exception as e:
        app.logger.error(f'Error in get_user_details: {e}')
        app.logger.error(traceback.format_exc())
        return jsonify({'error': 'An internal server error occurred'}), 500

@app.route('/api/admin/users/<string:username>', methods=['DELETE'])
@admin_required
def delete_user(username):
    try:
        db = load_db()
        user = db['users'].get(username)
        if not user:
            app.logger.warning(f'Admin tried to delete non-existent user: {username}')
            return jsonify({'error': 'User not found'}), 404

        # Delete all servers associated with the user
        for server in user.get('servers', []):
            container_name = server.get('container_name')
            server_dir = get_remote_server_dir(container_name)
            commands = [
                f"docker-compose -f {server_dir}/docker-compose.yml down",
                f"rm -rf {server_dir}",
                "docker volume prune -f",
                "docker system prune -f"
            ]
            full_command = ' && '.join(commands)

            ssh_client = connect_via_ssh()
            if ssh_client:
                try:
                    stdout_str, stderr_str, exit_status = execute_ssh_command(full_command, ssh_client)
                    if exit_status != 0:
                        app.logger.error(f'Failed to delete server {server["name"]} for user {username}: {stderr_str}')
                except Exception as e:
                    app.logger.error(f'Error deleting server {server["name"]} for user {username}: {e}')
                finally:
                    ssh_client.close()

        del db['users'][username]
        save_db(db)
        log_activity(current_user, f'delete_user_{username}', request.headers.get('User-Agent', ''))
        app.logger.info(f'Admin deleted user: {username}')
        return jsonify({'message': f'User {username} deleted successfully'}), 200
    except Exception as e:
        app.logger.error(f'Error in delete_user: {e}')
        app.logger.error(traceback.format_exc())
        return jsonify({'error': 'An internal server error occurred'}), 500

@app.route('/api/admin/logs', methods=['GET'])
@admin_required
def get_logs():
    try:
        log_file_path = os.path.join(LOGS_DIR, 'app.log')
        if not os.path.exists(log_file_path):
            return jsonify({'logs': []}), 200

        with open(log_file_path, 'r') as log_file:
            logs = log_file.readlines()
        return jsonify({'logs': logs}), 200
    except Exception as e:
        app.logger.error(f'Error in get_logs: {e}')
        app.logger.error(traceback.format_exc())
        return jsonify({'error': 'Unable to retrieve logs'}), 500

@app.route('/api/admin/tickets', methods=['GET'])
@admin_required
def admin_get_all_tickets():
    try:
        db = load_db()
        all_tickets = []
        for username, user in db['users'].items():
            for ticket in user.get('tickets', []):
                ticket_info = ticket.copy()
                ticket_info['username'] = username
                all_tickets.append(ticket_info)
        return jsonify(all_tickets), 200
    except Exception as e:
        app.logger.error(f'Error in admin_get_all_tickets: {e}')
        app.logger.error(traceback.format_exc())
        return jsonify({'error': 'An internal server error occurred'}), 500
    
@app.route('/api/admin/tickets/<string:username>/<int:ticket_id>/reply', methods=['POST'])
@admin_required
def admin_reply_ticket(username, ticket_id):
    try:
        db = load_db()
        user_data = db['users'].get(username)
        if not user_data:
            return jsonify({'error': 'User not found'}), 404

        ticket = next((t for t in user_data['tickets'] if t['id'] == ticket_id), None)
        if not ticket:
            return jsonify({'error': 'Ticket not found'}), 404

        data = request.get_json()
        if not data or 'message' not in data:
            return jsonify({'error': 'Message is required'}), 400

        message = {
            'content': data['message'],
            'sender': 'admin',  # Indicate that the admin is replying
            'timestamp': datetime.now().isoformat()
        }

        ticket['messages'].append(message)
        save_db(db)
        return jsonify(message), 201

    except Exception as e:
        app.logger.error(f'Error in admin_reply_ticket: {e}')
        app.logger.error(traceback.format_exc())
        return jsonify({'error': 'An internal server error occurred'}), 500
    
@app.route('/api/admin/users/<string:username>/tickets/<int:ticket_id>', methods=['GET'])
@admin_required
def admin_get_ticket(username, ticket_id):
    try:
        db = load_db()
        user_data = db['users'].get(username)
        if not user_data:
            return jsonify({'error': 'User not found'}), 404

        ticket = next((t for t in user_data['tickets'] if t['id'] == ticket_id), None)
        if not ticket:
            return jsonify({'error': 'Ticket not found'}), 404

        return jsonify(ticket), 200
    except Exception as e:
        app.logger.error(f'Error in admin_get_ticket: {e}')
        app.logger.error(traceback.format_exc())
        return jsonify({'error': 'An internal server error occurred'}), 500

@app.route('/api/admin/servers', methods=['GET'])
@admin_required
def get_all_servers():
    try:
        db = load_db()
        servers = []
        for username, user in db['users'].items():
            for server in user.get('servers', []):
                server_info = server.copy()
                server_info['owner'] = username
                servers.append(server_info)
        app.logger.info('Admin accessed all servers list.')
        return jsonify(servers), 200
    except Exception as e:
        app.logger.error(f'Error in get_all_servers: {e}')
        app.logger.error(traceback.format_exc())
        return jsonify({'error': 'An internal server error occurred'}), 500

@app.route('/api/admin/servers/<int:server_id>/toggle', methods=['POST'])
@admin_required
def admin_toggle_server(server_id):
    try:
        db = load_db()
        target_server = None
        target_user = None

        # Find the server and its owner
        for username, user in db['users'].items():
            server = next((s for s in user.get('servers', []) if s['id'] == server_id), None)
            if server:
                target_server = server
                target_user = user
                break

        if not target_server:
            app.logger.warning(f'Admin tried to toggle non-existent server ID: {server_id}')
            return jsonify({'error': 'Server not found'}), 404

        container_name = target_server.get('container_name')
        if not container_name:
            app.logger.error('Container name not found for the server')
            return jsonify({'error': 'Container name not found for this server.'}), 400

        server_dir = get_remote_server_dir(container_name)
        if target_server['status'] == 'active':
            cmd = f"docker-compose -f {server_dir}/docker-compose.yml stop"
            new_status = 'frozen'
        else:
            cmd = f"docker-compose -f {server_dir}/docker-compose.yml start"
            new_status = 'active'

        ssh_client = connect_via_ssh()
        if not ssh_client:
            return jsonify({'error': 'Failed to establish SSH connection'}), 500

        try:
            stdout_str, stderr_str, exit_status = execute_ssh_command(cmd, ssh_client)
            if exit_status != 0:
                app.logger.error(f'SSH command error: {stderr_str}')
                return jsonify({'error': f'Failed to toggle server status: {stderr_str}'}), 500

            target_server['status'] = new_status
            save_db(db)
            log_activity(target_user['username'], f'admin_toggle_server_{server_id}', request.headers.get('User-Agent', ''))
            app.logger.info(f'Admin toggled server ID {server_id} to {new_status}')
            return jsonify({'message': f"Server status changed to {new_status}"}), 200

        except Exception as e:
            app.logger.error(f'Error during SSH operation: {e}')
            return jsonify({'error': f'Failed to toggle server status: {str(e)}'}), 500
        finally:
            ssh_client.close()
    except Exception as e:
        app.logger.error(f'Unhandled exception in admin_toggle_server: {str(e)}')
        app.logger.error(traceback.format_exc())
        return jsonify({'error': 'An internal server error occurred'}), 500

# Scheduler Initialization
initialize_scheduler()

if __name__ == '__main__':
    setup_logging()
    app.run(host='0.0.0.0', port=5000)



const API_URL = '/api';

// Initialize currentUser variable
let currentUser = null;

// Plan configurations
const planConfigs = {
    basic: { key: 'basic', name: 'Базовый', cpu: 2, ram: 4, disk: '50GB', price: 500 },
    advanced: { key: 'advanced', name: 'Продвинутый', cpu: 4, ram: 8, disk: '100GB', price: 1000 },
    premium: { key: 'premium', name: 'Премиум', cpu: 8, ram: 16, disk: '200GB', price: 2000 }
};

// Helper functions
function showElement(id) {
    const element = document.getElementById(id);
    if (element) {
        element.classList.remove('hidden');
    } else {
        console.warn(`Element with id "${id}" not found.`);
    }
}

function hideElement(id) {
    const element = document.getElementById(id);
    if (element) {
        element.classList.add('hidden');
    } else {
        console.warn(`Element with id "${id}" not found.`);
    }
}

function setActiveTab(tabId) {
    const contents = document.querySelectorAll('.tab-content');
    contents.forEach(content => {
        content.classList.remove('active');
        content.style.opacity = 0; // Fade out
    });

    const newContent = document.getElementById(tabId);
    newContent.classList.add('active');
    setTimeout(() => {
        newContent.style.opacity = 1; // Fade in
    }, 50);
}

function getAuthHeaders() {
    const token = localStorage.getItem('authToken');
    if (!token) {
        window.location.href = '/';
        return {};
    }
    return {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${token}`
    };
}


function showNotification(message, type = 'error') {
    const container = document.getElementById('notification-container');
    const notification = document.createElement('div');
    notification.className = `notification ${type}`;
    notification.innerHTML = `
        <span>${message}</span>
        <button class="close-notification">&times;</button>
    `;
    container.appendChild(notification);

    setTimeout(() => {
        notification.classList.add('show');
    }, 100);

    setTimeout(() => {
        notification.classList.remove('show');
        setTimeout(() => {
            notification.remove();
        }, 500);
    }, 5000);

    notification.querySelector('.close-notification').addEventListener('click', () => {
        notification.classList.remove('show');
        setTimeout(() => {
            notification.remove();
        }, 500);
    });
}

function logout() {
    localStorage.removeItem('authToken');
    localStorage.removeItem('username');
    localStorage.removeItem('balance');
    localStorage.removeItem('role');
    currentUser = null;
    updateUserInterface();
    window.location.href = '/';
}


// Обработчик регистрации
document.getElementById('register').addEventListener('submit', async (e) => {
    e.preventDefault();
    const username = document.getElementById('reg-username').value.trim();
    const password = document.getElementById('reg-password').value.trim();

    if (!username || !password) {
        showNotification('Пожалуйста, заполните все поля', 'error');
        return;
    }

    try {
        const response = await fetch(`${API_URL}/register`, {
            method: 'POST',
            headers: { 
                'Content-Type': 'application/json',
                'Accept': 'application/json'
            },
            body: JSON.stringify({ username, password })
        });

        const data = await response.json();

        if (response.ok) {
            showNotification('Регистрация успешна! Теперь вы можете войти.', 'success');
            showElement('login-form');
            hideElement('register-form');
        } else {
            showNotification(data.error || 'Ошибка регистрации', 'error');
        }
    } catch (error) {
        console.error('Ошибка регистрации:', error);
        showNotification('Ошибка при регистрации', 'error');
    }
});

// Login function
document.getElementById('login').addEventListener('submit', async (e) => {
    e.preventDefault();
    const username = document.getElementById('login-username').value.trim();
    const password = document.getElementById('login-password').value.trim();

    if (!username || !password) {
        showNotification('Пожалуйста, заполните все поля', 'error');
        return;
    }

    try {
        const response = await fetch(`${API_URL}/login`, {
            method: 'POST',
            headers: { 
                'Content-Type': 'application/json',
                'Accept': 'application/json',
                'X-Requested-With': 'XMLHttpRequest'
            },
            body: JSON.stringify({ username, password })
        });

        if (!response.ok) {
            const data = await response.json();
            throw new Error(data.error || `HTTP error! status: ${response.status}`);
        }

        const data = await response.json();

        if (data.token) {
            localStorage.setItem('authToken', data.token);
            localStorage.setItem('username', data.username);
            localStorage.setItem('balance', data.balance);
            localStorage.setItem('role', data.role);  // Store the role
            currentUser = {
                username: data.username,
                balance: data.balance,
                role: data.role
            };
            if (currentUser.role === 'admin') {
                window.location.href = '/admin.html';  // Redirect admins
            } else {
                updateUserInterface();
                setActiveTab('servers');
                showNotification('Вход выполнен успешно!', 'success');
            }
        } else {
            showNotification('Неверные учетные данные', 'error');
        }
    } catch (error) {
        console.error('Ошибка входа:', error);
        showNotification(`Ошибка при входе в систему: ${error.message}`, 'error');
    }
});

// Проверка аутентификации
function isAuthenticated() {
    return localStorage.getItem('authToken') !== null;
}

// Управление серверами
async function loadServers() {
    try {
        const response = await fetch(`${API_URL}/servers`, {
            method: 'GET',
            headers: getAuthHeaders()
        });

        if (!response.ok) {
            const errorData = await response.json();
            throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
        }

        const servers = await response.json();
        displayServers(servers);
    } catch (error) {
        console.error('Ошибка загрузки серверов:', error);
        showNotification(error.message, 'error');
    }
}

function displayServers(servers) {
    const serversList = document.getElementById('servers-list');
    serversList.innerHTML = '';

    if (!Array.isArray(servers) || servers.length === 0) {
        serversList.innerHTML = '<p>У вас нет серверов.</p>';
        return;
    }

    servers.forEach(server => {
        const serverCard = document.createElement('div');
        serverCard.className = 'server-card';
        serverCard.innerHTML = `
            <h3>${server.name}</h3>
            <span class="server-status status-${server.status}">${server.status}</span>
            <p><strong>IP:</strong> ${server.ip}</p>
            <p><strong>VNC:</strong> <a href="${server.vnc_address}" target="_blank">${server.vnc_address}</a></p>
            <p><strong>VNC Пароль:</strong> <span class="password-field" title="Наведи курсор для отображения">${server.vnc_password}</span></p>
            <p>План: ${server.plan.name}</p>
            <p>CPU: ${server.plan.cpu} / RAM: ${server.plan.ram}GB</p>
            <button class="btn primary view-server" data-server-id="${server.id}">Просмотреть</button>
            <button class="btn secondary update-server" data-server-id="${server.id}">Обновить</button>
            <button class="btn danger toggle-server" data-server-id="${server.id}">${server.status === 'active' ? 'Остановить' : 'Запустить'}</button>
            <button class="btn danger delete-server" data-server-id="${server.id}">Удалить</button>
            <button class="btn secondary restart-server" data-server-id="${server.id}">Перезапустить</button>
        `;
        serversList.appendChild(serverCard);
    });

    attachServerEventListeners();
    // Удаляем возможность показа пароля через кнопку
}

function attachServerEventListeners() {
    // Просмотр деталей сервера
    document.querySelectorAll('.view-server').forEach(button => {
        button.addEventListener('click', () => {
            const serverId = button.getAttribute('data-server-id');
            openServerDetailsModal(serverId);
        });
    });

    // Обновление сервера
    document.querySelectorAll('.update-server').forEach(button => {
        button.addEventListener('click', () => {
            const serverId = button.getAttribute('data-server-id');
            openUpdateServerModal(serverId);
        });
    });

    // Переключение статуса сервера
    document.querySelectorAll('.toggle-server').forEach(button => {
        button.addEventListener('click', () => {
            const serverId = button.getAttribute('data-server-id');
            toggleServerStatus(serverId);
        });
    });

    // Удаление сервера
    document.querySelectorAll('.delete-server').forEach(button => {
        button.addEventListener('click', () => {
            const serverId = button.getAttribute('data-server-id');
            deleteServer(serverId);
        });
    });

    // Перезапуск сервера
    document.querySelectorAll('.restart-server').forEach(button => {
        button.addEventListener('click', () => {
            const serverId = button.getAttribute('data-server-id');
            restartServer(serverId);
        });
    });
}

// Функция переключения статуса сервера
async function toggleServerStatus(serverId) {
    try {
        const response = await fetch(`${API_URL}/servers/${serverId}/toggle`, {
            method: 'POST',
            headers: getAuthHeaders()
        });

        if (!response.ok) {
            const errorData = await response.json();
            throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
        }

        const result = await response.json();
        showNotification(result.message, 'success');
        loadServers();
    } catch (error) {
        console.error('Ошибка при переключении сервера:', error);
        showNotification(error.message, 'error');
    }
}

// Функция удаления сервера
async function deleteServer(serverId) {
    if (!confirm('Вы уверены, что хотите удалить этот сервер? Это действие нельзя отменить.')) return;

    try {
        const response = await fetch(`${API_URL}/servers/${serverId}`, {
            method: 'DELETE',
            headers: getAuthHeaders()
        });

        if (!response.ok) {
            const errorData = await response.json();
            throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
        }

        const result = await response.json();
        showNotification(result.message, 'success');
        loadServers();
    } catch (error) {
        console.error('Ошибка при удалении сервера:', error);
        showNotification(error.message, 'error');
    }
}

// Функция перезапуска сервера
async function restartServer(serverId) {
    try {
        const response = await fetch(`${API_URL}/servers/${serverId}/restart`, {
            method: 'POST',
            headers: getAuthHeaders()
        });

        if (!response.ok) {
            const errorData = await response.json();
            throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
        }

        const result = await response.json();
        showNotification(result.message, 'success');
        loadServers();
    } catch (error) {
        console.error('Ошибка при перезагрузке сервера:', error);
        showNotification(error.message, 'error');
    }
}

// Управление тикетами
document.getElementById('ticket-form').addEventListener('submit', async (e) => {
    e.preventDefault();
    const title = document.getElementById('ticket-title').value.trim();
    const description = document.getElementById('ticket-description').value.trim();

    if (!title || !description) {
        showNotification('Пожалуйста, заполните все поля', 'error');
        return;
    }

    try {
        const response = await fetch(`${API_URL}/tickets`, {
            method: 'POST',
            headers: getAuthHeaders(),
            body: JSON.stringify({ title, description })
        });

        if (!response.ok) {
            const data = await response.json();
            throw new Error(data.error || 'Ошибка при создании тикета');
        }

        showNotification('Тикет успешно создан!', 'success');
        document.getElementById('ticket-form').reset();
        loadTickets();
    } catch (error) {
        console.error('Ошибка создания тикета:', error);
        showNotification(error.message, 'error');
    }
});

async function loadTickets() {
    try {
        const response = await fetch(`${API_URL}/tickets`, {
            method: 'GET',
            headers: getAuthHeaders()
        });

        if (!response.ok) {
            const errorData = await response.json();
            throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
        }

        const tickets = await response.json();
        displayTickets(tickets);
    } catch (error) {
        console.error('Ошибка загрузки тикетов:', error);
        showNotification(error.message, 'error');
    }
}

function displayTickets(tickets) {
    const ticketsList = document.getElementById('tickets-list');
    ticketsList.innerHTML = '';

    if (!Array.isArray(tickets) || tickets.length === 0) {
        ticketsList.innerHTML = '<p>У вас нет тикетов.</p>';
        return;
    }

    tickets.forEach(ticket => {
        const ticketCard = document.createElement('div');
        ticketCard.className = 'ticket-card';
        ticketCard.innerHTML = `
            <div class="ticket-header">
                <h3>${ticket.title}</h3>
                <span class="ticket-status status-${ticket.status}">${ticket.status}</span>
            </div>
            <p>${ticket.description}</p>
            <small>Создан: ${new Date(ticket.created_at).toLocaleString()}</small>
            <button class="btn primary view-ticket" data-ticket-id="${ticket.id}">Просмотреть</button>
        `;
        ticketsList.appendChild(ticketCard);
    });

    document.querySelectorAll('.view-ticket').forEach(button => {
        button.addEventListener('click', () => {
            const ticketId = button.getAttribute('data-ticket-id');
            openTicketModal(ticketId);
        });
    });
}

async function openTicketModal(ticketId) {
    try {
        const response = await fetch(`${API_URL}/tickets/${ticketId}`, {
            method: 'GET',
            headers: getAuthHeaders()
        });

        if (!response.ok) {
            const errorData = await response.json();
            throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
        }

        const ticket = await response.json();
        displayTicketDetails(ticket);
    } catch (error) {
        console.error('Ошибка загрузки тикета:', error);
        showNotification(error.message, 'error');
    }
}

function displayTicketDetails(ticket) {
    document.getElementById('modal-ticket-title').textContent = ticket.title;
    document.getElementById('modal-ticket-description').textContent = ticket.description;

    const messagesContainer = document.getElementById('modal-ticket-messages');
    messagesContainer.innerHTML = '';

    if (!Array.isArray(ticket.messages) || ticket.messages.length === 0) {
        messagesContainer.innerHTML = '<p>Нет сообщений.</p>';
    } else {
        ticket.messages.forEach(msg => {
            const msgDiv = document.createElement('div');
            msgDiv.className = 'ticket-message';
            msgDiv.innerHTML = `
                <p><strong>${msg.sender}</strong> <em>${new Date(msg.timestamp).toLocaleString()}</em></p>
                <p>${msg.content}</p>
            `;
            messagesContainer.appendChild(msgDiv);
        });
    }

    const ticketModal = document.getElementById('ticket-modal');
    ticketModal.setAttribute('data-ticket-id', ticket.id);
    showElement('ticket-modal');

    // Обработчик отправки ответа
    document.getElementById('reply-form').onsubmit = async (e) => {
        e.preventDefault();
        const messageContent = document.getElementById('reply-message').value.trim();

        if (!messageContent) {
            showNotification('Пожалуйста, введите сообщение', 'error');
            return;
        }

        try {
            const response = await fetch(`${API_URL}/tickets/${ticketId}/reply`, {
                method: 'POST',
                headers: getAuthHeaders(),
                body: JSON.stringify({ message: messageContent })
            });

            if (!response.ok) {
                const data = await response.json();
                throw new Error(data.error || 'Ошибка при отправке ответа');
            }

            const newMessage = await response.json();
            showNotification('Ответ успешно отправлен!', 'success');
            // Добавить новое сообщение в интерфейс
            const msgDiv = document.createElement('div');
            msgDiv.className = 'ticket-message';
            msgDiv.innerHTML = `
                <p><strong>${newMessage.sender}</strong> <em>${new Date(newMessage.timestamp).toLocaleString()}</em></p>
                <p>${newMessage.content}</p>
            `;
            messagesContainer.appendChild(msgDiv);
            document.getElementById('reply-form').reset();
        } catch (error) {
            console.error('Ошибка отправки ответа:', error);
            showNotification(error.message, 'error');
        }
    };
}

// Функциональность модального окна настроек
document.getElementById('settings').addEventListener('click', () => {
    // Установка активной вкладки
    setActiveTab('settings');
});

// Update Userinfo Form Handler
document.getElementById('settings-form-main').addEventListener('submit', async (e) => {
    e.preventDefault();
    const newUsername = document.getElementById('new-username-main').value.trim();
    const currentPassword = document.getElementById('current-password-main').value.trim();
    const newPassword = document.getElementById('new-password-main').value.trim();

    if (!currentPassword) {
        showNotification('Текущий пароль обязателен', 'error');
        return;
    }

    if (!newUsername && !newPassword) {
        showNotification('Пожалуйста, введите новое имя пользователя или пароль', 'error');
        return;
    }

    const payload = { current_password: currentPassword };
    if (newUsername) payload.new_username = newUsername;
    if (newPassword) payload.new_password = newPassword;

    try {
        const response = await fetch(`${API_URL}/userinfo`, {
            method: 'PUT',
            headers: getAuthHeaders(),
            body: JSON.stringify(payload)
        });

        if (!response.ok) {
            const data = await response.json();
            throw new Error(data.error || `HTTP error! status: ${response.status}`);
        }

        const data = await response.json();
        if (data.username) {
            localStorage.setItem('username', data.username);
            currentUser.username = data.username;
            document.getElementById('username').textContent = `Пользователь: ${currentUser.username}`;
        }

        if (data.balance !== undefined) {
            localStorage.setItem('balance', data.balance);
            currentUser.balance = data.balance;
            document.getElementById('balance').textContent = `Баланс: ${currentUser.balance} ₽`;
        }

        showNotification('Настройки успешно обновлены!', 'success');
        setActiveTab('servers'); // Переключение обратно на вкладку серверов
    } catch (error) {
        console.error('Ошибка обновления настроек:', error);
        showNotification(error.message, 'error');
    }
});

// Обработчики кнопок закрытия модальных окон
document.querySelectorAll('.close-modal').forEach(button => {
    button.addEventListener('click', () => {
        button.closest('.modal').classList.add('hidden');
    });
});

// Функция открытия модального окна создания сервера
document.getElementById('open-buy-server').addEventListener('click', () => {
    showElement('buy-server-modal');
});

// Обработчик формы покупки сервера
document.getElementById('buy-server-form').addEventListener('submit', async (e) => {
    e.preventDefault();
    const serverName = document.getElementById('server-name').value.trim();
    const serverOS = document.getElementById('server-os').value;

    const selectedPlanKey = document.querySelector('.buy-server.active')?.dataset.plan;
    const planDetails = planConfigs[selectedPlanKey];

    if (!serverName || !serverOS || !planDetails) {
        showNotification('Пожалуйста, заполните все поля и выберите план', 'error');
        return;
    }

    try {
        const response = await fetch(`${API_URL}/servers`, {
            method: 'POST',
            headers: getAuthHeaders(),
            body: JSON.stringify({
                name: serverName,
                plan: planDetails.key,
                os: serverOS
            })
        });

        if (!response.ok) {
            const errorData = await response.json();
            throw new Error(errorData.error || 'Ошибка при создании сервера');
        }

        const newServer = await response.json();
        showNotification('Сервер успешно создан!', 'success');
        hideElement('buy-server-modal');
        document.getElementById('buy-server-form').reset();
        loadServers();

        // Обновить баланс пользователя
        currentUser.balance -= planDetails.price;
        localStorage.setItem('balance', currentUser.balance);
        document.getElementById('balance').textContent = `Баланс: ${currentUser.balance} ₽`;
    } catch (error) {
        console.error('Ошибка покупки сервера:', error);
        showNotification(error.message, 'error');
    }
});

// Функция открытия модального окна деталей сервера
async function openServerDetailsModal(serverId) {
    try {
        const response = await fetch(`${API_URL}/servers/${serverId}`, {
            method: 'GET',
            headers: getAuthHeaders()
        });

        if (!response.ok) {
            const errorData = await response.json();
            throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
        }

        const server = await response.json();
        displayServerDetails(server);
    } catch (error) {
        console.error('Ошибка загрузки сервера:', error);
        showNotification(error.message, 'error');
    }
}

function displayServerDetails(server) {
    document.getElementById('modal-server-name').textContent = server.name;
    document.getElementById('modal-server-ip').textContent = server.ip;
    document.getElementById('modal-server-vnc-address').innerHTML = `<a href="${server.vnc_address}" target="_blank">${server.vnc_address}</a>`;
    document.getElementById('modal-server-vnc-password').textContent = server.vnc_password;

    // Используем CSS для раскрытия пароля при наведении
    // Здесь отсутствует кнопка "Показать", так что просто оставляем текст размытым

    // Добавить информацию о сервере
    document.getElementById('modal-server-plan').textContent = server.plan.name;
    document.getElementById('modal-server-os').textContent = server.os;
    document.getElementById('modal-server-status').textContent = server.status;

    // Заполнить порты
    const portsInfo = `
        <p><strong>Port 8006/tcp:</strong> ${server.ports["8006/tcp"]}</p>
        <p><strong>RDP TCP:</strong> ${server.ports["3389/tcp"]}</p>
        <p><strong>RDP UDP:</strong> ${server.ports["3389/udp"]}</p>
        <p><strong>VNC TCP:</strong> ${server.ports["5900/tcp"]}</p>
    `;
    document.getElementById('server-ports').innerHTML = portsInfo;

    showElement('server-details-modal');
}

// Функция обновления интерфейса пользователя
function updateUserInterface() {
    const authContainer = document.getElementById('auth-container');
    const mainContent = document.getElementById('main-content');
    const usernameElement = document.getElementById('username');
    const balanceElement = document.getElementById('balance');

    if (isAuthenticated()) {
        if (!currentUser) {
            const username = localStorage.getItem('username');
            const balance = parseFloat(localStorage.getItem('balance'));

            if (username && !isNaN(balance)) {
                currentUser = {
                    username: username,
                    balance: balance
                };
                finalizeUserInterface();
            } else {
                // Получить информацию о пользователе с сервера
                fetch(`${API_URL}/userinfo`, {
                    method: 'GET',
                    headers: getAuthHeaders()
                })
                .then(response => {
                    if (!response.ok) {
                        throw new Error('Не удалось получить информацию о пользователе');
                    }
                    return response.json();
                })
                .then(data => {
                    currentUser = {
                        username: data.username,
                        balance: data.balance
                    };
                    // Сохранить эти данные в localStorage
                    localStorage.setItem('username', data.username);
                    localStorage.setItem('balance', data.balance);
                    // Обновить интерфейс
                    finalizeUserInterface();
                })
                .catch(error => {
                    console.error('Не удалось получить информацию о пользователе:', error);
                    logout(); // Опционально, выйти из системы, если не удалось получить информацию
                });
            }
        } else {
            finalizeUserInterface();
        }
    } else {
        // Показать контейнер аутентификации и скрыть основной контент
        authContainer.classList.remove('hidden');
        mainContent.classList.add('hidden');
    }
}

function finalizeUserInterface() {
    const authContainer = document.getElementById('auth-container');
    const mainContent = document.getElementById('main-content');
    const usernameElement = document.getElementById('username');
    const balanceElement = document.getElementById('balance');

    authContainer.classList.add('hidden');
    mainContent.classList.remove('hidden');

    // Обновить информацию о пользователе в заголовке
    if (usernameElement) usernameElement.textContent = `Пользователь: ${currentUser.username}`;
    if (balanceElement) balanceElement.textContent = `Баланс: ${currentUser.balance} ₽`;

    // Загрузить данные пользователя
    loadServers();
    loadTickets();
}

// Обработчики переключения форм
document.getElementById('show-register').addEventListener('click', (e) => {
    e.preventDefault();
    hideElement('login-form');
    showElement('register-form');
});

document.getElementById('show-login').addEventListener('click', (e) => {
    e.preventDefault();
    hideElement('register-form');
    showElement('login-form');
});

// Обработчики навигации по вкладкам
document.querySelectorAll('.tab-btn').forEach(btn => {
    btn.addEventListener('click', () => {
        setActiveTab(btn.dataset.tab);
    });
});

// Обработчик кнопки выхода
document.getElementById('logout-btn').addEventListener('click', () => {
    logout();
});

// Инициализация при загрузке страницы
document.addEventListener('DOMContentLoaded', function() {
    updateUserInterface();
});



{
  "users": {
    "test": {
      "password": "$pbkdf2-sha256$29000$<hashed_password_here>",
      "servers": [
        {
          "id": 1,
          "name": "Server-1730443463374",
          "plan": {
            "key": "advanced",
            "name": "jrngjae",
            "cpu": 4,
            "ram": 8,
            "price": 1000
          },
          "status": "active",
          "password": "SVBes7qlNc40",
          "created_at": "2024-11-01T13:44:23.380705",
          "auto_renewal": true,
          "container_name": "server_47387a38",
          "ports": {
            "8006/tcp": 12345,
            "3389/tcp": 23456,
            "3389/udp": 23456
          }
        }
      ],
      "balance": 34765734657833657,
      "tickets": [
        {
          "id": 1,
          "title": "test",
          "description": "test",
          "status": "open",
          "created_at": "2024-11-01T13:34:30.026514",
          "messages": []
        }
      ],
      "logs": [],
      "created_at": "2024-11-01T13:23:04.989224",
      "role": "user"
    },
    "test4453": {
      "password": "$pbkdf2-sha256$29000$IUQo5VyLsZYyJsT4fw9BKA$0F/z5qNGFgVhu//bW7cDzKRKZL66MXjEoxqYTKveyAY",
      "servers": [],
      "balance": 6994000,
      "tickets": [],
      "logs": [],
      "created_at": "2024-11-02T04:08:25.433258",
      "role": "user"
    },
    "admin": {
      "password": "$pbkdf2-sha256$29000$IqS0Vurdu3fufU.pNSYEYA$xroF5KX/IT/28bzR9V0gcfPA2jzWGZoseacqm4wpG.U",
      "servers": [],
      "balance": 0,
      "tickets": [],
      "logs": [],
      "created_at": "2024-11-02T21:30:36.217539",
      "role": "admin"
    }
  },
  "logs": [
    {
      "timestamp": "2024-11-02T04:18:25.090630",
      "username": "test4453",
      "action": "login",
      "browser": "Chrome",
      "os": "Windows",
      "device": "Other",
      "ip": "127.0.0.1"
    },
    {
      "timestamp": "2024-11-02T04:18:48.982819",
      "username": "test4453",
      "action": "create_server",
      "browser": "Chrome",
      "os": "Windows",
      "device": "Other",
      "ip": "127.0.0.1"
    },
    {
      "timestamp": "2024-11-02T21:30:47.311132",
      "username": "test4453",
      "action": "login",
      "browser": "Chrome",
      "os": "Windows",
      "device": "Other",
      "ip": "127.0.0.1"
    },
    {
      "timestamp": "2024-11-02T21:31:10.051326",
      "username": "test4453",
      "action": "login",
      "browser": "Chrome",
      "os": "Windows",
      "device": "Other",
      "ip": "127.0.0.1"
    },
    {
      "timestamp": "2024-11-02T21:37:34.117611",
      "username": "test4453",
      "action": "login",
      "browser": "Chrome",
      "os": "Windows",
      "device": "Other",
      "ip": "127.0.0.1"
    },
    {
      "timestamp": "2024-11-02T21:49:29.231965",
      "username": "test4453",
      "action": "login",
      "browser": "Chrome",
      "os": "Windows",
      "device": "Other",
      "ip": "127.0.0.1"
    },
    {
      "timestamp": "2024-11-02T21:57:00.389988",
      "username": "test4453",
      "action": "login",
      "browser": "Chrome",
      "os": "Windows",
      "device": "Other",
      "ip": "127.0.0.1"
    },
    {
      "timestamp": "2024-11-02T23:07:11.415035",
      "username": "test4453",
      "action": "login",
      "browser": "Chrome",
      "os": "Windows",
      "device": "Other",
      "ip": "127.0.0.1"
    },
    {
      "timestamp": "2024-11-02T23:29:35.571245",
      "username": "test4453",
      "action": "login",
      "browser": "Chrome",
      "os": "Windows",
      "device": "Other",
      "ip": "127.0.0.1"
    },
    {
      "timestamp": "2024-11-02T23:39:01.080747",
      "username": "test4453",
      "action": "login",
      "browser": "Chrome",
      "os": "Windows",
      "device": "Other",
      "ip": "127.0.0.1"
    },
    {
      "timestamp": "2024-11-02T23:49:32.263749",
      "username": "test4453",
      "action": "login",
      "browser": "Chrome",
      "os": "Windows",
      "device": "Other",
      "ip": "127.0.0.1"
    },
    {
      "timestamp": "2024-11-02T23:49:51.502472",
      "username": "test4453",
      "action": "login",
      "browser": "Chrome",
      "os": "Windows",
      "device": "Other",
      "ip": "127.0.0.1"
    },
    {
      "timestamp": "2024-11-03T00:20:27.228696",
      "username": "test4453",
      "action": "login",
      "browser": "Chrome",
      "os": "Windows",
      "device": "Other",
      "ip": "127.0.0.1"
    }
  ]
}



Ошибка входа: Error: Invalid credentials
    at HTMLFormElement.<anonymous> (script.js:159:19)
(anonymous) @ script.js:185
script.js:393 
        
        
       GET https://niggers.drugdeal.store/api/tickets 404 (Not Found)
loadTickets @ script.js:393
finalizeUserInterface @ script.js:764
updateUserInterface @ script.js:740
(anonymous) @ script.js:177
script.js:406 Ошибка загрузки тикетов: Error: User not found
    at loadTickets (script.js:400:19)