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"}}Settings · DoGaMa{{template "sidebar" .}}

Settings

Personal account preferences.

Profile

Signed in as {{.User.Username}}.

Security

Language

Email notifications

Important failures are enabled by default. Routine start, stop and backup messages are disabled until you opt in.

{{end}} +{{define "account.html"}} + + + + + + + Settings · DoGaMa + + + + + {{template "sidebar" .}} +
+
+

+ Settings +

+

+ Personal account preferences. +

+
+ +
+

+ Profile +

+

+ Signed in as + + {{.User.Username}} + + . +

+
+ + + +
+
+
+

+ Security +

+
+ + + + + +
+
+
+

+ Language +

+
+ + + +
+
+
+

+ Email notifications +

+

+ Important failures are enabled by default. Routine start, stop and backup messages are disabled until you opt in. +

+
+ + + + + + + + + +
+
+
+ + + +{{end}} diff --git a/internal/web/templates/audit.html b/internal/web/templates/audit.html index f727701..8f9c8b7 100644 --- a/internal/web/templates/audit.html +++ b/internal/web/templates/audit.html @@ -1 +1,245 @@ -{{define "audit.html"}}{{.Title}} · DoGaMa{{template "sidebar" .}}

{{.Msg "audit.eyebrow"}}

{{.Msg "audit.title"}}

{{.AuditEventCount}} {{.Msg "audit.events"}} · {{.Msg "audit.timezone"}} {{.AuditFilter.Timezone}}

{{.Msg "audit.filters"}}

{{.Msg "audit.filter"}}

{{.Msg "audit.reset"}}

{{.Msg "audit.results"}}

{{.AuditEventCount}} {{.Msg "audit.events"}}

{{.Msg "audit.timezone"}} {{.AuditFilter.Timezone}}

{{if .AuditEvents}}
{{range .AuditEvents}}
{{.ActionLabel}}{{.Action}} · {{.OutcomeLabel}}
{{if .Actor}}{{.Actor}}{{else}}—{{end}}{{if .Instance}}{{.Instance}}{{else}}—{{end}}
{{if .Details}}
{{$.Msg "audit.details"}}

{{.Details}}

{{end}}
{{end}}
{{else}}
◈

{{if .AuditQuery}}{{.Msg "audit.empty_filtered"}}{{else}}{{.Msg "audit.empty"}}{{end}}

{{end}}{{if gt .AuditPages 1}}{{end}}
{{end}} +{{define "audit.html"}} + + + + + + + {{.Title}} + · DoGaMa + + + + + + {{template "sidebar" .}} +
+
+
+

+ {{.Msg "audit.eyebrow"}} +

+

+ {{.Msg "audit.title"}} +

+

+ {{.AuditEventCount}} + {{.Msg "audit.events"}} + · + {{.Msg "audit.timezone"}} + {{.AuditFilter.Timezone}} +

+
+
+
+
+
+

+ {{.Msg "audit.filters"}} +

+

+ {{.Msg "audit.filter"}} +

+
+ + {{.Msg "audit.reset"}} + +
+
+ + + + + + + + + +
+
+
+
+
+

+ {{.Msg "audit.results"}} +

+

+ {{.AuditEventCount}} + {{.Msg "audit.events"}} +

+
+

+ {{.Msg "audit.timezone"}} + {{.AuditFilter.Timezone}} +

+
+ {{if .AuditEvents}} +
+ {{range .AuditEvents}} +
+ +
+ + {{.ActionLabel}} + + + + {{.Action}} + + · + {{.OutcomeLabel}} + +
+
+ + {{if .Actor}} + {{.Actor}} + {{else}} + — + {{end}} + + + {{if .Instance}} + {{.Instance}} + {{else}} + — + {{end}} + +
+ {{if .Details}} +
+ + {{$.Msg "audit.details"}} + +

+ {{.Details}} +

+
+ {{end}} +
+ {{end}} +
+ {{else}} +
+ + ◈ + +

+ {{if .AuditQuery}} + {{.Msg "audit.empty_filtered"}} + {{else}} + {{.Msg "audit.empty"}} + {{end}} +

+
+ {{end}} + {{if gt .AuditPages 1}} + + {{end}} +
+
+ + + +{{end}} diff --git a/internal/web/templates/catalog-detail.html b/internal/web/templates/catalog-detail.html index 661445a..d458801 100644 --- a/internal/web/templates/catalog-detail.html +++ b/internal/web/templates/catalog-detail.html @@ -1 +1,108 @@ -{{define "catalog-detail.html"}}{{.CatalogDetail.Game.Name}} · DoGaMa{{template "sidebar" .}}
← {{.Msg "catalog.back"}}

{{.Msg "catalog.eyebrow"}}

{{.CatalogDetail.Game.Name}}

{{.CatalogDetail.Game.Description}}

{{.Msg "catalog.minimum"}}

CPU
{{.CatalogDetail.Requirements.Minimum.CPUCores}}
{{.Msg "catalog.memory"}}
{{.CatalogDetail.Requirements.Minimum.MemoryMB}} MB
{{.Msg "catalog.storage"}}
{{.CatalogDetail.Requirements.Minimum.StorageGB}} GB

{{.Msg "catalog.recommended"}}

CPU
{{.CatalogDetail.Requirements.Recommended.CPUCores}}
{{.Msg "catalog.memory"}}
{{.CatalogDetail.Requirements.Recommended.MemoryMB}} MB
{{.Msg "catalog.storage"}}
{{.CatalogDetail.Requirements.Recommended.StorageGB}} GB
{{end}} +{{define "catalog-detail.html"}} + + + + + + + {{.CatalogDetail.Game.Name}} + · DoGaMa + + + + + + {{template "sidebar" .}} +
+ + ← + {{.Msg "catalog.back"}} + +
+ +
+

+ {{.Msg "catalog.eyebrow"}} +

+

+ {{.CatalogDetail.Game.Name}} +

+

+ {{.CatalogDetail.Game.Description}} +

+
+
+

+ {{.Msg "catalog.minimum"}} +

+
+
+ CPU +
+
+ {{.CatalogDetail.Requirements.Minimum.CPUCores}} +
+
+ {{.Msg "catalog.memory"}} +
+
+ {{.CatalogDetail.Requirements.Minimum.MemoryMB}} + MB +
+
+ {{.Msg "catalog.storage"}} +
+
+ {{.CatalogDetail.Requirements.Minimum.StorageGB}} + GB +
+
+
+
+

+ {{.Msg "catalog.recommended"}} +

+
+
+ CPU +
+
+ {{.CatalogDetail.Requirements.Recommended.CPUCores}} +
+
+ {{.Msg "catalog.memory"}} +
+
+ {{.CatalogDetail.Requirements.Recommended.MemoryMB}} + MB +
+
+ {{.Msg "catalog.storage"}} +
+
+ {{.CatalogDetail.Requirements.Recommended.StorageGB}} + GB +
+
+
+
+
+
+
+ + +{{end}} diff --git a/internal/web/templates/catalog.html b/internal/web/templates/catalog.html index 354fad9..e30d1de 100644 --- a/internal/web/templates/catalog.html +++ b/internal/web/templates/catalog.html @@ -1 +1,113 @@ -{{define "catalog.html"}}{{.Title}} · DoGaMa{{template "sidebar" .}}

{{.Msg "catalog.eyebrow"}}

{{.Msg "catalog.heading"}}

{{if and .IsAdmin .CatalogScanner}}
{{end}}
{{if .CatalogScan}}
{{.CatalogScan.Found}} {{.Msg "catalog.found"}} · {{len .CatalogScan.Valid}} {{.Msg "catalog.valid"}} · {{len .CatalogScan.Errors}} {{.Msg "catalog.invalid"}}{{if .CatalogScan.Errors}}{{end}}
{{end}}{{if .Catalog}}
{{range .Catalog}}
{{.GameName}}◈

{{.GameName}}

{{end}}
{{else}}
◈

{{.Msg "catalog.empty"}}

{{if .IsAdmin}}

{{.Msg "catalog.empty_admin"}}

{{end}}
{{end}}
{{end}} +{{define "catalog.html"}} + + + + + + + {{.Title}} + · DoGaMa + + + + + + {{template "sidebar" .}} +
+
+
+

+ {{.Msg "catalog.eyebrow"}} +

+

+ {{.Msg "catalog.heading"}} +

+
+
+ + {{if and .IsAdmin .CatalogScanner}} +
+ + +
+ {{end}} +
+
+ {{if .CatalogScan}} +
+ + {{.CatalogScan.Found}} + {{.Msg "catalog.found"}} + · + {{len .CatalogScan.Valid}} + {{.Msg "catalog.valid"}} + · + {{len .CatalogScan.Errors}} + {{.Msg "catalog.invalid"}} + + {{if .CatalogScan.Errors}} + + {{end}} +
+ {{end}} + {{if .Catalog}} +
+ {{range .Catalog}} + +
+ {{.GameName}} + + ◈ + +
+

+ {{.GameName}} +

+
+ {{end}} +
+ + {{else}} +
+ + ◈ + +

+ {{.Msg "catalog.empty"}} +

+ {{if .IsAdmin}} +

+ {{.Msg "catalog.empty_admin"}} +

+ {{end}} +
+ {{end}} +
+ + + +{{end}} diff --git a/internal/web/templates/components.html b/internal/web/templates/components.html index 6a509db..e18eeec 100644 --- a/internal/web/templates/components.html +++ b/internal/web/templates/components.html @@ -1,22 +1,59 @@ {{define "sidebar"}} - -
- DoGaMa - -
- + +
+ + DoGaMa + + +
+ {{end}} diff --git a/internal/web/templates/deployment.html b/internal/web/templates/deployment.html index 8d679e2..de16244 100644 --- a/internal/web/templates/deployment.html +++ b/internal/web/templates/deployment.html @@ -1 +1,108 @@ -{{define "deployment.html"}}{{.Msg "deployment.title"}} · DoGaMa{{template "sidebar" .}}
← {{.Msg "catalog.back"}}
{{if .Deployment.Error}}

{{.Deployment.Error}}

{{end}}

{{.Msg "deployment.eyebrow"}}

{{.Msg "deployment.heading"}}

{{.Msg "deployment.parameters"}}

{{range .Deployment.Template.Configuration.Fields}}{{end}}{{if .Deployment.Template.Imports.Supported}}

{{.Msg "deployment.backup_help"}}

{{end}}
{{end}} +{{define "deployment.html"}} + + + + + + + {{.Msg "deployment.title"}} + · DoGaMa + + + + + + {{template "sidebar" .}} +
+ + ← + {{.Msg "catalog.back"}} + +
+ +
+ + {{if .Deployment.Error}} +

+ {{.Deployment.Error}} +

+ {{end}} +

+ {{.Msg "deployment.eyebrow"}} +

+

+ {{.Msg "deployment.heading"}} +

+ + +

+ {{.Msg "deployment.parameters"}} +

+ {{range .Deployment.Template.Configuration.Fields}} + + {{end}} + {{if .Deployment.Template.Imports.Supported}} + +

+ {{.Msg "deployment.backup_help"}} +

+ {{end}} + +
+
+
+ + + +{{end}} diff --git a/internal/web/templates/home.html b/internal/web/templates/home.html index 680adae..a10a5ee 100644 --- a/internal/web/templates/home.html +++ b/internal/web/templates/home.html @@ -1 +1,223 @@ -{{define "home.html"}}{{.Title}} · DoGaMa{{template "sidebar" .}}

{{.Msg "dashboard.eyebrow"}}

{{.Msg "dashboard.heading"}}

{{.Msg "dashboard.introduction"}}

{{.Msg "dashboard.total"}}{{len .Instances}}
{{.Msg "dashboard.running"}}{{index .StatusCounts "running"}}
{{.Msg "dashboard.stopped"}}{{index .StatusCounts "stopped"}}
{{.Msg "dashboard.updating"}}{{index .StatusCounts "updating"}}
{{.Msg "dashboard.error"}}{{index .StatusCounts "error"}}

{{.Msg "dashboard.instances_eyebrow"}}

{{.Msg "dashboard.instances"}}

{{if .Instances}}{{else}}
◈

{{.Msg "dashboard.empty_heading"}}

{{.Msg "dashboard.empty_copy"}}

{{end}}

{{.Msg "dashboard.activity_eyebrow"}}

{{.Msg "dashboard.recent_activity"}}

{{if .RecentActivity}}
    {{range .RecentActivity}}
  • {{activityLabel .Action}} · {{.Instance}} · {{.Actor}}
  • {{end}}
{{else}}

{{.Msg "dashboard.no_activity"}}

{{end}}

{{.Msg "dashboard.system_eyebrow"}}

{{.Msg "dashboard.system_status"}}

  • {{.Msg "dashboard.agent"}}: {{if .SystemStatus.AgentAvailable}}{{if .SystemStatus.AgentOnline}}{{.Msg "dashboard.online"}}{{else}}{{.Msg "dashboard.offline"}}{{end}}{{else}}{{.Msg "dashboard.unavailable"}}{{end}}
  • {{.Msg "dashboard.database"}}: {{if .SystemStatus.DatabaseHealthy}}{{.Msg "dashboard.healthy"}}{{else}}{{.Msg "dashboard.unavailable"}}{{end}}
  • {{.Msg "dashboard.storage"}}: {{if .SystemStatus.StorageAvailable}}{{storagePercent .SystemStatus.StorageUsed .SystemStatus.StorageTotal}}%{{else}}{{.Msg "dashboard.unavailable"}}{{end}}
{{end}} +{{define "home.html"}} + + + + + + + {{.Title}} + · DoGaMa + + + + + + {{template "sidebar" .}} +
+
+
+

+ {{.Msg "dashboard.eyebrow"}} +

+

+ {{.Msg "dashboard.heading"}} +

+

+ {{.Msg "dashboard.introduction"}} +

+
+ +
+
+
+ + {{.Msg "dashboard.total"}} + + + {{len .Instances}} + +
+
+ + {{.Msg "dashboard.running"}} + + + {{index .StatusCounts "running"}} + +
+
+ + {{.Msg "dashboard.stopped"}} + + + {{index .StatusCounts "stopped"}} + +
+
+ + {{.Msg "dashboard.updating"}} + + + {{index .StatusCounts "updating"}} + +
+
+ + {{.Msg "dashboard.error"}} + + + {{index .StatusCounts "error"}} + +
+
+
+
+
+

+ {{.Msg "dashboard.instances_eyebrow"}} +

+

+ {{.Msg "dashboard.instances"}} +

+
+
+ {{if .Instances}} + + + {{else}} +
+ + ◈ + +

+ {{.Msg "dashboard.empty_heading"}} +

+

+ {{.Msg "dashboard.empty_copy"}} +

+
+ {{end}} +
+
+
+
+
+

+ {{.Msg "dashboard.activity_eyebrow"}} +

+

+ {{.Msg "dashboard.recent_activity"}} +

+
+
+ {{if .RecentActivity}} +
    + {{range .RecentActivity}} +
  • + {{activityLabel .Action}} + · + {{.Instance}} + · + {{.Actor}} +
  • + {{end}} +
+ {{else}} +

+ {{.Msg "dashboard.no_activity"}} +

+ {{end}} +
+
+
+
+

+ {{.Msg "dashboard.system_eyebrow"}} +

+

+ {{.Msg "dashboard.system_status"}} +

+
+
+
    +
  • + {{.Msg "dashboard.agent"}} + : + {{if .SystemStatus.AgentAvailable}} + {{if .SystemStatus.AgentOnline}} + {{.Msg "dashboard.online"}} + {{else}} + {{.Msg "dashboard.offline"}} + {{end}} + {{else}} + {{.Msg "dashboard.unavailable"}} + {{end}} +
  • +
  • + {{.Msg "dashboard.database"}} + : + {{if .SystemStatus.DatabaseHealthy}} + {{.Msg "dashboard.healthy"}} + {{else}} + {{.Msg "dashboard.unavailable"}} + {{end}} +
  • +
  • + {{.Msg "dashboard.storage"}} + : + {{if .SystemStatus.StorageAvailable}} + {{storagePercent .SystemStatus.StorageUsed .SystemStatus.StorageTotal}} + % + {{else}} + {{.Msg "dashboard.unavailable"}} + {{end}} +
  • +
+
+
+
+ + + +{{end}} diff --git a/internal/web/templates/instance-detail.html b/internal/web/templates/instance-detail.html index 6f7f156..c638e9f 100644 --- a/internal/web/templates/instance-detail.html +++ b/internal/web/templates/instance-detail.html @@ -1 +1,278 @@ -{{define "instance-detail.html"}}{{.InstanceDetail.Instance.Preview.DisplayName}} · DoGaMa{{template "sidebar" .}}
← {{.Msg "instance.back"}}{{if .InstanceDetail.Message}}

{{.InstanceDetail.Message}}

{{end}}

{{.InstanceDetail.Instance.Preview.Game.Name}}

{{.InstanceDetail.Instance.Preview.DisplayName}}

{{.InstanceDetail.Instance.Preview.Description}}

{{statusLabel .InstanceDetail.Instance.LifecycleState}}

♙ {{.Msg "instance.players"}}
{{if .InstanceDetail.Live.Metrics}}{{.InstanceDetail.Live.Metrics.CurrentPlayers}} / {{.InstanceDetail.Live.Metrics.MaxPlayers}} live{{else}}{{.Msg "instance.unavailable"}}{{if .InstanceDetail.MaxPlayers}} / {{.InstanceDetail.MaxPlayers}} configured{{end}}{{end}}
⚿ {{.Msg "instance.password"}}
{{if .InstanceDetail.PasswordSet}}•••••••• {{if .IsAdmin}}
{{end}}{{else}}{{.Msg "instance.unavailable"}}{{end}}
{{if .InstanceDetail.Live.ServerInfo}}

{{.InstanceDetail.Live.ServerInfo.Name}} · {{.InstanceDetail.Live.ServerInfo.GameVersion}}

{{end}}

{{.Msg "instance.backups"}}

{{if .InstanceDetail.Backups}}
    {{range .InstanceDetail.Backups}}
  • {{.CreatedAt}}{{.Origin}} · {{.Status}} · {{formatBytes .SizeBytes}}
    {{if $.InstanceDetail.CanRestore}}
    {{end}}
  • {{end}}
{{else}}

{{.Msg "instance.no_backups"}}

{{end}}

{{.Msg "instance.server_functions"}}

{{if .InstanceDetail.ModuleAvailable}}{{if .InstanceDetail.CanAnnounce}}
{{end}}{{if .InstanceDetail.Live.Players}}
    {{range .InstanceDetail.Live.Players}}
  • {{.DisplayName}} {{.PlayerID}}{{if $.InstanceDetail.CanKick}}
    {{end}}{{if $.InstanceDetail.CanBan}}
    {{end}}
  • {{end}}
{{end}}{{if .InstanceDetail.BansAvailable}}{{if .InstanceDetail.Live.Bans}}
{{else}}

{{.Msg "instance.no_banned_players"}}

{{end}}{{else if .InstanceDetail.BansUnavailable}}

{{.Msg "instance.unavailable"}}

{{else if and .InstanceDetail.CanUnban (not .InstanceDetail.Live.Capabilities.list_bans)}}

{{.Msg "instance.bans_not_supported"}}

{{end}}{{else}}

{{.Msg "instance.module_unavailable"}}

{{end}}
{{end}} +{{define "instance-detail.html"}} + + + + + + + {{.InstanceDetail.Instance.Preview.DisplayName}} + · DoGaMa + + + + + + {{template "sidebar" .}} +
+ + ← + {{.Msg "instance.back"}} + + {{if .InstanceDetail.Message}} +

+ {{.InstanceDetail.Message}} +

+ {{end}} +
+ +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+

+ {{.InstanceDetail.Instance.Preview.Game.Name}} +

+

+ {{.InstanceDetail.Instance.Preview.DisplayName}} +

+

+ {{.InstanceDetail.Instance.Preview.Description}} +

+

+ + + {{statusLabel .InstanceDetail.Instance.LifecycleState}} +

+
+
+
+ ♙ + {{.Msg "instance.players"}} +
+
+ {{if .InstanceDetail.Live.Metrics}} + {{.InstanceDetail.Live.Metrics.CurrentPlayers}} + / + {{.InstanceDetail.Live.Metrics.MaxPlayers}} + live + {{else}} + {{.Msg "instance.unavailable"}} + {{if .InstanceDetail.MaxPlayers}} + / + {{.InstanceDetail.MaxPlayers}} + configured + {{end}} + {{end}} +
+
+
+
+ ⚿ + {{.Msg "instance.password"}} +
+
+ {{if .InstanceDetail.PasswordSet}} + •••••••• + {{if .IsAdmin}} +
+ + +
+ {{end}} + {{else}} + {{.Msg "instance.unavailable"}} + {{end}} +
+
+
+ {{if .InstanceDetail.Live.ServerInfo}} +

+ {{.InstanceDetail.Live.ServerInfo.Name}} + · + {{.InstanceDetail.Live.ServerInfo.GameVersion}} +

+ {{end}} +
+
+
+
+
+

+ {{.Msg "instance.backups"}} +

+
+ {{if .InstanceDetail.Backups}} +
    + {{range .InstanceDetail.Backups}} +
  • +
    + + {{.CreatedAt}} + + + {{.Origin}} + · + {{.Status}} + · + {{formatBytes .SizeBytes}} + +
    + {{if $.InstanceDetail.CanRestore}} +
    + + + +
    + {{end}} +
  • + {{end}} +
+ {{else}} +

+ {{.Msg "instance.no_backups"}} +

+ {{end}} +
+
+
+

+ {{.Msg "instance.server_functions"}} +

+
+ {{if .InstanceDetail.ModuleAvailable}} + {{if .InstanceDetail.CanAnnounce}} +
+ + + +
+ {{end}} + {{if .InstanceDetail.Live.Players}} +
    + {{range .InstanceDetail.Live.Players}} +
  • + {{.DisplayName}} + + {{.PlayerID}} + + {{if $.InstanceDetail.CanKick}} +
    + + + + +
    + {{end}} + {{if $.InstanceDetail.CanBan}} +
    + + + + +
    + {{end}} +
  • + {{end}} +
+ {{end}} + {{if .InstanceDetail.BansAvailable}} + {{if .InstanceDetail.Live.Bans}} +
+ + + +
+ {{else}} +

+ {{.Msg "instance.no_banned_players"}} +

+ {{end}} + {{else if .InstanceDetail.BansUnavailable}} +

+ {{.Msg "instance.unavailable"}} +

+ {{else if and .InstanceDetail.CanUnban (not .InstanceDetail.Live.Capabilities.list_bans)}} +
+ + +

+ {{.Msg "instance.bans_not_supported"}} +

+ +
+ {{end}} + {{else}} +

+ {{.Msg "instance.module_unavailable"}} +

+ {{end}} +
+
+
+ + + +{{end}} diff --git a/internal/web/templates/login.html b/internal/web/templates/login.html index 766a269..8db3ed9 100644 --- a/internal/web/templates/login.html +++ b/internal/web/templates/login.html @@ -1 +1,51 @@ -{{define "login.html"}}{{.Title}} · DoGaMa
DoGaMa

{{.Msg "brand.control"}}

{{.Msg "login.heading"}}

{{.Msg "brand.control"}}

{{if .Error}}{{end}}
{{end}} +{{define "login.html"}} + + + + + + + {{.Title}} + · DoGaMa + + + + +
+
+ DoGaMa +
+
+

+ {{.Msg "brand.control"}} +

+

+ {{.Msg "login.heading"}} +

+

+ {{.Msg "brand.control"}} +

+ {{if .Error}} + + {{end}} +
+ + + + +
+
+
+ + +{{end}} diff --git a/internal/web/templates/settings.html b/internal/web/templates/settings.html index ffca02f..114d6fc 100644 --- a/internal/web/templates/settings.html +++ b/internal/web/templates/settings.html @@ -1,20 +1,353 @@ {{define "settings.html"}} - - -{{.Title}} · DoGaMa -{{template "sidebar" .}} -
-

{{.Msg "settings.eyebrow"}}

Administration

{{.Msg "settings.introduction"}}

- -

{{.Msg "settings.web_access"}}

{{.Msg "settings.web_help"}}

{{.Msg "settings.https_help"}}

{{.Msg "settings.canonical_help"}}

-

{{.Msg "settings.channels"}}

{{.Msg "settings.channels_help"}}

-

{{.Msg "settings.notification_general"}}

{{.Msg "settings.notification_language_help"}}

- -

{{.Msg "settings.notification_email"}}

-

{{.Msg "settings.notification_discord"}}

-

{{.Msg "settings.notification_gotify"}}

{{.Msg "settings.gotify_help"}}

-
-

{{.Msg "settings.audit_retention"}}

{{.Msg "settings.audit_help"}}

{{.Msg "settings.view_audit"}}

{{.Msg "settings.zero_unlimited"}}

-

{{.Msg "settings.container_labels"}}

{{.Msg "settings.container_labels_help"}}

{{.Msg "settings.apply"}}
-
+ + + + + + + {{.Title}} + · DoGaMa + + + + + {{template "sidebar" .}} +
+
+
+

+ {{.Msg "settings.eyebrow"}} +

+

+ Administration +

+

+ {{.Msg "settings.introduction"}} +

+
+
+ +
+

+ {{.Msg "settings.web_access"}} +

+

+ {{.Msg "settings.web_help"}} +

+
+ + +

+ {{.Msg "settings.https_help"}} +

+ +

+ {{.Msg "settings.canonical_help"}} +

+ +
+
+
+

+ {{.Msg "settings.channels"}} +

+

+ {{.Msg "settings.channels_help"}} +

+ +
+

+ {{.Msg "settings.notification_general"}} +

+

+ {{.Msg "settings.notification_language_help"}} +

+
+ + + +
+
+ +
+

+ {{.Msg "settings.notification_email"}} +

+
+ + + + + +
+ + + + + + + + +
+ +
+
+
+

+ {{.Msg "settings.notification_discord"}} +

+
+ + + + + + + +
+
+
+

+ {{.Msg "settings.notification_gotify"}} +

+

+ {{.Msg "settings.gotify_help"}} +

+
+ + + + + + + + +
+
+
+
+

+ {{.Msg "settings.audit_retention"}} +

+

+ {{.Msg "settings.audit_help"}} +

+ + {{.Msg "settings.view_audit"}} + +
+ + + +

+ {{.Msg "settings.zero_unlimited"}} +

+ +
+
+ + + + +
+
+
+

+ {{.Msg "settings.container_labels"}} +

+

+ {{.Msg "settings.container_labels_help"}} +

+
+ + +
+ + {{.Msg "settings.apply"}} + + + +
+ +
+
+
+ + + {{end}} diff --git a/internal/web/templates/setup.html b/internal/web/templates/setup.html index 37a93c0..1c2e65b 100644 --- a/internal/web/templates/setup.html +++ b/internal/web/templates/setup.html @@ -1 +1,65 @@ -{{define "setup.html"}}{{.Title}} · DoGaMa
DoGaMa

{{.Msg "brand.control"}}

{{.Msg "setup.heading"}}

{{.Msg "setup.introduction"}}

{{if .Error}}{{end}}
{{end}} +{{define "setup.html"}} + + + + + + + {{.Title}} + · DoGaMa + + + + +
+
+ DoGaMa +
+
+

+ {{.Msg "brand.control"}} +

+

+ {{.Msg "setup.heading"}} +

+

+ {{.Msg "setup.introduction"}} +

+ {{if .Error}} + + {{end}} +
+ + + + + + +
+
+
+ + +{{end}} diff --git a/internal/web/templates/template-repositories.html b/internal/web/templates/template-repositories.html index 2a24c22..53a08cb 100644 --- a/internal/web/templates/template-repositories.html +++ b/internal/web/templates/template-repositories.html @@ -1,9 +1,114 @@ {{define "template-repositories.html"}} -{{.Title}} · DoGaMa -{{template "sidebar" .}}
-

Administration

{{.Msg "template_repositories.heading"}}

{{.Msg "template_repositories.introduction"}}

Administration
- -
{{.Msg "template_repositories.new"}}{{if .Error}}

{{.Error}}

{{end}}
-

{{.Msg "template_repositories.heading"}}

{{.Msg "template_repositories.not_implemented"}}

{{if .TemplateRepositories}}{{else}}

{{.Msg "template_repositories.empty"}}

{{end}}
-
+ + + + + + + {{.Title}} + · DoGaMa + + + + + {{template "sidebar" .}} +
+
+
+

+ Administration +

+

+ {{.Msg "template_repositories.heading"}} +

+

+ {{.Msg "template_repositories.introduction"}} +

+
+ + Administration + +
+ +
+ + + {{.Msg "template_repositories.new"}} + + {{if .Error}} +

+ {{.Error}} +

+ {{end}} +
+
+ + + +
+ +
+ +
+
+

+ {{.Msg "template_repositories.heading"}} +

+

+ {{.Msg "template_repositories.not_implemented"}} +

+ {{if .TemplateRepositories}} + + {{else}} +

+ {{.Msg "template_repositories.empty"}} +

+ {{end}} +
+
+ + + {{end}} diff --git a/internal/web/templates/users.html b/internal/web/templates/users.html index c78900b..b799e57 100644 --- a/internal/web/templates/users.html +++ b/internal/web/templates/users.html @@ -1,25 +1,211 @@ {{define "users.html"}} -Users · DoGaMa -{{template "sidebar" .}}
-

Administration

Users

Create identities, assign global roles and control access to each game server.

Administration settings
-

Create user

-{{range .Users}} -{{$user := .}} -

{{.Username}}

{{if .Disabled}}Disabled{{else}}Active{{end}} · {{.Role}}

-
-{{if eq .Role "user"}} -

Instance access

-{{if $.Instances}} -{{range $.Instances}} -{{$instance := .}}{{$membership := index (index $.UserAccess $user.ID) .ID}} -
{{if .Preview.DisplayName}}{{.Preview.DisplayName}}{{else}}{{.ID}}{{end}} — {{if $membership.Role}}{{$membership.Role}}{{else}}No access{{end}} -
-{{if $membership.Role}}
{{range $.Permissions}}{{$effect := index $membership.Overrides .}}
{{end}}
{{end}} -
-{{end}} -{{else}}

No game-server instance is currently registered.

{{end}} -{{else}}

Administrators have implicit access to every instance.

{{end}} -
-{{else}}

No users found.

{{end}} -
+ + + + + + + Users · DoGaMa + + + + + {{template "sidebar" .}} +
+
+
+

+ Administration +

+

+ Users +

+

+ Create identities, assign global roles and control access to each game server. +

+
+ + Administration settings + +
+
+

+ Create user +

+
+ +
+ + + + +
+ + +
+
+ {{range .Users}} + {{$user := .}} +
+
+
+

+ {{.Username}} +

+

+ {{if .Disabled}} + Disabled + {{else}} + Active + {{end}} + · + {{.Role}} +

+
+
+
+ +
+ + +
+ + +
+ {{if eq .Role "user"}} +

+ Instance access +

+ {{if $.Instances}} + {{range $.Instances}} + {{$instance := .}} + {{$membership := index (index $.UserAccess $user.ID) .ID}} +
+ + {{if .Preview.DisplayName}} + {{.Preview.DisplayName}} + {{else}} + {{.ID}} + {{end}} + — + {{if $membership.Role}} + {{$membership.Role}} + {{else}} + No access + {{end}} + +
+ + + +
+ {{if $membership.Role}} +
+ {{range $.Permissions}} + {{$effect := index $membership.Overrides .}} +
+ + + +
+ {{end}} +
+ {{end}} +
+ {{end}} + {{else}} +

+ No game-server instance is currently registered. +

+ {{end}} + {{else}} +

+ Administrators have implicit access to every instance. +

+ {{end}} +
+ {{else}} +
+

+ No users found. +

+
+ {{end}} +
+ + + {{end}}