+48
-11
src/App.svelte
+48
-11
src/App.svelte
···
6
import instancesData from './instances.json';
7
import numeral from 'numeral';
8
9
const AUTO_REFRESH_INTERVAL = 15 // in seconds
10
const BUNDLE_OPS = 10_000
11
···
25
26
let isUpdating = $state(false)
27
let canRefresh = $state(true)
28
let lastUpdated = $state(new Date())
29
let autoRefreshEnabled = $state(true)
30
let instances = $state(instancesData.sort(() => Math.random() - 0.5))
31
32
-
const instanceOrderBy = [['status.head', 'status.latency'], ['desc', 'asc']]
33
34
function formatNumber(n: number) {
35
return numeral(n).format()
···
60
if (statusResp) {
61
statusResp.latency = performance.now() - start;
62
}
63
return statusResp
64
}
65
66
async function doCheck() {
67
isUpdating = true
68
canRefresh = false
···
70
i.status = undefined
71
}
72
73
await Promise.all(instances.map(async (instance) => {
74
const status = await getStatus(instance)
75
-
76
instance.status = status
77
-
instance.status.head = status?.bundles?.last_bundle > lastKnownBundle.number
78
-
if (instance.status.head) {
79
lastKnownBundle.number = status?.bundles?.last_bundle
80
lastKnownBundle.hash = status?.bundles?.head_hash
81
lastKnownBundle.time = status?.bundles?.end_time
···
87
}
88
}
89
lastUpdated = new Date()
90
}))
91
isUpdating = false
92
setTimeout(() => (canRefresh = false), 1000)
93
}
94
95
-
onMount(() => {
96
-
doCheck()
97
98
setTimeout(() => {
99
if (autoRefreshEnabled) {
···
128
<div>
129
<div class="flex items-center gap-5">
130
<div class="font-semibold text-3xl">{lastKnownBundle.number}</div>
131
-
<div class="mt-1 font-mono badge preset-outlined-primary-500 text-xs">{lastKnownBundle?.hash?.slice(0, 7)}</div>
132
</div>
133
<div>
134
<span class="opacity-50">{#if lastKnownBundle?.time} {formatDistanceToNow(lastKnownBundle.time, { addSuffix: true })}{/if}</span>
···
179
{#each orderBy(instances, ...instanceOrderBy) as instance}
180
<tr>
181
<td><a href={instance.url} target="_blank" class="font-semibold">{instance.url.replace("https://", "")}</a></td>
182
-
<td>{#if instance.status?.bundles?.last_bundle === lastKnownBundle.number}✅{:else if instance.status}🔄{:else}⌛{/if}</td>
183
<td>{#if instance.status?.bundles?.last_bundle}{instance.status?.bundles?.last_bundle}{/if}</td>
184
<td>{#if instance.status?.mempool && instance.status?.bundles?.last_bundle === lastKnownBundle.number}{formatNumber(instance.status?.mempool.count)}{:else if instance.status}<span class="opacity-25">syncing</span>{/if}</td>
185
-
<td><span class="font-mono text-xs">{#if instance.status?.bundles?.head_hash}{instance.status?.bundles?.head_hash.slice(0, 7)}{/if}</span></td>
186
-
<td><span class="font-mono text-xs">{#if instance.status?.bundles?.root_hash}{instance.status?.bundles?.root_hash.slice(0, 7)}{/if}</span></td>
187
<td>{#if instance.status?.server?.version}{instance.status?.server?.version}{/if}</td>
188
<td class="opacity-50">{#if instance.status?.latency}{Math.round(instance.status?.latency)}ms{/if}</td>
189
</tr>
···
191
</tbody>
192
</table>
193
194
-
<div class="mt-12 opacity-50">
195
<div>
196
Last updated: {formatISO9075(lastUpdated)}
197
</div>
···
199
Source: <a href="https://tangled.org/@tree.fail/plcbundle-watch">https://tangled.org/@tree.fail/plcbundle-watch</a>
200
</div>
201
</div>
202
</div>
203
</main>
204
···
6
import instancesData from './instances.json';
7
import numeral from 'numeral';
8
9
+
const PLC_DIRECTORY = 'plc.directory'
10
+
const ROOT = 'cbab6809a136d6a621906ee11199d3b0faf85b422fe0d0d2c346ce8e9dcd7485'
11
const AUTO_REFRESH_INTERVAL = 15 // in seconds
12
const BUNDLE_OPS = 10_000
13
···
27
28
let isUpdating = $state(false)
29
let canRefresh = $state(true)
30
+
let isConflict = $state(false)
31
let lastUpdated = $state(new Date())
32
let autoRefreshEnabled = $state(true)
33
let instances = $state(instancesData.sort(() => Math.random() - 0.5))
34
35
+
const instanceOrderBy = [['_head', 'status.latency'], ['desc', 'asc']]
36
37
function formatNumber(n: number) {
38
return numeral(n).format()
···
63
if (statusResp) {
64
statusResp.latency = performance.now() - start;
65
}
66
+
//if (instance.url === 'https://plc.j4ck.xyz') { statusResp.bundles.head_hash = 'f3ad3544452b2c078cba24990486bb9c277a1155'; }
67
return statusResp
68
}
69
70
+
function recalculateHead() {
71
+
isConflict = false
72
+
const headHashes = []
73
+
for (const instance of instances) {
74
+
instance._head = instance.status?.bundles?.last_bundle === lastKnownBundle.number
75
+
if (instance._head) {
76
+
headHashes.push(instance.status?.bundles?.head_hash)
77
+
}
78
+
}
79
+
isConflict = [...new Set(headHashes)].length > 1
80
+
}
81
+
82
async function doCheck() {
83
isUpdating = true
84
canRefresh = false
···
86
i.status = undefined
87
}
88
89
+
const statuses = []
90
+
91
await Promise.all(instances.map(async (instance) => {
92
const status = await getStatus(instance)
93
instance.status = status
94
+
if (status?.bundles?.last_bundle > lastKnownBundle.number) {
95
lastKnownBundle.number = status?.bundles?.last_bundle
96
lastKnownBundle.hash = status?.bundles?.head_hash
97
lastKnownBundle.time = status?.bundles?.end_time
···
103
}
104
}
105
lastUpdated = new Date()
106
+
107
+
recalculateHead()
108
}))
109
+
110
isUpdating = false
111
setTimeout(() => (canRefresh = false), 1000)
112
}
113
114
+
onMount(async () => {
115
+
await doCheck()
116
117
setTimeout(() => {
118
if (autoRefreshEnabled) {
···
147
<div>
148
<div class="flex items-center gap-5">
149
<div class="font-semibold text-3xl">{lastKnownBundle.number}</div>
150
+
{#if !isConflict}
151
+
<div class="mt-1 font-mono badge preset-outlined-primary-500 text-xs">{lastKnownBundle?.hash?.slice(0, 7)}</div>
152
+
{:else}
153
+
<div class="mt-1 badge preset-filled-error-500">⚠️ conflict!</div>
154
+
{/if}
155
</div>
156
<div>
157
<span class="opacity-50">{#if lastKnownBundle?.time} {formatDistanceToNow(lastKnownBundle.time, { addSuffix: true })}{/if}</span>
···
202
{#each orderBy(instances, ...instanceOrderBy) as instance}
203
<tr>
204
<td><a href={instance.url} target="_blank" class="font-semibold">{instance.url.replace("https://", "")}</a></td>
205
+
<td>{#if instance._head}{#if isConflict}⚠️{:else}✅{/if}{:else if instance.status}🔄{:else}⌛{/if}</td>
206
<td>{#if instance.status?.bundles?.last_bundle}{instance.status?.bundles?.last_bundle}{/if}</td>
207
<td>{#if instance.status?.mempool && instance.status?.bundles?.last_bundle === lastKnownBundle.number}{formatNumber(instance.status?.mempool.count)}{:else if instance.status}<span class="opacity-25">syncing</span>{/if}</td>
208
+
<td><span class="font-mono text-xs {instance._head ? (isConflict ? 'text-error-600' : 'text-success-600') : 'opacity-50'}">{#if instance.status?.bundles?.head_hash}{instance.status?.bundles?.head_hash.slice(0, 7)}{/if}</span></td>
209
+
<td><span class="font-mono text-xs {instance.status ? (instance.status?.bundles?.root_hash === ROOT ? 'text-success-600' : 'text-error-600') : ''}">{#if instance.status?.bundles?.root_hash}{instance.status?.bundles?.root_hash.slice(0, 7)}{/if}</span></td>
210
<td>{#if instance.status?.server?.version}{instance.status?.server?.version}{/if}</td>
211
<td class="opacity-50">{#if instance.status?.latency}{Math.round(instance.status?.latency)}ms{/if}</td>
212
</tr>
···
214
</tbody>
215
</table>
216
217
+
218
+
<div class="mt-12">
219
+
<div>
220
+
<span class="opacity-75">PLC Directory:</span> <a href="https://{PLC_DIRECTORY}">{PLC_DIRECTORY}</a> <span class="opacity-50">(origin)</span>
221
+
</div>
222
+
<div class="mt-2">
223
+
<span class="opacity-75">Root:</span> <span class="font-mono text-xs">{ROOT.slice(0)}</span>
224
+
</div>
225
+
</div>
226
+
227
+
<hr class="hr mt-6" />
228
+
<div class="mt-2 opacity-50">
229
<div>
230
Last updated: {formatISO9075(lastUpdated)}
231
</div>
···
233
Source: <a href="https://tangled.org/@tree.fail/plcbundle-watch">https://tangled.org/@tree.fail/plcbundle-watch</a>
234
</div>
235
</div>
236
+
237
+
238
+
239
</div>
240
</main>
241