Data¶
Everything under data.sources: is one map of named entries. An entry is
either a source, which fetches from outside (an HTTP API, a command,
a file, a WebSocket, inline data), or a pipeline operator, which
reads other entries and reshapes them. Components and wrangl use
entries by name, and don't care which kind they are.
Every field is in Reference → Sources and Reference → Pipelines. For the model underneath, see How the data layer works.
Sources¶
data:
sources:
pods:
type: http
url: http://localhost:8001/api/v1/pods
root: items # the list lives under "items"
refresh: 5s # poll
branches:
type: exec
command: [gh, api, repos/jsdrews/tui-builder/branches]
logs:
type: exec
follow: true # stream lines as they arrive
command: [kubectl, logs, -f, deploy/web]
| Kind | For |
|---|---|
http |
REST and JSON APIs, kube proxies, streamed endpoints |
exec |
Any command that prints JSON, or streams lines |
file |
Fixtures and generated files, re-read on refresh |
websocket |
Live event streams |
static |
Inline data: lookup tables, fixtures, demos |
merge |
Several sources concatenated into one list |
How often a source delivers follows from its fields: follow: true (or
websocket) streams, refresh: polls, and otherwise it's fetched once
and again on r.
root: picks the list out of the response. format: text keeps the
response as a string, for logs.
Pipelines¶
Operators take from: (or several inputs) and produce a new entry:
data:
sources:
pods: {type: http, url: http://localhost:8001/api/v1/pods, root: items, refresh: 5s}
failing:
type: filter
from: pods
where: "status.phase != 'Running'"
slim:
type: project
from: failing
keep:
name: metadata.name
phase: status.phase
restarts: "status.containerStatuses[0].restartCount"
| Operator | Does |
|---|---|
filter |
keeps items matching where: |
project |
rebuilds items from keep: |
derive |
adds compute: fields |
sort |
orders by by: |
union |
concatenates several entries, tagging rows by origin |
compose |
bundles several entries into one object |
join |
enriches each row with a per-row lookup |
cache |
holds a snapshot for a TTL |
passthrough |
a second, stable name for an entry |
When the steps in between aren't worth naming, chain them inline with
pipe::
failing_slim:
type: http
url: http://localhost:8001/api/v1/pods
root: items
pipe:
- {type: filter, where: "status.phase != 'Running'"}
- {type: project, keep: {name: metadata.name, phase: status.phase}}
Expressions are expr-lang; see Templating → Expressions.
Parameters¶
A source can declare typed inputs and reference them as
${params.NAME}. That makes one definition serve many uses: a drill-down
screen, a detail pane that follows a cursor, a join, or the command line.
pod:
type: http
parameters:
namespace: {required: true}
name: {required: true}
url: http://localhost:8001/api/v1/namespaces/${params.namespace}/pods/${params.name}
wrangl ops.yaml pod --param namespace=default --param name=web-0
In the TUI, a push binding's bind: or a component's on_cursor.bind
supplies them from the focused row.
Big remote sets¶
For an API with more rows than you'd want in memory, there are two options:
paginate:walks every page up front into one list. The table then filters and sorts locally. Fine up to a few thousand rows.window:fetches only the rows on screen and sends the filter and sort to the server, soauthor:tolkiensearches the whole set. Binding a table to a windowed source switches it into this mode.
books:
type: http
url: https://openlibrary.org/search.json
root: docs
window:
offset_param: offset
limit_param: limit
total_path: numFound
search_param: q
filters: {Author: author}
window: works with exec too, with the request templated into the
argv. See Reference → window.
Walking from an edge¶
Some APIs can't jump to row N at all: Elasticsearch past 10,000 hits
(search_after), logs filtered by "before / after this timestamp", and
anything that pages with an ?after=<token>. Declare cursor: in the
window and the source is Anchored: each request walks from the oldest
or newest item held, using that item's cursor.
logs:
type: http
method: POST
url: ${env.ES_URL}/app-logs/_search
root: hits.hits
body: '{"query": {"match_all": {}}, "size": ${window.limit}}'
window:
cursor: sort
older: {sort: [{"@timestamp": desc}], search_after: "${window.cursor}"}
newer: {sort: [{"@timestamp": asc}], search_after: "${window.cursor}"}
older: and newer: are what the request changes for each direction,
merged into the body. An eventlog or a table can bind an Anchored source.
See Reference → Anchored windows.
Still growing¶
A running job keeps adding events. window.growing: says so, either
always (true) or while another source says the job is running:
window:
growing: {source: job, while: 'status in ["pending", "running"]'}
follow_every: 2s
While it grows, a bound eventlog follows the newest item and polls; scroll away and new items are counted instead of moving you.