mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-14 12:38:03 +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,20 @@
|
||||
#
|
||||
# Copyright 2025 Rafał Wabik - IceG - From eko.one.pl forum
|
||||
#
|
||||
# MIT License
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-crontab-wizard
|
||||
LUCI_TITLE:=Crontab configurator for LuCI JS
|
||||
MAINTAINER:=Rafał Wabik <4Rafal@gmail.com>
|
||||
LUCI_DESCRIPTION:=Super simple graphical configurator for crontab.
|
||||
LUCI_DEPENDS:=
|
||||
PKG_VERSION:=1.0.4
|
||||
PKG_RELEASE:=1
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
include $(TOPDIR)/feeds/luci/luci.mk
|
||||
|
||||
# call BuildPackage - OpenWrt buildroot signature
|
||||
@@ -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
|
||||
});
|
||||
@@ -0,0 +1,200 @@
|
||||
msgid "Graphical Crontab Configurator"
|
||||
msgstr "Graficzny konfigurator crontab"
|
||||
|
||||
msgid "Crontab Configurator"
|
||||
msgstr "Konfigurator crontab"
|
||||
|
||||
msgid "Minute"
|
||||
msgstr "Minuta"
|
||||
|
||||
msgid "Hour"
|
||||
msgstr "Godzina"
|
||||
|
||||
msgid "Day"
|
||||
msgstr "Dzień"
|
||||
|
||||
msgid "Month"
|
||||
msgstr "Miesiąc"
|
||||
|
||||
msgid "Weekday"
|
||||
msgstr "Dzień tygodnia"
|
||||
|
||||
msgid "Command to execute:"
|
||||
msgstr "Polecenie do wykonania:"
|
||||
|
||||
msgid "Command preview:"
|
||||
msgstr "Wizualizacja polecenia:"
|
||||
|
||||
msgid "Generated cron command:"
|
||||
msgstr "Wygenerowane polecenie cron:"
|
||||
|
||||
msgid "Move to crontab"
|
||||
msgstr "Przenieś do crontab"
|
||||
|
||||
msgid "Reset changes"
|
||||
msgstr "Resetuj zmiany"
|
||||
|
||||
msgid "every specified minute period"
|
||||
msgstr "co podany okres minut"
|
||||
|
||||
msgid "every specified hour period"
|
||||
msgstr "co podany okres godzin"
|
||||
|
||||
msgid "every specified day period"
|
||||
msgstr "co podany okres dni"
|
||||
|
||||
msgid "January"
|
||||
msgstr "Styczeń"
|
||||
|
||||
msgid "February"
|
||||
msgstr "Luty"
|
||||
|
||||
msgid "March"
|
||||
msgstr "Marzec"
|
||||
|
||||
msgid "April"
|
||||
msgstr "Kwiecień"
|
||||
|
||||
msgid "May"
|
||||
msgstr "Maj"
|
||||
|
||||
msgid "June"
|
||||
msgstr "Czerwiec"
|
||||
|
||||
msgid "July"
|
||||
msgstr "Lipiec"
|
||||
|
||||
msgid "August"
|
||||
msgstr "Sierpień"
|
||||
|
||||
msgid "September"
|
||||
msgstr "Wrzesień"
|
||||
|
||||
msgid "October"
|
||||
msgstr "Październik"
|
||||
|
||||
msgid "November"
|
||||
msgstr "Listopad"
|
||||
|
||||
msgid "December"
|
||||
msgstr "Grudzień"
|
||||
|
||||
msgid "Sunday"
|
||||
msgstr "Niedziela"
|
||||
|
||||
msgid "Monday"
|
||||
msgstr "Poniedziałek"
|
||||
|
||||
msgid "Tuesday"
|
||||
msgstr "Wtorek"
|
||||
|
||||
msgid "Wednesday"
|
||||
msgstr "Środa"
|
||||
|
||||
msgid "Thursday"
|
||||
msgstr "Czwartek"
|
||||
|
||||
msgid "Friday"
|
||||
msgstr "Piątek"
|
||||
|
||||
msgid "Saturday"
|
||||
msgstr "Sobota"
|
||||
|
||||
msgid "A super simple graphical configurator for crontab."
|
||||
msgstr "Bardzo prosty graficzny konfigurator dla crontab."
|
||||
|
||||
msgid "Please enter the command"
|
||||
msgstr "Proszę podać polecenie"
|
||||
|
||||
msgid "Check the cron entry, it must contain (time + command)"
|
||||
msgstr "Sprawdź wpis crona, musi zawierać (czas + polecenie)"
|
||||
|
||||
msgid "This entry already exists in cron"
|
||||
msgstr "Wpis już istnieje w cronie"
|
||||
|
||||
msgid "This entry already exists in cron"
|
||||
msgstr "Wpis już istnieje w cronie"
|
||||
|
||||
msgid "Added entry to cron"
|
||||
msgstr "Dodano wpis do crona"
|
||||
|
||||
msgid "Error: %s"
|
||||
msgstr "Błąd: %s"
|
||||
|
||||
msgid "Add to cron"
|
||||
msgstr "Dodaj do crona"
|
||||
|
||||
msgid "Cron command for copying"
|
||||
msgstr "Polecenie cron do skopiowania"
|
||||
|
||||
msgid "minute"
|
||||
msgstr "minuta"
|
||||
|
||||
msgid "minutes"
|
||||
msgstr "minut(-y)"
|
||||
|
||||
msgid "hour"
|
||||
msgstr "godzina"
|
||||
|
||||
msgid "hours"
|
||||
msgstr "godzin(-y)"
|
||||
|
||||
msgid "day"
|
||||
msgstr "dzień"
|
||||
|
||||
msgid "days"
|
||||
msgstr "dni(-ach)"
|
||||
|
||||
msgid "month"
|
||||
msgstr "miesiąc"
|
||||
|
||||
msgid "months"
|
||||
msgstr "miesiące(-ęcy)"
|
||||
|
||||
msgid "Description:"
|
||||
msgstr "Opis:"
|
||||
|
||||
msgid "at time: %s"
|
||||
msgstr "o godzinie: %s"
|
||||
|
||||
msgid "at times: %s"
|
||||
msgstr "o czasie: %s"
|
||||
|
||||
msgid "runs every %d %s"
|
||||
msgstr "działa z okresem %d %s"
|
||||
|
||||
msgid "in minutes: %s"
|
||||
msgstr "w minutach: %s"
|
||||
|
||||
msgid "in hours: %s"
|
||||
msgstr "w godzinach: %s"
|
||||
|
||||
msgid "runs every %d %s of the month"
|
||||
msgstr "z okresem co %d %s dni w miesiącu"
|
||||
|
||||
msgid "on day(s): %s"
|
||||
msgstr "w dniu(-ach): %s"
|
||||
|
||||
msgid "runs every %d month(s)"
|
||||
msgstr "z okresem co %d miesiąc(-ęcy)"
|
||||
|
||||
msgid "in month(s): %s"
|
||||
msgstr "w miesiącu(-ach): %s"
|
||||
|
||||
msgid "runs every %d day of the week"
|
||||
msgstr "działa każdego %d dnia tygodnia"
|
||||
|
||||
msgid "on weekday(s): %s"
|
||||
msgstr "w dni(-u) tygodnia: %s"
|
||||
|
||||
msgid "Command \"%s\" "
|
||||
msgstr "Polecenie \"%s\" "
|
||||
|
||||
msgid "Command"
|
||||
msgstr "Polecenie"
|
||||
|
||||
msgid "Command will run"
|
||||
msgstr "Polecenie zostanie wykonane"
|
||||
|
||||
msgid "Command \"%s\" will run"
|
||||
msgstr "Polecenie \"%s\" zostanie wykonane"
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
zh_Hans
|
||||
@@ -0,0 +1,202 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Project-Id-Version: luci-app-crontab-wizard\n"
|
||||
"Language: zh_Hans\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
|
||||
#: luci-app-crontab-wizard/root/usr/share/luci/menu.d/luci-app-crontab-wizard.json
|
||||
msgid "Scheduled Tasks"
|
||||
msgstr "计划任务"
|
||||
|
||||
msgid "Crontab Configurator"
|
||||
msgstr "Crontab 配置向导"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js
|
||||
msgid "Graphical Crontab Configurator"
|
||||
msgstr "图形化 Crontab 配置向导"
|
||||
|
||||
msgid "A super simple graphical configurator for crontab."
|
||||
msgstr "一个超级简单的图形化 Crontab 定时任务配置工具。"
|
||||
|
||||
msgid "Minute"
|
||||
msgstr "分钟"
|
||||
|
||||
msgid "every specified minute period"
|
||||
msgstr "每隔指定分钟周期"
|
||||
|
||||
msgid "Hour"
|
||||
msgstr "小时"
|
||||
|
||||
msgid "every specified hour period"
|
||||
msgstr "每隔指定小时周期"
|
||||
|
||||
msgid "Day"
|
||||
msgstr "日期"
|
||||
|
||||
msgid "every specified day period"
|
||||
msgstr "每隔指定天数周期"
|
||||
|
||||
msgid "Month"
|
||||
msgstr "月份"
|
||||
|
||||
msgid "Weekday"
|
||||
msgstr "星期"
|
||||
|
||||
msgid "Command to execute:"
|
||||
msgstr "要执行的命令:"
|
||||
|
||||
msgid "Command preview:"
|
||||
msgstr "命令预览:"
|
||||
|
||||
msgid "Description:"
|
||||
msgstr "规则说明:"
|
||||
|
||||
msgid "Generated cron command:"
|
||||
msgstr "生成的 Cron 命令:"
|
||||
|
||||
msgid "Cron command for copying"
|
||||
msgstr "用于复制的 Cron 命令"
|
||||
|
||||
msgid "Add to cron"
|
||||
msgstr "添加到 Crontab"
|
||||
|
||||
msgid "Reset changes"
|
||||
msgstr "重置更改"
|
||||
|
||||
msgid "Please enter the command"
|
||||
msgstr "请输入命令"
|
||||
|
||||
msgid "Check the cron entry, it must contain (time + command)"
|
||||
msgstr "请检查 Cron 条目,必须包含(时间 + 命令)"
|
||||
|
||||
msgid "This entry already exists in cron"
|
||||
msgstr "此条目已存在于 Crontab 中"
|
||||
|
||||
msgid "Added entry to cron"
|
||||
msgstr "已将条目添加到 Crontab"
|
||||
|
||||
msgid "Error: %s"
|
||||
msgstr "错误:%s"
|
||||
|
||||
msgid "Command \"%s\" "
|
||||
msgstr "命令 \"%s\" "
|
||||
|
||||
msgid "Command"
|
||||
msgstr "命令"
|
||||
|
||||
msgid "Command \"%s\" will run"
|
||||
msgstr "命令 \"%s\" 将运行"
|
||||
|
||||
msgid "Command will run"
|
||||
msgstr "命令将运行"
|
||||
|
||||
msgid "at time: %s"
|
||||
msgstr "时间:%s"
|
||||
|
||||
msgid "at times: %s"
|
||||
msgstr "时间:%s"
|
||||
|
||||
msgid "runs every %d %s"
|
||||
msgstr "每 %d %s运行一次"
|
||||
|
||||
msgid "in minutes: %s"
|
||||
msgstr "在指定分钟:%s"
|
||||
|
||||
msgid "in hours: %s"
|
||||
msgstr "在指定小时:%s"
|
||||
|
||||
msgid "runs every %d %s of the month"
|
||||
msgstr "每月每 %d %s运行一次"
|
||||
|
||||
msgid "on day(s): %s"
|
||||
msgstr "在日期:%s"
|
||||
|
||||
msgid "runs every %d month(s)"
|
||||
msgstr "每 %d 个月运行一次"
|
||||
|
||||
msgid "in month(s): %s"
|
||||
msgstr "在月份:%s"
|
||||
|
||||
msgid "runs every %d day of the week"
|
||||
msgstr "每周每 %d 天运行一次"
|
||||
|
||||
msgid "on weekday(s): %s"
|
||||
msgstr "在星期:%s"
|
||||
|
||||
msgid "minute"
|
||||
msgstr "分钟"
|
||||
|
||||
msgid "minutes"
|
||||
msgstr "分钟"
|
||||
|
||||
msgid "hour"
|
||||
msgstr "小时"
|
||||
|
||||
msgid "hours"
|
||||
msgstr "小时"
|
||||
|
||||
msgid "day"
|
||||
msgstr "天"
|
||||
|
||||
msgid "days"
|
||||
msgstr "天"
|
||||
|
||||
msgid "Sunday"
|
||||
msgstr "星期日"
|
||||
|
||||
msgid "Monday"
|
||||
msgstr "星期一"
|
||||
|
||||
msgid "Tuesday"
|
||||
msgstr "星期二"
|
||||
|
||||
msgid "Wednesday"
|
||||
msgstr "星期三"
|
||||
|
||||
msgid "Thursday"
|
||||
msgstr "星期四"
|
||||
|
||||
msgid "Friday"
|
||||
msgstr "星期五"
|
||||
|
||||
msgid "Saturday"
|
||||
msgstr "星期六"
|
||||
|
||||
msgid "January"
|
||||
msgstr "一月"
|
||||
|
||||
msgid "February"
|
||||
msgstr "二月"
|
||||
|
||||
msgid "March"
|
||||
msgstr "三月"
|
||||
|
||||
msgid "April"
|
||||
msgstr "四月"
|
||||
|
||||
msgid "May"
|
||||
msgstr "五月"
|
||||
|
||||
msgid "June"
|
||||
msgstr "六月"
|
||||
|
||||
msgid "July"
|
||||
msgstr "七月"
|
||||
|
||||
msgid "August"
|
||||
msgstr "八月"
|
||||
|
||||
msgid "September"
|
||||
msgstr "九月"
|
||||
|
||||
msgid "October"
|
||||
msgstr "十月"
|
||||
|
||||
msgid "November"
|
||||
msgstr "十一月"
|
||||
|
||||
msgid "December"
|
||||
msgstr "十二月"
|
||||
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"admin/system/crontab": {
|
||||
"title": "Scheduled Tasks",
|
||||
"order": 46,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "system/crontab"
|
||||
},
|
||||
"depends": {
|
||||
"acl": [ "luci-mod-system-cron" ]
|
||||
}
|
||||
},
|
||||
|
||||
"admin/system/crontab/crontab": {
|
||||
"title": "Scheduled Tasks",
|
||||
"order": 46,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "system/crontab"
|
||||
},
|
||||
"depends": {
|
||||
"acl": [ "luci-mod-system-cron" ]
|
||||
}
|
||||
},
|
||||
|
||||
"admin/system/crontab/wizard": {
|
||||
"title": "Crontab Configurator",
|
||||
"order": 47,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "system/crontab_wizard"
|
||||
},
|
||||
"depends": {
|
||||
"acl": [ "luci-app-crontab-wizard" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"luci-app-crontab-wizard": {
|
||||
"description": "Grant access to luci-app-crontab-wizard",
|
||||
"read": {
|
||||
"file": {
|
||||
"/etc/crontabs/root": [ "read" ]
|
||||
},
|
||||
"ubus": {
|
||||
"file": [ "read" ]
|
||||
}
|
||||
},
|
||||
"write": {
|
||||
"file": {
|
||||
"/etc/crontabs/root": [ "write" ],
|
||||
"/etc/init.d/cron reload": [ "exec" ]
|
||||
},
|
||||
"ubus": {
|
||||
"file": [ "write" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user