33import { useState , useEffect } from 'react' ;
44import type { Server } from '../../types/server' ;
55import { Button } from './ui/button' ;
6+ import { SettingsModal } from './SettingsModal' ;
67
78interface ExecutionModeModalProps {
89 isOpen : boolean ;
@@ -15,15 +16,33 @@ export function ExecutionModeModal({ isOpen, onClose, onExecute, scriptName }: E
1516 const [ servers , setServers ] = useState < Server [ ] > ( [ ] ) ;
1617 const [ loading , setLoading ] = useState ( false ) ;
1718 const [ error , setError ] = useState < string | null > ( null ) ;
18- const [ selectedMode , setSelectedMode ] = useState < 'local' | 'ssh' > ( 'local' ) ;
1919 const [ selectedServer , setSelectedServer ] = useState < Server | null > ( null ) ;
20+ const [ hasAutoExecuted , setHasAutoExecuted ] = useState ( false ) ;
21+ const [ settingsModalOpen , setSettingsModalOpen ] = useState ( false ) ;
2022
2123 useEffect ( ( ) => {
2224 if ( isOpen ) {
25+ setHasAutoExecuted ( false ) ;
2326 void fetchServers ( ) ;
2427 }
2528 } , [ isOpen ] ) ;
2629
30+ // Auto-execute when exactly one server is available
31+ useEffect ( ( ) => {
32+ if ( isOpen && ! loading && servers . length === 1 && ! hasAutoExecuted ) {
33+ setHasAutoExecuted ( true ) ;
34+ onExecute ( 'ssh' , servers [ 0 ] ) ;
35+ onClose ( ) ;
36+ }
37+ } , [ isOpen , loading , servers , hasAutoExecuted , onExecute , onClose ] ) ;
38+
39+ // Refresh servers when settings modal closes
40+ const handleSettingsModalClose = ( ) => {
41+ setSettingsModalOpen ( false ) ;
42+ // Refetch servers to reflect any changes made in settings
43+ void fetchServers ( ) ;
44+ } ;
45+
2746 const fetchServers = async ( ) => {
2847 setLoading ( true ) ;
2948 setError ( null ) ;
@@ -42,110 +61,60 @@ export function ExecutionModeModal({ isOpen, onClose, onExecute, scriptName }: E
4261 } ;
4362
4463 const handleExecute = ( ) => {
45- if ( selectedMode === 'ssh' && ! selectedServer ) {
64+ if ( ! selectedServer ) {
4665 setError ( 'Please select a server for SSH execution' ) ;
4766 return ;
4867 }
4968
50- onExecute ( selectedMode , selectedServer ?? undefined ) ;
69+ onExecute ( 'ssh' , selectedServer ) ;
5170 onClose ( ) ;
5271 } ;
5372
54- const handleModeChange = ( mode : 'local' | 'ssh' ) => {
55- setSelectedMode ( mode ) ;
56- if ( mode === 'local' ) {
57- setSelectedServer ( null ) ;
58- }
59- } ;
60-
6173 if ( ! isOpen ) return null ;
6274
6375 return (
64- < div className = "fixed inset-0 backdrop-blur-sm bg-black/50 flex items-center justify-center z-50 p-4" >
65- < div className = "bg-card rounded-lg shadow-xl max-w-md w-full border border-border" >
66- { /* Header */ }
67- < div className = "flex items-center justify-between p-6 border-b border-border" >
68- < h2 className = "text-xl font-bold text-foreground" > Execution Mode</ h2 >
69- < Button
70- onClick = { onClose }
71- variant = "ghost"
72- size = "icon"
73- className = "text-muted-foreground hover:text-foreground"
74- >
75- < svg className = "w-6 h-6" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
76- < path strokeLinecap = "round" strokeLinejoin = "round" strokeWidth = { 2 } d = "M6 18L18 6M6 6l12 12" />
77- </ svg >
78- </ Button >
79- </ div >
80-
81- { /* Content */ }
82- < div className = "p-6" >
83- < div className = "mb-6" >
84- < h3 className = "text-lg font-medium text-foreground mb-2" >
85- Where would you like to execute "{ scriptName } "?
86- </ h3 >
87-
76+ < >
77+ < div className = "fixed inset-0 backdrop-blur-sm bg-black/50 flex items-center justify-center z-50 p-4" >
78+ < div className = "bg-card rounded-lg shadow-xl max-w-md w-full border border-border" >
79+ { /* Header */ }
80+ < div className = "flex items-center justify-between p-6 border-b border-border" >
81+ < h2 className = "text-xl font-bold text-foreground" > Select Server</ h2 >
82+ < Button
83+ onClick = { onClose }
84+ variant = "ghost"
85+ size = "icon"
86+ className = "text-muted-foreground hover:text-foreground"
87+ >
88+ < svg className = "w-6 h-6" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
89+ < path strokeLinecap = "round" strokeLinejoin = "round" strokeWidth = { 2 } d = "M6 18L18 6M6 6l12 12" />
90+ </ svg >
91+ </ Button >
8892 </ div >
8993
90- { error && (
91- < div className = "mb-4 p-3 bg-destructive/10 border border-destructive/20 rounded-md" >
92- < div className = "flex" >
93- < div className = "flex-shrink-0" >
94- < svg className = "h-5 w-5 text-destructive" viewBox = "0 0 20 20" fill = "currentColor" >
95- < path fillRule = "evenodd" d = "M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clipRule = "evenodd" />
96- </ svg >
97- </ div >
98- < div className = "ml-3" >
99- < p className = "text-sm text-destructive" > { error } </ p >
100- </ div >
101- </ div >
94+ { /* Content */ }
95+ < div className = "p-6" >
96+ < div className = "mb-6" >
97+ < h3 className = "text-lg font-medium text-foreground mb-2" >
98+ Select server to execute "{ scriptName } "
99+ </ h3 >
102100 </ div >
103- ) }
104-
105- { /* Execution Mode Selection */ }
106- < div className = "space-y-4 mb-6" >
107-
108101
109- { /* SSH Execution */ }
110- < div
111- className = { `border rounded-lg p-4 cursor-pointer transition-colors ${
112- selectedMode === 'ssh'
113- ? 'border-primary bg-primary/10'
114- : 'border-border hover:border-primary/50'
115- } `}
116- onClick = { ( ) => handleModeChange ( 'ssh' ) }
117- >
118- < div className = "flex items-center" >
119- < input
120- type = "radio"
121- id = "ssh"
122- name = "executionMode"
123- value = "ssh"
124- checked = { selectedMode === 'ssh' }
125- onChange = { ( ) => handleModeChange ( 'ssh' ) }
126- className = "h-4 w-4 text-primary focus:ring-primary border-border"
127- />
128- < label htmlFor = "ssh" className = "ml-3 flex-1 cursor-pointer" >
129- < div className = "flex items-center" >
130- < div className = "flex-shrink-0" >
131- < div className = "w-10 h-10 bg-primary/10 rounded-full flex items-center justify-center" >
132- < svg className = "w-6 h-6 text-primary" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
133- < path strokeLinecap = "round" strokeLinejoin = "round" strokeWidth = { 2 } d = "M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
134- </ svg >
135- </ div >
136- </ div >
137- < div className = "ml-3" >
138- < h4 className = "text-sm font-medium text-foreground" > SSH Execution</ h4 >
139- < p className = "text-sm text-muted-foreground" > Run the script on a remote server</ p >
140- </ div >
102+ { error && (
103+ < div className = "mb-4 p-3 bg-destructive/10 border border-destructive/20 rounded-md" >
104+ < div className = "flex" >
105+ < div className = "flex-shrink-0" >
106+ < svg className = "h-5 w-5 text-destructive" viewBox = "0 0 20 20" fill = "currentColor" >
107+ < path fillRule = "evenodd" d = "M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clipRule = "evenodd" />
108+ </ svg >
109+ </ div >
110+ < div className = "ml-3" >
111+ < p className = "text-sm text-destructive" > { error } </ p >
141112 </ div >
142- </ label >
113+ </ div >
143114 </ div >
144- </ div >
145- </ div >
115+ ) }
146116
147- { /* Server Selection (only for SSH mode) */ }
148- { selectedMode === 'ssh' && (
117+ { /* Server Selection */ }
149118 < div className = "mb-6" >
150119 < label htmlFor = "server" className = "block text-sm font-medium text-foreground mb-2" >
151120 Select Server
@@ -158,7 +127,15 @@ export function ExecutionModeModal({ isOpen, onClose, onExecute, scriptName }: E
158127 ) : servers . length === 0 ? (
159128 < div className = "text-center py-4 text-muted-foreground" >
160129 < p className = "text-sm" > No servers configured</ p >
161- < p className = "text-xs mt-1" > Add servers in Settings to use SSH execution</ p >
130+ < p className = "text-xs mt-1" > Add servers in Settings to execute scripts</ p >
131+ < Button
132+ onClick = { ( ) => setSettingsModalOpen ( true ) }
133+ variant = "outline"
134+ size = "sm"
135+ className = "mt-3"
136+ >
137+ Open Server Settings
138+ </ Button >
162139 </ div >
163140 ) : (
164141 < select
@@ -180,29 +157,35 @@ export function ExecutionModeModal({ isOpen, onClose, onExecute, scriptName }: E
180157 </ select >
181158 ) }
182159 </ div >
183- ) }
184160
185- { /* Action Buttons */ }
186- < div className = "flex justify-end space-x-3" >
187- < Button
188- onClick = { onClose }
189- variant = "outline"
190- size = "default"
191- >
192- Cancel
193- </ Button >
194- < Button
195- onClick = { handleExecute }
196- disabled = { selectedMode === 'ssh' && ! selectedServer }
197- variant = "default"
198- size = "default"
199- className = { selectedMode === 'ssh' && ! selectedServer ? 'bg-gray-400 cursor-not-allowed' : '' }
200- >
201- { selectedMode === 'local' ? 'Run Locally' : 'Run on Server' }
202- </ Button >
161+ { /* Action Buttons */ }
162+ < div className = "flex justify-end space-x-3" >
163+ < Button
164+ onClick = { onClose }
165+ variant = "outline"
166+ size = "default"
167+ >
168+ Cancel
169+ </ Button >
170+ < Button
171+ onClick = { handleExecute }
172+ disabled = { ! selectedServer }
173+ variant = "default"
174+ size = "default"
175+ className = { ! selectedServer ? 'bg-gray-400 cursor-not-allowed' : '' }
176+ >
177+ Run on Server
178+ </ Button >
179+ </ div >
203180 </ div >
204181 </ div >
205182 </ div >
206- </ div >
183+
184+ { /* Server Settings Modal */ }
185+ < SettingsModal
186+ isOpen = { settingsModalOpen }
187+ onClose = { handleSettingsModalClose }
188+ />
189+ </ >
207190 ) ;
208191}
0 commit comments