this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at e5aa42cfc8d36e7e1111759921129147b4e4e05a 144 lines 5.7 kB view raw
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};