1.0.38
This commit is contained in:
177 files changed
+21182
-1042
No files matched your search
@@ -13,6 +13,9 @@ import {
|
||||
getAllAutoUpdateGitStacks,
|
||||
getAllEnvUpdateCheckSettings,
|
||||
getAllImagePruneSettings,
|
||||
getBackupConfigs,
|
||||
getBackupDestination,
|
||||
getBackupDestinations,
|
||||
getLastExecutionForSchedule,
|
||||
getRecentExecutionsForSchedule,
|
||||
getEnvironment,
|
||||
@@ -26,7 +29,7 @@ import { authorize } from '$lib/server/authorize';
|
||||
|
||||
export interface ScheduleInfo {
|
||||
id: number;
|
||||
type: 'container_update' | 'git_stack_sync' | 'system_cleanup' | 'env_update_check' | 'image_prune';
|
||||
type: 'container_update' | 'git_stack_sync' | 'system_cleanup' | 'env_update_check' | 'image_prune' | 'backup' | 'repo_prune' | 'repo_check' | 'repo_verify';
|
||||
name: string;
|
||||
entityName: string;
|
||||
description?: string;
|
||||
@@ -210,6 +213,121 @@ export const GET: RequestHandler = async ({ cookies }) => {
|
||||
);
|
||||
schedules.push(...imagePruneSchedules);
|
||||
|
||||
// Get backup schedules (audit #17 — GET must match the SSE stream listing)
|
||||
const allBackupConfigs = await getBackupConfigs();
|
||||
const backupSchedules = await Promise.all(
|
||||
allBackupConfigs
|
||||
.filter(c => c.schedule)
|
||||
.map(async (config) => {
|
||||
const [env, dest, lastExecution, recentExecutions] = await Promise.all([
|
||||
config.environmentId ? getEnvironment(config.environmentId) : null,
|
||||
getBackupDestination(config.destinationId),
|
||||
getLastExecutionForSchedule('backup', config.id),
|
||||
getRecentExecutionsForSchedule('backup', config.id, 5)
|
||||
]);
|
||||
const timezone = config.environmentId ? await getEnvironmentTimezone(config.environmentId) : 'UTC';
|
||||
const isEnabled = config.enabled ?? false;
|
||||
const nextRun = isEnabled && config.schedule ? getNextRun(config.schedule, timezone) : null;
|
||||
|
||||
return {
|
||||
id: config.id,
|
||||
type: 'backup' as const,
|
||||
name: `Backup: ${config.targetName}`,
|
||||
entityName: config.targetName,
|
||||
description: `Back up ${config.type} to ${dest?.name || 'unknown destination'}`,
|
||||
environmentId: config.environmentId,
|
||||
environmentName: env?.name ?? null,
|
||||
enabled: isEnabled,
|
||||
scheduleType: 'custom',
|
||||
cronExpression: config.schedule ?? null,
|
||||
nextRun: nextRun?.toISOString() ?? null,
|
||||
lastExecution: lastExecution ?? null,
|
||||
recentExecutions,
|
||||
isSystem: false
|
||||
};
|
||||
})
|
||||
);
|
||||
schedules.push(...backupSchedules);
|
||||
|
||||
// Get repo maintenance schedules (prune + check + verify from destination
|
||||
// policies). Synthetic ids: dest.id + 100000/200000/300000 (matches the
|
||||
// stream endpoint and the run/toggle/delete REPO_ID_OFFSET decoding).
|
||||
const allDestinations = await getBackupDestinations();
|
||||
for (const dest of allDestinations) {
|
||||
const policies = dest.policies ? (() => { try { return JSON.parse(dest.policies); } catch { return {}; } })() : {};
|
||||
if (policies.pruneEnabled && policies.pruneSchedule) {
|
||||
const [lastExec, recentExecs] = await Promise.all([
|
||||
getLastExecutionForSchedule('repo_prune', dest.id),
|
||||
getRecentExecutionsForSchedule('repo_prune', dest.id, 5)
|
||||
]);
|
||||
const nextRun = getNextRun(policies.pruneSchedule);
|
||||
const maxUnused = policies.pruneMaxUnused ?? '10';
|
||||
schedules.push({
|
||||
id: dest.id + 100000,
|
||||
type: 'repo_prune' as const,
|
||||
name: `Prune: ${dest.name}`,
|
||||
entityName: dest.name,
|
||||
description: `Prune unused data from ${dest.name} (max unused ${maxUnused}%)`,
|
||||
environmentId: null,
|
||||
environmentName: null,
|
||||
enabled: true,
|
||||
scheduleType: 'custom',
|
||||
cronExpression: policies.pruneSchedule,
|
||||
nextRun: nextRun?.toISOString() ?? null,
|
||||
lastExecution: lastExec ?? null,
|
||||
recentExecutions: recentExecs,
|
||||
isSystem: false
|
||||
});
|
||||
}
|
||||
if (policies.checkEnabled && policies.checkSchedule) {
|
||||
const [lastExec, recentExecs] = await Promise.all([
|
||||
getLastExecutionForSchedule('repo_check', dest.id),
|
||||
getRecentExecutionsForSchedule('repo_check', dest.id, 5)
|
||||
]);
|
||||
const nextRun = getNextRun(policies.checkSchedule);
|
||||
schedules.push({
|
||||
id: dest.id + 200000,
|
||||
type: 'repo_check' as const,
|
||||
name: `Check: ${dest.name}`,
|
||||
entityName: dest.name,
|
||||
description: `Check integrity of ${dest.name}`,
|
||||
environmentId: null,
|
||||
environmentName: null,
|
||||
enabled: true,
|
||||
scheduleType: 'custom',
|
||||
cronExpression: policies.checkSchedule,
|
||||
nextRun: nextRun?.toISOString() ?? null,
|
||||
lastExecution: lastExec ?? null,
|
||||
recentExecutions: recentExecs,
|
||||
isSystem: false
|
||||
});
|
||||
}
|
||||
if (policies.verifyEnabled && policies.verifySchedule) {
|
||||
const [lastExec, recentExecs] = await Promise.all([
|
||||
getLastExecutionForSchedule('repo_verify', dest.id),
|
||||
getRecentExecutionsForSchedule('repo_verify', dest.id, 5)
|
||||
]);
|
||||
const nextRun = getNextRun(policies.verifySchedule);
|
||||
const subset = policies.verifyDataSubset || '5%';
|
||||
schedules.push({
|
||||
id: dest.id + 300000,
|
||||
type: 'repo_verify' as const,
|
||||
name: `Verify: ${dest.name}`,
|
||||
entityName: dest.name,
|
||||
description: `Verify ${subset} of data in ${dest.name}`,
|
||||
environmentId: null,
|
||||
environmentName: null,
|
||||
enabled: true,
|
||||
scheduleType: 'custom',
|
||||
cronExpression: policies.verifySchedule,
|
||||
nextRun: nextRun?.toISOString() ?? null,
|
||||
lastExecution: lastExec ?? null,
|
||||
recentExecutions: recentExecs,
|
||||
isSystem: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Get system schedules
|
||||
const systemSchedules = await getSystemSchedules();
|
||||
const sysSchedules = await Promise.all(
|
||||
|
||||
@@ -11,7 +11,11 @@ import {
|
||||
getGitStack,
|
||||
updateGitStack,
|
||||
deleteEnvUpdateCheckSettings,
|
||||
deleteImagePruneSettings
|
||||
deleteImagePruneSettings,
|
||||
deleteBackupConfig,
|
||||
getBackupConfig,
|
||||
getBackupDestination,
|
||||
updateBackupDestination
|
||||
} from '$lib/server/db';
|
||||
import { unregisterSchedule } from '$lib/server/scheduler';
|
||||
import { authorize } from '$lib/server/authorize';
|
||||
@@ -75,6 +79,39 @@ export const DELETE: RequestHandler = async ({ params, cookies }) => {
|
||||
unregisterSchedule(scheduleId, 'image_prune');
|
||||
return json({ success: true });
|
||||
|
||||
} else if (type === 'backup') {
|
||||
const cfg = await getBackupConfig(scheduleId);
|
||||
if (!cfg) {
|
||||
return json({ error: 'Schedule not found' }, { status: 404 });
|
||||
}
|
||||
const envDenied = await auth.requireEnvAccess(cfg.environmentId);
|
||||
if (envDenied) return envDenied;
|
||||
await deleteBackupConfig(scheduleId);
|
||||
unregisterSchedule(scheduleId, 'backup');
|
||||
return json({ success: true });
|
||||
|
||||
} else if (type === 'repo_prune' || type === 'repo_check' || type === 'repo_verify') {
|
||||
// Repo maintenance schedules are policy-driven; there is no standalone
|
||||
// row to delete. "Delete" here means disable the policy (audit #18).
|
||||
// The schedule id is synthetic — decode to the real destination id.
|
||||
const REPO_ID_OFFSET: Record<string, number> = {
|
||||
repo_prune: 100000, repo_check: 200000, repo_verify: 300000
|
||||
};
|
||||
const destId = scheduleId - REPO_ID_OFFSET[type];
|
||||
const dest = await getBackupDestination(destId);
|
||||
if (!dest) {
|
||||
return json({ error: 'Destination not found' }, { status: 404 });
|
||||
}
|
||||
const policies = dest.policies
|
||||
? (() => { try { return JSON.parse(dest.policies); } catch { return {}; } })()
|
||||
: {};
|
||||
const enabledKey = type === 'repo_prune' ? 'pruneEnabled'
|
||||
: type === 'repo_check' ? 'checkEnabled' : 'verifyEnabled';
|
||||
policies[enabledKey] = false;
|
||||
await updateBackupDestination(destId, { policies: JSON.stringify(policies) });
|
||||
unregisterSchedule(destId, type);
|
||||
return json({ success: true });
|
||||
|
||||
} else if (type === 'system_cleanup') {
|
||||
return json({ error: 'System schedules cannot be removed' }, { status: 400 });
|
||||
|
||||
|
||||
@@ -11,8 +11,12 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { triggerContainerUpdate, triggerGitStackSync, triggerSystemJob, triggerEnvUpdateCheck, triggerImagePrune } from '$lib/server/scheduler';
|
||||
import { getBackupConfig, getBackupDestination } from '$lib/server/db';
|
||||
import { runScheduledBackup } from '$lib/server/scheduler/tasks/backup';
|
||||
import { runRepoPrune, runRepoCheck, runRepoVerify } from '$lib/server/scheduler/tasks/repo-maintenance';
|
||||
import { authorize } from '$lib/server/authorize';
|
||||
import { getAutoUpdateSettingById, getGitStack } from '$lib/server/db';
|
||||
import { BACKUPS_ENABLED } from '$lib/server/features';
|
||||
|
||||
export const POST: RequestHandler = async ({ params, cookies }) => {
|
||||
const auth = await authorize(cookies);
|
||||
@@ -28,6 +32,20 @@ export const POST: RequestHandler = async ({ params, cookies }) => {
|
||||
return json({ error: 'Invalid schedule ID' }, { status: 400 });
|
||||
}
|
||||
|
||||
// BETA GATE: backup-type schedules are unreachable unless FEAT_BACKUPS_ENABLED (see features.ts).
|
||||
if (!BACKUPS_ENABLED && (type === 'backup' || type === 'repo_prune' || type === 'repo_check' || type === 'repo_verify')) {
|
||||
return new Response('Not found', { status: 404 });
|
||||
}
|
||||
|
||||
// The schedules stream emits synthetic IDs for the three repo-maintenance
|
||||
// rows per destination (dest.id + 100000/200000/300000) to keep them unique
|
||||
// in the UI list. Decode back to the real destination id here (audit #7)
|
||||
// so getBackupDestination() actually matches. Backup/other types pass through.
|
||||
const REPO_ID_OFFSET: Record<string, number> = {
|
||||
repo_prune: 100000, repo_check: 200000, repo_verify: 300000
|
||||
};
|
||||
const destId = REPO_ID_OFFSET[type] ? scheduleId - REPO_ID_OFFSET[type] : scheduleId;
|
||||
|
||||
// Resolve schedule → environmentId so we can enforce per-env access
|
||||
// before triggering. System schedules (env null) are gated only by
|
||||
// the global schedules:run check above.
|
||||
@@ -52,6 +70,24 @@ export const POST: RequestHandler = async ({ params, cookies }) => {
|
||||
case 'system_cleanup':
|
||||
scheduleEnvId = null;
|
||||
break;
|
||||
case 'backup': {
|
||||
// Backup schedules ARE env-scoped (audit #6: these were missing here
|
||||
// and 400'd before ever reaching the dispatch switch below).
|
||||
const config = await getBackupConfig(scheduleId);
|
||||
if (!config) return json({ error: 'Backup config not found' }, { status: 404 });
|
||||
scheduleEnvId = config.environmentId;
|
||||
break;
|
||||
}
|
||||
case 'repo_prune':
|
||||
case 'repo_check':
|
||||
case 'repo_verify': {
|
||||
// Repo maintenance is destination-scoped, not env-scoped. Validate the
|
||||
// (decoded) destination exists; access is gated by schedules:run only.
|
||||
const dest = await getBackupDestination(destId);
|
||||
if (!dest) return json({ error: 'Destination not found' }, { status: 404 });
|
||||
scheduleEnvId = null;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return json({ error: 'Invalid schedule type' }, { status: 400 });
|
||||
}
|
||||
@@ -77,6 +113,37 @@ export const POST: RequestHandler = async ({ params, cookies }) => {
|
||||
case 'image_prune':
|
||||
result = await triggerImagePrune(scheduleId);
|
||||
break;
|
||||
case 'backup': {
|
||||
const config = await getBackupConfig(scheduleId);
|
||||
if (!config) {
|
||||
return json({ error: 'Backup config not found' }, { status: 404 });
|
||||
}
|
||||
await runScheduledBackup(scheduleId, config.targetName, config.environmentId, 'manual');
|
||||
result = { success: true };
|
||||
break;
|
||||
}
|
||||
case 'repo_prune': {
|
||||
const dest = await getBackupDestination(destId);
|
||||
if (!dest) return json({ error: 'Destination not found' }, { status: 404 });
|
||||
await runRepoPrune(destId, dest.name, 'manual');
|
||||
result = { success: true };
|
||||
break;
|
||||
}
|
||||
case 'repo_check': {
|
||||
const dest = await getBackupDestination(destId);
|
||||
if (!dest) return json({ error: 'Destination not found' }, { status: 404 });
|
||||
await runRepoCheck(destId, dest.name, 'manual');
|
||||
result = { success: true };
|
||||
break;
|
||||
}
|
||||
case 'repo_verify': {
|
||||
const dest = await getBackupDestination(destId);
|
||||
if (!dest) return json({ error: 'Destination not found' }, { status: 404 });
|
||||
const pol = dest.policies ? (() => { try { return JSON.parse(dest.policies); } catch { return {}; } })() : {};
|
||||
await runRepoVerify(destId, dest.name, pol.verifyDataSubset || '5%', 'manual');
|
||||
result = { success: true };
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// Unreachable — validated in the resolution switch above.
|
||||
return json({ error: 'Invalid schedule type' }, { status: 400 });
|
||||
|
||||
@@ -5,11 +5,13 @@
|
||||
|
||||
import { json } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { getAutoUpdateSettingById, updateAutoUpdateSettingById, getGitStack, updateGitStack, getEnvUpdateCheckSettings, setEnvUpdateCheckSettings, getImagePruneSettings, setImagePruneSettings } from '$lib/server/db';
|
||||
import { getAutoUpdateSettingById, updateAutoUpdateSettingById, getGitStack, updateGitStack, getEnvUpdateCheckSettings, setEnvUpdateCheckSettings, getImagePruneSettings, setImagePruneSettings, getBackupConfig, updateBackupConfig, getBackupDestination, updateBackupDestination } from '$lib/server/db';
|
||||
import { registerSchedule, unregisterSchedule } from '$lib/server/scheduler';
|
||||
import { authorize } from '$lib/server/authorize';
|
||||
import { auditBackup, auditBackupDestination } from '$lib/server/audit';
|
||||
|
||||
export const POST: RequestHandler = async ({ params, cookies }) => {
|
||||
export const POST: RequestHandler = async (event) => {
|
||||
const { params, cookies } = event;
|
||||
const auth = await authorize(cookies);
|
||||
|
||||
const permDenied = await auth.requirePermission('schedules', 'edit');
|
||||
@@ -110,6 +112,56 @@ export const POST: RequestHandler = async ({ params, cookies }) => {
|
||||
unregisterSchedule(scheduleId, 'image_prune');
|
||||
}
|
||||
|
||||
return json({ success: true, enabled: newEnabled });
|
||||
} else if (type === 'backup') {
|
||||
const config = await getBackupConfig(scheduleId);
|
||||
if (!config) {
|
||||
return json({ error: 'Backup config not found' }, { status: 404 });
|
||||
}
|
||||
const envDenied = await auth.requireEnvAccess(config.environmentId);
|
||||
if (envDenied) return envDenied;
|
||||
|
||||
const newEnabled = !config.enabled;
|
||||
await updateBackupConfig(scheduleId, { enabled: newEnabled });
|
||||
|
||||
if (newEnabled && config.schedule) {
|
||||
await registerSchedule(scheduleId, 'backup', config.environmentId);
|
||||
} else {
|
||||
unregisterSchedule(scheduleId, 'backup');
|
||||
}
|
||||
|
||||
await auditBackup(event, 'update', config.targetName, config.environmentId, { configId: scheduleId, enabled: newEnabled });
|
||||
|
||||
return json({ success: true, enabled: newEnabled });
|
||||
} else if (type === 'repo_prune' || type === 'repo_check' || type === 'repo_verify') {
|
||||
// Repo maintenance schedules are policy-driven on the destination.
|
||||
// Toggling flips the corresponding *Enabled flag in the destination's
|
||||
// policies JSON (audit #18). The schedule id is synthetic — decode it
|
||||
// back to the real destination id (same offsets as the run endpoint).
|
||||
const REPO_ID_OFFSET: Record<string, number> = {
|
||||
repo_prune: 100000, repo_check: 200000, repo_verify: 300000
|
||||
};
|
||||
const destId = scheduleId - REPO_ID_OFFSET[type];
|
||||
const dest = await getBackupDestination(destId);
|
||||
if (!dest) {
|
||||
return json({ error: 'Destination not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
const policies = dest.policies
|
||||
? (() => { try { return JSON.parse(dest.policies); } catch { return {}; } })()
|
||||
: {};
|
||||
const enabledKey = type === 'repo_prune' ? 'pruneEnabled'
|
||||
: type === 'repo_check' ? 'checkEnabled' : 'verifyEnabled';
|
||||
const newEnabled = !policies[enabledKey];
|
||||
policies[enabledKey] = newEnabled;
|
||||
|
||||
await updateBackupDestination(destId, { policies: JSON.stringify(policies) });
|
||||
|
||||
// Re-register (or unregister) the repo maintenance job (env-less).
|
||||
await registerSchedule(destId, type, null);
|
||||
|
||||
await auditBackupDestination(event, 'update', destId, dest.name, { policy: enabledKey, enabled: newEnabled });
|
||||
|
||||
return json({ success: true, enabled: newEnabled });
|
||||
} else if (type === 'system_cleanup') {
|
||||
return json({ error: 'System schedules cannot be paused' }, { status: 400 });
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* - scheduleType: 'container_update' | 'git_stack_sync'
|
||||
* - scheduleId: number
|
||||
* - environmentId: number
|
||||
* - status: 'queued' | 'running' | 'success' | 'failed' | 'skipped'
|
||||
* - status: 'queued' | 'running' | 'success' | 'warning' | 'failed' | 'skipped'
|
||||
* - triggeredBy: 'cron' | 'webhook' | 'manual'
|
||||
* - fromDate: ISO date string
|
||||
* - toDate: ISO date string
|
||||
|
||||
@@ -10,6 +10,9 @@ import {
|
||||
getAllAutoUpdateGitStacks,
|
||||
getAllEnvUpdateCheckSettings,
|
||||
getAllImagePruneSettings,
|
||||
getBackupConfigs,
|
||||
getBackupDestination,
|
||||
getBackupDestinations,
|
||||
getLastExecutionForSchedule,
|
||||
getRecentExecutionsForSchedule,
|
||||
getEnvironment,
|
||||
@@ -180,6 +183,121 @@ async function getSchedulesData(): Promise<ScheduleInfo[]> {
|
||||
);
|
||||
schedules.push(...imagePruneSchedules);
|
||||
|
||||
// Get backup schedules
|
||||
const allBackupConfigs = await getBackupConfigs();
|
||||
const backupSchedules = await Promise.all(
|
||||
allBackupConfigs
|
||||
.filter(c => c.schedule)
|
||||
.map(async (config) => {
|
||||
const [env, dest, lastExecution, recentExecutions] = await Promise.all([
|
||||
config.environmentId ? getEnvironment(config.environmentId) : null,
|
||||
getBackupDestination(config.destinationId),
|
||||
getLastExecutionForSchedule('backup', config.id),
|
||||
getRecentExecutionsForSchedule('backup', config.id, 5)
|
||||
]);
|
||||
const timezone = config.environmentId ? await getEnvironmentTimezone(config.environmentId) : 'UTC';
|
||||
const isEnabled = config.enabled ?? false;
|
||||
const nextRun = isEnabled && config.schedule ? getNextRun(config.schedule, timezone) : null;
|
||||
|
||||
return {
|
||||
id: config.id,
|
||||
type: 'backup' as const,
|
||||
name: `Backup: ${config.targetName}`,
|
||||
entityName: config.targetName,
|
||||
description: `Back up ${config.type} to ${dest?.name || 'unknown destination'}`,
|
||||
environmentId: config.environmentId,
|
||||
environmentName: env?.name ?? null,
|
||||
enabled: isEnabled,
|
||||
scheduleType: 'custom',
|
||||
cronExpression: config.schedule ?? null,
|
||||
nextRun: nextRun?.toISOString() ?? null,
|
||||
lastExecution: lastExecution ?? null,
|
||||
recentExecutions,
|
||||
isSystem: false
|
||||
};
|
||||
})
|
||||
);
|
||||
schedules.push(...backupSchedules);
|
||||
|
||||
// Get repo maintenance schedules (prune + check from destination policies)
|
||||
const allDestinations = await getBackupDestinations();
|
||||
for (const dest of allDestinations) {
|
||||
const policies = dest.policies ? (() => { try { return JSON.parse(dest.policies); } catch { return {}; } })() : {};
|
||||
if (policies.pruneEnabled && policies.pruneSchedule) {
|
||||
const [lastExec, recentExecs] = await Promise.all([
|
||||
getLastExecutionForSchedule('repo_prune', dest.id),
|
||||
getRecentExecutionsForSchedule('repo_prune', dest.id, 5)
|
||||
]);
|
||||
const nextRun = getNextRun(policies.pruneSchedule, 'UTC');
|
||||
const maxUnused = policies.pruneMaxUnused ?? '10';
|
||||
schedules.push({
|
||||
id: dest.id + 100000,
|
||||
type: 'repo_prune' as any,
|
||||
name: `Prune: ${dest.name}`,
|
||||
entityName: dest.name,
|
||||
description: `Prune unused data from ${dest.name} (max unused ${maxUnused}%)`,
|
||||
maxUnused: String(maxUnused),
|
||||
environmentId: null,
|
||||
environmentName: null,
|
||||
enabled: true,
|
||||
scheduleType: 'custom',
|
||||
cronExpression: policies.pruneSchedule,
|
||||
nextRun: nextRun?.toISOString() ?? null,
|
||||
lastExecution: lastExec ?? null,
|
||||
recentExecutions: recentExecs,
|
||||
isSystem: false
|
||||
});
|
||||
}
|
||||
if (policies.checkEnabled && policies.checkSchedule) {
|
||||
const [lastExec, recentExecs] = await Promise.all([
|
||||
getLastExecutionForSchedule('repo_check', dest.id),
|
||||
getRecentExecutionsForSchedule('repo_check', dest.id, 5)
|
||||
]);
|
||||
const nextRun = getNextRun(policies.checkSchedule, 'UTC');
|
||||
schedules.push({
|
||||
id: dest.id + 200000,
|
||||
type: 'repo_check' as any,
|
||||
name: `Check: ${dest.name}`,
|
||||
entityName: dest.name,
|
||||
description: `Check integrity of ${dest.name}`,
|
||||
environmentId: null,
|
||||
environmentName: null,
|
||||
enabled: true,
|
||||
scheduleType: 'custom',
|
||||
cronExpression: policies.checkSchedule,
|
||||
nextRun: nextRun?.toISOString() ?? null,
|
||||
lastExecution: lastExec ?? null,
|
||||
recentExecutions: recentExecs,
|
||||
isSystem: false
|
||||
});
|
||||
}
|
||||
if (policies.verifyEnabled && policies.verifySchedule) {
|
||||
const [lastExec, recentExecs] = await Promise.all([
|
||||
getLastExecutionForSchedule('repo_verify', dest.id),
|
||||
getRecentExecutionsForSchedule('repo_verify', dest.id, 5)
|
||||
]);
|
||||
const nextRun = getNextRun(policies.verifySchedule, 'UTC');
|
||||
const subset = policies.verifyDataSubset || '5%';
|
||||
schedules.push({
|
||||
id: dest.id + 300000,
|
||||
type: 'repo_verify' as any,
|
||||
name: `Verify: ${dest.name}`,
|
||||
entityName: dest.name,
|
||||
description: `Verify ${subset} of data in ${dest.name}`,
|
||||
dataSubset: subset,
|
||||
environmentId: null,
|
||||
environmentName: null,
|
||||
enabled: true,
|
||||
scheduleType: 'custom',
|
||||
cronExpression: policies.verifySchedule,
|
||||
nextRun: nextRun?.toISOString() ?? null,
|
||||
lastExecution: lastExec ?? null,
|
||||
recentExecutions: recentExecs,
|
||||
isSystem: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Get system schedules
|
||||
const systemSchedules = await getSystemSchedules();
|
||||
const sysSchedules = await Promise.all(
|
||||
|
||||
Reference in new issue
Block a user