import type { Metadata } from "next" import { getTranslations, getMessages, setRequestLocale } from "next-intl/server" import { Link } from "@/i18n/navigation" import Image from "next/image" import { ExternalLink } from "lucide-react" import { DocHeader } from "@/components/ui/doc-header" import { Callout } from "@/components/ui/callout" import CopyableCode from "@/components/CopyableCode" export async function generateMetadata({ params, }: { params: Promise<{ locale: string }> }): Promise { const { locale } = await params const t = await getTranslations({ locale, namespace: "docs.monitor.integrations.meta" }) return { title: t("title"), description: t("description"), keywords: [ "proxmox homepage integration", "proxmox home assistant", "proxmox grafana", "proxmox prometheus", "proxmox uptime kuma", "proxmox dashboard", "proxmenux integrations", "proxmox custom api widget", "proxmox rest sensor", ], alternates: { canonical: "https://proxmenux.com/docs/monitor/integrations" }, openGraph: { title: t("ogTitle"), description: t("ogDescription"), type: "article", url: "https://proxmenux.com/docs/monitor/integrations", }, twitter: { card: "summary", title: t("twitterTitle"), description: t("twitterDescription"), }, } } type Row2 = { query: string; confirms: string } type Row3 = { panel: string; promql: string } type WhereNextItem = { label: string; href: string; tail: string } export default async function MonitorIntegrationsPage({ params, }: { params: Promise<{ locale: string }> }) { const { locale } = await params setRequestLocale(locale) const t = await getTranslations({ locale, namespace: "docs.monitor.integrations" }) const messages = (await getMessages({ locale })) as unknown as { docs: { monitor: { integrations: { auth: { httpsItems: string[] } homeAssistant: { altViewSteps: string[] twoEditorsItems: string[] logoBrokenSteps: string[] } grafana: { verifyRows: Row2[]; panelRows: Row3[] } uptimeKuma: { kumaSteps: string[] } whereNext: { items: WhereNextItem[] } } } } } const i = messages.docs.monitor.integrations const httpsItems = i.auth.httpsItems const altViewSteps = i.homeAssistant.altViewSteps const twoEditorsItems = i.homeAssistant.twoEditorsItems const logoBrokenSteps = i.homeAssistant.logoBrokenSteps const verifyRows = i.grafana.verifyRows const panelRows = i.grafana.panelRows const kumaSteps = i.uptimeKuma.kumaSteps const whereNextItems = i.whereNext.items const code = (chunks: React.ReactNode) => {chunks} const strong = (chunks: React.ReactNode) => {chunks} const em = (chunks: React.ReactNode) => {chunks} const apiLink = (chunks: React.ReactNode) => ( {chunks} ) const accessLink = (chunks: React.ReactNode) => ( {chunks} ) const promAnchor = (chunks: React.ReactNode) => ( {chunks} ) const notifEventsLink = (chunks: React.ReactNode) => ( {chunks} ) const pveLink = (chunks: React.ReactNode) => ( {chunks} ) const ext = (href: string) => (chunks: React.ReactNode) => ( {chunks} ) return (
{t.rich("intro.body", { link: apiLink })}

{t("auth.heading")}

{t("auth.intro")}

{t("auth.optAtitle")}

{t.rich("auth.optAbody1", { strong, em })}

{t("auth.optAbody2")}

" http://:8008/api/system | jq`} className="my-4" />

{t("auth.optBtitle")}

{t("auth.optBbody")}

:8008/api/auth/login \\ -H "Content-Type: application/json" \\ -d '{"username":"admin","password":"","totp_token":"123456"}' | jq -r '.token' # 2. Use the returned token exactly like an API token curl -H "Authorization: Bearer " http://:8008/api/system | jq`} className="my-4" />

{t.rich("auth.outro", { link: accessLink })}

{t.rich("auth.httpsIntro", { code, strong })}
    {httpsItems.map((_, idx) => (
  • {t.rich(`auth.httpsItems.${idx}`, { strong, code })}
  • ))}

{t("homepage.heading")}

{t.rich("homepage.intro", { code })}

{t.rich("homepage.iconCalloutBody", { code, a1: ext("https://dashboardicons.com"), a2: ext("https://dashboardicons.com/icons/external/proxmenux"), })}
{t("homepage.imageAlt")}
{t.rich("homepage.imageCaption", { code })}

{t("homepage.basicTitle")}

{t.rich("homepage.basicIntro", { code })}

{t("homepage.authedTitle")}

{t.rich("homepage.authedIntro", { strong, code })}

{t("homepage.authedOutro")}

{t("homepage.multiTitle")}

{t("homepage.multiIntro")}

{t.rich("homepage.multiCalloutBody", { code })}

{t("homeAssistant.heading")}

{t.rich("homeAssistant.intro", { code, link: apiLink })}

{t("homeAssistant.imageAlt")}
{t("homeAssistant.imageCaption")}

{t("homeAssistant.step1Title")}

{t.rich("homeAssistant.step1Body", { code })}

"`} className="my-4" />

{t("homeAssistant.step2Title")}

{t.rich("homeAssistant.step2Body", { code })}

:8008/api/system headers: Authorization: !secret proxmenux_token_header scan_interval: 30 sensor: - name: "ProxMenux CPU" unique_id: proxmenux_cpu value_template: "{{ value_json.cpu_usage }}" unit_of_measurement: "%" state_class: measurement icon: mdi:cpu-64-bit - name: "ProxMenux RAM" unique_id: proxmenux_ram value_template: "{{ value_json.memory_usage }}" unit_of_measurement: "%" state_class: measurement icon: mdi:memory - name: "ProxMenux Memory Used" unique_id: proxmenux_memory_used value_template: "{{ value_json.memory_used }}" unit_of_measurement: "GB" state_class: measurement icon: mdi:memory - name: "ProxMenux Memory Total" unique_id: proxmenux_memory_total value_template: "{{ value_json.memory_total }}" unit_of_measurement: "GB" icon: mdi:memory - name: "ProxMenux CPU Temperature" unique_id: proxmenux_cpu_temperature value_template: "{{ value_json.temperature }}" unit_of_measurement: "°C" device_class: temperature state_class: measurement - name: "ProxMenux Uptime" unique_id: proxmenux_uptime value_template: "{{ value_json.uptime }}" icon: mdi:clock-outline - name: "ProxMenux Load 1m" unique_id: proxmenux_load_1m value_template: "{{ value_json.load_average[0] | round(2) }}" state_class: measurement icon: mdi:gauge - name: "ProxMenux Available Updates" unique_id: proxmenux_available_updates value_template: "{{ value_json.available_updates }}" state_class: measurement icon: mdi:package-up - name: "ProxMenux Host" unique_id: proxmenux_host value_template: "{{ value_json.hostname }}" json_attributes: - kernel_version - proxmox_version - cpu_cores - cpu_threads # ─── Block 2: Health Monitor (overall + per-category + active errors) ─── - resource: http://:8008/api/health/full headers: Authorization: !secret proxmenux_token_header scan_interval: 60 sensor: - name: "ProxMenux Health" unique_id: proxmenux_health_overall value_template: "{{ value_json.health.overall }}" json_attributes_path: "$.health" json_attributes: - summary - details icon: mdi:heart-pulse - name: "ProxMenux Active Errors" unique_id: proxmenux_active_errors value_template: "{{ value_json.active_errors | length }}" state_class: measurement icon: mdi:alert-circle json_attributes: - active_errors - name: "ProxMenux Dismissed Errors" unique_id: proxmenux_dismissed_errors value_template: "{{ value_json.dismissed | length }}" state_class: measurement icon: mdi:alert-circle-outline # ─── Block 3: VMs and containers ─── - resource: http://:8008/api/vms headers: Authorization: !secret proxmenux_token_header scan_interval: 60 sensor: - name: "ProxMenux VMs Total" unique_id: proxmenux_vms_total value_template: "{{ value_json | length }}" state_class: measurement icon: mdi:server json_attributes: - vms - name: "ProxMenux VMs Running" unique_id: proxmenux_vms_running value_template: > {{ value_json | selectattr('status', 'eq', 'running') | list | length }} state_class: measurement icon: mdi:play-circle - name: "ProxMenux VMs Stopped" unique_id: proxmenux_vms_stopped value_template: > {{ value_json | selectattr('status', 'eq', 'stopped') | list | length }} state_class: measurement icon: mdi:stop-circle # ─── Block 4: Storage summary ─── - resource: http://:8008/api/storage/summary headers: Authorization: !secret proxmenux_token_header scan_interval: 300 sensor: - name: "ProxMenux Storage Total" unique_id: proxmenux_storage_total value_template: "{{ value_json.total }}" unit_of_measurement: "TB" icon: mdi:harddisk - name: "ProxMenux Storage Used" unique_id: proxmenux_storage_used value_template: "{{ value_json.used }}" unit_of_measurement: "GB" state_class: measurement icon: mdi:harddisk - name: "ProxMenux Storage Available" unique_id: proxmenux_storage_available value_template: "{{ value_json.available }}" unit_of_measurement: "GB" state_class: measurement - name: "ProxMenux Disk Count" unique_id: proxmenux_disk_count value_template: "{{ value_json.disk_count }}" # ─── Block 5: Network summary ─── - resource: http://:8008/api/network/summary headers: Authorization: !secret proxmenux_token_header scan_interval: 30 sensor: - name: "ProxMenux Net Rx Bytes" unique_id: proxmenux_net_rx_bytes value_template: "{{ value_json.traffic.bytes_recv }}" unit_of_measurement: "B" device_class: data_size state_class: total_increasing - name: "ProxMenux Net Tx Bytes" unique_id: proxmenux_net_tx_bytes value_template: "{{ value_json.traffic.bytes_sent }}" unit_of_measurement: "B" device_class: data_size state_class: total_increasing - name: "ProxMenux Physical NICs Up" unique_id: proxmenux_physical_nics_up value_template: > {{ value_json.physical_active_count }} / {{ value_json.physical_total_count }} icon: mdi:ethernet - name: "ProxMenux Bridges Up" unique_id: proxmenux_bridges_up value_template: > {{ value_json.bridge_active_count }} / {{ value_json.bridge_total_count }} icon: mdi:bridge # ─── Block 6: ProxMenux update availability ─── - resource: http://:8008/api/proxmenux/update-status headers: Authorization: !secret proxmenux_token_header scan_interval: 3600 sensor: - name: "ProxMenux Monitor Update" unique_id: proxmenux_monitor_update value_template: > {{ 'update available' if (value_json.stable or value_json.beta) else 'up to date' }} json_attributes: - stable - stable_version - beta - beta_version icon: mdi:update`} className="my-4" />

{t("homeAssistant.step3Title")}

{t("homeAssistant.step3Body")}

{{ (states('sensor.proxmenux_memory_total') | float - states('sensor.proxmenux_memory_used') | float) | round(1) }} - name: "ProxMenux Storage Usage Percent" unique_id: proxmenux_storage_usage_percent unit_of_measurement: "%" state_class: measurement state: > {% set used = states('sensor.proxmenux_storage_used') | float %} {% set free = states('sensor.proxmenux_storage_available') | float %} {% set total = used + free %} {{ (used / total * 100) | round(1) if total > 0 else 0 }}`} className="my-4" />

{t("homeAssistant.step4Title")}

{t.rich("homeAssistant.step4Body", { em })}

{t.rich("homeAssistant.replaceBody", { em, code })}

{t("homeAssistant.step5Title")}

{t.rich("homeAssistant.step5Body", { strong, em })}

{t.rich("homeAssistant.viewTipBody", { em, code })}
{`- title: ProxMenux Monitor
  icon: mdi:server
  cards:
    # ... paste the cards from the vertical-stack above, without the
    # outer "type: vertical-stack" wrapper, indented one extra level
`}
{t("homeAssistant.viewTipOutro")}

{t("homeAssistant.altViewTitle")}

{t("homeAssistant.altViewIntro")}

    {altViewSteps.map((_, idx) => (
  1. {t.rich(`homeAssistant.altViewSteps.${idx}`, { em })}
  2. ))}
{t("homeAssistant.twoEditorsIntro")}
    {twoEditorsItems.map((_, idx) => (
  • {t.rich(`homeAssistant.twoEditorsItems.${idx}`, { strong, em, code })}
  • ))}
{t.rich("homeAssistant.twoEditorsOutro", { em, code })}
{t("homeAssistant.viewImageAlt")}
{t.rich("homeAssistant.viewImageCaption", { em })}
{t.rich("homeAssistant.twoColTipBody", { em, code })}
{`  - type: horizontal-stack
    cards:
      - type: entities
        title: System
        entities: [...]
      - type: glance
        title: "VMs & Containers"
        entities: [...]`}
{t("homeAssistant.twoColTipOutro")}

{t("homeAssistant.step6Title")}

{t.rich("homeAssistant.step6Body", { code })}

data: title: "Proxmox: warning" message: > {{ state_attr('sensor.proxmenux_health', 'summary') }} - alias: "ProxMenux — health CRITICAL" trigger: - platform: state entity_id: sensor.proxmenux_health to: "CRITICAL" action: - service: notify.mobile_app_ data: title: "🚨 Proxmox CRITICAL" message: > {{ state_attr('sensor.proxmenux_health', 'summary') }} - service: persistent_notification.create data: title: "Proxmox CRITICAL" message: > {{ state_attr('sensor.proxmenux_health', 'summary') }} notification_id: proxmenux_critical - alias: "ProxMenux — VM unexpectedly stopped" trigger: - platform: numeric_state entity_id: sensor.proxmenux_vms_stopped above: 0 for: "00:02:00" action: - service: notify.mobile_app_ data: title: "Proxmox: VM stopped" message: > {{ states('sensor.proxmenux_vms_stopped') }} VM(s) currently stopped`} className="my-4" />

{t("homeAssistant.logoTitle")}

{t.rich("homeAssistant.logoBody", { a1: ext("https://dashboardicons.com"), a2: ext("https://dashboardicons.com/icons/external/proxmenux"), })}

{t("homeAssistant.logoBrokenIntro")}
    {logoBrokenSteps.map((_, idx) => (
  1. {t.rich(`homeAssistant.logoBrokenSteps.${idx}`, { code, a: ext("https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/proxmenux.svg"), })}
  2. ))}
{t.rich("homeAssistant.scanTipBody", { code })}

Prometheus {" "}+{" "} Grafana

{t.rich("grafana.intro", { code })}

{t("grafana.imageAlt")}
{t.rich("grafana.imageCaption", { link: promAnchor })}

{t("grafana.step1Title")}

{t.rich("grafana.step1Body", { code })}

{t.rich("grafana.step1After", { code, em })}

{t.rich("grafana.tokenTipBody", { code })}

{t("grafana.step2Title")}

{t.rich("grafana.step2Body", { code, em })}

{verifyRows.map((row, idx) => ( ))}
{t("grafana.headerQuery")} {t("grafana.headerConfirms")}
{row.query} {t.rich(`grafana.verifyRows.${idx}.confirms`, { code })}
{t.rich("grafana.calloutBody", { em, code })}

{t("grafana.step3Title")}

{t.rich("grafana.step3Body", { em, code })}

{t("grafana.step4Title")}

{t("grafana.step4Body")}

{panelRows.map((row, idx) => ( ))}
{t("grafana.headerPanel")} {t("grafana.headerPromql")}
{row.panel} {row.promql}

{t.rich("grafana.outro", { em, code })}

Uptime Kuma {" "}and other status checkers

{t.rich("uptimeKuma.intro", { code })}

{t("uptimeKuma.kumaTitle")}

    {kumaSteps.map((_, idx) => (
  1. {t.rich(`uptimeKuma.kumaSteps.${idx}`, { em, code })}
  2. ))}

{t("uptimeKuma.healthchecksTitle")}

{t.rich("uptimeKuma.healthchecksBody", { code })}

{t.rich("uptimeKuma.richBody", { code })}

{t("workflows.heading")}

{t.rich("workflows.intro", { em, code })}

{t.rich("workflows.n8nBody", { em, code })}

{t.rich("workflows.severityBody", { code, link: notifEventsLink })}

{t("pveWebhook.heading")}

{t.rich("pveWebhook.intro1", { em })}

{t.rich("pveWebhook.intro2", { code, link: pveLink })}

{t("whereNext.heading")}

    {whereNextItems.map((item) => (
  • {item.label} {item.tail}
  • ))}
) }