Schedules - Dockhand
e.key === 'Escape' && (searchQuery = '')} />
{#if filterTypes.length === 0} All types {:else if filterTypes.length === 1} {#if filterTypes[0] === 'container_update'} Container updates {:else if filterTypes[0] === 'git_stack_sync'} Git stack syncs {:else if filterTypes[0] === 'env_update_check'} Env update checks {:else} System jobs {/if} {:else} {filterTypes.length} types {/if} {#if filterTypes.length > 0} {/if} Container updates Git stack syncs Env update checks {#if !hideSystemJobs} System jobs {/if} {#if filterEnvironments.length === 0} All envs {:else if filterEnvironments.length === 1} {environments.find(e => String(e.id) === filterEnvironments[0])?.name || 'Environment'} {:else} {filterEnvironments.length} envs {/if} {#if filterEnvironments.length > 0} {/if} {#each environments as env} {@const EnvIcon = getIconComponent(env.icon)} {env.name} {/each} {#if filterStatuses.length === 0} All statuses {:else if filterStatuses.length === 1} {#if filterStatuses[0] === 'success'} Success {:else if filterStatuses[0] === 'failed'} Failed {:else if filterStatuses[0] === 'skipped'} Up-to-date {:else if filterStatuses[0] === 'running'} Running {:else} {filterStatuses[0]} {/if} {:else} {filterStatuses.length} statuses {/if} {#if filterStatuses.length > 0} {/if} Success Failed Up-to-date Running {#if systemJobCount > 0} {/if}
{ if (schedule.lastExecution !== null) { toggleScheduleExpansion(schedule); } }} class="border-none" wrapperClass="border rounded-lg" > {#snippet cell(column, schedule, rowState)} {#if column.id === 'expand'} {#if schedule.lastExecution !== null} {/if} {:else if column.id === 'schedule'}
{#if schedule.type === 'container_update'} {:else if schedule.type === 'git_stack_sync'} {:else if schedule.type === 'env_update_check'} {#if schedule.autoUpdate} {:else} {/if} {:else} {/if}
{schedule.name} {#if schedule.isSystem} System {/if}
{#if schedule.type === 'container_update'} {#if schedule.envHasScanning} {@const criteria = (schedule.vulnerabilityCriteria || 'never') as VulnerabilityCriteria} {@const icon = vulnerabilityCriteriaIcons[criteria]} {@const IconComponent = icon.component} Check, scan & auto-update {:else} Check & auto-update {/if} {:else if schedule.type === 'git_stack_sync'} Git sync {:else if schedule.type === 'env_update_check'} {#if schedule.autoUpdate && schedule.envHasScanning && schedule.vulnerabilityCriteria} {@const criteria = schedule.vulnerabilityCriteria as VulnerabilityCriteria} {@const icon = vulnerabilityCriteriaIcons[criteria]} {@const IconComponent = icon.component} {/if} {schedule.description || 'Env update check'} {:else} {schedule.description || 'System job'} {/if}
{:else if column.id === 'environment'} {#if schedule.environmentName}
{schedule.environmentName}
{:else} - {/if} {:else if column.id === 'cron'}
{#if schedule.cronExpression} {(() => { try { const is12Hour = $appSettings.timeFormat === '12h'; return cronstrue.toString(schedule.cronExpression, { use24HourTimeFormat: !is12Hour, throwExceptionOnParseError: true, locale: 'en' }); } catch { return schedule.cronExpression; } })()} {:else} {schedule.scheduleType} {/if}
{:else if column.id === 'lastRun'} {#if schedule.lastExecution}
{formatTimestamp(schedule.lastExecution.triggeredAt)}
{#if schedule.lastExecution.duration}
{formatDuration(schedule.lastExecution.duration)}
{/if} {:else} Never {/if} {:else if column.id === 'nextRun'} {formatNextRun(schedule.nextRun)} {:else if column.id === 'status'} {#if schedule.lastExecution} {@const badge = getStatusBadge(schedule.lastExecution.status)} {@const envUpdateStatus = getEnvUpdateStatus(schedule.lastExecution)} {@const isBlockedByVuln = schedule.lastExecution.details?.reason === 'vulnerabilities_found'} {#if envUpdateStatus} {@const EnvUpdateIcon = envUpdateStatus.icon} {:else if isBlockedByVuln} {:else} {@const BadgeIcon = badge.icon} {/if}

{#if envUpdateStatus} {envUpdateStatus.label} {:else if isBlockedByVuln} Update blocked due to vulnerabilities {:else if schedule.lastExecution.status === 'skipped'} Up-to-date {:else} {schedule.lastExecution.status} {/if}

{:else}

No runs

{/if} {:else if column.id === 'actions'}
{#if schedule.lastExecution} {/if} {#if !schedule.isSystem} {@const scheduleKey = getScheduleKey(schedule)} deleteSchedule(schedule.type, schedule.id, schedule.entityName)} onOpenChange={(open) => confirmDeleteId = open ? scheduleKey : null} > {#snippet children({ open })} {/snippet} {/if}
{/if} {/snippet} {#snippet expandedRow(schedule, rowState)} {@const scheduleKey = getScheduleKey(schedule)} {@const executions = expandedExecutions.get(scheduleKey) || []} {@const isLoading = loadingMoreExecutions.has(scheduleKey)} {@const canLoadMore = hasMoreExecutions.get(scheduleKey) ?? false}

Execution history

{#if executions.length > 0} {/if}
{#if executions.length > 0}
{#each executions as exec} {@const badge = getStatusBadge(exec.status)} {@const trigger = getTriggerBadge(exec.triggeredBy)} {/each}
Triggered Trigger Duration Status Error
{formatTimestamp(exec.triggeredAt)} {@const TriggerIcon = trigger.icon}

{trigger.label}

{formatDuration(exec.duration)}
{#if exec.details?.reason === 'vulnerabilities_found'} {:else} {@const ExecBadgeIcon = badge.icon} {/if}

{exec.details?.reason === 'vulnerabilities_found' ? 'Update blocked due to vulnerabilities' : (exec.status === 'skipped' ? 'Up-to-date' : exec.status)}

{exec.errorMessage || ''}
{#if canLoadMore}
{/if}
{:else if isLoading}
{:else}

No executions found

{/if}
{/snippet} {#snippet emptyState()}

No schedules found

Enable auto-update on containers or auto-sync on git stacks to see them here

{/snippet}
{#if selectedExecution?.scheduleType === 'container_update'} {:else if selectedExecution?.scheduleType === 'git_stack_sync'} {:else if selectedExecution?.scheduleType === 'env_update_check'} {#if selectedExecution?.details?.autoUpdate} {:else} {/if} {:else} {/if} Execution details {#if selectedExecution} ({#if selectedExecution.scheduleType === 'container_update'}Container update{:else if selectedExecution.scheduleType === 'env_update_check'}Environment update{:else if selectedExecution.scheduleType === 'git_stack_sync'}Git stack sync{:else}System job{/if}) {/if} {#if selectedExecution} {formatTimestamp(selectedExecution.triggeredAt)} ยท {formatDuration(selectedExecution.duration)} {/if} {#if loadingExecutionDetail}
{:else if selectedExecution}
{#if selectedExecution.scheduleType === 'env_update_check' && selectedExecution.details?.autoUpdate}
{/if} {#if selectedExecution.details?.blockedContainers?.length > 0}
Blocked containers
{#each selectedExecution.details.blockedContainers as bc}
{bc.name} - {bc.reason}
{#if bc.scannerResults} {/if}
{/each}
{/if}
Status {#if selectedExecution.status} {@const badge = getStatusBadge(selectedExecution.status)} {@const envUpdateStatus = getEnvUpdateStatus(selectedExecution)} {@const isBlockedByVuln = selectedExecution.details?.reason === 'vulnerabilities_found'} {#if envUpdateStatus} {@const StatusIcon = envUpdateStatus.icon} {envUpdateStatus.label} {:else if isBlockedByVuln} Blocked {:else} {@const SelBadgeIcon = badge.icon} {selectedExecution.status === 'skipped' ? 'Up-to-date' : selectedExecution.status} {/if} {/if}
Trigger {#if selectedExecution.triggeredBy} {@const trigger = getTriggerBadge(selectedExecution.triggeredBy)} {@const SelTriggerIcon = trigger.icon} {trigger.label} {/if}
{#if selectedExecution.details?.vulnerabilityCriteria}
Update block criteria
{/if}
{#if selectedExecution.details?.reason === 'vulnerabilities_found'}
Block reason
{selectedExecution.details.blockReason || 'Update blocked due to vulnerabilities'}
{/if} {#if selectedExecution.details?.scanResult?.summary} {@const summary = selectedExecution.details.scanResult.summary} {@const scannerResults = selectedExecution.details.scanResult.scannerResults}
Vulnerability scan results
Scanned with {selectedExecution.details.scanResult.scanners?.join(', ') || 'scanner'} {#if selectedExecution.details.scanResult.scannedAt} at {formatDateTime(selectedExecution.details.scanResult.scannedAt)} {/if}
{/if} {#if selectedExecution.errorMessage}
Error
{selectedExecution.errorMessage}
{/if}
{/if}