Status Pages
A status page shows a client's services to the people who depend on them: at
<slug>.prxm.uz, behind a private link, or only inside Console. It is built from
uptime monitors and monitor groups, and from
maintenance windows marked Show on status page.
Find it under Uptime → Status pages. You need status_pages:read on the whole client to
see a page, and status_pages:write on the whole client to change one. A role granted on only
some of a client's environments sees no status pages at all; see Permissions.
The backend ships with PROXIMA_STATUS_PAGE_DOMAIN empty. Until an operator completes the
rollout runbook (docs/runbooks/status-pages-rollout.md), nothing is served at *.prxm.uz.
The Console screens, Preview and the Console login audience all work without it.
Console says so: a Public or Private link page shows Public address not configured on this deployment where its address would be (never "Console only", which it is not), Settings → Access carries a notice that public serving is off, and a private link minted meanwhile is shown as its bare key, which works once the domain is set.
What visitors see
Every page has three tabs: Status, Maintenance and Previous incidents.
- Status shows:
- the headline, which is the worst state of any component;
- a card for every open incident and every running maintenance window;
- the next window, if one starts within 7 days;
- the components, with 30, 60 or 90 daily bars;
- the incidents that ended in the last 7 days.
- Maintenance shows three months of windows. The arrows move three months at a time.
- Previous incidents shows three months of incidents, newest first.
On a phone (≤ 640 px), the page keeps the same structure: the tabs scroll sideways, the bars show the last 30 days, and the cards stack.
Pages never run JavaScript. Analytics tags and chat widgets cannot work on them.
Component states
| Monitor or group | Shown as |
|---|---|
| up | Operational |
| up, but failing in some locations or rate-limited (429) | Degraded performance |
| down | Outage |
| maintenance | Under maintenance |
| paused, pending, never checked | No data, or Operational with Show days with no data as green |
Each component can override two of these rules under Advanced:
- show an outage as Degraded;
- never show Degraded.
An outage is never hidden entirely.
With no component, or none that has data, the headline is Status is not available yet, never "All services are online".
Bars and uptime
There is one bar per day, in the page's time zone. A day's colour is:
- red when an outage touched it;
- amber when a degraded period did;
- blue for maintenance;
- green when it was checked;
- grey when nothing was checked.
A failed check that never became an incident (it did not pass the monitor's own quorum and retries) lowers the uptime % but does not colour the day.
Uptime % leaves maintenance checks out and is rounded down. So 100% means every check
passed.
Day cards. Hovering a bar, or tapping it on a phone, opens a card for that day:
- the date and the day's status ("No downtime recorded" on a quiet day);
- how long the component was down or degraded that day, counted once where incidents overlap;
- the day's uptime %, from the same checks as the page figure, or nothing when no check ran;
- what happened, oldest first: each incident's times, and each maintenance window shown on the page that covered the component. At most four are listed, then "+N more". A window planned for later today is not on the card yet; the bar is history.
The card is plain CSS, so the page still runs no JavaScript. Days with an outage, a degraded period or maintenance are keyboard stops, and their card is read out as the bar's name. Quiet days open on hover and tap but are skipped by Tab, so a keyboard user is not walked through ninety identical days per component.
The card counts a short incident that Hide incidents shorter than leaves out of the lists, because the bar it explains is coloured by it.
Incidents
Incidents come from the monitors' own history; nobody writes them.
- Merging. Incidents on components of one page are one public incident when they overlap, or when one starts no more than 5 minutes after the previous one ended. Its title names up to three components.
- Live card. An open incident reads "Detected — {names} isn't responding." or "Detected — {names} is slower than usual." It does not say that anyone was alerted: paging is off by default on every monitor, so that would not be true.
- Resolved. A resolved incident reads "Resolved — unavailable for {duration}." or "Resolved — slower than usual for {duration}."
- Short incidents. An incident shorter than Hide incidents shorter than is left out of the lists and the live card. It still counts in the bars and the %.
Visitors never see a monitor name, URL, host, location, error or alert. They see only the public names you give components. That is why a new component starts with an empty public name, and why a page cannot be saved until every component has one.
Building a page
-
Create status page. Pick the client, the company name and the subdomain, which is checked as you type. Renaming the subdomain later frees the old address at once, with no redirect.
-
Structure. Add sections and their components. A blank section name renders with no heading. Each component is a monitor or a monitor group of the same client. Drag to reorder. Adding a component needs
monitors:readon its environment. A component that is already on the page can be kept, renamed, moved or removed without it, even when you cannot see its source; adding a new one you cannot read is refused with403 component_monitors_read_required. -
Settings → Access. Choose Public, Private link or Console login, add an optional IP allowlist, and switch Published on. A draft shows "Page not found" at its address, for everyone.
If someone else saved the page's settings after you opened them, your save is refused (
409 stale_page) instead of overwriting theirs. Your edits stay in the form; Reload loads the latest settings and replaces them. A structure save or a key rotation does not count as a change to the settings. -
Preview shows the page as visitors will see it, drafts included.
Preview
Preview renders the page in a sandboxed frame. Scripts, same-origin access, pop-ups and top-level navigation are all off. The page's CSS and images are inlined.
Preview uses your system font, not the page's Public Sans. The layout, colours and content match the public page, but the text looks slightly different. This is a known gap.
Private links
The key is shown once:
- when the page is created as private;
- when it is switched to private;
- when the key is rotated.
Copy it then. Console stores only its SHA-256 hash, so it cannot show it again.
Rotate key invalidates the old link at once. Links and images on a private page carry the
key, but the page never sends it to other sites: it is served with Referrer-Policy: no-referrer.
Look & feel
Custom CSS is served as a separate stylesheet, never inside the page. These are refused, with the reason shown in the editor:
</stylein any letter case;@import,expression(,behavior:,-moz-bindingandjavascript:;- any
url(),src()orimage-set()/image()string that is nothttps:ordata:image/; var()insideimage-set(),image()orsrc();- a backslash before a line break, outside a string;
- invisible or space-like Unicode characters (such as a no-break space or a zero-width space) outside strings and comments. One byte order mark at the very start is removed, as a browser does, so CSS saved by a Windows editor is accepted;
- a symbol or punctuation character (such as
×,—or★) directly beforeurl(: browsers disagree on whether it ends the word beforeurl, so add a space; - NUL characters and text that is not valid UTF-8;
- more than 20,000 characters.
Escapes and comments do not hide a refused token: @\69mport and ex/**/pression( are refused
like the plain spelling.
Line endings are converted to line feeds before the checks, and the converted text is what is
saved. Images from other https: sites are allowed.
CSS saved under an older, looser check is re-checked whenever the page is built. If it fails, the live page is served without it, and the backend logs a warning with the page's ID.
Target the documented classes: .sp-header, .sp-announcement, .sp-banner,
.sp-incident, .sp-maintenance, .sp-section, .sp-component, .sp-component-name,
.sp-bars, .sp-history and .sp-footer.
Header and footer HTML keep only:
- the elements
p br strong em b i u a ul ol li span div h3 h4 img hr small; classon any of them;- links to
https:ormailto:addresses, which always getrel="noopener noreferrer nofollow"; https:images, with optionalalttext.
Anything else is removed when you save, and the form lists what was removed. The HTML is sanitized again every time the page is rendered.
Announcement is markdown with bold, italics and links.
Logos and favicon can be PNG, SVG, ICO or WebP. A logo can be up to 256 KB and the favicon up to 64 KB. The type is decided from the file's content, not its name. An SVG is rebuilt from a list of safe shapes; scripts, event handlers and external references are dropped.
Maintenance
The Maintenance tab lists the client's windows that cover at least one component of the
page. Listing them needs maintenance:read on the whole client.
Each row has the window's own Show on status page switch. It is not a per-page setting: it applies to every status page of the client that the window covers.
- Turning it on needs
maintenance:writeon the window, andstatus_pages:writeon the whole client, because it publishes the window. - Turning it off needs only
maintenance:write. - A monitor's own shortcut window (scheduled from the monitor's page) is internal: its title
carries the monitor's name. It can never be shown on a status page. The switch is disabled for
it, and the API refuses it with
400 shortcut_window_internal.
The same switch is in the maintenance form. Schedule maintenance on this tab opens that form with the page's components already picked as targets. Changes reach the public page within about a minute: the backend drops its cached copy at once, and Cloudflare may serve its own copy for up to 30 seconds and then revalidate in the background for 30 more.
Addresses
A subdomain is 1 to 40 lowercase ASCII letters, digits and hyphens. It cannot start or end with a
hyphen. It cannot have hyphens as both its 3rd and 4th characters: RFC 5891 reserves that form,
and it is how xn-- (punycode) look-alike names are spelled.
These subdomains are reserved:
- anything ending in
-console(Console's own hosts) or-rke2(cluster nodes); - the
*.prxm.uzinfrastructure hosts known when the feature shipped, such asinstall,grafana,bin,registry,gitlab,vault,teleportandnats; - common service names:
www,status,mail,admin,api,app,console,docs,help,support,static,assets,cdn,authandlogin; - brand and phishing-bait names, such as
proxima,proximaops,prxm,sso,id,account,billing,pay,portal,vpn,secureandsignin; - anything an operator lists in
PROXIMA_STATUS_PAGE_RESERVED.
The full list is in backend/internal/statuspage/slug.go. It is not a live DNS inventory: a host
added to the zone later is not reserved until it is added there or to
PROXIMA_STATUS_PAGE_RESERVED.
The live availability check tells anyone who can create a page whether a subdomain is taken by any client. That is inherent to one shared namespace, and the check is rate-limited.
Only plain ASCII hosts are served. A host that contains the status domain but cannot be read cleanly, such as one with a stray port or brackets, gets the same "Page not found" as an unknown page. It never reaches the API.
Troubleshooting: "Page not found"
Every refusal looks the same on purpose, so that a page's existence is never revealed. Check, in order:
- The page is Published.
- The audience is not Console login.
- A private link carries the current key.
- The visitor's IP is in the allowlist, if there is one.
PROXIMA_STATUS_PAGE_DOMAINis set on the backend.
Caching and load
- Public pages are sent with
Cache-Control: public, max-age=30, stale-while-revalidate=30, so Cloudflare can absorb a spike. - Private-link pages and pages with an IP allowlist are sent with
private, no-store, and are never cached at the edge. - The backend caches each page's data for 30 seconds, or for 10 seconds per replica when Valkey is down. Any edit in Console drops that copy at once, so an edit reaches visitors within about a minute: up to 30 seconds of the edge's copy plus up to 30 seconds of revalidation. After a Valkey error the backend skips Valkey for 10 seconds, so a hung Valkey delays one request, not every one.
- Logos, favicons and the page stylesheet are served at content-hashed URLs with
immutableand a one-year lifetime. A logo that was public stays fetchable from the edge at its URL after the page goes private, is unpublished or is deleted; to withdraw an image at once, purge it from Cloudflare. - Each visitor IP may make 120 requests a minute.
Permissions
status_pages:read and status_pages:write are client-wide permissions. Every check on a
page uses the client-wide form (CanClientWide), because a page may show monitors from any of
the client's environments. An environment-scoped grant of either permission authorizes nothing
on status pages: such a user does not see the page list or the editor.
status_pages:readis seeded to every role that holdsmonitors:read.status_pages:writeis seeded to Super Admin and Platform Admin.
status_pages:read is a tenant-scoped permission, so a tenant role (Tenant Viewer, Tenant
Engineer) may hold it. status_pages:write is platform-scoped: a tenant role cannot hold it,
and the role editor refuses to add it to one.
A component's internal source name (the monitor or group behind it) is shown in the editor only
to users with monitors:read on that component's environment.
API
| Method | Path | Permission (client-wide unless noted) |
|---|---|---|
| GET | /api/v1/status-pages | status_pages:read; lists only the clients where it is held |
| GET | /api/v1/status-pages/slug-available?slug= | status_pages:write on at least one client; rate-limited |
| GET, POST | /api/v1/clients/{id}/status-pages | read / write |
| GET, PUT, DELETE | /api/v1/clients/{id}/status-pages/{pid} | read / write / write; PUT sends the updated_at it loaded (409 stale_page when the page changed since) |
| GET, PUT | …/{pid}/structure | read / write, plus monitors:read on the client and on the environment of every component the save adds |
| GET, POST, DELETE | …/{pid}/assets/{kind} | read / write / write |
| POST | …/{pid}/link-key | write; returns the new key once |
| GET | …/{pid}/render?tab=&from= | read |
| GET | …/{pid}/maintenance | read, plus maintenance:read |
| PUT | /api/v1/clients/{id}/maintenance-windows/{windowID}/status-page | maintenance:write on the window; turning it on also needs status_pages:write |
A page of another client under the path is a 404.
Deleting a page frees its address at once. The page itself is soft-deleted: its sections, components, custom HTML and CSS and uploaded images stay in the database, and nothing purges them yet. A deleted page no longer ties its monitors to their client.
Metrics
proxima_status_page_requests_total{result,tab}, proxima_status_page_render_seconds and
proxima_status_page_snapshot_cache_total{result}. See
Metrics collected.