024dc095a5
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.
241 lines
9.5 KiB
Markdown
241 lines
9.5 KiB
Markdown
# 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 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](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` | 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.
|