feat: SOCKS5 代理管理系统 v1.0

Flask + SQLite + Bootstrap5 暗色主题
- 仪表盘: 在线/离线统计 + 最近代理 + 操作日志
- 代理 CRUD: 增删改 + 启用/禁用 + 单/批量健康检测(SOCKS5)
- 分组管理: 创建/删除/筛选
- 操作日志: 分页查询
- RESTful API + Web 界面
- 管理员登录认证 (SM_ADMIN_PASSWORD 环境变量)
This commit is contained in:
Your Name
2026-07-16 16:40:20 +08:00
commit aa8af6c2e0
20 changed files with 950 additions and 0 deletions
+77
View File
@@ -0,0 +1,77 @@
{% extends "base.html" %}
{% block title %}分组管理 · SOCK Manager{% endblock %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4">
<h4 class="mb-0"><i class="bi bi-collection"></i> 分组管理</h4>
<button class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#addGroupModal">
<i class="bi bi-plus-lg"></i> 添加分组</button>
</div>
<div class="row g-3">
{% for g in groups %}
<div class="col-md-4 col-lg-3">
<div class="card" style="border-left:4px solid {{ g.color }}">
<div class="card-body">
<h6 style="color:{{ g.color }}">{{ g.name }}</h6>
<p class="text-muted small mb-1">{{ g.description or '无描述' }}</p>
<div class="d-flex justify-content-between align-items-center">
<span class="badge bg-secondary">{{ g.proxies|length }} 个代理</span>
<button class="btn btn-outline-danger btn-sm" onclick="deleteGroup({{ g.id }}, '{{ g.name }}')">
<i class="bi bi-trash"></i></button>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
{% if not groups %}
<div class="text-center py-5 text-muted">暂无分组</div>
{% endif %}
<!-- Add Group Modal -->
<div class="modal fade" id="addGroupModal">
<div class="modal-dialog modal-sm">
<div class="modal-content" style="background:#1a1d27;border-color:#2a2d3a">
<div class="modal-header" style="border-color:#2a2d3a">
<h6 class="modal-title">添加分组</h6>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3"><label class="form-label">名称</label>
<input id="gName" class="form-control" required></div>
<div class="mb-3"><label class="form-label">颜色</label>
<input id="gColor" type="color" class="form-control form-control-color" value="#6366f1"></div>
<div class="mb-3"><label class="form-label">描述</label>
<input id="gDesc" class="form-control"></div>
</div>
<div class="modal-footer" style="border-color:#2a2d3a">
<button type="button" class="btn btn-sm btn-outline-secondary" data-bs-dismiss="modal">取消</button>
<button type="button" class="btn btn-sm btn-primary" onclick="addGroup()">添加</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_script %}
<script>
async function addGroup() {
const name = document.getElementById('gName').value.trim();
if (!name) return;
try {
await fetch('/groups/add', {method:'POST', headers:{'Content-Type':'application/x-www-form-urlencoded'},
body:`name=${encodeURIComponent(name)}&color=${document.getElementById('gColor').value}&description=${encodeURIComponent(document.getElementById('gDesc').value)}`});
toast('分组已添加', 'success'); location.reload();
} catch(e) { toast('添加失败', 'danger'); }
}
async function deleteGroup(id, name) {
if (!confirm(`确认删除分组 "${name}"`)) return;
const r = await fetch(`/groups/${id}`, {method:'DELETE'});
const j = await r.json();
if (j.error) { toast(j.error, 'warning'); } else { toast('分组已删除', 'success'); location.reload(); }
}
</script>
{% endblock %}