| author | |
| committer | |
| log | df1c802f8e4c1f91746ce7761590af6849d12d41 |
| tree | 01ea44df004e73b1ee8e773b754dea84d3e58f8f |
| parent | d16b9185f320c372908c7c4bdc6fba091e403b19 |
| signature | Signed by SSH key SHA256:52mNGHRsVFBDED9IAX5pe+LRWUefqTbxEReunq21QvU |
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-sol4 files changed, 9 insertions(+), 7 deletions(-)
dashboard/src/core.rs+5-3| ... | ... | @@ -486,8 +486,10 @@ async fn launcher_module(repo: &Path) -> Result<String> { |
| 486 | 486 | module.push_str(&format!("local services = Map({})\n", services.join(", "))); |
| 487 | 487 | module.push_str(r#"output { renderer = new JsonRenderer { omitNullProperties = false } |
| 488 | 488 | value = services.filter((_, s) -> s.enabled && s.meta.launcher).mapValues((_, s) -> |
| 489 | let (route = (s.containers?.toMap()?.values ?? List()).add(s.container).filterNonNull().map((task) -> task.http).filterNonNull().findOrNull((http) -> http.hostname != null)) | |
| 490 | new Dynamic { name = s.meta.name; tagline = s.meta.tagline; hostname = route?.hostname; access = s.meta.access ?? route?.authRole }) }"#); | |
| 489 | let (tasks = s.containers?.toMap() ?? Map("app", s.container)) | |
| 490 | let (routes = tasks.filter((_, task) -> task?.http?.hostname != null).mapValues((_, task) -> task.http)) | |
| 491 | let (route = routes.values.firstOrNull) | |
| 492 | new 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)") }) }"#); | |
| 491 | 493 | Ok(module) |
| 492 | 494 | } |
| 493 | 495 | |
| ... | ... | @@ -502,7 +504,7 @@ async fn launcher(app: Arc<App>) -> Result<Arc<Document>> { |
| 502 | 504 | if let Some(host) = value["hostname"].as_str() { |
| 503 | 505 | let path = std::path::Path::new(file); |
| 504 | 506 | 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})); | |
| 506 | 508 | } |
| 507 | 509 | } |
| 508 | 510 | Ok(json!(apps)) |
dashboard/web/api.contract.ts+1-1| ... | ... | @@ -72,7 +72,7 @@ export type Api = Hono<{}, { |
| 72 | 72 | $get: Endpoint<Me>; |
| 73 | 73 | }; |
| 74 | 74 | "/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; }[]>; | |
| 76 | 76 | }; |
| 77 | 77 | "/status": { |
| 78 | 78 | $get: Endpoint<{ load: number; issues: Issue[] | null }>; |
dashboard/web/pages/Deploy.tsx+1-1| ... | ... | @@ -407,7 +407,7 @@ export function Deploy() { |
| 407 | 407 | |
| 408 | 408 | /** The job's lines in the logs app, bounded to the window once its jobs stopped. */ |
| 409 | 409 | const logsUrl = (job: string, runtime: Runtime | undefined) => { |
| 410 | const url = service("victoria-logs")?.url; | |
| 410 | const url = service("victoria")?.url; | |
| 411 | 411 | const iso = (t: number) => new Date(t * 1000).toISOString(); |
| 412 | 412 | const window = runtime?.to ? ` _time:[${iso(runtime.from)}, ${iso(runtime.to)}]` : ""; |
| 413 | 413 | 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 }) { |
| 160 | 160 | const name = (id: string) => names()[id] ?? id; |
| 161 | 161 | const health = (service: { id: string; health: Health | null }) => |
| 162 | 162 | 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"); | |
| 164 | 164 | |
| 165 | 165 | const [menuId, setMenuId] = createSignal<string>(); |
| 166 | 166 | const selectedStage = () => history()?.stages.find((stage) => stage.id === menuId()); |
| ... | ... | @@ -340,7 +340,7 @@ function Dashboard(props: { me: Me }) { |
| 340 | 340 | |
| 341 | 341 | <div class="section-head"> |
| 342 | 342 | <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> | |
| 344 | 344 | <RangePicker value={range()} onChange={setRange} /> |
| 345 | 345 | </div> |
| 346 | 346 | <div class="strips"> |