@@ -43,10 +43,13 @@ interface LXCSettingsModalProps {
4343export function LXCSettingsModal ( { isOpen, script, onClose, onSave } : LXCSettingsModalProps ) {
4444 const [ activeTab , setActiveTab ] = useState < string > ( 'common' ) ;
4545 const [ showConfirmation , setShowConfirmation ] = useState ( false ) ;
46+ const [ showResultModal , setShowResultModal ] = useState ( false ) ;
47+ const [ resultType , setResultType ] = useState < 'success' | 'error' | null > ( null ) ;
48+ const [ resultMessage , setResultMessage ] = useState < string | null > ( null ) ;
4649 const [ error , setError ] = useState < string | null > ( null ) ;
47- const [ successMessage , setSuccessMessage ] = useState < string | null > ( null ) ;
4850 const [ hasChanges , setHasChanges ] = useState ( false ) ;
4951 const [ forceSync ] = useState ( false ) ;
52+ const [ isSaving , setIsSaving ] = useState ( false ) ;
5053
5154 const [ formData , setFormData ] = useState < any > ( {
5255 arch : '' ,
@@ -76,27 +79,41 @@ export function LXCSettingsModal({ isOpen, script, onClose, onSave }: LXCSetting
7679 } ) ;
7780
7881 // tRPC hooks
79- const { data : configData , isLoading } = api . installedScripts . getLXCConfig . useQuery (
82+ const { data : configData , isLoading, refetch } = api . installedScripts . getLXCConfig . useQuery (
8083 { scriptId : script ?. id ?? 0 , forceSync } ,
8184 { enabled : ! ! script && isOpen }
8285 ) ;
8386
8487 const saveMutation = api . installedScripts . saveLXCConfig . useMutation ( {
85- onSuccess : ( ) => {
86- setSuccessMessage ( 'LXC configuration saved successfully' ) ;
87- setHasChanges ( false ) ;
88+ onSuccess : ( data ) => {
89+ console . log ( 'Save mutation success data:' , data ) ;
90+ setIsSaving ( false ) ;
8891 setShowConfirmation ( false ) ;
89- onSave ( ) ;
92+
93+ if ( data . success ) {
94+ setResultType ( 'success' ) ;
95+ setResultMessage ( data . message ?? 'LXC configuration saved successfully' ) ;
96+ setHasChanges ( false ) ;
97+ } else {
98+ console . log ( 'Backend returned error:' , data . error ) ;
99+ setResultType ( 'error' ) ;
100+ setResultMessage ( data . error ?? 'Failed to save configuration' ) ;
101+ }
102+ setShowResultModal ( true ) ;
90103 } ,
91104 onError : ( err ) => {
92- setError ( `Failed to save configuration: ${ err . message } ` ) ;
105+ console . log ( 'Save mutation error:' , err ) ;
106+ setIsSaving ( false ) ;
107+ setShowConfirmation ( false ) ;
108+ setResultType ( 'error' ) ;
109+ setResultMessage ( `Failed to save configuration: ${ err . message } ` ) ;
110+ setShowResultModal ( true ) ;
93111 }
94112 } ) ;
95113
96114 const syncMutation = api . installedScripts . syncLXCConfig . useMutation ( {
97115 onSuccess : ( result ) => {
98116 populateFormData ( result ) ;
99- setSuccessMessage ( 'Configuration synced from server successfully' ) ;
100117 setHasChanges ( false ) ;
101118 } ,
102119 onError : ( err ) => {
@@ -158,13 +175,61 @@ export function LXCSettingsModal({ isOpen, script, onClose, onSave }: LXCSetting
158175 syncMutation . mutate ( { scriptId : script . id } ) ;
159176 } ;
160177
178+ const validateForm = ( ) => {
179+ // Check required fields
180+ if ( ! formData . arch ?. trim ( ) ) {
181+ setError ( 'Architecture is required' ) ;
182+ return false ;
183+ }
184+ if ( ! formData . cores || formData . cores < 1 ) {
185+ setError ( 'Cores must be at least 1' ) ;
186+ return false ;
187+ }
188+ if ( ! formData . memory || formData . memory < 128 ) {
189+ setError ( 'Memory must be at least 128 MB' ) ;
190+ return false ;
191+ }
192+ if ( ! formData . hostname ?. trim ( ) ) {
193+ setError ( 'Hostname is required' ) ;
194+ return false ;
195+ }
196+ if ( ! formData . ostype ?. trim ( ) ) {
197+ setError ( 'OS Type is required' ) ;
198+ return false ;
199+ }
200+ if ( ! formData . rootfs_storage ?. trim ( ) ) {
201+ setError ( 'Root filesystem storage is required' ) ;
202+ return false ;
203+ }
204+
205+ // Check if trying to decrease disk size
206+ const currentSize = configData ?. config ?. rootfs_size ?? '0G' ;
207+ const newSize = formData . rootfs_size ?? '0G' ;
208+ const currentSizeGB = parseFloat ( String ( currentSize ) ) ;
209+ const newSizeGB = parseFloat ( String ( newSize ) ) ;
210+
211+ if ( newSizeGB < currentSizeGB ) {
212+ setError ( 'Disk size cannot be decreased. Only increases are allowed for safety.' ) ;
213+ return false ;
214+ }
215+
216+ return true ;
217+ } ;
218+
161219 const handleSave = ( ) => {
162- setShowConfirmation ( true ) ;
220+ setError ( null ) ;
221+
222+ // Validate form - only show confirmation modal if no errors
223+ if ( validateForm ( ) ) {
224+ setShowConfirmation ( true ) ;
225+ }
163226 } ;
164227
165228 const handleConfirmSave = ( ) => {
166229 if ( ! script ) return ;
167230 setError ( null ) ;
231+ setIsSaving ( true ) ;
232+ setShowConfirmation ( false ) ;
168233
169234 saveMutation . mutate ( {
170235 scriptId : script . id ,
@@ -179,6 +244,14 @@ export function LXCSettingsModal({ isOpen, script, onClose, onSave }: LXCSetting
179244 } ) ;
180245 } ;
181246
247+ const handleResultModalClose = ( ) => {
248+ setShowResultModal ( false ) ;
249+ setResultType ( null ) ;
250+ setResultMessage ( null ) ;
251+ // Refresh the data to show updated values
252+ void refetch ( ) ;
253+ } ;
254+
182255 if ( ! isOpen || ! script ) return null ;
183256
184257 return (
@@ -229,23 +302,6 @@ export function LXCSettingsModal({ isOpen, script, onClose, onSave }: LXCSetting
229302 </ div >
230303 ) }
231304
232- { /* Success Message */ }
233- { successMessage && (
234- < div className = "bg-green-50 dark:bg-green-950/20 border-b border-green-200 dark:border-green-800 p-4" >
235- < div className = "flex items-start gap-3" >
236- < CheckCircle className = "h-5 w-5 text-green-600 dark:text-green-500 flex-shrink-0 mt-0.5" />
237- < div className = "flex-1" >
238- < p className = "text-sm font-medium text-green-800 dark:text-green-200" > { successMessage } </ p >
239- </ div >
240- < button
241- onClick = { ( ) => setSuccessMessage ( null ) }
242- className = "text-green-600 dark:text-green-500 hover:text-green-700 dark:hover:text-green-400"
243- >
244- ✕
245- </ button >
246- </ div >
247- </ div >
248- ) }
249305
250306 { /* Error Message */ }
251307 { error && (
@@ -485,13 +541,19 @@ export function LXCSettingsModal({ isOpen, script, onClose, onSave }: LXCSetting
485541 />
486542 </ div >
487543 < div className = "space-y-2" >
488- < label htmlFor = "rootfs_size" className = "block text-sm font-medium text-foreground" > Size</ label >
544+ < label htmlFor = "rootfs_size" className = "block text-sm font-medium text-foreground" >
545+ Size
546+ < span className = "text-xs text-muted-foreground ml-2" > (can only be increased)</ span >
547+ </ label >
489548 < Input
490549 id = "rootfs_size"
491550 value = { formData . rootfs_size }
492551 onChange = { ( e ) => handleInputChange ( 'rootfs_size' , e . target . value ) }
493552 placeholder = "4G"
494553 />
554+ < p className = "text-xs text-muted-foreground" >
555+ Disk size can only be increased for safety. Format: 4G, 8G, 16G, etc.
556+ </ p >
495557 </ div >
496558 </ div >
497559 </ div >
@@ -590,10 +652,10 @@ export function LXCSettingsModal({ isOpen, script, onClose, onSave }: LXCSetting
590652 </ Button >
591653 < Button
592654 onClick = { handleSave }
593- disabled = { saveMutation . isPending || ! hasChanges }
655+ disabled = { isSaving || saveMutation . isPending || ! hasChanges }
594656 variant = "default"
595657 >
596- { saveMutation . isPending ? 'Saving...' : 'Save Configuration' }
658+ { isSaving ? 'Saving & Resizing...' : saveMutation . isPending ? 'Saving...' : 'Save Configuration' }
597659 </ Button >
598660 </ div >
599661 </ div >
@@ -608,17 +670,55 @@ export function LXCSettingsModal({ isOpen, script, onClose, onSave }: LXCSetting
608670 } }
609671 onConfirm = { handleConfirmSave }
610672 title = "Confirm LXC Configuration Changes"
611- message = "Modifying LXC configuration can break your container and may require manual recovery. Ensure you understand these changes before proceeding. The container may need to be restarted for changes to take effect."
673+ message = { `Modifying LXC configuration can break your container and may require manual recovery. Ensure you understand these changes before proceeding.${
674+ formData . rootfs_size && configData ?. config ?. rootfs_size &&
675+ parseFloat ( String ( formData . rootfs_size ) ) > parseFloat ( String ( configData . config . rootfs_size ?? '0' ) )
676+ ? `\n\n⚠️ DISK RESIZE DETECTED: The disk size will be increased from ${ configData . config . rootfs_size } to ${ formData . rootfs_size } . This operation will automatically resize the underlying storage and filesystem.`
677+ : ''
678+ } \n\nThe container may need to be restarted for changes to take effect.`}
612679 variant = "danger"
613680 confirmText = { script . container_id ?? '' }
614- confirmButtonText = "Save Configuration"
681+ confirmButtonText = { formData . rootfs_size && configData ?. config ?. rootfs_size &&
682+ parseFloat ( String ( formData . rootfs_size ) ) > parseFloat ( String ( configData . config . rootfs_size ?? '0' ) )
683+ ? "Save & Resize Disk" : "Save Configuration" }
615684 />
616685
617686 { /* Loading Modal */ }
618687 < LoadingModal
619- isOpen = { isLoading }
620- action = " Loading LXC configuration..."
688+ isOpen = { isLoading || isSaving }
689+ action = { isSaving ? "Saving configuration and resizing disk..." : " Loading LXC configuration..."}
621690 />
691+
692+ { /* Result Modal */ }
693+ { showResultModal && resultType && resultMessage && (
694+ < div className = "fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50" >
695+ < div className = "bg-card text-card-foreground rounded-lg shadow-xl max-w-md w-full mx-4 border border-border" >
696+ < div className = "p-6" >
697+ < div className = "flex items-center gap-3 mb-4" >
698+ { resultType === 'success' ? (
699+ < CheckCircle className = "h-6 w-6 text-green-600 dark:text-green-500" />
700+ ) : (
701+ < AlertTriangle className = "h-6 w-6 text-red-600 dark:text-red-500" />
702+ ) }
703+ < h3 className = "text-lg font-semibold text-card-foreground" >
704+ { resultType === 'success' ? 'Success' : 'Error' }
705+ </ h3 >
706+ </ div >
707+ < p className = "text-muted-foreground mb-6" >
708+ { resultMessage }
709+ </ p >
710+ < div className = "flex justify-end" >
711+ < button
712+ onClick = { handleResultModalClose }
713+ className = "px-4 py-2 bg-primary text-primary-foreground hover:bg-primary/90 rounded-md transition-colors"
714+ >
715+ Close
716+ </ button >
717+ </ div >
718+ </ div >
719+ </ div >
720+ </ div >
721+ ) }
622722 </ >
623723 ) ;
624724}
0 commit comments