You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs: document the dashboard API, compression and the UI layout
- docs/API.md: /api/dashboard, the recentRuns field, the 403 on updating
a config-owned job
- docs/packages/web.md: what the rate limiter counts and what it exempts,
and that compression negotiates zstd or gzip
- docs/CONFIGURATION.md: OFELIA_UI_DEV_DIR
- web/AGENTS.md: the UI is templates plus assets now, the polling and
rate-limit budget, the CSP notes for external app.js
- AGENTS.md: go:embed walks a matched directory recursively, so ui/*
covers ui/templates/ — the previous note claimed the opposite
- CHANGELOG: the Unreleased entries for all of the above, with the
breaking API and interface changes called out under Changed
Signed-off-by: Kamil Pešek <pesek.kamil@seznam.cz>
Copy file name to clipboardExpand all lines: AGENTS.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -23,7 +23,7 @@ This file explains repo‑wide conventions and where to find scoped rules.
23
23
- Requires a running Docker daemon (start it with `sudo systemctl start docker` on systemd hosts, or `sudo service docker start` otherwise)
24
24
- Run the daemon with the bundled demo config: `go run . daemon --config example/ofelia.ini` — serves the web UI on `web-address` (default `:8081`, which binds all interfaces). The example config does not override it, so reach the UI at `http://127.0.0.1:8081/`; pass `--web-address 127.0.0.1:8081` to force loopback-only binding
25
25
-`example/ofelia.ini` ships working demo jobs (`run-date` runs `date` in alpine every 30s; `local-echo` runs on the host every 45s); the swarm and compose examples are commented out as they need extra infrastructure
26
-
- Web UI assets live in `static/ui/` and are embedded via `//go:embed ui/*` in `static/static.go`; new files added directly under `static/ui/` are picked up automatically (no registration needed). The `ui/*` pattern is not recursive, so a new nested subdirectory needs its own embed pattern (e.g. `ui/sub/*`)
26
+
- Web UI assets live in `static/ui/`(`styles.css`, `app.js`, `templates/`) and are embedded via `//go:embed ui/*` in `static/static.go`; new files added under `static/ui/` are picked up automatically (no registration needed). When a pattern matches a directory, `go:embed` walks it recursively, so `ui/*` also covers `ui/templates/` — files whose name starts with `.` or `_` are the exception and stay out
27
27
- After touching embedded assets, run `go build ./...` to confirm the embed still resolves
28
28
- Web package tests: `go test ./web/... -v -count=1` (`-count=1` bypasses the test cache)
`CopyContainerLogs`, needed to demux container output server-side.
77
+
Downstream Go code implementing the exported interface fails to
78
+
compile until it adds the method; permitted under
79
+
[SemVer §4](https://semver.org/#spec-item-4) for the current 0.y.z
80
+
line. Users of the provided implementations are unaffected.
81
+
-**BREAKING (source-only, pre-1.0):**`Scheduler.UpdateJob` now updates
82
+
a disabled job instead of returning `ErrJobNotFound`, so editing a
83
+
paused job no longer resumes it. Callers that relied on the error to
84
+
detect "not scheduled" must check the disabled state explicitly.
85
+
-**Only `/live` and `/ready` bypass the rate limiter.** A probe answered
86
+
429 reads as unhealthy and gets the daemon restarted, and both probes
87
+
are cheap. `/health` and `/healthz` stay token-free but counted:
88
+
`GetHealth` calls `runtime.ReadMemStats` on every request, which stops
89
+
the world, and answers with the version and goroutine count — an
90
+
exemption there would leave an unauthenticated, unthrottled endpoint
91
+
that pauses the GC per call. Every other request is counted too, static
92
+
assets included: each asset response is compressed per request, which
93
+
is exactly the work an unauthenticated flood would target. The UI stays
94
+
inside the budget by polling one aggregate endpoint per tick rather
95
+
than by being exempted. `/api/login` keeps its own stricter login
96
+
limiter.
97
+
-**A hidden browser tab stops polling** (Page Visibility API) and
98
+
refreshes immediately when it becomes visible again — n open dashboard
99
+
tabs cost one tab's request budget.
100
+
-**Short pages pin the footer to the bottom edge** (min-height 100dvh
101
+
flex column).
102
+
-**The web UI is assembled from templates and separate assets.** The former
103
+
single-file `static/ui/index.html` is split into `styles.css`, `app.js`, and
104
+
Go `html/template` partials (`templates/layout.html` plus one file per tab),
105
+
rendered server-side at `GET /`. No behavior or dependency change; still
106
+
vanilla CSS/JS with no build step.
107
+
-**Job history opens in a modal dialog** instead of a panel under the jobs
108
+
table. Close via the header button, Esc, or a backdrop click. The dialog is
109
+
anchored to the top of the viewport so the 5s refresh does not make it jump,
110
+
and its padding follows the compact/comfortable density setting.
111
+
-**Run output renders in a full-width subrow** of the history table instead
112
+
of inside the Output column. Expanding output no longer changes column
113
+
widths; long output wraps and scrolls in its own box; the run's row is
114
+
highlighted while open; expanded state still survives the 5s refresh, keyed
115
+
by execution timestamp and scoped to the shown job.
116
+
-**The tab bar moved into the sticky nav**, left-aligned next to the brand;
117
+
the footer spans the full page width. Both bars share the same horizontal
118
+
padding via the `--layout-pad-x` CSS variable, and repeated separator
119
+
borders use `--border-thin`. The nav dropped its `<ul><li>` wrappers —
120
+
single-item lists carried no semantics, and Pico's `nav li` padding
121
+
ignored the density setting.
122
+
-**Brand primary is teal `#2f99a4`** (was Pico blue), set via the
123
+
`--pico-primary*` token family for light, dark, and auto theme modes.
124
+
-**The dark theme background is neutral graphite** (`#181b1e`, cards
125
+
`#22262a`) instead of Pico's blue-tinted default, so the teal primary is
126
+
the only cool hue on screen. Form inputs and dropdowns follow the same
127
+
graphite family (`--pico-form-element-*` tokens).
128
+
-**Job-row action buttons are soft teal chips with SVG icons.** The emoji
129
+
glyphs (▶ ✎ ⏸ 🗑) became uniform inline stroke SVGs on a 24px grid,
130
+
colored via `currentColor`; delete is red-tinted at rest. Icon colors
131
+
come from `--action-fg`/`--action-del-fg` with per-theme shades.
132
+
-**Deleting a job asks for confirmation**, and API failures are no longer
133
+
silent: a reusable bottom-right toast (`toast.success/error/info`) shows
134
+
the server's message — notably the 403 explaining that INI-owned jobs
135
+
must be deleted in the config file. Run, pause, resume, and delete show
136
+
success toasts.
137
+
-**Job rows signal their clickability**: pointer cursor, hover tint, and
138
+
the job name is a link-styled button, so keyboard users can Tab to it
139
+
and open the history with Enter.
140
+
-**Tables are striped.** Pico's `.striped` variant on all four tables,
141
+
with the stripe color raised to 6% of the contrast color (Pico's ~4%
142
+
alpha was invisible on the graphite dark background). The history table
143
+
stripes in pure CSS by run/subrow pairs — the output subrow is always
144
+
rendered, shares its parent run's background, aligns with the Date
145
+
column, and gets density-scaled padding when open. The status-dot
146
+
column has a fixed narrow width (`--dot-col`) so rows stay aligned.
147
+
-**The rendered page and stylesheet pass the W3C Nu validator** (checked
148
+
locally via the `ghcr.io/validator/validator` Docker image).
149
+
10
150
## [0.29.1] - 2026-08-12
11
151
12
152
A security release. Jobs defined through Docker container labels could carry privilege-bearing keys the label-security policy did not cover, letting an untrusted self-labeling container escalate against the host or read another container's secrets — in the default configuration. See [GHSA-h7m7-v83x-vfp3](https://github.com/netresearch/ofelia/security/advisories/GHSA-h7m7-v83x-vfp3).
| `OFELIA_STATE_FILE` | JSON file persisting API-mutated jobs and disable flags across restarts (#593) | (none, disabled) |
238
+
| `OFELIA_UI_DEV_DIR` | Serve the web UI from this directory instead of the embedded assets, re-parsing templates on every request. Development only; leave unset in production | (none, embedded assets) |
238
239
239
240
**Limitations of Labels-Only Configuration**:
240
241
- No environment variable substitution in label values (`${VAR}` won't expand)
0 commit comments