ProxMenux 1.2.6.2-beta: OCI containers in the Monitor, docs and fixes

OCI manager Apps
- App tab: containers installed from an OCI image are identified from their
  installation record; the application and image versions are shown and an
  update is detected by image digest; repository link; Refresh data.
- Updates tab for OCI containers: Update and Recreate run the same flow as the
  OCI menu in the Monitor terminal; the pre-update backup can be kept in a
  backup storage; scheduled image updates with an optional minimum age.
- Logs tab: console output of the application, kept on the host
  (lxc.console.logfile + logrotate) and followed live.
- The Proxmox console opens a shell (cmode: shell) when the image has one.
- A damaged image download is fetched again before failing.
- Multi-container applications open at their LAN address; volume mount
  points on block storage report their usage.

Monitor
- Proxmox notifications are delivered to a loopback-only HTTP listener when
  HTTPS is enabled, so they no longer fail certificate verification.
- Log persistence counts recurring patterns only; an ended burst is not
  reported as persistent and its warning clears on its own (#386).
- Proxmox notification config backups are deduplicated and capped at three.
- The update icon on the Apps page opens the container on its Updates tab.
- Version 1.2.6.2-beta and its release notes in every Monitor language.

Docs
- OCI manager Apps and Audit & Report rebuilt as per-page message files,
  with a new page for OCI containers in the Monitor.
- Seven pages fixed where rich-text tags were missing from t.rich.

Translations
- Spanish fixes across the OCI engine, the Monitor and the TUI menus.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
MacRimi
2026-09-25 21:51:12 +02:00
co-authored by Claude Opus 5.5
parent 386d33df6e
commit 4437a671d2
524 changed files with 14459 additions and 3841 deletions
+4 -4
View File
@@ -63,7 +63,7 @@ export default async function MonitorApiPage({
auth: { rows: EndpointRow[]; items: string[] }
conventions: { items: string[] }
system: { rows: EndpointRow[] }
actions: { rows: EndpointRow[] }
actions: { rows: EndpointRow[]; shapeCode: string; curlCode: string; haCode: string }
health: { rows: EndpointRow[] }
storage: { rows: EndpointRow[] }
network: { rows: EndpointRow[] }
@@ -188,7 +188,7 @@ export default async function MonitorApiPage({
<h3 className="text-lg font-semibold mt-6 mb-2 text-gray-900">{t("actions.shapeTitle")}</h3>
<p className="mb-2 text-gray-800 leading-relaxed">{t("actions.shapeIntro")}</p>
<CopyableCode code={t("actions.shapeCode")} className="my-4" />
<CopyableCode code={messages.docs.monitor.apiReference.actions.shapeCode} className="my-4" />
<h3 className="text-lg font-semibold mt-6 mb-2 text-gray-900">{t("actions.concurrencyTitle")}</h3>
<p className="mb-4 text-gray-800 leading-relaxed">{t.rich("actions.concurrencyBody", { code })}</p>
@@ -200,11 +200,11 @@ export default async function MonitorApiPage({
<h3 className="text-lg font-semibold mt-6 mb-2 text-gray-900">{t("actions.curlTitle")}</h3>
<p className="mb-2 text-gray-800 leading-relaxed">{t("actions.curlBody")}</p>
<CopyableCode code={t("actions.curlCode")} className="my-4" />
<CopyableCode code={messages.docs.monitor.apiReference.actions.curlCode} className="my-4" />
<h3 className="text-lg font-semibold mt-6 mb-2 text-gray-900">{t("actions.haTitle")}</h3>
<p className="mb-2 text-gray-800 leading-relaxed">{t.rich("actions.haBody", { code })}</p>
<CopyableCode code={t("actions.haCode")} className="my-4" />
<CopyableCode code={messages.docs.monitor.apiReference.actions.haCode} className="my-4" />
<h2 className="text-2xl font-semibold mt-10 mb-4 text-gray-900">{t("health.heading")}</h2>
{endpointTable(healthRows, "health.rows")}
@@ -0,0 +1,26 @@
import type { Metadata } from "next"
import { getTranslations, setRequestLocale } from "next-intl/server"
import { DocPage } from "@/components/docs/DocBlocks"
const NAMESPACE = "docs.monitor.auditReport.assessment"
const LINKS = {
reportsLink: "/docs/monitor/audit-report/reports",
}
const URL = "https://proxmenux.com/docs/monitor/audit-report/assessment"
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }): Promise<Metadata> {
const { locale } = await params
const t = await getTranslations({ locale, namespace: `${NAMESPACE}.meta` })
return {
title: t("title"),
description: t("description"),
alternates: { canonical: URL },
openGraph: { title: t("title"), description: t("description"), type: "article", url: URL },
}
}
export default async function Page({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params
setRequestLocale(locale)
return <DocPage locale={locale} namespace={NAMESPACE} minutes={6} links={LINKS} />
}
@@ -0,0 +1,23 @@
import type { Metadata } from "next"
import { getTranslations, setRequestLocale } from "next-intl/server"
import { DocPage } from "@/components/docs/DocBlocks"
const NAMESPACE = "docs.monitor.auditReport.changes"
const URL = "https://proxmenux.com/docs/monitor/audit-report/changes"
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }): Promise<Metadata> {
const { locale } = await params
const t = await getTranslations({ locale, namespace: `${NAMESPACE}.meta` })
return {
title: t("title"),
description: t("description"),
alternates: { canonical: URL },
openGraph: { title: t("title"), description: t("description"), type: "article", url: URL },
}
}
export default async function Page({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params
setRequestLocale(locale)
return <DocPage locale={locale} namespace={NAMESPACE} minutes={5} />
}
@@ -0,0 +1,26 @@
import type { Metadata } from "next"
import { getTranslations, setRequestLocale } from "next-intl/server"
import { DocPage } from "@/components/docs/DocBlocks"
const NAMESPACE = "docs.monitor.auditReport"
const LINKS = {
healthLink: "/docs/monitor/health-monitor",
}
const URL = "https://proxmenux.com/docs/monitor/audit-report"
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }): Promise<Metadata> {
const { locale } = await params
const t = await getTranslations({ locale, namespace: `${NAMESPACE}.meta` })
return {
title: t("title"),
description: t("description"),
alternates: { canonical: URL },
openGraph: { title: t("title"), description: t("description"), type: "article", url: URL },
}
}
export default async function Page({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params
setRequestLocale(locale)
return <DocPage locale={locale} namespace={NAMESPACE} minutes={3} links={LINKS} />
}
@@ -0,0 +1,23 @@
import type { Metadata } from "next"
import { getTranslations, setRequestLocale } from "next-intl/server"
import { DocPage } from "@/components/docs/DocBlocks"
const NAMESPACE = "docs.monitor.auditReport.policy"
const URL = "https://proxmenux.com/docs/monitor/audit-report/policy"
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }): Promise<Metadata> {
const { locale } = await params
const t = await getTranslations({ locale, namespace: `${NAMESPACE}.meta` })
return {
title: t("title"),
description: t("description"),
alternates: { canonical: URL },
openGraph: { title: t("title"), description: t("description"), type: "article", url: URL },
}
}
export default async function Page({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params
setRequestLocale(locale)
return <DocPage locale={locale} namespace={NAMESPACE} minutes={4} />
}
@@ -0,0 +1,23 @@
import type { Metadata } from "next"
import { getTranslations, setRequestLocale } from "next-intl/server"
import { DocPage } from "@/components/docs/DocBlocks"
const NAMESPACE = "docs.monitor.auditReport.reports"
const URL = "https://proxmenux.com/docs/monitor/audit-report/reports"
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }): Promise<Metadata> {
const { locale } = await params
const t = await getTranslations({ locale, namespace: `${NAMESPACE}.meta` })
return {
title: t("title"),
description: t("description"),
alternates: { canonical: URL },
openGraph: { title: t("title"), description: t("description"), type: "article", url: URL },
}
}
export default async function Page({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params
setRequestLocale(locale)
return <DocPage locale={locale} namespace={NAMESPACE} minutes={6} />
}
@@ -0,0 +1,23 @@
import type { Metadata } from "next"
import { getTranslations, setRequestLocale } from "next-intl/server"
import { DocPage } from "@/components/docs/DocBlocks"
const NAMESPACE = "docs.monitor.auditReport.scope"
const URL = "https://proxmenux.com/docs/monitor/audit-report/scope"
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }): Promise<Metadata> {
const { locale } = await params
const t = await getTranslations({ locale, namespace: `${NAMESPACE}.meta` })
return {
title: t("title"),
description: t("description"),
alternates: { canonical: URL },
openGraph: { title: t("title"), description: t("description"), type: "article", url: URL },
}
}
export default async function Page({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params
setRequestLocale(locale)
return <DocPage locale={locale} namespace={NAMESPACE} minutes={3} />
}
@@ -292,6 +292,16 @@ export default async function VmsLxcsTabPage({
{t("drillIn.mountsCalloutBody")}
</Callout>
<h3 className="text-lg font-semibold mt-8 mb-2 text-gray-900">{t("drillIn.logsTitle")}</h3>
<p className="mb-4 text-gray-800 leading-relaxed">
{t.rich("drillIn.logsBody", {
code,
ociLink: (chunks: React.ReactNode) => (
<Link href="/docs/oci-manager/monitor" className="text-blue-600 hover:underline">{chunks}</Link>
),
})}
</p>
<h3 className="text-lg font-semibold mt-8 mb-2 text-gray-900">{t("drillIn.backupsTitle")}</h3>
<figure className="my-4">
@@ -173,7 +173,7 @@ export default async function HealthMonitorPage({
<tr key={row.category} className={idx < categoryRows.length - 1 ? "border-b border-gray-100" : ""}>
<td className="px-3 py-2 align-top whitespace-nowrap"><strong>{row.category}</strong></td>
<td className="px-3 py-2 align-top">{t.rich(`categories.rows.${idx}.checks`, { code })}</td>
<td className="px-3 py-2 align-top">{t.rich(`categories.rows.${idx}.events`, { code })}</td>
<td className="px-3 py-2 align-top">{t.rich(`categories.rows.${idx}.events`, { code, em })}</td>
</tr>
))}
</tbody>