77dcda6a1a
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>
50 lines
2.6 KiB
JavaScript
50 lines
2.6 KiB
JavaScript
// 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(); });
|
|
});
|
|
})();
|