mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-23 16:00:38 +00:00
452 lines
12 KiB
Svelte
452 lines
12 KiB
Svelte
<script lang="ts">
|
|
import { goto } from '$lib/navigation'
|
|
import { base } from '$lib/base'
|
|
import Dropdown from '$lib/components/DropdownV2.svelte'
|
|
import type MoveDrawer from '$lib/components/MoveDrawer.svelte'
|
|
import ScheduleEditor from '$lib/components/triggers/schedules/ScheduleEditor.svelte'
|
|
import SharedBadge from '$lib/components/SharedBadge.svelte'
|
|
import type ShareModal from '$lib/components/ShareModal.svelte'
|
|
|
|
import { ScriptService, type Script } from '$lib/gen'
|
|
import { hubBaseUrlStore, userStore, workspaceStore } from '$lib/stores'
|
|
|
|
import { createEventDispatcher } from 'svelte'
|
|
import Badge from '../badge/Badge.svelte'
|
|
import Button from '../button/Button.svelte'
|
|
import Row from './Row.svelte'
|
|
import { sendUserToast } from '$lib/toast'
|
|
import { capitalize, copyToClipboard, isOwner } from '$lib/utils'
|
|
import { isDeployable } from '$lib/utils_deployable'
|
|
|
|
import type DeployWorkspaceDrawer from '$lib/components/DeployWorkspaceDrawer.svelte'
|
|
import { LanguageIcon } from '../languageIcons'
|
|
import {
|
|
Archive,
|
|
Calendar,
|
|
Code,
|
|
Copy,
|
|
Eye,
|
|
FolderOpen,
|
|
ChevronUpSquare,
|
|
GitFork,
|
|
List,
|
|
Pen,
|
|
Shield,
|
|
Trash,
|
|
History,
|
|
Globe2,
|
|
FileText
|
|
} from 'lucide-svelte'
|
|
import ScriptVersionHistory from '$lib/components/ScriptVersionHistory.svelte'
|
|
import WacExportDrawer from '$lib/components/scripts/WacExportDrawer.svelte'
|
|
import { Drawer, DrawerContent } from '..'
|
|
import NoMainFuncBadge from '$lib/components/NoMainFuncBadge.svelte'
|
|
import Popover from '$lib/components/Popover.svelte'
|
|
import Tooltip from '$lib/components/Tooltip.svelte'
|
|
import { getDeployUiSettings } from '$lib/components/home/deploy_ui'
|
|
import { scriptToHubUrl } from '$lib/hub'
|
|
import { isRuleActive } from '$lib/workspaceProtectionRules.svelte'
|
|
import { buildForkEditUrl } from '$lib/utils/editInFork'
|
|
import { isCloudHosted } from '$lib/cloud'
|
|
|
|
interface Props {
|
|
script: Script & { canWrite: boolean; use_codebase: boolean }
|
|
marked: string | undefined
|
|
shareModal: ShareModal
|
|
moveDrawer: MoveDrawer
|
|
deploymentDrawer: DeployWorkspaceDrawer
|
|
deleteConfirmedCallback: (() => void) | undefined
|
|
errorHandlerMuted: boolean
|
|
showCode: (path: string, summary: string) => void
|
|
depth?: number
|
|
menuOpen?: boolean
|
|
showEditButton?: boolean
|
|
keyboardSelected?: boolean
|
|
}
|
|
|
|
let {
|
|
script,
|
|
marked,
|
|
shareModal,
|
|
moveDrawer,
|
|
deploymentDrawer,
|
|
deleteConfirmedCallback = $bindable(),
|
|
errorHandlerMuted,
|
|
showCode,
|
|
depth = 0,
|
|
menuOpen = $bindable(false),
|
|
showEditButton = $bindable(true),
|
|
keyboardSelected = false
|
|
}: Props = $props()
|
|
|
|
const dispatch = createEventDispatcher()
|
|
|
|
async function archiveScript(path: string): Promise<void> {
|
|
await ScriptService.archiveScriptByPath({ workspace: $workspaceStore!, path })
|
|
dispatch('change')
|
|
sendUserToast(`Archived script ${path}`)
|
|
}
|
|
|
|
async function unarchiveScript(path: string): Promise<void> {
|
|
const r = await ScriptService.getScriptByPath({ workspace: $workspaceStore!, path })
|
|
await ScriptService.createScript({
|
|
workspace: $workspaceStore!,
|
|
requestBody: {
|
|
...r,
|
|
parent_hash: r.hash,
|
|
lock: r.lock
|
|
}
|
|
})
|
|
dispatch('change')
|
|
sendUserToast(`Unarchived script ${path}`)
|
|
}
|
|
|
|
async function deleteScript(path: string): Promise<void> {
|
|
await ScriptService.deleteScriptByPath({ workspace: $workspaceStore!, path })
|
|
dispatch('change')
|
|
sendUserToast(`Deleted script ${path}`)
|
|
}
|
|
let scheduleEditor: ScheduleEditor | undefined = $state(undefined)
|
|
|
|
const dlt: 'delete' = 'delete'
|
|
let versionsDrawerOpen: boolean = $state(false)
|
|
let wacExportDrawer: WacExportDrawer | undefined = $state(undefined)
|
|
</script>
|
|
|
|
{#if menuOpen}
|
|
<ScheduleEditor onUpdate={() => goto('/schedules')} bind:this={scheduleEditor} />
|
|
{/if}
|
|
|
|
<Row
|
|
aiId={`script-run-button-${script.path}`}
|
|
aiDescription={`Button to access the form to run the script ${script.summary ?? script.path}`}
|
|
href={script.draft_only || (script.auto_kind === 'lib' && script.kind !== 'preprocessor')
|
|
? `${base}/scripts/edit/${script.path}`
|
|
: `${base}/scripts/get/${script.hash}?workspace=${$workspaceStore}`}
|
|
kind="script"
|
|
{marked}
|
|
path={script.path}
|
|
summary={script.summary}
|
|
{errorHandlerMuted}
|
|
workspaceId={$workspaceStore ?? ''}
|
|
canFavorite={!script.draft_only}
|
|
{depth}
|
|
{keyboardSelected}
|
|
>
|
|
{#snippet badges()}
|
|
{#if script.lock_error_logs}
|
|
<Badge color="red" baseClass="border border-red-200">Deployment failed</Badge>
|
|
{/if}
|
|
|
|
{#if script.archived}
|
|
<Badge color="red" baseClass="border">Archived</Badge>
|
|
{/if}
|
|
|
|
{#if script.auto_kind === 'lib' && script.kind !== 'preprocessor'}
|
|
<NoMainFuncBadge />
|
|
{/if}
|
|
{#if script.auto_kind === 'wac'}
|
|
<Popover notClickable>
|
|
{#snippet text()}
|
|
Workflow-as-Code
|
|
{/snippet}
|
|
<Badge small color="indigo" baseClass="border border-indigo-200">wac</Badge>
|
|
</Popover>
|
|
{/if}
|
|
{#if script.auto_kind === 'test'}
|
|
<Popover notClickable>
|
|
{#snippet text()}
|
|
CI test script
|
|
{/snippet}
|
|
<Badge small color="yellow" baseClass="border">CI test</Badge>
|
|
</Popover>
|
|
{/if}
|
|
{#if script.kind !== 'script'}
|
|
<Badge color="blue" baseClass="border"
|
|
>{script.kind === 'failure' ? 'Error handler' : capitalize(script.kind)}</Badge
|
|
>
|
|
{/if}
|
|
<SharedBadge canWrite={script.canWrite} extraPerms={script.extra_perms} />
|
|
{#if script.labels?.length}
|
|
<div class="flex items-center gap-0.5">
|
|
{#each script.labels.slice(0, 3) as label}
|
|
<Badge color="blue" small class="px-1" title="Label: {label}">{label}</Badge>
|
|
{/each}
|
|
{#if script.labels.length > 3}
|
|
<Badge
|
|
color="blue"
|
|
small
|
|
class="px-1"
|
|
title={script.labels
|
|
.slice(3)
|
|
.map((l) => 'Label: ' + l)
|
|
.join('\n')}>+{script.labels.length - 3}</Badge
|
|
>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
<div class="w-8 center-center">
|
|
<LanguageIcon lang={script.language} width={16} height={16} />
|
|
</div>
|
|
{/snippet}
|
|
|
|
{#snippet actions()}
|
|
<span class="hidden md:inline-flex gap-x-1">
|
|
{#if !$userStore?.operator}
|
|
{#if showEditButton}
|
|
{#if script.use_codebase}
|
|
<Badge
|
|
>bundle<Tooltip
|
|
>This script is deployed as a bundle and can only be deployed from the CLI for now</Tooltip
|
|
></Badge
|
|
>
|
|
{:else if script.canWrite && !script.archived}
|
|
<div>
|
|
<Button
|
|
aiId={`edit-script-button-${script.summary?.length > 0 ? script.summary : script.path}`}
|
|
aiDescription={`Edits the script ${script.summary?.length > 0 ? script.summary : script.path}`}
|
|
variant="subtle"
|
|
wrapperClasses="w-20"
|
|
unifiedSize="md"
|
|
startIcon={{ icon: Pen }}
|
|
href="{base}/scripts/edit/{script.path}"
|
|
>
|
|
Edit
|
|
</Button>
|
|
</div>
|
|
{/if}
|
|
{/if}
|
|
{#if !isCloudHosted() && !isRuleActive('DisableWorkspaceForking') && (!showEditButton || !script.canWrite)}
|
|
<div>
|
|
<Button
|
|
variant={!showEditButton ? 'default' : 'subtle'}
|
|
wrapperClasses="w-32"
|
|
unifiedSize="md"
|
|
startIcon={{ icon: GitFork }}
|
|
href={buildForkEditUrl('script', script.path)}
|
|
>
|
|
Edit in fork
|
|
</Button>
|
|
</div>
|
|
{/if}
|
|
{/if}
|
|
</span>
|
|
<Dropdown
|
|
aiId={`script-row-dropdown-${script.summary?.length > 0 ? script.summary : script.path}`}
|
|
aiDescription={`Open dropdown for script ${script.summary?.length > 0 ? script.summary : script.path} options`}
|
|
items={async () => {
|
|
let owner = isOwner(script.path, $userStore, $workspaceStore)
|
|
const canEdit = script.canWrite && showEditButton
|
|
if (script.draft_only) {
|
|
return [
|
|
{
|
|
displayName: 'View code',
|
|
icon: Code,
|
|
action: () => {
|
|
showCode(script.path, script.summary)
|
|
}
|
|
},
|
|
{
|
|
displayName: 'Delete',
|
|
icon: Trash,
|
|
action: (event) => {
|
|
// TODO
|
|
// @ts-ignore
|
|
if (event?.shiftKey) {
|
|
deleteScript(script.path)
|
|
} else {
|
|
deleteConfirmedCallback = () => {
|
|
deleteScript(script.path)
|
|
}
|
|
}
|
|
},
|
|
type: dlt,
|
|
disabled: !canEdit
|
|
}
|
|
]
|
|
}
|
|
return [
|
|
{
|
|
displayName: 'View code',
|
|
icon: Code,
|
|
action: () => {
|
|
showCode(script.path, script.summary)
|
|
}
|
|
},
|
|
...(script.auto_kind === 'wac'
|
|
? [
|
|
{
|
|
displayName: 'View JSON/YAML',
|
|
icon: FileText,
|
|
action: async () => {
|
|
const fullScript = await ScriptService.getScriptByPath({
|
|
workspace: $workspaceStore!,
|
|
path: script.path
|
|
})
|
|
wacExportDrawer?.open(fullScript)
|
|
}
|
|
}
|
|
]
|
|
: []),
|
|
{
|
|
displayName: 'Duplicate/Fork',
|
|
icon: GitFork,
|
|
href: `${base}/scripts/add?template=${script.path}`,
|
|
disabled: !showEditButton,
|
|
hide: $userStore?.operator
|
|
},
|
|
{
|
|
displayName: 'Edit in workspace fork',
|
|
icon: GitFork,
|
|
href: buildForkEditUrl('script', script.path),
|
|
hide: $userStore?.operator || isCloudHosted() || isRuleActive('DisableWorkspaceForking')
|
|
},
|
|
{
|
|
displayName: 'Move/Rename',
|
|
icon: FolderOpen,
|
|
action: () => {
|
|
moveDrawer.openDrawer(script.path, script.summary, 'script')
|
|
},
|
|
disabled: !owner || script.archived || !canEdit,
|
|
hide: $userStore?.operator
|
|
},
|
|
...(isDeployable('script', script.path, await getDeployUiSettings())
|
|
? [
|
|
{
|
|
displayName: 'Deploy to staging/prod',
|
|
icon: ChevronUpSquare,
|
|
action: () => {
|
|
deploymentDrawer.openDrawer(script.path, 'script')
|
|
},
|
|
disabled: script.archived,
|
|
hide: $userStore?.operator
|
|
}
|
|
]
|
|
: []),
|
|
{
|
|
displayName: 'View runs',
|
|
icon: List,
|
|
href: `${base}/runs/${script.path}`
|
|
},
|
|
{
|
|
displayName: 'Versions',
|
|
icon: History,
|
|
action: () => {
|
|
versionsDrawerOpen = true
|
|
}
|
|
},
|
|
{
|
|
displayName: 'Audit logs',
|
|
icon: Eye,
|
|
href: `${base}/audit_logs?resource=${script.path}`,
|
|
hide: $userStore?.operator
|
|
},
|
|
{
|
|
displayName: 'Schedule',
|
|
icon: Calendar,
|
|
action: () => {
|
|
scheduleEditor?.openNew(false, script.path)
|
|
},
|
|
disabled: script.archived,
|
|
hide: $userStore?.operator
|
|
},
|
|
{
|
|
displayName: 'Permissions',
|
|
icon: Shield,
|
|
action: () => {
|
|
shareModal.openDrawer && shareModal.openDrawer(script.path, 'script')
|
|
},
|
|
disabled: script.archived,
|
|
hide: $userStore?.operator
|
|
},
|
|
{
|
|
displayName: 'Copy path',
|
|
icon: Copy,
|
|
action: () => {
|
|
copyToClipboard(script.path)
|
|
}
|
|
},
|
|
{
|
|
displayName: 'Publish to Hub',
|
|
icon: Globe2,
|
|
action: async () => {
|
|
const scriptData = await ScriptService.getScriptByPath({
|
|
workspace: $workspaceStore!,
|
|
path: script.path
|
|
})
|
|
window.open(
|
|
scriptToHubUrl(
|
|
scriptData.content,
|
|
scriptData.summary,
|
|
scriptData.description ?? '',
|
|
scriptData.kind,
|
|
scriptData.language,
|
|
scriptData.schema,
|
|
scriptData.lock ?? '',
|
|
$hubBaseUrlStore
|
|
).toString(),
|
|
'_blank'
|
|
)
|
|
}
|
|
},
|
|
{
|
|
displayName: script.archived ? 'Unarchive' : 'Archive',
|
|
icon: Archive,
|
|
action: () => {
|
|
script.archived
|
|
? script.path && unarchiveScript(script.path)
|
|
: script.path && archiveScript(script.path)
|
|
},
|
|
type: 'delete',
|
|
disabled: !owner || !canEdit,
|
|
hide: $userStore?.operator
|
|
},
|
|
|
|
...($userStore?.is_admin || $userStore?.is_super_admin
|
|
? [
|
|
{
|
|
displayName: 'Delete',
|
|
icon: Trash,
|
|
action: (event) => {
|
|
if (event?.shiftKey) {
|
|
deleteScript(script.path)
|
|
} else {
|
|
deleteConfirmedCallback = () => {
|
|
deleteScript(script.path)
|
|
}
|
|
}
|
|
},
|
|
type: dlt,
|
|
disabled: !canEdit,
|
|
hide: $userStore?.operator
|
|
}
|
|
]
|
|
: [])
|
|
]
|
|
}}
|
|
on:open={() => {
|
|
menuOpen = true
|
|
}}
|
|
/>
|
|
{/snippet}
|
|
</Row>
|
|
|
|
{#if script}
|
|
<Drawer bind:open={versionsDrawerOpen} size="1200px">
|
|
<DrawerContent title="Versions History" on:close={() => (versionsDrawerOpen = false)}>
|
|
<ScriptVersionHistory
|
|
scriptPath={script.path}
|
|
openDetails
|
|
on:openDetails={(e) => {
|
|
if (script) {
|
|
goto(`/scripts/get/${e.detail.version}?workspace=${$workspaceStore}`)
|
|
}
|
|
versionsDrawerOpen = false
|
|
}}
|
|
/>
|
|
</DrawerContent>
|
|
</Drawer>
|
|
{/if}
|
|
|
|
<WacExportDrawer bind:this={wacExportDrawer} />
|