authorgravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-10-04 21:28:12-07:00
committergravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-10-04 21:28:29-07:00
logdf1c802f8e4c1f91746ce7761590af6849d12d41
tree01ea44df004e73b1ee8e773b754dea84d3e58f8f
parentd16b9185f320c372908c7c4bdc6fba091e403b19
signature Signed by SSH key SHA256:52mNGHRsVFBDED9IAX5pe+LRWUefqTbxEReunq21QvU

Preserve metrics and logs shortcuts for grouped Victoria

Expose the grouped app routes from the canonical service definitions so its metrics shortcut still reaches VictoriaMetrics and deployment logs still reach VictoriaLogs. Assisted-by: gpt-6.1-sol

4 files changed, 9 insertions(+), 7 deletions(-)

dashboard/src/core.rs+5-3
......@@ -486,8 +486,10 @@ async fn launcher_module(repo: &Path) -> Result<String> {
486486 module.push_str(&format!("local services = Map({})\n", services.join(", ")));
487487 module.push_str(r#"output { renderer = new JsonRenderer { omitNullProperties = false }
488488value = services.filter((_, s) -> s.enabled && s.meta.launcher).mapValues((_, s) ->
489let (route = (s.containers?.toMap()?.values ?? List()).add(s.container).filterNonNull().map((task) -> task.http).filterNonNull().findOrNull((http) -> http.hostname != null))
490new Dynamic { name = s.meta.name; tagline = s.meta.tagline; hostname = route?.hostname; access = s.meta.access ?? route?.authRole }) }"#);
489let (tasks = s.containers?.toMap() ?? Map("app", s.container))
490let (routes = tasks.filter((_, task) -> task?.http?.hostname != null).mapValues((_, task) -> task.http))
491let (route = routes.values.firstOrNull)
492new Dynamic { name = s.meta.name; tagline = s.meta.tagline; hostname = route?.hostname; access = s.meta.access ?? route?.authRole; urls = routes.mapValues((_, http) -> "https://\(http.hostname)") }) }"#);
491493 Ok(module)
492494}
493495
......@@ -502,7 +504,7 @@ async fn launcher(app: Arc<App>) -> Result<Arc<Document>> {
502504 if let Some(host) = value["hostname"].as_str() {
503505 let path = std::path::Path::new(file);
504506 let id = if path.file_name().unwrap() == "service.pkl" { path.parent().unwrap().file_name().unwrap() } else { path.file_stem().unwrap() }.to_string_lossy();
505 apps.push(json!({"id":id,"name":value["name"],"tagline":value["tagline"].as_str().filter(|s| !s.is_empty()),"url":format!("https://{host}"),"access":value["access"],"icon":icon_of(state.clone(),&id).await}));
507 apps.push(json!({"id":id,"name":value["name"],"tagline":value["tagline"].as_str().filter(|s| !s.is_empty()),"url":format!("https://{host}"),"urls":value["urls"],"access":value["access"],"icon":icon_of(state.clone(),&id).await}));
506508 }
507509 }
508510 Ok(json!(apps))
dashboard/web/api.contract.ts+1-1
......@@ -72,7 +72,7 @@ export type Api = Hono<{}, {
7272 $get: Endpoint<Me>;
7373 };
7474 "/launcher": {
75 $get: Endpoint<{ id: string; name: string; tagline: string | null; url: string; icon: { light: string; dark: string; } | null; health: Health | null; }[]>;
75 $get: Endpoint<{ id: string; name: string; tagline: string | null; url: string; urls: Record<string, string>; icon: { light: string; dark: string; } | null; health: Health | null; }[]>;
7676 };
7777 "/status": {
7878 $get: Endpoint<{ load: number; issues: Issue[] | null }>;
dashboard/web/pages/Deploy.tsx+1-1
......@@ -407,7 +407,7 @@ export function Deploy() {
407407
408408 /** The job's lines in the logs app, bounded to the window once its jobs stopped. */
409409 const logsUrl = (job: string, runtime: Runtime | undefined) => {
410 const url = service("victoria-logs")?.url;
410 const url = service("victoria")?.url;
411411 const iso = (t: number) => new Date(t * 1000).toISOString();
412412 const window = runtime?.to ? ` _time:[${iso(runtime.from)}, ${iso(runtime.to)}]` : "";
413413 return url && `${url}/select/vmui/#/?query=${encodeURIComponent(`{job="${job}"}${window}`)}`;
dashboard/web/pages/Overview.tsx+2-2
......@@ -160,7 +160,7 @@ function Dashboard(props: { me: Me }) {
160160 const name = (id: string) => names()[id] ?? id;
161161 const health = (service: { id: string; health: Health | null }) =>
162162 stream.latest()?.services[service.id]?.health ?? service.health;
163 const metrics = () => apps()?.find((app) => app.id === "victoria-metrics");
163 const metrics = () => apps()?.find((app) => app.id === "victoria");
164164
165165 const [menuId, setMenuId] = createSignal<string>();
166166 const selectedStage = () => history()?.stages.find((stage) => stage.id === menuId());
......@@ -340,7 +340,7 @@ function Dashboard(props: { me: Me }) {
340340
341341 <div class="section-head">
342342 <h2>machine</h2>
343 <Show when={metrics()}>{(app) => <OpenApp app={app()} href={`${app().url}/vmui/`} />}</Show>
343 <Show when={metrics()}>{(app) => <OpenApp app={app()} href={`${app().urls.metrics}/vmui/`} />}</Show>
344344 <RangePicker value={range()} onChange={setRange} />
345345 </div>
346346 <div class="strips">