Skip to content

Commit 90bbfac

Browse files
feat: Add auto-detect LXC containers feature with improved UX
- Add auto-detection for LXC containers with 'community-script' tag - SSH to Proxmox servers and scan /etc/pve/lxc/ config files - Extract container ID and hostname from config files - Automatically create installed script records for detected containers - Replace alert popups with modern status messages - Add visual feedback with success/error states - Auto-close form on successful detection - Add clear UI indicators for community-script tag requirement - Improve error handling and logging for better debugging - Support both local and SSH execution modes
1 parent 5eaafbd commit 90bbfac

File tree

3 files changed

+363
-31
lines changed

3 files changed

+363
-31
lines changed

server.log

Lines changed: 0 additions & 29 deletions
This file was deleted.

src/app/_components/InstalledScriptsTab.tsx

Lines changed: 164 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ export function InstalledScriptsTab() {
3131
const [editFormData, setEditFormData] = useState<{ script_name: string; container_id: string }>({ script_name: '', container_id: '' });
3232
const [showAddForm, setShowAddForm] = useState(false);
3333
const [addFormData, setAddFormData] = useState<{ script_name: string; container_id: string; server_id: string }>({ script_name: '', container_id: '', server_id: 'local' });
34+
const [showAutoDetectForm, setShowAutoDetectForm] = useState(false);
35+
const [autoDetectServerId, setAutoDetectServerId] = useState<string>('');
36+
const [autoDetectStatus, setAutoDetectStatus] = useState<{ type: 'success' | 'error' | null; message: string }>({ type: null, message: '' });
3437

3538
// Fetch installed scripts
3639
const { data: scriptsData, refetch: refetchScripts, isLoading } = api.installedScripts.getAllInstalledScripts.useQuery();
@@ -68,6 +71,37 @@ export function InstalledScriptsTab() {
6871
}
6972
});
7073

74+
// Auto-detect LXC containers mutation
75+
const autoDetectMutation = api.installedScripts.autoDetectLXCContainers.useMutation({
76+
onSuccess: (data) => {
77+
console.log('Auto-detect success:', data);
78+
void refetchScripts();
79+
setShowAutoDetectForm(false);
80+
setAutoDetectServerId('');
81+
setAutoDetectStatus({
82+
type: 'success',
83+
message: data.message || 'Auto-detection completed successfully!'
84+
});
85+
// Clear status after 5 seconds
86+
setTimeout(() => setAutoDetectStatus({ type: null, message: '' }), 5000);
87+
},
88+
onError: (error) => {
89+
console.error('Auto-detect mutation error:', error);
90+
console.error('Error details:', {
91+
message: error.message,
92+
cause: error.cause,
93+
stack: error.stack,
94+
data: error.data
95+
});
96+
setAutoDetectStatus({
97+
type: 'error',
98+
message: error.message || 'Auto-detection failed. Please try again.'
99+
});
100+
// Clear status after 5 seconds
101+
setTimeout(() => setAutoDetectStatus({ type: null, message: '' }), 5000);
102+
}
103+
});
104+
71105

72106
const scripts: InstalledScript[] = (scriptsData?.scripts as InstalledScript[]) ?? [];
73107
const stats = statsData?.stats;
@@ -197,6 +231,25 @@ export function InstalledScriptsTab() {
197231
setAddFormData({ script_name: '', container_id: '', server_id: 'local' });
198232
};
199233

234+
const handleAutoDetect = () => {
235+
if (!autoDetectServerId) {
236+
return;
237+
}
238+
239+
if (autoDetectMutation.isPending) {
240+
return;
241+
}
242+
243+
setAutoDetectStatus({ type: null, message: '' });
244+
console.log('Starting auto-detect for server ID:', autoDetectServerId);
245+
autoDetectMutation.mutate({ serverId: Number(autoDetectServerId) });
246+
};
247+
248+
const handleCancelAutoDetect = () => {
249+
setShowAutoDetectForm(false);
250+
setAutoDetectServerId('');
251+
};
252+
200253

201254
const formatDate = (dateString: string) => {
202255
return new Date(dateString).toLocaleString();
@@ -251,15 +304,22 @@ export function InstalledScriptsTab() {
251304
</div>
252305
)}
253306

254-
{/* Add Script Button */}
255-
<div className="mb-4">
307+
{/* Add Script and Auto-Detect Buttons */}
308+
<div className="mb-4 flex flex-col sm:flex-row gap-3">
256309
<Button
257310
onClick={() => setShowAddForm(!showAddForm)}
258311
variant={showAddForm ? "outline" : "default"}
259312
size="default"
260313
>
261314
{showAddForm ? 'Cancel Add Script' : '+ Add Manual Script Entry'}
262315
</Button>
316+
<Button
317+
onClick={() => setShowAutoDetectForm(!showAutoDetectForm)}
318+
variant={showAutoDetectForm ? "outline" : "secondary"}
319+
size="default"
320+
>
321+
{showAutoDetectForm ? 'Cancel Auto-Detect' : '🔍 Auto-Detect LXC Containers (Must contain a tag with "community-script")'}
322+
</Button>
263323
</div>
264324

265325
{/* Add Script Form */}
@@ -331,6 +391,108 @@ export function InstalledScriptsTab() {
331391
</div>
332392
)}
333393

394+
{/* Auto-Detect Status Message */}
395+
{autoDetectStatus.type && (
396+
<div className={`mb-4 p-4 rounded-lg border ${
397+
autoDetectStatus.type === 'success'
398+
? 'bg-green-50 dark:bg-green-950/20 border-green-200 dark:border-green-800'
399+
: 'bg-red-50 dark:bg-red-950/20 border-red-200 dark:border-red-800'
400+
}`}>
401+
<div className="flex items-center">
402+
<div className="flex-shrink-0">
403+
{autoDetectStatus.type === 'success' ? (
404+
<svg className="h-5 w-5 text-green-400" viewBox="0 0 20 20" fill="currentColor">
405+
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
406+
</svg>
407+
) : (
408+
<svg className="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor">
409+
<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" />
410+
</svg>
411+
)}
412+
</div>
413+
<div className="ml-3">
414+
<p className={`text-sm font-medium ${
415+
autoDetectStatus.type === 'success'
416+
? 'text-green-800 dark:text-green-200'
417+
: 'text-red-800 dark:text-red-200'
418+
}`}>
419+
{autoDetectStatus.message}
420+
</p>
421+
</div>
422+
</div>
423+
</div>
424+
)}
425+
426+
{/* Auto-Detect LXC Containers Form */}
427+
{showAutoDetectForm && (
428+
<div className="mb-6 p-4 sm:p-6 bg-card rounded-lg border border-border shadow-sm">
429+
<h3 className="text-lg font-semibold text-foreground mb-4 sm:mb-6">Auto-Detect LXC Containers (Must contain a tag with "community-script")</h3>
430+
<div className="space-y-4 sm:space-y-6">
431+
<div className="bg-blue-50 dark:bg-blue-950/20 border border-blue-200 dark:border-blue-800 rounded-lg p-4">
432+
<div className="flex items-start">
433+
<div className="flex-shrink-0">
434+
<svg className="h-5 w-5 text-blue-400" viewBox="0 0 20 20" fill="currentColor">
435+
<path fillRule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clipRule="evenodd" />
436+
</svg>
437+
</div>
438+
<div className="ml-3">
439+
<h4 className="text-sm font-medium text-blue-800 dark:text-blue-200">
440+
How it works
441+
</h4>
442+
<div className="mt-2 text-sm text-blue-700 dark:text-blue-300">
443+
<p>This feature will:</p>
444+
<ul className="list-disc list-inside mt-1 space-y-1">
445+
<li>Connect to the selected server via SSH</li>
446+
<li>Scan all LXC config files in /etc/pve/lxc/</li>
447+
<li>Find containers with "community-script" in their tags</li>
448+
<li>Extract the container ID and hostname</li>
449+
<li>Add them as installed script entries</li>
450+
</ul>
451+
</div>
452+
</div>
453+
</div>
454+
</div>
455+
456+
<div className="space-y-2">
457+
<label className="block text-sm font-medium text-foreground">
458+
Select Server *
459+
</label>
460+
<select
461+
value={autoDetectServerId}
462+
onChange={(e) => setAutoDetectServerId(e.target.value)}
463+
className="w-full px-3 py-2 border border-input rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:border-ring"
464+
>
465+
<option value="">Choose a server...</option>
466+
{serversData?.servers?.map((server: any) => (
467+
<option key={server.id} value={server.id}>
468+
{server.name} ({server.ip})
469+
</option>
470+
))}
471+
</select>
472+
</div>
473+
</div>
474+
<div className="flex flex-col sm:flex-row justify-end gap-3 mt-4 sm:mt-6">
475+
<Button
476+
onClick={handleCancelAutoDetect}
477+
variant="outline"
478+
size="default"
479+
className="w-full sm:w-auto"
480+
>
481+
Cancel
482+
</Button>
483+
<Button
484+
onClick={handleAutoDetect}
485+
disabled={autoDetectMutation.isPending || !autoDetectServerId}
486+
variant="default"
487+
size="default"
488+
className="w-full sm:w-auto"
489+
>
490+
{autoDetectMutation.isPending ? '🔍 Scanning...' : '🔍 Start Auto-Detection'}
491+
</Button>
492+
</div>
493+
</div>
494+
)}
495+
334496
{/* Filters */}
335497
<div className="space-y-4">
336498
{/* Search Input - Full Width on Mobile */}

0 commit comments

Comments
 (0)