#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import os
import time
from datetime import datetime
from flask import Flask, render_template_string, request, redirect, url_for, session, send_from_directory, jsonify, g
from werkzeug.utils import secure_filename

app = Flask(__name__)
app.secret_key = 'your-secret-key-change-in-production'  # 请改为随机字符串

# ---------- 配置 ----------
PASSWORD = 'Signals_and_Systems'                  # 用户密码
ADMIN_PASSWORD = 'Signals_and_Systems_admin3515'          # 管理密码
UPLOAD_FOLDER = './uploads'
MESSAGES_FILE = './messages.txt'

# 安全配置
LOCK_THRESHOLD = 5                   # 失败次数锁定阈值
LOCK_DURATION = 1800                 # 锁定时间（秒）
ACTIVE_TIMEOUT = 300                 # 在线超时（秒），5分钟无操作视为离线

# ---------- 全局状态 ----------
failed_attempts = {}   # ip -> {'count': int, 'lock_until': float, 'last_fail': float}
access_log = []        # 记录所有访问
blocked_ips = set()    # 被封禁的 IP
active_users = {}      # ip -> last_activity_timestamp

# ---------- 辅助函数 ----------
def get_client_ip():
    """获取客户端真实 IP（支持反向代理）"""
    # 检查 X-Forwarded-For 头（由代理添加）
    forwarded = request.headers.get('X-Forwarded-For')
    if forwarded:
        # X-Forwarded-For 可能包含多个 IP（代理链），取第一个（客户端原始 IP）
        ip = forwarded.split(',')[0].strip()
        return ip
    # 无代理时直接使用 remote_addr
    return request.remote_addr

def is_ip_locked(ip):
    if ip in failed_attempts and failed_attempts[ip].get('lock_until', 0) > time.time():
        return int(failed_attempts[ip]['lock_until'] - time.time())
    return 0

def log_access(ip, status, fail_count=0):
    access_log.append({
        'ip': ip,
        'time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
        'status': status,
        'fail_count': fail_count
    })
    if len(access_log) > 1000:
        access_log.pop(0)

def get_all_ips_info():
    """获取所有访问过的 IP 及其状态，包含在线状态"""
    ip_info = {}
    now = time.time()
    # 从 access_log 和 failed_attempts 收集 IP
    all_ips = set()
    for log in access_log:
        all_ips.add(log['ip'])
    for ip in failed_attempts:
        all_ips.add(ip)
    for ip in all_ips:
        # 查找最后访问时间
        last_time = None
        for log in reversed(access_log):
            if log['ip'] == ip:
                last_time = log['time']
                break
        if not last_time and ip in failed_attempts:
            last_time = datetime.fromtimestamp(failed_attempts[ip]['last_fail']).strftime('%Y-%m-%d %H:%M:%S')
        if not last_time:
            last_time = '未知'
        status = '已封禁' if ip in blocked_ips else '正常'
        # 在线状态：在 active_users 中且未超时
        online = False
        if ip in active_users and (now - active_users[ip]) < ACTIVE_TIMEOUT:
            online = True
        ip_info[ip] = {
            'last_seen': last_time,
            'status': status,
            'online': online
        }
    return ip_info

def allowed_file(filename):
    return True

def get_unique_filename(filepath):
    dirname = os.path.dirname(filepath)
    filename = os.path.basename(filepath)
    base, ext = os.path.splitext(filename)
    counter = 1
    new_name = filename
    while os.path.exists(os.path.join(dirname, new_name)):
        new_name = f"{base}({counter}){ext}"
        counter += 1
    return new_name

def load_message_text():
    if not os.path.exists(MESSAGES_FILE):
        return ''
    with open(MESSAGES_FILE, 'r', encoding='utf-8') as f:
        return f.read()

def save_message_text(text):
    with open(MESSAGES_FILE, 'w', encoding='utf-8') as f:
        f.write(text)

# ---------- 请求钩子 ----------
@app.before_request
def check_block_and_active():
    """对需要登录的路径进行 IP 封禁检查和活跃更新"""
    # 只处理 /cloud 下的路由，排除静态资源和登录页
    if not request.path.startswith('/cloud'):
        return
    # 登录页和静态资源不拦截
    if request.path in ('/cloud/login', '/cloud/static', '/cloud/favicon.ico'):
        return

    ip = get_client_ip()
    # 检查 IP 是否被封禁
    if ip in blocked_ips:
        # 如果该 IP 有 session，清除并重定向到登录页（显示封禁信息）
        if session.get('user_auth') or session.get('admin_auth'):
            session.clear()
        # 如果是 API 请求返回 JSON 错误，否则重定向到登录
        if request.path.startswith('/cloud/api/'):
            return jsonify({'error': '该 IP 已被封禁'}), 403
        else:
            return render_template_string(LOGIN_HTML, error='❌ 该 IP 已被管理员封禁，无法访问'), 403

    # 更新活跃时间（仅对已登录用户）
    if session.get('user_auth') or session.get('admin_auth'):
        active_users[ip] = time.time()

# ---------- 路由 ----------
@app.route('/cloud', methods=['GET'])
def cloud_index():
    if session.get('user_auth'):
        return render_template_string(USER_PAGE_HTML)
    elif session.get('admin_auth'):
        ip_info = get_all_ips_info()
        return render_template_string(ADMIN_PAGE_HTML, ip_info=ip_info)
    else:
        return redirect(url_for('login'))

@app.route('/cloud/login', methods=['GET', 'POST'])
def login():
    ip = get_client_ip()

    if ip in blocked_ips:
        return render_template_string(LOGIN_HTML, error='❌ 该 IP 已被管理员封禁，无法登录')

    lock_remaining = is_ip_locked(ip)
    if lock_remaining > 0:
        if request.method == 'GET':
            return render_template_string(LOGIN_HTML, error=f'⏳ 该 IP 已被锁定，请等待 {lock_remaining} 秒')
        else:
            return render_template_string(LOGIN_HTML, error=f'⏳ IP 已锁定，等待 {lock_remaining} 秒')

    if request.method == 'POST':
        pwd = request.form.get('password')
        if pwd == PASSWORD:
            if ip in failed_attempts:
                del failed_attempts[ip]
            session.clear()
            session['user_auth'] = True
            active_users[ip] = time.time()
            log_access(ip, 'success')
            return redirect(url_for('cloud_index'))
        elif pwd == ADMIN_PASSWORD:
            if ip in failed_attempts:
                del failed_attempts[ip]
            session.clear()
            session['admin_auth'] = True
            active_users[ip] = time.time()
            log_access(ip, 'success')
            return redirect(url_for('cloud_index'))
        else:
            if ip not in failed_attempts:
                failed_attempts[ip] = {'count': 0, 'lock_until': 0, 'last_fail': time.time()}
            data = failed_attempts[ip]
            data['count'] += 1
            data['last_fail'] = time.time()
            fail_count = data['count']

            if data['count'] >= LOCK_THRESHOLD:
                data['lock_until'] = time.time() + LOCK_DURATION
                log_access(ip, 'locked', fail_count)
                return render_template_string(LOGIN_HTML, error=f'🔒 失败次数过多，IP 已锁定 {LOCK_DURATION//60} 分钟')
            else:
                log_access(ip, 'failed', fail_count)
                remaining = LOCK_THRESHOLD - data['count']
                return render_template_string(LOGIN_HTML, error=f'❌ 密码错误，还剩 {remaining} 次尝试')

    return render_template_string(LOGIN_HTML, error=None)

@app.route('/cloud/logout')
def logout():
    ip = get_client_ip()
    if ip in active_users:
        del active_users[ip]
    session.clear()
    return redirect(url_for('login'))

# ---------- 管理 API（仅管理员） ----------
@app.route('/cloud/api/ips', methods=['GET'])
def get_ips():
    if not session.get('admin_auth'):
        return jsonify({'error': '未授权'}), 403
    ip_info = get_all_ips_info()
    return jsonify(ip_info)

@app.route('/cloud/api/block/<ip>', methods=['POST'])
def block_ip(ip):
    if not session.get('admin_auth'):
        return jsonify({'error': '未授权'}), 403
    # 不能封禁管理员自己的 IP
    if ip == get_client_ip():
        return jsonify({'error': '不能封禁自己的 IP'}), 400
    blocked_ips.add(ip)
    # 将该 IP 从活跃列表中移除（强制踢出）
    if ip in active_users:
        del active_users[ip]
    return jsonify({'success': True, 'message': f'IP {ip} 已封禁'})

@app.route('/cloud/api/unblock/<ip>', methods=['POST'])
def unblock_ip(ip):
    if not session.get('admin_auth'):
        return jsonify({'error': '未授权'}), 403
    if ip in blocked_ips:
        blocked_ips.remove(ip)
    return jsonify({'success': True, 'message': f'IP {ip} 已解封'})

# ---------- 网盘 API ----------
@app.route('/cloud/api/files', methods=['GET'])
def list_files():
    if not session.get('user_auth'):
        return jsonify({'error': '未授权'}), 403
    files = []
    for f in os.listdir(UPLOAD_FOLDER):
        path = os.path.join(UPLOAD_FOLDER, f)
        if os.path.isfile(path):
            mtime = os.path.getmtime(path)
            files.append({
                'name': f,
                'size': os.path.getsize(path),
                'modified': mtime,
                'modified_str': time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(mtime))
            })
    files.sort(key=lambda x: x['modified'], reverse=True)
    return jsonify(files)

@app.route('/cloud/api/upload', methods=['POST'])
def upload_file():
    if not session.get('user_auth'):
        return jsonify({'error': '未授权'}), 403
    if 'file' not in request.files:
        return jsonify({'error': '没有文件'}), 400
    file = request.files['file']
    if file.filename == '':
        return jsonify({'error': '文件名为空'}), 400
    if file and allowed_file(file.filename):
        safe_name = secure_filename(file.filename)
        target_path = os.path.join(UPLOAD_FOLDER, safe_name)
        unique_name = get_unique_filename(target_path)
        file.save(os.path.join(UPLOAD_FOLDER, unique_name))
        return jsonify({'success': True, 'filename': unique_name})
    return jsonify({'error': '文件类型不允许'}), 400

@app.route('/cloud/api/download/<filename>')
def download_file(filename):
    if not session.get('user_auth'):
        return jsonify({'error': '未授权'}), 403
    safe_name = secure_filename(filename)
    return send_from_directory(UPLOAD_FOLDER, safe_name, as_attachment=True)

@app.route('/cloud/api/delete/<filename>', methods=['DELETE'])
def delete_file(filename):
    if not session.get('user_auth'):
        return jsonify({'error': '未授权'}), 403
    safe_name = secure_filename(filename)
    path = os.path.join(UPLOAD_FOLDER, safe_name)
    if os.path.exists(path):
        os.remove(path)
        return jsonify({'success': True})
    return jsonify({'error': '文件不存在'}), 404

# ---------- 留言板 API ----------
@app.route('/cloud/api/messages', methods=['GET', 'POST'])
def messages_api():
    if not session.get('user_auth'):
        return jsonify({'error': '未授权'}), 403
    if request.method == 'GET':
        content = load_message_text()
        return jsonify({'content': content})
    elif request.method == 'POST':
        data = request.get_json()
        if not data or 'content' not in data:
            return jsonify({'error': '内容不能为空'}), 400
        new_content = data['content']
        save_message_text(new_content)
        return jsonify({'success': True})

# ---------- HTML 模板 ----------
LOGIN_HTML = '''
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>光遇云服务 - 登录</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: "Microsoft YaHei", "PingFang SC", Arial, sans-serif;
            height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
            background: url('/static/background.png') no-repeat center center fixed;
            background-size: cover;
            backdrop-filter: blur(4px);
        }
        .login-box {
            background: rgba(255, 255, 255, 0.88);
            backdrop-filter: blur(8px);
            padding: 50px 40px;
            border-radius: 24px;
            box-shadow: 0 20px 60px rgba(0,0,0,0.3);
            width: 360px;
            text-align: center;
            border: 1px solid rgba(255,255,255,0.3);
        }
        .login-box h2 {
            font-size: 28px;
            margin-bottom: 10px;
            color: #2c3e50;
            font-weight: 300;
            letter-spacing: 2px;
        }
        .login-box .sub {
            color: #7f8c8d;
            font-size: 14px;
            margin-bottom: 30px;
            border-bottom: 2px solid #ecf0f1;
            padding-bottom: 15px;
        }
        .login-box input {
            width: 100%;
            padding: 14px 16px;
            margin: 12px 0;
            border: 1px solid #ddd;
            border-radius: 40px;
            background: rgba(255,255,255,0.7);
            font-size: 16px;
            outline: none;
            transition: border 0.3s;
            box-sizing: border-box;
        }
        .login-box input:focus {
            border-color: #3498db;
            background: #fff;
        }
        .login-box button {
            width: 100%;
            padding: 14px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            border: none;
            border-radius: 40px;
            font-size: 18px;
            font-weight: 600;
            cursor: pointer;
            transition: transform 0.2s, box-shadow 0.2s;
            margin-top: 10px;
            letter-spacing: 2px;
        }
        .login-box button:hover {
            transform: scale(1.02);
            box-shadow: 0 8px 20px rgba(102, 126, 234, 0.4);
        }
        .error {
            color: #e74c3c;
            background: #fde0e0;
            padding: 10px;
            border-radius: 30px;
            margin: 15px 0 0;
            font-size: 14px;
        }
        .footer {
            margin-top: 25px;
            font-size: 12px;
            color: #95a5a6;
        }
    </style>
</head>
<body>
    <div class="login-box">
        <h2>🌤 光遇云服务</h2>
        <div class="sub">请输入您的访问密码</div>
        <form method="post" action="{{ url_for('login') }}">
            <input type="password" name="password" placeholder="密码" required autofocus>
            <button type="submit">进入</button>
            {% if error %}
                <div class="error">{{ error }}</div>
            {% endif %}
        </form>
        <div class="footer">👻👻👻👻👻👻👻👻👻👻</div>
    </div>
</body>
</html>
'''

USER_PAGE_HTML = '''
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>光遇云服务 - 网盘 & 留言</title>
    <style>
        * { box-sizing: border-box; }
        body {
            margin: 0;
            padding: 20px;
            font-family: "Microsoft YaHei", "PingFang SC", Arial, sans-serif;
            min-height: 100vh;
            background: url('/static/background.png') no-repeat center center fixed;
            background-size: cover;
            backdrop-filter: blur(2px);
        }
        .container {
            display: flex;
            gap: 24px;
            max-width: 1400px;
            margin: 0 auto;
            height: 90vh;
        }
        .card {
            background: rgba(255, 255, 255, 0.85);
            backdrop-filter: blur(10px);
            border-radius: 24px;
            padding: 28px;
            box-shadow: 0 10px 40px rgba(0,0,0,0.2);
            overflow-y: auto;
            border: 1px solid rgba(255,255,255,0.3);
            flex: 1;
        }
        .card h2 {
            margin-top: 0;
            border-bottom: 2px solid #ecf0f1;
            padding-bottom: 12px;
            display: flex;
            justify-content: space-between;
            align-items: center;
            color: #2c3e50;
            font-weight: 400;
            letter-spacing: 1px;
        }
        .card h2 .actions a {
            font-size: 14px;
            font-weight: normal;
            padding: 6px 16px;
            border-radius: 30px;
            text-decoration: none;
            transition: all 0.2s;
            margin-left: 8px;
        }
        .btn-logout {
            background: #e74c3c;
            color: white !important;
        }
        .btn-logout:hover {
            background: #c0392b;
            transform: scale(1.05);
        }
        .file-list, .msg-list { list-style: none; padding: 0; }
        .file-item {
            padding: 10px 14px;
            border-bottom: 1px solid #f0f0f0;
            display: flex;
            justify-content: space-between;
            align-items: center;
            border-radius: 10px;
            transition: background 0.2s;
        }
        .file-item:hover { background: rgba(0,0,0,0.03); }
        .file-name {
            cursor: pointer;
            color: #2980b9;
            font-weight: 500;
        }
        .file-name:hover { text-decoration: underline; }
        .file-size, .file-time { color: #7f8c8d; font-size: 13px; margin-left: 12px; }
        .del-btn { color: #e74c3c; cursor: pointer; font-size: 16px; }
        .del-btn:hover { color: #c0392b; }
        .upload-area {
            display: flex;
            gap: 12px;
            margin: 18px 0;
            flex-wrap: wrap;
            align-items: center;
        }
        .upload-area input[type="file"] {
            flex: 1;
            padding: 8px;
            background: rgba(255,255,255,0.6);
            border: 1px dashed #bdc3c7;
            border-radius: 40px;
        }
        .upload-area button {
            padding: 8px 24px;
            background: linear-gradient(135deg, #667eea, #764ba2);
            color: white;
            border: none;
            border-radius: 40px;
            cursor: pointer;
            transition: all 0.2s;
            font-weight: 500;
        }
        .upload-area button:hover { transform: scale(1.05); box-shadow: 0 4px 12px rgba(102,126,234,0.4); }
        .msg-editor {
            display: flex;
            flex-direction: column;
            gap: 12px;
            height: 70%;
        }
        .msg-editor textarea {
            flex: 1;
            padding: 14px;
            border: 1px solid #ddd;
            border-radius: 16px;
            resize: vertical;
            font-family: inherit;
            font-size: 15px;
            background: rgba(255,255,255,0.7);
            min-height: 200px;
        }
        .msg-editor button {
            align-self: flex-end;
            padding: 10px 30px;
            background: linear-gradient(135deg, #2ecc71, #27ae60);
            color: white;
            border: none;
            border-radius: 40px;
            cursor: pointer;
            transition: all 0.2s;
            font-weight: 500;
        }
        .msg-editor button:hover { transform: scale(1.05); box-shadow: 0 4px 12px rgba(46,204,113,0.4); }
        .toast { padding: 8px 16px; border-radius: 30px; margin: 5px 0; display: inline-block; }
        .toast-error { background: #fde0e0; color: #c0392b; }
        .toast-success { background: #e0f7e0; color: #27ae60; }
        .toast-info { background: #e0f0ff; color: #2980b9; }
        .empty-tip { color: #bdc3c7; text-align: center; padding: 30px 0; }
        .status { font-size: 14px; color: #7f8c8d; margin-left: 12px; }
        @media (max-width: 800px) {
            .container { flex-direction: column; height: auto; }
            .card { max-height: 500px; }
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="card">
            <h2>
                📁 网盘
                <span class="actions">
                    <a href="{{ url_for('logout') }}" class="btn-logout">退出</a>
                </span>
            </h2>
            <div class="upload-area">
                <input type="file" id="fileInput" multiple>
                <button onclick="uploadFiles()">上传</button>
                <span id="uploadStatus" class="status"></span>
            </div>
            <ul class="file-list" id="fileList"><li class="empty-tip">暂无文件</li></ul>
        </div>
        <div class="card">
            <h2>💬 留言便签</h2>
            <div class="msg-editor">
                <textarea id="msgTextarea" placeholder="在此编辑留言内容，支持 Emoji 😊，保存后立即生效"></textarea>
                <button onclick="saveMessage()">保存留言</button>
                <div id="messageToast"></div>
            </div>
        </div>
    </div>
    <script>
        let isUploading = false, isSaving = false;
        function showToast(msg, type='info') {
            const container = document.getElementById('messageToast');
            const cls = type === 'error' ? 'toast-error' : (type === 'success' ? 'toast-success' : 'toast-info');
            container.innerHTML = `<div class="toast ${cls}">${msg}</div>`;
            setTimeout(() => container.innerHTML = '', 3000);
        }
        function loadFiles() {
            fetch('/cloud/api/files')
                .then(res => res.json())
                .then(data => {
                    const list = document.getElementById('fileList');
                    if (data.length === 0) { list.innerHTML = '<li class="empty-tip">暂无文件</li>'; return; }
                    list.innerHTML = data.map(f => `
                        <li class="file-item">
                            <span>
                                <span class="file-name" onclick="downloadFile('${f.name}')">${f.name}</span>
                                <span class="file-size">${(f.size/1024).toFixed(1)} KB</span>
                                <span class="file-time">${f.modified_str}</span>
                            </span>
                            <span class="del-btn" onclick="deleteFile('${f.name}')">🗑️</span>
                        </li>
                    `).join('');
                })
                .catch(err => { console.error(err); showToast('加载文件失败', 'error'); });
        }
        function uploadFiles() {
            if (isUploading) return;
            const input = document.getElementById('fileInput');
            if (input.files.length === 0) { showToast('请选择文件', 'error'); return; }
            isUploading = true;
            const status = document.getElementById('uploadStatus');
            status.textContent = '上传中...';
            const formData = new FormData();
            for (let f of input.files) formData.append('file', f);
            fetch('/cloud/api/upload', { method: 'POST', body: formData })
                .then(res => res.json())
                .then(data => {
                    isUploading = false;
                    if (data.success) {
                        status.textContent = '✅ 上传成功';
                        input.value = '';
                        loadFiles();
                        showToast('上传成功', 'success');
                    } else {
                        status.textContent = '❌ ' + (data.error || '上传失败');
                        showToast('上传失败: ' + (data.error || '未知错误'), 'error');
                    }
                })
                .catch(err => { isUploading = false; status.textContent = '❌ 网络错误'; showToast('上传请求失败', 'error'); console.error(err); });
        }
        function downloadFile(filename) { window.open('/cloud/api/download/' + encodeURIComponent(filename), '_blank'); }
        function deleteFile(filename) {
            if (!confirm(`确认删除 "${filename}" ？`)) return;
            fetch('/cloud/api/delete/' + encodeURIComponent(filename), { method: 'DELETE' })
                .then(res => res.json())
                .then(data => {
                    if (data.success) { loadFiles(); showToast('删除成功', 'success'); }
                    else { showToast('删除失败: ' + (data.error || '未知错误'), 'error'); }
                })
                .catch(err => { showToast('删除请求失败', 'error'); console.error(err); });
        }
        function loadMessage() {
            fetch('/cloud/api/messages')
                .then(res => res.json())
                .then(data => { document.getElementById('msgTextarea').value = data.content || ''; })
                .catch(err => { console.error(err); showToast('加载留言失败', 'error'); });
        }
        function saveMessage() {
            if (isSaving) return;
            const content = document.getElementById('msgTextarea').value;
            isSaving = true;
            fetch('/cloud/api/messages', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ content })
            })
            .then(res => res.json())
            .then(data => {
                isSaving = false;
                if (data.success) showToast('留言已保存', 'success');
                else showToast('保存失败: ' + (data.error || '未知错误'), 'error');
            })
            .catch(err => { isSaving = false; showToast('保存请求失败', 'error'); console.error(err); });
        }
        document.addEventListener('keydown', function(e) {
            if ((e.ctrlKey || e.metaKey) && e.key === 's') { e.preventDefault(); saveMessage(); }
        });
        loadFiles();
        loadMessage();
    </script>
</body>
</html>
'''

ADMIN_PAGE_HTML = '''
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>光遇云服务 - 管理面板</title>
    <style>
        * { box-sizing: border-box; }
        body {
            margin: 0;
            padding: 20px;
            font-family: "Microsoft YaHei", "PingFang SC", Arial, sans-serif;
            min-height: 100vh;
            background: url('/static/background.png') no-repeat center center fixed;
            background-size: cover;
            backdrop-filter: blur(2px);
        }
        .admin-container {
            max-width: 1200px;
            margin: 0 auto;
            background: rgba(255, 255, 255, 0.88);
            backdrop-filter: blur(10px);
            border-radius: 24px;
            padding: 28px 32px;
            box-shadow: 0 10px 40px rgba(0,0,0,0.2);
            border: 1px solid rgba(255,255,255,0.3);
        }
        .header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            border-bottom: 2px solid #ecf0f1;
            padding-bottom: 15px;
        }
        .header h2 {
            margin: 0;
            color: #2c3e50;
            font-weight: 400;
            letter-spacing: 2px;
        }
        .header-actions a {
            padding: 6px 18px;
            border-radius: 30px;
            text-decoration: none;
            font-size: 14px;
            transition: all 0.2s;
            margin-left: 10px;
        }
        .btn-logout {
            background: #e74c3c;
            color: white;
        }
        .btn-logout:hover { background: #c0392b; transform: scale(1.05); }
        .btn-back {
            background: #3498db;
            color: white;
        }
        .btn-back:hover { background: #2980b9; transform: scale(1.05); }
        .btn-refresh {
            background: #95a5a6;
            color: white;
            border: none;
            padding: 6px 18px;
            border-radius: 30px;
            cursor: pointer;
            font-size: 14px;
        }
        .btn-refresh:hover { background: #7f8c8d; }
        table {
            width: 100%;
            border-collapse: collapse;
            margin-top: 18px;
        }
        th {
            background: rgba(0,0,0,0.03);
            padding: 12px 10px;
            text-align: left;
            border-bottom: 2px solid #ddd;
            font-weight: 500;
            color: #34495e;
        }
        td {
            padding: 10px;
            border-bottom: 1px solid #f0f0f0;
        }
        .status-badge {
            display: inline-block;
            padding: 3px 14px;
            border-radius: 30px;
            font-size: 13px;
            font-weight: 500;
        }
        .status-normal { background: #e0f7e0; color: #27ae60; }
        .status-blocked { background: #fde0e0; color: #c0392b; }
        .online-badge {
            display: inline-block;
            padding: 3px 12px;
            border-radius: 30px;
            font-size: 12px;
            font-weight: bold;
        }
        .online-yes { background: #d4edda; color: #155724; }
        .online-no { background: #f8d7da; color: #721c24; }
        .action-btn {
            padding: 4px 14px;
            border: none;
            border-radius: 30px;
            cursor: pointer;
            font-size: 13px;
            transition: all 0.2s;
            margin-right: 5px;
        }
        .btn-block { background: #e74c3c; color: white; }
        .btn-block:hover { background: #c0392b; transform: scale(1.05); }
        .btn-unblock { background: #2ecc71; color: white; }
        .btn-unblock:hover { background: #27ae60; transform: scale(1.05); }
        .empty-tip { color: #bdc3c7; text-align: center; padding: 30px; }
        .toast {
            padding: 8px 16px;
            border-radius: 30px;
            margin: 10px 0;
            display: inline-block;
        }
        .toast-success { background: #e0f7e0; color: #27ae60; }
        .toast-error { background: #fde0e0; color: #c0392b; }
        .section-title {
            margin-top: 30px;
            font-size: 18px;
            font-weight: 400;
            color: #2c3e50;
        }
    </style>
</head>
<body>
    <div class="admin-container">
        <div class="header">
            <h2>🔧 管理面板 - IP 访问控制</h2>
            <div class="header-actions">
                <button class="btn-refresh" onclick="location.reload()">🔄 刷新</button>
                <a href="{{ url_for('login') }}" class="btn-back" onclick="return confirm('切换到用户模式？您将退出管理。')">用户模式</a>
                <a href="{{ url_for('logout') }}" class="btn-logout">退出</a>
            </div>
        </div>
        <div id="toastContainer"></div>

        <!-- 在线 IP 列表 -->
        <div class="section-title">🟢 当前在线 IP（5 分钟内活跃）</div>
        <div id="onlineTableContainer"></div>

        <!-- 所有 IP 列表 -->
        <div class="section-title">📋 所有访问 IP</div>
        <div style="overflow-x:auto;">
            <table>
                <thead>
                    <tr>
                        <th>IP 地址</th>
                        <th>最后访问</th>
                        <th>状态</th>
                        <th>操作</th>
                    </tr>
                </thead>
                <tbody id="ipTableBody">
                    <!-- 由 JS 动态渲染 -->
                </tbody>
            </table>
        </div>
        <p style="color:#95a5a6; font-size:14px; margin-top:12px;">
            💡 封禁后该 IP 将无法登录（包括正确密码），解封后恢复。封禁时会立即踢出在线用户。
        </p>
    </div>
    <script>
        function loadIPs() {
            fetch('/cloud/api/ips')
                .then(res => res.json())
                .then(data => {
                    // 渲染在线 IP
                    const onlineContainer = document.getElementById('onlineTableContainer');
                    const onlineList = Object.entries(data).filter(([ip, info]) => info.online);
                    if (onlineList.length === 0) {
                        onlineContainer.innerHTML = '<p style="color:#7f8c8d;">当前没有在线用户</p>';
                    } else {
                        let html = '<table><thead><tr><th>IP</th><th>最后活动</th></tr></thead><tbody>';
                        for (const [ip, info] of onlineList) {
                            html += `<tr><td><strong>${ip}</strong></td><td>${info.last_seen}</td></tr>`;
                        }
                        html += '</tbody></table>';
                        onlineContainer.innerHTML = html;
                    }

                    // 渲染所有 IP
                    const tbody = document.getElementById('ipTableBody');
                    const entries = Object.entries(data);
                    if (entries.length === 0) {
                        tbody.innerHTML = '<tr><td colspan="4" class="empty-tip">暂无访问记录</td></tr>';
                        return;
                    }
                    let html = '';
                    for (const [ip, info] of entries) {
                        const statusClass = info.status === '已封禁' ? 'status-blocked' : 'status-normal';
                        const actionBtn = info.status === '已封禁'
                            ? `<button class="action-btn btn-unblock" onclick="unblockIP('${ip}')">解封</button>`
                            : `<button class="action-btn btn-block" onclick="blockIP('${ip}')">封禁</button>`;
                        const onlineBadge = info.online ? '<span class="online-badge online-yes">在线</span>' : '<span class="online-badge online-no">离线</span>';
                        html += `<tr>
                            <td><strong>${ip}</strong></td>
                            <td>${info.last_seen}</td>
                            <td><span class="status-badge ${statusClass}">${info.status}</span> ${onlineBadge}</td>
                            <td>${actionBtn}</td>
                        </tr>`;
                    }
                    tbody.innerHTML = html;
                })
                .catch(err => { console.error(err); showToast('加载 IP 失败', 'error'); });
        }

        function showToast(msg, type='info') {
            const container = document.getElementById('toastContainer');
            const cls = type === 'error' ? 'toast-error' : 'toast-success';
            container.innerHTML = `<div class="toast ${cls}">${msg}</div>`;
            setTimeout(() => container.innerHTML = '', 3000);
        }

        function blockIP(ip) {
            if (!confirm(`确定要封禁 IP ${ip} 吗？该用户将被立即踢出。`)) return;
            fetch('/cloud/api/block/' + encodeURIComponent(ip), { method: 'POST' })
                .then(res => res.json())
                .then(data => {
                    if (data.success) { showToast(data.message, 'success'); loadIPs(); }
                    else { showToast('操作失败: ' + data.error, 'error'); }
                })
                .catch(err => { showToast('请求失败', 'error'); console.error(err); });
        }

        function unblockIP(ip) {
            if (!confirm(`确定要解封 IP ${ip} 吗？`)) return;
            fetch('/cloud/api/unblock/' + encodeURIComponent(ip), { method: 'POST' })
                .then(res => res.json())
                .then(data => {
                    if (data.success) { showToast(data.message, 'success'); loadIPs(); }
                    else { showToast('操作失败: ' + data.error, 'error'); }
                })
                .catch(err => { showToast('请求失败', 'error'); console.error(err); });
        }

        // 初始加载，并每 10 秒自动刷新
        loadIPs();
        setInterval(loadIPs, 10000);
    </script>
</body>
</html>
'''

# ---------- 启动 ----------
if __name__ == '__main__':
    os.makedirs(UPLOAD_FOLDER, exist_ok=True)
    if not os.path.exists('./static/background.png'):
        print("⚠️ 提示：请将背景图片放置于 static/background.png，否则将使用备用背景色。")
    app.run(host='0.0.0.0', port=5000, debug=True)