Coverage for src / codeaudit / dashboard_reports.py: 0%
100 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-09 09:33 +0200
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-09 09:33 +0200
1"""
2License GPLv3 or higher.
4(C) 2026 Created by Maikel Mardjan - https://nocomplexity.com/
6This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
8This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
10You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
12API functions: Used for dashboard reporting (Panel / WASM) and notebooks, or to build custom reports.
13"""
15# import panel as pn
17SAST_REPORT_CSS = """
18<style>
19.sast-report {
20 font-family: Arial, sans-serif;
21 max-width: 1200px;
22 margin: 0 auto; /* centers it */
23}
25.sast-report table {
26 border-collapse: collapse;
27 width: 100%; /* ensures all tables take full container width */
28 max-width: 1200px; /* optional: caps table width */
29 margin-top: 10px;
30}
32.sast-report th, .sast-report td {
33 border: 1px solid #ddd;
34 padding: 8px;
35 vertical-align: top;
36}
38.sast-report th {
39 background-color: #E69F00;
40 text-align: left;
41}
44/* Code blocks */
45pre {
46 font-size: 1.25em;
47 border-radius: 5px;
48 overflow-x: auto;
49 width: 100%;
50 max-width: 800px;
51}
53/* Code styling */
54.sast-report pre {
55 margin: 0;
56}
58.sast-report pre code.language-python {
59 background-color: #2d2d2d;
60 color: #f8f8f2;
61 font-family: Consolas, Monaco, 'Courier New', monospace;
62 font-size: 13px;
63 line-height: 1.5;
64 padding: 10px;
65 border-radius: 6px;
66 display: block;
67 overflow-x: auto;
68 white-space: pre;
69}
71/* Severity colors */
72.severity-high { color: red; font-weight: bold; }
73.severity-medium { color: orange; font-weight: bold; }
74.severity-low { color: green; }
76details summary {
77 cursor: pointer;
78 font-weight: bold;
79 margin-top: 5px;
80}
81</style>
82"""
85def _require_panel():
86 """Import the optional Panel dependency.
88 Returns:
89 module: The imported panel module.
91 Raises:
92 ImportError: If the panel package is not installed. The error message
93 instructs the user to install the optional dependency.
94 """
95 try:
96 import panel as pn
98 return pn
99 except ImportError:
100 raise ImportError(
101 "Optional dependency 'panel' not installed. "
102 "Run: pip install your-package[panel]"
103 )
106def create_statistics_overview(scanresult):
107 """
108 Returns a Statistics Overview with Panel
109 layout with HTML panes (4 per row).
110 """
111 pn = _require_panel() # Panel module is needed for this function
112 if not scanresult or not isinstance(scanresult, dict):
113 return pn.pane.HTML("⚠️ No scan result")
115 statistics = scanresult.get("statistics_overview", {})
116 if not statistics:
117 return pn.pane.HTML("⚠️ No statistics found")
119 title = scanresult.get("package_name", "Unknown")
120 version = scanresult.get("package_release", "N/A")
122 # Style for statistic cards
123 custom_style = {
124 "background": "linear-gradient(135deg, #FFF3CC, #FFF9E6)",
125 "padding": "16px",
126 "min-width": "180px",
127 "font-size": "16px",
128 "color": "black",
129 "border": "none",
130 "border-left": "4px solid #E69F00",
131 "border-radius": "10px",
132 }
134 # Style for header (red accent)
135 header_style = {
136 "background": "#E8F5E9",
137 "padding": "16px",
138 "min-width": "180px",
139 "font-size": "16px",
140 "color": "black",
141 "border": "none",
142 "border-left": "4px solid #4CAF50",
143 "border-radius": "10px",
144 "margin-bottom": "12px", # Nice spacing below header
145 }
147 # Create statistic panes
148 panes = []
149 for key, value in statistics.items():
150 html_content = f"""
151 <div style="text-align: center;">
152 {key}<br>
153 <span style="font-size:22px; font-weight: bold;">{value}</span>
154 </div>
155 """
156 pane = pn.pane.HTML(html_content, styles=custom_style)
157 panes.append(pane)
159 # Arrange panes: 4 per row
160 rows = []
161 for i in range(0, len(panes), 4):
162 row = pn.Row(*panes[i : i + 4], sizing_mode="stretch_width")
163 rows.append(row)
165 # Styled Header (full width, matching row width)
166 header_html = f"""
167 <div>
168 Package Overview</br></br>
169 <b>{title}</b> — version <b>{version}</b>
170 </div>
171 """
172 header = pn.pane.HTML(header_html, styles=header_style)
174 # Final layout: Header + statistic rows
175 return pn.Column(header, *rows, sizing_mode="stretch_width")
178def report_sast_results(scanresult):
179 """
180 Generates a complete HTML report of all SAST findings from the scan result.
181 Each file's findings are shown in a collapsible <details> element.
182 """
183 pn = _require_panel() # Panel module is needed for this function
185 # --- Input validation ---
186 if not scanresult or not isinstance(scanresult, dict):
187 return '<br><h2">⚠️ No scan result provided</h2>'
189 file_security_info = scanresult.get("file_security_info")
190 if not isinstance(file_security_info, dict) or len(file_security_info) == 0:
191 return '<br><h2">⚠️ No file security info found</h2>'
193 # Collect files that have SAST results
194 files_with_findings = []
195 for file_id, file_info in file_security_info.items():
196 if isinstance(file_info, dict):
197 sast_result = file_info.get("sast_result")
198 if isinstance(sast_result, dict) and len(sast_result) > 0:
199 files_with_findings.append(file_info)
201 if not files_with_findings:
202 return '<br><h2">✅ No security weaknesses found</h2>'
204 total_number_of_files = scanresult["statistics_overview"]["Number_Of_Files"]
205 # --- HTML REPORT ---
206 html = SAST_REPORT_CSS + f"""
207 <div class="sast-report">
208 <h2>Detailed Code Security Report</h2>
209 <p><strong>Package:</strong> {scanresult.get("package_name", "N/A")}</p>
210 <p><strong>version:</strong> {scanresult.get("package_release", "N/A")}</p>
211 <p><strong>Total files with findings:</strong> {len(files_with_findings)} of {total_number_of_files} files in total</p>
212 """
214 for file_info in files_with_findings:
215 filename = file_info.get("FileName", "Unknown File")
216 sast_result = file_info.get("sast_result", {})
217 num_issues = len(sast_result)
219 html += f"""
220 <p>⚠️ <b>{num_issues}</b> potential security issue{"s" if num_issues > 1 else ""}
221 found in <b>{filename}</b></p>
222 """
224 html += "<details>"
225 html += "<summary>View identified security weaknesses</summary>"
227 html += """
228 <table>
229 <thead>
230 <tr>
231 <th>Line</th>
232 <th>Validation</th>
233 <th>Severity</th>
234 <th>Info</th>
235 <th>Code Snippet</th>
236 </tr>
237 </thead>
238 <tbody>
239 """
241 sorted_findings = sorted(
242 sast_result.values(), key=lambda x: int(x.get("line", 0))
243 )
245 for finding in sorted_findings:
246 line = finding.get("line", "—")
247 validation = finding.get("validation", "—")
248 severity = finding.get("severity", "—")
249 info = finding.get("info", "—")
250 code = finding.get("code", "")
252 html += f"""
253 <tr>
254 <td><strong>{line}</strong></td>
255 <td><code>{validation}</code></td>
256 <td><span class="severity-{severity}">{severity}</span></td>
257 <td>{info}</td>
258 <td>{code}</td>
259 </tr>
260 """
262 html += "</tbody></table>"
263 html += "</details><br>"
265 html += "</div>"
266 RESULT_HTML_PANE = {
267 "background": "#FFFFE0",
268 "padding": "16px",
269 "min-width": "180px",
270 "font-size": "16px",
271 "color": "black",
272 "border": "none",
273 "border-left": "4px solid #E69F00",
274 "border-radius": "10px",
275 }
276 sast_result = pn.pane.HTML(html, styles=RESULT_HTML_PANE)
277 return sast_result
280def report_used_modules(scanresult):
281 """reports used modules for a package"""
282 pn = _require_panel() # Panel module is needed for this function
283 # --- Input validation ---
284 card1 = ""
285 if not scanresult or not isinstance(scanresult, dict):
286 return '<br><h2">⚠️ No scan result provided</h2>'
287 modules_discovered = scanresult["module_overview"]
288 core_modules = modules_discovered["core_modules"]
289 external_modules = modules_discovered["imported_modules"]
290 card1 += "<details>"
291 card1 += "<summary><b>Used Python Standard libraries</b></summary>"
293 card1 += (
294 "<ul>\n"
295 + "\n".join(f" <li>{module}</li>" for module in core_modules)
296 + "\n</ul>"
297 )
298 card1 += "</details>"
300 card2 = "<details>"
301 card2 += "<summary><b>Imported libraries (modules)</b></summary>"
302 card2 += (
303 "<ul>\n"
304 + "\n".join(f" <li>{module}</li>" for module in external_modules)
305 + "\n</ul>"
306 )
307 card2 += "</details>"
308 # Style for statistic cards
309 custom_style = {
310 "background": "linear-gradient(135deg, #FFF3CC, #FFF9E6)",
311 "padding": "16px",
312 "margin-bottom": "24px",
313 "min-width": "180px",
314 "font-size": "16px",
315 "color": "black",
316 "border": "none",
317 "border-left": "4px solid #E69F00",
318 "border-radius": "10px",
319 }
320 cardoutput_1 = pn.pane.HTML(card1, styles=custom_style)
321 cardoutput_2 = pn.pane.HTML(card2, styles=custom_style)
322 cards = pn.Row(cardoutput_1, cardoutput_2)
323 return cards
326def get_info_text():
327 """returns the info text styled
328 for the sidebar
329 """
330 pn = _require_panel() # Panel module is needed for this function
331 custom_style = {
332 "background": "linear-gradient(135deg, #F0FDF4, #ECFDF5)", # soft green tint
333 "padding": "18px",
334 "min-width": "200px",
335 "font-size": "15px",
336 "color": "#1F2937", # slightly richer dark text
337 "border": "1px solid #D1FAE5", # subtle green border
338 "border-left": "4px solid #10B981", # emerald accent
339 "border-radius": "12px",
340 "box-shadow": "0 2px 6px rgba(0, 0, 0, 0.05)", # soft depth
341 }
343 infotext = pn.pane.HTML(
344 """
345 <p><b>Python Code Audit</b> is a Static Application Security Testing (SAST) tool used to find security weaknesses in Python code.</p>
346 <p>Use the CLI version for powerful command-line scanning with many more options.<p>
347 <br><br>
348 <a href="https://nocomplexity.com/documents/codeaudit/intro.html"
349 target="_blank"
350 style="
351 display: inline-flex;
352 align-items: center;
353 gap: 8px;
354 padding: 10px 18px;
355 background-color: #24292f;
356 color: white;
357 text-decoration: none;
358 border-radius: 8px;
359 font-weight: 600;
360 font-size: 15px;
361 transition: all 0.2s ease;
362 width: 200px;
363 ">Documentation
364 </a>
365 <br><br>
366 <a href="https://github.com/nocomplexity/codeaudit"
367 target="_blank"
368 style="
369 display: inline-flex;
370 align-items: center;
371 gap: 8px;
372 padding: 10px 18px;
373 background-color: #24292f;
374 color: white;
375 text-decoration: none;
376 border-radius: 8px;
377 font-weight: 600;
378 font-size: 15px;
379 transition: all 0.2s ease;
380 width: 200px;
381 ">
382 <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 24 24">
383 <path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.66-1.455-3.66-1.455-.495-1.26-1.2-1.59-1.2-1.59-.975-.66.075-.645.075-.645 1.08.075 1.65 1.11 1.65 1.11 0.96 1.635 2.505 1.17 3.12.885.09-.69.375-1.17.675-1.44-2.55-.285-5.22-1.275-5.22-5.67 0-1.26.45-2.28 1.185-3.09-.12-.285-.51-1.425.105-2.97 0 0 .975-.315 3.195 1.185.93-.255 1.92-.375 2.91-.375s1.98.12 2.91.375c2.22-1.5 3.195-1.185 3.195-1.185.615 1.545.225 2.685.105 2.97.735.81 1.185 1.83 1.185 3.09 0 4.41-2.67 5.385-5.22 5.67.405.345.765 1.02.765 2.07 0 1.5-.015 2.715-.015 3.09 0 .315.225.69.825.57C20.565 21.795 24 17.31 24 12c0-6.63-5.37-12-12-12z"/>
384 </svg>
385 GitHub
386 </a>
388 """,
389 sizing_mode="stretch_width",
390 stylesheets=["""
391 .bk-panel {
392 background: transparent !important;
393 }
394 """],
395 styles=custom_style,
396 )
397 return infotext
400def get_disclaimer_text():
401 """defines the sidebar disclaimer text"""
402 pn = _require_panel() # Panel module is needed for this function
403 disclaimer = (
404 "<br><b>Disclaimer:</b>This scan only evaluates Python files. "
405 "Security weaknesses can also exist in other files used by a Python package.<br><br>"
406 'This SAST tool <a href="https://github.com/nocomplexity/codeaudit" target="_blank">'
407 "Python Code Audit</a> provides a powerful, automatic security analysis for Python source code. "
408 "However, it's not a substitute for human review in combination with business knowledge. "
409 "Undetected vulnerabilities may still exist."
410 )
412 disclaimer_text = pn.pane.HTML(disclaimer)
413 return disclaimer_text