forked from
pds.ls/pdsls
this repo has no description
1import {
2 CompatibleOperationOrTombstone,
3 defs,
4 IndexedEntry,
5 processIndexedEntryLog,
6} from "@atcute/did-plc";
7import { createResource, createSignal, For, Show } from "solid-js";
8import { localDateFromTimestamp } from "../utils/date.js";
9import { createOperationHistory, DiffEntry, groupBy } from "../utils/plc-logs.js";
10
11type PlcEvent = "handle" | "rotation_key" | "service" | "verification_method";
12
13export const PlcLogView = (props: { did: string }) => {
14 const [activePlcEvent, setActivePlcEvent] = createSignal<PlcEvent | undefined>();
15
16 const fetchPlcLogs = async () => {
17 const res = await fetch(
18 `${localStorage.plcDirectory ?? "https://plc.directory"}/${props.did}/log/audit`,
19 );
20 const json = await res.json();
21 const logs = defs.indexedEntryLog.parse(json);
22 try {
23 await processIndexedEntryLog(props.did as any, logs);
24 } catch (e) {
25 console.error(e);
26 }
27 const opHistory = createOperationHistory(logs).reverse();
28 return Array.from(groupBy(opHistory, (item) => item.orig));
29 };
30
31 const [plcOps] =
32 createResource<[IndexedEntry<CompatibleOperationOrTombstone>, DiffEntry[]][]>(fetchPlcLogs);
33
34 const FilterButton = (props: { icon: string; event: PlcEvent }) => (
35 <button
36 classList={{
37 "flex items-center rounded-full p-1.5": true,
38 "bg-neutral-700 dark:bg-neutral-200": activePlcEvent() === props.event,
39 "hover:bg-neutral-200 dark:hover:bg-neutral-700": activePlcEvent() !== props.event,
40 }}
41 onclick={() => setActivePlcEvent(activePlcEvent() === props.event ? undefined : props.event)}
42 >
43 <span
44 class={`${props.icon} ${activePlcEvent() === props.event ? "text-neutral-200 dark:text-neutral-900" : ""}`}
45 ></span>
46 </button>
47 );
48
49 const DiffItem = (props: { diff: DiffEntry }) => {
50 const diff = props.diff;
51 let title = "Unknown log entry";
52 let icon = "lucide--circle-help";
53 let value = "";
54
55 if (diff.type === "identity_created") {
56 icon = "lucide--bell";
57 title = `Identity created`;
58 } else if (diff.type === "identity_tombstoned") {
59 icon = "lucide--skull";
60 title = `Identity tombstoned`;
61 } else if (diff.type === "handle_added" || diff.type === "handle_removed") {
62 icon = "lucide--at-sign";
63 title = diff.type === "handle_added" ? "Alias added" : "Alias removed";
64 value = diff.handle;
65 } else if (diff.type === "handle_changed") {
66 icon = "lucide--at-sign";
67 title = "Alias updated";
68 value = `${diff.prev_handle} → ${diff.next_handle}`;
69 } else if (diff.type === "rotation_key_added" || diff.type === "rotation_key_removed") {
70 icon = "lucide--key-round";
71 title = diff.type === "rotation_key_added" ? "Rotation key added" : "Rotation key removed";
72 value = diff.rotation_key;
73 } else if (diff.type === "service_added" || diff.type === "service_removed") {
74 icon = "lucide--hard-drive";
75 title = `Service ${diff.service_id} ${diff.type === "service_added" ? "added" : "removed"}`;
76 value = `${diff.service_endpoint}`;
77 } else if (diff.type === "service_changed") {
78 icon = "lucide--hard-drive";
79 title = `Service ${diff.service_id} updated`;
80 value = `${diff.prev_service_endpoint} → ${diff.next_service_endpoint}`;
81 } else if (
82 diff.type === "verification_method_added" ||
83 diff.type === "verification_method_removed"
84 ) {
85 icon = "lucide--shield-check";
86 title = `Verification method ${diff.method_id} ${diff.type === "verification_method_added" ? "added" : "removed"}`;
87 value = `${diff.method_key}`;
88 } else if (diff.type === "verification_method_changed") {
89 icon = "lucide--shield-check";
90 title = `Verification method ${diff.method_id} updated`;
91 value = `${diff.prev_method_key} → ${diff.next_method_key}`;
92 }
93
94 return (
95 <div class="grid grid-cols-[min-content_1fr] items-center gap-x-1">
96 <div class={icon + ` iconify shrink-0`} />
97 <p
98 classList={{
99 "font-semibold": true,
100 "text-neutral-400 line-through dark:text-neutral-600": diff.orig.nullified,
101 }}
102 >
103 {title}
104 </p>
105 <div></div>
106 {value}
107 </div>
108 );
109 };
110
111 return (
112 <div class="flex w-full flex-col gap-2 wrap-anywhere">
113 <div class="flex items-center gap-1">
114 <div class="iconify lucide--filter" />
115 <div class="dark:shadow-dark-700 dark:bg-dark-300 flex w-fit items-center rounded-full border-[0.5px] border-neutral-300 bg-neutral-50 shadow-xs dark:border-neutral-700">
116 <FilterButton icon="iconify lucide--at-sign" event="handle" />
117 <FilterButton icon="iconify lucide--key-round" event="rotation_key" />
118 <FilterButton icon="iconify lucide--hard-drive" event="service" />
119 <FilterButton icon="iconify lucide--shield-check" event="verification_method" />
120 </div>
121 </div>
122 <div class="flex flex-col gap-1 text-sm">
123 <For each={plcOps()}>
124 {([entry, diffs]) => (
125 <Show
126 when={!activePlcEvent() || diffs.find((d) => d.type.startsWith(activePlcEvent()!))}
127 >
128 <div class="flex flex-col">
129 <span class="text-neutral-500 dark:text-neutral-400">
130 {localDateFromTimestamp(new Date(entry.createdAt).getTime())}
131 </span>
132 {diffs.map((diff) => (
133 <Show when={!activePlcEvent() || diff.type.startsWith(activePlcEvent()!)}>
134 <DiffItem diff={diff} />
135 </Show>
136 ))}
137 </div>
138 </Show>
139 )}
140 </For>
141 </div>
142 </div>
143 );
144};