feat: auto-select single server and remove local execution (#102)
* feat: auto-select single server and remove local execution - Remove local execution option entirely, all scripts now execute via SSH - Auto-select and skip modal when exactly one server is configured - Add server settings button when no servers are configured - Auto-refresh server list when settings modal closes - Update modal title from 'Execution Mode' to 'Select Server' * fix: remove debug messages from WebSocket output - Remove console.log for WebSocket messages in Terminal component - Remove debug output from SSH command execution in installedScripts router - Clean up command output chunk logging and config data logging - Remove container check result debug messages - This eliminates unwanted debug messages appearing in terminal output
This commit is contained in:
committed by
GitHub
parent
d819cd79fe
commit
aa9e155b0c
@@ -3,6 +3,7 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import type { Server } from '../../types/server';
|
||||
import { Button } from './ui/button';
|
||||
import { SettingsModal } from './SettingsModal';
|
||||
|
||||
interface ExecutionModeModalProps {
|
||||
isOpen: boolean;
|
||||
@@ -15,15 +16,33 @@ export function ExecutionModeModal({ isOpen, onClose, onExecute, scriptName }: E
|
||||
const [servers, setServers] = useState<Server[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [selectedMode, setSelectedMode] = useState<'local' | 'ssh'>('local');
|
||||
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);
|
||||
@@ -42,110 +61,60 @@ export function ExecutionModeModal({ isOpen, onClose, onExecute, scriptName }: E
|
||||
};
|
||||
|
||||
const handleExecute = () => {
|
||||
if (selectedMode === 'ssh' && !selectedServer) {
|
||||
if (!selectedServer) {
|
||||
setError('Please select a server for SSH execution');
|
||||
return;
|
||||
}
|
||||
|
||||
onExecute(selectedMode, selectedServer ?? undefined);
|
||||
onExecute('ssh', selectedServer);
|
||||
onClose();
|
||||
};
|
||||
|
||||
const handleModeChange = (mode: 'local' | 'ssh') => {
|
||||
setSelectedMode(mode);
|
||||
if (mode === 'local') {
|
||||
setSelectedServer(null);
|
||||
}
|
||||
};
|
||||
|
||||
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">Execution Mode</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">
|
||||
Where would you like 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>
|
||||
)}
|
||||
|
||||
{/* Execution Mode Selection */}
|
||||
<div className="space-y-4 mb-6">
|
||||
|
||||
|
||||
{/* SSH Execution */}
|
||||
<div
|
||||
className={`border rounded-lg p-4 cursor-pointer transition-colors ${
|
||||
selectedMode === 'ssh'
|
||||
? 'border-primary bg-primary/10'
|
||||
: 'border-border hover:border-primary/50'
|
||||
}`}
|
||||
onClick={() => handleModeChange('ssh')}
|
||||
<>
|
||||
<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"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
type="radio"
|
||||
id="ssh"
|
||||
name="executionMode"
|
||||
value="ssh"
|
||||
checked={selectedMode === 'ssh'}
|
||||
onChange={() => handleModeChange('ssh')}
|
||||
className="h-4 w-4 text-primary focus:ring-primary border-border"
|
||||
/>
|
||||
<label htmlFor="ssh" className="ml-3 flex-1 cursor-pointer">
|
||||
<div className="flex items-center">
|
||||
<div className="flex-shrink-0">
|
||||
<div className="w-10 h-10 bg-primary/10 rounded-full flex items-center justify-center">
|
||||
<svg className="w-6 h-6 text-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-3">
|
||||
<h4 className="text-sm font-medium text-foreground">SSH Execution</h4>
|
||||
<p className="text-sm text-muted-foreground">Run the script on a remote server</p>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
|
||||
{/* Server Selection (only for SSH mode) */}
|
||||
{selectedMode === 'ssh' && (
|
||||
{/* 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
|
||||
@@ -158,7 +127,15 @@ export function ExecutionModeModal({ isOpen, onClose, onExecute, scriptName }: E
|
||||
) : 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 use SSH execution</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>
|
||||
) : (
|
||||
<select
|
||||
@@ -180,29 +157,35 @@ export function ExecutionModeModal({ isOpen, onClose, onExecute, scriptName }: E
|
||||
</select>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="flex justify-end space-x-3">
|
||||
<Button
|
||||
onClick={onClose}
|
||||
variant="outline"
|
||||
size="default"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleExecute}
|
||||
disabled={selectedMode === 'ssh' && !selectedServer}
|
||||
variant="default"
|
||||
size="default"
|
||||
className={selectedMode === 'ssh' && !selectedServer ? 'bg-gray-400 cursor-not-allowed' : ''}
|
||||
>
|
||||
{selectedMode === 'local' ? 'Run Locally' : 'Run on Server'}
|
||||
</Button>
|
||||
{/* 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>
|
||||
</div>
|
||||
|
||||
{/* Server Settings Modal */}
|
||||
<SettingsModal
|
||||
isOpen={settingsModalOpen}
|
||||
onClose={handleSettingsModalClose}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -314,7 +314,6 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate
|
||||
ws.onmessage = (event) => {
|
||||
try {
|
||||
const message = JSON.parse(event.data as string) as TerminalMessage;
|
||||
console.log('WebSocket message received:', message);
|
||||
handleMessage(message);
|
||||
} catch (error) {
|
||||
console.error('Error parsing WebSocket message:', error);
|
||||
|
||||
@@ -268,7 +268,6 @@ export const installedScriptsRouter = createTRPCRouter({
|
||||
server as any,
|
||||
command,
|
||||
(data: string) => {
|
||||
console.log('Command output chunk:', data);
|
||||
commandOutput += data;
|
||||
},
|
||||
(error: string) => {
|
||||
@@ -276,7 +275,6 @@ export const installedScriptsRouter = createTRPCRouter({
|
||||
},
|
||||
(exitCode: number) => {
|
||||
console.log('Command exit code:', exitCode);
|
||||
console.log('Full command output:', commandOutput);
|
||||
|
||||
// Parse the complete output to get config file paths that contain community-script tag
|
||||
const configFiles = commandOutput.split('\n')
|
||||
@@ -306,8 +304,6 @@ export const installedScriptsRouter = createTRPCRouter({
|
||||
server as any,
|
||||
readCommand,
|
||||
(configData: string) => {
|
||||
console.log('Config data for', containerId, ':', configData.substring(0, 300) + '...');
|
||||
|
||||
// Parse config file for hostname
|
||||
const lines = configData.split('\n');
|
||||
let hostname = '';
|
||||
@@ -316,7 +312,6 @@ export const installedScriptsRouter = createTRPCRouter({
|
||||
const trimmedLine = line.trim();
|
||||
if (trimmedLine.startsWith('hostname:')) {
|
||||
hostname = trimmedLine.substring(9).trim();
|
||||
console.log('Found hostname for', containerId, ':', hostname);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -368,7 +363,6 @@ export const installedScriptsRouter = createTRPCRouter({
|
||||
|
||||
// Get existing scripts to check for duplicates
|
||||
const existingScripts = db.getAllInstalledScripts();
|
||||
console.log('Existing scripts in database:', existingScripts.length);
|
||||
|
||||
// Create installed script records for detected containers (skip duplicates)
|
||||
const createdScripts = [];
|
||||
@@ -504,7 +498,6 @@ export const installedScriptsRouter = createTRPCRouter({
|
||||
server as any,
|
||||
checkCommand,
|
||||
(data: string) => {
|
||||
console.log(`Container check result for ${scriptData.script_name}:`, data.trim());
|
||||
resolve(data.trim() === 'exists');
|
||||
},
|
||||
(error: string) => {
|
||||
|
||||
Reference in New Issue
Block a user