Add the Analytics section: check runs, analytics API and page
Runs (migration 0011): a run groups the cycles of one launch. It opens when an address enters an idle queue, takes everything submitted or re-checked while it is open and is finalized when all its addresses are done; a re-check after that opens a new run, so results of different runs never mix. check_runs, run_results (one result per address and run, with the verdict and the expected and stored check counts), subnets, run_id on ip_queue and checks. Existing data is split into runs at pauses of more than an hour; ingress checks get the validator that held the address (also at write time from now on). Analytics (internal/analytics): figures computed from the stored checks of the latest cycle of each address in the run, as facts next to the verdict: summary, reasons of partial, data quality, subnets, targets and the subnet x target matrix by check type, ingress by site, error classes, validators, and the address lists behind the indicators and error classes. API: analytics runs, report, lists (JSON or CSV), subnet list; run and subnet filters for the registry. Dashboard: /analytics matching the approved mockup (run selector, indicators with address lists and CSV, error-class dialogs, drill-down to the registry), subnet list on /settings. Sidebar: the control-api link state, theme toggle and logout moved to the top, the three dots next to the logo removed, sections grouped. Rebuilt bin/control-api and bin/admin-dashboard to match. Plan, summary and the updated README, API, USAGE, DASHBOARD and ADMIN_CLEANUP docs are in docs/. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
864208238f
commit
b7669c9e41
44 files changed
+4123
-62
No files matched your search
@@ -180,11 +180,9 @@ main > h2.section-title:first-child { margin-top: 0; }
|
||||
padding: 18px 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
gap: 14px;
|
||||
}
|
||||
.brand { display: flex; align-items: center; gap: 9px; padding: 0 6px 12px; border-bottom: 1px solid var(--border-soft); }
|
||||
.brand-chrome { display: flex; gap: 4px; }
|
||||
.brand-chrome i { width: 6px; height: 6px; border-radius: 50%; background: var(--border); }
|
||||
.brand-mark {
|
||||
width: 26px; height: 26px;
|
||||
border-radius: var(--radius-xs);
|
||||
@@ -218,8 +216,10 @@ nav.nav-groups { display: flex; flex-direction: column; gap: 1px; }
|
||||
.nav-link.active svg { opacity: 1; }
|
||||
.nav-link:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
|
||||
|
||||
.sidebar-foot {
|
||||
margin-top: auto;
|
||||
/* Session block at the top of the sidebar: link state to control-api, theme
|
||||
toggle and, with login enabled, who is signed in and the logout button. */
|
||||
.sidebar-session {
|
||||
display: grid; gap: 8px;
|
||||
padding: 9px 10px;
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--surface-alt);
|
||||
@@ -227,9 +227,16 @@ nav.nav-groups { display: flex; flex-direction: column; gap: 1px; }
|
||||
font-family: var(--font-display);
|
||||
font-size: 11px;
|
||||
color: var(--text-muted);
|
||||
display: flex; align-items: center; justify-content: space-between; gap: 7px;
|
||||
}
|
||||
.sidebar-foot-status { display: flex; align-items: center; gap: 7px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.session-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; min-width: 0; margin: 0; }
|
||||
.session-api { display: flex; align-items: center; gap: 7px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.session-user { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.nav-group-title {
|
||||
font-family: var(--font-display); font-weight: 700; font-size: 10px;
|
||||
letter-spacing: .08em; text-transform: uppercase; color: var(--text-faint);
|
||||
padding: 12px 9px 4px;
|
||||
}
|
||||
nav.nav-groups > .nav-group-title:first-child { padding-top: 0; }
|
||||
.pulse-dot {
|
||||
width: 6px; height: 6px; border-radius: 1px;
|
||||
background: var(--success);
|
||||
@@ -238,6 +245,7 @@ nav.nav-groups { display: flex; flex-direction: column; gap: 1px; }
|
||||
animation: pulse 2.2s ease-in-out infinite;
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) { .pulse-dot { animation: none; } }
|
||||
.pulse-dot.down { background: var(--danger); box-shadow: 0 0 0 3px var(--danger-soft); animation: none; }
|
||||
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: .35; } }
|
||||
|
||||
.theme-toggle {
|
||||
@@ -568,13 +576,6 @@ textarea.autosize { overflow-y: hidden; resize: none; }
|
||||
.login-card .panel-body { display: flex; flex-direction: column; gap: 14px; }
|
||||
/* .field is flex: 1 1 220px (for rows); in this column the basis would become a 220px height. */
|
||||
.login-card .field { flex: 0 0 auto; }
|
||||
.sidebar-user {
|
||||
margin-top: auto; margin-bottom: 8px;
|
||||
display: flex; align-items: center; justify-content: space-between; gap: 8px;
|
||||
font-family: var(--font-display); font-size: 11px; color: var(--text-muted);
|
||||
}
|
||||
.sidebar-user-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.sidebar-user + .sidebar-foot { margin-top: 0; }
|
||||
|
||||
/* ---------- scan progress, progress bars, pager, bulk selection ---------- */
|
||||
progress {
|
||||
|
||||
Reference in new issue
Block a user