From c2ddd5a933afd97f3973b820a9bb2b6583dac152 Mon Sep 17 00:00:00 2001 From: vegard Date: Fri, 20 Mar 2026 02:03:42 +0000 Subject: [PATCH] Node Explorer trait + adm.synops.no viser arbeidsflaten MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ny trait: NodeExplorerTrait — søk og utforsk noder med edges. Split-visning: nodeliste til venstre, detaljer til høyre. Filtrer på node_kind, søk i tittel/innhold/ID. Klikk edges for å navigere i grafen. adm.synops.no setter isAdminHost flag via hooks/layout. Registrert i TRAIT_PANEL_INFO som 'node_explorer'. Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend/src/hooks.server.ts | 6 +- .../traits/NodeExplorerTrait.svelte | 303 ++++++++++++++++++ frontend/src/lib/workspace/types.ts | 1 + frontend/src/routes/+layout.server.ts | 3 +- frontend/src/routes/+page.svelte | 8 +- 5 files changed, 315 insertions(+), 6 deletions(-) create mode 100644 frontend/src/lib/components/traits/NodeExplorerTrait.svelte diff --git a/frontend/src/hooks.server.ts b/frontend/src/hooks.server.ts index 785d821..033b50b 100644 --- a/frontend/src/hooks.server.ts +++ b/frontend/src/hooks.server.ts @@ -16,11 +16,9 @@ const authorizationHandle: Handle = async ({ event, resolve }) => { throw redirect(303, '/auth/signin'); } - // adm.synops.no → redirect til admin-arbeidsflaten + // Sett isAdmin-flag basert på hostname (brukes av +page.svelte) const host = event.url.hostname; - if (host === 'adm.synops.no' && path === '/') { - throw redirect(303, '/admin'); - } + event.locals.isAdminHost = host === 'adm.synops.no'; return resolve(event); }; diff --git a/frontend/src/lib/components/traits/NodeExplorerTrait.svelte b/frontend/src/lib/components/traits/NodeExplorerTrait.svelte new file mode 100644 index 0000000..d2e9ea3 --- /dev/null +++ b/frontend/src/lib/components/traits/NodeExplorerTrait.svelte @@ -0,0 +1,303 @@ + + +
+ +
+ + +
+ +
+ +
+ {#each results as node (node.id)} + + {/each} + {#if results.length === 0} +
Ingen noder funnet
+ {/if} + {#if results.length >= limit} + + {/if} +
+ + + {#if selectedNode} +
+
+ {selectedNode.nodeKind} + {selectedNode.id} +
+ +

{selectedNode.title || 'Uten tittel'}

+ +
+
Opprettet: {formatDate(selectedNode.createdAt)}
+
Av: {selectedNode.createdBy?.slice(0, 8) ?? '—'}
+
Synlighet: {selectedNode.visibility}
+
+ + {#if selectedNode.content} +
+
Innhold
+
{truncate(selectedNode.content, 500)}
+
+ {/if} + + {#if selectedNode.metadata && selectedNode.metadata !== '{}'} +
+
Metadata
+
{parseMetadata(selectedNode.metadata)}
+
+ {/if} + + + {#if selectedEdgesOut.length > 0} +
+
Edges ut ({selectedEdgesOut.length})
+ {#each selectedEdgesOut as edge (edge.id)} + + {/each} +
+ {/if} + + + {#if selectedEdgesIn.length > 0} +
+
Edges inn ({selectedEdgesIn.length})
+ {#each selectedEdgesIn as edge (edge.id)} + + {/each} +
+ {/if} +
+ {:else} +
+ Velg en node for å se detaljer +
+ {/if} +
+
+ + diff --git a/frontend/src/lib/workspace/types.ts b/frontend/src/lib/workspace/types.ts index 17e982d..6f0a34b 100644 --- a/frontend/src/lib/workspace/types.ts +++ b/frontend/src/lib/workspace/types.ts @@ -57,6 +57,7 @@ export const TRAIT_PANEL_INFO: Record = { orchestration: { title: 'Orkestrering', icon: '⚡', defaultWidth: 550, defaultHeight: 500 }, mindmap: { title: 'Tankekart', icon: '🧠', defaultWidth: 600, defaultHeight: 500 }, ai: { title: 'AI-verktøy', icon: '🤖', defaultWidth: 420, defaultHeight: 500 }, + node_explorer: { title: 'Nodeutforsker', icon: '🔍', defaultWidth: 600, defaultHeight: 500 }, usage: { title: 'Ressursforbruk', icon: '📊', defaultWidth: 380, defaultHeight: 350 }, storyboard: { title: 'Storyboard', icon: '🎬', defaultWidth: 500, defaultHeight: 450 }, }; diff --git a/frontend/src/routes/+layout.server.ts b/frontend/src/routes/+layout.server.ts index a439011..6232a7a 100644 --- a/frontend/src/routes/+layout.server.ts +++ b/frontend/src/routes/+layout.server.ts @@ -2,6 +2,7 @@ import type { LayoutServerLoad } from './$types'; export const load: LayoutServerLoad = async (event) => { return { - session: await event.locals.auth() + session: await event.locals.auth(), + isAdminHost: (event.locals as Record).isAdminHost === true, }; }; diff --git a/frontend/src/routes/+page.svelte b/frontend/src/routes/+page.svelte index 25e7f38..4ee3e70 100644 --- a/frontend/src/routes/+page.svelte +++ b/frontend/src/routes/+page.svelte @@ -42,6 +42,7 @@ import GenericTrait from '$lib/components/traits/GenericTrait.svelte'; import AiToolPanel from '$lib/components/AiToolPanel.svelte'; import NodeUsage from '$lib/components/NodeUsage.svelte'; + import NodeExplorerTrait from '$lib/components/traits/NodeExplorerTrait.svelte'; import { createBlockReceiver, executeTransfer, resolveTransferMode, type DragPayload } from '$lib/transfer'; import type { BlockReceiver } from '$lib/components/blockshell/types'; @@ -50,6 +51,7 @@ const nodeId = $derived(session?.nodeId as string | undefined); const accessToken = $derived(session?.accessToken as string | undefined); const connected = $derived(connectionState.current === 'connected'); + const isAdminHost = $derived(($page.data as Record).isAdminHost === true); // ========================================================================= // Workspace node (fetched from backend) @@ -300,7 +302,7 @@ const knownTraits = new Set([ 'editor', 'chat', 'kanban', 'podcast', 'publishing', 'rss', 'calendar', 'recording', 'transcription', 'studio', 'mixer', 'mindmap', - 'ai', 'usage' + 'ai', 'usage', 'node_explorer' ]); // ========================================================================= @@ -453,6 +455,8 @@ {#if nodeId && accessToken} {/if} + {:else if panel.trait === 'node_explorer'} + {/if} {:else} @@ -519,6 +523,8 @@ {#if nodeId && accessToken} {/if} + {:else if trait === 'node_explorer'} + {/if} {:else}