diff --git a/src/app/_components/ConfirmationModal.tsx b/src/app/_components/ConfirmationModal.tsx
new file mode 100644
index 0000000..5314ced
--- /dev/null
+++ b/src/app/_components/ConfirmationModal.tsx
@@ -0,0 +1,111 @@
+'use client';
+
+import { useState } from 'react';
+import { Button } from './ui/button';
+import { AlertTriangle, Info } from 'lucide-react';
+
+interface ConfirmationModalProps {
+ isOpen: boolean;
+ onClose: () => void;
+ onConfirm: () => void;
+ title: string;
+ message: string;
+ variant: 'simple' | 'danger';
+ confirmText?: string; // What the user must type for danger variant
+ confirmButtonText?: string;
+ cancelButtonText?: string;
+}
+
+export function ConfirmationModal({
+ isOpen,
+ onClose,
+ onConfirm,
+ title,
+ message,
+ variant,
+ confirmText,
+ confirmButtonText = 'Confirm',
+ cancelButtonText = 'Cancel'
+}: ConfirmationModalProps) {
+ const [typedText, setTypedText] = useState('');
+
+ if (!isOpen) return null;
+
+ const isDanger = variant === 'danger';
+ const isConfirmEnabled = isDanger ? typedText === confirmText : true;
+
+ const handleConfirm = () => {
+ if (isConfirmEnabled) {
+ onConfirm();
+ setTypedText(''); // Reset for next time
+ }
+ };
+
+ const handleClose = () => {
+ onClose();
+ setTypedText(''); // Reset when closing
+ };
+
+ return (
+
+
+ {/* Header */}
+
+
+ {isDanger ? (
+
+ ) : (
+
+ )}
+
{title}
+
+
+
+ {/* Content */}
+
+
+ {message}
+
+
+ {/* Type-to-confirm input for danger variant */}
+ {isDanger && confirmText && (
+
+
+ setTypedText(e.target.value)}
+ 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"
+ placeholder={`Type "${confirmText}" here`}
+ autoComplete="off"
+ />
+
+ )}
+
+ {/* Action Buttons */}
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/app/_components/ErrorModal.tsx b/src/app/_components/ErrorModal.tsx
new file mode 100644
index 0000000..f0a82d4
--- /dev/null
+++ b/src/app/_components/ErrorModal.tsx
@@ -0,0 +1,87 @@
+'use client';
+
+import { useEffect } from 'react';
+import { Button } from './ui/button';
+import { AlertCircle, CheckCircle } from 'lucide-react';
+
+interface ErrorModalProps {
+ isOpen: boolean;
+ onClose: () => void;
+ title: string;
+ message: string;
+ details?: string;
+ type?: 'error' | 'success';
+}
+
+export function ErrorModal({
+ isOpen,
+ onClose,
+ title,
+ message,
+ details,
+ type = 'error'
+}: ErrorModalProps) {
+ // Auto-close after 10 seconds
+ useEffect(() => {
+ if (isOpen) {
+ const timer = setTimeout(() => {
+ onClose();
+ }, 10000);
+ return () => clearTimeout(timer);
+ }
+ }, [isOpen, onClose]);
+
+ if (!isOpen) return null;
+
+ return (
+
+
+ {/* Header */}
+
+
+ {type === 'success' ? (
+
+ ) : (
+
+ )}
+
{title}
+
+
+
+ {/* Content */}
+
+
{message}
+ {details && (
+
+
+ {type === 'success' ? 'Details:' : 'Error Details:'}
+
+
+ {details}
+
+
+ )}
+
+
+ {/* Footer */}
+
+
+
+
+
+ );
+}
diff --git a/src/app/_components/HelpModal.tsx b/src/app/_components/HelpModal.tsx
index 3c3fd9f..4b02af3 100644
--- a/src/app/_components/HelpModal.tsx
+++ b/src/app/_components/HelpModal.tsx
@@ -334,6 +334,29 @@ export function HelpModal({ isOpen, onClose, initialSection = 'server-settings'
• Update Scripts: Re-run or update existing script installations
+
+
+
Container Control (NEW)
+
+ Directly control LXC containers from the installed scripts page via SSH.
+
+
+ - • Start/Stop Button: Control container state with
pct start/stop <ID>
+ - • Container Status: Real-time status indicator (running/stopped/unknown)
+ - • Destroy Button: Permanently remove LXC container with
pct destroy <ID>
+ - • Confirmation Modals: Simple OK/Cancel for start/stop, type container ID to confirm destroy
+ - • SSH Execution: All commands executed remotely via configured SSH connections
+
+
+
⚠️ Safety Features:
+
+ - • Start/Stop actions require simple confirmation
+ - • Destroy action requires typing the container ID to confirm
+ - • All actions show loading states and error handling
+ - • Only works with SSH scripts that have valid container IDs
+
+
+
);
diff --git a/src/app/_components/InstalledScriptsTab.tsx b/src/app/_components/InstalledScriptsTab.tsx
index 9703ea1..1169b7e 100644
--- a/src/app/_components/InstalledScriptsTab.tsx
+++ b/src/app/_components/InstalledScriptsTab.tsx
@@ -6,6 +6,8 @@ import { Terminal } from './Terminal';
import { StatusBadge } from './Badge';
import { Button } from './ui/button';
import { ScriptInstallationCard } from './ScriptInstallationCard';
+import { ConfirmationModal } from './ConfirmationModal';
+import { ErrorModal } from './ErrorModal';
import { getContrastColor } from '../../lib/colorUtils';
interface InstalledScript {
@@ -22,6 +24,7 @@ interface InstalledScript {
installation_date: string;
status: 'in_progress' | 'success' | 'failed';
output_log: string | null;
+ execution_mode: 'local' | 'ssh';
container_status?: 'running' | 'stopped' | 'unknown';
}
@@ -41,7 +44,30 @@ export function InstalledScriptsTab() {
const [autoDetectStatus, setAutoDetectStatus] = useState<{ type: 'success' | 'error' | null; message: string }>({ type: null, message: '' });
const [cleanupStatus, setCleanupStatus] = useState<{ type: 'success' | 'error' | null; message: string }>({ type: null, message: '' });
const cleanupRunRef = useRef(false);
- const [containerStatuses, setContainerStatuses] = useState>({});
+
+ // Container control state
+ const [containerStatuses, setContainerStatuses] = useState