1<script lang="ts"> 2 import { onMount } from 'svelte'; 3 import instancesData from './instances.json'; 4 5 type Instance = { 6 url: string, 7 cors?: boolean, 8 status?: object, 9 modern?: boolean, 10 } 11 12 let lastKnownBundle = $state({ 13 number: 0, 14 hash: null, 15 }) 16 17 let instances = $state(instancesData) 18 let instancesSorted = $derived(instances.sort((a, b) => a.status?.responseTime > b.status?.responseTime ? 1 : -1)) 19 20 async function getStatus(instance: Instance) { 21 let statusResp: object | undefined; 22 let url: string = instance.url; 23 const start = performance.now(); 24 try { 25 statusResp = await (await fetch(`${url}/status`)).json() 26 } catch (e) {} 27 if (!statusResp) { 28 url = `https://keyoxide.org/api/3/get/http?url=${encodeURIComponent(url)}&format=text&time=${Date.now()}` 29 const indexResp = await (await fetch(url)).text() 30 const [ _, from, to ] = indexResp?.match(/Range:\s+(\d{6}) - (\d{6})/) 31 statusResp = { 32 bundles: { 33 last_bundle: Number(to), 34 root_hash: indexResp?.match(/Root: ([a-f0-9]{64})/)[1], 35 head_hash: indexResp?.match(/Head: ([a-f0-9]{64})/)[1], 36 }, 37 server: { 38 uptime: 1, 39 } 40 } 41 } 42 if (statusResp) { 43 statusResp.responseTime = performance.now() - start; 44 } 45 return statusResp 46 } 47 48 async function doCheck() { 49 for (const i of instances) { 50 i.status = undefined 51 } 52 53 await Promise.all(instances.map(async (instance) => { 54 const status = await getStatus(instance) 55 56 if (status?.bundles?.last_bundle > lastKnownBundle.number) { 57 lastKnownBundle.number = status?.bundles?.last_bundle 58 lastKnownBundle.hash = status?.bundles?.head_hash 59 } 60 instance.status = status 61 })) 62 } 63 64 onMount(() => { 65 doCheck() 66 }) 67</script> 68 69<main class="w-full mt-10"> 70 <div class="max-w-4xl mx-auto px-3"> 71 72 <header> 73 <h1 class="text-3xl">plcbundle instances</h1> 74 </header> 75 76 <div class="flex items-center gap-2 mt-10 flex-wrap"> 77 <div class="grow flex items-center text-lg"> 78 <div><span class="opacity-50">Last known bundle:</span> <span class="font-semibold">{lastKnownBundle.number}</span> [<span class="font-mono text-base">{lastKnownBundle?.hash?.slice(0, 7)}</span>]</div> 79 </div> 80 <div class=""> 81 <button type="button" class="btn btn-sm preset-tonal-primary" onclick={() => doCheck()}>Refresh</button> 82 </div> 83 </div> 84 85 <table class="table mt-4"> 86 <thead> 87 <tr> 88 <th>endpoint</th> 89 <th>status</th> 90 <th>last bundle</th> 91 <th>mempool</th> 92 <th>head</th> 93 <th>root</th> 94 <th>version</th> 95 <th>latency</th> 96 </tr> 97 </thead> 98 <tbody> 99 {#each instances as instance} 100 <tr> 101 <td><a href={instance.url} target="_blank" class="font-semibold">{instance.url.replace("https://", "")}</a></td> 102 <td>{#if instance.status?.bundles?.last_bundle === lastKnownBundle.number}{:else if instance.status}🔄{:else}{/if}</td> 103 <td>{#if instance.status?.bundles?.last_bundle}{instance.status?.bundles?.last_bundle}{/if}</td> 104 <td>{#if instance.status?.mempool && instance.status?.bundles?.last_bundle === lastKnownBundle.number}{instance.status?.mempool.count}{:else if instance.status}<span class="opacity-25">syncing</span>{/if}</td> 105 <td><span class="font-mono text-xs">{#if instance.status?.bundles?.head_hash}{instance.status?.bundles?.head_hash.slice(0, 7)}{/if}</span></td> 106 <td><span class="font-mono text-xs">{#if instance.status?.bundles?.root_hash}{instance.status?.bundles?.root_hash.slice(0, 7)}{/if}</span></td> 107 <td>{#if instance.status?.server?.version}{instance.status?.server?.version}{/if}</td> 108 <td class="opacity-50">{#if instance.status?.responseTime}{Math.round(instance.status?.responseTime)}ms{/if}</td> 109 </tr> 110 {/each} 111 </tbody> 112 </table> 113 114 <div class="mt-12 opacity-50"> 115 Source: <a href="https://tangled.org/@tree.fail/plcbundle-watch">https://tangled.org/@tree.fail/plcbundle-watch</a> 116 </div> 117 </div> 118</main> 119