Add 3x-ui v3.5.0 API panel connection and client management
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -10,6 +10,7 @@
|
||||
<nav>
|
||||
<a href="/admin" class="{% if request.url.path == '/admin' %}active{% endif %}">Дашборд</a>
|
||||
<a href="/admin/servers" class="{% if '/servers' in request.url.path %}active{% endif %}">Серверы</a>
|
||||
<a href="/admin/xui" class="{% if '/xui' in request.url.path %}active{% endif %}">3x-ui</a>
|
||||
<a href="/admin/clients" class="{% if '/clients' in request.url.path %}active{% endif %}">Клиенты</a>
|
||||
<a href="/" target="_blank">Сайт</a>
|
||||
</nav>
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
{% extends "admin/layout.html" %}
|
||||
{% block admin_title %}3x-ui{% endblock %}
|
||||
{% block admin_content %}
|
||||
<div class="admin-top">
|
||||
<h1>3x-ui панели</h1>
|
||||
<span class="badge badge-proto">API v3.5.0</span>
|
||||
</div>
|
||||
|
||||
{% if flash %}
|
||||
{% if flash.startswith('error:') %}
|
||||
<div class="flash flash-error">{{ flash[6:] }}</div>
|
||||
{% elif flash == 'created' %}
|
||||
<div class="flash">Панель 3x-ui подключена</div>
|
||||
{% elif flash == 'ok' %}
|
||||
<div class="flash">API отвечает</div>
|
||||
{% elif flash == 'deleted' %}
|
||||
<div class="flash">Панель удалена</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<div class="panel" style="margin-bottom:1.25rem">
|
||||
<div class="panel-head"><strong>Подключить 3x-ui</strong></div>
|
||||
<div class="panel-body">
|
||||
<p class="muted" style="margin-top:0">
|
||||
Укажите URL панели (с webBasePath, если есть) и <strong>API Token</strong>
|
||||
(Settings → Security → API Token) — предпочтительно для v3.5.0.
|
||||
Либо логин/пароль админа.
|
||||
</p>
|
||||
<form method="post" action="/admin/xui" class="stack">
|
||||
<div class="form-grid-2">
|
||||
<label>Название
|
||||
<input name="name" placeholder="Мой 3x-ui" />
|
||||
</label>
|
||||
<label>Base URL
|
||||
<input name="base_url" placeholder="https://1.2.3.4:2053/secret" required />
|
||||
</label>
|
||||
</div>
|
||||
<h3 class="form-section">Авторизация</h3>
|
||||
<label>API Token (рекомендуется)
|
||||
<input name="api_token" placeholder="Bearer token из 3x-ui" autocomplete="off" />
|
||||
</label>
|
||||
<div class="form-grid-2">
|
||||
<label>Логин (если без токена)
|
||||
<input name="username" autocomplete="username" />
|
||||
</label>
|
||||
<label>Пароль
|
||||
<input type="password" name="password" autocomplete="new-password" />
|
||||
</label>
|
||||
</div>
|
||||
<label class="check">
|
||||
<input type="checkbox" name="verify_ssl" value="1" checked /> проверять SSL-сертификат
|
||||
</label>
|
||||
<button class="btn btn-accent" type="submit">Проверить API и сохранить</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% for p in panels %}
|
||||
<div class="panel" style="margin-bottom:1rem">
|
||||
<div class="panel-head">
|
||||
<strong>
|
||||
<span class="ping {% if p.is_reachable %}ping-on{% else %}ping-off{% endif %}"></span>
|
||||
{{ p.name }}
|
||||
{% if p.is_reachable %}<span class="badge badge-ok">online</span>{% else %}<span class="badge badge-off">offline</span>{% endif %}
|
||||
</strong>
|
||||
<div class="actions">
|
||||
<a class="btn btn-sm btn-ghost" href="/admin/xui/{{ p.id }}">Открыть</a>
|
||||
<form method="post" action="/admin/xui/{{ p.id }}/check" class="inline-form">
|
||||
<button class="btn btn-sm btn-ghost" type="submit">Проверить</button>
|
||||
</form>
|
||||
<form method="post" action="/admin/xui/{{ p.id }}/delete" class="inline-form" onsubmit="return confirm('Удалить подключение?')">
|
||||
<button class="btn btn-sm btn-danger" type="submit">Удалить</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<p class="muted" style="margin-top:0">
|
||||
<a href="{{ p.base_url }}" target="_blank" rel="noopener">{{ p.base_url }}</a>
|
||||
· auth: {% if p.api_token %}API Token{% else %}login/password{% endif %}
|
||||
{% if p.last_checked %}· checked {{ p.last_checked.strftime('%Y-%m-%d %H:%M') }}{% endif %}
|
||||
</p>
|
||||
{% if p.last_error %}
|
||||
<div class="flash flash-error">{{ p.last_error }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="muted">Пока нет подключённых 3x-ui панелей.</p>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,132 @@
|
||||
{% extends "admin/layout.html" %}
|
||||
{% block admin_title %}{{ panel.name }}{% endblock %}
|
||||
{% block admin_content %}
|
||||
<div class="admin-top">
|
||||
<div>
|
||||
<h1>{{ panel.name }}</h1>
|
||||
<div class="muted"><a href="{{ panel.base_url }}" target="_blank" rel="noopener">{{ panel.base_url }}</a></div>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<a class="btn btn-sm btn-ghost" href="/admin/xui">Назад</a>
|
||||
<form method="post" action="/admin/xui/{{ panel.id }}/check" class="inline-form">
|
||||
<button class="btn btn-sm btn-accent" type="submit">Обновить API</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if flash %}
|
||||
{% if flash.startswith('error:') %}
|
||||
<div class="flash flash-error">{{ flash[6:] }}</div>
|
||||
{% elif flash == 'client_created' %}
|
||||
<div class="flash">Клиент создан в 3x-ui</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if error %}
|
||||
<div class="flash flash-error">{{ error }}</div>
|
||||
{% endif %}
|
||||
|
||||
{% if data %}
|
||||
<div class="stats">
|
||||
<div class="stat">
|
||||
<div class="label">Inbounds</div>
|
||||
<div class="value">{{ data.inbounds|length }}</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="label">Clients</div>
|
||||
<div class="value">{{ data.clients|length }}</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="label">Xray</div>
|
||||
<div class="value" style="font-size:1.1rem">
|
||||
{% if data.status and data.status.xray %}
|
||||
{% if data.status.xray.state %}running{% else %}stopped{% endif %}
|
||||
{% else %}—{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel" style="margin-bottom:1rem">
|
||||
<div class="panel-head"><strong>Добавить клиента через API</strong></div>
|
||||
<div class="panel-body">
|
||||
<form method="post" action="/admin/xui/{{ panel.id }}/clients" class="form-grid">
|
||||
<label>Email / имя
|
||||
<input name="email" placeholder="user1" required />
|
||||
</label>
|
||||
<label>Inbound
|
||||
<select name="inbound_id" required>
|
||||
{% for ib in data.inbounds %}
|
||||
<option value="{{ ib.id }}">#{{ ib.id }} {{ ib.remark or ib.protocol }} :{{ ib.port }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
<button class="btn btn-accent" type="submit">Создать</button>
|
||||
<label>Лимит GB (0 = безлимит)
|
||||
<input type="number" name="total_gb" value="0" min="0" />
|
||||
</label>
|
||||
<label>Limit IP
|
||||
<input type="number" name="limit_ip" value="0" min="0" />
|
||||
</label>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel" style="margin-bottom:1rem">
|
||||
<div class="panel-head"><strong>Inbounds</strong></div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Remark</th>
|
||||
<th>Protocol</th>
|
||||
<th>Port</th>
|
||||
<th>Enable</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for ib in data.inbounds %}
|
||||
<tr>
|
||||
<td>{{ ib.id }}</td>
|
||||
<td>{{ ib.remark or '—' }}</td>
|
||||
<td><span class="badge badge-proto">{{ ib.protocol }}</span></td>
|
||||
<td>{{ ib.port }}</td>
|
||||
<td>{% if ib.enable %}<span class="badge badge-ok">on</span>{% else %}<span class="badge badge-off">off</span>{% endif %}</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="5" class="muted">Нет inbound’ов</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<div class="panel-head"><strong>Clients</strong></div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Email</th>
|
||||
<th>Enable</th>
|
||||
<th>Inbounds</th>
|
||||
<th>Traffic</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for c in data.clients %}
|
||||
<tr>
|
||||
<td>{{ c.email }}</td>
|
||||
<td>{% if c.enable %}<span class="badge badge-ok">on</span>{% else %}<span class="badge badge-off">off</span>{% endif %}</td>
|
||||
<td>{{ c.inboundIds or c.inbound_ids or '—' }}</td>
|
||||
<td class="muted">
|
||||
{% if c.traffic %}
|
||||
↑ {{ c.traffic.up or 0 }} / ↓ {{ c.traffic.down or 0 }}
|
||||
{% else %}—{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="4" class="muted">Нет клиентов или endpoint /clients/list недоступен</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user