mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-21 00:02:30 +00:00
fix: show a page item tab whose item is gone instead of mounting its editor
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
ca44af5f5a
commit
09ab9b8e6a
@@ -2,11 +2,19 @@
|
||||
import { setContext, untrack } from 'svelte'
|
||||
import { Loader2 } from 'lucide-svelte'
|
||||
import { enterpriseLicense } from '$lib/stores'
|
||||
import { ResourceService } from '$lib/gen'
|
||||
import { resource } from 'runed'
|
||||
import { Button } from '$lib/components/common'
|
||||
import { lookupPageItem } from './pageItemLookup'
|
||||
import VariableEditor from '$lib/components/VariableEditor.svelte'
|
||||
import ResourceEditorDrawer from '$lib/components/ResourceEditorDrawer.svelte'
|
||||
import { setOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
|
||||
import { TRIGGER_PAGES, type PageItemRef, type TriggerKind } from './previewPaths'
|
||||
import {
|
||||
pageItemKindLabel,
|
||||
pageItemUrl,
|
||||
TRIGGER_PAGES,
|
||||
type PageItemRef,
|
||||
type TriggerKind
|
||||
} from './previewPaths'
|
||||
import type { SessionRuntime } from './sessionRuntime.svelte'
|
||||
|
||||
let {
|
||||
@@ -70,29 +78,35 @@
|
||||
const t = triggerEditor
|
||||
untrack(() => {
|
||||
v?.editVariable(path)
|
||||
if (r) void openResource(r, path)
|
||||
// An agent opens on JSON: the generic form would render its configuration field by
|
||||
// field and write a default into each one the value leaves out, drafting just by opening.
|
||||
void r?.initEdit(path, { json: lookup.current?.resourceType === 'ai_agent' })
|
||||
void t?.openEdit(path, false)
|
||||
})
|
||||
})
|
||||
|
||||
// An agent is edited as JSON here: the generic form would render its configuration field by
|
||||
// field and write a default into each one the value leaves out, drafting just by opening.
|
||||
async function openResource(editor: ResourceEditorDrawer, path: string) {
|
||||
let resourceType: string | undefined
|
||||
try {
|
||||
resourceType = (await ResourceService.getResource({ workspace: workspaceId, path }))
|
||||
.resource_type
|
||||
} catch {
|
||||
// A draft-only resource has no row yet; the editor reads the draft itself.
|
||||
}
|
||||
if (editor !== resourceEditor) return
|
||||
await editor.initEdit(path, { json: resourceType === 'ai_agent' })
|
||||
}
|
||||
|
||||
// A save can move the item; the tab follows it, which remounts the editor on what was
|
||||
// written. Saving in place remounts it too: the editors keep the pre-save baseline, and
|
||||
// their drawers only ever relied on being closed after a save.
|
||||
let savedNonce = $state(0)
|
||||
|
||||
// Whether the item still exists, deployed or as a draft, read again whenever the editor
|
||||
// remounts: a delete or a discarded draft reloads the tab too, and the editors have no state
|
||||
// of their own for an item that is gone. A lookup that fails otherwise is left to the editor,
|
||||
// which reports its own load errors.
|
||||
const lookup = resource(
|
||||
() => ({ ref: item, workspace: workspaceId, reload: `${reloadNonce}:${savedNonce}` }),
|
||||
({ ref, workspace }) =>
|
||||
lookupPageItem(ref, workspace).then(
|
||||
(found) => ({ exists: !!found, resourceType: found?.resource_type as string | undefined }),
|
||||
() => ({ exists: true, resourceType: undefined })
|
||||
)
|
||||
)
|
||||
|
||||
function closeTab() {
|
||||
const tab = runtime.previewTabs.tabs.find((t) => t.url === pageItemUrl(item))
|
||||
if (tab) runtime.previewTabs.close(tab.id)
|
||||
}
|
||||
function onSaved(path: string | undefined) {
|
||||
if (path && path !== item.path) {
|
||||
runtime.previewTabs.retargetPageItem(item, { ...item, path })
|
||||
@@ -111,6 +125,16 @@
|
||||
<div class="flex h-full min-h-0 flex-col">
|
||||
{#if eeLocked}
|
||||
<div class="p-4 text-sm text-secondary">This trigger requires an enterprise license.</div>
|
||||
{:else if lookup.loading || !lookup.current}
|
||||
{@render loading()}
|
||||
{:else if !lookup.current.exists}
|
||||
<div class="flex flex-col items-start gap-3 p-4 text-sm text-secondary">
|
||||
<p>
|
||||
{pageItemKindLabel(item)} <span class="font-mono">{item.path}</span> no longer exists in this
|
||||
workspace.
|
||||
</p>
|
||||
<Button variant="default" unifiedSize="sm" onClick={closeTab}>Close tab</Button>
|
||||
</div>
|
||||
{:else}
|
||||
{#key `${item.kind}:${triggerKey}:${item.path}:${workspaceId}:${reloadNonce}:${savedNonce}`}
|
||||
{#if item.kind === 'variable'}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import {
|
||||
AmqpTriggerService,
|
||||
ApiError,
|
||||
AzureTriggerService,
|
||||
EmailTriggerService,
|
||||
GcpTriggerService,
|
||||
HttpTriggerService,
|
||||
KafkaTriggerService,
|
||||
MqttTriggerService,
|
||||
NatsTriggerService,
|
||||
PostgresTriggerService,
|
||||
ResourceService,
|
||||
ScheduleService,
|
||||
SqsTriggerService,
|
||||
VariableService,
|
||||
WebsocketTriggerService
|
||||
} from '$lib/gen'
|
||||
import type { PageItemRef, TriggerKind } from './previewPaths'
|
||||
|
||||
type Get = (args: { workspace: string; path: string; getDraft: boolean }) => Promise<unknown>
|
||||
|
||||
const TRIGGER_GETS: Record<TriggerKind, Get> = {
|
||||
http: (a) => HttpTriggerService.getHttpTrigger(a),
|
||||
websocket: (a) => WebsocketTriggerService.getWebsocketTrigger(a),
|
||||
postgres: (a) => PostgresTriggerService.getPostgresTrigger(a),
|
||||
kafka: (a) => KafkaTriggerService.getKafkaTrigger(a),
|
||||
nats: (a) => NatsTriggerService.getNatsTrigger(a),
|
||||
mqtt: (a) => MqttTriggerService.getMqttTrigger(a),
|
||||
amqp: (a) => AmqpTriggerService.getAmqpTrigger(a),
|
||||
sqs: (a) => SqsTriggerService.getSqsTrigger(a),
|
||||
gcp: (a) => GcpTriggerService.getGcpTrigger(a),
|
||||
azure: (a) => AzureTriggerService.getAzureTrigger(a),
|
||||
email: (a) => EmailTriggerService.getEmailTrigger(a)
|
||||
}
|
||||
|
||||
function getFor(ref: PageItemRef): Get {
|
||||
switch (ref.kind) {
|
||||
case 'variable':
|
||||
return (a) => VariableService.getVariable(a)
|
||||
case 'resource':
|
||||
return (a) => ResourceService.getResource(a)
|
||||
case 'schedule':
|
||||
return (a) => ScheduleService.getSchedule(a)
|
||||
case 'trigger':
|
||||
return TRIGGER_GETS[ref.triggerKind]
|
||||
}
|
||||
}
|
||||
|
||||
/** A page item as its editor would load it — deployed, or only a draft — or undefined when it
|
||||
* is neither: deleted, or a draft that was discarded. Any other failure is thrown, for the
|
||||
* caller to leave to the editor rather than report as a missing item. */
|
||||
export async function lookupPageItem(
|
||||
ref: PageItemRef,
|
||||
workspace: string
|
||||
): Promise<Record<string, any> | undefined> {
|
||||
try {
|
||||
return (await getFor(ref)({ workspace, path: ref.path, getDraft: true })) as Record<string, any>
|
||||
} catch (e) {
|
||||
if (e instanceof ApiError && e.status === 404) return undefined
|
||||
throw e
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user