Files
socks-manager/templates/dashboard.html
T
Your Name aa8af6c2e0 feat: SOCKS5 代理管理系统 v1.0
Flask + SQLite + Bootstrap5 暗色主题
- 仪表盘: 在线/离线统计 + 最近代理 + 操作日志
- 代理 CRUD: 增删改 + 启用/禁用 + 单/批量健康检测(SOCKS5)
- 分组管理: 创建/删除/筛选
- 操作日志: 分页查询
- RESTful API + Web 界面
- 管理员登录认证 (SM_ADMIN_PASSWORD 环境变量)
2026-07-16 16:40:20 +08:00

89 lines
3.4 KiB
HTML

{% extends "base.html" %}
{% block title %}仪表盘 · SOCKS Manager{% endblock %}
{% block content %}
<h4 class="mb-4"><i class="bi bi-speedometer2"></i> 仪表盘</h4>
<div class="row g-3 mb-4">
<div class="col-md-3"><div class="card stat-card p-3">
<div class="text-muted small">代理总数</div>
<div class="stat-value">{{ total }}</div>
</div></div>
<div class="col-md-3"><div class="card stat-card p-3">
<div class="text-muted small"><span class="status-dot dot-online"></span>在线</div>
<div class="stat-value text-success">{{ online }}</div>
</div></div>
<div class="col-md-3"><div class="card stat-card p-3">
<div class="text-muted small"><span class="status-dot dot-offline"></span>离线</div>
<div class="stat-value text-danger">{{ offline }}</div>
</div></div>
<div class="col-md-3"><div class="card stat-card p-3">
<div class="text-muted small">分组数</div>
<div class="stat-value">{{ groups }}</div>
</div></div>
</div>
<div class="card mb-4">
<div class="card-header d-flex justify-content-between align-items-center">
<span><i class="bi bi-globe"></i> 最近活跃代理</span>
<button class="btn btn-outline-primary btn-sm" onclick="checkAll()">
<i class="bi bi-arrow-clockwise"></i> 全部检测</button>
</div>
<div class="card-body p-0">
{% if proxies %}
<table class="table table-dark table-hover mb-0">
<thead><tr><th>名称</th><th>地址</th><th>状态</th><th>延迟</th><th>最后检测</th></tr></thead>
<tbody>
{% for p in proxies %}
<tr>
<td>{{ p.name }}</td>
<td><code>{{ p.host }}:{{ p.port }}</code></td>
<td><span class="status-dot dot-{{ p.status }}"></span>{{ p.status }}</td>
<td>{{ "%.1f"|format(p.latency_ms) if p.latency_ms else '-' }}ms</td>
<td>{{ p.last_check.strftime('%Y-%m-%d %H:%M') if p.last_check else '-' }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<div class="text-center py-4 text-muted">暂无代理,<a href="/proxies/add" class="text-info">添加一个</a></div>
{% endif %}
</div>
</div>
<div class="card">
<div class="card-header"><span><i class="bi bi-journal-text"></i> 最近操作日志</span></div>
<div class="card-body p-0">
<table class="table table-dark mb-0 table-sm">
<thead><tr><th>时间</th><th>级别</th><th>操作</th><th>目标</th><th>详情</th></tr></thead>
<tbody>
{% for l in recent %}
<tr>
<td>{{ l.timestamp.strftime('%Y-%m-%d %H:%M:%S') }}</td>
<td><span class="badge bg-{{ 'success' if l.level=='INFO' else 'warning' if l.level=='WARN' else 'danger' }}">{{ l.level }}</span></td>
<td>{{ l.action }}</td>
<td>{{ l.target or '-' }}</td>
<td>{{ l.detail or '-' }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endblock %}
{% block extra_script %}
<script>
async function checkAll() {
const btn = event.currentTarget;
btn.disabled = true; btn.innerHTML = '<span class="spinner-border spinner-border-sm"></span> 检测中...';
try {
const r = await apiFetch('/health/check-all');
toast(`检测完成: ${r.checked} 个, ${r.online} 在线, ${r.offline} 离线`, r.offline?'warning':'success');
} catch(e) { toast('检测失败: '+e.message, 'danger'); }
btn.disabled = false; btn.innerHTML = '<i class="bi bi-arrow-clockwise"></i> 全部检测';
}
</script>
{% endblock %}