The Monitoring page
The Monitoring page is a single scrollable view with five cards. This page walks each one — what every column means, what numbers are healthy, when to act.
Reach the page from the 📊 Monitoring entry in the left sidebar (visible to superusers + roles granted monitoring:view).
At a glance
KPI strip
Four numbers at the top. Each is a quick read on a single dimension:
| KPI | What it counts | When to worry |
|---|---|---|
| Uptime | How long the framework process has been running. | If it resets unexpectedly, the framework crashed and restarted (or systemd / Docker re-pulled it). Check the logs around the restart timestamp. |
| Connected users | Sessions currently signed in (have a fresh access token). | Normal install: low single digits. A jump (a hundred at once) might mean a load test or a script with a leaked token. |
| Held locks | Row-level locks the framework holds — one per opened edit dialog. | If a lock is held for hours and the user has left, the dialog is stale. Either the user reopens and saves/closes, or you can release manually (see Held locks). |
| Pool connections | Total in-use connections across every pool. | Should stay well below the sum of pool sizes. If you regularly hit the limit, raise pool sizes or investigate slow queries. |
The KPIs refresh every few seconds via the framework's Socket.IO channel — no page reload needed.
Pools
One row per SQL pool the framework knows about. Pool config comes from Settings → Pools; this card is the live view of each.
| Column | Notes |
|---|---|
| Pool | The pool's name (the key under [pools.*] in the connectors file). |
| Dialect | postgresql, oracle, mysql, mssql, sqlite, db2 — what SQLAlchemy reports for this engine. |
| In use | Connections currently checked out of the pool by a running query. |
| Idle | Connections that are open but free to serve the next query. |
| Overflow | n/max — extra connections beyond pool size, opened temporarily when in-use hits the limit. A persistent non-zero overflow means the pool is under-sized. |
A pool shown as not opened (lazy) hasn't been used yet — the engine isn't materialised until the first query hits it. That's normal and saves resources; it doesn't mean misconfiguration.
What to do on a saturated pool
| Symptom | Action |
|---|---|
in use = pool size, overflow growing. | Either bump the pool size (Settings → Pools → <pool> → Pool size), or investigate slow queries — a stuck query holds a connection. |
| Many idle connections. | Pool is over-sized; you can shrink it to save DB-side memory. Not urgent. |
not opened on a pool that should be active. | The connector hasn't been used yet. Open a screen / dashboard that uses it; the pool materialises. |
Connected users
One row per currently-signed-in user.
| Column | Notes |
|---|---|
| User | The username. Superusers get a super chip. |
| Session | The Socket.IO session id (first 8 characters, the rest hidden — abbreviated for legibility). |
| Client | The client id from the JWT (also abbreviated). Identifies the browser tab — one user may have multiple sessions across tabs. |
The card is the live answer to "who's currently in?" — the framework's user store has timestamps for last sign-in, but this card shows right now.
When to investigate
| Pattern | What it usually means |
|---|---|
| Same user with many sessions. | The user has many browser tabs open. Usually harmless. |
| A user signed in days ago, still listed. | Their access token is still valid (the default TTL is 1 hour for the access token, 14 days for the refresh) — they renewed silently. Disabling the user in Settings → Access takes effect on their next refresh. |
| The list is empty. | Nobody is signed in. (Or there's a problem — verify by signing in yourself.) |
Held locks
One row per row-level lock the framework is holding. A lock is soft — it doesn't block the database; it blocks the UI. When user A opens row 14 for edit, user B sees the row marked locked and can't open the same edit dialog. The lock is released on dialog close (save or cancel) or after a TTL.
| Column | Notes |
|---|---|
| User | Who holds the lock. |
| Record | <app>.<screen> · <key>=<value> — which row, on which screen of which app. |
| Since | How long the lock has been held. Format: 12 sec ago, 3 min ago. |
Released automatically
A lock is released by:
- The user clicking Save or Cancel on the dialog.
- The user closing the browser tab (the framework detects the lost Socket.IO connection).
- The lock TTL expiring (default 1 hour) — the framework drops orphaned locks.
Manual release
For genuinely stuck locks (the user is gone, the lock TTL hasn't fired):
liberty-admin lock release --app crm --screen customers --key CUSTOMER_ID=14
This drops the lock immediately. The next user can open the dialog. Use sparingly — releasing a lock while the original user is mid-edit silently overwrites their work.
Logs (live)
The bottom card streams the framework's last few hundred log lines from the in-memory ring buffer.
| Element | What it does |
|---|---|
| Time | Server-side timestamp (HH:MM:SS.mmm). |
| Level | INFO / WARNING / ERROR / CRITICAL — colour-coded (green / yellow / red / dark red). |
| Name | The Python logger name (liberty.connectors, liberty.jobs, nomaflow.python, your plugin's logger). |
| Message | The log message. |
Controls
| Control | Effect |
|---|---|
| Pause | Stops auto-scroll. Stream continues; you can scroll back without the view jumping. Resume re-engages auto-scroll. |
| Level filter (All / Info / Warn / Err) | Hide lines below the chosen level. Err shows only ERROR + CRITICAL. |
| Text filter | Substring match on logger name + message. Case-insensitive. |
| Clear filter | Resets both filters. |
| Trash icon | Empties the client-side buffer (the server buffer is untouched — refresh and lines re-arrive). |
The stream is client-side bounded at ~1000 lines. Older lines fall off as new ones arrive. For historical logs beyond what the buffer holds, look at the framework's log file on disk (/var/log/liberty/app.log by default, or wherever your logging config points).
Permissions
The log viewer is superuser-only — the framework returns a Log viewer requires superuser access banner if a non-superuser opens the page. Logs frequently contain internal details (SQL fragments, function arguments, user emails) that aren't safe for general operator viewing.
Refresh interval
The page refreshes its data via Socket.IO at these cadences:
| Card | Refresh trigger |
|---|---|
| KPIs, Pools, Connected users, Held locks | Every ~5 seconds on a server tick, and immediately on user sign-in / sign-out / dialog open / dialog close events. |
| Logs | Push-based — every log line emitted by the framework streams to the page in real time (subject to the 1000-line client buffer). |
No manual reload button — the page is meant to stay open in an operations tab.
What this page is NOT
- Not a historical view. A process restart wipes Locks, Connected users and the Log buffer. For long-term retention, route logs to an external system + use the framework's audit tables.
- Not a per-query performance view. For slow-query investigation, enable Postgres'
pg_stat_statementsor wire an APM. - Not a resource usage view. Container CPU / RAM live in
docker stats/ Portainer / Kubernetes; OS-level metrics live in your monitoring agent.
The Monitoring page answers "what's the framework doing right now?". For "what did it do yesterday?" you need the layers below.
What's next
- Overview — when to look at Monitoring vs external systems.
- Health endpoints — the machine-readable side.