iam-policy-validator 1.0.4__py3-none-any.whl → 1.1.0__py3-none-any.whl

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.

Potentially problematic release.


This version of iam-policy-validator might be problematic. Click here for more details.

@@ -0,0 +1,428 @@
1
+ """Enhanced formatter - Rich-based console output with modern design."""
2
+
3
+ from io import StringIO
4
+
5
+ from rich.console import Console
6
+ from rich.panel import Panel
7
+ from rich.rule import Rule
8
+ from rich.table import Table
9
+ from rich.text import Text
10
+ from rich.tree import Tree
11
+
12
+ from iam_validator.core.formatters.base import OutputFormatter
13
+ from iam_validator.core.models import PolicyValidationResult, ValidationReport
14
+
15
+
16
+ class EnhancedFormatter(OutputFormatter):
17
+ """Enhanced console formatter with modern, visually rich output using Rich library."""
18
+
19
+ @property
20
+ def format_id(self) -> str:
21
+ return "enhanced"
22
+
23
+ @property
24
+ def description(self) -> str:
25
+ return "Enhanced console output with progress bars, tree structure, and rich visuals"
26
+
27
+ def format(self, report: ValidationReport, **kwargs) -> str:
28
+ """Format validation report as modern Rich console output.
29
+
30
+ This creates a visually enhanced string representation with:
31
+ - Gradient-styled headers
32
+ - Progress bars for validation metrics
33
+ - Tree structure for issues
34
+ - Bordered panels with icons
35
+
36
+ Args:
37
+ report: Validation report to format
38
+ **kwargs: Additional options (color: bool = True)
39
+
40
+ Returns:
41
+ Formatted string with ANSI codes for console display
42
+ """
43
+ # Allow disabling color for plain text output
44
+ color = kwargs.get("color", True)
45
+
46
+ # Use StringIO to capture Rich console output
47
+ string_buffer = StringIO()
48
+ console = Console(file=string_buffer, force_terminal=color, width=120)
49
+
50
+ # Header with title
51
+ console.print()
52
+ title = Text("IAM Policy Validation Report", style="bold cyan", justify="center")
53
+ console.print(Panel(title, border_style="bright_blue", padding=(1, 0)))
54
+ console.print()
55
+
56
+ # Executive Summary with progress bars
57
+ self._print_summary_panel(console, report)
58
+
59
+ # Severity breakdown if there are issues
60
+ if report.total_issues > 0:
61
+ console.print()
62
+ self._print_severity_breakdown(console, report)
63
+
64
+ console.print()
65
+ console.print(Rule(title="[bold]Detailed Results", style="bright_blue"))
66
+ console.print()
67
+
68
+ # Detailed results using tree structure
69
+ for idx, result in enumerate(report.results, 1):
70
+ self._format_policy_result_modern(console, result, idx, len(report.results))
71
+
72
+ # Final status with styled box
73
+ console.print()
74
+ self._print_final_status(console, report)
75
+
76
+ # Get the formatted output
77
+ output = string_buffer.getvalue()
78
+ string_buffer.close()
79
+
80
+ return output
81
+
82
+ def _print_summary_panel(self, console: Console, report: ValidationReport) -> None:
83
+ """Print summary panel with clean metrics display."""
84
+ # Create a simple table for metrics without progress bars
85
+ metrics_table = Table.grid(padding=(0, 2))
86
+ metrics_table.add_column(style="bold", justify="left", width=30)
87
+ metrics_table.add_column(style="bold", justify="left", width=20)
88
+
89
+ # Total policies
90
+ metrics_table.add_row(
91
+ "📋 Total Policies",
92
+ str(report.total_policies),
93
+ )
94
+
95
+ # Valid policies
96
+ if report.total_policies > 0:
97
+ valid_pct = report.valid_policies * 100 // report.total_policies
98
+ metrics_table.add_row(
99
+ "✅ Valid Policies",
100
+ f"[green]{report.valid_policies} ({valid_pct}%)[/green]",
101
+ )
102
+
103
+ # Invalid policies
104
+ invalid_pct = report.invalid_policies * 100 // report.total_policies
105
+ if report.invalid_policies > 0:
106
+ metrics_table.add_row(
107
+ "❌ Invalid Policies",
108
+ f"[red]{report.invalid_policies} ({invalid_pct}%)[/red]",
109
+ )
110
+ else:
111
+ metrics_table.add_row(
112
+ "❌ Invalid Policies",
113
+ f"[dim]{report.invalid_policies} ({invalid_pct}%)[/dim]",
114
+ )
115
+
116
+ # Total issues
117
+ if report.total_issues > 0:
118
+ metrics_table.add_row(
119
+ "⚠️ Total Issues Found",
120
+ f"[red]{report.total_issues}[/red]",
121
+ )
122
+ else:
123
+ metrics_table.add_row(
124
+ "⚠️ Total Issues Found",
125
+ f"[green]{report.total_issues}[/green]",
126
+ )
127
+
128
+ console.print(
129
+ Panel(
130
+ metrics_table,
131
+ title="📊 Executive Summary",
132
+ border_style="bright_blue",
133
+ padding=(1, 2),
134
+ )
135
+ )
136
+
137
+ def _create_progress_bar(self, value: int, total: int, color: str) -> str:
138
+ """Create a simple text-based progress bar."""
139
+ if total == 0:
140
+ return "[dim]───────────────────────[/dim]"
141
+
142
+ percentage = min(value * 100 // total, 100)
143
+ filled = int(percentage / 5) # 20 bars total (100/5)
144
+ empty = 20 - filled
145
+
146
+ bar = f"[{color}]{'█' * filled}[/{color}][dim]{'░' * empty}[/dim]"
147
+ return bar
148
+
149
+ def _print_severity_breakdown(self, console: Console, report: ValidationReport) -> None:
150
+ """Print a clean breakdown of issues by severity."""
151
+ # Count issues by severity
152
+ severity_counts = {
153
+ "critical": 0,
154
+ "high": 0,
155
+ "error": 0,
156
+ "medium": 0,
157
+ "warning": 0,
158
+ "low": 0,
159
+ "info": 0,
160
+ }
161
+
162
+ for result in report.results:
163
+ for issue in result.issues:
164
+ severity = issue.severity.lower()
165
+ if severity in severity_counts:
166
+ severity_counts[severity] += 1
167
+
168
+ # Create clean severity table
169
+ severity_table = Table.grid(padding=(0, 2))
170
+ severity_table.add_column(style="bold", justify="left", width=25)
171
+ severity_table.add_column(style="bold", justify="left", width=15)
172
+
173
+ # Show individual severity counts
174
+ if severity_counts["critical"] > 0:
175
+ severity_table.add_row(
176
+ "🔴 Critical",
177
+ f"[red]{severity_counts['critical']}[/red]",
178
+ )
179
+
180
+ if severity_counts["high"] > 0:
181
+ severity_table.add_row(
182
+ "🔴 High",
183
+ f"[red]{severity_counts['high']}[/red]",
184
+ )
185
+
186
+ if severity_counts["error"] > 0:
187
+ severity_table.add_row(
188
+ "🔴 Error",
189
+ f"[red]{severity_counts['error']}[/red]",
190
+ )
191
+
192
+ if severity_counts["medium"] > 0:
193
+ severity_table.add_row(
194
+ "🟡 Medium",
195
+ f"[yellow]{severity_counts['medium']}[/yellow]",
196
+ )
197
+
198
+ if severity_counts["warning"] > 0:
199
+ severity_table.add_row(
200
+ "🟡 Warning",
201
+ f"[yellow]{severity_counts['warning']}[/yellow]",
202
+ )
203
+
204
+ if severity_counts["low"] > 0:
205
+ severity_table.add_row(
206
+ "🔵 Low",
207
+ f"[blue]{severity_counts['low']}[/blue]",
208
+ )
209
+
210
+ if severity_counts["info"] > 0:
211
+ severity_table.add_row(
212
+ "🔵 Info",
213
+ f"[blue]{severity_counts['info']}[/blue]",
214
+ )
215
+
216
+ console.print(
217
+ Panel(severity_table, title="🎯 Issue Severity Breakdown", border_style="bright_blue")
218
+ )
219
+
220
+ def _format_policy_result_modern(
221
+ self, console: Console, result: PolicyValidationResult, idx: int, total: int
222
+ ) -> None:
223
+ """Format policy results with modern tree structure.
224
+
225
+ Args:
226
+ console: Rich console instance
227
+ result: Policy validation result
228
+ idx: Index of this policy (1-based)
229
+ total: Total number of policies
230
+ """
231
+ # Status icon and color
232
+ if result.is_valid and not result.issues:
233
+ icon = "✅"
234
+ color = "green"
235
+ status_text = "VALID"
236
+ elif result.is_valid and result.issues:
237
+ # Valid IAM policy but has security findings
238
+ # Check severity to determine the appropriate status
239
+ has_critical = any(i.severity in ("error", "critical", "high") for i in result.issues)
240
+ if has_critical:
241
+ icon = "⚠️"
242
+ color = "red"
243
+ status_text = "VALID (with security issues)"
244
+ else:
245
+ icon = "⚠️"
246
+ color = "yellow"
247
+ status_text = "VALID (with warnings)"
248
+ else:
249
+ # Policy failed validation (is_valid=false)
250
+ # Check if it's due to IAM errors or security issues
251
+ has_iam_errors = any(i.severity == "error" for i in result.issues)
252
+ has_security_critical = any(i.severity in ("critical", "high") for i in result.issues)
253
+
254
+ if has_iam_errors and has_security_critical:
255
+ # Both IAM errors and security issues
256
+ status_text = "INVALID (IAM errors + security issues)"
257
+ elif has_iam_errors:
258
+ # Only IAM validation errors
259
+ status_text = "INVALID (IAM errors)"
260
+ else:
261
+ # Only security issues (failed due to fail_on_severity config)
262
+ status_text = "FAILED (critical security issues)"
263
+
264
+ icon = "❌"
265
+ color = "red"
266
+
267
+ # Policy header
268
+ header = Text()
269
+ header.append(f"{icon} ", style=color)
270
+ header.append(f"[{idx}/{total}] ", style="dim")
271
+ header.append(result.policy_file, style=f"bold {color}")
272
+ header.append(f" • {status_text}", style=f"{color}")
273
+
274
+ if not result.issues:
275
+ console.print(header)
276
+ console.print(" [dim italic]No issues detected[/dim italic]")
277
+ console.print()
278
+ return
279
+
280
+ console.print(header)
281
+ console.print(f" [dim]{len(result.issues)} issue(s) found[/dim]")
282
+ console.print()
283
+
284
+ # Create tree structure for issues
285
+ tree = Tree(f"[bold]Issues ({len(result.issues)})[/bold]", guide_style="bright_black")
286
+
287
+ # Group issues by severity with proper categorization
288
+ critical_issues = [i for i in result.issues if i.severity == "critical"]
289
+ high_issues = [i for i in result.issues if i.severity == "high"]
290
+ error_issues = [i for i in result.issues if i.severity == "error"]
291
+ medium_issues = [i for i in result.issues if i.severity == "medium"]
292
+ warning_issues = [i for i in result.issues if i.severity == "warning"]
293
+ low_issues = [i for i in result.issues if i.severity == "low"]
294
+ info_issues = [i for i in result.issues if i.severity == "info"]
295
+
296
+ # Add critical issues (security checks)
297
+ if critical_issues:
298
+ critical_branch = tree.add("🔴 [bold red]Critical[/bold red]")
299
+ for issue in critical_issues:
300
+ self._add_issue_to_tree(critical_branch, issue, "red")
301
+
302
+ # Add high severity issues (security checks)
303
+ if high_issues:
304
+ high_branch = tree.add("🔴 [bold red]High[/bold red]")
305
+ for issue in high_issues:
306
+ self._add_issue_to_tree(high_branch, issue, "red")
307
+
308
+ # Add errors (IAM validation)
309
+ if error_issues:
310
+ error_branch = tree.add("🔴 [bold red]Error[/bold red]")
311
+ for issue in error_issues:
312
+ self._add_issue_to_tree(error_branch, issue, "red")
313
+
314
+ # Add medium severity issues (security checks)
315
+ if medium_issues:
316
+ medium_branch = tree.add("🟡 [bold yellow]Medium[/bold yellow]")
317
+ for issue in medium_issues:
318
+ self._add_issue_to_tree(medium_branch, issue, "yellow")
319
+
320
+ # Add warnings (IAM validation)
321
+ if warning_issues:
322
+ warning_branch = tree.add("🟡 [bold yellow]Warning[/bold yellow]")
323
+ for issue in warning_issues:
324
+ self._add_issue_to_tree(warning_branch, issue, "yellow")
325
+
326
+ # Add low severity issues (security checks)
327
+ if low_issues:
328
+ low_branch = tree.add("🔵 [bold blue]Low[/bold blue]")
329
+ for issue in low_issues:
330
+ self._add_issue_to_tree(low_branch, issue, "blue")
331
+
332
+ # Add info (IAM validation)
333
+ if info_issues:
334
+ info_branch = tree.add("🔵 [bold blue]Info[/bold blue]")
335
+ for issue in info_issues:
336
+ self._add_issue_to_tree(info_branch, issue, "blue")
337
+
338
+ console.print(" ", tree)
339
+ console.print()
340
+
341
+ def _add_issue_to_tree(self, branch: Tree, issue, color: str) -> None:
342
+ """Add an issue to a tree branch."""
343
+ # Build location string
344
+ location = f"Statement {issue.statement_index}"
345
+ if issue.statement_sid:
346
+ location = f"{issue.statement_sid} (#{issue.statement_index})"
347
+ if issue.line_number is not None:
348
+ location += f" @L{issue.line_number}"
349
+
350
+ # Issue summary
351
+ issue_text = Text()
352
+ issue_text.append(f"[{location}] ", style="dim")
353
+ issue_text.append(issue.issue_type, style=f"bold {color}")
354
+ issue_node = branch.add(issue_text)
355
+
356
+ # Message
357
+ msg_node = issue_node.add(Text(issue.message, style="white"))
358
+
359
+ # Details
360
+ if issue.action or issue.resource or issue.condition_key:
361
+ details = []
362
+ if issue.action:
363
+ details.append(f"Action: {issue.action}")
364
+ if issue.resource:
365
+ details.append(f"Resource: {issue.resource}")
366
+ if issue.condition_key:
367
+ details.append(f"Condition: {issue.condition_key}")
368
+ msg_node.add(Text(" • ".join(details), style="dim cyan"))
369
+
370
+ # Suggestion
371
+ if issue.suggestion:
372
+ suggestion_text = Text()
373
+ suggestion_text.append("💡 ", style="yellow")
374
+ suggestion_text.append(issue.suggestion, style="italic yellow")
375
+ msg_node.add(suggestion_text)
376
+
377
+ def _print_final_status(self, console: Console, report: ValidationReport) -> None:
378
+ """Print final status panel."""
379
+ if report.invalid_policies == 0 and report.total_issues == 0:
380
+ # Perfect success
381
+ status = Text("🎉 ALL POLICIES VALIDATED SUCCESSFULLY! 🎉", style="bold green")
382
+ message = Text(
383
+ f"All {report.valid_policies} policies passed validation with no issues.",
384
+ style="green",
385
+ )
386
+ border_color = "green"
387
+ elif report.invalid_policies == 0:
388
+ # Valid IAM policies but may have security findings
389
+ # Check if there are critical/high security issues
390
+ has_critical = any(
391
+ i.severity in ("error", "critical", "high")
392
+ for r in report.results
393
+ for i in r.issues
394
+ )
395
+
396
+ if has_critical:
397
+ status = Text("⚠️ All Policies Valid (with security issues)", style="bold red")
398
+ message = Text(
399
+ f"{report.valid_policies} policies are valid, but {report.total_issues} "
400
+ f"security issue(s) were found that must be addressed.",
401
+ style="red",
402
+ )
403
+ border_color = "red"
404
+ else:
405
+ status = Text("✅ All Policies Valid (with warnings)", style="bold yellow")
406
+ message = Text(
407
+ f"{report.valid_policies} policies are valid, but {report.total_issues} "
408
+ f"warning(s) were found that should be reviewed.",
409
+ style="yellow",
410
+ )
411
+ border_color = "yellow"
412
+ else:
413
+ # Has invalid policies
414
+ status = Text("❌ VALIDATION FAILED", style="bold red")
415
+ message = Text(
416
+ f"{report.invalid_policies} of {report.total_policies} policies have critical "
417
+ f"issues that must be resolved.",
418
+ style="red",
419
+ )
420
+ border_color = "red"
421
+
422
+ # Combine status and message
423
+ final_text = Text()
424
+ final_text.append(status)
425
+ final_text.append("\n\n")
426
+ final_text.append(message)
427
+
428
+ console.print(Panel(final_text, border_style=border_color, padding=(1, 2)))
@@ -228,21 +228,43 @@ class HTMLFormatter(OutputFormatter):
228
228
  text-transform: uppercase;
229
229
  }
230
230
 
231
+ /* IAM Validity Severities */
231
232
  .severity-error {
232
- background: var(--error-color);
233
+ background: #dc3545;
233
234
  color: white;
234
235
  }
235
236
 
236
237
  .severity-warning {
237
- background: var(--warning-color);
238
+ background: #ffc107;
238
239
  color: #333;
239
240
  }
240
241
 
241
242
  .severity-info {
242
- background: var(--info-color);
243
+ background: #0dcaf0;
243
244
  color: white;
244
245
  }
245
246
 
247
+ /* Security Severities */
248
+ .severity-critical {
249
+ background: #8b0000;
250
+ color: white;
251
+ }
252
+
253
+ .severity-high {
254
+ background: #ff6b6b;
255
+ color: white;
256
+ }
257
+
258
+ .severity-medium {
259
+ background: #ffa500;
260
+ color: #333;
261
+ }
262
+
263
+ .severity-low {
264
+ background: #90caf9;
265
+ color: #333;
266
+ }
267
+
246
268
  .chart-container {
247
269
  position: relative;
248
270
  height: 300px;
@@ -296,19 +318,6 @@ class HTMLFormatter(OutputFormatter):
296
318
 
297
319
  def _render_summary(self, report: ValidationReport, include_charts: bool) -> str:
298
320
  """Render summary section with statistics."""
299
- # Count issues by severity - support both IAM validity and security severities
300
- error_count = sum(
301
- 1
302
- for r in report.results
303
- for i in r.issues
304
- if i.severity in ("error", "critical", "high")
305
- )
306
- warning_count = sum(
307
- 1 for r in report.results for i in r.issues if i.severity in ("warning", "medium")
308
- )
309
- info_count = sum(
310
- 1 for r in report.results for i in r.issues if i.severity in ("info", "low")
311
- )
312
321
  total_issues = report.total_issues
313
322
 
314
323
  html_parts = [
@@ -321,24 +330,28 @@ class HTMLFormatter(OutputFormatter):
321
330
  <div class="stat-label">Total Policies</div>
322
331
  </div>
323
332
  <div class="stat-card">
324
- <div class="stat-value">{report.valid_policies}</div>
325
- <div class="stat-label">Valid Policies</div>
333
+ <div class="stat-value" style="color: var(--success-color)">{report.valid_policies}</div>
334
+ <div class="stat-label">Valid (IAM)</div>
326
335
  </div>
327
336
  <div class="stat-card">
328
- <div class="stat-value">{total_issues}</div>
329
- <div class="stat-label">Total Issues</div>
337
+ <div class="stat-value" style="color: var(--error-color)">{report.invalid_policies}</div>
338
+ <div class="stat-label">Invalid (IAM)</div>
339
+ </div>
340
+ <div class="stat-card">
341
+ <div class="stat-value" style="color: var(--warning-color)">{report.policies_with_security_issues}</div>
342
+ <div class="stat-label">Security Findings</div>
330
343
  </div>
331
344
  <div class="stat-card">
332
- <div class="stat-value" style="color: var(--error-color)">{error_count}</div>
333
- <div class="stat-label">Errors</div>
345
+ <div class="stat-value">{total_issues}</div>
346
+ <div class="stat-label">Total Issues</div>
334
347
  </div>
335
348
  <div class="stat-card">
336
- <div class="stat-value" style="color: var(--warning-color)">{warning_count}</div>
337
- <div class="stat-label">Warnings</div>
349
+ <div class="stat-value" style="color: var(--error-color)">{report.validity_issues}</div>
350
+ <div class="stat-label">Validity Issues</div>
338
351
  </div>
339
352
  <div class="stat-card">
340
- <div class="stat-value" style="color: var(--info-color)">{info_count}</div>
341
- <div class="stat-label">Info</div>
353
+ <div class="stat-value" style="color: var(--warning-color)">{report.security_issues}</div>
354
+ <div class="stat-label">Security Issues</div>
342
355
  </div>
343
356
  </div>
344
357
  """
@@ -370,9 +383,17 @@ class HTMLFormatter(OutputFormatter):
370
383
  <label for="severityFilter">Severity</label>
371
384
  <select id="severityFilter">
372
385
  <option value="">All</option>
373
- <option value="error">Errors</option>
374
- <option value="warning">Warnings</option>
375
- <option value="info">Info</option>
386
+ <optgroup label="IAM Validity">
387
+ <option value="error">Error</option>
388
+ <option value="warning">Warning</option>
389
+ <option value="info">Info</option>
390
+ </optgroup>
391
+ <optgroup label="Security">
392
+ <option value="critical">Critical</option>
393
+ <option value="high">High</option>
394
+ <option value="medium">Medium</option>
395
+ <option value="low">Low</option>
396
+ </optgroup>
376
397
  </select>
377
398
  </div>
378
399
  <div class="filter-group">
@@ -391,11 +412,38 @@ class HTMLFormatter(OutputFormatter):
391
412
  </section>
392
413
  """
393
414
 
415
+ def _format_suggestion(self, suggestion: str) -> str:
416
+ """Format suggestion field to show examples in code blocks."""
417
+ if not suggestion:
418
+ return "-"
419
+
420
+ # Check if suggestion contains "Example:" section
421
+ if "\nExample:\n" in suggestion:
422
+ parts = suggestion.split("\nExample:\n", 1)
423
+ text_part = html.escape(parts[0])
424
+ code_part = html.escape(parts[1])
425
+
426
+ return f"""
427
+ <div>
428
+ <div>{text_part}</div>
429
+ <details style="margin-top: 10px;">
430
+ <summary style="cursor: pointer; font-weight: 500; color: var(--text-secondary);">
431
+ 📖 View Example
432
+ </summary>
433
+ <pre class="code-block" style="margin-top: 10px; white-space: pre-wrap;">{code_part}</pre>
434
+ </details>
435
+ </div>
436
+ """
437
+ else:
438
+ return html.escape(suggestion)
439
+
394
440
  def _render_issues_table(self, report: ValidationReport) -> str:
395
441
  """Render issues table."""
396
442
  rows = []
397
443
  for policy_result in report.results:
398
444
  for issue in policy_result.issues:
445
+ formatted_suggestion = self._format_suggestion(issue.suggestion)
446
+
399
447
  row = f"""
400
448
  <tr class="issue-row"
401
449
  data-severity="{issue.severity}"
@@ -407,7 +455,7 @@ class HTMLFormatter(OutputFormatter):
407
455
  <td><span class="severity-badge severity-{issue.severity}">{issue.severity}</span></td>
408
456
  <td>{html.escape(issue.issue_type or "-")}</td>
409
457
  <td>{html.escape(issue.message)}</td>
410
- <td>{html.escape(issue.suggestion or "-")}</td>
458
+ <td>{formatted_suggestion}</td>
411
459
  </tr>
412
460
  """
413
461
  rows.append(row)
@@ -519,21 +567,63 @@ class HTMLFormatter(OutputFormatter):
519
567
  if (typeof Chart !== 'undefined') {
520
568
  const ctx = document.getElementById('severityChart');
521
569
  if (ctx) {
570
+ // Count all severity types
571
+ const criticalCount = document.querySelectorAll('[data-severity="critical"]').length;
572
+ const highCount = document.querySelectorAll('[data-severity="high"]').length;
573
+ const mediumCount = document.querySelectorAll('[data-severity="medium"]').length;
574
+ const lowCount = document.querySelectorAll('[data-severity="low"]').length;
522
575
  const errorCount = document.querySelectorAll('[data-severity="error"]').length;
523
576
  const warningCount = document.querySelectorAll('[data-severity="warning"]').length;
524
577
  const infoCount = document.querySelectorAll('[data-severity="info"]').length;
525
578
 
579
+ // Build labels and data arrays dynamically
580
+ const labels = [];
581
+ const data = [];
582
+ const colors = [];
583
+
584
+ if (criticalCount > 0) {
585
+ labels.push('Critical');
586
+ data.push(criticalCount);
587
+ colors.push('rgba(139, 0, 0, 0.8)');
588
+ }
589
+ if (highCount > 0) {
590
+ labels.push('High');
591
+ data.push(highCount);
592
+ colors.push('rgba(255, 107, 107, 0.8)');
593
+ }
594
+ if (errorCount > 0) {
595
+ labels.push('Error');
596
+ data.push(errorCount);
597
+ colors.push('rgba(220, 53, 69, 0.8)');
598
+ }
599
+ if (mediumCount > 0) {
600
+ labels.push('Medium');
601
+ data.push(mediumCount);
602
+ colors.push('rgba(255, 165, 0, 0.8)');
603
+ }
604
+ if (warningCount > 0) {
605
+ labels.push('Warning');
606
+ data.push(warningCount);
607
+ colors.push('rgba(255, 193, 7, 0.8)');
608
+ }
609
+ if (infoCount > 0) {
610
+ labels.push('Info');
611
+ data.push(infoCount);
612
+ colors.push('rgba(13, 202, 240, 0.8)');
613
+ }
614
+ if (lowCount > 0) {
615
+ labels.push('Low');
616
+ data.push(lowCount);
617
+ colors.push('rgba(144, 202, 249, 0.8)');
618
+ }
619
+
526
620
  new Chart(ctx, {
527
621
  type: 'doughnut',
528
622
  data: {
529
- labels: ['Errors', 'Warnings', 'Info'],
623
+ labels: labels,
530
624
  datasets: [{
531
- data: [errorCount, warningCount, infoCount],
532
- backgroundColor: [
533
- 'rgba(220, 53, 69, 0.8)',
534
- 'rgba(255, 193, 7, 0.8)',
535
- 'rgba(13, 202, 240, 0.8)'
536
- ]
625
+ data: data,
626
+ backgroundColor: colors
537
627
  }]
538
628
  },
539
629
  options: {