@greenarmor/ges-web-dashboard 1.5.0 → 1.5.1
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 +149 -8
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -195,7 +195,7 @@ export function collectDashboardData(projectPath) {
|
|
|
195
195
|
projectName: config?.project_name || "Unknown Project",
|
|
196
196
|
projectType: config?.project_type || "unknown",
|
|
197
197
|
frameworks: allFrameworks,
|
|
198
|
-
gesfVersion: "1.5.
|
|
198
|
+
gesfVersion: "1.5.1",
|
|
199
199
|
score,
|
|
200
200
|
controls,
|
|
201
201
|
findings,
|
package/dist/template.js
CHANGED
|
@@ -1754,6 +1754,9 @@ function renderComplianceFixCards(issues, idPrefix, assignmentMap, govRecords) {
|
|
|
1754
1754
|
}
|
|
1755
1755
|
}
|
|
1756
1756
|
html += `</div>`;
|
|
1757
|
+
const ctrlFkey = `${issue.controlId}::0`;
|
|
1758
|
+
const ctrlAssignment = assignmentMap?.get(ctrlFkey);
|
|
1759
|
+
html += renderGovernanceProvenanceSection(issue.controlId, issue.controlName, issue.severity, ctrlFkey, ctrlAssignment, govRecords);
|
|
1757
1760
|
html += `<div class="fix-section"><div class="fix-section-title">Traceability</div>`;
|
|
1758
1761
|
html += `<table><tbody>`;
|
|
1759
1762
|
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>`;
|
|
@@ -1770,6 +1773,142 @@ function renderComplianceFixCards(issues, idPrefix, assignmentMap, govRecords) {
|
|
|
1770
1773
|
}
|
|
1771
1774
|
return html;
|
|
1772
1775
|
}
|
|
1776
|
+
function renderGovernanceProvenanceSection(controlId, controlName, severity, ctrlFkey, ctrlAssignment, govRecords) {
|
|
1777
|
+
let html = `<div class="fix-section"><div class="fix-section-title">Governance Provenance Chain</div>`;
|
|
1778
|
+
if (ctrlAssignment && govRecords) {
|
|
1779
|
+
const record = govRecords.find(r => r.id === ctrlAssignment.governance_record_id);
|
|
1780
|
+
const aStatusColor = ctrlAssignment.status === "fixed" || ctrlAssignment.status === "verified"
|
|
1781
|
+
? "#22c55e"
|
|
1782
|
+
: ctrlAssignment.status === "in-progress"
|
|
1783
|
+
? "#3b82f6"
|
|
1784
|
+
: "#eab308";
|
|
1785
|
+
html += `<div style="padding:12px 16px;border-radius:8px;background:#f0fdf4;border:1px solid #bbf7d0;margin-bottom:10px;">`;
|
|
1786
|
+
html += `<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:8px;">`;
|
|
1787
|
+
html += `<span class="badge" style="background:${aStatusColor};color:#fff;font-size:10px;text-transform:uppercase;">${escapeHtml(ctrlAssignment.status)}</span>`;
|
|
1788
|
+
html += `<span style="font-size:12px;font-weight:600;color:#166534;">Assignee: ${escapeHtml(ctrlAssignment.assignee)}${ctrlAssignment.assignee_role ? ' (' + escapeHtml(ctrlAssignment.assignee_role) + ')' : ''}</span>`;
|
|
1789
|
+
html += `<span style="font-size:11px;color:#6b7280;">Assigned by ${escapeHtml(ctrlAssignment.assigned_by)} on ${escapeHtml(new Date(ctrlAssignment.assigned_at).toLocaleDateString())}</span>`;
|
|
1790
|
+
html += `</div>`;
|
|
1791
|
+
if (ctrlAssignment.notes) {
|
|
1792
|
+
html += `<div style="font-size:12px;color:#4b5563;margin-bottom:6px;"><strong>Notes:</strong> ${escapeHtml(ctrlAssignment.notes)}</div>`;
|
|
1793
|
+
}
|
|
1794
|
+
if (ctrlAssignment.resolution) {
|
|
1795
|
+
const r = ctrlAssignment.resolution;
|
|
1796
|
+
html += `<div style="font-size:12px;padding:6px 10px;background:#dcfce7;border-radius:6px;margin-top:6px;">`;
|
|
1797
|
+
html += `<strong>✓ Resolved</strong> by ${escapeHtml(r.resolved_by)}${r.resolved_by_role ? ' (' + escapeHtml(r.resolved_by_role) + ')' : ''} via <strong>${escapeHtml(r.method)}</strong> on ${escapeHtml(new Date(r.resolved_at).toLocaleDateString())}`;
|
|
1798
|
+
if (r.resolution_notes)
|
|
1799
|
+
html += `<br><span style="color:#4b5563;">${escapeHtml(r.resolution_notes)}</span>`;
|
|
1800
|
+
html += `</div>`;
|
|
1801
|
+
}
|
|
1802
|
+
html += `<div style="margin-top:8px;display:flex;gap:6px;">`;
|
|
1803
|
+
if (ctrlAssignment.status !== "fixed" && ctrlAssignment.status !== "verified") {
|
|
1804
|
+
html += `<button class="gov-action-btn" style="background:#22c55e;color:#fff;border:none;padding:4px 10px;border-radius:4px;font-size:11px;cursor:pointer;" onclick="event.stopPropagation();resolveFindingFix('${escapeHtml(ctrlFkey)}')">Mark Fixed</button>`;
|
|
1805
|
+
}
|
|
1806
|
+
html += `<button class="gov-action-btn" style="background:#fee2e2;color:#991b1b;border:none;padding:4px 10px;border-radius:4px;font-size:11px;cursor:pointer;" onclick="event.stopPropagation();unassignFix('${escapeHtml(ctrlFkey)}')">Unassign</button>`;
|
|
1807
|
+
html += `</div>`;
|
|
1808
|
+
html += `</div>`;
|
|
1809
|
+
if (record) {
|
|
1810
|
+
html += renderProvenanceChainInline(record);
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
else {
|
|
1814
|
+
html += `<div style="padding:12px 16px;border-radius:8px;background:#f9fafb;border:1px dashed #d1d5db;">`;
|
|
1815
|
+
html += `<div style="display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;">`;
|
|
1816
|
+
html += `<div style="font-size:13px;color:#6b7280;">This control is not linked to any governance record. Assign it to create a provenance chain for auditors.</div>`;
|
|
1817
|
+
html += `<button class="gov-action-btn" style="background:#4f46e5;color:#fff;border:none;padding:6px 14px;border-radius:6px;font-size:12px;cursor:pointer;font-weight:600;white-space:nowrap;" onclick="event.stopPropagation();openAssignModal('${escapeHtml(ctrlFkey)}','${escapeHtml(controlId)}','${escapeHtml(controlName.replace(/'/g, "\\'"))}','',0,'${escapeHtml(severity)}','${escapeHtml(controlId)}')">+ Assign to Governance Record</button>`;
|
|
1818
|
+
html += `</div>`;
|
|
1819
|
+
html += `</div>`;
|
|
1820
|
+
if (issueHasFindingLevelAssignments(ctrlFkey, controlId)) {
|
|
1821
|
+
html += `<div style="margin-top:8px;font-size:11px;color:#6b7280;">ℹ Individual audit findings within this control may already be assigned at the finding level below.</div>`;
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1824
|
+
html += `</div>`;
|
|
1825
|
+
return html;
|
|
1826
|
+
}
|
|
1827
|
+
function issueHasFindingLevelAssignments(ctrlFkey, controlId) {
|
|
1828
|
+
return false;
|
|
1829
|
+
}
|
|
1830
|
+
function renderProvenanceChainInline(record) {
|
|
1831
|
+
let html = `<div style="border:1px solid #e5e7eb;border-radius:8px;overflow:hidden;">`;
|
|
1832
|
+
html += `<div style="background:#f3f4f6;padding:10px 14px;display:flex;align-items:center;gap:8px;flex-wrap:wrap;">`;
|
|
1833
|
+
html += `<span style="font-size:14px;font-weight:700;color:#1f2937;">${escapeHtml(record.system_name)}</span>`;
|
|
1834
|
+
html += `<span class="badge" style="background:${record.status === "approved" ? "#22c55e" : record.status === "rejected" || record.status === "revoked" ? "#ef4444" : "#eab308"};color:#fff;font-size:10px;text-transform:uppercase;">${escapeHtml(record.status)}</span>`;
|
|
1835
|
+
html += `<span class="badge" style="background:${record.risk_level === "critical" ? "#ef4444" : record.risk_level === "high" ? "#f97316" : record.risk_level === "medium" ? "#eab308" : "#22c55e"};color:#fff;font-size:10px;text-transform:uppercase;">${escapeHtml(record.risk_level)} RISK</span>`;
|
|
1836
|
+
html += `<span style="font-size:11px;color:#9ca3af;font-family:monospace;">${escapeHtml(record.id)}</span>`;
|
|
1837
|
+
html += `</div>`;
|
|
1838
|
+
html += `<div style="padding:10px 14px;">`;
|
|
1839
|
+
html += `<table style="width:100%;font-size:12px;border-collapse:collapse;">`;
|
|
1840
|
+
if (record.approval) {
|
|
1841
|
+
const a = record.approval;
|
|
1842
|
+
const decColor = a.decision === "approved" ? "#22c55e" : "#ef4444";
|
|
1843
|
+
html += `<tr style="border-bottom:1px solid #f3f4f6;">`;
|
|
1844
|
+
html += `<td style="padding:6px 8px;font-weight:600;width:140px;color:#374151;">Approval</td>`;
|
|
1845
|
+
html += `<td style="padding:6px 8px;">`;
|
|
1846
|
+
html += `<span style="color:${decColor};font-weight:600;">${escapeHtml(a.decision.toUpperCase())}</span> by ${escapeHtml(a.approver_name)} (${escapeHtml(a.approver_role)})`;
|
|
1847
|
+
if (a.valid_until)
|
|
1848
|
+
html += ` — valid until ${escapeHtml(a.valid_until)}`;
|
|
1849
|
+
html += `</td></tr>`;
|
|
1850
|
+
}
|
|
1851
|
+
else {
|
|
1852
|
+
html += `<tr style="border-bottom:1px solid #f3f4f6;"><td style="padding:6px 8px;font-weight:600;width:140px;color:#374151;">Approval</td><td style="padding:6px 8px;color:#ef4444;">✗ Not recorded</td></tr>`;
|
|
1853
|
+
}
|
|
1854
|
+
if (record.risk_assessment) {
|
|
1855
|
+
const ra = record.risk_assessment;
|
|
1856
|
+
html += `<tr style="border-bottom:1px solid #f3f4f6;">`;
|
|
1857
|
+
html += `<td style="padding:6px 8px;font-weight:600;color:#374151;">Risk Assessment</td>`;
|
|
1858
|
+
html += `<td style="padding:6px 8px;">Score: <strong>${escapeHtml(ra.risk_score)}</strong> — Residual: ${escapeHtml(ra.residual_risk)} (${escapeHtml(ra.methodology)})</td>`;
|
|
1859
|
+
html += `</tr>`;
|
|
1860
|
+
}
|
|
1861
|
+
else {
|
|
1862
|
+
html += `<tr style="border-bottom:1px solid #f3f4f6;"><td style="padding:6px 8px;font-weight:600;color:#374151;">Risk Assessment</td><td style="padding:6px 8px;color:#9ca3af;">✗ Not assessed</td></tr>`;
|
|
1863
|
+
}
|
|
1864
|
+
if (record.policy_basis) {
|
|
1865
|
+
const pb = record.policy_basis;
|
|
1866
|
+
html += `<tr style="border-bottom:1px solid #f3f4f6;">`;
|
|
1867
|
+
html += `<td style="padding:6px 8px;font-weight:600;color:#374151;">Policy Basis</td>`;
|
|
1868
|
+
html += `<td style="padding:6px 8px;">${escapeHtml(pb.policy_name)} v${escapeHtml(pb.version)} (${escapeHtml(pb.standard)})</td>`;
|
|
1869
|
+
html += `</tr>`;
|
|
1870
|
+
}
|
|
1871
|
+
else {
|
|
1872
|
+
html += `<tr style="border-bottom:1px solid #f3f4f6;"><td style="padding:6px 8px;font-weight:600;color:#374151;">Policy Basis</td><td style="padding:6px 8px;color:#9ca3af;">✗ Not documented</td></tr>`;
|
|
1873
|
+
}
|
|
1874
|
+
html += `<tr style="border-bottom:1px solid #f3f4f6;">`;
|
|
1875
|
+
html += `<td style="padding:6px 8px;font-weight:600;color:#374151;">Evidence Chain</td>`;
|
|
1876
|
+
if (record.evidence.length > 0) {
|
|
1877
|
+
html += `<td style="padding:6px 8px;">`;
|
|
1878
|
+
for (const e of record.evidence) {
|
|
1879
|
+
html += `<span style="display:inline-block;margin-right:6px;margin-bottom:2px;padding:2px 8px;background:#eff6ff;border:1px solid #bfdbfe;border-radius:4px;font-size:11px;">${escapeHtml(e.title)} <span style="color:#6b7280;">(${escapeHtml(e.source_system)}: ${escapeHtml(e.reference)})</span></span>`;
|
|
1880
|
+
}
|
|
1881
|
+
html += `</td>`;
|
|
1882
|
+
}
|
|
1883
|
+
else {
|
|
1884
|
+
html += `<td style="padding:6px 8px;color:#9ca3af;">✗ No evidence references</td>`;
|
|
1885
|
+
}
|
|
1886
|
+
html += `</tr>`;
|
|
1887
|
+
if (record.review_cycle) {
|
|
1888
|
+
const rc = record.review_cycle;
|
|
1889
|
+
html += `<tr style="border-bottom:1px solid #f3f4f6;">`;
|
|
1890
|
+
html += `<td style="padding:6px 8px;font-weight:600;color:#374151;">Review Cycle</td>`;
|
|
1891
|
+
html += `<td style="padding:6px 8px;">${escapeHtml(rc.frequency)} — next review: ${escapeHtml(rc.next_review)}</td>`;
|
|
1892
|
+
html += `</tr>`;
|
|
1893
|
+
}
|
|
1894
|
+
else {
|
|
1895
|
+
html += `<tr style="border-bottom:1px solid #f3f4f6;"><td style="padding:6px 8px;font-weight:600;color:#374151;">Review Cycle</td><td style="padding:6px 8px;color:#9ca3af;">✗ Not scheduled</td></tr>`;
|
|
1896
|
+
}
|
|
1897
|
+
html += `<tr>`;
|
|
1898
|
+
html += `<td style="padding:6px 8px;font-weight:600;color:#374151;">Provenance Chain</td>`;
|
|
1899
|
+
const chainParts = [];
|
|
1900
|
+
chainParts.push(record.approval ? "✓" : "✗");
|
|
1901
|
+
chainParts.push(record.risk_assessment ? "✓" : "✗");
|
|
1902
|
+
chainParts.push(record.policy_basis ? "✓" : "✗");
|
|
1903
|
+
chainParts.push(record.evidence.length > 0 ? "✓" : "✗");
|
|
1904
|
+
chainParts.push(record.review_cycle ? "✓" : "✗");
|
|
1905
|
+
html += `<td style="padding:6px 8px;font-size:11px;">Approval ${chainParts[0]} → Risk ${chainParts[1]} → Policy ${chainParts[2]} → Evidence ${chainParts[3]} → Review ${chainParts[4]}</td>`;
|
|
1906
|
+
html += `</tr>`;
|
|
1907
|
+
html += `</table>`;
|
|
1908
|
+
html += `</div>`;
|
|
1909
|
+
html += `</div>`;
|
|
1910
|
+
return html;
|
|
1911
|
+
}
|
|
1773
1912
|
function renderActivityLogSection(entries) {
|
|
1774
1913
|
if (!entries || entries.length === 0) {
|
|
1775
1914
|
return `<div class="card">
|
|
@@ -2046,8 +2185,8 @@ function renderGovernanceSection(data) {
|
|
|
2046
2185
|
html += `<div><strong>Decision:</strong> <span style="color:${a.decision === "approved" ? "#22c55e" : "#ef4444"};font-weight:600;">${a.decision.toUpperCase()}</span></div>`;
|
|
2047
2186
|
html += `<div><strong>Date:</strong> ${escapeHtml(a.decision_date)}</div>`;
|
|
2048
2187
|
html += `<div><strong>Validity:</strong> ${escapeHtml(a.valid_from)} → ${escapeHtml(a.valid_until || "indefinite")}</div>`;
|
|
2049
|
-
if (a.conditions.length > 0) {
|
|
2050
|
-
html += `<div><strong>Conditions:</strong> ${a.conditions.map(c => escapeHtml(c)).join("; ")}</div>`;
|
|
2188
|
+
if (a.conditions && a.conditions.length > 0) {
|
|
2189
|
+
html += `<div><strong>Conditions:</strong> ${(a.conditions || []).map(c => escapeHtml(c)).join("; ")}</div>`;
|
|
2051
2190
|
}
|
|
2052
2191
|
if (a.rationale) {
|
|
2053
2192
|
html += `<div><strong>Rationale:</strong> ${escapeHtml(a.rationale)}</div>`;
|
|
@@ -2066,8 +2205,8 @@ function renderGovernanceSection(data) {
|
|
|
2066
2205
|
html += `<div><strong>Methodology:</strong> ${escapeHtml(ra.methodology)}</div>`;
|
|
2067
2206
|
html += `<div><strong>Risk Score:</strong> ${escapeHtml(ra.risk_score)} — <strong>Residual:</strong> ${escapeHtml(ra.residual_risk)}</div>`;
|
|
2068
2207
|
html += `<div><strong>Date:</strong> ${escapeHtml(ra.assessment_date)}</div>`;
|
|
2069
|
-
if (ra.identified_risks.length > 0) {
|
|
2070
|
-
html += `<div><strong>Identified Risks:</strong> ${ra.identified_risks.map(r => escapeHtml(r)).join(", ")}</div>`;
|
|
2208
|
+
if (ra.identified_risks && ra.identified_risks.length > 0) {
|
|
2209
|
+
html += `<div><strong>Identified Risks:</strong> ${(ra.identified_risks || []).map(r => escapeHtml(r)).join(", ")}</div>`;
|
|
2071
2210
|
}
|
|
2072
2211
|
html += `</div>`;
|
|
2073
2212
|
html += `</div>`;
|
|
@@ -2078,8 +2217,8 @@ function renderGovernanceSection(data) {
|
|
|
2078
2217
|
html += `<div style="font-size:13px;line-height:1.8;">`;
|
|
2079
2218
|
html += `<div><strong>Policy:</strong> ${escapeHtml(pb.policy_name)} (${escapeHtml(pb.policy_id)} v${escapeHtml(pb.version)})</div>`;
|
|
2080
2219
|
html += `<div><strong>Standard:</strong> ${escapeHtml(pb.standard)}</div>`;
|
|
2081
|
-
if (pb.clauses.length > 0) {
|
|
2082
|
-
html += `<div><strong>Clauses:</strong> ${pb.clauses.map(c => escapeHtml(c)).join(", ")}</div>`;
|
|
2220
|
+
if (pb.clauses && pb.clauses.length > 0) {
|
|
2221
|
+
html += `<div><strong>Clauses:</strong> ${(pb.clauses || []).map(c => escapeHtml(c)).join(", ")}</div>`;
|
|
2083
2222
|
}
|
|
2084
2223
|
html += `</div>`;
|
|
2085
2224
|
html += `</div>`;
|
|
@@ -2117,7 +2256,7 @@ function renderGovernanceSection(data) {
|
|
|
2117
2256
|
html += `<div><strong>Committee:</strong> ${escapeHtml(c.committee_name)}</div>`;
|
|
2118
2257
|
html += `<div><strong>Meeting:</strong> ${escapeHtml(c.meeting_date)} (${escapeHtml(c.meeting_reference)})</div>`;
|
|
2119
2258
|
if (c.attendees.length > 0) {
|
|
2120
|
-
html += `<div><strong>Attendees:</strong> ${c.attendees.map(a => escapeHtml(a)).join(", ")}</div>`;
|
|
2259
|
+
html += `<div><strong>Attendees:</strong> ${(c.attendees || []).map(a => escapeHtml(a)).join(", ")}</div>`;
|
|
2121
2260
|
}
|
|
2122
2261
|
html += `</div>`;
|
|
2123
2262
|
html += `</div>`;
|
|
@@ -2143,7 +2282,9 @@ function renderGovernanceSection(data) {
|
|
|
2143
2282
|
return html;
|
|
2144
2283
|
}
|
|
2145
2284
|
function escapeHtml(str) {
|
|
2146
|
-
|
|
2285
|
+
if (str === null || str === undefined)
|
|
2286
|
+
return "";
|
|
2287
|
+
return String(str)
|
|
2147
2288
|
.replace(/&/g, "&")
|
|
2148
2289
|
.replace(/</g, "<")
|
|
2149
2290
|
.replace(/>/g, ">")
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"dependencies": {
|
|
3
|
-
"@greenarmor/ges-audit-engine": "1.5.
|
|
4
|
-
"@greenarmor/ges-core": "1.5.
|
|
5
|
-
"@greenarmor/ges-policy-engine": "1.5.
|
|
6
|
-
"@greenarmor/ges-report-generator": "1.5.
|
|
7
|
-
"@greenarmor/ges-scoring-engine": "1.5.
|
|
3
|
+
"@greenarmor/ges-audit-engine": "1.5.1",
|
|
4
|
+
"@greenarmor/ges-core": "1.5.1",
|
|
5
|
+
"@greenarmor/ges-policy-engine": "1.5.1",
|
|
6
|
+
"@greenarmor/ges-report-generator": "1.5.1",
|
|
7
|
+
"@greenarmor/ges-scoring-engine": "1.5.1"
|
|
8
8
|
},
|
|
9
9
|
"description": "GESF Web Dashboard - Visual compliance dashboard for teams",
|
|
10
10
|
"devDependencies": {
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
},
|
|
42
42
|
"type": "module",
|
|
43
43
|
"types": "./dist/index.d.ts",
|
|
44
|
-
"version": "1.5.
|
|
44
|
+
"version": "1.5.1",
|
|
45
45
|
"scripts": {
|
|
46
46
|
"build": "tsc",
|
|
47
47
|
"clean": "rm -rf dist tsconfig.tsbuildinfo",
|