diff --git a/frontend/src/lib/favicon.ts b/frontend/src/lib/favicon.ts new file mode 100644 index 0000000000..ebfd48b76d --- /dev/null +++ b/frontend/src/lib/favicon.ts @@ -0,0 +1,54 @@ +import type { Job } from '$lib/gen' + +export type JobStatusKind = 'running' | 'success' | 'failure' + +const STATUS_COLORS: Record = { + running: '#eab308', + success: '#22c55e', + failure: '#ef4444' +} + +const DEFAULT_FAVICON = '/logo.svg' + +// Inlined windmill logo polygons (fills inlined so the SVG is self-contained as a data URI). +const LOGO_POLYGONS = + '' + + '' + + '' + + '' + + '' + + '' + +function faviconLink(): HTMLLinkElement { + let link = document.querySelector('link[rel="icon"]') + if (!link) { + link = document.createElement('link') + link.rel = 'icon' + document.head.appendChild(link) + } + return link +} + +/** Maps a job to one of the three favicon status colors, following the same + * discrimination as JobStatusIcon (`'success' in job` => completed). */ +export function getJobStatusKind(job: Job | undefined): JobStatusKind | undefined { + if (!job) return undefined + if ('success' in job) return job.success ? 'success' : 'failure' + if (job.canceled) return 'failure' + return 'running' +} + +export function setStatusFavicon(status: JobStatusKind): void { + const color = STATUS_COLORS[status] + const svg = + '' + + `${LOGO_POLYGONS}` + + '' + + `` + + '' + faviconLink().href = `data:image/svg+xml,${encodeURIComponent(svg)}` +} + +export function resetFavicon(): void { + faviconLink().href = DEFAULT_FAVICON +} diff --git a/frontend/src/routes/(root)/(logged)/run/[...run]/+page.svelte b/frontend/src/routes/(root)/(logged)/run/[...run]/+page.svelte index e31d99f6b3..05b53ece63 100644 --- a/frontend/src/routes/(root)/(logged)/run/[...run]/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/run/[...run]/+page.svelte @@ -77,7 +77,8 @@ import ExecutionDuration from '$lib/components/ExecutionDuration.svelte' import { isWindmillTooBigObject } from '$lib/components/job_args' import ScheduleEditor from '$lib/components/triggers/schedules/ScheduleEditor.svelte' - import { setContext, untrack } from 'svelte' + import { onDestroy, setContext, untrack } from 'svelte' + import { getJobStatusKind, resetFavicon, setStatusFavicon } from '$lib/favicon' import FlowAssetsHandler, { initFlowGraphAssetsCtx @@ -391,6 +392,15 @@ $effect(() => { job && untrack(() => onJobLoaded()) }) + $effect(() => { + const status = getJobStatusKind(job) + if (status) { + setStatusFavicon(status) + } else { + resetFavicon() + } + }) + onDestroy(resetFavicon)