The first stable release. From here a breaking change to the documented HTTP API or to the INI and label configuration takes a major bump, and the "pre-1.0" allowance two of the source-level changes below were taken under no longer applies.
Upgrading needs no configuration change. slack-webhook, poll-interval and no-poll were scheduled for removal in this release and still work; that window now runs to v2.0.0. poll-interval and no-poll migrate on their own, slack-webhook does not — a [webhook "name"] section with preset = slack replaces it when you get to it.
The dashboard, rebuilt
@KamilPesek rebuilt the web UI and most of the API underneath it: a /api/dashboard aggregate endpoint so a polling client spends one request per tick instead of five, response compression negotiated over zstd and gzip, ETagged embedded assets, and a recentRuns summary on job payloads that feeds a per-job sparkline. The page is assembled from html/template partials rather than one string, OFELIA_UI_DEV_DIR serves it from disk while you work on it, and it renders when the browser blocks site data.
Around that: stat cards for active and failing jobs and the next run, search and sorting on the job table, last/avg/max in the duration cell, history in a modal, run output in a full-width subrow, and origin badges that say when a job belongs to the INI file or to Docker labels — with the delete button hidden rather than offered and then refused.
Also from @KamilPesek: sortable headers operable from the keyboard, and the orchestrator probes exempted from rate limiting so /live and /ready never answer 429.
The API stopped editing what it does not own
POST /api/jobs/create and /api/jobs/update now answer 403 Forbidden for a name owned by the INI file or by Docker labels, mirroring the delete gate. Both previously succeeded, which is the breaking change most likely to be noticed: a script that edited config-owned jobs through the API now fails instead of silently diverging from the file it should have edited.
Jobs the API does own survive a restart, and an API-created job of any type now gets the config decoder's struct-tag defaults — most importantly HistoryLimit, which was 0 for exec, compose and local jobs and made them retain every execution for the life of the daemon.
maxRuntime is accepted on API-created run jobs, contributed by @matheusamendola. It inherits [global] max-runtime when omitted, the same ladder an INI [job-run] section climbs.
Fixes
A long log stays where you scrolled it — reported by @radoeka in #808, who noticed the view jumping back to the top on every refresh.
A delete landing inside an update no longer leaves a job in the by-name map without a cron entry: reported as live by the API, never firing again. A failed UI render answers 500 instead of committing a half-written 200. The server-time display keeps its zone offset. Tooltips reach screen readers and dismiss on Escape. The Exec-mode switch reports its own state.
Unauthenticated /api/* requests are counted by the rate limiter. It sat inside the auth middleware, so a 401 never reached it — token guessing was the one kind of traffic it never saw, while the same address was metered on every static asset it fetched.
Documentation
The Docker startup retry from #523 and the ofelia hash-password command were both reachable only by reading the source; the docs now describe them, and no longer suggest generating a bcrypt hash with a Python one-liner that puts the password in your shell history.
Full Changelog: v0.30.0...v1.0.0