- FastAPI + htmx + Jinja2 web frontend, started with --web flag - JWT HS256 auth (WEB_SECRET_KEY) with httpOnly cookies; access (15 min) + refresh (7 day) tokens; refresh rotation + JTI revocation in data/web.db - RBAC: superadmin > admin > reader enforced per route - Live SSE dashboard fed by tui/events broadcast queue - Config editor: keyword groups and channel list saved to data/runtime_config.json and hot-reloaded in-process (scorer.reload_from_config, signal_channel_changed) - config.py migrated to load groups/channels from runtime_config.json; falls back to hardcoded defaults when file absent - tui/events.py: subscribe/unsubscribe broadcast, set_bot_context/signal_channel_changed - utils/scorer.py: import config as _config (fixes local binding); reload_from_config() - utils/database.py: count_by_severity, recent_for_domains, count_by_severity_for_domains - 53 new tests (events bus, JWT lifecycle, web DB CRUD, RBAC enforcement, config round-trip); total 141 passing
163 lines
5.2 KiB
CSS
163 lines
5.2 KiB
CSS
/* ULPgrammer web UI — minimal, dark-ish */
|
|
|
|
:root {
|
|
--bg: #1a1a1a;
|
|
--surface: #252525;
|
|
--border: #3a3a3a;
|
|
--text: #e0e0e0;
|
|
--muted: #888;
|
|
--critical:#ff4444;
|
|
--high: #ff8800;
|
|
--medium: #ffcc00;
|
|
--low: #44bb44;
|
|
--accent: #4a90d9;
|
|
}
|
|
|
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
|
|
body {
|
|
background: var(--bg);
|
|
color: var(--text);
|
|
font-family: 'Segoe UI', system-ui, sans-serif;
|
|
font-size: 14px;
|
|
line-height: 1.5;
|
|
}
|
|
|
|
a { color: var(--accent); text-decoration: none; }
|
|
a:hover { text-decoration: underline; }
|
|
|
|
/* Nav */
|
|
nav {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
background: var(--surface);
|
|
border-bottom: 1px solid var(--border);
|
|
padding: 0.6rem 1.2rem;
|
|
}
|
|
.nav-brand { font-weight: 700; font-size: 1rem; color: var(--text); }
|
|
.nav-links { display: flex; gap: 1rem; align-items: center; }
|
|
|
|
main { padding: 1.2rem 1.4rem; max-width: 1200px; }
|
|
|
|
h2 { margin: 0.8rem 0 0.6rem; }
|
|
h3 { margin: 0.6rem 0 0.4rem; }
|
|
|
|
/* Stats bar */
|
|
.stats-bar {
|
|
display: flex;
|
|
gap: 1rem;
|
|
margin: 0.8rem 0;
|
|
padding: 0.5rem 0.8rem;
|
|
background: var(--surface);
|
|
border-radius: 6px;
|
|
border: 1px solid var(--border);
|
|
}
|
|
.stat { font-weight: 600; }
|
|
.stat.critical { color: var(--critical); }
|
|
.stat.high { color: var(--high); }
|
|
.stat.medium { color: var(--medium); }
|
|
.stat.low { color: var(--low); }
|
|
|
|
/* Groups bar */
|
|
.groups-bar { margin: 0.5rem 0 1rem; }
|
|
.group-pill {
|
|
display: inline-block;
|
|
margin: 0 0.3rem 0.3rem 0;
|
|
padding: 0.2rem 0.6rem;
|
|
background: var(--surface);
|
|
border: 1px solid var(--border);
|
|
border-radius: 12px;
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
/* Hit cards */
|
|
.hit-card {
|
|
padding: 0.5rem 0.8rem;
|
|
margin: 0.4rem 0;
|
|
border-left: 4px solid var(--border);
|
|
background: var(--surface);
|
|
border-radius: 0 4px 4px 0;
|
|
}
|
|
.hit-card.sev-critical { border-color: var(--critical); }
|
|
.hit-card.sev-high { border-color: var(--high); }
|
|
.hit-card.sev-medium { border-color: var(--medium); }
|
|
.hit-card.sev-low { border-color: var(--low); }
|
|
|
|
.sev-badge {
|
|
font-size: 0.75rem;
|
|
font-weight: 700;
|
|
text-transform: uppercase;
|
|
padding: 0.1rem 0.4rem;
|
|
border-radius: 3px;
|
|
margin-right: 0.5rem;
|
|
background: var(--border);
|
|
}
|
|
.sev-critical .sev-badge { background: var(--critical); color: #fff; }
|
|
.sev-high .sev-badge { background: var(--high); color: #000; }
|
|
.sev-medium .sev-badge { background: var(--medium); color: #000; }
|
|
.sev-low .sev-badge { background: var(--low); color: #000; }
|
|
|
|
code.raw { font-family: monospace; font-size: 0.9rem; word-break: break-all; }
|
|
.meta { color: var(--muted); font-size: 0.8rem; margin-left: 0.5rem; }
|
|
.reasons { margin: 0.2rem 0 0 1.5rem; color: var(--muted); font-size: 0.8rem; }
|
|
|
|
/* Tables */
|
|
.data-table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
margin-top: 0.8rem;
|
|
}
|
|
.data-table th, .data-table td {
|
|
padding: 0.4rem 0.6rem;
|
|
border: 1px solid var(--border);
|
|
text-align: left;
|
|
}
|
|
.data-table th { background: var(--surface); }
|
|
|
|
/* Forms */
|
|
label { display: block; margin: 0.4rem 0; }
|
|
label input, label select { display: block; width: 100%; margin-top: 0.2rem; }
|
|
input, select, textarea {
|
|
background: var(--surface);
|
|
border: 1px solid var(--border);
|
|
color: var(--text);
|
|
padding: 0.3rem 0.5rem;
|
|
border-radius: 4px;
|
|
}
|
|
input:focus, select:focus { outline: 1px solid var(--accent); }
|
|
|
|
button { cursor: pointer; padding: 0.3rem 0.7rem; border-radius: 4px; border: 1px solid var(--border); background: var(--surface); color: var(--text); }
|
|
.btn-primary { background: var(--accent); color: #fff; border-color: var(--accent); }
|
|
.btn-danger { background: var(--critical); color: #fff; border-color: var(--critical); }
|
|
.btn-add { margin-top: 0.3rem; }
|
|
.btn-link { background: none; border: none; color: var(--accent); padding: 0; }
|
|
|
|
/* Tabs */
|
|
.tabs { display: flex; gap: 0.5rem; margin: 0.6rem 0; }
|
|
.tab { padding: 0.3rem 0.8rem; border-radius: 4px; background: var(--surface); border: 1px solid var(--border); }
|
|
.tab.active { background: var(--accent); color: #fff; border-color: var(--accent); }
|
|
|
|
/* Config fieldsets */
|
|
.group-fieldset { border: 1px solid var(--border); padding: 0.7rem; margin: 0.6rem 0; border-radius: 4px; }
|
|
.group-fieldset legend input { background: transparent; border: none; font-size: 1rem; font-weight: 600; color: var(--text); }
|
|
.patterns-table { width: 100%; border-collapse: collapse; }
|
|
.patterns-table td { padding: 0.2rem 0.4rem; }
|
|
.patterns-table input { width: 100%; }
|
|
|
|
/* Login */
|
|
.login-body { display: flex; align-items: center; justify-content: center; min-height: 100vh; }
|
|
.login-box { width: 320px; padding: 2rem; background: var(--surface); border-radius: 8px; border: 1px solid var(--border); }
|
|
.login-box h1 { margin-bottom: 1rem; text-align: center; }
|
|
.login-box button { width: 100%; margin-top: 0.8rem; }
|
|
|
|
/* Misc */
|
|
.error { color: var(--critical); margin: 0.4rem 0; }
|
|
.hint { color: var(--muted); font-size: 0.85rem; margin: 0.3rem 0; }
|
|
.empty { color: var(--muted); font-style: italic; }
|
|
dialog { background: var(--surface); color: var(--text); border: 1px solid var(--border); border-radius: 8px; padding: 1.5rem; min-width: 340px; }
|
|
dialog::backdrop { background: rgba(0,0,0,0.6); }
|
|
.dialog-actions { display: flex; gap: 0.5rem; margin-top: 0.8rem; }
|
|
.patterns-list { margin: 0.5rem 0; }
|
|
details summary { cursor: pointer; }
|