import type { Metadata } from "next" import { getTranslations, getMessages, setRequestLocale } from "next-intl/server" import { Link } from "@/i18n/navigation" import { DocHeader } from "@/components/ui/doc-header" import { Callout } from "@/components/ui/callout" import { DataFlowDiagram } from "@/components/ui/data-flow-diagram" 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.architecture.meta" }) return { title: t("title"), description: t("description"), keywords: [ "proxmenux architecture", "proxmox monitor flask", "proxmox dashboard sqlite", "proxmox appimage dashboard", "proxmox websocket terminal", "proxmox monitor blueprints", ], alternates: { canonical: "https://proxmenux.com/docs/monitor/architecture" }, openGraph: { title: t("ogTitle"), description: t("ogDescription"), type: "article", url: "https://proxmenux.com/docs/monitor/architecture", }, twitter: { card: "summary", title: t("twitterTitle"), description: t("twitterDescription"), }, } } type ThreadRow = { thread: string; cadence: string; job: string } type BlueprintRow = { blueprint: string; prefix: string[]; owns: string } type DataRow = { source: string; usedFor: string } type PersistenceRow = { path: string; owner: string; contents: string } type WhereNextItem = { label: string; href: string; tail: string } export default async function MonitorArchitecturePage({ params, }: { params: Promise<{ locale: string }> }) { const { locale } = await params setRequestLocale(locale) const t = await getTranslations({ locale, namespace: "docs.monitor.architecture" }) const messages = (await getMessages({ locale })) as unknown as { docs: { monitor: { architecture: { requestFlow: { rows: ThreadRow[] } systemd: { items: string[] } appimage: { consequences: string[] } flask: { rows: BlueprintRow[] } dataSources: { rows: DataRow[] } persistence: { rows: PersistenceRow[] } health: { items: string[] } notifications: { items: string[] } websocket: { items: string[] } proxy: { items: string[] } whereNext: { items: WhereNextItem[] } } } } } const arch = messages.docs.monitor.architecture const threadRows = arch.requestFlow.rows const systemdItems = arch.systemd.items const consequences = arch.appimage.consequences const blueprintRows = arch.flask.rows const dataRows = arch.dataSources.rows const persistenceRows = arch.persistence.rows const healthItems = arch.health.items const notificationItems = arch.notifications.items const websocketItems = arch.websocket.items const proxyItems = arch.proxy.items const whereNextItems = arch.whereNext.items const code = (chunks: React.ReactNode) => {chunks} const strong = (chunks: React.ReactNode) => {chunks} const em = (chunks: React.ReactNode) => {chunks} const link = (chunks: React.ReactNode) => ( {chunks} ) const notifLink = (chunks: React.ReactNode) => ( {chunks} ) const aiLink = (chunks: React.ReactNode) => ( {chunks} ) const accessLink = (chunks: React.ReactNode) => ( {chunks} ) const fail2banLink = (chunks: React.ReactNode) => ( {chunks} ) const fail2banWarnLink = (chunks: React.ReactNode) => ( {chunks} ) return (
{t("intro.body")}

{t("requestFlow.heading")}

{t("requestFlow.intro")}

{t.rich("requestFlow.threadsIntro", { strong })}

{threadRows.map((row, idx) => ( ))}
{t("requestFlow.headerThread")} {t("requestFlow.headerCadence")} {t("requestFlow.headerJob")}
{row.thread} {row.cadence} {t.rich(`requestFlow.rows.${idx}.job`, { code })}

{t("systemd.heading")}

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

    {systemdItems.map((_, idx) => (
  • {t.rich(`systemd.items.${idx}`, { strong, code })}
  • ))}
{`systemctl cat proxmenux-monitor.service       # show the unit content
systemctl status proxmenux-monitor.service    # state + recent log
journalctl -u proxmenux-monitor.service -f    # follow live`}

{t("appimage.heading")}

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

{t("appimage.consequencesIntro")}

    {consequences.map((_, idx) => (
  • {t.rich(`appimage.consequences.${idx}`, { strong, code })}
  • ))}

{t("flask.heading")}

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

{blueprintRows.map((row, idx) => ( ))}
{t("flask.headerBlueprint")} {t("flask.headerPrefix")} {t("flask.headerOwns")}
{row.blueprint} {row.prefix.map((p, pidx) => ( {p} {pidx < row.prefix.length - 1 &&
}
))}
{t.rich(`flask.rows.${idx}.owns`, { code })}

{t.rich("flask.endpointsLink", { link })}

{t("dataSources.heading")}

{t("dataSources.intro")}

{dataRows.map((row, idx) => ( ))}
{t("dataSources.headerSource")} {t("dataSources.headerUsedFor")}
{row.source} {row.usedFor}
{t.rich("dataSources.cacheBody", { code })}

{t("persistence.heading")}

{t("persistence.intro")}

{persistenceRows.map((row, idx) => ( ))}
{t("persistence.headerPath")} {t("persistence.headerOwner")} {t("persistence.headerContents")}
{row.path} {t.rich(`persistence.rows.${idx}.owner`, { code })} {t.rich(`persistence.rows.${idx}.contents`, { code })}
{t.rich("persistence.backupBody", { code })}

{t("health.heading")}

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

    {healthItems.map((_, idx) => (
  1. {t.rich(`health.items.${idx}`, { code })}
  2. ))}

{t.rich("health.afterIntro", { code })}

{t.rich("health.cycleEnd", { em, code, link })}

{t("notifications.heading")}

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

    {notificationItems.map((_, idx) => (
  • {t.rich(`notifications.items.${idx}`, { strong, code })}
  • ))}

{t.rich("notifications.linksFooter", { notifLink, aiLink })}

{t("websocket.heading")}

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

    {websocketItems.map((_, idx) => (
  • {t.rich(`websocket.items.${idx}`, { strong, code })}
  • ))}

{t.rich("websocket.outro", { code })}

{t.rich("websocket.proxyNote", { code })}

{t("proxy.heading")}

{t("proxy.intro")}

    {proxyItems.map((_, idx) => (
  1. {t.rich(`proxy.items.${idx}`, { strong, code })}
  2. ))}
{t.rich("proxy.calloutBody", { strong, code, link: fail2banWarnLink })}

{t.rich("proxy.outro", { accessLink, fail2banLink })}

{t("whereNext.heading")}

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