terdut-server v0.10.2 serves a web UI you sign in to with a password,
and every user starts without one. Until now the only way to give
somebody their first password was a curl call with an API key. p in
Users sets the selected user's password.
The form asks for the current password only in the one case the server
checks it: you are changing your own password and already have one. The
client has no other way to know who its key belongs to, so opening the
form calls GET /api/me first and shows the fields once that answers.
Setting someone else's password sends no current_password at all,
rather than an empty one.
Length (at least 10) and the repeated entry are checked before anything
is sent, mirroring the server's rule so a typo costs no round trip. The
server stays authoritative: a wrong current password comes back as its
own 403 message on the dashboard. The status line says the user's other
web sessions were signed out, because the server does that on every
password change. API keys are not affected.
Older servers have no /api/me. The client now returns a typed
StatusError carrying the status code, so a 404 there reads as "needs
terdut-server v0.10.2 or later" rather than a bare "server returned
404". Its Error() text is unchanged, so every existing message reads as
before.
Requires terdut-server v0.10.2 only for this form. Everything else works
against the same servers as before.
Assigning over a day somebody else held did nothing but flash a 409 for
three seconds. The server holds one person per date and refused any that
was taken, all-or-nothing, so pressing W on a week where a single day was
already assigned placed none of the other six either. The only way
through was d on each day first — seven delete-and-confirm cycles to move
one week.
The clash is already on screen, so it is found before the request rather
than read back out of an error: the picker hands off to a confirmation
naming who loses the days and how many there are, and accepting sends the
whole selection with replace, which terdut-server v0.8.0 added. One
question to move a week, and nobody's shift moves without somebody being
asked. A day nobody holds still assigns with no prompt at all.
Reassigning somebody to a day they already hold raises no prompt, since
it takes nothing from anyone, but it does send replace: the server
rejects any date that exists, so without it a harmless no-op would fail.
terdut-server pages the on-call person through ntfy, but none of it was
reachable from here. A user's topic could only be set with curl, so a
new user silently got no pages and quietly fell back to the shared
fallback topic — which carries no Acknowledge button. And nothing said
whether anybody had been paged at all.
The Users section grows an Ntfy Topic column and t to edit it,
prefilled with the current value. Submitting an empty field clears the
topic rather than being rejected as a mistake: clearing is how somebody
is taken off their own topic, and it is what the server means by an
empty string. Nil and empty arrive as the same thing, because the
server stores a blank topic as NULL, so User.Topic flattens the two
instead of leaving every caller to.
The incident timeline renders the server's notified and notify_failed
events. No new fetch — the timeline endpoint already carried them, and
unknown types already fell through to a generic label; this is about
saying something useful. An event with no user means the fallback
topic, not "the server acted", which is the difference between somebody
having been paged and the rota having been empty.
Both need terdut-server v0.6.0 or later, and the timeline entries a
server newer than that. Against an older one the column stays empty and
editing a topic reports the server's 404, which is the honest answer.
terdut-server v0.4.0 splits the alerts row into two objects, and the
endpoints this client drove for acknowledgement, comments and archiving
are gone. Pointing the same screens at the new paths would have missed
the point of the split: alerts are now Alertmanager's record, read-only
and carrying no human state, while the incident is the thing anyone
actually works on.
Incidents lead the section list and are what the client opens on. The
queue shows severity, status, assignee and age, and the detail view adds
what only exists server-side now: the group labels Alertmanager
correlated on, the member alerts, and an append-only timeline where
system events and notes are interleaved. That timeline is the whole
history the server keeps — alert rows are still mutated in place — so
rendering it in order matters more than styling it.
Actions all move onto the incident: a/A acknowledge, s assign, z/Z
snooze, c note, d delete note, x archive, R resolve.
Two of those need care rather than a keybinding:
- R, not r, resolves, and it asks first. The server treats a manual
resolve as terminal: a later occurrence opens a new incident instead
of reopening this one, and an alert that never stops firing leaves
the incident closed for good. A stray keypress is not recoverable,
so the prompt says what it means.
- x refuses on an open incident rather than archiving it, since
archiving unresolved work only hides it. Snooze is offered as the
"not now" answer, and the client treats a snoozed_until in the past
as not snoozed, matching the server, which sweeps nothing.
Statistics lead with MTTA and MTTR, neither of which was computable
before. The server sends null until something has actually been
acknowledged or resolved, and that renders as — rather than 0: no data
is a different claim from instant.
Alerts keep a tab of their own as the raw feed — useful for asking what
Alertmanager is really sending — with an Incident column replacing Ack
By, and i in the detail view jumping to the incident where something can
be done about it. Archived now holds archived incidents; archiving an
alert is server-side housekeeping and no longer a user action.
BREAKING CHANGE: requires terdut-server v0.4.0 or later. Against an
older server every incident request 404s. Use terdut-tui v0.3.x with
servers before v0.4.0.
Add a fourth tab (Alerts | Archived | Schedule | Users).
Archived alerts are fetched lazily on first visit using the
archived=true query param on GET /api/alerts.
Press x from the Alerts list or detail to archive an alert;
the non-archived list refreshes immediately. Press x from the
Archived list or detail to unarchive; the archived list
refreshes. Ack/unack are disabled in the Archived detail view.
New API methods: ArchiveAlert (POST), UnarchiveAlert (DELETE).
ArchivedAt field added to the Alert type.
Add full user management section (tab to Users):
- User list table with username, email, created date
- n: create user (username + email form)
- d: delete user with confirmation (cascades API keys)
- k: API key management for selected user
- n: create new key with name input; one-time reveal
showing key value and integer key ID prominently
- r: revoke by integer ID (no list endpoint on server)
- c: copy revealed key to clipboard (atotto/clipboard)
API key listing is unavailable server-side, so the reveal
screen displays the key ID prominently for future revocation.
Adds a 14-day schedule list accessible via the Schedule tab.
Keybindings in schedule section:
←/→ (or h/l) shift the 2-week window back/forward by one week
j/k navigate rows
+ open user picker — select a user to assign to the date
d delete the selected day's assignment (y/N confirmation)
r refresh schedule from server
The user picker fetches the user list from the server on first open
(cached for the session). Selecting a user assigns them to the
highlighted date (POST /api/schedule all-or-nothing; 409 conflicts
surface as a status bar error).
The on-call header shows today's scheduled person and the current
window date range. On-call data refreshes on schedule actions.
API additions: GetSchedule, GetCurrentOnCall, AssignSchedule,
DeleteScheduleEntry, ListUsers. ScheduleEntry and User types added.
Press enter on any alert to open a full-screen detail pane (viewport).
Keybindings in detail mode:
a/A acknowledge / unacknowledge (updates immediately)
c compose a comment (text input at bottom)
[/] cycle comment cursor up/down
d delete selected comment (y/N confirmation)
s assign placeholder — shows "not yet supported by server"
S open stats view: top alerts + by-hour/by-day ASCII bar charts
esc back to dashboard
API additions: GetAlert, AcknowledgeAlert, UnacknowledgeAlert,
GetComments, AddComment, DeleteComment, GetTopAlerts,
GetStatsByHour, GetStatsByDay.
Adds a bubbles/table alert list showing Name, Status, Started (relative
time), and Ack By columns. Stats bar shows total/firing/resolved counts.
Filter cycles firing → resolved → all with [f]. Auto-refresh fires on
the configured interval via tea.Tick. Table scrolls with j/k.
Sets up the full project structure following the hactl/gokapi-tui
architecture: strict Elm-pattern Bubbletea TUI, YAML config at
~/.config/terdut-tui/config.yaml, REST API client with Bearer auth,
self-update via GitHub Releases, and a three-section tab placeholder
(Alerts / Schedule / Users) that verifies server connectivity on startup.