diff --git a/.cursor/debug.log b/.cursor/debug.log index 74ab587..97e219e 100644 --- a/.cursor/debug.log +++ b/.cursor/debug.log @@ -397,3 +397,42 @@ {"data":{"method":"GET","status":200,"url":"https://athena-db.com"},"hypothesisId":"E","location":"http_probe.rs:call_endpoint","message":"response received","runId":"pre-fix","sessionId":"debug-session","timestamp":1772171281746} {"data":{"method":"GET","status":200,"url":"https://mirror2.athena-db.com"},"hypothesisId":"E","location":"http_probe.rs:call_endpoint","message":"response received","runId":"pre-fix","sessionId":"debug-session","timestamp":1772171341737} {"data":{"method":"GET","status":200,"url":"https://athena-db.com"},"hypothesisId":"E","location":"http_probe.rs:call_endpoint","message":"response received","runId":"pre-fix","sessionId":"debug-session","timestamp":1772171341739} +{"data":{"value":null},"hypothesisId":"A","location":"metrics.rs:initialize","message":"env OTEL_METRICS_EXPORTER","runId":"pre-fix","sessionId":"debug-session","timestamp":1777342896251} +{"data":{},"hypothesisId":"B","location":"metrics.rs:initialize","message":"no exporter configured","runId":"pre-fix","sessionId":"debug-session","timestamp":1777342896256} +{"data":{"has_traces":true},"hypothesisId":"D","location":"tracing.rs:create_tracer","message":"tracer initialized","runId":"pre-fix","sessionId":"debug-session","timestamp":1777342896268} +{"data":{},"hypothesisId":"C","location":"metrics.rs:new","message":"meter created","runId":"pre-fix","sessionId":"debug-session","timestamp":1777342896301} +{"data":{"value":null},"hypothesisId":"A","location":"metrics.rs:initialize","message":"env OTEL_METRICS_EXPORTER","runId":"pre-fix","sessionId":"debug-session","timestamp":1777343044064} +{"data":{},"hypothesisId":"B","location":"metrics.rs:initialize","message":"no exporter configured","runId":"pre-fix","sessionId":"debug-session","timestamp":1777343044065} +{"data":{"has_traces":true},"hypothesisId":"D","location":"tracing.rs:create_tracer","message":"tracer initialized","runId":"pre-fix","sessionId":"debug-session","timestamp":1777343044065} +{"data":{},"hypothesisId":"C","location":"metrics.rs:new","message":"meter created","runId":"pre-fix","sessionId":"debug-session","timestamp":1777343044068} +{"data":{"value":null},"hypothesisId":"A","location":"metrics.rs:initialize","message":"env OTEL_METRICS_EXPORTER","runId":"pre-fix","sessionId":"debug-session","timestamp":1777368522702} +{"data":{},"hypothesisId":"B","location":"metrics.rs:initialize","message":"no exporter configured","runId":"pre-fix","sessionId":"debug-session","timestamp":1777368522708} +{"data":{"has_traces":true},"hypothesisId":"D","location":"tracing.rs:create_tracer","message":"tracer initialized","runId":"pre-fix","sessionId":"debug-session","timestamp":1777368522721} +{"data":{},"hypothesisId":"C","location":"metrics.rs:new","message":"meter created","runId":"pre-fix","sessionId":"debug-session","timestamp":1777368522756} +{"data":{"value":null},"hypothesisId":"A","location":"metrics.rs:initialize","message":"env OTEL_METRICS_EXPORTER","runId":"pre-fix","sessionId":"debug-session","timestamp":1777369026430} +{"data":{},"hypothesisId":"B","location":"metrics.rs:initialize","message":"no exporter configured","runId":"pre-fix","sessionId":"debug-session","timestamp":1777369026430} +{"data":{"has_traces":true},"hypothesisId":"D","location":"tracing.rs:create_tracer","message":"tracer initialized","runId":"pre-fix","sessionId":"debug-session","timestamp":1777369026430} +{"data":{},"hypothesisId":"C","location":"metrics.rs:new","message":"meter created","runId":"pre-fix","sessionId":"debug-session","timestamp":1777369026434} +{"data":{"method":"GET","status":200,"url":"https://athena-cluster.com"},"hypothesisId":"E","location":"http_probe.rs:call_endpoint","message":"response received","runId":"pre-fix","sessionId":"debug-session","timestamp":1777369026567} +{"data":{"method":"GET","status":200,"url":"https://athena-cluster.com"},"hypothesisId":"E","location":"http_probe.rs:call_endpoint","message":"response received","runId":"pre-fix","sessionId":"debug-session","timestamp":1777369086546} +{"data":{"method":"GET","status":200,"url":"https://athena-cluster.com"},"hypothesisId":"E","location":"http_probe.rs:call_endpoint","message":"response received","runId":"pre-fix","sessionId":"debug-session","timestamp":1777369146544} +{"data":{"value":null},"hypothesisId":"A","location":"metrics.rs:initialize","message":"env OTEL_METRICS_EXPORTER","runId":"pre-fix","sessionId":"debug-session","timestamp":1777369186162} +{"data":{},"hypothesisId":"B","location":"metrics.rs:initialize","message":"no exporter configured","runId":"pre-fix","sessionId":"debug-session","timestamp":1777369186162} +{"data":{"has_traces":true},"hypothesisId":"D","location":"tracing.rs:create_tracer","message":"tracer initialized","runId":"pre-fix","sessionId":"debug-session","timestamp":1777369186163} +{"data":{},"hypothesisId":"C","location":"metrics.rs:new","message":"meter created","runId":"pre-fix","sessionId":"debug-session","timestamp":1777369186166} +{"data":{"method":"GET","status":200,"url":"https://mirror3.athena-db.com"},"hypothesisId":"E","location":"http_probe.rs:call_endpoint","message":"response received","runId":"pre-fix","sessionId":"debug-session","timestamp":1777369186234} +{"data":{"method":"GET","status":200,"url":"https://athena-cluster.com"},"hypothesisId":"E","location":"http_probe.rs:call_endpoint","message":"response received","runId":"pre-fix","sessionId":"debug-session","timestamp":1777369186270} +{"data":{"method":"GET","status":200,"url":"https://mirror3.athena-db.com"},"hypothesisId":"E","location":"http_probe.rs:call_endpoint","message":"response received","runId":"pre-fix","sessionId":"debug-session","timestamp":1777369188390} +{"data":{"method":"GET","status":200,"url":"https://mirror3.athena-db.com"},"hypothesisId":"E","location":"http_probe.rs:call_endpoint","message":"response received","runId":"pre-fix","sessionId":"debug-session","timestamp":1777369239608} +{"data":{"method":"GET","status":200,"url":"https://mirror3.athena-db.com"},"hypothesisId":"E","location":"http_probe.rs:call_endpoint","message":"response received","runId":"pre-fix","sessionId":"debug-session","timestamp":1777369246221} +{"data":{"method":"GET","status":200,"url":"https://athena-cluster.com"},"hypothesisId":"E","location":"http_probe.rs:call_endpoint","message":"response received","runId":"pre-fix","sessionId":"debug-session","timestamp":1777369246272} +{"data":{"method":"GET","status":200,"url":"https://mirror3.athena-db.com"},"hypothesisId":"E","location":"http_probe.rs:call_endpoint","message":"response received","runId":"pre-fix","sessionId":"debug-session","timestamp":1777369246683} +{"data":{"method":"GET","status":200,"url":"https://athena-cluster.com"},"hypothesisId":"E","location":"http_probe.rs:call_endpoint","message":"response received","runId":"pre-fix","sessionId":"debug-session","timestamp":1777369261460} +{"data":{"method":"GET","status":200,"url":"https://mirror3.athena-db.com"},"hypothesisId":"E","location":"http_probe.rs:call_endpoint","message":"response received","runId":"pre-fix","sessionId":"debug-session","timestamp":1777369306222} +{"data":{"method":"GET","status":200,"url":"https://athena-cluster.com"},"hypothesisId":"E","location":"http_probe.rs:call_endpoint","message":"response received","runId":"pre-fix","sessionId":"debug-session","timestamp":1777369306272} +{"data":{"method":"GET","status":200,"url":"https://mirror3.athena-db.com"},"hypothesisId":"E","location":"http_probe.rs:call_endpoint","message":"response received","runId":"pre-fix","sessionId":"debug-session","timestamp":1777369366233} +{"data":{"method":"GET","status":200,"url":"https://athena-cluster.com"},"hypothesisId":"E","location":"http_probe.rs:call_endpoint","message":"response received","runId":"pre-fix","sessionId":"debug-session","timestamp":1777369366300} +{"data":{"method":"GET","status":200,"url":"https://mirror3.athena-db.com"},"hypothesisId":"E","location":"http_probe.rs:call_endpoint","message":"response received","runId":"pre-fix","sessionId":"debug-session","timestamp":1777369426224} +{"data":{"method":"GET","status":200,"url":"https://athena-cluster.com"},"hypothesisId":"E","location":"http_probe.rs:call_endpoint","message":"response received","runId":"pre-fix","sessionId":"debug-session","timestamp":1777369426270} +{"data":{"method":"GET","status":200,"url":"https://mirror3.athena-db.com"},"hypothesisId":"E","location":"http_probe.rs:call_endpoint","message":"response received","runId":"pre-fix","sessionId":"debug-session","timestamp":1777369486226} +{"data":{"method":"GET","status":200,"url":"https://athena-cluster.com"},"hypothesisId":"E","location":"http_probe.rs:call_endpoint","message":"response received","runId":"pre-fix","sessionId":"debug-session","timestamp":1777369486267} diff --git a/README.md b/README.md index f307e14..644db5a 100644 --- a/README.md +++ b/README.md @@ -153,6 +153,13 @@ See `.env.example.github` for detailed documentation of all GitHub workflow envi - Pressing the restart button sends a POST to `/api/restart` and executes the shell command in `XBP_RESTART_CMD`. - **Building the dashboard**: before running `cargo run`, go to the `dashboard/` directory, install dependencies (`npm install`), and run `npm run build`. That produces the static files in `dashboard/out/`, which the Rust server serves at `/dashboard`. +### Dashboard local development (Next + Rust) + +- Rust backend serves API endpoints on `http://127.0.0.1:3000`. +- Dashboard dev server runs on `http://127.0.0.1:3001/dashboard`. +- Next rewrites proxy `/api/*`, `/probes/*`, and `/stories/*` from port `3001` to Rust on port `3000`. +- Start backend first, then run dashboard dev in another terminal to avoid API 404 HTML responses. + ## Config and YAML - Deserialize config with `serde_yaml`; top-level shape is `Config { probes, stories }`. diff --git a/dashboard/app/config/page.tsx b/dashboard/app/config/page.tsx new file mode 100644 index 0000000..eb9d893 --- /dev/null +++ b/dashboard/app/config/page.tsx @@ -0,0 +1,164 @@ +"use client"; + +import { useCallback, useEffect, useState } from "react"; +import { Button, Input, Label, TextField, Spinner } from "@heroui/react"; +import { Icon } from "@iconify/react"; +import { useApiUrl } from "@/lib/useApiUrl"; +import { useToast } from "@/components/ToastProvider"; +import { restartServer } from "@/lib/api"; + +export default function ConfigPage() { + const { toast } = useToast(); + const { url: apiUrl, setUrl: setApiUrl, mounted } = useApiUrl(); + const [yaml, setYaml] = useState(""); + const [loading, setLoading] = useState(true); + const [saving, setSaving] = useState(false); + const [restarting, setRestarting] = useState(false); + const [tempApiUrl, setTempApiUrl] = useState(""); + + const loadConfig = useCallback(async () => { + try { + const res = await fetch("/api/config"); + if (!res.ok) throw new Error(await res.text()); + setYaml(await res.text()); + } catch (err) { + toast(String(err), { variant: "danger" }); + } finally { + setLoading(false); + } + }, [toast]); + + useEffect(() => { + loadConfig(); + }, [loadConfig]); + + useEffect(() => { + if (mounted) setTempApiUrl(apiUrl); + }, [apiUrl, mounted]); + + async function handleSave() { + setSaving(true); + try { + const res = await fetch("/api/config", { + method: "PUT", + headers: { "Content-Type": "text/yaml" }, + body: yaml, + }); + if (!res.ok) throw new Error(await res.text()); + toast("Config saved. Restart to apply changes.", { variant: "success" }); + } catch (err) { + toast(String(err), { variant: "danger" }); + } finally { + setSaving(false); + } + } + + async function handleRestart() { + if (!confirm("Restart the monitoring service?")) return; + setRestarting(true); + try { + await restartServer(); + toast("Restart signal sent.", { variant: "success" }); + } catch (err) { + toast(String(err), { variant: "danger" }); + } finally { + setRestarting(false); + } + } + + function handleSaveApiUrl() { + setApiUrl(tempApiUrl); + toast("API base URL saved.", { variant: "success" }); + } + + function resetApiUrl() { + setTempApiUrl(apiUrl); + toast("Changes discarded.", { variant: "warning" }); + } + + return ( +
+
+
+

Config

+

+ Raw YAML configuration — edits here require a restart +

+
+
+ + + +
+
+ + {mounted && ( +
+
+

API Settings

+

+ Configure where the dashboard sends API requests. Leave empty to use the same origin (production). +

+
+
+ + + setTempApiUrl(e.target.value)} + /> +
+ e.g., http://127.0.0.1:3000 for local dev, empty for production +
+
+
+ + +
+
+
+ )} + + {loading ? ( +
+ +
+ ) : ( +
+
+ + xbp.yml +
+