fix: improve host diagnostics, storage handling, and maintenance workflows

- add zero-downtime Proxmox TLS certificate refresh from the Security panel (#307)
- classify storage availability independently from missing capacity information (#309)
- update ZFS ARC sizing and safely reconcile conflicting module configurations
- preserve and restore migrated ZFS settings without overwriting later administrator changes
- stop memory optimization from forcing the kernel overcommit policy
- correlate multi-line OOM events and identify the affected LXC, cgroup limits, swap and killed process
- add selectable Bash prompt path styles and clearer shell activation guidance
- protect technical names during automatic translation and correct localized terminology
- update the Coral and VM/LXC Apps and Updates documentation, translations and screenshots
This commit is contained in:
MacRimi
2026-08-25 18:48:39 +02:00
parent 2302b0967b
commit 46158209f1
48 changed files with 2449 additions and 2466 deletions
@@ -41,6 +41,7 @@ export default async function InstallCoralTPUHostPage({
pcie: { items: StringItem[]; kernelPatches: StringItem[]; afterItems: StringItem[] }
usb: { items: StringItem[] }
}
legacyCleanup: { items: StringItem[] }
reinstallUninstall: { uninstallItems: StringItem[] }
related: { items: RelatedItem[] }
} } }
@@ -50,6 +51,7 @@ export default async function InstallCoralTPUHostPage({
const kernelPatches = messages.docs.hardware.installCoralTpuHost.walkthrough.pcie.kernelPatches
const pcieAfterItems = messages.docs.hardware.installCoralTpuHost.walkthrough.pcie.afterItems
const usbItems = messages.docs.hardware.installCoralTpuHost.walkthrough.usb.items
const legacyCleanupItems = messages.docs.hardware.installCoralTpuHost.legacyCleanup.items
const uninstallItems = messages.docs.hardware.installCoralTpuHost.reinstallUninstall.uninstallItems
const relatedItems = messages.docs.hardware.installCoralTpuHost.related.items
@@ -165,54 +167,18 @@ export default async function InstallCoralTPUHostPage({
<p className="mb-4 text-gray-800 leading-relaxed">{t("howRuns.body")}</p>
<pre className="bg-gray-100 text-gray-800 p-4 rounded-md overflow-x-auto text-sm my-4 border border-gray-200 leading-snug">
{`┌────────────────────────────────────────────────┐
│ 1. detect_coral_hardware() │
│ → count PCIe (vendor 1ac1) + USB (IDs) │
└────────────────┬───────────────────────────────┘
▼
┌───────────┴───────────┐
│ │
▼ ▼
None At least one
│ │
▼ ▼
Dialog pre_install_prompt()
"No Coral" → shows what was detected
exit 0 and what will be installed
│
▼
┌────────────────┴────────────────┐
│ │
▼ ▼
PCIe detected? USB detected?
│ │
Yes Yes
▼ ▼
install_gasket_apex_dkms install_libedgetpu_runtime
├─ cleanup_broken_gasket_dkms ├─ add Google GPG keyring
├─ apt install deps │ /etc/apt/keyrings/...
│ (git, dkms, build-essential, ├─ add APT repo (signed-by)
│ proxmox-headers-$(uname-r)) │ /etc/apt/sources.list.d/
├─ clone feranick/gasket-driver │ coral-edgetpu.list
│ (google fallback + patches) ├─ apt install libedgetpu1-std
├─ copy src/ → /usr/src/ └─ udev reload + trigger
│ gasket-1.0/
├─ generate dkms.conf
├─ dkms add / build / install
└─ modprobe gasket + apex
+ ensure_apex_group_and_udev
│ │
└────────────────┬────────────────┘
▼
┌────────────────┴────────────────┐
│ │
PCIe ran? USB only
│ │
▼ ▼
restart_prompt() "No reboot required"
(reboot required to (runtime + udev rules
load fresh kernel are already active)
module cleanly)`}
{`Detect Coral hardware
│
├─ PCIe / M.2 detected ──► build gasket + apex with DKMS
│ └─ reboot recommended
│
├─ USB detected ─────────► install libedgetpu runtime
│ └─ no reboot required
│
└─ no PCIe / M.2 detected
├─ no legacy gasket-dkms state ─► no PCIe changes
└─ legacy gasket-dkms state ────► offer optional cleanup
└─ USB runtime untouched`}
</pre>
<h2 className="text-2xl font-semibold mt-10 mb-4 text-gray-900">{t("walkthrough.heading")}</h2>
@@ -290,6 +256,19 @@ export default async function InstallCoralTPUHostPage({
</Steps.Step>
</Steps>
<h2 className="text-2xl font-semibold mt-10 mb-4 text-gray-900">{t("legacyCleanup.heading")}</h2>
<p className="mb-4 text-gray-800 leading-relaxed">
{t.rich("legacyCleanup.intro", { code, strong })}
</p>
<ul className="list-disc pl-6 mb-4 text-gray-800 leading-relaxed space-y-1">
{legacyCleanupItems.map((_, idx) => (
<li key={idx}>{t.rich(`legacyCleanup.items.${idx}`, { code, strong })}</li>
))}
</ul>
<Callout variant="warning" title={t("legacyCleanup.warningTitle")}>
{t("legacyCleanup.warningBody")}
</Callout>
<h2 className="text-2xl font-semibold mt-10 mb-4 text-gray-900">{t("reinstallUninstall.heading")}</h2>
<p className="mb-4 text-gray-800 leading-relaxed">
@@ -1,10 +1,10 @@
import type { Metadata } from "next"
import type React from "react"
import { getTranslations, getMessages, setRequestLocale } from "next-intl/server"
import { getMessages, getTranslations, 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"
import { DocHeader } from "@/components/ui/doc-header"
export async function generateMetadata({
params,
@@ -16,21 +16,15 @@ export async function generateMetadata({
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 }
type DetectorRow = { method: string; use: string }
type StateRow = { state: string; display: string; meaning: string }
type ProblemRow = { problem: string; resolution: 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>
<img src={src} alt={alt} className="w-full rounded-lg border border-gray-200 shadow-sm" />
<figcaption className="mt-2 text-center text-sm italic text-gray-500">{caption}</figcaption>
</figure>
)
}
@@ -43,362 +37,178 @@ export default async function AppTabPage({
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[] }
options: { items: string[] }
notDetected: { steps: string[] }
overview: { items: string[] }
discovery: { items: string[] }
registration: { steps: string[] }
catalog: { items: string[] }
docker: { items: string[] }
webLinks: { items: string[] }
tracking: { detectorRows: DetectorRow[]; sources: string[]; regexRules: string[] }
updater: { items: string[] }
states: { rows: StateRow[] }
management: { items: string[] }
troubleshooting: { rows: ProblemRow[] }
} } } }
}
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 optionsItems = v.options.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 code = (chunks: React.ReactNode) => <code className="rounded bg-gray-100 px-1 text-sm">{chunks}</code>
const linkUpdates = (chunks: React.ReactNode) => (
<Link href="/docs/monitor/dashboard/vms-lxcs/updates" className="text-blue-600 hover:underline">
{chunks}
</Link>
)
const richList = (base: string, items: string[]) => (
<ul className="mt-2 list-disc space-y-2 pl-6 text-gray-800">
{items.map((_, idx) => (
<li key={idx}>{t.rich(`${base}.${idx}`, { strong, em, code, link: linkUpdates })}</li>
))}
</ul>
)
return (
<div className="max-w-4xl mx-auto px-4 py-8">
<DocHeader
title={t("header.title")}
description={t("header.description")}
estimatedMinutes={12}
/>
<div className="mx-auto max-w-4xl px-4 py-8">
<DocHeader title={t("header.title")} description={t("header.description")} estimatedMinutes={11} />
<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>
<p className="mt-6 text-gray-800">{t.rich("intro.p1", { strong, em, code })}</p>
<p className="mt-4 text-gray-800">{t.rich("intro.p2", { strong, em, code, link: linkUpdates })}</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("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="mt-10 mb-4 text-2xl font-semibold text-gray-900">{t("overview.heading")}</h2>
<p className="text-gray-800">{t("overview.lead")}</p>
{richList("overview.items", v.overview.items)}
<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>
<h2 className="mt-10 mb-4 text-2xl font-semibold text-gray-900">{t("discovery.heading")}</h2>
<p className="text-gray-800">{t.rich("discovery.lead", { strong, em, code })}</p>
{richList("discovery.items", v.discovery.items)}
<Callout variant="tip">{t.rich("discovery.callout", { strong, em, code })}</Callout>
<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>
<h2 className="mt-10 mb-4 text-2xl font-semibold text-gray-900">{t("registration.heading")}</h2>
<p className="text-gray-800">{t("registration.lead")}</p>
<ol className="mt-2 list-decimal space-y-2 pl-6 text-gray-800">
{v.registration.steps.map((_, idx) => (
<li key={idx}>{t.rich(`registration.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>
<h2 className="mt-10 mb-4 text-2xl font-semibold text-gray-900">{t("catalog.heading")}</h2>
<p className="text-gray-800">{t.rich("catalog.lead", { strong, em, code })}</p>
{richList("catalog.items", v.catalog.items)}
<Figure
src="/monitor/vms-modal-app-02.png"
alt={t("figures.f02.alt")}
caption={t("figures.f02.caption")}
alt={t("figures.catalog.alt")}
caption={t("figures.catalog.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>
<h2 className="mt-10 mb-4 text-2xl font-semibold text-gray-900">{t("docker.heading")}</h2>
<p className="text-gray-800">{t.rich("docker.lead", { strong, em, code })}</p>
{richList("docker.items", v.docker.items)}
<Callout variant="info">{t.rich("docker.callout", { strong, em, code })}</Callout>
<h2 className="mt-10 mb-4 text-2xl font-semibold text-gray-900">{t("webLinks.heading")}</h2>
<p className="text-gray-800">{t("webLinks.lead")}</p>
{richList("webLinks.items", v.webLinks.items)}
<Figure
src="/monitor/vms-modal-app-03.png"
alt={t("figures.f03.alt")}
caption={t("figures.f03.caption")}
alt={t("figures.webLinks.alt")}
caption={t("figures.webLinks.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">
<h2 className="mt-10 mb-4 text-2xl font-semibold text-gray-900">{t("tracking.heading")}</h2>
<p className="text-gray-800">{t.rich("tracking.lead", { strong, em, code })}</p>
<div className="my-4 overflow-x-auto">
<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>
<th className="border border-gray-300 px-3 py-2 text-left">{t("tracking.colMethod")}</th>
<th className="border border-gray-300 px-3 py-2 text-left">{t("tracking.colUse")}</th>
</tr>
</thead>
<tbody>
{methodsRows.map((row, idx) => (
{v.tracking.detectorRows.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>
<td className="border border-gray-300 px-3 py-2">{row.use}</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="mt-8 mb-2 text-lg font-semibold text-gray-900">{t("tracking.sourcesHeading")}</h3>
{richList("tracking.sources", v.tracking.sources)}
<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="mt-8 mb-2 text-lg font-semibold text-gray-900">{t("tracking.regexHeading")}</h3>
<p className="text-gray-800">{t.rich("tracking.regexLead", { strong, em, code })}</p>
{richList("tracking.regexRules", v.tracking.regexRules)}
<p className="mt-4 text-gray-800">{t("tracking.regexExampleLead")}</p>
<CopyableCode code={t.raw("tracking.regexExample") as string} language="text" />
<Callout variant="warning">{t.rich("tracking.regexCallout", { strong, em, code })}</Callout>
<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>
<Figure
src="/monitor/vms-modal-app-05.png"
alt={t("figures.tracking.alt")}
caption={t("figures.tracking.caption")}
/>
<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>
<h2 className="mt-10 mb-4 text-2xl font-semibold text-gray-900">{t("updater.heading")}</h2>
<p className="text-gray-800">{t.rich("updater.lead", { strong, em, code, link: linkUpdates })}</p>
{richList("updater.items", v.updater.items)}
<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">
<h2 className="mt-10 mb-4 text-2xl font-semibold text-gray-900">{t("states.heading")}</h2>
<div className="my-4 overflow-x-auto">
<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>
<th className="border border-gray-300 px-3 py-2 text-left">{t("states.colState")}</th>
<th className="border border-gray-300 px-3 py-2 text-left">{t("states.colDisplay")}</th>
<th className="border border-gray-300 px-3 py-2 text-left">{t("states.colMeaning")}</th>
</tr>
</thead>
<tbody>
{breakdownRows.map((row, idx) => (
{v.states.rows.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 font-medium">{row.state}</td>
<td className="border border-gray-300 px-3 py-2">{row.display}</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>
<Figure
src="/monitor/vms-modal-app-06.png"
alt={t("figures.card.alt")}
caption={t("figures.card.caption")}
/>
<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">
<h2 className="mt-10 mb-4 text-2xl font-semibold text-gray-900">{t("management.heading")}</h2>
<p className="text-gray-800">{t("management.lead")}</p>
{richList("management.items", v.management.items)}
<h2 className="mt-10 mb-4 text-2xl font-semibold text-gray-900">{t("troubleshooting.heading")}</h2>
<div className="my-4 overflow-x-auto">
<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>
<th className="border border-gray-300 px-3 py-2 text-left">{t("troubleshooting.colProblem")}</th>
<th className="border border-gray-300 px-3 py-2 text-left">{t("troubleshooting.colResolution")}</th>
</tr>
</thead>
<tbody>
{exampleRows.map((row, idx) => (
{v.troubleshooting.rows.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>
<td className="border border-gray-300 px-3 py-2 font-medium">{row.problem}</td>
<td className="border border-gray-300 px-3 py-2">{row.resolution}</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("options.heading")}</h2>
<p className="text-gray-800">{t("options.lead")}</p>
<ul className="list-disc pl-6 mt-2 space-y-1 text-gray-800">
{optionsItems.map((_, idx) => (
<li key={idx}>{t.rich(`options.items.${idx}`, { strong, em, code })}</li>
))}
</ul>
<p className="text-gray-800 mt-4">{t.rich("options.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>
)
}
@@ -225,38 +225,34 @@ 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>
<h3 className="text-lg font-semibold mt-8 mb-2 text-gray-900">{t("drillIn.appTitle")}</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.
{t.rich("drillIn.appIntro", { strong, em, code })}
</p>
<p className="mb-6 text-gray-800 leading-relaxed">
See the{" "}
{t("drillIn.appLinkLead")}{" "}
<Link
href="/docs/monitor/dashboard/vms-lxcs/app"
className="text-blue-600 hover:underline"
>
dedicated App page
{t("drillIn.appLinkLabel")}
</Link>{" "}
for the catalog, manual registration, version-tracking methods and regex patterns.
{t("drillIn.appLinkTail")}
</p>
<h3 className="text-lg font-semibold mt-8 mb-2 text-gray-900">Updates</h3>
<h3 className="text-lg font-semibold mt-8 mb-2 text-gray-900">{t("drillIn.updatesTitle")}</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>.
{t.rich("drillIn.updatesIntro", { strong, em, code })}
</p>
<p className="mb-6 text-gray-800 leading-relaxed">
See the{" "}
{t("drillIn.updatesLinkLead")}{" "}
<Link
href="/docs/monitor/dashboard/vms-lxcs/updates"
className="text-blue-600 hover:underline"
>
dedicated Updates page
{t("drillIn.updatesLinkLabel")}
</Link>{" "}
for the decision matrix, custom-command guidance, backup / restart preferences and scheduled updates.
{t("drillIn.updatesLinkTail")}
</p>
<h3 className="text-lg font-semibold mt-8 mb-2 text-gray-900">{t("drillIn.mountsTitle")}</h3>
@@ -1,11 +1,11 @@
import type { Metadata } from "next"
import type React from "react"
import { getTranslations, getMessages, setRequestLocale } from "next-intl/server"
import { Link } from "@/i18n/navigation"
import { getMessages, getTranslations, setRequestLocale } from "next-intl/server"
import { ExternalLink } from "lucide-react"
import { DocHeader } from "@/components/ui/doc-header"
import { Link } from "@/i18n/navigation"
import { Callout } from "@/components/ui/callout"
import CopyableCode from "@/components/CopyableCode"
import { DocHeader } from "@/components/ui/doc-header"
export async function generateMetadata({
params,
@@ -17,20 +17,15 @@ export async function generateMetadata({
return { title: t("title"), description: t("description") }
}
type DecisionRow = { situation: string; action: string }
type DifferenceRow = { field: string; location: string; role: string }
type MechanismRow = { source: string; action: string; notes: string }
type StatusRow = { state: string; appearance: string; meaning: string }
type ProblemRow = { problem: string; resolution: 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>
<img src={src} alt={alt} className="w-full rounded-lg border border-gray-200 shadow-sm" />
<figcaption className="mt-2 text-center text-sm italic text-gray-500">{caption}</figcaption>
</figure>
)
}
@@ -43,58 +38,31 @@ export default async function UpdatesTabPage({
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[] }
overview: { items: string[] }
mechanisms: { rows: MechanismRow[] }
docker: { items: string[] }
actions: { items: string[]; statusRows: StatusRow[] }
custom: { items: string[] }
bulk: { items: string[] }
options: { items: string[] }
scheduled: { items: string[] }
completion: { items: string[] }
troubleshooting: { rows: ProblemRow[] }
} } } }
}
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 code = (chunks: React.ReactNode) => <code className="rounded bg-gray-100 px-1 text-sm">{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) => (
const linkHelper = (chunks: React.ReactNode) => (
<a
href="https://community-scripts.org/docs/tools/pve/update-apps"
target="_blank"
@@ -106,244 +74,134 @@ export default async function UpdatesTabPage({
</a>
)
return (
<div className="max-w-4xl mx-auto px-4 py-8">
<DocHeader
title={t("header.title")}
description={t("header.description")}
estimatedMinutes={12}
/>
const richList = (base: string, items: string[]) => (
<ul className="mt-2 list-disc space-y-2 pl-6 text-gray-800">
{items.map((_, idx) => (
<li key={idx}>{t.rich(`${base}.${idx}`, { strong, em, code, link: linkApp, helper: linkHelper })}</li>
))}
</ul>
)
<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>
return (
<div className="mx-auto max-w-4xl px-4 py-8">
<DocHeader title={t("header.title")} description={t("header.description")} estimatedMinutes={10} />
<p className="mt-6 text-gray-800">{t.rich("intro.p1", { strong, em, code, link: linkApp })}</p>
<p className="mt-4 text-gray-800">{t.rich("intro.p2", { strong, em, code })}</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>
<h2 className="mt-10 mb-4 text-2xl font-semibold text-gray-900">{t("overview.heading")}</h2>
<p className="text-gray-800">{t("overview.lead")}</p>
{richList("overview.items", v.overview.items)}
<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")}
alt={t("figures.osPending.alt")}
caption={t("figures.osPending.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">
<h2 className="mt-10 mb-4 text-2xl font-semibold text-gray-900">{t("mechanisms.heading")}</h2>
<p className="text-gray-800">{t.rich("mechanisms.lead", { strong, em, code, helper: linkHelper })}</p>
<div className="my-4 overflow-x-auto">
<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>
<th className="border border-gray-300 px-3 py-2 text-left">{t("mechanisms.colSource")}</th>
<th className="border border-gray-300 px-3 py-2 text-left">{t("mechanisms.colAction")}</th>
<th className="border border-gray-300 px-3 py-2 text-left">{t("mechanisms.colNotes")}</th>
</tr>
</thead>
<tbody>
{diffRows.map((row, idx) => (
{v.mechanisms.rows.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>
<td className="border border-gray-300 px-3 py-2 font-medium">{row.source}</td>
<td className="border border-gray-300 px-3 py-2">{row.action}</td>
<td className="border border-gray-300 px-3 py-2">{row.notes}</td>
</tr>
))}
</tbody>
</table>
</div>
<p className="text-gray-800 mt-4">{t.rich("difference.trailing", { strong, em, code })}</p>
<Callout variant="warning">{t.rich("mechanisms.callout", { strong, em, code })}</Callout>
<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>
<h2 className="mt-10 mb-4 text-2xl font-semibold text-gray-900">{t("docker.heading")}</h2>
<p className="text-gray-800">{t.rich("docker.lead", { strong, em, code })}</p>
{richList("docker.items", v.docker.items)}
<Callout variant="info">{t.rich("docker.callout", { strong, em, code })}</Callout>
<Figure
src="/monitor/vms-modal-updates-05.png"
alt={t("figures.f05.alt")}
caption={t("figures.f05.caption")}
/>
<h2 className="mt-10 mb-4 text-2xl font-semibold text-gray-900">{t("actions.heading")}</h2>
<p className="text-gray-800">{t("actions.lead")}</p>
{richList("actions.items", v.actions.items)}
<div className="my-4 overflow-x-auto">
<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("actions.statusColState")}</th>
<th className="border border-gray-300 px-3 py-2 text-left">{t("actions.statusColAppearance")}</th>
<th className="border border-gray-300 px-3 py-2 text-left">{t("actions.statusColMeaning")}</th>
</tr>
</thead>
<tbody>
{v.actions.statusRows.map((row, idx) => (
<tr key={idx}>
<td className="border border-gray-300 px-3 py-2 font-medium">{row.state}</td>
<td className="border border-gray-300 px-3 py-2">{row.appearance}</td>
<td className="border border-gray-300 px-3 py-2">{row.meaning}</td>
</tr>
))}
</tbody>
</table>
</div>
<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="mt-10 mb-4 text-2xl font-semibold text-gray-900">{t("custom.heading")}</h2>
<p className="text-gray-800">{t.rich("custom.lead", { strong, em, code })}</p>
{richList("custom.items", v.custom.items)}
<p className="mt-4 text-gray-800">{t("custom.exampleLead")}</p>
<CopyableCode code={t("custom.example")} language="sh" />
<Callout variant="warning">{t.rich("custom.callout", { strong, em, code })}</Callout>
<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>
<h2 className="mt-10 mb-4 text-2xl font-semibold text-gray-900">{t("bulk.heading")}</h2>
<p className="text-gray-800">{t.rich("bulk.lead", { strong, em, code })}</p>
{richList("bulk.items", v.bulk.items)}
<Callout variant="info">{t.rich("bulk.callout", { strong, em, code })}</Callout>
<h2 className="mt-10 mb-4 text-2xl font-semibold text-gray-900">{t("options.heading")}</h2>
<p className="text-gray-800">{t("options.lead")}</p>
{richList("options.items", v.options.items)}
<Figure
src="/monitor/vms-modal-updates-06.png"
alt={t("figures.f06.alt")}
caption={t("figures.f06.caption")}
alt={t("figures.options.alt")}
caption={t("figures.options.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>
<h2 className="mt-10 mb-4 text-2xl font-semibold text-gray-900">{t("scheduled.heading")}</h2>
<p className="text-gray-800">{t.rich("scheduled.lead", { strong, em, code })}</p>
{richList("scheduled.items", v.scheduled.items)}
<Callout variant="tip">{t.rich("scheduled.callout", { strong, em, code })}</Callout>
<Figure
src="/monitor/vms-modal-updates-07.png"
alt={t("figures.f07.alt")}
caption={t("figures.f07.caption")}
/>
<h2 className="mt-10 mb-4 text-2xl font-semibold text-gray-900">{t("completion.heading")}</h2>
<p className="text-gray-800">{t("completion.lead")}</p>
{richList("completion.items", v.completion.items)}
<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>
<h2 className="mt-10 mb-4 text-2xl font-semibold text-gray-900">{t("troubleshooting.heading")}</h2>
<div className="my-4 overflow-x-auto">
<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("troubleshooting.colProblem")}</th>
<th className="border border-gray-300 px-3 py-2 text-left">{t("troubleshooting.colResolution")}</th>
</tr>
</thead>
<tbody>
{v.troubleshooting.rows.map((row, idx) => (
<tr key={idx}>
<td className="border border-gray-300 px-3 py-2 font-medium">{row.problem}</td>
<td className="border border-gray-300 px-3 py-2">{row.resolution}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)
}