docs(monitor): new App + Updates sub-pages for VMs & LXCs with i18n

This commit is contained in:
MacRimi
2026-08-11 20:14:17 +02:00
parent d2c7be9a32
commit 55a95a2346
27 changed files with 2300 additions and 42 deletions
@@ -0,0 +1,393 @@
import type { Metadata } from "next"
import type React from "react"
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 CopyableCode from "@/components/CopyableCode"
export async function generateMetadata({
params,
}: {
params: Promise<{ locale: string }>
}): Promise<Metadata> {
const { locale } = await params
const t = await getTranslations({ locale, namespace: "docs.monitor.dashboard.vmsLxcsApp.meta" })
return { title: t("title"), description: t("description") }
}
type TableRow = { method: string; when: string }
type BreakdownRow = { part: string; meaning: string }
type ExampleRow = { text: string; regex: string; result: string }
function Figure({ src, alt, caption }: { src: string; alt: string; caption: string }) {
return (
<figure className="my-6">
<img
src={src}
alt={alt}
className="rounded-lg border border-gray-200 shadow-sm w-full"
/>
<figcaption className="text-sm text-gray-500 mt-2 text-center italic">
{caption}
</figcaption>
</figure>
)
}
export default async function AppTabPage({
params,
}: {
params: Promise<{ locale: string }>
}) {
const { locale } = await params
setRequestLocale(locale)
const t = await getTranslations({ locale, namespace: "docs.monitor.dashboard.vmsLxcsApp" })
const messages = (await getMessages({ locale })) as unknown as {
docs: { monitor: { dashboard: { vmsLxcsApp: {
whatYouGet: { items: string[] }
registerSuggested: { steps: string[] }
manual: { linksItems: string[] }
multiple: { usefulItems: string[] }
tracking: {
ingredients: string[]
methodsTable: { rows: TableRow[] }
sourceItems: string[]
regexTwoItems: string[]
step2Items: string[]
step2Breakdown: { rows: BreakdownRow[] }
step3Examples: { rows: ExampleRow[] }
step4Items: string[]
step6CorrectItems: string[]
}
state: { items: string[] }
manage: { items: string[] }
notDetected: { steps: string[] }
} } } }
}
const v = messages.docs.monitor.dashboard.vmsLxcsApp
const whatYouGetItems = v.whatYouGet.items
const registerSteps = v.registerSuggested.steps
const manualLinks = v.manual.linksItems
const multipleUseful = v.multiple.usefulItems
const ingredients = v.tracking.ingredients
const methodsRows = v.tracking.methodsTable.rows
const sourceItems = v.tracking.sourceItems
const regexTwoItems = v.tracking.regexTwoItems
const step2Items = v.tracking.step2Items
const breakdownRows = v.tracking.step2Breakdown.rows
const exampleRows = v.tracking.step3Examples.rows
const step4Items = v.tracking.step4Items
const step6CorrectItems = v.tracking.step6CorrectItems
const stateItems = v.state.items
const manageItems = v.manage.items
const notDetectedSteps = v.notDetected.steps
// Rich-text tag handlers
const strong = (chunks: React.ReactNode) => <strong>{chunks}</strong>
const em = (chunks: React.ReactNode) => <em>{chunks}</em>
const code = (chunks: React.ReactNode) => (
<code className="text-sm bg-gray-100 px-1 rounded">{chunks}</code>
)
const linkUpdates = (chunks: React.ReactNode) => (
<Link href="/docs/monitor/dashboard/vms-lxcs/updates" className="text-blue-600 hover:underline">
{chunks}
</Link>
)
return (
<div className="max-w-4xl mx-auto px-4 py-8">
<DocHeader
title={t("header.title")}
description={t("header.description")}
estimatedMinutes={12}
/>
<p className="text-gray-800 mt-6">{t.rich("intro.p1", { strong, em, code })}</p>
<p className="text-gray-800 mt-4">{t.rich("intro.p2", { strong, em, code })}</p>
<p className="text-gray-800 mt-4">{t.rich("intro.p3", { strong, em, code, link: linkUpdates })}</p>
<h2 className="text-2xl font-semibold mt-10 mb-4 text-gray-900">{t("whatYouGet.heading")}</h2>
<p className="text-gray-800">{t("whatYouGet.lead")}</p>
<ul className="list-disc pl-6 mt-2 space-y-1 text-gray-800">
{whatYouGetItems.map((_, idx) => (
<li key={idx}>{t.rich(`whatYouGet.items.${idx}`, { strong, em, code })}</li>
))}
</ul>
<p className="text-gray-800 mt-4">{t.rich("whatYouGet.trailing", { strong, em, code })}</p>
<Callout variant="warning">{t.rich("whatYouGet.callout", { strong, em, code })}</Callout>
<h2 className="text-2xl font-semibold mt-10 mb-4 text-gray-900">{t("firstOpening.heading")}</h2>
<p className="text-gray-800">{t.rich("firstOpening.p1", { strong, em, code })}</p>
<p className="text-gray-800 mt-4">{t.rich("firstOpening.p2", { strong, em, code })}</p>
<Figure
src="/monitor/vms-modal-app-01.png"
alt={t("figures.f01.alt")}
caption={t("figures.f01.caption")}
/>
<h3 className="text-lg font-semibold mt-8 mb-2 text-gray-900">{t("registerSuggested.heading")}</h3>
<ol className="list-decimal pl-6 space-y-1 text-gray-800">
{registerSteps.map((_, idx) => (
<li key={idx}>{t.rich(`registerSuggested.steps.${idx}`, { strong, em, code })}</li>
))}
</ol>
<p className="text-gray-800 mt-4">{t.rich("registerSuggested.trailing", { strong, em, code })}</p>
<h2 className="text-2xl font-semibold mt-10 mb-4 text-gray-900">{t("catalog.heading")}</h2>
<p className="text-gray-800">{t.rich("catalog.p1", { strong, em, code })}</p>
<p className="text-gray-800 mt-4">{t.rich("catalog.p2", { strong, em, code })}</p>
<p className="text-gray-800 mt-4">{t.rich("catalog.p3", { strong, em, code })}</p>
<Figure
src="/monitor/vms-modal-app-02.png"
alt={t("figures.f02.alt")}
caption={t("figures.f02.caption")}
/>
<h2 className="text-2xl font-semibold mt-10 mb-4 text-gray-900">{t("manual.heading")}</h2>
<p className="text-gray-800">{t.rich("manual.p1", { strong, em, code })}</p>
<p className="text-gray-800 mt-4">{t.rich("manual.p2", { strong, em, code })}</p>
<h3 className="text-lg font-semibold mt-8 mb-2 text-gray-900">{t("manual.nameHeading")}</h3>
<p className="text-gray-800">{t.rich("manual.nameBody", { strong, em, code })}</p>
<h3 className="text-lg font-semibold mt-8 mb-2 text-gray-900">{t("manual.linksHeading")}</h3>
<p className="text-gray-800">{t("manual.linksLead")}</p>
<ul className="list-disc pl-6 mt-2 space-y-1 text-gray-800">
{manualLinks.map((_, idx) => (
<li key={idx}>{t.rich(`manual.linksItems.${idx}`, { strong, em, code })}</li>
))}
</ul>
<p className="text-gray-800 mt-4">{t.rich("manual.linksTrailing", { strong, em, code })}</p>
<p className="text-gray-800 mt-4">{t.rich("manual.linksConfirm", { strong, em, code })}</p>
<Figure
src="/monitor/vms-modal-app-03.png"
alt={t("figures.f03.alt")}
caption={t("figures.f03.caption")}
/>
<h2 className="text-2xl font-semibold mt-10 mb-4 text-gray-900">{t("multiple.heading")}</h2>
<p className="text-gray-800">{t.rich("multiple.intro", { strong, em, code })}</p>
<p className="text-gray-800 mt-4">{t("multiple.usefulLead")}</p>
<ul className="list-disc pl-6 mt-2 space-y-1 text-gray-800">
{multipleUseful.map((_, idx) => (
<li key={idx}>{t.rich(`multiple.usefulItems.${idx}`, { strong, em, code })}</li>
))}
</ul>
<p className="text-gray-800 mt-4">{t.rich("multiple.dontGroup", { strong, em, code })}</p>
<Figure
src="/monitor/vms-modal-app-04.png"
alt={t("figures.f04.alt")}
caption={t("figures.f04.caption")}
/>
<h2 className="text-2xl font-semibold mt-10 mb-4 text-gray-900">{t("tracking.heading")}</h2>
<p className="text-gray-800">{t("tracking.intro")}</p>
<ol className="list-decimal pl-6 mt-2 space-y-1 text-gray-800">
{ingredients.map((_, idx) => (
<li key={idx}>{t.rich(`tracking.ingredients.${idx}`, { strong, em, code })}</li>
))}
</ol>
<p className="text-gray-800 mt-4">{t.rich("tracking.trailing", { strong, em, code })}</p>
<h3 className="text-lg font-semibold mt-8 mb-2 text-gray-900">{t("tracking.methodsHeading")}</h3>
<p className="text-gray-800">{t("tracking.methodsLead")}</p>
<div className="overflow-x-auto my-4">
<table className="w-full border-collapse text-sm">
<thead>
<tr className="bg-gray-100">
<th className="border border-gray-300 px-3 py-2 text-left">{t("tracking.methodsTable.colMethod")}</th>
<th className="border border-gray-300 px-3 py-2 text-left">{t("tracking.methodsTable.colWhen")}</th>
</tr>
</thead>
<tbody>
{methodsRows.map((row, idx) => (
<tr key={idx}>
<td className="border border-gray-300 px-3 py-2 font-mono text-xs">{row.method}</td>
<td className="border border-gray-300 px-3 py-2">{row.when}</td>
</tr>
))}
</tbody>
</table>
</div>
<p className="text-gray-800 mt-4">{t.rich("tracking.methodsTrailing", { strong, em, code })}</p>
<h4 className="text-base font-semibold mt-6 mb-2 text-gray-900">{t("tracking.commandHeading")}</h4>
<p className="text-gray-800">{t.rich("tracking.commandP1", { strong, em, code })}</p>
<p className="text-gray-800 mt-4">{t("tracking.commandP2")}</p>
<CopyableCode code={t("tracking.commandExample1")} language="text" />
<p className="text-gray-800 mt-4">{t("tracking.commandP3")}</p>
<CopyableCode code={t("tracking.commandExample2")} language="text" />
<p className="text-gray-800 mt-4">{t.rich("tracking.commandP4", { strong, em, code })}</p>
<h3 className="text-lg font-semibold mt-8 mb-2 text-gray-900">{t("tracking.sourceHeading")}</h3>
<p className="text-gray-800">{t("tracking.sourceLead")}</p>
<ul className="list-disc pl-6 mt-2 space-y-1 text-gray-800">
{sourceItems.map((_, idx) => (
<li key={idx}>{t.rich(`tracking.sourceItems.${idx}`, { strong, em, code })}</li>
))}
</ul>
<p className="text-gray-800 mt-4">{t.rich("tracking.sourceTrailing", { strong, em, code })}</p>
<h3 className="text-lg font-semibold mt-8 mb-2 text-gray-900">{t("tracking.regexHeading")}</h3>
<p className="text-gray-800">{t.rich("tracking.regexIntro", { strong, em, code })}</p>
<p className="text-gray-800 mt-4">{t.rich("tracking.regexOptional", { strong, em, code })}</p>
<h4 className="text-base font-semibold mt-6 mb-2 text-gray-900">{t("tracking.regexTwoHeading")}</h4>
<ul className="list-disc pl-6 mt-2 space-y-1 text-gray-800">
{regexTwoItems.map((_, idx) => (
<li key={idx}>{t.rich(`tracking.regexTwoItems.${idx}`, { strong, em, code })}</li>
))}
</ul>
<p className="text-gray-800 mt-4">{t.rich("tracking.regexTwoTrailing", { strong, em, code })}</p>
<h4 className="text-base font-semibold mt-6 mb-2 text-gray-900">{t("tracking.step1Heading")}</h4>
<p className="text-gray-800">{t("tracking.step1P1")}</p>
<p className="text-gray-800 mt-4">{t("tracking.step1P2")}</p>
<p className="text-gray-800 mt-4">{t("tracking.step1P3")}</p>
<CopyableCode code={t("tracking.step1Cmd")} language="sh" />
<p className="text-gray-800 mt-4">{t("tracking.step1P4")}</p>
<CopyableCode code={t("tracking.step1Output")} language="text" />
<p className="text-gray-800 mt-4">{t("tracking.step1P5")}</p>
<p className="text-gray-800 mt-4">{t("tracking.step1P6")}</p>
<h4 className="text-base font-semibold mt-6 mb-2 text-gray-900">{t("tracking.step2Heading")}</h4>
<p className="text-gray-800">{t.rich("tracking.step2Lead", { strong, em, code })}</p>
<ul className="list-disc pl-6 mt-2 space-y-1 text-gray-800">
{step2Items.map((_, idx) => (
<li key={idx}>{t.rich(`tracking.step2Items.${idx}`, { strong, em, code })}</li>
))}
</ul>
<p className="text-gray-800 mt-4">{t("tracking.step2Recommended")}</p>
<CopyableCode code={t("tracking.step2Regex")} language="text" />
<p className="text-gray-800 mt-4">{t("tracking.step2ReadLead")}</p>
<div className="overflow-x-auto my-4">
<table className="w-full border-collapse text-sm">
<thead>
<tr className="bg-gray-100">
<th className="border border-gray-300 px-3 py-2 text-left">{t("tracking.step2Breakdown.colPart")}</th>
<th className="border border-gray-300 px-3 py-2 text-left">{t("tracking.step2Breakdown.colMeaning")}</th>
</tr>
</thead>
<tbody>
{breakdownRows.map((row, idx) => (
<tr key={idx}>
<td className="border border-gray-300 px-3 py-2 font-mono text-xs">{row.part}</td>
<td className="border border-gray-300 px-3 py-2">{row.meaning}</td>
</tr>
))}
</tbody>
</table>
</div>
<p className="text-gray-800 mt-4">{t.rich("tracking.step2DotNote", { strong, em, code })}</p>
<h4 className="text-base font-semibold mt-6 mb-2 text-gray-900">{t("tracking.step3Heading")}</h4>
<p className="text-gray-800">{t("tracking.step3Lead")}</p>
<div className="overflow-x-auto my-4">
<table className="w-full border-collapse text-sm">
<thead>
<tr className="bg-gray-100">
<th className="border border-gray-300 px-3 py-2 text-left">{t("tracking.step3Examples.colText")}</th>
<th className="border border-gray-300 px-3 py-2 text-left">{t("tracking.step3Examples.colRegex")}</th>
<th className="border border-gray-300 px-3 py-2 text-left">{t("tracking.step3Examples.colResult")}</th>
</tr>
</thead>
<tbody>
{exampleRows.map((row, idx) => (
<tr key={idx}>
<td className="border border-gray-300 px-3 py-2 font-mono text-xs">{row.text}</td>
<td className="border border-gray-300 px-3 py-2 font-mono text-xs">{row.regex}</td>
<td className="border border-gray-300 px-3 py-2 font-mono text-xs">{row.result}</td>
</tr>
))}
</tbody>
</table>
</div>
<p className="text-gray-800 mt-4">{t.rich("tracking.step3Note1", { strong, em, code })}</p>
<p className="text-gray-800 mt-4">{t.rich("tracking.step3Note2", { strong, em, code })}</p>
<h4 className="text-base font-semibold mt-6 mb-2 text-gray-900">{t("tracking.step4Heading")}</h4>
<p className="text-gray-800">{t("tracking.step4Intro")}</p>
<ul className="list-disc pl-6 mt-2 space-y-1 text-gray-800">
{step4Items.map((_, idx) => (
<li key={idx}>{t.rich(`tracking.step4Items.${idx}`, { strong, em, code })}</li>
))}
</ul>
<p className="text-gray-800 mt-4">{t.rich("tracking.step4Trailing", { strong, em, code })}</p>
<p className="text-gray-800 mt-4"><strong>{t("tracking.step4RecLabel")}</strong></p>
<CopyableCode code={t.raw("tracking.step4RecRegex") as string} language="text" />
<p className="text-gray-800 mt-4"><strong>{t("tracking.step4LessLabel")}</strong></p>
<CopyableCode code={t("tracking.step4LessRegex")} language="text" />
<p className="text-gray-800 mt-4">{t.rich("tracking.step4Note", { strong, em, code })}</p>
<h4 className="text-base font-semibold mt-6 mb-2 text-gray-900">{t("tracking.step5Heading")}</h4>
<p className="text-gray-800">{t("tracking.step5Lead")}</p>
<CopyableCode code={t("tracking.step5Regex")} language="text" />
<p className="text-gray-800 mt-4">{t.rich("tracking.step5P1", { strong, em, code })}</p>
<p className="text-gray-800 mt-4">{t("tracking.step5P2")}</p>
<h4 className="text-base font-semibold mt-6 mb-2 text-gray-900">{t("tracking.step6Heading")}</h4>
<p className="text-gray-800">{t.rich("tracking.step6Lead", { strong, em, code })}</p>
<CopyableCode code={t("tracking.step6Output")} language="text" />
<p className="text-gray-800 mt-4">{t("tracking.step6CorrectLead")}</p>
<ul className="list-disc pl-6 mt-2 space-y-1 text-gray-800">
{step6CorrectItems.map((_, idx) => (
<li key={idx}>{t.rich(`tracking.step6CorrectItems.${idx}`, { strong, em, code })}</li>
))}
</ul>
<p className="text-gray-800 mt-4">{t.rich("tracking.step6ErrorNote", { strong, em, code })}</p>
<Callout variant="tip">{t.rich("tracking.step6Callout", { strong, em, code })}</Callout>
<Figure
src="/monitor/vms-modal-app-05.png"
alt={t("figures.f05.alt")}
caption={t("figures.f05.caption")}
/>
<h2 className="text-2xl font-semibold mt-10 mb-4 text-gray-900">{t("state.heading")}</h2>
<Figure
src="/monitor/vms-modal-app-06.png"
alt={t("figures.f06.alt")}
caption={t("figures.f06.caption")}
/>
<p className="text-gray-800">{t("state.lead")}</p>
<ul className="list-disc pl-6 mt-2 space-y-1 text-gray-800">
{stateItems.map((_, idx) => (
<li key={idx}>{t.rich(`state.items.${idx}`, { strong, em, code })}</li>
))}
</ul>
<p className="text-gray-800 mt-4">{t.rich("state.trailing", { strong, em, code })}</p>
<h2 className="text-2xl font-semibold mt-10 mb-4 text-gray-900">{t("manage.heading")}</h2>
<p className="text-gray-800">{t("manage.lead")}</p>
<ul className="list-disc pl-6 mt-2 space-y-1 text-gray-800">
{manageItems.map((_, idx) => (
<li key={idx}>{t.rich(`manage.items.${idx}`, { strong, em, code })}</li>
))}
</ul>
<p className="text-gray-800 mt-4">{t.rich("manage.trailing", { strong, em, code })}</p>
<h2 className="text-2xl font-semibold mt-10 mb-4 text-gray-900">{t("notDetected.heading")}</h2>
<p className="text-gray-800">{t("notDetected.intro")}</p>
<ol className="list-decimal pl-6 mt-2 space-y-1 text-gray-800">
{notDetectedSteps.map((_, idx) => (
<li key={idx}>{t.rich(`notDetected.steps.${idx}`, { strong, em, code, link: linkUpdates })}</li>
))}
</ol>
<p className="text-gray-800 mt-4">{t.rich("notDetected.trailing", { strong, em, code })}</p>
<Figure
src="/monitor/vms-modal-app-07.png"
alt={t("figures.f07.alt")}
caption={t("figures.f07.caption")}
/>
</div>
)
}
@@ -39,7 +39,6 @@ export default async function VmsLxcsTabPage({
mountTypesItems: string[]
mountStateItems: string[]
backupsItems: string[]
updatesPanelItems: string[]
firewallItems: string[]
lifecycleRows: LifecycleRow[]
}
@@ -56,7 +55,6 @@ export default async function VmsLxcsTabPage({
const mountTypesItems = v.drillIn.mountTypesItems
const mountStateItems = v.drillIn.mountStateItems
const backupsItems = v.drillIn.backupsItems
const updatesPanelItems = v.drillIn.updatesPanelItems
const firewallItems = v.drillIn.firewallItems
const lifecycleRows = v.drillIn.lifecycleRows
const dataRows = v.dataCollected.rows
@@ -227,6 +225,40 @@ export default async function VmsLxcsTabPage({
<h4 className="text-base font-semibold mt-6 mb-2 text-gray-900">{t("drillIn.ipsTitle")}</h4>
<p className="mb-6 text-gray-800 leading-relaxed">{t("drillIn.ipsBody")}</p>
<h3 className="text-lg font-semibold mt-8 mb-2 text-gray-900">App</h3>
<p className="mb-4 text-gray-800 leading-relaxed">
The <strong>App</strong> section lets you register the applications running inside an LXC, wire quick web
links, and optionally track installed vs. upstream versions. Registrations feed the App-level notifications
and the update flows on the next section.
</p>
<p className="mb-6 text-gray-800 leading-relaxed">
See the{" "}
<Link
href="/docs/monitor/dashboard/vms-lxcs/app"
className="text-blue-600 hover:underline"
>
dedicated App page
</Link>{" "}
for the catalog, manual registration, version-tracking methods and regex patterns.
</p>
<h3 className="text-lg font-semibold mt-8 mb-2 text-gray-900">Updates</h3>
<p className="mb-4 text-gray-800 leading-relaxed">
The <strong>Updates</strong> section covers OS package updates (APT / APK) and application updates via
Community Scripts helpers or custom commands. Detection runs unconditionally on running LXCs; whether pending
updates also trigger a notification is controlled from <strong>Settings Notifications</strong>.
</p>
<p className="mb-6 text-gray-800 leading-relaxed">
See the{" "}
<Link
href="/docs/monitor/dashboard/vms-lxcs/updates"
className="text-blue-600 hover:underline"
>
dedicated Updates page
</Link>{" "}
for the decision matrix, custom-command guidance, backup / restart preferences and scheduled updates.
</p>
<h3 className="text-lg font-semibold mt-8 mb-2 text-gray-900">{t("drillIn.mountsTitle")}</h3>
<figure className="my-4">
@@ -287,45 +319,6 @@ export default async function VmsLxcsTabPage({
{t.rich("drillIn.backupsOutro", { strong })}
</p>
<h3 className="text-lg font-semibold mt-8 mb-2 text-gray-900">{t("drillIn.updatesTitle")}</h3>
<figure className="my-4">
<img
src="/monitor/vms-modal-lxc-updates.png"
alt={t("drillIn.updatesImageAlt")}
className="rounded-lg border border-gray-200 shadow-sm w-full"
/>
<figcaption className="text-sm text-gray-500 mt-2 text-center italic">
{t("drillIn.updatesImageCaption")}
</figcaption>
</figure>
<p className="mb-4 text-gray-800 leading-relaxed">
{t.rich("drillIn.updatesIntro", { strong, code })}
</p>
<h4 className="text-base font-semibold mt-6 mb-2 text-gray-900">{t("drillIn.updatesPanelTitle")}</h4>
<ul className="list-disc pl-6 mb-4 text-gray-800 leading-relaxed space-y-1">
{updatesPanelItems.map((_, idx) => (
<li key={idx}>{t.rich(`drillIn.updatesPanelItems.${idx}`, { strong })}</li>
))}
</ul>
<h4 className="text-base font-semibold mt-6 mb-2 text-gray-900">{t("drillIn.updatesScopeTitle")}</h4>
<p className="mb-4 text-gray-800 leading-relaxed">
{t.rich("drillIn.updatesScopeBody", { strong, em, code })}
</p>
<h4 className="text-base font-semibold mt-6 mb-2 text-gray-900">{t("drillIn.updatesToggleTitle")}</h4>
<Callout variant="info" title={t("drillIn.updatesToggleCalloutTitle")}>
{t.rich("drillIn.updatesToggleCalloutBody", { strong, code })}
</Callout>
<h4 className="text-base font-semibold mt-6 mb-2 text-gray-900">{t("drillIn.updatesApplyTitle")}</h4>
<p className="mb-6 text-gray-800 leading-relaxed">
{t.rich("drillIn.updatesApplyBody", { code })}
</p>
<h3 className="text-lg font-semibold mt-8 mb-2 text-gray-900">{t("drillIn.firewallTitle")}</h3>
<p className="mb-4 text-gray-800 leading-relaxed">{t("drillIn.firewallIntro")}</p>
@@ -0,0 +1,349 @@
import type { Metadata } from "next"
import type React from "react"
import { getTranslations, getMessages, setRequestLocale } from "next-intl/server"
import { Link } from "@/i18n/navigation"
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<Metadata> {
const { locale } = await params
const t = await getTranslations({ locale, namespace: "docs.monitor.dashboard.vmsLxcsUpdates.meta" })
return { title: t("title"), description: t("description") }
}
type DecisionRow = { situation: string; action: string }
type DifferenceRow = { field: string; location: string; role: string }
function Figure({ src, alt, caption }: { src: string; alt: string; caption: string }) {
return (
<figure className="my-6">
<img
src={src}
alt={alt}
className="rounded-lg border border-gray-200 shadow-sm w-full"
/>
<figcaption className="text-sm text-gray-500 mt-2 text-center italic">
{caption}
</figcaption>
</figure>
)
}
export default async function UpdatesTabPage({
params,
}: {
params: Promise<{ locale: string }>
}) {
const { locale } = await params
setRequestLocale(locale)
const t = await getTranslations({ locale, namespace: "docs.monitor.dashboard.vmsLxcsUpdates" })
const messages = (await getMessages({ locale })) as unknown as {
docs: { monitor: { dashboard: { vmsLxcsUpdates: {
decision: { table: { rows: DecisionRow[] } }
figureOut: {
step3Items: string[]
step4Items: string[]
}
requirements: { items: string[] }
difference: { table: { rows: DifferenceRow[] } }
apply: {
steps: string[]
systemItems: string[]
appItems: string[]
}
scheduled: { createSteps: string[] }
} } } }
}
const v = messages.docs.monitor.dashboard.vmsLxcsUpdates
const decisionRows = v.decision.table.rows
const step3Items = v.figureOut.step3Items
const step4Items = v.figureOut.step4Items
const reqItems = v.requirements.items
const diffRows = v.difference.table.rows
const applySteps = v.apply.steps
const applySystem = v.apply.systemItems
const applyApp = v.apply.appItems
const schedSteps = v.scheduled.createSteps
// Rich-text tag handlers
const strong = (chunks: React.ReactNode) => <strong>{chunks}</strong>
const em = (chunks: React.ReactNode) => <em>{chunks}</em>
const code = (chunks: React.ReactNode) => (
<code className="text-sm bg-gray-100 px-1 rounded">{chunks}</code>
)
const linkApp = (chunks: React.ReactNode) => (
<Link href="/docs/monitor/dashboard/vms-lxcs/app" className="text-blue-600 hover:underline">
{chunks}
</Link>
)
const linkHelperHome = (chunks: React.ReactNode) => (
<a
href="https://community-scripts.org"
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1 text-blue-600 hover:underline"
>
{chunks}
<ExternalLink className="h-3.5 w-3.5" aria-hidden="true" />
</a>
)
const linkHelperDocs = (chunks: React.ReactNode) => (
<a
href="https://community-scripts.org/docs/tools/pve/update-apps"
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1 text-blue-600 hover:underline"
>
{chunks}
<ExternalLink className="h-3.5 w-3.5" aria-hidden="true" />
</a>
)
return (
<div className="max-w-4xl mx-auto px-4 py-8">
<DocHeader
title={t("header.title")}
description={t("header.description")}
estimatedMinutes={12}
/>
<p className="text-gray-800 mt-6">{t.rich("intro.p1", { strong, em, code })}</p>
<p className="text-gray-800 mt-4">{t.rich("intro.p2", { strong, em, code, link: linkApp })}</p>
<Callout variant="info">{t.rich("intro.callout", { strong, em, code })}</Callout>
<h2 className="text-2xl font-semibold mt-10 mb-4 text-gray-900">{t("mechanisms.heading")}</h2>
<p className="text-gray-800">{t("mechanisms.intro")}</p>
<h3 className="text-lg font-semibold mt-8 mb-2 text-gray-900">{t("mechanisms.osHeading")}</h3>
<p className="text-gray-800">{t("mechanisms.osP1")}</p>
<p className="text-gray-800 mt-4">{t.rich("mechanisms.osP2", { strong, em, code })}</p>
<p className="text-gray-800 mt-4">{t("mechanisms.osP3")}</p>
<h3 className="text-lg font-semibold mt-8 mb-2 text-gray-900">{t("mechanisms.helperHeading")}</h3>
<p className="text-gray-800">{t.rich("mechanisms.helperP1", { strong, em, code, linkHelperHome })}</p>
<p className="text-gray-800 mt-4">{t.rich("mechanisms.helperP2", { strong, em, code })}</p>
<p className="text-gray-800 mt-4">
{t.rich("mechanisms.helperP3", { strong, em, code, linkHelperHome, linkHelperDocs })}
</p>
<p className="text-gray-800 mt-4">{t.rich("mechanisms.helperP4", { strong, em, code })}</p>
<h3 className="text-lg font-semibold mt-8 mb-2 text-gray-900">{t("mechanisms.customHeading")}</h3>
<p className="text-gray-800">{t.rich("mechanisms.customP1", { strong, em, code })}</p>
<p className="text-gray-800 mt-4">{t("mechanisms.customP2")}</p>
<h2 className="text-2xl font-semibold mt-10 mb-4 text-gray-900">{t("decision.heading")}</h2>
<div className="overflow-x-auto my-4">
<table className="w-full border-collapse text-sm">
<thead>
<tr className="bg-gray-100">
<th className="border border-gray-300 px-3 py-2 text-left">{t("decision.table.colSituation")}</th>
<th className="border border-gray-300 px-3 py-2 text-left">{t("decision.table.colAction")}</th>
</tr>
</thead>
<tbody>
{decisionRows.map((row, idx) => (
<tr key={idx}>
<td className="border border-gray-300 px-3 py-2">{row.situation}</td>
<td className="border border-gray-300 px-3 py-2 font-mono text-xs">{row.action}</td>
</tr>
))}
</tbody>
</table>
</div>
<p className="text-gray-800 mt-4">{t("decision.trailing")}</p>
<Figure
src="/monitor/vms-modal-updates-01.png"
alt={t("figures.f01.alt")}
caption={t("figures.f01.caption")}
/>
<Figure
src="/monitor/vms-modal-updates-02.png"
alt={t("figures.f02.alt")}
caption={t("figures.f02.caption")}
/>
<h2 className="text-2xl font-semibold mt-10 mb-4 text-gray-900">{t("custom.heading")}</h2>
<p className="text-gray-800">{t.rich("custom.p1", { strong, em, code })}</p>
<p className="text-gray-800 mt-4">{t("custom.p2")}</p>
<Figure
src="/monitor/vms-modal-updates-03.png"
alt={t("figures.f03.alt")}
caption={t("figures.f03.caption")}
/>
<Figure
src="/monitor/vms-modal-updates-04.png"
alt={t("figures.f04.alt")}
caption={t("figures.f04.caption")}
/>
<h2 className="text-2xl font-semibold mt-10 mb-4 text-gray-900">{t("figureOut.heading")}</h2>
<p className="text-gray-800">{t("figureOut.intro")}</p>
<h3 className="text-lg font-semibold mt-8 mb-2 text-gray-900">{t("figureOut.step1Heading")}</h3>
<p className="text-gray-800">{t.rich("figureOut.step1P1", { strong, em, code })}</p>
<p className="text-gray-800 mt-4">{t("figureOut.step1P2")}</p>
<CopyableCode code={t("figureOut.step1Cmd1")} language="sh" />
<p className="text-gray-800 mt-4">{t("figureOut.step1P3")}</p>
<CopyableCode code={t("figureOut.step1Cmd2")} language="sh" />
<p className="text-gray-800 mt-4">{t.rich("figureOut.step1P4", { strong, em, code })}</p>
<h3 className="text-lg font-semibold mt-8 mb-2 text-gray-900">{t("figureOut.step2Heading")}</h3>
<p className="text-gray-800">{t.rich("figureOut.step2P1", { strong, em, code })}</p>
<p className="text-gray-800 mt-4">{t("figureOut.step2P2")}</p>
<h3 className="text-lg font-semibold mt-8 mb-2 text-gray-900">{t("figureOut.step3Heading")}</h3>
<p className="text-gray-800">{t("figureOut.step3Lead")}</p>
<ul className="list-disc pl-6 mt-2 space-y-1 text-gray-800">
{step3Items.map((_, idx) => (
<li key={idx}>{t.rich(`figureOut.step3Items.${idx}`, { strong, em, code })}</li>
))}
</ul>
<p className="text-gray-800 mt-4">{t("figureOut.step3P1")}</p>
<CopyableCode code={t("figureOut.step3Cmd")} language="sh" />
<p className="text-gray-800 mt-4">{t.rich("figureOut.step3P2", { strong, em, code })}</p>
<h3 className="text-lg font-semibold mt-8 mb-2 text-gray-900">{t("figureOut.step4Heading")}</h3>
<p className="text-gray-800">{t("figureOut.step4Lead")}</p>
<ul className="list-disc pl-6 mt-2 space-y-1 text-gray-800">
{step4Items.map((_, idx) => (
<li key={idx}>{t.rich(`figureOut.step4Items.${idx}`, { strong, em, code })}</li>
))}
</ul>
<p className="text-gray-800 mt-4">{t("figureOut.step4Note")}</p>
<h3 className="text-lg font-semibold mt-8 mb-2 text-gray-900">{t("figureOut.step5Heading")}</h3>
<p className="text-gray-800">{t("figureOut.step5P1")}</p>
<CopyableCode code={t.raw("figureOut.step5Cmd1") as string} language="text" />
<p className="text-gray-800 mt-4">{t.rich("figureOut.step5P2", { strong, em, code })}</p>
<p className="text-gray-800 mt-4">{t("figureOut.step5P3")}</p>
<CopyableCode code={t("figureOut.step5Cmd2")} language="sh" />
<p className="text-gray-800 mt-4">{t("figureOut.step5P4")}</p>
<h2 className="text-2xl font-semibold mt-10 mb-4 text-gray-900">{t("requirements.heading")}</h2>
<p className="text-gray-800">{t("requirements.lead")}</p>
<ul className="list-disc pl-6 mt-2 space-y-1 text-gray-800">
{reqItems.map((_, idx) => (
<li key={idx}>{t.rich(`requirements.items.${idx}`, { strong, em, code })}</li>
))}
</ul>
<p className="text-gray-800 mt-4">{t.rich("requirements.trailing", { strong, em, code })}</p>
<h2 className="text-2xl font-semibold mt-10 mb-4 text-gray-900">{t("difference.heading")}</h2>
<p className="text-gray-800">{t("difference.lead")}</p>
<div className="overflow-x-auto my-4">
<table className="w-full border-collapse text-sm">
<thead>
<tr className="bg-gray-100">
<th className="border border-gray-300 px-3 py-2 text-left">{t("difference.table.colField")}</th>
<th className="border border-gray-300 px-3 py-2 text-left">{t("difference.table.colLocation")}</th>
<th className="border border-gray-300 px-3 py-2 text-left">{t("difference.table.colRole")}</th>
</tr>
</thead>
<tbody>
{diffRows.map((row, idx) => (
<tr key={idx}>
<td className="border border-gray-300 px-3 py-2 font-medium">{row.field}</td>
<td className="border border-gray-300 px-3 py-2 text-xs">{row.location}</td>
<td className="border border-gray-300 px-3 py-2 text-xs">{row.role}</td>
</tr>
))}
</tbody>
</table>
</div>
<p className="text-gray-800 mt-4">{t.rich("difference.trailing", { strong, em, code })}</p>
<h2 className="text-2xl font-semibold mt-10 mb-4 text-gray-900">{t("apply.heading")}</h2>
<p className="text-gray-800">{t("apply.lead")}</p>
<ol className="list-decimal pl-6 mt-2 space-y-1 text-gray-800">
{applySteps.map((_, idx) => (
<li key={idx}>{t.rich(`apply.steps.${idx}`, { strong, em, code })}</li>
))}
</ol>
<p className="text-gray-800 mt-4">{t("apply.trailing1")}</p>
<p className="text-gray-800 mt-4">{t("apply.systemLead")}</p>
<ul className="list-disc pl-6 mt-2 space-y-1 text-gray-800">
{applySystem.map((_, idx) => (
<li key={idx}>{t.rich(`apply.systemItems.${idx}`, { strong, em, code })}</li>
))}
</ul>
<p className="text-gray-800 mt-4">{t("apply.appLead")}</p>
<ul className="list-disc pl-6 mt-2 space-y-1 text-gray-800">
{applyApp.map((_, idx) => (
<li key={idx}>{t.rich(`apply.appItems.${idx}`, { strong, em, code })}</li>
))}
</ul>
<p className="text-gray-800 mt-4">{t("apply.trailing2")}</p>
<Figure
src="/monitor/vms-modal-updates-05.png"
alt={t("figures.f05.alt")}
caption={t("figures.f05.caption")}
/>
<h2 className="text-2xl font-semibold mt-10 mb-4 text-gray-900">{t("backup.heading")}</h2>
<p className="text-gray-800">{t.rich("backup.p1", { strong, em, code })}</p>
<p className="text-gray-800 mt-4">{t("backup.p2")}</p>
<p className="text-gray-800 mt-4">{t("backup.p3")}</p>
<h2 className="text-2xl font-semibold mt-10 mb-4 text-gray-900">{t("restart.heading")}</h2>
<p className="text-gray-800">{t.rich("restart.p1", { strong, em, code })}</p>
<p className="text-gray-800 mt-4">{t("restart.p2")}</p>
<p className="text-gray-800 mt-4">{t("restart.p3")}</p>
<Figure
src="/monitor/vms-modal-updates-06.png"
alt={t("figures.f06.alt")}
caption={t("figures.f06.caption")}
/>
<h2 className="text-2xl font-semibold mt-10 mb-4 text-gray-900">{t("scheduled.heading")}</h2>
<p className="text-gray-800">{t.rich("scheduled.p1", { strong, em, code })}</p>
<p className="text-gray-800 mt-4">{t("scheduled.createLead")}</p>
<ol className="list-decimal pl-6 mt-2 space-y-1 text-gray-800">
{schedSteps.map((_, idx) => (
<li key={idx}>{t.rich(`scheduled.createSteps.${idx}`, { strong, em, code })}</li>
))}
</ol>
<p className="text-gray-800 mt-4">{t("scheduled.p2")}</p>
<p className="text-gray-800 mt-4">{t("scheduled.p3")}</p>
<Callout variant="tip">{t("scheduled.callout")}</Callout>
<Figure
src="/monitor/vms-modal-updates-07.png"
alt={t("figures.f07.alt")}
caption={t("figures.f07.caption")}
/>
<h2 className="text-2xl font-semibold mt-10 mb-4 text-gray-900">{t("verify.heading")}</h2>
<p className="text-gray-800">{t("verify.p1")}</p>
<p className="text-gray-800 mt-4">{t.rich("verify.p2", { strong, em, code, link: linkApp })}</p>
<p className="text-gray-800 mt-4">{t("verify.p3")}</p>
<h2 className="text-2xl font-semibold mt-10 mb-4 text-gray-900">{t("troubleshoot.heading")}</h2>
<h3 className="text-lg font-semibold mt-8 mb-2 text-gray-900">{t("troubleshoot.noButtonHeading")}</h3>
<Callout variant="troubleshoot">{t.rich("troubleshoot.noButtonBody", { strong, em, code })}</Callout>
<h3 className="text-lg font-semibold mt-8 mb-2 text-gray-900">{t("troubleshoot.aptHeading")}</h3>
<Callout variant="troubleshoot">{t.rich("troubleshoot.aptBody", { strong, em, code })}</Callout>
<h3 className="text-lg font-semibold mt-8 mb-2 text-gray-900">{t("troubleshoot.noUpdaterHeading")}</h3>
<Callout variant="troubleshoot">{t.rich("troubleshoot.noUpdaterBody", { strong, em, code })}</Callout>
<h3 className="text-lg font-semibold mt-8 mb-2 text-gray-900">{t("troubleshoot.helperDetectedHeading")}</h3>
<Callout variant="troubleshoot">{t.rich("troubleshoot.helperDetectedBody", { strong, em, code })}</Callout>
<h3 className="text-lg font-semibold mt-8 mb-2 text-gray-900">{t("troubleshoot.customFailsHeading")}</h3>
<Callout variant="troubleshoot">{t.rich("troubleshoot.customFailsBody", { strong, em, code })}</Callout>
</div>
)
}