mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-22 00:02:38 +00:00
* feat: track and rotate gitlab git-sync repository tokens * chore: point ee-repo-ref at the gitlab credential branch * fix: strip server-owned credential status and correct expiry copy * fix: gate credential maintenance on enterprise and alert on stalled renewal * fix: alert on an auto-renewed token only once it has actually expired * feat: receive gitlab push webhooks for instant git sync pull * feat: open gitlab merge requests and post diff previews on them Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * fix: keep gitlab merge request previews out of the project's own pipeline Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * fix: bound the credential maintenance pass and gate the gitlab picker on a license Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * fix: create the gitlab picker's variable in the edited workspace Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * fix: make the gitlab picker's variable path collision-resistant Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * docs: state the gitlab scope and rotation facts the code relies on Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * fix: resolve the check marker's repository from its path, not a stored url Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * fix: refuse to finish a check whose repository has been repointed Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * fix: trust a check marker's captured url when it carries no identity Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * fix: recreate a missing webhook from credential maintenance Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * docs: state that relative-url gitlab installs are out of scope Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * fix: keep credential status out of exports and clear stale webhook warnings Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * fix: refuse an unprovable check and guard the picker on the stored repository Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * fix: re-check the picker's target path at the moment it is written Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * fix: snapshot the picker's inputs before it starts writing Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * docs: recommend a project access token per repository Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * [ee] feat: keep the git-sync credential in workspace settings * [ee] fix: drop a removed repo's credential and honor the workspace override * [ee] fix: resolve a fork's git credential from its whole ancestry * [ee] refactor: reuse fork_ancestor_chain instead of a second ancestry walk * [ee] fix: resolve an app installation from the whole ancestry, not the parent * [ee] revert: keep the app installation fallback at one level * fix: store the git credential only once the resource is saved * fix: keep a repository's credential when it leaves git sync settings * docs: cut the gitlab picker's token guidance down to what it needs * feat: mark a repository whose credential windmill holds * fix: ignore the managed-credential marker when the url carries a token * docs: drop the picker's setup alert for a line by the token field * feat: replace a repository's stored token from its resource * fix: store a picked credential for its own workspace, before the resource * refactor: key a stored git credential by its repository, not its resource Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * chore: refresh the sqlx cache for the repository-keyed credential queries Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * fix: gate the credential pass budget on the features that use it Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * fix: decide credential rotation ownership by repository, not resource path Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * refactor: renew only the credentials windmill holds, not tokens in a repo url Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * fix: authenticate the fork-branch poll and correct the renewal guidance Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * fix: do not claim a managed credential for a url the client cannot resolve Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * fix: define the credential facade for private builds without enterprise Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * fix: pin the listed token before the await and name the real renewal blocker Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * fix: pin the token the replace flow checked, and derive the scope test once Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * fix: classify the renewal state once so the card cannot contradict itself Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * refactor: ask only whether the token gets renewed, not why it does not Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * refactor: replace the managed-credential marker with a server answer Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * fix: read renewal from the credential and its origin, not a removed field Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * fix: read the provider for url-token repos, await the origin before defaulting, and visit unchecked repos last The maintenance pass sorted repositories with no recorded check first on the premise that they cost nothing, but a token-in-URL remote on a host that is not GitLab is probed every pass and never records a check, so it held the head of the list ahead of the tokens that expire. Such repositories now sort last. The card decided its delivery defaults before the origin lookup landed, so a freshly picked GitLab repository never got webhook delivery; the two lookups are awaited together. The resource editor offers to replace a token only where it is held, not in a fork that borrows it, and the replace flow refuses a URL it cannot parse instead of keying the token to it. Attaching a stored credential to a commit-hash probe now requires admin, matching the installation credential beside it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * docs: describe the gitlab listing token the way the picker and the setup guide do Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * refactor: a token in the repository url is a plain remote, not a tracked credential Drops the status fingerprint that told one URL token from another, the docs' promise that such a token's expiry is reported, and the test's expectation that a URL-token repository declares a host. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * fix: the card reads the credential origin for managed controls and honours the licence for a borrowed token Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * chore: bump the ee ref Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * fix: hide a repository's credential line once nothing is held for the repository it names Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * docs: describe the exported credential status as it is Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * refactor: run the credential maintenance pass as its own task, without a budget The pass ran inside the monitor's join, whose deadline cancels every future in it, and a rotation cancelled between GitLab issuing a token and Windmill storing it loses the token family. A wall-clock budget with a least-recently-checked ordering kept it under the deadline. Spawning the pass instead makes the deadline irrelevant, so the budget, the ordering and the counter go; the advisory lock keeps a slow pass from overlapping the next, as it already did. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * docs: say what detaching the maintenance pass buys, and what it does not Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * chore: run git sync on the hub script version that reads a stored credential Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * chore: run the deploy push and the connection test on the hub versions that read a stored credential Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * fix: keep App repositories and plain remotes out of the stored-credential paths Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * chore: host-neutral deploy preview wording, drop the project filter from the GitLab picker Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * chore: bump ee ref, rotation no longer retains a second connection per repository Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * chore: bump ee ref, the rotation write-back holds a single connection Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * chore: hold the credential maintenance lock in a transaction so a dead sweep releases it Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * docs: describe the credential-stored callback as it fires Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * fix: keep the credential maintenance lock past the pool's idle-in-transaction timeout Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * chore: update ee-repo-ref to e092518ee60e33160fee9ae91a4d109566f7b0ee This commit updates the EE repository reference after PR #771 was merged in windmill-ee-private. Previous ee-repo-ref: 74481f7cc345757aebb2a8b04d3a22978328c348 New ee-repo-ref: e092518ee60e33160fee9ae91a4d109566f7b0ee Automated by sync-ee-ref workflow. --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
346 lines
11 KiB
Svelte
346 lines
11 KiB
Svelte
<script lang="ts">
|
|
import type { Schema } from '$lib/common'
|
|
import type { Resource, ResourceType } from '$lib/gen'
|
|
import { onDestroy } from 'svelte'
|
|
import { setEditorUnparseable } from './pendingEditorFlush'
|
|
import { emptyString, isOwner, urlize } from '$lib/utils'
|
|
import { Alert, Skeleton } from './common'
|
|
import Path from './Path.svelte'
|
|
import LabelsInput from './LabelsInput.svelte'
|
|
import Required from './Required.svelte'
|
|
import { userStore, workspaceStore } from '$lib/stores'
|
|
import SchemaForm from './SchemaForm.svelte'
|
|
import SimpleEditor from './SimpleEditor.svelte'
|
|
import FilesetEditor from './FilesetEditor.svelte'
|
|
import Toggle from './Toggle.svelte'
|
|
import TestConnection from './TestConnection.svelte'
|
|
import { Pen } from 'lucide-svelte'
|
|
import autosize from '$lib/autosize'
|
|
import GfmMarkdown from './GfmMarkdown.svelte'
|
|
import TestTriggerConnection from './triggers/TestTriggerConnection.svelte'
|
|
import GitHubAppIntegration from './GitHubAppIntegration.svelte'
|
|
import GitLabIntegration from './GitLabIntegration.svelte'
|
|
import Button from './common/button/Button.svelte'
|
|
import ResourceGen from './copilot/ResourceGen.svelte'
|
|
import SyncResourceTypes from './SyncResourceTypes.svelte'
|
|
import Label from './Label.svelte'
|
|
import ResourcePathHint from './ResourcePathHint.svelte'
|
|
|
|
interface Props {
|
|
path: string
|
|
initialPath: string
|
|
hidePath?: boolean
|
|
labels: string[] | undefined
|
|
description: string
|
|
args: Record<string, any>
|
|
wsSpecific: boolean
|
|
isValid: boolean
|
|
viewJsonSchema: boolean
|
|
jsonError: string
|
|
deployTo: string | undefined
|
|
can_write: boolean
|
|
resource_type: string | undefined
|
|
resourceTypeInfo: ResourceType | undefined
|
|
resourceSchema: Schema | undefined
|
|
loadingSchema: boolean
|
|
resourceToEdit: Resource | undefined
|
|
onLoadResourceType?: () => void
|
|
/** Workspace the path is validated against and the connection is tested in;
|
|
* defaults to the nav workspace. */
|
|
workspace?: string | undefined
|
|
/** Fired once the GitLab picker has stored the picked project's token, so a
|
|
* form that would otherwise file the URL as a secret knows it holds none. */
|
|
onCredentialStored?: () => void
|
|
}
|
|
|
|
let {
|
|
path = $bindable(),
|
|
initialPath,
|
|
hidePath = false,
|
|
labels = $bindable(),
|
|
description = $bindable(),
|
|
args = $bindable(),
|
|
wsSpecific = $bindable(),
|
|
isValid = $bindable(),
|
|
viewJsonSchema = $bindable(),
|
|
jsonError = $bindable(),
|
|
deployTo,
|
|
can_write,
|
|
resource_type,
|
|
resourceTypeInfo,
|
|
resourceSchema,
|
|
loadingSchema,
|
|
resourceToEdit,
|
|
onLoadResourceType,
|
|
workspace = undefined,
|
|
onCredentialStored
|
|
}: Props = $props()
|
|
|
|
let ws = $derived(workspace ?? $workspaceStore)
|
|
|
|
let editDescription = $state(false)
|
|
let rawCode: string | undefined = $state(undefined)
|
|
let textFileContent: string = $state('')
|
|
|
|
// This field is a bare SimpleEditor parsed here, so it never passes through JsonEditor —
|
|
// it has to register itself, or a caller persisting what is on screen would save the
|
|
// last value that parsed and leave without the text in front of the user.
|
|
const unparseableKey = {}
|
|
onDestroy(() => setEditorUnparseable(unparseableKey, false))
|
|
|
|
function parseJson() {
|
|
try {
|
|
args = JSON.parse(rawCode ?? '')
|
|
jsonError = ''
|
|
} catch (e) {
|
|
jsonError = e.message
|
|
}
|
|
}
|
|
|
|
function parseTextFileContent() {
|
|
args = { content: textFileContent }
|
|
}
|
|
|
|
// The raw JSON editor is the active input whenever the "As JSON" toggle is on,
|
|
// or no schema-based form can be rendered (e.g. the resource type is missing
|
|
// from the workspace). In both cases rawCode must be seeded from args.
|
|
let usesRawEditor = $derived(
|
|
!loadingSchema &&
|
|
(viewJsonSchema ||
|
|
(!resourceTypeInfo?.is_fileset && !(resourceSchema && resourceSchema.properties)))
|
|
)
|
|
|
|
$effect(() => {
|
|
if (rawCode !== undefined) parseJson()
|
|
})
|
|
|
|
// Both halves, and from the current parse rather than from a transition: `rawCode`
|
|
// outlives the raw editor, so text that does not parse is the user's to fix exactly
|
|
// while that editor is the active input — which the schema loading and the resource
|
|
// type flip as well as the toggle, and only the toggle reseeds `rawCode`.
|
|
$effect(() => {
|
|
setEditorUnparseable(unparseableKey, usesRawEditor && jsonError !== '')
|
|
})
|
|
|
|
$effect(() => {
|
|
if (usesRawEditor && rawCode === undefined) {
|
|
rawCode = JSON.stringify(args, null, 2)
|
|
}
|
|
})
|
|
|
|
// Seed the JSON editor when the resource type schema is missing
|
|
// (restores the old ResourceEditor's catch-block behavior)
|
|
$effect(() => {
|
|
if (resource_type && !loadingSchema && !resourceSchema && rawCode === undefined) {
|
|
rawCode = JSON.stringify(args, null, 2)
|
|
}
|
|
})
|
|
|
|
$effect(() => {
|
|
if (textFileContent) parseTextFileContent()
|
|
})
|
|
|
|
$effect(() => {
|
|
if (resourceTypeInfo?.format_extension && !resourceTypeInfo?.is_fileset && !viewJsonSchema) {
|
|
textFileContent = args?.content ?? ''
|
|
}
|
|
})
|
|
</script>
|
|
|
|
{#if !emptyString(resourceTypeInfo?.description)}
|
|
<GfmMarkdown md={urlize(resourceTypeInfo?.description ?? '', 'md')} prose="sm" noPadding />
|
|
{/if}
|
|
|
|
{#if !hidePath}
|
|
<div>
|
|
{#if !can_write}
|
|
<div class="my-2">
|
|
<Alert type="warning" title="Only read access">
|
|
You only have read access to this resource and cannot edit it
|
|
</Alert>
|
|
</div>
|
|
{/if}
|
|
<Label label="Path">
|
|
<ResourcePathHint />
|
|
<Path
|
|
disabled={initialPath != '' && !isOwner(initialPath, $userStore, ws)}
|
|
bind:path
|
|
{initialPath}
|
|
namePlaceholder="resource"
|
|
kind="resource"
|
|
workspaceOverride={workspace}
|
|
/>
|
|
</Label>
|
|
</div>
|
|
{/if}
|
|
<LabelsInput bind:labels class="-mt-4" />
|
|
|
|
{#if deployTo}
|
|
<Label
|
|
label="Workspace specific"
|
|
tooltip="Prevents this resource from being deployed to prod/staging. When enabled, any variable referenced via $var: inside the resource value is also automatically marked workspace-specific. Disabling this toggle does not un-mark those variables — they may be referenced by other resources."
|
|
>
|
|
<Toggle bind:checked={wsSpecific} />
|
|
</Label>
|
|
{/if}
|
|
|
|
<div class="flex flex-col gap-1">
|
|
<h4 class="inline-flex items-center gap-2 text-xs text-emphasis font-semibold"
|
|
>Resource description <Required required={false} />
|
|
{#if can_write}
|
|
<Button
|
|
variant="subtle"
|
|
unifiedSize="xs"
|
|
btnClasses={editDescription ? 'bg-surface-hover' : ''}
|
|
startIcon={{ icon: Pen }}
|
|
on:click={() => (editDescription = !editDescription)}
|
|
/>
|
|
{/if}
|
|
</h4>
|
|
{#if can_write && editDescription}
|
|
<div class="relative">
|
|
<div class="text-2xs text-primary absolute -top-4 right-0">GH Markdown</div>
|
|
<textarea
|
|
class="text-xs text-primary font-normal"
|
|
disabled={!can_write}
|
|
use:autosize
|
|
bind:value={description}
|
|
placeholder="Describe what this resource is for"
|
|
></textarea>
|
|
</div>
|
|
{:else if description == undefined || description == ''}
|
|
<div class="text-xs text-secondary font-normal">No description provided</div>
|
|
{:else}
|
|
<GfmMarkdown md={description} prose="sm" noPadding />
|
|
{/if}
|
|
</div>
|
|
|
|
<div class="flex flex-col gap-1">
|
|
<div class="w-full flex gap-4 flex-row-reverse items-center">
|
|
<Toggle
|
|
bind:checked={viewJsonSchema}
|
|
on:change={(e) => {
|
|
if (e.detail) {
|
|
rawCode = JSON.stringify(args, null, 2)
|
|
} else if (resourceTypeInfo?.format_extension && !resourceTypeInfo?.is_fileset) {
|
|
textFileContent = args?.content ?? ''
|
|
}
|
|
}}
|
|
options={{
|
|
right: 'As JSON'
|
|
}}
|
|
/>
|
|
<ResourceGen
|
|
bind:args
|
|
resourceType={resource_type}
|
|
resourceName={path}
|
|
resourceDescription={description}
|
|
{resourceSchema}
|
|
/>
|
|
{#if resourceToEdit?.resource_type === 'nats' || resourceToEdit?.resource_type === 'kafka'}
|
|
<TestTriggerConnection kind={resourceToEdit?.resource_type} args={{ connection: args }} />
|
|
{:else}
|
|
<TestConnection
|
|
resourceType={resourceToEdit?.resource_type}
|
|
{args}
|
|
workspaceOverride={workspace}
|
|
/>
|
|
{/if}
|
|
{#if resource_type === 'git_repository' && $workspaceStore && ($userStore?.is_admin || $userStore?.is_super_admin)}
|
|
<GitHubAppIntegration
|
|
resourceType={resource_type}
|
|
{args}
|
|
{description}
|
|
onArgsUpdate={(newArgs) => {
|
|
args = newArgs
|
|
// The raw editor is also what a workspace missing the resource type
|
|
// gets, and it holds its own copy of the value: without this the
|
|
// picker fills in a URL nothing on screen ever shows.
|
|
if (viewJsonSchema || !resourceSchema) {
|
|
rawCode = JSON.stringify(args, null, 2)
|
|
}
|
|
}}
|
|
onDescriptionUpdate={(newDescription) => (description = newDescription)}
|
|
/>
|
|
<GitLabIntegration
|
|
resourceType={resource_type}
|
|
{args}
|
|
workspace={ws}
|
|
{onCredentialStored}
|
|
onArgsUpdate={(newArgs) => {
|
|
args = newArgs
|
|
// The raw editor is also what a workspace missing the resource type
|
|
// gets, and it holds its own copy of the value: without this the
|
|
// picker fills in a URL nothing on screen ever shows.
|
|
if (viewJsonSchema || !resourceSchema) {
|
|
rawCode = JSON.stringify(args, null, 2)
|
|
}
|
|
}}
|
|
/>
|
|
{/if}
|
|
</div>
|
|
|
|
<div>
|
|
{#if loadingSchema}
|
|
<Skeleton layout={[[4]]} />
|
|
{:else if !viewJsonSchema && resourceTypeInfo?.is_fileset}
|
|
<div class="mt-1 flex items-center gap-2">
|
|
<h5 class="inline-flex items-center gap-4">Fileset</h5>
|
|
<ResourceGen
|
|
bind:args
|
|
resourceType={resource_type}
|
|
resourceName={path}
|
|
resourceDescription={description}
|
|
{resourceSchema}
|
|
isFileset
|
|
/>
|
|
</div>
|
|
<FilesetEditor bind:args />
|
|
{:else if !viewJsonSchema && resourceSchema && resourceSchema?.properties}
|
|
{#if resourceTypeInfo?.format_extension}
|
|
<h5 class="mt-1 inline-flex items-center gap-4">
|
|
File content ({resourceTypeInfo.format_extension})
|
|
</h5>
|
|
<div class="">
|
|
<SimpleEditor
|
|
autoHeight
|
|
lang={resourceTypeInfo.format_extension}
|
|
bind:code={textFileContent}
|
|
fixedOverflowWidgets={false}
|
|
/>
|
|
</div>
|
|
{:else}
|
|
<SchemaForm
|
|
onlyMaskPassword
|
|
noDelete
|
|
disabled={!can_write}
|
|
compact
|
|
schema={resourceSchema}
|
|
bind:args
|
|
bind:isValid
|
|
{workspace}
|
|
/>
|
|
{/if}
|
|
{:else if !can_write}
|
|
<input type="text" disabled value={rawCode} />
|
|
{:else}
|
|
{#if !viewJsonSchema && !resourceSchema}
|
|
<div class="flex flex-col gap-2 mb-4">
|
|
<p class="text-red-500 dark:text-red-400 text-xs">
|
|
Resource type '{resource_type}' not found in your workspace
|
|
</p>
|
|
<SyncResourceTypes resourceType={resource_type} onSynced={() => onLoadResourceType?.()} />
|
|
<p class="italic text-secondary text-xs"> Define the value in JSON directly </p>
|
|
</div>
|
|
{/if}
|
|
|
|
{#if !emptyString(jsonError)}<span class="text-red-400 text-xs mb-1 flex flex-row-reverse"
|
|
>{jsonError}</span
|
|
>{:else}<div class="py-2"></div>{/if}
|
|
<div class="bg-surface-tertiary rounded-md border py-2.5">
|
|
<SimpleEditor autoHeight lang="json" bind:code={rawCode} />
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
</div>
|