Components¶
Components live in tui.components:, keyed by name, and are placed on
screens by layout nodes (see app.md). A component
is either static, holding content declared in the YAML, or
source-bound, populated from a data.sources entry named by
source: after every fetch.
| Kind | Shows | Static content | Source-bound mapping |
|---|---|---|---|
list |
one string per row | items: |
item: |
table |
rows and columns | rows: |
each column's value: |
logview |
appended lines, searchable | lines: |
none (lines as-is) |
textview |
one block of text | content: |
none (text as-is) |
tree |
a hierarchy, expandable | root: |
label: + children: or group_by: |
inspector |
label/value pairs, nested | fields: |
each field's path:, or auto: |
eventlog |
a timeline of paged events or lines | — | key: + text: (windowed sources only) |
Common fields¶
These apply to every kind unless the column says otherwise.
| Field | Type | Default | Valid on/with | What it does |
|---|---|---|---|---|
type |
string | — | required | The kind: list, table, logview, textview, tree, inspector or eventlog. |
title |
template | — | — | Shown on the pane border. |
source |
string | — | — | The data.sources entry that populates this component. When set, static content (items, rows, lines, content, root, fields values) is ignored. |
colors |
map | — | — | Per-component color overrides. See colors. |
color_rules |
list | — | list, logview, eventlog | Colors each item or line by its value. Tables put rules on each column and inspectors on each field. See color rules. |
filterable |
bool | false |
list, table, inspector, eventlog | / opens a filter that narrows the rows (f on an eventlog, where / is search). |
searchable |
bool | false |
logview, textview, tree, eventlog | / opens a search that highlights matches; n / N move between them. |
filter_placeholder |
template | — | any filterable or searchable kind | Hint shown inside the empty filter or search input. |
initial_filter |
template | — | list, table | Starts with this filter applied. |
initial_query |
template | — | logview, textview, tree, inspector | Starts with this search or filter query applied. |
initial_cursor |
int | 0 |
list, table, tree, inspector | Row the cursor starts on. |
loading_label |
template | — | list, table, tree | Shown beside the spinner while the component has no data yet, e.g. loading pods…. |
markable |
bool | false |
list, table, tree | Multi-select: adds a mark gutter and binds x (toggle), X (extend from the last mark), A (all), D (none). Actions with multi: true then run once per marked row. |
mark_key |
path | — | source-bound markable table; span table; table with activity |
The stable identity marks and activity are held by, read from the source item (not the rendered cells), e.g. metadata.uid. Required on a markable source-bound table, on every span table and on a table with activity. |
activity |
map | — | table, list, tree on a fetched or polled source | Shows a spinner on rows the data says are being worked on. See activity. |
on_cursor |
map | — | source-bound target | Refetches this component's source as another component's cursor moves. See on_cursor. |
on_input |
map | — | source-bound target | Refetches this component's source when an input or toggle on the screen commits. See on_input. |
Validation:
typeis one of the nine kinds;source:names a defined entry.loading_label,line_numbers,gutterandnew_forare errors on a kind their row doesn't list.markableonly on list, table, tree. On a table bound to a source,mark_keyis required. It isn't accepted on lists (they key on the item string), trees (the node path), static tables (row position) or withoutmarkable: true. It isn't defaulted to the first column because a non-unique column collapses rows onto one mark and a volatile one (AGE, STATUS) loses the marks on the next poll.markablewith a windowed source is an error: a window holds rows without keys. The exception is a span table (bound to an Anchored source), whose rows are keyed, so marking works.
list¶
One string per row.
| Field | Type | Default | Valid on/with | What it does |
|---|---|---|---|---|
items |
list of template | — | static | The rows. |
item |
string | — | required when source-bound | Dot-path to each element's display string. |
regions:
type: list
title: Regions
source: regions
item: name
filterable: true
table¶
Rows and columns, with sorting, filtering and horizontal scroll.
| Field | Type | Default | Valid on/with | What it does |
|---|---|---|---|---|
columns |
list | — | required | Column definitions. See columns. |
rows |
list of list | — | static | Each row is a list of cells. A cell is a string, {value, color} for a colored cell, or {label, url} for a hyperlink. |
initial_sort |
map | — | — | Sort to start with: column (a column title, case-insensitive prefix, or a 1-based number) and desc (bool). Only sortable columns match; anything else is ignored. |
max_items |
int | 5000 |
span table | Rows held at once; the end furthest from the view is trimmed. |
sort_debounce |
duration | tuilib's (400ms) | windowed source | How long sort input must go quiet before the source is asked for the new order, so stepping through columns with [ / ] sends one request. 0 sends every change. |
max_rows |
int | 100 |
streaming source, no row_key |
Ring-buffer size: each arriving event is prepended as a row and the oldest drop past this size. -1 is unbounded. |
row_key |
path | — | streaming source | Keyed-upsert mode: an event whose key matches a row updates it in place; a new key appends. Without it, a streaming table is a ring buffer. Distinct from mark_key. |
How a table fills depends on its source:
- Fetched or polled: the rows are replaced on each fetch; cursor, filter, sort and marks survive.
- Streaming (
follow: true, websocket): each event adds or updates a row, permax_rows/row_key. - Windowed (the source declares
window:): the table holds only the rows on screen and the server answers the filter and sort. It switches to remote filtering and sorting automatically. Committing a filter or sort keeps the current rows on screen, dimmed, until the answer lands; a failed query keeps the rows and the typed filter, andrretries. Each query's outcome is logged to the output console under the source's name. See sources.md. - Span (the source is Anchored): the
table holds a run of rows walked from an edge, oldest at the top and
newest at the bottom, growing as you scroll towards either end and
trimmed past
max_items. It opens at the endwindow.anchor:names. Rows are keyed by their cursor andmark_key, somark_keyis required and marking works. The order is the walk's, so columns can't be sorted; the filter is remote, and committing one re-walks from the anchor.
Validation: columns is required. When source-bound, every column
needs value:. On a windowed table, a sortable column needs
window.sort_param:. sort_debounce is only accepted on a windowed
table, since a local sort is instant. A span table needs mark_key, and
rejects sortable columns, initial_sort and sort_debounce;
max_items is only accepted on span tables (and eventlogs).
Columns¶
| Field | Type | Default | Valid on/with | What it does |
|---|---|---|---|---|
title |
template | — | required | Header text. Also the name used by ${selection.Title}, ${cursor.Title}, title:value filter terms and window.filters. |
value |
path | — | required when source-bound | Dot-path to the cell value in each item. A list of paths is tried in order and the first non-empty wins. |
width |
int | 0 |
— | 0 sizes to content; a positive number is a fixed width. |
flex |
int | 0 |
— | Share of leftover width. |
max_width |
int | — | — | Caps a flex or auto column. |
align |
string | left |
— | left, right or center. |
sortable |
bool | false |
— | The column can be sorted from the keyboard. |
sort |
string | string |
sortable |
Comparator: string (case-insensitive), number (commas stripped), si (number with K/M/B/G/T suffix). |
hidden |
bool | false |
— | Not drawn, but still in the row: it matches filters and resolves in ${selection.*} / ${cursor.*}. For identity columns a drilldown needs. |
distinct |
list or map | — | windowed tables | The values this column's filter suggests and completes, which otherwise come only from the page that's loaded. A list ([Running, Pending, Failed]), or {source: NAME} for a source that returns a list of strings; it's fetched and polled alongside the table. The source can't be windowed. |
color_rules |
list | — | — | Colors each cell by its value. See color rules. |
metric |
map | — | source-bound tables; not with value, color_rules or sortable |
Draws the cell as a badge, ratio, progress bar or sparkline. See metrics. |
pods_table:
type: table
source: pods
filterable: true
markable: true
mark_key: metadata.uid
initial_sort: {column: Restarts, desc: true}
columns:
- {title: Name, value: metadata.name, flex: 1}
- {title: Namespace, value: metadata.namespace, hidden: true}
- title: Status
value: [status.containerStatuses.0.state.waiting.reason, status.phase]
color_rules:
- {when: Running, color: green}
- {when: "~Err|Crash", color: red}
- {title: Restarts, value: status.containerStatuses.0.restartCount, align: right, sortable: true, sort: number}
Metrics¶
metric: on a column or an inspector field draws a
small inline summary instead of text. It takes exactly one kind key, whose
inputs are dot-paths into the row's item, in this order. Compute anything
that isn't already a field with a derive operator.
| Field | Type | Default | Valid on/with | What it does |
|---|---|---|---|---|
badge |
list of 3 paths | — | one kind key | Status counts, ok / warn / down: 12✓ 3⚠ 1✗. Zero counts are left out. |
ratio |
list of 2 paths | — | one kind key | Done / total, 3/5: green when complete, yellow partway, red at zero. |
bar |
path | — | one kind key | A value drawn as a progress bar against max. |
spark |
path | — | one kind key | A list of numbers drawn as a sparkline. The data must hold the series. |
max |
number or path | — | bar, required |
The bar's full value. |
width |
int | the column's width, else 10 |
bar, spark |
Width in cells. |
color |
color | severity colors (bar), blue (spark) | bar, spark |
A fixed color: a name or a 0–255 index. |
${selection.<column>} and ${cursor.<column>} read a metric column's
first input, not its glyphs, so ratio: [ready, desired] binds the ready
count.
columns:
- {title: Ready, metric: {ratio: [status.readyReplicas, spec.replicas]}}
- {title: Health, metric: {badge: [ok, warn, down]}}
- {title: CPU, metric: {bar: cpu_pct, max: 100, color: blue}}
- {title: Trend, metric: {spark: latency_series}}
Validation: exactly one kind key, with the right number of paths; a
bar needs max, and max, color and width are errors on the other
kinds. A metric needs a source-bound table or inspector, and can't share a
column with value, color_rules or sortable: true (or a field with
value, path or color_rules). Lists and trees have no metrics: a row
there is one string, and a table already puts a metric next to text.
logview¶
Lines appended over time, with search and an optional filter mode. The natural target for streaming sources.
| Field | Type | Default | Valid on/with | What it does |
|---|---|---|---|---|
lines |
list of template | — | static | Initial lines. |
max_lines |
int | 10000 |
— | Lines kept before the oldest drop. -1 is unbounded. |
filter_mode |
bool | false |
searchable |
Starts in filter mode: the search hides non-matching lines instead of highlighting matches. |
line_numbers |
bool | false |
— | Numbers lines in a gutter, from the start of what's held. Numbers locate a line on screen; they shift as old lines drop. |
When source-bound, a format: text body is split on newlines, a JSON
list of strings is used as-is, and a streaming source appends one line
per event. When a stream ends, the log gets a dimmed — stream ended —
line; r reconnects it and adds — reconnected —. r leaves a live
stream alone.
pod_logs:
type: logview
title: Logs
source: pod_logs # http with follow: true
searchable: true
color_rules:
- {when: "~ERROR", color: red}
textview¶
One block of text, replaced on each fetch. For help panes, command output and documents.
| Field | Type | Default | Valid on/with | What it does |
|---|---|---|---|---|
content |
string | — | static | The text. Replaced by the source's value when source-bound. |
wrap |
bool | false |
— | Word-wrap. w toggles it at runtime. |
When source-bound, a format: text body passes through verbatim and a
JSON value is shown formatted.
help:
type: textview
title: Help
searchable: true
wrap: true
content: |
Press / to search, w to toggle wrap.
tree¶
A hierarchy with expand and collapse. Static trees declare root:;
source-bound trees build nodes from the data.
| Field | Type | Default | Valid on/with | What it does |
|---|---|---|---|---|
root |
node | — | required when static | The root node: label (template) and children (a list of nodes). |
label |
path | — | required when source-bound | Dot-path to each node's display label. |
children |
path | — | source-bound; not with group_by |
Dot-path on each node to its list of child nodes, for nested data (filesystem trees, org charts). Nodes with none are leaves. |
group_by |
path | — | source-bound; not with children |
Buckets a flat list by this value; each bucket becomes a parent node labelled with it (e.g. group_by: kind). |
root_label |
template | title, then the source name |
source-bound | Label of the root node. Kept stable across refreshes so expansion state survives. |
initial_depth |
int | 0 |
— | Expands every node shallower than this: 0 root only, 1 root expanded, 2 one level more. |
Cursor and expansion state survive refreshes.
by_team:
type: tree
title: People
source: people
label: name
group_by: team
initial_depth: 1
searchable: true
inspector¶
Label/value pairs for one record, with nested groups that expand.
| Field | Type | Default | Valid on/with | What it does |
|---|---|---|---|---|
fields |
list | — | not with auto |
The rows. See inspector fields. |
auto |
bool | false |
source-bound; not with fields |
Builds the rows from whatever the source returns: maps expand into groups, arrays into [0], [1]…, scalars render as-is. |
initial_depth |
int | 0 |
— | Expands every group shallower than this. |
Inspector fields¶
| Field | Type | Default | Valid on/with | What it does |
|---|---|---|---|---|
label |
template | — | required | The left-hand text. |
value |
template | — | — | Static right-hand text. Leave empty for a group header. |
path |
string | — | source-bound | Dot-path into the source's value; overrides value. |
color_rules |
list | — | — | Colors the value. See color rules. |
children |
list | — | — | Nested fields, shown as an expandable group. |
metric |
map | — | source-bound; not with value, path or color_rules |
Draws the value as a badge, ratio, bar or sparkline. See metrics. |
pod_detail:
type: inspector
source: pod
fields:
- {label: Name, path: metadata.name}
- label: Status
children:
- {label: Phase, path: status.phase, color_rules: [{when: Running, color: green}]}
- {label: Node, path: spec.nodeName}
eventlog¶
A timeline of events or lines read in pages from a source too large to
hold: an AWX job's events, a search index, a long log. Each item draws as
the lines of its text. Unlike a logview, which holds a stream whole, an
eventlog binds only to a windowed source and fetches
as you scroll. Over an Anchored source it
opens at the end window.anchor: names and walks from there.
| Field | Type | Default | Valid on/with | What it does |
|---|---|---|---|---|
key |
path | — | required | Each item's identity. Bare ${selection} when enter fires an action. |
text |
path | — | required | The text an item draws as, split on newlines. An empty value draws as a dim "· no output" line: the item still happened, and enter still opens it. |
mark |
path | — | — | Shown as-is in the gutter, normally the server's timestamp. |
start |
string | oldest |
paged (not Anchored) sources | Where the view opens once the source isn't growing: oldest or newest. A growing source always opens at the newest item and follows. An Anchored source uses window.anchor: instead. |
max_items |
int | tuilib's | — | Items held at once; the end furthest from the view is trimmed. |
gutter |
bool | true |
— | The gutter of positions down the left. false hides it. |
new_for |
duration | 2s |
— | How long items that just loaded stay marked in the gutter. - never marks them. |
Keys: / searches the items held, and n / N jump between
matches. f filters (narrowing the query the source answers) when
filterable: is set. G goes to the newest item and follows it; moving
away while the source is growing counts new items as "↓ N new" instead
of moving you.
Enter (or a double click) fires the screen's key: enter action if
one is bound with this eventlog as from:, with the item as
${selection}: bare ${selection} is the key, and every field of the
item is ${selection.PATH} (${selection.event},
${selection.event_data.host}). With no action bound, enter opens the
item's raw data in an inspector; esc closes it.
Validation: source: must declare window:; key and text are
required; start is oldest or newest. These fields are rejected on
other kinds. An eventlog can't be markable.
events:
type: eventlog
title: Job events
source: job_events # declares window:
key: id
text: stdout
mark: created
searchable: true
color_rules:
- {when: "~^fatal|FAILED", color: red}
input¶
A one-line text field. It doesn't display a source; other components
read its committed value as ${input.NAME} (NAME is the component's
name) in on_input / on_cursor binds and in actions.
| Field | Type | Default | Valid on/with | What it does |
|---|---|---|---|---|
initial |
template | — | — | The value before anything is committed. It's bound for the first fetch. |
placeholder |
template | — | — | Shown while the field is empty. |
char_limit |
int | 0 (none) |
— | The longest value accepted. |
mask |
bool | false |
— | Shows typed characters as bullets (app.glyphs.mask). |
Tab onto the field to edit it; while it's being edited every key is
typing, so q and esc don't reach the app. enter commits the value,
and every component that reads it refetches. esc puts back the
committed value and leaves the field; enter starts editing again. Tabbing
away puts back anything typed but not committed.
toggle¶
A yes/no switch, read as ${input.NAME}: true or false. Every flip
(←/→, space or a click) commits, and the components that read it
refetch.
| Field | Type | Default | Valid on/with | What it does |
|---|---|---|---|---|
initial |
string | false |
— | true or false. |
yes_label |
string | yes |
— | The "true" side's label. |
no_label |
string | no |
— | The "false" side's label. |
An input or toggle is three rows tall: in a vstack it takes fixed: 3
on its own, and any other height is an error. In an hstack, flex /
fixed set its width as usual.
search: {type: input, title: Search, placeholder: name…, initial: "${env.Q}"}
show_deleted: {type: toggle, title: Deleted?, yes_label: shown, no_label: hidden}
pods_table:
type: table
source: pods # declares parameters: q, deleted
on_input:
bind: {q: "${input.search}", deleted: "${input.show_deleted}"}
Validation: neither takes source:; initial on a toggle is true or
false; char_limit can't be negative; their fields are errors on other
kinds.
activity¶
Shows a spinner on the rows that are being worked on, according to the
data: a pod Pending, a sync Running, a job queued. The server is the
source of truth. Every fetch re-evaluates every row, so a row starts
spinning when a read says it's busy and stops when a read says it isn't.
Without activity: nothing changes; with it, a busy row shows the spinner
and its status (⣾ Pending) in the activity column, and the rest render as
usual.
| Field | Type | Default | Valid on/with | What it does |
|---|---|---|---|---|
status |
path | — | required | The field that says what a row is doing. Its value is shown beside the spinner while the row is busy. |
busy |
list of string | — | not with busy_when |
The status values that mean busy, matched exactly. |
busy_when |
expr | — | not with busy |
An expression over the row's item, true while it's busy, for when busy-ness isn't one field's value: status.observedGeneration < generation. |
column |
string | the column whose value: is status |
table | The column the spinner draws in. A list or tree draws it beside the row. |
revision |
path | — | — | A value that moves when work on the row finishes, e.g. metadata.resourceVersion. With it, a request whose status ends where it began (Synced, then Synced) still ends the spinner as done, and the report can say whether anything changed. Without it, the spinner waits out the claim's settle. |
pods_table:
type: table
source: pods # refresh: 5s
mark_key: metadata.uid
activity:
status: status.phase
busy: [Pending, Terminating]
columns:
- {title: Name, value: metadata.name}
- {title: Phase, value: status.phase, width: 14} # the spinner draws here
Give the activity column a width: that fits the spinner and the longest
busy status; the indicator never widens a column.
Reads failing. When a polled source's fetches start failing, every
component bound to it shows · reads failing on its title until a fetch
succeeds again, and the error is reported once rather than on every poll.
Rows keep what the last good read said; rows that were busy then show a
static, muted marker instead of spinning. This applies to every polled
component, with or without activity:.
Validation: status and exactly one of busy / busy_when are
required, and busy_when must compile. A table needs mark_key: (activity
is tracked per row) and a column to draw in. Activity isn't available on
streaming sources (one event isn't a whole read of the rows) or windowed
ones (their rows have no keys).
Color rules¶
A rule pairs a matcher with a color. Rules are tried in order and the first match colors the text; with no match it renders plain. The selected-row background passes through.
| Field | Type | Default | Valid on/with | What it does |
|---|---|---|---|---|
when |
string | — (always matches) | — | Exact text (case-insensitive), ~regex (case-insensitive), or a numeric comparison: > 5, <= 10, == 0, != 0, with an optional K/M/B/G/T suffix on the number. Empty matches everything, which makes a final default rule. |
color |
color | — | required | See color values. |
A theme: color in a rule is re-rendered when the theme cycles. On a
windowed table or an eventlog, rows already loaded keep their colors
until the next page is fetched.
colors¶
Overrides individual theme colors on one component. Unset fields keep
the theme's. Values are color values,
including theme:<token>, which follows the palette when themes cycle.
| Field | Type | Default | Valid on/with | What it does |
|---|---|---|---|---|
border_active |
color | theme | all | Border color when focused. |
border_inactive |
color | theme | all | Border color when unfocused. |
spinner |
color | theme | all | The loading spinner. |
selected |
color | theme | list | The focused item. |
selected_fg |
color | theme | table, eventlog | Focused row text. |
selected_bg |
color | theme | table, eventlog | Focused row background. |
header |
color | theme | table | Header text. |
cell |
color | theme | table, eventlog | Ordinary cell text. |
column_separator |
color | theme | table | The line between columns. Palette colors only (named or 0–255). |
header_rule |
color | theme | table | The line under the header. Palette colors only. |
label |
color | theme | inspector | Field labels. |
value |
color | theme | inspector | Field values. |
match |
color | theme | inspector, logview, tree | Search-match highlight. |
current_line_bg |
color | theme | inspector, logview, tree | Background of the cursor line. |
mark |
color | theme | list, table, tree | The ✓ on a marked row that isn't under the cursor. |
gutter |
color | theme | logview, eventlog | The line-number or position gutter. |
new |
color | theme | eventlog | Positions of items that just loaded. |
on_cursor¶
Refetches this component's source whenever another component's cursor
moves, binding the driver's focused row into the source's
parameters:. This is the "table on top, detail below" pattern, without
pushing a screen. Repeat visits to a row hit the source's param cache.
| Field | Type | Default | Valid on/with | What it does |
|---|---|---|---|---|
source |
string | — | required | The driver: a table, list or tree on the same screen. (It names a component, not a data source.) |
bind |
map of template | — | — | Maps this component's source parameters to templates over the driver's focused row: ${cursor}, ${cursor.N}, ${cursor.Title}, ${cursor.path}, plus ${env.*}. See templating.md. |
Validation: the driver is a table, list or tree placed on the same
screen; the component declaring on_cursor must itself have a source:.
repo_detail:
type: inspector
source: repo # declares parameters: owner, name
auto: true
on_cursor:
source: repos_table
bind:
owner: ${cursor.Owner}
name: ${cursor.Repo}
${input.NAME} works in on_cursor.bind too, so one source can follow a
cursor and an input at once.
on_input¶
Refetches this component's source when an input or toggle on
the same screen commits, binding their values into the source's
parameters:. Use it on a component with no cursor driver; it's fetched
with the inputs' initial values when the screen opens.
| Field | Type | Default | Valid on/with | What it does |
|---|---|---|---|---|
bind |
map of template | — | required | Maps source parameters to templates over ${input.NAME}, plus ${env.*}. |
Validation: the component has a source: that declares each bound
parameter; ${input.NAME} names an input or toggle on this screen; a
parameter can't be bound by both on_input and on_cursor.