This is a status updating service to monitor the current uptime for the services we have running to track outages at a glance.
/– Overview dashboard with global status, aggregated uptime, and active incident count/#services– Per-service table with current status, 30‑day uptime, and last incident/#history– Incident history timeline showing recent incidents and affected services
Open index.html in a browser to view the current dashboard framework. Service data and incident history are mock values that can be wired to real uptime endpoints later.
The dashboard can talk to a small Node.js backend that probes your services and exposes JSON APIs.
-
Ensure you have a recent version of Node.js installed.
-
From the project root, create a
.envfile with your service URLs, for example:SVC_DEV_GATEWAY_URL=https://example.dev/gateway/status SVC_DEV_ARCHIVER_URL=https://example.dev/archiver/status # …other SVC_* variables as needed…
-
Start the backend server (matching the frontend default
API_BASEon port7070):PORT=7070 node backend-server.js
You should see output similar to:
Status backend listening on http://localhost:7070 -
Open
index.htmlin your browser. By default the frontend will send requests tohttp://localhost:7070for:GET /api/summary– current snapshot of all servicesGET /api/history?days=90&interval=5m– historical uptime data
If you run the backend on a different host or port, set window.LIBERDUS_STATUS_API before loading main.js so the dashboard points at the correct base URL.
In main.js the current network is selected via:
const CURRENT_NETWORK = "testnet"; // or "devnet"This value is passed as ?network=... to the backend APIs so the dashboard can focus on devnet, testnet, or any other environment you configure.
- Backend:
backend-server.js(Node.js + optional SQLite)- Probes each configured service on a schedule and computes:
- A per-service
state(operational,degraded, oroutage) - A global
indicatorandstatusDescriptionfor the whole system
- A per-service
- Exposes JSON endpoints:
GET /api/summary– current snapshot (services + overall indicator)GET /api/history– historical uptime by time bucketGET /health– simple health check for the status server itself
- Probes each configured service on a schedule and computes:
- Frontend:
index.html+main.js- Renders the Liberdus status dashboard in the browser.
- Periodically calls the backend to:
- Update the overview banner and per-service state.
- Draw uptime bar charts from the historical data.
- Pings
/healthevery 30 seconds to show if the status backend is online.
main.js picks the backend URL like this:
const API_BASE =
typeof window !== "undefined" &&
window.LIBERDUS_STATUS_API &&
typeof window.LIBERDUS_STATUS_API === "string"
? window.LIBERDUS_STATUS_API
: "http://localhost:7070";If window.LIBERDUS_STATUS_API is set before main.js loads, the frontend will use that as the base URL. Otherwise it defaults to http://localhost:7070.
For a long-running production deployment you can use pm2 to keep the backend alive and restart it on failure or reboot.
-
Install pm2 globally on the server:
npm install -g pm2
-
On the server, clone this repository and create your
.envfile with the real service URLs. -
Start the backend under pm2, choosing a port that will be exposed (for example
7070):cd /path/to/Status PORT=7070 pm2 start backend-server.js --name liberdus-status -
Check that it is running:
pm2 status pm2 logs liberdus-status
-
To have pm2 restart the process after a reboot:
pm2 save pm2 startup # follow the printed instructions once, then run `pm2 save` again if needed
With this setup the backend HTTP server will be accessible on:
http://SERVER_IP:7070(directly by IP and port), and- any DNS name (for example
status.example.com) that resolves toSERVER_IPand forwards traffic to port7070(usually via a reverse proxy such as Nginx or a load balancer).
To make the status page available at a friendly name:
- Create a DNS
AorAAAArecord:Arecord:status.example.com→SERVER_IP.- Or
AAAArecord if your server has IPv6.
- On the server, either:
- Expose the backend port directly (e.g. allow
7070in your firewall and security group), or - Put a reverse proxy in front (for example Nginx on port 80/443) that forwards requests to
http://127.0.0.1:7070.
- Expose the backend port directly (e.g. allow
If you serve the frontend (the index.html + main.js files) from the same domain as the backend, you avoid most CORS issues.
The backend APIs set the Access-Control-Allow-Origin header so browsers can call them from other origins:
/api/summary/api/history/health
By default, the backend is configured to allow all origins:
res.setHeader("Access-Control-Allow-Origin", "*");This is the simplest option and works when:
- You want to embed the status dashboard from multiple domains.
- You are not exposing sensitive data from the status API.
If you want to restrict access to a specific frontend origin (for example only https://status.example.com), update the backend responses to set:
res.setHeader("Access-Control-Allow-Origin", "https://status.example.com");In that case the browser will only accept responses when the page making the request is served from https://status.example.com. Make sure:
- The protocol, host, and port match exactly.
- You restart the backend after changing the header.
If you plan to call the backend from multiple known origins, you can implement a small whitelist in backend-server.js and choose the header based on req.headers.origin.
At the lowest level each probe of a service produces:
state:"operational","degraded", or"outage".healthPct: a numeric health score.
The backend follows these rules for each probe:
- Query the configured endpoint.
- If there is no response (timeout, connection error, etc.) →
state = "outage",healthPct = 0.
- If the endpoint responds, run the configured checker:
checker: "statusEquals":- Compares the JSON
statusfield toexpectedStatus.
- Compares the JSON
checker: "cycleFreshSeconds":- Reads the cycle start timestamp (for example
cycleInfo[0].start). - Computes how old it is and compares it to
maxAgeSeconds(typically 120s).
- Reads the cycle start timestamp (for example
- If the checker reports bad data (missing status, mismatched status, or stale cycle) →
state = "degraded",healthPct = 20.
- If the checker reports good data:
- If the response took more than 1 second:
state = "operational",healthPct = 80(slow but not degraded).
- If the response took 1 second or less:
state = "operational",healthPct = 100.
- If the response took more than 1 second:
When the backend stores history, it converts healthPct into a daily/bucketed state using these thresholds:
< 10→down< 50→issue< 95→slow- otherwise →
up
The historical API returns per-bucket successPct values based on those rules. The frontend:
- Uses
classifyStateFromPct(successPct)to convert the percentage into"up","partial"or"down". - Colors bars and calendar cells based on that state:
< 10→ red.< 50→ orange.< 95→ orange (slow/partial).≥ 95→ green.
This keeps backend scoring and frontend coloring aligned on a simple 0 / 20 / 80 / 100 model while only treating invalid or stale data as degraded.
Each service that the backend probes ends up with a state:
77→- operational – checks are passing and data is fresh.
78→- degraded – checks are failing (wrong status, missing fields, or stale data).
outage– endpoint is unreachable, missing configuration, or consistently failing.
The backend combines these into an overall indicator:
computeIndicator(services)inbackend-server.jscounts:outageCount– services in stateoutagedegradedCount– services in statedegradedtotal– total number of services
- Rules:
- If more than half of all services are
outage→ indicator =major. - Else if any service is
outageordegraded→ indicator =minor. - Else → indicator =
none.
- If more than half of all services are
- That indicator is mapped to a human description:
none→All Systems Operationalminor→Partial System Outagemajor→Major Service Outage
The frontend uses a mirrored rule in summarizeOverallStatus to compute:
status–operational,degraded, oroutagestatusLabel– text likeAll Systems OperationalorPartial System Outage
The top banner:
- Uses
snapshot.statusDescriptionfrom/api/summarywhen available. - Falls back to its own
statusLabelif the backend did not provide one. - Applies CSS classes:
degradedwhen overall status is degraded.outagewhen overall status is a major outage.
This keeps the UI and backend in sync and makes sure “Major Service Outage” is only shown when a majority of services are fully down.
The frontend runs two different timers:
- Every 5 minutes: calls
/api/summaryand/api/historyto refresh:- The overview banner.
- Per-service status and uptime history charts.
- Every 30 seconds: calls
/healthto confirm that the backend is reachable.
When /health fails:
- The footer shows
Status server is downand turns it red.
When /health starts succeeding again after failures:
- The footer switches to
Status server: online. - The frontend immediately triggers a data refresh, so you do not have to manually reload the page after bringing the backend back up.
From GET /api/summary:
- An object with:
services: array of{ id, name, environment, group, state, ... }indicator:none|minor|majorstatusDescription: optional banner text (used when present)generatedAt: ISO timestamp for “Last updated” in the footer
From GET /api/history:
- An object with:
days: number of days includedintervalMinutes: size of each bucket in minutesservices: array of{ id, history }, wherehistoryis an array of:{ date, successPct, state }for each time bucket.
From GET /health:
-
A simple JSON payload like:
{ "status": "ok", "generatedAt": "2026-01-27T12:34:56.000Z" }
The important part is that it returns a 200 OK with the CORS header so the frontend can tell the backend is alive.
The backend tracks services via the SERVICES array in backend-server.js:
const SERVICES = [
{
id: "dev-gateway",
name: "Gateway",
environment: "devnet",
group: "Devnet",
url: process.env.SVC_DEV_GATEWAY_URL,
checker: "statusEquals",
expectedStatus: "online",
},
// ...
];To add a new service:
-
Duplicate one of the existing entries in
SERVICES. -
Change:
id– a unique identifier for this service (no spaces).name– label that will appear in the frontend.environment– e.g."devnet","testnet","shared".group– grouping label, such as"Devnet","Testnet","Core".url– endpoint the backend should probe.checkerand its config:checker: "statusEquals"withexpectedStatus, when the endpoint returns JSON like{ "status": "online" }.checker: "cycleFreshSeconds"withmaxAgeSeconds, when the endpoint exposes cycle info and you want to ensure the latest cycle is recent.
-
Add the corresponding environment variable in
.env, for example:SVC_NEW_SERVICE_URL=https://example.test/new/status
-
Restart the backend:
PORT=7070 node backend-server.js
To remove a service:
- Delete its entry from the
SERVICESarray inbackend-server.js. - Optionally remove the related environment variable from
.env. - Restart the backend.
Once the backend is restarted:
- The new services will appear in the frontend automatically.
- Their status will be included in the overall banner and in the category/group they match.
Categories in the frontend are inferred from service names:
- Names containing
"gateway","archiver","explorer","monitor","notification","faucet","oauth", or"golden"are grouped under the matching category (Gateways, Archivers, Explorers, Monitors, Notification, Faucet, OAuth, Golden Ticket). - If a service name does not contain any of these keywords, it is ignored for category-level grouping but still counted for the overall status.