diff --git a/guarddog_nexus/web/static/app.js b/guarddog_nexus/web/static/app.js index 8cee215..00dd8db 100644 --- a/guarddog_nexus/web/static/app.js +++ b/guarddog_nexus/web/static/app.js @@ -1,16 +1,5 @@ // GuardDog Nexus — shared UI utilities -function toggleFindings() { - var container = document.getElementById('findings-container'); - if (!container) return; - var details = container.querySelectorAll('details'); - if (details.length === 0) return; - var isOpen = details[0].open; - details.forEach(function (d) { d.open = !isOpen; }); - var btn = document.querySelector('.toggle-all-btn'); - if (btn) btn.textContent = isOpen ? 'Expand All' : 'Collapse All'; -} - function copyCode(btn, codeId) { var el = document.getElementById(codeId); if (!el) return; diff --git a/guarddog_nexus/web/static/style.css b/guarddog_nexus/web/static/style.css index 1180e3a..ee296a4 100644 --- a/guarddog_nexus/web/static/style.css +++ b/guarddog_nexus/web/static/style.css @@ -1,130 +1,71 @@ /* GuardDog Nexus — Web UI styles */ -/* Status badges */ -.flagged { - color: var(--pico-color-red-400); - font-weight: bold; -} +/* ------------------------------------------------------------------ */ +/* Status / severity colours */ +/* ------------------------------------------------------------------ */ +.flagged { color: var(--pico-color-red-400); font-weight: bold; } +.clean { color: var(--pico-color-green-400); } -.clean { - color: var(--pico-color-green-400); -} +.status-pending { color: var(--pico-color-yellow-400); } +.status-scanning { color: var(--pico-color-blue-400); animation: pulse 1.5s ease-in-out infinite; } +.status-completed { color: var(--pico-color-green-400); } +.status-failed { color: var(--pico-color-red-400); } -.status-pending { - color: var(--pico-color-yellow-400); -} +.severity-WARNING { color: var(--pico-color-yellow-400); } +.severity-ERROR { color: var(--pico-color-red-400); } -.status-scanning { - color: var(--pico-color-blue-400); - animation: pulse 1.5s ease-in-out infinite; -} - -.status-completed { - color: var(--pico-color-green-400); -} - -.status-failed { - color: var(--pico-color-red-400); -} - -/* Severity colors */ -.severity-WARNING { - color: var(--pico-color-yellow-400); -} - -.severity-ERROR { - color: var(--pico-color-red-400); -} - -/* Finding cards */ -.finding-card { - margin-bottom: 0.5rem; - padding: 0.5rem; - border-left: 3px solid; - transition: opacity 0.2s; -} - -.finding-card:hover { +/* ------------------------------------------------------------------ */ +/* Dashboard mini-bar */ +/* ------------------------------------------------------------------ */ +.stat-minibar { + display: flex; + gap: 1.5rem; + padding: 0.6rem 0; + margin-bottom: 1.5rem; + border-bottom: 1px solid var(--pico-color-gray-500); + font-size: 0.9rem; opacity: 0.9; } -.finding-card.WARNING { - border-left-color: var(--pico-color-yellow-400); -} - -.finding-card.ERROR { - border-left-color: var(--pico-color-red-400); -} - -.finding-card.INFO { - border-left-color: var(--pico-color-blue-400); -} - -/* Tables */ -table { - font-size: 0.9rem; -} - -/* Nav */ -nav { +/* Dashboard block grid (2 cols → 1 on mobile) */ +.dashboard-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 1rem; margin-bottom: 1rem; } -/* Stats grid */ -.stats-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); - gap: 1rem; - margin-bottom: 2rem; -} - -.stat-card { - text-align: center; +.dash-block { padding: 1rem; - transition: transform 0.2s, box-shadow 0.2s; + margin-bottom: 0; } -.stat-card:hover { - transform: translateY(-2px); - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); +.dash-block h3 { + margin-top: 0; + font-size: 0.95rem; } -/* Severity ratio bar */ -.severity-bar { - display: flex; - height: 8px; - border-radius: 4px; - overflow: hidden; - margin-top: 4px; - margin-bottom: 2rem; +.dash-block-warn { + border-left: 3px solid var(--pico-color-red-400); } -.severity-bar .bar-error { - background: var(--pico-color-red-400); - transition: width 0.5s ease; -} +/* ------------------------------------------------------------------ */ +/* Tables */ +/* ------------------------------------------------------------------ */ +table { font-size: 0.9rem; } +table.compact { font-size: 0.82rem; } +table.compact th, +table.compact td { padding: 0.35rem 0.5rem; } -.severity-bar .bar-warning { - background: var(--pico-color-yellow-400); - transition: width 0.5s ease; -} - -.severity-bar-labels { - display: flex; - justify-content: space-between; - font-size: 0.75rem; - margin-top: 2px; - margin-bottom: 2rem; -} - -/* Scan activity heatmap */ +/* ------------------------------------------------------------------ */ +/* Heatmap */ +/* ------------------------------------------------------------------ */ .heatmap { display: flex; align-items: flex-end; gap: 2px; height: 40px; - margin-top: 4px; - margin-bottom: 2rem; + margin: 0.4rem 0 0 0; } .heatmap-day { @@ -139,12 +80,9 @@ nav { border-radius: 2px 2px 0 0; opacity: 0.8; transition: height 0.3s ease, opacity 0.2s; - cursor: default; } -.heatmap-day:hover .bar { - opacity: 1; -} +.heatmap-day:hover .bar { opacity: 1; } .heatmap-day .tooltip { display: none; @@ -162,138 +100,152 @@ nav { margin-bottom: 4px; } -.heatmap-day:hover .tooltip { - display: block; +.heatmap-day:hover .tooltip { display: block; } + +/* ------------------------------------------------------------------ */ +/* Scan info block (detail page) */ +/* ------------------------------------------------------------------ */ +.scan-info-block { + padding: 1rem 1.25rem; + margin-bottom: 1.5rem; } -/* Top rules bar chart */ -.top-rules-chart { - margin-bottom: 2rem; +.scan-info-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); + gap: 0.6rem 1.5rem; } -.top-rules-chart .rule-bar-row { +.sha256 { + font-size: 0.72rem; + word-break: break-all; +} + +.scan-error { + margin-top: 0.75rem; + padding: 0.5rem 0.75rem; + background: rgba(var(--pico-color-red-400), 0.1); + border-left: 3px solid var(--pico-color-red-400); + border-radius: 4px; + font-size: 0.85rem; +} + +/* ------------------------------------------------------------------ */ +/* Finding blocks (replaces accordion details/summary) */ +/* ------------------------------------------------------------------ */ +.finding-block { + margin-bottom: 0.75rem; + border: 1px solid var(--pico-color-gray-600); + border-radius: 6px; + overflow: hidden; +} + +.finding-summary { display: flex; align-items: center; - margin-bottom: 0.4rem; -} - -.top-rules-chart .rule-name { - flex: 0 0 200px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - text-align: right; - padding-right: 0.75rem; - font-size: 0.85rem; -} - -.top-rules-chart .rule-bar-container { - flex: 1; - background: var(--pico-color-gray-500); - border-radius: 4px; - overflow: hidden; - height: 20px; -} - -.top-rules-chart .rule-bar { - height: 100%; - background: var(--pico-color-blue-400); - border-radius: 4px; - transition: width 0.5s ease; - min-width: 2px; -} - -.top-rules-chart .rule-count { - flex: 0 0 50px; - padding-left: 0.5rem; - font-size: 0.85rem; -} - -/* Sticky nav */ -nav.sticky { - position: sticky; - top: 0; - z-index: 100; - background: var(--pico-color-dark); - padding: 0.5rem 0; - border-bottom: 1px solid var(--pico-color-gray-500); -} - -/* Breadcrumbs */ -.breadcrumbs { - margin-bottom: 1rem; - font-size: 0.85rem; - opacity: 0.7; -} - -.breadcrumbs a { - text-decoration: none; -} - -.breadcrumbs .separator { - margin: 0 0.5rem; - opacity: 0.5; -} - -/* Empty states */ -.empty-state { - text-align: center; - padding: 2rem 1rem; - opacity: 0.5; - font-style: italic; -} - -/* Filter bar */ -.filter-bar { - display: flex; - flex-wrap: wrap; gap: 0.5rem; - margin-bottom: 1rem; - align-items: center; + padding: 0.5rem 0.75rem; + background: var(--pico-color-gray-650); + font-size: 0.9rem; + border-bottom: 1px solid var(--pico-color-gray-600); } -.filter-bar input[type="text"], -.filter-bar select { - margin-bottom: 0; +.finding-body { + padding: 0.75rem; } -.filter-bar .filter-btn { - margin-bottom: 0; +.finding-body p { + margin-bottom: 0.5rem; } -/* Sortable columns */ -th.sortable { - cursor: pointer; - user-select: none; -} - -th.sortable:hover { - background: var(--pico-color-gray-600); -} - -th.sortable .sort-icon { - margin-left: 0.25rem; - opacity: 0.3; -} - -th.sortable.active .sort-icon { - opacity: 1; -} - -/* Collapsible findings */ -.finding-details { - margin-top: 0.5rem; -} - -.finding-details pre { +.finding-body pre { background: var(--pico-color-gray-700); padding: 0.5rem; border-radius: 4px; overflow-x: auto; font-size: 0.8rem; + margin-bottom: 0; +} + +.finding-header-row { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 0.75rem; +} + +.finding-header-row h2 { + margin-bottom: 0; +} + +/* ------------------------------------------------------------------ */ +/* LLM report — verdict-based colour scheme */ +/* ------------------------------------------------------------------ */ +.llm-report { + margin-top: 0.75rem; + padding: 0.8rem 1rem; + border-radius: 6px; + font-size: 0.85rem; + line-height: 1.55; + border-left: 4px solid var(--pico-color-gray-500); + background: var(--pico-color-gray-750); +} + +.llm-header { + display: flex; + align-items: center; + gap: 0.5rem; + margin-bottom: 0.5rem; +} + +.llm-badge { + display: inline-block; + padding: 0.1rem 0.5rem; + border-radius: 3px; + font-size: 0.75rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.llm-badge-safe { background: #1a5c2a; color: #4ade80; } +.llm-badge-suspicious { background: #5c4a1a; color: #facc15; } +.llm-badge-malicious { background: #5c1a1a; color: #f87171; } + +.llm-safe { border-left-color: var(--pico-color-green-400); background: rgba(74,222,128,0.06); } +.llm-suspicious { border-left-color: var(--pico-color-yellow-400); background: rgba(250,204,21,0.06); } +.llm-malicious { border-left-color: var(--pico-color-red-400); background: rgba(248,113,113,0.08); } + +.llm-severity { + font-size: 0.72rem; + opacity: 0.6; + text-transform: uppercase; + letter-spacing: 0.04em; +} + +.llm-summary { + font-style: italic; + margin-bottom: 0.4rem; + color: var(--pico-color-zinc-300); +} + +.llm-analysis { + margin-bottom: 0; + white-space: pre-line; +} + +.llm-actions { margin-top: 0.5rem; } +.llm-actions button { font-size: 0.8rem; } + +/* ------------------------------------------------------------------ */ +/* Shared controls */ +/* ------------------------------------------------------------------ */ +.code-toolbar { + display: flex; + justify-content: flex-end; + margin-bottom: 0.25rem; } -/* Copy button */ .copy-btn { cursor: pointer; background: none; @@ -305,16 +257,57 @@ th.sortable.active .sort-icon { transition: background 0.2s; } -.copy-btn:hover { - background: var(--pico-color-gray-600); +.copy-btn:hover { background: var(--pico-color-gray-600); } +.copy-btn.copied { color: var(--pico-color-green-400); border-color: var(--pico-color-green-400); } + +.toggle-all-btn { + font-size: 0.8rem; + cursor: pointer; + background: none; + border: 1px solid var(--pico-color-gray-500); + padding: 0.2rem 0.6rem; + border-radius: 3px; + color: var(--pico-color-gray-300); } -.copy-btn.copied { - color: var(--pico-color-green-400); - border-color: var(--pico-color-green-400); +.toggle-all-btn:hover { background: var(--pico-color-gray-600); } + +.htmx-indicator { display: inline; } + +/* ------------------------------------------------------------------ */ +/* Nav / breadcrumbs / empty state */ +/* ------------------------------------------------------------------ */ +nav { margin-bottom: 1rem; } +nav.sticky { position: sticky; top: 0; z-index: 100; background: var(--pico-color-dark); padding: 0.5rem 0; border-bottom: 1px solid var(--pico-color-gray-500); } + +.breadcrumbs { margin-bottom: 1rem; font-size: 0.85rem; opacity: 0.7; } +.breadcrumbs a { text-decoration: none; } +.breadcrumbs .separator { margin: 0 0.5rem; opacity: 0.5; } + +.empty-state { text-align: center; padding: 2rem 1rem; opacity: 0.5; font-style: italic; } + +/* ------------------------------------------------------------------ */ +/* Filter bar / sortable cols */ +/* ------------------------------------------------------------------ */ +.filter-bar { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + margin-bottom: 1rem; + align-items: center; } -/* Spinner for scanning status */ +.filter-bar input[type="text"], +.filter-bar select { margin-bottom: 0; } + +th.sortable { cursor: pointer; user-select: none; } +th.sortable:hover { background: var(--pico-color-gray-600); } +th.sortable .sort-icon { margin-left: 0.25rem; opacity: 0.3; } +th.sortable.active .sort-icon { opacity: 1; } + +/* ------------------------------------------------------------------ */ +/* Spinner / animations */ +/* ------------------------------------------------------------------ */ .spinner { display: inline-block; width: 12px; @@ -327,156 +320,33 @@ th.sortable.active .sort-icon { vertical-align: middle; } -@keyframes spin { - to { - transform: rotate(360deg); - } -} +@keyframes spin { to { transform: rotate(360deg); } } +@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } } -@keyframes pulse { - 0%, 100% { - opacity: 1; - } - 50% { - opacity: 0.5; - } -} - -/* Finding header row */ -.finding-header-row { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 0.5rem; -} - -/* Finding summary */ -.finding-summary { - cursor: pointer; - list-style: none; - display: flex; - align-items: center; - gap: 0.5rem; - padding: 0.25rem 0; -} - -/* Finding summary hint */ -.finding-summary-hint { - margin-left: auto; - font-size: 0.8rem; - opacity: 0.5; -} - -/* Code block toolbar */ -.code-toolbar { - display: flex; - justify-content: flex-end; - margin-bottom: 0.25rem; -} - -/* LLM report */ -.llm-report { - margin-top: 0.75rem; - padding: 0.6rem 0.8rem; - background: var(--pico-color-gray-700); - border-radius: 6px; - font-size: 0.85rem; - line-height: 1.5; - border-left: 3px solid var(--pico-color-blue-400); -} - -.llm-report strong { - color: var(--pico-color-blue-300); -} - -.verdict-safe { - color: var(--pico-color-green-400); - font-weight: bold; -} - -.verdict-suspicious { - color: var(--pico-color-yellow-400); - font-weight: bold; -} - -.verdict-malicious { - color: var(--pico-color-red-400); - font-weight: bold; -} - -.llm-actions { - margin-top: 0.5rem; -} - -.llm-actions button { - font-size: 0.8rem; -} - -/* htmx indicator */ -.htmx-indicator { - display: inline; -} -.toggle-all-btn { - font-size: 0.8rem; - margin-bottom: 0.5rem; - cursor: pointer; - background: none; - border: 1px solid var(--pico-color-gray-500); - padding: 0.2rem 0.6rem; - border-radius: 3px; - color: var(--pico-color-gray-300); -} - -.toggle-all-btn:hover { - background: var(--pico-color-gray-600); -} - -/* Responsive */ +/* ------------------------------------------------------------------ */ +/* Responsive */ +/* ------------------------------------------------------------------ */ @media (max-width: 768px) { - .stats-grid { - grid-template-columns: repeat(2, 1fr); - } - - .filter-bar { - flex-direction: column; - align-items: stretch; - } - - .top-rules-chart .rule-name { - flex: 0 0 100px; - } - - nav ul { - flex-wrap: wrap; - } - - table { - font-size: 0.8rem; - } - - th, td { - padding: 0.35rem 0.5rem; - } + .dashboard-grid { grid-template-columns: 1fr; } + .scan-info-grid { grid-template-columns: 1fr 1fr; } + .stat-minibar { flex-wrap: wrap; gap: 0.75rem; } + .filter-bar { flex-direction: column; align-items: stretch; } + nav ul { flex-wrap: wrap; } + table, table.compact { font-size: 0.78rem; } + th, td { padding: 0.3rem 0.4rem; } } @media (max-width: 480px) { - .stats-grid { - grid-template-columns: 1fr; - } - - .stat-card { - padding: 0.75rem; - } + .scan-info-grid { grid-template-columns: 1fr; } + .stat-minibar { font-size: 0.8rem; } } -/* Print styles */ +/* ------------------------------------------------------------------ */ +/* Print */ +/* ------------------------------------------------------------------ */ @media print { - nav, .filter-bar, .copy-btn, .toggle-all-btn, nav.sticky { - display: none !important; - } - - body { - background: white; - color: black; - } + nav, .filter-bar, .copy-btn, .toggle-all-btn, nav.sticky, + .llm-actions, .breadcrumbs { display: none !important; } + body { background: white; color: black; } + .llm-report { border: 1px solid #ccc; background: none; } } diff --git a/guarddog_nexus/web/templates/_llm_report_fragment.html b/guarddog_nexus/web/templates/_llm_report_fragment.html index 730bd53..b278d83 100644 --- a/guarddog_nexus/web/templates/_llm_report_fragment.html +++ b/guarddog_nexus/web/templates/_llm_report_fragment.html @@ -1,9 +1,10 @@ -
- LLM Analysis - [{{ report.verdict }}] - {% if report.severity_rating %} - ({{ report.severity_rating }}) - {% endif %} -

{{ report.summary }}

-

{{ report.analysis }}

+
+
+ {{ report.verdict }} + {% if report.severity_rating %} + {{ report.severity_rating }} + {% endif %} +
+

{{ report.summary }}

+

{{ report.analysis }}

diff --git a/guarddog_nexus/web/templates/dashboard_stats.html b/guarddog_nexus/web/templates/dashboard_stats.html index 4088502..5a66dba 100644 --- a/guarddog_nexus/web/templates/dashboard_stats.html +++ b/guarddog_nexus/web/templates/dashboard_stats.html @@ -1,150 +1,70 @@ -
-
-

{{ total_scans }}

- Total Scans -
-
-

{{ flagged_scans }}

- ⚠ Flagged -
-
-

{{ recent_flagged }}

- Flagged (7 days) -
-
-

{{ total_findings }}

- Total Findings -
-
-

{{ errors_count }}

- Errors -
-
-

{{ warnings_count }}

- Warnings -
+
+ {{ total_scans }} scans + {{ flagged_scans }} flagged + {{ total_findings }} findings + {{ errors_count }} errors + {{ warnings_count }} warnings
-{% if total_findings > 0 %} -
- Severity ratio -
- {% set err_pct = (errors_count / total_findings * 100) | int %} - {% set warn_pct = 100 - err_pct %} -
-
-
-
- ERROR {{ errors_count }} - WARNING {{ warnings_count }} -
-
-{% else %} -

No findings yet — scan results will appear here once packages are processed.

-{% endif %} - -{% if days %} -
- Scan activity (14 days) -
- {% set max_cnt = days | map(attribute=1) | max %} - {% for day, cnt, fl in days %} -
- {% set h = (cnt / max_cnt * 38) | int if max_cnt > 0 else 0 %} -
-
{{ day }}: {{ cnt }} scans, {{ fl }} flagged
+
+ {% if days %} +
+

Scan activity (14 days)

+
+ {% set max_cnt = days | map(attribute=1) | max %} + {% for day, cnt, fl in days %} +
+ {% set h = (cnt / max_cnt * 38) | int if max_cnt > 0 else 0 %} +
+
{{ day }}: {{ cnt }} scans, {{ fl }} flagged
+
+ {% endfor %}
- {% endfor %} -
-
-{% endif %} + + {% endif %} -{% if most_flagged %} -
-

⚠ Most Flagged Packages

- + {% if latest_flagged %} +
+

Latest Flagged

+
+ + + {% for s in latest_flagged %} + + + + + + + {% endfor %} + +
PackageVersionFindingsTime
{{ s.package_name }}{{ s.package_version }}{{ s.total_findings }}{{ s.started_at.strftime('%m-%d %H:%M') if s.started_at }}
+ + {% endif %} +
+ +
+

Latest Scans

+ - + - {% for p in most_flagged %} + {% for s in latest_scans %} - - - - - {% endfor %} - -
PackageVersionFindings
PackageVersionRepoStatusTime
{{ p.package_name }}{{ p.package_version }} - {{ p.total }} - -
-
-{% endif %} - -{% if latest_flagged %} -
-

🔴 Latest Flagged

- - - - - - {% for s in latest_flagged %} - - + - + + + {% endfor %}
PackageVersionFindingsTime
{{ s.package_name }}{{ s.package_name }} {{ s.package_version }}{{ s.total_findings }}{{ s.repository }} + {% if s.status == 'scanning' %}scanning{% else %}{{ s.status }}{% endif %} + {% if s.flagged %}⚠ {{ s.total_findings }}{% elif s.status == 'completed' %}{% else %}-{% endif %} {{ s.started_at.strftime('%m-%d %H:%M') if s.started_at }}
-
-{% endif %} + View all scans → + -

Latest Scans

- - - - - - - - - - - - - {% for s in latest_scans %} - - - - - - - - - {% endfor %} - -
PackageVersionRepoStatusTime
{{ s.package_name }}{{ s.package_version }}{{ s.repository }} - {% if s.status == 'scanning' %}scanning{% else %}{{ s.status }}{% endif %} - {% if s.flagged %}⚠ {{ s.total_findings }}{% elif s.status == 'completed' %}{% else %}-{% endif %}{{ s.started_at.strftime('%m-%d %H:%M') if s.started_at }}
-View all scans → - -{% if top_rules %} -
-

Top Rules Triggered

- {% for r in top_rules %} -
- {{ r.rule }} -
-
-
- {{ r.count }} -
- {% endfor %} -
-{% endif %} - -Last refresh: {{ now.strftime('%H:%M:%S') }} (auto every 30s) +Last refresh: {{ now.strftime('%H:%M:%S') }} (auto every 30s) diff --git a/guarddog_nexus/web/templates/package_detail.html b/guarddog_nexus/web/templates/package_detail.html index 08dc270..582eb06 100644 --- a/guarddog_nexus/web/templates/package_detail.html +++ b/guarddog_nexus/web/templates/package_detail.html @@ -12,44 +12,40 @@ {% block content %}

{{ pkg_name }} v{{ pkg_version }}

-

Scans ({{ scans|length }})

- - - - - - {% for s in scans %} - - - - - - - - {% endfor %} - -
IDRepoStatusFindingsTime
#{{ s.id }}{{ s.repository }} - {% if s.status == 'scanning' %}scanning{% else %}{{ s.status }}{% endif %} - {% if s.flagged %}{{ s.total_findings }}{% else %}0{% endif %}{{ s.started_at.strftime('%Y-%m-%d %H:%M') if s.started_at }}
+
+

Scans ({{ scans|length }})

+ + + + + + {% for s in scans %} + + + + + + + + {% endfor %} + +
IDRepoStatusFindingsTime
#{{ s.id }}{{ s.repository }} + {% if s.status == 'scanning' %}scanning{% else %}{{ s.status }}{% endif %} + {% if s.flagged %}{{ s.total_findings }}{% else %}0{% endif %}{{ s.started_at.strftime('%Y-%m-%d %H:%M') if s.started_at }}
+
-
-

Findings ({{ findings|length }})

- {% if findings|length > 1 %} - - {% endif %} -
+

Findings ({{ findings|length }})

{% if findings %}
{% for f in findings %} -
- +
+
[{{ f.data.severity }}] {{ f.data.rule }} {% if f.data.location %} @ {{ f.data.location }}{% endif %} - click to expand -
-
+
+

{{ f.data.message }}

{% if f.data.code %}
@@ -59,12 +55,15 @@ {% endif %} {% if f.report %} -
- LLM Analysis - [{{ f.report.verdict }}] - ({{ f.report.severity_rating }}) -

{{ f.report.summary }}

-

{{ f.report.analysis }}

+
+
+ {{ f.report.verdict }} + {% if f.report.severity_rating %} + {{ f.report.severity_rating }} + {% endif %} +
+

{{ f.report.summary }}

+

{{ f.report.analysis }}

{% else %}
@@ -81,7 +80,7 @@
{% endif %}
-
+
{% endfor %}
{% else %} diff --git a/guarddog_nexus/web/templates/scan_detail.html b/guarddog_nexus/web/templates/scan_detail.html index e50f4ac..0a2ddd4 100644 --- a/guarddog_nexus/web/templates/scan_detail.html +++ b/guarddog_nexus/web/templates/scan_detail.html @@ -12,38 +12,34 @@ {% block content %}

Scan #{{ scan.id }}

- - - - - - - - - - {% if scan.error_message %}{% endif %} -
Package{{ scan.package_name }}
Version{{ scan.package_version }}
Ecosystem{{ scan.ecosystem }}
Repository{{ scan.repository }}
Status - {% if scan.status == 'scanning' %}scanning{% else %}{{ scan.status }}{% endif %} -
SHA256{{ scan.sha256 or '-' }}
Started{{ scan.started_at.isoformat() if scan.started_at }}
Finished{{ scan.finished_at.isoformat() if scan.finished_at }}
Error{{ scan.error_message }}
+
+
+
Package
{{ scan.package_name }}
+
Version
{{ scan.package_version }}
+
Ecosystem
{{ scan.ecosystem }}
+
Repository
{{ scan.repository }}
+
Status
+ {% if scan.status == 'scanning' %}scanning{% else %}{{ scan.status }}{% endif %} +
+
SHA256
{{ scan.sha256 or '-' }}
+
Started
{{ scan.started_at.strftime('%Y-%m-%d %H:%M') if scan.started_at }}
+
Finished
{{ scan.finished_at.strftime('%Y-%m-%d %H:%M') if scan.finished_at }}
+
+ {% if scan.error_message %}
Error: {{ scan.error_message }}
{% endif %} +
-
-

Findings ({{ scan.findings|length }})

- {% if scan.findings|length > 1 %} - - {% endif %} -
+

Findings ({{ scan.findings|length }})

{% if scan.findings %}
{% for f in scan.findings %} -
- +
+
[{{ f.data.severity }}] {{ f.data.rule }} {% if f.data.location %} @ {{ f.data.location }}{% endif %} - click to expand -
-
+
+

{{ f.data.message }}

{% if f.data.code %}
@@ -53,12 +49,15 @@ {% endif %} {% if f.report %} -
- LLM Analysis - [{{ f.report.verdict }}] - ({{ f.report.severity_rating }}) -

{{ f.report.summary }}

-

{{ f.report.analysis }}

+
+
+ {{ f.report.verdict }} + {% if f.report.severity_rating %} + {{ f.report.severity_rating }} + {% endif %} +
+

{{ f.report.summary }}

+

{{ f.report.analysis }}

{% else %}
@@ -75,7 +74,7 @@
{% endif %}
-
+
{% endfor %}
{% else %} diff --git a/tests/test_api.py b/tests/test_api.py index 16cf72f..9d0355b 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -174,7 +174,7 @@ async def test_web_ui_dashboard(client): async def test_web_ui_dashboard_stats_fragment(client): resp = await client.get("/dashboard/stats") assert resp.status_code == 200 - assert "Total Scans" in resp.text + assert "scans" in resp.text.lower() @pytest.mark.asyncio