diff --git a/frontend/src/lib/components/FlowBuilder.svelte b/frontend/src/lib/components/FlowBuilder.svelte index 43a713033b..d3376054a7 100644 --- a/frontend/src/lib/components/FlowBuilder.svelte +++ b/frontend/src/lib/components/FlowBuilder.svelte @@ -88,7 +88,7 @@ const dispatch = createEventDispatcher() async function createSchedule(path: string) { - const { cron, timezone, args, enabled } = $scheduleStore + const { cron, timezone, args, enabled, summary } = $scheduleStore try { await ScheduleService.createSchedule({ @@ -100,7 +100,8 @@ script_path: path, is_flow: true, args, - enabled + enabled, + summary } }) } catch (err) { @@ -215,7 +216,7 @@ // console.log('flow', computeUnlockedSteps(flow)) // del // loadingSave = false // del // return - const { cron, timezone, args, enabled } = $scheduleStore + const { cron, timezone, args, enabled, summary } = $scheduleStore if (newFlow) { try { localStorage.removeItem('flow') @@ -256,14 +257,20 @@ workspace: $workspaceStore ?? '', path: initialPath }) - if (JSON.stringify(schedule.args) != JSON.stringify(args) || schedule.schedule != cron) { + if ( + JSON.stringify(schedule.args) != JSON.stringify(args) || + schedule.schedule != cron || + schedule.timezone != timezone || + schedule.summary != summary + ) { await ScheduleService.updateSchedule({ workspace: $workspaceStore ?? '', path: initialPath, requestBody: { schedule: formatCron(cron), timezone, - args + args, + summary } }) } @@ -338,6 +345,7 @@ } const scheduleStore = writable({ + summary: undefined, args: {}, cron: '', timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, @@ -373,14 +381,15 @@ }) async function loadSchedule() { - loadFlowSchedule(initialPath, $workspaceStore) + loadFlowSchedule(initialPath, $workspaceStore!) .then((schedule: Schedule) => { scheduleStore.set(schedule) }) .catch(() => { scheduleStore.set({ + summary: undefined, cron: '0 */5 * * *', - timezone: 'UTC', + timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, args: {}, enabled: false }) diff --git a/frontend/src/lib/components/ScheduleEditorInner.svelte b/frontend/src/lib/components/ScheduleEditorInner.svelte index cb0a98fe30..0b631ff77f 100644 --- a/frontend/src/lib/components/ScheduleEditorInner.svelte +++ b/frontend/src/lib/components/ScheduleEditorInner.svelte @@ -72,9 +72,10 @@ drawer?.openDrawer() } - export async function openNew(is_flow: boolean, initial_script_path?: string) { + export async function openNew(nis_flow: boolean, initial_script_path?: string) { args = {} runnable = undefined + is_flow = nis_flow let defaultErrorHandlerMaybe = undefined let defaultRecoveryHandlerMaybe = undefined if ($workspaceStore) { @@ -87,7 +88,7 @@ } edit = false - itemKind = is_flow ? 'flow' : 'script' + itemKind = nis_flow ? 'flow' : 'script' initialScriptPath = initial_script_path ?? '' summary = '' no_flow_overlap = false diff --git a/frontend/src/lib/components/ScriptBuilder.svelte b/frontend/src/lib/components/ScriptBuilder.svelte index 5b3d8ad91a..6785c3aef0 100644 --- a/frontend/src/lib/components/ScriptBuilder.svelte +++ b/frontend/src/lib/components/ScriptBuilder.svelte @@ -1,5 +1,12 @@ @@ -387,6 +484,7 @@ cannot be inferred from the type directly. + Schedule
@@ -858,6 +956,9 @@
+ + +
@@ -888,6 +989,21 @@
+ {#if $scheduleStore.enabled} + + {/if}
+
+ + {#if schedules} + {#if schedules.length == 0} +
No other schedules
+ {:else} +
+ {#each schedules as schedule (schedule.path)} +
{schedule.path}
{schedule.schedule}
+
{schedule.enabled ? 'on' : 'off'}
+ +
+ {/each} +
+ {/if} + {:else} + + {/if} +{/if} diff --git a/frontend/src/lib/components/flows/content/FlowSchedules.svelte b/frontend/src/lib/components/flows/content/FlowSchedules.svelte index e996b4a3e5..c0922c3e3a 100644 --- a/frontend/src/lib/components/flows/content/FlowSchedules.svelte +++ b/frontend/src/lib/components/flows/content/FlowSchedules.svelte @@ -5,10 +5,45 @@ import { emptyString } from '$lib/utils' import { getContext } from 'svelte' import type { FlowEditorContext } from '../types' + import { Alert, Button, Skeleton } from '$lib/components/common' + import ScheduleEditor from '$lib/components/ScheduleEditor.svelte' + import { ScheduleService, type Schedule } from '$lib/gen' + import { workspaceStore } from '$lib/stores' + import { Calendar } from 'lucide-svelte' - const { schedule, flowStore } = getContext('FlowEditorContext') + const { schedule, flowStore, initialPath } = getContext('FlowEditorContext') + let schedules: Schedule[] | undefined = undefined + + async function loadSchedules() { + try { + schedules = ( + await ScheduleService.listSchedules({ + workspace: $workspaceStore ?? '', + path: initialPath, + isFlow: true + }) + ).filter((s) => s.path != initialPath) + } catch (e) { + console.error('impossible to load schedules') + } + } + + let scheduleEditor: ScheduleEditor + + $: initialPath && loadSchedules() +
+ + +
+
@@ -23,3 +58,46 @@ right: 'Schedule enabled' }} /> + + Changes to the primary schedule are only applied upon deploy. Other schedules' changes are applied + immediately. + + +{#if initialPath != ''} + { + loadSchedules() + }} + bind:this={scheduleEditor} + /> +

Other schedules

+
+ +
+ + {#if schedules} + {#if schedules.length == 0} +
No other schedules
+ {:else} +
+ {#each schedules as schedule (schedule.path)} +
{schedule.path}
{schedule.schedule}
+
{schedule.enabled ? 'on' : 'off'}
+ +
+ {/each} +
+ {/if} + {:else} + + {/if} +{/if} diff --git a/frontend/src/lib/components/flows/scheduleUtils.ts b/frontend/src/lib/components/flows/scheduleUtils.ts index d264fe7529..2390043ac1 100644 --- a/frontend/src/lib/components/flows/scheduleUtils.ts +++ b/frontend/src/lib/components/flows/scheduleUtils.ts @@ -1,6 +1,7 @@ import { ScheduleService } from '$lib/gen' export type Schedule = { + summary: string | undefined args: Record cron: string timezone: string @@ -8,7 +9,7 @@ export type Schedule = { } // Load the schedule of a flow given its path and the workspace -export async function loadFlowSchedule(path: string, workspace: string = ''): Promise { +export async function loadFlowSchedule(path: string, workspace: string): Promise { const existsSchedule = await ScheduleService.existsSchedule({ workspace, path @@ -24,6 +25,7 @@ export async function loadFlowSchedule(path: string, workspace: string = ''): Pr }) return { + summary: schedule.summary, enabled: schedule.enabled, cron: schedule.schedule, timezone: schedule.timezone, diff --git a/frontend/src/lib/scripts.ts b/frontend/src/lib/scripts.ts index 7880aca94f..64f8be7f4f 100644 --- a/frontend/src/lib/scripts.ts +++ b/frontend/src/lib/scripts.ts @@ -1,6 +1,6 @@ import { get } from 'svelte/store' import type { Schema, SupportedLanguage } from './common' -import { FlowService, Script, ScriptService } from './gen' +import { FlowService, Script, ScriptService, ScheduleService } from './gen' import { workspaceStore } from './stores' export function scriptLangToEditorLang(lang: Script.language) { @@ -35,6 +35,42 @@ export function scriptLangToEditorLang(lang: Script.language) { } } +export type ScriptSchedule = { + summary: string | undefined + args: Record + cron: string + timezone: string + enabled: boolean +} + +// Load the schedule of a flow given its path and the workspace +export async function loadScriptSchedule( + path: string, + workspace: string +): Promise { + const existsSchedule = await ScheduleService.existsSchedule({ + workspace, + path + }) + + if (!existsSchedule) { + return undefined + } + + const schedule = await ScheduleService.getSchedule({ + workspace, + path + }) + + return { + summary: schedule.summary, + enabled: schedule.enabled, + cron: schedule.schedule, + timezone: schedule.timezone, + args: schedule.args ?? {} + } +} + export async function loadSchemaFlow(path: string): Promise { const flow = await FlowService.getFlowByPath({ workspace: get(workspaceStore)!, diff --git a/frontend/src/routes/(root)/(logged)/flows/get/[...path]/+page.svelte b/frontend/src/routes/(root)/(logged)/flows/get/[...path]/+page.svelte index 556918241f..1fb17133a0 100644 --- a/frontend/src/routes/(root)/(logged)/flows/get/[...path]/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/flows/get/[...path]/+page.svelte @@ -6,7 +6,7 @@ import DetailPageLayout from '$lib/components/details/DetailPageLayout.svelte' import { goto } from '$app/navigation' - import { Alert, Badge as HeaderBadge, Skeleton } from '$lib/components/common' + import { Alert, Button, Badge as HeaderBadge, Skeleton } from '$lib/components/common' import MoveDrawer from '$lib/components/MoveDrawer.svelte' import RunForm from '$lib/components/RunForm.svelte' import ShareModal from '$lib/components/ShareModal.svelte' @@ -27,7 +27,8 @@ History, Columns, Pen, - Eye + Eye, + Calendar } from 'lucide-svelte' import DetailPageHeader from '$lib/components/details/DetailPageHeader.svelte' @@ -42,6 +43,7 @@ import TimeAgo from '$lib/components/TimeAgo.svelte' import ClipboardPanel from '$lib/components/details/ClipboardPanel.svelte' import FlowGraphViewerStep from '$lib/components/FlowGraphViewerStep.svelte' + import { loadFlowSchedule, type Schedule } from '$lib/components/flows/scheduleUtils' let flow: Flow | undefined let can_write = false @@ -76,9 +78,13 @@ goto('/') } + let schedule: Schedule | undefined = undefined async function loadFlow(): Promise { flow = await FlowService.getFlowByPath({ workspace: $workspaceStore!, path }) can_write = canWrite(flow.path, flow.extra_perms!, $userStore) + try { + schedule = await loadFlowSchedule(path, $workspaceStore!) + } catch {} } $: urlAsync = `${$page.url.origin}/api/w/${$workspaceStore}/jobs/run/f/${flow?.path}` @@ -307,6 +313,21 @@
{/if} + {#if schedule?.enabled} + + {/if} diff --git a/frontend/src/routes/(root)/(logged)/scripts/get/[...hash]/+page.svelte b/frontend/src/routes/(root)/(logged)/scripts/get/[...hash]/+page.svelte index e6fd59e173..2f5b4a9f27 100644 --- a/frontend/src/routes/(root)/(logged)/scripts/get/[...hash]/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/scripts/get/[...hash]/+page.svelte @@ -21,7 +21,8 @@ Badge, Alert, DrawerContent, - Drawer + Drawer, + Button } from '$lib/components/common' import Skeleton from '$lib/components/common/skeleton/Skeleton.svelte' import RunForm from '$lib/components/RunForm.svelte' @@ -41,6 +42,7 @@ Activity, Archive, ArchiveRestore, + Calendar, Eye, FolderOpen, GitFork, @@ -64,6 +66,7 @@ import TimeAgo from '$lib/components/TimeAgo.svelte' import ClipboardPanel from '$lib/components/details/ClipboardPanel.svelte' import PersistentScriptDrawer from '$lib/components/PersistentScriptDrawer.svelte' + import { loadScriptSchedule, type ScriptSchedule } from '$lib/scripts' let script: Script | undefined let topHash: string | undefined @@ -139,6 +142,7 @@ } } } + let schedule: ScriptSchedule | undefined = undefined async function loadScript(hash: string): Promise { try { @@ -147,6 +151,7 @@ script = await ScriptService.getScriptByPath({ workspace: $workspaceStore!, path: hash }) hash = script.hash } + schedule = await loadScriptSchedule(script.path, $workspaceStore!) can_write = script.workspace_id == $workspaceStore && canWrite(script.path, script.extra_perms!, $userStore) @@ -488,6 +493,21 @@
{/if} + {#if schedule?.enabled} + + {/if}