* feat: implement server color coding feature - Add color column to servers table with migration - Add SERVER_COLOR_CODING_ENABLED environment variable - Create API route for color coding toggle settings - Add color field to Server and CreateServerData types - Update database CRUD operations to handle color field - Update server API routes to handle color field - Create colorUtils.ts with contrast calculation function - Add color coding toggle to GeneralSettingsModal - Add color picker to ServerForm component (only shown when enabled) - Apply colors to InstalledScriptsTab (borders and server column) - Apply colors to ScriptInstallationCard component - Apply colors to ServerList component - Fix 'Local' display issue in installed scripts table * fix: resolve TypeScript errors in color coding implementation - Fix unsafe argument type errors in GeneralSettingsModal and ServerForm - Remove unused import in ServerList component * feat: add color-coded dropdown for server selection - Create ColorCodedDropdown component with server color indicators - Replace HTML select with custom dropdown in ExecutionModeModal - Add color dots next to server names in dropdown options - Maintain all existing functionality with improved visual design * fix: generate new execution ID for each script run - Change executionId from useState to allow updates - Generate new execution ID in startScript function for each run - Fixes issue where scripts couldn't be run multiple times without page reload - Resolves 'Script execution already running' error on subsequent runs * fix: improve whiptail handling and execution ID generation - Remove premature terminal clearing for whiptail sessions - Let whiptail handle its own display without interference - Generate new execution ID for both initial and manual script runs - Fix whiptail session state management - Should resolve blank screen and script restart issues * fix: revert problematic whiptail changes that broke terminal display - Remove complex whiptail session handling that caused blank screen - Simplify output handling to just write data directly to terminal - Keep execution ID generation fix for multiple script runs - Remove unused inWhiptailSession state variable - Terminal should now display output normally again * fix: remove remaining inWhiptailSession reference - Remove inWhiptailSession from useEffect dependency array - Fixes ReferenceError: inWhiptailSession is not defined - Terminal should now work without JavaScript errors * debug: add console logging to terminal message handling - Add debug logs to see what messages are being received - Help diagnose why terminal shows blank screen - Will remove debug logs once issue is identified * fix: prevent WebSocket reconnection loop - Remove executionId from useEffect dependency arrays - Fixes terminal constantly reconnecting and showing blank screen - WebSocket now maintains stable connection during script execution - Removes debug console logs * fix: prevent WebSocket reconnection on second script run - Remove handleMessage from useEffect dependency array - Fixes loop of START messages and connection blinking on subsequent runs - WebSocket connection now stable for multiple script executions - handleMessage recreation no longer triggers WebSocket reconnection * debug: add logging to identify WebSocket reconnection cause - Add console logs to useEffect and startScript - Track what dependencies are changing - Identify why WebSocket reconnects on second run * fix: remove isRunning from WebSocket useEffect dependencies - isRunning state change was causing WebSocket reconnection loop - Each script start changed isRunning from false to true - This triggered useEffect to reconnect WebSocket - Removing isRunning from dependencies breaks the loop - WebSocket connection now stable during script execution * feat: preselect SSH mode in execution modal and clean up debug logs - Preselect SSH execution mode by default since it's the only available option - Remove debug console logs from Terminal component - Clean up code for production readiness * fix: resolve build errors and warnings - Add missing SettingsModal import to ExecutionModeModal - Remove unused selectedMode and handleModeChange variables - Add ESLint disable comments for intentional useEffect dependency exclusions - Build now passes successfully with no errors or warnings
189 lines
6.4 KiB
TypeScript
189 lines
6.4 KiB
TypeScript
'use client';
|
|
|
|
import { useState, useEffect } from 'react';
|
|
import type { Server } from '../../types/server';
|
|
import { Button } from './ui/button';
|
|
import { ColorCodedDropdown } from './ColorCodedDropdown';
|
|
import { SettingsModal } from './SettingsModal';
|
|
|
|
|
|
interface ExecutionModeModalProps {
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
onExecute: (mode: 'local' | 'ssh', server?: Server) => void;
|
|
scriptName: string;
|
|
}
|
|
|
|
export function ExecutionModeModal({ isOpen, onClose, onExecute, scriptName }: ExecutionModeModalProps) {
|
|
const [servers, setServers] = useState<Server[]>([]);
|
|
const [loading, setLoading] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [selectedServer, setSelectedServer] = useState<Server | null>(null);
|
|
const [hasAutoExecuted, setHasAutoExecuted] = useState(false);
|
|
const [settingsModalOpen, setSettingsModalOpen] = useState(false);
|
|
|
|
useEffect(() => {
|
|
if (isOpen) {
|
|
setHasAutoExecuted(false);
|
|
void fetchServers();
|
|
}
|
|
}, [isOpen]);
|
|
|
|
// Auto-execute when exactly one server is available
|
|
useEffect(() => {
|
|
if (isOpen && !loading && servers.length === 1 && !hasAutoExecuted) {
|
|
setHasAutoExecuted(true);
|
|
onExecute('ssh', servers[0]);
|
|
onClose();
|
|
}
|
|
}, [isOpen, loading, servers, hasAutoExecuted, onExecute, onClose]);
|
|
|
|
// Refresh servers when settings modal closes
|
|
const handleSettingsModalClose = () => {
|
|
setSettingsModalOpen(false);
|
|
// Refetch servers to reflect any changes made in settings
|
|
void fetchServers();
|
|
};
|
|
|
|
const fetchServers = async () => {
|
|
setLoading(true);
|
|
setError(null);
|
|
try {
|
|
const response = await fetch('/api/servers');
|
|
if (!response.ok) {
|
|
throw new Error('Failed to fetch servers');
|
|
}
|
|
const data = await response.json();
|
|
setServers(data as Server[]);
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'An error occurred');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleExecute = () => {
|
|
if (!selectedServer) {
|
|
setError('Please select a server for SSH execution');
|
|
return;
|
|
}
|
|
|
|
onExecute('ssh', selectedServer);
|
|
onClose();
|
|
};
|
|
|
|
|
|
const handleServerSelect = (server: Server | null) => {
|
|
setSelectedServer(server);
|
|
};
|
|
|
|
|
|
if (!isOpen) return null;
|
|
|
|
return (
|
|
<>
|
|
<div className="fixed inset-0 backdrop-blur-sm bg-black/50 flex items-center justify-center z-50 p-4">
|
|
<div className="bg-card rounded-lg shadow-xl max-w-md w-full border border-border">
|
|
{/* Header */}
|
|
<div className="flex items-center justify-between p-6 border-b border-border">
|
|
<h2 className="text-xl font-bold text-foreground">Select Server</h2>
|
|
<Button
|
|
onClick={onClose}
|
|
variant="ghost"
|
|
size="icon"
|
|
className="text-muted-foreground hover:text-foreground"
|
|
>
|
|
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
|
</svg>
|
|
</Button>
|
|
</div>
|
|
|
|
{/* Content */}
|
|
<div className="p-6">
|
|
<div className="mb-6">
|
|
<h3 className="text-lg font-medium text-foreground mb-2">
|
|
Select server to execute "{scriptName}"
|
|
</h3>
|
|
</div>
|
|
|
|
{error && (
|
|
<div className="mb-4 p-3 bg-destructive/10 border border-destructive/20 rounded-md">
|
|
<div className="flex">
|
|
<div className="flex-shrink-0">
|
|
<svg className="h-5 w-5 text-destructive" viewBox="0 0 20 20" fill="currentColor">
|
|
<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" />
|
|
</svg>
|
|
</div>
|
|
<div className="ml-3">
|
|
<p className="text-sm text-destructive">{error}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Server Selection */}
|
|
<div className="mb-6">
|
|
<label htmlFor="server" className="block text-sm font-medium text-foreground mb-2">
|
|
Select Server
|
|
</label>
|
|
{loading ? (
|
|
<div className="text-center py-4">
|
|
<div className="inline-block animate-spin rounded-full h-6 w-6 border-b-2 border-primary"></div>
|
|
<p className="mt-2 text-sm text-muted-foreground">Loading servers...</p>
|
|
</div>
|
|
) : servers.length === 0 ? (
|
|
<div className="text-center py-4 text-muted-foreground">
|
|
<p className="text-sm">No servers configured</p>
|
|
<p className="text-xs mt-1">Add servers in Settings to execute scripts</p>
|
|
<Button
|
|
onClick={() => setSettingsModalOpen(true)}
|
|
variant="outline"
|
|
size="sm"
|
|
className="mt-3"
|
|
>
|
|
Open Server Settings
|
|
</Button>
|
|
</div>
|
|
) : (
|
|
<ColorCodedDropdown
|
|
servers={servers}
|
|
selectedServer={selectedServer}
|
|
onServerSelect={handleServerSelect}
|
|
placeholder="Select a server..."
|
|
/>
|
|
)}
|
|
</div>
|
|
|
|
{/* Action Buttons */}
|
|
<div className="flex justify-end space-x-3">
|
|
<Button
|
|
onClick={onClose}
|
|
variant="outline"
|
|
size="default"
|
|
>
|
|
Cancel
|
|
</Button>
|
|
<Button
|
|
onClick={handleExecute}
|
|
disabled={!selectedServer}
|
|
variant="default"
|
|
size="default"
|
|
className={!selectedServer ? 'bg-gray-400 cursor-not-allowed' : ''}
|
|
>
|
|
Run on Server
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Server Settings Modal */}
|
|
<SettingsModal
|
|
isOpen={settingsModalOpen}
|
|
onClose={handleSettingsModalClose}
|
|
/>
|
|
</>
|
|
);
|
|
}
|