commit
13ecbd79fb
4 files changed
+595
-73
No files matched your search
@@ -1592,6 +1592,11 @@ const LOCALES = {
|
||||
cron_schedule_preset_weekly: 'Weekly',
|
||||
cron_schedule_preset_monthly: 'Monthly',
|
||||
cron_schedule_preset_custom: 'Custom',
|
||||
cron_schedule_hour_label: 'Hour',
|
||||
cron_schedule_minute_label: 'Minute',
|
||||
cron_schedule_weekday_label: 'Weekday (0-6, Sun-Sat)',
|
||||
cron_schedule_month_day_label: 'Day',
|
||||
cron_schedule_time_hint: 'Time is server time; cron runs server-side.',
|
||||
cron_schedule_hint: "Use 'every 1h' or a cron expression for recurring jobs. Bare durations like '30m' run once.",
|
||||
cron_schedule_once_warning: "Duration forms like '30m' run once and are removed after running. Use 'every 30m' to keep a recurring job.",
|
||||
cron_prompt_label: 'Prompt',
|
||||
@@ -3258,6 +3263,11 @@ const LOCALES = {
|
||||
cron_schedule_preset_weekly: 'Ogni settimana',
|
||||
cron_schedule_preset_monthly: 'Ogni mese',
|
||||
cron_schedule_preset_custom: 'Personalizzato',
|
||||
cron_schedule_hour_label: 'Ora',
|
||||
cron_schedule_minute_label: 'Minuto',
|
||||
cron_schedule_weekday_label: 'Giorno della settimana (0-6, Dom-Sab)',
|
||||
cron_schedule_month_day_label: 'Giorno',
|
||||
cron_schedule_time_hint: 'L\'ora è quella del server; cron viene eseguito lato server.',
|
||||
cron_schedule_hint: 'Usa \'every 1h\' o un\'espressione cron per job ricorrenti. Durate semplici come \'30m\' vengono eseguite una volta sola.',
|
||||
cron_schedule_once_warning: 'Forme di durata come \'30m\' vengono eseguite una volta e rimosse dopo l\'esecuzione. Usa \'every 30m\' per mantenere un job ricorrente.',
|
||||
cron_prompt_label: 'Prompt',
|
||||
@@ -4932,6 +4942,11 @@ const LOCALES = {
|
||||
cron_schedule_preset_weekly: '毎週',
|
||||
cron_schedule_preset_monthly: '毎月',
|
||||
cron_schedule_preset_custom: 'カスタム',
|
||||
cron_schedule_hour_label: '時',
|
||||
cron_schedule_minute_label: '分',
|
||||
cron_schedule_weekday_label: '曜日 (0-6, 日-土)',
|
||||
cron_schedule_month_day_label: '日',
|
||||
cron_schedule_time_hint: '時刻はサーバー時刻です。cron はサーバー側で実行されます。',
|
||||
cron_schedule_hint: "繰り返し実行には 'every 1h' または Cron 式を使います。'30m' のような期間だけの指定は 1 回だけ実行されます。",
|
||||
cron_schedule_once_warning: "'30m' のような期間指定は 1 回だけ実行され、実行後に削除されます。繰り返すには 'every 30m' を使ってください。",
|
||||
cron_prompt_label: 'プロンプト',
|
||||
@@ -6248,6 +6263,11 @@ const LOCALES = {
|
||||
cron_schedule_preset_weekly: 'Еженедельно',
|
||||
cron_schedule_preset_monthly: 'Ежемесячно',
|
||||
cron_schedule_preset_custom: 'Пользовательский',
|
||||
cron_schedule_hour_label: 'Час',
|
||||
cron_schedule_minute_label: 'Минута',
|
||||
cron_schedule_weekday_label: 'День недели (0-6, вс-сб)',
|
||||
cron_schedule_month_day_label: 'День',
|
||||
cron_schedule_time_hint: 'Время указано по серверу; cron выполняется на сервере.',
|
||||
cron_schedule_hint: "Для повторяющихся заданий используйте 'every 1h' или cron-выражение. Простые интервалы вроде '30m' выполняются один раз.",
|
||||
cron_schedule_once_warning: "Интервалы вроде '30m' выполняются один раз и удаляются после запуска. Используйте 'every 30m' для повторяющегося задания.",
|
||||
cron_prompt_label: 'Запрос',
|
||||
@@ -7841,6 +7861,11 @@ const LOCALES = {
|
||||
cron_schedule_preset_weekly: 'Semanal',
|
||||
cron_schedule_preset_monthly: 'Mensual',
|
||||
cron_schedule_preset_custom: 'Personalizado',
|
||||
cron_schedule_hour_label: 'Hora',
|
||||
cron_schedule_minute_label: 'Minuto',
|
||||
cron_schedule_weekday_label: 'Día de la semana (0-6, dom-sáb)',
|
||||
cron_schedule_month_day_label: 'Día',
|
||||
cron_schedule_time_hint: 'La hora es la del servidor; cron se ejecuta del lado del servidor.',
|
||||
cron_schedule_hint: "Usa 'every 1h' o una expresión cron para trabajos recurrentes. Duraciones como '30m' se ejecutan una sola vez.",
|
||||
cron_schedule_once_warning: "Las duraciones como '30m' se ejecutan una vez y se eliminan después de correr. Usa 'every 30m' para mantener un trabajo recurrente.",
|
||||
cron_prompt_label: 'Prompt',
|
||||
@@ -9116,6 +9141,11 @@ const LOCALES = {
|
||||
cron_schedule_preset_weekly: 'Wöchentlich',
|
||||
cron_schedule_preset_monthly: 'Monatlich',
|
||||
cron_schedule_preset_custom: 'Benutzerdefiniert',
|
||||
cron_schedule_hour_label: 'Stunde',
|
||||
cron_schedule_minute_label: 'Minute',
|
||||
cron_schedule_weekday_label: 'Wochentag (0-6, So-Sa)',
|
||||
cron_schedule_month_day_label: 'Tag',
|
||||
cron_schedule_time_hint: 'Die Zeit ist Serverzeit; cron läuft serverseitig.',
|
||||
cron_schedule_hint: "Für wiederkehrende Aufgaben 'every 1h' oder einen Cron-Ausdruck verwenden. Reine Dauern wie '30m' laufen einmal.",
|
||||
cron_schedule_once_warning: "Dauerangaben wie '30m' laufen einmal und werden nach der Ausführung entfernt. Verwende 'every 30m' für eine wiederkehrende Aufgabe.",
|
||||
cron_prompt_label: 'Prompt',
|
||||
@@ -11063,6 +11093,11 @@ const LOCALES = {
|
||||
cron_schedule_preset_weekly: '每周',
|
||||
cron_schedule_preset_monthly: '每月',
|
||||
cron_schedule_preset_custom: '自定义',
|
||||
cron_schedule_hour_label: '小时',
|
||||
cron_schedule_minute_label: '分钟',
|
||||
cron_schedule_weekday_label: '星期 (0-6,周日-周六)',
|
||||
cron_schedule_month_day_label: '日期',
|
||||
cron_schedule_time_hint: '时间以服务器时间为准;cron 在服务器端运行。',
|
||||
cron_schedule_hint: "循环任务请用 'every 1h' 或 Cron 表达式。像 '30m' 这样的裸时长只会运行一次。",
|
||||
cron_schedule_once_warning: "像 '30m' 这样的时长写法只会运行一次,并在运行后移除。要保留循环任务,请使用 'every 30m'。",
|
||||
cron_prompt_label: '提示词',
|
||||
@@ -13031,6 +13066,11 @@ const LOCALES = {
|
||||
cron_schedule_preset_weekly: '每週',
|
||||
cron_schedule_preset_monthly: '每月',
|
||||
cron_schedule_preset_custom: '自訂',
|
||||
cron_schedule_hour_label: '小時',
|
||||
cron_schedule_minute_label: '分鐘',
|
||||
cron_schedule_weekday_label: '星期 (0-6,週日-週六)',
|
||||
cron_schedule_month_day_label: '日期',
|
||||
cron_schedule_time_hint: '時間以伺服器時間為準;cron 在伺服器端執行。',
|
||||
cron_schedule_hint: '週期性任務請用「every 1h」或 Cron 表示式。像「30m」這樣的裸時長只會執行一次。',
|
||||
cron_schedule_once_warning: '像「30m」這樣的時長寫法只會執行一次,並在執行後移除。要保留週期性任務,請使用「every 30m」。',
|
||||
cron_prompt_label: '提示',
|
||||
@@ -14438,6 +14478,11 @@ const LOCALES = {
|
||||
cron_schedule_preset_weekly: 'Semanal',
|
||||
cron_schedule_preset_monthly: 'Mensal',
|
||||
cron_schedule_preset_custom: 'Personalizado',
|
||||
cron_schedule_hour_label: 'Hora',
|
||||
cron_schedule_minute_label: 'Minuto',
|
||||
cron_schedule_weekday_label: 'Dia da semana (0-6, dom-sáb)',
|
||||
cron_schedule_month_day_label: 'Dia',
|
||||
cron_schedule_time_hint: 'O horário usa a hora do servidor; cron roda no servidor.',
|
||||
cron_schedule_hint: "Use 'every 1h' ou uma expressão Cron para tarefas recorrentes. Durações como '30m' rodam uma vez.",
|
||||
cron_schedule_once_warning: "Durações como '30m' rodam uma vez e são removidas após executar. Use 'every 30m' para manter uma tarefa recorrente.",
|
||||
cron_prompt_label: 'Prompt',
|
||||
@@ -16088,6 +16133,11 @@ const LOCALES = {
|
||||
cron_schedule_preset_weekly: 'Weekly',
|
||||
cron_schedule_preset_monthly: 'Monthly',
|
||||
cron_schedule_preset_custom: 'Custom',
|
||||
cron_schedule_hour_label: '시간',
|
||||
cron_schedule_minute_label: '분',
|
||||
cron_schedule_weekday_label: '요일 (0-6, 일-토)',
|
||||
cron_schedule_month_day_label: '일',
|
||||
cron_schedule_time_hint: '시간은 서버 시간 기준이며 cron은 서버에서 실행됩니다.',
|
||||
cron_schedule_hint: "Use 'every 1h' or a cron expression for recurring jobs. Bare durations like '30m' run once.",
|
||||
cron_schedule_once_warning: "Duration forms like '30m' run once and are removed after running. Use 'every 30m' to keep a recurring job.",
|
||||
cron_prompt_label: 'Prompt',
|
||||
@@ -17830,6 +17880,11 @@ const LOCALES = {
|
||||
cron_schedule_preset_weekly: 'Hebdomadaire',
|
||||
cron_schedule_preset_monthly: 'Mensuel',
|
||||
cron_schedule_preset_custom: 'Personnalisé',
|
||||
cron_schedule_hour_label: 'Heure',
|
||||
cron_schedule_minute_label: 'Minute',
|
||||
cron_schedule_weekday_label: 'Jour de la semaine (0-6, dim-sam)',
|
||||
cron_schedule_month_day_label: 'Jour',
|
||||
cron_schedule_time_hint: 'L\'heure est celle du serveur; cron s\'exécute côté serveur.',
|
||||
cron_schedule_hint: "Utilisez 'every 1h' ou une expression cron pour les tâches récurrentes. Les durées simples comme '30m' s'exécutent une fois.",
|
||||
cron_schedule_once_warning: "Les formes de durée comme '30m' s'exécutent une fois et sont supprimées après l'exécution. Utilisez 'every 30m' pour conserver une tâche récurrente.",
|
||||
cron_prompt_label: 'Invite',
|
||||
@@ -19404,6 +19459,11 @@ const LOCALES = {
|
||||
cron_schedule_preset_weekly: 'Haftalık',
|
||||
cron_schedule_preset_monthly: 'Aylık',
|
||||
cron_schedule_preset_custom: 'Özel',
|
||||
cron_schedule_hour_label: 'Saat',
|
||||
cron_schedule_minute_label: 'Dakika',
|
||||
cron_schedule_weekday_label: 'Haftanın günü (0-6, Paz-Cmt)',
|
||||
cron_schedule_month_day_label: 'Gün',
|
||||
cron_schedule_time_hint: 'Saat sunucu saatidir; cron sunucu tarafında çalışır.',
|
||||
cron_schedule_hint: "Use 'every 1h' or a cron expression for recurring jobs. Bare durations like '30m' run once.",
|
||||
cron_schedule_once_warning: "Duration forms like '30m' run once and are removed after running. Use 'every 30m' to keep a recurring job.",
|
||||
cron_prompt_label: 'Çabuk',
|
||||
@@ -21158,6 +21218,11 @@ const LOCALES = {
|
||||
cron_schedule_preset_weekly: 'Co tydzień',
|
||||
cron_schedule_preset_monthly: 'Co miesiąc',
|
||||
cron_schedule_preset_custom: 'Niestandardowe',
|
||||
cron_schedule_hour_label: 'Godzina',
|
||||
cron_schedule_minute_label: 'Minuta',
|
||||
cron_schedule_weekday_label: 'Dzień tygodnia (0-6, niedz.-sob.)',
|
||||
cron_schedule_month_day_label: 'Dzień',
|
||||
cron_schedule_time_hint: 'Czas jest czasem serwera; cron działa po stronie serwera.',
|
||||
cron_schedule_hint: "Użyj 'every 1h' lub wyrażenia cron dla zadań cyklicznych. Same formy czasu trwania, jak '30m', zostaną uruchomione tylko raz.",
|
||||
cron_schedule_once_warning: 'Formy czasu trwania, takie jak \'30m\', są uruchamiane raz i usuwane po uruchomieniu. Użyj \'every 30m\', aby utrzymać zadanie cykliczne.',
|
||||
cron_prompt_label: 'Monit',
|
||||
@@ -22666,6 +22731,11 @@ const LOCALES = {
|
||||
cron_schedule_preset_weekly: 'Hàng tuần',
|
||||
cron_schedule_preset_monthly: 'Hàng tháng',
|
||||
cron_schedule_preset_custom: 'Tùy chỉnh',
|
||||
cron_schedule_hour_label: 'Giờ',
|
||||
cron_schedule_minute_label: 'Phút',
|
||||
cron_schedule_weekday_label: 'Thứ trong tuần (0-6, CN-T7)',
|
||||
cron_schedule_month_day_label: 'Ngày',
|
||||
cron_schedule_time_hint: 'Thời gian dùng giờ máy chủ; cron chạy phía máy chủ.',
|
||||
cron_schedule_hint: "Dùng 'every 1h' hoặc biểu thức cron cho job lặp lại. Các khoảng thời gian đơn như '30m' chỉ chạy một lần.",
|
||||
cron_schedule_once_warning: "Các dạng thời lượng như '30m' chỉ chạy một lần và sẽ bị xóa sau khi chạy. Dùng 'every 30m' để giữ job lặp lại.",
|
||||
cron_prompt_label: 'Prompt',
|
||||
|
||||
+191
-18
@@ -467,12 +467,12 @@ function _syncCronScheduleWarning() {
|
||||
}
|
||||
|
||||
const CRON_SCHEDULE_PRESETS = [
|
||||
{ id: 'custom', value: '', label: 'cron_schedule_preset_custom', fallback: 'Custom' },
|
||||
{ id: 'hourly', value: 'every 1h', label: 'cron_schedule_preset_hourly', fallback: 'Hourly' },
|
||||
{ id: 'daily', value: '0 9 * * *', label: 'cron_schedule_preset_daily', fallback: 'Daily' },
|
||||
{ id: 'weekdays', value: '0 9 * * 1-5', label: 'cron_schedule_preset_weekdays', fallback: 'Weekdays' },
|
||||
{ id: 'weekly', value: '0 9 * * 1', label: 'cron_schedule_preset_weekly', fallback: 'Weekly' },
|
||||
{ id: 'monthly', value: '0 9 1 * *', label: 'cron_schedule_preset_monthly', fallback: 'Monthly' },
|
||||
{ id: 'custom', label: 'cron_schedule_preset_custom', fallback: 'Custom', fields: [] },
|
||||
{ id: 'hourly', label: 'cron_schedule_preset_hourly', fallback: 'Hourly', fields: ['minute'], defaults: { minute: 0 } },
|
||||
{ id: 'daily', label: 'cron_schedule_preset_daily', fallback: 'Daily', fields: ['hour', 'minute'], defaults: { hour: 9, minute: 0 } },
|
||||
{ id: 'weekdays', label: 'cron_schedule_preset_weekdays', fallback: 'Weekdays', fields: ['hour', 'minute'], defaults: { hour: 9, minute: 0 } },
|
||||
{ id: 'weekly', label: 'cron_schedule_preset_weekly', fallback: 'Weekly', fields: ['hour', 'minute', 'weekday'], defaults: { hour: 9, minute: 0, weekday: 1 } },
|
||||
{ id: 'monthly', label: 'cron_schedule_preset_monthly', fallback: 'Monthly', fields: ['hour', 'minute', 'monthDay'], defaults: { hour: 9, minute: 0, monthDay: 1 } },
|
||||
];
|
||||
|
||||
function _cronSchedulePresetOptionHtml() {
|
||||
@@ -481,18 +481,159 @@ function _cronSchedulePresetOptionHtml() {
|
||||
.join('');
|
||||
}
|
||||
|
||||
function _cronSchedulePresetIdForValue(value) {
|
||||
function _cronSchedulePresetForId(presetId) {
|
||||
return CRON_SCHEDULE_PRESETS.find((entry) => entry.id === presetId) || null;
|
||||
}
|
||||
|
||||
function _cronSchedulePresetControlIds() {
|
||||
return {
|
||||
hour: 'cronFormScheduleHour',
|
||||
minute: 'cronFormScheduleMinute',
|
||||
weekday: 'cronFormScheduleWeekday',
|
||||
monthDay: 'cronFormScheduleMonthDay',
|
||||
};
|
||||
}
|
||||
|
||||
function _cronSchedulePresetFieldId(field) {
|
||||
const ids = _cronSchedulePresetControlIds();
|
||||
return ids[field] || '';
|
||||
}
|
||||
|
||||
function _cronSchedulePresetFieldEl(field) {
|
||||
const id = _cronSchedulePresetFieldId(field);
|
||||
return id ? $(id) : null;
|
||||
}
|
||||
|
||||
function _cronSchedulePresetBounds(field) {
|
||||
if (field === 'hour') return { min: 0, max: 23 };
|
||||
if (field === 'minute') return { min: 0, max: 59 };
|
||||
if (field === 'weekday') return { min: 0, max: 6 };
|
||||
if (field === 'monthDay') return { min: 1, max: 31 };
|
||||
return { min: 0, max: 999 };
|
||||
}
|
||||
|
||||
function _cronSchedulePresetNormalizeValue(field, value, fallback) {
|
||||
const bounds = _cronSchedulePresetBounds(field);
|
||||
const parsed = parseInt(String(value ?? '').trim(), 10);
|
||||
const fallbackParsed = parseInt(String(fallback ?? bounds.min).trim(), 10);
|
||||
const safeFallback = Number.isFinite(fallbackParsed) ? fallbackParsed : bounds.min;
|
||||
const n = Number.isFinite(parsed) ? parsed : safeFallback;
|
||||
return String(Math.min(bounds.max, Math.max(bounds.min, n)));
|
||||
}
|
||||
|
||||
function _cronSchedulePresetValueForField(field, fallback) {
|
||||
const el = _cronSchedulePresetFieldEl(field);
|
||||
const raw = el ? String(el.value || '').trim() : '';
|
||||
return _cronSchedulePresetNormalizeValue(field, raw, fallback);
|
||||
}
|
||||
|
||||
function _cronSchedulePresetRawFieldInBounds(field, value) {
|
||||
const raw = String(value || '').trim();
|
||||
if (!/^\d+$/.test(raw)) return false;
|
||||
const bounds = _cronSchedulePresetBounds(field);
|
||||
const parsed = parseInt(raw, 10);
|
||||
return Number.isFinite(parsed) && parsed >= bounds.min && parsed <= bounds.max;
|
||||
}
|
||||
|
||||
function _cronSchedulePresetApplyValues(values) {
|
||||
['hour', 'minute', 'weekday', 'monthDay'].forEach((field) => {
|
||||
const el = _cronSchedulePresetFieldEl(field);
|
||||
if (!el || values[field] == null) return;
|
||||
el.value = _cronSchedulePresetNormalizeValue(field, values[field], values[field]);
|
||||
});
|
||||
}
|
||||
|
||||
function _cronSchedulePresetSyncVisibility(presetId) {
|
||||
const wrapper = $('cronFormSchedulePresetParams');
|
||||
const preset = _cronSchedulePresetForId(presetId);
|
||||
const showFields = preset && Array.isArray(preset.fields) ? preset.fields : [];
|
||||
if (wrapper) wrapper.style.display = presetId === 'custom' ? 'none' : '';
|
||||
['hour', 'minute', 'weekday', 'monthDay'].forEach((field) => {
|
||||
const fieldWrap = $(`cronFormSchedule${field === 'monthDay' ? 'MonthDay' : field.charAt(0).toUpperCase() + field.slice(1)}Field`);
|
||||
if (fieldWrap) fieldWrap.style.display = showFields.includes(field) ? '' : 'none';
|
||||
});
|
||||
}
|
||||
|
||||
function _cronSchedulePresetValuesForSelection(presetId) {
|
||||
const preset = _cronSchedulePresetForId(presetId);
|
||||
const defaults = (preset && preset.defaults) || {};
|
||||
if (presetId === 'hourly') {
|
||||
return { minute: _cronSchedulePresetValueForField('minute', defaults.minute) };
|
||||
}
|
||||
if (presetId === 'daily' || presetId === 'weekdays') {
|
||||
return {
|
||||
hour: _cronSchedulePresetValueForField('hour', defaults.hour),
|
||||
minute: _cronSchedulePresetValueForField('minute', defaults.minute),
|
||||
};
|
||||
}
|
||||
if (presetId === 'weekly') {
|
||||
return {
|
||||
hour: _cronSchedulePresetValueForField('hour', defaults.hour),
|
||||
minute: _cronSchedulePresetValueForField('minute', defaults.minute),
|
||||
weekday: _cronSchedulePresetValueForField('weekday', defaults.weekday),
|
||||
};
|
||||
}
|
||||
if (presetId === 'monthly') {
|
||||
return {
|
||||
hour: _cronSchedulePresetValueForField('hour', defaults.hour),
|
||||
minute: _cronSchedulePresetValueForField('minute', defaults.minute),
|
||||
monthDay: _cronSchedulePresetValueForField('monthDay', defaults.monthDay),
|
||||
};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
function _cronSchedulePresetValueForSelection(presetId, selectedValues) {
|
||||
const values = selectedValues || _cronSchedulePresetValuesForSelection(presetId);
|
||||
if (presetId === 'hourly') return `${values.minute} * * * *`;
|
||||
if (presetId === 'daily') return `${values.minute} ${values.hour} * * *`;
|
||||
if (presetId === 'weekdays') return `${values.minute} ${values.hour} * * 1-5`;
|
||||
if (presetId === 'weekly') return `${values.minute} ${values.hour} * * ${values.weekday}`;
|
||||
if (presetId === 'monthly') return `${values.minute} ${values.hour} ${values.monthDay} * *`;
|
||||
return '';
|
||||
}
|
||||
|
||||
function _cronSchedulePresetStateForInput(value) {
|
||||
const schedule = String(value || '').trim();
|
||||
const preset = CRON_SCHEDULE_PRESETS.find((entry) => entry.value && entry.value === schedule);
|
||||
return preset ? preset.id : 'custom';
|
||||
if (!schedule) return { presetId: 'custom' };
|
||||
if (/^every\s+1h$/i.test(schedule)) {
|
||||
return { presetId: 'hourly', minute: '0' };
|
||||
}
|
||||
if (schedule.startsWith('@')) return { presetId: 'custom' };
|
||||
const parts = schedule.split(/\s+/);
|
||||
if (parts.length !== 5) return { presetId: 'custom' };
|
||||
const [minute, hour, dayOfMonth, month, dayOfWeek] = parts;
|
||||
if (!_cronSchedulePresetRawFieldInBounds('minute', minute)) return { presetId: 'custom' };
|
||||
if (_cronSchedulePresetRawFieldInBounds('hour', hour) && dayOfMonth === '*' && month === '*' && dayOfWeek === '*') {
|
||||
return { presetId: 'daily', minute, hour };
|
||||
}
|
||||
if (_cronSchedulePresetRawFieldInBounds('hour', hour) && dayOfMonth === '*' && month === '*' && dayOfWeek === '1-5') {
|
||||
return { presetId: 'weekdays', minute, hour };
|
||||
}
|
||||
if (hour === '*' && dayOfMonth === '*' && month === '*' && dayOfWeek === '*') {
|
||||
return { presetId: 'hourly', minute };
|
||||
}
|
||||
if (_cronSchedulePresetRawFieldInBounds('hour', hour) && dayOfMonth === '*' && month === '*' && (_cronSchedulePresetRawFieldInBounds('weekday', dayOfWeek) || dayOfWeek === '7')) {
|
||||
return { presetId: 'weekly', minute, hour, weekday: dayOfWeek === '7' ? '0' : dayOfWeek };
|
||||
}
|
||||
if (_cronSchedulePresetRawFieldInBounds('hour', hour) && _cronSchedulePresetRawFieldInBounds('monthDay', dayOfMonth) && month === '*' && dayOfWeek === '*') {
|
||||
return { presetId: 'monthly', minute, hour, monthDay: dayOfMonth };
|
||||
}
|
||||
return { presetId: 'custom' };
|
||||
}
|
||||
|
||||
function _cronSchedulePresetIdForValue(value) {
|
||||
return _cronSchedulePresetStateForInput(value).presetId;
|
||||
}
|
||||
|
||||
function _syncCronSchedulePresetFromInput() {
|
||||
const presetEl = $('cronFormSchedulePreset');
|
||||
const scheduleEl = $('cronFormSchedule');
|
||||
if (!presetEl || !scheduleEl) return;
|
||||
const presetId = _cronSchedulePresetIdForValue(scheduleEl.value);
|
||||
presetEl.value = presetId;
|
||||
const state = _cronSchedulePresetStateForInput(scheduleEl.value);
|
||||
presetEl.value = state.presetId;
|
||||
_cronSchedulePresetSyncVisibility(state.presetId);
|
||||
if (state.presetId !== 'custom') _cronSchedulePresetApplyValues(state);
|
||||
}
|
||||
|
||||
function _syncCronSchedulePresetAndWarning() {
|
||||
@@ -505,12 +646,15 @@ function _applyCronSchedulePresetSelection() {
|
||||
const scheduleEl = $('cronFormSchedule');
|
||||
if (!presetEl || !scheduleEl) return;
|
||||
const presetId = presetEl.value;
|
||||
const preset = CRON_SCHEDULE_PRESETS.find((entry) => entry.id === presetId);
|
||||
if (preset && preset.value) {
|
||||
scheduleEl.value = preset.value;
|
||||
_syncCronSchedulePresetAndWarning();
|
||||
if (presetId !== 'custom') {
|
||||
const values = _cronSchedulePresetValuesForSelection(presetId);
|
||||
_cronSchedulePresetApplyValues(values);
|
||||
scheduleEl.value = _cronSchedulePresetValueForSelection(presetId, values);
|
||||
_cronSchedulePresetSyncVisibility(presetId);
|
||||
_syncCronScheduleWarning();
|
||||
return;
|
||||
}
|
||||
_cronSchedulePresetSyncVisibility(presetId);
|
||||
_syncCronScheduleWarning();
|
||||
}
|
||||
|
||||
@@ -519,6 +663,14 @@ function _initCronSchedulePresetControls() {
|
||||
const scheduleEl = $('cronFormSchedule');
|
||||
if (!presetEl || !scheduleEl) return;
|
||||
presetEl.addEventListener('change', _applyCronSchedulePresetSelection);
|
||||
if ($('cronFormSchedulePresetParams')) {
|
||||
['cronFormScheduleHour', 'cronFormScheduleMinute', 'cronFormScheduleWeekday', 'cronFormScheduleMonthDay'].forEach((id) => {
|
||||
const el = $(id);
|
||||
if (!el) return;
|
||||
el.addEventListener('change', _applyCronSchedulePresetSelection);
|
||||
el.addEventListener('input', _applyCronSchedulePresetSelection);
|
||||
});
|
||||
}
|
||||
scheduleEl.addEventListener('input', _syncCronSchedulePresetAndWarning);
|
||||
scheduleEl.addEventListener('change', _syncCronSchedulePresetAndWarning);
|
||||
_syncCronSchedulePresetAndWarning();
|
||||
@@ -1371,9 +1523,30 @@ function _renderCronForm({ name, schedule, prompt, deliver, profile, toast_notif
|
||||
</div>
|
||||
<div class="detail-form-row">
|
||||
<label for="cronFormSchedulePreset">${esc(t('cron_schedule_preset_label') || 'Preset')}</label>
|
||||
<select id="cronFormSchedulePreset">
|
||||
${_cronSchedulePresetOptionHtml()}
|
||||
</select>
|
||||
<div class="cron-schedule-preset-shell">
|
||||
<select id="cronFormSchedulePreset">
|
||||
${_cronSchedulePresetOptionHtml()}
|
||||
</select>
|
||||
<div id="cronFormSchedulePresetParams" class="cron-schedule-preset-params" style="display:none">
|
||||
<div class="cron-schedule-preset-field" id="cronFormScheduleHourField">
|
||||
<label for="cronFormScheduleHour">${esc(t('cron_schedule_hour_label') || 'Hour')}</label>
|
||||
<input type="number" id="cronFormScheduleHour" min="0" max="23" step="1" value="9" autocomplete="off">
|
||||
</div>
|
||||
<div class="cron-schedule-preset-field" id="cronFormScheduleMinuteField">
|
||||
<label for="cronFormScheduleMinute">${esc(t('cron_schedule_minute_label') || 'Minute')}</label>
|
||||
<input type="number" id="cronFormScheduleMinute" min="0" max="59" step="1" value="0" autocomplete="off">
|
||||
</div>
|
||||
<div class="cron-schedule-preset-field" id="cronFormScheduleWeekdayField">
|
||||
<label for="cronFormScheduleWeekday">${esc(t('cron_schedule_weekday_label') || 'Weekday')}</label>
|
||||
<input type="number" id="cronFormScheduleWeekday" min="0" max="6" step="1" value="1" autocomplete="off">
|
||||
</div>
|
||||
<div class="cron-schedule-preset-field" id="cronFormScheduleMonthDayField">
|
||||
<label for="cronFormScheduleMonthDay">${esc(t('cron_schedule_month_day_label') || 'Day')}</label>
|
||||
<input type="number" id="cronFormScheduleMonthDay" min="1" max="31" step="1" value="1" autocomplete="off">
|
||||
</div>
|
||||
<div class="detail-form-hint cron-schedule-preset-time-hint">${esc(t('cron_schedule_time_hint') || 'Time is server time; cron runs server-side.')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail-form-row">
|
||||
<label for="cronFormSchedule">${esc(t('cron_schedule_label') || 'Schedule')}</label>
|
||||
|
||||
@@ -6318,6 +6318,7 @@ main.main > .main-view:not([id="mainChat"]):not([id="mainSettings"]) .main-view-
|
||||
.detail-form-row > label{font-size:12px;font-weight:500;color:var(--muted);letter-spacing:.01em;}
|
||||
.detail-form-row input[type="text"],
|
||||
.detail-form-row input[type="password"],
|
||||
.detail-form-row input[type="number"],
|
||||
.detail-form-row input:not([type]),
|
||||
.detail-form-row select,
|
||||
.detail-form-row textarea{
|
||||
@@ -6329,10 +6330,18 @@ main.main > .main-view:not([id="mainChat"]):not([id="mainSettings"]) .main-view-
|
||||
}
|
||||
.detail-form-row input[type="text"]:focus,
|
||||
.detail-form-row input[type="password"]:focus,
|
||||
.detail-form-row input[type="number"]:focus,
|
||||
.detail-form-row input:not([type]):focus,
|
||||
.detail-form-row select:focus,
|
||||
.detail-form-row textarea:focus{border-color:var(--accent,var(--accent-bg-strong,var(--border)));}
|
||||
.detail-form-row input:disabled{opacity:.6;cursor:not-allowed;}
|
||||
.cron-schedule-preset-shell{display:flex;flex-wrap:wrap;gap:8px;align-items:flex-start;}
|
||||
.cron-schedule-preset-shell > select{width:auto;min-width:160px;flex:0 0 160px;}
|
||||
.cron-schedule-preset-params{display:flex;flex-wrap:wrap;gap:8px;align-items:flex-end;min-width:0;}
|
||||
.cron-schedule-preset-field{display:flex;flex-direction:column;gap:4px;flex:0 0 110px;min-width:110px;}
|
||||
.cron-schedule-preset-field label{font-size:11px;font-weight:500;color:var(--muted);letter-spacing:.01em;}
|
||||
.cron-schedule-preset-field input[type="number"]{width:100%;}
|
||||
.cron-schedule-preset-time-hint{flex-basis:100%;}
|
||||
.detail-form-row textarea{resize:vertical;font-family:'SF Mono',ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;}
|
||||
.detail-form-row .detail-form-hint{font-size:11px;color:var(--muted);line-height:1.5;}
|
||||
.detail-form-warning{font-size:11px;line-height:1.5;border:1px solid rgba(245,158,11,.35);background:rgba(245,158,11,.1);color:rgba(245,158,11,.98);border-radius:8px;padding:8px 10px;}
|
||||
|
||||
@@ -81,19 +81,28 @@ def test_cron_schedule_preset_matching():
|
||||
script = _cron_schedule_source() + r"""
|
||||
const cases = {
|
||||
hourly: _cronSchedulePresetIdForValue("every 1h"),
|
||||
daily: _cronSchedulePresetIdForValue("0 9 * * *"),
|
||||
weekdays: _cronSchedulePresetIdForValue("0 9 * * 1-5"),
|
||||
weekly: _cronSchedulePresetIdForValue("0 9 * * 1"),
|
||||
monthly: _cronSchedulePresetIdForValue("0 9 1 * *"),
|
||||
hourlyCron: _cronSchedulePresetIdForValue("15 * * * *"),
|
||||
daily: _cronSchedulePresetIdForValue("30 7 * * *"),
|
||||
weekdays: _cronSchedulePresetIdForValue("5 22 * * 1-5"),
|
||||
weekly: _cronSchedulePresetIdForValue("45 8 * * 3"),
|
||||
monthly: _cronSchedulePresetIdForValue("10 6 15 * *"),
|
||||
empty: _cronSchedulePresetIdForValue(""),
|
||||
trimMatch: _cronSchedulePresetIdForValue(" 0 9 * * * "),
|
||||
trimMatch: _cronSchedulePresetIdForValue(" 30 7 * * * "),
|
||||
custom: _cronSchedulePresetIdForValue("0 9 * * * 0"),
|
||||
shorthand: _cronSchedulePresetIdForValue("@daily"),
|
||||
invalidDaily: _cronSchedulePresetIdForValue("99 99 * * *"),
|
||||
invalidHourly: _cronSchedulePresetIdForValue("60 * * * *"),
|
||||
ambiguousSunday: _cronSchedulePresetIdForValue("0 9 * * 7"),
|
||||
invalidWeekly: _cronSchedulePresetIdForValue("0 9 * * 8"),
|
||||
invalidMonthDayZero: _cronSchedulePresetIdForValue("0 9 0 * *"),
|
||||
invalidMonthly: _cronSchedulePresetIdForValue("0 9 32 * *"),
|
||||
};
|
||||
console.log(JSON.stringify(cases));
|
||||
"""
|
||||
cases = json.loads(_run_node(script))
|
||||
|
||||
assert cases["hourly"] == "hourly"
|
||||
assert cases["hourlyCron"] == "hourly"
|
||||
assert cases["daily"] == "daily"
|
||||
assert cases["weekdays"] == "weekdays"
|
||||
assert cases["weekly"] == "weekly"
|
||||
@@ -101,16 +110,23 @@ console.log(JSON.stringify(cases));
|
||||
assert cases["empty"] == "custom"
|
||||
assert cases["trimMatch"] == "daily"
|
||||
assert cases["custom"] == "custom"
|
||||
assert cases["shorthand"] == "custom"
|
||||
assert cases["invalidDaily"] == "custom"
|
||||
assert cases["invalidHourly"] == "custom"
|
||||
assert cases["ambiguousSunday"] == "weekly"
|
||||
assert cases["invalidWeekly"] == "custom"
|
||||
assert cases["invalidMonthDayZero"] == "custom"
|
||||
assert cases["invalidMonthly"] == "custom"
|
||||
|
||||
|
||||
def test_cron_schedule_preset_controls_sync_raw_and_preset_paths():
|
||||
script = _cron_schedule_source() + r"""
|
||||
const elements = {};
|
||||
function $(id) { return elements[id]; }
|
||||
function makeElement() {
|
||||
function makeElement(initialValue = '') {
|
||||
return {
|
||||
value: '',
|
||||
style: {},
|
||||
value: initialValue,
|
||||
style: { display: '' },
|
||||
listeners: {},
|
||||
addEventListener(type, handler) {
|
||||
(this.listeners[type] || (this.listeners[type] = [])).push(handler);
|
||||
@@ -135,64 +151,310 @@ function t(key) {
|
||||
}
|
||||
function esc(value) { return value == null ? '' : String(value); }
|
||||
|
||||
elements.cronFormSchedule = makeElement();
|
||||
elements.cronFormSchedulePreset = makeElement();
|
||||
elements.cronFormScheduleOnceWarning = { style: {} };
|
||||
[
|
||||
'cronFormSchedule',
|
||||
'cronFormSchedulePreset',
|
||||
'cronFormSchedulePresetParams',
|
||||
'cronFormScheduleHourField',
|
||||
'cronFormScheduleMinuteField',
|
||||
'cronFormScheduleWeekdayField',
|
||||
'cronFormScheduleMonthDayField',
|
||||
'cronFormScheduleHour',
|
||||
'cronFormScheduleMinute',
|
||||
'cronFormScheduleWeekday',
|
||||
'cronFormScheduleMonthDay',
|
||||
].forEach((id) => {
|
||||
elements[id] = makeElement();
|
||||
});
|
||||
elements.cronFormScheduleOnceWarning = { style: { display: 'none' } };
|
||||
elements.cronFormSchedulePresetParams.style.display = 'none';
|
||||
|
||||
_initCronSchedulePresetControls();
|
||||
|
||||
elements.cronFormSchedule.value = "0 9 * * *";
|
||||
elements.cronFormSchedule.dispatchEvent('input');
|
||||
const selectAfterDaily = elements.cronFormSchedulePreset.value;
|
||||
const warningAfterDaily = elements.cronFormScheduleOnceWarning.style.display;
|
||||
function snapshot() {
|
||||
return {
|
||||
preset: elements.cronFormSchedulePreset.value,
|
||||
schedule: elements.cronFormSchedule.value,
|
||||
paramsDisplay: elements.cronFormSchedulePresetParams.style.display,
|
||||
hourFieldDisplay: elements.cronFormScheduleHourField.style.display,
|
||||
minuteFieldDisplay: elements.cronFormScheduleMinuteField.style.display,
|
||||
weekdayFieldDisplay: elements.cronFormScheduleWeekdayField.style.display,
|
||||
monthDayFieldDisplay: elements.cronFormScheduleMonthDayField.style.display,
|
||||
hour: elements.cronFormScheduleHour.value,
|
||||
minute: elements.cronFormScheduleMinute.value,
|
||||
weekday: elements.cronFormScheduleWeekday.value,
|
||||
monthDay: elements.cronFormScheduleMonthDay.value,
|
||||
warning: elements.cronFormScheduleOnceWarning.style.display,
|
||||
kind: _cronScheduleKindForInput(elements.cronFormSchedule.value),
|
||||
};
|
||||
}
|
||||
|
||||
elements.cronFormSchedule.value = "30m";
|
||||
elements.cronFormSchedule.value = '15 * * * *';
|
||||
elements.cronFormSchedule.dispatchEvent('input');
|
||||
const selectAfterOnce = elements.cronFormSchedulePreset.value;
|
||||
const warningAfterOnce = elements.cronFormScheduleOnceWarning.style.display;
|
||||
const hourlySync = snapshot();
|
||||
|
||||
elements.cronFormSchedulePreset.value = 'hourly';
|
||||
elements.cronFormSchedulePreset.dispatchEvent('change');
|
||||
const presetWriteValue = elements.cronFormSchedule.value;
|
||||
const selectAfterPresetWrite = elements.cronFormSchedulePreset.value;
|
||||
elements.cronFormScheduleMinute.value = '15';
|
||||
elements.cronFormScheduleMinute.dispatchEvent('change');
|
||||
const hourlyWrite = snapshot();
|
||||
|
||||
elements.cronFormSchedulePreset.value = "custom";
|
||||
elements.cronFormSchedulePreset.dispatchEvent('change');
|
||||
const preservedExactPresetValue = elements.cronFormSchedule.value;
|
||||
const selectAfterExactPresetCustom = elements.cronFormSchedulePreset.value;
|
||||
|
||||
elements.cronFormSchedule.value = "advanced: cron expression";
|
||||
elements.cronFormSchedule.value = '30 7 * * *';
|
||||
elements.cronFormSchedule.dispatchEvent('input');
|
||||
elements.cronFormSchedulePreset.value = "custom";
|
||||
const dailySync = snapshot();
|
||||
elements.cronFormScheduleHour.value = '7';
|
||||
elements.cronFormScheduleHour.dispatchEvent('change');
|
||||
elements.cronFormScheduleMinute.value = '30';
|
||||
elements.cronFormScheduleMinute.dispatchEvent('change');
|
||||
const dailyWrite = snapshot();
|
||||
|
||||
elements.cronFormSchedule.value = '5 22 * * 1-5';
|
||||
elements.cronFormSchedule.dispatchEvent('input');
|
||||
const weekdaysSync = snapshot();
|
||||
|
||||
elements.cronFormSchedule.value = '45 8 * * 3';
|
||||
elements.cronFormSchedule.dispatchEvent('input');
|
||||
const weeklySync = snapshot();
|
||||
elements.cronFormSchedule.value = '0 9 * * 7';
|
||||
elements.cronFormSchedule.dispatchEvent('input');
|
||||
const weeklyRawSundaySync = snapshot();
|
||||
elements.cronFormScheduleWeekday.value = '3';
|
||||
elements.cronFormScheduleWeekday.dispatchEvent('change');
|
||||
elements.cronFormScheduleHour.value = '8';
|
||||
elements.cronFormScheduleHour.dispatchEvent('change');
|
||||
elements.cronFormScheduleMinute.value = '45';
|
||||
elements.cronFormScheduleMinute.dispatchEvent('change');
|
||||
const weeklyWrite = snapshot();
|
||||
|
||||
elements.cronFormSchedule.value = '10 6 15 * *';
|
||||
elements.cronFormSchedule.dispatchEvent('input');
|
||||
const monthlySync = snapshot();
|
||||
elements.cronFormScheduleMonthDay.value = '15';
|
||||
elements.cronFormScheduleMonthDay.dispatchEvent('change');
|
||||
elements.cronFormScheduleHour.value = '6';
|
||||
elements.cronFormScheduleHour.dispatchEvent('change');
|
||||
elements.cronFormScheduleMinute.value = '10';
|
||||
elements.cronFormScheduleMinute.dispatchEvent('change');
|
||||
const monthlyWrite = snapshot();
|
||||
|
||||
elements.cronFormSchedule.value = '30 7 * * *';
|
||||
elements.cronFormSchedule.dispatchEvent('input');
|
||||
const dailyAfterMonthlySync = snapshot();
|
||||
|
||||
elements.cronFormSchedulePreset.value = 'monthly';
|
||||
elements.cronFormSchedulePreset.dispatchEvent('change');
|
||||
const preservedCustomValue = elements.cronFormSchedule.value;
|
||||
const selectAfterCustom = elements.cronFormSchedulePreset.value;
|
||||
elements.cronFormScheduleHour.value = '99';
|
||||
elements.cronFormScheduleMinute.value = '-8';
|
||||
elements.cronFormScheduleMonthDay.value = '99';
|
||||
elements.cronFormScheduleMonthDay.dispatchEvent('change');
|
||||
const normalizedMonthlyWrite = snapshot();
|
||||
|
||||
elements.cronFormSchedule.value = '@daily';
|
||||
elements.cronFormSchedule.dispatchEvent('input');
|
||||
const shorthandCustom = snapshot();
|
||||
|
||||
elements.cronFormSchedule.value = 'advanced: cron expression';
|
||||
elements.cronFormSchedule.dispatchEvent('input');
|
||||
const unsupportedCustom = snapshot();
|
||||
|
||||
elements.cronFormSchedulePreset.value = 'custom';
|
||||
elements.cronFormSchedulePreset.dispatchEvent('change');
|
||||
const customSelectionPreserved = snapshot();
|
||||
|
||||
console.log(JSON.stringify({
|
||||
selectAfterDaily,
|
||||
warningAfterDaily,
|
||||
selectAfterOnce,
|
||||
warningAfterOnce,
|
||||
presetWriteValue,
|
||||
selectAfterPresetWrite,
|
||||
preservedExactPresetValue,
|
||||
selectAfterExactPresetCustom,
|
||||
preservedCustomValue,
|
||||
selectAfterCustom,
|
||||
hourlySync,
|
||||
hourlyWrite,
|
||||
dailySync,
|
||||
dailyWrite,
|
||||
weekdaysSync,
|
||||
weeklySync,
|
||||
weeklyRawSundaySync,
|
||||
weeklyWrite,
|
||||
monthlySync,
|
||||
monthlyWrite,
|
||||
dailyAfterMonthlySync,
|
||||
normalizedMonthlyWrite,
|
||||
shorthandCustom,
|
||||
unsupportedCustom,
|
||||
customSelectionPreserved,
|
||||
}));
|
||||
"""
|
||||
result = json.loads(_run_node(script))
|
||||
|
||||
assert result["selectAfterDaily"] == "daily"
|
||||
assert result["warningAfterDaily"] == "none"
|
||||
assert result["selectAfterOnce"] == "custom"
|
||||
assert result["warningAfterOnce"] == ""
|
||||
assert result["presetWriteValue"] == "every 1h"
|
||||
assert result["selectAfterPresetWrite"] == "hourly"
|
||||
assert result["preservedExactPresetValue"] == "every 1h"
|
||||
assert result["selectAfterExactPresetCustom"] == "custom"
|
||||
assert result["preservedCustomValue"] == "advanced: cron expression"
|
||||
assert result["selectAfterCustom"] == "custom"
|
||||
assert result["hourlySync"]["preset"] == "hourly"
|
||||
assert result["hourlySync"]["schedule"] == "15 * * * *"
|
||||
assert result["hourlySync"]["paramsDisplay"] == ""
|
||||
assert result["hourlySync"]["hourFieldDisplay"] == "none"
|
||||
assert result["hourlySync"]["minuteFieldDisplay"] == ""
|
||||
assert result["hourlySync"]["kind"] == "cron"
|
||||
assert result["hourlyWrite"]["schedule"] == "15 * * * *"
|
||||
assert result["hourlyWrite"]["kind"] == "cron"
|
||||
|
||||
assert result["dailySync"]["preset"] == "daily"
|
||||
assert result["dailySync"]["schedule"] == "30 7 * * *"
|
||||
assert result["dailySync"]["hourFieldDisplay"] == ""
|
||||
assert result["dailySync"]["minuteFieldDisplay"] == ""
|
||||
assert result["dailySync"]["weekdayFieldDisplay"] == "none"
|
||||
assert result["dailySync"]["monthDayFieldDisplay"] == "none"
|
||||
assert result["dailyWrite"]["schedule"] == "30 7 * * *"
|
||||
assert result["dailyWrite"]["kind"] == "cron"
|
||||
|
||||
assert result["weekdaysSync"]["preset"] == "weekdays"
|
||||
assert result["weekdaysSync"]["schedule"] == "5 22 * * 1-5"
|
||||
assert result["weekdaysSync"]["hourFieldDisplay"] == ""
|
||||
assert result["weekdaysSync"]["minuteFieldDisplay"] == ""
|
||||
assert result["weekdaysSync"]["weekdayFieldDisplay"] == "none"
|
||||
assert result["weekdaysSync"]["monthDayFieldDisplay"] == "none"
|
||||
|
||||
assert result["weeklySync"]["preset"] == "weekly"
|
||||
assert result["weeklySync"]["schedule"] == "45 8 * * 3"
|
||||
assert result["weeklySync"]["weekdayFieldDisplay"] == ""
|
||||
assert result["weeklySync"]["monthDayFieldDisplay"] == "none"
|
||||
assert result["weeklyRawSundaySync"]["preset"] == "weekly"
|
||||
assert result["weeklyRawSundaySync"]["schedule"] == "0 9 * * 7"
|
||||
assert result["weeklyRawSundaySync"]["weekday"] == "0"
|
||||
assert result["weeklyWrite"]["schedule"] == "45 8 * * 3"
|
||||
assert result["weeklyWrite"]["kind"] == "cron"
|
||||
|
||||
assert result["monthlySync"]["preset"] == "monthly"
|
||||
assert result["monthlySync"]["schedule"] == "10 6 15 * *"
|
||||
assert result["monthlySync"]["monthDayFieldDisplay"] == ""
|
||||
assert result["monthlySync"]["weekdayFieldDisplay"] == "none"
|
||||
assert result["monthlyWrite"]["schedule"] == "10 6 15 * *"
|
||||
assert result["monthlyWrite"]["kind"] == "cron"
|
||||
|
||||
assert result["dailyAfterMonthlySync"]["preset"] == "daily"
|
||||
assert result["dailyAfterMonthlySync"]["schedule"] == "30 7 * * *"
|
||||
assert result["dailyAfterMonthlySync"]["hour"] == "7"
|
||||
assert result["dailyAfterMonthlySync"]["minute"] == "30"
|
||||
assert result["dailyAfterMonthlySync"]["monthDayFieldDisplay"] == "none"
|
||||
|
||||
assert result["normalizedMonthlyWrite"]["schedule"] == "0 23 31 * *"
|
||||
assert result["normalizedMonthlyWrite"]["hour"] == "23"
|
||||
assert result["normalizedMonthlyWrite"]["minute"] == "0"
|
||||
assert result["normalizedMonthlyWrite"]["monthDay"] == "31"
|
||||
assert result["normalizedMonthlyWrite"]["kind"] == "cron"
|
||||
|
||||
assert result["shorthandCustom"]["preset"] == "custom"
|
||||
assert result["shorthandCustom"]["schedule"] == "@daily"
|
||||
assert result["shorthandCustom"]["paramsDisplay"] == "none"
|
||||
|
||||
assert result["unsupportedCustom"]["preset"] == "custom"
|
||||
assert result["unsupportedCustom"]["schedule"] == "advanced: cron expression"
|
||||
assert result["unsupportedCustom"]["paramsDisplay"] == "none"
|
||||
|
||||
assert result["customSelectionPreserved"]["preset"] == "custom"
|
||||
assert result["customSelectionPreserved"]["schedule"] == "advanced: cron expression"
|
||||
assert result["customSelectionPreserved"]["paramsDisplay"] == "none"
|
||||
|
||||
|
||||
def test_cron_schedule_raw_warning_listener_survives_missing_preset_params():
|
||||
script = _cron_schedule_source() + r"""
|
||||
const elements = {};
|
||||
function $(id) { return elements[id]; }
|
||||
function makeElement(initialValue = '') {
|
||||
return {
|
||||
value: initialValue,
|
||||
style: { display: '' },
|
||||
listeners: {},
|
||||
addEventListener(type, handler) {
|
||||
(this.listeners[type] || (this.listeners[type] = [])).push(handler);
|
||||
},
|
||||
dispatchEvent(eventType) {
|
||||
const handlers = this.listeners[eventType] || [];
|
||||
for (const handler of handlers) handler({ type: eventType, target: this });
|
||||
},
|
||||
};
|
||||
}
|
||||
elements.cronFormSchedule = makeElement();
|
||||
elements.cronFormSchedulePreset = makeElement('custom');
|
||||
elements.cronFormScheduleOnceWarning = { style: { display: 'none' } };
|
||||
|
||||
_initCronSchedulePresetControls();
|
||||
elements.cronFormSchedule.value = '30m';
|
||||
elements.cronFormSchedule.dispatchEvent('input');
|
||||
|
||||
console.log(JSON.stringify({
|
||||
warning: elements.cronFormScheduleOnceWarning.style.display,
|
||||
inputListeners: (elements.cronFormSchedule.listeners.input || []).length,
|
||||
presetChangeListeners: (elements.cronFormSchedulePreset.listeners.change || []).length,
|
||||
}));
|
||||
"""
|
||||
result = json.loads(_run_node(script))
|
||||
|
||||
assert result["warning"] == ""
|
||||
assert result["inputListeners"] == 1
|
||||
assert result["presetChangeListeners"] == 1
|
||||
|
||||
|
||||
def test_cron_schedule_custom_selection_preserves_raw_schedule_exactly():
|
||||
script = _cron_schedule_source() + r"""
|
||||
const elements = {};
|
||||
function $(id) { return elements[id]; }
|
||||
function makeElement(initialValue = '') {
|
||||
return {
|
||||
value: initialValue,
|
||||
style: { display: '' },
|
||||
listeners: {},
|
||||
addEventListener(type, handler) {
|
||||
(this.listeners[type] || (this.listeners[type] = [])).push(handler);
|
||||
},
|
||||
dispatchEvent(eventType) {
|
||||
const handlers = this.listeners[eventType] || [];
|
||||
for (const handler of handlers) handler({ type: eventType, target: this });
|
||||
},
|
||||
};
|
||||
}
|
||||
function t(key) {
|
||||
const dict = {
|
||||
cron_schedule_preset_label: 'Preset',
|
||||
cron_schedule_preset_hourly: 'Hourly',
|
||||
cron_schedule_preset_daily: 'Daily',
|
||||
cron_schedule_preset_weekdays: 'Weekdays',
|
||||
cron_schedule_preset_weekly: 'Weekly',
|
||||
cron_schedule_preset_monthly: 'Monthly',
|
||||
cron_schedule_preset_custom: 'Custom',
|
||||
};
|
||||
return dict[key];
|
||||
}
|
||||
function esc(value) { return value == null ? '' : String(value); }
|
||||
|
||||
[
|
||||
'cronFormSchedule',
|
||||
'cronFormSchedulePreset',
|
||||
'cronFormSchedulePresetParams',
|
||||
'cronFormScheduleHourField',
|
||||
'cronFormScheduleMinuteField',
|
||||
'cronFormScheduleWeekdayField',
|
||||
'cronFormScheduleMonthDayField',
|
||||
'cronFormScheduleHour',
|
||||
'cronFormScheduleMinute',
|
||||
'cronFormScheduleWeekday',
|
||||
'cronFormScheduleMonthDay',
|
||||
].forEach((id) => {
|
||||
elements[id] = makeElement();
|
||||
});
|
||||
elements.cronFormScheduleOnceWarning = { style: { display: 'none' } };
|
||||
elements.cronFormSchedulePresetParams.style.display = 'none';
|
||||
|
||||
_initCronSchedulePresetControls();
|
||||
|
||||
elements.cronFormSchedule.value = 'advanced: cron expression';
|
||||
elements.cronFormSchedule.dispatchEvent('input');
|
||||
elements.cronFormSchedulePreset.value = 'custom';
|
||||
elements.cronFormSchedulePreset.dispatchEvent('change');
|
||||
|
||||
console.log(JSON.stringify({
|
||||
preset: elements.cronFormSchedulePreset.value,
|
||||
schedule: elements.cronFormSchedule.value,
|
||||
paramsDisplay: elements.cronFormSchedulePresetParams.style.display,
|
||||
}));
|
||||
"""
|
||||
result = json.loads(_run_node(script))
|
||||
|
||||
assert result["preset"] == "custom"
|
||||
assert result["schedule"] == "advanced: cron expression"
|
||||
assert result["paramsDisplay"] == "none"
|
||||
|
||||
|
||||
def test_cron_form_surfaces_one_shot_warning_copy_markers_and_preset_markup():
|
||||
@@ -202,6 +464,11 @@ def test_cron_form_surfaces_one_shot_warning_copy_markers_and_preset_markup():
|
||||
|
||||
assert "id=\"cronFormScheduleOnceWarning\"" in panels
|
||||
assert "id=\"cronFormSchedulePreset\"" in panels
|
||||
assert "id=\"cronFormSchedulePresetParams\"" in panels
|
||||
assert "id=\"cronFormScheduleHour\"" in panels
|
||||
assert "id=\"cronFormScheduleMinute\"" in panels
|
||||
assert "id=\"cronFormScheduleWeekday\"" in panels
|
||||
assert "id=\"cronFormScheduleMonthDay\"" in panels
|
||||
assert "cron_schedule_once_warning" in panels
|
||||
assert "_cronSchedulePresetIdForValue" in panels
|
||||
assert "_cronSchedulePresetOptionHtml" in panels
|
||||
@@ -210,12 +477,15 @@ def test_cron_form_surfaces_one_shot_warning_copy_markers_and_preset_markup():
|
||||
assert "addEventListener('change', _syncCronSchedulePresetAndWarning" in panels
|
||||
assert "addEventListener('change', _applyCronSchedulePresetSelection" in panels
|
||||
assert ".cron-once-warning" in style
|
||||
assert "id: 'hourly'" in panels
|
||||
assert "id: 'daily'" in panels
|
||||
assert "id: 'weekdays'" in panels
|
||||
assert "id: 'weekly'" in panels
|
||||
assert "id: 'monthly'" in panels
|
||||
assert "id: 'custom'" in panels
|
||||
assert ".cron-schedule-preset-shell" in style
|
||||
assert ".cron-schedule-preset-params" in style
|
||||
assert ".cron-schedule-preset-field" in style
|
||||
assert ".cron-schedule-preset-time-hint" in style
|
||||
assert "cron_schedule_time_hint" in panels
|
||||
assert "fields: ['minute']" in panels
|
||||
assert "fields: ['hour', 'minute']" in panels
|
||||
assert "fields: ['hour', 'minute', 'weekday']" in panels
|
||||
assert "fields: ['hour', 'minute', 'monthDay']" in panels
|
||||
assert "Duration forms like '30m' run once" in i18n
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user