/* portal/dashboard/public/dashboard.js — 대시보드 요약 카드·주의 항목·최근 활동 렌더(30초마다 갱신). */ (() => { const el = document.getElementById('dash'); if (!el) return; const ACT = { 'deploy.start': '배포 시작', 'deploy.done': '배포 완료', 'order.created': '주문 제출', 'order.approve': '주문 승인', 'member.invited': '구성원 초대', 'member.joined': '구성원 참여', 'member.role': '역할 변경', 'group.settings': '그룹 설정 변경', 'permission.site': '사이트 권한 변경', 'ownership.group_transferred': '그룹 소유권 이전', 'member.suspended': '구성원 정지' }; const fmtKrw = (n) => `${Number(n || 0).toLocaleString('ko-KR')}원`; async function load() { try { const d = await aw.api('GET', '/api/dashboard'); const s = d.summary; const card = (title, stat, sub) => `

${aw.esc(title)}

${stat}
${sub ? `
${sub}
` : ''}
`; el.innerHTML = `
${card('운영 사이트', `${s.sites.inUse} / 구매 ${s.sites.purchased}개`, `사용 중 ${s.sites.inUse} · 예약됨 ${s.sites.reserved} · 미배정 ${s.sites.unallocated}`)} ${card('초안·제작 중', s.drafts, '운영 사이트 수를 사용하지 않습니다')} ${card('사이트 상태', `● 정상 ${s.health.ok} ! 경고 ${s.health.warn} ✘ 장애 ${s.health.bad}`)} ${card('배포 방식', `관리형 ${s.deploy.managed} · 외부 ${s.deploy.external}`)} ${card('도메인', `${s.domains.total}`, s.domains.action ? `조치 필요 ${s.domains.action}개` : '조치 필요 없음')} ${card('메일', `서비스 ${s.mail.services} · 메일함 ${s.mail.mailboxes}`)} ${card('백업 옵션 사이트', s.backup)} ${card('진행 중 작업', s.jobs.length ? s.jobs.map((j) => `${aw.esc(j.label)} ${j.n}`).join(' · ') : '0')} ${card('이번 달 예상 비용', fmtKrw(s.cost.estimated), `결제 대기 ${fmtKrw(s.cost.awaiting)} · ${aw.esc(s.cost.note)}`)}

주의 항목

${d.attention.length ? d.attention.map((a) => `${a.severity === 'critical' ? '⚠' : '!'} ${aw.esc(a.text)}`).join('') : '
지금 조치할 항목이 없습니다
'}

최근 활동

${d.recent.length ? `${d.recent.map((r) => ``).join('')}
시각내용주체결과
${aw.esc(aw.fmt(r.at))}${aw.esc(ACT[r.action] || r.action)}${r.subjectType ? ` ${aw.esc(r.subjectType)} ${aw.esc(r.subjectId || '')}` : ''}${r.automatic ? '⚙ 자동' : `👤 ${aw.esc(r.actor)}`}${r.outcome === 'ok' ? '✔ 성공' : `✘ ${aw.esc(r.outcome)}`}
` : '
최근 활동이 없습니다
'}

마지막 갱신 ${aw.esc(aw.fmt(d.generatedAt))}

`; } catch (err) { aw.state(el, err.status === 403 ? 'forbidden' : err.code === 'service_unreachable' ? 'stale' : 'failed', err.message, { retry: load }); } } load(); aw.poll(load, 30000); })();