mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-14 20:48:17 +08:00
🦄 Sync 2026-11-13 03:09:08
This commit is contained in:
1 parent
b8837465cd
commit
be1c08bda8
255 files changed
+45916
-22815
No files matched your search
@@ -0,0 +1,750 @@
|
||||
'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;i<hours.length;i++){
|
||||
for (let j=0;j<mins.length;j++){
|
||||
combos.push(this._pad2(hours[i])+':'+this._pad2(mins[j]));
|
||||
}
|
||||
}
|
||||
if (combos.length===1) return { single: combos[0], list: null };
|
||||
if (combos.length>0 && 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 '<span class="cron-badge" style="background:' + bg + '; color:' + color + '">' + content + '</span>';
|
||||
},
|
||||
|
||||
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 ? '<span class="cron-badge" style="background: var(--border-color-medium, var(--border-color-strong)); color:var(--badge-text)">' + command + '</span>'
|
||||
: 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
|
||||
});
|
||||
Reference in new issue
Block a user