Files
echo-core/dashboard/handlers/cron.py

96 lines
3.2 KiB
Python

"""/api/cron — reads echo-core/cron/jobs.json (flat schema)."""
import json
from datetime import datetime
import constants
def _parse_cron_time(expr):
"""Extract a display-time string from a cron expression.
Echo-core cron strings are already Bucharest local time (Lane B
scheduler sets tz=Europe/Bucharest), so NO UTC→local conversion.
"""
parts = expr.split()
if len(parts) < 2:
return expr[:15]
minute, hour = parts[0], parts[1]
if minute.isdigit() and (hour.isdigit() or '-' in hour):
if '-' in hour:
hour = hour.split('-')[0]
try:
return f"{int(hour):02d}:{int(minute):02d}"
except ValueError:
return expr[:15]
return expr[:15]
def _iso_to_epoch_ms(iso_str):
"""Convert an ISO 8601 datetime string to epoch ms. Returns 0 on failure."""
if not iso_str:
return 0
try:
dt = datetime.fromisoformat(iso_str.replace('Z', '+00:00'))
return int(dt.timestamp() * 1000)
except (ValueError, TypeError):
return 0
class CronHandlers:
"""Mixin for /api/cron."""
def handle_cron_status(self):
"""Get enabled cron jobs from echo-core/cron/jobs.json (flat schema).
Output shape preserved for the frontend: id, name, time, schedule,
ranToday, lastStatus, lastRunAtMs, nextRunAtMs.
"""
try:
jobs_file = constants.BASE_DIR / 'cron' / 'jobs.json'
if not jobs_file.exists():
self.send_json({'jobs': [], 'error': 'No jobs file found'})
return
all_jobs = json.loads(jobs_file.read_text())
if not isinstance(all_jobs, list):
self.send_json({'jobs': [], 'error': 'Unexpected jobs.json shape'})
return
today_start = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
today_start_ms = today_start.timestamp() * 1000
jobs = []
for job in all_jobs:
if not job.get('enabled', False):
continue
name = job.get('name', '')
expr = job.get('cron', '')
last_run_iso = job.get('last_run')
next_run_iso = job.get('next_run')
last_status = job.get('last_status', 'unknown')
last_run_ms = _iso_to_epoch_ms(last_run_iso)
next_run_ms = _iso_to_epoch_ms(next_run_iso) or None
ran_today = last_run_ms >= today_start_ms
jobs.append({
'id': name, # echo-core has no separate id; use name
'name': name,
'time': _parse_cron_time(expr),
'schedule': expr,
'ranToday': ran_today,
'lastStatus': last_status if ran_today else None,
'lastRunAtMs': last_run_ms,
'nextRunAtMs': next_run_ms,
})
jobs.sort(key=lambda j: j['time'])
self.send_json({
'jobs': jobs,
'total': len(jobs),
'ranToday': sum(1 for j in jobs if j['ranToday']),
})
except Exception as e:
self.send_json({'error': str(e)}, 500)