Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
201 changes: 92 additions & 109 deletions src/app/_components/ExecutionModeModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
Expand All @@ -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 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>

{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>
{/* 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>
)}

{/* 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="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>
{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>
</label>
</div>
</div>
</div>
</div>
)}

{/* Server Selection (only for SSH mode) */}
{selectedMode === 'ssh' && (
{/* Server Selection */}
<div className="mb-6">
<label htmlFor="server" className="block text-sm font-medium text-foreground mb-2">
Select Server
Expand All @@ -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
Expand All @@ -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}
/>
</>
);
}
1 change: 0 additions & 1 deletion src/app/_components/Terminal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
7 changes: 0 additions & 7 deletions src/server/api/routers/installedScripts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,15 +268,13 @@ export const installedScriptsRouter = createTRPCRouter({
server as any,
command,
(data: string) => {
console.log('Command output chunk:', data);
commandOutput += data;
},
(error: string) => {
console.error('Command error:', error);
},
(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')
Expand Down Expand Up @@ -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 = '';
Expand All @@ -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;
}
}
Expand Down Expand Up @@ -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 = [];
Expand Down Expand Up @@ -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) => {
Expand Down