Files
core/internal/api/ui_shared.tmpl

480 lines
12 KiB
Cheetah
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.
{{define "head"}}
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Reanimator - {{.PageTitle}}</title>
<style>
:root {
color-scheme: light;
--bg: #0b0f14;
--bg-secondary: #121821;
--card: #ffffff;
--ink: #101624;
--muted: #5c6b7a;
--accent: #0f766e;
--accent-soft: #d2f4ef;
--warning: #f97316;
--border: #e5e7eb;
--shadow: 0 12px 30px rgba(15, 23, 42, 0.12);
}
* { box-sizing: border-box; }
body {
margin: 0;
font-family: "Space Grotesk", "IBM Plex Sans", "Segoe UI", sans-serif;
color: var(--ink);
background: radial-gradient(1000px 600px at 20% -10%, #0f766e44, transparent),
linear-gradient(180deg, #f8fafc 0%, #eef2f7 100%);
min-height: 100vh;
}
a { color: inherit; }
.topbar {
background: linear-gradient(90deg, #0f766e 0%, #0b4f4c 100%);
color: #ffffff;
padding: 24px 32px;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: var(--shadow);
}
.brand {
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
font-size: 12px;
opacity: 0.8;
}
.title {
font-size: 26px;
font-weight: 700;
margin: 6px 0 0 0;
}
.subtitle {
margin-top: 6px;
font-size: 13px;
color: rgba(255, 255, 255, 0.8);
}
.nav {
display: flex;
gap: 16px;
padding: 12px 32px;
background: #f1f5f9;
border-bottom: 1px solid var(--border);
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.08em;
font-weight: 600;
}
.nav-item {
position: relative;
}
.nav a, .nav-group-label {
text-decoration: none;
color: var(--ink);
opacity: 0.7;
cursor: pointer;
display: block;
}
.nav a.active, .nav-group-label.active {
opacity: 1;
color: var(--accent);
}
.nav-group {
position: relative;
}
.nav-group-label {
display: flex;
align-items: center;
gap: 4px;
}
.nav-group-label::after {
content: '▾';
font-size: 10px;
opacity: 0.5;
}
.nav-submenu {
display: none;
position: absolute;
top: 100%;
left: 0;
background: white;
border: 1px solid var(--border);
border-radius: 8px;
box-shadow: var(--shadow);
margin-top: 4px;
min-width: 160px;
z-index: 100;
padding: 8px 0;
}
.nav-submenu::before {
content: '';
position: absolute;
top: -4px;
left: 0;
right: 0;
height: 4px;
background: transparent;
}
.nav-group:hover .nav-submenu,
.nav-submenu:hover {
display: block;
}
.nav-submenu a {
padding: 8px 16px;
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.06em;
opacity: 0.7;
transition: all 0.2s;
}
.nav-submenu a:hover {
opacity: 1;
background: var(--accent-soft);
}
.nav-submenu a.active {
opacity: 1;
color: var(--accent);
background: var(--accent-soft);
}
.container {
max-width: 1100px;
margin: 28px auto;
padding: 0 24px 48px;
display: grid;
gap: 24px;
}
.card {
background: var(--card);
border-radius: 16px;
padding: 20px 24px;
box-shadow: var(--shadow);
border: 1px solid var(--border);
}
.card h2 {
margin: 0 0 16px 0;
font-size: 18px;
color: var(--ink);
}
.meta-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 12px 24px;
font-size: 14px;
}
.meta-grid div span {
display: block;
color: var(--muted);
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.06em;
margin-bottom: 4px;
}
.pill {
display: inline-flex;
align-items: center;
padding: 4px 10px;
border-radius: 999px;
font-size: 12px;
font-weight: 600;
}
.pill-neutral {
background: var(--accent-soft);
color: var(--accent);
}
.table {
width: 100%;
border-collapse: collapse;
font-size: 14px;
}
.table th {
text-align: left;
color: var(--muted);
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.08em;
padding: 8px 0;
border-bottom: 1px solid var(--border);
}
.table td {
padding: 12px 0;
border-bottom: 1px solid var(--border);
}
.table tr:last-child td {
border-bottom: none;
}
.table tr.clickable {
cursor: pointer;
transition: background-color 0.15s;
}
.table tr.clickable:hover {
background: var(--accent-soft);
}
.meta {
color: var(--muted);
font-size: 12px;
}
.timeline {
display: grid;
gap: 12px;
}
.event {
display: grid;
grid-template-columns: 160px 1fr;
gap: 12px;
padding: 12px 16px;
border: 1px solid var(--border);
border-radius: 12px;
background: #f8fafc;
}
.event .time {
color: var(--muted);
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.06em;
}
.event .detail {
font-weight: 600;
font-size: 14px;
}
.event .meta {
color: var(--muted);
font-size: 12px;
margin-top: 4px;
}
.tickets {
display: grid;
gap: 10px;
}
.ticket {
border: 1px solid var(--border);
padding: 12px 16px;
border-radius: 12px;
background: #fff7ed;
}
.ticket .title {
font-size: 15px;
margin: 0 0 4px 0;
}
.ticket .meta {
color: var(--muted);
font-size: 12px;
}
.stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 12px;
}
.stat {
border: 1px solid var(--border);
border-radius: 14px;
padding: 14px 16px;
background: #f8fafc;
}
.stat span {
display: block;
color: var(--muted);
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.08em;
}
.stat strong {
font-size: 20px;
}
.split {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 20px;
}
.form {
display: grid;
gap: 12px;
}
.field {
display: grid;
gap: 6px;
}
.field label {
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--muted);
}
.input {
padding: 10px 12px;
border-radius: 10px;
border: 1px solid var(--border);
font-family: inherit;
}
.button {
padding: 10px 14px;
border-radius: 10px;
border: none;
background: var(--accent);
color: white;
font-weight: 600;
cursor: pointer;
}
.error {
border: 1px solid #fecaca;
background: #fff1f2;
color: #9f1239;
padding: 10px 12px;
border-radius: 12px;
font-size: 13px;
}
.badge {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 8px;
border-radius: 8px;
font-size: 12px;
font-weight: 600;
}
.status-green {
background: #dcfce7;
color: #166534;
}
.status-gray {
background: #e5e7eb;
color: #374151;
}
.status-red {
background: #fee2e2;
color: #991b1b;
}
.status-yellow {
background: #fef3c7;
color: #92400e;
}
.breadcrumbs {
display: flex;
align-items: center;
gap: 8px;
padding: 12px 32px;
background: white;
border-bottom: 1px solid var(--border);
font-size: 13px;
}
.breadcrumbs a {
text-decoration: none;
color: var(--muted);
transition: color 0.2s;
}
.breadcrumbs a:hover {
color: var(--accent);
}
.breadcrumbs .home-icon {
font-size: 16px;
line-height: 1;
}
.breadcrumbs .separator {
color: var(--muted);
opacity: 0.5;
font-size: 11px;
}
.breadcrumbs .current {
color: var(--ink);
font-weight: 600;
}
@media (max-width: 720px) {
.topbar {
flex-direction: column;
align-items: flex-start;
gap: 8px;
}
.nav {
flex-wrap: wrap;
}
.event {
grid-template-columns: 1fr;
}
}
</style>
<script>
function navigateToRow(url) {
window.location.href = url;
}
// Улучшенная работа выпадающих меню
document.addEventListener('DOMContentLoaded', function() {
const navGroups = document.querySelectorAll('.nav-group');
navGroups.forEach(group => {
let hideTimer;
group.addEventListener('mouseenter', function() {
clearTimeout(hideTimer);
this.querySelector('.nav-submenu').style.display = 'block';
});
group.addEventListener('mouseleave', function() {
const submenu = this.querySelector('.nav-submenu');
hideTimer = setTimeout(() => {
submenu.style.display = 'none';
}, 150);
});
});
});
</script>
</head>
{{end}}
{{define "topbar"}}
<header class="topbar">
<div>
<div class="brand">Reanimator</div>
<h1 class="title">{{.PageTitle}}</h1>
{{if .PageSubtitle}}<div class="subtitle">{{.PageSubtitle}}</div>{{end}}
</div>
{{if .HeroTag}}<div class="pill {{if .HeroTagClass}}{{.HeroTagClass}}{{else}}pill-neutral{{end}}">{{.HeroTag}}</div>{{end}}
</header>
<nav class="nav">
<div class="nav-item">
<a href="/ui" class="{{if eq .ActiveNav "dashboard"}}active{{end}}">Dashboard</a>
</div>
<div class="nav-item">
<a href="/ui/customers" class="{{if eq .ActiveNav "customers"}}active{{end}}">Customers</a>
</div>
<div class="nav-item">
<a href="/ui/locations" class="{{if eq .ActiveNav "locations"}}active{{end}}">Locations</a>
</div>
<div class="nav-item nav-group">
<span class="nav-group-label {{if or (eq .ActiveNav "assets") (eq .ActiveNav "components") (eq .ActiveNav "lots")}}active{{end}}">Hardware</span>
<div class="nav-submenu">
<a href="/ui/assets" class="{{if eq .ActiveNav "assets"}}active{{end}}">Assets</a>
<a href="/ui/components" class="{{if eq .ActiveNav "components"}}active{{end}}">Components</a>
<a href="/ui/lots" class="{{if eq .ActiveNav "lots"}}active{{end}}">Lots</a>
</div>
</div>
<div class="nav-item nav-group">
<span class="nav-group-label {{if or (eq .ActiveNav "tickets") (eq .ActiveNav "failures") (eq .ActiveNav "analytics")}}active{{end}}">Health</span>
<div class="nav-submenu">
<a href="/ui/tickets" class="{{if eq .ActiveNav "tickets"}}active{{end}}">Tickets</a>
<a href="/ui/failures" class="{{if eq .ActiveNav "failures"}}active{{end}}">Failures</a>
<a href="/ui/analytics" class="{{if eq .ActiveNav "analytics"}}active{{end}}">Analytics</a>
</div>
</div>
<div class="nav-item nav-group">
<span class="nav-group-label {{if eq .ActiveNav "ingest"}}active{{end}}">Settings</span>
<div class="nav-submenu">
<a href="/ui/ingest" class="{{if eq .ActiveNav "ingest"}}active{{end}}">Ingest</a>
</div>
</div>
</nav>
{{end}}
{{define "breadcrumbs"}}
{{if .Breadcrumbs}}
<nav class="breadcrumbs">
<a href="/ui" class="home-icon">🏥</a>
{{range $i, $crumb := .Breadcrumbs}}
<span class="separator"></span>
{{if $crumb.URL}}
<a href="{{$crumb.URL}}">{{$crumb.Label}}</a>
{{else}}
<span class="current">{{$crumb.Label}}</span>
{{end}}
{{end}}
</nav>
{{end}}
{{end}}