Files
socks-manager/templates/users.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

265 lines
13 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends "base.html" %}
{% block title %}用户管理 · SOCKS Manager{% endblock %}
{% block content %}
<div class="page-header">
<div>
<h5 class="mb-0"><i class="bi bi-people"></i> 用户管理</h5>
<span class="text-muted small">共 {{ total }} 个用户</span>
</div>
<div class="d-flex gap-2">
<form method="get" class="d-flex">
<input name="search" class="form-control form-control-sm" placeholder="搜索用户名"
value="{{ search }}" style="width:180px">
<button type="submit" class="btn btn-outline-secondary btn-sm" title="搜索"><i class="bi bi-search"></i></button>
</form>
<button class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#addUserModal">
<i class="bi bi-plus-lg"></i> 新建用户</button>
</div>
</div>
<!-- 用户列表 -->
<div class="card">
<div class="card-body p-0">
<table class="table table-dark align-middle">
<thead>
<tr><th>用户名</th><th>状态</th><th>流量</th><th>限速</th><th>并发</th>
<th>白名单/黑名单</th><th>过期</th><th>创建时间</th><th>操作</th></tr>
</thead>
<tbody>
{% for u in users %}
<tr class="{% if not u.active %}table-secondary{% endif %}">
<td>
<strong>{{ u.username }}</strong>
{% if u.banned %}<span class="badge badge-danger ms-1">封禁</span>{% endif %}
{% if not u.enabled %}<span class="badge badge-secondary ms-1">禁用</span>{% endif %}
</td>
<td>
<span class="badge badge-{{ 'success' if u.active else 'danger' }}">
{{ '正常' if u.active else '不可用' }}</span>
</td>
<td>
<span class="text-muted small">↑{{ "%.0f"|format(u.bytes_in/1024/1024) }}M ↓{{ "%.0f"|format(u.bytes_out/1024/1024) }}M</span>
{% if u.total_traffic_mb %}<br><small class="text-muted">{{ "%.0f"|format(u.bytes_total_mb) }} / {{ u.total_traffic_mb }} MB</small>{% endif %}
</td>
<td>
<span class="text-muted small">↓{{ u.bandwidth_down or '不限' }} ↑{{ u.bandwidth_up or '不限' }} Mbps</span>
</td>
<td>{{ u.max_concurrent }}</td>
<td>
{% if u.ip_whitelist %}<span class="badge badge-info" title="白名单">白 {{ u.ip_whitelist|length }}</span>{% endif %}
{% if u.ip_blacklist %}<span class="badge badge-warning" title="黑名单">黑 {{ u.ip_blacklist|length }}</span>{% endif %}
{% if not u.ip_whitelist and not u.ip_blacklist %}<span class="text-muted">-</span>{% endif %}
</td>
<td>{{ u.expire_at or '不过期' }}</td>
<td><small class="text-muted">{{ u.created_at[:10] }}</small></td>
<td>
<div class="btn-group btn-group-sm">
<button class="btn btn-outline-secondary" title="编辑" data-bs-toggle="modal" data-bs-target="#editUserModal"
data-id="{{ u.id }}"
data-username="{{ u.username }}"
data-bw-down="{{ u.bandwidth_down }}"
data-bw-up="{{ u.bandwidth_up }}"
data-concurrent="{{ u.max_concurrent }}"
data-total="{{ u.total_traffic_mb }}"
data-monthly="{{ u.monthly_traffic_mb }}"
data-wl="{{ u.ip_whitelist }}"
data-bl="{{ u.ip_blacklist }}"
data-expire="{{ u.expire_at[:10] if u.expire_at else '' }}">
<i class="bi bi-pencil"></i>
</button>
<form method="post" action="/users/{{ u.id }}/toggle" style="display:inline">
<input type="hidden" name="enabled" value="{{ 'false' if u.enabled else 'true' }}">
<button class="btn btn-outline-{{ 'warning' if u.enabled else 'success' }}"
title="{{ '禁用' if u.enabled else '启用' }}">
<i class="bi bi-{{ 'eye-slash' if u.enabled else 'eye' }}"></i>
</button>
</form>
<form method="post" action="/users/{{ u.id }}/ban" style="display:inline">
<input type="hidden" name="ban" value="{{ 'false' if u.banned else 'true' }}">
<button class="btn btn-outline-{{ 'warning' if not u.banned else 'secondary' }}"
title="{{ '封禁' if not u.banned else '解封' }}">
<i class="bi bi-{{ 'ban' if not u.banned else 'check-circle' }}"></i>
</button>
</form>
<form method="post" action="/users/{{ u.id }}/delete" style="display:inline"
onsubmit="return confirmDelete('确认删除用户 {{ u.username }}')">
<button class="btn btn-outline-danger" title="删除"><i class="bi bi-trash"></i></button>
</form>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% if not users %}
<div class="text-center py-5 text-muted">暂无用户</div>
{% endif %}
</div>
<!-- 分页 -->
{% if pages > 1 %}
<div class="card-footer d-flex justify-content-between align-items-center">
<span class="text-muted small">第 {{ page }} / {{ pages }} 页</span>
<nav><ul class="pagination pagination-sm mb-0">
{% if page > 1 %}<li class="page-item"><a class="page-link" href="?page=1&search={{ search|urlencode }}">首页</a></li>{% endif %}
{% if page > 1 %}<li class="page-item"><a class="page-link" href="?page={{ page-1 }}&search={{ search|urlencode }}">上页</a></li>{% endif %}
{% for p in range(max(1,page-2), min(pages,page+2)+1) %}
<li class="page-item {% if p==page %}active{% endif %}">
<a class="page-link" href="?page={{ p }}&search={{ search|urlencode }}">{{ p }}</a></li>
{% endfor %}
{% if page < pages %}<li class="page-item"><a class="page-link" href="?page={{ page+1 }}&search={{ search|urlencode }}">下页</a></li>{% endif %}
{% if page < pages %}<li class="page-item"><a class="page-link" href="?page={{ pages }}&search={{ search|urlencode }}">末页</a></li>{% endif %}
</ul></nav>
</div>
{% endif %}
</div>
<!-- 添加用户模态框 -->
<div class="modal fade" id="addUserModal">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h6 class="modal-title">新建用户</h6>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form method="post" action="/users/add">
<div class="modal-body">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label">用户名</label>
<input name="username" class="form-control" required placeholder="登录时使用的用户名">
</div>
<div class="col-md-6">
<label class="form-label">密码</label>
<input name="password" class="form-control" placeholder="实例认证方式为账号密码时必填">
</div>
<div class="col-md-4">
<label class="form-label">下行限速 (Mbps)</label>
<input name="bandwidth_down" type="number" class="form-control" value="0" step="0.1">
</div>
<div class="col-md-4">
<label class="form-label">上行限速 (Mbps)</label>
<input name="bandwidth_up" type="number" class="form-control" value="0" step="0.1">
</div>
<div class="col-md-4">
<label class="form-label">最大并发连接</label>
<input name="max_concurrent" type="number" class="form-control" value="10">
</div>
<div class="col-md-4">
<label class="form-label">总流量上限 (MB)</label>
<input name="total_traffic_mb" type="number" class="form-control" value="0" placeholder="0=不限">
</div>
<div class="col-md-4">
<label class="form-label">月流量上限 (MB)</label>
<input name="monthly_traffic_mb" type="number" class="form-control" value="0" placeholder="0=不限">
</div>
<div class="col-md-4">
<label class="form-label">过期时间</label>
<input name="expire_at" type="datetime-local" class="form-control">
</div>
<div class="col-md-12">
<label class="form-label">IP 白名单 (逗号分隔, 留空=不限)</label>
<input name="ip_whitelist" class="form-control" placeholder="例: 192.168.1.0/24, 10.0.0.1">
</div>
<div class="col-md-12">
<label class="form-label">IP 黑名单 (逗号分隔)</label>
<input name="ip_blacklist" class="form-control" placeholder="例: 1.2.3.4, 5.6.7.8">
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary btn-sm" data-bs-dismiss="modal">取消</button>
<button type="submit" class="btn btn-primary btn-sm">创建</button>
</div>
</form>
</div>
</div>
</div>
<!-- 编辑用户模态框 -->
<div class="modal fade" id="editUserModal">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h6 class="modal-title" id="editUserTitle">编辑用户</h6>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form id="editUserForm" method="post">
<div class="modal-body">
<input type="hidden" name="id" id="editUid">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label">用户名</label>
<input class="form-control" id="editUsername" disabled>
</div>
<div class="col-md-6">
<label class="form-label">密码 (留空=不修改)</label>
<input name="password" class="form-control" id="editPassword" placeholder="修改密码">
</div>
<div class="col-md-4">
<label class="form-label">下行限速 (Mbps)</label>
<input name="bandwidth_down" type="number" class="form-control" id="editBwDown" step="0.1">
</div>
<div class="col-md-4">
<label class="form-label">上行限速 (Mbps)</label>
<input name="bandwidth_up" type="number" class="form-control" id="editBwUp" step="0.1">
</div>
<div class="col-md-4">
<label class="form-label">最大并发连接</label>
<input name="max_concurrent" type="number" class="form-control" id="editConcurrent">
</div>
<div class="col-md-4">
<label class="form-label">总流量上限 (MB)</label>
<input name="total_traffic_mb" type="number" class="form-control" id="editTotal" placeholder="0=不限">
</div>
<div class="col-md-4">
<label class="form-label">月流量上限 (MB)</label>
<input name="monthly_traffic_mb" type="number" class="form-control" id="editMonthly" placeholder="0=不限">
</div>
<div class="col-md-4">
<label class="form-label">过期时间</label>
<input name="expire_at" type="datetime-local" class="form-control" id="editExpire">
</div>
<div class="col-md-12">
<label class="form-label">IP 白名单</label>
<input name="ip_whitelist" class="form-control" id="editWL">
</div>
<div class="col-md-12">
<label class="form-label">IP 黑名单</label>
<input name="ip_blacklist" class="form-control" id="editBL">
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary btn-sm" data-bs-dismiss="modal">取消</button>
<button type="submit" class="btn btn-primary btn-sm">保存</button>
</div>
</form>
</div>
</div>
</div>
{% endblock %}
{% block extra_script %}
<script>
document.getElementById('editUserModal').addEventListener('show.bs.modal', function(e) {
const btn = e.relatedTarget;
const form = document.getElementById('editUserForm');
form.action = '/users/' + btn.dataset.id + '/edit';
document.getElementById('editUid').value = btn.dataset.id;
document.getElementById('editUsername').value = btn.dataset.username;
document.getElementById('editBwDown').value = btn.dataset.bwDown;
document.getElementById('editBwUp').value = btn.dataset.bwUp;
document.getElementById('editConcurrent').value = btn.dataset.concurrent;
document.getElementById('editTotal').value = btn.dataset.total;
document.getElementById('editMonthly').value = btn.dataset.monthly;
document.getElementById('editWL').value = btn.dataset.wl || '';
document.getElementById('editBL').value = btn.dataset.bl || '';
document.getElementById('editExpire').value = btn.dataset.expire || '';
document.getElementById('editUserTitle').textContent = '编辑用户: ' + btn.dataset.username;
});
</script>
{% endblock %}