Initial commit: VPN panel on Go, PostgreSQL 17, Docker, Xray-core

This commit is contained in:
vpn-panel
2026-05-21 18:55:14 +03:00
commit 3c2f5226d1
27 changed files with 1778 additions and 0 deletions
+71
View File
@@ -0,0 +1,71 @@
{{define "content"}}
<section class="hero">
<div class="hero-glow"></div>
<p class="hero-label">Панель управления VPN</p>
<h1>Управляйте <span class="gradient-text">Xray</span> из одного места</h1>
<p class="hero-desc">
Централизованная панель на базе
<a href="https://github.com/XTLS/Xray-core" target="_blank" rel="noopener">Xray-core</a>:
пользователи, ноды, конфигурации и мониторинг.
</p>
<div class="hero-actions">
{{if .User}}
<span class="pill pill-ok">Вы вошли как {{.User.Email}}</span>
{{else if .CanRegister}}
<a href="/register" class="btn btn-primary">Создать администратора</a>
<a href="/login" class="btn btn-ghost">Войти</a>
{{else}}
<a href="/login" class="btn btn-primary">Войти в панель</a>
{{end}}
</div>
</section>
<section class="stats">
<article class="stat-card">
<span class="stat-value">{{if .HasAdmin}}1{{else}}0{{end}}</span>
<span class="stat-label">Администратор</span>
</article>
<article class="stat-card">
<span class="stat-value">{{.UserCount}}</span>
<span class="stat-label">Пользователей</span>
</article>
<article class="stat-card">
<span class="stat-value">{{.XrayVersion}}</span>
<span class="stat-label">Ядро</span>
</article>
<article class="stat-card">
<span class="stat-value">{{if .Installed}}✓{{else}}—{{end}}</span>
<span class="stat-label">Установка</span>
</article>
</section>
<section class="features">
<h2>Возможности</h2>
<div class="feature-grid">
<div class="feature-card">
<h3>VLESS / REALITY</h3>
<p>Поддержка современных протоколов Xray: VLESS, XTLS Vision, REALITY.</p>
</div>
<div class="feature-card">
<h3>PostgreSQL 17</h3>
<p>Надёжное хранение пользователей и настроек в PostgreSQL.</p>
</div>
<div class="feature-card">
<h3>Docker</h3>
<p>Развёртывание панели и БД через Docker Compose за минуты.</p>
</div>
<div class="feature-card">
<h3>Один админ</h3>
<p>При первом запуске регистрируется единственный администратор панели.</p>
</div>
</div>
</section>
{{if not .HasAdmin}}
<section class="cta-banner">
<h2>Первый запуск</h2>
<p>Администратор ещё не создан. Зарегистрируйте единственную учётную запись администратора.</p>
<a href="/register" class="btn btn-primary">Регистрация администратора</a>
</section>
{{end}}
{{end}}
+43
View File
@@ -0,0 +1,43 @@
{{define "layout"}}<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{.Title}} — VPN Panel</title>
<link rel="stylesheet" href="/static/css/style.css">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;600&family=Outfit:wght@300;400;600;700&display=swap" rel="stylesheet">
</head>
<body>
<div class="bg-grid"></div>
<header class="header">
<a href="/" class="logo">
<span class="logo-icon"></span>
<span>VPN Panel</span>
<span class="logo-badge">Xray</span>
</a>
<nav class="nav">
<a href="/">Главная</a>
{{if .User}}
<span class="nav-user">{{.User.Email}}</span>
<a href="/logout">Выход</a>
{{else}}
<a href="/login">Вход</a>
{{if .CanRegister}}<a href="/register" class="btn-nav">Регистрация</a>{{end}}
{{end}}
</nav>
</header>
{{if .Flash}}
<div class="flash flash-{{.Flash.Level}}">{{.Flash.Message}}</div>
{{end}}
<main class="main">
{{template "content" .}}
</main>
<footer class="footer">
<p>Ядро: <a href="https://github.com/XTLS/Xray-core" target="_blank" rel="noopener">Xray-core</a> · {{.Domain}} · © {{.Year}}</p>
</footer>
</body>
</html>{{end}}
+19
View File
@@ -0,0 +1,19 @@
{{define "content"}}
<section class="auth-page">
<div class="auth-card">
<h1>Вход в панель</h1>
<form method="post" action="/login" class="form">
<label>
<span>Email</span>
<input type="email" name="email" required autocomplete="email">
</label>
<label>
<span>Пароль</span>
<input type="password" name="password" required autocomplete="current-password">
</label>
<button type="submit" class="btn btn-primary btn-block">Войти</button>
</form>
<p class="auth-footer"><a href="/">← На главную</a></p>
</div>
</section>
{{end}}
+30
View File
@@ -0,0 +1,30 @@
{{define "content"}}
<section class="auth-page">
<div class="auth-card">
<h1>Регистрация администратора</h1>
<p class="auth-sub">Допускается только <strong>один</strong> администратор. После создания регистрация будет закрыта.</p>
{{if .CanRegister}}
<form method="post" action="/register" class="form">
<label>
<span>Email</span>
<input type="email" name="email" required autocomplete="email" placeholder="admin@example.com">
</label>
<label>
<span>Пароль (мин. 8 символов)</span>
<input type="password" name="password" required minlength="8" autocomplete="new-password">
</label>
<label>
<span>Подтверждение пароля</span>
<input type="password" name="password_confirm" required minlength="8" autocomplete="new-password">
</label>
<button type="submit" class="btn btn-primary btn-block">Создать администратора</button>
</form>
{{else}}
<p class="auth-warn">Администратор уже существует.</p>
<a href="/login" class="btn btn-ghost btn-block">Войти</a>
{{end}}
<p class="auth-footer"><a href="/">← На главную</a></p>
</div>
</section>
{{end}}