Files
Niklas Ye 024dc095a5
CI / test (push) Successful in 15s
Release / test (push) Successful in 4s
Release / binaries (push) Successful in 28s
Show a colour-coded team picker instead of cycling with T
T used to silently cycle Model.activeTeamID through the caller's teams with
no visible list of choices. It now opens a full picker (modelled on the
existing user picker) listing every team plus "All teams", each with a
stable identity colour from a new six-colour theme palette. The same colour
now also shows as a bullet next to "team: <name>" in the header, so the
active team stays visible without opening the picker.

Adds an Identity palette to the theme package (six hues, skipping the ones
that already mean firing/critical), Styles.TeamColor to pick one by team id,
and identity_1..6 as theme-file tokens alongside the existing twelve so a
fully custom theme can still set every token.
2026-09-27 18:14:45 +02:00

9.5 KiB
Raw Permalink Blame History

terdut-tui

A terminal user interface for terdut-server. Communicates with the server over its REST API.

Written in Go using Bubbletea.

Features

  • Incident queue — open incidents with severity, status, assignee and age, auto-refreshing
  • Incident actions — acknowledge, assign, snooze, note, resolve and archive
  • Timeline — the full history of an incident, system events, pages and notes together
  • Alert feed — the raw read-only alerts underneath, each linked to its incident
  • Teams — switch between your teams, or see all of them at once
  • On-call schedule — visual calendar of who is on duty in a team, assign and remove entries
  • Statistics — MTTA and MTTR, plus alert frequency by name, hour and day
  • User management — add and remove users, manage API keys, set each user's ntfy topic

Requires terdut-server v0.20.0 or later. The server became team-scoped in v0.12 and this client follows it; earlier servers answer 404 for /api/teams and the TUI says so on start. Use terdut-tui v0.9.x with servers before v0.12. Escalation ladders, invites, integrations and the admin settings stay in the server's web UI.

Teams

Everything the server returns is scoped to the teams your key's user belongs to. The header shows which are on screen, and T steps through all → each of your teams in turn. With several teams showing, incident and alert rows carry a Team column.

The schedule is one team's rota, so the Schedule section shows the active team, or with all showing the first team you own. Only a team's owners, and administrators, can change its rota; anyone else gets the reason in the status bar instead of a picker. The picker offers only that team's members, because the server refuses anybody else. Stats always cover all your teams; the server cannot narrow them to one.

Administrators are the only users who can create or delete users, or act on someone else's password, topic or API keys. Everyone can manage their own.

Alerts and incidents

The server keeps two objects and this client follows that split:

  • An alert is Alertmanager's record — firing or resolved, and read-only here.
  • An incident is the work item. It is what you acknowledge, assign, snooze, discuss and resolve, and it is where all the actions live.

Incidents are correlated by the groupKey Alertmanager already computed from your group_by configuration, so several alerts commonly share one incident.

Two behaviours worth knowing before you press a key:

  • Resolving is final. The server treats a manual resolve as terminal: a later occurrence opens a new incident rather than reopening this one, and if the alert underneath never stops firing the incident stays closed. The TUI asks for confirmation before doing it.
  • Snooze is the "not now" button. It hides an incident from the default queue without closing it, and expires on its own.

Push notifications

When the server is configured for ntfy, an incident that opens pages whoever is on call. Each user has their own topic, shown as a column in the Users section and edited with t. A user with no topic falls back to the server's shared fallback topic, which carries no Acknowledge button — the topic is shared, so a button on it would let any subscriber acknowledge as somebody else.

Every delivery lands on the incident's timeline: Notified <user> (triggered) when ntfy accepted the page, and Notification to <user> failed when it ran out of retries. That second one is the one to look for when nobody's phone rang.

Installation

Download the latest release binary for your platform from the releases page, or build from source:

go install git.ryuvia.com/niklas/terdut-tui@latest

Configuration

Create ~/.config/terdut-tui/config.yaml:

server_url: https://terdut.example.com
username: niklas      # optional, prefills the sign-in form
refresh_interval: 30  # seconds, optional
theme: gruvbox-dark   # optional, this is the default
team: Ops             # optional, a team name or id to start on; default is all

Signing in

The TUI signs in the way the web UI does: with a user account's username and password, on a form shown at start. It keeps the server's session, not the password, in ~/.config/terdut-tui/session.json (readable by you only), so the next start resumes it. The server's sessions last 30 days and slide with use. When it has ended, or the account is disabled or the session is ended from the web UI, the TUI returns to the form and says so. L signs out, which also ends the session on the server and deletes the saved one.

The account needs a password, since that is what signing in uses. A user created only for API access has none and cannot sign in: the server answers it exactly like a wrong password. Set one in the web UI, or have an administrator press p on that user in Users. Too many failed attempts are rate limited by the server for a few minutes.

Upgrading from v0.10.0 and earlier: api_key in config.yaml is no longer used. Remove it and sign in. API keys still exist on the server, and k in Users still manages them, for whatever else uses them.

Themes

Two themes ship with the client: gruvbox-dark (the default) and gruvbox-light. Both colour foregrounds only — the terminal supplies the background — so pick the one that matches the background you already run.

To make your own, drop a file in ~/.config/terdut-tui/themes/ and name it in theme:. extends inherits a built-in, so a file only has to list what it changes:

# ~/.config/terdut-tui/themes/mine.yaml
extends: gruvbox-dark
primary: "#d3869b"
accent: "#fabd2f"

A file may shadow a built-in name — themes/gruvbox-dark.yaml is how you tweak the default without renaming it.

Without extends, every token must be set. The eighteen are:

Token Where it shows
primary header, active tab, selected row, cursors
on_primary text drawn on primary — the active tab and selected row
text incident titles and other emphasis
muted secondary text, dividers, footer, table headers
accent status line, acknowledged incidents, the by-day chart
firing firing alerts, triggered incidents, the by-hour chart
resolved resolved alerts and incidents, the top-alerts chart
error error banners
sev_critical, sev_error, sev_warning, sev_info the severity label
identity_1 .. identity_6 a team's colour in the header and the T picker — carries no meaning of its own, so pick six colours that just read as distinct from one another (and from firing/sev_critical, which already mean something)

Values are hex (#83a598 or #abc) or an ANSI palette index (0–255) if you would rather follow your terminal's own colours. Colours are downsampled automatically on 256- and 16-colour terminals, and NO_COLOR is honoured.

Usage

terdut-tui                 start the TUI
terdut-tui --version       print version
terdut-tui --self-update   update to the latest release

Keybindings

Global:

Key Action
j / ↓ Move down
k / ↑ Move up
tab / shift+tab Next / previous section
enter Open detail
esc Go back
r Refresh
f Cycle filter
L Sign out
T Switch team: all → each of your teams (when you have more than one)
q Quit

The sections, in tab order: Incidents · Alerts · Stats · Archived · Schedule · Users.

Incidents section:

Key Action
f Cycle: open → triggered → acknowledged → resolved → snoozed
x Archive (resolved incidents only)

Incident detail:

Key Action
a / A Acknowledge / clear acknowledgement
R Resolve — asks to confirm, and is final
s Assign to a user
z / Z Snooze for a duration / un-snooze
c Add a note
[ / ] Select a note
d Delete the selected note (your own only)
x Archive / un-archive

Alerts section (read-only):

Key Action
f Cycle: firing → resolved → all → archived
i In detail: jump to the alert's incident

Stats section:

Key Action
j / k, pgup / pgdn Scroll

Schedule section:

Key Action
+ / W Assign a day / a whole week
d Remove the assignment
← / → Shift the week window

One person holds a given day. Assigning over days somebody else already has asks first — naming them and how many days are being taken — and moves the whole selection at once when you accept, so reassigning a week is one confirmation rather than seven deletions. The header line names the team whose rota this is, and "On-call today" lists everyone on call across your teams.

Users section:

Key Action
n Create a user
t Edit the user's ntfy topic — submit empty to clear it
d Delete a user
k API keys for the selected user
p Set the selected user's web UI password — asks for the current one when it is your own

In Users, k and d act on the selected row, so move with ↑/↓ there rather than k. The Flags column marks administrators and disabled accounts. n and d are for administrators; t, k and p work on your own row, or on anyone's if you are one.