Skip to content

Commit e63958e

Browse files
feat: Add confirmation modal for script installation and server sorting (#105)
- Add confirmation modal when only one server is saved - Show script name and server details in confirmation view - Auto-select single server but require user confirmation - Preserve existing behavior for multiple servers (no pre-selection) - Sort servers alphabetically by name in all components - Fix ESLint issues with nullish coalescing operators Fixes: PROX2 now appears before PROX3 in server lists
1 parent ba57302 commit e63958e

File tree

3 files changed

+118
-63
lines changed

3 files changed

+118
-63
lines changed

src/app/_components/ColorCodedDropdown.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,9 @@ export function ColorCodedDropdown({
9494
</button>
9595

9696
{/* Server Options */}
97-
{servers.map((server) => (
97+
{servers
98+
.sort((a, b) => (a.name ?? '').localeCompare(b.name ?? ''))
99+
.map((server) => (
98100
<button
99101
key={server.id}
100102
type="button"

src/app/_components/ExecutionModeModal.tsx

Lines changed: 110 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -19,24 +19,20 @@ export function ExecutionModeModal({ isOpen, onClose, onExecute, scriptName }: E
1919
const [loading, setLoading] = useState(false);
2020
const [error, setError] = useState<string | null>(null);
2121
const [selectedServer, setSelectedServer] = useState<Server | null>(null);
22-
const [hasAutoExecuted, setHasAutoExecuted] = useState(false);
2322
const [settingsModalOpen, setSettingsModalOpen] = useState(false);
2423

2524
useEffect(() => {
2625
if (isOpen) {
27-
setHasAutoExecuted(false);
2826
void fetchServers();
2927
}
3028
}, [isOpen]);
3129

32-
// Auto-execute when exactly one server is available
30+
// Auto-select server when exactly one server is available
3331
useEffect(() => {
34-
if (isOpen && !loading && servers.length === 1 && !hasAutoExecuted) {
35-
setHasAutoExecuted(true);
36-
onExecute('ssh', servers[0]);
37-
onClose();
32+
if (isOpen && !loading && servers.length === 1) {
33+
setSelectedServer(servers[0] ?? null);
3834
}
39-
}, [isOpen, loading, servers, hasAutoExecuted, onExecute, onClose]);
35+
}, [isOpen, loading, servers]);
4036

4137
// Refresh servers when settings modal closes
4238
const handleSettingsModalClose = () => {
@@ -54,7 +50,11 @@ export function ExecutionModeModal({ isOpen, onClose, onExecute, scriptName }: E
5450
throw new Error('Failed to fetch servers');
5551
}
5652
const data = await response.json();
57-
setServers(data as Server[]);
53+
// Sort servers by name alphabetically
54+
const sortedServers = (data as Server[]).sort((a, b) =>
55+
(a.name ?? '').localeCompare(b.name ?? '')
56+
);
57+
setServers(sortedServers);
5858
} catch (err) {
5959
setError(err instanceof Error ? err.message : 'An error occurred');
6060
} finally {
@@ -101,12 +101,6 @@ export function ExecutionModeModal({ isOpen, onClose, onExecute, scriptName }: E
101101

102102
{/* Content */}
103103
<div className="p-6">
104-
<div className="mb-6">
105-
<h3 className="text-lg font-medium text-foreground mb-2">
106-
Select server to execute &quot;{scriptName}&quot;
107-
</h3>
108-
</div>
109-
110104
{error && (
111105
<div className="mb-4 p-3 bg-destructive/10 border border-destructive/20 rounded-md">
112106
<div className="flex">
@@ -122,58 +116,113 @@ export function ExecutionModeModal({ isOpen, onClose, onExecute, scriptName }: E
122116
</div>
123117
)}
124118

125-
{/* Server Selection */}
126-
<div className="mb-6">
127-
<label htmlFor="server" className="block text-sm font-medium text-foreground mb-2">
128-
Select Server
129-
</label>
130-
{loading ? (
131-
<div className="text-center py-4">
132-
<div className="inline-block animate-spin rounded-full h-6 w-6 border-b-2 border-primary"></div>
133-
<p className="mt-2 text-sm text-muted-foreground">Loading servers...</p>
119+
{loading ? (
120+
<div className="text-center py-8">
121+
<div className="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
122+
<p className="mt-2 text-sm text-muted-foreground">Loading servers...</p>
123+
</div>
124+
) : servers.length === 0 ? (
125+
<div className="text-center py-8 text-muted-foreground">
126+
<p className="text-sm">No servers configured</p>
127+
<p className="text-xs mt-1">Add servers in Settings to execute scripts</p>
128+
<Button
129+
onClick={() => setSettingsModalOpen(true)}
130+
variant="outline"
131+
size="sm"
132+
className="mt-3"
133+
>
134+
Open Server Settings
135+
</Button>
136+
</div>
137+
) : servers.length === 1 ? (
138+
/* Single Server Confirmation View */
139+
<div className="space-y-6">
140+
<div className="text-center">
141+
<h3 className="text-lg font-medium text-foreground mb-2">
142+
Install Script Confirmation
143+
</h3>
144+
<p className="text-sm text-muted-foreground">
145+
Do you want to install &quot;{scriptName}&quot; on the following server?
146+
</p>
147+
</div>
148+
149+
<div className="bg-muted/50 rounded-lg p-4 border border-border">
150+
<div className="flex items-center space-x-3">
151+
<div className="flex-shrink-0">
152+
<div className="w-3 h-3 bg-green-500 rounded-full"></div>
153+
</div>
154+
<div className="flex-1 min-w-0">
155+
<p className="text-sm font-medium text-foreground truncate">
156+
{selectedServer?.name ?? 'Unnamed Server'}
157+
</p>
158+
<p className="text-sm text-muted-foreground">
159+
{selectedServer?.ip}
160+
</p>
161+
</div>
162+
</div>
163+
</div>
164+
165+
{/* Action Buttons */}
166+
<div className="flex justify-end space-x-3">
167+
<Button
168+
onClick={onClose}
169+
variant="outline"
170+
size="default"
171+
>
172+
Cancel
173+
</Button>
174+
<Button
175+
onClick={handleExecute}
176+
variant="default"
177+
size="default"
178+
>
179+
Install
180+
</Button>
181+
</div>
182+
</div>
183+
) : (
184+
/* Multiple Servers Selection View */
185+
<div className="space-y-6">
186+
<div className="mb-6">
187+
<h3 className="text-lg font-medium text-foreground mb-2">
188+
Select server to execute &quot;{scriptName}&quot;
189+
</h3>
190+
</div>
191+
192+
{/* Server Selection */}
193+
<div className="mb-6">
194+
<label htmlFor="server" className="block text-sm font-medium text-foreground mb-2">
195+
Select Server
196+
</label>
197+
<ColorCodedDropdown
198+
servers={servers}
199+
selectedServer={selectedServer}
200+
onServerSelect={handleServerSelect}
201+
placeholder="Select a server..."
202+
/>
134203
</div>
135-
) : servers.length === 0 ? (
136-
<div className="text-center py-4 text-muted-foreground">
137-
<p className="text-sm">No servers configured</p>
138-
<p className="text-xs mt-1">Add servers in Settings to execute scripts</p>
204+
205+
{/* Action Buttons */}
206+
<div className="flex justify-end space-x-3">
139207
<Button
140-
onClick={() => setSettingsModalOpen(true)}
208+
onClick={onClose}
141209
variant="outline"
142-
size="sm"
143-
className="mt-3"
210+
size="default"
211+
>
212+
Cancel
213+
</Button>
214+
<Button
215+
onClick={handleExecute}
216+
disabled={!selectedServer}
217+
variant="default"
218+
size="default"
219+
className={!selectedServer ? 'bg-gray-400 cursor-not-allowed' : ''}
144220
>
145-
Open Server Settings
221+
Run on Server
146222
</Button>
147223
</div>
148-
) : (
149-
<ColorCodedDropdown
150-
servers={servers}
151-
selectedServer={selectedServer}
152-
onServerSelect={handleServerSelect}
153-
placeholder="Select a server..."
154-
/>
155-
)}
156-
</div>
157-
158-
{/* Action Buttons */}
159-
<div className="flex justify-end space-x-3">
160-
<Button
161-
onClick={onClose}
162-
variant="outline"
163-
size="default"
164-
>
165-
Cancel
166-
</Button>
167-
<Button
168-
onClick={handleExecute}
169-
disabled={!selectedServer}
170-
variant="default"
171-
size="default"
172-
className={!selectedServer ? 'bg-gray-400 cursor-not-allowed' : ''}
173-
>
174-
Run on Server
175-
</Button>
176-
</div>
224+
</div>
225+
)}
177226
</div>
178227
</div>
179228
</div>

src/app/_components/SettingsModal.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,11 @@ export function SettingsModal({ isOpen, onClose }: SettingsModalProps) {
3131
throw new Error('Failed to fetch servers');
3232
}
3333
const data = await response.json();
34-
setServers(data as Server[]);
34+
// Sort servers by name alphabetically
35+
const sortedServers = (data as Server[]).sort((a, b) =>
36+
(a.name ?? '').localeCompare(b.name ?? '')
37+
);
38+
setServers(sortedServers);
3539
} catch (err) {
3640
setError(err instanceof Error ? err.message : 'An error occurred');
3741
} finally {

0 commit comments

Comments
 (0)