public uptime monitoring + (soon) observability with events saved to PDS
1<script lang="ts">
2 import { onMount } from 'svelte';
3 import { fetchUptimeChecks } from './lib/atproto.ts';
4 import UptimeDisplay from './lib/uptime-display.svelte';
5 import type { UptimeCheckRecord } from './lib/types.ts';
6 import { config } from './lib/config.ts';
7
8 let checks = $state<UptimeCheckRecord[]>([]);
9 let loading = $state(true);
10 let error = $state('');
11 let lastUpdate = $state<Date | null>(null);
12
13 async function loadChecks() {
14 loading = true;
15 error = '';
16
17 try {
18 checks = await fetchUptimeChecks(config.pds, config.did);
19 lastUpdate = new Date();
20 } catch (err) {
21 error = (err as Error).message || 'failed to fetch uptime checks';
22 checks = [];
23 } finally {
24 loading = false;
25 }
26 }
27
28 onMount(() => {
29 // load checks immediately
30 loadChecks();
31
32 // refresh every 10 seconds
33 const interval = setInterval(loadChecks, 10 * 1000);
34 return () => clearInterval(interval);
35 });
36</script>
37
38<main class="max-w-6xl mx-auto p-8">
39 <header class="text-center mb-8">
40 <h1 class="text-5xl font-bold text-accent mb-2">{config.title}</h1>
41 <p class="text-muted-foreground">{config.subtitle}</p>
42 </header>
43
44 <div class="bg-card rounded-lg shadow-sm p-4 mb-8 flex justify-between items-center">
45 <div class="flex items-center gap-4">
46 {#if lastUpdate}
47 <span class="text-sm text-muted-foreground">
48 last updated: {lastUpdate.toLocaleTimeString()}
49 </span>
50 {/if}
51 </div>
52 <button
53 class="px-4 py-2 bg-primary text-primary-foreground rounded-md text-sm font-medium hover:opacity-90 disabled:opacity-50 transition-opacity"
54 onclick={loadChecks}
55 disabled={loading}
56 >
57 {loading ? 'refreshing...' : 'refresh'}
58 </button>
59 </div>
60
61 {#if error}
62 <div class="bg-destructive/10 text-destructive rounded-lg p-4 mb-4">
63 {error}
64 </div>
65 {/if}
66
67 {#if loading && checks.length === 0}
68 <div class="text-center py-12 bg-card rounded-lg shadow-sm text-muted-foreground">
69 loading uptime data...
70 </div>
71 {:else if checks.length > 0}
72 <UptimeDisplay {checks} />
73 {:else if !loading}
74 <div class="text-center py-12 bg-card rounded-lg shadow-sm text-muted-foreground">
75 no uptime data available
76 </div>
77 {/if}
78
79 <footer class="mt-12 pt-8 border-t border-border text-center text-sm text-muted-foreground">
80 <p>
81 built by <a href="https://bsky.app/profile/nekomimi.pet" target="_blank" rel="noopener noreferrer" class="text-accent hover:underline">@nekomimi.pet</a>
82 · <a href="https://tangled.org/@nekomimi.pet/cute-monitor" target="_blank" rel="noopener noreferrer" class="text-accent hover:underline">source</a>
83 </p>
84 </footer>
85</main>
86