diff --git a/dashboard/src/lib/api.ts b/dashboard/src/lib/api.ts index 31ed8a1..770f609 100644 --- a/dashboard/src/lib/api.ts +++ b/dashboard/src/lib/api.ts @@ -349,6 +349,44 @@ export interface CreatePubsubTriggerInput { retry_base_ms?: number; } +// F-U-001: dashboard create surfaces for the four data-plane triggers. +export interface CreateKvTriggerInput { + script_id: string; + collection_glob: string; + ops?: Array<'insert' | 'update' | 'delete'>; + dispatch_mode?: TriggerDispatchMode; + retry_max_attempts?: number; + retry_backoff?: 'exponential' | 'linear' | 'constant'; + retry_base_ms?: number; +} + +export interface CreateDocsTriggerInput { + script_id: string; + collection_glob: string; + ops?: Array<'create' | 'update' | 'delete'>; + dispatch_mode?: TriggerDispatchMode; + retry_max_attempts?: number; + retry_backoff?: 'exponential' | 'linear' | 'constant'; + retry_base_ms?: number; +} + +export interface CreateFilesTriggerInput { + script_id: string; + collection_glob: string; + ops?: Array<'create' | 'update' | 'delete'>; + dispatch_mode?: TriggerDispatchMode; + retry_max_attempts?: number; + retry_backoff?: 'exponential' | 'linear' | 'constant'; + retry_base_ms?: number; +} + +export interface CreateDeadLetterTriggerInput { + script_id: string; + source_filter?: string | null; + trigger_id_filter?: string | null; + script_id_filter?: string | null; +} + // v1.1.9 — queue:receive trigger. export interface CreateQueueTriggerInput { script_id: string; @@ -793,6 +831,28 @@ export const api = { `/api/v1/admin/apps/${encodeURIComponent(idOrSlug)}/triggers/queue`, { method: 'POST', body: JSON.stringify(input) } ), + // F-U-001: matching frontend methods for the four data-plane + // trigger kinds the backend has long supported. + createKv: (idOrSlug: string, input: CreateKvTriggerInput) => + adminRequest( + `/api/v1/admin/apps/${encodeURIComponent(idOrSlug)}/triggers/kv`, + { method: 'POST', body: JSON.stringify(input) } + ), + createDocs: (idOrSlug: string, input: CreateDocsTriggerInput) => + adminRequest( + `/api/v1/admin/apps/${encodeURIComponent(idOrSlug)}/triggers/docs`, + { method: 'POST', body: JSON.stringify(input) } + ), + createFiles: (idOrSlug: string, input: CreateFilesTriggerInput) => + adminRequest( + `/api/v1/admin/apps/${encodeURIComponent(idOrSlug)}/triggers/files`, + { method: 'POST', body: JSON.stringify(input) } + ), + createDeadLetter: (idOrSlug: string, input: CreateDeadLetterTriggerInput) => + adminRequest( + `/api/v1/admin/apps/${encodeURIComponent(idOrSlug)}/triggers/dead_letter`, + { method: 'POST', body: JSON.stringify(input) } + ), remove: (idOrSlug: string, triggerId: string) => adminRequest( `/api/v1/admin/apps/${encodeURIComponent(idOrSlug)}/triggers/${triggerId}`, diff --git a/dashboard/src/routes/apps/[slug]/+page.svelte b/dashboard/src/routes/apps/[slug]/+page.svelte index b54655f..6021538 100644 --- a/dashboard/src/routes/apps/[slug]/+page.svelte +++ b/dashboard/src/routes/apps/[slug]/+page.svelte @@ -138,6 +138,40 @@ let createQueueMaxAttempts = $state(3); let creatingQueue = $state(false); let createQueueError = $state(null); + // F-U-001: KV / Docs / Files / Dead-letter trigger forms. + let createKvScriptId = $state(''); + let createKvCollectionGlob = $state('*'); + let createKvOps = $state<{ insert: boolean; update: boolean; delete: boolean }>({ + insert: true, + update: true, + delete: true + }); + let creatingKv = $state(false); + let createKvError = $state(null); + let createDocsScriptId = $state(''); + let createDocsCollectionGlob = $state('*'); + let createDocsOps = $state<{ create: boolean; update: boolean; delete: boolean }>({ + create: true, + update: true, + delete: true + }); + let creatingDocs = $state(false); + let createDocsError = $state(null); + let createFilesScriptId = $state(''); + let createFilesCollectionGlob = $state('*'); + let createFilesOps = $state<{ create: boolean; update: boolean; delete: boolean }>({ + create: true, + update: true, + delete: true + }); + let creatingFiles = $state(false); + let createFilesError = $state(null); + let createDeadLetterScriptId = $state(''); + let createDeadLetterSourceFilter = $state(''); + let createDeadLetterTriggerIdFilter = $state(''); + let createDeadLetterScriptIdFilter = $state(''); + let creatingDeadLetter = $state(false); + let createDeadLetterError = $state(null); let triggerToRemove = $state(null); let removingTrigger = $state(false); // Endpoint scripts only — modules can't be trigger targets. @@ -215,6 +249,109 @@ } } + // F-U-001: handlers for kv / docs / files / dead-letter trigger creation. + function selectedOps>(ops: T): Array { + return (Object.keys(ops) as Array).filter((k) => ops[k]); + } + + async function submitCreateKv(e: SubmitEvent) { + e.preventDefault(); + if (!app) return; + creatingKv = true; + createKvError = null; + try { + await api.triggers.createKv(app.id, { + script_id: createKvScriptId, + collection_glob: createKvCollectionGlob.trim(), + ops: selectedOps(createKvOps) as Array<'insert' | 'update' | 'delete'> + }); + createKvScriptId = ''; + createKvCollectionGlob = '*'; + await loadTriggers(app.id); + } catch (err) { + createKvError = + err instanceof ApiError ? err.message : err instanceof Error ? err.message : String(err); + } finally { + creatingKv = false; + } + } + + async function submitCreateDocs(e: SubmitEvent) { + e.preventDefault(); + if (!app) return; + creatingDocs = true; + createDocsError = null; + try { + await api.triggers.createDocs(app.id, { + script_id: createDocsScriptId, + collection_glob: createDocsCollectionGlob.trim(), + ops: selectedOps(createDocsOps) as Array<'create' | 'update' | 'delete'> + }); + createDocsScriptId = ''; + createDocsCollectionGlob = '*'; + await loadTriggers(app.id); + } catch (err) { + createDocsError = + err instanceof ApiError ? err.message : err instanceof Error ? err.message : String(err); + } finally { + creatingDocs = false; + } + } + + async function submitCreateFiles(e: SubmitEvent) { + e.preventDefault(); + if (!app) return; + creatingFiles = true; + createFilesError = null; + try { + await api.triggers.createFiles(app.id, { + script_id: createFilesScriptId, + collection_glob: createFilesCollectionGlob.trim(), + ops: selectedOps(createFilesOps) as Array<'create' | 'update' | 'delete'> + }); + createFilesScriptId = ''; + createFilesCollectionGlob = '*'; + await loadTriggers(app.id); + } catch (err) { + createFilesError = + err instanceof ApiError ? err.message : err instanceof Error ? err.message : String(err); + } finally { + creatingFiles = false; + } + } + + async function submitCreateDeadLetter(e: SubmitEvent) { + e.preventDefault(); + if (!app) return; + creatingDeadLetter = true; + createDeadLetterError = null; + try { + await api.triggers.createDeadLetter(app.id, { + script_id: createDeadLetterScriptId, + source_filter: + createDeadLetterSourceFilter.trim() === '' ? null : createDeadLetterSourceFilter.trim(), + trigger_id_filter: + createDeadLetterTriggerIdFilter.trim() === '' + ? null + : createDeadLetterTriggerIdFilter.trim(), + script_id_filter: + createDeadLetterScriptIdFilter.trim() === '' + ? null + : createDeadLetterScriptIdFilter.trim() + }); + createDeadLetterScriptId = ''; + createDeadLetterSourceFilter = ''; + createDeadLetterTriggerIdFilter = ''; + createDeadLetterScriptIdFilter = ''; + await loadTriggers(app.id); + } catch (err) { + createDeadLetterError = + err instanceof ApiError ? err.message : err instanceof Error ? err.message : String(err); + } finally { + creatingDeadLetter = false; + } + } + async function submitCreateQueue(e: SubmitEvent) { e.preventDefault(); if (!app) return; @@ -1295,6 +1432,164 @@ + +

Create kv:event trigger

+
+
+ + +
+
+ Ops: + + + +
+ {#if createKvError}
{createKvError}
{/if} +
+ +
+
+ +

Create docs:event trigger

+
+
+ + +
+
+ Ops: + + + +
+ {#if createDocsError}
{createDocsError}
{/if} +
+ +
+
+ +

Create files:event trigger

+
+
+ + +
+
+ Ops: + + + +
+ {#if createFilesError}
{createFilesError}
{/if} +
+ +
+
+ +

Create dead_letter trigger

+

+ Fires when any other trigger or route dead-letters. All filter fields are optional; + leaving them blank routes every dead-letter to this script. +

+
+
+ + +
+
+ + +
+ {#if createDeadLetterError}
{createDeadLetterError}
{/if} +
+ +
+
+ {#if triggers.length === 0}

No triggers in this app yet.

{:else}