@@ -4,85 +4,164 @@ import { useState, useEffect, useCallback } from 'react';
44import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter' ;
55import { tomorrow } from 'react-syntax-highlighter/dist/esm/styles/prism' ;
66import { Button } from './ui/button' ;
7+ import type { Script } from '../../types/script' ;
78
89interface TextViewerProps {
910 scriptName : string ;
1011 isOpen : boolean ;
1112 onClose : ( ) => void ;
13+ script ?: Script | null ;
1214}
1315
1416interface ScriptContent {
1517 ctScript ?: string ;
1618 installScript ?: string ;
19+ alpineCtScript ?: string ;
20+ alpineInstallScript ?: string ;
1721}
1822
19- export function TextViewer ( { scriptName, isOpen, onClose } : TextViewerProps ) {
23+ export function TextViewer ( { scriptName, isOpen, onClose, script } : TextViewerProps ) {
2024 const [ scriptContent , setScriptContent ] = useState < ScriptContent > ( { } ) ;
2125 const [ isLoading , setIsLoading ] = useState ( false ) ;
2226 const [ error , setError ] = useState < string | null > ( null ) ;
2327 const [ activeTab , setActiveTab ] = useState < 'ct' | 'install' > ( 'ct' ) ;
28+ const [ selectedVersion , setSelectedVersion ] = useState < 'default' | 'alpine' > ( 'default' ) ;
2429
2530 // Extract slug from script name (remove .sh extension)
26- const slug = scriptName . replace ( / \. s h $ / , '' ) ;
31+ const slug = scriptName . replace ( / \. s h $ / , '' ) . replace ( / ^ a l p i n e - / , '' ) ;
32+
33+ // Check if alpine variant exists
34+ const hasAlpineVariant = script ?. install_methods ?. some (
35+ method => method . type === 'alpine' && method . script ?. startsWith ( 'ct/' )
36+ ) ;
37+
38+ // Get script names for default and alpine versions
39+ const defaultScriptName = scriptName . replace ( / ^ a l p i n e - / , '' ) ;
40+ const alpineScriptName = scriptName . startsWith ( 'alpine-' ) ? scriptName : `alpine-${ scriptName } ` ;
2741
2842 const loadScriptContent = useCallback ( async ( ) => {
2943 setIsLoading ( true ) ;
3044 setError ( null ) ;
3145
3246 try {
33- // Try to load from different possible locations
34- const [ ctResponse , toolsResponse , vmResponse , vwResponse , installResponse ] = await Promise . allSettled ( [
35- fetch ( `/api/trpc/scripts.getScriptContent?input=${ encodeURIComponent ( JSON . stringify ( { json : { path : `ct/${ scriptName } ` } } ) ) } ` ) ,
36- fetch ( `/api/trpc/scripts.getScriptContent?input=${ encodeURIComponent ( JSON . stringify ( { json : { path : `tools/pve/${ scriptName } ` } } ) ) } ` ) ,
37- fetch ( `/api/trpc/scripts.getScriptContent?input=${ encodeURIComponent ( JSON . stringify ( { json : { path : `vm/${ scriptName } ` } } ) ) } ` ) ,
38- fetch ( `/api/trpc/scripts.getScriptContent?input=${ encodeURIComponent ( JSON . stringify ( { json : { path : `vw/${ scriptName } ` } } ) ) } ` ) ,
47+ // Build fetch requests for default version
48+ const requests : Promise < Response > [ ] = [ ] ;
49+
50+ // Default CT script
51+ requests . push (
52+ fetch ( `/api/trpc/scripts.getScriptContent?input=${ encodeURIComponent ( JSON . stringify ( { json : { path : `ct/${ defaultScriptName } ` } } ) ) } ` )
53+ ) ;
54+
55+ // Tools, VM, VW scripts
56+ requests . push (
57+ fetch ( `/api/trpc/scripts.getScriptContent?input=${ encodeURIComponent ( JSON . stringify ( { json : { path : `tools/pve/${ defaultScriptName } ` } } ) ) } ` )
58+ ) ;
59+ requests . push (
60+ fetch ( `/api/trpc/scripts.getScriptContent?input=${ encodeURIComponent ( JSON . stringify ( { json : { path : `vm/${ defaultScriptName } ` } } ) ) } ` )
61+ ) ;
62+ requests . push (
63+ fetch ( `/api/trpc/scripts.getScriptContent?input=${ encodeURIComponent ( JSON . stringify ( { json : { path : `vw/${ defaultScriptName } ` } } ) ) } ` )
64+ ) ;
65+
66+ // Default install script
67+ requests . push (
3968 fetch ( `/api/trpc/scripts.getScriptContent?input=${ encodeURIComponent ( JSON . stringify ( { json : { path : `install/${ slug } -install.sh` } } ) ) } ` )
40- ] ) ;
69+ ) ;
70+
71+ // Alpine versions if variant exists
72+ if ( hasAlpineVariant ) {
73+ requests . push (
74+ fetch ( `/api/trpc/scripts.getScriptContent?input=${ encodeURIComponent ( JSON . stringify ( { json : { path : `ct/${ alpineScriptName } ` } } ) ) } ` )
75+ ) ;
76+ requests . push (
77+ fetch ( `/api/trpc/scripts.getScriptContent?input=${ encodeURIComponent ( JSON . stringify ( { json : { path : `install/alpine-${ slug } -install.sh` } } ) ) } ` )
78+ ) ;
79+ }
80+
81+ const responses = await Promise . allSettled ( requests ) ;
4182
4283 const content : ScriptContent = { } ;
84+ let responseIndex = 0 ;
4385
44- if ( ctResponse . status === 'fulfilled' && ctResponse . value . ok ) {
86+ // Default CT script
87+ const ctResponse = responses [ responseIndex ] ;
88+ if ( ctResponse ?. status === 'fulfilled' && ctResponse . value . ok ) {
4589 const ctData = await ctResponse . value . json ( ) as { result ?: { data ?: { json ?: { success ?: boolean ; content ?: string } } } } ;
4690 if ( ctData . result ?. data ?. json ?. success ) {
4791 content . ctScript = ctData . result . data . json . content ;
4892 }
4993 }
5094
51- if ( toolsResponse . status === 'fulfilled' && toolsResponse . value . ok ) {
95+ responseIndex ++ ;
96+ // Tools script
97+ const toolsResponse = responses [ responseIndex ] ;
98+ if ( toolsResponse ?. status === 'fulfilled' && toolsResponse . value . ok ) {
5299 const toolsData = await toolsResponse . value . json ( ) as { result ?: { data ?: { json ?: { success ?: boolean ; content ?: string } } } } ;
53100 if ( toolsData . result ?. data ?. json ?. success ) {
54101 content . ctScript = toolsData . result . data . json . content ; // Use ctScript field for tools scripts too
55102 }
56103 }
57104
58- if ( vmResponse . status === 'fulfilled' && vmResponse . value . ok ) {
105+ responseIndex ++ ;
106+ // VM script
107+ const vmResponse = responses [ responseIndex ] ;
108+ if ( vmResponse ?. status === 'fulfilled' && vmResponse . value . ok ) {
59109 const vmData = await vmResponse . value . json ( ) as { result ?: { data ?: { json ?: { success ?: boolean ; content ?: string } } } } ;
60110 if ( vmData . result ?. data ?. json ?. success ) {
61111 content . ctScript = vmData . result . data . json . content ; // Use ctScript field for VM scripts too
62112 }
63113 }
64114
65- if ( vwResponse . status === 'fulfilled' && vwResponse . value . ok ) {
115+ responseIndex ++ ;
116+ // VW script
117+ const vwResponse = responses [ responseIndex ] ;
118+ if ( vwResponse ?. status === 'fulfilled' && vwResponse . value . ok ) {
66119 const vwData = await vwResponse . value . json ( ) as { result ?: { data ?: { json ?: { success ?: boolean ; content ?: string } } } } ;
67120 if ( vwData . result ?. data ?. json ?. success ) {
68121 content . ctScript = vwData . result . data . json . content ; // Use ctScript field for VW scripts too
69122 }
70123 }
71124
72- if ( installResponse . status === 'fulfilled' && installResponse . value . ok ) {
125+ responseIndex ++ ;
126+ // Default install script
127+ const installResponse = responses [ responseIndex ] ;
128+ if ( installResponse ?. status === 'fulfilled' && installResponse . value . ok ) {
73129 const installData = await installResponse . value . json ( ) as { result ?: { data ?: { json ?: { success ?: boolean ; content ?: string } } } } ;
74130 if ( installData . result ?. data ?. json ?. success ) {
75131 content . installScript = installData . result . data . json . content ;
76132 }
77133 }
134+ responseIndex ++ ;
135+ // Alpine CT script
136+ if ( hasAlpineVariant ) {
137+ const alpineCtResponse = responses [ responseIndex ] ;
138+ if ( alpineCtResponse ?. status === 'fulfilled' && alpineCtResponse . value . ok ) {
139+ const alpineCtData = await alpineCtResponse . value . json ( ) as { result ?: { data ?: { json ?: { success ?: boolean ; content ?: string } } } } ;
140+ if ( alpineCtData . result ?. data ?. json ?. success ) {
141+ content . alpineCtScript = alpineCtData . result . data . json . content ;
142+ }
143+ }
144+ responseIndex ++ ;
145+ }
146+
147+ // Alpine install script
148+ if ( hasAlpineVariant ) {
149+ const alpineInstallResponse = responses [ responseIndex ] ;
150+ if ( alpineInstallResponse ?. status === 'fulfilled' && alpineInstallResponse . value . ok ) {
151+ const alpineInstallData = await alpineInstallResponse . value . json ( ) as { result ?: { data ?: { json ?: { success ?: boolean ; content ?: string } } } } ;
152+ if ( alpineInstallData . result ?. data ?. json ?. success ) {
153+ content . alpineInstallScript = alpineInstallData . result . data . json . content ;
154+ }
155+ }
156+ }
78157
79158 setScriptContent ( content ) ;
80159 } catch ( err ) {
81160 setError ( err instanceof Error ? err . message : 'Failed to load script content' ) ;
82161 } finally {
83162 setIsLoading ( false ) ;
84163 }
85- } , [ scriptName , slug ] ) ;
164+ } , [ defaultScriptName , alpineScriptName , slug , hasAlpineVariant ] ) ;
86165
87166 useEffect ( ( ) => {
88167 if ( isOpen && scriptName ) {
@@ -106,11 +185,30 @@ export function TextViewer({ scriptName, isOpen, onClose }: TextViewerProps) {
106185 < div className = "bg-card rounded-lg shadow-xl max-w-6xl w-full max-h-[90vh] flex flex-col border border-border mx-4 sm:mx-0" >
107186 { /* Header */ }
108187 < div className = "flex items-center justify-between p-6 border-b border-border" >
109- < div className = "flex items-center space-x-4" >
188+ < div className = "flex items-center space-x-4 flex-1 " >
110189 < h2 className = "text-2xl font-bold text-foreground" >
111- Script Viewer: { scriptName }
190+ Script Viewer: { defaultScriptName }
112191 </ h2 >
113- { scriptContent . ctScript && scriptContent . installScript && (
192+ { hasAlpineVariant && (
193+ < div className = "flex space-x-2" >
194+ < Button
195+ variant = { selectedVersion === 'default' ? 'default' : 'outline' }
196+ onClick = { ( ) => setSelectedVersion ( 'default' ) }
197+ className = "px-3 py-1 text-sm"
198+ >
199+ Default
200+ </ Button >
201+ < Button
202+ variant = { selectedVersion === 'alpine' ? 'default' : 'outline' }
203+ onClick = { ( ) => setSelectedVersion ( 'alpine' ) }
204+ className = "px-3 py-1 text-sm"
205+ >
206+ Alpine
207+ </ Button >
208+ </ div >
209+ ) }
210+ { ( ( selectedVersion === 'default' && ( scriptContent . ctScript || scriptContent . installScript ) ) ||
211+ ( selectedVersion === 'alpine' && ( scriptContent . alpineCtScript || scriptContent . alpineInstallScript ) ) ) && (
114212 < div className = "flex space-x-2" >
115213 < Button
116214 variant = { activeTab === 'ct' ? 'outline' : 'ghost' }
@@ -151,44 +249,87 @@ export function TextViewer({ scriptName, isOpen, onClose }: TextViewerProps) {
151249 </ div >
152250 ) : (
153251 < div className = "flex-1 overflow-auto" >
154- { activeTab === 'ct' && scriptContent . ctScript ? (
155- < SyntaxHighlighter
156- language = "bash"
157- style = { tomorrow }
158- customStyle = { {
159- margin : 0 ,
160- padding : '1rem' ,
161- fontSize : '14px' ,
162- lineHeight : '1.5' ,
163- minHeight : '100%'
164- } }
165- showLineNumbers = { true }
166- wrapLines = { true }
167- >
168- { scriptContent . ctScript }
169- </ SyntaxHighlighter >
170- ) : activeTab === 'install' && scriptContent . installScript ? (
171- < SyntaxHighlighter
172- language = "bash"
173- style = { tomorrow }
174- customStyle = { {
175- margin : 0 ,
176- padding : '1rem' ,
177- fontSize : '14px' ,
178- lineHeight : '1.5' ,
179- minHeight : '100%'
180- } }
181- showLineNumbers = { true }
182- wrapLines = { true }
183- >
184- { scriptContent . installScript }
185- </ SyntaxHighlighter >
186- ) : (
187- < div className = "flex items-center justify-center h-full" >
188- < div className = "text-lg text-muted-foreground" >
189- { activeTab === 'ct' ? 'CT script not found' : 'Install script not found' }
252+ { activeTab === 'ct' && (
253+ selectedVersion === 'default' && scriptContent . ctScript ? (
254+ < SyntaxHighlighter
255+ language = "bash"
256+ style = { tomorrow }
257+ customStyle = { {
258+ margin : 0 ,
259+ padding : '1rem' ,
260+ fontSize : '14px' ,
261+ lineHeight : '1.5' ,
262+ minHeight : '100%'
263+ } }
264+ showLineNumbers = { true }
265+ wrapLines = { true }
266+ >
267+ { scriptContent . ctScript }
268+ </ SyntaxHighlighter >
269+ ) : selectedVersion === 'alpine' && scriptContent . alpineCtScript ? (
270+ < SyntaxHighlighter
271+ language = "bash"
272+ style = { tomorrow }
273+ customStyle = { {
274+ margin : 0 ,
275+ padding : '1rem' ,
276+ fontSize : '14px' ,
277+ lineHeight : '1.5' ,
278+ minHeight : '100%'
279+ } }
280+ showLineNumbers = { true }
281+ wrapLines = { true }
282+ >
283+ { scriptContent . alpineCtScript }
284+ </ SyntaxHighlighter >
285+ ) : (
286+ < div className = "flex items-center justify-center h-full" >
287+ < div className = "text-lg text-muted-foreground" >
288+ { selectedVersion === 'default' ? 'Default CT script not found' : 'Alpine CT script not found' }
289+ </ div >
290+ </ div >
291+ )
292+ ) }
293+ { activeTab === 'install' && (
294+ selectedVersion === 'default' && scriptContent . installScript ? (
295+ < SyntaxHighlighter
296+ language = "bash"
297+ style = { tomorrow }
298+ customStyle = { {
299+ margin : 0 ,
300+ padding : '1rem' ,
301+ fontSize : '14px' ,
302+ lineHeight : '1.5' ,
303+ minHeight : '100%'
304+ } }
305+ showLineNumbers = { true }
306+ wrapLines = { true }
307+ >
308+ { scriptContent . installScript }
309+ </ SyntaxHighlighter >
310+ ) : selectedVersion === 'alpine' && scriptContent . alpineInstallScript ? (
311+ < SyntaxHighlighter
312+ language = "bash"
313+ style = { tomorrow }
314+ customStyle = { {
315+ margin : 0 ,
316+ padding : '1rem' ,
317+ fontSize : '14px' ,
318+ lineHeight : '1.5' ,
319+ minHeight : '100%'
320+ } }
321+ showLineNumbers = { true }
322+ wrapLines = { true }
323+ >
324+ { scriptContent . alpineInstallScript }
325+ </ SyntaxHighlighter >
326+ ) : (
327+ < div className = "flex items-center justify-center h-full" >
328+ < div className = "text-lg text-muted-foreground" >
329+ { selectedVersion === 'default' ? 'Default install script not found' : 'Alpine install script not found' }
330+ </ div >
190331 </ div >
191- </ div >
332+ )
192333 ) }
193334 </ div >
194335 ) }
0 commit comments