0bd9bcac12
后端修复: - engine/instances.py: Socks5Server.start() 端口占用时死循环, 增加超时和异常透传 - engine/instances.py: import time 移至顶层 - engine/instances.py: sync_instances/start_instance 容忍单实例启动失败 - run.py: 启动时 sync_instances 失败不退出进程 - web/routes.py: instances start/restart/edit 路由捕获 OSError, flash 友好提示 - api/v1.py: instances start/restart/update API 返回 JSON 错误而非 500 崩溃 - web/routes.py: toggle_user/ban_user 支持 hidden 字段语义, 缺字段时切换状态 - web/routes.py: inject_nav 增加独立 nav.dashboard 和 page_name, 修复双 active bug - auth.py: login 路由添加 logging import, 修 NameError 前端重构: - templates/base.html: 全局对比度提升(--text #f1f5f9, --accent #a5b4fc), 标题色显式声明, alert/modal/progress/table 完整暗色样式 - templates/base.html: main-content padding-top 修复 navbar 遮挡内容 - templates/base.html: 侧边栏 active 高亮 (白字 + elevated bg + 左边框) - templates/base.html: 顶栏动态显示当前页面名 - templates/*.html: 统一 page-header 类, 所有图标按钮添加 title tooltip - templates/users.html: toggle/ban 表单添加 hidden enabled/ban 字段 - templates/connections.html: fmtBytes 改为 Jinja macro, 时间格式化 - templates/dashboard.html: 时间格式化为 YYYY-MM-DD HH:MM:SS, chart canvas 高度调整 - templates/logs.html: 时间格式化为可读格式 - templates/stats.html: 提升 chart 颜色对比度 - templates/system.html: 使用 time.localtime() 显示本地时间
270 lines
16 KiB
HTML
270 lines
16 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;
|
|
--bg-elevated:#1f2433;
|
|
--border:#3a4254; --border-strong:#4b5563;
|
|
--text:#f1f5f9; /* 提高主文字亮度, 之前 #e2e8f0 在小字号下偏暗 */
|
|
--text-muted:#a3acbf; /* 之前 #9ca3af, 稍微提亮 */
|
|
--accent:#a5b4fc; /* 提亮主色, 之前 #818cf8 对比度太低 */
|
|
--accent-hover:#818cf8;
|
|
--accent-dim:rgba(165,180,252,.15);
|
|
--green:#34d399; --red:#f87171; --yellow:#fbbf24; --gray:#94a3b8;
|
|
--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:15px; line-height:1.6; }
|
|
a { color:var(--accent); text-decoration:none; }
|
|
a:hover { color:var(--accent-hover); }
|
|
|
|
/* 显式覆盖 Bootstrap 默认的标题色 (默认是 inherit, 但某些情况下浏览器渲染为黑) */
|
|
h1, h2, h3, h4, h5, h6 { color:var(--text); margin-top:0; }
|
|
.h1, .h2, .h3, .h4, .h5, .h6 { color:var(--text); }
|
|
.text-secondary, .text-body { color:var(--text) !important; }
|
|
|
|
/* ── 导航 ─────────────────────────────────────────────────────── */
|
|
.navbar { background:var(--bg-secondary)!important; border-bottom:1px solid var(--border)!important;
|
|
padding:.6rem 1rem; height:56px; }
|
|
.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-elevated); color:#fff!important;
|
|
border-left:3px solid var(--accent); font-weight:600; }
|
|
.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); padding-top:calc(56px + 1.2rem); }
|
|
.page-header { display:flex; justify-content:space-between; align-items:center; margin-bottom:1rem; }
|
|
.page-header h5 { margin:0; color:var(--text); font-size:1.1rem; font-weight:600; }
|
|
|
|
/* ── 卡片 ─────────────────────────────────────────────────────── */
|
|
.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;
|
|
color:var(--text); font-weight:600; }
|
|
.card-header i { color:var(--accent); margin-right:.4rem; }
|
|
.card-body { padding:1rem; color:var(--text); }
|
|
.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:#cbd5e1; font-weight:600;
|
|
font-size:12px; text-transform:uppercase; letter-spacing:.04em; border-bottom:1px solid var(--border);
|
|
padding:.7rem .8rem; }
|
|
.table tbody td { padding:.65rem .8rem; border-bottom:1px solid var(--border); font-size:14px;
|
|
vertical-align:middle; color:var(--text); }
|
|
.table tbody tr:hover { background:var(--bg-tertiary); }
|
|
.table-dark { background:var(--card-bg); }
|
|
.table-dark th, .table-dark td { color:var(--text); border-color:var(--border); }
|
|
.table code { background:var(--bg-tertiary); color:var(--accent); }
|
|
|
|
/* ── 表单 ─────────────────────────────────────────────────────── */
|
|
.form-control, .form-select { background:#1a1d2b; border:1px solid var(--border-strong);
|
|
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(165,180,252,.15);
|
|
background:#1a1d2b; color:var(--text); }
|
|
.form-control::placeholder { color:#6b7592; }
|
|
.form-text { color:var(--text-muted); font-size:12px; }
|
|
.form-label { color:#d1d9e6 !important; font-size:13px; margin-bottom:.3rem; font-weight:500; }
|
|
.form-check-input { background-color:var(--input-bg); border-color:var(--border-strong); }
|
|
.form-check-input:checked { background-color:var(--accent); border-color:var(--accent); }
|
|
|
|
/* ── 按钮 ─────────────────────────────────────────────────────── */
|
|
.btn-primary { background:var(--accent); border-color:var(--accent); color:#0b0e14; font-weight:600; }
|
|
.btn-primary:hover { background:var(--accent-hover); border-color:var(--accent-hover); color:#0b0e14; }
|
|
.btn-primary:active { background:var(--accent-hover); }
|
|
.btn-primary:focus { box-shadow:0 0 0 3px rgba(165,180,252,.3); }
|
|
.btn-outline-primary { color:var(--accent); border-color:var(--accent); }
|
|
.btn-outline-primary:hover { background:var(--accent); border-color:var(--accent); color:#0b0e14; }
|
|
.btn-outline-secondary { color:var(--text-muted); border-color:var(--border-strong); }
|
|
.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:#0b0e14; }
|
|
.btn-outline-warning { color:var(--yellow); border-color:var(--yellow); }
|
|
.btn-outline-warning:hover { background:var(--yellow); border-color:var(--yellow); color:#0b0e14; }
|
|
.btn-sm { font-size:13px; padding:.3rem .6rem; }
|
|
.btn-group-sm > .btn { padding:.25rem .55rem; }
|
|
|
|
/* ── 徽章 ─────────────────────────────────────────────────────── */
|
|
.badge { font-size:11px; padding:.3em .6em; border-radius:.3rem; font-weight:600; }
|
|
.badge-success { background:rgba(52,211,153,.18); color:#34d399; }
|
|
.badge-danger { background:rgba(248,113,113,.18); color:#f87171; }
|
|
.badge-warning { background:rgba(251,191,36,.18); color:#fbbf24; }
|
|
.badge-secondary { background:rgba(148,163,184,.18); color:#cbd5e1; }
|
|
.badge-info { background:var(--accent-dim); 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(52,211,153,.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; color:var(--text); }
|
|
.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-hover); }
|
|
.page-item.active .page-link { background:var(--accent); border-color:var(--accent); color:#0b0e14; }
|
|
.page-item.disabled .page-link { color:var(--gray); }
|
|
|
|
/* ── 进度条 ──────────────────────────────────────────────────── */
|
|
.progress { background:var(--bg-tertiary); }
|
|
.progress-bar { font-weight:600; }
|
|
|
|
/* ── 模态框 ───────────────────────────────────────────────────── */
|
|
.modal-content { background:var(--card-bg); border:1px solid var(--border-strong); color:var(--text); }
|
|
.modal-header { border-bottom:1px solid var(--border); }
|
|
.modal-header .modal-title { color:var(--text); font-weight:600; }
|
|
.modal-footer { border-top:1px solid var(--border); }
|
|
.btn-close { filter:invert(1) grayscale(100%) brightness(200%); }
|
|
|
|
/* ── Alert ─────────────────────────────────────────────────────── */
|
|
.alert { border:1px solid var(--border); color:var(--text); background:var(--card-bg); }
|
|
.alert-success { background:rgba(52,211,153,.12); border-color:rgba(52,211,153,.4); color:#34d399; }
|
|
.alert-danger { background:rgba(248,113,113,.12); border-color:rgba(248,113,113,.4); color:#f87171; }
|
|
.alert-warning { background:rgba(251,191,36,.12); border-color:rgba(251,191,36,.4); color:#fbbf24; }
|
|
.alert-info { background:var(--accent-dim); border-color:rgba(165,180,252,.4); color:var(--accent); }
|
|
|
|
/* ── 消息 ─────────────────────────────────────────────────────── */
|
|
.toast { background:var(--card-bg); border:1px solid var(--border); color:var(--text); }
|
|
.toast-container { position:fixed; top:1rem; right:1rem; z-index:1060; }
|
|
|
|
/* ── Chart.js canvas 容器 ─────────────────────────────────────── */
|
|
canvas { max-width:100%; }
|
|
|
|
/* ── 响应式 ───────────────────────────────────────────────────── */
|
|
@media(max-width:768px){
|
|
.sidebar { display:none; }
|
|
.main-content { margin-left:0; padding:1rem; padding-top:calc(56px + 1rem); }
|
|
}
|
|
|
|
/* ── 滚动条 ───────────────────────────────────────────────────── */
|
|
::-webkit-scrollbar { width:8px; height:8px; }
|
|
::-webkit-scrollbar-track { background:var(--bg-primary); }
|
|
::-webkit-scrollbar-thumb { background:var(--border-strong); border-radius:4px; }
|
|
::-webkit-scrollbar-thumb:hover { background:var(--gray); }
|
|
|
|
/* ── 覆盖 Bootstrap text-muted 提高深色背景对比度 ─────────────── */
|
|
.text-muted { color:var(--text-muted) !important; }
|
|
</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 page_name %}<span class="text-muted small">{{ page_name }}</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.dashboard %}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>
|