aa8af6c2e0
Flask + SQLite + Bootstrap5 暗色主题 - 仪表盘: 在线/离线统计 + 最近代理 + 操作日志 - 代理 CRUD: 增删改 + 启用/禁用 + 单/批量健康检测(SOCKS5) - 分组管理: 创建/删除/筛选 - 操作日志: 分页查询 - RESTful API + Web 界面 - 管理员登录认证 (SM_ADMIN_PASSWORD 环境变量)
66 lines
3.1 KiB
HTML
66 lines
3.1 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}代理列表 · SOCKS Manager{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<h4 class="mb-0"><i class="bi bi-globe"></i> 代理列表</h4>
|
|
<div class="d-flex gap-2">
|
|
<select class="form-select form-select-sm" id="groupFilter" style="width:180px"
|
|
onchange="location.href='/proxies?group='+this.value">
|
|
<option value="">全部分组</option>
|
|
{% for g in groups %}
|
|
<option value="{{ g.id }}" {% if active_group|default('') == g.id|string %}selected{% endif %}>
|
|
{{ g.name }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
<a href="/proxies/add" class="btn btn-primary btn-sm"><i class="bi bi-plus-lg"></i> 添加代理</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-body p-0">
|
|
<table class="table table-dark table-hover align-middle mb-0">
|
|
<thead><tr><th>名称</th><th>主机</th><th>端口</th><th>认证</th><th>分组</th><th>状态</th><th>延迟</th><th>操作</th></tr></thead>
|
|
<tbody>
|
|
{% for p in proxies %}
|
|
<tr class="{% if not p.enabled %}table-secondary{% endif %}">
|
|
<td><a href="/proxies/{{ p.id }}/edit" class="text-decoration-none">{{ p.name }}</a></td>
|
|
<td><code>{{ p.host }}</code></td>
|
|
<td>{{ p.port }}</td>
|
|
<td>{{ '有认证' if p.username else '无' }}</td>
|
|
<td>{% if p.group %}<span style="color:{{ p.group.color }}">{{ p.group.name }}</span>{% else %}-{% endif %}</td>
|
|
<td><span class="status-dot dot-{{ p.status }}"></span>{{ p.status }}<br><small class="text-muted">{{ p.last_check.strftime('%Y-%m-%d %H:%M') if p.last_check else '未检测' }}</small></td>
|
|
<td>{{ "%.1f"|format(p.latency_ms) if p.latency_ms else '-' }}ms</td>
|
|
<td>
|
|
<div class="btn-group btn-group-sm">
|
|
<a href="/proxies/{{ p.id }}/edit" class="btn btn-outline-secondary"><i class="bi bi-pencil"></i></a>
|
|
<a href="/proxies/toggle/{{ p.id }}" class="btn btn-outline-{{ 'secondary' if p.enabled else 'success' }}" onclick="return confirm('确认{{ '禁用' if p.enabled else '启用' }}?')">
|
|
<i class="bi bi-{{ 'eye-slash' if p.enabled else 'eye' }}"></i></a>
|
|
<a href="/proxies/{{ p.id }}" class="btn btn-outline-danger" onclick="return confirm('确认删除?')"><i class="bi bi-trash"></i></a>
|
|
<button class="btn btn-outline-info" onclick="checkOne({{ p.id }})"><i class="bi bi-arrow-clockwise"></i></button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
{% if not proxies %}
|
|
<div class="text-center py-5 text-muted">暂无代理</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
{% endblock %}
|
|
|
|
{% block extra_script %}
|
|
<script>
|
|
async function checkOne(pid) {
|
|
try {
|
|
const r = await apiFetch(`/api/proxies/${pid}/check`, {method:'POST'});
|
|
toast(`${r.name}: ${r.status}${r.latency_ms? ' ('+r.latency_ms+'ms)':''}`, r.status==='online'?'success':'danger');
|
|
location.reload();
|
|
} catch(e) { toast('检测失败', 'danger'); }
|
|
}
|
|
</script>
|
|
{% endblock %}
|