diff --git a/ui/src/lib/components/k8s/Drawer/component.svelte b/ui/src/lib/components/k8s/Drawer/component.svelte index 699e9766..a0faca25 100644 --- a/ui/src/lib/components/k8s/Drawer/component.svelte +++ b/ui/src/lib/components/k8s/Drawer/component.svelte @@ -3,7 +3,7 @@ + + + Details +
+ {#each details.messages || [] as msg} +
{msg}
+ {/each} +
+
diff --git a/ui/src/routes/monitor/pepr/[[stream]]/(details)/denied-details/comonent.test.ts b/ui/src/routes/monitor/pepr/[[stream]]/(details)/denied-details/comonent.test.ts new file mode 100644 index 00000000..f2fbe300 --- /dev/null +++ b/ui/src/routes/monitor/pepr/[[stream]]/(details)/denied-details/comonent.test.ts @@ -0,0 +1,25 @@ +import { render } from '@testing-library/svelte' + +import type { SvelteComponent } from 'svelte' +import DeniedDetails from './DeniedDetails.svelte' + +// Mock the carbon-icons-svelte module + +const comp = vi.fn().mockImplementation(() => ({ + $$: { + on_mount: [], + on_destroy: [], + before_update: [], + after_update: [], + }, +})) as unknown as SvelteComponent + +describe('Denied Details', () => { + test('renders denied messages', () => { + const peprDetails = { component: comp, messages: ['Authorized: test', 'Found: test'] } + const { getByText } = render(DeniedDetails, { props: { details: peprDetails } }) + expect(getByText('Details')).toBeInTheDocument() + expect(getByText(peprDetails.messages[0])).toBeInTheDocument() + expect(getByText(peprDetails.messages[1])).toBeInTheDocument() + }) +}) diff --git a/ui/src/routes/monitor/pepr/[[stream]]/(details)/mutated-details/MutatedDetails.svelte b/ui/src/routes/monitor/pepr/[[stream]]/(details)/mutated-details/MutatedDetails.svelte new file mode 100644 index 00000000..9f7a906b --- /dev/null +++ b/ui/src/routes/monitor/pepr/[[stream]]/(details)/mutated-details/MutatedDetails.svelte @@ -0,0 +1,34 @@ + + + + + +
+ Details +
+ {#each Object.entries(details.operations || {}) as [key, ops], index} + {#if index > 0} +
+ {/if} +

{key}:

+ {#each ops as op} +

+ {op.path} + {#if op.value} + ={JSON.stringify(op.value)} + {/if} +

+ {/each} + {/each} +
+
+ + diff --git a/ui/src/routes/monitor/pepr/[[stream]]/(details)/mutated-details/comonent.test.ts b/ui/src/routes/monitor/pepr/[[stream]]/(details)/mutated-details/comonent.test.ts new file mode 100644 index 00000000..88785667 --- /dev/null +++ b/ui/src/routes/monitor/pepr/[[stream]]/(details)/mutated-details/comonent.test.ts @@ -0,0 +1,32 @@ +import { render, screen } from '@testing-library/svelte' + +import type { SvelteComponent } from 'svelte' +import MutatedDetails from './MutatedDetails.svelte' + +// Mock the carbon-icons-svelte module + +const comp = vi.fn().mockImplementation(() => ({ + $$: { + on_mount: [], + on_destroy: [], + before_update: [], + after_update: [], + }, +})) as unknown as SvelteComponent + +describe('Denied Details', () => { + test('renders exemption title', () => { + const peprDetails = { + component: comp, + operations: { + ADDED: [{ op: 'add', path: '/path', value: 'value' }], + REPLACED: [{ op: 'add', path: '/path', value: 'value' }], + REMOVED: [{ op: 'add', path: '/path', value: '' }], + }, + } + render(MutatedDetails, { props: { details: peprDetails } }) + + expect(screen.getByText('Details')).toBeInTheDocument() + //todo: figure out assertions for split up text + }) +}) diff --git a/ui/src/routes/monitor/pepr/[[stream]]/+page.svelte b/ui/src/routes/monitor/pepr/[[stream]]/+page.svelte index 213c1a86..98e35d06 100644 --- a/ui/src/routes/monitor/pepr/[[stream]]/+page.svelte +++ b/ui/src/routes/monitor/pepr/[[stream]]/+page.svelte @@ -10,6 +10,7 @@ import { page } from '$app/stores' import { type PeprEvent } from '$lib/types' import './page.postcss' + import { getDetails } from './helpers' let loaded = false let streamFilter = '' @@ -45,9 +46,9 @@ eventSource.onmessage = (e) => { try { const payload: PeprEvent = JSON.parse(e.data) - // The event type is the first word in the header payload.event = payload.header.split(' ')[0] + payload.details = getDetails(payload) // If this is a repeated event, update the count if (payload.repeated) { @@ -141,6 +142,7 @@ Event Resource + Details Count Timestamp @@ -158,6 +160,13 @@ {item.event} {item._name} + + {#if item.details} + + {:else} + - + {/if} + {item.count || 1} {item.ts} diff --git a/ui/src/routes/monitor/pepr/[[stream]]/helpers.ts b/ui/src/routes/monitor/pepr/[[stream]]/helpers.ts new file mode 100644 index 00000000..cb71de90 --- /dev/null +++ b/ui/src/routes/monitor/pepr/[[stream]]/helpers.ts @@ -0,0 +1,62 @@ +import type { PatchOperation, PeprDetails, PeprEvent } from '$lib/types' +import type { SvelteComponent } from 'svelte' +import DeniedDetails from './(details)/denied-details/DeniedDetails.svelte' +import MutatedDetails from './(details)/mutated-details/MutatedDetails.svelte' + +// Utility function to decode base64 +function decodeBase64(base64String: string) { + try { + return atob(base64String) + } catch (e) { + console.error('Failed to decode base64 string:', e) + return '' + } +} + +export function getDetails(payload: PeprEvent): PeprDetails | undefined { + if (!payload.res) { + return undefined + } + + if (payload.event === 'DENIED') { + if (payload.res) { + const status = payload.res.status as Record + const split = status.message.split(' Authorized: ') + + // No "Authorized" or "Found" in the message + if (split.length !== 2) { + return { component: DeniedDetails as unknown as SvelteComponent, messages: split } + } + + const authorized = `Authorized: ${split[1].split(' Found: ')[0]}` + const found = `Found: ${split[1].split(' Found:')[1]}` + return { component: DeniedDetails as unknown as SvelteComponent, messages: [authorized, found] } + } + } + + if (payload.event === 'MUTATED') { + if (payload.res.patch) { + const decodedPatch = decodeBase64(payload.res.patch as string) + const parsedPatch = JSON.parse(decodedPatch) + + const opMap: { [key: string]: string } = { + add: 'ADDED', + remove: 'REMOVED', + replace: 'REPLACED', + } + + // Group by operation type + const groups: { [key: string]: PatchOperation[] } = {} + for (const op of parsedPatch) { + if (!groups[opMap[op.op]]) { + groups[opMap[op.op]] = [] + } + groups[opMap[op.op]].push(op) + } + + return { component: MutatedDetails as unknown as SvelteComponent, operations: groups } + } + } + + return undefined +}