The operator console
The FerroBRIDGE Console is a separate binary, ferrobridge-console, that
serves a web console in front of the bridge. Its server calls the bridge
over the bridge’s public HTTP surface with the signed-in operator’s own
access token, so the bridge checks that token as it checks any other
client’s. No specification governs the console: it is FerroBRIDGE’s own
design.
Configuring and checking it
The console reads its configuration the way the bridge does, through the
FerroHEALTH family’s loader: its defaults, the file --config or
FERROBRIDGE_CONSOLE_CONFIG names, FERROBRIDGE_CONSOLE__<SECTION>__<KEY>
variables over it, and --set key=value pairs last. A secret is set inline
or through its <key>_file sibling, whose trailing line ending is removed.
[telemetry] is the family’s console section, and RUST_LOG sets its
filter. ferrobridge-console config check prints the running configuration
with every secret as *** and exits 78 when the console would not start.
ferrobridge-console healthcheck asks the console on loopback for
GET /health/readiness and exits 0 only on 200; the image’s HEALTHCHECK
runs it.
Signing in
Operators sign in at your OpenID Provider. The console is an OAuth 2.0
client that uses the authorization code grant with PKCE (S256, RFC 7636)
and checks the ID Token against the provider’s JWK Set, its issuer, its
audience and the sign-in’s nonce (OpenID Connect Core 1.0 §3.1.3.7).
Register the console at the provider with:
- the redirect URI
https://<console>/auth/callback; - the post-logout redirect URI, when you set one;
- a client secret, when you run the console as a confidential client.
Then configure [oidc]:
[oidc]
issuer = "https://idp.example.org/realms/ferrobridge"
authorization_endpoint = "https://idp.example.org/realms/ferrobridge/protocol/openid-connect/auth"
token_endpoint = "https://idp.example.org/realms/ferrobridge/protocol/openid-connect/token"
jwks_uri = "https://idp.example.org/realms/ferrobridge/protocol/openid-connect/certs"
client_id = "ferrobridge-console"
client_secret_file = "/run/secrets/console-client-secret"
redirect_uri = "https://console.example.org/auth/callback"
offline_access = true
end_session_endpoint = "https://idp.example.org/realms/ferrobridge/protocol/openid-connect/logout"
post_logout_redirect_uri = "https://console.example.org/"
The access token the provider issues is the one the bridge receives, so its
audience and scopes must be ones the bridge’s [auth] accepts, and the
operator API needs the operator scope (see
The operator API).
Sessions
The session lives on the console’s server. The browser holds one opaque
HttpOnly, SameSite=Lax cookie, under the __Host- prefix, and never a
token. With offline_access = true the provider issues a refresh token,
and the console refreshes the access token on its server shortly before it
expires. A refresh the provider refuses ends the session, and the operator
signs in again; a provider that does not answer leaves the session for the
next request to try again. [session] bounds the pools and sets the
timeouts:
| Key | Default | Meaning |
|---|---|---|
secure_cookie | true | Cookies carry Secure and the __Host- prefix; false only for a console on http loopback |
sign_in_timeout_s | 300 | How long a started sign-in waits for the provider |
max_sign_ins | 1000 | Pending sign-ins held at once; a new one drops the oldest |
idle_timeout_s | 1800 | How long a session lives without a request |
absolute_timeout_s | 43200 | How long a session lives at most |
max_sessions | 10000 | Sessions held at once; a sign-in past it is refused |
Sessions are held in the console’s memory, so a restart signs every operator out, and replicas behind a load balancer need sticky sessions.
The console’s pages
A signed-in operator reads a top bar with the search, the bridge’s status
and the user menu, and a sidebar grouped Overview, Mappings, Bridge and
Console (a drawer on a narrow screen, a rail when you fold it). The status
is a word as well as a colour: Bridge up with the version beside it,
Bridge refused 401 when the bridge refuses the operator’s token,
Bridge unreachable, or Bridge unknown with the reason for any other
answer. The Mapping set entry counts the errors of the running load.
The user menu holds Your preferences, the theme and Sign out.
The search finds contexts by name or resource type, mapping files by path and templates by id, and never takes a patient identifier. As you type, a panel opens under the field with the matches grouped by kind, five of each at most; the arrow keys walk them, Enter opens the chosen one, and Enter with none chosen, or “See all results”, opens the search page, which lists every match in the page.
The console draws every page on its server and sends it whole: a link, the search, a tab or an action loads one complete page, and the browser’s back and forward buttons walk the pages you visited. A few small scripts add behaviour on top, such as the editor’s highlighting as you type, the search’s suggestions and the menus and dialogs, and every page and every action works without them: each action is a plain form post that the console answers with the page it drew.
- Dashboard (
/): the running load’s contexts, its errors and warnings, the upstreams the bridge reaches, what needs attention, and the flow from the callers through the faces and the mapping set to the upstreams (GET /health/info,GET /operator/readinessor, with the operator API off,GET /health/readiness,GET /operator/state). - Mapping set (
/mappings): the load report and five tabs. Contexts lists each context with its resource type, template, models, search declaration, owner and state; OMOCL, Templates, Files and Diagnostics live at/mappings/omocl,/mappings/templates,/mappings/filesand/mappings/diagnostics. The Files tab filters by path, language and owner, and pages its rows. More holds Reload the set, which asks first, then compiles the files on disk and replaces the running set when they compile. - A mapping file (
/mappings/files/{path}): the file as the bridge serves it, byte for byte, with numbered lines, highlighted YAML, its SHA-256 and the diagnostics beside it, each linked to its line. The Program tab (?tab=program) shows what the context compiled to. - A template (
/mappings/templates/{id}): where the template came from and the contexts that name it. - The editor (
/mappings/edit/{path}, or/mappings/newfor a new file): see below. - Playground (
/playground,/playground/toopenehr,/playground/validate): runs$tofhir,$toopenehrand$validateas the operator, and keeps nothing. - FHIR facade (
/facade,/facade/capability,/facade/security,/facade/settings): the facade’s state, the resource types itsCapabilityStatementserves, the SMART discovery document, and its settings. - HL7 v2 (
/hl7v2,/hl7v2/settings) and OMOP ETL (/omop,/omop/settings): whether the MLLP listener runs and where, whether an ETL is configured, and each face’s settings. - Connections (
/connections,/connections/{name},/connections/{name}/settings): each upstream with its host, how it signs in, the lanes that use it and its state from the bridge’s readiness, and its settings with its credentials by reference. - Configuration (
/configuration,/configuration/mappings,/configuration/core): the settings no face or connection owns: logging, the mapping directories, and the server and who may call it. - Your preferences (
/preferences,/preferences/language,/preferences/keyboard): appearance, language and region, and keyboard hints. - About (
/about): the console’s and the bridge’s versions, the licence and the maker, Cadasto B.V.
The editor changes the console’s own mapping files, which live under the
bridge’s [operator] mapping_dir:
- Check compiles the whole set with your draft in place of the file and lists every diagnostic. Nothing is written.
- Save needs a one-line reason and a confirmation in the bar at the
foot of the page. The bridge compiles the set with the draft again and
writes the file only when it compiles, then reloads, so a refused draft
leaves the disk and the running set as they were. The save carries the
digest the file was opened at: when someone changed the file in the
meantime, the bridge refuses with
412and your draft stays in the editor. The draft is saved with LF line ends, whatever line ends the browser posts.
A provisioned file opens for a check only, or to save under a path of its own: the console never saves over one. The bridge’s audit log names who saved which file, the two digests and the reason, never the content.
What you enter in the playground goes to the bridge in the request body
alone, never in an address or the browser’s history, and the console logs
none of it. A $toopenehr answer shows the composition, with the issues
of the OperationOutcome the bridge returned beside it above the first
line. When the bridge’s operations lane is off, the playground says so and
its run button is disabled.
The mapping counts, the Mapping set, the files and the editor read the
operator API, so they need [operator] enabled = true on the bridge and
an operator token that carries the operator scope. Without either, those
sections say the operator API is off, or show the bridge’s refusal with
its status, while the rest of the page still draws.
Every Settings tab and the Configuration page read
GET /operator/configuration, the configuration the bridge started on,
so they are read-only: each row shows its value, its TOML key on hover,
and whether the value came from the environment or is the default. A
secret shows only whether it is set and the file it is read from, never
its value. How configuration works, at the end of the line that names the
file, says where each setting is shown and that a change is made in the
file and takes effect at the next start.
Your preferences keeps each choice in a cookie of its own for a year
(ferrobridge_console_theme, _density, _motion, _language,
_week-start and _hints, under the __Host- prefix when the cookies
are secure), and the server draws each page in them, so nothing is kept
in the browser’s storage. Reset to defaults clears them all. The theme
offers light, dark and system, which follows the operating system, and is
set in the user menu and in Your preferences alone.
Every screen
Each screen below is shown in the light theme at 1440 pixels wide, with a link to the same screen in the dark theme. The browser journeys take them against a running bridge with a synthetic mapping set, so what you read is what the console draws.
Signing in and out
The sign-in card a visitor reads. Dark theme.
The card a signed-out operator reads. Dark theme.
Dashboard
The dashboard with the flow. How it is drawn, in the flow’s head, opens a short dialog with each state’s line and what it means. Dark theme.
Mapping set
The Mapping set on its Contexts tab. Dark theme.
The OMOCL tab. Dark theme.
The Templates tab. Dark theme.
The Files tab with its filters. Dark theme.
The Diagnostics tab. Dark theme.
The set after a reload. Dark theme.
A provisioned context mapping file with its diagnostics. Dark theme.
The program a context compiled to. Dark theme.
A model mapping file. Dark theme.
A template and the contexts that name it. Dark theme.
The editor
A new console file. Dark theme.
A provisioned file, open to check or to save under a path of its own. Dark theme.
A draft the set does not compile with, and its save blocked. Dark theme.
The confirmation a save asks for. Dark theme.
A draft the bridge refused to save, with the running set unchanged. Dark theme.
A draft the bridge saved and reloaded the set with. Dark theme.
Playground
The playground on its $tofhir tab. Dark theme.
The $toopenehr tab. Dark theme.
The $validate tab. Dark theme.
A $toopenehr the bridge answered, with its outcome’s issues.
Dark theme.
A $tofhir the bridge refused, with its status.
Dark theme.
A bridge whose operations lane is off. Dark theme.
Bridge
The FHIR facade. Dark theme.
The resource types the facade serves. Dark theme.
The SMART discovery document. Dark theme.
The facade’s settings. Dark theme.
The HL7 v2 face of a bridge that runs no listener. Dark theme.
The HL7 v2 face’s settings. Dark theme.
The OMOP ETL of a bridge with no ETL configured. Dark theme.
The OMOP ETL’s settings. Dark theme.
The upstreams. Dark theme.
The openEHR CDR. Dark theme.
The openEHR CDR’s settings, its credentials by reference. Dark theme.
Console
Configuration, Logging. Dark theme.
Configuration, Mappings. Dark theme.
Configuration, Server and access. Dark theme.
Your preferences, Appearance. Dark theme.
Your preferences, Language and region. Dark theme.
The search page, every match of the words searched for, one card per kind. Dark theme.
The About page. Dark theme.
The page for a path the console does not serve. Dark theme.
Signing out
Sign out, in the user menu, is a POST to /logout. It ends the session
and, when end_session_endpoint is set, sends the browser to the provider
with the ID Token hint (OpenID Connect RP-Initiated Logout 1.0); without
one it lands on /signed-out. The console takes a POST only from its
own pages, by the browser’s Sec-Fetch-Site header, or by Origin when
the browser sends no fetch metadata.
Upgrading from ferrobridge-viewer
Up to 0.0.10 the console was named ferrobridge-viewer. From 0.0.11 it is
ferrobridge-console, and the old image is no longer built. When you
upgrade, change each old name to the new one:
| What | Up to 0.0.10 | From 0.0.11 |
|---|---|---|
| Image | ghcr.io/ferrohealth/ferrobridge-viewer | ghcr.io/ferrohealth/ferrobridge-console |
| Binary | ferrobridge-viewer | ferrobridge-console |
| Configuration file variable | FERROBRIDGE_VIEWER_CONFIG | FERROBRIDGE_CONSOLE_CONFIG |
| Environment prefix | FERROBRIDGE_VIEWER__ | FERROBRIDGE_CONSOLE__ |
| Compose profile and service | viewer | console |
| Compose secret file | secrets/viewer_oidc_client_secret | secrets/console_oidc_client_secret |
| Cookies | ferrobridge_viewer_* | ferrobridge_console_* |
The cookies change with the name, so every operator signs in again after the upgrade.
The 0.0.11 console is also drawn anew on FerroUI 0.3.6, and its addresses moved with it. A bookmark to an old address answers the page-not-found page; follow its links, or use the new address:
| Up to 0.0.10 | From 0.0.11 |
|---|---|
/files | /mappings/files |
/files/{path} | /mappings/files/{path} |
/edit | /mappings/new |
/edit/{path} | /mappings/edit/{path} |
/validate | /playground/validate |
/capability | /facade/capability and /facade/security |
The theme toggle left the top bar: set the theme in the user menu or in
Your preferences. Each preference, the theme among them, is kept in a
cookie of its own for a year, so every operator chooses the theme again
after the upgrade. A sign-out with no end_session_endpoint lands on /signed-out
instead of the sign-in.