Skip to content

Commit a3e2108

Browse files
feat: Add automatic cleanup and duplicate prevention for LXC auto-detection
- Add automatic cleanup of orphaned LXC container scripts on tab load - Implement duplicate checking to prevent re-adding existing scripts - Replace flashy blue messages with subtle slate color scheme - Add comprehensive status messages for cleanup and auto-detection - Fix all ESLint errors and warnings - Improve user experience with non-intrusive feedback - Add detailed logging for debugging cleanup process - Support both success and error states with appropriate styling
1 parent 90bbfac commit a3e2108

File tree

2 files changed

+292
-62
lines changed

2 files changed

+292
-62
lines changed

src/app/_components/InstalledScriptsTab.tsx

Lines changed: 125 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client';
22

3-
import { useState } from 'react';
3+
import { useState, useEffect, useRef } from 'react';
44
import { api } from '~/trpc/react';
55
import { Terminal } from './Terminal';
66
import { StatusBadge } from './Badge';
@@ -34,6 +34,8 @@ export function InstalledScriptsTab() {
3434
const [showAutoDetectForm, setShowAutoDetectForm] = useState(false);
3535
const [autoDetectServerId, setAutoDetectServerId] = useState<string>('');
3636
const [autoDetectStatus, setAutoDetectStatus] = useState<{ type: 'success' | 'error' | null; message: string }>({ type: null, message: '' });
37+
const [cleanupStatus, setCleanupStatus] = useState<{ type: 'success' | 'error' | null; message: string }>({ type: null, message: '' });
38+
const cleanupRunRef = useRef(false);
3739

3840
// Fetch installed scripts
3941
const { data: scriptsData, refetch: refetchScripts, isLoading } = api.installedScripts.getAllInstalledScripts.useQuery();
@@ -78,34 +80,80 @@ export function InstalledScriptsTab() {
7880
void refetchScripts();
7981
setShowAutoDetectForm(false);
8082
setAutoDetectServerId('');
83+
84+
// Show detailed message about what was added/skipped
85+
let statusMessage = data.message ?? 'Auto-detection completed successfully!';
86+
if (data.skippedContainers && data.skippedContainers.length > 0) {
87+
const skippedNames = data.skippedContainers.map((c: any) => String(c.hostname)).join(', ');
88+
statusMessage += ` Skipped duplicates: ${skippedNames}`;
89+
}
90+
8191
setAutoDetectStatus({
8292
type: 'success',
83-
message: data.message || 'Auto-detection completed successfully!'
93+
message: statusMessage
8494
});
85-
// Clear status after 5 seconds
86-
setTimeout(() => setAutoDetectStatus({ type: null, message: '' }), 5000);
95+
// Clear status after 8 seconds (longer for detailed info)
96+
setTimeout(() => setAutoDetectStatus({ type: null, message: '' }), 8000);
8797
},
8898
onError: (error) => {
8999
console.error('Auto-detect mutation error:', error);
90100
console.error('Error details:', {
91101
message: error.message,
92-
cause: error.cause,
93-
stack: error.stack,
94102
data: error.data
95103
});
96104
setAutoDetectStatus({
97105
type: 'error',
98-
message: error.message || 'Auto-detection failed. Please try again.'
106+
message: error.message ?? 'Auto-detection failed. Please try again.'
99107
});
100108
// Clear status after 5 seconds
101109
setTimeout(() => setAutoDetectStatus({ type: null, message: '' }), 5000);
102110
}
103111
});
104112

113+
// Cleanup orphaned scripts mutation
114+
const cleanupMutation = api.installedScripts.cleanupOrphanedScripts.useMutation({
115+
onSuccess: (data) => {
116+
console.log('Cleanup success:', data);
117+
void refetchScripts();
118+
119+
if (data.deletedCount > 0) {
120+
setCleanupStatus({
121+
type: 'success',
122+
message: `Cleanup completed! Removed ${data.deletedCount} orphaned script(s): ${data.deletedScripts.join(', ')}`
123+
});
124+
} else {
125+
setCleanupStatus({
126+
type: 'success',
127+
message: 'Cleanup completed! No orphaned scripts found.'
128+
});
129+
}
130+
// Clear status after 8 seconds (longer for cleanup info)
131+
setTimeout(() => setCleanupStatus({ type: null, message: '' }), 8000);
132+
},
133+
onError: (error) => {
134+
console.error('Cleanup mutation error:', error);
135+
setCleanupStatus({
136+
type: 'error',
137+
message: error.message ?? 'Cleanup failed. Please try again.'
138+
});
139+
// Clear status after 5 seconds
140+
setTimeout(() => setCleanupStatus({ type: null, message: '' }), 5000);
141+
}
142+
});
143+
105144

106145
const scripts: InstalledScript[] = (scriptsData?.scripts as InstalledScript[]) ?? [];
107146
const stats = statsData?.stats;
108147

148+
// Run cleanup when component mounts and scripts are loaded (only once)
149+
useEffect(() => {
150+
if (scripts.length > 0 && serversData?.servers && !cleanupMutation.isPending && !cleanupRunRef.current) {
151+
console.log('Running automatic cleanup check...');
152+
cleanupRunRef.current = true;
153+
void cleanupMutation.mutate();
154+
}
155+
}, [scripts.length, serversData?.servers, cleanupMutation]);
156+
109157
// Filter scripts based on search and filters
110158
const filteredScripts = scripts.filter((script: InstalledScript) => {
111159
const matchesSearch = script.script_name.toLowerCase().includes(searchTerm.toLowerCase()) ||
@@ -391,60 +439,97 @@ export function InstalledScriptsTab() {
391439
</div>
392440
)}
393441

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-
)}
442+
{/* Status Messages */}
443+
{(autoDetectStatus.type ?? cleanupStatus.type) && (
444+
<div className="mb-4 space-y-2">
445+
{/* Auto-Detect Status Message */}
446+
{autoDetectStatus.type && (
447+
<div className={`p-4 rounded-lg border ${
448+
autoDetectStatus.type === 'success'
449+
? 'bg-green-50 dark:bg-green-950/20 border-green-200 dark:border-green-800'
450+
: 'bg-red-50 dark:bg-red-950/20 border-red-200 dark:border-red-800'
451+
}`}>
452+
<div className="flex items-center">
453+
<div className="flex-shrink-0">
454+
{autoDetectStatus.type === 'success' ? (
455+
<svg className="h-5 w-5 text-green-400" viewBox="0 0 20 20" fill="currentColor">
456+
<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" />
457+
</svg>
458+
) : (
459+
<svg className="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor">
460+
<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" />
461+
</svg>
462+
)}
463+
</div>
464+
<div className="ml-3">
465+
<p className={`text-sm font-medium ${
466+
autoDetectStatus.type === 'success'
467+
? 'text-green-800 dark:text-green-200'
468+
: 'text-red-800 dark:text-red-200'
469+
}`}>
470+
{autoDetectStatus.message}
471+
</p>
472+
</div>
473+
</div>
412474
</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>
475+
)}
476+
477+
{/* Cleanup Status Message */}
478+
{cleanupStatus.type && (
479+
<div className={`p-4 rounded-lg border ${
480+
cleanupStatus.type === 'success'
481+
? 'bg-slate-50 dark:bg-slate-900/50 border-slate-200 dark:border-slate-700'
482+
: 'bg-red-50 dark:bg-red-950/20 border-red-200 dark:border-red-800'
483+
}`}>
484+
<div className="flex items-center">
485+
<div className="flex-shrink-0">
486+
{cleanupStatus.type === 'success' ? (
487+
<svg className="h-5 w-5 text-slate-500 dark:text-slate-400" viewBox="0 0 20 20" fill="currentColor">
488+
<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" />
489+
</svg>
490+
) : (
491+
<svg className="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor">
492+
<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" />
493+
</svg>
494+
)}
495+
</div>
496+
<div className="ml-3">
497+
<p className={`text-sm font-medium ${
498+
cleanupStatus.type === 'success'
499+
? 'text-slate-700 dark:text-slate-300'
500+
: 'text-red-800 dark:text-red-200'
501+
}`}>
502+
{cleanupStatus.message}
503+
</p>
504+
</div>
505+
</div>
421506
</div>
422-
</div>
507+
)}
423508
</div>
424509
)}
425510

426511
{/* Auto-Detect LXC Containers Form */}
427512
{showAutoDetectForm && (
428513
<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>
514+
<h3 className="text-lg font-semibold text-foreground mb-4 sm:mb-6">Auto-Detect LXC Containers (Must contain a tag with &quot;community-script&quot;)</h3>
430515
<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">
516+
<div className="bg-slate-50 dark:bg-slate-900/30 border border-slate-200 dark:border-slate-700 rounded-lg p-4">
432517
<div className="flex items-start">
433518
<div className="flex-shrink-0">
434-
<svg className="h-5 w-5 text-blue-400" viewBox="0 0 20 20" fill="currentColor">
519+
<svg className="h-5 w-5 text-slate-500 dark:text-slate-400" viewBox="0 0 20 20" fill="currentColor">
435520
<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" />
436521
</svg>
437522
</div>
438523
<div className="ml-3">
439-
<h4 className="text-sm font-medium text-blue-800 dark:text-blue-200">
524+
<h4 className="text-sm font-medium text-slate-700 dark:text-slate-300">
440525
How it works
441526
</h4>
442-
<div className="mt-2 text-sm text-blue-700 dark:text-blue-300">
527+
<div className="mt-2 text-sm text-slate-600 dark:text-slate-400">
443528
<p>This feature will:</p>
444529
<ul className="list-disc list-inside mt-1 space-y-1">
445530
<li>Connect to the selected server via SSH</li>
446531
<li>Scan all LXC config files in /etc/pve/lxc/</li>
447-
<li>Find containers with "community-script" in their tags</li>
532+
<li>Find containers with &quot;community-script&quot; in their tags</li>
448533
<li>Extract the container ID and hostname</li>
449534
<li>Add them as installed script entries</li>
450535
</ul>

0 commit comments

Comments
 (0)