/* portal/usage/public/usage.js — 사용량 화면(§13.2·§13.4). /api/usage 를 그려 준다. */ (() => { const el = document.getElementById('usage'); const esc = aw.esc; const bar = (pct, lvl) => `
`; const lvlOf = (p) => (p >= 100 ? 100 : p >= 95 ? 95 : p >= 90 ? 90 : p >= 80 ? 80 : p >= 70 ? 70 : 0); async function load() { try { const d = await aw.api('GET', '/api/usage'); if (!d.product) { aw.state(el, 'empty', '활성 상품이 없습니다. 상품을 주문하면 사용량이 표시됩니다', { href: '/products', hrefLabel: '상품 보기' }); return; } let h = `
적용 상품정책
${esc(d.product.name)} v${d.product.version} (경고 구간 ${d.product.warnLevels.join('·')}%)
다음 KST 월 초기화
${esc(d.nextResetText)}
마지막 계측
${esc(d.lastMeasuredText)}${d.stale ? ' ! 정보 확인 지연' : ''}
`; if (d.limits.length) h += `

경고·제한

`; h += `

그룹 전체(구매 총량 기준)

${d.resources.map((r) => ``).join('')}
자원구매 총량배정예약(진행 중 작업)미배정실제 사용사용률
${esc(r.label)}${r.monthly ? ' (월간)' : ''}${fmt(r.purchased, r.unit)}${fmt(r.allocated, r.unit)}${fmt(r.reserved, r.unit)}${fmt(r.unallocated, r.unit)}${fmt(r.used, r.unit)}${bar(r.usedPercent, lvlOf(r.usedPercent))} ${r.usedPercent}%
`; h += `

서비스별 배정과 사용

${d.allocations.length ? `${d.allocations.map((a) => ``).join('')}
서비스자원배정상태사용계측
${esc(a.subjectLabel)} · ${esc(a.subjectName)}${esc(a.label)}${esc(a.amountText)}○ ${esc(a.stateLabel)}${esc(a.usedText)} ${a.percent != null ? bar(a.percent, lvlOf(a.percent)) + a.percent + '%' : ''}${a.measured_at ? aw.fmt(a.measured_at) : '계측 전'}${a.stale ? ' ! 지연' : ''}배정 변경
` : '

아직 배정된 서비스가 없습니다. 사이트나 메일 서비스를 만들면 여기에 표시됩니다.

'}
`; if (d.reservations.length) h += `

진행 중 작업의 예약

${d.reservations.map((r) => ``).join('')}
자원양상태사유만료·반환 예정
${esc(r.label)}${r.amount}${esc(r.stateLabel)}${esc(r.reason || '')}${r.return_due_at ? aw.fmt(r.return_due_at) : r.expires_at ? aw.fmt(r.expires_at) : ''}
`; el.innerHTML = h; } catch (e) { aw.state(el, e.status === 403 ? 'forbidden' : 'failed', e.message, { retry: load }); } } function fmt(n, unit) { if (unit.startsWith('bytes')) return n >= 1024 ** 3 ? (n / 1024 ** 3).toFixed(1) + ' GB' : n >= 1024 ** 2 ? (n / 1024 ** 2).toFixed(0) + ' MB' : n + ' B'; return Number(n).toLocaleString('ko-KR'); } document.addEventListener('click', async (e) => { if (e.target.closest('[data-action=evaluate]')) { try { const r = await aw.api('POST', '/api/usage/evaluate'); aw.toast(`평가 ${r.evaluated}건 · 새 알림 ${r.notified.length}건`); load(); } catch (err) { aw.toast(err.message); } } }); load(); })();