dragonflight/services/web-ui/public/login.html
Zac Gaetano ff2865b5d8 chore(web-ui): delete legacy standalone HTML pages; SPA is the only entry
Before this commit /public had two parallel UIs: the React SPA (index.html
+ screens-*.jsx) and a stack of pre-SPA standalone pages (home.html,
recorders.html, jobs.html, ...). The SPA replaces every standalone page,
nothing in the .jsx tree links to them, and the only outside references
were login.html redirecting to home.html and the nginx fallback pointing
at home.html.

Delete 16 standalone pages (~9.2k lines of dead markup, ~430KB on disk):
  _primitives-smoke.html  api-tokens.html  capture.html  cluster.html
  containers.html         edit.html        editor.html   home.html
  jobs.html               player.html      projects.html recorders.html
  settings.html           tokens.html      upload.html   users.html

Keep:
  index.html  — the React SPA shell
  login.html  — the sign-in / setup screen

Wire the redirects to the SPA:
- login.html post-signin: home.html -> /
- nginx try_files fallback: /home.html -> /index.html

After this, sign-in lands the operator on the real React app instead of
the stale 2025-era home page. The Editor screen continues to embed the
separate editor service via the /editor/ nginx proxy (unaffected).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 16:48:38 -04:00

289 lines
10 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<title>Sign in - Dragonflight</title>
<link rel="stylesheet" href="/dist/app.css">
<style>
/* Page-only layout. Everything visual is from /dist/app.css primitives. */
body { display: grid; grid-template-columns: 1fr 460px; min-height: 100vh; margin: 0; }
@media (max-width: 900px) {
body { grid-template-columns: 1fr; grid-template-rows: 40vh 1fr; }
}
.hero {
position: relative;
overflow: hidden;
background: radial-gradient(ellipse at 30% 40%, var(--bg-panel) 0%, var(--bg-deep) 70%);
display: flex;
align-items: flex-end;
padding: 48px 56px;
}
.hero-img {
position: absolute;
inset: 0;
background-image: url(img/ampp-safe.png?v=hardhat3);
background-size: contain;
background-position: center;
background-repeat: no-repeat;
}
.hero-grad-bot {
position: absolute;
inset: auto 0 0 0;
height: 40%;
background: linear-gradient(to top, var(--bg-deep) 0%, transparent 100%);
pointer-events: none;
}
.hero-stamp {
position: absolute;
top: 32px;
left: 32px;
display: inline-flex;
align-items: center;
gap: 8px;
z-index: 2;
background: var(--overlay);
backdrop-filter: blur(6px);
padding: 6px 12px;
border: 1px solid var(--accent-border);
border-radius: 999px;
}
.hero-stamp-dot {
width: 6px;
height: 6px;
background: var(--accent);
border-radius: 50%;
box-shadow: 0 0 10px var(--accent);
}
.hero-stamp-text {
font: 600 10px/1 var(--font);
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--accent-bright);
}
.hero-caption {
position: relative;
z-index: 2;
max-width: 520px;
}
.hero-caption h2 {
font: 600 28px/1.15 var(--font);
letter-spacing: -0.01em;
color: var(--text-primary);
margin: 0 0 10px;
}
.hero-caption p {
font: 400 14px/1.55 var(--font);
color: var(--text-secondary);
margin: 0;
}
.hero-caption .accent { color: var(--accent-bright); }
/* Right column: brand + form panel */
.panel {
display: flex;
flex-direction: column;
justify-content: center;
padding: 48px 56px;
background: var(--bg-panel);
border-left: 1px solid var(--border);
}
@media (max-width: 900px) {
.panel { padding: 32px 24px; border-left: none; border-top: 1px solid var(--border); }
}
.brand {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 36px;
}
.brand-icon {
width: 32px;
height: 32px;
background: var(--accent);
color: var(--bg-deep);
border-radius: 7px;
display: flex;
align-items: center;
justify-content: center;
font: 700 15px/1 var(--font);
}
.brand-name {
font: 600 15px/1.2 var(--font);
letter-spacing: -0.01em;
color: var(--text-primary);
}
.brand-sub {
font: 500 10px/1 var(--font);
color: var(--text-tertiary);
letter-spacing: 0.14em;
text-transform: uppercase;
margin-top: 3px;
}
.panel h1 {
font: 600 22px/1.2 var(--font);
letter-spacing: -0.01em;
color: var(--text-primary);
margin: 0 0 6px;
}
.panel .subtitle {
font: 400 13px/1.5 var(--font);
color: var(--text-secondary);
margin: 0 0 28px;
}
/* Flash messages — inline toast-shaped */
.flash {
display: none;
position: relative;
background: var(--bg-panel);
border: 1px solid var(--border);
border-radius: 4px;
padding: 10px 12px;
margin-bottom: 18px;
font: 400 13px/1.4 var(--font);
color: var(--text-primary);
}
.flash::before {
content: '';
position: absolute;
top: 0; left: 0; right: 0;
height: 3px;
border-radius: 4px 4px 0 0;
}
.flash.error { display: block; border-color: var(--signal-bad); }
.flash.error::before { background: var(--signal-bad); }
.flash.success { display: block; border-color: var(--signal-good); }
.flash.success::before { background: var(--signal-good); }
.setup-link {
text-align: center;
margin-top: 24px;
font: 400 12px/1.4 var(--font);
color: var(--text-tertiary);
}
.setup-link a {
color: var(--accent-bright);
text-decoration: none;
}
.setup-link a:hover { color: var(--accent); text-decoration: underline; }
#setup-panel { display: none; }
/* Make the login button full-width (the primitive is inline-flex by default) */
.wd-btn--full { width: 100%; margin-top: 10px; }
/* Stack form groups vertically with consistent gap */
.login-form { display: flex; flex-direction: column; gap: 14px; }
</style>
</head>
<body>
<section class="hero" aria-hidden="true">
<div class="hero-img"></div>
<div class="hero-grad-bot"></div>
<div class="hero-stamp">
<span class="hero-stamp-dot"></span>
<span class="hero-stamp-text">Dragonflight</span>
</div>
<div class="hero-caption">
<h2>Broadcast-safe media,<br><span class="accent">end to end.</span></h2>
<p>Dragonflight is the Wild Dragon media-asset hub. Ingest live SRT &amp; RTMP feeds, upload masters, and hand proxies to your editors in seconds.</p>
</div>
</section>
<section class="panel">
<div class="brand">
<div class="brand-icon">
<img src="img/dragon-logo.png?v=1" alt="Dragonflight" style="width:20px;height:20px;filter:brightness(0) invert(1);">
</div>
<div>
<div class="brand-name">Dragonflight</div>
<div class="brand-sub">Media Asset Management</div>
</div>
</div>
<div id="flash" class="flash"></div>
<div id="login-panel">
<h1>Sign in</h1>
<p class="subtitle">Enter your credentials to continue.</p>
<form id="login-form" class="login-form" autocomplete="on">
<div class="wd-form-group">
<label class="wd-label" for="username">Username</label>
<input class="wd-input" id="username" name="username" type="text" autocomplete="username" required placeholder="username">
</div>
<div class="wd-form-group">
<label class="wd-label" for="password">Password</label>
<input class="wd-input" id="password" name="password" type="password" autocomplete="current-password" required placeholder="••••••••">
</div>
<button type="submit" class="wd-btn wd-btn--primary wd-btn--md wd-btn--full" id="login-btn">Sign in</button>
</form>
<div class="setup-link">First time? <a href="#" id="show-setup">Create admin account</a></div>
</div>
<div id="setup-panel">
<h1>Create admin</h1>
<p class="subtitle">No accounts exist yet, create the first admin.</p>
<form id="setup-form" class="login-form" autocomplete="off">
<div class="wd-form-group">
<label class="wd-label" for="su-username">Username</label>
<input class="wd-input" id="su-username" name="username" type="text" required placeholder="admin">
</div>
<div class="wd-form-group">
<label class="wd-label" for="su-password">Password (min 8 chars)</label>
<input class="wd-input" id="su-password" name="password" type="password" required minlength="8" placeholder="••••••••">
</div>
<div class="wd-form-group">
<label class="wd-label" for="su-display">Display name (optional)</label>
<input class="wd-input" id="su-display" name="display_name" type="text" placeholder="Admin">
</div>
<button type="submit" class="wd-btn wd-btn--primary wd-btn--md wd-btn--full" id="setup-btn">Create account</button>
</form>
<div class="setup-link"><a href="#" id="show-login">Back to login</a></div>
</div>
</section>
<script>
const API = '/api/v1/auth';
const $ = id => document.getElementById(id);
const flash = $('flash');
function showFlash(m,t){ flash.textContent=m; flash.className='flash '+t; }
function clearFlash(){ flash.className='flash'; flash.textContent=''; }
$('show-setup').onclick = e => { e.preventDefault(); clearFlash(); $('login-panel').style.display='none'; $('setup-panel').style.display='block'; };
$('show-login').onclick = e => { e.preventDefault(); clearFlash(); $('setup-panel').style.display='none'; $('login-panel').style.display='block'; };
$('login-form').onsubmit = async (e) => {
e.preventDefault(); clearFlash();
const btn=$('login-btn'); btn.disabled=true; btn.textContent='Signing in...';
try{
const res = await fetch(API + '/login', {method:'POST',headers:{'Content-Type':'application/json'},credentials:'same-origin',
body: JSON.stringify({username:$('username').value.trim(),password:$('password').value})});
if(res.ok){ showFlash('Signed in, redirecting...','success'); setTimeout(()=>{location.href='/'},600); }
else{ const d=await res.json().catch(()=>({})); showFlash(d.error||'Login failed','error'); }
} catch(err){ showFlash('Network error: '+err.message,'error'); }
finally{ btn.disabled=false; btn.textContent='Sign in'; }
};
$('setup-form').onsubmit = async (e) => {
e.preventDefault(); clearFlash();
const btn=$('setup-btn'); btn.disabled=true; btn.textContent='Creating...';
try{
const res = await fetch(API + '/setup', {method:'POST',headers:{'Content-Type':'application/json'},credentials:'same-origin',
body: JSON.stringify({username:$('su-username').value.trim(),password:$('su-password').value,display_name:$('su-display').value.trim()})});
if(res.ok){
showFlash('Admin account created, you can now log in','success');
setTimeout(()=>{ $('setup-panel').style.display='none'; $('login-panel').style.display='block'; },1200);
} else{
const d=await res.json().catch(()=>({}));
showFlash(d.error||'Setup failed','error');
}
} catch(err){ showFlash('Network error: '+err.message,'error'); }
finally{ btn.disabled=false; btn.textContent='Create account'; }
};
</script>
</body></html>