From d50aff366cb500dbd425637a19a25edb8bbcfaba Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 26 Nov 2025 08:47:07 +0100 Subject: [PATCH] Fix script viewer to support vm/ and tools/ scripts - Update ScriptDetailModal to extract scriptName from any path (ct/, vm/, tools/) - Refactor TextViewer to use actual script paths from install_methods - Remove hardcoded path assumptions and use dynamic script paths - Only show Install Script tab for ct/ scripts that have install scripts - Rename CT Script tab to Script for better clarity --- src/app/_components/ScriptDetailModal.tsx | 2 +- src/app/_components/TextViewer.tsx | 215 +++++++++------------- 2 files changed, 92 insertions(+), 125 deletions(-) diff --git a/src/app/_components/ScriptDetailModal.tsx b/src/app/_components/ScriptDetailModal.tsx index 8a6f3c2..d6d5e0b 100644 --- a/src/app/_components/ScriptDetailModal.tsx +++ b/src/app/_components/ScriptDetailModal.tsx @@ -882,7 +882,7 @@ export function ScriptDetailModal({ method.script?.startsWith("ct/")) + ?.find((method) => method.script && (method.script.startsWith("ct/") || method.script.startsWith("vm/") || method.script.startsWith("tools/"))) ?.script?.split("/") .pop() ?? `${script.slug}.sh` } diff --git a/src/app/_components/TextViewer.tsx b/src/app/_components/TextViewer.tsx index 20785a6..3fc8425 100644 --- a/src/app/_components/TextViewer.tsx +++ b/src/app/_components/TextViewer.tsx @@ -14,9 +14,9 @@ interface TextViewerProps { } interface ScriptContent { - ctScript?: string; + mainScript?: string; installScript?: string; - alpineCtScript?: string; + alpineMainScript?: string; alpineInstallScript?: string; } @@ -24,18 +24,27 @@ export function TextViewer({ scriptName, isOpen, onClose, script }: TextViewerPr const [scriptContent, setScriptContent] = useState({}); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); - const [activeTab, setActiveTab] = useState<'ct' | 'install'>('ct'); + const [activeTab, setActiveTab] = useState<'main' | 'install'>('main'); const [selectedVersion, setSelectedVersion] = useState<'default' | 'alpine'>('default'); // Extract slug from script name (remove .sh extension) const slug = scriptName.replace(/\.sh$/, '').replace(/^alpine-/, ''); - // Check if alpine variant exists - const hasAlpineVariant = script?.install_methods?.some( - method => method.type === 'alpine' && method.script?.startsWith('ct/') - ); + // Get default and alpine install methods + const defaultMethod = script?.install_methods?.find(method => method.type === 'default'); + const alpineMethod = script?.install_methods?.find(method => method.type === 'alpine'); - // Get script names for default and alpine versions + // Check if alpine variant exists + const hasAlpineVariant = !!alpineMethod; + + // Get script paths from install_methods + const defaultScriptPath = defaultMethod?.script; + const alpineScriptPath = alpineMethod?.script; + + // Determine if install scripts exist (only for ct/ scripts typically) + const hasInstallScript = defaultScriptPath?.startsWith('ct/') || alpineScriptPath?.startsWith('ct/'); + + // Get script names for display const defaultScriptName = scriptName.replace(/^alpine-/, ''); const alpineScriptName = scriptName.startsWith('alpine-') ? scriptName : `alpine-${scriptName}`; @@ -44,116 +53,72 @@ export function TextViewer({ scriptName, isOpen, onClose, script }: TextViewerPr setError(null); try { - // Build fetch requests for default version + // Build fetch requests based on actual script paths from install_methods const requests: Promise[] = []; - - // Default CT script - requests.push( - fetch(`/api/trpc/scripts.getScriptContent?input=${encodeURIComponent(JSON.stringify({ json: { path: `ct/${defaultScriptName}` } }))}`) - ); - - // Tools, VM, VW scripts - requests.push( - fetch(`/api/trpc/scripts.getScriptContent?input=${encodeURIComponent(JSON.stringify({ json: { path: `tools/pve/${defaultScriptName}` } }))}`) - ); - requests.push( - fetch(`/api/trpc/scripts.getScriptContent?input=${encodeURIComponent(JSON.stringify({ json: { path: `vm/${defaultScriptName}` } }))}`) - ); - requests.push( - fetch(`/api/trpc/scripts.getScriptContent?input=${encodeURIComponent(JSON.stringify({ json: { path: `vw/${defaultScriptName}` } }))}`) - ); - - // Default install script - requests.push( - fetch(`/api/trpc/scripts.getScriptContent?input=${encodeURIComponent(JSON.stringify({ json: { path: `install/${slug}-install.sh` } }))}`) - ); - - // Alpine versions if variant exists - if (hasAlpineVariant) { + const requestTypes: Array<'default-main' | 'default-install' | 'alpine-main' | 'alpine-install'> = []; + + // Default main script (ct/, vm/, tools/, etc.) + if (defaultScriptPath) { requests.push( - fetch(`/api/trpc/scripts.getScriptContent?input=${encodeURIComponent(JSON.stringify({ json: { path: `ct/${alpineScriptName}` } }))}`) + fetch(`/api/trpc/scripts.getScriptContent?input=${encodeURIComponent(JSON.stringify({ json: { path: defaultScriptPath } }))}`) ); + requestTypes.push('default-main'); + } + + // Default install script (only for ct/ scripts) + if (hasInstallScript && defaultScriptPath?.startsWith('ct/')) { + requests.push( + fetch(`/api/trpc/scripts.getScriptContent?input=${encodeURIComponent(JSON.stringify({ json: { path: `install/${slug}-install.sh` } }))}`) + ); + requestTypes.push('default-install'); + } + + // Alpine main script + if (hasAlpineVariant && alpineScriptPath) { + requests.push( + fetch(`/api/trpc/scripts.getScriptContent?input=${encodeURIComponent(JSON.stringify({ json: { path: alpineScriptPath } }))}`) + ); + requestTypes.push('alpine-main'); + } + + // Alpine install script (only for ct/ scripts) + if (hasAlpineVariant && hasInstallScript && alpineScriptPath?.startsWith('ct/')) { requests.push( fetch(`/api/trpc/scripts.getScriptContent?input=${encodeURIComponent(JSON.stringify({ json: { path: `install/alpine-${slug}-install.sh` } }))}`) ); + requestTypes.push('alpine-install'); } const responses = await Promise.allSettled(requests); - const content: ScriptContent = {}; - let responseIndex = 0; - // Default CT script - const ctResponse = responses[responseIndex]; - if (ctResponse?.status === 'fulfilled' && ctResponse.value.ok) { - const ctData = await ctResponse.value.json() as { result?: { data?: { json?: { success?: boolean; content?: string } } } }; - if (ctData.result?.data?.json?.success) { - content.ctScript = ctData.result.data.json.content; - } - } - - responseIndex++; - // Tools script - const toolsResponse = responses[responseIndex]; - if (toolsResponse?.status === 'fulfilled' && toolsResponse.value.ok) { - const toolsData = await toolsResponse.value.json() as { result?: { data?: { json?: { success?: boolean; content?: string } } } }; - if (toolsData.result?.data?.json?.success) { - content.ctScript = toolsData.result.data.json.content; // Use ctScript field for tools scripts too - } - } - - responseIndex++; - // VM script - const vmResponse = responses[responseIndex]; - if (vmResponse?.status === 'fulfilled' && vmResponse.value.ok) { - const vmData = await vmResponse.value.json() as { result?: { data?: { json?: { success?: boolean; content?: string } } } }; - if (vmData.result?.data?.json?.success) { - content.ctScript = vmData.result.data.json.content; // Use ctScript field for VM scripts too - } - } - - responseIndex++; - // VW script - const vwResponse = responses[responseIndex]; - if (vwResponse?.status === 'fulfilled' && vwResponse.value.ok) { - const vwData = await vwResponse.value.json() as { result?: { data?: { json?: { success?: boolean; content?: string } } } }; - if (vwData.result?.data?.json?.success) { - content.ctScript = vwData.result.data.json.content; // Use ctScript field for VW scripts too - } - } - - responseIndex++; - // Default install script - const installResponse = responses[responseIndex]; - if (installResponse?.status === 'fulfilled' && installResponse.value.ok) { - const installData = await installResponse.value.json() as { result?: { data?: { json?: { success?: boolean; content?: string } } } }; - if (installData.result?.data?.json?.success) { - content.installScript = installData.result.data.json.content; - } - } - responseIndex++; - // Alpine CT script - if (hasAlpineVariant) { - const alpineCtResponse = responses[responseIndex]; - if (alpineCtResponse?.status === 'fulfilled' && alpineCtResponse.value.ok) { - const alpineCtData = await alpineCtResponse.value.json() as { result?: { data?: { json?: { success?: boolean; content?: string } } } }; - if (alpineCtData.result?.data?.json?.success) { - content.alpineCtScript = alpineCtData.result.data.json.content; + // Process responses based on their types + await Promise.all(responses.map(async (response, index) => { + if (response.status === 'fulfilled' && response.value.ok) { + try { + const data = await response.value.json() as { result?: { data?: { json?: { success?: boolean; content?: string } } } }; + const type = requestTypes[index]; + if (data.result?.data?.json?.success && data.result.data.json.content) { + switch (type) { + case 'default-main': + content.mainScript = data.result.data.json.content; + break; + case 'default-install': + content.installScript = data.result.data.json.content; + break; + case 'alpine-main': + content.alpineMainScript = data.result.data.json.content; + break; + case 'alpine-install': + content.alpineInstallScript = data.result.data.json.content; + break; + } + } + } catch { + // Ignore errors } } - responseIndex++; - } - - // Alpine install script - if (hasAlpineVariant) { - const alpineInstallResponse = responses[responseIndex]; - if (alpineInstallResponse?.status === 'fulfilled' && alpineInstallResponse.value.ok) { - const alpineInstallData = await alpineInstallResponse.value.json() as { result?: { data?: { json?: { success?: boolean; content?: string } } } }; - if (alpineInstallData.result?.data?.json?.success) { - content.alpineInstallScript = alpineInstallData.result.data.json.content; - } - } - } + })); setScriptContent(content); } catch (err) { @@ -161,7 +126,7 @@ export function TextViewer({ scriptName, isOpen, onClose, script }: TextViewerPr } finally { setIsLoading(false); } - }, [defaultScriptName, alpineScriptName, slug, hasAlpineVariant]); + }, [defaultScriptPath, alpineScriptPath, slug, hasAlpineVariant, hasInstallScript]); useEffect(() => { if (isOpen && scriptName) { @@ -207,23 +172,25 @@ export function TextViewer({ scriptName, isOpen, onClose, script }: TextViewerPr )} - {((selectedVersion === 'default' && (scriptContent.ctScript || scriptContent.installScript)) || - (selectedVersion === 'alpine' && (scriptContent.alpineCtScript || scriptContent.alpineInstallScript))) && ( + {((selectedVersion === 'default' && (scriptContent.mainScript || scriptContent.installScript)) || + (selectedVersion === 'alpine' && (scriptContent.alpineMainScript || scriptContent.alpineInstallScript))) && (
- + {hasInstallScript && ( + + )}
)} @@ -249,8 +216,8 @@ export function TextViewer({ scriptName, isOpen, onClose, script }: TextViewerPr ) : (
- {activeTab === 'ct' && ( - selectedVersion === 'default' && scriptContent.ctScript ? ( + {activeTab === 'main' && ( + selectedVersion === 'default' && scriptContent.mainScript ? ( - {scriptContent.ctScript} + {scriptContent.mainScript} - ) : selectedVersion === 'alpine' && scriptContent.alpineCtScript ? ( + ) : selectedVersion === 'alpine' && scriptContent.alpineMainScript ? ( - {scriptContent.alpineCtScript} + {scriptContent.alpineMainScript} ) : (
- {selectedVersion === 'default' ? 'Default CT script not found' : 'Alpine CT script not found'} + {selectedVersion === 'default' ? 'Default script not found' : 'Alpine script not found'}
)