Files
terdut-tui/README.md
T
Niklas Ye ec5df0449b Manage notification channels instead of an ntfy topic
Mirrors the server's channels API: add/remove channels per user from the Users
section. Requires the matching server release.

Claude-Session: https://claude.ai/code/session_016mBLURvJoMuUEr9cB2RpUN
2026-10-12 21:00:40 +02:00

247 lines
9.9 KiB
Markdown
Raw 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, manage each user's notification channels
> 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, notification channels 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.
## Notifications
When the server has a notification provider configured (ntfy today), an incident
that opens pages whoever is on call, on every notification channel they have
switched on. A channel is a provider type and an address (an ntfy topic); each
user's kinds of channel are shown as a column in the Users section and managed
with `t`: `a` adds one as `type:address` (for example `ntfy:my-topic`), `r`
removes one by its ID. Addresses are only sent to their owner, so an
administrator sees the kind, not where it goes. Turning a channel off is done in
the web UI. A user with no channel falls back to the server's shared fallback,
which carries **no Acknowledge button** — the fallback is shared, so a button on
it would let any recipient acknowledge as somebody else.
Every delivery lands on the incident's timeline: `Notified <user> (triggered via
ntfy)` when the provider 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
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:
```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 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` | Manage the user's notification channels — `a` add (`type:address`), `r` remove by ID |
| `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.