Files
socks-manager/templates/base.html
T
Your Name c8230f0d16 fix: 修复3个问题
1. 对比度: --text-muted 从 #94a3b8 改为 #c8d6e5 (文字更亮)
2. 登录: auth.py 从 .env 文件读取密码,解决后台进程环境变量丢失
3. 实例创建: 同步 instances.py 的 db import (之前缺 import db)
2026-07-16 18:35:03 +08:00

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:#c8d6e5;
--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:16px; line-height:1.7; }
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>