The console
The FerroTERM Console is the browser interface of a FerroTERM deployment: a
dashboard of what the server serves, listings and detail screens for its code
systems, value sets and concept maps, a runner for each terminology operation,
and editors for the resources your deployment writes. It is its own program
and its own image, ferroterm-console, which you run beside the server. It is
one of the FerroHEALTH consoles and looks like the others: the same shell,
navigation, tables and forms, in FerroTERM’s teal.
The console renders every page on its own server and talks to FerroTERM over the public FHIR API, server to server. Anything the console does, any other FHIR client can do. The terminology server serves no user interface itself.
Running it
With the quickstart, the console profile starts the console beside the
server:
$ docker compose --profile console up
$ open http://localhost:3000
Run the binary yourself with a configuration file:
$ ferroterm-console serve --config console.toml
$ ferroterm-console config check --config console.toml
$ ferroterm-console healthcheck
The configuration is a TOML file, named by --config or
FERROTERM_CONSOLE_CONFIG. Every key can be set from the environment as
FERROTERM_CONSOLE__<SECTION>__<KEY>, for example
FERROTERM_CONSOLE__FERROTERM__BASE_URL, --set key=value sets one over
both, and every secret also reads from a file through its _file sibling,
whose trailing line ending is dropped. An unknown key, or a
FERROTERM_CONSOLE__ variable that names none, is refused at start.
The [telemetry] section is the one the server reads
(Telemetry): format and filter set the
console log, and RUST_LOG sets the filter too.
[server]
listen = "0.0.0.0:3000"
[ferroterm]
# Where the console reaches the server, on your private network.
base_url = "http://ferroterm:8080/"
# The address readers see the FHIR API at, shown on the screens.
public_url = "https://terminology.example.org/"
# The synchronisation service's admin listener, which Synchronisation reads.
sync_url = "http://ferroterm-sync:8090/"
# The server's admin listener, which Reload the served set posts to.
admin_url = "http://ferroterm:8081/"
timeout_ms = 30000
[session]
secure_cookie = true
idle_timeout_s = 1800
absolute_timeout_s = 28800
max_sessions = 1000
[oidc]
issuer = "https://idp.example.org/realms/ferroterm"
authorization_endpoint = "https://idp.example.org/realms/ferroterm/protocol/openid-connect/auth"
token_endpoint = "https://idp.example.org/realms/ferroterm/protocol/openid-connect/token"
jwks_uri = "https://idp.example.org/realms/ferroterm/protocol/openid-connect/certs"
client_id = "ferroterm-console"
client_secret_file = "/run/secrets/console-client-secret"
redirect_uri = "https://console.example.org/auth/callback"
scopes = ["openid", "fhirUser", "user/CodeSystem.cud", "user/ValueSet.cud", "user/ConceptMap.cud", "__ferroterm.sync", "__ferroterm.admin"]
end_session_endpoint = "https://idp.example.org/realms/ferroterm/protocol/openid-connect/logout"
revocation_endpoint = "https://idp.example.org/realms/ferroterm/protocol/openid-connect/revoke"
post_logout_redirect_uri = "https://console.example.org/"
Signing in
Reading is open: a visitor reads every screen, because the server’s read
surface asks for no token. Signing in is for editing. Without an [oidc]
section the console offers no sign-in and no editing screen.
With one, the top bar gains Sign in. The console signs you in at your
identity provider with the authorization code flow and PKCE and keeps the
session on its own server, behind one cookie the browser cannot read; the
browser never holds a token. Register the console as a client at the provider
with the redirect address https://your-console/auth/callback and the scopes
above, and point the terminology server at the same issuer with
FERROTERM__AUTH__ISSUER and the audience the provider puts in the console’s
tokens with FERROTERM__AUTH__AUDIENCE
(Configuration), so it accepts the tokens
the console presents. A provider that types its access tokens JWT rather
than at+jwt (RFC 9068) needs FERROTERM__AUTH__TOKEN_PROFILE=jwt. A role that
gets none of the three user/ scopes reads the server and changes nothing;
the menu under your name says what you may edit.
Sign out ends the session, revokes its token where the provider publishes a revocation endpoint (RFC 7009), and takes you to the provider’s end-session endpoint when one is configured.
The screens
Every screen is drawn whole on the console’s server and works with scripting
off; FerroUI’s script modules add the theme menu, the copy buttons, the
concept tree’s keys and the keyboard shortcuts on top. / focuses the
command bar, ? opens the list of shortcuts, g then a letter opens a
screen of the navigation (g c code systems, g v value sets, g m concept
maps, g b the concept browser, g o operations, g s the server, g d
the dashboard), Ctrl Enter runs an operation or the composer’s preview from
inside a field, and Ctrl S saves an editor. Keyboard shortcuts under
your name lists them at their own address. The top bar carries the command bar, the FHIR
version you are reading through, the server’s health, and the menu under your
name with the theme. The version menu keeps the version for your session, so
every screen you open next reads the same root. An address that names a
version with ?fhir= reads that version for that page.
From the second character you type into the command bar, a panel under it
shows the code systems, value sets and concept maps whose name or canonical
holds the words, five of a kind, and the screens that take what you typed.
The arrow keys move through it, Enter opens a hit and Escape closes it. The
panel asks GET /find/answer?q=, which answers as JSON and says when a read
the server refused leaves the matches short. Submitting the command bar
opens Find, which lists every match in the page.
Dashboard
What the server serves on the version in view: the number of code systems, value sets and concept maps, the writes it recorded most recently, the first code systems, and the way into each operation.
Code systems
Every code system the root declares in its TerminologyCapabilities, with its
source, the versions it holds, its content mode and whether it reads as a
hierarchy. Sort by a column and page through; past ten rows, find by name or
canonical and keep the sources you tick in the Source menu. Each row opens the code
system’s own screen, whose tabs are links: Overview (its definition from
the published resource), Versions (what this server declares it can do
with each version), History for a resource this server stores, and
JSON, the resource as the server sent it.
Concept browser
Search a code system, read a concept with $lookup, and walk its hierarchy one
level at a time, where its version declares the child-of filter operator.
Every move is in the address, so a walk is a link you can send.
Value sets and concept maps
The resources the root publishes. A value set opens on Definition (what its definition selects), Expansion (the codes it expands to, a page at a time) and JSON; a concept map on Mappings (every mapping it writes, group by group), Overview and JSON.
The operations
Look up a code ($lookup), check a code against a code system or a value set
($validate-code) and how two codes relate ($subsumes), expand a value set
($expand) a page at a time, and translate a code through the concept maps
($translate). Each runner offers the canonicals this root publishes, keeps
its parameters in the address, and draws the answer as a result panel, with a
refusal shown in the server’s own words.
Server, Configuration and About
Server shows the four FHIR roots side by side, read from each root’s
CapabilityStatement, the operations each root declares, and the
conformance and benchmark evidence this build ships. Configuration lists
the settings the console runs with, secrets redacted, a value an
environment override sets marked From the environment and one nothing
sets marked Default, and How configuration works, at the end of the line that names the file, opens a dialog on where
they come from and how you change them. When the console was started with a
configuration file, More in the title row offers Copy the file’s path,
the path shown under it. About names the
build, the licence and the projects it stands on.
With admin_url set and a token that carries __ferroterm.admin, More
on Server offers Reload the served set. It asks first, then posts the
server admin listener’s POST /reload, and the page says what the server
answered: the new set is in front of readers, or the old one stays and the
reason is shown.
Your preferences
Open from the menu under your name: the theme and the density, reduced motion, the default FHIR version, the display language concept names are asked for in, the page size (25 rows unless you choose another) and the keyboard hints. The console keeps each in a cookie of its own for your browser and draws the next page in it. A choice saves as you make it, and the display language and the page size save when you press Enter or leave the field changed (from 1 to 1000 rows); with scripts off, each has a Save button.
Editing
Signed in with the write scopes, the Code system editor, the Value set
composer and the Concept map editor write local resources through the
same REST API any client uses. Each is a plain form: Save posts it, the
console sends the resource, and the page says which version the server wrote.
Every save states If-Match, so a resource someone
changed since you opened it is refused with 412, your draft stays in the
form, and the screen offers to reload the current version. Content your
server serves from its loaded indexes has no write path, so it opens to read
and offers no save.
Composing a value set
Signed in with user/ValueSet.cud, the Value set composer
screen builds a local ValueSet.compose and saves it through the same REST API
any client uses.
A definition is a set of clauses. Every include adds to the selection and the
value set is the union of them all; the criteria inside one include are taken
together, so a code has to satisfy all of them; an exclude takes codes back out
of that union (https://hl7.org/fhir/R4B/valueset.html#compositions). The
screen says which of those each clause is doing, in a sentence under it, and
names the invariant a clause breaks by its own number: vsd-1 for a clause
that selects nothing, vsd-2 for codes or filters with no code system, vsd-3
for codes and filters at once.
A clause draws in a value set your server already publishes, picked from the
list, or any canonical you type, including a form a code system defines for
itself. It selects out of a code system with the filter properties and
operators that system’s served version declares, which the screen reads from
TerminologyCapabilities: a system declaring an expression-constraint filter
offers it here, a system declaring none offers none, and a clause pinned to a
code system version is offered that version’s filters. A filter value the
server refuses is shown with the character its diagnostic points at marked.
Own codes are typed one per row, and each one can carry the display this value set gives it; the preview shows any code the code system does not hold.
Run the preview expands the definition on screen, saved or not: it posts it
to ValueSet/$expand in the valueSet parameter and pages the answer
(https://hl7.org/fhir/R4B/valueset-operation-expand.html). The page and the
text filter are in the address, so a preview is a link you can send.
Save sends the resource with If-Match, so a value set someone else
changed since you opened it is refused with 412 and the screen offers to reload
it (https://hl7.org/fhir/R4B/http.html#concurrency). An update replaces the
whole resource, so the save carries back every element the form does not draw,
from the description to a code’s own designations
(https://hl7.org/fhir/R4B/http.html#update). Every refusal is shown in the
server’s own words. Content your server serves from its loaded indexes has no
write path, so it opens here to read and offers no save under any role.
History and restoring a version
A resource this server stores has a History tab, which opens /history on
it. The screen lists the revisions the server holds, newest first, with when
each was written and the interaction that wrote it where the answer states
one.
How the list is read depends on what your server declares in its
CapabilityStatement. A server declaring history-instance answers the whole
list at once. FerroTERM declares vread, so the console reads each earlier
revision back one request at a time, counting down from the revision the
resource states, and the screen says so. Only the 20 most recent revisions are
listed.
Compare takes an earlier and a later revision and lists the elements they
disagree about: the path, whether it was added, removed or changed, and both
values. It compares the two documents structurally, so what you read is which
elements moved. meta.versionId and meta.lastUpdated are left out, because
your server assigns both on every write. The two revisions are in the address,
so a comparison is a link you can send.
Restore on an earlier revision asks first, in a dialog that names the
revision, and then writes that revision’s whole resource back as a new one.
It states If-Match of the revision the server holds now, so a change someone
else made since you opened the screen is refused with 412 and the screen
offers to reload the revisions. The page then says which revision the restore
wrote. Restore appears only for an account whose token carries the write scope
for the resource type; everyone else reads the same revisions and restores
nothing.
Synchronisation
With sync_url set and a token that carries the
synchronisation service’s product scope (__ferroterm.sync), the
Console group of the navigation has Synchronisation. It lists the runs the
service made, newest first, read from its admin listener: when each started,
what started it, how it ended, what it did and how many findings it made.
A run opens on Summary: its sources, the releases it took, what it left behind, the activation and the revalidation of your own resources, with Download the record for the whole run record as JSON. Findings lists what the release did to the codes your own resources name, each row linked to the resource.
Run now asks first and then posts the service’s POST /run. The service
answers once the run has finished, which can take as long as a build, and the
list then names the run with a link to it. Activate what is staged, under
More, posts POST /activate the same way. Neither needs a script. A
reader without the scope, or a console without sync_url, has no
Synchronisation entry and its address is not found.
Deleting a value set
A value set stored on this server offers Delete this value set last under
More, for a token that opens delete on value sets
(user/ValueSet.d, SMART App Launch scopes). It asks first, then sends the
server a FHIR DELETE (https://hl7.org/fhir/R4B/http.html#delete), and the
value set listing says it is gone. Its revisions stay in the server’s history.
About these screenshots
Each image is a capture of a running console over a running server, taken by
a browser driven through WebDriver, over the synthetic fixture in
e2e/fixtures/codesystems. The repository distributes no SNOMED CT content and
no derived edition data, so no licensed release is ever the subject of one.
The browser battery’s script refreshes them when asked, and then runs only the capture:
$ scripts/ui-e2e.sh --docs-shots
--review DIR captures every screen at desktop and phone width in both
themes, with the hover, focus and open states, into DIR for a visual review
of a change. With --pseudo-locale as well, the review draws every screen in
en-XA, a pseudo-locale written from the English catalog with every message
40 percent longer, accented and bracketed, so the capture shows where a
screen has no room for a longer translation. The console’s words come from
one catalog per language, so a language joins as one catalog file. Without
either flag the battery changes no file in the checkout, which is why an
ordinary run on a pull request never rewrites an image here.