tangled
alpha
login
or
join now
nekomimi.pet
/
wisp.place-monorepo
87
fork
atom
Monorepo for wisp.place. A static site hosting service built on top of the AT Protocol.
wisp.place
87
fork
atom
overview
issues
9
pulls
pipelines
clean up admin db view
nekomimi.pet
1 month ago
285def4a
8cd5c1b1
1/2
deploy-wisp.yml
success
36s
test.yml
failed
30s
+182
-26
2 changed files
expand all
collapse all
unified
split
apps
main-app
public
admin
admin.tsx
src
routes
admin.ts
+130
-22
apps/main-app/public/admin/admin.tsx
reviewed
···
127
127
const [database, setDatabase] = useState<any>(null)
128
128
const [sites, setSites] = useState<any>(null)
129
129
const [health, setHealth] = useState<any>(null)
130
130
+
const [firehose, setFirehose] = useState<any>(null)
130
131
const [autoRefresh, setAutoRefresh] = useState(true)
131
132
132
133
// Filters
···
190
191
}
191
192
}
192
193
194
194
+
const fetchFirehose = async () => {
195
195
+
const res = await fetch('/api/admin/firehose', { credentials: 'include' })
196
196
+
if (res.ok) {
197
197
+
const data = await res.json()
198
198
+
setFirehose(data)
199
199
+
}
200
200
+
}
201
201
+
193
202
const logout = async () => {
194
203
await fetch('/api/admin/logout', { method: 'POST', credentials: 'include' })
195
204
window.location.reload()
···
199
208
fetchMetrics()
200
209
fetchDatabase()
201
210
fetchHealth()
211
211
+
fetchFirehose()
202
212
fetchLogs()
203
213
fetchErrors()
204
214
fetchSites()
···
215
225
if (tab === 'overview') {
216
226
fetchMetrics()
217
227
fetchHealth()
228
228
+
fetchFirehose()
218
229
} else if (tab === 'logs') {
219
230
fetchLogs()
220
231
} else if (tab === 'errors') {
···
307
318
</div>
308
319
)}
309
320
321
321
+
{/* Firehose Worker */}
322
322
+
{firehose && (
323
323
+
<div>
324
324
+
<h2 className="text-xl font-bold mb-4">Firehose Worker</h2>
325
325
+
<div className="bg-gray-900 border border-gray-800 rounded-lg p-4">
326
326
+
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
327
327
+
<div>
328
328
+
<div className="text-sm text-gray-400">Status</div>
329
329
+
<div className="flex items-center gap-2 mt-1">
330
330
+
<span className={`inline-block w-3 h-3 rounded-full ${firehose.firehose?.healthy ? 'bg-green-500' : 'bg-red-500'}`}></span>
331
331
+
<span className="text-lg font-bold">{firehose.firehose?.connected ? 'Connected' : 'Disconnected'}</span>
332
332
+
</div>
333
333
+
</div>
334
334
+
<div>
335
335
+
<div className="text-sm text-gray-400">Mode</div>
336
336
+
<div className="text-lg font-bold capitalize">{firehose.mode || 'unknown'}</div>
337
337
+
</div>
338
338
+
<div>
339
339
+
<div className="text-sm text-gray-400">Queue Size</div>
340
340
+
<div className="text-lg font-bold">{firehose.firehose?.queueSize || 0}</div>
341
341
+
</div>
342
342
+
<div>
343
343
+
<div className="text-sm text-gray-400">Active Handlers</div>
344
344
+
<div className="text-lg font-bold">{firehose.firehose?.activeHandlers || 0}</div>
345
345
+
</div>
346
346
+
</div>
347
347
+
{firehose.firehose?.lastEventTime && (
348
348
+
<div className="mt-3 text-sm text-gray-400">
349
349
+
Last event: {new Date(firehose.firehose.lastEventTime).toLocaleString()}
350
350
+
({Math.round(firehose.firehose.timeSinceLastEvent / 1000)}s ago)
351
351
+
</div>
352
352
+
)}
353
353
+
</div>
354
354
+
</div>
355
355
+
)}
356
356
+
310
357
{/* Metrics */}
311
358
{metrics && (
312
359
<div>
···
527
574
{tab === 'database' && database && (
528
575
<div className="space-y-6">
529
576
{/* Stats */}
530
530
-
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
577
577
+
<div className="grid grid-cols-2 md:grid-cols-5 gap-4">
531
578
<div className="bg-gray-900 border border-gray-800 rounded-lg p-4">
532
579
<div className="text-sm text-gray-400 mb-1">Total Sites</div>
533
580
<div className="text-3xl font-bold">{database.stats.totalSites}</div>
···
540
587
<div className="text-sm text-gray-400 mb-1">Custom Domains</div>
541
588
<div className="text-3xl font-bold">{database.stats.totalCustomDomains}</div>
542
589
</div>
590
590
+
<div className="bg-gray-900 border border-gray-800 rounded-lg p-4">
591
591
+
<div className="text-sm text-gray-400 mb-1">Site Cache</div>
592
592
+
<div className="text-3xl font-bold">{database.stats.totalSiteCache}</div>
593
593
+
</div>
594
594
+
<div className="bg-gray-900 border border-gray-800 rounded-lg p-4">
595
595
+
<div className="text-sm text-gray-400 mb-1">Settings Cache</div>
596
596
+
<div className="text-3xl font-bold">{database.stats.totalSiteSettingsCache}</div>
597
597
+
</div>
543
598
</div>
544
599
545
600
{/* Recent Sites */}
···
550
605
<thead className="bg-gray-800">
551
606
<tr>
552
607
<th className="px-4 py-2 text-left">Site Name</th>
553
553
-
<th className="px-4 py-2 text-left">Subdomain</th>
608
608
+
<th className="px-4 py-2 text-left">Links</th>
554
609
<th className="px-4 py-2 text-left">DID</th>
555
610
<th className="px-4 py-2 text-left">RKey</th>
556
611
<th className="px-4 py-2 text-left">Created</th>
612
612
+
<th className="px-4 py-2 text-left">PDSls</th>
557
613
</tr>
558
614
</thead>
559
615
<tbody>
···
561
617
<tr key={i} className="border-t border-gray-800">
562
618
<td className="px-4 py-2">{site.display_name || 'Untitled'}</td>
563
619
<td className="px-4 py-2">
564
564
-
{site.subdomain ? (
620
620
+
<div className="flex flex-col gap-1">
565
621
<a
566
566
-
href={`https://${site.subdomain}`}
622
622
+
href={`https://sites.wisp.place/${site.did}/${site.rkey || 'self'}`}
567
623
target="_blank"
568
624
rel="noopener noreferrer"
569
569
-
className="text-blue-400 hover:underline"
625
625
+
className="text-blue-400 hover:underline text-xs"
570
626
>
571
571
-
{site.subdomain}
627
627
+
sites.wisp.place
572
628
</a>
573
573
-
) : (
574
574
-
<span className="text-gray-500">No domain</span>
575
575
-
)}
629
629
+
{site.subdomain && (
630
630
+
<a
631
631
+
href={`https://${site.subdomain}`}
632
632
+
target="_blank"
633
633
+
rel="noopener noreferrer"
634
634
+
className="text-green-400 hover:underline text-xs"
635
635
+
>
636
636
+
{site.subdomain}
637
637
+
</a>
638
638
+
)}
639
639
+
{site.custom_domain && (
640
640
+
<a
641
641
+
href={`https://${site.custom_domain}`}
642
642
+
target="_blank"
643
643
+
rel="noopener noreferrer"
644
644
+
className="text-purple-400 hover:underline text-xs"
645
645
+
>
646
646
+
{site.custom_domain}
647
647
+
</a>
648
648
+
)}
649
649
+
</div>
576
650
</td>
577
651
<td className="px-4 py-2 text-gray-400 font-mono text-xs">
578
578
-
{site.did.slice(0, 20)}...
652
652
+
{site.did}
579
653
</td>
580
654
<td className="px-4 py-2 text-gray-400">{site.rkey || 'self'}</td>
581
655
<td className="px-4 py-2 text-gray-400">
···
610
684
<tr>
611
685
<th className="px-4 py-2 text-left">Domain</th>
612
686
<th className="px-4 py-2 text-left">DID</th>
687
687
+
<th className="px-4 py-2 text-left">RKey</th>
613
688
<th className="px-4 py-2 text-left">Verified</th>
614
689
<th className="px-4 py-2 text-left">Created</th>
615
690
</tr>
···
617
692
<tbody>
618
693
{database.recentDomains.map((domain: any, i: number) => (
619
694
<tr key={i} className="border-t border-gray-800">
620
620
-
<td className="px-4 py-2">{domain.domain}</td>
695
695
+
<td className="px-4 py-2">
696
696
+
{domain.verified ? (
697
697
+
<a
698
698
+
href={`https://${domain.domain}`}
699
699
+
target="_blank"
700
700
+
rel="noopener noreferrer"
701
701
+
className="text-blue-400 hover:underline"
702
702
+
>
703
703
+
{domain.domain}
704
704
+
</a>
705
705
+
) : (
706
706
+
<span className="text-gray-400">{domain.domain}</span>
707
707
+
)}
708
708
+
</td>
621
709
<td className="px-4 py-2 text-gray-400 font-mono text-xs">
622
622
-
{domain.did.slice(0, 20)}...
710
710
+
{domain.did}
623
711
</td>
712
712
+
<td className="px-4 py-2 text-gray-400">{domain.rkey || 'self'}</td>
624
713
<td className="px-4 py-2">
625
714
<span
626
715
className={`px-2 py-1 rounded text-xs ${
···
654
743
<thead className="bg-gray-800">
655
744
<tr>
656
745
<th className="px-4 py-2 text-left">Site Name</th>
657
657
-
<th className="px-4 py-2 text-left">Subdomain</th>
746
746
+
<th className="px-4 py-2 text-left">Links</th>
658
747
<th className="px-4 py-2 text-left">DID</th>
659
748
<th className="px-4 py-2 text-left">RKey</th>
660
749
<th className="px-4 py-2 text-left">Created</th>
750
750
+
<th className="px-4 py-2 text-left">PDSls</th>
661
751
</tr>
662
752
</thead>
663
753
<tbody>
···
665
755
<tr key={i} className="border-t border-gray-800 hover:bg-gray-800">
666
756
<td className="px-4 py-2">{site.display_name || 'Untitled'}</td>
667
757
<td className="px-4 py-2">
668
668
-
{site.subdomain ? (
758
758
+
<div className="flex flex-col gap-1">
669
759
<a
670
670
-
href={`https://${site.subdomain}`}
760
760
+
href={`https://sites.wisp.place/${site.did}/${site.rkey || 'self'}`}
671
761
target="_blank"
672
762
rel="noopener noreferrer"
673
673
-
className="text-blue-400 hover:underline"
763
763
+
className="text-blue-400 hover:underline text-xs"
674
764
>
675
675
-
{site.subdomain}
765
765
+
sites.wisp.place
676
766
</a>
677
677
-
) : (
678
678
-
<span className="text-gray-500">No domain</span>
679
679
-
)}
767
767
+
{site.subdomain && (
768
768
+
<a
769
769
+
href={`https://${site.subdomain}`}
770
770
+
target="_blank"
771
771
+
rel="noopener noreferrer"
772
772
+
className="text-green-400 hover:underline text-xs"
773
773
+
>
774
774
+
{site.subdomain}
775
775
+
</a>
776
776
+
)}
777
777
+
{site.custom_domain && (
778
778
+
<a
779
779
+
href={`https://${site.custom_domain}`}
780
780
+
target="_blank"
781
781
+
rel="noopener noreferrer"
782
782
+
className="text-purple-400 hover:underline text-xs"
783
783
+
>
784
784
+
{site.custom_domain}
785
785
+
</a>
786
786
+
)}
787
787
+
</div>
680
788
</td>
681
789
<td className="px-4 py-2 text-gray-400 font-mono text-xs">
682
682
-
{site.did.slice(0, 30)}...
790
790
+
{site.did}
683
791
</td>
684
792
<td className="px-4 py-2 text-gray-400">{site.rkey || 'self'}</td>
685
793
<td className="px-4 py-2 text-gray-400">
···
749
857
</span>
750
858
</td>
751
859
<td className="px-4 py-2 text-gray-400 font-mono text-xs">
752
752
-
{domain.did.slice(0, 30)}...
860
860
+
{domain.did}
753
861
</td>
754
862
<td className="px-4 py-2 text-gray-400">{domain.rkey || 'self'}</td>
755
863
<td className="px-4 py-2 text-gray-400">
+52
-4
apps/main-app/src/routes/admin.ts
reviewed
···
270
270
// Get database stats (protected)
271
271
/**
272
272
* GET /api/admin/database
273
273
-
* Success: { stats, recentSites, recentDomains }
273
273
+
* Success: { stats, recentSites, recentDomains, siteCacheStats }
274
274
* Failure (500): { error, message }
275
275
*/
276
276
.get('/database', async ({ cookie, set }) => {
···
282
282
const allSitesResult = await db`SELECT COUNT(*) as count FROM sites`
283
283
const wispSubdomainsResult = await db`SELECT COUNT(*) as count FROM domains WHERE domain LIKE '%.wisp.place'`
284
284
const customDomainsResult = await db`SELECT COUNT(*) as count FROM custom_domains WHERE verified = true`
285
285
+
const siteCacheResult = await db`SELECT COUNT(*) as count FROM site_cache`
286
286
+
const siteSettingsCacheResult = await db`SELECT COUNT(*) as count FROM site_settings_cache`
285
287
286
288
// Get recent sites (including those without domains)
287
289
const recentSites = await db`
···
290
292
s.rkey,
291
293
s.display_name,
292
294
s.created_at,
293
293
-
d.domain as subdomain
295
295
+
d.domain as subdomain,
296
296
+
cd.domain as custom_domain
294
297
FROM sites s
295
298
LEFT JOIN domains d ON s.did = d.did AND s.rkey = d.rkey AND d.domain LIKE '%.wisp.place'
299
299
+
LEFT JOIN custom_domains cd ON s.did = cd.did AND s.rkey = cd.rkey AND cd.verified = true
296
300
ORDER BY s.created_at DESC
297
301
LIMIT 10
298
302
`
···
304
308
stats: {
305
309
totalSites: allSitesResult[0].count,
306
310
totalWispSubdomains: wispSubdomainsResult[0].count,
307
307
-
totalCustomDomains: customDomainsResult[0].count
311
311
+
totalCustomDomains: customDomainsResult[0].count,
312
312
+
totalSiteCache: siteCacheResult[0].count,
313
313
+
totalSiteSettingsCache: siteSettingsCacheResult[0].count
308
314
},
309
315
recentSites: recentSites,
310
316
recentDomains: recentDomains
···
385
391
s.rkey,
386
392
s.display_name,
387
393
s.created_at,
388
388
-
d.domain as subdomain
394
394
+
d.domain as subdomain,
395
395
+
cd.domain as custom_domain
389
396
FROM sites s
390
397
LEFT JOIN domains d ON s.did = d.did AND s.rkey = d.rkey AND d.domain LIKE '%.wisp.place'
398
398
+
LEFT JOIN custom_domains cd ON s.did = cd.did AND s.rkey = cd.rkey AND cd.verified = true
391
399
ORDER BY s.created_at DESC
392
400
LIMIT ${limit} OFFSET ${offset}
393
401
`
···
412
420
set.status = 500
413
421
return {
414
422
error: 'Failed to fetch sites',
423
423
+
message: error instanceof Error ? error.message : String(error)
424
424
+
}
425
425
+
}
426
426
+
}, {
427
427
+
cookie: t.Cookie({
428
428
+
admin_session: t.Optional(t.String())
429
429
+
}, {
430
430
+
secrets: cookieSecret,
431
431
+
sign: ['admin_session']
432
432
+
})
433
433
+
})
434
434
+
435
435
+
// Get firehose worker status (protected)
436
436
+
/**
437
437
+
* GET /api/admin/firehose
438
438
+
* Success: firehose health data from firehose-service
439
439
+
* Failure (503|500): { error, message }
440
440
+
*/
441
441
+
.get('/firehose', async ({ cookie, set }) => {
442
442
+
const check = requireAdmin({ cookie, set })
443
443
+
if (check) return check
444
444
+
445
445
+
try {
446
446
+
const firehoseServiceUrl = process.env.FIREHOSE_SERVICE_URL || `http://localhost:${process.env.FIREHOSE_PORT || '3002'}`
447
447
+
const response = await fetch(`${firehoseServiceUrl}/health`)
448
448
+
449
449
+
if (response.ok) {
450
450
+
const data = await response.json()
451
451
+
return data
452
452
+
} else {
453
453
+
set.status = 503
454
454
+
return {
455
455
+
error: 'Failed to fetch firehose status',
456
456
+
message: 'Firehose service unavailable'
457
457
+
}
458
458
+
}
459
459
+
} catch (error) {
460
460
+
set.status = 500
461
461
+
return {
462
462
+
error: 'Failed to fetch firehose status',
415
463
message: error instanceof Error ? error.message : String(error)
416
464
}
417
465
}