···
export const PlcLogView = (props: { did: string }) => {
const [activePlcEvent, setActivePlcEvent] = createSignal<PlcEvent | undefined>();
16
+
const shouldShowDiff = (diff: DiffEntry) =>
17
+
!activePlcEvent() || diff.type.startsWith(activePlcEvent()!);
19
+
const shouldShowEntry = (diffs: DiffEntry[]) =>
20
+
!activePlcEvent() || diffs.some((d) => d.type.startsWith(activePlcEvent()!));
const fetchPlcLogs = async () => {
`${localStorage.plcDirectory ?? "https://plc.directory"}/${props.did}/log/audit`,
···
createResource<[IndexedEntry<CompatibleOperationOrTombstone>, DiffEntry[]][]>(fetchPlcLogs);
34
-
const FilterButton = (props: { icon: string; event: PlcEvent }) => (
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,
41
-
onclick={() => setActivePlcEvent(activePlcEvent() === props.event ? undefined : props.event)}
44
-
class={`${props.icon} ${activePlcEvent() === props.event ? "text-neutral-200 dark:text-neutral-900" : ""}`}
40
+
const FilterButton = (props: { icon: string; event: PlcEvent; label: string }) => {
41
+
const isActive = () => activePlcEvent() === props.event;
42
+
const toggleFilter = () => setActivePlcEvent(isActive() ? undefined : props.event);
47
+
"flex items-center gap-1 sm:gap-1.5 rounded-lg px-2 py-1.5 text-xs sm:text-sm transition-colors": true,
48
+
"bg-neutral-700 text-white dark:bg-neutral-200 dark:text-neutral-900": isActive(),
49
+
"bg-neutral-200 text-neutral-700 hover:bg-neutral-300 dark:bg-neutral-700 dark:text-neutral-300 dark:hover:bg-neutral-600":
52
+
onclick={toggleFilter}
54
+
<span class={props.icon}></span>
55
+
<span class="font-medium">{props.label}</span>
const DiffItem = (props: { diff: DiffEntry }) => {
51
-
let title = "Unknown log entry";
52
-
let icon = "lucide--circle-help";
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}`;
82
-
diff.type === "verification_method_added" ||
83
-
diff.type === "verification_method_removed"
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}`;
63
+
const getDiffConfig = () => {
64
+
switch (diff.type) {
65
+
case "identity_created":
66
+
return { icon: "lucide--bell", title: "Identity created" };
67
+
case "identity_tombstoned":
68
+
return { icon: "lucide--skull", title: "Identity tombstoned" };
69
+
case "handle_added":
71
+
icon: "lucide--at-sign",
72
+
title: "Alias added",
76
+
case "handle_removed":
78
+
icon: "lucide--at-sign",
79
+
title: "Alias removed",
83
+
case "handle_changed":
85
+
icon: "lucide--at-sign",
86
+
title: "Alias updated",
87
+
oldValue: diff.prev_handle,
88
+
newValue: diff.next_handle,
90
+
case "rotation_key_added":
92
+
icon: "lucide--key-round",
93
+
title: "Rotation key added",
94
+
value: diff.rotation_key,
97
+
case "rotation_key_removed":
99
+
icon: "lucide--key-round",
100
+
title: "Rotation key removed",
101
+
value: diff.rotation_key,
104
+
case "service_added":
106
+
icon: "lucide--hard-drive",
107
+
title: "Service added",
108
+
badge: diff.service_id,
109
+
value: diff.service_endpoint,
112
+
case "service_removed":
114
+
icon: "lucide--hard-drive",
115
+
title: "Service removed",
116
+
badge: diff.service_id,
117
+
value: diff.service_endpoint,
120
+
case "service_changed":
122
+
icon: "lucide--hard-drive",
123
+
title: "Service updated",
124
+
badge: diff.service_id,
125
+
oldValue: diff.prev_service_endpoint,
126
+
newValue: diff.next_service_endpoint,
128
+
case "verification_method_added":
130
+
icon: "lucide--shield-check",
131
+
title: "Verification method added",
132
+
badge: diff.method_id,
133
+
value: diff.method_key,
136
+
case "verification_method_removed":
138
+
icon: "lucide--shield-check",
139
+
title: "Verification method removed",
140
+
badge: diff.method_id,
141
+
value: diff.method_key,
144
+
case "verification_method_changed":
146
+
icon: "lucide--shield-check",
147
+
title: "Verification method updated",
148
+
badge: diff.method_id,
149
+
oldValue: diff.prev_method_key,
150
+
newValue: diff.next_method_key,
153
+
return { icon: "lucide--circle-help", title: "Unknown log entry" };
157
+
const config = getDiffConfig();
165
+
isAddition = false,
95
-
<div class="grid grid-cols-[min-content_1fr] items-center gap-x-1">
96
-
<div class={icon + ` iconify shrink-0`} />
99
-
"font-semibold": true,
100
-
"text-neutral-400 line-through dark:text-neutral-600": diff.orig.nullified,
172
+
"grid grid-cols-[auto_1fr] gap-x-2 gap-y-1": true,
173
+
"opacity-60": diff.orig.nullified,
176
+
<div class={`${icon} iconify shrink-0 self-center`} />
177
+
<div class="flex min-w-0 items-center gap-1.5">
180
+
"font-semibold text-sm": true,
181
+
"line-through": diff.orig.nullified,
186
+
<Show when={badge}>
187
+
<span class="shrink-0 rounded bg-neutral-200 px-1.5 py-0.5 text-xs font-medium dark:bg-neutral-700">
191
+
<Show when={diff.orig.nullified}>
192
+
<span class="ml-auto rounded bg-neutral-200 px-2 py-0.5 text-xs font-medium dark:bg-neutral-700">
197
+
<Show when={value}>
201
+
"text-sm break-all flex items-start gap-2 min-w-0": true,
202
+
"text-green-600 dark:text-green-400": isAddition,
203
+
"text-red-600 dark:text-red-400": isRemoval,
204
+
"text-neutral-600 dark:text-neutral-400": !isAddition && !isRemoval,
207
+
<Show when={isAddition}>
208
+
<span class="shrink-0">+</span>
210
+
<Show when={isRemoval}>
211
+
<span class="shrink-0">−</span>
213
+
<span class="break-all">{value}</span>
216
+
<Show when={oldValue && newValue}>
218
+
<div class="flex min-w-0 flex-col gap-1 text-sm">
219
+
<div class="flex items-start gap-2 text-red-600 dark:text-red-400">
220
+
<span class="shrink-0">−</span>
221
+
<span class="break-all">{oldValue}</span>
223
+
<div class="flex items-start gap-2 text-green-600 dark:text-green-400">
224
+
<span class="shrink-0">+</span>
225
+
<span class="break-all">{newValue}</span>
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" />
234
+
<div class="flex w-full flex-col gap-4 wrap-anywhere">
235
+
<div class="flex flex-col gap-2">
236
+
<div class="flex items-center gap-1.5 text-sm">
237
+
<div class="iconify lucide--filter" />
238
+
<p class="font-semibold">Filter by type</p>
240
+
<div class="flex flex-wrap gap-1 sm:gap-2">
241
+
<FilterButton icon="iconify lucide--at-sign" event="handle" label="Alias" />
243
+
icon="iconify lucide--key-round"
244
+
event="rotation_key"
245
+
label="Rotation Key"
247
+
<FilterButton icon="iconify lucide--hard-drive" event="service" label="Service" />
249
+
icon="iconify lucide--shield-check"
250
+
event="verification_method"
251
+
label="Verification"
122
-
<div class="flex flex-col gap-1 text-sm">
255
+
<div class="flex flex-col gap-3">
126
-
when={!activePlcEvent() || diffs.find((d) => d.type.startsWith(activePlcEvent()!))}
128
-
<div class="flex flex-col">
129
-
<span class="text-neutral-500 dark:text-neutral-400">
130
-
{localDateFromTimestamp(new Date(entry.createdAt).getTime())}
132
-
{diffs.map((diff) => (
133
-
<Show when={!activePlcEvent() || diff.type.startsWith(activePlcEvent()!)}>
134
-
<DiffItem diff={diff} />
258
+
<Show when={shouldShowEntry(diffs)}>
259
+
<div class="flex flex-col gap-2">
260
+
<div class="flex items-center gap-2 text-sm">
261
+
<div class="iconify lucide--clock text-neutral-600 dark:text-neutral-400" />
262
+
<span class="font-medium text-neutral-700 dark:text-neutral-300">
263
+
{localDateFromTimestamp(new Date(entry.createdAt).getTime())}
266
+
<div class="flex flex-col gap-2 rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-3 text-sm dark:border-neutral-700 dark:bg-neutral-800">
267
+
<For each={diffs.filter(shouldShowDiff)}>
268
+
{(diff) => <DiffItem diff={diff} />}