Files
AriWeb-Suite/portal/usage/public/usage.js
T

22 lines
4.6 KiB
JavaScript

/* portal/usage/public/usage.js — 사용량 화면(§13.2·§13.4). /api/usage 를 그려 준다. */
(() => {
const el = document.getElementById('usage'); const esc = aw.esc;
const bar = (pct, lvl) => `<div class="bar w${lvl || 0}" aria-label="${pct}%"><i style="width:${Math.min(100, pct)}%"></i></div>`;
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 = `<div class="card"><dl class="kv"><dt>적용 상품정책</dt><dd>${esc(d.product.name)} v${d.product.version} (경고 구간 ${d.product.warnLevels.join('·')}%)</dd><dt>다음 KST 월 초기화</dt><dd>${esc(d.nextResetText)}</dd><dt>마지막 계측</dt><dd>${esc(d.lastMeasuredText)}${d.stale ? ' <span class="badge warning">! 정보 확인 지연</span>' : ''}</dd></dl></div>`;
if (d.limits.length) h += `<div class="card"><h2>경고·제한</h2><ul>${d.limits.map((l) => `<li><span class="badge ${l.limited ? 'failed' : 'warning'}">${l.limited ? '✘ 제한' : '! ' + l.level + '%'}</span> ${esc(l.resource)} · ${esc(l.subject_type)} ${esc(l.subject_id)}${l.limited && l.semantics ? ` — 제한: ${esc(l.semantics.blocked.join(', '))} / 유지: ${esc(l.semantics.kept.join(', '))} / 해제: ${esc(l.semantics.recovery)}` : ''}${l.overage ? ' (초과 사용 허용 중)' : ''}</li>`).join('')}</ul></div>`;
h += `<div class="card"><h2>그룹 전체(구매 총량 기준)</h2><table class="r"><thead><tr><th>자원</th><th>구매 총량</th><th>배정</th><th>예약(진행 중 작업)</th><th>미배정</th><th>실제 사용</th><th>사용률</th></tr></thead><tbody>${d.resources.map((r) => `<tr><td data-l="자원">${esc(r.label)}${r.monthly ? ' <span class="sub">(월간)</span>' : ''}</td><td data-l="구매">${fmt(r.purchased, r.unit)}</td><td data-l="배정">${fmt(r.allocated, r.unit)}</td><td data-l="예약">${fmt(r.reserved, r.unit)}</td><td data-l="미배정">${fmt(r.unallocated, r.unit)}</td><td data-l="사용">${fmt(r.used, r.unit)}</td><td data-l="사용률">${bar(r.usedPercent, lvlOf(r.usedPercent))} ${r.usedPercent}%</td></tr>`).join('')}</tbody></table></div>`;
h += `<div class="card"><h2>서비스별 배정과 사용</h2>${d.allocations.length ? `<table class="r"><thead><tr><th>서비스</th><th>자원</th><th>배정</th><th>상태</th><th>사용</th><th>계측</th><th></th></tr></thead><tbody>${d.allocations.map((a) => `<tr><td data-l="서비스">${esc(a.subjectLabel)} · ${esc(a.subjectName)}</td><td data-l="자원">${esc(a.label)}</td><td data-l="배정">${esc(a.amountText)}</td><td data-l="상태"><span class="badge neutral">○ ${esc(a.stateLabel)}</span></td><td data-l="사용">${esc(a.usedText)} ${a.percent != null ? bar(a.percent, lvlOf(a.percent)) + a.percent + '%' : ''}</td><td data-l="계측">${a.measured_at ? aw.fmt(a.measured_at) : '계측 전'}${a.stale ? ' <span class="badge warning">! 지연</span>' : ''}</td><td data-l=""><a class="btn" href="/usage/allocate/${esc(a.subject_type)}:${esc(a.subject_id)}">배정 변경</a></td></tr>`).join('')}</tbody></table>` : '<p class="muted">아직 배정된 서비스가 없습니다. 사이트나 메일 서비스를 만들면 여기에 표시됩니다.</p>'}</div>`;
if (d.reservations.length) h += `<div class="card"><h2>진행 중 작업의 예약</h2><table class="r"><thead><tr><th>자원</th><th>양</th><th>상태</th><th>사유</th><th>만료·반환 예정</th></tr></thead><tbody>${d.reservations.map((r) => `<tr><td data-l="자원">${esc(r.label)}</td><td data-l="양">${r.amount}</td><td data-l="상태">${esc(r.stateLabel)}</td><td data-l="사유">${esc(r.reason || '')}</td><td data-l="예정">${r.return_due_at ? aw.fmt(r.return_due_at) : r.expires_at ? aw.fmt(r.expires_at) : ''}</td></tr>`).join('')}</tbody></table></div>`;
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();
})();