Skip to content

Commit 5b45293

Browse files
feat: Add LXC Container Control Features (#124)
* feat: Add LXC container control functionality to Installed Scripts page - Add reusable ConfirmationModal component with simple and type-to-confirm variants - Add three new tRPC endpoints for container control: - getContainerStatus: Check container running/stopped state via pct status - controlContainer: Start/stop containers via pct start/stop commands - destroyContainer: Destroy containers via pct destroy and delete DB records - Enhance InstalledScriptsTab with container status management and confirmation flows - Update ScriptInstallationCard with Start/Stop and Destroy buttons for SSH scripts - Add container control buttons to desktop table view with proper status handling - Update help documentation with comprehensive container control feature guide - Implement safety features: - Simple OK/Cancel confirmation for start/stop actions - Type-to-confirm modal requiring container ID for destroy actions - SSH connection validation and error handling - Loading states and user feedback for all operations - Only show control buttons for SSH scripts with valid container IDs - Maintain backward compatibility with existing delete functionality for non-SSH scripts All container control operations execute via SSH using existing infrastructure. Real-time container status checking and caching for optimal performance. * fix: Resolve linting errors in LXC control functionality - Remove unused getStatusMutation variable - Fix floating promises by adding void operator - Add missing dependencies to useEffect hooks - Fix unsafe argument types by casting server IDs to Number - Remove unused commandOutput variables - Use useCallback for fetchContainerStatus to fix dependency issues - Move function definition before usage to resolve hoisting errors * fix: Add missing execution_mode property to InstalledScript interface in ScriptInstallationCard - Add execution_mode: local | ssh property to InstalledScript interface - Fixes TypeScript build error when checking script.execution_mode === ssh - Ensures type consistency across all components * fix: Resolve status detection conflicts by using unified bulk fetching - Remove individual fetchContainerStatus function that was conflicting with bulk fetching - Update controlContainerMutation to use fetchContainerStatuses instead of individual calls - Remove unused utils variable to clean up linting warnings - Simplify status detection to use only the bulk getContainerStatuses endpoint - This should resolve the status detection issues by eliminating competing fetch mechanisms * fix: Stop infinite API call loops that were overwhelming the server - Remove fetchContainerStatuses from useEffect dependencies to prevent infinite loops - Use useRef to access current scripts without causing dependency cycles - Reduce multiple useEffect hooks that were all triggering status checks - This should stop the 30+ simultaneous API calls that were redlining the server - Status checks now happen only when needed: on load, after operations, and every 60s * feat: Implement efficient pct list approach for container status checking - Replace individual container status checks with bulk pct list per server - Update getContainerStatuses to run pct list once per server and parse all results - Simplify frontend to just pass server IDs instead of individual container data - Much more efficient: 1 SSH call per server instead of 1 call per container - Parse pct list output format: CTID Status Name - Map pct list status (running/stopped) to our status format - This should resolve the server overload issues while maintaining functionality * fix: Remove duplicate container status display from STATUS column - Remove container runtime status from STATUS column in both desktop and mobile views - Keep container status display next to container ID where it belongs - STATUS column now only shows installation status (SUCCESS/FAILED) - Container runtime status (running/stopped) remains next to container ID - Cleaner UI with no duplicate status information * feat: Trigger status check when switching to installed scripts tab - Add useEffect hook that triggers fetchContainerStatuses when component mounts - This ensures container statuses are refreshed every time user switches to the tab - Improves user experience by always showing current container states - Uses empty dependency array to run only once per tab switch * cleanup: Remove all console.log statements from codebase - Remove console.log statements from InstalledScriptsTab.tsx - Remove console.log statements from installedScripts.ts router - Remove console.log statements from VersionDisplay.tsx - Remove console.log statements from ScriptsGrid.tsx - Keep console.error statements for proper error logging - Cleaner production logs without debug output * feat: Display detailed SSH error messages for container operations - Capture both stdout and stderr from pct start/stop/destroy commands - Show actual SSH error output to users instead of generic error messages - Update controlContainer and destroyContainer to return detailed error messages - Improve frontend error handling to display backend error messages - Users now see specific error details like permission denied, container not found, etc. - Better debugging experience with meaningful error feedback * feat: Auto-stop containers before destroy and improve error UI - Automatically stop running containers before destroying them - Create custom ErrorModal component to replace ugly browser alerts - Support both error and success modal types with appropriate styling - Show detailed SSH error messages in a beautiful modal interface - Update destroy success message to indicate if container was stopped first - Better UX with consistent design language and proper error handling - Auto-close modals after 10 seconds for better user experience * fix: Replace dialog component with custom modal implementation - Remove dependency on non-existent dialog component - Use same modal pattern as ConfirmationModal for consistency - Custom modal with backdrop, proper styling, and responsive design - Maintains all functionality while fixing module resolution error - Consistent with existing codebase patterns * feat: Add instant success feedback for container start/stop operations - Show success modal immediately after start/stop operations - Update container status in UI instantly before background status check - Prevents user confusion by showing expected status change immediately - Add containerId to backend response for proper script identification - Success modals show appropriate messages for start vs stop operations - Background status check still runs to ensure accuracy - Better UX with instant visual feedback * fix: Improve Container Control section styling in help modal - Replace bright red styling with subtle accent colors - Use consistent design language that matches the rest of the interface - Change safety features from red to yellow warning styling - Better visual hierarchy and readability - Maintains warning importance while being less jarring * fix: Make safety features section much more subtle in help modal - Replace bright yellow with muted background colors - Use standard text colors (text-foreground, text-muted-foreground) - Maintains warning icon but with consistent styling - Much less jarring against dark theme - Better integration with overall design language * feat: Replace update script alerts with custom confirmation modal - Replace browser alert() with custom ErrorModal for validation errors - Replace browser confirm() with custom ConfirmationModal for update confirmation - Add type-to-confirm safety feature requiring container ID input - Include data loss warning and backup recommendation in confirmation message - Consistent UI/UX with other confirmation dialogs - Better error messaging with detailed information * fix: Resolve all build errors and warnings - Fix nullish coalescing operator warnings (|| to ??) - Remove unused imports and variables - Fix TypeScript type errors with proper casting - Update ConfirmationModal state type to include missing properties - Fix useEffect dependency warnings - All build errors resolved, only minor unused variable warning remains - Build now passes successfully * feat: Disable update button when container is stopped - Add disabled condition to update button in table view - Add disabled condition to update button in mobile card view - Prevents users from updating stopped containers - Uses containerStatus to determine if button should be disabled - Improves UX by preventing invalid operations on stopped containers * fix: Resolve infinite loop in status updates - Remove containerStatusMutation from fetchContainerStatuses dependencies - Use empty dependency array for fetchContainerStatuses useCallback - Remove fetchContainerStatuses from useEffect dependencies - Only depend on scripts.length to prevent infinite loops - Status updates now run only when scripts change, not on every render * fix: Correct misleading text in update confirmation modal - Change "will re-run the script installation process" to "will update the script" - More accurate description of what the update operation actually does - Maintains warning about potential container impact and backup recommendation - Better user understanding of the actual operation being performed * refactor: Remove all comments from InstalledScriptsTab.tsx - Remove all single-line comments (//) - Remove all multi-line comments (/* */) - Clean up excessive empty lines - Improve code readability and reduce file size - Maintain all functionality while removing documentation comments * refactor: Improve code organization and add comprehensive comments - Add clear section comments for better code organization - Document all major state variables and their purposes - Add detailed comments for complex logic and operations - Improve readability with better spacing and structure - Maintain all existing functionality while improving maintainability - Add comments for container control, mutations, and UI sections
1 parent 53b5074 commit 5b45293

File tree

8 files changed

+1005
-276
lines changed

8 files changed

+1005
-276
lines changed
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
'use client';
2+
3+
import { useState } from 'react';
4+
import { Button } from './ui/button';
5+
import { AlertTriangle, Info } from 'lucide-react';
6+
7+
interface ConfirmationModalProps {
8+
isOpen: boolean;
9+
onClose: () => void;
10+
onConfirm: () => void;
11+
title: string;
12+
message: string;
13+
variant: 'simple' | 'danger';
14+
confirmText?: string; // What the user must type for danger variant
15+
confirmButtonText?: string;
16+
cancelButtonText?: string;
17+
}
18+
19+
export function ConfirmationModal({
20+
isOpen,
21+
onClose,
22+
onConfirm,
23+
title,
24+
message,
25+
variant,
26+
confirmText,
27+
confirmButtonText = 'Confirm',
28+
cancelButtonText = 'Cancel'
29+
}: ConfirmationModalProps) {
30+
const [typedText, setTypedText] = useState('');
31+
32+
if (!isOpen) return null;
33+
34+
const isDanger = variant === 'danger';
35+
const isConfirmEnabled = isDanger ? typedText === confirmText : true;
36+
37+
const handleConfirm = () => {
38+
if (isConfirmEnabled) {
39+
onConfirm();
40+
setTypedText(''); // Reset for next time
41+
}
42+
};
43+
44+
const handleClose = () => {
45+
onClose();
46+
setTypedText(''); // Reset when closing
47+
};
48+
49+
return (
50+
<div className="fixed inset-0 backdrop-blur-sm bg-black/50 flex items-center justify-center z-50 p-4">
51+
<div className="bg-card rounded-lg shadow-xl max-w-md w-full border border-border">
52+
{/* Header */}
53+
<div className="flex items-center justify-center p-6 border-b border-border">
54+
<div className="flex items-center gap-3">
55+
{isDanger ? (
56+
<AlertTriangle className="h-8 w-8 text-red-600" />
57+
) : (
58+
<Info className="h-8 w-8 text-blue-600" />
59+
)}
60+
<h2 className="text-2xl font-bold text-card-foreground">{title}</h2>
61+
</div>
62+
</div>
63+
64+
{/* Content */}
65+
<div className="p-6">
66+
<p className="text-sm text-muted-foreground mb-6">
67+
{message}
68+
</p>
69+
70+
{/* Type-to-confirm input for danger variant */}
71+
{isDanger && confirmText && (
72+
<div className="mb-6">
73+
<label className="block text-sm font-medium text-foreground mb-2">
74+
Type <code className="bg-muted px-2 py-1 rounded text-sm">{confirmText}</code> to confirm:
75+
</label>
76+
<input
77+
type="text"
78+
value={typedText}
79+
onChange={(e) => setTypedText(e.target.value)}
80+
className="w-full px-3 py-2 border border-input rounded-md bg-background text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:border-ring"
81+
placeholder={`Type "${confirmText}" here`}
82+
autoComplete="off"
83+
/>
84+
</div>
85+
)}
86+
87+
{/* Action Buttons */}
88+
<div className="flex flex-col sm:flex-row justify-end gap-3">
89+
<Button
90+
onClick={handleClose}
91+
variant="outline"
92+
size="default"
93+
className="w-full sm:w-auto"
94+
>
95+
{cancelButtonText}
96+
</Button>
97+
<Button
98+
onClick={handleConfirm}
99+
disabled={!isConfirmEnabled}
100+
variant={isDanger ? "destructive" : "default"}
101+
size="default"
102+
className="w-full sm:w-auto"
103+
>
104+
{confirmButtonText}
105+
</Button>
106+
</div>
107+
</div>
108+
</div>
109+
</div>
110+
);
111+
}

src/app/_components/ErrorModal.tsx

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
'use client';
2+
3+
import { useEffect } from 'react';
4+
import { Button } from './ui/button';
5+
import { AlertCircle, CheckCircle } from 'lucide-react';
6+
7+
interface ErrorModalProps {
8+
isOpen: boolean;
9+
onClose: () => void;
10+
title: string;
11+
message: string;
12+
details?: string;
13+
type?: 'error' | 'success';
14+
}
15+
16+
export function ErrorModal({
17+
isOpen,
18+
onClose,
19+
title,
20+
message,
21+
details,
22+
type = 'error'
23+
}: ErrorModalProps) {
24+
// Auto-close after 10 seconds
25+
useEffect(() => {
26+
if (isOpen) {
27+
const timer = setTimeout(() => {
28+
onClose();
29+
}, 10000);
30+
return () => clearTimeout(timer);
31+
}
32+
}, [isOpen, onClose]);
33+
34+
if (!isOpen) return null;
35+
36+
return (
37+
<div className="fixed inset-0 backdrop-blur-sm bg-black/50 flex items-center justify-center z-50 p-4">
38+
<div className="bg-card rounded-lg shadow-xl max-w-lg w-full border border-border">
39+
{/* Header */}
40+
<div className="flex items-center justify-center p-6 border-b border-border">
41+
<div className="flex items-center gap-3">
42+
{type === 'success' ? (
43+
<CheckCircle className="h-8 w-8 text-green-600 dark:text-green-400" />
44+
) : (
45+
<AlertCircle className="h-8 w-8 text-red-600 dark:text-red-400" />
46+
)}
47+
<h2 className="text-xl font-semibold text-foreground">{title}</h2>
48+
</div>
49+
</div>
50+
51+
{/* Content */}
52+
<div className="p-6">
53+
<p className="text-sm text-foreground mb-4">{message}</p>
54+
{details && (
55+
<div className={`rounded-lg p-3 ${
56+
type === 'success'
57+
? 'bg-green-50 dark:bg-green-950/20 border border-green-200 dark:border-green-800'
58+
: 'bg-red-50 dark:bg-red-950/20 border border-red-200 dark:border-red-800'
59+
}`}>
60+
<p className={`text-xs font-medium mb-1 ${
61+
type === 'success'
62+
? 'text-green-800 dark:text-green-200'
63+
: 'text-red-800 dark:text-red-200'
64+
}`}>
65+
{type === 'success' ? 'Details:' : 'Error Details:'}
66+
</p>
67+
<pre className={`text-xs whitespace-pre-wrap break-words ${
68+
type === 'success'
69+
? 'text-green-700 dark:text-green-300'
70+
: 'text-red-700 dark:text-red-300'
71+
}`}>
72+
{details}
73+
</pre>
74+
</div>
75+
)}
76+
</div>
77+
78+
{/* Footer */}
79+
<div className="flex justify-end gap-3 p-6 border-t border-border">
80+
<Button variant="outline" onClick={onClose}>
81+
Close
82+
</Button>
83+
</div>
84+
</div>
85+
</div>
86+
);
87+
}

src/app/_components/HelpModal.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,29 @@ export function HelpModal({ isOpen, onClose, initialSection = 'server-settings'
334334
<li><strong>Update Scripts:</strong> Re-run or update existing script installations</li>
335335
</ul>
336336
</div>
337+
338+
<div className="p-4 border border-border rounded-lg bg-accent/50 dark:bg-accent/20">
339+
<h4 className="font-medium text-foreground mb-2">Container Control (NEW)</h4>
340+
<p className="text-sm text-muted-foreground mb-3">
341+
Directly control LXC containers from the installed scripts page via SSH.
342+
</p>
343+
<ul className="text-sm text-muted-foreground space-y-2">
344+
<li><strong>Start/Stop Button:</strong> Control container state with <code>pct start/stop &lt;ID&gt;</code></li>
345+
<li><strong>Container Status:</strong> Real-time status indicator (running/stopped/unknown)</li>
346+
<li><strong>Destroy Button:</strong> Permanently remove LXC container with <code>pct destroy &lt;ID&gt;</code></li>
347+
<li><strong>Confirmation Modals:</strong> Simple OK/Cancel for start/stop, type container ID to confirm destroy</li>
348+
<li><strong>SSH Execution:</strong> All commands executed remotely via configured SSH connections</li>
349+
</ul>
350+
<div className="mt-3 p-3 bg-muted/30 dark:bg-muted/20 rounded-lg border border-border">
351+
<p className="text-sm font-medium text-foreground">⚠️ Safety Features:</p>
352+
<ul className="text-sm text-muted-foreground mt-1 space-y-1">
353+
<li>• Start/Stop actions require simple confirmation</li>
354+
<li>• Destroy action requires typing the container ID to confirm</li>
355+
<li>• All actions show loading states and error handling</li>
356+
<li>• Only works with SSH scripts that have valid container IDs</li>
357+
</ul>
358+
</div>
359+
</div>
337360
</div>
338361
</div>
339362
);

0 commit comments

Comments
 (0)