diff --git a/assets/cron-scheduler-heatmap.png b/assets/cron-scheduler-heatmap.png new file mode 100644 index 0000000..b18f292 Binary files /dev/null and b/assets/cron-scheduler-heatmap.png differ diff --git a/assets/cron-scheduler-no-fixed-time.png b/assets/cron-scheduler-no-fixed-time.png new file mode 100644 index 0000000..653e719 Binary files /dev/null and b/assets/cron-scheduler-no-fixed-time.png differ diff --git a/docs/cron-scheduler.md b/docs/cron-scheduler.md index 9d7e27c..e75ea88 100644 --- a/docs/cron-scheduler.md +++ b/docs/cron-scheduler.md @@ -7,21 +7,17 @@ A Node-RED tab fetches the inventory straight from NocoDB's REST API at request time — no cache — and renders it as a 7×24 grid (Mon–Sun × 00:00–23:00). Each cell's color is a **computed load weight**, not a raw job count: base `1` per job, plus a duration-bucket weight, plus a resource-tag weight (`disk-intensive`=2, `cpu-intensive`=1.5, `network-intensive`=0.5, summed if a job carries more than one), plus a flat `+1` if the job's category is `backup` — disk I/O contention from an overlapping backup is judged a worse collision than two lightweight jobs sharing an hour, and the weight formula says so explicitly rather than pretending every job costs the same. Weights bucket into 4 color bands (green → yellow → orange → red) relative to the current busiest cell. Jobs whose time can't be resolved to a specific day+hour are excluded from the grid and listed separately rather than forced into a misleading cell. -This is the real table, not a description of it: +This is the real heatmap, not a description of it — 50 known jobs, 15 without a fixed time: -``` -$ curl -s -H "xc-token: [REDACTED]" \ - "https://nocodb.[internal zone]/api/v2/tables/[table-id]/records?limit=100" | jq '.list | length' -50 -$ ... | jq '.list | group_by(.Category) | map({category: .[0].Category, count: length})' -[{"category":"backup","count":6},{"category":"maintenance","count":23}, - {"category":"media","count":11},{"category":"monitoring","count":5}, - {"category":"security","count":5}] -``` +![The real Fleet Schedule Heatmap widget in Dashy, with Tue 00:00 clicked open showing its 8 real jobs — name, host, category, and FIXED/APP UI badges for jobs that can't simply be rescheduled](../assets/cron-scheduler-heatmap.png) > [!TIP] > Clicking a populated cell expands the full job list for that slot — name, host, category, and whether that job's schedule can actually be moved or is fixed by whatever app owns it. That last distinction matters in practice: there's no point suggesting a new job avoid a time slot occupied by something that can't be rescheduled anyway versus one that easily could be. +Jobs with no fixed time get their own list instead of a misleading grid cell: + +![The heatmap's "no fixed time" section expanded — 15 real jobs excluded from the grid, including the manually-triggered-only Kestra flows and the PBS prune job with its mismatched datastore name](../assets/cron-scheduler-no-fixed-time.png) + ## Why a manual inventory instead of an auto-discovered one The harder, more "correct" version of this tool would auto-discover every scheduled job by querying Kestra's API, parsing every host's crontab and systemd timers, and re-syncing continuously. That was scoped down deliberately: auto-discovery across three genuinely different scheduling mechanisms (Kestra's API, systemd's timer units, cron's own format) is real engineering effort for a tool whose entire job is picking a good time slot for the next new job — a task that happens occasionally, not continuously. A manually-maintained table, kept honest by being checked at the one moment it actually matters (adding a new job), was judged the better return on effort than building and maintaining three different scanners for a problem that doesn't need real-time accuracy.