Files
socks-manager/templates/base.html
T
Your Name 0bd9bcac12 fix: 修复暗色主题UI对比度、端口占用死循环及多项Web表单bug
后端修复:
- 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() 显示本地时间
2026-07-16 23:01:39 +08:00

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>