Files
socks-manager/templates/users.html
T
Your Name 558bd8153f feat: 重构为 SOCKS5 代理管理平台 v2.0
架构:
- 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 项功能测试全部通过
2026-07-16 17:31:53 +08:00

261 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="d-flex justify-content-between align-items-center mb-3">
<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"><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" 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">
<button class="btn btn-outline-{{ 'warning' if u.enabled else 'success' }}">
<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">
<button class="btn btn-outline-{{ 'warning' if not u.banned else 'secondary' }}">
<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"><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 %}