diff --git a/admelist.html b/admelist.html index 14677d0..757040e 100644 --- a/admelist.html +++ b/admelist.html @@ -6,8 +6,8 @@ Edit List - GD fedl - -
+ +

FEDL Admin

+ + + +
diff --git a/app.js b/app.js index 6c88545..598f586 100644 --- a/app.js +++ b/app.js @@ -452,6 +452,9 @@ } if(page==='admelist'){ + const loginScreenEl = qs('admin-login-screen'); + const adminShellContentEl = qs('admin-shell-content'); + const loginFormEl = qs('admin-login-form'); const statusEl = qs('admin-status'); const listTbody = qs('admin-list-body'); const addBtn = qs('add-row'); @@ -481,7 +484,7 @@ authStatusEl.textContent = password ? 'Password saved for this browser session.' : 'Saved only in this browser session.'; - authStatusEl.classList.remove('error-text'); + if(password) authStatusEl.classList.remove('error-text'); } } @@ -496,9 +499,54 @@ function handleAdminAuthFailure(message, targetSetter){ setAdminPassword(''); + document.body.classList.add('admin-locked'); + if(adminShellContentEl) adminShellContentEl.hidden = true; + if(loginScreenEl) loginScreenEl.hidden = false; targetSetter(message || 'Admin password required or incorrect.', true); } + function unlockAdminShell(){ + document.body.classList.remove('admin-locked'); + if(loginScreenEl) loginScreenEl.hidden = true; + if(adminShellContentEl) adminShellContentEl.hidden = false; + } + + function verifyAdminPassword(){ + if(!getAdminPassword()){ + handleAdminAuthFailure('Enter the admin password to continue.', function(message, isError){ + if(!authStatusEl) return; + authStatusEl.textContent = message; + authStatusEl.classList.toggle('error-text', !!isError); + }); + return Promise.resolve(false); + } + return fetch(`${liveRunsUrl}/__authcheck__`, { + method:'DELETE', + headers:authHeaders() + }).then(r=>{ + if(r.status === 401) throw new Error('Admin auth failed'); + if(r.status !== 404) throw new Error('Admin verify failed'); + return true; + }).then(ok=>{ + unlockAdminShell(); + if(authStatusEl){ + authStatusEl.textContent = 'Access granted for this browser session.'; + authStatusEl.classList.remove('error-text'); + } + loadAdmin(); + loadRunsAdmin(); + return ok; + }).catch(err=>{ + console.error(err); + handleAdminAuthFailure('Wrong admin password. Try again.', function(message, isError){ + if(!authStatusEl) return; + authStatusEl.textContent = message; + authStatusEl.classList.toggle('error-text', !!isError); + }); + return false; + }); + } + function setStatus(message, isError){ if(!statusEl) return; statusEl.textContent = message; @@ -839,8 +887,16 @@ } if(adminPasswordEl){ setAdminPassword(getAdminPassword()); - adminPasswordEl.addEventListener('input', function(){ - setAdminPassword(adminPasswordEl.value.trim()); + } + if(loginFormEl){ + loginFormEl.addEventListener('submit', function(event){ + event.preventDefault(); + setAdminPassword((adminPasswordEl && adminPasswordEl.value || '').trim()); + if(authStatusEl){ + authStatusEl.textContent = 'Checking password...'; + authStatusEl.classList.remove('error-text'); + } + verifyAdminPassword(); }); } @@ -863,8 +919,15 @@ setRunsStatus('Run queue reloaded from the live server.'); }); - loadAdmin(); - loadRunsAdmin(); + if(getAdminPassword()){ + verifyAdminPassword(); + }else{ + handleAdminAuthFailure('Enter the admin password to continue.', function(message, isError){ + if(!authStatusEl) return; + authStatusEl.textContent = message; + authStatusEl.classList.toggle('error-text', !!isError); + }); + } } if(page==='run'){ diff --git a/server/server.js b/server/server.js index 4597f56..15ebefe 100644 --- a/server/server.js +++ b/server/server.js @@ -297,10 +297,6 @@ const server = http.createServer((req, res) => { return; } - if ((pathname === '/admelist.html' || pathname === '/admelist') && !requireAdmin(req, res)) { - return; - } - serveFile(pathname, res); }); diff --git a/styles.css b/styles.css index ccc3412..b7b7c84 100644 --- a/styles.css +++ b/styles.css @@ -34,10 +34,20 @@ table.levels-table tr:hover td{background:rgba(255,255,255,0.01)} .small-btn{padding:8px 12px} .danger-btn{background:#ff7b7b;color:#2a0909} .admin-shell{padding:18px 20px} +.admin-shell-content[hidden]{display:none} +.admin-locked{overflow:hidden} +.admin-locked #admin-page-header,.admin-locked #admin-shell-content{visibility:hidden} +.admin-login-screen{position:fixed;inset:0;z-index:2000;display:flex;align-items:center;justify-content:center;padding:30px;background: +radial-gradient(circle at top,rgba(255,184,77,0.16),transparent 24%), +linear-gradient(180deg,rgba(6,16,33,0.98),rgba(7,20,38,0.99))} +.admin-login-card{width:min(100%,520px);padding:34px;border-radius:28px;background:linear-gradient(145deg,rgba(255,184,77,0.12),rgba(7,19,38,0.96) 42%),radial-gradient(circle at top left,rgba(255,255,255,0.08),transparent 35%);border:1px solid rgba(255,184,77,0.16)} +.admin-login-card h2{margin:0 0 10px 0;font-size:clamp(2rem,4vw,2.8rem)} +.admin-login-form{display:grid;gap:16px;margin-top:20px} +.admin-login-actions{display:grid;gap:12px} +.admin-login-screen[hidden]{display:none} .admin-panel{padding:20px} .admin-overview-panel{display:flex;align-items:center;justify-content:space-between;gap:18px;padding:24px 26px;border-radius:28px;background:linear-gradient(135deg,rgba(255,184,77,0.12),rgba(7,19,38,0.95));border:1px solid rgba(255,184,77,0.16)} .admin-overview-panel h2{margin:0 0 8px 0;font-size:clamp(1.9rem,4vw,2.8rem)} -.admin-access-panel{display:grid;gap:12px;justify-items:start;min-width:min(100%,320px)} .admin-overview-badges{display:flex;flex-wrap:wrap;gap:10px} .admin-overview-badges span{padding:10px 14px;border-radius:999px;background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.08);color:#eef5ff;font-weight:700} .admin-password-field{display:grid;gap:8px;width:100%;font-weight:700;color:#f2f7fd} @@ -259,5 +269,5 @@ linear-gradient(180deg,rgba(8,18,32,0.98),rgba(7,19,38,0.94))} .status-card span{display:block;color:var(--muted);line-height:1.5} @media(max-width:900px){.layout{flex-direction:column}.sidebar{width:100%}.sidebar .panel{height:auto}.video-modal iframe{height:320px}.home-hero{grid-template-columns:1fr;min-height:auto}.home-copy,.hero-stage{padding:24px 22px}.hero-stats,.home-section-grid,.spotlight-grid{grid-template-columns:1fr}.spotlight-panel{grid-template-columns:1fr;padding:24px 22px}.list-hero-panel,.table-header,.search-row,.roulette-head{flex-direction:column;align-items:flex-start}.control-select{max-width:none;width:100%}.roulette-shell{grid-template-columns:1fr}.discord-panel{flex-direction:column;align-items:flex-start;padding:22px 20px}.discord-panel h2{font-size:1.55rem}.discord-actions{width:100%;justify-content:flex-start}.rules-view header{padding:16px 18px}.rules-view header h1{font-size:1.55rem}.rules-page{padding:24px 20px}.rules-page h2{font-size:1.65rem}} @media(max-width:900px){.error-grid{grid-template-columns:1fr}.error-copy,.error-side{padding:24px 20px}.error-copy h2{font-size:2.5rem}} -@media(max-width:900px){.admin-overview-panel,.admin-toolbar{flex-direction:column;align-items:stretch}.admin-actions{flex-direction:column;align-items:stretch}.admin-password-field input,.admin-actions input[type=text],.admin-actions input[type=password]{min-width:0;width:100%}.run-shell{grid-template-columns:1fr}.run-head{flex-direction:column;align-items:flex-start}.run-form{grid-template-columns:1fr}} +@media(max-width:900px){.admin-overview-panel,.admin-toolbar{flex-direction:column;align-items:stretch}.admin-actions{flex-direction:column;align-items:stretch}.admin-password-field input,.admin-actions input[type=text],.admin-actions input[type=password]{min-width:0;width:100%}.run-shell{grid-template-columns:1fr}.run-head{flex-direction:column;align-items:flex-start}.run-form{grid-template-columns:1fr}.admin-login-screen{padding:18px}.admin-login-card{padding:26px 22px}} @media(max-width:640px){header{flex-direction:column;align-items:flex-start;gap:12px}nav{display:flex;flex-wrap:wrap;gap:10px}nav a{margin-left:0}.home-copy h2{max-width:none}.hero-actions{flex-direction:column;align-items:stretch}.hero-actions .btn,.roulette-btn{width:100%;text-align:center}.list-hero-panel,.list-controls-panel,.list-table-panel,.roulette-panel,.roulette-rules-card,.run-panel,.run-side-panel,.submission-panel{padding:22px 18px}.table-hint{text-align:left}.run-admin-actions{justify-content:flex-start}}