| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758 |
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
- <title>Task Scheduler</title>
- <script src="../../script/jquery.min.js"></script>
- <script src="../../script/ao_module.js"></script>
- <script src="../../script/applocale.js"></script>
- <script src="js/moment.min.js"></script>
- <style>
- *, *::before, *::after { box-sizing: border-box; }
- /* ── Palette — matches system_setting/main.css ── */
- :root {
- --bg: #f3f3f3;
- --sidebar-bg: #ebebeb;
- --sidebar-border: #dcdcdc;
- --text: #202020;
- --text-dim: #555;
- --text-muted: #888;
- --text-desc: #666;
- --nav-hover: rgba(0,0,0,0.055);
- --nav-active: rgba(0,0,0,0.09);
- --card-bg: #ffffff;
- --card-border: #e5e5e5;
- --card-hover-bg: #fafafa;
- --accent: #0071e3;
- --danger: #ff3b30;
- --success: #34c759;
- --warning: #ff9f0a;
- --divider: #e0e0e0;
- --scrollbar: #c8c8c8;
- }
- body.dark {
- --bg: #1f1f1f;
- --sidebar-bg: #282828;
- --sidebar-border: #363636;
- --text: #e3e3e3;
- --text-dim: #999;
- --text-muted: #666;
- --text-desc: #aaa;
- --nav-hover: rgba(255,255,255,0.06);
- --nav-active: rgba(255,255,255,0.10);
- --card-bg: #2d2d2d;
- --card-border: #3a3a3a;
- --card-hover-bg: #333;
- --accent: #2997ff;
- --danger: #ff453a;
- --success: #30d158;
- --warning: #ffd60a;
- --divider: #3a3a3a;
- --scrollbar: #555;
- }
- html, body {
- height: 100%;
- margin: 0;
- overflow: hidden;
- background: var(--bg);
- color: var(--text);
- font-family: 'Segoe UI', system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
- font-size: 14px;
- -webkit-font-smoothing: antialiased;
- transition: background 0.15s, color 0.15s;
- }
- /* ── Layout shell ── */
- #app { display: flex; height: 100vh; overflow: hidden; }
- /* ── Sidebar ── */
- #sidebar {
- width: 220px;
- min-width: 220px;
- background: var(--sidebar-bg);
- border-right: 1px solid var(--sidebar-border);
- display: flex;
- flex-direction: column;
- overflow: hidden;
- transition: background 0.15s, border-color 0.15s;
- }
- #sidebar-title {
- padding: 18px 16px 10px;
- font-size: 17px;
- font-weight: 600;
- letter-spacing: -0.1px;
- display: flex;
- align-items: center;
- gap: 9px;
- user-select: none;
- flex-shrink: 0;
- color: var(--text);
- }
- #sidebar-title img { width: 20px; height: 20px; opacity: 0.7; }
- #sidebar-nav {
- flex: 1;
- overflow-y: auto;
- padding: 4px 8px 10px;
- }
- #sidebar-nav::-webkit-scrollbar { width: 3px; }
- #sidebar-nav::-webkit-scrollbar-thumb { background: var(--scrollbar); border-radius: 2px; }
- .nav-item {
- display: flex;
- align-items: center;
- gap: 10px;
- padding: 7px 10px;
- border-radius: 6px;
- cursor: pointer;
- user-select: none;
- transition: background 0.08s;
- font-size: 13.5px;
- color: var(--text);
- margin-bottom: 1px;
- }
- .nav-item:hover { background: var(--nav-hover); }
- .nav-item.active { background: var(--nav-active); font-weight: 500; }
- .nav-icon { width: 16px; height: 16px; flex-shrink: 0; opacity: 0.72; display: flex; align-items: center; }
- .nav-icon svg { width: 16px; height: 16px; }
- /* Filter box */
- #sidebar-filter {
- padding: 0 10px 10px;
- flex-shrink: 0;
- }
- #filter-input {
- width: 100%;
- padding: 6px 10px;
- border-radius: 6px;
- border: 1px solid var(--card-border);
- background: var(--card-bg);
- color: var(--text);
- font-size: 12.5px;
- font-family: inherit;
- outline: none;
- transition: border-color 0.1s;
- }
- #filter-input:focus { border-color: var(--accent); }
- body.dark #filter-input { background: #333; border-color: #444; }
- /* ── Main content ── */
- #main {
- flex: 1;
- overflow-y: auto;
- padding: 20px 24px;
- background: var(--bg);
- }
- #main::-webkit-scrollbar { width: 6px; }
- #main::-webkit-scrollbar-thumb { background: var(--scrollbar); border-radius: 3px; }
- .view { display: none; }
- .view.active { display: block; }
- /* ── Section heading ── */
- .section-title {
- font-size: 18px;
- font-weight: 600;
- margin-bottom: 14px;
- color: var(--text);
- }
- /* ── Card / list ── */
- .card {
- background: var(--card-bg);
- border: 1px solid var(--card-border);
- border-radius: 10px;
- overflow: hidden;
- margin-bottom: 16px;
- }
- /* ── Task list rows ── */
- .task-row {
- display: grid;
- grid-template-columns: minmax(120px,1.8fr) minmax(0,2fr) 90px 100px;
- align-items: center;
- padding: 10px 14px;
- border-bottom: 1px solid var(--divider);
- gap: 10px;
- font-size: 13px;
- transition: background 0.08s;
- }
- .task-row:last-child { border-bottom: none; }
- .task-row.header {
- font-size: 11px;
- font-weight: 600;
- text-transform: uppercase;
- letter-spacing: 0.04em;
- color: var(--text-muted);
- padding: 8px 14px;
- background: var(--bg);
- cursor: default;
- }
- body.dark .task-row.header { background: #282828; }
- .task-row:not(.header):hover { background: var(--card-hover-bg); }
- /* Admin "all tasks" has an extra Creator column */
- .task-row.all-cols {
- grid-template-columns: minmax(100px,1.4fr) minmax(80px,1.2fr) minmax(0,2fr) 80px 90px 100px;
- }
- /* Remove view has action button column */
- .task-row.remove-cols {
- grid-template-columns: minmax(100px,1.4fr) minmax(80px,1.2fr) minmax(0,2fr) 80px 90px 80px;
- }
- .task-name { font-weight: 500; color: var(--text); }
- .task-script { color: var(--text-dim); font-size: 12.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
- .task-interval { color: var(--text-desc); font-size: 12.5px; }
- .task-basetime { color: var(--text-desc); font-size: 11.5px; }
- .app-badge {
- display: inline-block;
- padding: 2px 8px;
- border-radius: 20px;
- font-size: 11px;
- font-weight: 500;
- background: rgba(0,113,227,0.10);
- color: var(--accent);
- white-space: nowrap;
- max-width: 88px;
- overflow: hidden;
- text-overflow: ellipsis;
- }
- body.dark .app-badge { background: rgba(41,151,255,0.15); }
- /* Empty state */
- .empty-state {
- padding: 32px 20px;
- text-align: center;
- color: var(--text-muted);
- font-size: 13.5px;
- }
- /* ── Buttons ── */
- .btn {
- display: inline-flex;
- align-items: center;
- gap: 5px;
- padding: 6px 14px;
- border-radius: 6px;
- border: 1px solid var(--card-border);
- font-family: inherit;
- font-size: 13px;
- font-weight: 500;
- cursor: pointer;
- outline: none;
- background: var(--card-bg);
- color: var(--text);
- transition: background 0.08s, opacity 0.12s;
- white-space: nowrap;
- }
- .btn:hover { background: var(--card-hover-bg); }
- .btn.primary { background: var(--accent); color: #fff; border-color: var(--accent); }
- .btn.primary:hover { opacity: 0.85; }
- .btn.danger { background: var(--danger); color: #fff; border-color: var(--danger); }
- .btn.danger:hover { opacity: 0.85; }
- .btn.sm { padding: 4px 10px; font-size: 12px; }
- .btn:disabled { opacity: 0.35; cursor: default; pointer-events: none; }
- /* ── New Task form ── */
- .form-card {
- background: var(--card-bg);
- border: 1px solid var(--card-border);
- border-radius: 10px;
- padding: 18px 20px;
- margin-bottom: 16px;
- }
- .form-row { margin-bottom: 14px; }
- .form-label {
- display: block;
- font-size: 12.5px;
- font-weight: 500;
- color: var(--text-dim);
- margin-bottom: 5px;
- }
- .form-req { color: var(--danger); }
- .form-input {
- width: 100%;
- padding: 7px 11px;
- border-radius: 7px;
- border: 1px solid var(--card-border);
- background: var(--bg);
- color: var(--text);
- font-size: 13.5px;
- font-family: inherit;
- outline: none;
- transition: border-color 0.12s;
- }
- body.dark .form-input { background: #333; border-color: #444; }
- .form-input:focus { border-color: var(--accent); }
- .form-hint { font-size: 11.5px; color: var(--text-muted); margin-top: 4px; }
- .form-row-inline { display: flex; gap: 8px; align-items: flex-start; }
- .form-row-inline .form-input { flex: 1; }
- select.form-input { cursor: pointer; }
- .input-with-btn { display: flex; gap: 6px; }
- .input-with-btn .form-input { flex: 1; }
- /* Permission denied banner */
- #noPermMessage {
- background: rgba(255,159,10,0.10);
- border: 1px solid rgba(255,159,10,0.28);
- border-radius: 8px;
- padding: 11px 14px;
- font-size: 13px;
- color: var(--warning);
- margin-bottom: 14px;
- display: none;
- }
- /* ── Toast ── */
- #toast {
- position: fixed;
- bottom: 16px;
- right: 18px;
- background: var(--card-bg);
- border: 1px solid var(--card-border);
- border-radius: 8px;
- padding: 9px 15px;
- font-size: 13px;
- box-shadow: 0 4px 16px rgba(0,0,0,0.14);
- display: none;
- z-index: 9999;
- color: var(--text);
- }
- /* ── Warning bar ── */
- .warn-bar {
- background: rgba(255,159,10,0.10);
- border: 1px solid rgba(255,159,10,0.25);
- border-radius: 7px;
- padding: 8px 12px;
- font-size: 12.5px;
- color: var(--warning);
- margin-top: 6px;
- display: none;
- }
- /* ── Remove view danger header ── */
- .danger-header {
- background: rgba(255,59,48,0.08);
- border-bottom: 1px solid rgba(255,59,48,0.18);
- padding: 10px 14px;
- font-size: 12.5px;
- color: var(--danger);
- font-weight: 500;
- }
- body.dark .danger-header { background: rgba(255,69,58,0.12); }
- /* Responsive */
- @media (max-width: 640px) {
- #sidebar { display: none; }
- #main { padding: 14px 12px; }
- }
- </style>
- </head>
- <body>
- <div id="app">
- <!-- ── Sidebar ────────────────────────────────────────── -->
- <div id="sidebar">
- <div id="sidebar-title">
- <img src="img/small_icon.png" onerror="this.style.display='none'">
- <span locale="sidebar/title">Scheduler</span>
- </div>
- <div id="sidebar-nav">
- <div class="nav-item active" id="nav-mine" onclick="showView('mine')">
- <span class="nav-icon"><svg viewBox="0 0 16 16" fill="none"><path d="M5 4h7M5 8h7M5 12h7" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><circle cx="2.5" cy="4" r="0.75" fill="currentColor"/><circle cx="2.5" cy="8" r="0.75" fill="currentColor"/><circle cx="2.5" cy="12" r="0.75" fill="currentColor"/></svg></span>
- <span locale="nav/mine">My Tasks</span>
- </div>
- <div class="nav-item" id="nav-all" onclick="showView('all')">
- <span class="nav-icon"><svg viewBox="0 0 16 16" fill="none"><rect x="2" y="2" width="5" height="5" rx="1" stroke="currentColor" stroke-width="1.4"/><rect x="9" y="2" width="5" height="5" rx="1" stroke="currentColor" stroke-width="1.4"/><rect x="2" y="9" width="5" height="5" rx="1" stroke="currentColor" stroke-width="1.4"/><rect x="9" y="9" width="5" height="5" rx="1" stroke="currentColor" stroke-width="1.4"/></svg></span>
- <span locale="nav/all">All Tasks</span>
- </div>
- <div class="nav-item" id="nav-new" onclick="showView('new')">
- <span class="nav-icon"><svg viewBox="0 0 16 16" fill="none"><path d="M8 3v10M3 8h10" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg></span>
- <span locale="nav/new">New Task</span>
- </div>
- <div class="nav-item" id="nav-remove" onclick="showView('remove')">
- <span class="nav-icon"><svg viewBox="0 0 16 16" fill="none"><path d="M3 5h10M6 5V4h4v1M6.5 8v4M9.5 8v4M4 5l.9 9h6.2L12 5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg></span>
- <span locale="nav/remove">Remove Task</span>
- </div>
- </div>
- <div id="sidebar-filter">
- <input id="filter-input" type="text" placeholder="filter/placeholder"
- oninput="applyFilter()" onkeydown="if(event.key==='Enter')applyFilter()">
- </div>
- </div>
- <!-- ── Main ──────────────────────────────────────────── -->
- <div id="main">
- <!-- My Tasks -->
- <div id="view-mine" class="view active">
- <div class="section-title" locale="view/mine/title">My Scheduled Tasks</div>
- <div class="card" id="card-mine">
- <div class="task-row header">
- <span locale="header/task-script">Task / Script</span>
- <span locale="header/path">Path</span>
- <span locale="header/app">App</span>
- <span locale="header/interval">Interval</span>
- </div>
- <div id="list-mine"><div class="empty-state" locale="js/loading">Loading…</div></div>
- </div>
- </div>
- <!-- All Tasks (admin) -->
- <div id="view-all" class="view">
- <div class="section-title" locale="view/all/title">All Scheduled Tasks</div>
- <div class="card">
- <div class="task-row all-cols header">
- <span locale="header/taskname">Task Name</span>
- <span locale="header/creator">Creator</span>
- <span locale="header/scriptpath">Script Path</span>
- <span locale="header/app">App</span>
- <span locale="header/interval">Interval</span>
- <span locale="header/basetime">Base Time</span>
- </div>
- <div id="list-all"><div class="empty-state" locale="js/loading">Loading…</div></div>
- </div>
- </div>
- <!-- New Task -->
- <div id="view-new" class="view">
- <div class="section-title" locale="view/new/title">New Scheduled Task</div>
- <div id="noPermMessage">
- <svg width="14" height="14" viewBox="0 0 16 16" fill="none" style="flex-shrink:0;margin-right:6px;vertical-align:-2px"><path d="M8 2L1.5 14h13L8 2z" stroke="currentColor" stroke-width="1.4" stroke-linejoin="round"/><path d="M8 7v3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><circle cx="8" cy="12" r="0.8" fill="currentColor"/></svg>
- <span locale="noperm/message">Your account does not have permission to create scheduled tasks.
- Ask an administrator to grant cron job access via <strong>System Settings → Tasks Scheduler</strong>.</span>
- </div>
- <div id="newTaskForm">
- <div class="form-card">
- <div class="form-row">
- <label class="form-label"><span locale="form/taskname/label">Task Name</span> <span class="form-req">*</span></label>
- <input class="form-input" id="taskname" type="text"
- placeholder="taskname/placeholder" maxlength="32" autocomplete="off">
- <div class="form-hint" locale="form/taskname/hint">Max 32 characters, must be unique.</div>
- </div>
- <div class="form-row">
- <label class="form-label" locale="form/desc/label">Description</label>
- <input class="form-input" id="desc" type="text"
- placeholder="desc/placeholder" autocomplete="off">
- </div>
- <div class="form-row">
- <label class="form-label"><span locale="form/scriptpath/label">Script Path</span> <span class="form-req">*</span></label>
- <div class="input-with-btn">
- <input class="form-input" id="scriptpath" type="text"
- placeholder="scriptpath/placeholder" autocomplete="off"
- oninput="checkExt(this.value)">
- <button class="btn" onclick="openFileSelector()" locale="form/browse">Browse…</button>
- </div>
- <div class="warn-bar" id="extWarn">
- <svg width="13" height="13" viewBox="0 0 16 16" fill="none" style="flex-shrink:0;vertical-align:-2px;margin-right:4px"><path d="M8 2L1.5 14h13L8 2z" stroke="currentColor" stroke-width="1.4" stroke-linejoin="round"/><path d="M8 7v3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><circle cx="8" cy="12" r="0.8" fill="currentColor"/></svg>
- <span locale="form/extwarn">This file extension may not be supported. Only .agi and .js files are executed.</span>
- </div>
- </div>
- <div class="form-row">
- <label class="form-label"><span locale="form/runevery/label">Run Every</span> <span class="form-req">*</span></label>
- <div class="form-row-inline">
- <input class="form-input" id="intervalvalue" type="number"
- value="1" min="1" style="max-width:90px;">
- <select class="form-input" id="intervalunit" style="max-width:160px;">
- <option value="60" locale="form/unit/minutes">Minutes</option>
- <option value="3600" locale="form/unit/hours">Hours</option>
- <option value="86400" selected locale="form/unit/days">Days</option>
- <option value="2628333" locale="form/unit/months">Months (approx.)</option>
- </select>
- </div>
- <div class="form-hint" locale="form/month/hint">Month is approximated as 2 628 333 seconds.</div>
- </div>
- <div class="form-row">
- <label class="form-label"><span locale="form/alignto/label">Align to</span> <span class="form-req">*</span></label>
- <select class="form-input" id="intervalbase">
- <option value="now" locale="form/base/now">Now (current minute)</option>
- <option value="hour" locale="form/base/hour">Start of the Hour</option>
- <option value="day" locale="form/base/day">Start of the Day</option>
- <option value="month" locale="form/base/month">Start of the Month</option>
- <option value="year" locale="form/base/year">Start of the Year</option>
- <option value="mon" locale="form/base/mon">Monday midnight</option>
- <option value="tue" locale="form/base/tue">Tuesday midnight</option>
- <option value="wed" locale="form/base/wed">Wednesday midnight</option>
- <option value="thu" locale="form/base/thu">Thursday midnight</option>
- <option value="fri" locale="form/base/fri">Friday midnight</option>
- <option value="sat" locale="form/base/sat">Saturday midnight</option>
- <option value="sun" locale="form/base/sun">Sunday midnight</option>
- </select>
- <div class="form-hint" locale="form/base/hint">Sets the reference point for interval alignment (useful for weekly/monthly schedules).</div>
- </div>
- <button class="btn primary" onclick="submitNewTask()" locale="form/submit">Create Task</button>
- <span style="margin-left:10px; font-size:12px; color:var(--text-muted);" locale="form/required/hint">
- Fields with <span class="form-req">*</span> are required.
- </span>
- </div>
- </div>
- </div>
- <!-- Remove Task -->
- <div id="view-remove" class="view">
- <div class="section-title" locale="view/remove/title">Remove Scheduled Tasks</div>
- <div class="card">
- <div class="danger-header"><svg width="13" height="13" viewBox="0 0 16 16" fill="none" style="flex-shrink:0;vertical-align:-2px;margin-right:5px"><path d="M8 2L1.5 14h13L8 2z" stroke="currentColor" stroke-width="1.4" stroke-linejoin="round"/><path d="M8 7v3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><circle cx="8" cy="12" r="0.8" fill="currentColor"/></svg><span locale="danger/message">Removal is permanent and cannot be undone.</span></div>
- <div class="task-row remove-cols header">
- <span locale="header/taskname">Task Name</span>
- <span locale="header/creator">Creator</span>
- <span locale="header/scriptpath">Script Path</span>
- <span locale="header/app">App</span>
- <span locale="header/interval">Interval</span>
- <span locale="header/action">Action</span>
- </div>
- <div id="list-remove"><div class="empty-state" locale="js/loading">Loading…</div></div>
- </div>
- </div>
- </div><!-- /#main -->
- </div>
- <div id="toast"></div>
- <script>
- /* ── Theme ── */
- ao_module_getSystemThemeColor(function(c) {
- document.body.classList.toggle('dark', c !== 'whiteTheme');
- });
- /* ── i18n helper ── */
- function t(key, fallback) {
- if (typeof applocale !== 'undefined') {
- return applocale.getString(key, fallback);
- }
- return fallback;
- }
- /* ── Navigation ── */
- var currentView = 'mine';
- function showView(name) {
- document.querySelectorAll('.view').forEach(function(el) { el.classList.remove('active'); });
- document.querySelectorAll('.nav-item').forEach(function(el) { el.classList.remove('active'); });
- document.getElementById('view-' + name).classList.add('active');
- document.getElementById('nav-' + name).classList.add('active');
- currentView = name;
- if (name === 'mine') loadMine();
- if (name === 'all') loadAll();
- if (name === 'new') initNewTaskView();
- if (name === 'remove') loadRemove();
- }
- /* ── Helpers ── */
- function fmtInterval(s) {
- s = Number(s);
- var d = Math.floor(s / 86400),
- h = Math.floor(s % 86400 / 3600),
- m = Math.floor(s % 3600 / 60),
- sec = s % 60;
- var parts = [];
- if (d) parts.push(d + 'd');
- if (h) parts.push(h + 'h');
- if (m) parts.push(m + 'min');
- if (sec) parts.push(sec + 's');
- return parts.length ? parts.join(' ') : s + 's';
- }
- function fmtTime(unix) {
- if (!unix) return '—';
- return moment.unix(unix).format('MMM D, YYYY HH:mm');
- }
- function appBadge(name) {
- if (!name) return '<span style="color:var(--text-muted)">—</span>';
- return '<span class="app-badge" title="' + esc(name) + '">' + esc(name) + '</span>';
- }
- function esc(s) {
- return String(s || '').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
- }
- function filter() { return $('#filter-input').val().trim().toLowerCase(); }
- function matchFilter(task) {
- var f = filter();
- if (!f) return true;
- return (task.Name || '').toLowerCase().includes(f)
- || (task.Creator || '').toLowerCase().includes(f)
- || (task.ScriptVpath|| '').toLowerCase().includes(f)
- || (task.Description|| '').toLowerCase().includes(f)
- || (task.AppName || '').toLowerCase().includes(f);
- }
- function applyFilter() {
- if (currentView === 'mine') loadMine();
- if (currentView === 'all') loadAll();
- if (currentView === 'remove') loadRemove();
- }
- function toast(msg) {
- var el = document.getElementById('toast');
- el.textContent = msg;
- el.style.display = 'block';
- setTimeout(function() { el.style.display = 'none'; }, 2500);
- }
- /* ── My Tasks ── */
- function loadMine() {
- $.getJSON('../../system/arsm/aecron/list', function(data) {
- var el = document.getElementById('list-mine');
- var rows = (data || []).filter(matchFilter);
- if (!rows.length) {
- el.innerHTML = '<div class="empty-state">' + t('js/empty/notfound', 'No scheduled tasks found.') + '</div>';
- return;
- }
- el.innerHTML = rows.map(function(task) {
- var script = (task.ScriptVpath || '').split('/').pop();
- return '<div class="task-row">'
- + '<span class="task-name" title="' + esc(task.Name) + '">' + esc(task.Name)
- + (task.Description ? '<br><small style="color:var(--text-muted);font-weight:400">' + esc(task.Description) + '</small>' : '')
- + '</span>'
- + '<span class="task-script" title="' + esc(task.ScriptVpath) + '">' + esc(task.ScriptVpath) + '</span>'
- + '<span>' + appBadge(task.AppName) + '</span>'
- + '<span class="task-interval">' + t('js/task/every', 'Every ') + fmtInterval(task.ExecutionInterval) + '</span>'
- + '</div>';
- }).join('');
- });
- }
- /* ── All Tasks ── */
- function loadAll() {
- $.getJSON('../../system/arsm/aecron/list?listall=true', function(data) {
- var el = document.getElementById('list-all');
- var rows = (data || []).filter(matchFilter);
- if (!rows.length) {
- el.innerHTML = '<div class="empty-state">' + t('js/empty/notfound', 'No scheduled tasks found.') + '</div>';
- return;
- }
- el.innerHTML = rows.map(function(task) {
- return '<div class="task-row all-cols">'
- + '<span class="task-name" title="' + esc(task.Name) + '">' + esc(task.Name) + '</span>'
- + '<span style="color:var(--text-dim);font-size:12.5px">' + esc(task.Creator) + '</span>'
- + '<span class="task-script" title="' + esc(task.ScriptVpath) + '">' + esc(task.ScriptVpath) + '</span>'
- + '<span>' + appBadge(task.AppName) + '</span>'
- + '<span class="task-interval">' + t('js/task/every', 'Every ') + fmtInterval(task.ExecutionInterval) + '</span>'
- + '<span class="task-basetime">' + fmtTime(task.BaseTime) + '</span>'
- + '</div>';
- }).join('');
- });
- }
- /* ── Remove ── */
- function loadRemove() {
- $.getJSON('../../system/arsm/aecron/list?listall=true', function(data) {
- var el = document.getElementById('list-remove');
- var rows = (data || []).filter(matchFilter);
- if (!rows.length) {
- el.innerHTML = '<div class="empty-state">' + t('js/empty/none', 'No scheduled tasks.') + '</div>';
- return;
- }
- el.innerHTML = rows.map(function(task) {
- return '<div class="task-row remove-cols">'
- + '<span class="task-name" title="' + esc(task.Name) + '">' + esc(task.Name) + '</span>'
- + '<span style="color:var(--text-dim);font-size:12.5px">' + esc(task.Creator) + '</span>'
- + '<span class="task-script" title="' + esc(task.ScriptVpath) + '">' + esc(task.ScriptVpath) + '</span>'
- + '<span>' + appBadge(task.AppName) + '</span>'
- + '<span class="task-interval">' + t('js/task/every', 'Every ') + fmtInterval(task.ExecutionInterval) + '</span>'
- + '<span><button class="btn danger sm" onclick="removeTask(' + JSON.stringify(esc(task.Name)) + ')">' + t('js/btn/remove', 'Remove') + '</button></span>'
- + '</div>';
- }).join('');
- });
- }
- function removeTask(name) {
- if (!confirm(t('js/confirm/remove/pre', 'Remove task "') + name + t('js/confirm/remove/post', '"? This cannot be undone.'))) return;
- $.post('../../system/arsm/aecron/remove', { name: name }, function(data) {
- if (data && data.error) { alert(data.error); return; }
- toast(t('js/toast/removed', 'Task removed.'));
- loadRemove();
- });
- }
- /* ── New Task ── */
- function initNewTaskView() {
- $.getJSON('../../system/arsm/aecron/permission', function(data) {
- if (!data || !data.CanCreate) {
- document.getElementById('noPermMessage').style.display = 'block';
- document.getElementById('newTaskForm').style.display = 'none';
- } else {
- document.getElementById('noPermMessage').style.display = 'none';
- document.getElementById('newTaskForm').style.display = 'block';
- }
- });
- }
- function checkExt(val) {
- var ext = (val.split('.').pop() || '').toLowerCase();
- var warn = document.getElementById('extWarn');
- warn.style.display = (ext === 'agi' || ext === 'js' || val === '') ? 'none' : 'block';
- }
- // ao_module_openFileSelector requires a named function — it passes callback.name
- // as a string to the parent window. An anonymous function has name="" and fails.
- function scriptSelected(files) {
- if (!files || !files.length) return;
- var fp = files[0].filepath;
- document.getElementById('scriptpath').value = fp;
- checkExt(fp);
- }
- function openFileSelector() {
- ao_module_openFileSelector(scriptSelected, 'user:/', 'file', false);
- }
- function baseUnix(base) {
- switch (base) {
- case 'now': return moment().startOf('minute').unix();
- case 'hour': return moment().startOf('hour').unix();
- case 'day': return moment().startOf('day').unix();
- case 'month': return moment().startOf('month').unix();
- case 'year': return moment().startOf('year').unix();
- case 'mon': return moment().startOf('week').add(1,'days').unix();
- case 'tue': return moment().startOf('week').add(2,'days').unix();
- case 'wed': return moment().startOf('week').add(3,'days').unix();
- case 'thu': return moment().startOf('week').add(4,'days').unix();
- case 'fri': return moment().startOf('week').add(5,'days').unix();
- case 'sat': return moment().startOf('week').add(6,'days').unix();
- case 'sun': return moment().startOf('week').unix();
- default: return moment().startOf('minute').unix();
- }
- }
- function submitNewTask() {
- var name = $('#taskname').val().trim();
- var desc = $('#desc').val().trim();
- var scriptPath = $('#scriptpath').val().trim();
- var interval = parseFloat($('#intervalvalue').val()) * parseFloat($('#intervalunit').val());
- var base = baseUnix($('#intervalbase').val());
- if (!name) { alert(t('js/alert/noname', 'Task name is required.')); return; }
- if (!scriptPath) { alert(t('js/alert/nopath', 'Script path is required.')); return; }
- if (!interval || interval < 60) { alert(t('js/alert/interval', 'Interval must be at least 1 minute.')); return; }
- $.post('../../system/arsm/aecron/add', {
- name: name, desc: desc, path: scriptPath,
- interval: interval, base: base
- }, function(data) {
- if (data && data.error) { alert(data.error); return; }
- $('#taskname').val('');
- $('#desc').val('');
- $('#scriptpath').val('');
- toast(t('js/toast/created', 'Task created!'));
- showView('mine');
- });
- }
- /* ── Init ── */
- applocale.init("../locale/scheduler.json", function() {
- applocale.translate();
- loadMine();
- });
- </script>
- </body>
- </html>
|