const state = { token: sessionStorage.getItem('aigw_admin_token') || '', actor: {}, permissions: new Set(), overview: {}, tenants: [], projects: [], keys: [], providers: [], models: [], billingAccounts: [], ledger: [], usage: [], usageSummary: [], limits: [], users: [], audit: [] }; const $ = (selector) => document.querySelector(selector); const $$ = (selector) => [...document.querySelectorAll(selector)]; const can = (permission) => state.permissions.has(permission); function esc(value) { return String(value ?? '').replace(/[&<>'"]/g, (char) => ({ '&':'&', '<':'<', '>':'>', "'":''', '"':'"' }[char])); } function date(value) { return value ? new Date(value).toLocaleString() : '—'; } function shortID(value) { const text = String(value || ''); return text ? `${text.slice(0, 10)}${text.length > 10 ? '…' : ''}` : '—'; } function percent(part, total) { return total ? `${Math.round((part / total) * 100)}%` : '—'; } function toast(message, error = false) { const node = $('#toast'); node.textContent = message; node.className = `toast visible ${error ? 'error' : ''}`; setTimeout(() => { node.className = 'toast'; }, 3200); } async function api(path, options = {}) { const response = await fetch(`./api${path}`, { ...options, headers: { 'Content-Type':'application/json', 'Authorization':`Bearer ${state.token}`, ...(options.headers || {}) } }); const payload = await response.json().catch(() => ({})); if (!response.ok) throw new Error(payload?.error?.message || `Request failed (${response.status})`); return payload; } function setConnected(connected) { $('#connection-state').textContent = connected ? state.actor.role?.replaceAll('_', ' ') || 'Connected' : 'Offline'; $('#connection-state').className = `state ${connected ? 'online' : ''}`; $('#actor-label').textContent = connected ? state.actor.display_name || state.actor.email || '' : ''; } function formJSON(form) { return Object.fromEntries(new FormData(form).entries()); } function selectOptions(items, valueKey, labelKey, empty = 'Select…') { return `${items.map(item => ``).join('')}`; } function decimalToScaled(value, digits) { const match = String(value).trim().match(/^(-?)(\d+)(?:\.(\d+))?$/); if (!match) throw new Error('Enter a valid decimal amount'); const fraction = match[3] || ''; if (fraction.length > digits && /[1-9]/.test(fraction.slice(digits))) throw new Error(`Use at most ${digits} decimal places`); const scale = 10n ** BigInt(digits); const absolute = BigInt(match[2]) * scale + BigInt((fraction.slice(0, digits) + '0'.repeat(digits)).slice(0, digits) || '0'); const result = Number(match[1] ? -absolute : absolute); if (!Number.isSafeInteger(result)) throw new Error('Amount is too large'); return result; } function scaledToDecimal(value, digits) { const number = BigInt(value || 0); const whole = number / (10n ** BigInt(digits)); const fraction = String(number % (10n ** BigInt(digits))).padStart(digits, '0').replace(/0+$/, ''); return fraction ? `${whole}.${fraction}` : String(whole); } function currencyDigits(currency) { return ['bif','clp','djf','gnf','jpy','kmf','krw','mga','pyg','rwf','ugx','vnd','vuv','xaf','xof','xpf'].includes(currency) ? 0 : ['bhd','jod','kwd','omr','tnd'].includes(currency) ? 3 : 2; } function money(micros, currency = state.overview.billing_currency || 'usd') { return new Intl.NumberFormat(undefined, { style:'currency', currency:currency.toUpperCase(), minimumFractionDigits:2, maximumFractionDigits:6 }).format(Number(micros || 0) / 1_000_000); } function integer(value) { return new Intl.NumberFormat().format(Number(value || 0)); } function emptyRow(span) { return `No records yet`; } function showSecret(title, value) { $('#secret-title').textContent = title; $('#created-secret').textContent = value; $('#secret-dialog').showModal(); } async function permitted(permission, path) { if (!can(permission)) return []; return api(path); } async function loadAll() { if (!state.token) { setConnected(false); return; } try { const session = await api('/me'); state.actor = session.actor || {}; state.permissions = new Set(session.permissions || []); state.overview = await api('/overview'); const results = await Promise.all([ permitted('tenants.read','/tenants'), permitted('projects.read','/projects'), permitted('keys.read','/keys'), permitted('platform.read','/providers'), permitted('platform.read','/models'), permitted('usage.read','/usage'), permitted('usage.read','/usage/summary'), permitted('limits.read','/limits'), permitted('users.read','/users'), permitted('audit.read','/audit'), state.overview.billing_enabled ? permitted('billing.read','/billing/accounts') : [], state.overview.billing_enabled ? permitted('billing.read','/billing/ledger') : [] ]); [state.tenants,state.projects,state.keys,state.providers,state.models,state.usage,state.usageSummary,state.limits,state.users,state.audit,state.billingAccounts,state.ledger] = results; renderAll(); setConnected(true); } catch (error) { setConnected(false); toast(error.message, true); } } function applyPermissions() { $$('[data-permission]').forEach(node => node.classList.toggle('hidden', !can(node.dataset.permission))); $('#billing-tab').classList.toggle('hidden', !state.overview.billing_enabled || !can('billing.read')); $('#topup-form').classList.toggle('hidden', !state.overview.stripe_enabled || !can('billing.topup')); const active = $('.tab.active'); if (active?.classList.contains('hidden')) $('.tab[data-section="overview"]').click(); } function renderAll() { applyPermissions(); renderOverview(); renderTenants(); renderProjects(); renderKeys(); renderProviders(); renderModels(); renderBilling(); renderUsage(); renderLimits(); renderUsers(); renderAudit(); renderRouteEditor(); } function renderOverview() { const data = state.overview; const propagation = !data.redis_configured ? 'PG polling' : data.redis_connected ? 'Redis live' : 'PG fallback'; const now = new Date(); const current = state.usageSummary.filter(item => { const period = new Date(item.period_start); return period.getUTCMonth() === now.getUTCMonth() && period.getUTCFullYear() === now.getUTCFullYear(); }); const totals = current.reduce((acc,item) => { acc.requests += item.request_count; acc.success += item.successful_requests; acc.tokens += item.total_tokens; acc.cost += item.cost_micros; return acc; }, {requests:0,success:0,tokens:0,cost:0}); const items = [['Requests', integer(totals.requests), 'this month'], ['Success rate', percent(totals.success,totals.requests), 'completed requests'], ['Tokens', integer(totals.tokens), 'this month'], ['Spend', money(totals.cost), 'metered cost'], ['Projects', data.projects, 'active workspaces'], ['Propagation', propagation, `generation ${data.runtime_generation}`]]; $('#metrics').innerHTML = items.map(([label,value,sub]) => `
${label}${esc(value)}${esc(sub)}
`).join(''); $('#overview-usage-body').innerHTML = current.map(item => `${esc(item.project_name)}${integer(item.request_count)}${percent(item.successful_requests,item.request_count)}${integer(item.total_tokens)}${money(item.cost_micros)}${money(item.uncollected_micros)}`).join('') || emptyRow(6); } function renderTenants() { $('#tenants-body').innerHTML = state.tenants.map(item => `${esc(item.name)}${esc(item.slug)}${esc(item.status)}${date(item.created_at)}`).join('') || emptyRow(4); ['project-tenant','key-tenant','topup-tenant','adjustment-tenant','user-tenant'].forEach(id => { const node=$(`#${id}`); if (node) node.innerHTML=selectOptions(state.tenants,'id','name', state.actor.tenant_id ? 'Current tenant' : 'Select tenant…'); }); if (state.actor.tenant_id) ['project-tenant','key-tenant','topup-tenant','adjustment-tenant','user-tenant'].forEach(id => { const node=$(`#${id}`); if (node) node.value=state.actor.tenant_id; }); } function renderProjects() { $('#projects-body').innerHTML = state.projects.map(item => `${esc(item.name)}${shortID(item.tenant_id)}${esc(item.slug)}${esc(item.status)}`).join('') || emptyRow(4); renderKeyProjects(); } function renderKeyProjects() { const tenant = $('#key-tenant').value; const projects = state.projects.filter(item => !tenant || item.tenant_id === tenant); $('#key-project').innerHTML = selectOptions(projects,'id','name'); } function renderKeys() { $('#keys-body').innerHTML = state.keys.map(item => `${esc(item.name)}${esc(item.key_prefix)}${shortID(item.project_id)}${(item.scopes||[]).map(scope=>`${esc(scope)}`).join('')}${esc(item.status)}${item.status==='active'&&can('keys.write')?``:''}`).join('') || emptyRow(6); } function renderProviders() { $('#providers-body').innerHTML = state.providers.map(item => `${esc(item.name)}${esc(item.protocol)}${esc(item.base_url)}${integer(item.route_count)}${item.enabled?'enabled':'disabled'}${can('platform.write')?``:''}`).join('') || emptyRow(6); } function renderModels() { $('#models-body').innerHTML = state.models.map(item => `${esc(item.public_id)}${esc(item.owned_by||'—')}in ${money(item.input_price_micros_per_million)}/1M · out ${money(item.output_price_micros_per_million)}/1M
${(item.routes||[]).map(route=>`${esc(route.provider_name||route.provider_id).slice(0,24)} → ${esc(route.upstream_model)} p${route.priority} / w${route.weight}`).join('')}
${item.enabled?'enabled':'disabled'}${can('platform.write')?``:''}`).join('') || emptyRow(5); } function renderBilling() { $('#billing-currency').textContent=(state.overview.billing_currency||'').toUpperCase(); $('#billing-accounts-body').innerHTML=state.billingAccounts.map(item=>`${esc(item.tenant_name)}
${shortID(item.tenant_id)}${money(item.balance_micros,item.currency)}${money(item.reserved_micros,item.currency)}${money(item.available_micros,item.currency)}${date(item.updated_at)}`).join('')||emptyRow(5); $('#billing-ledger-body').innerHTML=state.ledger.map(item=>`${date(item.created_at)}${shortID(item.tenant_id)}${esc(item.kind)}${money(item.amount_micros,item.currency)}${money(item.balance_after_micros,item.currency)}${shortID(item.source_id)}`).join('')||emptyRow(6); } function renderUsage() { $('#usage-summary-body').innerHTML=state.usageSummary.map(item=>`${new Date(item.period_start).toLocaleDateString(undefined,{year:'numeric',month:'short'})}${esc(item.project_name)}${integer(item.request_count)}${percent(item.successful_requests,item.request_count)}${integer(item.input_tokens)}${integer(item.output_tokens)}${money(item.cost_micros)}`).join('')||emptyRow(7); $('#usage-events-body').innerHTML=state.usage.map(item=>`${date(item.started_at)}${shortID(item.request_id)}${esc(item.public_model)}${item.status_code}${item.error_type?`${esc(item.error_type)}`:''}${integer(item.total_tokens)}${money(item.cost_micros)}${integer(item.duration_ms)} ms`).join('')||emptyRow(7); } function renderLimits() { const existing=new Map(state.limits.map(item=>[item.project_id,item])); $('#limits-body').innerHTML=state.projects.map(project=>{const item=existing.get(project.id)||{};return `${esc(project.name)}
${shortID(project.id)}${can('limits.write')?'':''}`}).join('')||emptyRow(6); } function renderUsers() { const roles = state.actor.tenant_id ? [['tenant_admin','Tenant admin'],['tenant_billing','Billing'],['tenant_developer','Developer'],['tenant_viewer','Viewer']] : [['platform_admin','Platform admin'],['platform_viewer','Platform viewer'],['tenant_admin','Tenant admin'],['tenant_billing','Billing'],['tenant_developer','Developer'],['tenant_viewer','Viewer']]; $('#user-role').innerHTML=roles.map(([value,label])=>``).join(''); $('#users-body').innerHTML=state.users.map(item=>`${esc(item.display_name)}
${esc(item.email)}${esc(item.role.replaceAll('_',' '))}${shortID(item.tenant_id)}${esc(item.token_prefix)}${date(item.last_used_at)}${esc(item.status)}${item.status==='active'&&can('users.write')?``:''}`).join('')||emptyRow(7); } function renderAudit() { $('#audit-body').innerHTML=state.audit.map(item=>`${date(item.created_at)}${esc(item.actor_role.replaceAll('_',' '))}${esc(item.action)}${esc(item.method)}${item.status_code}${shortID(item.request_id)}${esc(item.remote_ip||'—')}`).join('')||emptyRow(7); } function renderRouteEditor() { const current=$('#route-editor');if(!current.children.length&&can('platform.write'))addRoute();$$('.route-provider').forEach(select=>{const selected=select.value;select.innerHTML=selectOptions(state.providers.filter(item=>item.enabled),'id','name','Provider…');select.value=selected;}); } function addRoute() { const wrapper=document.createElement('div');wrapper.className='route-row';wrapper.innerHTML='';$('#route-editor').appendChild(wrapper);renderRouteEditor(); } document.addEventListener('click',async(event)=>{ const tab=event.target.closest('.tab');if(tab){$$('.tab').forEach(node=>node.classList.toggle('active',node===tab));$$('.section').forEach(node=>node.classList.toggle('active',node.id===tab.dataset.section));return;} if(event.target.id==='reload'){try{await api('/reload',{method:'POST',body:'{}'});await loadAll();toast('Snapshot reloaded');}catch(error){toast(error.message,true);}} if(event.target.id==='add-route')addRoute();if(event.target.closest('.remove-route'))event.target.closest('.route-row').remove(); const revokeKey=event.target.closest('[data-revoke-key]');if(revokeKey&&confirm('Revoke this API key?')){try{await api(`/keys/${revokeKey.dataset.revokeKey}/revoke`,{method:'POST',body:'{}'});await loadAll();toast('API key revoked');}catch(error){toast(error.message,true);}} const revokeUser=event.target.closest('[data-revoke-user]');if(revokeUser&&confirm('Revoke this console credential?')){try{await api(`/users/${revokeUser.dataset.revokeUser}/revoke`,{method:'POST',body:'{}'});await loadAll();toast('Console credential revoked');}catch(error){toast(error.message,true);}} const provider=event.target.closest('[data-toggle-provider]');if(provider){try{await api(`/providers/${provider.dataset.toggleProvider}/toggle`,{method:'POST',body:JSON.stringify({enabled:provider.dataset.enabled==='true'})});await loadAll();toast('Provider updated');}catch(error){toast(error.message,true);}} const model=event.target.closest('[data-toggle-model]');if(model){try{await api(`/models/${model.dataset.toggleModel}/toggle`,{method:'POST',body:JSON.stringify({enabled:model.dataset.enabled==='true'})});await loadAll();toast('Model updated');}catch(error){toast(error.message,true);}} const save=event.target.closest('.save-limit');if(save){const row=save.closest('[data-limit-project]');try{await api(`/limits/${row.dataset.limitProject}`,{method:'POST',body:JSON.stringify({requests_per_minute:Number(row.querySelector('.limit-rpm').value),tokens_per_minute:Number(row.querySelector('.limit-tpm').value),concurrent_requests:Number(row.querySelector('.limit-concurrency').value),monthly_spend_micros:decimalToScaled(row.querySelector('.limit-spend').value,6)})});await loadAll();toast('Project limits updated');}catch(error){toast(error.message,true);}} }); $('#key-tenant').addEventListener('change',renderKeyProjects); $('#session-form').addEventListener('submit',async(event)=>{event.preventDefault();state.token=$('#admin-token').value.trim();sessionStorage.setItem('aigw_admin_token',state.token);await loadAll();}); $('#tenant-form').addEventListener('submit',async(event)=>{event.preventDefault();try{await api('/tenants',{method:'POST',body:JSON.stringify(formJSON(event.target))});event.target.reset();await loadAll();toast('Tenant created');}catch(error){toast(error.message,true);}}); $('#project-form').addEventListener('submit',async(event)=>{event.preventDefault();try{await api('/projects',{method:'POST',body:JSON.stringify(formJSON(event.target))});event.target.reset();await loadAll();toast('Project created');}catch(error){toast(error.message,true);}}); $('#key-form').addEventListener('submit',async(event)=>{event.preventDefault();try{const data=formJSON(event.target);data.scopes=data.scopes.split(',').map(value=>value.trim()).filter(Boolean);const result=await api('/keys',{method:'POST',body:JSON.stringify(data)});event.target.reset();showSecret('API key created',result.key);await loadAll();}catch(error){toast(error.message,true);}}); $('#provider-form').addEventListener('submit',async(event)=>{event.preventDefault();try{await api('/providers',{method:'POST',body:JSON.stringify(formJSON(event.target))});event.target.reset();await loadAll();toast('Provider added');}catch(error){toast(error.message,true);}}); $('#model-form').addEventListener('submit',async(event)=>{event.preventDefault();try{const data=formJSON(event.target);data.input_price_micros_per_million=decimalToScaled(data.input_price,6);data.output_price_micros_per_million=decimalToScaled(data.output_price,6);data.cache_read_price_micros_per_million=decimalToScaled(data.cache_read_price,6);data.cache_write_price_micros_per_million=decimalToScaled(data.cache_write_price,6);delete data.input_price;delete data.output_price;delete data.cache_read_price;delete data.cache_write_price;data.routes=$$('.route-row').map(row=>({provider_id:row.querySelector('.route-provider').value,upstream_model:row.querySelector('.route-upstream').value,priority:Number(row.querySelector('.route-priority').value),weight:Number(row.querySelector('.route-weight').value)}));await api('/models',{method:'POST',body:JSON.stringify(data)});event.target.reset();$('#route-editor').innerHTML='';renderRouteEditor();await loadAll();toast('Model created');}catch(error){toast(error.message,true);}}); $('#topup-form').addEventListener('submit',async(event)=>{event.preventDefault();try{const data=formJSON(event.target);const digits=currencyDigits(state.overview.billing_currency||'usd');const result=await api('/billing/checkout-sessions',{method:'POST',body:JSON.stringify({tenant_id:data.tenant_id,amount_minor:decimalToScaled(data.amount,digits)})});window.location.assign(result.url);}catch(error){toast(error.message,true);}}); $('#adjustment-form').addEventListener('submit',async(event)=>{event.preventDefault();try{const data=formJSON(event.target);await api('/billing/adjustments',{method:'POST',body:JSON.stringify({tenant_id:data.tenant_id,amount_micros:decimalToScaled(data.amount,6),description:data.description})});event.target.reset();await loadAll();toast('Balance adjusted');}catch(error){toast(error.message,true);}}); $('#user-form').addEventListener('submit',async(event)=>{event.preventDefault();try{const result=await api('/users',{method:'POST',body:JSON.stringify(formJSON(event.target))});event.target.reset();showSecret('Console token issued',result.token);await loadAll();}catch(error){toast(error.message,true);}}); $('#close-dialog').addEventListener('click',()=>$('#secret-dialog').close());$('#copy-secret').addEventListener('click',async()=>{await navigator.clipboard.writeText($('#created-secret').textContent);toast('Credential copied');}); $('#admin-token').value=state.token;applyPermissions();if(state.token)loadAll();