@@ -0,0 +1,33 @@
|
||||
{% extends "admin/layout.html" %}
|
||||
{% block admin_title %}{{ client.name }}{% endblock %}
|
||||
{% block admin_content %}
|
||||
<div class="admin-top">
|
||||
<div>
|
||||
<h1>{{ client.name }}</h1>
|
||||
<div class="muted">{{ client.server.name }} · {{ client.server.protocol }} · {{ client.address }}</div>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<a class="btn btn-sm btn-ghost" href="/admin/clients">Назад</a>
|
||||
<a class="btn btn-sm btn-accent" href="/admin/clients/{{ client.id }}/config">Скачать .conf</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-grid">
|
||||
<div class="panel">
|
||||
<div class="panel-head"><strong>Конфиг</strong></div>
|
||||
<div class="panel-body">
|
||||
<pre class="mono">{{ config }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel">
|
||||
<div class="panel-head"><strong>QR‑код</strong></div>
|
||||
<div class="panel-body">
|
||||
<img class="qr" src="/admin/clients/{{ client.id }}/qr" alt="QR config" />
|
||||
<p class="muted" style="margin-top:0.8rem">Отсканируйте в клиенте WireGuard / AmneziaWG.</p>
|
||||
{% if client.notes %}
|
||||
<p><strong>Заметка:</strong> {{ client.notes }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,87 @@
|
||||
{% extends "admin/layout.html" %}
|
||||
{% block admin_title %}Клиенты{% endblock %}
|
||||
{% block admin_content %}
|
||||
<div class="admin-top">
|
||||
<h1>Клиенты</h1>
|
||||
<div class="actions">
|
||||
<a class="btn btn-sm btn-ghost" href="/admin/clients">Все</a>
|
||||
<a class="btn btn-sm btn-ghost" href="/admin/clients?protocol=wireguard">WireGuard</a>
|
||||
<a class="btn btn-sm btn-ghost" href="/admin/clients?protocol=awg2">AWG 2.0</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if flash %}
|
||||
{% if flash.startswith('error:') %}
|
||||
<div class="flash flash-error">{{ flash[6:] }}</div>
|
||||
{% elif flash == 'created' %}
|
||||
<div class="flash">Клиент создан</div>
|
||||
{% elif flash == 'deleted' %}
|
||||
<div class="flash">Клиент удалён</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<div class="panel" style="margin-bottom:1rem">
|
||||
<div class="panel-head"><strong>Новый доступ</strong></div>
|
||||
<div class="panel-body">
|
||||
<form method="post" action="/admin/clients" class="form-grid">
|
||||
<label>Имя
|
||||
<input name="name" placeholder="phone / laptop" required />
|
||||
</label>
|
||||
<label>Сервер / протокол
|
||||
<select name="server_id" required>
|
||||
{% for s in servers %}
|
||||
<option value="{{ s.id }}">{{ s.name }} ({{ s.protocol }})</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
<button class="btn btn-accent" type="submit">Создать</button>
|
||||
<label style="grid-column: 1 / -1">Заметка
|
||||
<input name="notes" placeholder="опционально" />
|
||||
</label>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Имя</th>
|
||||
<th>Протокол</th>
|
||||
<th>IP</th>
|
||||
<th>Статус</th>
|
||||
<th>Создан</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for c in clients %}
|
||||
<tr>
|
||||
<td><a href="/admin/clients/{{ c.id }}">{{ c.name }}</a></td>
|
||||
<td><span class="badge badge-proto">{{ c.server.protocol }}</span></td>
|
||||
<td>{{ c.address }}</td>
|
||||
<td>
|
||||
{% if c.is_enabled %}
|
||||
<span class="badge badge-ok">on</span>
|
||||
{% else %}
|
||||
<span class="badge badge-off">off</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="muted">{{ c.created_at.strftime('%Y-%m-%d %H:%M') if c.created_at else '—' }}</td>
|
||||
<td class="actions">
|
||||
<a class="btn btn-sm btn-ghost" href="/admin/clients/{{ c.id }}">Открыть</a>
|
||||
<form method="post" action="/admin/clients/{{ c.id }}/toggle" class="inline-form">
|
||||
<button class="btn btn-sm btn-ghost" type="submit">{% if c.is_enabled %}Выкл{% else %}Вкл{% endif %}</button>
|
||||
</form>
|
||||
<form method="post" action="/admin/clients/{{ c.id }}/delete" class="inline-form" onsubmit="return confirm('Удалить клиента?')">
|
||||
<button class="btn btn-sm btn-danger" type="submit">Удалить</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="6" class="muted">Клиентов пока нет</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,59 @@
|
||||
{% extends "admin/layout.html" %}
|
||||
{% block admin_title %}Дашборд{% endblock %}
|
||||
{% block admin_content %}
|
||||
<div class="admin-top">
|
||||
<h1>Дашборд</h1>
|
||||
</div>
|
||||
|
||||
<div class="stats">
|
||||
<div class="stat">
|
||||
<div class="label">Серверы</div>
|
||||
<div class="value">{{ servers|length }}</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="label">Клиенты</div>
|
||||
<div class="value">{{ clients_total }}</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="label">Активные</div>
|
||||
<div class="value">{{ clients_active }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<div class="panel-head">
|
||||
<strong>Протоколы</strong>
|
||||
<a class="btn btn-sm btn-accent" href="/admin/clients">Управлять клиентами</a>
|
||||
</div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Имя</th>
|
||||
<th>Протокол</th>
|
||||
<th>Endpoint</th>
|
||||
<th>Подсеть</th>
|
||||
<th>Статус</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for s in servers %}
|
||||
<tr>
|
||||
<td>{{ s.name }}</td>
|
||||
<td><span class="badge badge-proto">{{ s.protocol }}</span></td>
|
||||
<td class="mono" style="background:transparent;color:inherit;padding:0">{{ s.public_host }}:{{ s.public_port }}</td>
|
||||
<td>{{ s.subnet }}</td>
|
||||
<td>
|
||||
{% if s.is_enabled %}
|
||||
<span class="badge badge-ok">enabled</span>
|
||||
{% else %}
|
||||
<span class="badge badge-off">disabled</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="5" class="muted">Серверы ещё не созданы</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,27 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{% block admin_title %}Админка{% endblock %} — {{ app_name }}{% endblock %}
|
||||
{% block body %}
|
||||
<div class="admin-shell">
|
||||
<aside class="sidebar">
|
||||
<a class="brand" href="/admin">
|
||||
<span class="brand-mark">V</span>
|
||||
<span>{{ app_name }}</span>
|
||||
</a>
|
||||
<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/clients" class="{% if '/clients' in request.url.path %}active{% endif %}">Клиенты</a>
|
||||
<a href="/" target="_blank">Сайт</a>
|
||||
</nav>
|
||||
<div class="meta">
|
||||
<div>{{ admin.username }}</div>
|
||||
<form class="inline-form" method="post" action="/admin/logout" style="margin-top:0.6rem">
|
||||
<button class="btn btn-ghost btn-sm" type="submit" style="color:#d7dee5;border-color:#2a343c">Выйти</button>
|
||||
</form>
|
||||
</div>
|
||||
</aside>
|
||||
<main class="admin-main">
|
||||
{% block admin_content %}{% endblock %}
|
||||
</main>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,24 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Вход — {{ app_name }}{% endblock %}
|
||||
{% block body %}
|
||||
<div class="login-wrap">
|
||||
<div class="login-card">
|
||||
<div class="brand">
|
||||
<span class="brand-mark">V</span>
|
||||
<span>{{ app_name }}</span>
|
||||
</div>
|
||||
<h1>Вход в админку</h1>
|
||||
<p>Логин и пароль задаются в переменных окружения.</p>
|
||||
{% if error %}<div class="error">{{ error }}</div>{% endif %}
|
||||
<form method="post" action="/admin/login" class="stack">
|
||||
<label>Логин
|
||||
<input name="username" autocomplete="username" required />
|
||||
</label>
|
||||
<label>Пароль
|
||||
<input type="password" name="password" autocomplete="current-password" required />
|
||||
</label>
|
||||
<button class="btn btn-primary" type="submit">Войти</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,39 @@
|
||||
{% extends "admin/layout.html" %}
|
||||
{% block admin_title %}Серверы{% endblock %}
|
||||
{% block admin_content %}
|
||||
<div class="admin-top">
|
||||
<h1>Серверы</h1>
|
||||
</div>
|
||||
|
||||
{% for s in servers %}
|
||||
<div class="panel" style="margin-bottom:1rem">
|
||||
<div class="panel-head">
|
||||
<strong>{{ s.name }} <span class="badge badge-proto">{{ s.protocol }}</span></strong>
|
||||
<form method="post" action="/admin/servers/{{ s.id }}/sync" class="inline-form">
|
||||
<button class="btn btn-sm btn-ghost" type="submit">Синхронизировать conf</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<p class="muted" style="margin-top:0">Интерфейс <code>{{ s.interface_name }}</code> · подсеть <code>{{ s.subnet }}</code> · клиентов: {{ s.clients|length }}</p>
|
||||
<p><code>Public key:</code> <span class="muted">{{ s.server_public_key }}</span></p>
|
||||
<form method="post" action="/admin/servers/{{ s.id }}" class="form-grid">
|
||||
<label>Public host
|
||||
<input name="public_host" value="{{ s.public_host }}" required />
|
||||
</label>
|
||||
<label>Port
|
||||
<input type="number" name="public_port" value="{{ s.public_port }}" required />
|
||||
</label>
|
||||
<button class="btn btn-accent" type="submit">Сохранить</button>
|
||||
<label style="grid-column: 1 / -1">DNS
|
||||
<input name="dns" value="{{ s.dns }}" />
|
||||
</label>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<p class="muted">
|
||||
Конфиги пишутся в volume: <code>/data/wireguard</code> и <code>/data/amneziawg</code>.
|
||||
Подключите их к WG/AWG контейнерам на том же хосте.
|
||||
</p>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user