Files
terdut-tui/README.md
T
Niklas Ye 27008086b0
CI / test (push) Successful in 12s
Release / test (push) Successful in 3s
Release / binaries (push) Successful in 14s
Follow terdut-server into teams: switch team, per-team schedule
terdut-server v0.12 made everything team-scoped and v0.20 is what this
client now targets. Against it the old client was wrong in three ways:
the schedule moved to /api/teams/{id}/schedule, GET /api/schedule/current
became a list with one entry per team, and users, incidents, alerts and
schedule entries all grew fields the client ignored.

T steps through all teams and then each of yours. The header names what
is showing, and incident and alert rows gain a Team column when more than
one team can appear. team: in config.yaml picks the team to start on, by
name or id; an unknown one is reported and falls back to all teams.

The schedule is one team's rota, so it shows the active team, or with
all teams showing the first one you own. Writes need an owner or an
administrator, and the picker offers only the team's members, since the
server answers 404 for anybody else. Both are checked up front and the
reason goes in the status bar, rather than surfacing as a 403 after the
user has picked somebody. Stats are not team-scoped by the server and
stay that way here.

Users shows an admin/disabled Flags column. Creating and deleting users
is administrators only, and topic, keys and password work on your own
row or on anyone's for an administrator; the server enforces the same
rule, this only explains it before the round trip.

The server has no version endpoint, so an older one is recognised by
GET /api/teams answering 404, and the TUI says it needs v0.20 or later.
Connecting now also loads /api/teams and /api/me with the key, which
means a wrong key fails on start instead of on the first list; /healthz
does not check it. There is no fallback to the pre-team paths.

Rebuilding a table whose column count changes under loaded rows panicked
inside bubbles, because it re-renders the old rows on SetColumns. The
rows are now cleared first and the cursor put back, so a refresh still
does not jump to the top.

Escalation ladders, invites, integrations and the admin settings are
left to the server's web UI. Checked against a real v0.20.1 server with
two teams, an administrator and a plain member.

Breaking: requires terdut-server v0.20.0 or later. Use terdut-tui v0.9.x
with servers before v0.12.
2026-09-23 21:57:28 +02:00

221 lines
8.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# terdut-tui
A terminal user interface for [terdut-server](https://git.ryuvia.com/niklas/terdut-server). Communicates with the server over its REST API.
Written in Go using [Bubbletea](https://github.com/charmbracelet/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 are not team-scoped by the server and always
cover all your teams.
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](https://git.ryuvia.com/niklas/terdut-tui/releases), or build from source:
```bash
go install git.ryuvia.com/niklas/terdut-tui@latest
```
## Configuration
Create `~/.config/terdut-tui/config.yaml`:
```yaml
server_url: https://terdut.example.com
api_key: <your-api-key>
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
```
The API key is generated in terdut-server. See the server documentation for how to bootstrap a user and issue an API key.
## 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:
```yaml
# ~/.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 twelve 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 |
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 |
| `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.