{{.Action}} · {{.OutcomeLabel}}{{$.Msg "audit.details"}}
{{.Details}}
diff --git a/.gitignore b/.gitignore index c69de3f..e684654 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,7 @@ # equivalent local directories if a developer exports them for inspection. deploy/dev/caddy-data/ deploy/dev/caddy-config/ + +# Local runtime and browser-validation artifacts. +data/ +.playwright-cli/ diff --git a/docs/PROJECT-STATE.md b/docs/PROJECT-STATE.md index 9b844e7..c808e2f 100644 --- a/docs/PROJECT-STATE.md +++ b/docs/PROJECT-STATE.md @@ -5,6 +5,7 @@ Read this compact operational baseline before starting a milestone. Open detaile ## Baseline - Current reference: post-v0.1.1 release-gate correction based on tagged baseline `88a45c7`. +- Source-facing HTML templates, CSS and browser JavaScript are structurally formatted for maintainability; this formatting-only baseline preserves their existing contracts and behavior. - SQLite is initialized directly from one embedded current schema and can be reopened safely after first initialization. Development databases from earlier revisions are intentionally unsupported until versioned production migrations are introduced. - Roadmap milestones 1-10 are implemented; this is the V1 feature baseline. diff --git a/internal/web/static/app.v1.css b/internal/web/static/app.v1.css index b4ab96f..dbf9677 100644 --- a/internal/web/static/app.v1.css +++ b/internal/web/static/app.v1.css @@ -1,11 +1,1195 @@ -:root{color-scheme:dark;--bg-primary:#070913;--bg-secondary:#0d1020;--surface:#15182d;--surface-hover:#1c203a;--text-primary:#f4f7ff;--text-secondary:#9ba7c0;--accent-cyan:#20d9f3;--accent-magenta:#f02fb8;--accent-orange:#ff9b45;--status-running:#29d7a0;--status-stopped:#8791a8;--status-warning:#ff9b45;--status-error:#ff5d71;--border:#292d49;--radius:14px;--shadow:0 18px 50px #0007;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;line-height:1.5;font-synthesis:none}*{box-sizing:border-box}html{scroll-behavior:smooth}body{margin:0;background:radial-gradient(circle at 75% -10%,#32154d55,transparent 35rem),var(--bg-primary);color:var(--text-primary)}button,input,textarea,select{font:inherit}a{color:inherit}.skip-link{position:fixed;z-index:100;top:.5rem;left:.5rem;transform:translateY(-150%);padding:.7rem 1rem;background:var(--accent-cyan);color:#041017;border-radius:8px}.skip-link:focus{transform:none}.app-body{min-height:100vh}.sidebar{position:fixed;inset:0 auto 0 0;width:250px;display:flex;flex-direction:column;padding:24px 16px;background:#0b0d18ee;border-right:1px solid var(--border);backdrop-filter:blur(18px);z-index:20}.brand{display:flex;align-items:center;gap:12px;padding:0 8px 25px;text-decoration:none}.brand img{width:54px;height:54px;border-radius:13px;object-fit:cover;box-shadow:0 0 18px #ef2fb833}.brand strong{display:block;font-size:1.3rem;letter-spacing:.04em}.brand small,.account small{display:block;color:var(--text-secondary);font-size:.72rem}.sidebar nav{display:grid;gap:5px}.sidebar nav p{margin:22px 12px 7px;color:#68728b;font-size:.68rem;font-weight:800;letter-spacing:.14em;text-transform:uppercase}.sidebar nav a{padding:11px 13px;border:1px solid transparent;border-radius:10px;color:var(--text-secondary);font-weight:650;text-decoration:none}.sidebar nav a:hover{background:var(--surface);color:var(--text-primary)}.sidebar nav a.active{border-color:#c22f9b66;background:linear-gradient(100deg,#6b287566,#13365355);color:#fff;box-shadow:inset 3px 0 var(--accent-cyan)}.account{margin-top:auto;padding-top:18px;border-top:1px solid var(--border)}.account>div{display:flex;align-items:center;gap:10px;margin-bottom:12px}.avatar{display:grid;width:36px;height:36px;place-items:center;border-radius:50%;background:linear-gradient(135deg,var(--accent-cyan),var(--accent-magenta));color:#081019;font-weight:900;text-transform:uppercase}.app-main{min-height:100vh;margin-left:250px;padding:44px clamp(24px,4vw,64px) 70px}.mobile-header{display:none}.page-heading{display:flex;justify-content:space-between;align-items:end;margin:0 auto 28px;max-width:1400px}.eyebrow{margin:0 0 5px;color:var(--accent-cyan);font-size:.74rem;font-weight:850;letter-spacing:.14em;text-transform:uppercase}.page-heading h1,.auth-card h1{margin:0;font-size:clamp(2rem,4vw,3.35rem);line-height:1.08;letter-spacing:-.04em}.page-heading p:not(.eyebrow),.auth-copy{margin:.6rem 0 0;color:var(--text-secondary)}.instance-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(100%,260px),1fr));gap:20px;max-width:1400px;margin:auto}.instance-card{min-width:0;overflow:hidden;background:linear-gradient(145deg,#181b32,#111426);border:1px solid var(--border);border-radius:var(--radius);box-shadow:0 10px 24px #0004;transition:border-color .18s,transform .18s,box-shadow .18s}.instance-card:hover{transform:translateY(-2px);border-color:#20d9f388;box-shadow:0 14px 34px #0007,0 0 20px #20d9f312}.instance-art{position:relative;display:grid;height:145px;place-items:center;overflow:hidden;background:linear-gradient(135deg,#232645,#12162c)}.instance-art:after{content:"";position:absolute;inset:auto 0 0;height:45%;background:linear-gradient(transparent,#111426)}.instance-art img{width:100%;height:100%;object-fit:cover}.instance-art>span:not(.status-badge){font-size:2rem;font-weight:900;color:#20d9f366}.status-badge{position:absolute;z-index:2;right:12px;bottom:12px;display:flex;align-items:center;gap:7px;padding:5px 9px;border:1px solid currentColor;border-radius:999px;background:#080b16dd;font-size:.72rem;font-weight:800}.status-badge i{width:7px;height:7px;border-radius:50%;background:currentColor}.status-running{color:var(--status-running)}.status-stopped{color:var(--status-stopped)}.status-warning{color:var(--status-warning)}.status-error{color:var(--status-error)}.status-unknown{color:#aab2c4}.instance-card-body{padding:17px 18px 19px}.game-name{margin:0;color:var(--accent-cyan);font-size:.78rem;font-weight:800;text-transform:uppercase;letter-spacing:.08em}.instance-card h2{margin:5px 0 7px;font-size:1.16rem}.instance-id{margin:0;overflow:hidden;color:#747f99;font-size:.72rem;text-overflow:ellipsis;white-space:nowrap}.summary-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;max-width:1400px;margin:26px auto 0}.summary-grid article{padding:18px;background:#111425aa;border:1px solid var(--border);border-radius:12px}.summary-grid span{display:block;color:var(--text-secondary);font-size:.78rem}.summary-grid strong{font-size:1.65rem}.empty-state{max-width:1400px;margin:auto;padding:80px 30px;text-align:center;background:#11142588;border:1px dashed #343a5c;border-radius:var(--radius)}.empty-icon{color:var(--accent-magenta);font-size:3rem}.empty-state h2{margin:.5rem 0}.empty-state p{color:var(--text-secondary)}.panel{max-width:1400px;margin:0 auto 22px;padding:clamp(18px,3vw,30px);background:linear-gradient(145deg,#15182d,#101322);border:1px solid var(--border);border-radius:var(--radius);box-shadow:0 12px 35px #0003}.panel h2{margin:0}.section-heading{display:flex;align-items:start;justify-content:space-between;gap:20px;margin-bottom:22px}.section-heading p,.help,.muted{margin:.35rem 0;color:var(--text-secondary);font-size:.88rem}.settings-section{scroll-margin-top:20px}.tabs{display:flex;gap:8px;max-width:1400px;margin:0 auto 20px;padding:7px;background:#111425;border:1px solid var(--border);border-radius:12px;overflow-x:auto}.tabs a{padding:8px 13px;border-radius:8px;color:var(--text-secondary);font-weight:700;text-decoration:none;white-space:nowrap}.tabs a:hover,.tabs a:focus{background:var(--surface-hover);color:var(--text-primary)}form{display:grid;gap:15px}label{display:grid;gap:6px;color:#dce3f4;font-size:.84rem;font-weight:700}input,textarea,select{width:100%;padding:11px 12px;background:#0a0d1a;color:var(--text-primary);border:1px solid #343957;border-radius:8px;outline:0}textarea{resize:vertical}input:hover,textarea:hover,select:hover{border-color:#59617f}input:focus-visible,textarea:focus-visible,select:focus-visible,button:focus-visible,a:focus-visible{outline:2px solid var(--accent-cyan);outline-offset:3px}button,.link-button{justify-self:start;padding:10px 15px;border:0;border-radius:8px;background:linear-gradient(100deg,#7d37ed,var(--accent-magenta));color:#fff;font-weight:800;cursor:pointer;text-decoration:none}.button-secondary{background:#171b30;border:1px solid #383d5b;color:#dce3f4}.danger{background:#a5283b;color:#fff}.warning{padding:13px 15px;border:1px solid #8c572c;border-left:4px solid var(--accent-orange);border-radius:8px;background:#2d1b14;color:#ffd9bd}.error{padding:12px 14px;border-left:4px solid var(--status-error);border-radius:7px;background:#351620;color:#ffd8de}.form-grid,.filter-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:15px}.filter-grid{grid-template-columns:repeat(4,minmax(140px,1fr));margin-bottom:25px}.filter-grid button{align-self:end}.inline{display:inline}.inline+form{margin-left:7px}.channel-list{display:grid;gap:9px;margin:0 0 24px;padding:0;list-style:none}.channel-list li{display:flex;align-items:center;justify-content:space-between;gap:15px;padding:13px 15px;background:#0d1020;border:1px solid var(--border);border-radius:9px}.channel-list small{display:block;color:var(--text-secondary)}.two-panels{display:grid;grid-template-columns:1fr 1fr;gap:24px}.two-panels>form+form{padding-left:24px;border-left:1px solid var(--border)}fieldset{display:flex;flex-wrap:wrap;gap:16px;margin:0;padding:12px;border:1px solid var(--border);border-radius:8px}legend{padding:0 6px;color:var(--text-secondary);font-size:.8rem}.check{display:flex;grid-template-columns:auto 1fr;align-items:center;gap:8px}.check input{width:auto}.table-wrap{overflow-x:auto}table{width:100%;border-collapse:collapse}th,td{padding:13px 12px;text-align:left;border-bottom:1px solid var(--border);white-space:nowrap}th{color:#78829b;font-size:.7rem;text-transform:uppercase;letter-spacing:.08em}td{font-size:.85rem}code{color:#8eeaf8}.outcome{padding:3px 7px;border-radius:99px;background:#20243a}.outcome-allowed{color:var(--status-running)}.outcome-denied,.outcome-failed{color:var(--status-error)}.auth-body{min-height:100vh;display:grid;place-items:center;padding:24px;background:radial-gradient(circle at 50% 0,#43175188,transparent 32rem),linear-gradient(145deg,#060812,#10132a)}.auth-card{width:min(100%,430px);padding:34px;background:#101322ee;border:1px solid #343957;border-radius:18px;box-shadow:var(--shadow)}.auth-logo{width:78px;height:78px;margin-bottom:20px;object-fit:cover;border-radius:18px;box-shadow:0 0 25px #ef2fb844}.auth-card h1{font-size:2.1rem}.auth-card form{margin-top:26px}.auth-card button{width:100%;justify-self:stretch}.auth-copy{font-size:.9rem} -@media(max-width:1050px){.summary-grid{grid-template-columns:repeat(2,1fr)}.filter-grid{grid-template-columns:repeat(2,1fr)}} -@media(max-width:760px){.sidebar{transform:translateX(-105%);transition:transform .2s;box-shadow:20px 0 50px #000a}.nav-open .sidebar{transform:none}.app-main{margin-left:0;padding:0 18px 50px}.mobile-header{display:flex;align-items:center;gap:10px;margin:0 -18px 28px;padding:12px 18px;background:#0b0d18ee;border-bottom:1px solid var(--border);position:sticky;top:0;z-index:15}.mobile-header img{width:38px;height:38px;border-radius:9px}.menu-button{margin-left:auto;padding:8px 12px;background:#171b30;border:1px solid #383d5b}.page-heading{align-items:start}.two-panels{grid-template-columns:1fr}.two-panels>form+form{padding:20px 0 0;border-top:1px solid var(--border);border-left:0}.section-heading{display:grid}.channel-list li{align-items:start;flex-direction:column}.form-grid{grid-template-columns:1fr}} -@media(max-width:520px){.instance-grid{grid-template-columns:1fr}.summary-grid,.filter-grid{grid-template-columns:1fr}.panel{padding:17px}.auth-card{padding:25px 22px}.instance-art{height:160px}} -.audit-filter-grid{display:grid;grid-template-columns:repeat(4,minmax(150px,1fr));gap:14px}.audit-filter-grid button{align-self:end}.audit-list{display:grid;gap:8px}.audit-event{display:grid;grid-template-columns:180px minmax(220px,1fr) minmax(120px,.6fr) auto;gap:16px;align-items:start;padding:15px 0;border-bottom:1px solid var(--border)}.audit-event time,.audit-event small{color:var(--text-secondary);font-size:.8rem}.audit-event small{display:block;margin-top:3px}.audit-event details{max-width:260px;color:var(--text-secondary);font-size:.82rem}.audit-event details p{white-space:normal}.audit-pagination{display:flex;justify-content:center;align-items:center;gap:12px;margin-top:22px}@media(max-width:760px){.audit-filter-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.audit-event{grid-template-columns:1fr 1fr}.audit-event details{grid-column:1/-1}}@media(max-width:520px){.audit-filter-grid,.audit-event{grid-template-columns:1fr}} -.app-body{background-color:#050814;background-image:radial-gradient(circle at 12% 14%,#20d9f355 0 1px,transparent 1.5px),radial-gradient(circle at 78% 8%,#f4f7ff77 0 1px,transparent 1.5px),radial-gradient(circle at 55% 26%,#f02fb844 0 1px,transparent 1.5px),linear-gradient(165deg,#060917 0%,#080b18 52%,#070916 100%);background-size:137px 149px,191px 173px,223px 211px,auto}.sidebar{width:220px;border-right-color:#f02fb866;box-shadow:8px 0 40px #0005}.app-main{margin-left:220px}.brand{display:grid;justify-items:center;text-align:center;padding-bottom:20px}.brand img{width:72px;height:72px}.brand small{display:none}.page-heading{padding-bottom:16px;border-bottom:1px solid #232943}.page-heading h1{font-size:clamp(1.9rem,3vw,2.6rem)}.instance-grid{grid-template-columns:repeat(auto-fill,minmax(min(100%,280px),1fr));gap:16px}.instance-card{border-color:#3d4667;border-radius:10px}.instance-art{height:112px;border-bottom:1px solid #424b6d}.instance-art:after{height:25%}.instance-card-body{padding:13px 15px 15px}.instance-card h2{margin:0;font-size:1.05rem}.instance-name{margin:3px 0 12px;color:var(--text-secondary);font-size:.84rem}.status-badge{position:static;display:inline-flex;padding:0;border:0;background:transparent;font-size:.82rem}.summary-grid{grid-template-columns:repeat(5,1fr);gap:12px}.summary-grid article{position:relative;padding:15px 17px;border-top:2px solid #384260}.summary-grid strong{display:block;line-height:1.1}.summary-total{border-top-color:var(--accent-cyan)!important}.summary-running{border-top-color:var(--status-running)!important}.summary-stopped{border-top-color:var(--status-stopped)!important}.summary-warning{border-top-color:var(--status-warning)!important}.summary-error{border-top-color:var(--status-error)!important}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}@media(max-width:1050px){.summary-grid{grid-template-columns:repeat(3,1fr)}}@media(max-width:760px){.sidebar{width:250px}.app-main{margin-left:0}.brand{display:flex;justify-items:initial;text-align:left}.brand img{width:54px;height:54px}.brand small{display:block}}@media(max-width:520px){.summary-grid{grid-template-columns:1fr 1fr}.summary-grid article:first-child{grid-column:1/-1}}@media(prefers-reduced-motion:reduce){*{scroll-behavior:auto!important;transition:none!important}} - -.auth-card{padding:0;overflow:hidden}.auth-brand{width:100%;line-height:0}.auth-brand img{width:100%;height:auto;display:block;object-fit:contain}.auth-content{padding:28px 34px 34px}.language-select{display:flex;grid-auto-flow:column;justify-content:end;gap:7px;margin:0 0 18px}.language-select button{padding:5px 9px;font-size:.75rem}.auth-content form:not(.language-select){margin-top:26px}@media(max-width:520px){.auth-card{padding:0}.auth-content{padding:22px}.language-select{justify-content:start}} -.instance-detail{display:grid;grid-template-columns:minmax(190px,300px) minmax(0,1fr);gap:clamp(24px,5vw,64px);max-width:1120px;margin:auto;padding:clamp(20px,4vw,42px);background:linear-gradient(145deg,#15182d,#101322);border:1px solid var(--border);border-radius:var(--radius)}.instance-detail h1{margin:0;font-size:clamp(2rem,5vw,3.6rem)}.detail-actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:9px;margin:0 0 30px}.detail-actions form{display:block}.detail-actions button,.action-restore{margin:0;border:1px solid #ffffff22;border-radius:9px;padding:10px 14px;font-weight:850}.action-start{background:linear-gradient(120deg,#117b58,#2bd69d)}.action-stop{background:linear-gradient(120deg,#7d1d37,#f04f6c)}.action-backup{background:linear-gradient(120deg,#164f96,#24b9ee)}.action-update{background:linear-gradient(120deg,#93640d,#ffc644)}button:disabled{opacity:.42;cursor:not-allowed}.instance-info{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:28px 0 0}.instance-info div{padding:14px;background:#0b1020;border:1px solid var(--border);border-radius:10px}.instance-info dt{color:var(--text-secondary);font-size:.8rem}.instance-info dd{margin:5px 0 0;font-weight:750}.instance-detail-lower{display:grid;grid-template-columns:1fr 1fr;gap:22px;max-width:1120px;margin:22px auto}.backup-list{display:grid;gap:9px;margin:0;padding:0;list-style:none}.backup-list li{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px;background:#0b1020;border:1px solid var(--border);border-radius:9px}.backup-list small{display:block;color:var(--text-secondary)}.notice{padding:12px 14px;background:#103427;border-left:4px solid var(--status-running);border-radius:7px}@media(max-width:700px){.instance-detail,.instance-detail-lower{grid-template-columns:1fr}.instance-detail aside{max-width:300px;margin:auto;width:100%}.detail-actions{justify-content:flex-start}.instance-info{grid-template-columns:1fr}} - -.catalog-tools{display:flex;align-items:center;gap:12px}.scan-button,.deploy-band button{border:1px solid #20d9f388;border-radius:10px;padding:10px 15px;background:linear-gradient(110deg,#123b51,#55264d);color:#fff;font-weight:800;cursor:pointer}.catalog-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:22px;max-width:1400px;margin:auto}.catalog-card{text-decoration:none;min-width:0}.catalog-cover,.game-cover{position:relative;overflow:hidden;background:linear-gradient(135deg,#25233f,#101322);border:1px solid var(--border);border-radius:12px;box-shadow:0 12px 26px #0005;aspect-ratio:2/3}.catalog-cover img,.game-cover img{position:relative;z-index:1;width:100%;height:100%;object-fit:cover}.cover-fallback,.game-cover>span{position:absolute;inset:0;display:grid;place-items:center;color:#20d9f366;font-size:3rem}.catalog-card h2{margin:10px 3px 0;font-size:1rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.catalog-card:hover .catalog-cover{border-color:var(--accent-cyan);box-shadow:0 16px 32px #0008,0 0 22px #20d9f322;transform:translateY(-3px)}.catalog-scan-result{max-width:1400px;margin:0 auto 22px;padding:14px 18px;background:#12314466;border:1px solid #20d9f366;border-radius:12px}.catalog-scan-result ul{margin:8px 0 0;padding-left:20px;color:var(--text-secondary)}.back-link{display:inline-block;margin:0 auto 22px;max-width:1400px;color:var(--accent-cyan);text-decoration:none}.game-detail{display:grid;grid-template-columns:minmax(210px,330px) minmax(0,1fr);gap:clamp(28px,5vw,70px);max-width:1120px;margin:auto;padding:clamp(20px,4vw,42px);background:linear-gradient(145deg,#15182d,#101322);border:1px solid var(--border);border-radius:var(--radius)}.game-detail h1{margin:0;font-size:clamp(2rem,5vw,3.6rem)}.game-description{max-width:70ch;color:var(--text-secondary);font-size:1.05rem}.deploy-band{margin-top:12px;padding:12px;background:#0b1020;border:1px solid var(--border);border-radius:10px}.deploy-band button{width:100%;cursor:not-allowed;opacity:.7}.requirements-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px;margin-top:30px}.requirements-grid section{padding:18px;background:#0c1020;border:1px solid var(--border);border-radius:12px}.requirements-grid h2{margin:0 0 12px;color:var(--accent-cyan);font-size:1rem}.requirements-grid dl{display:grid;grid-template-columns:auto 1fr;gap:8px 12px;margin:0}.requirements-grid dt{color:var(--text-secondary)}.requirements-grid dd{margin:0;text-align:right}@media(max-width:700px){.catalog-header{align-items:start;flex-direction:column}.catalog-tools{width:100%;justify-content:space-between}.catalog-tools .instance-search{flex:1}.game-detail{grid-template-columns:1fr}.game-detail aside{max-width:300px;margin:auto;width:100%}.requirements-grid{grid-template-columns:1fr}} +:root{ + color-scheme: dark; + --bg-primary: #070913; + --bg-secondary: #0d1020; + --surface: #15182d; + --surface-hover: #1c203a; + --text-primary: #f4f7ff; + --text-secondary: #9ba7c0; + --accent-cyan: #20d9f3; + --accent-magenta: #f02fb8; + --accent-orange: #ff9b45; + --status-running: #29d7a0; + --status-stopped: #8791a8; + --status-warning: #ff9b45; + --status-error: #ff5d71; + --border: #292d49; + --radius: 14px; + --shadow: 0 18px 50px #0007; + font-family: Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif; + line-height: 1.5; + font-synthesis: none +} +*{ + box-sizing: border-box +} +html{ + scroll-behavior: smooth +} +body{ + margin: 0; + background: radial-gradient(circle at 75% -10%,#32154d55,transparent 35rem),var(--bg-primary); + color: var(--text-primary) +} +button,input,textarea,select{ + font: inherit +} +a{ + color: inherit +} +.skip-link{ + position: fixed; + z-index: 100; + top: .5rem; + left: .5rem; + transform: translateY(-150%); + padding: .7rem 1rem; + background: var(--accent-cyan); + color: #041017; + border-radius: 8px +} +.skip-link:focus{ + transform: none +} +.app-body{ + min-height: 100vh +} +.sidebar{ + position: fixed; + inset: 0 auto 0 0; + width: 250px; + display: flex; + flex-direction: column; + padding: 24px 16px; + background: #0b0d18ee; + border-right: 1px solid var(--border); + backdrop-filter: blur(18px); + z-index: 20 +} +.brand{ + display: flex; + align-items: center; + gap: 12px; + padding: 0 8px 25px; + text-decoration: none +} +.brand img{ + width: 54px; + height: 54px; + border-radius: 13px; + object-fit: cover; + box-shadow: 0 0 18px #ef2fb833 +} +.brand strong{ + display: block; + font-size: 1.3rem; + letter-spacing: .04em +} +.brand small,.account small{ + display: block; + color: var(--text-secondary); + font-size: .72rem +} +.sidebar nav{ + display: grid; + gap: 5px +} +.sidebar nav p{ + margin: 22px 12px 7px; + color: #68728b; + font-size: .68rem; + font-weight: 800; + letter-spacing: .14em; + text-transform: uppercase +} +.sidebar nav a{ + padding: 11px 13px; + border: 1px solid transparent; + border-radius: 10px; + color: var(--text-secondary); + font-weight: 650; + text-decoration: none +} +.sidebar nav a:hover{ + background: var(--surface); + color: var(--text-primary) +} +.sidebar nav a.active{ + border-color: #c22f9b66; + background: linear-gradient(100deg,#6b287566,#13365355); + color: #fff; + box-shadow: inset 3px 0 var(--accent-cyan) +} +.account{ + margin-top: auto; + padding-top: 18px; + border-top: 1px solid var(--border) +} +.account>div{ + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 12px +} +.avatar{ + display: grid; + width: 36px; + height: 36px; + place-items: center; + border-radius: 50%; + background: linear-gradient(135deg,var(--accent-cyan),var(--accent-magenta)); + color: #081019; + font-weight: 900; + text-transform: uppercase +} +.app-main{ + min-height: 100vh; + margin-left: 250px; + padding: 44px clamp(24px,4vw,64px) 70px +} +.mobile-header{ + display: none +} +.page-heading{ + display: flex; + justify-content: space-between; + align-items: end; + margin: 0 auto 28px; + max-width: 1400px +} +.eyebrow{ + margin: 0 0 5px; + color: var(--accent-cyan); + font-size: .74rem; + font-weight: 850; + letter-spacing: .14em; + text-transform: uppercase +} +.page-heading h1,.auth-card h1{ + margin: 0; + font-size: clamp(2rem,4vw,3.35rem); + line-height: 1.08; + letter-spacing: -.04em +} +.page-heading p:not(.eyebrow),.auth-copy{ + margin: .6rem 0 0; + color: var(--text-secondary) +} +.instance-grid{ + display: grid; + grid-template-columns: repeat(auto-fill,minmax(min(100%,260px),1fr)); + gap: 20px; + max-width: 1400px; + margin: auto +} +.instance-card{ + min-width: 0; + overflow: hidden; + background: linear-gradient(145deg,#181b32,#111426); + border: 1px solid var(--border); + border-radius: var(--radius); + box-shadow: 0 10px 24px #0004; + transition: border-color .18s,transform .18s,box-shadow .18s +} +.instance-card:hover{ + transform: translateY(-2px); + border-color: #20d9f388; + box-shadow: 0 14px 34px #0007,0 0 20px #20d9f312 +} +.instance-art{ + position: relative; + display: grid; + height: 145px; + place-items: center; + overflow: hidden; + background: linear-gradient(135deg,#232645,#12162c) +} +.instance-art:after{ + content: ""; + position: absolute; + inset: auto 0 0; + height: 45%; + background: linear-gradient(transparent,#111426) +} +.instance-art img{ + width: 100%; + height: 100%; + object-fit: cover +} +.instance-art>span:not(.status-badge){ + font-size: 2rem; + font-weight: 900; + color: #20d9f366 +} +.status-badge{ + position: absolute; + z-index: 2; + right: 12px; + bottom: 12px; + display: flex; + align-items: center; + gap: 7px; + padding: 5px 9px; + border: 1px solid currentColor; + border-radius: 999px; + background: #080b16dd; + font-size: .72rem; + font-weight: 800 +} +.status-badge i{ + width: 7px; + height: 7px; + border-radius: 50%; + background: currentColor +} +.status-running{ + color: var(--status-running) +} +.status-stopped{ + color: var(--status-stopped) +} +.status-warning{ + color: var(--status-warning) +} +.status-error{ + color: var(--status-error) +} +.status-unknown{ + color: #aab2c4 +} +.instance-card-body{ + padding: 17px 18px 19px +} +.game-name{ + margin: 0; + color: var(--accent-cyan); + font-size: .78rem; + font-weight: 800; + text-transform: uppercase; + letter-spacing: .08em +} +.instance-card h2{ + margin: 5px 0 7px; + font-size: 1.16rem +} +.instance-id{ + margin: 0; + overflow: hidden; + color: #747f99; + font-size: .72rem; + text-overflow: ellipsis; + white-space: nowrap +} +.summary-grid{ + display: grid; + grid-template-columns: repeat(4,1fr); + gap: 14px; + max-width: 1400px; + margin: 26px auto 0 +} +.summary-grid article{ + padding: 18px; + background: #111425aa; + border: 1px solid var(--border); + border-radius: 12px +} +.summary-grid span{ + display: block; + color: var(--text-secondary); + font-size: .78rem +} +.summary-grid strong{ + font-size: 1.65rem +} +.empty-state{ + max-width: 1400px; + margin: auto; + padding: 80px 30px; + text-align: center; + background: #11142588; + border: 1px dashed #343a5c; + border-radius: var(--radius) +} +.empty-icon{ + color: var(--accent-magenta); + font-size: 3rem +} +.empty-state h2{ + margin: .5rem 0 +} +.empty-state p{ + color: var(--text-secondary) +} +.panel{ + max-width: 1400px; + margin: 0 auto 22px; + padding: clamp(18px,3vw,30px); + background: linear-gradient(145deg,#15182d,#101322); + border: 1px solid var(--border); + border-radius: var(--radius); + box-shadow: 0 12px 35px #0003 +} +.panel h2{ + margin: 0 +} +.section-heading{ + display: flex; + align-items: start; + justify-content: space-between; + gap: 20px; + margin-bottom: 22px +} +.section-heading p,.help,.muted{ + margin: .35rem 0; + color: var(--text-secondary); + font-size: .88rem +} +.settings-section{ + scroll-margin-top: 20px +} +.tabs{ + display: flex; + gap: 8px; + max-width: 1400px; + margin: 0 auto 20px; + padding: 7px; + background: #111425; + border: 1px solid var(--border); + border-radius: 12px; + overflow-x: auto +} +.tabs a{ + padding: 8px 13px; + border-radius: 8px; + color: var(--text-secondary); + font-weight: 700; + text-decoration: none; + white-space: nowrap +} +.tabs a:hover,.tabs a:focus{ + background: var(--surface-hover); + color: var(--text-primary) +} +form{ + display: grid; + gap: 15px +} +label{ + display: grid; + gap: 6px; + color: #dce3f4; + font-size: .84rem; + font-weight: 700 +} +input,textarea,select{ + width: 100%; + padding: 11px 12px; + background: #0a0d1a; + color: var(--text-primary); + border: 1px solid #343957; + border-radius: 8px; + outline: 0 +} +textarea{ + resize: vertical +} +input:hover,textarea:hover,select:hover{ + border-color: #59617f +} +input:focus-visible,textarea:focus-visible,select:focus-visible,button:focus-visible,a:focus-visible{ + outline: 2px solid var(--accent-cyan); + outline-offset: 3px +} +button,.link-button{ + justify-self: start; + padding: 10px 15px; + border: 0; + border-radius: 8px; + background: linear-gradient(100deg,#7d37ed,var(--accent-magenta)); + color: #fff; + font-weight: 800; + cursor: pointer; + text-decoration: none +} +.button-secondary{ + background: #171b30; + border: 1px solid #383d5b; + color: #dce3f4 +} +.danger{ + background: #a5283b; + color: #fff +} +.warning{ + padding: 13px 15px; + border: 1px solid #8c572c; + border-left: 4px solid var(--accent-orange); + border-radius: 8px; + background: #2d1b14; + color: #ffd9bd +} +.error{ + padding: 12px 14px; + border-left: 4px solid var(--status-error); + border-radius: 7px; + background: #351620; + color: #ffd8de +} +.form-grid,.filter-grid{ + display: grid; + grid-template-columns: repeat(2,minmax(0,1fr)); + gap: 15px +} +.filter-grid{ + grid-template-columns: repeat(4,minmax(140px,1fr)); + margin-bottom: 25px +} +.filter-grid button{ + align-self: end +} +.inline{ + display: inline +} +.inline+form{ + margin-left: 7px +} +.channel-list{ + display: grid; + gap: 9px; + margin: 0 0 24px; + padding: 0; + list-style: none +} +.channel-list li{ + display: flex; + align-items: center; + justify-content: space-between; + gap: 15px; + padding: 13px 15px; + background: #0d1020; + border: 1px solid var(--border); + border-radius: 9px +} +.channel-list small{ + display: block; + color: var(--text-secondary) +} +.two-panels{ + display: grid; + grid-template-columns: 1fr 1fr; + gap: 24px +} +.two-panels>form+form{ + padding-left: 24px; + border-left: 1px solid var(--border) +} +fieldset{ + display: flex; + flex-wrap: wrap; + gap: 16px; + margin: 0; + padding: 12px; + border: 1px solid var(--border); + border-radius: 8px +} +legend{ + padding: 0 6px; + color: var(--text-secondary); + font-size: .8rem +} +.check{ + display: flex; + grid-template-columns: auto 1fr; + align-items: center; + gap: 8px +} +.check input{ + width: auto +} +.table-wrap{ + overflow-x: auto +} +table{ + width: 100%; + border-collapse: collapse +} +th,td{ + padding: 13px 12px; + text-align: left; + border-bottom: 1px solid var(--border); + white-space: nowrap +} +th{ + color: #78829b; + font-size: .7rem; + text-transform: uppercase; + letter-spacing: .08em +} +td{ + font-size: .85rem +} +code{ + color: #8eeaf8 +} +.outcome{ + padding: 3px 7px; + border-radius: 99px; + background: #20243a +} +.outcome-allowed{ + color: var(--status-running) +} +.outcome-denied,.outcome-failed{ + color: var(--status-error) +} +.auth-body{ + min-height: 100vh; + display: grid; + place-items: center; + padding: 24px; + background: radial-gradient(circle at 50% 0,#43175188,transparent 32rem),linear-gradient(145deg,#060812,#10132a) +} +.auth-card{ + width: min(100%,430px); + padding: 34px; + background: #101322ee; + border: 1px solid #343957; + border-radius: 18px; + box-shadow: var(--shadow) +} +.auth-logo{ + width: 78px; + height: 78px; + margin-bottom: 20px; + object-fit: cover; + border-radius: 18px; + box-shadow: 0 0 25px #ef2fb844 +} +.auth-card h1{ + font-size: 2.1rem +} +.auth-card form{ + margin-top: 26px +} +.auth-card button{ + width: 100%; + justify-self: stretch +} +.auth-copy{ + font-size: .9rem +} +@media(max-width:1050px){ + .summary-grid{ + grid-template-columns: repeat(2,1fr) + } + .filter-grid{ + grid-template-columns: repeat(2,1fr) + } +} +@media(max-width:760px){ + .sidebar{ + transform: translateX(-105%); + transition: transform .2s; + box-shadow: 20px 0 50px #000a + } + .nav-open .sidebar{ + transform: none + } + .app-main{ + margin-left: 0; + padding: 0 18px 50px + } + .mobile-header{ + display: flex; + align-items: center; + gap: 10px; + margin: 0 -18px 28px; + padding: 12px 18px; + background: #0b0d18ee; + border-bottom: 1px solid var(--border); + position: sticky; + top: 0; + z-index: 15 + } + .mobile-header img{ + width: 38px; + height: 38px; + border-radius: 9px + } + .menu-button{ + margin-left: auto; + padding: 8px 12px; + background: #171b30; + border: 1px solid #383d5b + } + .page-heading{ + align-items: start + } + .two-panels{ + grid-template-columns: 1fr + } + .two-panels>form+form{ + padding: 20px 0 0; + border-top: 1px solid var(--border); + border-left: 0 + } + .section-heading{ + display: grid + } + .channel-list li{ + align-items: start; + flex-direction: column + } + .form-grid{ + grid-template-columns: 1fr + } +} +@media(max-width:520px){ + .instance-grid{ + grid-template-columns: 1fr + } + .summary-grid,.filter-grid{ + grid-template-columns: 1fr + } + .panel{ + padding: 17px + } + .auth-card{ + padding: 25px 22px + } + .instance-art{ + height: 160px + } +} +.audit-filter-grid{ + display: grid; + grid-template-columns: repeat(4,minmax(150px,1fr)); + gap: 14px +} +.audit-filter-grid button{ + align-self: end +} +.audit-list{ + display: grid; + gap: 8px +} +.audit-event{ + display: grid; + grid-template-columns: 180px minmax(220px,1fr) minmax(120px,.6fr) auto; + gap: 16px; + align-items: start; + padding: 15px 0; + border-bottom: 1px solid var(--border) +} +.audit-event time,.audit-event small{ + color: var(--text-secondary); + font-size: .8rem +} +.audit-event small{ + display: block; + margin-top: 3px +} +.audit-event details{ + max-width: 260px; + color: var(--text-secondary); + font-size: .82rem +} +.audit-event details p{ + white-space: normal +} +.audit-pagination{ + display: flex; + justify-content: center; + align-items: center; + gap: 12px; + margin-top: 22px +} +@media(max-width:760px){ + .audit-filter-grid{ + grid-template-columns: repeat(2,minmax(0,1fr)) + } + .audit-event{ + grid-template-columns: 1fr 1fr + } + .audit-event details{ + grid-column: 1/-1 + } +} +@media(max-width:520px){ + .audit-filter-grid,.audit-event{ + grid-template-columns: 1fr + } +} +.app-body{ + background-color: #050814; + background-image: radial-gradient(circle at 12% 14%,#20d9f355 0 1px,transparent 1.5px),radial-gradient(circle at 78% 8%,#f4f7ff77 0 1px,transparent 1.5px),radial-gradient(circle at 55% 26%,#f02fb844 0 1px,transparent 1.5px),linear-gradient(165deg,#060917 0%,#080b18 52%,#070916 100%); + background-size: 137px 149px,191px 173px,223px 211px,auto +} +.sidebar{ + width: 220px; + border-right-color: #f02fb866; + box-shadow: 8px 0 40px #0005 +} +.app-main{ + margin-left: 220px +} +.brand{ + display: grid; + justify-items: center; + text-align: center; + padding-bottom: 20px +} +.brand img{ + width: 72px; + height: 72px +} +.brand small{ + display: none +} +.page-heading{ + padding-bottom: 16px; + border-bottom: 1px solid #232943 +} +.page-heading h1{ + font-size: clamp(1.9rem,3vw,2.6rem) +} +.instance-grid{ + grid-template-columns: repeat(auto-fill,minmax(min(100%,280px),1fr)); + gap: 16px +} +.instance-card{ + border-color: #3d4667; + border-radius: 10px +} +.instance-art{ + height: 112px; + border-bottom: 1px solid #424b6d +} +.instance-art:after{ + height: 25% +} +.instance-card-body{ + padding: 13px 15px 15px +} +.instance-card h2{ + margin: 0; + font-size: 1.05rem +} +.instance-name{ + margin: 3px 0 12px; + color: var(--text-secondary); + font-size: .84rem +} +.status-badge{ + position: static; + display: inline-flex; + padding: 0; + border: 0; + background: transparent; + font-size: .82rem +} +.summary-grid{ + grid-template-columns: repeat(5,1fr); + gap: 12px +} +.summary-grid article{ + position: relative; + padding: 15px 17px; + border-top: 2px solid #384260 +} +.summary-grid strong{ + display: block; + line-height: 1.1 +} +.summary-total{ + border-top-color: var(--accent-cyan)!important +} +.summary-running{ + border-top-color: var(--status-running)!important +} +.summary-stopped{ + border-top-color: var(--status-stopped)!important +} +.summary-warning{ + border-top-color: var(--status-warning)!important +} +.summary-error{ + border-top-color: var(--status-error)!important +} +.sr-only{ + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0,0,0,0); + white-space: nowrap; + border: 0 +} +@media(max-width:1050px){ + .summary-grid{ + grid-template-columns: repeat(3,1fr) + } +} +@media(max-width:760px){ + .sidebar{ + width: 250px + } + .app-main{ + margin-left: 0 + } + .brand{ + display: flex; + justify-items: initial; + text-align: left + } + .brand img{ + width: 54px; + height: 54px + } + .brand small{ + display: block + } +} +@media(max-width:520px){ + .summary-grid{ + grid-template-columns: 1fr 1fr + } + .summary-grid article:first-child{ + grid-column: 1/-1 + } +} +@media(prefers-reduced-motion:reduce){ + *{ + scroll-behavior: auto!important; + transition: none!important + } +} +.auth-card{ + padding: 0; + overflow: hidden +} +.auth-brand{ + width: 100%; + line-height: 0 +} +.auth-brand img{ + width: 100%; + height: auto; + display: block; + object-fit: contain +} +.auth-content{ + padding: 28px 34px 34px +} +.language-select{ + display: flex; + grid-auto-flow: column; + justify-content: end; + gap: 7px; + margin: 0 0 18px +} +.language-select button{ + padding: 5px 9px; + font-size: .75rem +} +.auth-content form:not(.language-select){ + margin-top: 26px +} +@media(max-width:520px){ + .auth-card{ + padding: 0 + } + .auth-content{ + padding: 22px + } + .language-select{ + justify-content: start + } +} +.instance-detail{ + display: grid; + grid-template-columns: minmax(190px,300px) minmax(0,1fr); + gap: clamp(24px,5vw,64px); + max-width: 1120px; + margin: auto; + padding: clamp(20px,4vw,42px); + background: linear-gradient(145deg,#15182d,#101322); + border: 1px solid var(--border); + border-radius: var(--radius) +} +.instance-detail h1{ + margin: 0; + font-size: clamp(2rem,5vw,3.6rem) +} +.detail-actions{ + display: flex; + flex-wrap: wrap; + justify-content: flex-end; + gap: 9px; + margin: 0 0 30px +} +.detail-actions form{ + display: block +} +.detail-actions button,.action-restore{ + margin: 0; + border: 1px solid #ffffff22; + border-radius: 9px; + padding: 10px 14px; + font-weight: 850 +} +.action-start{ + background: linear-gradient(120deg,#117b58,#2bd69d) +} +.action-stop{ + background: linear-gradient(120deg,#7d1d37,#f04f6c) +} +.action-backup{ + background: linear-gradient(120deg,#164f96,#24b9ee) +} +.action-update{ + background: linear-gradient(120deg,#93640d,#ffc644) +} +button:disabled{ + opacity: .42; + cursor: not-allowed +} +.instance-info{ + display: grid; + grid-template-columns: repeat(2,minmax(0,1fr)); + gap: 12px; + margin: 28px 0 0 +} +.instance-info div{ + padding: 14px; + background: #0b1020; + border: 1px solid var(--border); + border-radius: 10px +} +.instance-info dt{ + color: var(--text-secondary); + font-size: .8rem +} +.instance-info dd{ + margin: 5px 0 0; + font-weight: 750 +} +.instance-detail-lower{ + display: grid; + grid-template-columns: 1fr 1fr; + gap: 22px; + max-width: 1120px; + margin: 22px auto +} +.backup-list{ + display: grid; + gap: 9px; + margin: 0; + padding: 0; + list-style: none +} +.backup-list li{ + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 12px; + background: #0b1020; + border: 1px solid var(--border); + border-radius: 9px +} +.backup-list small{ + display: block; + color: var(--text-secondary) +} +.notice{ + padding: 12px 14px; + background: #103427; + border-left: 4px solid var(--status-running); + border-radius: 7px +} +@media(max-width:700px){ + .instance-detail,.instance-detail-lower{ + grid-template-columns: 1fr + } + .instance-detail aside{ + max-width: 300px; + margin: auto; + width: 100% + } + .detail-actions{ + justify-content: flex-start + } + .instance-info{ + grid-template-columns: 1fr + } +} +.catalog-tools{ + display: flex; + align-items: center; + gap: 12px +} +.scan-button,.deploy-band button{ + border: 1px solid #20d9f388; + border-radius: 10px; + padding: 10px 15px; + background: linear-gradient(110deg,#123b51,#55264d); + color: #fff; + font-weight: 800; + cursor: pointer +} +.catalog-grid{ + display: grid; + grid-template-columns: repeat(auto-fill,minmax(150px,1fr)); + gap: 22px; + max-width: 1400px; + margin: auto +} +.catalog-card{ + text-decoration: none; + min-width: 0 +} +.catalog-cover,.game-cover{ + position: relative; + overflow: hidden; + background: linear-gradient(135deg,#25233f,#101322); + border: 1px solid var(--border); + border-radius: 12px; + box-shadow: 0 12px 26px #0005; + aspect-ratio: 2/3 +} +.catalog-cover img,.game-cover img{ + position: relative; + z-index: 1; + width: 100%; + height: 100%; + object-fit: cover +} +.cover-fallback,.game-cover>span{ + position: absolute; + inset: 0; + display: grid; + place-items: center; + color: #20d9f366; + font-size: 3rem +} +.catalog-card h2{ + margin: 10px 3px 0; + font-size: 1rem; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis +} +.catalog-card:hover .catalog-cover{ + border-color: var(--accent-cyan); + box-shadow: 0 16px 32px #0008,0 0 22px #20d9f322; + transform: translateY(-3px) +} +.catalog-scan-result{ + max-width: 1400px; + margin: 0 auto 22px; + padding: 14px 18px; + background: #12314466; + border: 1px solid #20d9f366; + border-radius: 12px +} +.catalog-scan-result ul{ + margin: 8px 0 0; + padding-left: 20px; + color: var(--text-secondary) +} +.back-link{ + display: inline-block; + margin: 0 auto 22px; + max-width: 1400px; + color: var(--accent-cyan); + text-decoration: none +} +.game-detail{ + display: grid; + grid-template-columns: minmax(210px,330px) minmax(0,1fr); + gap: clamp(28px,5vw,70px); + max-width: 1120px; + margin: auto; + padding: clamp(20px,4vw,42px); + background: linear-gradient(145deg,#15182d,#101322); + border: 1px solid var(--border); + border-radius: var(--radius) +} +.game-detail h1{ + margin: 0; + font-size: clamp(2rem,5vw,3.6rem) +} +.game-description{ + max-width: 70ch; + color: var(--text-secondary); + font-size: 1.05rem +} +.deploy-band{ + margin-top: 12px; + padding: 12px; + background: #0b1020; + border: 1px solid var(--border); + border-radius: 10px +} +.deploy-band button{ + width: 100%; + cursor: not-allowed; + opacity: .7 +} +.requirements-grid{ + display: grid; + grid-template-columns: repeat(2,minmax(0,1fr)); + gap: 16px; + margin-top: 30px +} +.requirements-grid section{ + padding: 18px; + background: #0c1020; + border: 1px solid var(--border); + border-radius: 12px +} +.requirements-grid h2{ + margin: 0 0 12px; + color: var(--accent-cyan); + font-size: 1rem +} +.requirements-grid dl{ + display: grid; + grid-template-columns: auto 1fr; + gap: 8px 12px; + margin: 0 +} +.requirements-grid dt{ + color: var(--text-secondary) +} +.requirements-grid dd{ + margin: 0; + text-align: right +} +@media(max-width:700px){ + .catalog-header{ + align-items: start; + flex-direction: column + } + .catalog-tools{ + width: 100%; + justify-content: space-between + } + .catalog-tools .instance-search{ + flex: 1 + } + .game-detail{ + grid-template-columns: 1fr + } + .game-detail aside{ + max-width: 300px; + margin: auto; + width: 100% + } + .requirements-grid{ + grid-template-columns: 1fr + } +} diff --git a/internal/web/static/app.v1.js b/internal/web/static/app.v1.js index b9035a9..97d78a3 100644 --- a/internal/web/static/app.v1.js +++ b/internal/web/static/app.v1.js @@ -34,10 +34,30 @@ if (deploymentForm) { deploymentForm.addEventListener("submit", (event) => { event.preventDefault(); const submit = deploymentForm.querySelector("[data-deployment-submit]"); - if (submit) { submit.disabled = true; submit.textContent = "…"; } + if (submit) { + submit.disabled = true; + submit.textContent = "…"; + } let progress = document.querySelector("[data-deployment-progress]"); - if (!progress) { progress = document.createElement("section"); progress.className = "panel deployment-progress"; progress.dataset.deploymentProgress = ""; deploymentForm.after(progress); } - progress.replaceChildren(); const heading = document.createElement("h2"); heading.textContent = "Deployment progress"; const status = document.createElement("p"); status.dataset.operationStatus = ""; const list = document.createElement("ol"); list.dataset.operationSteps = ""; const error = document.createElement("p"); error.className = "error"; error.hidden = true; const link = document.createElement("a"); link.hidden = true; progress.append(heading, status, list, error, link); + if (!progress) { + progress = document.createElement("section"); + progress.className = "panel deployment-progress"; + progress.dataset.deploymentProgress = ""; + deploymentForm.after(progress); + } + progress.replaceChildren(); + const heading = document.createElement("h2"); + heading.textContent = "Deployment progress"; + const status = document.createElement("p"); + status.dataset.operationStatus = ""; + const list = document.createElement("ol"); + list.dataset.operationSteps = ""; + const error = document.createElement("p"); + error.className = "error"; + error.hidden = true; + const link = document.createElement("a"); + link.hidden = true; + progress.append(heading, status, list, error, link); fetch(deploymentForm.action || window.location.href, { method: "POST", body: new FormData(deploymentForm), credentials: "same-origin", headers: { Accept: "application/json" } }).then((response) => response.ok ? response.json() : Promise.reject(new Error("Deployment could not be queued."))).then(({ operation_id, instance_id }) => { progress.hidden = false; deploymentForm.hidden = true; const poll = () => fetch(`/api/v1/operations/${encodeURIComponent(operation_id)}`, { credentials: "same-origin" }).then((r) => r.ok ? r.json() : Promise.reject(new Error("Progress unavailable."))).then((operation) => { diff --git a/internal/web/static/mobile-fixes.css b/internal/web/static/mobile-fixes.css index 1cab801..cb7bd84 100644 --- a/internal/web/static/mobile-fixes.css +++ b/internal/web/static/mobile-fixes.css @@ -1,5 +1,14 @@ @media (max-width: 760px) { - html, body { overflow-x: hidden; } - .mobile-header { width: 100vw; margin-left: calc(50% - 50vw); margin-right: 0; } - .tabs { flex-wrap: wrap; overflow-x: visible; } + html, body { + overflow-x: hidden; + } + .mobile-header { + width: 100vw; + margin-left: calc(50% - 50vw); + margin-right: 0; + } + .tabs { + flex-wrap: wrap; + overflow-x: visible; + } } diff --git a/internal/web/static/theme.v1.css b/internal/web/static/theme.v1.css index f89d850..22e4816 100644 --- a/internal/web/static/theme.v1.css +++ b/internal/web/static/theme.v1.css @@ -1,279 +1,458 @@ .app-main { - isolation: isolate; - position: relative; + isolation: isolate; + position: relative; } - .app-main::before { - background: url("/static/dogama-logo.png") center / min(58vw, 720px) no-repeat; - content: ""; - filter: saturate(.8); - inset: 0; - opacity: .025; - pointer-events: none; - position: fixed; - z-index: 0; + background: url("/static/dogama-logo.png") center / min(58vw, 720px) no-repeat; + content: ""; + filter: saturate(.8); + inset: 0; + opacity: .025; + pointer-events: none; + position: fixed; + z-index: 0; } - .app-main::after { background-image: - linear-gradient(90deg, #20d9f312 1px, transparent 1px), - linear-gradient(#f02fb80d 1px, transparent 1px); - background-size: 64px 64px; - bottom: 0; - content: ""; - height: 34vh; - left: 220px; - mask-image: linear-gradient(transparent, #000); - opacity: .45; - perspective: 400px; - pointer-events: none; - position: fixed; - right: 0; - transform: perspective(380px) rotateX(62deg) scale(1.25); - transform-origin: bottom; - z-index: 0; + linear-gradient(90deg, #20d9f312 1px, transparent 1px), + linear-gradient(#f02fb80d 1px, transparent 1px); + background-size: 64px 64px; + bottom: 0; + content: ""; + height: 34vh; + left: 220px; + mask-image: linear-gradient(transparent, #000); + opacity: .45; + perspective: 400px; + pointer-events: none; + position: fixed; + right: 0; + transform: perspective(380px) rotateX(62deg) scale(1.25); + transform-origin: bottom; + z-index: 0; } - .app-main > * { - position: relative; - z-index: 1; + position: relative; + z-index: 1; } - .sidebar { - padding: 26px 8px 18px; - width: 200px; + padding: 26px 8px 18px; + width: 200px; } - .app-main { - margin-left: 200px; + margin-left: 200px; } - .app-main::after { - left: 200px; + left: 200px; } - .brand { - gap: 8px; - padding: 0 8px 28px; + gap: 8px; + padding: 0 8px 28px; } - .brand img { - height: 76px; - width: 76px; + height: 76px; + width: 76px; } - .brand strong { - font-size: 1.45rem; + font-size: 1.45rem; } - .brand small { - display: none !important; + display: none !important; } - .sidebar nav { - gap: 2px; - margin: 8px -8px 0; + gap: 2px; + margin: 8px -8px 0; } - .sidebar nav a, .sidebar nav span.disabled { - align-items: center; - display: flex; - gap: 11px; - min-height: 52px; - padding-left: 24px; - padding-right: 20px; + align-items: center; + display: flex; + gap: 11px; + min-height: 52px; + padding-left: 24px; + padding-right: 20px; } - .sidebar nav a.active { - border-left-color: var(--accent-cyan); - border-radius: 0 10px 10px 0; - border-right-color: var(--accent-magenta); - box-shadow: inset 4px 0 var(--accent-cyan), inset -3px 0 var(--accent-magenta), 0 0 22px #cf2fc326; + border-left-color: var(--accent-cyan); + border-radius: 0 10px 10px 0; + border-right-color: var(--accent-magenta); + box-shadow: inset 4px 0 var(--accent-cyan), inset -3px 0 var(--accent-magenta), 0 0 22px #cf2fc326; } - -.sidebar nav a::before, +.sidebar nav a: :before, .sidebar nav span.disabled::before { - color: #a9b4ca; - display: grid; - flex: 0 0 22px; - font-size: 1rem; - height: 22px; - place-items: center; - width: 22px; + color: #a9b4ca; + display: grid; + flex: 0 0 22px; + font-size: 1rem; + height: 22px; + place-items: center; + width: 22px; } - .sidebar nav a.active::before { - color: var(--accent-cyan); + color: var(--accent-cyan); +} +.nav-instances::before { + content: "\25A4"; +} +.nav-catalog::before { + content: "\2667"; +} +.nav-backups::before { + content: "\25A3"; +} +.nav-settings::after { + content: "\203A"; + margin-left: auto; } - -.nav-instances::before { content: "\25A4"; } -.nav-catalog::before { content: "\2667"; } -.nav-backups::before { content: "\25A3"; } -.nav-settings::after { content: "\203A"; margin-left: auto; } - .sidebar nav span.disabled { - border: 1px solid transparent; - border-radius: 10px; - color: #a5aec2; - cursor: not-allowed; - font-weight: 650; - opacity: .72; - padding-bottom: 11px; - padding-top: 11px; + border: 1px solid transparent; + border-radius: 10px; + color: #a5aec2; + cursor: not-allowed; + font-weight: 650; + opacity: .72; + padding-bottom: 11px; + padding-top: 11px; } - .account-card { - background: #0d1120cc; - border: 1px solid #252c45; - border-radius: 10px; - margin-bottom: 10px; - padding: 13px 12px; + background: #0d1120cc; + border: 1px solid #252c45; + border-radius: 10px; + margin-bottom: 10px; + padding: 13px 12px; } - .account .avatar { - background: #151126; - border: 1px solid var(--accent-magenta); - color: #ef54cf; - height: 42px; - width: 42px; + background: #151126; + border: 1px solid var(--accent-magenta); + color: #ef54cf; + height: 42px; + width: 42px; } - .account-chevron { - color: var(--text-secondary); - margin-left: auto; + color: var(--text-secondary); + margin-left: auto; } - .account form { - border-top: 1px solid #22283e; - padding-top: 10px; + border-top: 1px solid #22283e; + padding-top: 10px; } - .account .signout-button { - align-items: center; - background: transparent; - border: 0; - box-shadow: none; - color: var(--text-secondary); - display: flex; - gap: 11px; - padding: 10px 12px; - width: 100%; + align-items: center; + background: transparent; + border: 0; + box-shadow: none; + color: var(--text-secondary); + display: flex; + gap: 11px; + padding: 10px 12px; + width: 100%; } - .account .signout-button:hover { - color: var(--text-primary); + color: var(--text-primary); +} +.nav-dashboard::before { + content: "⌂"; +} +.nav-audit::before { + content: "▣"; +} +.nav-settings::before { + content: "⚙"; } - -.nav-dashboard::before { content: "⌂"; } -.nav-audit::before { content: "▣"; } -.nav-settings::before { content: "⚙"; } - .page-heading { - align-items: center; - gap: 24px; + align-items: center; + gap: 24px; } - .instance-search { - max-width: 320px; - position: relative; - width: min(100%, 320px); + max-width: 320px; + position: relative; + width: min(100%, 320px); } - .instance-search input { - background: #090c19cc; - border-color: #3c4565; - border-radius: 9px; - padding-left: 42px; - padding-right: 42px; + background: #090c19cc; + border-color: #3c4565; + border-radius: 9px; + padding-left: 42px; + padding-right: 42px; } - .instance-search::before { - color: var(--text-secondary); - content: "⌕"; - font-size: 1.35rem; - left: 13px; - position: absolute; - top: 5px; - z-index: 1; + color: var(--text-secondary); + content: "⌕"; + font-size: 1.35rem; + left: 13px; + position: absolute; + top: 5px; + z-index: 1; } - .instance-search kbd { - background: #15192b; - border: 1px solid #313753; - border-radius: 5px; - color: var(--text-secondary); - font: inherit; - padding: 0 7px; - position: absolute; - right: 10px; - top: 9px; + background: #15192b; + border: 1px solid #313753; + border-radius: 5px; + color: var(--text-secondary); + font: inherit; + padding: 0 7px; + position: absolute; + right: 10px; + top: 9px; } - .summary-grid { - margin: 0 auto 22px; + margin: 0 auto 22px; } - .summary-grid article { - background: linear-gradient(145deg, #111628dd, #0b0f1ddd); - min-height: 76px; + background: linear-gradient(145deg, #111628dd, #0b0f1ddd); + min-height: 76px; } - .instance-grid { - position: relative; + position: relative; } - .instance-card[hidden] { - display: none; + display: none; } - .search-empty { - color: var(--text-secondary); - padding: 48px 20px; - text-align: center; + color: var(--text-secondary); + padding: 48px 20px; + text-align: center; } - button, .link-button { - background: linear-gradient(100deg, #782fd9, #e52eb0 65%, #14c9ed); - border: 1px solid #ef45c2; - box-shadow: 0 0 18px #f02fb81f; + background: linear-gradient(100deg, #782fd9, #e52eb0 65%, #14c9ed); + border: 1px solid #ef45c2; + box-shadow: 0 0 18px #f02fb81f; } - .button-secondary { - background: #11162a; - border-color: #38415f; + background: #11162a; + border-color: #38415f; + box-shadow: none; +} +.tabs { + background: #0c1020dd; + border-color: #303955; +} +@media (max-width: 760px) { + .app-main::after { + left: 0; + } + .page-heading { + align-items: stretch; + display: grid; + } + .instance-search { + max-width: none; + width: 100%; + } +} +.brand.brand-image { + display: block; + padding: 0 14px 28px; +} +.brand.brand-image img { + display: block; + width: min(100%, 156px); + height: auto; + border-radius: 0; box-shadow: none; } - -.tabs { - background: #0c1020dd; - border-color: #303955; +.dashboard-header { + margin-bottom: 26px; } - -@media (max-width: 760px) { - .app-main::after { left: 0; } - .page-heading { align-items: stretch; display: grid; } - .instance-search { max-width: none; width: 100%; } +.dashboard-section { + max-width: 1400px; + margin: 0 auto 24px; +} +.summary-grid { + grid-template-columns: repeat(5, minmax(0,1fr)); +} +.summary-card { + min-width: 0; + overflow: hidden; + position: relative; +} +.summary-card::after { + content: ""; + position: absolute; + inset: auto -12px -32px auto; + width: 70px; + height: 70px; + border-radius: 50%; + background: currentColor; + filter: blur(26px); + opacity: .18; +} +.summary-card .summary-icon { + float: right; + display: grid; + place-items: center; + width: 32px; + height: 32px; + border: 1px solid currentColor; + border-radius: 9px; + font-size: 1rem; +} +.summary-card.total { + color: var(--accent-cyan); +} +.summary-card.running { + color: var(--status-running); +} +.summary-card.stopped { + color: var(--status-error); +} +.summary-card.updating { + color: var(--status-warning); +} +.summary-card.error { + color: #ff3c5f; +} +.summary-card > span:not(.summary-icon) { + color: var(--text-secondary); +} +.summary-card strong { + display: block; + color: var(--text-primary); +} +.instance-card h3 { + margin: 5px 0 7px; + font-size: 1.16rem; +} +.dashboard-lower { + display: grid; + grid-template-columns: repeat(2,minmax(0,1fr)); + gap: 22px; + max-width: 1400px; + margin: auto; +} +.dashboard-lower .panel { + margin: 0; + max-width: none; +} +.panel-empty { + color: var(--text-secondary); + margin: 0; + padding: 26px 0; +} +.activity-list { + display: grid; + gap: 14px; + list-style: none; + margin: 0; + padding: 0; +} +.activity-list li { + display: grid; + grid-template-columns: 32px minmax(0,1fr) auto; + align-items: center; + gap: 11px; + padding-bottom: 14px; + border-bottom: 1px solid #262d47; +} +.activity-list li:last-child { + border: 0; + padding-bottom: 0; +} +.activity-icon { + display: grid; + place-items: center; + width: 30px; + height: 30px; + color: var(--accent-cyan); + background: #102237; + border: 1px solid #245574; + border-radius: 8px; +} +.activity-list strong,.activity-list span { + display: block; +} +.activity-list div span,.activity-list time { + color: var(--text-secondary); + font-size: .78rem; +} +.activity-list time { + white-space: nowrap; +} +.system-list { + display: grid; + gap: 0; + margin: 0; +} +.system-list > div { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + min-height: 44px; + border-bottom: 1px solid #262d47; +} +.system-list > div:last-child { + border: 0; +} +.system-list dt { + color: var(--text-secondary); +} +.system-list dd { + display: flex; + align-items: center; + justify-content: end; + gap: 8px; + margin: 0; + text-align: right; +} +.system-list .is-good::before,.system-list .is-bad::before { + content: ""; + width: 8px; + height: 8px; + border-radius: 50%; + background: currentColor; + box-shadow: 0 0 10px currentColor; +} +.system-list .is-good { + color: var(--status-running); +} +.system-list .is-bad { + color: var(--status-error); +} +.storage-track { + display: block; + width: 90px; + height: 6px; + overflow: hidden; + border-radius: 99px; + background: #272e49; +} +.storage-track i { + display: block; + height: 100%; + border-radius: inherit; + background: linear-gradient(90deg,var(--accent-cyan),var(--accent-magenta)); +} +@media (max-width: 1050px) { + .summary-grid { + grid-template-columns: repeat(3,minmax(0,1fr)); + } + .dashboard-lower { + grid-template-columns: 1fr; + } +} +@media (max-width: 600px) { + .summary-grid { + grid-template-columns: repeat(2,minmax(0,1fr)); + } + .summary-card:last-child { + grid-column: span 2; + } + .activity-list li { + grid-template-columns: 32px minmax(0,1fr); + } + .activity-list time { + grid-column: 2; + } + .system-list > div { + align-items: flex-start; + flex-direction: column; + padding: 10px 0; + gap: 3px; + } + .system-list dd { + justify-content: start; + text-align: left; + } } - -.brand.brand-image { display:block; padding:0 14px 28px; } -.brand.brand-image img { display:block; width:min(100%, 156px); height:auto; border-radius:0; box-shadow:none; } -.dashboard-header { margin-bottom:26px; } -.dashboard-section { max-width:1400px; margin:0 auto 24px; } -.summary-grid { grid-template-columns:repeat(5, minmax(0,1fr)); } -.summary-card { min-width:0; overflow:hidden; position:relative; } -.summary-card::after { content:""; position:absolute; inset:auto -12px -32px auto; width:70px; height:70px; border-radius:50%; background:currentColor; filter:blur(26px); opacity:.18; } -.summary-card .summary-icon { float:right; display:grid; place-items:center; width:32px; height:32px; border:1px solid currentColor; border-radius:9px; font-size:1rem; } -.summary-card.total { color:var(--accent-cyan); }.summary-card.running { color:var(--status-running); }.summary-card.stopped { color:var(--status-error); }.summary-card.updating { color:var(--status-warning); }.summary-card.error { color:#ff3c5f; } -.summary-card > span:not(.summary-icon) { color:var(--text-secondary); }.summary-card strong { display:block; color:var(--text-primary); } -.instance-card h3 { margin:5px 0 7px; font-size:1.16rem; } -.dashboard-lower { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:22px; max-width:1400px; margin:auto; } -.dashboard-lower .panel { margin:0; max-width:none; } -.panel-empty { color:var(--text-secondary); margin:0; padding:26px 0; } -.activity-list { display:grid; gap:14px; list-style:none; margin:0; padding:0; } -.activity-list li { display:grid; grid-template-columns:32px minmax(0,1fr) auto; align-items:center; gap:11px; padding-bottom:14px; border-bottom:1px solid #262d47; } -.activity-list li:last-child { border:0; padding-bottom:0; }.activity-icon { display:grid; place-items:center; width:30px; height:30px; color:var(--accent-cyan); background:#102237; border:1px solid #245574; border-radius:8px; }.activity-list strong,.activity-list span { display:block; }.activity-list div span,.activity-list time { color:var(--text-secondary); font-size:.78rem; }.activity-list time { white-space:nowrap; } -.system-list { display:grid; gap:0; margin:0; }.system-list > div { display:flex; align-items:center; justify-content:space-between; gap:16px; min-height:44px; border-bottom:1px solid #262d47; }.system-list > div:last-child { border:0; }.system-list dt { color:var(--text-secondary); }.system-list dd { display:flex; align-items:center; justify-content:end; gap:8px; margin:0; text-align:right; }.system-list .is-good::before,.system-list .is-bad::before { content:""; width:8px; height:8px; border-radius:50%; background:currentColor; box-shadow:0 0 10px currentColor; }.system-list .is-good { color:var(--status-running); }.system-list .is-bad { color:var(--status-error); }.storage-track { display:block; width:90px; height:6px; overflow:hidden; border-radius:99px; background:#272e49; }.storage-track i { display:block; height:100%; border-radius:inherit; background:linear-gradient(90deg,var(--accent-cyan),var(--accent-magenta)); } -@media (max-width: 1050px) { .summary-grid { grid-template-columns:repeat(3,minmax(0,1fr)); } .dashboard-lower { grid-template-columns:1fr; } } -@media (max-width: 600px) { .summary-grid { grid-template-columns:repeat(2,minmax(0,1fr)); } .summary-card:last-child { grid-column:span 2; } .activity-list li { grid-template-columns:32px minmax(0,1fr); } .activity-list time { grid-column:2; } .system-list > div { align-items:flex-start; flex-direction:column; padding:10px 0; gap:3px; } .system-list dd { justify-content:start; text-align:left; } } diff --git a/internal/web/templates/account.html b/internal/web/templates/account.html index 8a5a78c..8df20c1 100644 --- a/internal/web/templates/account.html +++ b/internal/web/templates/account.html @@ -1 +1,150 @@ -{{define "account.html"}}
Personal account preferences.
Signed in as {{.User.Username}}.
Important failures are enabled by default. Routine start, stop and backup messages are disabled until you opt in.
+ Personal account preferences. +
++ Signed in as + + {{.User.Username}} + + . +
+ ++ Important failures are enabled by default. Routine start, stop and backup messages are disabled until you opt in. +
+ +{{.Msg "audit.eyebrow"}}
{{.AuditEventCount}} {{.Msg "audit.events"}} · {{.Msg "audit.timezone"}} {{.AuditFilter.Timezone}}
{{.Msg "audit.filters"}}
{{.Msg "audit.results"}}
{{.Msg "audit.timezone"}} {{.AuditFilter.Timezone}}
{{.Action}} · {{.OutcomeLabel}}{{.Details}}
+ {{.Msg "audit.eyebrow"}} +
++ {{.AuditEventCount}} + {{.Msg "audit.events"}} + · + {{.Msg "audit.timezone"}} + {{.AuditFilter.Timezone}} +
++ {{.Msg "audit.filters"}} +
++ {{.Msg "audit.results"}} +
++ {{.Msg "audit.timezone"}} + {{.AuditFilter.Timezone}} +
+
+ {{.Action}}
+
+ ·
+ {{.OutcomeLabel}}
+
+ + {{.Details}} +
+{{.Msg "catalog.eyebrow"}}
{{.CatalogDetail.Game.Description}}
+ {{.Msg "catalog.eyebrow"}} +
++ {{.CatalogDetail.Game.Description}} +
+{{.Msg "catalog.eyebrow"}}
{{.Msg "catalog.no_match"}}
{{else}}{{.Msg "catalog.empty_admin"}}
{{end}}+ {{.Msg "catalog.eyebrow"}} +
++ {{.Msg "catalog.no_match"}} +
+ {{else}} ++ {{.Msg "catalog.empty_admin"}} +
+ {{end}} +
-
-
+
+
+ {{.Msg "dashboard.eyebrow"}}
{{.Msg "dashboard.introduction"}}
{{.Msg "dashboard.instances_eyebrow"}}
{{.Preview.Game.Name}}
{{.ID}}
{{.Msg "dashboard.no_match"}}
{{else}}{{.Msg "dashboard.empty_copy"}}
{{.Msg "dashboard.activity_eyebrow"}}
{{.Msg "dashboard.no_activity"}}
{{end}}{{.Msg "dashboard.system_eyebrow"}}
+ {{.Msg "dashboard.eyebrow"}} +
++ {{.Msg "dashboard.introduction"}} +
++ {{.Msg "dashboard.instances_eyebrow"}} +
++ {{.Preview.Game.Name}} +
++ {{.ID}} +
++ {{.Msg "dashboard.no_match"}} +
+ {{else}} ++ {{.Msg "dashboard.empty_copy"}} +
++ {{.Msg "dashboard.activity_eyebrow"}} +
++ {{.Msg "dashboard.no_activity"}} +
+ {{end}} ++ {{.Msg "dashboard.system_eyebrow"}} +
+{{.InstanceDetail.Message}}
{{end}}{{.InstanceDetail.Instance.Preview.Game.Name}}
{{.InstanceDetail.Instance.Preview.Description}}
{{statusLabel .InstanceDetail.Instance.LifecycleState}}
{{.InstanceDetail.Live.ServerInfo.Name}} · {{.InstanceDetail.Live.ServerInfo.GameVersion}}
{{end}}{{.Msg "instance.no_backups"}}
{{end}}{{.PlayerID}}{{if $.InstanceDetail.CanKick}}{{end}}{{if $.InstanceDetail.CanBan}}{{end}}{{.Msg "instance.no_banned_players"}}
{{end}}{{else if .InstanceDetail.BansUnavailable}}{{.Msg "instance.unavailable"}}
{{else if and .InstanceDetail.CanUnban (not .InstanceDetail.Live.Capabilities.list_bans)}}{{end}}{{else}}{{.Msg "instance.module_unavailable"}}
{{end}}+ {{.InstanceDetail.Message}} +
+ {{end}} ++ {{.InstanceDetail.Instance.Preview.Game.Name}} +
++ {{.InstanceDetail.Instance.Preview.Description}} +
++ + + {{statusLabel .InstanceDetail.Instance.LifecycleState}} +
++ {{.InstanceDetail.Live.ServerInfo.Name}} + · + {{.InstanceDetail.Live.ServerInfo.GameVersion}} +
+ {{end}} ++ {{.Msg "instance.no_backups"}} +
+ {{end}} +
+ {{.PlayerID}}
+
+ {{if $.InstanceDetail.CanKick}}
+
+ {{end}}
+ {{if $.InstanceDetail.CanBan}}
+
+ {{end}}
+ + {{.Msg "instance.no_banned_players"}} +
+ {{end}} + {{else if .InstanceDetail.BansUnavailable}} ++ {{.Msg "instance.unavailable"}} +
+ {{else if and .InstanceDetail.CanUnban (not .InstanceDetail.Live.Capabilities.list_bans)}} + + {{end}} + {{else}} ++ {{.Msg "instance.module_unavailable"}} +
+ {{end}} +
{{.Msg "brand.control"}}
{{.Msg "brand.control"}}
{{if .Error}}{{.Error}}
{{end}}
+ + {{.Msg "brand.control"}} +
++ {{.Msg "brand.control"}} +
+ {{if .Error}} ++ {{.Error}} +
+ {{end}} + +{{.Msg "settings.eyebrow"}}
{{.Msg "settings.introduction"}}
{{.Msg "settings.web_help"}}
{{.Msg "settings.channels_help"}}
{{.Msg "settings.notification_language_help"}}
{{.Msg "settings.gotify_help"}}
{{.Msg "settings.audit_help"}}
{{.Msg "settings.view_audit"}}{{.Msg "settings.container_labels_help"}}
+ {{.Msg "settings.eyebrow"}} +
++ {{.Msg "settings.introduction"}} +
++ {{.Msg "settings.web_help"}} +
+ ++ {{.Msg "settings.channels_help"}} +
++ {{.Msg "settings.notification_language_help"}} +
+ ++ {{.Msg "settings.gotify_help"}} +
+ ++ {{.Msg "settings.audit_help"}} +
+ + {{.Msg "settings.view_audit"}} + + + ++ {{.Msg "settings.container_labels_help"}} +
+ +
{{.Msg "brand.control"}}
{{.Msg "setup.introduction"}}
{{if .Error}}{{.Error}}
{{end}}
+ + {{.Msg "brand.control"}} +
++ {{.Msg "setup.introduction"}} +
+ {{if .Error}} ++ {{.Error}} +
+ {{end}} + +Administration
{{.Msg "template_repositories.introduction"}}
{{.Error}}
{{end}}{{.Msg "template_repositories.not_implemented"}}
{{if .TemplateRepositories}}{{.Msg "template_repositories.empty"}}
{{end}}+ Administration +
++ {{.Msg "template_repositories.introduction"}} +
++ {{.Error}} +
+ {{end}} + ++ {{.Msg "template_repositories.not_implemented"}} +
+ {{if .TemplateRepositories}} ++ {{.Msg "template_repositories.empty"}} +
+ {{end}} +Administration
Create identities, assign global roles and control access to each game server.
{{if .Disabled}}Disabled{{else}}Active{{end}} · {{.Role}}
No game-server instance is currently registered.
{{end}} -{{else}}Administrators have implicit access to every instance.
{{end}} -No users found.
+ Administration +
++ Create identities, assign global roles and control access to each game server. +
++ {{if .Disabled}} + Disabled + {{else}} + Active + {{end}} + · + {{.Role}} +
++ No game-server instance is currently registered. +
+ {{end}} + {{else}} ++ Administrators have implicit access to every instance. +
+ {{end}} ++ No users found. +
+