mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-11-02 19:02:31 +00:00
docs: review Monitor documentation in all seven translations
This commit is contained in:
@@ -19,7 +19,7 @@
|
||||
"reaching": {
|
||||
"heading": "Reaching the dashboard",
|
||||
"intro": "ProxMenux Monitor binds to <code>0.0.0.0:8008</code>. There are three common ways to reach it:",
|
||||
"outro": "Direct access matches what the systemd unit ships out of the box. The reverse-proxy and Secure Gateway sections below cover the other two. The Monitor honours <code>X-Forwarded-For</code>, <code>X-Forwarded-Proto</code> and <code>X-Forwarded-Host</code> so URLs and CORS work behind any of them without manual configuration."
|
||||
"outro": "Direct access matches the default systemd unit. Reverse-proxy and Secure Gateway access are described below. Trust for client IPs in <code>X-Forwarded-For</code> requires PROXMENUX_TRUST_PROXY=1 and a proxy you control. The presence of <code>X-Forwarded-Proto</code> or <code>X-Forwarded-Host</code> alone does not enable proxy trust; the frontend builds API URLs from the browser’s origin."
|
||||
},
|
||||
"firstLaunch": {
|
||||
"heading": "First-launch flow",
|
||||
@@ -32,7 +32,7 @@
|
||||
"rows": [
|
||||
{
|
||||
"button": "Yes, Setup Password",
|
||||
"what": "Opens a form with the mandatory username + password and an optional <em>display name</em> + <em>avatar image</em>. Stores them in <code>auth.json</code> with <code>enabled: true</code>. Returns a JWT so you're logged in immediately. The form is documented in detail below.",
|
||||
"what": "Opens a form requiring a username and password, with optional <em>display name</em> and <em>avatar image</em>. Enables authentication in <code>auth.json</code> with <code>enabled: true</code> and returns a JWT to log you in. Optional profile fields are saved by subsequent calls, as documented below.",
|
||||
"api": "POST /api/auth/setup"
|
||||
},
|
||||
{
|
||||
@@ -103,12 +103,12 @@
|
||||
"what": "Removes the avatar. The header falls back to the initial-on-coloured-circle placeholder."
|
||||
}
|
||||
],
|
||||
"reversibleTitle": "Continuing without protection is reversible — but only from the host",
|
||||
"reversibleTitle": "Continuing without protection is reversible",
|
||||
"reversibleBody": "Once you click <em>No, Continue Without Protection</em>, the welcome dialog never appears again. You can re-enable authentication from <strong>the Security tab</strong> inside the dashboard, or by editing <code>/root/.config/proxmenux-monitor/auth.json</code> and removing the <code>declined</code> flag, then restarting the service."
|
||||
},
|
||||
"password": {
|
||||
"heading": "Password authentication",
|
||||
"intro": "After Set up, every API call (except the few public endpoints listed below) requires a JWT in <code>Authorization: Bearer <token></code>:",
|
||||
"intro": "After setup, protected API calls require a JWT in <code>Authorization: Bearer <token></code>:",
|
||||
"items": [
|
||||
"<strong>Session token (login):</strong> 24-hour expiration. Issued by <code>POST /api/auth/login</code>.",
|
||||
"<strong>API token (integrations):</strong> 365-day expiration. Issued by <code>POST /api/auth/generate-api-token</code>. Documented separately in the next section."
|
||||
@@ -117,11 +117,11 @@
|
||||
"loginImageCaption": "Once authentication is configured, every visit to the dashboard starts here. With 2FA enabled, the screen asks for the 6-digit code in a second step after the password is accepted.",
|
||||
"loginFlowTitle": "Login flow",
|
||||
"twofaIntro": "With 2FA enabled, the same call returns <code>requires_totp: true</code> first. Re-issue with the 6-digit code:",
|
||||
"publicTitle": "Public endpoints (no token)",
|
||||
"publicIntro": "These are the only endpoints that work without authentication, even when auth is enabled:",
|
||||
"publicTitle": "Public access and protected probes",
|
||||
"publicIntro": "Login and authentication status are public. Initial setup is available while no username and password are stored, including after declining protection. System-data probes require authentication when protection is enabled:",
|
||||
"publicItems": [
|
||||
"<code>/api/auth/login</code>, <code>/api/auth/status</code>, <code>/api/auth/setup</code> — the auth flow itself, by necessity.",
|
||||
"<code>/api/system-info</code> — lightweight system snapshot (hostname, uptime, <code>health.status</code>). The right endpoint for external probes (Uptime Kuma, load-balancer health checks, status pages)."
|
||||
"<code>/api/auth/login</code> and <code>/api/auth/status</code> are public. <code>/api/auth/setup</code> creates the initial account and rejects attempts to overwrite existing credentials.",
|
||||
"<code>/api/system-info</code> is a public lightweight system-information endpoint, including <code>health.status</code>. It is not decorated with the token requirement. /api/auth/status is another public endpoint suitable for a reachability check."
|
||||
],
|
||||
"cryptoTitle": "Cryptography and storage",
|
||||
"cryptoIntro": "ProxMenux Monitor is open source — none of this is secret. Documenting the stack here explicitly is a deliberate choice: operators who store credentials on their host deserve to know how those credentials are protected before they decide to trust them. The algorithms below are the same ones the code in <code>scripts/auth_manager.py</code> uses; this section is a contract, not a marketing promise.",
|
||||
@@ -146,7 +146,7 @@
|
||||
},
|
||||
{
|
||||
"asset": "2FA TOTP secret",
|
||||
"algorithm": "Standard TOTP (RFC 6238) base32-encoded. Backup codes are pre-generated, single-use, hashed with the same PBKDF2 scheme as the password.",
|
||||
"algorithm": "Standard TOTP (RFC 6238), with a base32-encoded secret. The eight recovery codes are single-use and stored as SHA-256 hashes.",
|
||||
"where": "<code>auth.json</code> → <code>totp_secret</code> + <code>backup_codes[]</code>"
|
||||
},
|
||||
{
|
||||
@@ -170,7 +170,7 @@
|
||||
"heading": "Two-Factor Authentication (TOTP)",
|
||||
"intro": "2FA adds a second factor on top of your password: a 6-digit code that rotates every 30 seconds, generated on a phone or password manager you control. Even if someone obtains the password, they still can't log in without the code from your device. ProxMenux Monitor implements the standard <strong>TOTP</strong> protocol (RFC 6238), so any authenticator app works.",
|
||||
"pickTitle": "Pick an authenticator app",
|
||||
"pickIntro": "If you already use one for Google / GitHub / your bank, that one will work — skip to the setup walkthrough. If not, here's a survey of common options. All of them are free; the differences are mainly about which platforms they run on and how (or whether) they back up your secrets.",
|
||||
"pickIntro": "If you already have a TOTP app, you can use it here. Common choices are listed below. Some are free and others require a subscription; supported platforms and backup methods also differ.",
|
||||
"headerApp": "App",
|
||||
"headerPlatforms": "Platforms",
|
||||
"headerAppNotes": "Notes",
|
||||
@@ -190,8 +190,8 @@
|
||||
{
|
||||
"name": "Authy",
|
||||
"href": "https://authy.com/",
|
||||
"platforms": "iOS, Android, desktop",
|
||||
"notes": "Multi-device encrypted sync (the desktop app is being phased out — check the latest status)."
|
||||
"platforms": "iOS, Android",
|
||||
"notes": "Encrypted sync across devices. The desktop app reached end of life on 19 March 2024 and is no longer supported."
|
||||
},
|
||||
{
|
||||
"name": "Apple Passwords",
|
||||
@@ -203,7 +203,7 @@
|
||||
"name": "Bitwarden",
|
||||
"href": "https://bitwarden.com/",
|
||||
"platforms": "iOS, Android, desktop, browser",
|
||||
"notes": "Open source. TOTP lives next to the password it protects (handy if you also use BW for passwords; defeats \"separate device\" if you don't)."
|
||||
"notes": "Open source. TOTP can be stored alongside passwords in the vault. Keeping both in the same vault is convenient, but does not provide separation onto a second device."
|
||||
},
|
||||
{
|
||||
"name": "1Password",
|
||||
@@ -238,21 +238,21 @@
|
||||
{
|
||||
"name": "FreeOTP+",
|
||||
"href": "https://github.com/helloworld1/FreeOTPPlus",
|
||||
"platforms": "Android, iOS",
|
||||
"notes": "Open source (Red Hat-led). Minimal — no cloud, no account."
|
||||
"platforms": "Android",
|
||||
"notes": "Open-source fork of FreeOTP for Android, with import/export features. No account required."
|
||||
}
|
||||
],
|
||||
"backupTitle": "What \"backup\" really matters for",
|
||||
"backupBody": "If you lose the device that has the authenticator on it, the only ways back in are (1) a backup code saved when you enabled 2FA, or (2) a backup of the authenticator's vault. Apps with cloud sync (Google Auth, Microsoft Auth, Authy, Apple Passwords, Ente, 2FAS, Bitwarden, 1Password) can restore on a new device. Apps without cloud (Aegis, Raivo, FreeOTP+) need an encrypted export file you've copied somewhere safe. Either approach works — the bad case is \"no backup at all\".",
|
||||
"backupBody": "If you lose the authenticator device, use a saved recovery code or restore the app from a backup you configured beforehand. Cloud sync and local exports differ by app; check that your chosen method actually restores the TOTP secrets. Root access to the host also allows recovery, as explained below. Keep a separate, secure backup.",
|
||||
"setupTitle": "Step-by-step setup from the dashboard",
|
||||
"setupImageAlt": "2FA setup screen with QR code and backup codes",
|
||||
"setupImageCaption": "The 2FA setup dialog — QR code, Base32 secret (for manual entry), and the ten one-time backup codes. The codes are only displayed here; if you close the dialog without copying them, they're gone.",
|
||||
"setupImageCaption": "The 2FA setup dialog shows a QR code, a Base32 secret for manual entry, and eight one-time recovery codes. Copy the codes before closing the dialog; they are not displayed again.",
|
||||
"setupSteps": [
|
||||
"<strong>Install the authenticator app on your phone</strong> (or open your password manager). One of the apps from the table above. You only need to do this once — the same app will hold codes for every service you protect.",
|
||||
"<strong>Log into the dashboard</strong> with your username and password.",
|
||||
"<strong>Open the Security tab</strong> in the dashboard sidebar, then click <strong>Enable 2FA</strong>. A dialog opens with a QR code, a long string in Base32 format and ten short codes labelled \"backup codes\".",
|
||||
"<strong>Open the Security tab</strong> in the dashboard sidebar, then click <strong>Enable 2FA</strong>. A dialog shows a QR code, the Base32 secret and eight recovery codes.",
|
||||
"<strong>Add the entry to the authenticator app:</strong>",
|
||||
"<strong>Save the backup codes.</strong> Copy the ten codes somewhere safe — a password manager, an encrypted note, a printed copy in a drawer. Treat them like spare keys: each works exactly once and gets you in if your phone is gone or broken.",
|
||||
"<strong>Save the recovery codes.</strong> Store all eight safely in a password manager, encrypted note or secure printed copy. Each works once if your authenticator is unavailable.",
|
||||
"<strong>Confirm by typing the current 6-digit code</strong> from the app into the \"Verification code\" field of the setup dialog and submit. Codes refresh every 30 seconds, so if it expires while you're typing, just enter the next one.",
|
||||
"<strong>Done.</strong> 2FA is now active. Next time you log in, the dashboard asks for the password first; once it's accepted it asks for the current 6-digit code."
|
||||
],
|
||||
@@ -261,17 +261,17 @@
|
||||
"<em>Manual fallback</em> (when scanning isn't possible — e.g. setting up on the same phone you opened the dashboard with): tap <em>Add account</em> → <em>Enter setup key</em>. Type any name (e.g. <em>Proxmox Monitor</em>), paste the Base32 string from the dialog, leave <em>Type</em> as <em>Time-based</em>, save."
|
||||
],
|
||||
"testTitle": "Test before logging out",
|
||||
"testBody": "Once you click Save, log out and log back in <em>immediately</em>, while the setup dialog is still fresh in your mind. If the code is rejected (clock-skew between server and phone is the most common cause), you can still fix it from the open session. Logging out without testing first means a one-trip-no-return — at that point only a backup code or editing <code>auth.json</code> on the host gets you back in.",
|
||||
"testBody": "Keep your current session open and test a new login in a private window <em>immediately</em> after setup. If the code is rejected, check the server and phone clocks before logging out of the working session. If access is already lost, use a recovery code or edit <code>auth.json</code> from the host shell.",
|
||||
"lostTitle": "Lost authenticator",
|
||||
"lostIntro": "Three escape hatches, in order of how disruptive they are:",
|
||||
"lostItems": [
|
||||
"<strong>Use a backup code.</strong> At the login screen, in the TOTP field, type one of the ten codes you saved at setup time. Each works once and is then consumed; the remaining codes still work. Once you're in, regenerate 2FA from Settings to get a fresh ten.",
|
||||
"<strong>Use a recovery code.</strong> Enter one of the eight saved codes in the TOTP field. It works once; unused codes remain valid. After logging in, set up 2FA again from Settings to obtain eight new codes.",
|
||||
"<strong>Restore the authenticator from cloud / backup.</strong> If your app has a cloud sync (Google, Microsoft, Authy, Apple Passwords via iCloud Keychain, Ente, 2FAS) install it on a new device, sign in, and the entries reappear. If your app uses an encrypted export file (Aegis, Raivo, FreeOTP+), install the app on the new device and import the file.",
|
||||
"<strong>Disable 2FA from the host shell.</strong> When the previous options aren't available, edit <code>/root/.config/proxmenux-monitor/auth.json</code> on the Proxmox host (you need root SSH or console access), set <code>totp_enabled</code> to <code>false</code>, save, and restart the service:"
|
||||
],
|
||||
"lostShellOutro": "You can log in with username + password only, then re-enable 2FA from Settings.",
|
||||
"disableTitle": "Disable 2FA",
|
||||
"disableBody": "From the dashboard, open the <strong>Security</strong> tab and click <strong>Disable 2FA</strong>. The endpoint <code>POST /api/auth/totp/disable</code> requires the current password as confirmation, then deletes the TOTP secret and clears the backup codes. Remember to also remove the entry in the authenticator app — the app doesn't know the server side is gone, so the dead entry will sit there forever otherwise.",
|
||||
"disableBody": "Open <strong>Security</strong> and choose <strong>Disable 2FA</strong>. <code>POST /api/auth/totp/disable</code> requires the current password and a valid TOTP or recovery code. It removes the TOTP secret and recovery codes. Remove the old entry from your authenticator app too.",
|
||||
"rejectedTitle": "The 6-digit code is always rejected",
|
||||
"rejectedIntro": "TOTP is time-based — server clock and phone clock must agree to within ~30 s. Two checks:",
|
||||
"rejectedItems": [
|
||||
@@ -310,31 +310,31 @@
|
||||
"intro": "Two paths to TLS:",
|
||||
"items": [
|
||||
"<strong>Reverse proxy (recommended).</strong> Terminate TLS on Nginx / Caddy / Traefik and forward HTTP on port 8008 to the Flask process. Snippets below.",
|
||||
"<strong>Direct HTTPS in the AppImage.</strong> Configure a certificate via <code>POST /api/ssl/configure</code> (UI: <strong>Settings → SSL</strong>). When SSL is configured the process switches from Flask's dev server to <code>gevent.pywsgi</code> with the gevent-websocket handler so WebSocket terminal also works over WSS. The cert files live wherever you point them; the paths are stored in the SSL config."
|
||||
"<strong>Direct HTTPS in the AppImage.</strong> Configure a certificate with <code>POST /api/ssl/configure</code> (UI: <strong>Settings → SSL</strong>). The HTTPS path uses <code>gevent.pywsgi</code> with its standard handler; flask-sock handles WebSocket upgrades for WSS. Certificate paths are stored in the SSL configuration."
|
||||
],
|
||||
"calloutTitle": "Direct HTTPS limitations",
|
||||
"calloutBody": "The bundled gevent path is suitable for self-signed or LAN-only certificates. For Let's Encrypt / ACME and automatic renewal, run a real reverse proxy in front — Caddy auto-renews and Traefik / Nginx have well-known patterns. The Monitor doesn't implement ACME on its own."
|
||||
"calloutBody": "The Monitor can use a Proxmox-managed certificate or custom certificate files. It does not issue ACME certificates itself. For automatic issuance and renewal, use Proxmox ACME or a reverse proxy such as Caddy, Traefik or Nginx, and ensure renewed certificates are loaded."
|
||||
},
|
||||
"gateway": {
|
||||
"heading": "Secure Gateway (Tailscale)",
|
||||
"intro": "Reverse proxies are the classic answer to \"reach the dashboard from outside\" but they require a public domain, certificate, and an open port on the edge. <strong>Secure Gateway</strong> is the zero-port alternative shipped inside the Monitor itself — a pre-built deployable app that spins up an Alpine LXC running <a>Tailscale</a> as a subnet router. Once joined to your tailnet, every device on it can hit the Monitor at the host's own LAN IP — from a laptop on holiday, a phone on 5G, or another node — without exposing TCP 8008 to the internet.",
|
||||
"intro": "A reverse proxy is one way to provide remote access. <strong>Secure Gateway</strong> offers access without exposing TCP 8008: it deploys an Alpine LXC running <a>Tailscale</a> as a subnet router. Access to the host’s LAN IP depends on approved subnet routes and tailnet access rules.",
|
||||
"calloutTitle": "Why this is convenient",
|
||||
"calloutBody": "The URL stays the same as on the LAN — <code>http://<proxmox-lan-ip>:8008</code> works everywhere Tailscale works. No certificates, no DNS, no port forwarding. The Monitor itself sees the request as coming from a tailnet IP (typically <code>100.x.y.z</code>), so the auth log and the Fail2Ban hook still function as on the LAN.",
|
||||
"calloutBody": "Use the LAN URL <code>http://<proxmox-lan-ip>:8008</code> through the approved subnet route. Tailscale subnet routers use SNAT by default, so the Monitor normally sees the gateway’s LAN address, not the client’s <code>100.x.y.z</code> address. Logs and Fail2Ban therefore identify the source address the host actually receives.",
|
||||
"deployBody": "The deploy flow is one screen — pick the host LXC storage, paste a Tailscale auth-key (generated at <a>login.tailscale.com/admin/settings/keys</a>), choose which subnets to advertise, click Deploy. The LXC takes ~30 seconds to bootstrap and registers in the tailnet automatically.",
|
||||
"outro": "Step-by-step deployment, subnet-routes configuration, Tailscale ACLs and Exit Node mode are documented separately in <link>Dashboard → Security → Secure Gateway</link> — that's where the deploy wizard lives in the dashboard UI. This page only covers the access pattern."
|
||||
},
|
||||
"proxy": {
|
||||
"heading": "Reverse proxy snippets",
|
||||
"intro": "The simplest layout is a <strong>dedicated host name</strong> for the Monitor (e.g. <code>monitor.example.com</code>) pointing at port 8008 on the Proxmox host. Snippets below use that pattern. Sub-path mounts (<code>example.com/proxmenux-monitor/</code>) are possible but require extra rewriting and are not the default — see the callout at the end.",
|
||||
"intro": "Use a <strong>dedicated host name</strong> such as <code>monitor.example.com</code> forwarding to port 8008 on the Proxmox host. The examples follow this pattern. A sub-path such as <code>example.com/proxmenux-monitor/</code> needs additional frontend and proxy work; it is not supported by prefix stripping alone.",
|
||||
"nginxTitle": "Nginx",
|
||||
"caddyTitle": "Caddy",
|
||||
"traefikTitle": "Traefik (labels — Docker / Kubernetes)",
|
||||
"subPathTitle": "Advanced: sub-path mounts under an existing domain",
|
||||
"subPathBody": "If you don't want a dedicated host name, you can mount the Monitor under a path on an existing domain — for example <code>example.com/proxmenux-monitor/</code>. The Next.js build uses relative asset paths so static files resolve, but the proxy must <strong>strip the prefix</strong> before forwarding so the Monitor still receives plain <code>/api/*</code> URLs. On Nginx that's a <code>location /proxmenux-monitor/ } proxy_pass http://127.0.0.1:8008/; {</code> (the trailing slash on <code>proxy_pass</code> does the strip). On Caddy, use <code>handle_path /proxmenux-monitor/*</code>. A dedicated host name is simpler."
|
||||
"subPathBody": "The current frontend has no configured sub-path base and builds API calls from root-relative <code>/api/*</code> URLs. Merely stripping a prefix such as <code>example.com/proxmenux-monitor/</code> does not handle those API calls or all frontend assets. Use a dedicated host name. A sub-path deployment requires coordinated frontend and proxy changes and separate testing."
|
||||
},
|
||||
"audit": {
|
||||
"heading": "Audit log",
|
||||
"intro": "Every authentication event (success and failure) is appended to <code>/var/log/proxmenux-auth.log</code> in a single line, syslog-style format:",
|
||||
"intro": "Login failures and rate-limit warnings are written to <code>/var/log/proxmenux-auth.log</code> in a single-line format. Successful logins are not recorded by this logger:",
|
||||
"outro": "Tail it the usual way: <code>tail -F /var/log/proxmenux-auth.log</code>. The file is rotated by <code>logrotate</code> if a config drop-in is added; the Monitor itself does not rotate it."
|
||||
},
|
||||
"fail2ban": {
|
||||
@@ -346,7 +346,7 @@
|
||||
"Reads <code>/var/log/proxmenux-auth.log</code>.",
|
||||
"Matches the <code>authentication failure; rhost=<ip></code> pattern with a dedicated filter.",
|
||||
"Bans the offending IP at the kernel firewall level by default.",
|
||||
"Is queried by the Flask <code>before_request</code> hook every 30 s — so even when the firewall can't block (because the connection comes from the reverse proxy), the application returns HTTP 403 to banned IPs based on what Fail2Ban knows."
|
||||
"The Flask <code>before_request</code> hook checks requests against the ban list, cached for 30 s. Behind a reverse proxy, client-IP blocking also requires PROXMENUX_TRUST_PROXY=1 and a controlled proxy that sets trustworthy forwarding headers."
|
||||
],
|
||||
"outro": "Configuration, ban time tuning and unban procedures are in <link>Security → Fail2Ban</link>."
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"meta": {
|
||||
"title": "Proxmox AI Assistant — OpenAI, Claude, Gemini, Groq, Ollama | ProxMenux Monitor",
|
||||
"description": "ProxMenux Monitor's optional AI Assistant rewrites Proxmox VE notification bodies in plain language. Six providers supported: OpenAI, Anthropic Claude, Google Gemini, Groq, OpenRouter and local Ollama. Twelve languages, three detail levels per channel, custom prompt mode with a community library, and a context-enrichment layer that adds uptime, recurrence and SMART data to disk events.",
|
||||
"description": "The optional AI Assistant rewrites Proxmox VE notifications in plain language. Six providers: OpenAI, Anthropic Claude, Google Gemini, Groq, OpenRouter and Ollama. The UI offers thirteen languages, three detail levels per channel, custom prompts with a community library, and event context including uptime, recurrence and SMART data.",
|
||||
"ogTitle": "Proxmox AI Assistant — OpenAI, Claude, Gemini, Groq, Ollama",
|
||||
"ogDescription": "Rewrite Proxmox VE notifications in plain language with OpenAI, Anthropic Claude, Google Gemini, Groq, OpenRouter or local Ollama.",
|
||||
"twitterTitle": "Proxmox AI Assistant | ProxMenux Monitor",
|
||||
@@ -9,12 +9,12 @@
|
||||
},
|
||||
"header": {
|
||||
"title": "AI Assistant",
|
||||
"description": "The opt-in rewriter that runs every outbound notification through an LLM before fan-out — turning structured templates into plain-language messages, with per-channel detail levels, twelve languages, a custom prompt mode with a public community library, and a context-enrichment layer that adds uptime, recurrence, SMART data and known-error matches to the prompt.",
|
||||
"description": "Optional AI rewriting turns eligible notification templates into plain-language messages, with per-channel detail levels, thirteen UI languages, custom prompts, a public community library and context enrichment. Backup reports <code>backup_complete</code> and <code>backup_fail</code> bypass AI entirely.",
|
||||
"section": "ProxMenux Monitor"
|
||||
},
|
||||
"intro": {
|
||||
"title": "Off by default, single switch to enable",
|
||||
"body": "AI rewrite is opt-in. Until you flip the master toggle inside <em>Settings → Notifications → Advanced: AI Enhancement</em>, every event is dispatched with the original templated body. When the toggle is on and a provider call fails or times out, the dispatcher falls back to the templated body silently — your notifications never block on the LLM."
|
||||
"body": "AI rewriting is off by default. Enable it in <em>Settings → Notifications → Advanced: AI Enhancement</em>. If enrichment or rewriting fails, the dispatcher sends the original template. Each rewrite has a 25-second dispatch timeout; reaching it falls back to the template instead of waiting indefinitely."
|
||||
},
|
||||
"howItWorks": {
|
||||
"heading": "How it works",
|
||||
@@ -83,7 +83,7 @@
|
||||
"intro1": "Every commercial provider charges per <em>token</em>, so it's worth understanding what a token is before picking a plan. A token is roughly four characters of English text or about three quarters of a word. The phrase <em>\"Backup completed on storage local-bak\"</em> is around eight tokens. A short journal excerpt of ten lines can be 200-400 tokens depending on the technical density.",
|
||||
"intro2": "Two things are billed on every call:",
|
||||
"items": [
|
||||
"<strong>Input tokens</strong> — the system prompt plus the user message (severity, title, body, enriched context). For ProxMenux the system prompt alone is on the order of 1.5-2 KB (≈ 400-500 tokens) and the user message varies from 50 tokens (a clean backup-complete) to ~1500 tokens (a disk error with 30 lines of journal context).",
|
||||
"<strong>Input tokens</strong> cover the system prompt and user message: severity, title, rendered body and enriched context. Their number depends on the actual prompt and event content. Each rewritten channel can incur its own input-token usage; backup-complete and backup-fail reports bypass AI.",
|
||||
"<strong>Output tokens</strong> — what the model writes back. The Monitor caps this with <code>max_tokens</code> (see the table below). The cap is a <em>limit</em>, not a charge: if the model produces 250 tokens with a cap of 1500, you pay for 250."
|
||||
],
|
||||
"capsIntro": "These are the actual caps the dispatcher applies, taken straight from <code>AI_DETAIL_TOKENS</code> in <code>notification_templates.py</code>:",
|
||||
@@ -109,7 +109,7 @@
|
||||
],
|
||||
"customNote": "Custom prompt mode uses a fixed cap of 500 output tokens regardless of detail level — the custom prompt is in your control and the cap protects against runaway responses.",
|
||||
"sizingTitle": "Practical sizing",
|
||||
"sizingBody": "A homelab with 50-100 events per day on <code>standard</code> typically consumes a few thousand tokens per day. With the free tiers offered by Groq and Gemini, that fits without touching a paid plan. With OpenAI or Anthropic, billed per-token, the cost lands in the cents-per-month range for that volume. If your event count is much higher, the <link>Detail level per channel</link> section explains how to keep chat channels on <code>brief</code> while letting Email take the full report."
|
||||
"sizingBody": "Estimate usage as rewritten events × AI-enabled channels × input-plus-output tokens per call. For example, 50 events × one channel × (500 input + 300 output tokens) is 40,000 tokens per day, not a few thousand. Actual usage and charges depend on model, context, caching and provider limits. The <link>Detail level per channel</link> section shows how to use <code>brief</code> for chat instead of <code>standard</code>, while keeping detailed email reports."
|
||||
},
|
||||
"providers": {
|
||||
"heading": "AI providers",
|
||||
@@ -121,8 +121,8 @@
|
||||
"tagline": "Very fast, generous free tier (30 req/min). Ideal to start.",
|
||||
"items": [
|
||||
"API key: <a>console.groq.com/keys</a>",
|
||||
"Verified models: <code>llama-3.3-70b-versatile</code>, <code>llama-3.1-70b-versatile</code>, <code>llama-3.1-8b-instant</code>, <code>llama3-70b-8192</code>, <code>llama3-8b-8192</code>, <code>mixtral-8x7b-32768</code>, <code>gemma2-9b-it</code>.",
|
||||
"Recommended: <strong><code>llama-3.3-70b-versatile</code></strong> — best quality at full Groq inference speed."
|
||||
"Examples from the bundled model list: <code>allam-2-7b</code>, <code>qwen/qwen3.8-27b</code>, <code>openai/gpt-oss-20b</code>, <code>groq/compound-mini</code>.",
|
||||
"Bundled recommendation: <strong><code>openai/gpt-oss-20b</code></strong>. Use the provider selector and connection test to check availability for your account."
|
||||
]
|
||||
},
|
||||
"openai": {
|
||||
@@ -130,8 +130,8 @@
|
||||
"tagline": "Industry standard. Very accurate and widely used.",
|
||||
"items": [
|
||||
"API key: <a>platform.openai.com/api-keys</a>",
|
||||
"Verified models: <code>gpt-4.1-nano</code>, <code>gpt-4.1-mini</code>, <code>gpt-4o-mini</code>, <code>gpt-4.1</code>, <code>gpt-4o</code>, <code>gpt-5-chat-latest</code>, <code>gpt-5.4-nano</code>, <code>gpt-5.4-mini</code>.",
|
||||
"Recommended: <strong><code>gpt-4.1-nano</code></strong> — the cheapest member of the chat family, sufficient quality for translation and re-formatting. Reasoning models (o-series, gpt-5 non-chat) are supported by the provider plumbing but kept off the verified list: higher latency without measurable quality gain on this workload."
|
||||
"Examples from the bundled model list: <code>gpt-4.1-nano</code>, <code>gpt-4.1-mini</code>, <code>gpt-4o-mini</code>, <code>gpt-4.1</code>, <code>gpt-4o</code>, <code>gpt-5-nano</code>.",
|
||||
"Bundled recommendation: <strong><code>gpt-4.1-nano</code></strong>. The adapter also handles model-specific requirements for reasoning models; the bundled list includes gpt-5-nano, so reasoning models are not universally excluded."
|
||||
],
|
||||
"baseUrlTitle": "OpenAI-compatible base URL",
|
||||
"baseUrlBody": "The OpenAI provider also accepts a custom <em>Base URL</em>, which lets you point the Monitor at any OpenAI-compatible endpoint. Confirmed to work with <strong>BytePlus / ByteDance (Kimi K2.5)</strong>, <strong>LocalAI</strong>, <strong>LM Studio</strong>, <strong>vLLM</strong>, <strong>Together AI</strong>, <strong>Fireworks AI</strong>, and any other service that speaks the OpenAI <code>/v1/chat/completions</code> dialect. Set the URL in the OpenAI tab next to the API key field."
|
||||
@@ -141,8 +141,8 @@
|
||||
"tagline": "Excellent for writing and translation. Fast and economical.",
|
||||
"items": [
|
||||
"API key: <a>console.anthropic.com/settings/keys</a>",
|
||||
"Verified models: <code>claude-3-5-haiku-latest</code>, <code>claude-3-5-sonnet-latest</code>, <code>claude-3-opus-latest</code>.",
|
||||
"Recommended: <strong><code>claude-3-5-haiku-latest</code></strong> — Claude's smallest, fastest model with strong language quality for the translation workload."
|
||||
"Examples from the bundled model list: <code>claude-haiku-4-5-20251001</code>, <code>claude-sonnet-4-6</code>, <code>claude-opus-4-6</code>.",
|
||||
"Bundled recommendation: <strong><code>claude-haiku-4-5-20251001</code></strong>. This exact snapshot is the configured recommendation; the older Claude 3.5 entries shown in earlier documentation are no longer in the bundled list."
|
||||
]
|
||||
},
|
||||
"gemini": {
|
||||
@@ -150,7 +150,7 @@
|
||||
"tagline": "Free tier available, great quality/price ratio.",
|
||||
"items": [
|
||||
"API key: <a>aistudio.google.com/app/apikey</a>",
|
||||
"Verified models: <code>gemini-2.5-flash-lite</code>, <code>gemini-2.5-flash</code>, <code>gemini-3-flash-preview</code>.",
|
||||
"Examples from the bundled model list: <code>gemini-2.5-flash-lite</code>, <code>gemini-2.5-flash</code>, <code>gemini-3-flash-preview</code>.",
|
||||
"Recommended: <strong><code>gemini-2.5-flash-lite</code></strong> — flash and flash-lite pass the verifier consistently. The pro variants reject the <code>thinkingBudget=0</code> setting the Monitor uses and are overkill for this workload."
|
||||
]
|
||||
},
|
||||
@@ -159,13 +159,13 @@
|
||||
"tagline": "Aggregator with access to 100+ models using a single API key. Maximum flexibility.",
|
||||
"items": [
|
||||
"API key: <a>openrouter.ai/keys</a>",
|
||||
"Verified models: <code>meta-llama/llama-3.3-70b-instruct</code>, <code>meta-llama/llama-3.1-70b-instruct</code>, <code>meta-llama/llama-3.1-8b-instruct</code>, <code>anthropic/claude-3.5-haiku</code>, <code>anthropic/claude-3.5-sonnet</code>, <code>google/gemini-flash-1.5</code>, <code>openai/gpt-4o-mini</code>, <code>mistralai/mistral-7b-instruct</code>, <code>mistralai/mixtral-8x7b-instruct</code>.",
|
||||
"Examples from the bundled model list: <code>meta-llama/llama-3.3-70b-instruct</code>, <code>meta-llama/llama-3.1-70b-instruct</code>, <code>meta-llama/llama-3.1-8b-instruct</code>, <code>anthropic/claude-haiku-4.5</code>, <code>google/gemini-2.5-flash-lite</code>, <code>openai/gpt-4o-mini</code>, <code>mistralai/mistral-medium-3</code>.",
|
||||
"Recommended: <strong><code>meta-llama/llama-3.3-70b-instruct</code></strong> — same model as the Groq entry but routed through OpenRouter, which means a single key for all the listed models."
|
||||
]
|
||||
},
|
||||
"ollama": {
|
||||
"heading": "Ollama (Local)",
|
||||
"tagline": "Uses models available on your Ollama server. 100% local, no costs, total privacy.",
|
||||
"tagline": "Uses models exposed by your configured Ollama server. Local models execute there; Ollama cloud models forward requests to a cloud provider. Local execution avoids a hosted API charge but still uses your hardware and electricity.",
|
||||
"items": [
|
||||
"No API key. Set the <em>Ollama URL</em> field to your server (default <code>http://localhost:11434</code> or whatever host runs your Ollama instance).",
|
||||
"Models: <strong>not filtered.</strong> The Monitor reads whichever models you have pulled on the Ollama side via <code>ollama pull <model></code>. The dropdown is populated from <code>GET /api/tags</code> on your Ollama server.",
|
||||
@@ -175,14 +175,14 @@
|
||||
},
|
||||
"models": {
|
||||
"heading": "Why these specific models",
|
||||
"intro": "The model dropdown for each commercial provider is populated from a curated list shipped with the Monitor (<code>verified_ai_models.json</code>). Models on this list have been tested end-to-end with the chat / completions API format the Monitor uses, with the exact <code>system_prompt + user_message + max_tokens</code> shape the AI Enhancer sends. The list is refreshed before each ProxMenux release with a private verifier tool that probes every candidate model and prunes the ones that misbehave.",
|
||||
"intro": "Commercial-provider model choices come from the bundled compatibility catalogue <code>verified_ai_models.json</code>, combined with the provider adapter’s availability checks. Adapters map the internal <code>system_prompt + user_message + max_tokens</code> request to each provider’s API. The catalogue contains tested entries and manual additions; it is a snapshot, not a guarantee of current account access or a promise that every entry passed the same verifier.",
|
||||
"consequencesIntro": "Two consequences worth being aware of:",
|
||||
"consequences": [
|
||||
"<strong>The recommended model</strong> for each provider is the one that has the best balance of quality, latency and cost for notification translation specifically — not the most capable model the provider sells. Notification rewrites don't need frontier-model reasoning; they need fast and cheap.",
|
||||
"<strong>You can still pick another verified model</strong> from the dropdown — sometimes you have a free-tier quota you want to spend on a particular model, or you have a strong preference. Pick any of the listed entries; they've all passed the verifier."
|
||||
"<strong>The bundled recommendation</strong> is a starting point selected for notification formatting. Assess language quality, latency and cost for your own event content and provider account rather than treating it as a universal model ranking.",
|
||||
"<strong>You can choose another listed model</strong> from the dropdown to suit your quota or preferences. Check its availability with the connection test; being listed does not guarantee current access for your account."
|
||||
],
|
||||
"ollamaTitle": "Ollama is the exception",
|
||||
"ollamaBody": "Ollama models are local and the Monitor doesn't filter them — the dropdown reflects whatever you have pulled. Pick a model in the 7B-13B range with at least an 8K context window for the AI rewrite to behave reasonably with the journal context blocks."
|
||||
"ollamaBody": "The Ollama dropdown is not filtered by the commercial-provider catalogue; it reflects models exposed by your Ollama server. For local execution choose a local model that fits your RAM and has enough context for the journal blocks. Cloud-tagged models are remote even when accessed through a local Ollama server."
|
||||
},
|
||||
"defaultPrompt": {
|
||||
"heading": "Default prompt",
|
||||
@@ -205,8 +205,8 @@
|
||||
"changes": [
|
||||
"<strong>The default prompt is replaced entirely.</strong> The Proxmox mappings, the context-handling rules and the emoji instructions are all gone. If you want to keep any of them, paste them into your prompt — the bundled <code>EXAMPLE_CUSTOM_PROMPT</code> shown below is a starting point.",
|
||||
"<strong>The Language selector is ignored.</strong> The default prompt has a <code>'{'language'}'</code> placeholder; the custom prompt does not. If you want output in a specific language, declare it inside your prompt (\"Translate to Spanish\", \"Output everything in French\").",
|
||||
"<strong>Detail level still applies</strong> in the sense that it's available as a setting per channel, but the cap on output tokens becomes a fixed 500 in custom mode (vs the 500 / 1500 / 3000 ramp of the default prompt). If your custom prompt asks for a long report, raise the cap by editing the prompt or split the request.",
|
||||
"<strong>The Output Format markers are still mandatory.</strong> The Monitor parses the response by looking for <code>[TITLE]</code> and <code>[BODY]</code> on their own lines. A custom prompt that doesn't emit those markers will break the parser and fall back to the original templated body.",
|
||||
"<strong>The custom-mode output cap is fixed at 500 tokens</strong>, independent of the stored per-channel detail setting. Default mode uses the 500 / 1500 / 3000 limits. Editing the custom prompt cannot raise the cap; request a shorter answer or use an appropriate Default detail level.",
|
||||
"<strong>The output markers remain mandatory.</strong> Include <code>[TITLE]</code> and <code>[BODY]</code> in the response format. The parser extracts their content; if either marker is missing, it returns the original template instead.",
|
||||
"<strong>Rich messages emoji rules are not auto-injected.</strong> If you want emojis, tell the prompt to use them. If you want plain text, tell it not to. The toggle only gates the bundled blocks of the default prompt, not your custom string."
|
||||
],
|
||||
"starterTitle": "Starter prompt",
|
||||
@@ -224,10 +224,10 @@
|
||||
"rulesIntro": "The rules baked into the addon (visible in the collapsible block under the Default prompt section above):",
|
||||
"rules": [
|
||||
"The tip is included <em>only</em> if the journal context or the Known Errors database clearly points to a specific fix.",
|
||||
"The tip is capped at 100 characters.",
|
||||
"It must be specific (concrete command or path) — generic advice is rejected by the prompt itself.",
|
||||
"The prompt requests a tip under 100 characters; the parser does not independently enforce that length.",
|
||||
"The prompt requires a specific command or path and instructs the model to avoid generic advice.",
|
||||
"If a Known Error provides a solution, the LLM must use that solution, not invent a new one.",
|
||||
"If nothing in the input gives the LLM enough certainty to suggest a concrete fix, the tip is skipped — no guessing."
|
||||
"The prompt instructs the model to omit the tip if the input does not support a concrete fix."
|
||||
],
|
||||
"betaTitle": "Why BETA",
|
||||
"betaBody": "Tip quality depends on two things outside of ProxMenux: the model picked, and how rich the journal context for the event was. With a strong model (Claude 3.5 Haiku, GPT-4.1 Mini, Llama 3.3 70B) and a disk error that comes with smartctl + journal lines, the tip is consistently useful. With a tiny local Ollama model and a one-line event, the tip can fall flat or get skipped entirely. Disable the toggle if you find the tips noisy and re-enable it when you want it back."
|
||||
@@ -265,12 +265,12 @@
|
||||
"<strong>Email</strong> — <code>detailed</code>. Email is the channel where you typically want the full picture for archival."
|
||||
],
|
||||
"emailTitle": "Email detail level appends the original",
|
||||
"emailBody": "When Email is on <code>detailed</code> and the original templated body has substantial content (more than 50 characters), the dispatcher appends the original message at the bottom of the AI rewrite, separated by a 40-dash divider and an <code>Original message:</code> label. This means a detailed email always carries both the AI-friendly version and the machine-friendly raw template — useful when you want to grep an old alert later."
|
||||
"emailBody": "When the AI formatter returns a result for Email on <code>detailed</code> and the original body is longer than 50 characters, the formatter appends that original text after a 40-dash divider and the label <code>Original message:</code>. This retains the source details for later searches. AI bypasses and failures can instead deliver the original template alone."
|
||||
},
|
||||
"language": {
|
||||
"heading": "Language",
|
||||
"intro": "Twelve languages are wired in. The dropdown sets <code>ai_language</code> in the config and the value is interpolated into the system prompt at the place where the prompt says <em>\"translate alerts into '{'language'}'\"</em>. The full list:",
|
||||
"list": "English (<code>en</code>), Spanish (<code>es</code>), French (<code>fr</code>), German (<code>de</code>), Portuguese (<code>pt</code>), Italian (<code>it</code>), Russian (<code>ru</code>), Swedish (<code>sv</code>), Norwegian (<code>no</code>), Japanese (<code>ja</code>), Chinese (<code>zh</code>), Dutch (<code>nl</code>).",
|
||||
"intro": "The UI selector offers thirteen languages. It stores <code>ai_language</code>, which the default system prompt uses in <em>\"translate alerts into '{'language'}'\"</em>. The UI list is:",
|
||||
"list": "English (<code>en</code>), Slovak (<code>sk</code>), Spanish (<code>es</code>), French (<code>fr</code>), German (<code>de</code>), Portuguese (<code>pt</code>), Italian (<code>it</code>), Russian (<code>ru</code>), Swedish (<code>sv</code>), Norwegian (<code>no</code>), Japanese (<code>ja</code>), Chinese (<code>zh</code>), Dutch (<code>nl</code>).",
|
||||
"rulesIntro": "Two important rules taken straight from the prompt:",
|
||||
"rules": [
|
||||
"<strong>Translate</strong>: labels, descriptions, status words, units (e.g. GB → Go in French).",
|
||||
@@ -280,7 +280,7 @@
|
||||
},
|
||||
"templates": {
|
||||
"heading": "A note on templates",
|
||||
"body1": "The body the AI receives is not raw event data — it's a pre-rendered template. Each event type (<code>backup_complete</code>, <code>vm_start</code>, <code>auth_fail</code>, <code>health_degraded</code>, etc.) has a template in <code>notification_templates.py</code> that knows how to format that specific event into a structured plain-text body. The AI rewrites that body, it doesn't replace the templating step.",
|
||||
"body1": "The AI receives a rendered template rather than raw event data. Types such as <code>vm_start</code>, <code>auth_fail</code> and <code>health_degraded</code> have templates in <code>notification_templates.py</code>. The AI rewrites eligible template output; <code>backup_complete</code> and <code>backup_fail</code> retain their deterministic reports and bypass AI.",
|
||||
"body2": "Two practical implications: the AI never sees a hostname or VMID it has to invent — those fields are placed by the template before the rewrite. And if AI is disabled, the templated body is what gets dispatched directly. The <link>Notifications</link> page documents the dispatch pipeline in full and is the right cross-reference for everything that happens to an event before it reaches this layer."
|
||||
},
|
||||
"privacy": {
|
||||
@@ -291,11 +291,11 @@
|
||||
"rows": [
|
||||
{
|
||||
"provider": "Ollama",
|
||||
"destination": "Stays on the Ollama host. If Ollama runs on the same Proxmox node, nothing leaves the network at all."
|
||||
"destination": "Local models run on the configured Ollama host. Their requests stay on that host when it is the Proxmox node itself. Ollama cloud models forward requests outside the host, even through a local Ollama server."
|
||||
},
|
||||
{
|
||||
"provider": "OpenAI",
|
||||
"destination": "<code>api.openai.com</code> (or your custom Base URL endpoint). Subject to OpenAI's data-handling policy at the time of the call."
|
||||
"destination": "<code>api.openai.com</code> by default, or the configured custom Base URL. The data-handling policy of the actual endpoint operator applies."
|
||||
},
|
||||
{
|
||||
"provider": "Anthropic",
|
||||
@@ -315,7 +315,7 @@
|
||||
}
|
||||
],
|
||||
"calloutTitle": "If event content cannot leave the network",
|
||||
"calloutBody": "Use Ollama, or disable the AI rewriter. There is no middle ground — the dispatcher does not try to redact hostnames or IPs before sending; the prompt is built from the actual event payload as the Monitor sees it."
|
||||
"calloutBody": "Use a locally hosted endpoint with a local model, such as local Ollama or an OpenAI-compatible server, or disable AI rewriting. Ollama cloud models do not keep processing local. The dispatcher does not redact hostnames or IPs; it builds the prompt from the actual event content."
|
||||
},
|
||||
"whereNext": {
|
||||
"heading": "Where to next",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"meta": {
|
||||
"title": "Proxmox Monitor API — Integration Reference | ProxMenux",
|
||||
"description": "HTTP endpoints exposed by ProxMenux Monitor for integrations: Home Assistant, Homepage, Grafana, Prometheus, n8n and custom dashboards. Read-only data export plus the safe write operations needed by automations — VM control, backup trigger, notification dispatch, alert acknowledgement.",
|
||||
"description": "HTTP endpoints exposed by ProxMenux Monitor for Home Assistant, Homepage, Grafana, Prometheus, n8n and custom dashboards. Read state and invoke documented write operations such as VM control, backup submission, notification dispatch and alert acknowledgement.",
|
||||
"ogTitle": "Proxmox Monitor API — Integration Reference",
|
||||
"ogDescription": "Endpoints exposed by ProxMenux Monitor for Home Assistant, Homepage, Grafana, Prometheus, n8n and custom dashboards.",
|
||||
"twitterTitle": "Proxmox Monitor API | ProxMenux",
|
||||
@@ -9,7 +9,7 @@
|
||||
},
|
||||
"header": {
|
||||
"title": "API Reference",
|
||||
"description": "The HTTP endpoints integrators use to read state and trigger safe actions on ProxMenux Monitor — Home Assistant sensors, Homepage cards, Grafana dashboards, Prometheus scrapes, n8n flows and custom scripts. Every category, plus a complete list of Prometheus metrics, with curl examples.",
|
||||
"description": "HTTP endpoints for reading state and invoking documented actions on ProxMenux Monitor: Home Assistant sensors, Homepage cards, Grafana dashboards, Prometheus collection, n8n flows and custom scripts. Includes endpoint categories, exported metrics and curl examples.",
|
||||
"section": "ProxMenux Monitor"
|
||||
},
|
||||
"intro": {
|
||||
@@ -21,18 +21,18 @@
|
||||
"headerUse": "Use",
|
||||
"auth": {
|
||||
"heading": "Authentication",
|
||||
"intro": "Every endpoint marked \"authenticated\" expects a JWT bearer token in the request header:",
|
||||
"intro": "When authentication is enabled, protected endpoints expect a JWT bearer token in the request header:",
|
||||
"tokensIntro": "Two ways to obtain the token:",
|
||||
"items": [
|
||||
"<strong>API tokens (recommended for integrations).</strong> Long-lived tokens minted from <strong>Settings → Security → API Tokens</strong> in the dashboard. Each token is named, can be revoked individually, and is what you should hand to Home Assistant / Homepage / Grafana / n8n.",
|
||||
"<strong>Login flow (short-lived JWT).</strong> <code>POST /api/auth/login</code> with a username, password and TOTP token if 2FA is enabled. The returned JWT is short-lived and refreshed by the dashboard automatically; useful for ad-hoc scripts that authenticate as a human user."
|
||||
"<strong>API tokens (recommended for integrations).</strong> Tokens valid for 365 days, created under <strong>Security → API Tokens</strong>. Each has a name and can be revoked individually. Use read_only for data collection and select full_admin only for integrations that need administrative actions.",
|
||||
"<strong>Login flow (24-hour JWT).</strong> <code>POST /api/auth/login</code> with a username, password and TOTP token when required by 2FA. The returned session JWT expires after 24 hours; scripts must authenticate again when it expires."
|
||||
],
|
||||
"flowLink": "The auth flow, password policy, 2FA setup, audit log and TLS configuration all live in <link>Access & Authentication</link>.",
|
||||
"rows": [
|
||||
{
|
||||
"endpoint": "/api/auth/login",
|
||||
"method": "POST",
|
||||
"use": "Body: <code>'{'\"username\",\"password\",\"totp_token?\"'}'</code>. Returns a short-lived JWT."
|
||||
"use": "Body fields (schematic): <code>'{'\"username\",\"password\",\"totp_token?\"'}'</code>. Returns a 24-hour session JWT."
|
||||
},
|
||||
{
|
||||
"endpoint": "/api/auth/api-tokens",
|
||||
@@ -40,9 +40,9 @@
|
||||
"use": "List API tokens (metadata only — names, prefixes, dates; never the actual secret)."
|
||||
},
|
||||
{
|
||||
"endpoint": "/api/auth/api-tokens",
|
||||
"endpoint": "/api/auth/generate-api-token",
|
||||
"method": "POST",
|
||||
"use": "Mint a new long-lived API token. Body: <code>'{'\"name\":\"'<'label'>'\"'}'</code>. The token value is returned once and cannot be retrieved again."
|
||||
"use": "Create a 365-day API token using an authenticated session. Body fields: <code>password, token_name?, totp_token?, scope?</code>. Password and any required TOTP are checked again. Scope defaults to read_only; full_admin must be selected explicitly. The secret is returned only once."
|
||||
},
|
||||
{
|
||||
"endpoint": "/api/auth/api-tokens/<id>",
|
||||
@@ -54,10 +54,10 @@
|
||||
"conventions": {
|
||||
"heading": "Conventions",
|
||||
"items": [
|
||||
"All requests and responses are JSON unless explicitly noted (log download is plain text, <code>/api/prometheus</code> is text/plain in the OpenMetrics format, task log is plain text).",
|
||||
"Successful mutating endpoints return <code>'{'\"success\": true, ...'}'</code>. Error responses use a non-2xx HTTP status with <code>'{'\"success\": false, \"error\": \"'<'reason'>'\"'}'</code>.",
|
||||
"List endpoints accept optional <code>limit</code>, <code>offset</code>, <code>since</code> and category-specific filters via query string.",
|
||||
"Time fields are returned as Unix epoch seconds or ISO-8601 with explicit timezone, never as locale strings."
|
||||
"Most endpoints use JSON. Downloads can return plain text, and <code>/api/prometheus</code> returns Prometheus text exposition format version 0.0.4 rather than OpenMetrics. Check the response type of the specific route.",
|
||||
"Many write endpoints return <code>'{'\"success\": true, ...'}'</code>. Error formats vary; a common form is <code>'{'\"success\": false, \"error\": \"'<'reason'>'\"'}'</code>. Check both HTTP status and response fields: some authentication challenges return HTTP 200 with success false.",
|
||||
"Query parameters vary by endpoint. Use <code>limit</code>, <code>offset</code>, <code>since</code> and category-specific filters only where the route supports them.",
|
||||
"Time formats vary by endpoint: Unix timestamps, ISO-8601 strings and systemd-formatted dates all occur. Do not assume every time field has the same unit or an explicit timezone."
|
||||
]
|
||||
},
|
||||
"system": {
|
||||
@@ -102,12 +102,12 @@
|
||||
},
|
||||
"actions": {
|
||||
"heading": "System actions",
|
||||
"intro": "Fire-and-forget endpoints that trigger the same operations the operator would run from the Monitor UI or the shell menu — designed for Home Assistant, Homepage, Ansible, custom dashboards and any other automation that needs to reach into the host over HTTP. Every mutating route requires a token with <code>full_admin</code> scope; read-only <code>read_only</code> tokens can still poll the <code>.../status</code> endpoints without triggering anything. Each action returns 202 with a state snapshot; poll the matching <code>.../status</code> to observe progress.",
|
||||
"intro": "The asynchronous system actions below start operations in transient systemd units. With authentication enabled, their write routes require <code>full_admin</code>; <code>read_only</code> tokens can poll <code>.../status</code>. Accepted starts return HTTP 202 with a state snapshot; poll the matching <code>.../status</code> for progress. Guest control and backup use different response contracts, described below.",
|
||||
"shapeTitle": "Common response shape",
|
||||
"shapeIntro": "All action endpoints return the same JSON object — one shape to parse from any client:",
|
||||
"shapeIntro": "Asynchronous system actions share the JSON state object below. Guest-control and backup responses have different shapes:",
|
||||
"shapeCode": "{\n \"unit\": \"proxmenux-action-pve-update.service\",\n \"state\": \"idle | running | success | failed | cancelled\",\n \"started_at\": \"Sat 2026-08-01 11:01:32 CEST\",\n \"finished_at\": null,\n \"exit_code\": null,\n \"result\": null\n}",
|
||||
"concurrencyTitle": "Concurrency and cancellation",
|
||||
"concurrencyBody": "A second POST while a run is in flight returns 409 with the current running state. Cancel an in-flight run with a DELETE to the action's base URL; the state moves to <code>cancelled</code> and timestamps are preserved so the caller can still see when it was triggered and stopped. The last terminal state (success / failed / cancelled) is remembered until the next run replaces it.",
|
||||
"concurrencyBody": "Starting a second run of the same system action returns HTTP 409 while it is active. Cancellation is available only for actions with a documented DELETE route, such as PVE update. Their state becomes <code>cancelled</code> and preserves timestamps. Terminal snapshots are kept in process memory and can be lost when the Monitor restarts.",
|
||||
"rows": [
|
||||
{
|
||||
"endpoint": "/api/system/power/reboot",
|
||||
@@ -147,7 +147,7 @@
|
||||
{
|
||||
"endpoint": "/api/proxmenux/self-update/run",
|
||||
"method": "POST",
|
||||
"use": "Update ProxMenux itself by piping the canonical stable installer. Runs in its own systemd unit so the update completes across the proxmenux-monitor restart the installer performs."
|
||||
"use": "Update ProxMenux using the installer for the configured stable or beta channel. It runs in its own systemd unit so the job can survive the proxmenux-monitor service restart performed by the installer."
|
||||
},
|
||||
{
|
||||
"endpoint": "/api/proxmenux/self-update/status",
|
||||
@@ -157,12 +157,12 @@
|
||||
{
|
||||
"endpoint": "/api/vms/<vmid>/control",
|
||||
"method": "POST",
|
||||
"use": "Start / stop / shutdown / reboot a VM or LXC container (the same operations the Monitor's VM & LXC modal exposes). The body carries <code>action</code>: <code>start</code>, <code>stop</code>, <code>shutdown</code> or <code>reboot</code>. Synchronous — returns the outcome directly with no polling needed."
|
||||
"use": "Submit VM or LXC power control. Body field <code>action</code> accepts <code>start</code>, <code>stop</code>, <code>shutdown</code> or <code>reboot</code>. A successful response returns a confirmed PVE task; it does not mean the operation has completed. Poll the guest control-task endpoint for its outcome."
|
||||
},
|
||||
{
|
||||
"endpoint": "/api/vms/<vmid>/backup",
|
||||
"method": "POST",
|
||||
"use": "Create a vzdump backup of a VM or LXC. Body fields, all optional unless the defaults do not match the storage layout: <code>storage</code> (a PVE storage), <code>mode</code> (<code>snapshot</code>, <code>suspend</code> or <code>stop</code>), <code>compress</code> (<code>zstd</code>, <code>lzo</code>, <code>gz</code> or <code>none</code>), <code>protected</code> (<code>true</code> or <code>false</code>), <code>notes</code>, <code>notification</code> (<code>auto</code>, <code>always</code>, <code>failure</code> or <code>never</code>) and <code>pbs_change_detection</code> (<code>default</code>, <code>legacy</code> or <code>data</code>). Returns the PVE task UPID."
|
||||
"use": "Create a vzdump backup of a VM or LXC. Body fields, all optional unless the defaults do not match the storage layout: <code>storage</code> (a PVE storage), <code>mode</code> (<code>snapshot</code>, <code>suspend</code> or <code>stop</code>), <code>compress</code> (<code>zstd</code>, <code>lzo</code>, <code>gzip</code> or <code>none</code>), <code>protected</code> (<code>true</code> or <code>false</code>), <code>notes</code>, <code>notification</code> (<code>auto</code>, <code>always</code>, <code>failure</code> or <code>never</code>) and <code>pbs_change_detection</code> (<code>default</code>, <code>legacy</code> or <code>data</code>). Returns the PVE task UPID."
|
||||
},
|
||||
{
|
||||
"endpoint": "/api/vms/<vmid>/backups",
|
||||
@@ -171,7 +171,7 @@
|
||||
}
|
||||
],
|
||||
"syncVsAsyncTitle": "Two flavours of action",
|
||||
"syncVsAsyncBody": "System-level actions (host power, PVE update, ProxMenux self-update) can take minutes — they run in their own transient systemd unit and expose a <code>.../status</code> endpoint plus a <code>DELETE</code> to cancel. VM / LXC actions (start, stop, shutdown, reboot, backup) are fast and use the fire-and-return style the Monitor's UI already exposes: the POST returns the outcome directly. Both flavours share the same authentication model (JWT or long-lived API token).",
|
||||
"syncVsAsyncBody": "Host power, PVE update and ProxMenux self-update run in transient systemd units with a <code>.../status</code> endpoint. Only PVE update has the <code>DELETE</code> cancellation route listed here. VM/LXC control returns a PVE task that must be polled; backup returns a UPID for a background job. An accepted request is not completion. Authentication and scope requirements depend on the route.",
|
||||
"curlTitle": "curl example",
|
||||
"curlBody": "Fire a PVE update and poll for completion — the exact same sequence a Home Assistant automation or an Ansible playbook would run:",
|
||||
"curlCode": "TOKEN=<your full_admin token>\n\ncurl -sSf -X POST \\\n -H \"Authorization: Bearer $TOKEN\" \\\n https://<host>:8008/api/system/pve-update/run\n\nwhile true; do\n state=$(curl -sSf -H \"Authorization: Bearer $TOKEN\" \\\n https://<host>:8008/api/system/pve-update/status | jq -r .state)\n [ \"$state\" != \"running\" ] && break\n sleep 30\ndone\necho \"final state: $state\"",
|
||||
@@ -185,7 +185,7 @@
|
||||
{
|
||||
"endpoint": "/api/health",
|
||||
"method": "GET",
|
||||
"use": "Small health probe — returns JSON with <code>status</code>, <code>timestamp</code>, <code>version</code>. Suitable for Uptime Kuma keyword checks; the receiver must send the bearer header."
|
||||
"use": "Small health probe returning JSON with <code>status</code>, <code>timestamp</code> and <code>version</code>. Suitable for Uptime Kuma keyword checks; when authentication is enabled, the client must send the bearer header."
|
||||
},
|
||||
{
|
||||
"endpoint": "/api/health/status",
|
||||
@@ -200,7 +200,7 @@
|
||||
{
|
||||
"endpoint": "/api/health/full",
|
||||
"method": "GET",
|
||||
"use": "Full snapshot — categories + active errors + dismissed list + custom suppression settings. Backs the modal in one round-trip; uses a 6-min background cache for instant response."
|
||||
"use": "Full snapshot: categories, active errors, dismissed alerts and custom suppression settings. It uses background check results when less than six minutes old; otherwise, or on a forced refresh, it runs a fresh check."
|
||||
},
|
||||
{
|
||||
"endpoint": "/api/health/active-errors",
|
||||
@@ -286,7 +286,7 @@
|
||||
{
|
||||
"endpoint": "/api/storage/smart/<disk>/test",
|
||||
"method": "POST",
|
||||
"use": "Trigger a SMART self-test. Body: <code>'{'\"type\":\"short\"|\"long\"'}'</code>."
|
||||
"use": "Start a SMART self-test. Body schema: <code>'{'\"type\":\"short\"|\"long\"'}'</code>."
|
||||
},
|
||||
{
|
||||
"endpoint": "/api/storage/smart/schedules",
|
||||
@@ -336,7 +336,7 @@
|
||||
{
|
||||
"endpoint": "/api/vms",
|
||||
"method": "GET",
|
||||
"use": "List of all VMs and CTs with status, vmid, name."
|
||||
"use": "VMs and CTs on the local node, with status, vmid and name."
|
||||
},
|
||||
{
|
||||
"endpoint": "/api/vms/<vmid>",
|
||||
@@ -361,7 +361,7 @@
|
||||
{
|
||||
"endpoint": "/api/vms/<vmid>/control",
|
||||
"method": "POST",
|
||||
"use": "Body: <code>'{'\"action\":\"start|stop|reboot|shutdown\"'}'</code>. Power-cycle a guest."
|
||||
"use": "Choose one action from the body schema: <code>'{'\"action\":\"start|stop|reboot|shutdown\"'}'</code>. Controls guest power state."
|
||||
},
|
||||
{
|
||||
"endpoint": "/api/vms/<vmid>/backup",
|
||||
@@ -386,7 +386,7 @@
|
||||
{
|
||||
"endpoint": "/api/backups",
|
||||
"method": "GET",
|
||||
"use": "Cluster-wide list of backups."
|
||||
"use": "Backups found in dir, NFS, CIFS and PBS storages queried through the local node. This is not an exhaustive scan of every cluster node."
|
||||
},
|
||||
{
|
||||
"endpoint": "/api/backup-storages",
|
||||
@@ -467,7 +467,7 @@
|
||||
{
|
||||
"endpoint": "/api/notifications/test-ai",
|
||||
"method": "POST",
|
||||
"use": "Test the AI provider connection. Body: <code>'{'\"provider\",\"api_key\",\"model\",\"ollama_url?\"'}'</code>."
|
||||
"use": "Test the AI provider connection. Body fields (schematic): <code>'{'\"provider\",\"api_key\",\"model\",\"ollama_url?\"'}'</code>."
|
||||
},
|
||||
{
|
||||
"endpoint": "/api/notifications/provider-models",
|
||||
@@ -542,13 +542,13 @@
|
||||
{
|
||||
"endpoint": "/api/proxmenux/tool-source/<key>",
|
||||
"method": "GET",
|
||||
"use": "Source code of a specific post-install function — the exact bash that was applied."
|
||||
"use": "Source of the mapped post-install function, extracted from current host scripts or preserved legacy code. This is not a guaranteed snapshot of the exact code originally executed."
|
||||
}
|
||||
]
|
||||
},
|
||||
"prometheus": {
|
||||
"heading": "Prometheus metrics",
|
||||
"intro": "ProxMenux Monitor exposes a Prometheus-format scrape endpoint at <code>GET /api/prometheus</code> (authenticated) returning OpenMetrics-format text. Every metric is labelled with <code>node=\"<hostname>\"</code> and carries an explicit timestamp so it ingests cleanly into Prometheus, VictoriaMetrics, Mimir or any compatible TSDB.",
|
||||
"intro": "The authenticated <code>GET /api/prometheus</code> endpoint returns Prometheus text exposition format version 0.0.4. Every metric includes <code>node=\"<hostname>\"</code> and an explicit timestamp. Collect it with Prometheus or another compatible time-series database.",
|
||||
"exportedTitle": "Exported metrics",
|
||||
"headerGroup": "Group",
|
||||
"headerMetric": "Metric",
|
||||
@@ -597,7 +597,7 @@
|
||||
},
|
||||
{
|
||||
"metric": "proxmox_disk_temperature_celsius",
|
||||
"desc": "Per-disk temperature. Label <code>device</code>."
|
||||
"desc": "Temperature per disk. Label <code>disk</code>."
|
||||
},
|
||||
{
|
||||
"metric": "proxmox_fan_speed_rpm",
|
||||
@@ -610,15 +610,15 @@
|
||||
"metrics": [
|
||||
{
|
||||
"metric": "proxmox_disk_total_bytes",
|
||||
"desc": "Total disk space per mount. Label <code>mountpoint</code>."
|
||||
"desc": "Total capacity per exported disk entry. Label <code>disk</code>."
|
||||
},
|
||||
{
|
||||
"metric": "proxmox_disk_used_bytes",
|
||||
"desc": "Used disk space per mount."
|
||||
"desc": "Used capacity per exported disk entry."
|
||||
},
|
||||
{
|
||||
"metric": "proxmox_disk_usage_percent",
|
||||
"desc": "Disk usage percentage per mount."
|
||||
"desc": "Used capacity percentage per exported disk entry."
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -660,7 +660,7 @@
|
||||
},
|
||||
{
|
||||
"metric": "proxmox_vm_status",
|
||||
"desc": "Per-VM/CT status (1 = running, 0 = stopped). Labels <code>vmid</code>, <code>name</code>, <code>type</code>."
|
||||
"desc": "Status per VM/CT (1 = running, 0 = otherwise). Labels <code>node</code>, <code>vmid</code>, <code>name</code>."
|
||||
},
|
||||
{
|
||||
"metric": "proxmox_vm_cpu_usage",
|
||||
@@ -677,7 +677,7 @@
|
||||
"metrics": [
|
||||
{
|
||||
"metric": "proxmox_gpu_temperature_celsius",
|
||||
"desc": "GPU temperature. Labels <code>slot</code>, <code>vendor</code>."
|
||||
"desc": "GPU temperature. Labels <code>slot</code>, <code>vendor</code>; also includes the gpu name label."
|
||||
},
|
||||
{
|
||||
"metric": "proxmox_gpu_utilization_percent",
|
||||
@@ -724,7 +724,7 @@
|
||||
}
|
||||
],
|
||||
"scrapeTitle": "Prometheus scrape config",
|
||||
"scrapeIntro": "The endpoint requires authentication. Pass the API token as a bearer header in your Prometheus scrape config:",
|
||||
"scrapeIntro": "When authentication is enabled, pass an API token in the bearer header of your Prometheus collection configuration:",
|
||||
"perHostTitle": "Per-host scrape",
|
||||
"perHostBody": "Each ProxMenux Monitor instance exports metrics for the host it runs on. In a cluster, point Prometheus at every node — the <code>node</code> label on every series lets you distinguish them in Grafana queries (<code>proxmox_vms_running'{'node=\"pve01\"'}'</code>)."
|
||||
},
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
},
|
||||
"requestFlow": {
|
||||
"heading": "Request flow",
|
||||
"intro": "From the browser to the kernel, every dashboard view follows the same path:",
|
||||
"diagramCaption": "Each request is authenticated by JWT (when auth is enabled), dispatched to a blueprint, and answered with data collected on demand from host tooling. If Fail2Ban is installed and the proxmenux jail is active, the middleware also checks the request against the jail's banned IP list. The optional reverse proxy is transparent to Flask — it forwards X-Forwarded-* headers and the app recovers the real client IP from them. State that needs to outlive a request lives in SQLite.",
|
||||
"intro": "A typical dashboard data request follows this path; browser-only preferences can be handled locally:",
|
||||
"diagramCaption": "Protected routes validate JWT when authentication is enabled. Core routes and blueprints return cached or newly collected host data. The optional Fail2Ban hook checks its cached jail ban list. Forwarded client-IP headers are trusted only with PROXMENUX_TRUST_PROXY=1; otherwise the peer address is used. Persistent state includes SQLite databases and JSON files, including auth.json.",
|
||||
"diagramArrowLabel": "HTTP / WS",
|
||||
"nodes": {
|
||||
"clientLabel": "Client",
|
||||
@@ -31,7 +31,7 @@
|
||||
"stateLabel": "Local state",
|
||||
"stateDetail": "SQLite DB\n+ auth.json"
|
||||
},
|
||||
"threadsIntro": "The same process also runs four <strong>background threads</strong> started at boot — they don't serve HTTP, they push state into SQLite or into the notification queue while the host is up:",
|
||||
"threadsIntro": "The process also runs <strong>background threads</strong>. The examples below sample host data, update caches and SQLite, and dispatch notifications. Other workers also exist, and notification delivery depends on its enabled setting:",
|
||||
"headerThread": "Thread",
|
||||
"headerCadence": "Cadence",
|
||||
"headerJob": "Job",
|
||||
@@ -48,8 +48,8 @@
|
||||
},
|
||||
{
|
||||
"thread": "_vital_signs_sampler",
|
||||
"cadence": "~1 s",
|
||||
"job": "High-frequency CPU + temperature sampler used for live widgets in the Overview panel."
|
||||
"cadence": "CPU 5 s; temperature 15 s; memory 30 s",
|
||||
"job": "Samples CPU usage, memory and CPU temperature for live metrics and sustained-condition detection, independently of the five-minute health collector."
|
||||
},
|
||||
{
|
||||
"thread": "notification_manager.start()",
|
||||
@@ -62,9 +62,9 @@
|
||||
"heading": "systemd unit",
|
||||
"intro": "The installer drops a unit at <code>/etc/systemd/system/proxmenux-monitor.service</code>. Default content:",
|
||||
"items": [
|
||||
"<strong><code>User=root</code></strong> — required: SMART, <code>pvesh</code>, journal scopes, ZFS commands and the web terminal all need root.",
|
||||
"<strong><code>User=root</code></strong> — the supported service runs with root privileges for host operations such as SMART access, <code>pvesh</code> and ZFS management. Its web-terminal processes inherit those privileges.",
|
||||
"<strong><code>Restart=on-failure</code></strong> with a 10-second back-off — non-zero exits relaunch automatically.",
|
||||
"<strong><code>After=network.target</code></strong> — waits for the host network stack to be online."
|
||||
"<strong><code>After=network.target</code></strong> — orders startup after that target; it does not guarantee an assigned IP, DNS or external connectivity."
|
||||
],
|
||||
"inspectTitle": "Inspect the live unit"
|
||||
},
|
||||
@@ -73,13 +73,13 @@
|
||||
"intro": "The AppImage is a self-mounting filesystem. <code>AppRun</code> at the root sets up the environment and execs <code>flask_server.py</code>:",
|
||||
"consequencesIntro": "Two consequences of this layout:",
|
||||
"consequences": [
|
||||
"<strong>No host Python pollution.</strong> The vendored interpreter and packages are isolated inside the AppImage — upgrading the host's system Python doesn't affect the Monitor and vice-versa.",
|
||||
"<strong>Hardware tools are bundled too.</strong> <code>ipmitool</code>, <code>lm-sensors</code> and <code>upsc</code> ship inside the AppImage so the dashboard can read out-of-band sensors and UPS state without forcing the user to install Debian packages."
|
||||
"<strong>Bundled Python environment.</strong> The interpreter and Python packages are shipped inside the AppImage, avoiding installation into the host’s system Python environment. The application still uses host kernel interfaces and external host tools.",
|
||||
"<strong>Hardware clients are bundled too.</strong> <code>ipmitool</code>, <code>lm-sensors</code> and <code>upsc</code> are included. Readings still depend on supported hardware, access to sensors and, for UPS data, a configured NUT server."
|
||||
]
|
||||
},
|
||||
"flask": {
|
||||
"heading": "Flask app structure",
|
||||
"intro": "<code>flask_server.py</code> creates a single <code>Flask(__name__)</code> instance, enables CORS, and registers six blueprints plus a WebSocket initializer:",
|
||||
"intro": "<code>flask_server.py</code> creates one <code>Flask(__name__)</code> instance with restricted CORS origins, registers nine blueprints directly and initializes terminal routes. The table highlights the main modules; audit, VM-backup and PBS blueprints are also registered. PVE-specific prewarmers run only on PVE hosts.",
|
||||
"headerBlueprint": "Blueprint / module",
|
||||
"headerPrefix": "Routes prefix",
|
||||
"headerOwns": "Owns",
|
||||
@@ -109,7 +109,7 @@
|
||||
"prefix": [
|
||||
"/api/health/*"
|
||||
],
|
||||
"owns": "Public health probe, detailed status, active / dismissed errors, suppression settings."
|
||||
"owns": "Health probes, detailed status, active and dismissed errors, and suppression settings. Protected routes require authentication when it is enabled."
|
||||
},
|
||||
{
|
||||
"blueprint": "flask_terminal_routes.py",
|
||||
@@ -137,7 +137,7 @@
|
||||
"prefix": [
|
||||
"/api/proxmenux/*"
|
||||
],
|
||||
"owns": "Reads which ProxMenux post-install optimizations are installed on the host."
|
||||
"owns": "Reads the ProxMenux post-install optimizations recorded in the host registry."
|
||||
},
|
||||
{
|
||||
"blueprint": "flask_oci_routes.py",
|
||||
@@ -147,11 +147,11 @@
|
||||
"owns": "OCI / container app deployment helpers (Proxmox VE 9.1+)."
|
||||
}
|
||||
],
|
||||
"endpointsLink": "The full endpoint list with request / response shapes is in <link>API Reference</link>."
|
||||
"endpointsLink": "Integration endpoints and their request/response formats are described in <link>API Reference</link>."
|
||||
},
|
||||
"dataSources": {
|
||||
"heading": "Data sources",
|
||||
"intro": "Nothing is collected from a custom agent — the Monitor reads the same files and runs the same commands a human admin would:",
|
||||
"intro": "Host data comes mainly from standard libraries, files and administrative commands. Some guest information also depends on the QEMU Guest Agent or commands executed inside a CT:",
|
||||
"headerSource": "Source",
|
||||
"headerUsedFor": "Used for",
|
||||
"rows": [
|
||||
@@ -181,7 +181,7 @@
|
||||
},
|
||||
{
|
||||
"source": "nvidia-smi · intel_gpu_top",
|
||||
"usedFor": "GPU utilisation, VRAM, temperature, encoder / decoder load."
|
||||
"usedFor": "Available GPU utilisation, VRAM, temperature and encoder/decoder measurements; fields depend on the vendor, tool and driver."
|
||||
},
|
||||
{
|
||||
"source": "lspci · lscpu · dmidecode",
|
||||
@@ -201,7 +201,7 @@
|
||||
},
|
||||
"persistence": {
|
||||
"heading": "Persistence",
|
||||
"intro": "Two filesystem locations split state by sensitivity:",
|
||||
"intro": "Several state files have different contents and access requirements:",
|
||||
"headerPath": "Path",
|
||||
"headerOwner": "Owner",
|
||||
"headerContents": "Contents",
|
||||
@@ -214,25 +214,25 @@
|
||||
{
|
||||
"path": "/usr/local/share/proxmenux/.notification_key",
|
||||
"owner": "root <code>0600</code>",
|
||||
"contents": "32-byte XOR key used to encrypt sensitive notification settings before storing them in the DB (Telegram tokens, AI API keys, etc.)."
|
||||
"contents": "32-byte master key for the ENC2 notification-secret format. The implementation uses a per-value random salt, derived keys and a MAC; old fixed-key XOR values are supported for migration."
|
||||
},
|
||||
{
|
||||
"path": "/root/.config/proxmenux-monitor/auth.json",
|
||||
"owner": "root:root",
|
||||
"contents": "Authentication state: enabled flag, username, SHA-256 password hash, TOTP secret, backup codes, list of issued API tokens, list of revoked token hashes."
|
||||
"contents": "Authentication configuration: enabled flag, username, salted PBKDF2-HMAC-SHA256 password hash, TOTP secret, backup codes, per-install JWT signing secret, API-token metadata and revocation hashes. Legacy SHA-256 password hashes are migrated on successful login."
|
||||
},
|
||||
{
|
||||
"path": "/var/log/proxmenux-auth.log",
|
||||
"owner": "root:root",
|
||||
"contents": "Plain-text auth event log. Always written. If Fail2Ban is installed with the <code>[proxmenux]</code> jail, the jail reads this file to ban brute-force attempts; if not, the file simply accumulates the log entries."
|
||||
"contents": "Plain-text authentication event log, written independently of Fail2Ban. When the <code>[proxmenux]</code> jail is installed and enabled, it reads this log to detect repeated failed logins. Without that jail, the log remains available for inspection."
|
||||
}
|
||||
],
|
||||
"backupTitle": "Back up auth.json before reinstalling",
|
||||
"backupBody": "Reinstalling the AppImage replaces the binary but leaves <code>/root/.config/proxmenux-monitor/auth.json</code> and <code>/usr/local/share/proxmenux/health_monitor.db</code> intact. If you restore from a host backup, keep both files together — the API tokens stored in <code>auth.json</code> are validated against <code>JWT_SECRET</code>; if the DB and auth.json get out of sync, dismissed errors and stored tokens may misbehave."
|
||||
"backupBody": "Replacing the AppImage normally preserves <code>/root/.config/proxmenux-monitor/auth.json</code> and <code>/usr/local/share/proxmenux/health_monitor.db</code>. Preserve <code>auth.json</code> with its per-install jwt_secret to retain token validity and metadata. The database stores health and notification state independently. Back up <code>.notification_key</code> with the database to restore encrypted channel settings; restoring the DB alone does not restore API authentication."
|
||||
},
|
||||
"health": {
|
||||
"heading": "Health Monitor cycle",
|
||||
"intro": "Every 5 minutes <code>health_monitor.py</code> runs a deterministic cycle across the ten categories shown on the dashboard:",
|
||||
"intro": "About every five minutes, the background collector invokes <code>health_monitor.py</code> to run the checks behind the dashboard’s ten categories:",
|
||||
"items": [
|
||||
"Critical PVE services (<code>pveproxy</code>, <code>pvedaemon</code>, <code>pvestatd</code>, <code>pve-cluster</code>).",
|
||||
"Proxmox storage pools (<code>pvesh get /storage</code> + per-storage availability).",
|
||||
@@ -242,11 +242,11 @@
|
||||
"Updates: pending package upgrades and security patches.",
|
||||
"Logs: persistent / spike / cascade pattern detection in the system journal.",
|
||||
"Memory: OOM killer activity, sustained high pressure.",
|
||||
"Temperature: CPU / chassis sensors against vendor thresholds.",
|
||||
"Temperature: CPU and disk readings checked against monitoring thresholds; available sensors depend on the hardware.",
|
||||
"Security: authentication failures, ban events, fail2ban jail status."
|
||||
],
|
||||
"afterIntro": "Each finding is normalised into a stable <code>error_key</code> + category + severity. The persistence layer deduplicates against the existing <code>errors</code> table — repeated events update <code>last_seen</code> and the occurrence counter without spamming notifications.",
|
||||
"cycleEnd": "The cycle also auto-resolves stale errors using the per-category <em>Suppression Duration</em> setting, cleans up errors for resources that no longer exist (deleted VMs / removed disks / unmounted storages), and prunes the <code>events</code> log older than 30 days. The full catalogue of categories and the dashboard view that surfaces them is documented in <link>Dashboard → Health Monitor</link>."
|
||||
"afterIntro": "Findings use a stable <code>error_key</code>, category and severity. Existing records in <code>errors</code> are updated with <code>last_seen</code> and occurrence counts rather than duplicated. Notification delivery also has its own fingerprint and cooldown rules.",
|
||||
"cycleEnd": "Cleanup can auto-resolve stale errors using category <em>Suppression Duration</em> values and state-based rules, remove orphan records for resources that were removed, and prune <code>events</code> older than 30 days. The categories and dashboard are described in <link>Dashboard → Health Monitor</link>."
|
||||
},
|
||||
"notifications": {
|
||||
"heading": "Notification engine",
|
||||
@@ -254,7 +254,7 @@
|
||||
"items": [
|
||||
"<strong>Watchers</strong> push events: <code>JournalWatcher</code> tails the system journal, <code>TaskWatcher</code> polls the Proxmox task list, <code>ProxmoxHookWatcher</code> reacts to backup / replication / snapshot hooks, and <code>PollingCollector</code> handles slow data sources.",
|
||||
"<strong>Templates</strong> turn an event into a (title, body) pair. The same template can run through the configured AI provider (OpenAI / Anthropic / Gemini / Groq / Ollama / OpenRouter) to produce a plain-language rewrite; both versions are stored in <code>notification_history</code>.",
|
||||
"<strong>Channels</strong> deliver messages: Telegram, Discord, Email, Gotify, Pushover and Apprise (multi-channel). Each is implemented in <code>notification_channels.py</code> behind the same <code>create_channel()</code> / <code>send()</code> interface, so adding a new channel is a single class.",
|
||||
"<strong>Channels</strong> deliver messages through services including Telegram, Discord, Email, Gotify, Pushover and Apprise. Implementations in <code>notification_channels.py</code> use the shared <code>create_channel()</code> / <code>send()</code> interface.",
|
||||
"<strong>Encryption.</strong> Sensitive settings (<code>telegram.bot_token</code>, <code>discord.webhook_url</code>, <code>pushover.user_key</code>, <code>pushover.api_token</code>, <code>ai_api_key_*</code>, <code>email.password</code>) are encrypted with the key in <code>.notification_key</code> before being written to the DB and are masked in the interface."
|
||||
],
|
||||
"linksFooter": "Per-event toggles, channel overrides and AI configuration are surfaced in <notifLink>Settings → Notifications</notifLink> and <aiLink>Settings → AI Assistant</aiLink>."
|
||||
@@ -263,21 +263,21 @@
|
||||
"heading": "WebSocket terminal",
|
||||
"intro": "The <em>Terminal</em> tab in the dashboard is a thin <code>xterm.js</code> client wired to a server-side PTY through a WebSocket. Two transport modes:",
|
||||
"items": [
|
||||
"<strong>HTTP mode (default):</strong> Flask's development server with <code>flask-sock</code> handles upgrade requests. Good enough for LAN / direct access.",
|
||||
"<strong>HTTPS / WSS mode:</strong> when an SSL certificate is configured, the process switches to <code>gevent.pywsgi.WSGIServer</code> with <code>geventwebsocket.handler.WebSocketHandler</code>, so WebSockets work over TLS without polyfills."
|
||||
"<strong>HTTP mode (default):</strong> the built-in Flask server with <code>flask-sock</code> handles WebSocket upgrade requests.",
|
||||
"<strong>HTTPS / WSS mode:</strong> the TLS server uses <code>gevent.pywsgi.WSGIServer</code> with <code>geventwebsocket.handler.WebSocketHandler</code> for WebSocket connections over TLS."
|
||||
],
|
||||
"outro": "The PTY is a child of the Flask process, so it inherits <code>User=root</code> from the unit. Every terminal request goes through JWT auth; the user must already be logged in to the dashboard before a PTY is allocated.",
|
||||
"outro": "Terminal child processes inherit <code>User=root</code> from the service. With authentication enabled, opening a WebSocket requires a short-lived, single-use ticket obtained from the authenticated terminal-ticket endpoint with administrative scope. When authentication is disabled or declined, the handshake follows that unauthenticated mode.",
|
||||
"proxyNote": "If you access the Monitor through a reverse proxy, make sure WebSocket forwarding is enabled (the <code>Upgrade</code> and <code>Connection</code> headers). Without it the terminal won't work."
|
||||
},
|
||||
"proxy": {
|
||||
"heading": "Reverse proxy & Fail2Ban",
|
||||
"intro": "Two safeguards make sure security works the same way whether the dashboard is hit directly or through a reverse proxy:",
|
||||
"intro": "Client-IP selection and optional application-level blocking depend on these settings and components:",
|
||||
"items": [
|
||||
"<strong>Real client IP recovery.</strong> A <code>before_request</code> hook reads <code>X-Forwarded-For</code> and <code>X-Real-IP</code> in that order, falling back to <code>request.remote_addr</code>. The recovered address is what auth logging and rate limiting see. This is always on.",
|
||||
"<strong>Application-level Fail2Ban check (optional).</strong> When the dashboard sits behind a proxy, the kernel firewall can't block the real attacker IP — the connection always comes from the proxy. To plug that gap, the same hook above queries the <code>proxmenux</code> Fail2Ban jail every 30 seconds, caches the banned IP set, and short-circuits requests from those IPs with HTTP 403 inside Flask."
|
||||
"<strong>Client IP selection.</strong> The <code>before_request</code> ban check and auth logging trust <code>X-Forwarded-For</code>, then <code>X-Real-IP</code>, only when PROXMENUX_TRUST_PROXY=1. Otherwise they use <code>request.remote_addr</code>. Forwarded headers are not accepted automatically.",
|
||||
"<strong>Optional application-level Fail2Ban.</strong> A proxy connection can present the proxy as the TCP peer. When the trusted client address is available and the <code>proxmenux</code> jail is active, Flask rejects listed IPs with HTTP 403. The ban list is cached for 30 seconds and refreshed by request processing after expiry, rather than polled by a dedicated timer."
|
||||
],
|
||||
"calloutTitle": "Fail2Ban is not bundled",
|
||||
"calloutBody": "Fail2Ban is <strong>not</strong> installed by ProxMenux Monitor itself. The application-level check is a no-op until you install Fail2Ban on the host (e.g. via <link>Security → Fail2Ban</link> in the ProxMenux menu). When the <code>fail2ban-client</code> binary or the <code>proxmenux</code> jail is absent, the call fails silently and requests are not gated — auth still applies, but no IP-level banning.",
|
||||
"calloutBody": "Fail2Ban is <strong>not</strong> bundled or installed automatically. It can be installed through the optional Monitor Security workflow or <link>Security → Fail2Ban</link> in ProxMenux Scripts. If <code>fail2ban-client</code> or the <code>proxmenux</code> jail is absent, this application hook does not block requests. The configured authentication policy still applies.",
|
||||
"outro": "Reverse-proxy snippets (Nginx / Caddy / Traefik) and the Fail2Ban jail walkthrough are in <accessLink>Access & Authentication</accessLink> and <fail2banLink>Security → Fail2Ban</fail2banLink>."
|
||||
},
|
||||
"whereNext": {
|
||||
@@ -291,7 +291,7 @@
|
||||
{
|
||||
"label": "API Reference",
|
||||
"href": "/docs/monitor/api",
|
||||
"tail": " — every endpoint, token management, security best-practices."
|
||||
"tail": " — integration endpoints, token management and authentication conventions."
|
||||
},
|
||||
{
|
||||
"label": "Settings → ProxMenux Monitor",
|
||||
|
||||
@@ -108,7 +108,7 @@
|
||||
"coral": {
|
||||
"heading": "Coral TPU / AI Accelerators",
|
||||
"subHeading": "(conditional)",
|
||||
"intro": "Renders when the host has Google Coral or other AI-accelerator devices wired up. Each device opens a modal with its connection type (M.2 / mini-PCIe / USB), PCIe link width, vendor / product ID, kernel driver (<code>apex</code> for PCIe, <code>libedgetpu</code> for USB), kernel modules (<code>gasket</code> + <code>apex</code>), device nodes (<code>/dev/apex_*</code>), Edge TPU runtime status, live temperature and the firmware hardware-warning thresholds.",
|
||||
"intro": "Renders when the host has Google Coral or other AI-accelerator devices wired up. Each device opens a modal with its connection type (M.2 / mini-PCIe / USB), PCIe link width, vendor / product ID, software support (the <code>apex</code> kernel driver for PCIe, the <code>libedgetpu</code> user-space library for USB), kernel modules (<code>gasket</code> + <code>apex</code>), device nodes (<code>/dev/apex_*</code>), Edge TPU runtime status, live temperature and the firmware hardware-warning thresholds.",
|
||||
"imageAlt": "Coral Edge TPU detail modal: PCIe / M.2 connection, PCIe 5.0 GT/s x1 link, vendor 1ac1:089a, kernel driver apex, gasket and apex modules loaded, /dev/apex_0 present, Edge TPU Runtime not installed, temperature 53.5 °C with hardware warning thresholds",
|
||||
"imageCaption": "M.2 Coral with the host kernel modules loaded, the device node up and the firmware temperature warnings exposed. The runtime line goes green once the matching Edge TPU runtime is installed.",
|
||||
"pathsIntro": "Two install paths exist depending on the form factor:",
|
||||
@@ -189,7 +189,7 @@
|
||||
{
|
||||
"section": "CPU temperature history",
|
||||
"endpoint": "/api/temperature/history",
|
||||
"source": "Time series sampled by the Health Monitor every 5 min and persisted to SQLite."
|
||||
"source": "Time series recorded by the temperature collector every 60 seconds and persisted to SQLite."
|
||||
},
|
||||
{
|
||||
"section": "GPU live metrics",
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
"<strong>Click</strong> — every node except the host is clickable and opens the same per-interface drill-in modal documented below. Tapping the host is intentionally a no-op — there is no host-level modal in this view."
|
||||
],
|
||||
"useTitle": "When it's useful",
|
||||
"useBody": "The group cards below tell you <em>what</em> exists; the diagram tells you <em>how it's connected</em> and where the traffic is flowing right now. Patterns easy to spot at a glance: a bridge with no active guest attached, two heavy guests going through the same NIC (bottleneck candidate), or a single VM saturating a link while everything else is quiet."
|
||||
"useBody": "The group cards show <em>what</em> exists; the diagram shows <em>how it is connected</em> and where traffic is flowing. You can spot guests sharing a bridge, two busy guests sharing a NIC (a possible bottleneck), or one VM saturating a link while others are quiet."
|
||||
},
|
||||
"groups": {
|
||||
"heading": "Three interface groups",
|
||||
@@ -121,7 +121,7 @@
|
||||
"mode1Title": "Mode 1 — Historical view",
|
||||
"mode1Alt": "Network Latency modal in historical mode — Gateway target with a 1-hour timeframe and the past samples plotted",
|
||||
"mode1Caption": "Historical view — Gateway target over the last hour, fed from the latency-history database written every 60 seconds by the temperature/latency collector thread.",
|
||||
"mode1Body1": "The default mode when the modal opens. A second dropdown picks the timeframe (<em>1 Hour / 24 Hours / 7 Days / 30 Days / 1 Year</em>); data resolution drops with the window so the chart stays readable. Headline stats — Current / Min / Avg / Max — render above the chart, with a status pill (Excellent / Good / Fair / Poor) reflecting the current value against the thresholds below.",
|
||||
"mode1Body1": "The default mode when the dialog opens. A second dropdown selects the timeframe (<em>1 Hour / 6 Hours / 24 Hours / 3 Days / 7 Days</em>). Current / Min / Avg / Max appear above the chart, with an Excellent / Good / Fair / Poor status indicator based on the thresholds below.",
|
||||
"mode1Body2": "Source: the same latency samples the Health Monitor uses to detect persistent network slowdowns — sampled every 60 seconds against the gateway by the background <code>_temperature_collector_loop</code> thread, written to a local SQLite history.",
|
||||
"mode2Title": "Mode 2 — Real-time test",
|
||||
"mode2Alt": "Network Latency modal running a real-time ping test against Cloudflare — progress bar at 50%, live samples accumulating on the chart",
|
||||
@@ -162,9 +162,9 @@
|
||||
}
|
||||
],
|
||||
"reportTitle": "Network Latency Report (PDF)",
|
||||
"reportIntro": "Both modes have a <strong>Report</strong> button next to the target selector. Clicking it generates a PDF with everything you'd send to your ISP if you wanted to make a case for poor service.",
|
||||
"reportIntro": "Both modes have a <strong>Report</strong> button beside the target selector. It generates a PDF of the measurements that you can attach to a troubleshooting request to your ISP.",
|
||||
"reportPreviewAlt": "First page of the Network Latency Report PDF — Executive Summary with the gauge, latency statistics, the latency graph and the threshold guide",
|
||||
"reportPreviewCaption": "First page of the Network Latency Report — Executive Summary with the gauge dial and headline stats, the per-second latency graph, and the threshold guide. Page 2 onwards has the per-sample table and methodology.",
|
||||
"reportPreviewCaption": "The first page of the Network Latency Report contains the executive summary, gauge, main statistics, time-series latency graph and threshold guide. Page 2 onwards contains the per-sample table and methodology.",
|
||||
"downloadLabel": "Download sample Network Latency report (PDF)",
|
||||
"sectionsIntro": "The report has six sections:",
|
||||
"sections": [
|
||||
@@ -173,10 +173,10 @@
|
||||
"<strong>Latency Graph</strong> — area chart of every sample over the test window with a min/avg/max y-axis grid.",
|
||||
"<strong>Performance Thresholds</strong> — the same four-tier scale documented above, with a coloured dot per tier.",
|
||||
"<strong>Detailed Test Results</strong> — numbered table with timestamp, latency, packet loss and status for every sample. Useful for spotting micro-bursts that the headline averages hide.",
|
||||
"<strong>Methodology</strong> — test method (ICMP Echo Request), samples per test (3 consecutive pings), target name, target IP and a final \"Performance Assessment\" paragraph derived from the verdict."
|
||||
"<strong>Methodology</strong> — ICMP Echo Request, 3 consecutive pings per sample, target name and IP, and a final performance assessment derived from the status."
|
||||
],
|
||||
"useCaseTitle": "Use case: claims to your ISP",
|
||||
"useCaseBody": "Run the real-time test against Cloudflare for 2 minutes during a moment of perceived slowness, then click <em>Test Again</em> a few times to extend the dataset, and finally <em>Report</em>. The PDF carries the full per-sample table plus the methodology block — ISPs typically accept this as evidence, especially when correlated with timestamps from a separate complaint."
|
||||
"useCaseTitle": "Use case: support from your ISP",
|
||||
"useCaseBody": "Run the real-time Cloudflare test for 2 minutes while the connection feels slow, use <em>Test Again</em> to extend the record, then select <em>Report</em>. The PDF includes all samples and the methodology. You can attach it to a support request with the incident timestamps; your ISP decides what evidence it requires."
|
||||
},
|
||||
"excluding": {
|
||||
"heading": "Excluding noisy interfaces",
|
||||
@@ -202,7 +202,7 @@
|
||||
{
|
||||
"section": "Per-interface RX/TX time series",
|
||||
"endpoint": "/api/network/<iface>/metrics",
|
||||
"source": "<code>/proc/net/dev</code> sampled by the Health Monitor with byte-rate calculation."
|
||||
"source": "Live counters use <code>/proc/net/dev</code>; chart history comes from Proxmox RRD through pvesh. VM/LXC interfaces use guest RRD data; physical and bridge interfaces use node-level RRD data."
|
||||
},
|
||||
{
|
||||
"section": "Latency: current",
|
||||
@@ -212,7 +212,7 @@
|
||||
{
|
||||
"section": "Latency: historical",
|
||||
"endpoint": "/api/network/latency/history",
|
||||
"source": "Persisted samples — every 5 min by the Health Monitor cycle."
|
||||
"source": "Samples persisted about every 60 seconds by the background temperature/latency collector."
|
||||
}
|
||||
],
|
||||
"codeComment1": "# Cross-check the interface state seen by the dashboard",
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
},
|
||||
"intro": {
|
||||
"title": "Two scopes, one tab",
|
||||
"body": "The Security tab is divided into two clearly separated sections: <strong>ProxMenux Monitor</strong> (settings for the dashboard itself) and <strong>Proxmox VE</strong> (settings for the host underneath). Cards render conditionally — Fail2Ban and Lynis only appear once installed."
|
||||
"body": "The Security tab has two sections: <strong>ProxMenux Monitor</strong> (dashboard settings) and <strong>Proxmox VE</strong> (host settings). The Fail2Ban and Lynis cards offer installation when those tools are missing, then show their controls once installed."
|
||||
},
|
||||
"monitor": {
|
||||
"heading": "ProxMenux Monitor",
|
||||
@@ -101,13 +101,13 @@
|
||||
},
|
||||
"pve": {
|
||||
"heading": "Proxmox VE",
|
||||
"intro": "The host's own protections — firewall, intrusion prevention and security audit. Last two only render when their respective tools are installed."
|
||||
"intro": "The host’s protections: firewall, intrusion prevention and security audit. The Fail2Ban and Lynis cards offer installation if the tools are missing, then expose their management controls."
|
||||
},
|
||||
"firewall": {
|
||||
"heading": "Proxmox Firewall",
|
||||
"imageAlt": "Proxmox Firewall card showing Cluster Firewall and Host Firewall status, Quick Access Rules for ProxMenux Monitor and Proxmox Web UI, Rules Overview counters and a list of Firewall Rules with Add Rule button",
|
||||
"imageCaption": "Proxmox Firewall card — cluster-level and host-level enable / disable toggles, common ports as <em>Quick Access Rules</em>, totals across <em>Rules Overview</em>, and the full rule list with <em>+ Add Rule</em>.",
|
||||
"intro": "The card surfaces the Proxmox VE built-in firewall (which is independent from any host-level <code>iptables</code> / <code>nftables</code> you may run alongside). Three blocks:",
|
||||
"intro": "The card manages the Proxmox VE firewall configuration. The classic backend uses <code>iptables</code>; the alternative backend uses <code>nftables</code>. It does not manage arbitrary rules created outside Proxmox. Three blocks:",
|
||||
"items": [
|
||||
"<strong>Cluster Firewall + Host Firewall</strong> — global toggles. The cluster firewall must be active for any host-level rule to take effect; the card flags this dependency inline.",
|
||||
"<strong>Quick Access Rules</strong> — pre-defined rows for ports that matter to ProxMenux itself: <code>8008/TCP</code> (Monitor), <code>8006/TCP</code> (Proxmox Web UI). Each row shows the current allow / deny / unprotected state. The Proxmox Web UI is allowed via the <em>built-in</em> Proxmox firewall macro and can't be removed accidentally.",
|
||||
@@ -135,8 +135,8 @@
|
||||
"configAlt": "Configure sshd jail form with Max Retries, Ban Time in seconds with Permanent Ban option, Find Time, common values reminder, and Save Configuration button",
|
||||
"configCaption": "Editing the sshd jail — pick a stricter <em>Max Retries</em> for SSH if you only ever log in from your own subnet, or extend <em>Ban Time</em> for the public-facing dashboard.",
|
||||
"outro": "The full <em>What it installs / how it's configured / how to uninstall</em> walkthrough — including the manual install path, the SSH MaxAuthTries side effect, and the relationship with the <code>proxmenux-auth.log</code> journal — is in <link>ProxMenux → Security → Fail2Ban</link>.",
|
||||
"calloutTitle": "Without Fail2Ban, brute-force protection is best-effort",
|
||||
"calloutBody": "ProxMenux Monitor has its own <em>application-level</em> ban hook in the Flask request pipeline — but it only takes effect if Fail2Ban is installed and writes to the bans table. Without Fail2Ban, the Monitor logs failed logins to <code>proxmenux-auth.log</code> for future inspection but doesn't actively block IPs."
|
||||
"calloutTitle": "Without Fail2Ban, this mechanism does not block IPs",
|
||||
"calloutBody": "ProxMenux Monitor checks Fail2Ban’s banned IPs at the <em>application level</em> before handling Flask requests. It queries fail2ban-client and caches the result for 30 seconds; it does not read a SQL bans table. Without Fail2Ban, failed logins are recorded in <code>proxmenux-auth.log</code>, but this mechanism does not actively block IPs."
|
||||
},
|
||||
"lynis": {
|
||||
"heading": "Lynis Security Audit",
|
||||
@@ -145,7 +145,7 @@
|
||||
"whyUseful": "<strong>Why it's useful.</strong> Knowing the security posture of your server is hard to do by reading config files one by one. Lynis catches the things that are routinely overlooked: kernel hardening flags missing, weak SSH ciphers enabled, journal not persistent, sudoers <code>NOPASSWD</code> on default accounts, and many more. Re-running it after applying ProxMenux post-install tweaks gives you an objective number for the improvement.",
|
||||
"notInstalledAlt": "Lynis Security Audit card with Not Installed state and Install Lynis button, listing features: hardening scoring, vulnerability detection, compliance checking and GitHub source",
|
||||
"notInstalledCaption": "Lynis card before install — the blue box summarises what the tool does.",
|
||||
"notBundled": "Lynis is also <strong>not bundled</strong>. ProxMenux installs the latest release directly from the official GitHub source (not the Debian package, which lags several minor versions).",
|
||||
"notBundled": "Lynis is also <strong>not bundled</strong>. ProxMenux installs it directly from the official GitHub source rather than the Debian package.",
|
||||
"confirmAlt": "Install Lynis confirmation modal listing what Lynis does: hardening scoring, vulnerability detection, configuration analysis, compliance checking, source from official GitHub repository",
|
||||
"confirmCaption": "Install confirmation — explicit about the GitHub source.",
|
||||
"progressAlt": "Lynis Installation streaming panel: installing latest scan tool, version 3.1.6 confirmed, installation completed message",
|
||||
@@ -157,7 +157,7 @@
|
||||
"runningCaption": "Audit in progress — the action button shows a spinner and the card explicitly warns that the scan can take 2–5 minutes.",
|
||||
"finishedBody": "When it finishes, the card flips to the results view: hardening index, warnings, suggestions and an <em>Audit Reports</em> list with each historical scan.",
|
||||
"resultsAlt": "Lynis Security Audit card with results: Hardening Index 71 with Lynis 66 PVE 71 breakdown, 3 warnings, 40 suggestions, Security Hardening Score progress bar Proxmox Adjusted 71 of 100 in the Good range, audit reports list with PDF download and Run Security Audit button",
|
||||
"resultsCaption": "Audit results — Hardening Index <strong>71/100 (Good)</strong> on a sample run. The card also shows the \"Lynis raw score\" (66) versus the Proxmox-adjusted score (71) which adds back 11 points for findings the Lynis test corpus flags but are expected behaviour on Proxmox VE.",
|
||||
"resultsCaption": "Audit results — Hardening Index <strong>71/100 (Good)</strong> in a sample run. The card shows the Lynis raw score (66) and the Proxmox-adjusted score (71). The adjustment depends on the raw score and the share of findings classified as expected on Proxmox VE; it is not a fixed 11-point addition.",
|
||||
"scoreTitle": "Lynis raw score vs Proxmox-adjusted score",
|
||||
"scoreIntro": "Lynis ships rules tuned for general-purpose Debian. Proxmox legitimately diverges from some of them (services running as root for cluster reasons, custom <code>journald</code> tuning, etc.). The card shows both numbers so you can:",
|
||||
"scoreItems": [
|
||||
@@ -179,7 +179,7 @@
|
||||
{
|
||||
"card": "Authentication, 2FA, password change",
|
||||
"endpoint": "/api/auth/*",
|
||||
"source": "Local SQLite + JWT issued by the Monitor."
|
||||
"source": "Local auth configuration in ~/.config/proxmenux-monitor/auth.json; JWTs issued by the Monitor."
|
||||
},
|
||||
{
|
||||
"card": "SSL / HTTPS",
|
||||
|
||||
@@ -76,18 +76,18 @@
|
||||
},
|
||||
"thresholds": {
|
||||
"heading": "Health Monitor Thresholds",
|
||||
"intro": "Where the previous card decides <em>how long to stay quiet after a dismiss</em>, this one decides <strong>at what value an alert fires in the first place</strong>. Every check the Health Monitor runs is parameterised by a pair of numbers — a <strong>Warning</strong> threshold and a <strong>Critical</strong> threshold — and both are exposed here for the operator to tune.",
|
||||
"intro": "The previous card sets <em>how long to stay quiet after a dismiss</em>; this one sets <strong>the value at which an alert is raised</strong>. Most threshold-based checks have adjustable <strong>Warning</strong> and <strong>Critical</strong> values. The table below explains exceptions such as the combined swap rule and the current RAM detector.",
|
||||
"whatForTitle": "What it is for",
|
||||
"whatForIntro": "Defaults that ship with ProxMenux are sane for the average Proxmox host, but every environment has its own envelope:",
|
||||
"whatForItems": [
|
||||
"A small homelab with a single-disk SSD may want to page earlier on capacity (75 / 90 %) to leave room for snapshots.",
|
||||
"A datacenter host with redundant Ceph nodes can be more relaxed on memory warnings (a 90 % working set is normal under ZFS ARC).",
|
||||
"A passively-cooled mini-PC needs lower temperature thresholds than a server with forced-air cooling — same drive class, different physical envelope.",
|
||||
"Memory thresholds should reflect the host’s memory measurements and workload. Ceph redundancy alone does not justify relaxing memory warnings, and high ZFS ARC usage must not be confused with an unreclaimable working set.",
|
||||
"Choose drive-temperature thresholds using the drive’s specifications and observed operating conditions. Passive versus forced-air cooling alone does not determine a universally lower or higher safe limit.",
|
||||
"A heavily-virtualized host that pegs CPU during builds should not page on every 80 % spike, but must still alert on sustained pressure."
|
||||
],
|
||||
"whatForOutro": "Editing a threshold takes effect <strong>on the next scan</strong> — the Health Monitor re-reads the values from <code>/usr/local/share/proxmenux/health_thresholds.json</code> on every cycle, no service restart needed. The same numbers also feed the colour ranges of the dashboard widgets (the temperature line in the disk-temperature modal, the bars on the storage cards) so the visual classification matches what triggers the alert.",
|
||||
"whatForOutro": "Supported threshold changes take effect <strong>on the next scan</strong>. The Health Monitor reads <code>/usr/local/share/proxmenux/health_thresholds.json</code> each cycle without a restart. Selected widgets, including disk temperature, use these values; some capacity bars retain separate visual cutoffs. See the RAM row below for a current runtime exception.",
|
||||
"coloursTitle": "Status colours: how Warning and Critical render in the dashboard",
|
||||
"coloursIntro": "Every threshold below produces the same three-state classification across the dashboard — same colours for storage bars, CPU/memory rings, temperature chips, and the dot on the disk modal. Reading a colour anywhere in the Monitor maps to a definite range relative to the configured pair:",
|
||||
"coloursIntro": "Widgets that use the configured Warning/Critical pair follow the three-state classification below. Some visual indicators, including the PVE storage capacity bars, use their own cutoffs; do not infer a Health Monitor alert solely from their colour.",
|
||||
"headerColour": "Colour",
|
||||
"headerRange": "Range",
|
||||
"headerMeaning": "Meaning",
|
||||
@@ -105,7 +105,7 @@
|
||||
{
|
||||
"colour": "Red",
|
||||
"range": "value ≥ Critical",
|
||||
"meaning": "Critical state. The Health Monitor fires a CRITICAL event; CRITICAL bypasses Quiet Hours and always reaches the channel."
|
||||
"meaning": "Critical state. The Health Monitor raises a CRITICAL event, which bypasses Quiet Hours. Channel category/event settings still apply; delivery also depends on the channel being available."
|
||||
}
|
||||
],
|
||||
"sectionsTitle": "Sections and recommended defaults",
|
||||
@@ -125,13 +125,13 @@
|
||||
"section": "Memory",
|
||||
"warning": "85 %",
|
||||
"critical": "95 %",
|
||||
"gates": "RAM pressure on the host."
|
||||
"gates": "Host RAM pressure. The configurable Critical default shown here is 95 %, but the current detector uses a hard-coded 90 % sustained RAM rule; changing that setting does not currently change this runtime branch."
|
||||
},
|
||||
{
|
||||
"section": "Swap (critical only)",
|
||||
"warning": "—",
|
||||
"critical": "5 %",
|
||||
"gates": "Swap actually being used. The number is intentionally low: a healthy Proxmox host should rarely touch swap, so even 5 % is a meaningful signal of RAM pressure."
|
||||
"critical": "80 %",
|
||||
"gates": "Swap CRITICAL requires both swap usage above 80 % and available RAM below 15 %, sustained across the detector’s samples. Used swap alone does not trigger this rule."
|
||||
},
|
||||
{
|
||||
"section": "CPU temperature",
|
||||
@@ -143,25 +143,25 @@
|
||||
"section": "Disk temp — HDD",
|
||||
"warning": "60 °C",
|
||||
"critical": "65 °C",
|
||||
"gates": "Standard spinning drives. Manufacturer envelope tops out around 60–65 °C, so Critical is set right at the hard limit."
|
||||
"gates": "HDD temperature. The defaults are 60 °C Warning and 65 °C Critical; these are monitoring defaults, not a universal manufacturer limit. Check the specific drive model’s specifications."
|
||||
},
|
||||
{
|
||||
"section": "Disk temp — SSD",
|
||||
"warning": "70 °C",
|
||||
"critical": "75 °C",
|
||||
"gates": "2.5'' / M.2 SATA SSDs — run cooler than NVMe but warmer than HDDs."
|
||||
"gates": "SATA SSDs in 2.5-inch or M.2 formats. This class uses 70/75 °C monitoring defaults; actual temperature limits depend on the drive model."
|
||||
},
|
||||
{
|
||||
"section": "Disk temp — NVMe",
|
||||
"warning": "80 °C",
|
||||
"critical": "85 °C",
|
||||
"gates": "NVMe drives run hotter by design; controllers self-throttle above ~85 °C, so Warning catches the climb before throttling kicks in."
|
||||
"gates": "NVMe temperature. The class uses 80/85 °C monitoring defaults. Controller throttling temperatures vary by model and are not universally 85 °C."
|
||||
},
|
||||
{
|
||||
"section": "Disk temp — SAS",
|
||||
"warning": "55 °C",
|
||||
"critical": "65 °C",
|
||||
"gates": "Enterprise SAS drives share the same ~65 °C manufacturer limit as HDDs, but are normally deployed in rack chassis with active cooling. A reading at 55 °C already signals a cooling problem (failed fan, HVAC issue) <em>before</em> the drive itself is at risk — hence a lower Warning than HDD, not because SAS is less heat-tolerant."
|
||||
"gates": "SAS temperature. This class uses 55 °C Warning and 65 °C Critical. These are <em>monitoring defaults</em>, not proof of a cooling fault at 55 °C or a universal manufacturer limit."
|
||||
},
|
||||
{
|
||||
"section": "Disk space — host",
|
||||
@@ -185,7 +185,7 @@
|
||||
"section": "PVE storage",
|
||||
"warning": "85 %",
|
||||
"critical": "95 %",
|
||||
"gates": "Block-style PVE storages (LVM, LVM-thin, ZFS-pool, RBD/Ceph, PBS)."
|
||||
"gates": "PVE-managed storages, including LVM, LVM-thin, ZFS-pool, RBD/Ceph and PBS. These are not all block-storage backends."
|
||||
},
|
||||
{
|
||||
"section": "ZFS pool",
|
||||
@@ -197,7 +197,7 @@
|
||||
"defaultsTitle": "Defaults, overrides and reset",
|
||||
"defaultsBody": "The backend exposes a merged view: every section starts from the ProxMenux defaults (the values you see when the host is fresh) and you override only the knobs you care about. The card shows the <em>effective</em> value — the override if you set one, otherwise the default. A <strong>Reset</strong> button wipes every override and goes back to defaults across all sections at once.",
|
||||
"validationTitle": "Validation",
|
||||
"validationBody": "Saving rejects values that don't make sense (percentages outside 0–100, critical below warning, negative temperatures). The frontend shows the inline error; the backend validates again before persisting, so the API can't be tricked into a broken threshold by a hand-crafted PUT."
|
||||
"validationBody": "Saving validates values against each field’s allowed range and rejects Critical below Warning. The interface shows validation errors, and the backend validates API requests again before persisting changes."
|
||||
},
|
||||
"lxcDetection": {
|
||||
"heading": "LXC Update Detection",
|
||||
@@ -207,18 +207,18 @@
|
||||
"whatRunsTitle": "What the scan actually runs",
|
||||
"whatRunsIntro": "For each CT in <code>running</code> state with a supported package manager:",
|
||||
"whatRunsItems": [
|
||||
"<strong>Cache freshness gate.</strong> If the in-container apt/apk metadata cache is older than <strong>24 hours</strong>, a best-effort refresh runs first (<code>apt-get update -qq</code> on Debian/Ubuntu, <code>apk update</code> on Alpine) with a 60 s timeout. Any failure (no network, broken repo, timeout) is swallowed silently — the listing below still runs against whatever cache exists, so a transient repo issue can never make detection worse than before.",
|
||||
"<strong>Metadata freshness.</strong> If the in-container apt/apk metadata cache is older than <strong>24 hours</strong>, a refresh is attempted first (<code>apt-get update -qq</code> on Debian/Ubuntu, <code>apk update</code> on Alpine), with a 60 s timeout. If it fails, listing still uses the available cache; results may therefore be stale.",
|
||||
"<strong>Listing.</strong> Then ProxMenux runs <code>apt list --upgradable</code> / <code>apk list -u</code> and parses the output into a structured count plus a sample of the top package names.",
|
||||
"<strong>Per-CT dedup.</strong> A fingerprint built from count, security-count and the sorted top names is stored so a stable set of pending updates doesn't re-notify daily, while a meaningfully different set does."
|
||||
],
|
||||
"selfUpdateTitle": "CTs that self-update outside apt may legitimately report 0",
|
||||
"selfUpdateBody": "Detection only sees what the in-container package manager knows about. A CT whose key software updates itself outside apt (Plex's <code>plexupdate</code> cron, Docker containers updated via <code>docker pull</code>, Frigate's built-in updater, etc.) will keep reporting low or zero apt updates even when the appliance is actively staying current — that's correct, not a bug. The apt-level base system on the same CT may still have its own pending updates, which the scan does surface.",
|
||||
"selfUpdateBody": "Detection only covers the in-container package manager. Software updated outside apt, such as a configured <code>plexupdate</code> job or Docker images fetched with <code>docker pull</code>, is outside that count. Zero apt updates does not prove that every application is current. The same CT’s base system can still have package updates detected by the scan.",
|
||||
"refreshTitle": "Why the 24 h auto-refresh",
|
||||
"refreshBody": "Long-running appliance CTs frequently end up with apt caches months out of date because nobody routinely runs <code>apt update</code> inside them. Without the refresh, <code>apt list --upgradable</code> reports 0 updates from a frozen snapshot and the operator never sees the backlog. The threshold matches the rest of the check cycle — if the CT was refreshed within the last 24 h, ProxMenux trusts that signal and skips the refresh.",
|
||||
"refreshBody": "Long-running CTs can have stale apt metadata if nobody runs <code>apt update</code>. Then <code>apt list --upgradable</code> can miss newer updates. ProxMenux attempts a refresh when the metadata is older than 24 hours and skips it when the cache was refreshed more recently.",
|
||||
"toggleTitle": "Conditional notification toggle",
|
||||
"toggleBody": "The <code>lxc_updates_available</code> per-channel notification toggle in <strong>Notifications → Services</strong> only renders while detection is enabled. When you turn detection OFF, that row disappears from every channel's category list — but its stored preference is preserved in the DB, so re-enabling detection brings the toggle back at the value it had before.",
|
||||
"purgeTitle": "What gets purged when you disable detection",
|
||||
"purgeBody": "Turning the switch OFF immediately removes every <code>type=lxc</code> entry from <code>/usr/local/share/proxmenux/managed_installs.json</code>. The Hardware tab badges drop to zero on the next dashboard refresh. Turning it back ON repopulates the registry on the next detection cycle (or sooner if you trigger a manual refresh from the API)."
|
||||
"purgeBody": "Turning the switch OFF immediately removes every <code>type=lxc</code> entry from <code>/usr/local/share/proxmenux/managed_installs.json</code>. LXC update counts on the Hardware tab clear on the next dashboard refresh. Turning it back ON repopulates the registry on the next detection cycle, or sooner with a manual API refresh."
|
||||
},
|
||||
"storageExclusions": {
|
||||
"heading": "Remote Storage Exclusions",
|
||||
@@ -238,9 +238,9 @@
|
||||
"intro": "Same shape as Storage Exclusions but for network interfaces. Per interface: exclude from Health checks and / or exclude from notifications. Typical use cases:",
|
||||
"items": [
|
||||
"An intentionally-down spare bridge.",
|
||||
"A NIC that was physically removed but still references in <code>/etc/network/interfaces</code>.",
|
||||
"A NIC that was physically removed but is still referenced in <code>/etc/network/interfaces</code>.",
|
||||
"A VLAN sub-interface used only during maintenance windows.",
|
||||
"A management bridge that is up but doesn't carry traffic — flapping noisily on every cycle."
|
||||
"A management bridge with known, repeated link-state changes during maintenance."
|
||||
],
|
||||
"outro": "State is persisted in the <code>excluded_interfaces</code> SQLite table. Same purple <em>excluded</em> badge on the <link>Network tab</link> so excluded interfaces stay visible."
|
||||
},
|
||||
@@ -255,26 +255,26 @@
|
||||
},
|
||||
"optimizations": {
|
||||
"heading": "ProxMenux Optimizations",
|
||||
"intro": "A live, transparent inventory of every ProxMenux post-install optimization currently active on the host. Every time you apply a post-install option from the Scripts side — either via the <autoLink>Automated post-install</autoLink> or via the à-la-carte <customLink>Customizable post-install</customLink> — the corresponding script registers itself in <code>/usr/local/share/proxmenux/installed_tools.json</code>. The Monitor reads that file and renders this card so you can see, at a glance, what's been changed on your server.",
|
||||
"intro": "An inventory of post-install optimizations recorded by ProxMenux on the host. Options from <autoLink>Automated post-install</autoLink> and <customLink>Customizable post-install</customLink> register their application in <code>/usr/local/share/proxmenux/installed_tools.json</code>. The Monitor reads that registry for this card. A recorded entry is not a fresh check that every underlying setting remains unchanged.",
|
||||
"imageAlt": "ProxMenux Optimizations card with grid of installed tools, each row showing a green dot, tool name and version. Examples include APT IPv4 Force, Bashrc Customization, Fastfetch, Log2ram SSD Protection, Memory Settings Optimization, Setting persistent network interfaces, System Limits Increase, APT Language Skip, Entropy Generation haveged with Legacy badge, Kernel Panic Configuration, Logrotate Optimization, Network Optimizations, Subscription Banner Removal, VFIO IOMMU Passthrough — 14 active total",
|
||||
"imageCaption": "The card lists every active optimization with its name, version, a coloured dot and an orange <em>14 active</em> counter at the top right. Tools whose source is reachable are clickable.",
|
||||
"dotsTitle": "What the dots mean",
|
||||
"dotsItems": [
|
||||
"<green/> <strong>Green dot</strong> — current optimization, registered by the active version of ProxMenux. Source code is reachable: click the row to open it.",
|
||||
"<amber/> <strong>Amber dot + <em>legacy</em> badge</strong> — applied by an older ProxMenux version whose script has since been renamed or replaced. Still active on the host; the source opens in \"legacy\" mode (with an amber accent) so you can audit what was actually run."
|
||||
"<green/> <strong>Green dot</strong> — a registered optimization that is not marked legacy and has no available update indicated. Click the row when source code is available.",
|
||||
"<amber/> <strong>Amber dot + <em>legacy</em> badge</strong> — a registered optimization whose script was renamed or replaced. Its preserved source opens in legacy mode with an amber accent. This does not revalidate the current host configuration."
|
||||
],
|
||||
"clickTitle": "Click-through to source code",
|
||||
"clickBody": "Clicking a tool opens a modal with the exact bash function that applied the change, plus the script file path it lives in (<code>auto_post_install.sh</code> for the Automated bundle, <code>customizable_post_install.sh</code> for the à-la-carte side). Comments and shell constructs are syntax-highlighted; a Copy button puts the source on your clipboard. This is the \"show your work\" surface — verify what ProxMenux did to your host before any manual change you make on top.",
|
||||
"clickBody": "Clicking a tool opens a modal with its mapped bash function and script name (<code>auto_post_install.sh</code> or <code>customizable_post_install.sh</code>). Current tools are extracted from files on the host; legacy tools can use preserved source. Syntax highlighting and Copy help inspect the code. This is not a guaranteed snapshot of the exact code that ran at installation time.",
|
||||
"detailAlt": "Tool source code modal for APT IPv4 Force showing the bash function force_apt_ipv4 from customizable_post_install.sh version 1.0 with syntax-highlighted code that configures /etc/apt/apt.conf.d/99-force-ipv4 with Acquire ForceIPv4 true, registers the tool and emits a translate APT IPv4 configuration completed message",
|
||||
"detailCaption": "Source modal for <em>APT IPv4 Force</em> — exact <code>force_apt_ipv4()</code> function from <code>customizable_post_install.sh v1.0</code>, with syntax highlighting and a one-click Copy.",
|
||||
"whyTitle": "Why this matters",
|
||||
"whyBody": "ProxMenux changes things on your host: kernel parameters, repository configuration, network bits, log rotation, GPU passthrough, etc. Knowing exactly what's active is essential before you start adding manual customisation on top — and even more so if a different admin runs the host than the one who set it up. This card is the auditable record of every optimisation currently in effect, with the exact code that produced it.",
|
||||
"whyBody": "ProxMenux can change kernel parameters, repository settings, networking, log rotation and GPU passthrough. Review the recorded optimizations before adding manual changes, especially when another administrator set up the host. The card shows registry entries and available source; it does not audit every live setting or preserve an exact execution history.",
|
||||
"updatesTitle": "Updates available banner",
|
||||
"updatesBody": "When a post-install optimization gets a newer version on disk than the one currently registered on the host, the card shows an \"Updates available\" banner at the top with the count and an <strong>Apply</strong> button. Clicking <strong>Apply</strong> opens a per-optimization picker (the same one available from the Post-Install menu's <em>Apply available updates</em> entry). Pick which optimizations to lift; ProxMenux re-runs the corresponding function and refreshes the version in the registry. When everything is current the banner disappears.",
|
||||
"updatesAlt": "ProxMenux Optimizations card with an Updates available banner at the top — count of pending updates plus an Apply button that opens the per-optimization picker",
|
||||
"updatesCaption": "The banner only renders when at least one optimization has a newer version on disk. See <link>Apply Available Updates</link> for the full update flow and the Path-A equivalent in the shell menu.",
|
||||
"revertTitle": "Reverting an optimization",
|
||||
"revertBody": "The card is read-only — to undo an optimization, run the corresponding <link>Uninstall Optimizations</link> option from the ProxMenux Scripts menu. The uninstall step removes the entry from <code>installed_tools.json</code>, so it disappears from this card on the next refresh."
|
||||
"revertBody": "To undo an optimization, use the corresponding <link>Uninstall Optimizations</link> option in the ProxMenux Scripts menu. Uninstalling removes its entry from <code>installed_tools.json</code>, so the card stops listing it on the next refresh."
|
||||
},
|
||||
"dataCollected": {
|
||||
"heading": "How the data is collected",
|
||||
@@ -320,7 +320,7 @@
|
||||
{
|
||||
"card": "Optimization source-code modal",
|
||||
"endpoint": "/api/proxmenux/tool-source",
|
||||
"source": "Extracts the matching bash function from <code>auto_post_install.sh</code> or <code>customizable_post_install.sh</code> on the host."
|
||||
"source": "Extracts the mapped bash function from <code>auto_post_install.sh</code> or <code>customizable_post_install.sh</code> on the host; legacy tools can use preserved source."
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
},
|
||||
"thresholds": {
|
||||
"title": "Status colours and thresholds applied here",
|
||||
"intro": "Every bar, chip, and dot on this tab follows the same three-state classification — <green/> <strong>green</strong> below Warning, <amber/> <strong>amber</strong> from Warning to Critical, <red/> <strong>red</strong> at Critical and above. Recommended defaults shipped with ProxMenux:",
|
||||
"intro": "Health Monitor classifies status as <green/> <strong>green</strong> below Warning, <amber/> <strong>amber</strong> from Warning to Critical, and <red/> <strong>red</strong> at Critical and above. Its recommended defaults follow. Individual dashboard bars can use different visual cutoffs, described below.",
|
||||
"items": [
|
||||
"<strong>Capacity</strong> (host disks, PVE storages, ZFS pools, LXC mounts) — Warning 85 %, Critical 95 %.",
|
||||
"<strong>Disk temperature</strong> — HDD 60/65 °C · SSD 70/75 °C · NVMe 80/85 °C · SAS 55/65 °C (warning / critical)."
|
||||
@@ -33,7 +33,7 @@
|
||||
"localLabel": "Local Used",
|
||||
"localWhat": "Bytes used on local storages (LVM / LVM-thin / ZFS / dir on the host's own disks). Shows the used bytes prominently, with a footer line of <em>X.XX % of Y TB</em> so you see the fill-percentage at the same time.",
|
||||
"remoteLabel": "Remote Used",
|
||||
"remoteWhat": "Same shape as Local Used but for remote storages (NFS / CIFS / PBS / Ceph / iSCSI). Counted separately because remote outages don't affect local data and you typically size and monitor them differently.",
|
||||
"remoteWhat": "Same format as Local Used, for remote storages (NFS / CIFS / PBS / Ceph / iSCSI). Counted separately because local and remote failures have different scopes and these resources are typically sized and monitored differently.",
|
||||
"disksLabel": "Physical Disks",
|
||||
"disksIntro": "Two lines of breakdown for the inventory:",
|
||||
"disksItems": [
|
||||
@@ -45,9 +45,9 @@
|
||||
"heading": "Proxmox Storage card",
|
||||
"intro": "One row per storage configured in <code>/etc/pve/storage.cfg</code>. Each row shows the type badge (<code>nfs</code> / <code>cifs</code> / <code>zfspool</code> / <code>lvm</code> / <code>lvmthin</code> / <code>dir</code> / <code>pbs</code>), the storage name, an active / error / not-monitored badge, the usage percentage and a coloured progress bar:",
|
||||
"items": [
|
||||
"<strong>< 75 %</strong> — blue progress bar, value in blue.",
|
||||
"<strong>75 – 90 %</strong> — yellow progress bar, value in yellow (Health Monitor warns at this point).",
|
||||
"<strong>> 90 %</strong> — red progress bar, value in red (Health Monitor escalates).",
|
||||
"<strong>Up to 75 %</strong> — blue progress bar and value.",
|
||||
"<strong>Above 75 % through 90 %</strong> — yellow progress bar and value. These are visual cutoffs, separate from Health Monitor thresholds.",
|
||||
"<strong>> 90 %</strong> — red progress bar and value; this visual cutoff is separate from Health Monitor escalation.",
|
||||
"<strong>error</strong> — full row outlined in red, used when the storage is configured but unreachable (NFS server down, CIFS creds expired).",
|
||||
"<strong>excluded</strong> — purple outline + the badge \"not monitored\". Storages explicitly excluded by the user from health checks (handy for manual / archive volumes that are intentionally offline)."
|
||||
],
|
||||
@@ -70,7 +70,7 @@
|
||||
"items": [
|
||||
"<strong>Device path</strong> — <code>/dev/sda</code>, <code>/dev/nvme0n1</code>.",
|
||||
"<strong>Type badge</strong> — SATA / NVMe (and the relevant icon).",
|
||||
"<strong>System badge</strong> — orange tag that marks disks the host's OS is running from. The dashboard derives this from the mountpoints of <code>/</code> and <code>/boot</code>: any physical disk hosting them gets the <em>System</em> tag so you don't accidentally wipe or repurpose it. Disks without the tag are pure data drives.",
|
||||
"<strong>System badge</strong> — orange tag for disks identified as hosting the OS. Detection uses the mountpoints of <code>/</code> and <code>/boot</code>; physical disks hosting them receive the <em>System</em> tag. A missing tag does not prove a disk is safe to erase or repurpose.",
|
||||
"<strong>Model</strong> — vendor + model string from <code>smartctl -i</code>.",
|
||||
"<strong>Capacity</strong> — formatted human-readable.",
|
||||
"<strong>Temperature</strong> — current °C, coloured by the disk-type threshold (NVMe runs warmer than SATA).",
|
||||
@@ -96,7 +96,7 @@
|
||||
"overviewItems": [
|
||||
"<strong>Identity</strong> — model, serial, capacity, Health badge (Healthy / Warning / Critical).",
|
||||
"<strong>Wear & Lifetime</strong> — large life-remaining ring (97 %, 50 %, …) with the source attribute spelled out (<em>Media Wearout Indicator</em>, <em>Percentage Used</em>, …), a wear bar (current consumption %), an <em>Est. Life</em> projection in years and the total Data Written. NVMe drives also show <em>Available Spare</em>.",
|
||||
"<strong>SMART Attributes</strong> — six headline fields on a 2-column grid: Temperature, Power On Hours (with humanised duration like <em>3y 116d</em>), Rotation Rate (or <em>SSD</em>), Power Cycles, SMART Status, Reallocated Sectors, Pending Sectors, CRC Errors. The full attribute table lives in the SMART tab."
|
||||
"<strong>SMART Attributes</strong> — key fields on a 2-column grid: Temperature, Power On Hours (with humanised duration like <em>3y 116d</em>), Rotation Rate (or <em>SSD</em>), Power Cycles, SMART Status, Reallocated Sectors, Pending Sectors, CRC Errors. The full attribute table lives in the SMART tab."
|
||||
],
|
||||
"smartTitle": "Tab 2 — SMART",
|
||||
"smartImageAlt": "Disk drill-in modal — SMART tab with Run SMART Test buttons (Short / Extended), last-test result and the full SMART attribute table",
|
||||
@@ -108,11 +108,11 @@
|
||||
"<strong>SMART Attributes</strong> — the full attribute table (ID / name / value / worst / status with OK / warning / critical icons). For SATA / SAS, the classical numbered list. For NVMe, the structured fields from <code>nvme smart-log</code> (temperature, available spare, percentage used, data units written / read, host reads / writes, controller busy time, power cycles, unsafe shutdowns, media errors, error-log entries, warning / critical composite temperature time)."
|
||||
],
|
||||
"pdfTitle": "View Full SMART Report (PDF)",
|
||||
"pdfIntro": "At the bottom of the SMART tab, the <strong>View Full SMART Report</strong> button generates a printable, archive-ready PDF — the same structured report you'd send to a vendor for an RMA.",
|
||||
"pdfIntro": "At the bottom of the SMART tab, <strong>View Full SMART Report</strong> opens a printable report. Use the browser’s print dialog to save it as PDF for archiving or to share with vendor support for an RMA.",
|
||||
"pdfPreviewAlt": "First page of the generated SMART Health Report PDF — Executive Summary with the PASSED ring + Disk Information block",
|
||||
"pdfPreviewCaption": "First page of the SMART Health Report — Executive Summary with the PASSED ring and the full Disk Information block. The full PDF below has the SSD wear ring, every SMART attribute and the test history.",
|
||||
"pdfDownloadLabel": "Download sample SMART report (PDF)",
|
||||
"pdfSectionsIntro": "The report has five top-level sections:",
|
||||
"pdfSectionsIntro": "The report has six main sections:",
|
||||
"pdfSections": [
|
||||
"<strong>Executive Summary</strong> — large PASSED / FAILED verdict, plain-language disk health assessment paragraph (\"your disk is healthy / showing signs of wear / failing\"), and four quick stats (report timestamp, last-test type, test result, attributes checked).",
|
||||
"<strong>Disk Information</strong> — model, serial, capacity, type (HDD / SSD / NVMe), family, form factor, interface (SATA 3.3 · 6.0 Gb/s, …), TRIM support, current temperature with the optimal threshold, power-on time, power cycles, SMART status, plus the headline counters (pending sectors, CRC errors, reallocated sectors).",
|
||||
@@ -121,13 +121,13 @@
|
||||
"<strong>Last Self-Test Result + Full Self-Test History</strong> — the latest test (type, result, completion message, at which power-on-hours mark) plus a numbered table of every retained test.",
|
||||
"<strong>Recommendations</strong> — action items based on the verdict: <em>Disk is Healthy / Schedule periodic tests / Backup strategy</em> for healthy drives, escalating language with replacement guidance when attributes are out of range."
|
||||
],
|
||||
"pdfOutro": "The PDF is produced server-side and downloaded with a stable filename pattern (<code>SMART-<short-id>.pdf</code>) so multiple snapshots over time can sit side-by-side in your archive. Useful when you're tracking degradation across months or sending evidence to vendor support.",
|
||||
"pdfOutro": "The browser builds the printable report; Print and Save as PDF both open its print dialog. Choose a filename such as <code>SMART-<short-id>.pdf</code> when saving. Keep dated snapshots to compare changes over time or share them with vendor support.",
|
||||
"historyTitle": "Tab 3 — History",
|
||||
"historyImageAlt": "Disk drill-in modal — History tab listing past SMART tests with download and delete actions",
|
||||
"historyImageCaption": "History tab — every retained SMART test for this disk. Per row: type, timestamp, \"X days ago\" tag, latest marker, download (raw <code>smartctl</code> output) and delete actions.",
|
||||
"historyIntro": "The retained pool of SMART tests for this disk — both short and extended runs that completed. Each entry is the raw <code>smartctl</code> output captured at run time, plus the structured fields the Monitor parsed out for the dashboard. Per-row actions:",
|
||||
"historyItems": [
|
||||
"<strong>Download</strong> — saves the raw <code>smartctl -a</code> output as a text file. Identical to what the PDF report parses, useful when you need the exact line a vendor asks for.",
|
||||
"<strong>Download</strong> — saves the retained JSON snapshot with the <code>smartctl -a</code> data used by the report. Useful when vendor support needs the original diagnostic fields.",
|
||||
"<strong>Delete</strong> — removes the test from history. The retention limit set in the Schedule tab (<em>Last 5 / 10 / 20</em>) deletes oldest-first automatically; this action is the manual override."
|
||||
],
|
||||
"scheduleTitle": "Tab 4 — Schedule",
|
||||
@@ -154,7 +154,7 @@
|
||||
"HDD — typically cooler thresholds.",
|
||||
"SSD — moderate thresholds.",
|
||||
"NVMe — higher thresholds (NVMe runs hotter by design).",
|
||||
"SAS — same defaults as HDD."
|
||||
"SAS — defaults of 55 °C warning and 65 °C critical, distinct from HDD’s warning threshold."
|
||||
],
|
||||
"tempConfigurable": "All four are configurable from <em>Settings → Health Monitor Thresholds</em>.",
|
||||
"tempWhyTitle": "Why a history matters here",
|
||||
@@ -164,7 +164,7 @@
|
||||
"<strong>Threshold tuning.</strong> Before raising or lowering a threshold in <em>Settings → Health Monitor Thresholds</em>, the 30-day chart shows the disk's actual operating range so the new value lines up with what the hardware really does rather than a guess."
|
||||
],
|
||||
"obsTitle": "Observation history (across tabs)",
|
||||
"obsIntro": "Modern disks fail gradually. A disk can report SMART <strong>PASSED</strong> and still log occasional read errors in dmesg, drop SATA links, or expose pending sectors that come and go. The standard Proxmox UI shows you the current SMART verdict — it does not keep a history of those <em>signals</em>. ProxMenux does, and surfaces them right inside the disk modal.",
|
||||
"obsIntro": "Disk faults can develop gradually. A disk may report SMART <strong>PASSED</strong> while logging read errors in dmesg, losing SATA links or reporting intermittent pending sectors. ProxMenux keeps a history of these <em>signals</em> and shows it inside the disk dialog alongside the current SMART verdict.",
|
||||
"obsImageAlt": "Disk Details modal Overview tab showing a healthy disk with SMART status Passed, 0 reallocated/pending/CRC errors, and an Observations section listing one recorded I/O Error event with the raw kernel message, a human translation of the ATA error code, first and last occurrence timestamps and an occurrence count",
|
||||
"obsImageCaption": "A disk that <strong>SMART says is fine</strong> can still have an observation history. The card is the historical signal layer underneath the SMART verdict.",
|
||||
"obsWhatTitle": "What an observation is",
|
||||
@@ -177,8 +177,8 @@
|
||||
],
|
||||
"obsWhyTitle": "Why ProxMenux records and shows them",
|
||||
"obsWhyItems": [
|
||||
"<strong>Disk failure is rarely a single event.</strong> It usually starts with sporadic ATA bus errors, the odd UNC sector, or a couple of medium errors weeks before SMART flips to <em>FAILED</em>. Without persistence those early warnings disappear from dmesg on the next boot.",
|
||||
"<strong>SMART can lie.</strong> A drive can show all attributes green and still be on the way out — the observation layer catches the symptoms SMART doesn't expose (especially ICRC, IDNF, link resets at lower SATA speeds).",
|
||||
"<strong>Faults can develop over time.</strong> Sporadic ATA bus errors, UNC sectors or media errors may appear before SMART reports <em>FAILED</em>. A dmesg buffer alone does not preserve them across a reboot; the observation history does.",
|
||||
"<strong>SMART does not rule out every fault.</strong> A drive can show green attributes while experiencing problems. Observations retain additional symptoms, including ICRC, IDNF and SATA link resets or speed reductions.",
|
||||
"<strong>It separates \"is happening now\" from \"happened recently\".</strong> The Health Monitor auto-resolves transient errors as soon as they stop firing, which is great for keeping the active alert list clean — but you still want to see, days later, that this disk had three I/O errors that night. The observation table is the answer.",
|
||||
"<strong>It feeds the tiered notification model.</strong> The disk_io detector reads observation rate from this table to decide silent / WARNING / CRITICAL (the sliding 24h window introduced in 1.2.1.2). The history is what makes that classification possible."
|
||||
],
|
||||
@@ -213,12 +213,12 @@
|
||||
{
|
||||
"section": "SMART current values",
|
||||
"endpoint": "/api/storage/smart/<disk>",
|
||||
"source": "<code>smartctl -A <dev></code> — refreshed on demand, not cached."
|
||||
"source": "<code>smartctl -A <dev></code> is one available probe; the detail endpoint also uses full SMART JSON and NVMe-specific data."
|
||||
},
|
||||
{
|
||||
"section": "SMART self-test history",
|
||||
"endpoint": "/api/storage/smart/<disk>/history",
|
||||
"source": "Stored under <code>/var/lib/proxmenux-monitor/smart/<disk>/</code> as JSON snapshots."
|
||||
"source": "Stored under <code>/usr/local/share/proxmenux/smart/<disk>/</code> as JSON snapshots."
|
||||
},
|
||||
{
|
||||
"section": "Permanent observations",
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
"<strong>Message</strong> — the full untruncated message in a monospace block, ready to copy."
|
||||
],
|
||||
"maxLevelStoreTitle": "Journald MaxLevelStore",
|
||||
"maxLevelStoreBody": "On a fresh Proxmox install, journald defaults to <code>MaxLevelStore=warning</code>, which silently drops info-level messages. The Monitor detects this on startup and adds a drop-in (<code>/etc/systemd/journald.conf.d/proxmenux-loglevel.conf</code>) raising the threshold to <code>info</code> so the Logs tab actually has something to show across all severities.",
|
||||
"maxLevelStoreBody": "On a fresh Proxmox install, journald defaults to <code>MaxLevelStore=warning</code>, which silently drops info-level messages. The Monitor detects this on startup and adds a drop-in (<code>/etc/systemd/journald.conf.d/proxmenux-loglevel.conf</code>) raising the threshold to <code>info</code> so informational and more severe messages are stored. This does not include debug messages.",
|
||||
"backupsTitle": "Backups",
|
||||
"backupsBody": "Proxmox task history filtered to backup-related entries. One row per task (<code>vzdump</code>, PBS transfers, Garbage Collect, Verify) with the status (OK / WARNINGS / ERROR), guest involved, source storage, duration and the UPID. Click a row to load the full task log via <code>GET /api/task-log/<upid></code> — the same data Proxmox exposes through <em>Datacenter → Tasks</em>, scoped to backups.",
|
||||
"notificationsTitle": "Notifications",
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
},
|
||||
"header": {
|
||||
"title": "Dashboard: System Overview tab",
|
||||
"description": "The first tab the dashboard opens on. Four live metric cards across the top, the historical-metrics chart in the middle, and condensed storage / network panels at the bottom — all derived from the same APIs that drive the dedicated tabs.",
|
||||
"description": "The tab the dashboard opens on by default. Four live metric cards across the top, the historical-metrics chart in the middle, and condensed storage / network panels at the bottom — all derived from the same APIs that drive the dedicated tabs.",
|
||||
"section": "ProxMenux Monitor · Dashboard"
|
||||
},
|
||||
"readOnly": {
|
||||
@@ -13,7 +13,7 @@
|
||||
"body": "Nothing on this tab is a control surface — every panel is informational. Actions live in the dedicated tabs they link to: drill into Storage to manage disks, into VMs & LXCs to start / stop guests, into the Security tab to configure auth, and so on."
|
||||
},
|
||||
"captureAlt": "System Overview tab — four metric cards (CPU, Memory, Active VMs, Temperature), node metrics chart, and Storage / Network summary cards",
|
||||
"captureCaption": "The System Overview tab — what the dashboard opens on. The four cards are live, the chart below is historical, and the two cards at the bottom summarise Storage and Network.",
|
||||
"captureCaption": "The System Overview tab — the default landing tab. The four cards are live, the chart below is historical, and the two cards at the bottom summarise Storage and Network.",
|
||||
"topRow": {
|
||||
"heading": "Top row: live metric cards",
|
||||
"intro": "Four cards in a 2×2 grid on mobile, single row on desktop. Each updates from <code>/api/system</code> every few seconds.",
|
||||
@@ -102,9 +102,9 @@
|
||||
"intro": "Each panel manages its own loading state (<code>loadingStates.cpu</code>, <code>loadingStates.storage</code>, …) so a slow source doesn't block the rest. While a panel is fetching, it shows a pulse-animated skeleton; failed fetches degrade gracefully — for example, a missing temperature sensor renders the card as <em>N/A</em> instead of an error.",
|
||||
"items": [
|
||||
"<strong>Top metric cards</strong> — refresh every ~5 s. The CPU and temperature panels also receive a 1 s push from the vital-signs sampler.",
|
||||
"<strong>Node metrics chart</strong> — refresh every 30 s, or on timeframe change.",
|
||||
"<strong>Node metrics chart</strong> — loads when opened and when the timeframe or language changes. When a retry is needed, it waits 60 s.",
|
||||
"<strong>Storage card</strong> — refresh every 60 s. SMART data is cached longer (the Storage tab triggers a fresh read on demand).",
|
||||
"<strong>Network card</strong> — refresh every 5 s on the active timeframe.",
|
||||
"<strong>Network card</strong> — summary refreshes every 59 s and the traffic chart every 60 s on the active timeframe.",
|
||||
"<strong>Manual refresh</strong> — the Refresh button in the header forces all panels to re-fetch immediately."
|
||||
]
|
||||
},
|
||||
@@ -137,7 +137,7 @@
|
||||
{
|
||||
"card": "Refresh cadence",
|
||||
"endpoint": "—",
|
||||
"source": "CPU / network 5 s; storage / VMs 30 s; static info every 5 min. The Refresh button in the header forces an immediate re-fetch on every panel."
|
||||
"source": "System data every 5 s; storage, VMs and network summary every 59 s. The Refresh button in the header forces an immediate re-fetch on every panel."
|
||||
}
|
||||
],
|
||||
"codeComment1": "# Single call that backs the header pill",
|
||||
@@ -165,7 +165,7 @@
|
||||
{
|
||||
"label": "Dashboard index",
|
||||
"href": "/docs/monitor/dashboard",
|
||||
"tail": " — the other eight tabs at a glance."
|
||||
"tail": " — the other dashboard tabs at a glance."
|
||||
},
|
||||
{
|
||||
"label": "Architecture",
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
"auth": {
|
||||
"heading": "Authentication",
|
||||
"items": [
|
||||
"The WebSocket upgrade carries the JWT in the <code>Authorization</code> header. If auth is enabled and the token is missing or expired, the connection is rejected with HTTP 401 before a PTY is allocated.",
|
||||
"The browser first requests a single-use ticket through <code>POST /api/terminal/ticket</code>, authenticated with a JWT in the <code>Authorization</code> header. It then passes that ticket when opening the WebSocket. The ticket expires after 5 seconds; with authentication enabled, a missing or invalid ticket closes the connection before a PTY is allocated.",
|
||||
"If the Monitor sits behind a reverse proxy, the proxy must forward WebSocket upgrades. See the <link>Access & Authentication</link> page for Nginx / Caddy / Traefik snippets."
|
||||
]
|
||||
},
|
||||
@@ -120,7 +120,7 @@
|
||||
"headerFix": "Fix",
|
||||
"rows": [
|
||||
{
|
||||
"cause": "Session JWT expired (24 h window).",
|
||||
"cause": "Session JWT expired when opening or reconnecting a terminal (24 h window).",
|
||||
"fix": "Refresh the page and log in again. The terminal isn't designed for unattended sessions, so the JWT lifetime matches the regular dashboard login."
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"meta": {
|
||||
"title": "ProxMenux Monitor — Dashboard: VMs & LXCs tab | ProxMenux Documentation",
|
||||
"description": "The VMs & LXCs tab inventories every guest on the host with live CPU / memory / disk usage. Per-guest drill-in shows configuration, resources, backups, full guest logs, notes, and Start / Shutdown / Reboot / Stop controls."
|
||||
"description": "The VMs & LXCs tab inventories every guest on the host with live CPU / memory / disk usage. Per-guest details show configuration, resources, backups, recent Proxmox log results, notes, and Start / Shutdown / Reboot / Stop controls."
|
||||
},
|
||||
"header": {
|
||||
"title": "Dashboard: VMs & LXCs tab",
|
||||
@@ -10,7 +10,7 @@
|
||||
},
|
||||
"intro": {
|
||||
"title": "The control surface for guests",
|
||||
"body": "Other tabs are read-only; this is the one you act from. Anything that changes guest state goes through <code>POST /api/vms/<vmid>/control</code> with an explicit confirmation and the response is reflected back in the guest's row. There is no force-shutdown without going through the dedicated Stop button."
|
||||
"body": "Use this tab to control guest lifecycles. Lifecycle actions go through <code>POST /api/vms/<vmid>/control</code> with an explicit confirmation and the response is reflected back in the guest’s row. Force shutdown requires the dedicated Force Stop button."
|
||||
},
|
||||
"topRow": {
|
||||
"heading": "Top row: four stat cards",
|
||||
@@ -186,9 +186,9 @@
|
||||
"source": "PVE RRD data (<code>pvesh get /nodes/<node>/qemu/<id>/rrddata</code>) condensed to a chart-friendly shape."
|
||||
},
|
||||
{
|
||||
"section": "Recent task logs (modal)",
|
||||
"section": "Recent Proxmox log results",
|
||||
"endpoint": "/api/vms/<vmid>/logs",
|
||||
"source": "Tasks for that <code>vmid</code> from <code>/var/log/pve/tasks/index</code>."
|
||||
"source": "<code>pvesh get /nodes/<node>/<qemu|lxc>/<vmid>/log --start 0 --limit 1000</code>. A command failure returns an empty list; this does not provide complete in-guest system logs."
|
||||
},
|
||||
{
|
||||
"section": "Backups available for guest",
|
||||
|
||||
@@ -18,13 +18,13 @@
|
||||
},
|
||||
"howItWorks": {
|
||||
"heading": "How it works",
|
||||
"intro": "The Health Monitor runs on two parallel lanes inside the Monitor process. A lightweight <strong>vital signs sampler</strong> reads CPU, memory and temperature every few seconds so that sustained-threshold conditions are detected fast; in parallel, the <strong>full health cycle</strong> runs every five minutes and exercises every category from end to end. Both lanes converge into the same SQLite tables — and from there, three consumers read the state independently.",
|
||||
"intro": "The Health Monitor uses two parallel threads inside the Monitor process. A lightweight <strong>vital signs sampler</strong> fills CPU, memory and temperature history buffers. The <strong>full health cycle</strong> reads those buffers, evaluates sustained conditions and runs the other checks approximately every five minutes, then writes findings to SQLite for the dashboard and notification flow.",
|
||||
"scannerTitle": "From sample to stored finding",
|
||||
"scannerCaption": "The scanner. Vital signs are sampled fast so sustained-CPU / sustained-memory pressure can be detected before the next 5-min cycle. The full cycle reads those buffers and runs the heavier checks (SMART, ZFS pool state, journal scanning, service health, etc.) before writing the structured findings to SQLite.",
|
||||
"scannerCaption": "The sampler fills history buffers between health cycles: CPU usage every 5 s, memory every 30 s and temperature every 15 s. It does not evaluate alerts itself. The full cycle evaluates those buffers and runs heavier checks (SMART, ZFS pool state, journal scanning and service health) before writing findings to SQLite.",
|
||||
"scannerArrowLabel": "step",
|
||||
"scannerNodes": {
|
||||
"samplerLabel": "Vital signs sampler",
|
||||
"samplerDetail": "CPU usage 30 s\nMemory 30 s\nTemperature 15 s\n→ history buffers",
|
||||
"samplerDetail": "CPU usage 5 s\nMemory 30 s\nTemperature 15 s\n→ history buffers",
|
||||
"cycleLabel": "Full health cycle",
|
||||
"cycleDetail": "Every 5 min\nReads buffers\n+ live probes\n(SMART, ZFS,\nservices, journal…)",
|
||||
"checksLabel": "Per-category checks",
|
||||
@@ -58,7 +58,7 @@
|
||||
{
|
||||
"category": "CPU & Temperature",
|
||||
"checks": "CPU usage with hysteresis, sensor temperature",
|
||||
"events": "High sustained load; CPU temperature crossing the vendor warning / critical thresholds."
|
||||
"events": "Sustained high CPU usage; sensor-temperature warnings based on sampled readings."
|
||||
},
|
||||
{
|
||||
"category": "Memory & Swap",
|
||||
@@ -68,7 +68,7 @@
|
||||
{
|
||||
"category": "Storage",
|
||||
"checks": "Proxmox storages, root filesystem",
|
||||
"events": "Storage offline (NFS server unreachable, CIFS expired creds); root mount > 90 %; LVM thin pool nearing full."
|
||||
"events": "Root or other monitored filesystems crossing configured space thresholds (85% warning / 95% critical by default); offline NFS export; CIFS authentication failure; full LVM-thin pool."
|
||||
},
|
||||
{
|
||||
"category": "Disks & SMART",
|
||||
@@ -98,7 +98,7 @@
|
||||
{
|
||||
"category": "System Updates",
|
||||
"checks": "Pending updates, security updates, kernel / PVE version, system age",
|
||||
"events": "Security updates available; pinned kernel several minor versions behind; host uptime > 90 days. When updates are pending, the Overview header shows an <em>Update Now</em> button that runs the full ProxMenux update flow inline; on close it re-hits the health endpoint with <code>?refresh=1</code> so the badge count reflects the new state without waiting for the next 5-min cycle."
|
||||
"events": "Pending security or running-kernel updates; a system not updated for a long time. The Overview header shows an <em>Update Now</em> button when updates are pending. It runs the full ProxMenux update flow inline; closing it refreshes health with <code>?refresh=1</code> so the badge reflects the new state without waiting for the next cycle."
|
||||
},
|
||||
{
|
||||
"category": "Security & Certificates",
|
||||
@@ -141,7 +141,7 @@
|
||||
],
|
||||
"infoNote": "A category that is <strong>OK</strong> but has dismissed events still inside their suppression window is rendered as <strong>INFO</strong> — to remind you that something is being silenced rather than that nothing was ever wrong.",
|
||||
"unknownTitle": "UNKNOWN, when a check can't complete",
|
||||
"unknownBody": "A check that fails to produce a verdict for three cycles in a row (a probe that times out, a sensor that disappeared, a tool that exits with an error) is recorded internally as <code>UNKNOWN</code>. The dashboard surfaces this as a yellow status — the overall view caps <code>UNKNOWN</code> at <strong>WARNING</strong> so it never escalates a healthy host to CRITICAL on its own."
|
||||
"unknownBody": "An unavailable check returns <code>UNKNOWN</code>; after three consecutive such cycles, a persistent finding is recorded. The dashboard shows an unavailable check in yellow. The overall view caps <code>UNKNOWN</code> at <strong>WARNING</strong>, so an unavailable check alone does not make the host CRITICAL."
|
||||
},
|
||||
"dashboardView": {
|
||||
"heading": "The dashboard view",
|
||||
@@ -161,7 +161,7 @@
|
||||
"intro": "Some events are noisy by nature — a <em>System Updates: pending updates available</em> stays true until you patch the host, and you don't want a notification every five minutes for a week. The <em>Update Now</em> button in the Overview header is the direct shortcut to resolve that specific case in place; for everything else, or when patching is deferred, the Health Monitor also offers two coupled silencing mechanisms:",
|
||||
"step1": "<strong>Per-event Dismiss action</strong> in the modal. The Dismiss button opens a small dropdown with three options — <strong>24 hours</strong>, <strong>7 days</strong> or <strong>Permanently</strong> — letting you choose how long this specific alert stays silenced regardless of the category's default. Picking one calls <code>POST /api/health/acknowledge</code> with the <code>error_key</code> and the chosen <code>suppression_hours</code> (<code>-1</code> for permanent). The event moves to the Dismissed list with a timestamped <code>acknowledged_at</code>.",
|
||||
"dropdownImageAlt": "Dismiss dropdown on a Health Monitor alert — 24 hours, 7 days or Permanently",
|
||||
"dropdownImageCaption": "Per-event Dismiss dropdown. The chosen window applies to this single alert; if no per-event window is selected the category's default is used. Permanent dismisses are tagged with a distinct amber <em>Permanent</em> badge in the Dismissed list and never re-fire.",
|
||||
"dropdownImageCaption": "Per-event Dismiss dropdown. The chosen window applies to this single alert; without a per-event choice the category default is used. Permanent dismissals have an amber <em>Permanent</em> badge and normally do not re-fire. The critical CPU-temperature override described below is an exception.",
|
||||
"step2": "<strong>Per-category Suppression Duration setting</strong>. From the Settings → Health Monitor card (or <code>POST /api/health/settings</code>), each of the ten categories has its own default window applied when a Dismiss is fired without a per-event choice:",
|
||||
"imageAlt": "Per-category Suppression Duration settings card in Settings → Health Monitor",
|
||||
"imageCaption": "Suppression Duration card — one dropdown per category. Pick a longer window for noisy events (e.g. pending updates) and shorter for ones you want to re-evaluate quickly. Active Suppressions are listed underneath (see below).",
|
||||
@@ -171,38 +171,38 @@
|
||||
"autoTitle": "Auto-suppression when you change the Duration",
|
||||
"autoBody": "Setting a category's Suppression Duration to anything other than the default 24 h has a second effect beyond user-initiated dismissals: <strong>future findings in that category enter the table already acknowledged</strong> with that duration. This is by design — if you've told the Monitor that you want disk-related events silenced for a week, brand-new disk findings honour that intent without you having to dismiss each one by hand. They appear directly in the Dismissed list with the configured remaining time. Categories left at 24 h are unaffected and behave the classic way (new findings land in Active until you act).",
|
||||
"tempTitle": "CPU temperature CRITICAL is the safety override",
|
||||
"tempBody": "One specific finding bypasses the suppression entirely: <strong>CPU temperature CRITICAL</strong>. If the sensor crosses the critical threshold, the alert re-fires regardless of any prior dismissal — a cooked CPU is a cooked CPU. This is the only built-in override of the dismiss model.",
|
||||
"nonDismissableTitle": "Findings that cannot be dismissed",
|
||||
"nonDismissableBody": "A handful of findings are flagged non-dismissable on purpose — they signal a condition where silencing the alert could cost data, hardware or connectivity. The Dismiss button is hidden for these rows; the alert clears only when the underlying condition recovers and the auto-resolve cleanup picks it up. Other findings (transient I/O events on a healthy disk, recovered states) are also marked non-dismissable but for the opposite reason: there's nothing to silence because the row is already informational and self-clearing.",
|
||||
"tempBody": "The persistence layer has a special guard for a record marked <strong>CPU temperature CRITICAL</strong>: if such a record is received, it bypasses prior acknowledgment and auto-suppression. This guard does not mean that every temperature checker emits a critical record whenever a configured critical threshold is crossed.",
|
||||
"nonDismissableTitle": "Which findings can be dismissed?",
|
||||
"nonDismissableBody": "The modal shows Dismiss only when an individual check explicitly returns dismissable = true. A false or missing flag hides that button. Severity alone does not determine this permission: several critical checks allow acknowledgment. The examples below describe the current payloads; silencing an alert does not repair its cause.",
|
||||
"headerFinding": "Finding",
|
||||
"headerWhy": "Why it can't be dismissed",
|
||||
"headerWhy": "Dismiss behavior",
|
||||
"rows": [
|
||||
{
|
||||
"finding": "CPU temperature warning / critical",
|
||||
"why": "Hardware risk — sustained over-temperature damages silicon. Silencing would let a cooking CPU run unnoticed."
|
||||
"why": "The sampled sensor-temperature warning explicitly allows dismissal. A separate persistence guard bypasses acknowledgment if a CPU-temperature record arrives with CRITICAL severity."
|
||||
},
|
||||
{
|
||||
"finding": "Filesystem space critical (root mount)",
|
||||
"why": "Data loss risk — a full root prevents writes and corrupts state. The alert must remain visible until you free space."
|
||||
"why": "Filesystem-space checks explicitly allow dismissal, including critical usage. Acknowledging the alert does not free space or remove the risk of failed writes."
|
||||
},
|
||||
{
|
||||
"finding": "ZFS pool DEGRADED / FAULTED",
|
||||
"why": "Data integrity risk — pool failure threatens every dataset on it. Silencing while the pool is unhealthy is never the right answer."
|
||||
"why": "ZFS pool-capacity checks explicitly allow dismissal. Other pool findings follow their own flag. Acknowledgment does not repair a degraded or faulted pool."
|
||||
},
|
||||
{
|
||||
"finding": "Disk I/O errors with SMART FAILED",
|
||||
"why": "Drive failure confirmed by SMART — masking hides real hardware dying. The alert stays until the device is replaced (or removed from the host)."
|
||||
"why": "Persisted journal-derived disk findings can allow dismissal. SMART FAILED still needs investigation; whether the button appears depends on the individual check flag, not on the alert text alone."
|
||||
},
|
||||
{
|
||||
"finding": "Network interface DOWN",
|
||||
"why": "Connectivity loss — bridges, bonds and physical interfaces with active traffic must stay visible. Silencing them would mask a remote-management outage."
|
||||
"why": "Persisted interface-DOWN checks explicitly allow dismissal. This does not restore a bridge, bond or physical link, or recover remote access."
|
||||
},
|
||||
{
|
||||
"finding": "I/O events on healthy disks (INFO)",
|
||||
"why": "Transient ATA / dmesg events on a disk whose SMART says OK — flagged INFO and self-clearing. Nothing to dismiss because the next cycle already removes them."
|
||||
"why": "Transient ATA or dmesg events with healthy SMART can be informational and self-clearing. Informational rows need not expose a Dismiss button; the checker determines when the condition is resolved."
|
||||
}
|
||||
],
|
||||
"principle": "Everything else can be dismissed. The principle is: alerts that indicate \"real damage in progress\" or that have already self-resolved are kept off the dismiss path; alerts about sustained conditions you may want to acknowledge and re-check later (high CPU usage, pending updates, certificate near expiry, log warnings, VM startup hiccups, etc.) all expose the Dismiss button."
|
||||
"principle": "Use the button shown for the actual finding, rather than inferring availability from its category or severity. Acknowledgment controls alert presentation and notifications; recovery checks and disk history remain separate."
|
||||
},
|
||||
"autoresolve": {
|
||||
"heading": "Auto-resolution and cleanup",
|
||||
@@ -283,13 +283,13 @@
|
||||
"If the event type is <strong>enabled</strong> in the global notification settings, and the channel hasn't silenced this category, an event is queued.",
|
||||
"The template engine renders a (title, body) pair from the structured details. If the AI rewriter is enabled, the same pair is also passed through the configured provider for a plain-language version.",
|
||||
"The channel implementation ships it: Telegram message, Discord embed, Gotify push or email. The dispatch outcome is stored in <code>notification_history</code>.",
|
||||
"If a dismiss arrives later, the suppression window kicks in and any further re-fires of the same <code>error_key</code> stay queue-side until the window closes."
|
||||
"Dismissing an alert starts its suppression window. Further detections of the same <code>error_key</code> normally produce no new notification during that window; they are not automatically queued for delivery when it ends. The critical CPU-temperature override remains an exception."
|
||||
],
|
||||
"outro": "Channel configuration (Telegram bot token, webhook URLs, AI provider keys, per-event toggles, channel overrides) is documented in <notifLink>Notifications</notifLink> and <aiLink>AI Assistant</aiLink>."
|
||||
},
|
||||
"rest": {
|
||||
"heading": "REST endpoints",
|
||||
"intro": "Everything the modal does is callable from the API — handy for scripts, custom dashboards or your own chat-bot integration. Every call needs an API token; calls that change something — dismissing an alert, saving the suppression settings, cleaning up orphans — require one with full_admin scope, and a read_only token receives 403.",
|
||||
"intro": "The modal actions are also available through the API for scripts, custom dashboards or chatbots. When Monitor authentication is enabled, calls need a valid bearer token, either from login or the API-token screen. Changes such as acknowledging alerts, saving suppression settings or cleaning orphan errors require full_admin scope; read_only receives 403. When authentication is disabled or declined, these decorators allow access without a token.",
|
||||
"headerEndpoint": "Endpoint",
|
||||
"headerMethod": "Method",
|
||||
"headerUse": "Use",
|
||||
@@ -297,7 +297,7 @@
|
||||
{
|
||||
"endpoint": "/api/health",
|
||||
"method": "GET",
|
||||
"use": "Small health probe — returns JSON with <code>status</code>, <code>timestamp</code> and <code>version</code>. Suitable for Uptime Kuma keyword checks; the receiver must send the bearer header."
|
||||
"use": "Lightweight health probe returning JSON with <code>status</code>, <code>timestamp</code> and <code>version</code>. Suitable for Uptime Kuma keyword checks; when Monitor authentication is enabled, the client must send a bearer header."
|
||||
},
|
||||
{
|
||||
"endpoint": "/api/health/status",
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
},
|
||||
{
|
||||
"name": "VMs & Containers",
|
||||
"description": "Inventory of all VMs and LXCs with status, resources and uptime. Drill-in shows config, historical metrics, full guest logs and start/stop/reboot/shutdown actions."
|
||||
"description": "Inventory of VMs and LXCs with status, resources and uptime. The detail view shows configuration, historical metrics, Proxmox log downloads and start/stop/reboot/shutdown actions. Log availability is limited by the Proxmox endpoint; this is not a complete guest OS log archive."
|
||||
},
|
||||
{
|
||||
"name": "Hardware",
|
||||
@@ -52,7 +52,7 @@
|
||||
},
|
||||
{
|
||||
"name": "Terminal",
|
||||
"description": "Browser shell to the host or to any VM/CT, powered by <code>xterm.js</code> over WebSockets. Authenticated and audited like the rest of the API."
|
||||
"description": "Browser shell for the host and supported VM/CT console access, powered by <code>xterm.js</code> over WebSockets. It follows the configured authentication mode: when authentication is disabled or declined, reachable clients can open a session without login. This description does not imply per-command auditing."
|
||||
},
|
||||
{
|
||||
"name": "Security",
|
||||
@@ -81,7 +81,7 @@
|
||||
"intro": "Two access patterns are supported and the application detects which one is in use:",
|
||||
"codeComment1": "# 1) Direct access on the host",
|
||||
"codeComment2": "# 2) Via reverse proxy (Nginx / Caddy / Traefik)",
|
||||
"afterCode": "When fronted by a reverse proxy, the Monitor honours <code>X-Forwarded-For</code>, <code>X-Forwarded-Proto</code> and <code>X-Forwarded-Host</code> so URLs and CORS behave correctly without manual configuration.",
|
||||
"afterCode": "A reverse proxy should set <code>X-Forwarded-For</code>, <code>X-Forwarded-Proto</code> and <code>X-Forwarded-Host</code> appropriately and forward WebSocket upgrades. The browser uses its current origin for standard proxy ports. For backend client-IP handling, forwarded addresses are trusted only when PROXMENUX_TRUST_PROXY=1 is explicitly set; restrict direct access to the backend when enabling that trust.",
|
||||
"footer": "First-launch setup, password + TOTP 2FA, and reverse-proxy snippets are covered in <link>Access & Authentication</link>."
|
||||
},
|
||||
"mobile": {
|
||||
@@ -112,9 +112,9 @@
|
||||
},
|
||||
"api": {
|
||||
"heading": "REST API and integrations",
|
||||
"intro": "Everything the UI shows is available as JSON over HTTP/HTTPS. The same endpoints power Homepage widgets, Home Assistant sensors, Grafana dashboards (via the Prometheus exporter at <code>/api/prometheus</code>), Uptime Kuma probes and any custom script that speaks <code>curl</code>.",
|
||||
"intro": "The dashboard’s REST endpoints expose monitoring data as JSON over HTTP/HTTPS for Homepage widgets, Home Assistant sensors, Uptime Kuma probes and custom scripts. Grafana can use the separate Prometheus text exporter at <code>/api/prometheus</code>. Requests can be made with <code>curl</code>.",
|
||||
"tokens": "Long-lived API tokens (365 days) are generated from <strong>Settings → API Access Tokens</strong> or via <code>POST /api/auth/generate-api-token</code>.",
|
||||
"bearer": "Tokens travel as <code>Authorization: Bearer …</code>. Public endpoints (<code>/api/health</code>, <code>/api/auth/*</code>) work without a token so external uptime probes can hit the host without handing out credentials.",
|
||||
"bearer": "Send tokens as <code>Authorization: Bearer …</code>. The public <code>/api/health</code> endpoint is suitable for external uptime probes without credentials. Routes under <code>/api/auth/*</code> include both public setup/login/status routes and protected account/token management routes; they are not all anonymous.",
|
||||
"catalog": "The full endpoint catalog, token rotation guidance and security best-practices live in <linkApi>API Reference</linkApi>; ready-made examples for Homepage, Home Assistant, Grafana, Uptime Kuma and a generic cURL pattern are in <linkIntegrations>Integrations</linkIntegrations>."
|
||||
},
|
||||
"serviceControl": {
|
||||
|
||||
@@ -48,16 +48,16 @@
|
||||
"multiTitle": "Multi-widget setup — system, storage, network",
|
||||
"multiIntro": "For a richer view, render three separate cards backed by different endpoints — one for system metrics, one for storage, one for network. Use the same token in every card; it's the same Monitor instance.",
|
||||
"multiCalloutTitle": "Multiple Proxmox hosts",
|
||||
"multiCalloutBody": "Repeat the entry block per host to get the multi-card layout in the screenshot above — each entry points at the <code>http://<host>:8008</code> URL of its own ProxMenux Monitor instance. The token can be different per host (one secret entry per host) or shared, depending on how you generate them."
|
||||
"multiCalloutBody": "Repeat the entry block for each host to get the multi-card layout shown above. Each entry uses the URL <code>http://<host>:8008</code> of that host’s Monitor instance. Generate and store a token for each host; separate installations have their own signing secrets, so a token from one host should not be assumed to work on another."
|
||||
},
|
||||
"homeAssistant": {
|
||||
"heading": "Home Assistant",
|
||||
"headingHref": "https://www.home-assistant.io",
|
||||
"intro": "There is no native HACS integration for ProxMenux Monitor (yet) — but you don't need one. The built-in <code>rest</code> integration in Home Assistant can pull every endpoint documented in the <link>API Reference</link> and turn the responses into sensors, attributes and triggers. The complete reference build below exposes ~25 sensors covering system resources, the Health Monitor, VMs / CTs, storage, network, gateway latency and ProxMenux update status — drop the YAML into <code>configuration.yaml</code>, restart, and you have a full Proxmox observability layer inside HA.",
|
||||
"intro": "These examples use Home Assistant’s built-in <code>rest</code> integration, so a custom HACS component is not required. It reads Monitor endpoints described in the <link>API Reference</link> and turns their responses into sensors and attributes for automations. The reference configuration below provides roughly 25 sensors for system resources, health, VMs/CTs, storage, network and ProxMenux updates. Add it to <code>configuration.yaml</code> and reload or restart HA.",
|
||||
"imageAlt": "Home Assistant dashboard showing ProxMenux Monitor entities — health status badge, CPU / RAM / Temp gauges, VM count, storage usage and active errors counter",
|
||||
"imageCaption": "ProxMenux Monitor as a first-class Home Assistant integration — sensors built from the YAML recipe below.",
|
||||
"step1Title": "1 · Store the API token",
|
||||
"step1Body": "Drop the token into Home Assistant's <code>secrets.yaml</code> so it never leaks into a config dump. The whole bearer prefix goes in one line — that lets the YAML reference it directly as a header value. Filename and location depend on your HA install (typically <code>/config/secrets.yaml</code> for HA OS / Container).",
|
||||
"step1Body": "Store the token in Home Assistant’s <code>secrets.yaml</code> to keep it separate from the main configuration. Include the full Bearer prefix and token in one value so YAML can reference it as a header. This file is not encrypted and must also be kept private. Its location depends on the installation, typically <code>/config/secrets.yaml</code> on HA OS or Container.",
|
||||
"step2Title": "2 · Drop in the REST configuration",
|
||||
"step2Body": "Six REST blocks cover the full surface — one per major Monitor area. Each block has a sensible <code>scan_interval</code> tuned to how often the underlying data changes (system resources every 30 s, health every 60 s, slowly-changing inventories every 5-10 min). Paste into <code>configuration.yaml</code>:",
|
||||
"step3Title": "3 · Add binary sensors and template helpers",
|
||||
@@ -72,14 +72,14 @@
|
||||
"viewTipBody": "Open the dashboard's 3-dot menu → <em>Raw configuration editor</em> and add a new view with this header (above the <code>cards:</code> list from the YAML above):",
|
||||
"viewTipOutro": "That gives you a dedicated tab/view in your dashboard with its own icon and title, instead of one long card on an existing view.",
|
||||
"altViewTitle": "Alternative — a dedicated dashboard view",
|
||||
"altViewIntro": "If you'd rather have a full dedicated page (its own tab in the dashboard sidebar) than a single card inside an existing view, Home Assistant lets you create a new view directly with YAML. Steps:",
|
||||
"altViewIntro": "To create a dedicated view tab inside an existing dashboard, rather than one card within another view, add a view with the YAML below. A view is a tab within a dashboard; it does not create a new sidebar dashboard entry. Steps:",
|
||||
"altViewSteps": [
|
||||
"Open the dashboard where you want the new tab.",
|
||||
"Click the pencil (edit dashboard) at the top right.",
|
||||
"Click the <em>+</em> tab at the end of the existing tabs to create a new view.",
|
||||
"In the dialog that opens, switch to the <em>Code editor</em> tab (top right of the dialog — toggles between visual editor and YAML).",
|
||||
"Paste the YAML below.",
|
||||
"Save. The new <em>ProxMenux Monitor</em> tab appears in the sidebar with all cards rendered."
|
||||
"Save. The new <em>ProxMenux Monitor</em> view appears as a tab inside the dashboard with its cards."
|
||||
],
|
||||
"twoEditorsTitle": "Two YAML editors in HA — pick the right one",
|
||||
"twoEditorsIntro": "Home Assistant has two YAML editors that look similar but expect different formats:",
|
||||
@@ -89,7 +89,7 @@
|
||||
],
|
||||
"twoEditorsOutro": "Pasting view-body YAML into the whole-dashboard editor (or vice versa) leaves you with a <em>Vista sin nombre</em> and <code>cards: []</code>. The YAML below is for the single-view editor — paste exactly as shown.",
|
||||
"viewImageAlt": "Home Assistant dedicated view rendering ProxMenux Monitor — picture-entity header, glance KPIs, and System / VMs / Storage / Network entity cards laid out automatically by HA across multiple columns",
|
||||
"viewImageCaption": "The dedicated <em>ProxMenux Monitor</em> view as Home Assistant renders it on a wide screen — HA's default layout splits the cards into multiple columns automatically.",
|
||||
"viewImageCaption": "The dedicated <em>ProxMenux Monitor</em> view on a wide screen. A Masonry layout arranges the cards into columns automatically; the layout depends on the configured view type.",
|
||||
"twoColTipTitle": "Want a fixed two-column layout instead of the auto layout?",
|
||||
"twoColTipBody": "Replace any pair of cards (e.g. <em>System</em> + <em>VMs</em>, or <em>Storage</em> + <em>Network</em>) with a single <code>horizontal-stack</code> wrapping both, so they always render side by side regardless of screen width:",
|
||||
"twoColTipOutro": "On mobile the row stays compressed; HA's auto layout (no horizontal-stack) reflows better at narrow widths.",
|
||||
@@ -105,19 +105,19 @@
|
||||
"In the Lovelace YAML, replace the <code>image:</code> URL with <code>/local/icons/proxmenux.svg</code>. Save and reload — the image renders from the local file, no internet needed."
|
||||
],
|
||||
"scanTipTitle": "scan_interval rule of thumb",
|
||||
"scanTipBody": "<code>/api/system</code> is cheap to call — 30 s is fine. <code>/api/health/full</code> uses an internal 6-min cache, so polling it more often than ~60 s gains you nothing. <code>/api/storage/summary</code> changes slowly — every 5 min is plenty. <code>/api/proxmenux/update-status</code> only matters once an hour. Tune to your hardware budget if you have many sensors across many hosts."
|
||||
"scanTipBody": "Polling <code>/api/system</code> every 30 s is a reasonable starting point. <code>/api/health/full</code> normally reads a background result valid for up to 6 min; polling every 60 s does not bypass that cache. Try 5 min for <code>/api/storage/summary</code> and an hour for <code>/api/proxmenux/update-status</code>. Adjust intervals for your hardware and number of hosts."
|
||||
},
|
||||
"grafana": {
|
||||
"heading": "Prometheus + Grafana",
|
||||
"promHref": "https://prometheus.io",
|
||||
"grafanaHref": "https://grafana.com",
|
||||
"intro": "ProxMenux Monitor exposes a Prometheus-format scrape endpoint at <code>GET /api/prometheus</code> (authenticated) returning OpenMetrics text. Wire it into Prometheus, then build a Grafana dashboard on top — same data the dashboard UI shows, in the format your TSDB expects.",
|
||||
"intro": "ProxMenux Monitor exposes <code>GET /api/prometheus</code>, returning Prometheus text exposition format 0.0.4. It requires a bearer token when Monitor authentication is enabled. Configure Prometheus to collect it, then use Grafana to visualize the metrics in your time-series database.",
|
||||
"imageAlt": "Grafana dashboard rendering ProxMenux Monitor metrics — CPU usage gauge, memory usage timeseries, running VMs count, network throughput",
|
||||
"imageCaption": "A basic Grafana dashboard built from the ProxMenux Prometheus scrape — CPU, memory, running VMs, network throughput. The full metric catalogue lives in the <link>API Reference → Prometheus metrics</link>.",
|
||||
"step1Title": "1 · Add the scrape job to Prometheus",
|
||||
"step1Body": "Pass the API token via Prometheus' native <code>authorization</code> block (cleaner than custom headers and works with secret stores):",
|
||||
"step1After": "Reload Prometheus (<code>kill -HUP</code> or <code>systemctl reload prometheus</code>, or <code>docker compose restart prometheus</code> if you run it as a container) and check <em>Status → Targets</em> — the proxmenux job should turn green within one scrape interval. Each metric carries a <code>node=\"<hostname>\"</code> label so you can distinguish hosts in queries.",
|
||||
"tokenTipTitle": "Token via file or env, not inline",
|
||||
"step1After": "Reload Prometheus with <code>kill -HUP <PID></code> (replace PID with the Prometheus process ID), <code>systemctl reload prometheus</code> if supported by your service, or <code>docker compose restart prometheus</code> for a container. Check <em>Status → Targets</em>: the proxmenux job should become UP within one collection interval. Monitor metrics carry a <code>node=\"<hostname>\"</code> label to distinguish hosts.",
|
||||
"tokenTipTitle": "Keep the token in a separate file",
|
||||
"tokenTipBody": "For production deployments avoid inlining the token. Prometheus supports <code>credentials_file: /etc/prometheus/secrets/proxmenux.token</code> as an alternative — keep the token in a 0600 file and let Prometheus read it.",
|
||||
"step2Title": "2 · Verify the scrape with a couple of queries",
|
||||
"step2Body": "Before configuring Grafana, confirm Prometheus actually has the data. Open Prometheus' own UI at <code>http://<prometheus-host>:9090</code>, click <em>Query</em> and run any of these — you should get back live numbers from your Proxmox host:",
|
||||
@@ -142,9 +142,9 @@
|
||||
}
|
||||
],
|
||||
"calloutTitle": "The 401 you may see when clicking the endpoint URL is fine",
|
||||
"calloutBody": "On the <em>Status → Targets</em> page, clicking the endpoint link (<code>/api/prometheus</code>) makes your browser fetch it directly — without the bearer header that Prometheus uses for its own scrapes. So you'll see <code>'{'\"error\":\"Authentication required\"'}'</code>. That confirms the API is properly protected; Prometheus itself authenticates correctly because it has the token from the scrape config. Trust the green <em>State: UP</em>, not the click-through.",
|
||||
"calloutBody": "With Monitor authentication enabled, clicking the endpoint link on <em>Status → Targets</em> fetches <code>/api/prometheus</code> in the browser without Prometheus’s bearer header. The response can contain <code>'{'\"error\":\"Authentication required\"'}'</code>. Check the target’s actual <em>State: UP</em> to confirm that Prometheus itself is collecting successfully with its configured token.",
|
||||
"step3Title": "3 · Add Prometheus as a Grafana data source",
|
||||
"step3Body": "In Grafana: <em>Connections → Data sources → Add new → Prometheus</em>. Set the URL to your Prometheus instance (e.g. <code>http://prometheus.lan:9090</code>), save and test. No extra auth needed at this layer — Prometheus has already authenticated to ProxMenux.",
|
||||
"step3Body": "In Grafana, open <em>Connections → Data sources → Add new → Prometheus</em>. Set the Prometheus URL, for example <code>http://prometheus.lan:9090</code>, then save and test. If your Prometheus server requires authentication, configure that separately here. The Monitor API token belongs in the Prometheus collection job.",
|
||||
"step4Title": "4 · Build panels with these PromQL queries",
|
||||
"step4Body": "A starter set that maps directly to what users typically watch on a Proxmox host:",
|
||||
"headerPanel": "Panel idea",
|
||||
@@ -200,7 +200,7 @@
|
||||
"uptimeKuma": {
|
||||
"heading": "Uptime Kuma and other status checkers",
|
||||
"href": "https://github.com/louislam/uptime-kuma",
|
||||
"intro": "For external probes, use <code>GET /api/system-info</code> — it is the one endpoint that works without a token, returning a small JSON payload with hostname, uptime and the overall health status (mapped to <code>healthy</code> / <code>warning</code> / <code>critical</code>). That's exactly what a keyword-based monitor needs.",
|
||||
"intro": "For a keyword-based check, use <code>GET /api/system-info</code>. This route has no token decorator and returns hostname, uptime and a health status mapped to <code>healthy</code>, <code>warning</code> or <code>critical</code>. It is a public probe, unlike the authenticated detailed health endpoints.",
|
||||
"kumaTitle": "Uptime Kuma — HTTP keyword monitor",
|
||||
"kumaSteps": [
|
||||
"In Uptime Kuma, click <em>+ Add New Monitor</em>.",
|
||||
@@ -209,22 +209,22 @@
|
||||
"URL: <code>http://pve01.lan:8008/api/system-info</code>.",
|
||||
"Keyword: <code>healthy</code> (the value of <code>health.status</code> when the host is OK).",
|
||||
"Heartbeat Interval: 60 seconds is enough.",
|
||||
"Save. No headers needed — the endpoint is public."
|
||||
"Save. This system-info route does not require a bearer header."
|
||||
],
|
||||
"healthchecksTitle": "healthchecks.io / cron-style pings",
|
||||
"healthchecksBody": "Same endpoint, same shape — point your cron-style ping at <code>/api/system-info</code> and assert <code>.health.status == \"healthy\"</code>. Most of these services accept a 2xx HTTP status as the \"up\" signal too, in which case even a curl without parsing is enough.",
|
||||
"healthchecksBody": "A cron job can query <code>/api/system-info</code> and test <code>.health.status == \"healthy\"</code>. Only after a successful health test should it send a success ping to the check’s unique Healthchecks.io URL. Healthchecks.io receives pings from jobs; it does not poll the Monitor URL. An HTTP 2xx response alone does not prove that the reported host health is good.",
|
||||
"richTitle": "Want richer health data",
|
||||
"richBody": "For the full state (the ten Health Monitor categories + active errors + dismissed list), use <code>GET /api/health/full</code> instead — that one needs an API token but gives you everything the dashboard modal renders in a single response."
|
||||
"richBody": "For the full state — ten health categories, active errors and dismissed items — use <code>GET /api/health/full</code>. It returns the dashboard modal’s data in one response and needs a bearer token when Monitor authentication is enabled."
|
||||
},
|
||||
"workflows": {
|
||||
"heading": "n8n, Zapier and custom scripts",
|
||||
"intro": "For workflow tools and ad-hoc scripts that need to <em>raise</em> notifications through the Monitor (a CI failure, a smart-home sensor, a cron job that ran too long), the recipe is one POST to <code>/api/notifications/send</code>. The event flows through the same dispatch pipeline as anything emitted internally — dedup, cooldown, optional AI rewrite, fan-out to the configured channels.",
|
||||
"n8nBody": "In n8n, the equivalent is an <em>HTTP Request</em> node with method POST, the URL above, an <em>Authorization</em> header set to <code>Bearer '{''{'$credentials.proxmenux.token'}''}'</code> (using n8n credentials), and a JSON body matching the curl payload. Wire any preceding node as the trigger (cron, webhook, condition).",
|
||||
"severityBody": "Severity values are <code>INFO</code>, <code>WARNING</code> or <code>CRITICAL</code> (uppercase). The <code>data</code> payload is free-form JSON — the AI rewriter, when enabled, will pull anything useful from it for the rendered body. Full event-type semantics live in <link>Notifications → Event catalogue</link>."
|
||||
"intro": "Workflow tools and scripts can <em>send</em> notifications through a POST to <code>/api/notifications/send</code>, for example after a CI failure or an overlong cron job. This is a direct send: it bypasses the event queue and fingerprint cooldown. It still uses channel formatting, optional AI rewriting and delivery to configured channels. Apply any required duplicate control in the caller.",
|
||||
"n8nBody": "In n8n, use an <em>HTTP Request</em> node with POST, the URL above and the same JSON body as curl. Choose generic Header Auth credentials: set the header name to <em>Authorization</em> and its value to <code>Bearer YOUR_API_TOKEN</code>, replacing the placeholder. Connect a schedule, webhook or other preceding node to trigger the request.",
|
||||
"severityBody": "Severity values are <code>INFO</code>, <code>WARNING</code> or <code>CRITICAL</code> in uppercase. The <code>data</code> object supplies event context. AI rewriting uses the rendered title and message plus supported enrichment; arbitrary fields are not guaranteed to appear in its prompt or output. See <link>Notifications → Event catalogue</link> for event semantics."
|
||||
},
|
||||
"pveWebhook": {
|
||||
"heading": "Native Proxmox VE webhook (inbound)",
|
||||
"intro1": "Proxmox VE 8.1+ has its own notification system. ProxMenux Monitor registers itself as a webhook target so that everything PVE emits on its own (HA fencing, replication, vzdump from the GUI, certificate renewal) lands in the same dispatch pipeline as the Monitor's own events. This happens automatically when you press <em>Enable Notifications</em> on the Settings tab — no integration work required on the user side.",
|
||||
"intro1": "The PVE notification system was introduced in 8.1; native webhook targets require Proxmox VE 8.3 or later. Enabling notifications with <em>Enable Notifications</em> in Settings attempts to register the Monitor as a target. PVE notifications routed to that target, such as backup or replication reports, then enter the Monitor’s dispatch flow. Setup depends on the notification configuration being writable and available.",
|
||||
"intro2": "Mechanics, the body template PVE sends, the entries written to <code>/etc/pve/notifications.cfg</code>, and behaviour in clusters are documented in <link>Notifications → PVE webhook integration</link>."
|
||||
},
|
||||
"whereNext": {
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"sourcesLabel": "Sources",
|
||||
"sourcesDetail": "Health Monitor\nJournal watcher\nTask watcher\nPVE webhook hook\nPolling collector\nIn-process emitters",
|
||||
"dispatchLabel": "Dispatch pipeline",
|
||||
"dispatchDetail": "Per-event toggle\nFingerprint dedup\nCooldown\nBurst aggregation",
|
||||
"dispatchDetail": "Burst aggregation\nFingerprint / cooldown checks\nGroup rate limits\nPer-channel controls",
|
||||
"aiLabel": "AI rewrite (opt.)",
|
||||
"aiDetail": "OpenAI / Anthropic\nGemini / Groq\nOpenRouter / Ollama\n(off by default)",
|
||||
"channelsLabel": "Channels",
|
||||
@@ -40,7 +40,7 @@
|
||||
"stepsIntro": "Pressing the button does three things in sequence:",
|
||||
"steps": [
|
||||
"Flips the panel to its <em>Active</em> state and unfolds the channel form below.",
|
||||
"Registers a Proxmox VE webhook target in <code>/etc/pve/notifications.cfg</code> pointing at <code>POST http://127.0.0.1:8008/api/notifications/webhook</code>. From this moment on, anything Proxmox VE emits on its own (HA, replication, vzdump from the GUI) flows into the same pipeline as the Monitor's own events. See <pvelink>PVE webhook integration</pvelink> below for the full mechanics.",
|
||||
"Registers a Proxmox webhook target in <code>/etc/pve/notifications.cfg</code>. Without Monitor HTTPS, it uses <code>POST http://127.0.0.1:8008/api/notifications/webhook</code>; with HTTPS enabled, it uses the HTTP loopback listener on port 8009. PVE notifications then enter the Monitor’s dispatch process. See <pvelink>PVE webhook integration</pvelink> below.",
|
||||
"Starts the dispatch background thread. The thread polls the event queue and walks every event through the pipeline diagrammed above."
|
||||
],
|
||||
"activeAlt": "Notifications card after enabling — Active badge, channel tabs, Display Name field and Advanced AI Enhancement collapsible section",
|
||||
@@ -48,7 +48,7 @@
|
||||
},
|
||||
"sources": {
|
||||
"heading": "Event sources",
|
||||
"intro": "Six independent collectors feed the notification engine. They run as background threads inside the Monitor process and emit a structured <code>NotificationEvent</code> every time something happens.",
|
||||
"intro": "Six source types feed the notification engine: background collectors, the webhook receiver and direct emitters inside the process. They produce structured <code>NotificationEvent</code> objects.",
|
||||
"headerCollector": "Collector",
|
||||
"headerWatches": "Watches",
|
||||
"headerEvents": "Typical events",
|
||||
@@ -70,7 +70,7 @@
|
||||
},
|
||||
{
|
||||
"collector": "Proxmox webhook hook",
|
||||
"watches": "Listens on <code>POST /api/notifications/webhook</code>. Proxmox VE 8.1+ pushes its own notifications here once the integration is set up (see <pvelink>below</pvelink>).",
|
||||
"watches": "Receives <code>POST /api/notifications/webhook</code>. PVE versions with webhook notification targets can forward their notifications here after setup (see <pvelink>below</pvelink>).",
|
||||
"events": "Anything PVE emits — including events the Monitor would otherwise miss (HA, replication, vzdump from the GUI)."
|
||||
},
|
||||
{
|
||||
@@ -156,7 +156,7 @@
|
||||
"intro": "Email is the most flexible channel — and the one with the most fields. You need an SMTP server, a port, a TLS mode, optionally a username and password, a sender address and at least one recipient.",
|
||||
"imageAlt": "Email channel form with SMTP Host, Port, TLS Mode dropdown, Username, Password, From Address, To Addresses comma-separated and Subject Prefix fields",
|
||||
"imageCaption": "Email — SMTP host / port / TLS mode, optional username + password, sender address, comma-separated recipients and a subject prefix to make alerts easy to filter inbox-side.",
|
||||
"appNote": "If you use a personal Gmail or Microsoft 365 account, the password field cannot be your normal account password — both providers require an <strong>app password</strong> generated specifically for third-party clients. The two flows are below.",
|
||||
"appNote": "The Monitor uses password-based SMTP authentication. Gmail may require an <strong>app password</strong>. Microsoft support depends on the service and tenant policy; an app password does not override a ban on Basic authentication. Verify that your SMTP service permits this method.",
|
||||
"gmailTitle": "Gmail app password",
|
||||
"gmailIntro": "Gmail app passwords require <strong>2-Step Verification</strong> to be active on the Google account. If it isn't, the <em>App passwords</em> page won't exist.",
|
||||
"gmailItems": [
|
||||
@@ -166,15 +166,15 @@
|
||||
"Fill the Email tab with: <em>Host</em> <code>smtp.gmail.com</code>, <em>Port</em> <code>587</code>, <em>TLS Mode</em> <code>STARTTLS</code>, <em>Username</em> your Gmail address, <em>Password</em> the 16-character app password."
|
||||
],
|
||||
"outlookTitle": "Microsoft / Outlook app password",
|
||||
"outlookIntro": "Microsoft now requires <strong>two-step verification</strong> on the personal account before an app password can be created. Enterprise tenants where the admin has disabled SMTP basic auth need a different path (OAuth2) which is not currently supported by the Monitor — point those at an SMTP relay you control instead.",
|
||||
"outlookIntro": "Microsoft app passwords require <strong>two-step verification</strong>, but their availability does not guarantee SMTP compatibility. Where the service or tenant requires OAuth2, the Monitor’s password-based SMTP client cannot authenticate. Use an SMTP relay that supports the required upstream authentication.",
|
||||
"outlookItems": [
|
||||
"Open <a>account.microsoft.com/security</a> and enable two-step verification.",
|
||||
"Open <em>Advanced security options</em>, scroll to <em>App passwords</em> and click <em>Create a new app password</em>.",
|
||||
"Microsoft shows a long random password — copy it.",
|
||||
"Fill the Email tab with: <em>Host</em> <code>smtp-mail.outlook.com</code>, <em>Port</em> <code>587</code>, <em>TLS Mode</em> <code>STARTTLS</code>, <em>Username</em> your Outlook / Microsoft 365 address, <em>Password</em> the generated app password."
|
||||
"If your account offers <em>Advanced security options</em> → <em>App passwords</em>, choose <em>Create a new app password</em>. This option alone does not establish that your mail service accepts password-based SMTP.",
|
||||
"If Microsoft generates an app password, copy it and verify that your SMTP service accepts it.",
|
||||
"Only if your provider permits password-based SMTP, fill in its approved settings: <em>Host</em> <code>smtp.office365.com</code> for Exchange Online, <em>Port</em> <code>587</code>, <em>TLS Mode</em> <code>STARTTLS</code>, <em>Username</em> your mailbox address, and <em>Password</em> the permitted SMTP credential. Personal Outlook.com settings and authentication requirements are different; do not assume these credentials will work."
|
||||
],
|
||||
"relayTitle": "Self-hosted SMTP relay",
|
||||
"relayBody": "If you run your own SMTP relay (Postfix, msmtp, etc.) on the LAN, point the Monitor at it and skip the app-password dance entirely. The relay handles auth upstream and the Monitor sends in cleartext on a trusted network."
|
||||
"relayBody": "Point the Monitor at an SMTP relay you control, such as Postfix. The relay handles upstream authentication. Configure the Monitor’s authentication and TLS mode to match the relay; using a local relay does not itself disable encryption."
|
||||
},
|
||||
"pushover": {
|
||||
"heading": "Pushover",
|
||||
@@ -223,10 +223,10 @@
|
||||
"richHeader": "Rich — Rich messages on",
|
||||
"richOutro": "The toggle is per-channel: leave Email plain for inbox-rule readability while letting Telegram and Discord render the rich version. Channels that don't support inline formatting (plain-text email, Gotify) ignore the formatting and fall back to text either way.",
|
||||
"togglesTitle": "Per-event categories",
|
||||
"togglesIntro": "Around seventy event types are grouped into eleven UI categories. Each event has a master toggle and a per-channel override — two layers that decide whether a given event reaches a given channel:",
|
||||
"togglesIntro": "Each channel independently controls categories and individual event types. Turning a category off suppresses its events for that channel. Individual event toggles apply within enabled categories; there is no global master-event filter in the dispatcher.",
|
||||
"togglesItems": [
|
||||
"<strong>Per-event master toggle.</strong> If <code>vm_start</code> is off everywhere, no channel ever sees a <code>vm_start</code>. Toggles persist as <code>event_toggles[event_type] = true | false</code>.",
|
||||
"<strong>Per-channel overrides.</strong> An event type can also be muted for a specific channel (<em>\"send <code>backup_complete</code> to Discord but not to Telegram\"</em>). These live in <code>channel_overrides[channel_name][event_type]</code> and only apply if the event passed the master toggle."
|
||||
"<strong>Per-channel category toggle.</strong> For example, disabling the VM/CT category suppresses <code>vm_start</code> and its peers for that channel. Category preferences are stored as <code><channel>.events.<group></code>.",
|
||||
"<strong>Per-channel event toggle.</strong> Within an enabled category, you can choose <em>send <code>backup_complete</code> to Discord but not Telegram</em>. Individual preferences are stored as <code><channel>.event.<event_type></code>."
|
||||
],
|
||||
"togglesOutro": "Each category header in the screenshot also shows the count of events <em>currently enabled</em> / <em>total</em> for that group, and a category-level toggle that flips every event inside it on or off in one click — the shortcut for muting a whole group (e.g. all <code>info</code> backups, all update-related events) without expanding the section."
|
||||
},
|
||||
@@ -238,7 +238,7 @@
|
||||
"purposeTitle": "What it is for",
|
||||
"purposeItems": [
|
||||
"<strong>Don't wake me at 03:00 for an update notice.</strong> Backups, app updates, post-install optimisations and other INFO-level events stop pinging your phone at night.",
|
||||
"<strong>But still wake me for a fire.</strong> Disk failures, OOM kills, host shutdowns, fail2ban bans — anything classified as CRITICAL — bypass the window and arrive immediately.",
|
||||
"<strong>Critical events still arrive.</strong> Events actually classified as CRITICAL bypass Quiet Hours. The event name alone does not make it critical: the journal watcher emits Fail2Ban blocks and host shutdown/reboot notices as INFO, so those can be buffered.",
|
||||
"<strong>Don't miss anything either.</strong> The events suppressed during the window aren't silently dropped — they sit in a SQLite buffer until you're back on the clock."
|
||||
],
|
||||
"howTitle": "How it works",
|
||||
@@ -251,11 +251,11 @@
|
||||
"<strong>Across restarts.</strong> If the Monitor restarts mid-window, the buffer is intact on disk. If the restart happens just after the window closed, the next dispatch cycle detects the pending rows and flushes them with a single \"recovery\" summary — no notifications are lost to a deploy or a reboot."
|
||||
],
|
||||
"criticalTitle": "What counts as CRITICAL",
|
||||
"criticalBody": "Severity is set at event creation, not at dispatch time. Disk failures, OOM kills, cluster split-brain, host shutdowns and the \"hard\" tier of disk I/O errors ship as CRITICAL by design. Everything else (backups OK, updates available, INFO logs, rate-limit hits) defaults to INFO or WARNING and is therefore quietable. You can verify a given event's default severity in the <link>Event catalogue</link> further down this page."
|
||||
"criticalBody": "The producer sets the severity when creating each event. A disk, shutdown or security event is not automatically CRITICAL because of its name. For example, the journal watcher emits ip_block, system_shutdown and system_reboot as INFO. The <link>Event catalogue</link> lists the event types; inspect the producer and the received event for its actual severity."
|
||||
},
|
||||
"digest": {
|
||||
"heading": "Daily digest of INFO events",
|
||||
"intro1": "The Daily Digest is the opposite knob: an <strong>opt-in</strong> setting that says \"don't send me every successful backup or update notice as it happens — collect them and send me one summary per day at 09:00 (or whatever hour I choose)\". Same goal as Quiet Hours (less noise) but a different mechanism (time-based summary instead of a daily window).",
|
||||
"intro1": "The Daily Digest is an <strong>opt-in</strong> setting that collects eligible INFO notices, such as available updates, into one summary at 09:00 or another chosen time. Backup and replication results and live-action events are exempt. Quiet Hours instead delays non-critical messages during a daily window.",
|
||||
"intro2": "It lives in the same channel-settings card as Quiet Hours (see the figure under <link>Quiet Hours</link>), right underneath. You enable each one independently.",
|
||||
"purposeTitle": "What it is for",
|
||||
"purposeItems": [
|
||||
@@ -266,7 +266,7 @@
|
||||
"howItems": [
|
||||
"<strong>Per-channel opt-in.</strong> Off by default — Telegram doesn't silently batch your alerts. You enable it on the channels where you want a digest, leaving others on live delivery.",
|
||||
"<strong>Delivery time</strong> in your local timezone. Defaults to 09:00 but you can pick any time; the dispatcher fires the digest within ~60 s of that minute.",
|
||||
"<strong>What goes into the digest:</strong> any event the channel would have received live whose severity is <strong>INFO</strong>. Examples — <em>vzdump complete</em>, <em>Tailscale update available</em>, <em>ProxMenux optimisation update available</em>, <em>APT security updates pending</em>, <em>rate-limit hit</em>.",
|
||||
"<strong>Included in the digest:</strong> eligible generic events with severity <strong>INFO</strong>. Examples include <em>application update available</em>, <em>Tailscale update available</em>, <em>ProxMenux optimisation update available</em>, <em>APT security updates pending</em> and <em>rate-limit notice</em>. Backup results and other exempt action events remain live.",
|
||||
"<strong>What is never delayed:</strong>",
|
||||
"<strong>Persistence.</strong> Pending events sit in a SQLite table (<code>digest_pending</code>) until the configured hour. The Monitor can restart freely without losing what the digest will eventually contain.",
|
||||
"<strong>Empty days are silent.</strong> If nothing INFO-level happened, no digest is sent — the channel stays quiet rather than receiving a \"no events to report\" message."
|
||||
@@ -274,10 +274,10 @@
|
||||
"neverDelayedSub": [
|
||||
"<strong>CRITICAL</strong> events always go through immediately.",
|
||||
"<strong>WARNING</strong> events always go through immediately.",
|
||||
"Live-action events (VM/CT start / stop / shutdown / restart, vm_fail / ct_fail, backup start / fail, replication start / fail, host shutdown / reboot) bypass the digest even at INFO severity — you opted in to see those live, the digest would defeat that opt-in."
|
||||
"Live-action events — VM/CT start, stop, shutdown, restart and failure; backup start and results; replication results; host shutdown and reboot — bypass the digest even at INFO severity. Quiet Hours can still delay non-critical events."
|
||||
],
|
||||
"comboTitle": "Combining Quiet Hours and Daily Digest",
|
||||
"comboBody": "The two work together. A channel can have <em>both</em> active — Quiet Hours from 22:00 to 07:00 plus a Daily Digest at 09:00. INFO events during the quiet window go to the quiet buffer and arrive at 07:00 as the close-of-window summary; INFO events during the day go to the digest buffer and arrive at 09:00 the next morning. CRITICAL and WARNING always cut through both. Choose Quiet Hours when the goal is a <em>window of silence</em>, the Daily Digest when the goal is a <em>fixed-time summary</em>; many setups want both."
|
||||
"comboBody": "A channel can have <em>both</em> enabled: Quiet Hours from 22:00 to 07:00 and a Daily Digest at 09:00. Non-critical events during Quiet Hours enter its buffer and are released in the 07:00 summary. Eligible INFO events outside that window enter the digest. CRITICAL bypasses both; WARNING bypasses the digest but still waits during Quiet Hours. Choose a <em>window of silence</em>, a <em>fixed-time summary</em>, or both."
|
||||
},
|
||||
"displayName": {
|
||||
"heading": "Display Name",
|
||||
@@ -288,23 +288,23 @@
|
||||
},
|
||||
"dispatch": {
|
||||
"heading": "Dispatch pipeline",
|
||||
"intro": "Between an event being raised and a message leaving the host, three stages run in this order:",
|
||||
"intro": "The table describes three related mechanisms. The actual flow begins with burst aggregation, then delivery-claim/cooldown checks and group rate limiting, followed by rendering and per-channel delivery controls:",
|
||||
"headerStage": "Stage",
|
||||
"headerWhat": "What it does",
|
||||
"headerTunable": "Tunable?",
|
||||
"rows": [
|
||||
{
|
||||
"stage": "1. Fingerprint dedup",
|
||||
"stage": "Fingerprint deduplication",
|
||||
"what": "Each event is hashed into a fingerprint (<code>event_type + key fields from data</code>). Identical fingerprints inside a short window are considered duplicates of the first one.",
|
||||
"tunable": "No — internal dispatcher logic."
|
||||
},
|
||||
{
|
||||
"stage": "2. Cooldown",
|
||||
"what": "After a fingerprint is sent, the same fingerprint is suppressed for the per-severity cooldown duration. Stored in the <code>notification_last_sent</code> SQLite table so it survives restarts. Defaults: <code>CRITICAL</code> 60 s, <code>WARNING</code> 300 s, <code>INFO</code> 900 s, plus a per-category override on top (e.g. <code>resources</code> 900 s, <code>updates</code> 86 400 s).",
|
||||
"tunable": "No — defaults baked into the dispatcher."
|
||||
"stage": "Cooldown",
|
||||
"what": "After successful delivery, repeated fingerprints are suppressed and the state is saved in <code>notification_last_sent</code>. Base defaults for <code>CRITICAL</code>, <code>WARNING</code> and <code>INFO</code> are 86 400 s, as are <code>resources</code> and <code>updates</code>. Exceptions include 5 s for VM/CT actions and host shutdown/reboot, 10 s for backup/replication results, and 1 800–3 600 s for several health-state categories. Explicit event/group settings can override these values.",
|
||||
"tunable": "Yes — explicit cooldown settings for an event type or group override the defaults; these are dispatcher configuration values."
|
||||
},
|
||||
{
|
||||
"stage": "3. Burst aggregation",
|
||||
"stage": "Burst aggregation",
|
||||
"what": "When N events of a kind arrive inside a short window (e.g. an SSH brute-force flood), they are merged into a single <code>burst_*</code> message with a count and a sample.",
|
||||
"tunable": "No — window and threshold are hard-coded per event type."
|
||||
}
|
||||
@@ -314,32 +314,32 @@
|
||||
},
|
||||
"aiRewrite": {
|
||||
"heading": "Optional AI rewrite",
|
||||
"body1": "Any event can be passed through an LLM that rewrites its body in plain language and (optionally) in the target user's language before fan-out. The AI rewriter is off by default. When enabled it runs in the dispatch thread; if the provider call fails or times out, the original templated body is used instead.",
|
||||
"body1": "Eligible events can be rewritten by an LLM before delivery. Backup results (backup_complete and backup_fail) bypass this rewrite. AI is off by default and runs in the dispatch thread when enabled. If the call fails or times out, the original template text is used.",
|
||||
"body2": "Six providers are supported (OpenAI, Anthropic, Google Gemini, Groq, OpenRouter and local Ollama), with per-channel detail level (<code>brief</code>, <code>standard</code>, <code>detailed</code>), output language, prompt mode (<code>default</code> or <code>custom</code>) and an optional custom prompt. Full configuration walk-through, captures and prompt examples live in the dedicated <link>AI Assistant</link> page.",
|
||||
"privacyTitle": "Privacy note",
|
||||
"privacyBody": "AI rewrite sends the event body — which can include hostnames, IP addresses, usernames, error messages and journal lines — to the configured provider. Ollama keeps everything on-host; the other five providers transmit data to their respective endpoints. Disable the rewriter, or use Ollama, if the host runs in an environment where event content cannot leave the network."
|
||||
"privacyBody": "AI requests can include the title, severity, rendered body, system/custom prompt and collected context, including hostnames, IPs, usernames, errors and journal lines. They go to the selected endpoint. Ollama keeps processing local only with both a local server and a local model; a remote server or cloud model sends data outside the host. Disable rewriting or use a controlled local endpoint and model when event content must stay on the network."
|
||||
},
|
||||
"pveWebhook": {
|
||||
"heading": "PVE webhook integration",
|
||||
"intro1": "Proxmox VE 8.1+ has its own notification system with built-in <em>endpoints</em> (sendmail, gotify, SMTP, webhook). When you enable Notifications on the Monitor, it registers itself as one of those endpoints — a <code>webhook</code> target that points back at the Monitor's own API. From that moment on, anything Proxmox itself emits (HA fencing, replication, vzdump from the GUI, certificate renewal, etc.) flows through the same dispatch pipeline as the Monitor's own events.",
|
||||
"intro1": "Proxmox’s notification system uses <em>targets</em>, including sendmail, Gotify, SMTP and, on versions supporting it, webhook. Enabling Monitor notifications registers a <code>webhook</code> target. PVE-emitted notifications such as HA, replication and GUI backup reports can then enter the same dispatch process.",
|
||||
"intro2": "The target is visible from the Proxmox GUI at <em>Datacenter → Notifications → Notification Targets</em>:",
|
||||
"imageAlt": "Proxmox VE Edit Webhook dialog showing the auto-created proxmenux-webhook target with method POST, URL http://127.0.0.1:8008/api/notifications/webhook and a JSON body template using escape title, escape message, escape severity, escape timestamp and json fields",
|
||||
"imageCaption": "The PVE-side webhook target as Proxmox sees it (the GUI is in the host's configured locale — Spanish in this example). Same fields apply in any language.",
|
||||
"registeredIntro": "What gets registered:",
|
||||
"registeredItems": [
|
||||
"<strong>Method & URL.</strong> <code>POST http://127.0.0.1:8008/api/notifications/webhook</code>. Loopback only — PVE talks to the Monitor process running on the same host.",
|
||||
"<strong>Method and URL.</strong> <code>POST http://127.0.0.1:8008/api/notifications/webhook</code> when Monitor HTTPS is off. With HTTPS enabled, the target uses the separate HTTP loopback listener on port 8009. Both stay on the same host.",
|
||||
"<strong>Body template.</strong> A JSON body using PVE's native Handlebars helpers — stored base64-encoded in the config file by PVE, but it expands to:",
|
||||
"<strong>Matcher.</strong> A companion <code>matcher: proxmenux-matcher</code> block with <code>mode all</code> so every PVE notification reaches the target.",
|
||||
"<strong>Companion priv block.</strong> An empty <code>webhook: proxmenux-webhook</code> entry is appended to <code>/etc/pve/priv/notifications.cfg</code>. PVE refuses to instantiate any webhook endpoint without a matching private block, even when no secrets are needed — so the Monitor writes a header-only stub there. No tokens, headers or HMAC are configured on the PVE side."
|
||||
"<strong>Private configuration.</strong> Setup writes the matching <code>webhook: proxmenux-webhook</code> block in <code>/etc/pve/priv/notifications.cfg</code>, including the shared secret encoded in base64. The public target references that secret to send X-Webhook-Secret. The block is not an empty stub."
|
||||
],
|
||||
"securityTitle": "How the receiver is secured",
|
||||
"securityIntro": "The webhook receiver at <code>POST /api/notifications/webhook</code> applies different security layers depending on where the request comes from:",
|
||||
"securityItems": [
|
||||
"<strong>Loopback (<code>127.0.0.1</code> / <code>::1</code>).</strong> Rate-limit only. The endpoint trusts the loopback interface — only processes running on the host can reach it, and PVE itself cannot send custom auth headers in the body it generates. This is the path every PVE-emitted notification travels.",
|
||||
"<strong>Remote callers.</strong> Five layers stack on top of rate-limiting: a shared secret in the <code>X-Webhook-Secret</code> header, a freshness timestamp in <code>X-ProxMenux-Timestamp</code> (rejected if it drifts more than the configured window), a replay-cache lookup, and an optional IP allowlist. The shared secret lives in the Monitor's SQLite settings table — not in <code>/etc/pve/priv/notifications.cfg</code> — and is generated at first setup. This path exists for custom integrations posting from outside the host; the PVE-configured target never exercises it."
|
||||
"<strong>Local sources (<code>127.0.0.1</code> / <code>::1</code>).</strong> Rate limiting applies, but the remote secret/timestamp/replay checks are skipped. The receiver also treats addresses assigned to the host’s own interfaces as local. The automatically configured PVE target uses loopback.",
|
||||
"<strong>Remote callers.</strong> In addition to rate limiting, the receiver checks the configured secret in <code>X-Webhook-Secret</code>, a timestamp from <code>X-ProxMenux-Timestamp</code> or the JSON body, replay detection and an optional IP allowlist. The secret is stored in SQLite; setup also writes a base64-encoded copy to <code>/etc/pve/priv/notifications.cfg</code> so PVE can send the header."
|
||||
],
|
||||
"practiceTitle": "In practice",
|
||||
"practiceBody": "The PVE setup writes the target as <code>http://127.0.0.1:8008</code>, so PVE-emitted notifications always go through the loopback path with rate-limit-only security. The remote-caller path with the shared secret is opt-in for custom integrations — point an external service at <code>https://<monitor-host>:<port>/api/notifications/webhook</code> and supply the <code>X-Webhook-Secret</code> header to use it.",
|
||||
"practiceBody": "The PVE target uses <code>http://127.0.0.1:8008</code> without HTTPS, or HTTP port 8009 with Monitor HTTPS. For external integrations at <code>https://<monitor-host>:<port>/api/notifications/webhook</code>, send the configured <code>X-Webhook-Secret</code> and a current timestamp, and satisfy replay and IP checks. A secret header alone is not sufficient.",
|
||||
"actionsIntro": "The Monitor manages this target through three actions on the Settings tab:",
|
||||
"actionsItems": [
|
||||
"<strong>Setup</strong> — runs automatically when you enable Notifications. Creates the entry in <code>/etc/pve/notifications.cfg</code>. The file is written only when its content has to change, and a copy of the previous one is kept in <code>/usr/local/share/proxmenux/backups/pve-notifications/</code> (the three newest). Nothing is written while <code>/etc/pve</code> is not mounted: a Monitor that starts during a restart of <code>pve-cluster</code> registers the entry once it is back.",
|
||||
@@ -347,7 +347,7 @@
|
||||
"<strong>Read config</strong> — shows the current targets and matchers as PVE sees them. This is how you confirm the Monitor's entry is the one firing when PVE has multiple notification routes configured."
|
||||
],
|
||||
"clusterTitle": "Cluster nodes",
|
||||
"clusterBody": "<code>/etc/pve/</code> is replicated across cluster members, so the webhook target is visible on every node. Each node, however, posts to its <em>own</em> <code>127.0.0.1:8008</code> — meaning the Monitor running on that node receives the events that PVE generated locally. Run the Monitor on every node you want to see in the Notifications history."
|
||||
"clusterBody": "<code>/etc/pve/</code> is shared across cluster nodes, including the target URL and secret. Each node calls its <em>own</em> loopback listener: <code>127.0.0.1:8008</code> without HTTPS, or HTTP port 8009 with HTTPS. Run Monitor on each relevant node and keep the shared target consistent with every node’s listener configuration."
|
||||
},
|
||||
"catalogue": {
|
||||
"heading": "Event catalogue",
|
||||
@@ -434,7 +434,7 @@
|
||||
"<strong>The body lists each guest</strong> with its own result, size, duration and archive, followed by a line with the totals of the job.",
|
||||
"<strong>Warnings and errors are listed once.</strong> Repeated lines are shown a single time with the main cause first; past eight lines, the rest is counted in a closing line and stays in the Proxmox task log.",
|
||||
"<strong>A job that fails before its first guest</strong> shows the cause Proxmox gave, for example a storage that does not exist.",
|
||||
"<strong>Summaries keep the result.</strong> A backup listed in the Quiet Hours summary or the Daily Digest carries the icon of its own result."
|
||||
"<strong>Summaries preserve the result.</strong> A backup in the Quiet Hours summary keeps its own result icon. Backup result events bypass the Daily Digest."
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -483,7 +483,7 @@
|
||||
{
|
||||
"endpoint": "/api/notifications/webhook",
|
||||
"method": "POST",
|
||||
"use": "Receives Proxmox VE's own notifications. Loopback callers are rate-limited only; remote callers must additionally pass the <code>X-Webhook-Secret</code> header, <code>X-ProxMenux-Timestamp</code> freshness check, replay cache and optional IP allowlist."
|
||||
"use": "Receives Proxmox notifications. Local source addresses skip remote authentication checks but remain rate-limited. Remote requests are checked against <code>X-Webhook-Secret</code>, a timestamp in <code>X-ProxMenux-Timestamp</code> or the JSON body, replay detection and any configured IP allowlist."
|
||||
},
|
||||
{
|
||||
"endpoint": "/api/notifications/proxmox/setup-webhook",
|
||||
|
||||
Reference in New Issue
Block a user