Put a team's own settings in the web UI #18

Merged
niklas merged 1 commits from team-settings-ui into main 2026-09-20 19:26:22 +00:00
Owner

Closes #17. Everything a team owner configures was API-only — escalation, integrations, dead-man switches, membership, and the rota, which the on-call view still described as the TUI's job. Setting up the feature this whole line of work exists for meant using curl.

The Team tab

One team at a time, with a picker for somebody in more than one:

  • On-call rota — assign somebody for N days from a date, with a "take days somebody else holds" checkbox that maps to the API's replace, because silently moving a shift moves who gets paged without telling either person.
  • Escalation ladder — levels with a wait and targets ("whoever is on call" or a named member), add/remove, repeat count, fallback topic.
  • Alert sources — list, create, revoke.
  • Dead man's switches — matchers, timeout, severity, with the fiddly matcher syntax spelled out next to the field.
  • Members — roles, promote/demote, add, remove.

An owner edits; a member sees the same page without the controls, because the server refuses their writes anyway. Hiding a button is a courtesy to the reader, not the thing enforcing anything.

Two details worth the space

The escalation editor holds a draft and sends the whole ladder, because the API replaces it wholesale — the levels are an order, and patching one rung leaves the numbering of the others undecided. A new level defaults to five minutes and the rota, which is the shape almost every ladder starts as.

An integration key is returned exactly once, so creating one opens a panel that says so, shows the URL large with a copy button, and renders the Alertmanager receiver snippet with the URL already in it — the next thing anybody does with that key is paste it into a config. The panel stays until dismissed rather than vanishing on the next re-render.

The incident view now says what happens next

escalation_level and escalation_due_at are on the incident payload, so the detail view shows Escalating · level 2 · next in 4m. The due time is computed in the incident SELECT by joining the level's timeout, so a list costs no extra queries.

That was in #6's scope and deferred with the rest of the UI.

Verified

make fmt lint test helm-lint green with -race against Postgres 17, every module parses, and a script checks that every import in the UI resolves to a real export — the failure mode for a no-build ES-module frontend is a blank page at runtime, not a compile error.

Then against a live server, making every call the page makes including the writes: the six reads the tab issues, a two-level ladder saved and read back, an integration created with its key returned once, three days of rota assigned, switches set, and an incident showing level 1 with a due time five minutes out.

Not in here

No screenshots — I can't drive a browser in this environment, so "it renders correctly" is the one claim I can't make. The data and the imports are verified; the layout is not.

https://claude.ai/code/session_01RHPj4ggeFdEjKKfm4SHbD7

Closes #17. Everything a team owner configures was API-only — escalation, integrations, dead-man switches, membership, and the rota, which the on-call view still described as the TUI's job. Setting up the feature this whole line of work exists for meant using `curl`. ### The Team tab One team at a time, with a picker for somebody in more than one: - **On-call rota** — assign somebody for N days from a date, with a "take days somebody else holds" checkbox that maps to the API's `replace`, because silently moving a shift moves who gets paged without telling either person. - **Escalation ladder** — levels with a wait and targets ("whoever is on call" or a named member), add/remove, repeat count, fallback topic. - **Alert sources** — list, create, revoke. - **Dead man's switches** — matchers, timeout, severity, with the fiddly matcher syntax spelled out next to the field. - **Members** — roles, promote/demote, add, remove. An owner edits; a member sees the same page without the controls, because the server refuses their writes anyway. Hiding a button is a courtesy to the reader, not the thing enforcing anything. ### Two details worth the space **The escalation editor holds a draft and sends the whole ladder**, because the API replaces it wholesale — the levels are an order, and patching one rung leaves the numbering of the others undecided. A new level defaults to five minutes and the rota, which is the shape almost every ladder starts as. **An integration key is returned exactly once**, so creating one opens a panel that says so, shows the URL large with a copy button, and renders the Alertmanager receiver snippet with the URL already in it — the next thing anybody does with that key is paste it into a config. The panel stays until dismissed rather than vanishing on the next re-render. ### The incident view now says what happens next `escalation_level` and `escalation_due_at` are on the incident payload, so the detail view shows `Escalating · level 2 · next in 4m`. The due time is computed in the incident SELECT by joining the level's timeout, so a list costs no extra queries. That was in #6's scope and deferred with the rest of the UI. ### Verified `make fmt lint test helm-lint` green with `-race` against Postgres 17, every module parses, and a script checks that every `import` in the UI resolves to a real export — the failure mode for a no-build ES-module frontend is a blank page at runtime, not a compile error. Then against a live server, making every call the page makes **including the writes**: the six reads the tab issues, a two-level ladder saved and read back, an integration created with its key returned once, three days of rota assigned, switches set, and an incident showing level 1 with a due time five minutes out. ### Not in here No screenshots — I can't drive a browser in this environment, so "it renders correctly" is the one claim I can't make. The data and the imports are verified; the layout is not. https://claude.ai/code/session_01RHPj4ggeFdEjKKfm4SHbD7
niklas added 1 commit 2026-09-20 19:22:42 +00:00
Put a team's own settings in the web UI
CI / chart (pull_request) Successful in 1s
CI / security (pull_request) Successful in 13s
CI / test (pull_request) Successful in 2m19s
d728af53b1
Closes #17. Everything a team owner configures was API-only: escalation,
integrations, dead man's switches, membership, and the rota -- which the
on-call view still described as the TUI's job, and the TUI has been broken
against this server since teams landed. Setting up the feature this whole
line of work exists for meant using curl.

A Team tab now holds all of it, one team at a time, with a picker for
somebody in more than one. An owner edits; a member sees the same page
without the controls, because the server refuses their writes anyway --
hiding a button is a courtesy to the reader, not the thing enforcing
anything.

The escalation editor holds a draft and sends the whole ladder, because
the API replaces it wholesale: the levels are an order, and patching one
rung leaves the numbering of the others undecided. Adding a level
defaults to five minutes and the rota, which is the shape almost every
ladder starts as.

An integration key is returned exactly once, so creating one opens a
panel that says so, shows the URL large with a copy button, and renders
the Alertmanager receiver snippet with the URL already in it -- the next
thing anybody does with that key is paste it into a config. The panel
stays until it is dismissed rather than disappearing on the next
re-render.

The incident view gains where an incident is on the ladder and when the
next page is due, which is the question somebody looking at an
unacknowledged incident actually has. The API carries it: the incident
payload now includes escalation_level and escalation_due_at, the latter
computed in the incident SELECT by joining the level's timeout, so a list
costs no extra queries.

Verified against a live server by making every call the page makes,
including the writes: the six reads the Team tab issues, a two-level
ladder saved and read back, an integration created and its key returned
once, three days of rota assigned, switches set, and an incident showing
level 1 with a due time five minutes out.

Claude-Session: https://claude.ai/code/session_01RHPj4ggeFdEjKKfm4SHbD7
niklas merged commit fb927aa67b into main 2026-09-20 19:26:22 +00:00
Sign in to join this conversation.