Add Go WireGuard panel with PostgreSQL 17 and Dokploy compose.
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
:root {
|
||||
--bg-deep: #050508;
|
||||
--bg-card: rgba(12, 14, 22, 0.9);
|
||||
--border: rgba(0, 212, 255, 0.15);
|
||||
--border-bright: rgba(0, 212, 255, 0.4);
|
||||
--accent: #00d4ff;
|
||||
--accent-dim: #00a8cc;
|
||||
--accent-glow: rgba(0, 212, 255, 0.25);
|
||||
--text: #e8ecf4;
|
||||
--text-muted: #8b95a8;
|
||||
--error-bg: rgba(255, 82, 82, 0.12);
|
||||
--error-border: rgba(255, 82, 82, 0.35);
|
||||
--error-text: #ff9e9e;
|
||||
--success-bg: rgba(0, 230, 118, 0.12);
|
||||
--success-text: #00e676;
|
||||
--font-mono: 'JetBrains Mono', monospace;
|
||||
--font-sans: 'Outfit', system-ui, sans-serif;
|
||||
}
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: var(--font-sans);
|
||||
background: var(--bg-deep);
|
||||
color: var(--text);
|
||||
min-height: 100vh;
|
||||
}
|
||||
.bg-pattern {
|
||||
position: fixed; inset: 0; z-index: 0; pointer-events: none;
|
||||
background:
|
||||
linear-gradient(180deg, transparent 0%, rgba(0, 212, 255, 0.02) 50%, transparent 100%),
|
||||
repeating-linear-gradient(0deg, transparent, transparent 2px, rgba(0, 212, 255, 0.03) 2px, rgba(0, 212, 255, 0.03) 4px),
|
||||
repeating-linear-gradient(90deg, transparent, transparent 2px, rgba(0, 212, 255, 0.03) 2px, rgba(0, 212, 255, 0.03) 4px);
|
||||
}
|
||||
.bg-glow {
|
||||
position: fixed; width: 120%; height: 55vh; top: -10%; left: -10%; z-index: 0; pointer-events: none;
|
||||
background: radial-gradient(ellipse at 50% 0%, var(--accent-glow) 0%, transparent 55%);
|
||||
}
|
||||
.wrap { position: relative; z-index: 1; max-width: 1000px; margin: 0 auto; padding: 1.5rem; }
|
||||
.card {
|
||||
background: var(--bg-card); border: 1px solid var(--border); border-radius: 14px; padding: 1.5rem; margin-bottom: 1.25rem;
|
||||
}
|
||||
h1, .title { font-family: var(--font-mono); color: var(--accent); }
|
||||
.muted { color: var(--text-muted); }
|
||||
.btn {
|
||||
display: inline-block; padding: 0.75rem 1.2rem; font-family: var(--font-mono); font-size: 0.9rem;
|
||||
color: var(--bg-deep); background: var(--accent); border: 1px solid var(--accent); border-radius: 10px;
|
||||
text-decoration: none; cursor: pointer;
|
||||
}
|
||||
.btn:hover { background: var(--accent-dim); box-shadow: 0 0 20px var(--accent-glow); }
|
||||
.btn-ghost {
|
||||
background: transparent; color: var(--accent);
|
||||
}
|
||||
.btn-ghost:hover { background: rgba(0, 212, 255, 0.1); }
|
||||
.btn-danger { background: transparent; color: var(--error-text); border-color: var(--error-border); }
|
||||
input[type=text], input[type=password], input[type=email], textarea, input[type=file] {
|
||||
width: 100%; padding: 0.85rem 1rem; color: var(--text); background: rgba(255,255,255,0.04);
|
||||
border: 1px solid var(--border); border-radius: 10px; outline: none; font-family: var(--font-sans);
|
||||
}
|
||||
input:focus, textarea:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-glow); }
|
||||
label { display: block; font-family: var(--font-mono); font-size: 0.8rem; color: var(--text-muted); margin-bottom: 0.45rem; text-transform: uppercase; letter-spacing: 0.04em; }
|
||||
.form-group { margin-bottom: 1.1rem; }
|
||||
.alert { padding: 0.9rem 1rem; border-radius: 10px; margin-bottom: 1rem; }
|
||||
.alert.error { background: var(--error-bg); border: 1px solid var(--error-border); color: var(--error-text); }
|
||||
.alert.success { background: var(--success-bg); border: 1px solid rgba(0,230,118,0.3); color: var(--success-text); }
|
||||
.nav { display: flex; gap: 0.75rem; flex-wrap: wrap; align-items: center; margin-bottom: 1.5rem; }
|
||||
.nav a { color: var(--accent); text-decoration: none; font-family: var(--font-mono); font-size: 0.9rem; }
|
||||
.table { width: 100%; border-collapse: collapse; }
|
||||
.table th, .table td { text-align: left; padding: 0.75rem 0.5rem; border-bottom: 1px solid var(--border); font-size: 0.92rem; vertical-align: top; }
|
||||
.table th { color: var(--text-muted); font-family: var(--font-mono); font-weight: 500; }
|
||||
code { font-family: var(--font-mono); color: var(--accent); }
|
||||
.pager { display: flex; gap: 0.75rem; margin-top: 1rem; align-items: center; }
|
||||
.login-box {
|
||||
position: relative; z-index: 1; width: 100%; max-width: 420px; margin: 8vh auto;
|
||||
background: var(--bg-card); border: 1px solid var(--border); border-radius: 16px; padding: 2.25rem;
|
||||
}
|
||||
.center { text-align: center; }
|
||||
.qr img { border-radius: 12px; border: 1px solid var(--border); }
|
||||
pre.config {
|
||||
font-family: var(--font-mono); font-size: 0.8rem; line-height: 1.5; white-space: pre-wrap; word-break: break-all;
|
||||
background: rgba(0,0,0,0.35); border: 1px solid var(--border); border-radius: 10px; padding: 1rem; max-height: 320px; overflow: auto;
|
||||
}
|
||||
.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 1rem; }
|
||||
.stat { padding: 1.2rem; border: 1px solid var(--border); border-radius: 12px; background: rgba(0,0,0,0.25); }
|
||||
.stat .n { font-family: var(--font-mono); font-size: 1.8rem; color: var(--accent); }
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*, *::before, *::after { transition-duration: 0.01ms !important; }
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
{{define "admin_dashboard.html"}}
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
{{template "layout_head" .}}
|
||||
<title>Админка — {{siteName}}</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="bg-pattern"></div><div class="bg-glow"></div>
|
||||
<div class="wrap">
|
||||
<div class="nav">
|
||||
<strong class="title">Админ · {{siteName}}</strong>
|
||||
<a href="/admin/wg">WireGuard</a>
|
||||
<span style="flex:1"></span>
|
||||
<span class="muted">{{.User.Username}}</span>
|
||||
<a href="/logout">Выход</a>
|
||||
</div>
|
||||
<div class="stats">
|
||||
<div class="stat"><div class="muted">WG конфиги</div><div class="n">{{.TotalConfigs}}</div></div>
|
||||
<div class="stat"><div class="muted">Пользователи</div><div class="n">{{.TotalUsers}}</div></div>
|
||||
<div class="stat"><div class="muted">Сайт</div><div class="n">{{if .SiteOnline}}ON{{else}}OFF{{end}}</div></div>
|
||||
</div>
|
||||
<div class="card" style="margin-top:1.25rem">
|
||||
<h1 style="font-size:1.1rem;margin-bottom:1rem">Статус сайта</h1>
|
||||
<form method="POST" action="/admin/status">
|
||||
<div class="form-group">
|
||||
<label>Режим</label>
|
||||
<select name="is_online" style="width:100%;padding:0.85rem 1rem;border-radius:10px;background:rgba(255,255,255,0.04);color:var(--text);border:1px solid var(--border)">
|
||||
<option value="1" {{if .SiteOnline}}selected{{end}}>Онлайн</option>
|
||||
<option value="0" {{if not .SiteOnline}}selected{{end}}>Оффлайн</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Сообщение оффлайна</label>
|
||||
<textarea name="message" rows="3">{{.SiteMessage}}</textarea>
|
||||
</div>
|
||||
<button class="btn" type="submit">Сохранить</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
||||
@@ -0,0 +1,83 @@
|
||||
{{define "admin_wg.html"}}
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
{{template "layout_head" .}}
|
||||
<title>WireGuard — {{siteName}}</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="bg-pattern"></div><div class="bg-glow"></div>
|
||||
<div class="wrap">
|
||||
<div class="nav">
|
||||
<a href="/admin">← Админка</a>
|
||||
<strong class="title">WireGuard .conf</strong>
|
||||
<span style="flex:1"></span>
|
||||
<a href="/logout">Выход</a>
|
||||
</div>
|
||||
|
||||
{{if .Message}}
|
||||
<div class="alert {{.MessageType}}">{{.Message}}{{if .NewToken}} · <code>{{.PublicURL}}{{.NewToken}}</code>{{end}}</div>
|
||||
{{end}}
|
||||
|
||||
<div class="card">
|
||||
<h1 style="font-size:1.1rem;margin-bottom:1rem">Загрузить .conf</h1>
|
||||
<form method="POST" action="/admin/wg/upload" enctype="multipart/form-data">
|
||||
<div class="form-group">
|
||||
<label for="wg_conf">Файл WireGuard (.conf)</label>
|
||||
<input id="wg_conf" type="file" name="wg_conf" accept=".conf" required>
|
||||
</div>
|
||||
<button class="btn" type="submit" name="submit_form" value="1">Сохранить</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h1 style="font-size:1.1rem;margin-bottom:1rem">Настройки скачивания</h1>
|
||||
<form method="POST" action="/admin/wg/settings">
|
||||
<div class="form-group" style="display:flex;gap:0.6rem;align-items:center">
|
||||
<input type="checkbox" id="show_zip_full" name="show_zip_full" value="1" {{if .ShowZipFull}}checked{{end}}>
|
||||
<label for="show_zip_full" style="margin:0;text-transform:none;font-family:var(--font-sans)">ZIP (conf + QR)</label>
|
||||
</div>
|
||||
<div class="form-group" style="display:flex;gap:0.6rem;align-items:center">
|
||||
<input type="checkbox" id="show_zip_conf" name="show_zip_conf" value="1" {{if .ShowZipConf}}checked{{end}}>
|
||||
<label for="show_zip_conf" style="margin:0;text-transform:none;font-family:var(--font-sans)">ZIP только conf</label>
|
||||
</div>
|
||||
<button class="btn btn-ghost" type="submit">Обновить настройки</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h1 style="font-size:1.1rem;margin-bottom:1rem">Конфиги</h1>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr><th>ID</th><th>Токен</th><th>Файл</th><th>Дата</th><th>Ссылка</th><th></th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range .Configs}}
|
||||
<tr>
|
||||
<td>{{.ID}}</td>
|
||||
<td><code>{{.Token}}</code></td>
|
||||
<td>{{.OriginalFilename}}</td>
|
||||
<td class="muted">{{.CreatedAt.Format "2006-01-02 15:04"}}</td>
|
||||
<td><a style="color:var(--accent)" href="/wg?token={{.Token}}" target="_blank">открыть</a></td>
|
||||
<td>
|
||||
<form method="POST" action="/admin/wg/delete" onsubmit="return confirm('Удалить?')">
|
||||
<input type="hidden" name="id" value="{{.ID}}">
|
||||
<button class="btn btn-danger" type="submit">Удалить</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{{else}}
|
||||
<tr><td colspan="6" class="muted">Пока нет конфигов</td></tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="pager">
|
||||
{{if gt .Page 1}}<a class="btn btn-ghost" href="/admin/wg?page={{sub .Page 1}}">←</a>{{end}}
|
||||
<span class="muted">стр. {{.Page}} / {{.TotalPages}}</span>
|
||||
{{if lt .Page .TotalPages}}<a class="btn btn-ghost" href="/admin/wg?page={{add .Page 1}}">→</a>{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
||||
@@ -0,0 +1,29 @@
|
||||
{{define "home.html"}}
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
{{template "layout_head" .}}
|
||||
<title>{{siteName}}</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="bg-pattern"></div><div class="bg-glow"></div>
|
||||
<div class="wrap">
|
||||
<div class="nav">
|
||||
<strong class="title">{{siteName}}</strong>
|
||||
<span style="flex:1"></span>
|
||||
{{if .User}}
|
||||
<a href="/admin">Админка</a>
|
||||
<a href="/logout">Выход</a>
|
||||
{{else}}
|
||||
<a href="/login">Вход</a>
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="card">
|
||||
<h1>{{siteName}}</h1>
|
||||
<p class="muted" style="margin:1rem 0 1.5rem">WireGuard панель на Go + PostgreSQL 17</p>
|
||||
<a class="btn" href="/login">Войти в админку</a>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
||||
@@ -0,0 +1,33 @@
|
||||
{{define "login.html"}}
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
{{template "layout_head" .}}
|
||||
<title>Вход — {{siteName}}</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="bg-pattern"></div><div class="bg-glow"></div>
|
||||
<div class="login-box">
|
||||
<h1 class="center">Вход в аккаунт</h1>
|
||||
<p class="muted center" style="margin:0.4rem 0 1.5rem">{{siteName}}</p>
|
||||
{{if .Error}}<div class="alert error">{{.Error}}</div>{{end}}
|
||||
<form method="POST" action="/login">
|
||||
<div class="form-group">
|
||||
<label for="username">Имя пользователя или email</label>
|
||||
<input id="username" name="username" type="text" required autofocus value="{{.Username}}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">Пароль</label>
|
||||
<input id="password" name="password" type="password" required>
|
||||
</div>
|
||||
<div class="form-group" style="display:flex;gap:0.6rem;align-items:center">
|
||||
<input type="checkbox" id="remember_me" name="remember_me" value="1">
|
||||
<label for="remember_me" style="margin:0;text-transform:none;letter-spacing:normal;font-family:var(--font-sans)">Запомнить меня</label>
|
||||
</div>
|
||||
<button class="btn" style="width:100%" type="submit">Войти</button>
|
||||
</form>
|
||||
<p class="center muted" style="margin-top:1.2rem"><a href="/" style="color:var(--accent)">На главную</a></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
||||
@@ -0,0 +1,18 @@
|
||||
{{define "offline.html"}}
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
{{template "layout_head" .}}
|
||||
<title>Недоступен — {{.SiteName}}</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="bg-pattern"></div><div class="bg-glow"></div>
|
||||
<div class="wrap" style="max-width:560px;margin-top:10vh">
|
||||
<div class="card center">
|
||||
<h1>Сайт временно недоступен</h1>
|
||||
<p class="muted" style="margin-top:1rem">{{.Message}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
||||
@@ -0,0 +1,10 @@
|
||||
{{define "layout_head"}}
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="theme-color" content="#050508">
|
||||
<meta name="color-scheme" content="dark">
|
||||
<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=JetBrains+Mono:wght@400;500;600;700&family=Outfit:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="/static/app.css">
|
||||
{{end}}
|
||||
@@ -0,0 +1,41 @@
|
||||
{{define "wg_public.html"}}
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
{{template "layout_head" .}}
|
||||
<title>{{.FileName}}</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="bg-pattern"></div><div class="bg-glow"></div>
|
||||
<div class="wrap" style="max-width:560px">
|
||||
<div class="nav">
|
||||
<h1 style="font-size:1.2rem">{{.FileName}}</h1>
|
||||
</div>
|
||||
<div class="alert success">Конфиг готов.</div>
|
||||
{{if .QR}}
|
||||
<div class="card center qr">
|
||||
<h1 style="font-size:1rem;margin-bottom:1rem">Отсканируйте QR-код</h1>
|
||||
<img src="{{.QR}}" width="300" height="300" alt="QR">
|
||||
</div>
|
||||
{{end}}
|
||||
<div class="center" style="margin-bottom:1.25rem">
|
||||
{{if .ShowZipFull}}<a class="btn" href="/wg?token={{.Token}}&download=full">ZIP (conf + QR)</a>{{end}}
|
||||
{{if .ShowZipConf}}<a class="btn btn-ghost" href="/wg?token={{.Token}}&download=conf">ZIP только conf</a>{{end}}
|
||||
</div>
|
||||
<div class="card">
|
||||
<h1 style="font-size:1rem;margin-bottom:0.8rem">Текст конфигурации</h1>
|
||||
<pre class="config" id="cfg">{{.Config}}</pre>
|
||||
<button class="btn btn-ghost" style="margin-top:0.8rem" type="button" id="copyBtn">Копировать</button>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
document.getElementById('copyBtn')?.addEventListener('click', async function() {
|
||||
const t = document.getElementById('cfg')?.textContent || '';
|
||||
try { await navigator.clipboard.writeText(t); this.textContent = 'Скопировано'; }
|
||||
catch(e) { this.textContent = 'Ошибка'; }
|
||||
setTimeout(() => this.textContent = 'Копировать', 2000);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
||||
Reference in New Issue
Block a user