class CronModule { constructor() { this.commands = {}; this.terminal = null; this.jobs = {}; this.counter = 0; this.MIN_INTERVAL = 5; this.STORAGE_KEY = 'webx11_cron_jobs'; this._restoreJobs(); } requirements() { return { met: true, missing: [] }; } getHelpInfo() { return { 'cron-every': 'cron-every — Schedule a command to run every N seconds', 'cron-list': 'cron-list — List all active cron jobs', 'cron-stop': 'cron-stop — Stop a specific cron job', 'cron-clear': 'cron-clear — Stop all cron jobs' }; } registerCommands() { this.commands['cron-every'] = (args) => this.every(args); this.commands['cron-list'] = () => this.list(); this.commands['cron-stop'] = (args) => this.stop(args); this.commands['cron-clear'] = () => this.clear(); } _restoreJobs() { try { const saved = localStorage.getItem(this.STORAGE_KEY); if (!saved) return; const data = JSON.parse(saved); if (!Array.isArray(data) || data.length === 0) return; let maxId = 0; data.forEach(j => { if (j.id > maxId) maxId = j.id; if (this.counter < j.id) this.counter = j.id; this.jobs[j.id] = { cmd: j.cmd, interval: j.interval, runs: j.runs || 0, timer: setInterval(() => { if (!this.jobs[j.id]) return; this.jobs[j.id].runs++; this.terminal.print(`[cron #${j.id}] Running: ${j.cmd}`, 'info'); this.terminal.executeCommand(j.cmd); }, j.interval) }; }); this.counter = maxId; if (data.length > 0) this.terminal.print(`↻ Restored ${data.length} cron job(s) from storage`, 'info'); } catch (e) { /* silently ignore corrupt data */ } } _saveJobs() { try { const data = Object.entries(this.jobs).map(([id, j]) => ({ id: parseInt(id), cmd: j.cmd, interval: j.interval, runs: j.runs })); localStorage.setItem(this.STORAGE_KEY, JSON.stringify(data)); } catch (e) { /* quota exceeded */ } } every(args) { if (args.length < 2) return this.terminal.print('Usage: cron-every ', 'warning'); const secs = parseInt(args[0]); if (isNaN(secs) || secs < this.MIN_INTERVAL) return this.terminal.print(`Interval must be >= ${this.MIN_INTERVAL} seconds`, 'warning'); const cmd = args.slice(1).join(' '); this.counter++; const id = this.counter; this.jobs[id] = { cmd, interval: secs * 1000, runs: 0, timer: setInterval(() => { this.jobs[id].runs++; this.terminal.print(`[cron #${id}] Running: ${cmd}`, 'info'); this.terminal.executeCommand(cmd); }, secs * 1000) }; this._saveJobs(); this.terminal.print(`Cron job #${id} started: "${cmd}" every ${secs}s`, 'success'); } list() { const ids = Object.keys(this.jobs); if (ids.length === 0) return this.terminal.print('No active cron jobs', 'info'); this.terminal.print('=== Cron Jobs ===', 'info'); ids.forEach(id => { const j = this.jobs[id]; this.terminal.print(` #${id}: "${j.cmd}" every ${j.interval / 1000}s (${j.runs} runs)`); }); } stop(args) { if (!args[0]) return this.terminal.print('Usage: cron-stop ', 'warning'); const id = parseInt(args[0]); if (!this.jobs[id]) return this.terminal.print(`Cron job #${id} not found`, 'error'); clearInterval(this.jobs[id].timer); delete this.jobs[id]; this._saveJobs(); this.terminal.print(`Cron job #${id} stopped`, 'success'); } clear() { Object.keys(this.jobs).forEach(id => { clearInterval(this.jobs[id].timer); }); this.jobs = {}; localStorage.removeItem(this.STORAGE_KEY); this.terminal.print('All cron jobs stopped', 'success'); } } if (typeof window.webx11 !== 'undefined' && window.webx11) { webx11.moduleManager.registerModule(new CronModule(), 'https://webx11.duckdns.org/spaces/newos4/modules/ext/cron.js'); }