Niklas Ye 0006424eaf Act on the selected row, not the one the table moved to
In Users, pressing k on a user opened API keys for the user above it.
The dashboard hands every key to the section's table before the
section's own handler reads the cursor, and bubbles' table claims
several letters for navigation: k is up, d half a page down, f a page
down. A letter that is also an action therefore moved the cursor first,
and the action landed on the row it had moved to. With your own user
first in the list, k looked like it only ever showed your own keys.

The same collision hit two destructive keys:

- d in Users asked to delete a user half a page below the selected one.
  The confirmation names the user, and that was the only thing standing
  between a keypress and deleting the wrong person.
- d in Schedule targeted a different day's assignment the same way.

f in Incidents and Alerts cycled the filter and paged the cursor down
too, which was harmless but wrong.

Each table now gives up exactly the letters its section acts on,
through tableKeyMap. The arrow keys and every other default binding are
untouched. The cost is that k no longer moves up in Users, where it
means API keys, as the README has always said. The up arrow still
works, and the README now says to use it there.

users_test.go reproduces all four. With tableKeyMap reverted to the
defaults, each of them fails exactly as reported.
2026-09-19 21:09:06 +02:00

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
  • On-call schedule — visual calendar of who is on duty, 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.4.0 or later. Earlier servers have no incidents API; use terdut-tui v0.3.x with those.

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.

Editing topics needs terdut-server v0.6.0 or later; the timeline entries need v0.7.0 or later. Against an older server the topic column stays empty and editing one reports the server's 404.

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
api_key: <your-api-key>
refresh_interval: 30  # seconds, optional
theme: gruvbox-dark   # optional, this is the default

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:

# ~/.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
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. Taking somebody's shift needs terdut-server v0.8.0 or later; against an older server the assignment is refused with date already assigned.

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

In Users, k and d act on the selected row, so move with ↑/↓ there rather than k.

S
Description
Terminal user interface for terdut-server
Readme 653 KiB
v0.13.0 Latest
2026-09-27 16:32:44 +00:00
Languages
Go 99.1%
Makefile 0.9%