@@ -3,7 +3,7 @@ import * as Yup from 'yup';
33import { Trans , useTranslation } from 'react-i18next' ;
44
55// material-ui
6- import { Alert , Button , Checkbox , CircularProgress , FormControl , FormHelperText , Grid , Stack , Tooltip , Typography , Card , CardContent , CardActionArea , TextField } from '@mui/material' ;
6+ import { Alert , Button , Checkbox , CircularProgress , FormControl , FormHelperText , Grid , Stack , Tooltip , Typography , Card , CardContent , CardActionArea , TextField , Select , MenuItem , InputLabel } from '@mui/material' ;
77
88// ant-ui icons
99import { CheckCircleOutlined , LeftOutlined , QuestionCircleFilled , QuestionCircleOutlined , RightOutlined } from '@ant-design/icons' ;
@@ -63,24 +63,44 @@ const NewInstall = () => {
6363 const [ cleanInstall , setCleanInstall ] = useState ( true ) ;
6464
6565 // Runtime selection state
66- const [ runtimeType , setRuntimeType ] = useState ( 'docker' ) ;
66+ const [ runtimeType , setRuntimeType ] = useState ( null ) ; // null = not selected yet
6767 const [ proxmoxConfig , setProxmoxConfig ] = useState ( {
6868 host : '' ,
69- node : '' ,
7069 tokenID : '' ,
7170 tokenSecret : '' ,
72- storage : 'local-lvm' ,
73- skipTLSVerify : false
71+ node : '' ,
72+ storage : '' ,
73+ skipTLSVerify : true
7474 } ) ;
7575 const [ proxmoxTestResult , setProxmoxTestResult ] = useState ( null ) ;
7676 const [ proxmoxTesting , setProxmoxTesting ] = useState ( false ) ;
77+ const [ availableNodes , setAvailableNodes ] = useState ( [ ] ) ;
78+ const [ availableStorages , setAvailableStorages ] = useState ( [ ] ) ;
7779
7880 const testProxmoxConnection = async ( ) => {
7981 setProxmoxTesting ( true ) ;
8082 setProxmoxTestResult ( null ) ;
83+ setAvailableNodes ( [ ] ) ;
84+ setAvailableStorages ( [ ] ) ;
8185 try {
82- const result = await API . testProxmoxConnection ( proxmoxConfig ) ;
86+ const result = await API . testProxmoxConnection ( {
87+ host : proxmoxConfig . host ,
88+ tokenID : proxmoxConfig . tokenID ,
89+ tokenSecret : proxmoxConfig . tokenSecret ,
90+ skipTLSVerify : proxmoxConfig . skipTLSVerify
91+ } ) ;
8392 setProxmoxTestResult ( result ) ;
93+ if ( result . success ) {
94+ // Populate discovered nodes and storage
95+ if ( result . nodes && result . nodes . length > 0 ) {
96+ setAvailableNodes ( result . nodes ) ;
97+ setProxmoxConfig ( prev => ( { ...prev , node : result . nodes [ 0 ] } ) ) ;
98+ }
99+ if ( result . storages && result . storages . length > 0 ) {
100+ setAvailableStorages ( result . storages ) ;
101+ setProxmoxConfig ( prev => ( { ...prev , storage : result . storages [ 0 ] } ) ) ;
102+ }
103+ }
84104 } catch ( error ) {
85105 setProxmoxTestResult ( { success : false , message : error . message } ) ;
86106 }
@@ -192,7 +212,7 @@ const NewInstall = () => {
192212 transition : 'all 0.2s ease-in-out' ,
193213 '&:hover' : { borderColor : '#1976d2' }
194214 } }
195- onClick = { ( ) => { setRuntimeType ( 'docker' ) ; setProxmoxTestResult ( null ) ; } }
215+ onClick = { ( ) => { setRuntimeType ( 'docker' ) ; setProxmoxTestResult ( null ) ; setAvailableNodes ( [ ] ) ; setAvailableStorages ( [ ] ) ; } }
196216 >
197217 < CardContent sx = { { textAlign : 'center' , py : 3 } } >
198218 < Typography variant = "h1" sx = { { fontSize : '48px' , mb : 1 } } > 🐋</ Typography >
@@ -214,7 +234,7 @@ const NewInstall = () => {
214234 transition : 'all 0.2s ease-in-out' ,
215235 '&:hover' : { borderColor : '#e65100' }
216236 } }
217- onClick = { ( ) => setRuntimeType ( 'proxmox' ) }
237+ onClick = { ( ) => { setRuntimeType ( 'proxmox' ) ; } }
218238 >
219239 < CardContent sx = { { textAlign : 'center' , py : 3 } } >
220240 < Typography variant = "h1" sx = { { fontSize : '48px' , mb : 1 } } > 🖥️</ Typography >
@@ -267,13 +287,6 @@ const NewInstall = () => {
267287 onChange = { ( e ) => setProxmoxConfig ( { ...proxmoxConfig , host : e . target . value } ) }
268288 placeholder = "192.168.1.100:8006"
269289 />
270- < TextField
271- fullWidth
272- label = { t ( 'newInstall.proxmoxNode' ) || "Node Name" }
273- value = { proxmoxConfig . node }
274- onChange = { ( e ) => setProxmoxConfig ( { ...proxmoxConfig , node : e . target . value } ) }
275- placeholder = "pve"
276- />
277290 < TextField
278291 fullWidth
279292 label = { t ( 'newInstall.proxmoxTokenID' ) || "API Token ID (user@realm!tokenname)" }
@@ -289,13 +302,6 @@ const NewInstall = () => {
289302 onChange = { ( e ) => setProxmoxConfig ( { ...proxmoxConfig , tokenSecret : e . target . value } ) }
290303 placeholder = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
291304 />
292- < TextField
293- fullWidth
294- label = { t ( 'newInstall.proxmoxStorage' ) || "Storage Pool" }
295- value = { proxmoxConfig . storage }
296- onChange = { ( e ) => setProxmoxConfig ( { ...proxmoxConfig , storage : e . target . value } ) }
297- placeholder = "local-lvm"
298- />
299305 < Stack direction = "row" alignItems = "center" spacing = { 1 } >
300306 < Checkbox
301307 checked = { proxmoxConfig . skipTLSVerify }
@@ -320,14 +326,51 @@ const NewInstall = () => {
320326 { proxmoxTestResult . message }
321327 </ Alert >
322328 ) }
329+
330+ { /* Show node/storage selection after successful connection */ }
331+ { proxmoxTestResult && proxmoxTestResult . success && availableNodes . length > 0 && (
332+ < Stack spacing = { 2 } >
333+ < FormControl fullWidth >
334+ < InputLabel > { t ( 'newInstall.proxmoxNode' ) || "Node" } </ InputLabel >
335+ < Select
336+ value = { proxmoxConfig . node }
337+ label = { t ( 'newInstall.proxmoxNode' ) || "Node" }
338+ onChange = { ( e ) => setProxmoxConfig ( { ...proxmoxConfig , node : e . target . value } ) }
339+ >
340+ { availableNodes . map ( ( node ) => (
341+ < MenuItem key = { node } value = { node } > { node } </ MenuItem >
342+ ) ) }
343+ </ Select >
344+ </ FormControl >
345+
346+ { availableStorages . length > 0 && (
347+ < FormControl fullWidth >
348+ < InputLabel > { t ( 'newInstall.proxmoxStorage' ) || "Storage Pool" } </ InputLabel >
349+ < Select
350+ value = { proxmoxConfig . storage }
351+ label = { t ( 'newInstall.proxmoxStorage' ) || "Storage Pool" }
352+ onChange = { ( e ) => setProxmoxConfig ( { ...proxmoxConfig , storage : e . target . value } ) }
353+ >
354+ { availableStorages . map ( ( storage ) => (
355+ < MenuItem key = { storage } value = { storage } > { storage } </ MenuItem >
356+ ) ) }
357+ </ Select >
358+ </ FormControl >
359+ ) }
360+ </ Stack >
361+ ) }
323362 </ Stack >
324363 ) }
325364 </ Stack > ,
326365 nextButtonLabel : ( ) => {
366+ if ( ! runtimeType ) {
367+ return '' ; // No runtime selected yet
368+ }
327369 if ( runtimeType === 'docker' ) {
328370 return status && status . docker ? t ( 'global.next' ) : t ( 'newInstall.skipAction' ) ;
329371 } else {
330- return proxmoxTestResult && proxmoxTestResult . success ? t ( 'global.next' ) : '' ;
372+ // Proxmox: require successful test AND node selected
373+ return proxmoxTestResult && proxmoxTestResult . success && proxmoxConfig . node ? t ( 'global.next' ) : '' ;
331374 }
332375 }
333376 } ,
0 commit comments