Skip to content

Commit 34f2c34

Browse files
committed
Add support for all 6 NIST control status types in viewer
Extend the NIST viewer to support all 6 status types defined in the control file format, not just the initial 3. This allows proper representation of controls that are inherently met, do not meet requirements, or are not applicable. Status types and their visual representation: 1. automated - Green (#28a745) - Technical controls implemented 2. manual - Blue (#0366d6) - Manual processes required 3. inherently met - Purple (#6f42c1) - Met by system design 4. does not meet - Red (#dc3545) - Does not satisfy requirement 5. not applicable - Gray (#6c757d) - Not relevant to this product 6. pending - Yellow (#ffd33d) - Not yet evaluated Changes: Generator (generate_nist_viewer.py): - Add is_inherently_met, is_does_not_meet, is_not_applicable flags - Track counts for all 6 statuses in statistics - Calculate percentages for progress bars Templates: - _shared_styles.html: Add badge styles for 3 new status types - index.html: Add stat cards for inherently met, not applicable, does not meet - index.html: Update family progress bars to show all 6 status types - controls.html, family.html, gaps.html, statistics.html: Update statusClass determination to handle all 6 types Dashboard now shows: - 7 stat cards (Total + 6 status types) - Multi-segment progress bars with up to 6 colors - Proper filtering and display for all status types Example control file usage: ```yaml - id: pe-1 title: Physical Protection Policy levels: [low, moderate, high] rules: [] status: not applicable notes: Physical security is managed at datacenter level ``` This allows organizations to properly track controls that are: - Satisfied through architectural choices (inherently met) - Not relevant to their deployment model (not applicable) - Identified as gaps that cannot be remediated (does not meet)
1 parent ef06101 commit 34f2c34

5 files changed

Lines changed: 52 additions & 5 deletions

File tree

utils/nist_sync/generate_nist_viewer.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,9 @@ def merge_control_data(product_controls: Dict[str, Any], oscal_controls: Dict[st
153153
'is_automated': control.get('status') == 'automated',
154154
'is_manual': control.get('status') == 'manual',
155155
'is_pending': control.get('status') == 'pending',
156+
'is_inherently_met': control.get('status') == 'inherently met',
157+
'is_does_not_meet': control.get('status') == 'does not meet',
158+
'is_not_applicable': control.get('status') == 'not applicable',
156159
}
157160

158161
merged.append(merged_control)
@@ -190,6 +193,9 @@ def generate_viewer_data(products: List[str], repo_root: Path) -> Dict[str, Any]
190193
'automated': sum(1 for c in controls if c['is_automated']),
191194
'manual': sum(1 for c in controls if c['is_manual']),
192195
'pending': sum(1 for c in controls if c['is_pending']),
196+
'inherently_met': sum(1 for c in controls if c['is_inherently_met']),
197+
'does_not_meet': sum(1 for c in controls if c['is_does_not_meet']),
198+
'not_applicable': sum(1 for c in controls if c['is_not_applicable']),
193199
'with_rules': sum(1 for c in controls if c['has_rules']),
194200
'without_rules': sum(1 for c in controls if not c['has_rules']),
195201
}

utils/nist_sync/templates/_shared_styles.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,9 @@
120120
.badge-automated { background: #28a745; color: white; }
121121
.badge-manual { background: #0366d6; color: white; }
122122
.badge-pending { background: #ffd33d; color: #24292e; }
123+
.badge-inherently_met { background: #6f42c1; color: white; }
124+
.badge-does_not_meet { background: #dc3545; color: white; }
125+
.badge-not_applicable { background: #6c757d; color: white; }
123126
.badge-low { background: #dbedff; color: #0366d6; }
124127
.badge-moderate { background: #fff3cd; color: #856404; }
125128
.badge-high { background: #f8d7da; color: #721c24; }

utils/nist_sync/templates/controls.html

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,10 @@ <h4>Gap Status</h4>
338338

339339
container.innerHTML = filteredControls.map(control => {
340340
const statusClass = control.is_automated ? 'automated' :
341-
control.is_manual ? 'manual' : 'pending';
341+
control.is_manual ? 'manual' :
342+
control.is_inherently_met ? 'inherently_met' :
343+
control.is_does_not_meet ? 'does_not_meet' :
344+
control.is_not_applicable ? 'not_applicable' : 'pending';
342345
const gapIndicator = control.has_rules ? '' :
343346
'<span class="gap-indicator gap-full"></span>';
344347

utils/nist_sync/templates/family.html

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,10 @@ <h3>Controls in this Family</h3>
349349

350350
familyControls.sort((a, b) => a.id.localeCompare(b.id)).forEach(control => {
351351
const statusClass = control.is_automated ? 'automated' :
352-
control.is_manual ? 'manual' : 'pending';
352+
control.is_manual ? 'manual' :
353+
control.is_inherently_met ? 'inherently_met' :
354+
control.is_does_not_meet ? 'does_not_meet' :
355+
control.is_not_applicable ? 'not_applicable' : 'pending';
353356
const gapClass = control.has_rules ? '' : 'gap';
354357

355358
const item = document.createElement('div');

utils/nist_sync/templates/index.html

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,19 @@ <h2>Dashboard</h2>
3030
<div class="stat-label">Manual</div>
3131
</div>
3232
<div class="stat-card">
33-
<div class="stat-value" id="stat-pending" style="color: #d73a49;">0</div>
33+
<div class="stat-value" id="stat-inherently-met" style="color: #6f42c1;">0</div>
34+
<div class="stat-label">Inherently Met</div>
35+
</div>
36+
<div class="stat-card">
37+
<div class="stat-value" id="stat-not-applicable" style="color: #6c757d;">0</div>
38+
<div class="stat-label">Not Applicable</div>
39+
</div>
40+
<div class="stat-card">
41+
<div class="stat-value" id="stat-does-not-meet" style="color: #dc3545;">0</div>
42+
<div class="stat-label">Does Not Meet</div>
43+
</div>
44+
<div class="stat-card">
45+
<div class="stat-value" id="stat-pending" style="color: #ffd33d;">0</div>
3446
<div class="stat-label">Pending</div>
3547
</div>
3648
</div>
@@ -116,6 +128,9 @@ <h3>Coverage by Control Family</h3>
116128
document.getElementById('stat-total').textContent = stats.total;
117129
document.getElementById('stat-automated').textContent = stats.automated;
118130
document.getElementById('stat-manual').textContent = stats.manual;
131+
document.getElementById('stat-inherently-met').textContent = stats.inherently_met || 0;
132+
document.getElementById('stat-not-applicable').textContent = stats.not_applicable || 0;
133+
document.getElementById('stat-does-not-meet').textContent = stats.does_not_meet || 0;
119134
document.getElementById('stat-pending').textContent = stats.pending;
120135

121136
// Update coverage progress
@@ -188,7 +203,15 @@ <h3>Coverage by Control Family</h3>
188203
const familyCounts = {};
189204

190205
EMBEDDED_DATA.families.forEach(family => {
191-
familyCounts[family.id] = { total: 0, automated: 0, manual: 0, pending: 0 };
206+
familyCounts[family.id] = {
207+
total: 0,
208+
automated: 0,
209+
manual: 0,
210+
inherently_met: 0,
211+
does_not_meet: 0,
212+
not_applicable: 0,
213+
pending: 0
214+
};
192215
});
193216

194217
controls.forEach(control => {
@@ -198,6 +221,9 @@ <h3>Coverage by Control Family</h3>
198221
family.total++;
199222
if (control.is_automated) family.automated++;
200223
else if (control.is_manual) family.manual++;
224+
else if (control.is_inherently_met) family.inherently_met++;
225+
else if (control.is_does_not_meet) family.does_not_meet++;
226+
else if (control.is_not_applicable) family.not_applicable++;
201227
else if (control.is_pending) family.pending++;
202228
});
203229

@@ -210,6 +236,9 @@ <h3>Coverage by Control Family</h3>
210236

211237
const automatedPercent = (counts.automated / counts.total) * 100;
212238
const manualPercent = (counts.manual / counts.total) * 100;
239+
const inherentlyMetPercent = (counts.inherently_met / counts.total) * 100;
240+
const doesNotMeetPercent = (counts.does_not_meet / counts.total) * 100;
241+
const notApplicablePercent = (counts.not_applicable / counts.total) * 100;
213242
const pendingPercent = (counts.pending / counts.total) * 100;
214243

215244
const barHtml = `
@@ -221,7 +250,10 @@ <h3>Coverage by Control Family</h3>
221250
<div style="display: flex; height: 24px; background: #e1e4e8; border-radius: 4px; overflow: hidden;">
222251
<div style="width: ${automatedPercent}%; background: #28a745;" title="Automated: ${counts.automated}"></div>
223252
<div style="width: ${manualPercent}%; background: #0366d6;" title="Manual: ${counts.manual}"></div>
224-
<div style="width: ${pendingPercent}%; background: #d73a49;" title="Pending: ${counts.pending}"></div>
253+
<div style="width: ${inherentlyMetPercent}%; background: #6f42c1;" title="Inherently Met: ${counts.inherently_met}"></div>
254+
<div style="width: ${notApplicablePercent}%; background: #6c757d;" title="Not Applicable: ${counts.not_applicable}"></div>
255+
<div style="width: ${doesNotMeetPercent}%; background: #dc3545;" title="Does Not Meet: ${counts.does_not_meet}"></div>
256+
<div style="width: ${pendingPercent}%; background: #ffd33d;" title="Pending: ${counts.pending}"></div>
225257
</div>
226258
</div>
227259
`;

0 commit comments

Comments
 (0)