Skip to content

Commit 6855af3

Browse files
Declanclaude
andcommitted
feat: add not-run pillars to Services Not Assessed section
Not-run pillars (auditor invoked but no report produced) are now shown in the same "Services Not Assessed" table as N/A pillars, with a Service / Status / Reason column layout and colour-coded tags: - Not Licensed (grey) — IntuneNotLicensed, CloudOnlyTenant etc. - Not Run (amber) — auditor ran but produced no output file Removes the old grey NOT RUN pillar cards from the pillar grid. Header count includes both categories: "X assessed · Y not assessed". Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 23caef0 commit 6855af3

2 files changed

Lines changed: 23 additions & 17 deletions

File tree

‎tools/exec_summary.py‎

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -433,25 +433,25 @@ def write_html(overall_score, grade, pillar_stats, top_findings, quick_wins,
433433
</div>
434434
<div class="pillar-total">{ps['total']} resources checked</div>
435435
</div>"""
436-
# Not-run pillar cards (auditor attempted but produced no report JSON)
437-
for pname in (not_run_pillars or []):
438-
label = html_lib.escape(PILLAR_LABELS.get(pname, pname.upper()))
439-
pillar_cards_html += (
440-
f'<div class="pillar-card" style="border-left:5px solid #adb5bd;opacity:0.65">'
441-
f'<div class="pillar-name">{label}</div>'
442-
f'<div class="pillar-risk" style="color:#adb5bd">NOT RUN</div>'
443-
f'<div class="pillar-total" style="color:#adb5bd">Auditor ran but no report found</div>'
444-
f'</div>'
445-
)
446-
# Build N/A section rows
436+
# Build Services Not Assessed rows — N/A pillars + not-run pillars
447437
for ps in na_pillars:
448438
reason = html_lib.escape(ps["na_reason"]) if ps["na_reason"] else "Not applicable on this tenant."
449439
na_rows_html += (
450440
f'<tr>'
451441
f'<td><strong>{html_lib.escape(ps["label"])}</strong></td>'
442+
f'<td><span class="na-tag na-tag-license">Not Licensed</span></td>'
452443
f'<td>{reason}</td>'
453444
f'</tr>\n'
454445
)
446+
for pname in (not_run_pillars or []):
447+
label = html_lib.escape(PILLAR_LABELS.get(pname, pname.upper()))
448+
na_rows_html += (
449+
f'<tr>'
450+
f'<td><strong>{label}</strong></td>'
451+
f'<td><span class="na-tag na-tag-norun">Not Run</span></td>'
452+
f'<td>Auditor was invoked but produced no output — check the audit log for this pillar.</td>'
453+
f'</tr>\n'
454+
)
455455

456456
# Top findings table rows
457457
finding_rows = ""
@@ -672,8 +672,12 @@ def write_html(overall_score, grade, pillar_stats, top_findings, quick_wins,
672672
.na-section h3 {{ margin:0 0 4px; color:#495057; font-size:0.95em; }}
673673
.na-section p {{ margin:0 0 12px; color:#6c757d; font-size:0.82em; }}
674674
.na-section table {{ box-shadow:none; background:transparent; margin:0; }}
675-
.na-section td {{ font-size:0.85em; color:#495057; border-bottom:1px solid #e9ecef; padding:8px 10px; }}
676-
.na-section td:first-child {{ font-weight:600; white-space:nowrap; width:220px; }}
675+
.na-section td {{ font-size:0.85em; color:#495057; border-bottom:1px solid #e9ecef; padding:8px 10px; vertical-align:top; }}
676+
.na-section td:first-child {{ white-space:nowrap; width:200px; }}
677+
.na-section td:nth-child(2) {{ white-space:nowrap; width:110px; }}
678+
.na-tag {{ display:inline-block; padding:2px 8px; border-radius:4px; font-size:0.78em; font-weight:600; letter-spacing:0.3px; }}
679+
.na-tag-license {{ background:#e9ecef; color:#495057; }}
680+
.na-tag-norun {{ background:#fff3cd; color:#856404; }}
677681
.crit-callout {{ border-top:3px solid #dc3545; }}
678682
.crit-callout h2 {{ color:#dc3545; border-color:#dc354533; }}
679683
.crit-items {{ display:flex; flex-direction:column; gap:12px; }}
@@ -713,7 +717,7 @@ def write_html(overall_score, grade, pillar_stats, top_findings, quick_wins,
713717
<body>
714718
<div class="header">
715719
{client_meta_html}
716-
<p style="color:#aaa;margin:8px 0 0;font-size:0.85em">Generated: {html_lib.escape(generated_at)} &nbsp;|&nbsp; {len(pillar_stats) - len(na_pillars)} pillar{'s' if (len(pillar_stats) - len(na_pillars)) != 1 else ''} assessed{(' &nbsp;|&nbsp; ' + str(len(na_pillars)) + ' not assessed (see below)') if na_pillars else ''}</p>
720+
<p style="color:#aaa;margin:8px 0 0;font-size:0.85em">Generated: {html_lib.escape(generated_at)} &nbsp;|&nbsp; {len(pillar_stats) - len(na_pillars)} pillar{'s' if (len(pillar_stats) - len(na_pillars)) != 1 else ''} assessed{(' &nbsp;|&nbsp; ' + str(len(na_pillars) + len(not_run_pillars or [])) + ' not assessed (see below)') if (na_pillars or not_run_pillars) else ''}</p>
717721
</div>
718722
{scope_section_html}
719723
{warnings_html}
@@ -724,7 +728,7 @@ def write_html(overall_score, grade, pillar_stats, top_findings, quick_wins,
724728
{('''<div class="na-section">
725729
<h3>&#128683; Services Not Assessed</h3>
726730
<p>The following services were not scanned because the required licence or configuration was not detected on this tenant. These are not failures — they are included here so the scope of this assessment is fully transparent.</p>
727-
<table><tbody>''' + na_rows_html + '''</tbody></table>
731+
<table><thead><tr><th style="background:#6c757d;color:#fff;padding:8px 10px;font-size:0.8em;text-align:left">Service</th><th style="background:#6c757d;color:#fff;padding:8px 10px;font-size:0.8em;text-align:left">Status</th><th style="background:#6c757d;color:#fff;padding:8px 10px;font-size:0.8em;text-align:left">Reason</th></tr></thead><tbody>''' + na_rows_html + '''</tbody></table>
728732
</div>''') if na_rows_html else ''}
729733
730734
<div class="score-zone">

‎tools/tests/test_exec_summary.py‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -509,14 +509,16 @@ def test_run_empty_dir_still_creates_html(tmp_path):
509509

510510

511511
def test_run_audit_manifest_not_run_pillar(tmp_path):
512-
"""audit_manifest.json lists 'iam' but no iam_report.json → HTML shows NOT RUN."""
512+
"""audit_manifest.json lists 'iam' but no iam_report.json → HTML shows in Services Not Assessed."""
513513
_write_fixture(tmp_path, "s3_report.json", _S3_FIXTURE)
514514
manifest = {"auditors_attempted": ["s3", "iam"]}
515515
(tmp_path / "audit_manifest.json").write_text(json.dumps(manifest))
516516
out = str(tmp_path / "exec_summary.html")
517517
es.run(input_dir=str(tmp_path), output_path=out)
518518
content = (tmp_path / "exec_summary.html").read_text()
519-
assert "NOT RUN" in content
519+
assert "Services Not Assessed" in content
520+
assert "Not Run" in content
521+
assert "IAM Privileges" in content
520522

521523

522524
def test_run_partial_azure_warning_in_html(tmp_path):

0 commit comments

Comments
 (0)