mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-05 08:02:18 +00:00
* feat(frontend): group the AI agent step form and edit saved agents in a modal Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * feat: edit a saved AI agent through its own resource draft Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * refactor: drop the agent fork-for-edit session now that edits live in a draft Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * feat: edit ai_agent resources from the resources page with the agent editor Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: send a standalone agent's brain from the module when testing a step Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: keep the agent draft faithful to the resource it deploys to Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore: add the sqlx cache entry for the eval subject rename Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * refactor: share the module insert between the graph and the agent editor Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * feat(frontend): open evals inside the agent editor, actions in its header Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * feat(frontend): add tools from the agent editor and lighten its test pane Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(frontend): open an ai_agent deep link in the agent editor Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(frontend): drop the failed result badge on a step that never ran Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * feat(frontend): head the agent editor's levels with a back control Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * feat(frontend): drop connect and fill inputs from the agent editor Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * feat(frontend): lighten the agent editor's run panel Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(frontend): stop a nested agent tool's config reading as AI-filled Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * feat(frontend): offer only AI or static on an agent tool's inputs Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * feat(frontend): give a saved agent's tool editor a static-only surface Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * feat(frontend): open an agent tool in a drawer beside the agent Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * feat(frontend): hide unset agent config in the run form Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * refactor(frontend): share the input forms' pickers and s3 lookup Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore(frontend): drop a dead agent-editor export and fix two stale comments Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(frontend): reach an ai_agent's resource-level settings and copilot Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(frontend): open an ai_agent's resource view as JSON, not the generic form Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(frontend): address review findings on the agent editor's draft and streaming Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(frontend): close the agent editor on a version restore, as the resource editor does Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(frontend): stop the provider picker auto-writing a kind, and clear review nits Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * refactor(frontend): drop the fork-for-edit leftovers from the agent card Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(frontend): mount the agent editor in the dev flow editor and guard the deep-link race Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(frontend): deploy the agent config that was submitted, and refuse one no run could use Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * refactor(frontend): build the agent editor's rows from the design-system button Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(frontend): keep a draft-only agent's draft, and let a blank MCP summary deploy Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(frontend): guard read-only agents, incomplete MCP tools and duplicate editor mounts Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: read-only agent editor, linked-card refresh, atomic eval rename * fix: eval rename needs the privileged pool, per-workspace write access * fix(frontend): drop the agent editor target when its mount goes away * refactor: drop the agent rename work from this PR, unban the bindable defaults * fix(frontend): refuse a renaming deploy and drop the copilot from static-only fields * fix(frontend): mirror the worker's streaming rule and scope agent writes to their target * fix(frontend): read runtime streaming as off and reset the drawer's json view * fix(frontend): read an unsettled output_type as non-streaming too * fix(frontend): let the showing modal claim an agent opened from inside it * fix(frontend): keep in-flight edits, tool replacements and every linked step in sync * fix(frontend): keep attachments in the run form and bind the agent ref to its tools * fix(frontend): preview the agent as authored and re-evaluate step args on run * fix(frontend): scope agent-editor ownership to the flow's workspace * fix(frontend): drop the tool drill-in where there is no graph to select on * fix(frontend): require a provider kind and keep one resource editor open at a time * fix(frontend): keep legacy nulls, static-only text literal, and the handover anchor * test(worker): pin the agent streaming default * fix(frontend): let an AI-fillable input be switched to static * fix(frontend): report agent editor background failures instead of floating them * fix(frontend): keep the version pane's path alive while the editor closes * fix(frontend): clear the anchor-keep flag at the start of each drawer session * fix(frontend): preview the agent without its synthetic path, refresh the baseline on external writes * refactor(frontend): drop the unverifiable baseline refresh, state the synthetic-path rule * fix(frontend): keep the synthetic path out of agent tool test runs too * refactor(frontend): host the agent editor under the agent's own path * fix(frontend): mark an agent editor's host explicitly instead of inferring it from the path * fix(frontend): discard linked-agent responses from before a deploy * fix(frontend): keep a flow mount from claiming an agent editor's nested target * feat(frontend): keep an agent used as a tool inside the agent being edited * fix(frontend): reserve the agent editor's root module id * docs(frontend): record why the agent editor previews under the agent's path * fix(frontend): refuse to open or deploy a resource that is not an agent * docs(frontend): put the scope-migration comment on the function it describes * fix(frontend): refuse an agent path whose resource type is not proven * fix(frontend): recheck the resource type before deploying, and keep expressions off static-only inputs * fix(frontend): lazy-load the agent editor and slide its levels like the evals pane * refactor: drop unreachable non-list tools check from agent deploy Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(frontend): clear text-only agent fields on image output, reserve the root id Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(frontend): keep the agent editor usable for a non-list tools value Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(frontend): stop the parked eval run list from taking arrow keys Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(frontend): report a non-list tools value on deploy instead of throwing Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(frontend): keep temperature editable for image output Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(frontend): skip non-object tool entries when rendering an agent Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(frontend): guard tool entry reads instead of copying the tool array Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(frontend): key tool rows by position so duplicate ids render Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
419 lines
14 KiB
Svelte
419 lines
14 KiB
Svelte
<script lang="ts">
|
|
import type { Schema } from '$lib/common'
|
|
import { ResourceService, WorkspaceService, type Resource, type ResourceType } from '$lib/gen'
|
|
import { canWrite } from '$lib/utils'
|
|
import { createEventDispatcher, untrack } from 'svelte'
|
|
import { userStore, workspaceStore } from '$lib/stores'
|
|
import { sendUserToast } from '$lib/toast'
|
|
import { clearJsonSchemaResourceCache } from './schema/jsonSchemaResource.svelte'
|
|
import ResourceForm from './ResourceForm.svelte'
|
|
import { invalidateWorkspacePaths } from './PathNameAutocomplete.svelte'
|
|
import Alert from './common/alert/Alert.svelte'
|
|
import { resource } from 'runed'
|
|
import { getUserExt } from '$lib/user'
|
|
import type { UserExt } from '$lib/stores'
|
|
import { UserDraft, draftValuesEqual, type UserDraftHandle } from '$lib/userDraft.svelte'
|
|
import { setLocalDraftHint } from '$lib/localDraftHints.svelte'
|
|
|
|
interface Props {
|
|
canSave?: boolean
|
|
resource_type?: string | undefined
|
|
path?: string
|
|
hidePath?: boolean
|
|
onChange?: (args: { path: string; args: Record<string, any>; description: string }) => void
|
|
defaultValues?: Record<string, any> | undefined
|
|
workspace?: string | undefined
|
|
selected?: string | undefined
|
|
/** Show the value as JSON rather than as the resource type's form. Bindable so a caller can
|
|
* choose the view a given resource opens on, and the in-form toggle still works. */
|
|
viewJsonSchema?: boolean
|
|
/** Notifies the parent drawer whether a local draft for the selected
|
|
* workspace diverges from the deployed baseline, so it can show the
|
|
* "unsaved changes" banner below its header. */
|
|
onDraftStateChange?: (hasDraft: boolean) => void
|
|
/** Notifies the parent drawer of write-access for the selected workspace,
|
|
* so it can hide the banner's Discard button in read-only mode (matches
|
|
* the trigger editors' `disabled={!can_write}` wiring). */
|
|
onCanWriteChange?: (canWrite: boolean) => void
|
|
}
|
|
|
|
let {
|
|
canSave = $bindable(true),
|
|
resource_type = $bindable(undefined),
|
|
path = $bindable(''),
|
|
hidePath = false,
|
|
onChange,
|
|
defaultValues = undefined,
|
|
workspace = undefined,
|
|
selected: selectedProp = $bindable(),
|
|
viewJsonSchema = $bindable(),
|
|
onDraftStateChange,
|
|
onCanWriteChange
|
|
}: Props = $props()
|
|
|
|
type ResourceState = {
|
|
path: string
|
|
description: string
|
|
args: Record<string, any>
|
|
labels: string[] | undefined
|
|
wsSpecific: boolean
|
|
}
|
|
|
|
const dispatch = createEventDispatcher()
|
|
|
|
let effectiveWorkspace = $derived(workspace ?? $workspaceStore!)
|
|
// Fallback to `effectiveWorkspace` insulates against reactify-style
|
|
// parents that re-spread props without `selected` — otherwise it
|
|
// transiently resets and the form below remounts on every keystroke.
|
|
let selected = $derived(selectedProp ?? effectiveWorkspace)
|
|
let initialPath = path
|
|
|
|
// Per-workspace handles are driven by `useMany`. We track the workspace
|
|
// IDs (and their seeded defaults) in a parallel `$state` array; on every
|
|
// mutation `useMany` reconciles, acquiring entries for new workspaces and
|
|
// releasing them on component teardown. `states` indexes the resulting
|
|
// handles by workspace ID for ergonomic lookup downstream.
|
|
let workspaceSpecs = $state<Array<{ ws: string; defaultValue: ResourceState }>>([])
|
|
let initialStates: Record<string, ResourceState> = $state({})
|
|
let existedInitially: Record<string, boolean> = $state({})
|
|
let fetchedResources: Record<string, Resource> = $state({})
|
|
let perWsUser: Record<string, UserExt | undefined> = $state({})
|
|
|
|
const handlesArray = UserDraft.useMany<ResourceState>(() =>
|
|
workspaceSpecs.map((s) => ({
|
|
itemKind: 'resource' as const,
|
|
path: initialPath ?? '',
|
|
workspace: s.ws,
|
|
defaultValue: s.defaultValue,
|
|
// Autosaves landing back on the deployed value become deletes;
|
|
// `existedInitially` guards draft-only items from self-destructing.
|
|
discardIf: (val) => !!existedInitially[s.ws] && draftValuesEqual(val, initialStates[s.ws])
|
|
}))
|
|
)
|
|
const states = $derived.by(() => {
|
|
const out: Record<string, UserDraftHandle<ResourceState>> = {}
|
|
for (let i = 0; i < workspaceSpecs.length; i++) {
|
|
const handle = handlesArray[i]
|
|
if (handle) out[workspaceSpecs[i].ws] = handle
|
|
}
|
|
return out
|
|
})
|
|
|
|
/** Register a workspace so `useMany` acquires (or reuses) its handle.
|
|
* `defaultValue` is what the handle reports when no autosave is persisted;
|
|
* an existing autosave always wins. The default itself never round-trips
|
|
* to localStorage — only the user's first real edit triggers a write. */
|
|
function ensureHandle(ws: string, defaultValue: ResourceState): void {
|
|
if (workspaceSpecs.some((s) => s.ws === ws)) return
|
|
workspaceSpecs.push({ ws, defaultValue })
|
|
}
|
|
|
|
let isValid = $state(true)
|
|
let jsonError = $state('')
|
|
let perWsValid: Record<string, boolean> = $state({})
|
|
|
|
const deployToResource = resource(
|
|
() => selected,
|
|
async (ws) =>
|
|
ws ? (await WorkspaceService.getDeployTo({ workspace: ws })).deploy_to : undefined
|
|
)
|
|
const resourceTypeResource = resource(
|
|
[() => resource_type, () => effectiveWorkspace],
|
|
async ([rt, ws]) =>
|
|
rt && ws ? ResourceService.getResourceType({ path: rt, workspace: ws }) : null
|
|
)
|
|
|
|
let deployTo = $derived(deployToResource.current)
|
|
let resourceTypeInfo: ResourceType | undefined = $derived(
|
|
resourceTypeResource.current ?? undefined
|
|
)
|
|
let resourceSchema: Schema | undefined = $derived.by(() => {
|
|
const rt = resourceTypeResource.current
|
|
if (!rt?.schema) return undefined
|
|
const schema = rt.schema as Schema
|
|
return {
|
|
...schema,
|
|
// A resource type may declare no properties at all — `dbt_profile` is a
|
|
// `profiles.yml` block whose keys are its adapter's, not Windmill's — and
|
|
// the form renders those as one JSON editor. `Object.keys(undefined)`
|
|
// threw here, which left the editor on its loading skeleton forever.
|
|
order: schema.order ?? Object.keys(schema.properties ?? {}).sort()
|
|
}
|
|
})
|
|
let loadingSchema = $derived(resourceTypeResource.loading)
|
|
|
|
let current = $derived(selected ? states[selected]?.draft : undefined)
|
|
let resourceToEdit: Resource | undefined = $derived(
|
|
selected ? fetchedResources[selected] : undefined
|
|
)
|
|
let can_write = $derived.by(() => {
|
|
if (!selected) return true
|
|
const r = fetchedResources[selected]
|
|
if (!r) return true
|
|
return canWrite(
|
|
current?.path ?? initialPath,
|
|
r.extra_perms ?? {},
|
|
perWsUser[selected] ?? $userStore
|
|
)
|
|
})
|
|
|
|
const dirtyWorkspaces = $derived(
|
|
Object.keys(states).filter((ws) => !draftValuesEqual(states[ws].draft, initialStates[ws]))
|
|
)
|
|
const anyDirty = $derived(dirtyWorkspaces.length > 0)
|
|
|
|
// The syncer owns the list-page `*` hint; the editor only CLEARS it when a
|
|
// workspace is at the deployed baseline (so a draft discarded elsewhere
|
|
// vanishes on reopen). Never SET here. See VariableEditor for the full note.
|
|
$effect(() => {
|
|
const p = initialPath
|
|
const loadedWs = Object.keys(states)
|
|
const dirty = dirtyWorkspaces
|
|
untrack(() => {
|
|
if (!p) return
|
|
for (const ws of loadedWs) {
|
|
if (!dirty.includes(ws)) setLocalDraftHint(ws, 'resource', p, false)
|
|
}
|
|
})
|
|
})
|
|
// Banner is scoped to the selected workspace — the diff/discard only
|
|
// operate on it, so showing it for an unrelated dirty workspace would be
|
|
// misleading. The cross-workspace `otherDirty` alert below still covers
|
|
// that case.
|
|
const selectedDirty = $derived(!!selected && dirtyWorkspaces.includes(selected))
|
|
const otherDirty = $derived(
|
|
dirtyWorkspaces.length == 1
|
|
? dirtyWorkspaces.filter((ws) => ws !== $workspaceStore)
|
|
: dirtyWorkspaces
|
|
)
|
|
const dirtyValid = $derived(dirtyWorkspaces.every((ws) => perWsValid[ws] !== false))
|
|
const dirtyCanWrite = $derived(
|
|
dirtyWorkspaces.every((ws) => {
|
|
const r = fetchedResources[ws]
|
|
return (
|
|
!r ||
|
|
canWrite(
|
|
states[ws]?.draft?.path ?? initialPath,
|
|
r.extra_perms ?? {},
|
|
perWsUser[ws] ?? $userStore
|
|
)
|
|
)
|
|
})
|
|
)
|
|
|
|
// New-resource bootstrap: seed empty state per workspace (edit mode
|
|
// is seeded by the lazy-fetch effect below).
|
|
$effect(() => {
|
|
if (!selected) return
|
|
if (initialPath) return
|
|
if (selected in initialStates) return
|
|
untrack(() => {
|
|
const s: ResourceState = {
|
|
path: '',
|
|
description: '',
|
|
args: (defaultValues && Object.keys(defaultValues).length > 0 ? defaultValues : {}) as any,
|
|
labels: undefined,
|
|
wsSpecific: false
|
|
}
|
|
ensureHandle(selected, s)
|
|
initialStates[selected] = structuredClone(s)
|
|
existedInitially[selected] = false
|
|
})
|
|
})
|
|
|
|
// Lazy-fetch the resource for the selected workspace when not already cached
|
|
$effect(() => {
|
|
const ws = selected
|
|
if (!ws || !initialPath) return
|
|
if (ws in states) return
|
|
untrack(() => {
|
|
Promise.all([
|
|
ResourceService.getResource({ workspace: ws, path: initialPath, getDraft: true }),
|
|
getUserExt(ws)
|
|
]).then(([r, user]) => {
|
|
// `.draft` already holds the editor's `ResourceState` shape.
|
|
const savedDraftState = (r as any).draft as ResourceState | undefined
|
|
fetchedResources[ws] = r
|
|
// Deployed baseline as the dirty-check reference, so the banner
|
|
// compares draft-vs-deployed and fires immediately when a draft exists.
|
|
const deployedState: ResourceState = {
|
|
path: r.path,
|
|
description: r.description ?? '',
|
|
args: (r.value ?? {}) as any,
|
|
labels: r.labels ?? undefined,
|
|
wsSpecific: r.ws_specific ?? false
|
|
}
|
|
// Open with the saved draft if present, else the deployed.
|
|
const s: ResourceState = savedDraftState ?? deployedState
|
|
ensureHandle(ws, s)
|
|
initialStates[ws] = structuredClone(deployedState)
|
|
// Draft-only paths (`no_deployed`) have no row — saving must
|
|
// CREATE, not update (update 404s).
|
|
existedInitially[ws] = !(r as any).no_deployed
|
|
perWsUser[ws] = user
|
|
// Keep resource_type in sync for the base workspace (controls the schema)
|
|
if (ws === effectiveWorkspace) {
|
|
resource_type = r.resource_type
|
|
}
|
|
})
|
|
})
|
|
})
|
|
|
|
// Keep current.path bound to the outer `path` prop for consumers
|
|
$effect(() => {
|
|
if (current) path = current.path
|
|
})
|
|
|
|
$effect(() => {
|
|
if (selected !== undefined) {
|
|
perWsValid[selected] = isValid && jsonError == ''
|
|
}
|
|
})
|
|
|
|
$effect(() => {
|
|
canSave = anyDirty && dirtyValid && dirtyCanWrite
|
|
})
|
|
|
|
// Drive the parent drawer's "unsaved changes" banner. The drawer chrome
|
|
// (header + banner slot) lives in ResourceEditorDrawer, above this
|
|
// lazily-imported content, so the state is lifted up via these accessors.
|
|
$effect(() => {
|
|
onDraftStateChange?.(!!initialPath && selectedDirty)
|
|
})
|
|
$effect(() => {
|
|
onCanWriteChange?.(can_write)
|
|
})
|
|
|
|
export function localDraftDeployed(): ResourceState | undefined {
|
|
return selected ? initialStates[selected] : undefined
|
|
}
|
|
export function localDraftCurrent(): ResourceState | undefined {
|
|
return current
|
|
}
|
|
export function discardLocalDraft(): void {
|
|
if (!selected) return
|
|
UserDraft.discard('resource', initialPath ?? '', initialStates[selected], {
|
|
workspace: selected
|
|
})
|
|
}
|
|
|
|
$effect(() => {
|
|
if (current)
|
|
// $state.snapshot deep-reads (so the effect re-runs on nested
|
|
// args mutations) and returns a plain object (React consumers
|
|
// can't diff a $state proxy by reference or JSON.stringify).
|
|
onChange?.({
|
|
path: current.path,
|
|
args: $state.snapshot(current.args) as Record<string, any>,
|
|
description: current.description
|
|
})
|
|
})
|
|
|
|
/** Sole writer of `current.path` — an arg still holding `$var:<the path being
|
|
* replaced>` is the resource's own linked secret, which the backend renames
|
|
* along with the resource, so the reference moves with it. An arg pointing at
|
|
* any other variable was set by the user and is left alone. */
|
|
function setPath(npath: string): void {
|
|
if (!current) return
|
|
const prev = current.path
|
|
// `args` is whatever the raw JSON editor parsed — `null` included.
|
|
for (const [k, v] of Object.entries(current.args ?? {})) {
|
|
if (v === `$var:${prev}`) current.args[k] = `$var:${npath}`
|
|
}
|
|
current.path = npath
|
|
}
|
|
|
|
/** Whether the write landed. It toasts its own failure, so most callers ignore this;
|
|
* one that follows the save with bookkeeping of its own has to know not to. */
|
|
export async function save(): Promise<boolean> {
|
|
const dirty = dirtyWorkspaces
|
|
try {
|
|
for (const ws of dirty) {
|
|
const s = states[ws].draft!
|
|
const ini = initialStates[ws]
|
|
if (existedInitially[ws]) {
|
|
await ResourceService.updateResource({
|
|
workspace: ws,
|
|
path: ini.path,
|
|
requestBody: {
|
|
path: s.path,
|
|
value: s.args,
|
|
description: s.description,
|
|
labels: s.labels,
|
|
ws_specific: s.wsSpecific
|
|
}
|
|
})
|
|
const fetched = fetchedResources[ws]
|
|
if (fetched?.resource_type === 'json_schema') {
|
|
clearJsonSchemaResourceCache(ini.path, ws)
|
|
}
|
|
} else {
|
|
await ResourceService.createResource({
|
|
workspace: ws,
|
|
requestBody: {
|
|
path: s.path,
|
|
value: s.args,
|
|
description: s.description,
|
|
resource_type: resource_type!,
|
|
labels: s.labels,
|
|
ws_specific: s.wsSpecific
|
|
}
|
|
})
|
|
}
|
|
// Reset the handle to the new deployed baseline via `discard`, not
|
|
// `remove`. See VariableEditor for the full rationale.
|
|
initialStates[ws] = $state.snapshot(s) as ResourceState
|
|
existedInitially[ws] = true
|
|
UserDraft.discard('resource', initialPath ?? '', s, { workspace: ws })
|
|
// Path now exists server-side — drop the autocomplete cache so
|
|
// it shows up immediately instead of after the 60s TTL.
|
|
invalidateWorkspacePaths(ws)
|
|
}
|
|
sendUserToast(
|
|
dirty.length > 1 ? `Saved resource in ${dirty.length} workspaces` : `Saved resource`
|
|
)
|
|
dispatch('refresh', current?.path ?? path)
|
|
return true
|
|
} catch (err) {
|
|
sendUserToast(`Could not save resource: ${err.body ?? err.message}`, true)
|
|
return false
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<div>
|
|
<div class="flex flex-col gap-6 pb-2">
|
|
{#if otherDirty.length > 0}
|
|
<Alert type="warning" title="Editing multiple workspaces">
|
|
You are going to edit the value in: {otherDirty.join(', ')}
|
|
</Alert>
|
|
{/if}
|
|
|
|
{#if current}
|
|
{#key current}
|
|
<ResourceForm
|
|
bind:path={() => current!.path, setPath}
|
|
bind:labels={current.labels}
|
|
bind:description={current.description}
|
|
bind:args={current.args}
|
|
bind:wsSpecific={current.wsSpecific}
|
|
bind:isValid
|
|
bind:viewJsonSchema={() => viewJsonSchema ?? false, (v) => (viewJsonSchema = v)}
|
|
bind:jsonError
|
|
{initialPath}
|
|
{hidePath}
|
|
{deployTo}
|
|
{can_write}
|
|
{resource_type}
|
|
{resourceTypeInfo}
|
|
{resourceSchema}
|
|
{loadingSchema}
|
|
{resourceToEdit}
|
|
onLoadResourceType={() => resourceTypeResource.refetch()}
|
|
workspace={selected}
|
|
/>
|
|
{/key}
|
|
{/if}
|
|
</div>
|
|
</div>
|