558bd8153f
架构: - engine/server.py: asyncio SOCKS5 服务器引擎(含隧道/认证/限速) - engine/instances.py: 多实例管理器(独立事件循环/热启动/热停止) - services/user_service.py: 用户认证/流量统计/IP白名单/防爆破 - services/stats_service.py: 实时监控/流量趋势/连接追踪 - services/backup_service.py: 数据库备份与恢复 - api/v1.py: 完整 RESTful API - web/routes.py: Web 管理面板(暗色 Bootstrap 5 主题) 功能覆盖: ✅ 多实例 SOCKS5 管理(创建/启动/停止/重启/热加载) ✅ 用户认证(无认证/账号密码/流量上限/限速/并发限制) ✅ IP 白名单/黑名单/防爆破 ✅ 实时仪表盘(CPU/内存/网络/活跃连接) ✅ 流量统计(30天趋势图/用户排名/实例统计) ✅ 活跃连接追踪 ✅ 审计日志(事件/用户/IP 筛选) ✅ 数据库备份与恢复 ✅ 管理员密码保护 ✅ 16 项功能测试全部通过
233 lines
14 KiB
HTML
233 lines
14 KiB
HTML
<!doctype html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
<title>{% block title %}SOCKS Manager{% endblock %}</title>
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" rel="stylesheet">
|
|
<link href="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js" rel="stylesheet">
|
|
<style>
|
|
/* ── 全局 ─────────────────────────────────────────────────────── */
|
|
:root {
|
|
--bg-primary:#0b0e14; --bg-secondary:#161922; --bg-tertiary:#1e2235;
|
|
--border:#1f2335; --text:#e2e8f0; --text-muted:#94a3b8;
|
|
--accent:#818cf8; --accent-hover:#6366f1;
|
|
--green:#10b981; --red:#ef4444; --yellow:#f59e0b; --gray:#6b7280;
|
|
--card-bg:#161922; --input-bg:#0b0e14;
|
|
}
|
|
* { box-sizing:border-box; }
|
|
body { background:var(--bg-primary); color:var(--text);
|
|
font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
|
|
font-size:14px; line-height:1.6; }
|
|
a { color:var(--accent); text-decoration:none; }
|
|
a:hover { color:var(--accent-hover); }
|
|
|
|
/* ── 导航 ─────────────────────────────────────────────────────── */
|
|
.navbar { background:var(--bg-secondary)!important; border-bottom:1px solid var(--border)!important;
|
|
padding:.6rem 1rem; }
|
|
.navbar-brand { color:var(--accent)!important; font-weight:700; font-size:1.1rem; }
|
|
.navbar-brand i { margin-right:6px; }
|
|
.nav-link { color:var(--text-muted)!important; font-size:14px; padding:.5rem 1rem!important; }
|
|
.nav-link:hover, .nav-link.active { color:var(--accent)!important; }
|
|
|
|
/* ── 侧边栏 ───────────────────────────────────────────────────── */
|
|
.sidebar { width:220px; min-height:100vh; position:fixed; left:0; top:56px; bottom:0;
|
|
background:var(--bg-secondary); border-right:1px solid var(--border); padding:.5rem 0; z-index:100; }
|
|
.sidebar .nav-link { padding:.55rem 1.2rem; border-radius:0 .4rem .4rem 0; margin:.15rem .6rem; font-size:14px; }
|
|
.sidebar .nav-link:hover { background:var(--bg-tertiary); color:var(--accent)!important; }
|
|
.sidebar .nav-link.active { background:var(--bg-tertiary); color:var(--accent)!important;
|
|
border-left:3px solid var(--accent); }
|
|
.sidebar .nav-link i { margin-right:10px; width:18px; text-align:center; }
|
|
.sidebar .sidebar-section { padding:.3rem 1.2rem .1rem; font-size:11px; color:var(--text-muted);
|
|
text-transform:uppercase; letter-spacing:.05em; margin-top:.8rem; }
|
|
|
|
/* ── 主内容 ───────────────────────────────────────────────────── */
|
|
.main-content { margin-left:220px; padding:1.2rem 1.5rem; min-height:calc(100vh - 56px); }
|
|
|
|
/* ── 卡片 ─────────────────────────────────────────────────────── */
|
|
.card { background:var(--card-bg); border:1px solid var(--border); border-radius:.6rem; margin-bottom:1rem; }
|
|
.card-header { background:transparent; border-bottom:1px solid var(--border); padding:.8rem 1rem; }
|
|
.card-body { padding:1rem; }
|
|
.card-footer { background:transparent; border-top:1px solid var(--border); }
|
|
|
|
/* ── 表格 ─────────────────────────────────────────────────────── */
|
|
.table { color:var(--text); margin-bottom:0; }
|
|
.table thead th { background:var(--bg-tertiary); color:var(--text-muted); font-weight:600;
|
|
font-size:12px; text-transform:uppercase; letter-spacing:.04em; border-bottom:1px solid var(--border);
|
|
padding:.65rem .8rem; }
|
|
.table tbody td { padding:.6rem .8rem; border-bottom:1px solid var(--border); font-size:14px;
|
|
vertical-align:middle; }
|
|
.table tbody tr:hover { background:var(--bg-tertiary); }
|
|
.table-dark { background:var(--card-bg); }
|
|
|
|
/* ── 表单 ─────────────────────────────────────────────────────── */
|
|
.form-label { color:var(--text-muted); font-size:13px; margin-bottom:.3rem; font-weight:500; }
|
|
.form-control, .form-select { background:var(--input-bg); border:1px solid var(--border);
|
|
color:var(--text); border-radius:.4rem; font-size:14px; padding:.5rem .7rem; }
|
|
.form-control:focus, .form-select:focus { border-color:var(--accent); box-shadow:0 0 0 3px rgba(129,140,248,.12);
|
|
background:var(--input-bg); color:var(--text); }
|
|
.form-control::placeholder { color:#5a6178; }
|
|
.form-text { color:var(--text-muted); font-size:12px; }
|
|
.form-check-input { background-color:var(--input-bg); border-color:var(--border); }
|
|
.form-check-input:checked { background-color:var(--accent); border-color:var(--accent); }
|
|
|
|
/* ── 按钮 ─────────────────────────────────────────────────────── */
|
|
.btn-primary { background:var(--accent); border-color:var(--accent); }
|
|
.btn-primary:hover { background:var(--accent-hover); border-color:var(--accent-hover); }
|
|
.btn-outline-primary { color:var(--accent); border-color:var(--accent); }
|
|
.btn-outline-primary:hover { background:var(--accent); border-color:var(--accent); }
|
|
.btn-outline-secondary { color:var(--text-muted); border-color:var(--border); }
|
|
.btn-outline-secondary:hover { color:var(--text); border-color:var(--text-muted); background:var(--bg-tertiary); }
|
|
.btn-outline-danger { color:var(--red); border-color:var(--red); }
|
|
.btn-outline-danger:hover { background:var(--red); border-color:var(--red); color:#fff; }
|
|
.btn-outline-success { color:var(--green); border-color:var(--green); }
|
|
.btn-outline-success:hover { background:var(--green); border-color:var(--green); color:#fff; }
|
|
.btn-outline-warning { color:var(--yellow); border-color:var(--yellow); }
|
|
.btn-outline-warning:hover { background:var(--yellow); border-color:var(--yellow); color:#000; }
|
|
.btn-sm { font-size:13px; padding:.3rem .6rem; }
|
|
|
|
/* ── 徽章 ─────────────────────────────────────────────────────── */
|
|
.badge { font-size:11px; padding:.3em .6em; border-radius:.3rem; }
|
|
.badge-success { background:rgba(16,185,129,.15); color:var(--green); }
|
|
.badge-danger { background:rgba(239,68,68,.15); color:var(--red); }
|
|
.badge-warning { background:rgba(245,158,11,.15); color:var(--yellow); }
|
|
.badge-secondary { background:rgba(107,114,128,.15); color:var(--gray); }
|
|
.badge-info { background:rgba(129,140,248,.15); color:var(--accent); }
|
|
|
|
/* ── 状态点 ───────────────────────────────────────────────────── */
|
|
.status-dot { width:9px; height:9px; border-radius:50%; display:inline-block; margin-right:5px;
|
|
vertical-align:middle; }
|
|
.dot-online { background:var(--green); box-shadow:0 0 6px rgba(16,185,129,.6); }
|
|
.dot-offline { background:var(--red); }
|
|
.dot-warning { background:var(--yellow); }
|
|
.dot-gray { background:var(--gray); }
|
|
|
|
/* ── 统计卡片 ─────────────────────────────────────────────────── */
|
|
.stat-card { background:var(--card-bg); border:1px solid var(--border); border-radius:.6rem; padding:1rem; }
|
|
.stat-card .stat-label { color:var(--text-muted); font-size:12px; text-transform:uppercase; letter-spacing:.04em; }
|
|
.stat-card .stat-value { font-size:1.8rem; font-weight:700; margin:.2rem 0; }
|
|
.stat-card .stat-sub { color:var(--text-muted); font-size:12px; }
|
|
|
|
/* ── 表格代码 ─────────────────────────────────────────────────── */
|
|
code { background:var(--bg-tertiary); color:var(--accent); padding:.15rem .4rem; border-radius:.25rem;
|
|
font-size:13px; font-family:"SF Mono",Monaco,Menlo,Consolas,monospace; }
|
|
|
|
/* ── 分页 ─────────────────────────────────────────────────────── */
|
|
.pagination { margin-bottom:0; }
|
|
.page-link { color:var(--accent); background:var(--card-bg); border-color:var(--border); }
|
|
.page-link:hover { background:var(--bg-tertiary); color:var(--accent); }
|
|
.page-item.active .page-link { background:var(--accent); border-color:var(--accent); color:#fff; }
|
|
.page-item.disabled .page-link { color:var(--gray); }
|
|
|
|
/* ── 模态框 ───────────────────────────────────────────────────── */
|
|
.modal-content { background:var(--card-bg); border:1px solid var(--border); color:var(--text); }
|
|
.modal-header { border-bottom:1px solid var(--border); }
|
|
.modal-footer { border-top:1px solid var(--border); }
|
|
.btn-close { filter:invert(1); }
|
|
|
|
/* ── 消息 ─────────────────────────────────────────────────────── */
|
|
.toast { background:var(--card-bg); border:1px solid var(--border); color:var(--text); }
|
|
.toast-container { position:fixed; top:1rem; right:1rem; z-index:1060; }
|
|
|
|
/* ── 响应式 ───────────────────────────────────────────────────── */
|
|
@media(max-width:768px){
|
|
.sidebar { display:none; }
|
|
.main-content { margin-left:0; padding:1rem; }
|
|
}
|
|
|
|
/* ── 滚动条 ───────────────────────────────────────────────────── */
|
|
::-webkit-scrollbar { width:6px; height:6px; }
|
|
::-webkit-scrollbar-track { background:var(--bg-primary); }
|
|
::-webkit-scrollbar-thumb { background:var(--border); border-radius:3px; }
|
|
::-webkit-scrollbar-thumb:hover { background:var(--gray); }
|
|
</style>
|
|
{% block extra_style %}{% endblock %}
|
|
</head>
|
|
<body>
|
|
<!-- 导航栏 -->
|
|
<nav class="navbar navbar-expand fixed-top">
|
|
<div class="container-fluid">
|
|
<a class="navbar-brand" href="/"><i class="bi bi-hdd-rack"></i> SOCKS Manager</a>
|
|
<div class="d-flex align-items-center gap-2">
|
|
{% if nav.instances %}<span class="text-muted small">仪表盘</span>{% endif %}
|
|
<a href="/logout" class="btn btn-outline-secondary btn-sm"><i class="bi bi-box-arrow-right"></i> 退出</a>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
|
|
<!-- 侧边栏 -->
|
|
<div class="sidebar">
|
|
<div class="nav flex-column">
|
|
<div class="sidebar-section">概览</div>
|
|
<a class="nav-link {% if nav.instances %}active{% endif %}" href="/dashboard">
|
|
<i class="bi bi-speedometer2"></i> 仪表盘</a>
|
|
<a class="nav-link {% if nav.instances %}active{% endif %}" href="/instances">
|
|
<i class="bi bi-server"></i> 代理实例</a>
|
|
|
|
<div class="sidebar-section">用户</div>
|
|
<a class="nav-link {% if nav.users %}active{% endif %}" href="/users">
|
|
<i class="bi bi-people"></i> 用户管理</a>
|
|
|
|
<div class="sidebar-section">监控</div>
|
|
<a class="nav-link {% if nav.stats %}active{% endif %}" href="/stats">
|
|
<i class="bi bi-graph-up"></i> 流量统计</a>
|
|
<a class="nav-link {% if nav.stats %}active{% endif %}" href="/stats/connections">
|
|
<i class="bi bi-activity"></i> 活跃连接</a>
|
|
<a class="nav-link {% if nav.logs %}active{% endif %}" href="/logs">
|
|
<i class="bi bi-journal-text"></i> 审计日志</a>
|
|
|
|
<div class="sidebar-section">系统</div>
|
|
<a class="nav-link {% if nav.system %}active{% endif %}" href="/system">
|
|
<i class="bi bi-shield-lock"></i> 系统管理</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 主内容 -->
|
|
<div class="main-content">
|
|
<!-- 闪消息 -->
|
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
|
{% if messages %}
|
|
<div class="mb-3">
|
|
{% for category, msg in messages %}
|
|
<div class="alert alert-{{ category }} alert-dismissible fade show" role="alert">
|
|
{{ msg }}
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
{% endwith %}
|
|
|
|
{% block content %}{% endblock %}
|
|
</div>
|
|
|
|
<!-- Toast 容器 -->
|
|
<div class="toast-container" id="toastContainer"></div>
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
|
|
<script>
|
|
function toast(msg, type='info') {
|
|
const c = document.getElementById('toastContainer');
|
|
if (!c) return;
|
|
const colors = { success:'bg-success', danger:'bg-danger', warning:'bg-warning', info:'bg-primary' };
|
|
const t = document.createElement('div');
|
|
t.className = `toast show align-items-center ${colors[type]||'bg-primary'} text-white border-0`;
|
|
t.innerHTML = `<div class="d-flex"><div class="toast-body">${msg}</div><button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></button></div>`;
|
|
c.appendChild(t);
|
|
setTimeout(()=>t.remove(), 4000);
|
|
}
|
|
function fmtBytes(b) {
|
|
if (!b) return '0 B';
|
|
const u = ['B','KB','MB','GB','TB'];
|
|
let i = 0;
|
|
while (b >= 1024 && i < u.length-1) { b/=1024; i++; }
|
|
return b.toFixed(1)+' '+u[i];
|
|
}
|
|
function confirmDelete(msg) { return confirm(msg || '确认删除?此操作不可恢复。'); }
|
|
</script>
|
|
{% block extra_script %}{% endblock %}
|
|
</body>
|
|
</html>
|