mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-20 00:02:19 +00:00
04d2ef419d
* graph mode * show colors * show module diff viewer button * better diff logic * small width merge graph diff * invert logic * simplify * better logic * put removed modules in initial position * nit * nit * fix conflicting ids * fix * add shadowed for after * better position logic * fix * cleaning * use splitpanes * add toggle * fix * sync move * icons * handle zoom * left header snippet * cleaning * cleaning * remove stats * big cleaning * fix * fix * fix * remove not working logic * invert logic * nit * use in deploymentui * fix typo * no custom style * handle nested * simpler logic * fix * fix
724 lines
20 KiB
Svelte
724 lines
20 KiB
Svelte
<script lang="ts">
|
|
import { createEventDispatcher, untrack } from 'svelte'
|
|
import { base } from '$lib/base'
|
|
import { enterpriseLicense, superadmin, workspaceStore } from '$lib/stores'
|
|
import {
|
|
AppService,
|
|
FlowService,
|
|
FolderService,
|
|
RawAppService,
|
|
ResourceService,
|
|
ScheduleService,
|
|
ScriptService,
|
|
UserService,
|
|
VariableService,
|
|
WorkspaceService
|
|
} from '$lib/gen'
|
|
import { getAllModules } from './flows/flowExplorer'
|
|
import Button from './common/button/Button.svelte'
|
|
import Tooltip from './Tooltip.svelte'
|
|
import Alert from './common/alert/Alert.svelte'
|
|
import Toggle from './Toggle.svelte'
|
|
import { Loader2 } from 'lucide-svelte'
|
|
import Badge from './common/badge/Badge.svelte'
|
|
import DiffDrawer from './DiffDrawer.svelte'
|
|
import {
|
|
existsTrigger,
|
|
getTriggerDependency,
|
|
getTriggersDeployData,
|
|
getTriggerValue,
|
|
type AdditionalInformation,
|
|
type Kind
|
|
} from '$lib/utils_deployable'
|
|
import type { TriggerKind } from './triggers'
|
|
import type { App } from './apps/types'
|
|
import { getAllGridItems } from './apps/editor/appUtils'
|
|
|
|
const dispatch = createEventDispatcher()
|
|
|
|
interface Props {
|
|
kind: Kind
|
|
initialPath?: string
|
|
additionalInformation?: AdditionalInformation | undefined
|
|
workspaceToDeployTo?: string | undefined
|
|
hideButton?: boolean
|
|
}
|
|
|
|
let {
|
|
kind,
|
|
initialPath = '',
|
|
additionalInformation = undefined,
|
|
workspaceToDeployTo = $bindable(undefined),
|
|
hideButton = false
|
|
}: Props = $props()
|
|
|
|
let seeTarget: boolean | undefined = $state(undefined)
|
|
|
|
let dependencies: { kind: Kind; path: string; include: boolean }[] | undefined = $state(undefined)
|
|
|
|
const allAlreadyExists: { [key: string]: boolean } = $state({})
|
|
|
|
let diffDrawer: DiffDrawer | undefined = $state(undefined)
|
|
let notSet: boolean | undefined = $state(undefined)
|
|
let isFlow: boolean | undefined = $state(undefined)
|
|
|
|
async function reload(path: string) {
|
|
try {
|
|
if (!$superadmin) {
|
|
await UserService.whoami({ workspace: workspaceToDeployTo! })
|
|
}
|
|
seeTarget = true
|
|
} catch {
|
|
seeTarget = false
|
|
return
|
|
}
|
|
|
|
const allDeps = await getDependencies(kind, path)
|
|
let sortedSet: { kind: Kind; path: string }[] = []
|
|
allDeps.forEach((x) => {
|
|
if (!sortedSet.find((y) => y.kind == x.kind && y.path == x.path)) {
|
|
sortedSet.push(x)
|
|
}
|
|
})
|
|
for (const dep of sortedSet) {
|
|
allAlreadyExists[computeStatusPath(dep.kind, dep.path)] = await checkAlreadyExists(
|
|
dep.kind,
|
|
dep.path
|
|
)
|
|
}
|
|
dependencies = sortedSet.map((x) => ({
|
|
...x,
|
|
include:
|
|
x.kind != 'variable' &&
|
|
x.kind != 'resource' &&
|
|
x.kind != 'resource_type' &&
|
|
(x.kind != 'folder' || !allAlreadyExists[computeStatusPath(x.kind, x.path)])
|
|
}))
|
|
}
|
|
|
|
async function getDependencies(
|
|
kind: Kind,
|
|
path: string
|
|
): Promise<{ kind: Kind; path: string }[]> {
|
|
async function rec(kind: Kind, path: string): Promise<{ kind: Kind; path: string }[]> {
|
|
if (kind == 'schedule') {
|
|
const schedule = await ScheduleService.getSchedule({ workspace: $workspaceStore!, path })
|
|
if (schedule.script_path && schedule.script_path != '') {
|
|
if (schedule.script_path) {
|
|
return [{ kind: 'script', path: schedule.script_path }]
|
|
} else {
|
|
return [{ kind: 'flow', path: schedule.script_path }]
|
|
}
|
|
} else {
|
|
return []
|
|
}
|
|
} else if (kind == 'flow') {
|
|
const flow = await FlowService.getFlowByPath({ workspace: $workspaceStore!, path })
|
|
return getAllModules(flow.value.modules, flow.value.failure_module).flatMap((x) => {
|
|
let result: { kind: Kind; path: string }[] = []
|
|
if (x.value.type == 'script' || x.value.type == 'rawscript' || x.value.type == 'flow') {
|
|
Object.values(x.value.input_transforms).forEach((y) => {
|
|
if (y.type == 'static' && typeof y.value == 'string') {
|
|
if (y.value.startsWith('$res:')) {
|
|
result.push({ kind: 'resource', path: y.value.substring(5) })
|
|
} else if (y.value.startsWith('$var:')) {
|
|
result.push({ kind: 'variable', path: y.value.substring(5) })
|
|
}
|
|
}
|
|
})
|
|
}
|
|
if (x.value.type == 'script') {
|
|
if (x.value.path && !x.value.path.startsWith('hub/')) {
|
|
result.push({ kind: 'script', path: x.value.path })
|
|
}
|
|
} else if (x.value.type == 'flow') {
|
|
if (x.value.path) {
|
|
result.push({ kind: 'flow', path: x.value.path })
|
|
}
|
|
}
|
|
return result
|
|
})
|
|
} else if (kind == 'app') {
|
|
const app = await AppService.getAppByPath({ workspace: $workspaceStore!, path })
|
|
console.log('app', app)
|
|
let appValue = app.value as App
|
|
let result: { kind: Kind; path: string }[] = []
|
|
getAllGridItems(appValue).forEach((gridItem) => {
|
|
const ci = gridItem.data.componentInput
|
|
if (ci?.type == 'runnable' && ci.runnable?.type == 'runnableByPath') {
|
|
if (ci.runnable.runType == 'script') {
|
|
result.push({ kind: 'script', path: ci.runnable.path })
|
|
} else if (ci.runnable.runType == 'flow') {
|
|
result.push({ kind: 'flow', path: ci.runnable.path })
|
|
}
|
|
}
|
|
})
|
|
return result
|
|
} else if (kind == 'resource') {
|
|
const res = await ResourceService.getResource({ workspace: $workspaceStore!, path })
|
|
function recObj(obj: any) {
|
|
if (typeof obj == 'string' && obj.startsWith('$var:')) {
|
|
return [{ kind: 'variable', path: obj.substring(5) }]
|
|
} else if (typeof obj == 'object') {
|
|
return Object.values(obj).flatMap((x) => recObj(x))
|
|
} else {
|
|
return []
|
|
}
|
|
}
|
|
|
|
return [...recObj(res.value), { kind: 'resource_type', path: res.resource_type }]
|
|
} else if (kind == 'trigger') {
|
|
if (additionalInformation?.triggers) {
|
|
return getTriggerDependency(additionalInformation.triggers.kind, path, $workspaceStore!)
|
|
}
|
|
throw new Error('Missing trigger information')
|
|
}
|
|
return []
|
|
}
|
|
let toProcess = [{ kind, path }]
|
|
let processed: { kind: Kind; path: string }[] = []
|
|
while (toProcess.length > 0) {
|
|
const { kind, path } = toProcess.pop()!
|
|
toProcess.push(...(await rec(kind, path)))
|
|
processed.push({ kind, path })
|
|
}
|
|
let folders: string[] = []
|
|
for (const p of processed) {
|
|
let split = p.path.split('/')
|
|
if (split.length > 2 && split[0] == 'f' && !folders.includes(split[1])) {
|
|
folders.push(split[1])
|
|
processed.push({ kind: 'folder', path: split[1] })
|
|
}
|
|
}
|
|
return processed
|
|
}
|
|
|
|
async function checkAlreadyExists(kind: Kind, path: string): Promise<boolean> {
|
|
let exists: boolean
|
|
if (kind == 'flow') {
|
|
exists = await FlowService.existsFlowByPath({
|
|
workspace: workspaceToDeployTo!,
|
|
path: path
|
|
})
|
|
} else if (kind == 'script') {
|
|
exists = await ScriptService.existsScriptByPath({
|
|
workspace: workspaceToDeployTo!,
|
|
path: path
|
|
})
|
|
} else if (kind == 'app') {
|
|
exists = await AppService.existsApp({
|
|
workspace: workspaceToDeployTo!,
|
|
path: path
|
|
})
|
|
} else if (kind == 'raw_app') {
|
|
exists = await RawAppService.existsRawApp({
|
|
workspace: workspaceToDeployTo!,
|
|
path: path
|
|
})
|
|
} else if (kind == 'variable') {
|
|
exists = await VariableService.existsVariable({
|
|
workspace: workspaceToDeployTo!,
|
|
path: path
|
|
})
|
|
} else if (kind == 'resource') {
|
|
exists = await ResourceService.existsResource({
|
|
workspace: workspaceToDeployTo!,
|
|
path: path
|
|
})
|
|
} else if (kind == 'schedule') {
|
|
exists = await ScheduleService.existsSchedule({
|
|
workspace: workspaceToDeployTo!,
|
|
path: path
|
|
})
|
|
} else if (kind == 'resource_type') {
|
|
exists = await ResourceService.existsResourceType({
|
|
workspace: workspaceToDeployTo!,
|
|
path: path
|
|
})
|
|
} else if (kind == 'folder') {
|
|
exists = await FolderService.existsFolder({
|
|
workspace: workspaceToDeployTo!,
|
|
name: path
|
|
})
|
|
} else if (kind === 'trigger') {
|
|
const triggersKind: TriggerKind[] = [
|
|
'kafka',
|
|
'mqtt',
|
|
'nats',
|
|
'postgres',
|
|
'routes',
|
|
'schedules',
|
|
'sqs',
|
|
'websockets',
|
|
'gcp'
|
|
]
|
|
if (
|
|
additionalInformation?.triggers &&
|
|
triggersKind.includes(additionalInformation.triggers.kind)
|
|
) {
|
|
exists = await existsTrigger(
|
|
{ workspace: workspaceToDeployTo!, path },
|
|
additionalInformation.triggers.kind
|
|
)
|
|
} else {
|
|
throw new Error(
|
|
`Unexpected triggers kind, expected one of: '${triggersKind.join(', ')}' got: ${
|
|
additionalInformation?.triggers?.kind
|
|
}`
|
|
)
|
|
}
|
|
} else {
|
|
throw new Error(`Unknown kind ${kind}`)
|
|
}
|
|
return exists
|
|
}
|
|
|
|
const deploymentStatus: Record<
|
|
string,
|
|
{ status: 'loading' | 'deployed' | 'failed'; error?: string }
|
|
> = $state({})
|
|
|
|
async function deploy(kind: Kind, path: string) {
|
|
const statusPath = `${kind}:${path}`
|
|
deploymentStatus[statusPath] = { status: 'loading' }
|
|
try {
|
|
let alreadyExists = await checkAlreadyExists(kind, path)
|
|
if (kind == 'flow') {
|
|
const flow = await FlowService.getFlowByPath({
|
|
workspace: $workspaceStore!,
|
|
path: path
|
|
})
|
|
getAllModules(flow.value.modules).forEach((x) => {
|
|
if (x.value.type == 'script' && x.value.hash != undefined) {
|
|
x.value.hash = undefined
|
|
}
|
|
})
|
|
if (alreadyExists) {
|
|
await FlowService.updateFlow({
|
|
workspace: workspaceToDeployTo!,
|
|
path: path,
|
|
requestBody: {
|
|
...flow
|
|
}
|
|
})
|
|
} else {
|
|
await FlowService.createFlow({
|
|
workspace: workspaceToDeployTo!,
|
|
requestBody: {
|
|
...flow
|
|
}
|
|
})
|
|
}
|
|
} else if (kind == 'script') {
|
|
const script = await ScriptService.getScriptByPath({
|
|
workspace: $workspaceStore!,
|
|
path: path
|
|
})
|
|
await ScriptService.createScript({
|
|
workspace: workspaceToDeployTo!,
|
|
requestBody: {
|
|
...script,
|
|
lock: script.lock,
|
|
parent_hash: alreadyExists
|
|
? (
|
|
await ScriptService.getScriptByPath({
|
|
workspace: workspaceToDeployTo!,
|
|
path: path
|
|
})
|
|
).hash
|
|
: undefined
|
|
}
|
|
})
|
|
} else if (kind == 'app') {
|
|
const app = await AppService.getAppByPath({
|
|
workspace: $workspaceStore!,
|
|
path: path
|
|
})
|
|
if (alreadyExists) {
|
|
await AppService.updateApp({
|
|
workspace: workspaceToDeployTo!,
|
|
path: path,
|
|
requestBody: {
|
|
...app
|
|
}
|
|
})
|
|
} else {
|
|
await AppService.createApp({
|
|
workspace: workspaceToDeployTo!,
|
|
requestBody: {
|
|
...app
|
|
}
|
|
})
|
|
}
|
|
} else if (kind == 'variable') {
|
|
const variable = await VariableService.getVariable({
|
|
workspace: $workspaceStore!,
|
|
path: path,
|
|
decryptSecret: true
|
|
})
|
|
if (alreadyExists) {
|
|
await VariableService.updateVariable({
|
|
workspace: workspaceToDeployTo!,
|
|
path: path,
|
|
requestBody: {
|
|
path: path,
|
|
value: variable.value ?? '',
|
|
is_secret: variable.is_secret,
|
|
description: variable.description ?? ''
|
|
},
|
|
alreadyEncrypted: false
|
|
})
|
|
} else {
|
|
await VariableService.createVariable({
|
|
workspace: workspaceToDeployTo!,
|
|
requestBody: {
|
|
path: path,
|
|
value: variable.value ?? '',
|
|
is_secret: variable.is_secret,
|
|
description: variable.description ?? ''
|
|
}
|
|
})
|
|
}
|
|
} else if (kind == 'resource') {
|
|
const resource = await ResourceService.getResource({
|
|
workspace: $workspaceStore!,
|
|
path: path
|
|
})
|
|
if (alreadyExists) {
|
|
await ResourceService.updateResource({
|
|
workspace: workspaceToDeployTo!,
|
|
path: path,
|
|
requestBody: {
|
|
path: path,
|
|
value: resource.value ?? '',
|
|
description: resource.description ?? ''
|
|
}
|
|
})
|
|
} else {
|
|
await ResourceService.createResource({
|
|
workspace: workspaceToDeployTo!,
|
|
requestBody: {
|
|
path: path,
|
|
value: resource.value ?? '',
|
|
resource_type: resource.resource_type,
|
|
description: resource.description ?? ''
|
|
}
|
|
})
|
|
}
|
|
} else if (kind == 'resource_type') {
|
|
const resource = await ResourceService.getResourceType({
|
|
workspace: $workspaceStore!,
|
|
path: path
|
|
})
|
|
if (alreadyExists) {
|
|
await ResourceService.updateResourceType({
|
|
workspace: workspaceToDeployTo!,
|
|
path: path,
|
|
requestBody: {
|
|
schema: resource.schema,
|
|
description: resource.description ?? ''
|
|
}
|
|
})
|
|
} else {
|
|
await ResourceService.createResourceType({
|
|
workspace: workspaceToDeployTo!,
|
|
requestBody: {
|
|
description: resource.description ?? '',
|
|
schema: resource.schema,
|
|
name: resource.name
|
|
}
|
|
})
|
|
}
|
|
} else if (kind == 'raw_app') {
|
|
throw new Error('Raw app deploy not implemented yet')
|
|
// const app = await RawAppService.getRawAppData({
|
|
// workspace: $workspaceStore!,
|
|
// path: path
|
|
// })
|
|
// if (alreadyExists) {
|
|
// }
|
|
// await RawAppService.updateRawApp({
|
|
// workspace: $workspaceStore!,
|
|
// path: path,
|
|
// requestBody: {
|
|
// path: path
|
|
// }
|
|
// })
|
|
} else if (kind == 'folder') {
|
|
await FolderService.createFolder({
|
|
workspace: workspaceToDeployTo!,
|
|
requestBody: {
|
|
name: path
|
|
}
|
|
})
|
|
} else if (kind === 'trigger') {
|
|
if (additionalInformation?.triggers) {
|
|
const { data, createFn, updateFn } = await getTriggersDeployData(
|
|
additionalInformation.triggers.kind,
|
|
path,
|
|
$workspaceStore!
|
|
)
|
|
if (alreadyExists) {
|
|
await updateFn({
|
|
path,
|
|
workspace: workspaceToDeployTo!,
|
|
requestBody: data
|
|
} as any)
|
|
} else {
|
|
await createFn({
|
|
workspace: workspaceToDeployTo!,
|
|
requestBody: data
|
|
} as any)
|
|
}
|
|
} else {
|
|
throw new Error('Missing triggers kind')
|
|
}
|
|
} else {
|
|
throw new Error(`Unknown kind ${kind}`)
|
|
}
|
|
|
|
allAlreadyExists[statusPath] = true
|
|
deploymentStatus[statusPath] = { status: 'deployed' }
|
|
} catch (e) {
|
|
deploymentStatus[statusPath] = { status: 'failed', error: e.body || e.message }
|
|
}
|
|
}
|
|
|
|
export function deployAll() {
|
|
dependencies?.slice().forEach(async ({ kind, path, include }) => {
|
|
if (include) {
|
|
await deploy(kind, path)
|
|
}
|
|
})
|
|
dispatch('update', initialPath)
|
|
}
|
|
|
|
function computeStatusPath(kind: Kind, path: string) {
|
|
return `${kind}:${path}`
|
|
}
|
|
|
|
async function getValue(kind: Kind, path: string, workspace: string) {
|
|
try {
|
|
if (kind == 'flow') {
|
|
const flow = await FlowService.getFlowByPath({
|
|
workspace: workspace,
|
|
path: path
|
|
})
|
|
getAllModules(flow.value.modules).forEach((x) => {
|
|
if (x.value.type == 'script' && x.value.hash != undefined) {
|
|
x.value.hash = undefined
|
|
}
|
|
})
|
|
return { summary: flow.summary, description: flow.description, value: flow.value }
|
|
} else if (kind == 'script') {
|
|
const script = await ScriptService.getScriptByPath({
|
|
workspace: workspace,
|
|
path: path
|
|
})
|
|
return {
|
|
content: script.content,
|
|
lock: script.lock,
|
|
schema: script.schema,
|
|
summary: script.summary,
|
|
language: script.language
|
|
}
|
|
} else if (kind == 'app') {
|
|
const app = await AppService.getAppByPath({
|
|
workspace: workspace,
|
|
path: path
|
|
})
|
|
return app
|
|
} else if (kind == 'variable') {
|
|
const variable = await VariableService.getVariable({
|
|
workspace: workspace,
|
|
path: path,
|
|
decryptSecret: true
|
|
})
|
|
return variable.value
|
|
} else if (kind == 'resource') {
|
|
const resource = await ResourceService.getResource({
|
|
workspace: workspace,
|
|
path: path
|
|
})
|
|
return resource.value
|
|
} else if (kind == 'resource_type') {
|
|
const resource = await ResourceService.getResourceType({
|
|
workspace: workspace,
|
|
path: path
|
|
})
|
|
return resource.schema
|
|
} else if (kind == 'raw_app') {
|
|
throw new Error('Raw app deploy not implemented yet')
|
|
// const app = await RawAppService.getRawAppData({
|
|
// workspace: workspace,
|
|
// path: path
|
|
// })
|
|
// if (alreadyExists) {
|
|
// }
|
|
// await RawAppService.updateRawApp({
|
|
// workspace: workspace,
|
|
// path: path,
|
|
// requestBody: {
|
|
// path: path
|
|
// }
|
|
// })
|
|
} else if (kind == 'folder') {
|
|
const folder = await FolderService.getFolder({
|
|
workspace: workspace,
|
|
name: path
|
|
})
|
|
return {
|
|
name: folder.name
|
|
}
|
|
} else if (kind == 'trigger') {
|
|
if (additionalInformation?.triggers) {
|
|
return await getTriggerValue(additionalInformation.triggers.kind, path, workspace)
|
|
} else {
|
|
throw new Error(`Missing trigger information`)
|
|
}
|
|
} else {
|
|
throw new Error(`Unknown kind ${kind}`)
|
|
}
|
|
} catch {
|
|
return {}
|
|
}
|
|
}
|
|
|
|
async function showDiff(kind: Kind, path: string) {
|
|
diffDrawer?.openDrawer()
|
|
let values = await Promise.all([
|
|
getValue(kind, path, workspaceToDeployTo!),
|
|
getValue(kind, path, $workspaceStore!)
|
|
])
|
|
diffDrawer?.setDiff({
|
|
mode: 'simple',
|
|
original: values?.[0] as any,
|
|
current: values?.[1] as any,
|
|
title: 'Staging/prod <> Dev'
|
|
})
|
|
}
|
|
$effect(() => {
|
|
WorkspaceService.getDeployTo({ workspace: $workspaceStore! }).then((x) => {
|
|
workspaceToDeployTo = x.deploy_to
|
|
if (x.deploy_to == undefined) {
|
|
notSet = true
|
|
}
|
|
})
|
|
})
|
|
$effect(() => {
|
|
workspaceToDeployTo && initialPath && untrack(() => reload(initialPath))
|
|
})
|
|
</script>
|
|
|
|
<div class="mt-6"></div>
|
|
|
|
{#if !$enterpriseLicense}
|
|
<Alert type="warning" title="Enterprise license required"
|
|
>Deploy to staging/prod from the web UI is only available with an enterprise license</Alert
|
|
>
|
|
{:else if notSet == true}
|
|
<Alert type="error" title="Staging/Prod deploy not set up"
|
|
>As an admin, go to Settings {'->'} Workspace {'->'} Deployment UI</Alert
|
|
>
|
|
{:else}
|
|
<Alert type="info" title="Shareable page"
|
|
>Share this <a href="{base}/deploy/{kind}/{initialPath}">link</a> to have another properly permissioned
|
|
user do the deployment</Alert
|
|
>
|
|
|
|
<h3 class="mb-2 mt-8"
|
|
>Destination Workspace <Tooltip
|
|
>Workspace to deploy to is set in the workspace settings</Tooltip
|
|
></h3
|
|
>
|
|
<input class="max-w-xs" type="text" disabled value={workspaceToDeployTo} />
|
|
|
|
{#if seeTarget == undefined}
|
|
<div class="mt-6"></div>
|
|
<Loader2 class="animate-spin" />
|
|
{:else if seeTarget == true}
|
|
<h3 class="mb-6 mt-16">All related deployable items</h3>
|
|
|
|
<DiffDrawer bind:this={diffDrawer} {isFlow} />
|
|
<div class="grid grid-cols-9 justify-center max-w-3xl gap-2">
|
|
{#each dependencies ?? [] as { kind, path, include }, i}
|
|
{@const statusPath = computeStatusPath(kind, path)}
|
|
<div class="col-span-1 truncate text-secondary text-sm pt-0.5">{kind}</div><div
|
|
class="col-span-5 truncate font-semibold">{path}</div
|
|
><div class="col-span-1 pt-1.5">
|
|
<Toggle
|
|
size="xs"
|
|
checked={include}
|
|
on:change={(e) => {
|
|
if (dependencies?.[i]) {
|
|
dependencies[i].include = e.detail
|
|
}
|
|
}}
|
|
/>
|
|
</div>
|
|
<div class="col-span-1">
|
|
{#if allAlreadyExists[statusPath] == false}
|
|
{#if include}
|
|
<Badge
|
|
>New <Tooltip
|
|
>This {kind} doesn't exist yet on the target and will be created by the deployment</Tooltip
|
|
></Badge
|
|
>
|
|
{:else}
|
|
<Badge color="red">
|
|
Missing
|
|
<Tooltip
|
|
>{#if kind == 'resource_type'}
|
|
Resource types are not re-deployed by default. We strongly recommend to add
|
|
shared resource types in 'admin' workspace, which will have them be shared to
|
|
every workspace.
|
|
{:else}
|
|
This {kind} doesn't exist and is not included in the deployment. Variables and Resources
|
|
are considered to be workspace specific and are never included by default.
|
|
{/if}</Tooltip
|
|
>
|
|
</Badge>
|
|
{/if}
|
|
{:else if allAlreadyExists[statusPath] == true}
|
|
<button
|
|
class="text-blue-600 font-normal mt-1"
|
|
onclick={() => {
|
|
showDiff(kind, path)
|
|
isFlow = kind === 'flow'
|
|
}}>diff</button
|
|
>
|
|
{/if}</div
|
|
>
|
|
<div class="col-span-1 pr-1">
|
|
{#if deploymentStatus[statusPath]}
|
|
{#if deploymentStatus[statusPath].status == 'loading'}
|
|
<Loader2 class="animate-spin" />
|
|
{:else if deploymentStatus[statusPath].status == 'deployed'}
|
|
<Badge color="green">Deployed</Badge>
|
|
{:else if deploymentStatus[statusPath].status == 'failed'}
|
|
<div class="inline-flex gap-1">
|
|
<Badge color="red">Failed</Badge>
|
|
<Tooltip>{deploymentStatus[statusPath].error}</Tooltip></div
|
|
>
|
|
{/if}
|
|
{:else}
|
|
<Button color="light" size="xs" on:click={() => deploy(kind, path)}>Deploy</Button>
|
|
{/if}
|
|
</div>
|
|
{/each}
|
|
</div>
|
|
|
|
{#if !hideButton}
|
|
<div class="mt-16 flex flex-row-reverse max-w-3xl"
|
|
><Button on:click={deployAll}>Deploy all toggled</Button></div
|
|
>
|
|
{/if}
|
|
{:else}
|
|
<div class="my-2"></div>
|
|
<Alert type="error" title="User not allowed to deploy to this workspace"
|
|
>Ask a permissioned user to deploy this item using the shareable link or get the proper
|
|
permissions on the target workspace</Alert
|
|
>
|
|
{/if}
|
|
{/if}
|