Skip to content

Monitor App

The Monitor App is the web interface to the workflow engine. It shows workflows, runs, jobs, function blocks and workers, tells you which runs need a person, and holds a builder for authoring and publishing workflows.

Integration roadmap

The Monitor App runs on the Neops Web Client’s session and has no sign-in of its own. Set WEBCLIENT_ORIGIN on the container to the web client’s exact origin; with it unset the app reports that it has no session. The app calls the engine from the browser, so its own origin has to be listed in the engine’s NEOPS_CORS_ORIGINS. Its functionality will be integrated into the web client in a future release. Until then it is the primary UI for workflow management and execution monitoring.

Deployment

The container image serves the static build with nginx and reads three environment variables at start (rest/monitor-app/docker-entrypoint.d/40-runtime-config.sh). All three are rendered into /config.js, which the page loads before the app bundle, so no rebuild is needed to change them.

Variable Meaning
ENGINE_BASE_URL Browser-facing base URL of the engine, e.g. https://engine.example.com. Unset: http://localhost:3030.
WEBCLIENT_ORIGIN Exact origin of the Neops Web Client the app takes its session from. Unset: no session.
MONITOR_BASE_PATH Path prefix the app is published under, e.g. /workflows. Unset: the origin root.

The engine address is deployment configuration. The Settings pages do not edit it.

Session modes

How the app obtains an access token depends on where it is served relative to WEBCLIENT_ORIGIN. The two are compared as origins, and the mode is chosen once per page load (rest/monitor-app/src/lib/auth/token-provider.ts).

  • Another origin (host relay). The app is opened from the web client, in an iframe or a popup, and asks the host window for a token over postMessage. The host answers with the bare JWT and its expiry, the app asks again ahead of expiry and on a 401, and a logout in the web client is relayed as a message. Opened directly, with no host window, the app has no session.
  • The web client’s own origin (shared session). Served from the same origin, for instance under https://neops.example.com/workflows, the app reads the web SDK’s access token straight from the shared localStorage key token and follows the storage event for that key, so a refresh or a logout in any web client tab reaches it. The refresh token is never read, and the key is never written: the SDK’s cross-tab logout listens on it. Without a token the app asks the user to sign in to the web client and picks the session up as soon as that happens.

An engine that runs with NEOPS_AUTHZ_MODE=disabled verifies no token and serves every caller as anonymous. The app then works in either mode without a session.

Base path

SvelteKit fixes its base path at build time, and the image is built once. MONITOR_BASE_PATH therefore works on the served HTML: the reverse proxy in front strips the prefix, nginx keeps serving from /, and at container start the entrypoint rewrites every .html file of the build so the browser requests <prefix>/_app/..., <prefix>/config.js and <prefix>/favicon.svg and the SvelteKit runtime base carries the prefix. The rewrite always starts from pristine copies taken at image build (/html.template/), so a restart with a different value cannot stack prefixes. The value is validated as one or more /segment parts from [A-Za-z0-9._~-] with no trailing slash and no . or .. segment.

The rewrite is tied to SvelteKit’s HTML output

The entrypoint uses sed with a fixed set of patterns: href="/_app/, import("/_app/, href="/favicon.svg", src="/config.js" and a line that is exactly base: "" inside the inline __sveltekit_<hash> script. The client bundle reads base from that global at runtime, which is what makes the rewrite reach $app/paths and the router. After a SvelteKit or adapter upgrade, compare a built build/index.html with those patterns and run the image with MONITOR_BASE_PATH=/workflows before releasing.

Quick Start

Follow the Quick Start to start PostgreSQL, the Monitor App, and the engine. Then open http://localhost:5173 in your browser.

  1. Ensure the workflow engine is running (see Setup)
  2. Start the Monitor App:

    make run-monitor
    

    Or directly:

    cd rest/monitor-app && npm run dev
    
  3. Open http://localhost:5173 in your browser

The app has these places:

Place Route Holds
Overview /overview Needs attention, runs in progress, the last 24 hours, workers and function blocks, unpublished changes, licence
Runs /runs Every run, in views; a run opens at /runs/<id>
Jobs /jobs The jobs queue; a row opens its run with the job expanded
Workflows /workflows Published workflows and their versions; a version opens with its steps and a comparison of two versions
Function blocks /function-blocks Every version of every function block with the workers that registered it
Workers /workers Every worker with its state and the function blocks it serves; a worker’s page adds its log lines (see Logs across runs)
Logs /logs Every run’s log lines, narrowed by workflow, function block, entity, worker or job type (see Logs across runs)
Builder /workflow-builder Authoring and publishing; discarded drafts sit in /workflow-builder/trash
Settings /settings Preferences of this browser and the engine’s licence

/ redirects to the start page chosen in Settings. The default start page is Overview. The routes /dashboard and /executions redirect to /overview and /runs, and a ?workflowId= query on /executions becomes the Runs workflow filter.

Where the sections sit depends on how the app is opened (see Framed and standalone). Opened directly, a header lists them, with a Help menu and a gear for Settings; below 66rem width the sections move into a menu opened from the header. Inside the web client, a side rail lists them. Ctrl+K (⌘K on macOS) opens a search over the engine’s lists, and ? opens the sheet of keyboard shortcuts.

A section appears only when the signed-in role may read it. The app reads the role from GET /me. Overview opens for any role that reads runs or workers.

Needs attention

Needs attention lists runs that failed unsafely and keep their locks, and runs rolling back. A run is in it when its status is FAILED_UNSAFE_ACK or ROLLBACK; FAILED_UNSAFE_ACK runs come first.

A FAILED_UNSAFE_ACK run keeps its CMS locks until a person remediates it. A ROLLBACK run leaves the list when the rollback ends. Every other status is In progress or Finished.

Needs attention shows in two places:

  • Overview leads with a Needs attention card. It lists the runs, then offline workers and function blocks that have pending jobs and no online worker. A new installation without workers, function blocks or workflows lists what is missing.
  • Runs has the views Needs attention, In progress, Finished and All, with a count on each. The list is ordered most urgent first. A run in a status that needs a person is solid red.

Run page

/runs/<id> shows one run on a single scrolling page. A prefix of the id in the URL resolves to the full id. While the tab is visible the page asks the engine again, often while the run is in flight and less often after it ends. If the engine stops answering, the page keeps the last data and marks it stale.

  • Header. The engine status, the reason, where a run in flight is, the submit time, the wait and run time, and the person who started it where the engine records one. A note names the run that keeps a lock this run waits for, and the runs that wait for this one’s locks.
  • Facts and parameters. The parameters are written whole, as the run received them.
  • Targets and locks. The targets as submitted, and the locks the workflow acquires.
  • Outcome. A run that did not end well gets a callout: what happened in one line, the engine’s words with a copy button, the devices, groups and interfaces whose locks stay held, and the next step. Held locks are named from the run’s context where the engine serves it. A card beside the parameters counts devices by state.
  • Steps. The steps of the workflow definition the run used. A deleted version still shows.
  • Devices. Once the engine serves the run’s jobs, each device with its state, the step where it ended, the worker’s message and the duration. Problems come first. A step opens to show its Result data, which the page reads from the blackboard when you open it. #job=<job id> in the URL opens that job’s device and step.
  • Logs. The run’s log lines from the engine, the workers and neops-core, oldest first, at a minimum level you choose. The Step tab reads one step of the run, chosen from the run’s steps; a #job=<job id> address adds a tab for that job, opens on it and puts its step on the Step tab. The job tab includes the lines of the earlier attempts the job retries, each marked earlier attempt. The panel appears only when GET /version reports logs.queryable, and shows a banner while the log store is unreachable or refuses the engine. It asks for new lines on the page’s cadence, a few seconds behind the producers (NEOPS_LOG_SETTLE_MS), and reads everything again on Reload, after a failed poll, when the log store recovers or its outbox drains, and when the run leaves RUNNING, so a line delivered late still appears. Pause holds the lines shown while new ones keep arriving, and says how many wait; Follow shows them. It holds the first 5,000 lines. Reading them needs workflow-execution-log:read. On the run tab, Step, Job type (acquire, execute or rollback, as the Jobs list names them), Entity type, Entity, Worker, Workflow, Function block and Job narrow the lines, and CMS lines and Engine lines leave neops-core’s or the engine’s own lines out; the choices are the values in the lines shown. A line names its step, function block, job type, entity, worker and logger. A line the log store could not take at the time its producer wrote it (a log store such as Loki keeps a line only within a window) sits at the time it was stored and says written with its producer’s time. Details opens the record’s keys and the fields its producer added. Hovering its source shows the full workflow and function block ids.

Run again opens the run dialog filled with the run’s workflow, targets and parameters. It is the primary action on a finished run. On a FAILED_UNSAFE_ACK run it is a quiet action while the engine holds the locks. On a run in flight it sits in the overflow menu. Copy link is in the same menu; the run id in the title copies the id. Abort is hidden while the engine cannot stop a run.

Workflow page

The Workflows list shows each published workflow. Open one to reach its version page (/workflows/<package>/<name>:<version>), which shows one published version:

  • Header: the name and the version. The version opens a menu of every version, the shown one checked. Beside it are the package, when the version was published and its last run. An older version names the latest and offers Compare with latest; a deleted version says so, and Restore brings it back for a role that may write workflows.
  • Inputs: each input with its type and what reads it: the steps, the Targets, or the workflow itself (its own condition, assert or parameters). A required input that nothing reads says No step · still required: every run must send it, and nothing uses it.
  • Steps: every step with its function block, its effect (Pure, Idempotent, or no tag for a block that is neither) and its type. The line beside the title says what a run locks and what the steps do to devices, for example Locks each picked device · 2 steps change devices · a failure once backup_before has started keeps the locks. A step that can’t be rolled back says so on its row; the ⓘ explains the effects and the FAILED_UNSAFE end. A workflow that acquires more than its seed shows a Targets section, whose plan line says what a run locks.
  • Versions: every version with when it was published and its last run. Compare sets a version beside the shown one; Compare two… picks any two.

Run… opens the run dialog on the shown version; on an older version the dialog names the latest version and offers to use it instead. Edit opens the version in the builder; publishing there creates a new version, since a published version never changes (see Publishing and versioning). A role that may not run sees Run… disabled with its reason, and Open in builder in place of Edit.

Comparing two versions

A comparison opens as the first section under the header and lives in the address (?compare=1.0.0..2.0.0), so a link to it opens it. Its verdict says what the changes ask of the version number and what the newer version is, for example Major change · v2.0.0 is a major version, as the changes ask; a version below the floor the changes ask carries a warning glyph. The change list groups the engine’s findings by workflow, inputs and step, each with its level (Major, Minor, Patch or Note). An edit the engine reports no finding for, such as an input’s description, is listed with No finding in place of a level and counted apart from the findings. Show YAML diff shows the two documents line by line.

Logs across runs

A run’s Logs section shows one run. To follow a function block or a workflow across every run, use the Logs section of its catalog page; to search every run by what the lines are about, use the Logs page (/logs). Both read GET /logs:

  • A function block fails since a release. Open the function block’s page, go to Logs, choose All versions and widen Window until the first failures show.
  • Which steps of a workflow warn. Open the workflow’s page, go to Logs, set the minimum level to Warning and pick the version.
  • What happened on one device. Open Logs, type the device’s CMS id in Entity and press Enter.
  • Every rollback of the last week. Open Logs, choose Job type rollback and widen Window to 7 days; name a workflow or function block as well to narrow it to one.

The catalog section opens on the page’s version and reads the newest lines first, over the last 24 hours. Window widens it to 7 or 30 days, and Step, Job type, Entity type and Entity narrow it. Each line links to its run. A full page of 1,000 lines offers Load older lines, which reads the 1,000 before the oldest one shown, up to 5,000; an empty list names the window it read. Reading it needs workflow-execution-log:read.

A typed value applies when you press Enter or leave the field. The Logs page reads nothing until a filter narrows the read: a workflow or function block (its full id package/name:x.y.z, or its name), an entity, a worker id or a job type. A level, a step or the CMS lines and Engine lines choices alone do not, and the engine refuses such a read (400 LOG_FILTER_REQUIRED), so the page asks for one first. A filter in the address is where it starts, for example /logs?entityId=21&jobType=rollback. A role without workflow-execution-log:read sees the page hidden, with the grant it needs.

A worker’s page shows the lines the worker wrote outside any job: startup, heartbeats, poll errors. Its Workflow and Function block filters (package, name, version) switch to the worker’s job lines across runs, which are run logs: they need workflow-execution-log:read as well as worker:read, and a role without it is told so.

Builder

The builder authors a workflow as a draft and publishes it as a new version. /workflow-builder?id=<package>/<name>:<version> opens a published version for editing. The create flow starts from a blank workflow, a starter (Reachability check, Config backup, Hello world) or a copy of an existing workflow.

The builder edits workflow inputs, targets and steps as a form. The YAML view shows the same document as YAML or JSON text. One undo history spans form and text. The YAML view takes the text of a whole workflow in either format. Without the workflow:write grant the builder opens read-only.

Drafts

Drafts live in the browser’s localStorage, under the keys neops.monitor.drafts:<engine address> and neops.monitor.draft-body:<engine address>:<draft key>. Each document has its own draft. A new workflow is kept as soon as it is created. An edit to a published version is kept once it differs from that version. A draft leaves when it is published.

  • A draft belongs to one browser and one engine address. Another browser, another profile or another engine address does not see it. Overview lists drafts under Unpublished changes.
  • Deleting a draft moves it to Trash, where it stays for 30 days. Restore and Delete now are in Trash.
  • The drafts record carries a format version. The monitor leaves a record it cannot read untouched and starts with no drafts, so a draft kept by an older monitor build may not carry over.

Publish review

Publish… (or Ctrl+S / ⌘S) opens the review. It needs the workflow:write grant.

  1. The review sends one dry-run publish to the engine. The engine returns the lowest version level the changes need, the next free version and its findings.
  2. You pick the level: patch, minor or major. The engine’s recommendation is marked. A level below the engine’s floor asks you to accept the bump.
  3. The review lists the changes from the published version and shows a line diff of the YAML.
  4. The checks show what the engine predicts for runs of the version. When a check predicts that every run fails at its start, the button reads “Publish vX anyway”.
  5. The confirm sends the publish. If someone published the version meanwhile, the review says so and offers a comparison. If the engine does not answer, the review says the publish may not have finished and rereads the versions before it sends again.

A published draft leaves the drafts list. If the licence allows no more workflows, the review says so.

Framed and standalone

The app runs in one of two shapes.

  • Framed. The app runs inside the Neops Web Client, in an iframe under the client’s /monitor route. A page is also framed when its URL carries ?embed=1. The frame draws no header, brand or identity, because the host supplies them. It draws the section list as a side rail, or as a bar under the host’s header. The rail folds into the bar when the frame is narrow. A host that draws the frame edge to edge declares full-bleed, and the app keeps its full page gutter; inside any other host, which pads the frame, the app keeps a narrow gutter. The theme is the host’s. The host’s address bar and tab title follow the app’s current page, and the host asks before it leaves a page where leaving loses work.
  • Standalone. The app opens directly in its own tab, with its own header, Help menu, notification bell and theme. ?embed=0 forces this shape.

Both shapes show the same pages. The web client and the app talk over postMessage, and the messages are the host contract. Their names begin with neops::

Direction Message Purpose
App to host neops:token-request Asks for a token, and with force asks the host to rotate first
Host to app neops:token, neops:logout Hands over the bare JWT with its expiry, and ends the session
App to host neops:location, neops:dirty Reports the current path and title, and whether leaving loses work
Host to app neops:navigate, neops:open Opens a page, the search or the shortcut sheet
App to host neops:notify Passes a notice to the host’s own stack

Only the token relay is required. A host declares anything else with neops:host, and a host that never sends it is treated as offering the token relay alone. The app accepts messages only from the origin in WEBCLIENT_ORIGIN.

Settings

/settings opens General. Each section is a page of its own. A change applies at once and shows an Undo.

Section Holds
General The start page (Overview, Runs or Workflows), the time zone (browser or UTC), the code format (YAML or JSON), and in a standalone tab the theme (system, light or dark). Framed, General has where the sections sit (side rail, section bar or the web client’s menu) and whether the rail stays expanded. It also shows who is signed in and what the role allows.
Notifications For each kind of news, whether it pops up, goes to the inbox only or stays off, split by your own runs and other people’s. Quiet hours, a preview over the last 24 hours of runs, and a reset to the instance defaults.
Licence Every licence cap with its current usage, read from GET /license.
Shortcuts The keyboard shortcuts, read-only.
About The engine, app and web client versions. A standalone tab adds read-only diagnostics.

General is stored in this browser. The Overview layout is stored per user and browser.

Technology

The Monitor App is built with Svelte and communicates with the engine via browser-side HTTP requests to the REST API. When started via Docker Compose, it runs as a static production build served by nginx. For local development (make run-monitor), it runs as a Vite dev server with hot-reload.


Next: Your First Workflow – deploy and execute a workflow end to end.