@greenarmor/ges-web-dashboard 1.2.2 → 1.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1 -1
- package/dist/template.js +225 -55
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -189,7 +189,7 @@ export function collectDashboardData(projectPath) {
|
|
|
189
189
|
projectName: config?.project_name || "Unknown Project",
|
|
190
190
|
projectType: config?.project_type || "unknown",
|
|
191
191
|
frameworks: config?.frameworks || [],
|
|
192
|
-
gesfVersion: "1.2.
|
|
192
|
+
gesfVersion: "1.2.4",
|
|
193
193
|
score,
|
|
194
194
|
controls,
|
|
195
195
|
findings,
|
package/dist/template.js
CHANGED
|
@@ -1,3 +1,29 @@
|
|
|
1
|
+
function matchPackForControl(controlId, packs) {
|
|
2
|
+
const idUpper = controlId.toUpperCase();
|
|
3
|
+
for (const p of packs) {
|
|
4
|
+
if (p.id === "gdpr" && idUpper.startsWith("GDPR-"))
|
|
5
|
+
return p;
|
|
6
|
+
if (p.id === "owasp" && idUpper.startsWith("OWASP-"))
|
|
7
|
+
return p;
|
|
8
|
+
if (p.id === "cis" && idUpper.startsWith("CIS-"))
|
|
9
|
+
return p;
|
|
10
|
+
if (p.id === "nist" && idUpper.startsWith("NIST-"))
|
|
11
|
+
return p;
|
|
12
|
+
if (p.id === "ai" && idUpper.startsWith("AI-"))
|
|
13
|
+
return p;
|
|
14
|
+
if (p.id === "blockchain" && idUpper.startsWith("BC-"))
|
|
15
|
+
return p;
|
|
16
|
+
if (p.id === "government" && idUpper.startsWith("GOV-"))
|
|
17
|
+
return p;
|
|
18
|
+
if (p.id === "iso27001" && idUpper.startsWith("ISO27K-"))
|
|
19
|
+
return p;
|
|
20
|
+
if (p.id === "iso27701" && idUpper.startsWith("ISO277-"))
|
|
21
|
+
return p;
|
|
22
|
+
if (p.id === "hipaa" && idUpper.startsWith("HIPAA-"))
|
|
23
|
+
return p;
|
|
24
|
+
}
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
1
27
|
function gradeColor(grade) {
|
|
2
28
|
switch (grade) {
|
|
3
29
|
case "A": return "#22c55e";
|
|
@@ -106,6 +132,45 @@ export function renderDashboard(data) {
|
|
|
106
132
|
...p,
|
|
107
133
|
_controls: undefined,
|
|
108
134
|
})));
|
|
135
|
+
const complianceIssues = controls
|
|
136
|
+
.filter(c => c.status !== "pass" && c.status !== "not-applicable")
|
|
137
|
+
.map(c => {
|
|
138
|
+
const pack = matchPackForControl(c.id, packs);
|
|
139
|
+
const auditFindings = findings.filter(f => f.controlIds.includes(c.id));
|
|
140
|
+
return {
|
|
141
|
+
controlId: c.id,
|
|
142
|
+
controlName: c.name,
|
|
143
|
+
severity: c.severity,
|
|
144
|
+
status: c.status,
|
|
145
|
+
category: c.category,
|
|
146
|
+
framework: c.framework,
|
|
147
|
+
article: c.article,
|
|
148
|
+
description: c.description,
|
|
149
|
+
implementation_guidance: c.implementation_guidance,
|
|
150
|
+
packId: pack?.id || "",
|
|
151
|
+
packName: pack?.name || "Direct",
|
|
152
|
+
passedChecks: c.checks.filter(ch => ch.status === "pass").length,
|
|
153
|
+
totalChecks: c.checks.length,
|
|
154
|
+
auditFindings,
|
|
155
|
+
};
|
|
156
|
+
})
|
|
157
|
+
.sort((a, b) => {
|
|
158
|
+
const sevOrder = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
159
|
+
return (sevOrder[a.severity] ?? 4) - (sevOrder[b.severity] ?? 4);
|
|
160
|
+
});
|
|
161
|
+
const issuesBySeverity = {
|
|
162
|
+
critical: complianceIssues.filter(i => i.severity === "critical").length,
|
|
163
|
+
high: complianceIssues.filter(i => i.severity === "high").length,
|
|
164
|
+
medium: complianceIssues.filter(i => i.severity === "medium").length,
|
|
165
|
+
low: complianceIssues.filter(i => i.severity === "low").length,
|
|
166
|
+
};
|
|
167
|
+
const issuesByPackId = {};
|
|
168
|
+
for (const issue of complianceIssues) {
|
|
169
|
+
const key = issue.packId || "direct";
|
|
170
|
+
if (!issuesByPackId[key])
|
|
171
|
+
issuesByPackId[key] = [];
|
|
172
|
+
issuesByPackId[key].push(issue);
|
|
173
|
+
}
|
|
109
174
|
return `<!DOCTYPE html>
|
|
110
175
|
<html lang="en">
|
|
111
176
|
<head>
|
|
@@ -405,100 +470,97 @@ export function renderDashboard(data) {
|
|
|
405
470
|
<div id="page-fixes" class="page">
|
|
406
471
|
<div class="tab-bar" style="margin-bottom:0;">
|
|
407
472
|
<button class="tab-btn active" onclick="showFixesTab('history', this)">Fix History (${data.fixHistory.length})</button>
|
|
408
|
-
<button class="tab-btn" onclick="showFixesTab('pending', this)">Pending Fixes (${
|
|
473
|
+
<button class="tab-btn" onclick="showFixesTab('pending', this)">Pending Fixes (${complianceIssues.length})</button>
|
|
409
474
|
</div>
|
|
410
475
|
|
|
411
476
|
<div id="fixes-tab-history" class="tab-panel active">
|
|
412
|
-
${renderFixHistorySection(data.fixHistory)}
|
|
477
|
+
${renderFixHistorySection(data.fixHistory, complianceIssues)}
|
|
413
478
|
</div>
|
|
414
479
|
|
|
415
480
|
<div id="fixes-tab-pending" class="tab-panel">
|
|
416
|
-
${
|
|
481
|
+
${renderComplianceFixCards(complianceIssues, "fix")}
|
|
417
482
|
</div>
|
|
418
483
|
</div>
|
|
419
484
|
|
|
420
485
|
<div id="page-findings" class="page">
|
|
421
486
|
<div id="findings-main">
|
|
422
|
-
<h2 style="font-size:20px;font-weight:700;margin-bottom:
|
|
487
|
+
<h2 style="font-size:20px;font-weight:700;margin-bottom:8px;">Compliance Findings & Issues</h2>
|
|
488
|
+
<p style="color:#6b7280;font-size:14px;margin-bottom:20px;">Every control that is not passing is a compliance finding. Code-level audit evidence is shown where available.</p>
|
|
489
|
+
|
|
490
|
+
<div class="grid grid-4" style="margin-bottom:20px;">
|
|
491
|
+
<div class="card stat"><div class="num" style="color:${complianceIssues.length > 0 ? '#ef4444' : '#22c55e'};">${complianceIssues.length}</div><div class="label">Total Issues</div></div>
|
|
492
|
+
<div class="card stat"><div class="num" style="color:#ef4444;">${issuesBySeverity.critical}</div><div class="label">Critical</div></div>
|
|
493
|
+
<div class="card stat"><div class="num" style="color:#f97316;">${issuesBySeverity.high}</div><div class="label">High</div></div>
|
|
494
|
+
<div class="card stat"><div class="num">${findings.length}</div><div class="label">Audit Evidence</div></div>
|
|
495
|
+
</div>
|
|
496
|
+
|
|
423
497
|
<div class="tab-bar">
|
|
424
|
-
<button class="tab-btn active" onclick="showFindingsTab('all', this)">All (${
|
|
425
|
-
<button class="tab-btn" onclick="showFindingsTab('critical', this)">Critical (${
|
|
426
|
-
<button class="tab-btn" onclick="showFindingsTab('high', this)">High (${
|
|
427
|
-
<button class="tab-btn" onclick="showFindingsTab('medium', this)">Medium (${
|
|
428
|
-
<button class="tab-btn" onclick="showFindingsTab('low', this)">Low (${
|
|
498
|
+
<button class="tab-btn active" onclick="showFindingsTab('all', this)">All Issues (${complianceIssues.length})</button>
|
|
499
|
+
<button class="tab-btn" onclick="showFindingsTab('critical', this)">Critical (${issuesBySeverity.critical})</button>
|
|
500
|
+
<button class="tab-btn" onclick="showFindingsTab('high', this)">High (${issuesBySeverity.high})</button>
|
|
501
|
+
<button class="tab-btn" onclick="showFindingsTab('medium', this)">Medium (${issuesBySeverity.medium})</button>
|
|
502
|
+
<button class="tab-btn" onclick="showFindingsTab('low', this)">Low (${issuesBySeverity.low})</button>
|
|
429
503
|
<button class="tab-btn" onclick="showFindingsTab('bypack', this)">By Pack</button>
|
|
504
|
+
${findings.length > 0 ? `<button class="tab-btn" onclick="showFindingsTab('evidence', this)">Audit Evidence (${findings.length})</button>` : ''}
|
|
430
505
|
</div>
|
|
431
506
|
|
|
432
507
|
<div id="findings-tab-all" class="tab-panel active">
|
|
433
|
-
${
|
|
508
|
+
${renderComplianceIssuesTable(complianceIssues)}
|
|
434
509
|
</div>
|
|
435
|
-
<div id="findings-tab-critical" class="tab-panel">${
|
|
436
|
-
<div id="findings-tab-high" class="tab-panel">${
|
|
437
|
-
<div id="findings-tab-medium" class="tab-panel">${
|
|
438
|
-
<div id="findings-tab-low" class="tab-panel">${
|
|
510
|
+
<div id="findings-tab-critical" class="tab-panel">${renderComplianceIssuesTable(complianceIssues.filter(i => i.severity === "critical"))}</div>
|
|
511
|
+
<div id="findings-tab-high" class="tab-panel">${renderComplianceIssuesTable(complianceIssues.filter(i => i.severity === "high"))}</div>
|
|
512
|
+
<div id="findings-tab-medium" class="tab-panel">${renderComplianceIssuesTable(complianceIssues.filter(i => i.severity === "medium"))}</div>
|
|
513
|
+
<div id="findings-tab-low" class="tab-panel">${renderComplianceIssuesTable(complianceIssues.filter(i => i.severity === "low"))}</div>
|
|
439
514
|
<div id="findings-tab-bypack" class="tab-panel">
|
|
440
|
-
${packs.filter(p => (
|
|
515
|
+
${packs.filter(p => (issuesByPackId[p.id] || []).length > 0).length > 0 ? packs.filter(p => (issuesByPackId[p.id] || []).length > 0).map(p => `
|
|
441
516
|
<div class="card" style="margin-bottom:16px;">
|
|
442
517
|
<div class="card-title" style="cursor:pointer;" onclick="loadPackDetail('${p.id}')">
|
|
443
|
-
${escapeHtml(p.name)} — ${(
|
|
518
|
+
${escapeHtml(p.name)} — ${(issuesByPackId[p.id] || []).length} issues
|
|
444
519
|
<span style="float:right;color:#0f766e;font-weight:400;font-size:11px;">View pack details →</span>
|
|
445
520
|
</div>
|
|
446
|
-
${
|
|
521
|
+
${renderComplianceIssuesTable(issuesByPackId[p.id] || [])}
|
|
447
522
|
</div>
|
|
448
|
-
`).join('') : '<div class="empty-state"><div class="msg">No
|
|
523
|
+
`).join('') : '<div class="empty-state"><div class="msg">No compliance issues mapped to policy packs</div></div>'}
|
|
449
524
|
</div>
|
|
525
|
+
${findings.length > 0 ? `<div id="findings-tab-evidence" class="tab-panel">${renderFindingsTable(findings)}</div>` : ''}
|
|
450
526
|
</div>
|
|
451
527
|
<div id="finding-detail" style="display:none;"></div>
|
|
452
528
|
</div>
|
|
453
529
|
|
|
454
530
|
<div id="page-traceability" class="page">
|
|
455
|
-
<h2 style="font-size:20px;font-weight:700;margin-bottom:8px;">
|
|
456
|
-
<p style="color:#6b7280;font-size:14px;margin-bottom:20px;">
|
|
531
|
+
<h2 style="font-size:20px;font-weight:700;margin-bottom:8px;">Compliance Traceability Matrix</h2>
|
|
532
|
+
<p style="color:#6b7280;font-size:14px;margin-bottom:20px;">Full traceability: Control → Framework → Policy Pack → Severity → Fix Guidance for every compliance issue.</p>
|
|
457
533
|
<div class="tab-bar">
|
|
458
|
-
<button class="tab-btn active" onclick="showTraceTab('matrix', this)">Matrix</button>
|
|
534
|
+
<button class="tab-btn active" onclick="showTraceTab('matrix', this)">Matrix (${complianceIssues.length})</button>
|
|
459
535
|
<button class="tab-btn" onclick="showTraceTab('fixes', this)">Prioritized Fixes</button>
|
|
460
536
|
<button class="tab-btn" onclick="showTraceTab('controls', this)">Control Coverage</button>
|
|
461
537
|
</div>
|
|
462
538
|
|
|
463
539
|
<div id="trace-tab-matrix" class="tab-panel active">
|
|
464
|
-
${
|
|
540
|
+
${complianceIssues.length > 0 ? `<div class="card">
|
|
465
541
|
<table>
|
|
466
|
-
<thead><tr><th>
|
|
542
|
+
<thead><tr><th>Control</th><th>Severity</th><th>Framework</th><th>Policy Pack</th><th>Status</th><th>Checks</th><th>Audit</th><th>Fix Guidance</th></tr></thead>
|
|
467
543
|
<tbody>
|
|
468
|
-
${
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
<div style="font-weight:600;font-size:13px;">${escapeHtml(f.title)}</div>
|
|
482
|
-
<div style="font-size:11px;color:#6b7280;">${escapeHtml(f.ruleId)}</div>
|
|
483
|
-
</td>
|
|
484
|
-
<td><span class="badge badge-sev" style="background:${severityColor(f.severity)}">${f.severity.toUpperCase()}</span></td>
|
|
485
|
-
<td style="font-family:monospace;font-size:11px;">${escapeHtml(f.file)}${f.line ? ':' + f.line : ''}</td>
|
|
486
|
-
<td>${linkedControls.length > 0 ? linkedControls.map(c => `<div style="margin-bottom:2px;"><span class="link" onclick="showControlDetail('${escapeHtml(c.id)}')">${escapeHtml(c.id)}</span> <span style="color:#6b7280;font-size:11px;">${escapeHtml(c.name)}</span></div>`).join('') : '<span style="color:#9ca3af;">No linked controls</span>'}</td>
|
|
487
|
-
<td>${linkedPackIds.size > 0 ? [...linkedPackIds].map(pid => {
|
|
488
|
-
const pk = packs.find(pp => pp.id === pid);
|
|
489
|
-
return pk ? `<span class="tag" style="cursor:pointer;" onclick="loadPackDetail('${pk.id}')">${escapeHtml(pk.name)}</span>` : '';
|
|
490
|
-
}).join(' ') : '<span style="color:#9ca3af;">-</span>'}</td>
|
|
491
|
-
<td style="max-width:300px;font-size:12px;color:#374151;">${escapeHtml(f.fix)}</td>
|
|
492
|
-
</tr>`;
|
|
493
|
-
}).join('')}
|
|
544
|
+
${complianceIssues.map(issue => `<tr>
|
|
545
|
+
<td>
|
|
546
|
+
<span class="link" onclick="showControlDetail('${escapeHtml(issue.controlId)}')">${escapeHtml(issue.controlId)}</span>
|
|
547
|
+
<div style="font-size:12px;color:#4b5563;">${escapeHtml(issue.controlName)}</div>
|
|
548
|
+
</td>
|
|
549
|
+
<td><span class="badge badge-sev" style="background:${severityColor(issue.severity)}">${issue.severity.toUpperCase()}</span></td>
|
|
550
|
+
<td style="font-size:12px;">${escapeHtml(issue.framework)}${issue.article ? '<br><span style="color:#6b7280;">' + escapeHtml(issue.article) + '</span>' : ''}</td>
|
|
551
|
+
<td>${issue.packId ? `<span class="tag" style="cursor:pointer;" onclick="loadPackDetail('${issue.packId}')">${escapeHtml(issue.packName)}</span>` : '<span style="color:#9ca3af;">-</span>'}</td>
|
|
552
|
+
<td><span class="badge badge-status" style="background:${statusColor(issue.status)}">${statusLabel(issue.status)}</span></td>
|
|
553
|
+
<td style="font-size:12px;">${issue.passedChecks}/${issue.totalChecks}</td>
|
|
554
|
+
<td>${issue.auditFindings.length > 0 ? `<span style="color:#ef4444;font-weight:600;">${issue.auditFindings.length}</span>` : '<span style="color:#9ca3af;">0</span>'}</td>
|
|
555
|
+
<td style="max-width:280px;font-size:12px;color:#374151;">${escapeHtml(issue.implementation_guidance.substring(0, 150))}${issue.implementation_guidance.length > 150 ? '...' : ''}</td>
|
|
556
|
+
</tr>`).join('')}
|
|
494
557
|
</tbody>
|
|
495
558
|
</table>
|
|
496
|
-
|
|
497
|
-
</div>` : '<div class="card"><div class="empty-state"><div class="icon">✓</div><div class="msg" style="color:#22c55e;">No findings to trace</div><div class="sub">All clear</div></div></div>'}
|
|
559
|
+
</div>` : '<div class="card"><div class="empty-state"><div class="icon">✓</div><div class="msg" style="color:#22c55e;">No issues to trace</div><div class="sub">All controls passing</div></div></div>'}
|
|
498
560
|
</div>
|
|
499
561
|
|
|
500
562
|
<div id="trace-tab-fixes" class="tab-panel">
|
|
501
|
-
${
|
|
563
|
+
${renderComplianceFixCards(complianceIssues, "trace")}
|
|
502
564
|
</div>
|
|
503
565
|
|
|
504
566
|
<div id="trace-tab-controls" class="tab-panel">
|
|
@@ -597,6 +659,11 @@ export function renderDashboard(data) {
|
|
|
597
659
|
if (btn) btn.classList.add('active');
|
|
598
660
|
};
|
|
599
661
|
|
|
662
|
+
window.goToPendingFixes = function() {
|
|
663
|
+
var btns = document.querySelectorAll('#page-fixes .tab-btn');
|
|
664
|
+
showFixesTab('pending', btns.length > 1 ? btns[1] : (btns[0] || null));
|
|
665
|
+
};
|
|
666
|
+
|
|
600
667
|
window.showTraceTab = function(tab, btn) {
|
|
601
668
|
var panels = document.querySelectorAll('#page-traceability .tab-panel');
|
|
602
669
|
for (var i = 0; i < panels.length; i++) panels[i].classList.remove('active');
|
|
@@ -703,7 +770,7 @@ export function renderDashboard(data) {
|
|
|
703
770
|
|
|
704
771
|
html += '<div class="tab-bar">';
|
|
705
772
|
html += '<button class="tab-btn active" onclick="showPackTab(\\'all\\',this)">All Controls (' + controls.length + ')</button>';
|
|
706
|
-
html += '<button class="tab-btn" onclick="showPackTab(\\'failing\\',this)">Failing (' + (controls.filter(function(c){return c.status
|
|
773
|
+
html += '<button class="tab-btn" onclick="showPackTab(\\'failing\\',this)">Failing (' + (controls.filter(function(c){return c.status!=="pass"&&c.status!=="not-applicable"}).length) + ')</button>';
|
|
707
774
|
html += '<button class="tab-btn" onclick="showPackTab(\\'withfindings\\',this)">With Findings (' + (controls.filter(function(c){return c.relatedFindings.length>0}).length) + ')</button>';
|
|
708
775
|
html += '</div>';
|
|
709
776
|
|
|
@@ -711,7 +778,7 @@ export function renderDashboard(data) {
|
|
|
711
778
|
html += renderControlsTable(controls);
|
|
712
779
|
html += '</div>';
|
|
713
780
|
html += '<div id="pack-controls-failing" style="display:none;">';
|
|
714
|
-
html += renderControlsTable(controls.filter(function(c){return c.status
|
|
781
|
+
html += renderControlsTable(controls.filter(function(c){return c.status!=="pass"&&c.status!=="not-applicable"}));
|
|
715
782
|
html += '</div>';
|
|
716
783
|
html += '<div id="pack-controls-withfindings" style="display:none;">';
|
|
717
784
|
html += renderControlsTable(controls.filter(function(c){return c.relatedFindings.length>0}));
|
|
@@ -1040,7 +1107,7 @@ function renderDetailedFixesList(findings, controls, packs) {
|
|
|
1040
1107
|
}
|
|
1041
1108
|
return html;
|
|
1042
1109
|
}
|
|
1043
|
-
function renderFixHistorySection(entries) {
|
|
1110
|
+
function renderFixHistorySection(entries, complianceIssues = []) {
|
|
1044
1111
|
if (entries.length === 0) {
|
|
1045
1112
|
return `<div class="card">
|
|
1046
1113
|
<h2 style="font-size:20px;font-weight:700;margin-bottom:8px;">Compliance Fix History</h2>
|
|
@@ -1049,6 +1116,7 @@ function renderFixHistorySection(entries) {
|
|
|
1049
1116
|
<div class="icon">📋</div>
|
|
1050
1117
|
<div class="msg">No fixes recorded yet</div>
|
|
1051
1118
|
<div class="sub">Run <code style="background:#f3f4f6;padding:2px 6px;border-radius:4px;font-size:12px;">ges fix</code> or use the MCP <code style="background:#f3f4f6;padding:2px 6px;border-radius:4px;font-size:12px;">auto_fix</code> tool to apply fixes. Each fix will be recorded here.</div>
|
|
1119
|
+
${complianceIssues.length > 0 ? `<div style="margin-top:16px;"><span class="badge badge-status" style="background:#f97316;font-size:12px;padding:4px 12px;">${complianceIssues.length} pending fixes</span> <span class="link" style="font-size:13px;" onclick="goToPendingFixes()">View pending fixes →</span></div>` : ''}
|
|
1052
1120
|
</div>
|
|
1053
1121
|
</div>`;
|
|
1054
1122
|
}
|
|
@@ -1193,6 +1261,108 @@ function renderFixHistorySection(entries) {
|
|
|
1193
1261
|
html += `</div>`;
|
|
1194
1262
|
return html;
|
|
1195
1263
|
}
|
|
1264
|
+
function renderComplianceIssuesTable(issues) {
|
|
1265
|
+
if (issues.length === 0) {
|
|
1266
|
+
return '<div class="empty-state"><div class="icon">✓</div><div class="msg" style="color:#22c55e;">No compliance issues in this category</div></div>';
|
|
1267
|
+
}
|
|
1268
|
+
return `<table>
|
|
1269
|
+
<thead><tr><th>Severity</th><th>Control</th><th>Framework</th><th>Policy Pack</th><th>Status</th><th>Checks</th><th>Audit</th><th>Fix Guidance</th></tr></thead>
|
|
1270
|
+
<tbody>
|
|
1271
|
+
${issues.map(issue => `<tr>
|
|
1272
|
+
<td><span class="badge badge-sev" style="background:${severityColor(issue.severity)}">${issue.severity.toUpperCase()}</span></td>
|
|
1273
|
+
<td>
|
|
1274
|
+
<span class="link" onclick="showControlDetail('${escapeHtml(issue.controlId)}')">${escapeHtml(issue.controlId)}</span>
|
|
1275
|
+
<div style="font-size:12px;color:#4b5563;">${escapeHtml(issue.controlName)}</div>
|
|
1276
|
+
</td>
|
|
1277
|
+
<td style="font-size:12px;">${escapeHtml(issue.framework)}${issue.article ? '<br><span style="color:#6b7280;">' + escapeHtml(issue.article) + '</span>' : ''}</td>
|
|
1278
|
+
<td>${issue.packId ? `<span class="tag" style="cursor:pointer;" onclick="loadPackDetail('${issue.packId}')">${escapeHtml(issue.packName)}</span>` : '<span style="color:#9ca3af;">Direct</span>'}</td>
|
|
1279
|
+
<td><span class="badge badge-status" style="background:${statusColor(issue.status)}">${statusLabel(issue.status)}</span></td>
|
|
1280
|
+
<td style="font-size:12px;">${issue.passedChecks}/${issue.totalChecks}</td>
|
|
1281
|
+
<td>${issue.auditFindings.length > 0 ? `<span style="color:#ef4444;font-weight:600;">${issue.auditFindings.length}</span>` : '<span style="color:#9ca3af;">0</span>'}</td>
|
|
1282
|
+
<td style="max-width:280px;font-size:12px;color:#4b5563;">${escapeHtml(issue.implementation_guidance.substring(0, 200))}${issue.implementation_guidance.length > 200 ? '...' : ''}</td>
|
|
1283
|
+
</tr>`).join('')}
|
|
1284
|
+
</tbody>
|
|
1285
|
+
</table>`;
|
|
1286
|
+
}
|
|
1287
|
+
function renderComplianceFixCards(issues, idPrefix) {
|
|
1288
|
+
if (issues.length === 0) {
|
|
1289
|
+
return '<div class="card"><div class="empty-state"><div class="icon">✓</div><div class="msg" style="color:#22c55e;">All controls passing</div><div class="sub">No fixes needed</div></div></div>';
|
|
1290
|
+
}
|
|
1291
|
+
const totalAuditFindings = issues.reduce((sum, i) => sum + i.auditFindings.length, 0);
|
|
1292
|
+
const criticalCount = issues.filter(i => i.severity === "critical").length;
|
|
1293
|
+
const highCount = issues.filter(i => i.severity === "high").length;
|
|
1294
|
+
let html = '';
|
|
1295
|
+
html += `<div style="margin-bottom:20px;">`;
|
|
1296
|
+
html += `<div class="grid grid-4" style="margin-bottom:20px;">`;
|
|
1297
|
+
html += `<div class="card stat"><div class="num" style="color:#ef4444;">${issues.length}</div><div class="label">Controls to Fix</div></div>`;
|
|
1298
|
+
html += `<div class="card stat"><div class="num" style="color:#ef4444;">${criticalCount}</div><div class="label">Critical</div></div>`;
|
|
1299
|
+
html += `<div class="card stat"><div class="num" style="color:#f97316;">${highCount}</div><div class="label">High</div></div>`;
|
|
1300
|
+
html += `<div class="card stat"><div class="num">${totalAuditFindings}</div><div class="label">Audit Findings</div></div>`;
|
|
1301
|
+
html += `</div>`;
|
|
1302
|
+
html += `</div>`;
|
|
1303
|
+
for (let i = 0; i < issues.length; i++) {
|
|
1304
|
+
const issue = issues[i];
|
|
1305
|
+
const fixId = `${idPrefix}-${i}`;
|
|
1306
|
+
html += `<div class="fix-detail-card">`;
|
|
1307
|
+
html += `<div class="fix-detail-header ${issue.severity}" onclick="toggleFix('${fixId}')">`;
|
|
1308
|
+
html += `<div class="fix-detail-num" style="color:${severityColor(issue.severity)};">${i + 1}</div>`;
|
|
1309
|
+
html += `<div class="fix-detail-info">`;
|
|
1310
|
+
html += `<div class="fix-detail-title">${escapeHtml(issue.controlName)}</div>`;
|
|
1311
|
+
html += `<div class="fix-detail-meta">${escapeHtml(issue.controlId)} | ${escapeHtml(issue.category)} | ${escapeHtml(issue.framework)}${issue.article ? ' | ' + escapeHtml(issue.article) : ''} | Pack: ${escapeHtml(issue.packName)}</div>`;
|
|
1312
|
+
html += `</div>`;
|
|
1313
|
+
html += `<div class="fix-detail-badges">`;
|
|
1314
|
+
html += `<span class="badge badge-sev" style="background:${severityColor(issue.severity)}">${issue.severity.toUpperCase()}</span>`;
|
|
1315
|
+
html += `<span class="badge badge-status" style="background:${statusColor(issue.status)}">${statusLabel(issue.status)}</span>`;
|
|
1316
|
+
html += `<span style="font-size:12px;color:#6b7280;">${issue.passedChecks}/${issue.totalChecks} checks</span>`;
|
|
1317
|
+
if (issue.auditFindings.length > 0) {
|
|
1318
|
+
html += `<span style="font-size:12px;color:#ef4444;font-weight:600;">${issue.auditFindings.length} evidence</span>`;
|
|
1319
|
+
}
|
|
1320
|
+
html += `<span class="fix-toggle" id="${fixId}-toggle">Expand</span>`;
|
|
1321
|
+
html += `</div></div>`;
|
|
1322
|
+
html += `<div class="fix-detail-body" id="${fixId}">`;
|
|
1323
|
+
html += `<div class="fix-section"><div class="fix-section-title">Description</div>`;
|
|
1324
|
+
html += `<div style="font-size:13px;color:#4b5563;line-height:1.6;">${escapeHtml(issue.description)}</div>`;
|
|
1325
|
+
html += `</div>`;
|
|
1326
|
+
if (issue.auditFindings.length > 0) {
|
|
1327
|
+
html += `<div class="fix-section"><div class="fix-section-title">Audit Evidence (${issue.auditFindings.length})</div>`;
|
|
1328
|
+
for (const f of issue.auditFindings) {
|
|
1329
|
+
html += `<div class="fix-finding-item ${f.severity}">`;
|
|
1330
|
+
html += `<div style="display:flex;align-items:center;gap:8px;margin-bottom:4px;">`;
|
|
1331
|
+
html += `<span class="badge badge-sev" style="background:${severityColor(f.severity)};font-size:10px;">${f.severity.toUpperCase()}</span>`;
|
|
1332
|
+
html += `<strong style="font-size:13px;">${escapeHtml(f.title)}</strong></div>`;
|
|
1333
|
+
html += `<div style="font-size:12px;color:#6b7280;"><span style="font-family:monospace;font-weight:600;">${escapeHtml(f.ruleId)}</span> — <span style="font-family:monospace;">${escapeHtml(f.file)}${f.line ? ':' + f.line : ''}</span></div>`;
|
|
1334
|
+
if (f.description)
|
|
1335
|
+
html += `<div style="font-size:12px;color:#4b5563;margin-top:4px;">${escapeHtml(f.description)}</div>`;
|
|
1336
|
+
if (f.evidence)
|
|
1337
|
+
html += `<div class="fix-evidence">${escapeHtml(f.evidence)}</div>`;
|
|
1338
|
+
html += `</div>`;
|
|
1339
|
+
}
|
|
1340
|
+
html += `</div>`;
|
|
1341
|
+
}
|
|
1342
|
+
html += `<div class="fix-section"><div class="fix-section-title">Fix Guidance</div>`;
|
|
1343
|
+
html += `<div class="fix-guidance-box"><strong>How to fix:</strong> ${escapeHtml(issue.implementation_guidance)}</div>`;
|
|
1344
|
+
for (const f of issue.auditFindings) {
|
|
1345
|
+
if (f.fix) {
|
|
1346
|
+
html += `<div class="fix-guidance-box" style="margin-top:8px;background:#eff6ff;border-color:#bfdbfe;"><strong>Fix for ${escapeHtml(f.ruleId)}:</strong> ${escapeHtml(f.fix)}</div>`;
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
html += `</div>`;
|
|
1350
|
+
html += `<div class="fix-section"><div class="fix-section-title">Traceability</div>`;
|
|
1351
|
+
html += `<table><tbody>`;
|
|
1352
|
+
html += `<tr><td style="font-weight:600;width:160px;">Control</td><td><span class="link" onclick="showControlDetail('${escapeHtml(issue.controlId)}')">${escapeHtml(issue.controlId)}</span> — ${escapeHtml(issue.controlName)}</td></tr>`;
|
|
1353
|
+
html += `<tr><td style="font-weight:600;">Category</td><td>${escapeHtml(issue.category)}</td></tr>`;
|
|
1354
|
+
html += `<tr><td style="font-weight:600;">Framework</td><td>${escapeHtml(issue.framework)}${issue.article ? ' / ' + escapeHtml(issue.article) : ''}</td></tr>`;
|
|
1355
|
+
html += `<tr><td style="font-weight:600;">Policy Pack</td><td>${issue.packId ? `<span class="tag" style="cursor:pointer;" onclick="loadPackDetail('${issue.packId}')">${escapeHtml(issue.packName)}</span>` : 'Direct'}</td></tr>`;
|
|
1356
|
+
html += `<tr><td style="font-weight:600;">Severity</td><td><span class="badge badge-sev" style="background:${severityColor(issue.severity)}">${issue.severity.toUpperCase()}</span></td></tr>`;
|
|
1357
|
+
html += `<tr><td style="font-weight:600;">Status</td><td><span class="badge badge-status" style="background:${statusColor(issue.status)}">${statusLabel(issue.status)}</span></td></tr>`;
|
|
1358
|
+
html += `<tr><td style="font-weight:600;">Checks</td><td>${issue.passedChecks}/${issue.totalChecks} passed</td></tr>`;
|
|
1359
|
+
html += `<tr><td style="font-weight:600;">Audit Evidence</td><td>${issue.auditFindings.length} finding(s)</td></tr>`;
|
|
1360
|
+
html += `</tbody></table>`;
|
|
1361
|
+
html += `</div>`;
|
|
1362
|
+
html += `</div></div>`;
|
|
1363
|
+
}
|
|
1364
|
+
return html;
|
|
1365
|
+
}
|
|
1196
1366
|
function escapeHtml(str) {
|
|
1197
1367
|
return str
|
|
1198
1368
|
.replace(/&/g, "&")
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"dependencies": {
|
|
3
|
-
"@greenarmor/ges-audit-engine": "1.2.
|
|
4
|
-
"@greenarmor/ges-core": "1.2.
|
|
5
|
-
"@greenarmor/ges-policy-engine": "1.2.
|
|
6
|
-
"@greenarmor/ges-scoring-engine": "1.2.
|
|
3
|
+
"@greenarmor/ges-audit-engine": "1.2.4",
|
|
4
|
+
"@greenarmor/ges-core": "1.2.4",
|
|
5
|
+
"@greenarmor/ges-policy-engine": "1.2.4",
|
|
6
|
+
"@greenarmor/ges-scoring-engine": "1.2.4"
|
|
7
7
|
},
|
|
8
8
|
"description": "GESF Web Dashboard - Visual compliance dashboard for teams",
|
|
9
9
|
"devDependencies": {
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
},
|
|
41
41
|
"type": "module",
|
|
42
42
|
"types": "./dist/index.d.ts",
|
|
43
|
-
"version": "1.2.
|
|
43
|
+
"version": "1.2.4",
|
|
44
44
|
"scripts": {
|
|
45
45
|
"build": "tsc",
|
|
46
46
|
"clean": "rm -rf dist tsconfig.tsbuildinfo",
|