Files
cloud-ip-validator/internal/dashboard/templates/layout.html
T

123 lines
7.8 KiB
HTML
Raw Normal View History

2026-08-23 20:39:22 +03:00
{{/* html/template requires {{template "name"}} names to be string
literals, not dynamic pipelines — so a single "layout" wrapper that
dispatches to a per-page content block by name isn't possible within
one shared template set. Instead, layout.html provides small named
2026-08-23 21:58:19 +03:00
partials (html_head, sidebar_nav, topbar_mobile, banner_inner,
error_banner) that each page's *_page template (see e.g.
overview.html) assembles around its own *_content block explicitly.
A few lines of boilerplate are duplicated per page as a result — see
any *_page define for the pattern. */}}
2026-08-23 20:39:22 +03:00
{{define "html_head"}}
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Cloud IP Validator — Admin</title>
2026-08-23 21:58:19 +03:00
<link rel="stylesheet" href="/static/vendor/fonts.css">
2026-08-23 20:39:22 +03:00
<link rel="stylesheet" href="/static/dashboard.css">
2026-08-26 21:18:58 +03:00
<script>
// Apply a saved theme choice before first paint, so a page load never
// flashes the OS-default theme before switching to the stored one. Runs
// synchronously, after the stylesheet link above (browsers block a
// same-document script on a preceding <link rel="stylesheet"> until it
// loads), and before body markup starts rendering.
(function () {
try {
var saved = localStorage.getItem('theme');
if (saved === 'light' || saved === 'dark') {
document.documentElement.setAttribute('data-theme', saved);
}
} catch (e) {}
})();
</script>
2026-08-23 20:39:22 +03:00
<script src="/static/vendor/htmx.min.js"></script>
<script src="/static/vendor/alpine.min.js" defer></script>
{{end}}
2026-08-23 21:58:19 +03:00
{{define "sidebar_nav"}}
<aside class="sidebar">
<div class="brand">
2026-08-26 21:08:18 +03:00
<span class="brand-chrome"><i></i><i></i><i></i></span>
2026-08-23 21:58:19 +03:00
<span class="brand-mark">
2026-08-26 21:08:18 +03:00
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2v6M12 16v6M2 12h6M16 12h6"/><circle cx="12" cy="12" r="3"/></svg>
2026-08-23 21:58:19 +03:00
</span>
<span class="brand-name">Cloud IP Validator<small>admin</small></span>
2026-08-23 21:15:31 +03:00
</div>
2026-08-23 21:58:19 +03:00
<nav class="nav-groups">
<a class="nav-link{{if eq .ActiveNav "overview"}} active{{end}}" {{if eq .ActiveNav "overview"}}aria-current="page"{{end}} href="/overview">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="9" rx="1.5"/><rect x="14" y="3" width="7" height="5" rx="1.5"/><rect x="14" y="12" width="7" height="9" rx="1.5"/><rect x="3" y="16" width="7" height="5" rx="1.5"/></svg>
Обзор
</a>
<a class="nav-link{{if eq .ActiveNav "ips"}} active{{end}}" {{if eq .ActiveNav "ips"}}aria-current="page"{{end}} href="/ips">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M3 10h18M8 4v16"/></svg>
Очередь IP
</a>
<a class="nav-link{{if eq .ActiveNav "registry"}} active{{end}}" {{if eq .ActiveNav "registry"}}aria-current="page"{{end}} href="/registry">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2Z"/></svg>
Реестр
</a>
2026-08-23 21:58:19 +03:00
<a class="nav-link{{if eq .ActiveNav "validators"}} active{{end}}" {{if eq .ActiveNav "validators"}}aria-current="page"{{end}} href="/validators">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="3" width="16" height="7" rx="1.5"/><rect x="4" y="14" width="16" height="7" rx="1.5"/><circle cx="8" cy="6.5" r="1"/><circle cx="8" cy="17.5" r="1"/></svg>
Валидаторы
</a>
<a class="nav-link{{if eq .ActiveNav "sites"}} active{{end}}" {{if eq .ActiveNav "sites"}}aria-current="page"{{end}} href="/sites">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2 3 7l9 5 9-5-9-5Z"/><path d="m3 12 9 5 9-5M3 17l9 5 9-5"/></svg>
Площадки
</a>
<a class="nav-link{{if eq .ActiveNav "targets"}} active{{end}}" {{if eq .ActiveNav "targets"}}aria-current="page"{{end}} href="/targets">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="4.5"/><circle cx="12" cy="12" r=".6" fill="currentColor"/></svg>
Цели
</a>
<a class="nav-link{{if eq .ActiveNav "check-types"}} active{{end}}" {{if eq .ActiveNav "check-types"}}aria-current="page"{{end}} href="/check-types">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg>
Типы проверок
</a>
2026-08-24 10:29:08 +03:00
<a class="nav-link{{if eq .ActiveNav "settings"}} active{{end}}" {{if eq .ActiveNav "settings"}}aria-current="page"{{end}} href="/settings">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1Z"/></svg>
Настройки
</a>
2026-08-23 20:39:22 +03:00
</nav>
2026-08-23 21:58:19 +03:00
{{if .AuthEnabled}}
<form class="sidebar-user" method="post" action="/logout">
<span class="sidebar-user-name" title="{{.User}}">{{.User}}</span>
<button type="submit" class="btn btn-ghost btn-sm">Выйти</button>
</form>
{{end}}
2026-08-23 21:58:19 +03:00
<div class="sidebar-foot">
2026-08-26 21:18:58 +03:00
<span class="sidebar-foot-status"><span class="pulse-dot"></span>control-api</span>
<button type="button" class="theme-toggle" id="themeToggle" aria-label="Переключить тему" title="Переключить тему">
<svg class="icon-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg>
<svg class="icon-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.8A9 9 0 1 1 11.2 3 7 7 0 0 0 21 12.8Z"/></svg>
</button>
2026-08-23 21:58:19 +03:00
</div>
</aside>
2026-08-26 21:18:58 +03:00
<script>
(function () {
var btn = document.getElementById('themeToggle');
if (!btn) return;
function effectiveTheme() {
return getComputedStyle(document.documentElement).colorScheme.indexOf('dark') !== -1 ? 'dark' : 'light';
}
btn.addEventListener('click', function () {
var next = effectiveTheme() === 'dark' ? 'light' : 'dark';
document.documentElement.setAttribute('data-theme', next);
try { localStorage.setItem('theme', next); } catch (e) {}
});
})();
</script>
2026-08-23 20:39:22 +03:00
{{end}}
2026-08-23 21:58:19 +03:00
{{define "topbar_mobile"}}
<div class="topbar-mobile">
<label class="burger" for="nav-toggle">
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M4 7h16M4 12h16M4 17h16"/></svg>
</label>
<strong>Cloud IP Validator</strong>
</div>
{{end}}
{{define "banner_inner"}}{{if .Message}}<div class="alert {{if .Client}}alert-warning{{else}}alert-danger{{end}}" role="alert">{{.Message}}</div>{{end}}{{end}}
2026-08-23 20:39:22 +03:00
{{define "error_banner"}}<div id="error-banner" hx-swap-oob="true">{{template "banner_inner" .}}</div>{{end}}