Admin: move the bonus panel script out of the page (CSP)
The site sends script-src 'self'. The panel's logic was an inline <script> block, so the browser refused to run it: window.agBoot was never defined, admin.js's guarded call did nothing, and the "What" select stayed empty with no console-visible cause beyond the CSP report. admin.html had no inline scripts before this panel — the extraction restores that, so the next person adding a panel copies a pattern that works. Fourth thing this CSP has silently broken. Inline scripts are not an option on this site; new panels ship as files. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
// Award Suite Capacity panel. Lives in its own file because the site's CSP
|
||||
// is script-src 'self' — an inline <script> in admin.html is silently blocked.
|
||||
(function(){
|
||||
var $=function(i){return document.getElementById(i)};
|
||||
var TOOLS=[];
|
||||
function esc(x){return String(x==null?'':x).replace(/[&<>"']/g,function(c){return {'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]})}
|
||||
function renderLog(rows){
|
||||
var tb=$('agLog'); if(!tb) return;
|
||||
tb.innerHTML=(rows||[]).map(function(r){
|
||||
var t=TOOLS.filter(function(x){return x.key===r.tool})[0]||{};
|
||||
return '<tr><td><b>#'+esc(r.to)+'</b></td><td>'+esc(t.label||r.tool)+'</td><td>'+Number(r.n).toLocaleString()+' '+esc(t.unit||'')+
|
||||
'</td><td>'+esc(r.note||'')+'</td><td>'+esc(String(r.at||'').slice(0,10))+'</td></tr>';
|
||||
}).join('')||'<tr><td colspan="5" class="muted">None yet this month.</td></tr>';
|
||||
}
|
||||
async function boot(){
|
||||
try{
|
||||
var r=await fetch('/api/admin/grant'); if(!r.ok) return;
|
||||
var d=await r.json(); TOOLS=d.tools||[];
|
||||
$('agTool').innerHTML=TOOLS.map(function(t){return '<option value="'+t.key+'">'+esc(t.label)+'</option>'}).join('');
|
||||
renderLog(d.log);
|
||||
}catch(e){}
|
||||
}
|
||||
async function award(){
|
||||
var msg=$('agMsg'); msg.textContent='';
|
||||
var to=Number($('agTo').value)||0, n=Number($('agN').value)||0;
|
||||
if(!to||!n){ msg.style.color='#f0a05a'; msg.textContent='Need a position and an amount.'; return; }
|
||||
$('agGo').disabled=true;
|
||||
try{
|
||||
var r=await fetch('/api/admin/grant',{method:'POST',headers:{'Content-Type':'application/json'},
|
||||
body:JSON.stringify({to:to,tool:$('agTool').value,n:n,note:$('agNote').value})});
|
||||
var d=await r.json();
|
||||
if(!r.ok){ msg.style.color='#f0a05a'; msg.textContent=d.error||'Failed.'; }
|
||||
else{
|
||||
var t=TOOLS.filter(function(x){return x.key===d.granted.tool})[0]||{};
|
||||
msg.style.color='var(--teal)';
|
||||
msg.textContent='Awarded '+Number(d.granted.n).toLocaleString()+' '+(t.unit||'')+' to #'+d.granted.to+
|
||||
' ('+(d.levelName||'')+'). It is in their allowance now.';
|
||||
$('agNote').value=''; renderLog(d.log);
|
||||
}
|
||||
}catch(e){ msg.style.color='#f0a05a'; msg.textContent='Connection hiccup.'; }
|
||||
$('agGo').disabled=false;
|
||||
}
|
||||
window.agBoot=boot;
|
||||
document.addEventListener('DOMContentLoaded',function(){
|
||||
var b=$('agGo'); if(b) b.addEventListener('click',award);
|
||||
// Belt and braces: if the select is still empty when it's opened, try again.
|
||||
var sel=$('agTool'); if(sel) sel.addEventListener('focus',function(){ if(!sel.options.length) boot(); });
|
||||
});
|
||||
})();
|
||||
Reference in New Issue
Block a user