# The UI

> morca's web UI: its pages and their JSON, coverage in one number, filtering runs and acting on many at once, the actions and the dialog they ask in, logins and sessions, a run's page with its cards, steps and machines, and code on hover.

[morca](https://morca.dev/)

[](https://morca.dev/search "Search")

`orca ui` serves pipelines, runs and datasets as web pages, from the same `orca` binary, on `127.0.0.1:8270` unless `ORCA_UI_ADDR` says otherwise. It holds no state: it reads the tables the scheduler and the runs write, connecting to Postgres per request, so it can run beside the scheduler or anywhere else that reaches the database. Its pages load nothing from other sites, so they work offline; only code on hover needs GitHub, and the UI fetches that itself.

## Pages

```
ORCA_DATABASE_URL=postgresql:///orca ./build/orca ui
curl -H 'Accept: application/json' http://127.0.0.1:8270/runs/7
```

| Path | What it shows |
| --- | --- |
| / | pipelines, with their partitions, coverage, schedules and last runs; its Timeline tab draws each pipeline's runs over 1 h to 7 d, in dataset order, with what started what (/api/gantt) |
| /pipelines/<name> | three tabs: Overview (the Progress, Config, Code and Resources cards, the Configuration table, steps as a graph or a table, next runs), ?tab=runs (runs and versions) and ?tab=operate (the coverage calendar, Start runs and Stop runs); a keyed one's keys, done, live and failed |
| /pipelines/<name>/keys/<key> | one key of a keyed pipeline: its steps in every run it was in |
| /runs | runs, newest first, a page at a time, with a filter bar and, logged in, actions on the runs ticked |
| /runs/<id> | a run's Status, Partitions & trigger, Code, Resources and Settings cards, its steps as a table, a timeline or a graph, its machines and instances, its Dista timeline, actions and snapshots |
| /runs/<id>/attempts/<attempt> | an attempt's log, its last MiB, with its error, peak memory and CPU |
| /runs/<id>/timeline.json | the run's Dista timeline, for Perfetto |
| /runs/<id>/driver.log | the run's driver output, as the scheduler kept it |
| /runs/<id>/cluster/<role>-<instance>.log | a head's or worker's output, from an EC2 run |
| /datasets | every dataset with its writers and readers, and the graph of pipelines and datasets, drawn top to bottom |
| /datasets/<name> | a dataset's place in that graph, its snapshots, its rebuilds and the form to start one |
| /datasets/<name>/snapshots/<id> | a snapshot's lineage, Upstream and Downstream, as a graph and as tables |
| /rebuilds/<id> | a rebuild's pipelines by level, with their runs |
| /ops/control | Ops › Control: the machine running the scheduler, the UI and Postgres: CPU, memory, disk, network, containers, its memory pressure, the local and control-placed runs going with their memory against their limits, and the alerts sent |
| /ops/pools | Ops › Pools: every machine runs are placed on, a row each: the EC2 instances morca launches, a Kubernetes run's pods and the machines running orca worker, filtered with ?kind= and ?pool=; with EC2 in view, the fleet's cost and charts |
| /ops/pools/<kind>/<id> | one machine: an EC2 instance's samples over its run, or the runs a worker claimed |
| /watchdog | Ops › Watchdog: the rules with each threshold and value, the trips and what each tripped on, and the form to snooze a rule |
| /login, /logout | a session for the actions, as a cookie |

Every path also answers JSON, to `Accept: application/json` or `?format=json`, built from the same rows, so a script and a person see the same thing. A page with something going on refreshes that part by itself while the tab is visible: a run's page and the runs tables every 5 seconds while a run is queued or running, an attempt's log every 2, Ops › Pools every 10 and Ops › Control and the watchdog every 30\. A refresh keeps the row at the top of the window, the chosen tab and a field being typed in. Every column header and every fact's name has a "?" that explains it, and a value from a fixed set, such as a status or a trigger, says what it means on hover. The old `/ops/fleet` and `/ops/workers` redirect to Ops › Pools.

## Coverage

Since 0.16.0 each pipeline has one coverage number: the home page's Coverage column and its pipeline's Progress card (`1141 / 1407 days done (81%)`), linking to its year calendar. For a daily or monthly pipeline it's the partitions done of those in its range so far, from its first through the last that has ended; for a keyed one, the keys done of the keys morca last read. Done is what the calendar and a backfill count: every mapped step succeeded in a run of the current code. A pipeline with nothing to count shows "—". The JSON has it as `coverage` (`done`, `total`, `pct`, `unit`) on each pipeline of `/` and on `/api/pipelines/<name>`.

A daily pipeline's Coverage card, on its Operate tab, has Month and Year tabs: Year is twelve small months of days coloured done, failed, running or queued and not run, each with its share done, and a strip of every month of the range. A sensor that follows another pipeline's dataset marks the days it hasn't done yet as **behind**, and its Progress card says how far behind it is.

## Lists of runs

`/runs`, a pipeline's Runs tab and a dataset's snapshots come a page at a time, newest first, keyed by run id (`?before=`, `?after=`, `?limit=`), so a page stays put while new runs arrive. Since 0.15.0 a filter bar over the table narrows them; it's a plain `GET` form, so the filters work without scripts, combine with each other, and are kept in every link, the live refresh and the `1–50 of N` count:

| Parameter | Runs |
| --- | --- |
| ?status= | any of several statuses: ?status=failed&status=queued, or failed,queued; the box shows each status's count under the other filters |
| ?trigger= | schedule, sensor, backfill, fill, manual, ui, rerun or rebuild |
| ?pipeline= | one pipeline, on /runs |
| ?from=, ?to= | created from, and by the end of, a day (2024-06-01) or a time (2024-06-01T09:30), UTC |
| ?since= | created in the last 1h, 24h or 7d, or today: the quick picks beside the dates |
| ?key= | a partition: a glob such as 2023-08-\*, or any part of one, such as modular/ for a keyed pipeline's repositories |
| ?run= | one run: the page goes to it |

Logged in, each row has a checkbox, and the bar over the table acts on the runs ticked, or on all that match the filters: **Cancel**, **Retry failed** and **Backfill again**. It says what it did ("Cancelled 12 runs, 3 already finished"), does at most 2,000 runs at once, and records each run as its own action. `<pipeline> status --status failed,queued` filters the same way on the command line.

## Actions

| Action | What it does |
| --- | --- |
| Start | a queued run over the partitions entered, trigger ui |
| Backfill | what orca backfill does: a queued run for each partition without one, and with and what is ticked, the done ones too |
| Resume | a failed, cancelled or stalled run queued again, to resume at what hasn't succeeded; a run whose code has changed asks for confirmation first |
| Rerun, Rerun from here | a new run over the same partitions, every step again; or, from an attempt's page, a new run, trigger rerun, with the steps before that one copied from the old run, so only it and what follows run |
| Cancel | a queued or failed run cancelled at once; a running one stops before its next step, and the step running has ORCA\_CANCEL\_KILL\_SECONDS (60) to finish |
| Cancel, Retry failed, Backfill again | on a runs list, over the runs ticked or all that match the filters: what each row's Cancel and Retry do, and orca backfill --rerun over their partitions |
| Pause, Unpause | on a pipeline's page: what orca pause and orca unpause do |
| Cancel queued runs | on a pipeline's page: what orca cancel does, over @all or the partitions entered |
| Rebuild downstream | on a dataset's page: what orca rebuild does, over the partitions entered |
| Snooze, Keep paused | on /watchdog and the trip banner: what orca watchdog snooze and orca watchdog ack do |

The UI never starts a pipeline binary. An action writes rows in Postgres, and the scheduler starts the queued runs on its next tick, within `max_runs`, so the UI does nothing without a scheduler running. It binds to localhost unless told otherwise, and refuses a `POST` whose `Origin`, or `Referer` without one, isn't its own, so another site's page can't post to it through a browser.

Since 0.16.0 the actions that are hard to undo (Cancel, a list's bulk Cancel, Cancel queued runs, Rerun, Rerun from here and Rebuild downstream) ask first, in a dialog in the UI's own colours, light or dark: what will happen ("Cancel run 4418?") and two buttons, the safe one focused. Escape or a click outside is no. Without scripts the forms post without asking, as before.

## Login

Anyone who can reach the UI can read it. Its actions need a login once there's a user, or with `ORCA_UI_LOGIN=required` before there is one:

```
./build/orca user add alice          # reads the password from standard input
./build/orca user list
./build/orca user remove alice       # and ends their sessions
```

Passwords are kept as bcrypt hashes made by Postgres. A login is a session cookie, `HttpOnly` and `SameSite=Strict`, and `Secure` with `ORCA_UI_SECURE_COOKIES=1` behind HTTPS. Every action is recorded with its user and shown on its run's page, and so is every failed login: after 5 for one name, or 20 from one address, in 15 minutes, `/login` answers 429 without checking the password. Behind a proxy, `ORCA_UI_TRUST_PROXY=1` takes the address from `X-Forwarded-For`. The same users log in from the command line with [orca login](https://morca.dev/docs/cli#remote).

## A pipeline's page

Overview, Runs and Operate are tabs, each its own page (`?tab=`), so links and the back button work. The name, Pause and the Progress card are on every tab. Over the Overview are four cards:

- **Progress**: the runs going, the [fill](https://morca.dev/docs/run#fills), a failing sensor, the next run and the coverage. While the oldest queued run waits on its host it says why, under Waiting: `waiting: needs 2 GB, 1.3 GB free of grants`, or `waiting: memory pressure 23% (limit 10%)`.
- **Config**: partitions, schedule, run retries, placement and binary.
- **Code**: the pipeline, its steps and Dista tasks, each linked to its line, and the code hash.
- **Resources** (since 0.14.0): the median and the most of the peak memory, CPU time and peak cores of its last 20 measured runs, and the request (`cpus`, `memory_gb`) if it has one: what to [ask for](https://morca.dev/docs/run#resources).

The Configuration table under them has each setting with where it came from, linked to its line in orca.toml at the registered commit, or to the `Pipeline[...]` line for a default from the code.

## A run's page

A queued or running run's page refreshes itself every 5 seconds, and its crumb links the pipeline's previous and next run (the arrow keys follow them). Since 0.14.0 its header is cards: **Status** (with Resume, Rerun and Cancel), **Partitions & trigger**, **Code**, **Resources** and **Settings**, which has the settings the run had and its secrets by name only. Resources has the run's peak memory and CPU used, what it requested and was granted, and, where it ran in a cgroup of its own, its Memory limit. **Steps** is one card with Table, Timeline and Graph tabs; the table has each step's peak memory against its limit (`312 MB / 1 GB`). An attempt the kernel killed for its memory fails `out of memory at <limit>: ...`.

**Machines** draws the [machine metrics](https://morca.dev/docs/run#metrics) on the same time axis: the whole machine for the local runner and the control placement, saying so when other runs shared it, the run's own instances on EC2, the node on Kubernetes, and since 0.15.0 the worker's machine for a run on a remote worker. So Machines is the machine, and Resources is the run's own use. A run with Dista tasks draws its timeline in the page: a track per process, a lane per slot, spans coloured by function, with a tooltip, and wheel or drag to zoom. With `ORCA_UI_URL` set to the UI's public address, an Open in Perfetto link opens the full trace there. A run on EC2 lists its instances, with why each ended and each one's log. Snapshots lists what each step read and wrote, linked to their lineage. The headings link to these docs (`ORCA_DOCS_URL` changes where).

## Ops

The Ops menu has three pages. **Control** is the one machine running the scheduler, the UI and Postgres: its CPU, memory, disk, network and containers over the last 24 hours, 7 or 14 days, and since 0.15.0 its **Memory pressure** card (Linux's pressure stall information, and whether new runs wait for it) and a **Runs** card of the local and control-placed runs going, each with its memory against its limit. **Pools** (since 0.15.0, in place of Fleet and Workers) is a row per machine runs are placed on: EC2 instances, a Kubernetes run's pods and the machines running [orca worker](https://morca.dev/docs/workers), with each one's state, image, CPUs, memory, runs and last heartbeat; with EC2 in view it keeps Fleet's cost cards and charts. **Watchdog** has the rules and their trips ([the watchdog](https://morca.dev/docs/run#watchdog)). Each section's heading links to it, so `/ops/control#memory` opens at that chart.

## Code on hover

The step names, the pipeline and its Dista tasks link to their code ([versions and code links](https://morca.dev/docs/build#versions)), and hovering such a link shows the function, coloured, which the UI fetches from GitHub at the commit (not for a private repository). Hovering a step in a Steps card shows its card: its signature and docstring, its kind, inputs and outputs, the datasets it reads and writes, and what its attempts took, on a pipeline's page over its last 20 runs and on a run's page for that attempt. Since 0.16.0 the runs lists and the home page show the same cards too: a run's pipeline at the commit it ran, a failed run's failed step, and the home page's pipelines at the registered code. Every card leads with its docstring's first sentence, so that sentence should say what the function or pipeline is for. A card shows on hover, keyboard focus or a first tap.

The repository's [ui.md](https://github.com/morca-dev/morca/blob/main/docs/ui.md) has the rest: every JSON field, the live fragments, the anchors that link to one chart, and the Pools columns.

[morca](https://morca.dev/)

Data pipelines in Mojo. Early, and built in the open.

### Project

[GitHub organisation](https://github.com/morca-dev) [Issues](https://github.com/morca-dev/morca/issues) [morca](https://github.com/morca-dev/morca) [dista](https://github.com/morca-dev/dista) [census](https://github.com/morca-dev/census)

### Built on

[magmalake.dev](https://magmalake.dev) [The Iceberg, Parquet and codec libraries](https://magmalake.dev/tins) [mojoshelf.org](https://mojoshelf.org) [The Mojo language](https://www.modular.com/mojo)

### Standards

[Apache Iceberg](https://iceberg.apache.org) [Apache Parquet](https://parquet.apache.org) [PostgreSQL](https://www.postgresql.org)

Copyright 2026 Marius Seritan. Every repository is licensed [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0).

Apache Iceberg and Parquet are trademarks of the Apache Software Foundation. Mojo is a trademark of Modular Inc. This project is not affiliated with or endorsed by the ASF or Modular.
