this repo has no description
0
fork

Configure Feed

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

new logs style

Juliet 78f94294 142631ed

+222 -88
+222 -88
src/views/logs.tsx
··· 13 13 export const PlcLogView = (props: { did: string }) => { 14 14 const [activePlcEvent, setActivePlcEvent] = createSignal<PlcEvent | undefined>(); 15 15 16 + const shouldShowDiff = (diff: DiffEntry) => 17 + !activePlcEvent() || diff.type.startsWith(activePlcEvent()!); 18 + 19 + const shouldShowEntry = (diffs: DiffEntry[]) => 20 + !activePlcEvent() || diffs.some((d) => d.type.startsWith(activePlcEvent()!)); 21 + 16 22 const fetchPlcLogs = async () => { 17 23 const res = await fetch( 18 24 `${localStorage.plcDirectory ?? "https://plc.directory"}/${props.did}/log/audit`, ··· 31 37 const [plcOps] = 32 38 createResource<[IndexedEntry<CompatibleOperationOrTombstone>, DiffEntry[]][]>(fetchPlcLogs); 33 39 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 - ); 40 + const FilterButton = (props: { icon: string; event: PlcEvent; label: string }) => { 41 + const isActive = () => activePlcEvent() === props.event; 42 + const toggleFilter = () => setActivePlcEvent(isActive() ? undefined : props.event); 43 + 44 + return ( 45 + <button 46 + classList={{ 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": 50 + !isActive(), 51 + }} 52 + onclick={toggleFilter} 53 + > 54 + <span class={props.icon}></span> 55 + <span class="font-medium">{props.label}</span> 56 + </button> 57 + ); 58 + }; 48 59 49 60 const DiffItem = (props: { diff: DiffEntry }) => { 50 61 const diff = props.diff; 51 - let title = "Unknown log entry"; 52 - let icon = "lucide--circle-help"; 53 - let value = ""; 54 62 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 - } 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": 70 + return { 71 + icon: "lucide--at-sign", 72 + title: "Alias added", 73 + value: diff.handle, 74 + isAddition: true, 75 + }; 76 + case "handle_removed": 77 + return { 78 + icon: "lucide--at-sign", 79 + title: "Alias removed", 80 + value: diff.handle, 81 + isRemoval: true, 82 + }; 83 + case "handle_changed": 84 + return { 85 + icon: "lucide--at-sign", 86 + title: "Alias updated", 87 + oldValue: diff.prev_handle, 88 + newValue: diff.next_handle, 89 + }; 90 + case "rotation_key_added": 91 + return { 92 + icon: "lucide--key-round", 93 + title: "Rotation key added", 94 + value: diff.rotation_key, 95 + isAddition: true, 96 + }; 97 + case "rotation_key_removed": 98 + return { 99 + icon: "lucide--key-round", 100 + title: "Rotation key removed", 101 + value: diff.rotation_key, 102 + isRemoval: true, 103 + }; 104 + case "service_added": 105 + return { 106 + icon: "lucide--hard-drive", 107 + title: "Service added", 108 + badge: diff.service_id, 109 + value: diff.service_endpoint, 110 + isAddition: true, 111 + }; 112 + case "service_removed": 113 + return { 114 + icon: "lucide--hard-drive", 115 + title: "Service removed", 116 + badge: diff.service_id, 117 + value: diff.service_endpoint, 118 + isRemoval: true, 119 + }; 120 + case "service_changed": 121 + return { 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, 127 + }; 128 + case "verification_method_added": 129 + return { 130 + icon: "lucide--shield-check", 131 + title: "Verification method added", 132 + badge: diff.method_id, 133 + value: diff.method_key, 134 + isAddition: true, 135 + }; 136 + case "verification_method_removed": 137 + return { 138 + icon: "lucide--shield-check", 139 + title: "Verification method removed", 140 + badge: diff.method_id, 141 + value: diff.method_key, 142 + isRemoval: true, 143 + }; 144 + case "verification_method_changed": 145 + return { 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, 151 + }; 152 + default: 153 + return { icon: "lucide--circle-help", title: "Unknown log entry" }; 154 + } 155 + }; 156 + 157 + const config = getDiffConfig(); 158 + const { 159 + icon, 160 + title, 161 + value = "", 162 + oldValue = "", 163 + newValue = "", 164 + badge = "", 165 + isAddition = false, 166 + isRemoval = false, 167 + } = config; 93 168 94 169 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} 170 + <div 171 + classList={{ 172 + "grid grid-cols-[auto_1fr] gap-x-2 gap-y-1": true, 173 + "opacity-60": diff.orig.nullified, 174 + }} 175 + > 176 + <div class={`${icon} iconify shrink-0 self-center`} /> 177 + <div class="flex min-w-0 items-center gap-1.5"> 178 + <p 179 + classList={{ 180 + "font-semibold text-sm": true, 181 + "line-through": diff.orig.nullified, 182 + }} 183 + > 184 + {title} 185 + </p> 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"> 188 + #{badge} 189 + </span> 190 + </Show> 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"> 193 + Nullified 194 + </span> 195 + </Show> 196 + </div> 197 + <Show when={value}> 198 + <div></div> 199 + <div 200 + classList={{ 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, 205 + }} 206 + > 207 + <Show when={isAddition}> 208 + <span class="shrink-0">+</span> 209 + </Show> 210 + <Show when={isRemoval}> 211 + <span class="shrink-0">−</span> 212 + </Show> 213 + <span class="break-all">{value}</span> 214 + </div> 215 + </Show> 216 + <Show when={oldValue && newValue}> 217 + <div></div> 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> 222 + </div> 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> 226 + </div> 227 + </div> 228 + </Show> 107 229 </div> 108 230 ); 109 231 }; 110 232 111 233 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" /> 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> 239 + </div> 240 + <div class="flex flex-wrap gap-1 sm:gap-2"> 241 + <FilterButton icon="iconify lucide--at-sign" event="handle" label="Alias" /> 242 + <FilterButton 243 + icon="iconify lucide--key-round" 244 + event="rotation_key" 245 + label="Rotation Key" 246 + /> 247 + <FilterButton icon="iconify lucide--hard-drive" event="service" label="Service" /> 248 + <FilterButton 249 + icon="iconify lucide--shield-check" 250 + event="verification_method" 251 + label="Verification" 252 + /> 120 253 </div> 121 254 </div> 122 - <div class="flex flex-col gap-1 text-sm"> 255 + <div class="flex flex-col gap-3"> 123 256 <For each={plcOps()}> 124 257 {([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 - ))} 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())} 264 + </span> 265 + </div> 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} />} 269 + </For> 270 + </div> 137 271 </div> 138 272 </Show> 139 273 )}