Skip to content

Commit 0018531

Browse files
committed
put back the expandable JSON thing and fix the height
1 parent f0ab97b commit 0018531

File tree

1 file changed

+118
-7
lines changed

1 file changed

+118
-7
lines changed

app/pages/system/AuditLog.tsx

Lines changed: 118 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { useInfiniteQuery, useIsFetching } from '@tanstack/react-query'
1010
import { useVirtualizer } from '@tanstack/react-virtual'
1111
import cn from 'classnames'
1212
import { differenceInMilliseconds } from 'date-fns'
13-
import { useMemo, useRef } from 'react'
13+
import { memo, useCallback, useMemo, useRef, useState } from 'react'
1414
import { match } from 'ts-pattern'
1515

1616
import { api } from '@oxide/api'
@@ -31,6 +31,88 @@ import { docLinks } from '~/util/links'
3131

3232
export const handle = { crumb: 'Audit Log' }
3333

34+
const Indent = ({ depth }: { depth: number }) => (
35+
<span className="inline-block" style={{ width: `${depth * 4 + 1}ch` }} />
36+
)
37+
38+
const Primitive = ({ value }: { value: null | boolean | number | string }) => (
39+
<span className="text-[var(--base-blue-600)]">
40+
{value === null ? 'null' : typeof value === 'string' ? `"${value}"` : String(value)}
41+
</span>
42+
)
43+
44+
// silly faux highlighting
45+
// avoids unnecessary import of a library and all that overhead
46+
const HighlightJSON = memo(({ jsonString }: { jsonString: string }) => {
47+
const renderValue = (
48+
value: null | boolean | number | string | object,
49+
depth = 0
50+
): React.ReactNode => {
51+
if (
52+
value === null ||
53+
typeof value === 'boolean' ||
54+
typeof value === 'number' ||
55+
typeof value === 'string'
56+
) {
57+
return <Primitive value={value} />
58+
}
59+
60+
if (Array.isArray(value)) {
61+
if (value.length === 0) return <span className="text-quaternary">[]</span>
62+
63+
return (
64+
<>
65+
<span className="text-quaternary">[</span>
66+
{'\n'}
67+
{value.map((item, index) => (
68+
<span key={index}>
69+
<Indent depth={depth + 1} />
70+
{renderValue(item, depth + 1)}
71+
{index < value.length - 1 && <span className="text-quaternary">,</span>}
72+
{'\n'}
73+
</span>
74+
))}
75+
<Indent depth={depth} />
76+
<span className="text-quaternary">]</span>
77+
</>
78+
)
79+
}
80+
81+
if (typeof value === 'object') {
82+
const entries = Object.entries(value)
83+
if (entries.length === 0) return <span className="text-quaternary">{'{}'}</span>
84+
85+
return (
86+
<>
87+
<span className="text-quaternary">{'{'}</span>
88+
{'\n'}
89+
{entries.map(([key, val], index) => (
90+
<span key={key}>
91+
<Indent depth={depth + 1} />
92+
<span className="text-default">{key}</span>
93+
<span className="text-quaternary">: </span>
94+
{renderValue(val, depth + 1)}
95+
{index < entries.length - 1 && <span className="text-quaternary">,</span>}
96+
{'\n'}
97+
</span>
98+
))}
99+
<Indent depth={depth} />
100+
<span className="text-quaternary">{'}'}</span>
101+
</>
102+
)
103+
}
104+
105+
return String(value)
106+
}
107+
108+
try {
109+
const parsed = JSON.parse(jsonString)
110+
return <>{renderValue(parsed)}</>
111+
} catch {
112+
return <>{jsonString}</>
113+
}
114+
})
115+
34116
// todo
35117
// might want to still render the items in case of error
36118
const ErrorState = () => {
@@ -48,10 +130,11 @@ const colWidths = {
48130

49131
const HeaderCell = classed.div`text-mono-sm text-tertiary`
50132

51-
// for virtualizer
52-
const estimateSize = () => 36
133+
const EXPANDED_HEIGHT = 288 // h-72 * 4
53134

54135
export default function SiloAuditLogsPage() {
136+
const [expandedItem, setExpandedItem] = useState<string | null>(null)
137+
55138
// pass refetch interval to this to keep the date up to date
56139
const { preset, startTime, endTime, dateTimeRangePicker, onRangeChange } =
57140
useDateTimeRangePicker({
@@ -104,10 +187,23 @@ export default function SiloAuditLogsPage() {
104187
const rowVirtualizer = useVirtualizer({
105188
count: allItems.length,
106189
getScrollElement: () => document.querySelector('#scroll-container'),
107-
estimateSize,
190+
estimateSize: useCallback(
191+
(index) => {
192+
return expandedItem === index.toString() ? 36 + EXPANDED_HEIGHT : 36
193+
},
194+
[expandedItem]
195+
),
108196
overscan: 20,
109197
})
110198

199+
const handleToggle = useCallback(
200+
(index: string | null) => {
201+
setExpandedItem(index)
202+
rowVirtualizer.measure()
203+
},
204+
[rowVirtualizer]
205+
)
206+
111207
const logTable = (
112208
<>
113209
<div
@@ -118,6 +214,8 @@ export default function SiloAuditLogsPage() {
118214
>
119215
{rowVirtualizer.getVirtualItems().map((virtualRow) => {
120216
const log = allItems[virtualRow.index]
217+
const isExpanded = expandedItem === virtualRow.index.toString()
218+
const jsonString = JSON.stringify(log, null, 2)
121219

122220
const [userId, siloId] = match(log.actor)
123221
.with({ kind: 'silo_user' }, (actor) => [actor.siloUserId, actor.siloId])
@@ -134,12 +232,18 @@ export default function SiloAuditLogsPage() {
134232
transform: `translateY(${virtualRow.start}px)`,
135233
}}
136234
>
137-
<div
235+
<button
138236
className={cn(
139-
'grid h-9 w-full items-center gap-8 px-[var(--content-gutter)] text-left text-sans-md border-secondary',
237+
'grid h-9 w-full cursor-pointer items-center gap-8 px-[var(--content-gutter)] text-left text-sans-md border-secondary',
238+
isExpanded ? 'bg-raise' : 'hover:bg-raise',
140239
virtualRow.index !== 0 && 'border-t'
141240
)}
142241
style={colWidths}
242+
onClick={() => {
243+
const newValue = isExpanded ? null : virtualRow.index.toString()
244+
handleToggle(newValue)
245+
}}
246+
type="button"
143247
>
144248
{/* TODO: might be especially useful here to get the original UTC timestamp in a tooltip */}
145249
<div className="overflow-hidden whitespace-nowrap text-mono-sm">
@@ -188,7 +292,14 @@ export default function SiloAuditLogsPage() {
188292
{differenceInMilliseconds(new Date(log.timeCompleted), log.timeStarted)}
189293
ms
190294
</div>
191-
</div>
295+
</button>
296+
{isExpanded && (
297+
<div className="h-72 border-t px-[var(--content-gutter)] py-3 border-secondary">
298+
<pre className="h-full overflow-auto border-l pl-4 text-mono-code border-secondary">
299+
<HighlightJSON jsonString={jsonString} />
300+
</pre>
301+
</div>
302+
)}
192303
</div>
193304
)
194305
})}

0 commit comments

Comments
 (0)