feat: frps-manager v1.0 - frp 0.70.1 multi-tenant web management system

- Multi-tenant client/proxy management with Flask+SQLite
- Frps server control (start/restart/token rotation)
- Generate frpc.ini with shared server token + admin API
- Dashboard API integration (live client/proxy status)
- RBAC: admin/tenant_admin/user roles
- Audit logging for all operations
- Systemd service files included
- Requires legacy INI format (frp 0.70 TOML auth broken)
This commit is contained in:
Your Name
2026-08-10 14:17:49 +08:00
commit 71214524f4
27 changed files with 2847 additions and 0 deletions
+83
View File
@@ -0,0 +1,83 @@
{% extends "base.html" %}
{% block title %}代理 · frps-manager{% endblock %}
{% block content %}
<div class="page-header">
<h1>代理规则</h1>
<a class="btn btn-primary" href="{{ url_for('proxies_new') }}">+ 新建代理</a>
</div>
{% if tenants|length > 1 or clients|length > 1 %}
<form method="get" class="filter-bar">
{% if tenants|length > 1 %}
<label>租户
<select name="tenant_id" onchange="this.form.submit()">
<option value="">全部</option>
{% for t in tenants %}
<option value="{{ t.id }}" {% if filter_tenant_id == t.id %}selected{% endif %}>{{ t.name }}</option>
{% endfor %}
</select>
</label>
{% endif %}
<label>客户端
<select name="client_id" onchange="this.form.submit()">
<option value="">全部</option>
{% for c in clients %}
<option value="{{ c.id }}" {% if filter_client_id == c.id %}selected{% endif %}>{{ c.name }} ({{ c.tenant.name }})</option>
{% endfor %}
</select>
</label>
</form>
{% endif %}
<table class="table">
<thead>
<tr><th>名称</th><th>类型</th><th>客户端</th><th>远端</th><th>本地</th><th>在线</th><th>启用</th><th>操作</th></tr>
</thead>
<tbody>
{% for p in items %}
<tr>
<td>
<strong>{{ p.name }}</strong>
{% if p.note %}<br><span class="muted small">{{ p.note }}</span>{% endif %}
</td>
<td><span class="tag tag-{{ p.type }}">{{ p.type }}</span></td>
<td>{{ p.client.name }}<br><span class="muted small">{{ p.client.tenant.name }}</span></td>
<td>
{% if p.type in ('tcp','udp') %}
<code>:{{ p.remote_port or '?' }}</code>
{% elif p.subdomain %}
<code>{{ p.subdomain }}.&lt;base&gt;</code>
{% elif p.custom_domains %}
<code>{{ p.custom_domains.split(',')[0] }}{% if p.custom_domains.count(',') > 0 %} ...{% endif %}</code>
{% else %}-{% endif %}
</td>
<td><code>{{ p.local_ip }}:{{ p.local_port }}</code></td>
<td>
{% if p.name in online_names %}<span class="badge badge-ok">在线</span>
{% else %}<span class="badge badge-off">离线</span>{% endif %}
</td>
<td>{% if p.is_enabled %}<span class="badge badge-ok">启用</span>{% else %}<span class="badge badge-off">禁用</span>{% endif %}</td>
<td class="actions">
<a href="{{ url_for('proxies_edit', pid=p.id) }}">编辑</a>
<form method="post" action="{{ url_for('proxies_toggle', pid=p.id) }}" style="display:inline">
<button class="link-btn">{% if p.is_enabled %}禁用{% else %}启用{% endif %}</button>
</form>
<form method="post" action="{{ url_for('proxies_delete', pid=p.id) }}" style="display:inline"
onsubmit="return confirm('删除代理 {{ p.name }} ');">
<button class="link-btn link-danger">删除</button>
</form>
</td>
</tr>
{% else %}
<tr><td colspan="8" class="muted">暂无代理</td></tr>
{% endfor %}
</tbody>
</table>
<section style="margin-top: 24px;">
<p class="muted small">
修改代理规则后,对应 frpc 主机需要重新拉取 <code>frpc.ini</code> 并执行 <code>frpc reload -c /etc/frpc.ini</code> 让新代理生效。
仪表盘上的「在线」状态来自 frps 实时报告,需要 frpc reload 后才会刷新。
</p>
</section>
{% endblock %}