~james/core

core/web/src/lib/components/StatusDot.svelte -rw-r--r-- 1.5 KiB
452f17b7 — james chore(import): snapshot james.dad.town/core at f70c71c7696a 15 hours ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
<script lang="ts">
	import { onMount } from "svelte";
	import { resolve } from "$app/paths";
	import { STATUS_LABEL, type Status } from "#lib/status.ts";

	let status = $state<Status>("unknown");

	onMount(() => {
		const controller = new AbortController();
		fetch(resolve("/_internal/status"), { signal: controller.signal })
			.then((res) => (res.ok ? res.json() : { status: "unknown" }))
			.then((body: { status: Status }) => (status = body.status))
			.catch(() => {});
		return () => controller.abort();
	});
</script>

<span
	class="dot"
	data-status={status}
	role="img"
	aria-label={STATUS_LABEL[status]}
	title={STATUS_LABEL[status]}
></span>

<style>
	.dot {
		--dot: var(--color-foreground-placeholder);
		position: relative;
		display: inline-block;
		flex: none;
		width: 0.5rem;
		height: 0.5rem;
		margin-inline-start: 0.4em;
		vertical-align: middle;
		border-radius: 9999px;
		background: var(--dot);
	}
	.dot[data-status="up"] {
		--dot: var(--color-foreground-success);
	}
	.dot[data-status="degraded"],
	.dot[data-status="maintenance"] {
		--dot: var(--color-foreground-warning);
	}
	.dot[data-status="down"] {
		--dot: var(--color-foreground-danger);
	}
	.dot:not([data-status="unknown"])::after {
		content: "";
		position: absolute;
		inset: 0;
		border-radius: inherit;
		background: var(--dot);
		animation: ping 2s cubic-bezier(0, 0, 0.2, 1) infinite;
	}
	@keyframes ping {
		75%,
		100% {
			transform: scale(2.4);
			opacity: 0;
		}
	}
	@media (prefers-reduced-motion: reduce) {
		.dot::after {
			animation: none;
			display: none;
		}
	}
</style>