'use strict'; 'require baseclass'; 'require form'; 'require ui'; 'require view'; 'require fs'; 'require tools.widgets as widgets'; /* Copyright 2025 RafaƂ Wabik - IceG - From eko.one.pl forum MIT License */ function popTimeout(a, message, timeout, severity) { ui.addTimeLimitedNotification(a, message, timeout, severity) } return view.extend({ colors: { light: { minute: '#fdd2d6', hour: '#cfead1', day: '#ffd8ad', month: '#bbdefb', weekday:'#e1bee7' }, dark: { minute: '#5b2f35', hour: '#2f4732', day: '#4a3828', month: '#30475e', weekday:'#3a2f41' } }, getCurrentColors: function() { const isDarkMode = document.documentElement.getAttribute('data-darkmode') === 'true'; return isDarkMode ? this.colors.dark : this.colors.light; }, getBadgeTextColor: function() { const isDarkMode = document.documentElement.getAttribute('data-darkmode') === 'true'; return isDarkMode ? '#ffffff' : '#111111'; }, _weekdayNames: function(){ return [ _('Sunday'), _('Monday'), _('Tuesday'), _('Wednesday'), _('Thursday'), _('Friday'), _('Saturday') ]; }, _monthNames: function(){ return [ '', _('January'), _('February'), _('March'), _('April'), _('May'), _('June'), _('July'), _('August'), _('September'), _('October'), _('November'), _('December') ]; }, _formatRange: function(a, b){ return a + '-' + b; }, _parseSegments: function(expr, base) { if (!expr || expr === '*') return { type: 'any' }; if (/^\*\/\d+$/.test(expr)) return { type: 'step', step: parseInt(expr.slice(2), 10) }; const segs = []; expr.split(',').forEach(function(part){ const m = part.match(/^(\d+)-(\d+)$/); if (m) { const a = parseInt(m[1],10), b = parseInt(m[2],10); segs.push({ kind:'range', a:a, b:b }); } else { const v = parseInt(part,10); if (!isNaN(v)) segs.push({ kind:'single', v:v }); } }); return { type:'list', segs:segs }; }, _expand: function(parsed, minVal, maxVal) { if (parsed.type === 'any') return null; const vals = new Set(); if (parsed.type === 'step') { const n = parsed.step; for (let i=minVal; i<=maxVal; i+=n) vals.add(i); return Array.from(vals).sort(function(a,b){ return a-b; }); } parsed.segs.forEach(function(s){ if (s.kind==='single') { if (s.v>=minVal && s.v<=maxVal) vals.add(s.v); } else { for (let i=s.a; i<=s.b; i++) if (i>=minVal && i<=maxVal) vals.add(i); } }); return Array.from(vals).sort(function(a,b){ return a-b; }); }, _humanList: function(parsed, labeller) { if (parsed.type === 'any') return null; if (parsed.type === 'step') return null; const self = this; const items = parsed.segs.map(function(s){ if (s.kind==='single') return labeller(s.v, false); return self._formatRange(labeller(s.a,false), labeller(s.b,true)); }); return items.join(', '); }, _pluralMin: function(n){ return (n===1) ? _('minute') : _('minutes'); }, _pluralHour: function(n){ return (n===1) ? _('hour') : _('hours'); }, _pluralDay: function(n){ return (n===1) ? _('day') : _('days'); }, _pad2: function(n){ return (n<10 ? '0'+n : ''+n); }, _enumerateTimes: function(minExpr, hourExpr) { const pm = this._parseSegments(minExpr,0), ph = this._parseSegments(hourExpr,0); const mins = (pm.type==='any' || pm.type==='step') ? null : this._expand(pm,0,59); const hours = (ph.type==='any' || ph.type==='step') ? null : this._expand(ph,0,23); if (!mins || !hours) return null; const combos = []; for (let i=0;i0 && combos.length<=12) return { single: null, list: combos.join(', ') }; return null; }, _timeOfDayPhrase: function(minExpr, hourExpr) { const pm = this._parseSegments(minExpr,0), ph = this._parseSegments(hourExpr,0); const times = this._enumerateTimes(minExpr, hourExpr); if (times) { if (times.single) return _('at time: %s').format(times.single); return _('at times: %s').format(times.list); } let parts = []; // Minutes if (pm.type==='step') { parts.push(_('runs every %d %s').format(pm.step, this._pluralMin(pm.step))); } else if (pm.type==='list') { const list = this._humanList(pm, function(n){ return String(n); }); if (list) parts.push(_('in minutes: %s').format(list)); } // Hours if (ph.type==='step') { parts.push(_('runs every %d %s').format(ph.step, this._pluralHour(ph.step))); } else if (ph.type==='list') { const listH = this._humanList(ph, function(n){ return String(n); }); if (listH) parts.push(_('in hours: %s').format(listH)); } return parts.length > 0 ? parts.join('; ') : null; }, _descDayOfMonth: function(expr){ if (expr === '*') return null; const p = this._parseSegments(expr,1); if (p.type==='step') { return _('runs every %d %s of the month').format(p.step, this._pluralDay(p.step)); } const listStr = this._humanList(p, function(n){ return String(n); }); if (!listStr) return null; return _('on day(s): %s').format(listStr); }, _descMonth: function(expr){ if (expr === '*') return null; const p = this._parseSegments(expr,1); if (p.type==='step') return _('runs every %d month(s)').format(p.step); const months = this._monthNames(); const list = this._humanList(p, function(n){ return months[n]; }); return list ? _('in month(s): %s').format(list) : null; }, _descWeekday: function(expr){ if (expr === '*') return null; const p = this._parseSegments(expr,0); if (p.type==='step') return _('runs every %d day of the week').format(p.step); const days = this._weekdayNames(); const list = this._humanList(p, function(n){ return days[n===7?0:n]; }); return list ? _('on weekday(s): %s').format(list) : null; }, _describeCron: function(minute, hour, day, month, weekday, command){ const parts = []; const isMinuteChanged = minute !== '*'; const isHourChanged = hour !== '*'; const isDayChanged = day !== '*'; const isMonthChanged = month !== '*'; const isWeekdayChanged = weekday !== '*'; // Nothing changed if (!isMinuteChanged && !isHourChanged && !isDayChanged && !isMonthChanged && !isWeekdayChanged) { const prefix = command ? _('Command "%s" ').format(command) : _('Command'); return prefix + ' '; } // Time (hour and minute) const timePart = this._timeOfDayPhrase(minute, hour); if (timePart) parts.push(timePart); // Month const monthPart = this._descMonth(month); if (monthPart) parts.push(monthPart); // Day of month const dayPart = this._descDayOfMonth(day); if (dayPart) parts.push(dayPart); // Day of week const weekdayPart = this._descWeekday(weekday); if (weekdayPart) parts.push(weekdayPart); const prefix = command ? _('Command "%s" will run').format(command) : _('Command will run'); if (parts.length === 0) { return prefix + '.'; } return prefix + ' ' + parts.join('; ') + '.'; }, addStyles: function() { const style = document.createElement('style'); style.type = 'text/css'; style.textContent = ` .ifacebox .span-40 { grid-column: span 2; } .ifacebox .span-80 { grid-column: span 4; } .ifacebox .span-160 { grid-column: span 8; } .ifacebox .span-320 { grid-column: span 8; } @media (max-width: 560px) { .ifacebox .span-320 { grid-column: span 8 !important; } } :root { --tile-free: #eceff1; --tile-active: #34c759; --tile-text: #263238; --tile-text-on-active: #ffffff; --iface-head-bg: #f8f8f8; --app-id-font-color: #454545; --border-color-soft: #eceff1; --border-color-medium: #cfd8dc; --border-color-strong: #90a4ae; --tile-border: #b0bec5; --cron-minute: #fdd2d6; --cron-hour: #cfead1; --cron-day: #ffd8ad; --cron-month: #bbdefb; --cron-weekday: #e1bee7; --badge-text: #111111; --badge-border: rgba(0,0,0,.15); --badge-shadow: rgba(0,0,0,.06); --multiselect-bg: var(--border-color-low); --multiselect-text: #263238; --multiselect-border: #cfd8dc; --multiselect-selected-bg: #e3f2fd; --multiselect-selected-text: #1565c0; --multiselect-hover-bg: #f5f5f5; } :root[data-darkmode="true"] { --tile-free: #2a2f34; --tile-active: #2ecc71; --tile-text: #e5e7eb; --tile-text-on-active: #ffffff; --iface-head-bg: #1f2327; --border-color-soft: #2a2f34; --border-color-medium: #3a4146; --border-color-strong: #6b737a; --app-id-font-color: #f6f6f6; --tile-border: #54616b; --cron-minute: #8b4f5a; --cron-hour: #4f6b52; --cron-day: #7a5838; --cron-month: #4a6b8e; --cron-weekday:#5a4f71; --badge-text: #ffffff; --badge-border: rgba(255,255,255,.2); --badge-shadow: rgba(0,0,0,.35); --multiselect-bg: var(--border-color-low); --multiselect-text: #e5e7eb; --multiselect-border: #3a4146; --multiselect-hover-bg: #3a4146; } .cron-minute-head { background: var(--cron-minute) !important; } .cron-hour-head { background: var(--cron-hour) !important; } .cron-day-head { background: var(--cron-day) !important; } .cron-month-head { background: var(--cron-month) !important; } .cron-weekday-head{ background: var(--cron-weekday)!important; } :root .cron-minute-head { color: #263238 !important; } :root .cron-hour-head { color: #263238 !important; } :root .cron-day-head { color: #263238 !important; } :root .cron-month-head { color: #263238 !important; } :root .cron-weekday-head{ color: #263238 !important; } :root[data-darkmode="true"] .cron-minute-head { color: #ffcdd2 !important; } :root[data-darkmode="true"] .cron-hour-head { color: #c8e6c9 !important; } :root[data-darkmode="true"] .cron-day-head { color: #ffe0b2 !important; } :root[data-darkmode="true"] .cron-month-head { color: #bbdefb !important; } :root[data-darkmode="true"] .cron-weekday-head{ color: #e1bee7 !important; } .ifacebox .tile { font-weight: normal; } .cron-badge { display: inline-block; padding: 3px 6px; border-radius: 6px; border: 1px solid var(--badge-border); box-shadow: 0 1px 1px var(--badge-shadow), inset 0 1px 0 rgba(255,255,255,.08); font-weight: 600; letter-spacing: .2px; } .cbi-input-text { text-align: left !important; } #cron_preview_text { text-align: left !important; } #cron_preview { height: 40px !important; min-height: 40px !important; max-height: 40px !important; background-color: var(--multiselect-bg) !important; border: 1px solid var(--badge-border); line-height: 26px; display: flex; align-items: center; } .cron-actions { margin-top: 1em; display: flex; justify-content: flex-end; gap: .6em; } .cron-multiselect { width: 100%; min-height: 140px; background-color: var(--multiselect-bg) !important; color: var(--multiselect-text) !important; border: 1px solid var(--multiselect-border) !important; border-radius: 4px; padding: 4px; font-family: inherit; font-size: 13px; line-height: 1.4; outline: none; transition: border-color 0.2s, box-shadow 0.2s; } .cron-multiselect:focus { border-color: var(--tile-active) !important; box-shadow: 0 0 0 2px rgba(52, 199, 89, 0.2) !important; } :root:not([data-darkmode="true"]) .cron-multiselect option { padding: 4px 8px; border: none; outline: none; background-color: revert !important; color: revert !important; } :root:not([data-darkmode="true"]) .cron-multiselect option:not(:checked):hover { background-color: var(--multiselect-hover-bg) !important; } /* Dark mode */ :root[data-darkmode="true"] .cron-multiselect option { background-color: var(--multiselect-bg) !important; color: var(--multiselect-text) !important; padding: 4px 8px; border: none; outline: none; } :root[data-darkmode="true"] .cron-multiselect option:hover { background-color: var(--multiselect-hover-bg) !important; } :root[data-darkmode="true"] #minute_select option:checked { background-color: var(--cron-minute) !important; color: #ffffff !important; font-weight: 600; } :root[data-darkmode="true"] #hour_select option:checked { background-color: var(--cron-hour) !important; color: #ffffff !important; font-weight: 600; } :root[data-darkmode="true"] #day_select option:checked { background-color: var(--cron-day) !important; color: #ffffff !important; font-weight: 600; } :root[data-darkmode="true"] #month_select option:checked { background-color: var(--cron-month) !important; color: #ffffff !important; font-weight: 600; } :root[data-darkmode="true"] #weekday_select option:checked { background-color: var(--cron-weekday) !important; color: #ffffff !important; font-weight: 600; } :root[data-darkmode="true"] #minute_select option:checked:hover { background-color: var(--cron-minute) !important; color: #ffffff !important; } :root[data-darkmode="true"] #hour_select option:checked:hover { background-color: var(--cron-hour) !important; color: #ffffff !important; } :root[data-darkmode="true"] #day_select option:checked:hover { background-color: var(--cron-day) !important; color: #ffffff !important; } :root[data-darkmode="true"] #month_select option:checked:hover { background-color: var(--cron-month) !important; color: #ffffff !important; } :root[data-darkmode="true"] #weekday_select option:checked:hover { background-color: var(--cron-weekday) !important; color: #ffffff !important; } :root[data-darkmode="true"] .cron-multiselect::-webkit-scrollbar { width: 8px; } :root[data-darkmode="true"] .cron-multiselect::-webkit-scrollbar-track { background: #1a1e22; border-radius: 4px; } :root[data-darkmode="true"] .cron-multiselect::-webkit-scrollbar-thumb { background: #4a5158; border-radius: 4px; } :root[data-darkmode="true"] .cron-multiselect::-webkit-scrollbar-thumb:hover { background: #5a6168; } :root .cron-multiselect::-webkit-scrollbar { width: 8px; } :root .cron-multiselect::-webkit-scrollbar-track { background: #f1f1f1; border-radius: 4px; } :root .cron-multiselect::-webkit-scrollbar-thumb { background: #c1c1c1; } :root .cron-multiselect::-webkit-scrollbar-thumb:hover { background: #a8a8a8; } `; document.head.appendChild(style); }, badge: function(bg, content){ const color = this.getBadgeTextColor(); return '' + content + ''; }, getSelectedValues: function(selectId) { const sel = document.getElementById(selectId); if (!sel) return []; const vals = Array.from(sel.selectedOptions || []) .map(function(o){ return o.value; }) .filter(function(v){ return v !== undefined && v !== null; }); return vals; }, normalizeSelection: function(values) { if (!values || values.length === 0) return ['*']; if (values.includes('*')) return ['*']; const uniq = Array.from(new Set(values.map(function(v){ return parseInt(v, 10); }))); return uniq.sort(function(a,b){ return a-b; }).map(function(x){ return String(x); }); }, compressValues: function(values) { if (!values || values.length === 0) return '*'; if (values.length === 1) return values[0]; const nums = values.map(function(v){ return parseInt(v,10); }); let parts = []; let start = nums[0]; let prev = nums[0]; for (let i = 1; i < nums.length; i++) { const cur = nums[i]; if (cur === prev + 1) { prev = cur; continue; } parts.push(start === prev ? String(start) : (start + '-' + prev)); start = prev = cur; } parts.push(start === prev ? String(start) : (start + '-' + prev)); return parts.join(','); }, buildCronField: function(selectId, checkboxId /*, isPeriod*/) { const valuesRaw = this.getSelectedValues(selectId); const values = this.normalizeSelection(valuesRaw); if (checkboxId) { const cb = document.getElementById(checkboxId); if (cb && cb.checked) { const sel = values[0]; if (sel === '*' || sel === undefined) return '*'; return '*/' + sel; } } if (values.length === 0 || values[0] === '*') return '*'; return this.compressValues(values); }, updateCronPreview: function() { const minute = this.buildCronField('minute_select', 'minute_checkbox', true); const hour = this.buildCronField('hour_select', 'hour_checkbox', true); const day = this.buildCronField('day_select', 'day_checkbox', true); const month = this.buildCronField('month_select'); const weekday = this.buildCronField('weekday_select'); const cmdInput = document.getElementById('cron_command_input'); const command = cmdInput ? (cmdInput.value || '') : ''; const isMinuteChanged = minute !== '*'; const isHourChanged = hour !== '*'; const isDayChanged = day !== '*'; const isMonthChanged = month !== '*'; const isWeekdayChanged = weekday !== '*'; const isCommandChanged = command !== ''; const colors = this.getCurrentColors(); let cronHtml = ''; cronHtml += isMinuteChanged ? this.badge(colors.minute, minute) : minute; cronHtml += ' '; cronHtml += isHourChanged ? this.badge(colors.hour, hour) : hour; cronHtml += ' '; cronHtml += isDayChanged ? this.badge(colors.day, day) : day; cronHtml += ' '; cronHtml += isMonthChanged ? this.badge(colors.month, month) : month; cronHtml += ' '; cronHtml += isWeekdayChanged ? this.badge(colors.weekday, weekday) : weekday; cronHtml += ' '; cronHtml += isCommandChanged ? '' + command + '' : command document.getElementById('cron_preview').innerHTML = cronHtml; const plain = minute + ' ' + hour + ' ' + day + ' ' + month + ' ' + weekday + ' ' + command; document.getElementById('cron_preview_text').value = plain; const human = this._describeCron(minute, hour, day, month, weekday, command); const humanEl = document.getElementById('cron_human_text'); if (humanEl) humanEl.value = human; }, appendCronLine: function() { const input = document.getElementById('cron_preview_text'); if (!input) { ui.addNotification(null, E('p', _('Please enter the command')), 'info'); return; } const cronLine = (input.value || '').trim(); const isDefault = cronLine === '* * * * *' || cronLine === ''; const parts = cronLine.split(/\s+/); if (isDefault) { ui.addNotification(null, E('p', _('Check the cron entry, it must contain (time + command)')), 'info'); return; } if (parts.length < 6) { ui.addNotification(null, E('p', _('Please enter the command')), 'info'); return; } return fs.read('/etc/crontabs/root').then(function(content) { let current = (content || '').replace(/\r\n/g, '\n'); const haystack = '\n' + current + (current.endsWith('\n') ? '' : '\n'); const needle = '\n' + cronLine + '\n'; if (haystack.indexOf(needle) !== -1) { ui.addNotification(null, E('p', _('This entry already exists in cron') + ': ' + cronLine)); return Promise.resolve('duplicate'); } if (!current.endsWith('\n') && current.length > 0) current += '\n'; current += cronLine + '\n'; return fs.write('/etc/crontabs/root', current).then(function(){ return 'written'; }); }).then(function(state) { if (state === 'duplicate') return; return fs.exec('/etc/init.d/cron', ['restart']).then(function() { popTimeout(null, E('p', _('Added entry to cron') + ': ' + cronLine), 5000, 'info'); }); }).catch(function(e) { ui.addNotification(null, E('p', _('Error: %s').format(e.message))); }); }, generateOptions: function(start, end, labels) { const opts = []; opts.push(E('option', { value: '*', selected: true }, '*')); if (labels) { for (let i = 0; i < labels.length; i++) { opts.push(E('option', { value: (start + i).toString() }, labels[i])); } } else { for (let i = start; i <= end; i++) { opts.push(E('option', { value: i.toString() }, i.toString())); } } return opts; }, resetMulti: function(id) { const sel = document.getElementById(id); if (!sel) return; Array.from(sel.options).forEach(function(o){ o.selected = (o.value === '*'); }); }, render: function(){ this.addStyles(); const root = E('div', { class:'cbi-map' }, [ E('h4', {}, [ _('Graphical Crontab Configurator') ]), E('div', { 'class': 'cbi-section-descr fade-in' }, [_('A super simple graphical configurator for crontab.') ]), E('div', { 'style': 'display:grid;grid-template-columns:repeat(auto-fit, minmax(200px, 1fr));margin-bottom:1em;gap:1em' }, [ E('div', { 'class': 'ifacebox', 'style': 'margin:.25em;width:100%' }, [ E('div', { 'class': 'ifacebox-head cron-minute-head', 'style': 'font-weight:bold;padding:8px;color:#ffffff;' }, [ _('Minute') ]), E('div', { 'class': 'ifacebox-body', 'style': 'padding:8px' }, [ E('select', { 'id': 'minute_select', 'class': 'cbi-input-select cron-multiselect', 'multiple': true, 'size': 8, 'change': ui.createHandlerFn(this, 'updateCronPreview') }, this.generateOptions(0, 59)), E('div', { 'style': 'display:flex; align-items:center; gap:5px; margin-top:6px' }, [ E('input', { 'type': 'checkbox', 'id': 'minute_checkbox', 'change': ui.createHandlerFn(this, 'updateCronPreview') }), E('label', { 'for': 'minute_checkbox', 'style': 'font-size:12px' }, _('every specified minute period')) ]) ]) ]), E('div', { 'class': 'ifacebox', 'style': 'margin:.25em;width:100%' }, [ E('div', { 'class': 'ifacebox-head cron-hour-head', 'style': 'font-weight:bold;padding:8px;color:#ffffff;' }, [ _('Hour') ]), E('div', { 'class': 'ifacebox-body', 'style': 'padding:8px' }, [ E('select', { 'id': 'hour_select', 'class': 'cbi-input-select cron-multiselect', 'multiple': true, 'size': 8, 'change': ui.createHandlerFn(this, 'updateCronPreview') }, this.generateOptions(0, 23)), E('div', { 'style': 'display:flex; align-items:center; gap:5px; margin-top:6px' }, [ E('input', { 'type': 'checkbox', 'id': 'hour_checkbox', 'change': ui.createHandlerFn(this, 'updateCronPreview') }), E('label', { 'for': 'hour_checkbox', 'style': 'font-size:12px' }, _('every specified hour period')) ]) ]) ]), E('div', { 'class': 'ifacebox', 'style': 'margin:.25em;width:100%' }, [ E('div', { 'class': 'ifacebox-head cron-day-head', 'style': 'font-weight:bold;padding:8px;color:#ffffff;' }, [ _('Day') ]), E('div', { 'class': 'ifacebox-body', 'style': 'padding:8px' }, [ E('select', { 'id': 'day_select', 'class': 'cbi-input-select cron-multiselect', 'multiple': true, 'size': 8, 'change': ui.createHandlerFn(this, 'updateCronPreview') }, this.generateOptions(1, 31)), E('div', { 'style': 'display:flex; align-items:center; gap:5px; margin-top:6px' }, [ E('input', { 'type': 'checkbox', 'id': 'day_checkbox', 'change': ui.createHandlerFn(this, 'updateCronPreview') }), E('label', { 'for': 'day_checkbox', 'style': 'font-size:12px' }, _('every specified day period')) ]) ]) ]), E('div', { 'class': 'ifacebox', 'style': 'margin:.25em;width:100%' }, [ E('div', { 'class': 'ifacebox-head cron-month-head', 'style': 'font-weight:bold;padding:8px;color:#ffffff;' }, [ _('Month') ]), E('div', { 'class': 'ifacebox-body', 'style': 'padding:8px' }, [ E('select', { 'id': 'month_select', 'class': 'cbi-input-select cron-multiselect', 'multiple': true, 'size': 8, 'change': ui.createHandlerFn(this, 'updateCronPreview') }, this.generateOptions(1, 12, [ _('January'), _('February'), _('March'), _('April'), _('May'), _('June'), _('July'), _('August'), _('September'), _('October'), _('November'), _('December') ])) ]) ]), E('div', { 'class': 'ifacebox', 'style': 'margin:.25em;width:100%' }, [ E('div', { 'class': 'ifacebox-head cron-weekday-head', 'style': 'font-weight:bold;padding:8px;color:#ffffff;' }, [ _('Weekday') ]), E('div', { 'class': 'ifacebox-body', 'style': 'padding:8px' }, [ E('select', { 'id': 'weekday_select', 'class': 'cbi-input-select cron-multiselect', 'multiple': true, 'size': 8, 'change': ui.createHandlerFn(this, 'updateCronPreview') }, this.generateOptions(0, 6, [ _('Sunday'), _('Monday'), _('Tuesday'), _('Wednesday'), _('Thursday'), _('Friday'), _('Saturday') ])) ]) ]) ]), E('label', { 'style': 'font-weight:bold; display:block; margin-bottom:5px;' }, _('Command to execute:')), E('input', { 'id': 'cron_command_input', 'class': 'cbi-input-text', 'style': 'width:100%; margin-bottom:10px; text-align:center;', 'blur': ui.createHandlerFn(this, 'updateCronPreview') }), E('div', { 'style': 'margin-top:1em;' }, [ E('label', { 'style': 'font-weight:bold; display:block; margin-bottom:5px;' }, _('Command preview:')), E('div', { 'id': 'cron_preview', 'style': 'width:100%; margin-bottom:10px; font-family:monospace; border:1px solid var(--border-color-medium); padding:8px; background:var(--tile-free); border-radius:6px; color:var(--tile-text);' }), ]), // Desc E('div', { 'style': 'margin-top:1em;' }, [ E('label', { 'style': 'font-weight:bold; display:block; margin-bottom:5px;' }, _('Description:')), E('textarea', { 'id': 'cron_human_text', 'class': 'cbi-input-textarea', 'style': 'width:100%; min-height:60px; resize:vertical;', 'readonly': true }) ]), E('div', { 'style': 'margin-top:1em;' }, [ E('label', { 'style': 'font-weight:bold; display:block; margin-bottom:5px;' }, _('Generated cron command:')), E('input', { 'id': 'cron_preview_text', 'class': 'cbi-input-text', 'style': 'width:100%; margin-bottom:10px; font-family:monospace;', 'readonly': true, 'data-tooltip': _('Cron command for copying'), 'value': '* * * * * ' }) ]), E('div', { 'class': 'cron-actions' }, [ E('button', { 'class': 'cbi-button cbi-button-positive', 'click': ui.createHandlerFn(this, 'appendCronLine') }, _('Add to cron')), E('button', { 'class': 'cbi-button cbi-button-neutral', 'click': ui.createHandlerFn(this, function(){ this.resetMulti('minute_select'); this.resetMulti('hour_select'); this.resetMulti('day_select'); this.resetMulti('month_select'); this.resetMulti('weekday_select'); document.getElementById('cron_command_input').value = ''; const minuteCheckbox = document.getElementById('minute_checkbox'); const hourCheckbox = document.getElementById('hour_checkbox'); const dayCheckbox = document.getElementById('day_checkbox'); if (minuteCheckbox) minuteCheckbox.checked = false; if (hourCheckbox) hourCheckbox.checked = false; if (dayCheckbox) dayCheckbox.checked = false; this.updateCronPreview(); }) }, _('Reset changes')) ]) ]); setTimeout(() => this.updateCronPreview(), 0); return root; }, handleSaveApply: null, handleSave: null, handleReset: null });