Skip to content

Commit c40df01

Browse files
committed
fix(compliance): 카테고리별 현황을 카탈로그 트리와 동일하게 프레임워크·도메인·섹션 계층으로 그룹핑
1 parent d9c8cd9 commit c40df01

2 files changed

Lines changed: 51 additions & 11 deletions

File tree

src/mori_soc/api/payloads.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,10 +160,12 @@ def build_pdca_payload(
160160
_catalog = load_catalog()
161161
_sec_of: dict[str, str] = {}
162162
_fw_of: dict[str, str] = {}
163+
_dom_of: dict[str, str] = {}
163164
for cc in _catalog.get("controls", []):
164165
cid = str(cc.get("id", ""))
165166
_sec_of[cid] = cc.get("section") or cc.get("domain") or cid.rsplit(".", 1)[0]
166167
_fw_of[cid] = cc.get("framework", "")
168+
_dom_of[cid] = cc.get("domain") or _sec_of[cid]
167169

168170
def _cat_of(control_id: str) -> str:
169171
sec = _sec_of.get(control_id)
@@ -174,9 +176,11 @@ def _cat_of(control_id: str) -> str:
174176

175177
by_category: dict[str, dict[str, int]] = {}
176178
cat_fw: dict[str, str] = {}
179+
cat_dom: dict[str, str] = {}
177180
for c in checks:
178181
cat = _cat_of(c.control_id)
179182
cat_fw.setdefault(cat, _fw_of.get(c.control_id, ""))
183+
cat_dom.setdefault(cat, _dom_of.get(c.control_id, cat))
180184
bucket = by_category.setdefault(cat, {"pass": 0, "fail": 0, "warning": 0, "not_applicable": 0, "not_checked": 0, "total": 0})
181185
bucket[c.status] = bucket.get(c.status, 0) + 1
182186
bucket["total"] += 1
@@ -189,7 +193,7 @@ def _sort_key(cat: str):
189193
return (fw_rank, key)
190194

191195
categories = [
192-
{"category": cat, **counts}
196+
{"category": cat, "framework": cat_fw.get(cat, ""), "domain": cat_dom.get(cat, cat), **counts}
193197
for cat, counts in sorted(by_category.items(), key=lambda kv: _sort_key(kv[0]))
194198
]
195199

src/mori_soc/api/templates/dashboard.py

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3605,6 +3605,51 @@ def render_user_dashboard_html(
36053605
if (cats.length === 0) {
36063606
categoryEl.innerHTML = '<div class=\"empty\" style=\"color:#111827;padding:12px\">' + tt('dash.dyn.pdca.no_check_data','점검 데이터가 없습니다.') + '</div>';
36073607
} else {
3608+
// 통제 카탈로그 트리와 동일하게 프레임워크 → 도메인 → 섹션(카테고리) 계층으로 묶어 매칭.
3609+
const _fwLabel = { 'isms-p': 'ISMS-P', 'iso27001': 'ISO 27001:2022', 'custom': 'Custom / 법령' };
3610+
// cats 는 payload 에서 이미 트리 순서(ISMS-P 먼저 · 섹션번호 자연정렬)로 정렬됨.
3611+
const groups = []; // {fw, domain, rows:[], agg:{}}
3612+
cats.forEach(c => {
3613+
const fw = c.framework || '', dom = c.domain || c.category;
3614+
let g = groups.length ? groups[groups.length-1] : null;
3615+
if (!g || g.fw !== fw || g.domain !== dom) {
3616+
g = { fw, domain: dom, rows: [], agg: {pass:0,fail:0,warning:0,not_checked:0,total:0} };
3617+
groups.push(g);
3618+
}
3619+
g.rows.push(c);
3620+
g.agg.pass += c.pass; g.agg.fail += c.fail; g.agg.warning += c.warning;
3621+
g.agg.not_checked += c.not_checked; g.agg.total += c.total;
3622+
});
3623+
let lastFw = null;
3624+
const body = groups.map(g => {
3625+
let out = '';
3626+
if (g.fw !== lastFw) {
3627+
lastFw = g.fw;
3628+
out += `<tr style=\"background:#f9fafb\"><td colspan=\"6\" style=\"padding:6px 8px;color:#2563eb;font-weight:700;font-size:12px\">${escapeHtml(_fwLabel[g.fw]||g.fw||'')}</td></tr>`;
3629+
}
3630+
const domSameAsSec = (g.rows.length === 1 && g.rows[0].category === g.domain);
3631+
if (!domSameAsSec) {
3632+
out += `<tr style=\"border-bottom:1px solid #e5e7eb\">
3633+
<td style=\"padding:6px 8px;color:#111827;font-weight:700\">${escapeHtml(g.domain)}</td>
3634+
<td style=\"text-align:right;padding:6px 8px;color:#16a34a;font-weight:700\">${g.agg.pass}</td>
3635+
<td style=\"text-align:right;padding:6px 8px;color:#dc2626;font-weight:700\">${g.agg.fail}</td>
3636+
<td style=\"text-align:right;padding:6px 8px;color:#ea580c;font-weight:700\">${g.agg.warning}</td>
3637+
<td style=\"text-align:right;padding:6px 8px;color:#111827;font-weight:700\">${g.agg.not_checked}</td>
3638+
<td style=\"text-align:right;padding:6px 8px;color:#111827;font-weight:700\">${g.agg.total}</td></tr>`;
3639+
}
3640+
out += g.rows.map(c => {
3641+
const pad = domSameAsSec ? '6px 8px' : '5px 8px 5px 22px';
3642+
const fw2 = domSameAsSec ? 'font-weight:700' : 'font-weight:400';
3643+
return `<tr style=\"border-bottom:1px solid #f1f5f9\">
3644+
<td style=\"padding:${pad};color:#111827;${fw2}\">${escapeHtml(c.category)}</td>
3645+
<td style=\"text-align:right;padding:${pad};color:#16a34a\">${c.pass}</td>
3646+
<td style=\"text-align:right;padding:${pad};color:#dc2626\">${c.fail}</td>
3647+
<td style=\"text-align:right;padding:${pad};color:#ea580c\">${c.warning}</td>
3648+
<td style=\"text-align:right;padding:${pad};color:#111827\">${c.not_checked}</td>
3649+
<td style=\"text-align:right;padding:${pad};color:#111827\">${c.total}</td></tr>`;
3650+
}).join('');
3651+
return out;
3652+
}).join('');
36083653
categoryEl.innerHTML = `<table style=\"width:100%;border-collapse:collapse;font-size:13px\">
36093654
<thead><tr style=\"color:#111827;border-bottom:1px solid #e5e7eb\">
36103655
<th style=\"text-align:left;padding:6px 8px\">${tt('dash.dyn.pdca.category','카테고리')}</th>
@@ -3613,16 +3658,7 @@ def render_user_dashboard_html(
36133658
<th style=\"text-align:right;padding:6px 8px\">Warning</th>
36143659
<th style=\"text-align:right;padding:6px 8px\">${tt('dash.dyn.pdca.not_checked','미점검')}</th>
36153660
<th style=\"text-align:right;padding:6px 8px\">${tt('dash.dyn.lbl.total','합계')}</th>
3616-
</tr></thead><tbody>`
3617-
+ cats.map(c => `<tr style=\"border-bottom:1px solid #e5e7eb\">
3618-
<td style=\"padding:6px 8px;color:#111827;font-weight:600\">${escapeHtml(c.category)}</td>
3619-
<td style=\"text-align:right;padding:6px 8px;color:#16a34a\">${c.pass}</td>
3620-
<td style=\"text-align:right;padding:6px 8px;color:#dc2626\">${c.fail}</td>
3621-
<td style=\"text-align:right;padding:6px 8px;color:#ea580c\">${c.warning}</td>
3622-
<td style=\"text-align:right;padding:6px 8px;color:#111827\">${c.not_checked}</td>
3623-
<td style=\"text-align:right;padding:6px 8px;color:#111827\">${c.total}</td>
3624-
</tr>`).join('')
3625-
+ '</tbody></table>';
3661+
</tr></thead><tbody>` + body + '</tbody></table>';
36263662
}
36273663
}
36283664
// Pending remediations (control_check + trivy + alert)

0 commit comments

Comments
 (0)