From ae37003df882c5c7cd33c5dfd8acbb077e6ce160 Mon Sep 17 00:00:00 2001 From: VAIO73 <50487331+Vaso73@users.noreply.github.com> Date: Wed, 21 Oct 2026 03:09:13 +0000 Subject: [PATCH] i18n: localize optimization source viewer and protect pending reads --- .github/scripts/run_offline_qualified.py | 1 + AppImage/components/settings.tsx | 56 +++++++++---- AppImage/lib/optimization-source-errors.ts | 18 +++++ AppImage/messages/en/common.json | 7 ++ AppImage/messages/sk/common.json | 7 ++ tests/test_optimization_source.cjs | 93 ++++++++++++++++++++++ 6 files changed, 167 insertions(+), 15 deletions(-) create mode 100644 AppImage/lib/optimization-source-errors.ts create mode 100644 tests/test_optimization_source.cjs diff --git a/.github/scripts/run_offline_qualified.py b/.github/scripts/run_offline_qualified.py index 8a9548cd..773df892 100644 --- a/.github/scripts/run_offline_qualified.py +++ b/.github/scripts/run_offline_qualified.py @@ -28,6 +28,7 @@ PYTHON_FILES = ( "AppImage/scripts/tests/test_health_persistence_recurrence.py", ) NODE_FILES = ( + "tests/test_optimization_source.cjs", "tests/lxc_updates/test_local_app_icons_ui.cjs", "tests/lxc_updates/test_docker_app_discovery_ui.cjs", "tests/lxc_updates/test_native_app_discovery_ui.cjs", diff --git a/AppImage/components/settings.tsx b/AppImage/components/settings.tsx index 913e4c86..e5147995 100644 --- a/AppImage/components/settings.tsx +++ b/AppImage/components/settings.tsx @@ -1,6 +1,6 @@ "use client" -import { useState, useEffect } from "react" +import { useState, useEffect, useRef } from "react" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "./ui/card" import { Package, Ruler, HeartPulse, Cpu, MemoryStick, HardDrive, CircleDot, Network, Server, Settings2, FileText, RefreshCw, Shield, AlertTriangle, Info, Loader2, Check, Database, CloudOff, Code, X, Copy, Sparkles, ArrowUpCircle, BellOff, Globe2, Github } from "lucide-react" import { Badge } from "./ui/badge" @@ -19,6 +19,7 @@ import { SUPPORTED_LANGUAGES, useI18n } from "../lib/i18n/provider" import { useIsPbs } from "../lib/use-platform" import type { LanguageCode } from "../lib/i18n/languages" import { NavTabOrderCard } from "./nav-tab-order-card" +import { optimizationSourceError, type OptimizationSourceError } from "../lib/optimization-source-errors" // GitHub Dark color palette for bash syntax highlighting const BASH_KEYWORDS = new Set([ @@ -359,10 +360,25 @@ export function Settings() { functionName: string source: string script: string - error: string + error: OptimizationSourceError | null deprecated: boolean - }>({ open: false, loading: false, toolName: '', version: '', availableVersion: '', functionName: '', source: '', script: '', error: '', deprecated: false }) + }>({ open: false, loading: false, toolName: '', version: '', availableVersion: '', functionName: '', source: '', script: '', error: null, deprecated: false }) const [codeCopied, setCodeCopied] = useState(false) + const codeRequest = useRef(0) + const closeCodeModal = () => { + codeRequest.current += 1 + setCodeModal(prev => ({ ...prev, open: false })) + setCodeCopied(false) + } + useEffect(() => () => { codeRequest.current += 1 }, []) + useEffect(() => { + if (!codeModal.open) return + const onKeyDown = (event: KeyboardEvent) => { + if (event.key === "Escape") closeCodeModal() + } + window.addEventListener("keydown", onKeyDown) + return () => window.removeEventListener("keydown", onKeyDown) + }, [codeModal.open]) // Health Monitor suppression settings const [suppressionCategories, setSuppressionCategories] = useState([]) @@ -663,6 +679,8 @@ export function Settings() { } const viewToolSource = async (tool: ProxMenuxTool) => { + const request = ++codeRequest.current + setCodeCopied(false) setCodeModal({ open: true, loading: true, @@ -672,22 +690,26 @@ export function Settings() { functionName: '', source: '', script: '', - error: '', + error: null, deprecated: !!tool.deprecated, }) try { const data = await fetchApi(`/api/proxmenux/tool-source/${tool.key}`) - if (data.success) { - setCodeModal(prev => ({ ...prev, loading: false, functionName: data.function, source: data.source, script: data.script, deprecated: !!data.deprecated })) + if (request !== codeRequest.current) return + if (data?.success && typeof data.source === "string" && data.source.trim()) { + setCodeModal(prev => ({ ...prev, loading: false, functionName: typeof data.function === "string" ? data.function : '', source: data.source, script: typeof data.script === "string" ? data.script : '', deprecated: !!data.deprecated })) } else { - setCodeModal(prev => ({ ...prev, loading: false, error: data.error || 'Source code not available' })) + setCodeModal(prev => ({ ...prev, loading: false, error: { kind: "unavailable", detail: typeof data?.error === "string" ? data.error : undefined } })) } - } catch { - setCodeModal(prev => ({ ...prev, loading: false, error: 'Failed to load source code' })) + } catch (error: any) { + if (request !== codeRequest.current) return + const detail = typeof error?.body?.error === "string" ? error.body.error : undefined + setCodeModal(prev => ({ ...prev, loading: false, error: { kind: "loadFailed", detail } })) } } const copySourceCode = async () => { + const request = codeRequest.current const text = codeModal.source let ok = false @@ -721,9 +743,9 @@ export function Settings() { } } - if (ok) { + if (ok && request === codeRequest.current) { setCodeCopied(true) - setTimeout(() => setCodeCopied(false), 2000) + setTimeout(() => { if (request === codeRequest.current) setCodeCopied(false) }, 2000) } } @@ -2153,10 +2175,13 @@ export function Settings() { {/* Code Viewer Modal */} {codeModal.open && ( -
setCodeModal(prev => ({ ...prev, open: false }))}> +
e.stopPropagation()} > {/* Header */} @@ -2201,7 +2226,8 @@ export function Settings() { )}