···
import { BlobView } from "./blob.jsx";
import { TextInput } from "../components/text-input.jsx";
import Tooltip from "../components/tooltip.jsx";
19
+
import { CompatibleOperationOrTombstone, defs, IndexedEntry } from "@atcute/did-plc";
20
+
import { createOperationHistory, DiffEntry, groupBy } from "../utils/plc-logs.js";
21
+
import { localDateFromTimestamp } from "../utils/date.js";
type Tab = "collections" | "backlinks" | "doc" | "blobs";
···
const [nsids, setNsids] = createSignal<Record<string, { hidden: boolean; nsids: string[] }>>();
const [tab, setTab] = createSignal<Tab>("collections");
const [filter, setFilter] = createSignal<string>();
34
+
const [plcOps, setPlcOps] =
35
+
createSignal<[IndexedEntry<CompatibleOperationOrTombstone>, DiffEntry[]][]>();
36
+
const [showPlcLogs, setShowPlcLogs] = createSignal(false);
37
+
const [loading, setLoading] = createSignal(false);
···
55
+
const DiffItem = (props: { diff: DiffEntry }) => {
56
+
const diff = props.diff;
57
+
let title = "Unknown log entry";
58
+
let icon = "i-lucide-circle-help";
61
+
if (diff.type === "identity_created") {
62
+
icon = "i-lucide-bell";
63
+
title = `Identity created`;
64
+
} else if (diff.type === "identity_tombstoned") {
65
+
icon = "i-lucide-skull";
66
+
title = `Identity tombstoned`;
67
+
} else if (diff.type === "handle_added") {
68
+
icon = "i-lucide-at-sign";
69
+
title = "Alias added";
70
+
value = diff.handle;
71
+
} else if (diff.type === "handle_changed") {
72
+
icon = "i-lucide-at-sign";
73
+
title = "Alias updated";
74
+
value = `${diff.prev_handle} → ${diff.next_handle}`;
75
+
} else if (diff.type === "handle_removed") {
76
+
icon = "i-lucide-at-sign";
77
+
title = `Alias removed`;
78
+
value = diff.handle;
79
+
} else if (diff.type === "rotation_key_added") {
80
+
icon = "i-lucide-key-round";
81
+
title = `Rotation key added`;
82
+
value = diff.rotation_key;
83
+
} else if (diff.type === "rotation_key_removed") {
84
+
icon = "i-lucide-key-round";
85
+
title = `Rotation key removed`;
86
+
value = diff.rotation_key;
87
+
} else if (diff.type === "service_added") {
88
+
icon = "i-lucide-server";
89
+
title = `Service ${diff.service_id} added`;
90
+
value = `${diff.service_endpoint}`;
91
+
} else if (diff.type === "service_changed") {
92
+
icon = "i-lucide-server";
93
+
title = `Service ${diff.service_id} updated`;
94
+
value = `${diff.prev_service_endpoint} → ${diff.next_service_endpoint}`;
95
+
} else if (diff.type === "service_removed") {
96
+
icon = "i-lucide-server";
97
+
title = `Service ${diff.service_id} removed`;
98
+
value = `${diff.service_endpoint}`;
99
+
} else if (diff.type === "verification_method_added") {
100
+
icon = "i-lucide-shield-check";
101
+
title = `Verification method ${diff.method_id} added`;
102
+
value = `${diff.method_key}`;
103
+
} else if (diff.type === "verification_method_changed") {
104
+
icon = "i-lucide-shield-check";
105
+
title = `Verification method ${diff.method_id} updated`;
106
+
value = `${diff.prev_method_key} → ${diff.next_method_key}`;
107
+
} else if (diff.type === "verification_method_removed") {
108
+
icon = "i-lucide-shield-check";
109
+
title = `Verification method ${diff.method_id} removed`;
110
+
value = `${diff.method_key}`;
114
+
<div class="grid grid-cols-[min-content_1fr] items-center">
115
+
<div class={icon + ` mr-1 shrink-0 text-lg`} />
118
+
"font-semibold": true,
119
+
"text-gray-500 line-through dark:text-gray-400": diff.orig.nullified,
const fetchRepo = async () => {
pds = await resolvePDS(did);
···
<Show when={tab() === "doc"}>
231
-
<div class="break-anywhere flex flex-col gap-y-1">
232
-
<div class="flex items-center justify-between gap-2">
313
+
<div class="break-anywhere flex flex-col gap-y-2">
314
+
<div class="flex flex-col gap-y-1">
315
+
<div class="flex items-center justify-between gap-2">
317
+
<span class="font-semibold text-stone-600 dark:text-stone-400">ID </span>
318
+
<span>{didDocument().id}</span>
320
+
<Tooltip text="DID Document">
323
+
did.startsWith("did:plc") ?
324
+
`${localStorage.plcDirectory ?? "https://plc.directory"}/${did}`
325
+
: `https://${did.split("did:web:")[1]}/.well-known/did.json`
329
+
<div class="i-lucide-external-link text-lg" />
334
+
<p class="font-semibold text-stone-600 dark:text-stone-400">Identities</p>
336
+
<For each={didDocument().alsoKnownAs}>{(alias) => <li>{alias}</li>}</For>
234
-
<span class="font-semibold text-stone-600 dark:text-stone-400">ID </span>
235
-
<span>{didDocument().id}</span>
340
+
<p class="font-semibold text-stone-600 dark:text-stone-400">Services</p>
342
+
<For each={didDocument().service}>
344
+
<li class="flex flex-col">
345
+
<span>#{service.id.split("#")[1]}</span>
347
+
class="w-fit text-blue-400 hover:underline"
348
+
href={service.serviceEndpoint.toString()}
351
+
{service.serviceEndpoint.toString()}
359
+
<p class="font-semibold text-stone-600 dark:text-stone-400">
360
+
Verification methods
363
+
<For each={didDocument().verificationMethod}>
365
+
<li class="flex flex-col">
366
+
<span>#{verif.id.split("#")[1]}</span>
367
+
<span>{verif.publicKeyMultibase}</span>
237
-
<Tooltip text="DID Document">
240
-
did.startsWith("did:plc") ?
241
-
`${localStorage.plcDirectory ?? "https://plc.directory"}/${did}`
242
-
: `https://${did.split("did:web:")[1]}/.well-known/did.json`
374
+
<div class="flex justify-between">
375
+
<Show when={did.startsWith("did:plc")}>
376
+
<div class="flex items-center gap-1">
379
+
onclick={async () => {
382
+
const response = await fetch(`https://plc.directory/${did}/log/audit`);
383
+
const json = await response.json();
384
+
const logs = defs.indexedEntryLog.parse(json);
385
+
const opHistory = createOperationHistory(logs).reverse();
386
+
setPlcOps(Array.from(groupBy(opHistory, (item) => item.orig)));
390
+
setShowPlcLogs(!showPlcLogs());
392
+
class="dark:hover:bg-dark-100 dark:bg-dark-300 focus:outline-1.5 dark:shadow-dark-900 flex items-center gap-1 rounded-lg bg-white px-2 py-1.5 text-xs font-bold shadow-sm hover:bg-zinc-200/50 focus:outline-slate-900 dark:focus:outline-slate-100"
394
+
<div class="i-lucide-logs text-sm" />
395
+
{showPlcLogs() ? "Hide" : "Show"} PLC logs
397
+
<Show when={loading()}>
398
+
<div class="i-lucide-loader-circle animate-spin text-xl" />
402
+
<Show when={error()?.length === 0 || error() === undefined}>
405
+
"flex items-center gap-1": true,
406
+
"flex-row-reverse": did.startsWith("did:web"),
246
-
<div class="i-lucide-external-link text-lg" />
409
+
<Show when={downloading()}>
410
+
<div class="i-lucide-loader-circle animate-spin text-xl" />
414
+
onclick={() => downloadRepo()}
415
+
class="dark:hover:bg-dark-100 dark:bg-dark-300 focus:outline-1.5 dark:shadow-dark-900 flex items-center gap-1 rounded-lg bg-white px-2 py-1.5 text-xs font-bold shadow-sm hover:bg-zinc-200/50 focus:outline-slate-900 dark:focus:outline-slate-100"
417
+
<div class="i-lucide-download text-sm" />
251
-
<p class="font-semibold text-stone-600 dark:text-stone-400">Identities</p>
253
-
<For each={didDocument().alsoKnownAs}>{(alias) => <li>{alias}</li>}</For>
257
-
<p class="font-semibold text-stone-600 dark:text-stone-400">Services</p>
259
-
<For each={didDocument().service}>
261
-
<li class="flex flex-col">
262
-
<span>#{service.id.split("#")[1]}</span>
264
-
class="w-fit text-blue-400 hover:underline"
265
-
href={service.serviceEndpoint.toString()}
268
-
{service.serviceEndpoint.toString()}
276
-
<p class="font-semibold text-stone-600 dark:text-stone-400">
277
-
Verification methods
280
-
<For each={didDocument().verificationMethod}>
282
-
<li class="flex flex-col">
283
-
<span>#{verif.id.split("#")[1]}</span>
284
-
<span>{verif.publicKeyMultibase}</span>
423
+
<Show when={showPlcLogs()}>
424
+
<div class="flex flex-col gap-1 text-sm">
425
+
<For each={plcOps()}>
426
+
{([entry, diffs]) => (
427
+
<div class="flex flex-col">
428
+
<span class="text-neutral-500 dark:text-neutral-400">
429
+
{localDateFromTimestamp(new Date(entry.createdAt).getTime())}
431
+
{diffs.map((diff) => (
432
+
<DiffItem diff={diff} />
290
-
<Show when={did.startsWith("did:plc")}>
292
-
class="flex w-fit items-center text-blue-400 hover:underline"
293
-
href={`https://boat.kelinci.net/plc-oplogs?q=${did}`}
296
-
PLC operation logs <div class="i-lucide-external-link ml-0.5 text-sm" />
299
-
<Show when={error()?.length === 0 || error() === undefined}>
300
-
<div class="flex items-center gap-1">
303
-
onclick={() => downloadRepo()}
304
-
class="dark:hover:bg-dark-100 dark:bg-dark-300 focus:outline-1.5 dark:shadow-dark-900 flex items-center gap-1 rounded-lg bg-white px-2 py-1.5 text-xs font-bold shadow-sm hover:bg-zinc-200/50 focus:outline-slate-900 dark:focus:outline-slate-100"
306
-
<div class="i-lucide-download text-sm" />
309
-
<Show when={downloading()}>
310
-
<div class="i-lucide-loader-circle animate-spin text-xl" />