Merge remote-tracking branch 'origin/develop' into HEAD

# Conflicts:
#	.github/scripts/tests/test_command_descriptions.py
This commit is contained in:
MacRimi
2026-10-16 23:25:01 +02:00
70 changed files with 3407 additions and 302 deletions
+27 -3
View File
@@ -35,6 +35,23 @@ import DOMPurify from "dompurify"
import { marked } from "marked"
import { getCountFormKey, useI18n, useT } from "@/lib/i18n/provider"
// A schedule record predating the structured reason code still contains one
// of these exact diagnostic messages. Recognise only those known historical
// values so an already-recorded safe skip localizes after an upgrade, while
// unrelated freeform diagnostics remain verbatim for troubleshooting.
const LEGACY_STOPPED_SCHEDULE_REASONS = new Set([
"container is stopped; scheduled update was skipped",
"container was stopped before scheduled update could begin",
])
const scheduleReasonCodeFromPersisted = (reasonCode: unknown, reason: unknown): string | null => {
if (typeof reasonCode === "string" && reasonCode) return reasonCode
if (typeof reason === "string" && LEGACY_STOPPED_SCHEDULE_REASONS.has(reason.toLowerCase())) {
return "stopped_container"
}
return null
}
// Sent by /api/vms only for LXC rows, only when the user has enabled
// `lxc_updates_available` notifications. The Monitor populates this
// from managed_installs registry → frontend uses it to render the
@@ -2077,6 +2094,7 @@ const handleDownloadLogs = async (vmid: number, vmName: string) => {
const [scheduleLastRunAt, setScheduleLastRunAt] = useState<string | null>(null)
const [scheduleLastRunStatus, setScheduleLastRunStatus] = useState<string | null>(null)
const [scheduleLastRunReason, setScheduleLastRunReason] = useState<string | null>(null)
const [scheduleLastRunReasonCode, setScheduleLastRunReasonCode] = useState<string | null>(null)
const [scheduleLastRunLog, setScheduleLastRunLog] = useState<string | null>(null)
const [scheduleLastRunRebootRequired, setScheduleLastRunRebootRequired] = useState(false)
const [scheduleLastRunRebootPackages, setScheduleLastRunRebootPackages] = useState<string[]>([])
@@ -2153,6 +2171,7 @@ const handleDownloadLogs = async (vmid: number, vmName: string) => {
setScheduleLastRunAt(s.last_run_at || null)
setScheduleLastRunStatus(s.last_run_status || null)
setScheduleLastRunReason(s.last_run_reason || null)
setScheduleLastRunReasonCode(scheduleReasonCodeFromPersisted(s.last_run_reason_code, s.last_run_reason))
setScheduleLastRunLog(s.last_run_log || null)
setScheduleLastRunRebootRequired(s.last_run_reboot_required === true)
setScheduleLastRunRebootPackages(Array.isArray(s.last_run_reboot_packages)
@@ -2430,6 +2449,11 @@ const handleDownloadLogs = async (vmid: number, vmName: string) => {
const renderScheduleRunDetails = (indented = false) => {
if (!scheduleLastRunAt || !selectedVM) return null
const spacing = indented ? "pl-4" : ""
const scheduleLastRunReasonText = scheduleLastRunReasonCode === "stopped_container"
? selectedVM.status === "stopped"
? t("vmLxc.scheduled.reasonStoppedContainer")
: t("vmLxc.scheduled.reasonStoppedContainerHistorical")
: scheduleLastRunReason
return (
<div className={`space-y-1.5 ${spacing}`}>
<div className="text-xs text-muted-foreground">
@@ -2443,14 +2467,14 @@ const handleDownloadLogs = async (vmid: number, vmName: string) => {
: scheduleLastRunStatus === "deferred"
? t("vmLxc.scheduled.runDeferred")
: scheduleLastRunStatus === "skipped"
? t("vmLxc.scheduled.runSkipped")
? t("vmLxc.scheduled.runSkippedStatus")
: t("vmLxc.scheduled.runFailed")}
</span></>
)}
</div>
{scheduleLastRunReason && (
{scheduleLastRunReasonText && (
<div className="text-xs text-muted-foreground break-words">
{scheduleLastRunReason}
{scheduleLastRunReasonText}
</div>
)}
{scheduleLastRunRebootRequired && (