Files
panel-vpn/web/templates/client_view.html
T

133 lines
5.9 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{.Title}} · VPN Panel</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/static/css/app.css">
</head>
<body class="page-admin">
<aside class="sidebar">
<a class="brand" href="/admin">VPN Panel</a>
<nav>
<a href="/admin">Обзор</a>
<a class="active" href="/admin/clients">Пользователи</a>
<a href="/admin/nodes">Ноды</a>
<a href="/admin/profiles">Профили</a>
<a href="/admin/protocols">Протоколы</a>
</nav>
<div class="sidebar-foot">
<span class="user-chip">{{.UserName}}</span>
<form method="POST" action="/logout"><button type="submit" class="link-btn">Выйти</button></form>
</div>
</aside>
<main class="admin-main">
<header class="admin-header row-head">
<div>
<h1>{{.Client.Username}}</h1>
<p class="muted"><code>{{.Client.UUID}}</code></p>
</div>
<span class="badge {{statusClass (printf "%s" .Client.Status)}}">{{.Client.StatusLabel}}</span>
</header>
{{if .Flash}}<div class="alert ok">{{.Flash}}</div>{{end}}
{{if .Error}}<div class="alert">{{.Error}}</div>{{end}}
<div class="cta-row" style="margin-bottom:1.25rem">
<form method="POST" action="/admin/clients/{{.Client.ID}}/toggle">
<button class="btn btn-ghost" type="submit">{{if eq (printf "%s" .Client.Status) "active"}}Отключить{{else}}Включить{{end}}</button>
</form>
<form method="POST" action="/admin/clients/{{.Client.ID}}/delete" onsubmit="return confirm('Удалить пользователя?');">
<button class="btn btn-ghost" type="submit">Удалить</button>
</form>
<a class="btn btn-ghost" href="/admin/clients">К списку</a>
</div>
<section class="panel-section">
<h2>Subscription</h2>
<p class="muted">Ссылка подписки (в браузере — страница с инбаундами, в клиенте — base64):</p>
<pre class="code-block">{{.SubURL}}</pre>
<div class="cta-row" style="margin:0.75rem 0 1rem">
<a class="btn btn-sm btn-primary" href="{{.SubURL}}" target="_blank" rel="noopener">Открыть страницу</a>
<a class="btn btn-sm btn-ghost" href="{{.SubURL}}?format=plain" target="_blank" rel="noopener">Plain</a>
<a class="btn btn-sm btn-ghost" href="{{.SubURL}}/info" target="_blank" rel="noopener">JSON</a>
</div>
<h3 style="margin:0 0 0.75rem;font-size:1.05rem">Инбаунды в подписке</h3>
{{if .Hosts}}
<div class="proto-grid">
{{range .Hosts}}
<article class="proto-card {{if .Available}}is-on{{else}}is-off{{end}}">
<div class="proto-top">
<h3>{{.Title}}</h3>
<span class="badge {{if .Available}}on{{else}}off{{end}}">{{if .Available}}ON{{else}}OFF{{end}}</span>
</div>
<p><code>{{.Tag}}</code> · {{.ProtocolName}} · {{.Network}}/{{.Security}} :{{.Port}}</p>
<div class="proto-meta">
{{if .Address}}<span>{{.NodeName}}</span><code>{{.Address}}</code>{{else}}<span class="muted">offline / нет ноды</span>{{end}}
</div>
</article>
{{end}}
</div>
{{else}}
<p class="hint">Нет инбаундов — отметьте их ниже в форме.</p>
{{end}}
{{if .Links}}
<h3 style="margin-top:1.25rem;font-size:1rem">Ссылки</h3>
<pre class="code-block log">{{range .Links}}{{.}}
{{end}}</pre>
{{end}}
</section>
<section class="panel-section">
<h2>Редактирование</h2>
<form class="form-card" method="POST" action="/admin/clients/{{.Client.ID}}">
<div class="form-grid">
<label>Username
<input name="username" required value="{{.Client.Username}}">
</label>
<label>Email
<input name="email" type="email" value="{{.Client.Email}}">
</label>
<label>Статус
<select name="status">
<option value="active" {{if eq (printf "%s" .Client.Status) "active"}}selected{{end}}>Активен</option>
<option value="disabled" {{if eq (printf "%s" .Client.Status) "disabled"}}selected{{end}}>Отключён</option>
<option value="expired" {{if eq (printf "%s" .Client.Status) "expired"}}selected{{end}}>Истёк</option>
</select>
</label>
<label>Лимит трафика (GB, 0 = ∞)
<input name="traffic_limit_gb" type="number" step="0.1" min="0" value="{{printf "%.2f" .Client.TrafficLimitGB}}">
</label>
<label>Истекает
<input name="expire_at" type="datetime-local" value="{{formatTime .Client.ExpireAt}}">
</label>
<label>Заметка
<input name="note" value="{{.Client.Note}}">
</label>
</div>
<fieldset class="mode-pick">
<legend>Инбаунды</legend>
{{range .Inbounds}}
<label class="radio">
<input type="checkbox" name="inbound_ids" value="{{.ID}}" {{if $.Client.HasInbound .ID}}checked{{end}}>
<span><code>{{.Tag}}</code> · {{.ProtocolName}} :{{.Port}}</span>
</label>
{{else}}
<p class="muted">Нет доступных inbound — создайте в профиле.</p>
{{end}}
</fieldset>
<button class="btn btn-primary" type="submit">Сохранить</button>
</form>
</section>
</main>
</body>
</html>