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:
Michel Roegl-Brunner
2025-10-10 13:26:24 +02:00
committed by GitHub
parent d819cd79fe
commit aa9e155b0c
3 changed files with 96 additions and 121 deletions

View File

@@ -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 &quot;{scriptName}&quot;?
</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 &quot;{scriptName}&quot;
</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}
/>
</>
);
}

View File

@@ -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);

View File

@@ -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) => {