diff --git a/guarddog_nexus/web/static/style.css b/guarddog_nexus/web/static/style.css new file mode 100644 index 0000000..e0ee52a --- /dev/null +++ b/guarddog_nexus/web/static/style.css @@ -0,0 +1,419 @@ +/* GuardDog Nexus — Web UI styles */ + +/* Status badges */ +.flagged { + color: var(--pico-color-red-400); + font-weight: bold; +} + +.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); +} + +/* 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 { + 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 { + 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; + padding: 1rem; + transition: transform 0.2s, box-shadow 0.2s; +} + +.stat-card:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); +} + +/* Severity ratio bar */ +.severity-bar { + display: flex; + height: 8px; + border-radius: 4px; + overflow: hidden; + margin-top: 4px; + margin-bottom: 2rem; +} + +.severity-bar .bar-error { + background: var(--pico-color-red-400); + transition: width 0.5s ease; +} + +.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 { + display: flex; + align-items: flex-end; + gap: 2px; + height: 40px; + margin-top: 4px; + margin-bottom: 2rem; +} + +.heatmap-day { + flex: 1; + display: flex; + flex-direction: column; + justify-content: flex-end; + position: relative; +} + +.heatmap-day .bar { + 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 .tooltip { + display: none; + position: absolute; + bottom: 100%; + left: 50%; + transform: translateX(-50%); + background: var(--pico-color-gray-700); + color: var(--pico-color-white); + padding: 0.25rem 0.5rem; + border-radius: 4px; + font-size: 0.7rem; + white-space: nowrap; + z-index: 10; + margin-bottom: 4px; +} + +.heatmap-day:hover .tooltip { + display: block; +} + +/* Top rules bar chart */ +.top-rules-chart { + margin-bottom: 2rem; +} + +.top-rules-chart .rule-bar-row { + 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: 3rem 1rem; + opacity: 0.6; +} + +.empty-state svg { + width: 64px; + height: 64px; + margin-bottom: 1rem; + opacity: 0.4; +} + +.empty-state h3 { + margin-bottom: 0.5rem; +} + +/* Filter bar */ +.filter-bar { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + margin-bottom: 1rem; + align-items: center; +} + +.filter-bar input[type="text"], +.filter-bar select { + margin-bottom: 0; +} + +.filter-bar .filter-btn { + margin-bottom: 0; +} + +/* 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 { + background: var(--pico-color-gray-700); + padding: 0.5rem; + border-radius: 4px; + overflow-x: auto; + font-size: 0.8rem; +} + +/* Copy button */ +.copy-btn { + cursor: pointer; + background: none; + border: 1px solid var(--pico-color-gray-500); + padding: 0.15rem 0.5rem; + font-size: 0.7rem; + border-radius: 3px; + color: var(--pico-color-gray-300); + transition: background 0.2s; +} + +.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); +} + +/* Spinner for scanning status */ +.spinner { + display: inline-block; + width: 12px; + height: 12px; + border: 2px solid var(--pico-color-blue-400); + border-top-color: transparent; + border-radius: 50%; + animation: spin 0.8s linear infinite; + margin-right: 0.25rem; + vertical-align: middle; +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +@keyframes pulse { + 0%, 100% { + opacity: 1; + } + 50% { + opacity: 0.5; + } +} + +/* Expand/Collapse all button */ +.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 */ +@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; + } +} + +@media (max-width: 480px) { + .stats-grid { + grid-template-columns: 1fr; + } + + .stat-card { + padding: 0.75rem; + } +} + +/* Print styles */ +@media print { + nav, .filter-bar, .copy-btn, .toggle-all-btn, nav.sticky { + display: none !important; + } + + body { + background: white; + color: black; + } +} diff --git a/guarddog_nexus/web/templates/base.html b/guarddog_nexus/web/templates/base.html index c6526ff..9c3dd09 100644 --- a/guarddog_nexus/web/templates/base.html +++ b/guarddog_nexus/web/templates/base.html @@ -3,31 +3,14 @@
-