diff --git a/cmd/satoru/progress_ws.go b/cmd/satoru/progress_ws.go index 5ddfc13..42b1f8c 100644 --- a/cmd/satoru/progress_ws.go +++ b/cmd/satoru/progress_ws.go @@ -26,6 +26,7 @@ type liveSiteStatus struct { SiteID int64 `json:"site_id"` LastRunStatus string `json:"last_run_status"` LastRunOutput string `json:"last_run_output"` + LastScanState string `json:"last_scan_state"` LastRunAt time.Time `json:"last_run_at,omitempty"` } @@ -121,10 +122,14 @@ func (a *app) buildLiveProgressPayload(ctx context.Context) (liveProgressPayload SiteID: site.ID, LastRunStatus: strings.TrimSpace(site.LastRunStatus.String), LastRunOutput: strings.TrimSpace(site.LastRunOutput.String), + LastScanState: strings.TrimSpace(site.LastScanState.String), } if row.LastRunStatus == "" { row.LastRunStatus = "pending" } + if row.LastScanState == "" { + row.LastScanState = "pending" + } if site.LastRunAt.Valid { row.LastRunAt = site.LastRunAt.Time } diff --git a/internal/webui/dashboard.go b/internal/webui/dashboard.go index 3039bd5..6d9b10b 100644 --- a/internal/webui/dashboard.go +++ b/internal/webui/dashboard.go @@ -62,8 +62,10 @@ func Dashboard(data DashboardData) templ.Component { } var sites strings.Builder + var siteNav strings.Builder if len(data.Sites) == 0 { sites.WriteString(`

No sites added yet. Add your first Linux SSH site below.

`) + siteNav.WriteString(`

No sites yet.

`) } for _, site := range data.Sites { last := "Never run" @@ -90,6 +92,17 @@ func Dashboard(data DashboardData) templ.Component { if site.LastScanNotes.Valid && site.LastScanNotes.String != "" { scanNotes = site.LastScanNotes.String } + siteNav.WriteString(fmt.Sprintf(``, + site.ID, + html.EscapeString(site.SSHUser), + html.EscapeString(site.Host), + html.EscapeString(runStatus), + site.ID, + html.EscapeString(runStatus), + html.EscapeString(scanState), + site.ID, + html.EscapeString(scanState), + )) var targets strings.Builder if len(site.Targets) == 0 { @@ -238,55 +251,81 @@ func Dashboard(data DashboardData) templ.Component { %s -
-

Host Runtime Status

- -
-
-

Planned Workflow

-
    %s
-
-
-

Add Site

-
- - - - - - - -
-
-
-

Live Backup Progress

-

Connecting...

- -
- -
-
No live events yet.
-
-
-

Managed Sites

- %s -
+
+ +
+
+

Add Site

+
+ + + + + + + +
+
+
+

Active Site

+ %s +
+
+

Active Site Logs

+
    +
  • Waiting for job updates.
  • +
+
+ +
+
No live events yet.
+
+
+

Planned Workflow

+
    %s
+
+
+
@@ -449,9 +515,10 @@ func Dashboard(data DashboardData) templ.Component { html.EscapeString(data.User.Username), html.EscapeString(role), flash, + siteNav.String(), checks.String(), - flows.String(), sites.String(), + flows.String(), )) return err }) diff --git a/web/static/app.css b/web/static/app.css index 5b78ff4..10f83a0 100644 --- a/web/static/app.css +++ b/web/static/app.css @@ -14,8 +14,7 @@ body { margin: 0; min-height: 100vh; - display: grid; - place-items: center; + display: block; background: radial-gradient(circle at top, #1e293b 0, var(--bg) 55%); color: var(--fg); font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, sans-serif; @@ -30,7 +29,51 @@ body { } .dashboard-card { - width: min(980px, calc(100vw - 2rem)); + width: calc(100vw - 2rem); + max-width: none; + margin: 1rem; +} + +.dashboard-shell { + margin-top: 1.2rem; + display: grid; + grid-template-columns: 320px minmax(0, 1fr); + gap: 1rem; +} + +.site-sidebar { + border: 1px solid var(--border); + border-radius: 12px; + padding: 0.8rem; + height: fit-content; + position: sticky; + top: 1rem; +} + +.site-main-panel { + min-width: 0; +} + +.site-nav-list { + display: grid; + gap: 0.5rem; + margin: 0.75rem 0 1rem; +} + +.site-nav-item { + border: 1px solid var(--border); + border-radius: 10px; + background: color-mix(in srgb, var(--card) 75%, black); + color: var(--fg); + text-align: left; + padding: 0.55rem 0.65rem; + display: grid; + gap: 0.35rem; + cursor: pointer; +} + +.site-nav-item.active { + border-color: #38bdf8; } .eyebrow { @@ -142,6 +185,24 @@ h2 { gap: 0.75rem; } +.site-sidebar .status-list { + grid-template-columns: 1fr; + gap: 0.4rem; +} + +.site-sidebar .status { + padding: 0.4rem 0.5rem; + gap: 0.2rem; +} + +.site-sidebar .status strong { + font-size: 0.82rem; +} + +.site-sidebar .status span { + font-size: 0.74rem; +} + .status { border: 1px solid var(--border); border-radius: 12px; @@ -328,6 +389,16 @@ textarea { to { transform: rotate(360deg); } } +@media (max-width: 980px) { + .dashboard-shell { + grid-template-columns: 1fr; + } + + .site-sidebar { + position: static; + } +} + a { color: #7dd3fc; }