fix: Fixed schedule workflow col
Chart Release / chart (push) Successful in 20s
Server Deploy / deploy (push) Successful in 41s

This commit is contained in:
2026-08-04 14:42:10 +01:00
parent b21ac05547
commit c2635ed51a
2 changed files with 39 additions and 4 deletions
+32
View File
@@ -124,6 +124,38 @@ A library of reusable **steps** (bash or PowerShell scripts with declared inputs
Default steps are seeded per org at boot (`SeedDefaultSteps`) from `VANTAGE_DEFAULT_STEPS_DIR`, which `server/Dockerfile` bakes to `/opt/default-steps` from the repo's `default_steps/`. Deliberately **not** under `/data` — that is a bind mount, so the library would be editable from the host. Adding a step there means committing a file and rebuilding, which is why `default_steps/` is in the `server` rebuild trigger. **Steps with `source: "default"` are read-only**: `UpdateStep`/`DeleteStep` refuse with `ErrDefaultStep` (409), because seeding rewrites them on every boot, so an edit would silently revert and a delete would come back. `web/` mirrors this — the step modal opens read-only, Delete is hidden, and the designer's per-step script override is `readOnly` for a default library step — but as elsewhere, the API is the boundary and the UI is the courtesy. Seeding writes straight to the collection rather than through `UpdateStep`, so the guard does not lock out the seeder. Logs are swept by retention (`workflow_log_retention_days`; nil = 30 days, 0 = forever).
### Server tags and workflow targeting
A server carries `tags map[string]string` — lowercase `[a-z0-9_-]`, key ≤32,
value ≤64, 20 per server, `sys:` reserved. **There is no `tags` collection**: a
tag is a property of a server, not an entity, so `KnownTags` aggregates over
`servers` rather than reading a registry that would need reference counting to
know when a tag stopped existing. `PUT /api/servers/:id/tags` replaces the whole
map — last-write-wins over a small map beats merge semantics between two people
editing one server. The index is `{instance_id: 1, "tags.$**": 1}`, wildcard
because the queried key is chosen by the user at request time and cannot be named
in advance; `EnsureServerIndexes` warns rather than being fatal, since a missing
index degrades tag filtering to a scan of a small collection and is no reason to
refuse to serve the fleet list.
`services.ResolveTargets` is the **single** answer to which servers a workflow
touches — the run path and validation both go through it, so the readout and the
dispatch cannot disagree. It is the distinct union of `target_server_ids` and
`target_tags` (AND across keys), ordered by the fleet rather than by the
arguments, so two runs naming the same servers differently are still comparable
line by line. **An empty selector matches nothing** on purpose: "matches
everything" turns a cleared field in the designer into a fleet-wide run. Both
empty is `ErrNoTargets` (400), not a success over zero servers. Offline servers
are **not** filtered out — the dispatcher already answers 503 per server, and a
patch run that silently omits an unreachable machine is worse than one that
visibly fails on it.
`web/app/(app)/workflows/[id]/page.tsx` **duplicates that match logic in
TypeScript** to draw the resolved count without a round trip, since the browser
already holds the fleet. It is a second implementation of `UnionTargets` /
`MatchesTags` and must change in the same commit as the Go one — the same shape
of hazard as the mirrored token blocks.
### Monitors
HTTP, TCP, ICMP and TLS checks. Each monitor has a `runner`: `"server"` (executed by the server-side scheduler) or a `server_id` (pushed to that agent, which runs it locally and reports results). Consecutive failures beyond `retries` flip state to `down`, open an `Incident`, and notify. Hourly `Rollup` documents back the uptime graphs.
+7 -4
View File
@@ -64,7 +64,7 @@ export default function WorkflowsPage() {
<Th>Name</Th>
<Th>Targets</Th>
<Th>Steps</Th>
<Th>Schedule</Th>
<Th>Next Run</Th>
<Th />
</Tr>
</Thead>
@@ -82,9 +82,12 @@ export default function WorkflowsPage() {
<Td label="Steps">
<span className="text-text-secondary">{w.steps.length}</span>
</Td>
<Td label="Schedule">
{w.schedule?.enabled && <span className="rounded-sm border border-border px-1.5 py-0.5 font-mono text-[10px] text-text-secondary">{w.schedule.cron}</span>}
{w.next_run_at && <span className="font-mono text-[11px] text-text-tertiary">next {new Date(w.next_run_at).toLocaleString()}</span>}
<Td label="Next Run">
{w.next_run_at && w.schedule?.enabled ? (
<span className="font-mono text-[11px] text-text-tertiary">{new Date(w.next_run_at).toLocaleString()}</span>
) : (
<span className="font-mono text-[11px] text-text-tertiary">No Schedule Set</span>
)}
</Td>
<Td>
<div className="flex items-center justify-end gap-2">