major schedules rewrite

This commit is contained in:
Ruben Fiszel
2022-12-18 18:45:20 +01:00
parent 43732d2fb5
commit f5a01fc768
14 changed files with 364 additions and 269 deletions
+1 -1
View File
@@ -20,7 +20,6 @@
import type { SchemaProperty } from '$lib/common'
import SimpleEditor from './SimpleEditor.svelte'
import autosize from 'svelte-autosize'
import * as autosizeLib from 'autosize'
import Toggle from './Toggle.svelte'
import Password from './Password.svelte'
import type VariableEditor from './VariableEditor.svelte'
@@ -296,6 +295,7 @@
{#if properties && Object.keys(properties).length > 0}
<div class="p-4 pl-8 border rounded w-full">
<SchemaForm
{disabled}
schema={{ properties, $schema: '', required: [], type: 'object' }}
bind:args={value}
/>
@@ -11,11 +11,15 @@
faUser,
faBarsStaggered
} from '@fortawesome/free-solid-svg-icons'
import ScheduleEditor from './ScheduleEditor.svelte'
export let job: Job
const SMALL_ICON_SCALE = 0.7
let scheduleEditor: ScheduleEditor
</script>
<ScheduleEditor bind:this={scheduleEditor} />
<div class="rounded-md p-3 bg-gray-50 shadow-sm sm:text-sm md:text-base" style="min-height: 150px;">
<JobStatus {job} />
<div>
@@ -46,9 +50,10 @@
{:else if job && job.schedule_path}
<Icon class="text-gray-700" data={faCalendar} scale={SMALL_ICON_SCALE} />
<span
>Triggered by the schedule: <a
href={`/schedule/add?edit=${job.schedule_path}&isFlow=${job.job_kind == 'flow'}`}
>{job.schedule_path}</a
>Triggered by the schedule: <button
class="break-words text-sm text-blue-600 font-normal"
on:click={() => scheduleEditor?.openEdit(job.schedule_path ?? '', job.job_kind == 'flow')}
>{job.schedule_path}</button
></span
>
{/if}
+5 -2
View File
@@ -10,7 +10,6 @@
} from '$lib/gen'
import { GroupService } from '$lib/gen'
import { superadmin, userStore, workspaceStore } from '$lib/stores'
import { sleep } from '$lib/utils'
import { createEventDispatcher } from 'svelte'
import Required from './Required.svelte'
import { Button, Drawer, DrawerContent } from './common'
@@ -28,6 +27,7 @@
export let path = ''
export let error = ''
export let disabled = false
export let checkInitialPathExistence = false
export let kind: PathKind
@@ -105,7 +105,10 @@
clearTimeout(validateTimeout)
}
validateTimeout = setTimeout(async () => {
if ((path == '' || path != initialPath) && (await pathExists(path, kind))) {
if (
(path == '' || checkInitialPathExistence || path != initialPath) &&
(await pathExists(path, kind))
) {
error = 'path already used'
} else if (meta && validateName(meta)) {
error = ''
@@ -2,6 +2,7 @@
export let label = ''
export let options: [string | { title: string; desc: string }, any][]
export let value: any
export let disabled = false
import { createEventDispatcher } from 'svelte'
import Tooltip from './Tooltip.svelte'
@@ -19,6 +20,7 @@
{val === value ? '!bg-blue-50 !border-blue-500' : ''}"
>
<input
{disabled}
type="radio"
value={val}
class="sr-only"
@@ -0,0 +1,241 @@
<script context="module">
export function load() {
return {
stuff: { title: 'New Schedule' }
}
}
</script>
<script lang="ts">
import { sendUserToast, formatCron, canWrite } from '$lib/utils'
import { ScriptService, Script, ScheduleService, type Flow, FlowService } from '$lib/gen'
import Toggle from '$lib/components/Toggle.svelte'
import Path from '$lib/components/Path.svelte'
import { Alert, Badge, Button } from '$lib/components/common'
import Tooltip from '$lib/components/Tooltip.svelte'
import { userStore, workspaceStore } from '$lib/stores'
import SchemaForm from '$lib/components/SchemaForm.svelte'
import ScriptPicker from '$lib/components/ScriptPicker.svelte'
import Required from '$lib/components/Required.svelte'
import CronInput, { OFFSET } from '$lib/components/CronInput.svelte'
import { faSave } from '@fortawesome/free-solid-svg-icons'
import Drawer from '$lib/components/common/drawer/Drawer.svelte'
import DrawerContent from '$lib/components/common/drawer/DrawerContent.svelte'
import { createEventDispatcher } from 'svelte'
let initialPath = ''
let edit = true
let schedule: string = '0 0 12 * *'
let itemKind: 'flow' | 'script' = 'script'
let script_path = ''
let initialScriptPath = ''
export function openEdit(ePath: string, isFlow: boolean) {
is_flow = isFlow
initialPath = ePath
itemKind = is_flow ? 'flow' : 'script'
path = ePath
edit = true
drawer?.openDrawer()
}
export function openNew(is_flow: boolean, initial_script_path?: string) {
edit = false
itemKind = is_flow ? 'flow' : 'script'
initialScriptPath = initial_script_path ?? ''
path = initialScriptPath
initialPath = initialScriptPath
script_path = initialScriptPath
drawer?.openDrawer()
}
$: is_flow = itemKind == 'flow'
let runnable: Script | Flow | undefined
let args: Record<string, any> = {}
let isValid = true
let path: string = ''
let enabled: boolean = false
let pathError = ''
let validCRON = true
$: allowSchedule = isValid && validCRON && script_path != ''
$: script_path != '' && loadScript(script_path)
const dispatch = createEventDispatcher()
async function loadScript(p: string | undefined): Promise<void> {
if (p) {
if (is_flow) {
runnable = await FlowService.getFlowByPath({ workspace: $workspaceStore!, path: p })
} else {
runnable = await ScriptService.getScriptByPath({ workspace: $workspaceStore!, path: p })
}
} else {
runnable = undefined
}
}
let can_write = true
async function loadSchedule(): Promise<void> {
try {
const s = await ScheduleService.getSchedule({
workspace: $workspaceStore!,
path: initialPath
})
enabled = s.enabled
schedule = s.schedule
script_path = s.script_path ?? ''
is_flow = s.is_flow
args = s.args ?? {}
can_write = canWrite(s.path, s.extra_perms, $userStore)
} catch (err) {
sendUserToast(`Could not load schedule: ${err}`, true)
}
}
async function scheduleScript(): Promise<void> {
if (edit) {
await ScheduleService.updateSchedule({
workspace: $workspaceStore!,
path: initialPath,
requestBody: {
schedule: formatCron(schedule),
args
}
})
sendUserToast(`Schedule ${path} updated`)
} else {
await ScheduleService.createSchedule({
workspace: $workspaceStore!,
requestBody: {
path,
schedule: formatCron(schedule),
offset: OFFSET,
script_path,
is_flow,
args,
enabled: true
}
})
sendUserToast(`Schedule ${path} created`)
}
dispatch('update')
drawer.closeDrawer()
}
$: {
if ($workspaceStore) {
if (edit && path != '') {
loadSchedule()
}
}
}
let drawer: Drawer
</script>
<Drawer size="900px" bind:this={drawer}>
<DrawerContent
title={edit ? `Edit ${initialPath}` : 'New schedule'}
on:close={drawer.closeDrawer}
>
<svelte:fragment slot="actions">
{#if edit}
<div class="mr-8">
<Toggle
disabled={!can_write}
checked={enabled}
options={{ right: 'enable', left: 'disable' }}
on:change={async (e) => {
await ScheduleService.setScheduleEnabled({
path: initialPath,
workspace: $workspaceStore ?? '',
requestBody: { enabled: e.detail }
})
sendUserToast(`${e.detail ? 'enabled' : 'disabled'} schedule ${initialPath}`)
}}
/>
</div>
{/if}
<Button
startIcon={{ icon: faSave }}
disabled={!allowSchedule || pathError != ''}
on:click={scheduleScript}
>
{edit ? 'Save' : 'Schedule'}
</Button>
</svelte:fragment>
<div>
{#if !edit}
<span class="font-semibold text-gray-700">Path</span>
<Path
checkInitialPathExistence
bind:error={pathError}
bind:path
{initialPath}
namePlaceholder={'my_schedule'}
kind="schedule"
/>
<div class="mb-8" />
{/if}
<h2 class="border-b pb-1 mb-2">
<span class="mr-1">Schedule</span>
<Tooltip>Schedules use CRON syntax. Seconds are mandatory.</Tooltip>
</h2>
<CronInput disabled={!can_write} bind:schedule bind:validCRON />
<h2 class="border-b pb-1 mt-8 mb-2">Runnable</h2>
{#if !edit}
<p class="text-xs mb-1 text-gray-600">
Pick a script or flow to be triggered by the schedule<Required required={true} />
</p>
<ScriptPicker
disabled={initialScriptPath != '' || !can_write}
initialPath={initialScriptPath}
kind={Script.kind.SCRIPT}
allowFlow={true}
bind:itemKind
bind:scriptPath={script_path}
/>
{:else}
<Alert type="info" title="Runnable path cannot be edited">
Once a schedule is created, the runnable path cannot be changed. However, when renaming a
script or a flow, the runnable path will automatically update itself.
</Alert>
<div class="my-2" />
<ScriptPicker
disabled
initialPath={script_path}
scriptPath={script_path}
allowFlow={true}
{itemKind}
/>
{/if}
<div class="mt-6">
<span class="font-semibold text-gray-700">Arguments</span>
{#if runnable}
{#if runnable?.schema && runnable.schema.properties && Object.keys(runnable.schema.properties).length > 0}
<SchemaForm disabled={!can_write} schema={runnable.schema} bind:isValid bind:args />
{:else}
<div class="text-xs texg-gray-700">
This {is_flow ? 'flow' : 'script'} takes no argument
</div>
{/if}
{:else}
<div class="text-xs texg-gray-700 my-2">
Pick a {is_flow ? 'flow' : 'script'} and fill its argument here
</div>
{/if}
</div>
</div>
</DrawerContent>
</Drawer>
+60 -34
View File
@@ -10,6 +10,7 @@
import RadioButton from './RadioButton.svelte'
import { Button, Drawer, DrawerContent } from './common'
import HighlightCode from './HighlightCode.svelte'
import FlowPathViewer from './flows/content/FlowPathViewer.svelte'
export let initialPath: string | undefined = undefined
export let scriptPath: string | undefined = undefined
@@ -17,9 +18,11 @@
export let allowHub = false
export let itemKind: 'hub' | 'script' | 'flow' = allowHub ? 'hub' : 'script'
export let kind: Script.kind = Script.kind.SCRIPT
export let disabled = false
let items: { value: string; label: string }[] = []
let drawerViewer: Drawer
let drawerFlowViewer: Drawer
let code: string = ''
let lang: 'deno' | 'python3' | 'go' | 'bash' | undefined
@@ -53,41 +56,64 @@
$: itemKind && $workspaceStore && loadItems()
</script>
<div class="flex flex-row items-center gap-4 w-full">
{#if options.length > 1}
<div class="w-80 mt-1">
<RadioButton bind:value={itemKind} {options} />
</div>
{/if}
<Select
value={items.find((x) => x.value == initialPath)}
class="grow"
on:change={() => {
dispatch('select', { path: scriptPath })
}}
bind:justValue={scriptPath}
{items}
placeholder="Pick a {itemKind}"
/>
{#if scriptPath !== undefined && scriptPath !== ''}
<Button
color="light"
size="xs"
on:click={async () => {
const { language, content } = await getScriptByPath(scriptPath ?? '')
code = content
lang = language
drawerViewer.openDrawer()
}}
>
Show code
</Button>
{/if}
</div>
<Drawer bind:this={drawerViewer}>
<Drawer bind:this={drawerViewer} size="900px">
<DrawerContent title="Script {scriptPath}" on:close={drawerViewer.closeDrawer}>
<HighlightCode {code} language={lang} />
</DrawerContent>
</Drawer>
<Drawer bind:this={drawerFlowViewer} size="900px">
<DrawerContent title="Flow {scriptPath}" on:close={drawerFlowViewer.closeDrawer}>
<FlowPathViewer path={scriptPath ?? ''} />
</DrawerContent>
</Drawer>
<div class="flex flex-row items-center gap-4 w-full">
{#if options.length > 1}
<div class="w-80 mt-1">
<RadioButton {disabled} bind:value={itemKind} {options} />
</div>
{/if}
{#if disabled}
<input type="text" value={scriptPath} disabled />
{:else}
<Select
value={items.find((x) => x.value == initialPath)}
class="grow"
on:change={() => {
dispatch('select', { path: scriptPath })
}}
bind:justValue={scriptPath}
{items}
placeholder="Pick a {itemKind}"
/>
{/if}
{#if scriptPath !== undefined && scriptPath !== ''}
{#if itemKind == 'flow'}
<Button
color="light"
size="xs"
on:click={async () => {
drawerFlowViewer.openDrawer()
}}
>
Show flow
</Button>
{:else}
<Button
color="light"
size="xs"
on:click={async () => {
const { language, content } = await getScriptByPath(scriptPath ?? '')
code = content
lang = language
drawerViewer.openDrawer()
}}
>
Show code
</Button>
{/if}
{/if}
</div>
@@ -141,7 +141,7 @@
>
</div>
{/if}
<div class="mb-1 font-semibold text-gray-700">General</div>
<span class="font-semibold text-gray-700">Path</span>
<Path
disabled={!can_write}
bind:error={pathError}
@@ -153,6 +153,7 @@
</div>
<div class="mt-4">
<Toggle bind:checked={variable.is_secret} options={{ right: 'Secret' }} />
<div class="mb-2" />
{#if variable.is_secret}
<Alert type="warning" title="Audit log for each access">
Every secret is encrypted at rest and in transit with a key specific to this
@@ -1,5 +1,7 @@
<script lang="ts">
import { goto } from '$app/navigation'
import Dropdown from '$lib/components/Dropdown.svelte'
import ScheduleEditor from '$lib/components/ScheduleEditor.svelte'
import SharedBadge from '$lib/components/SharedBadge.svelte'
import type ShareModal from '$lib/components/ShareModal.svelte'
@@ -39,8 +41,10 @@
sendUserToast(`Could not archive this flow ${err.body}`, true)
}
}
let scheduleEditor: ScheduleEditor
</script>
<ScheduleEditor on:update={() => goto('/schedules')} bind:this={scheduleEditor} />
<Row
href={`/flows/run/${path}`}
kind="flow"
@@ -132,7 +136,9 @@
{
displayName: 'Schedule',
icon: faCalendarAlt,
href: `/schedule/add?path=${path}&isFlow=true`
action: () => {
scheduleEditor.openNew(true, path)
}
},
{
displayName: 'Share',
@@ -1,5 +1,7 @@
<script lang="ts">
import { goto } from '$app/navigation'
import Dropdown from '$lib/components/Dropdown.svelte'
import ScheduleEditor from '$lib/components/ScheduleEditor.svelte'
import SharedBadge from '$lib/components/SharedBadge.svelte'
import type ShareModal from '$lib/components/ShareModal.svelte'
@@ -46,8 +48,11 @@
dispatch('change')
sendUserToast(`Archived script ${path}`)
}
let scheduleEditor: ScheduleEditor
</script>
<ScheduleEditor on:update={() => goto('/schedules')} bind:this={scheduleEditor} />
<Row
href={`/scripts/run/${hash}`}
kind="script"
@@ -156,7 +161,9 @@
{
displayName: 'Schedule',
icon: faCalendarAlt,
href: `/schedule/add?path=${path}`
action: () => {
scheduleEditor.openNew(false, path)
}
},
{
displayName: 'Share',
@@ -25,12 +25,16 @@
import Icon from 'svelte-awesome'
import { check } from 'svelte-awesome/icons'
import { Badge } from '../common'
import ScheduleEditor from '../ScheduleEditor.svelte'
const SMALL_ICON_SCALE = 0.7
export let job: Job
let scheduleEditor: ScheduleEditor
</script>
<ScheduleEditor on:update={() => goto('/schedules')} bind:this={scheduleEditor} />
<div class="border border-gray-400 rounded py-4">
<div class="grid grid-cols-1 lg:grid-cols-3 w-full gap-4">
<div class="flex-col">
@@ -159,9 +163,11 @@
{:else if job && job.schedule_path}
<Icon class="text-gray-700" data={faCalendar} scale={SMALL_ICON_SCALE} />
<span class="mx-2"
>Triggered by the schedule: <a
href={`/schedule/add?edit=${job.schedule_path}&isFlow=${job.job_kind == 'flow'}`}
>{job.schedule_path}</a
>Triggered by the schedule: <button
class="break-words text-sm text-blue-600 font-normal"
on:click={() =>
scheduleEditor?.openEdit(job.schedule_path ?? '', job.job_kind == 'flow')}
>{job.schedule_path}</button
></span
>
{/if}
@@ -43,6 +43,7 @@
import Icon from 'svelte-awesome'
import RunForm from '$lib/components/RunForm.svelte'
import { goto } from '$app/navigation'
import ScheduleEditor from '$lib/components/ScheduleEditor.svelte'
let userSettings: UserSettings
@@ -115,8 +116,11 @@
})
await goto('/run/' + run + '?workspace=' + $workspaceStore)
}
let scheduleEditor: ScheduleEditor
</script>
<ScheduleEditor on:update={() => loadSchedule()} bind:this={scheduleEditor} />
<UserSettings bind:this={userSettings} />
<Skeleton
@@ -148,7 +152,7 @@
Publish to Hub
</Button>
<Button
href="/schedule/add?path={flow.path}&isFlow=true"
on:click={() => scheduleEditor?.openNew(true, flow?.path ?? '')}
variant="border"
color="light"
size="xs"
@@ -305,7 +309,7 @@
}
}}
/>
<Button size="xs" href="/schedule/add?edit={flow.path}&isFlow=true"
<Button size="xs" on:click={() => scheduleEditor?.openEdit(flow?.path ?? '', true)}
>Edit schedule</Button
>
</h2>
-214
View File
@@ -1,214 +0,0 @@
<script context="module">
export function load() {
return {
stuff: { title: 'New Schedule' }
}
}
</script>
<script lang="ts">
import { page } from '$app/stores'
import { sendUserToast, formatCron } from '$lib/utils'
import { ScriptService, Script, ScheduleService, type Flow, FlowService } from '$lib/gen'
import Toggle from '$lib/components/Toggle.svelte'
import Path from '$lib/components/Path.svelte'
import { Alert, Badge, Button } from '$lib/components/common'
import Tooltip from '$lib/components/Tooltip.svelte'
import { goto } from '$app/navigation'
import { workspaceStore } from '$lib/stores'
import CenteredPage from '$lib/components/CenteredPage.svelte'
import SchemaForm from '$lib/components/SchemaForm.svelte'
import ScriptPicker from '$lib/components/ScriptPicker.svelte'
import Required from '$lib/components/Required.svelte'
import CronInput, { OFFSET } from '$lib/components/CronInput.svelte'
import PageHeader from '$lib/components/PageHeader.svelte'
import { faList, faSave } from '@fortawesome/free-solid-svg-icons'
let initialPath = $page.url.searchParams.get('edit') || ''
let edit = initialPath === '' ? false : true
let schedule: string = '0 0 12 * *'
let script_path = $page.url.searchParams.get('path') || ''
let initialScriptPath = script_path
let is_flow = $page.url.searchParams.get('isFlow') == 'true'
let itemKind: 'flow' | 'script' = is_flow ? 'flow' : 'script'
$: is_flow = itemKind == 'flow'
let runnable: Script | Flow | undefined
let args: Record<string, any> = {}
let isValid = true
let path: string = ''
let enabled: boolean = false
let pathError = ''
let validCRON = true
$: allowSchedule = isValid && validCRON && script_path != ''
$: script_path && loadScript(script_path)
async function loadScript(p: string | undefined): Promise<void> {
if (p) {
if (is_flow) {
runnable = await FlowService.getFlowByPath({ workspace: $workspaceStore!, path: p })
} else {
runnable = await ScriptService.getScriptByPath({ workspace: $workspaceStore!, path: p })
}
} else {
runnable = undefined
}
}
async function loadSchedule(): Promise<void> {
try {
const s = await ScheduleService.getSchedule({
workspace: $workspaceStore!,
path: initialPath
})
enabled = s.enabled
schedule = s.schedule
script_path = s.script_path ?? ''
is_flow = s.is_flow
initialScriptPath = script_path
args = s.args ?? {}
} catch (err) {
sendUserToast(`Could not load schedule: ${err}`, true)
}
}
async function scheduleScript(): Promise<void> {
if (edit) {
await ScheduleService.updateSchedule({
workspace: $workspaceStore!,
path: initialPath,
requestBody: {
schedule: formatCron(schedule),
args
}
})
goto('/schedules')
} else {
await ScheduleService.createSchedule({
workspace: $workspaceStore!,
requestBody: {
path,
schedule: formatCron(schedule),
offset: OFFSET,
script_path,
is_flow,
args,
enabled: true
}
})
goto('/schedules')
}
}
$: {
if ($workspaceStore) {
if (edit) {
loadSchedule()
}
}
}
</script>
<CenteredPage>
<PageHeader title={edit ? 'Edit schedule ' + initialPath : 'New schedule'}>
<Button
startIcon={{ icon: faSave }}
disabled={!allowSchedule || pathError != ''}
on:click={scheduleScript}
>
{edit ? 'Save' : 'Schedule'}
</Button>
</PageHeader>
<div>
{#if !edit}
<h2 class="border-b pb-1 mt-8 mb-2">Save schedule as</h2>
<Path
bind:error={pathError}
bind:path
{initialPath}
namePlaceholder={'my_schedule'}
kind="schedule"
/>
{/if}
<h2 class="border-b pb-1 mt-8 mb-2">Runnable</h2>
{#if !edit}
<p class="text-xs mb-1 text-gray-600">
Pick a script or flow to be triggered by the schedule<Required required={true} />
</p>
<ScriptPicker
initialPath={initialScriptPath}
kind={Script.kind.SCRIPT}
allowFlow={true}
bind:itemKind
bind:scriptPath={script_path}
/>
{:else}
<Alert type="info" title="Runnable path cannot be edited">
Once a schedule is created, the runnable path cannot be changed. However, when renaming a
script or a flow, the runnable path will automatically update itself. To edit the runnable
path, you can always delete the schedule and create a new on.
</Alert>
<div class="mt-4 flex items-center gap-2 max-w-xl">
<div> <Badge large color="blue">{itemKind}</Badge></div><input
type="text"
disabled
value={script_path}
/>
<Button
variant="border"
href="/runs/{script_path}"
color="blue"
size="md"
startIcon={{ icon: faList }}
>
View runs
</Button>
</div>
{/if}
<div class={edit ? '' : 'mt-2 md:mt-6'}>
<h2 class="border-b pb-1 mt-8 mb-2">Arguments</h2>
{#if runnable}
{#if runnable?.schema && runnable.schema.properties && Object.keys(runnable.schema.properties).length > 0}
<SchemaForm schema={runnable.schema} bind:isValid bind:args />
{:else}
<div class="text-xs texg-gray-700">
This {is_flow ? 'flow' : 'script'} takes no argument
</div>
{/if}
{:else}
<div class="text-xs texg-gray-700 my-2">
Pick a {is_flow ? 'flow' : 'script'} and fill its argument here
</div>
{/if}
</div>
<h2 class="border-b pb-1 mt-8 mb-2">
<span class="mr-1">Schedule</span>
<Tooltip>Schedules use CRON syntax. Seconds are mandatory.</Tooltip>
</h2>
<CronInput bind:schedule bind:validCRON />
{#if edit}
<h2 class="border-b pb-1 mt-8 mb-2">Enable</h2>
<Toggle
checked={enabled}
on:change={async (e) => {
await ScheduleService.setScheduleEnabled({
path: initialPath,
workspace: $workspaceStore ?? '',
requestBody: { enabled: e.detail }
})
sendUserToast(`${e.detail ? 'enabled' : 'disabled'} schedule ${initialPath}`)
}}
/>
{/if}
</div>
</CenteredPage>
+10 -5
View File
@@ -13,7 +13,6 @@
import PageHeader from '$lib/components/PageHeader.svelte'
import TableCustom from '$lib/components/TableCustom.svelte'
import Dropdown from '$lib/components/Dropdown.svelte'
import { goto } from '$app/navigation'
import ShareModal from '$lib/components/ShareModal.svelte'
import SharedBadge from '$lib/components/SharedBadge.svelte'
import {
@@ -31,6 +30,7 @@
import { Badge, Button, Skeleton } from '$lib/components/common'
import Popover from '$lib/components/Popover.svelte'
import { Icon } from 'svelte-awesome'
import ScheduleEditor from '$lib/components/ScheduleEditor.svelte'
type ScheduleW = Schedule & { canWrite: boolean }
@@ -64,11 +64,15 @@
loadSchedules()
}
}
let scheduleEditor: ScheduleEditor
</script>
<ScheduleEditor on:update={loadSchedules} bind:this={scheduleEditor} />
<CenteredPage>
<PageHeader title="Schedules" tooltip="Trigger Scripts and Flows according to a cron schedule">
<Button size="md" startIcon={{ icon: faPlus }} href="/schedule/add">New&nbsp;schedule</Button>
<Button size="md" startIcon={{ icon: faPlus }} on:click={() => scheduleEditor.openNew(false)}
>New&nbsp;schedule</Button
>
</PageHeader>
<div class="mt-10 mb-40">
{#if loading}
@@ -92,8 +96,9 @@
{#each schedules as { path, error, edited_by, edited_at, schedule, offset_, enabled, script_path, is_flow, extra_perms, canWrite }}
<tr class={enabled ? '' : 'bg-gray-50'}>
<td class="max-w-sm"
><a class="break-words text-sm" href="/schedule/add?edit={path}&isFlow={is_flow}"
>{path}</a
><button
class="break-words text-sm text-blue-600 font-normal"
on:click={() => scheduleEditor?.openEdit(path, is_flow)}>{path}</button
>
<SharedBadge {canWrite} extraPerms={extra_perms} />
</td>
@@ -176,7 +181,7 @@
icon: faEdit,
disabled: !canWrite,
action: () => {
goto(`/schedule/add?edit=${path}&isFlow=${is_flow}`)
scheduleEditor?.openEdit(path, is_flow)
}
},
{
@@ -36,7 +36,6 @@
import ShareModal from '$lib/components/ShareModal.svelte'
import { superadmin, userStore, workspaceStore } from '$lib/stores'
import SharedBadge from '$lib/components/SharedBadge.svelte'
import SvelteMarkdown from 'svelte-markdown'
import SchemaViewer from '$lib/components/SchemaViewer.svelte'
import CenteredPage from '$lib/components/CenteredPage.svelte'
import { onDestroy } from 'svelte'
@@ -57,6 +56,7 @@
import RunForm from '$lib/components/RunForm.svelte'
import { goto } from '$app/navigation'
import Popover from '$lib/components/Popover.svelte'
import ScheduleEditor from '$lib/components/ScheduleEditor.svelte'
let userSettings: UserSettings
let script: Script | undefined
@@ -163,8 +163,11 @@
sendUserToast(`Could not create job: ${err.body}`, true)
}
}
let scheduleEditor: ScheduleEditor
</script>
<ScheduleEditor bind:this={scheduleEditor} />
{#if script}
<CenteredPage>
<Skeleton {loading} layout={[[{ h: 1.5, w: 40 }], 1, [{ h: 1, w: 30 }]]} />
@@ -279,7 +282,7 @@
Share
</Button>
<Button
href="/schedule/add?path={script.path}"
on:click={() => scheduleEditor?.openNew(false, script?.path ?? '')}
variant="border"
color="light"
size="xs"