From b451b13ca0146b5da1de2f06a6e7333813af2f4b Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 11:29:35 +0200 Subject: [PATCH 01/33] feat: comprehensive mobile responsiveness improvements - Made main layout responsive with proper mobile padding and spacing - Updated Terminal component with mobile-friendly controls and sizing - Enhanced VersionDisplay with responsive layout and condensed mobile text - Improved ScriptsGrid and DownloadedScriptsTab with mobile-first design - Made CategorySidebar responsive with horizontal scroll on mobile - Fixed FilterBar styling consistency and added Lucide icons - Enhanced all modals (Settings, ScriptDetail, ExecutionMode, etc.) for mobile - Updated ServerForm and ServerList with mobile-optimized layouts - Added global CSS improvements for mobile touch targets and typography - Fixed close button placement in ScriptDetailModal to follow UI conventions - Implemented responsive breakpoints throughout the application - Added proper viewport meta tag for mobile rendering All components now provide excellent user experience across all device sizes. --- scripts/ct/debian.sh | 43 ++++ scripts/install/debian-install.sh | 24 ++ src/app/_components/CategorySidebar.tsx | 8 +- src/app/_components/DiffViewer.tsx | 2 +- src/app/_components/DownloadedScriptsTab.tsx | 6 +- src/app/_components/ExecutionModeModal.tsx | 4 +- src/app/_components/FilterBar.tsx | 217 +++++++++++-------- src/app/_components/ScriptDetailModal.tsx | 106 ++++----- src/app/_components/ScriptsGrid.tsx | 6 +- src/app/_components/ServerForm.tsx | 6 +- src/app/_components/ServerList.tsx | 79 ++++--- src/app/_components/SettingsModal.tsx | 38 ++-- src/app/_components/Terminal.tsx | 51 +++-- src/app/_components/TextViewer.tsx | 2 +- src/app/_components/VersionDisplay.tsx | 74 ++++--- src/app/layout.tsx | 3 +- src/app/page.tsx | 37 ++-- src/styles/globals.css | 59 +++++ 18 files changed, 479 insertions(+), 286 deletions(-) create mode 100644 scripts/ct/debian.sh create mode 100644 scripts/install/debian-install.sh diff --git a/scripts/ct/debian.sh b/scripts/ct/debian.sh new file mode 100644 index 0000000..54b1748 --- /dev/null +++ b/scripts/ct/debian.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +SCRIPT_DIR="$(dirname "$0")" +source "$SCRIPT_DIR/../core/build.func" +# Copyright (c) 2021-2025 tteck +# Author: tteck (tteckster) +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE +# Source: https://www.debian.org/ + +APP="Debian" +var_tags="${var_tags:-os}" +var_cpu="${var_cpu:-1}" +var_ram="${var_ram:-512}" +var_disk="${var_disk:-2}" +var_os="${var_os:-debian}" +var_version="${var_version:-13}" +var_unprivileged="${var_unprivileged:-1}" + +header_info "$APP" +variables +color +catch_errors + +function update_script() { + header_info + check_container_storage + check_container_resources + if [[ ! -d /var ]]; then + msg_error "No ${APP} Installation Found!" + exit + fi + msg_info "Updating $APP LXC" + $STD apt update + $STD apt -y upgrade + msg_ok "Updated $APP LXC" + exit +} + +start +build_container +description + +msg_ok "Completed Successfully!\n" +echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}" diff --git a/scripts/install/debian-install.sh b/scripts/install/debian-install.sh new file mode 100644 index 0000000..7b00eca --- /dev/null +++ b/scripts/install/debian-install.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +# Copyright (c) 2021-2025 tteck +# Author: tteck (tteckster) +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE +# Source: https://www.debian.org/ + +source /dev/stdin <<<"$FUNCTIONS_FILE_PATH" +color +verb_ip6 +catch_errors +setting_up_container +network_check +update_os + +motd_ssh +customize + +msg_info "Cleaning up" +$STD apt -y autoremove +$STD apt -y autoclean +$STD apt -y clean +msg_ok "Cleaned" + diff --git a/src/app/_components/CategorySidebar.tsx b/src/app/_components/CategorySidebar.tsx index 8ddb568..84e2b33 100644 --- a/src/app/_components/CategorySidebar.tsx +++ b/src/app/_components/CategorySidebar.tsx @@ -196,7 +196,7 @@ export function CategorySidebar({ return (
{/* Header */}
@@ -292,7 +292,7 @@ export function CategorySidebar({ {/* Collapsed state - show only icons with counters and tooltips */} {isCollapsed && ( -
+
{/* "All Categories" option */}
{/* Tooltip */} -
+
{category} ({count})
diff --git a/src/app/_components/DiffViewer.tsx b/src/app/_components/DiffViewer.tsx index e2245bc..9123ac4 100644 --- a/src/app/_components/DiffViewer.tsx +++ b/src/app/_components/DiffViewer.tsx @@ -69,7 +69,7 @@ export function DiffViewer({ scriptSlug, filePath, isOpen, onClose }: DiffViewer className="fixed inset-0 backdrop-blur-sm bg-black/50 flex items-center justify-center p-4 z-50" onClick={handleBackdropClick} > -
+
{/* Header */}
diff --git a/src/app/_components/DownloadedScriptsTab.tsx b/src/app/_components/DownloadedScriptsTab.tsx index 0ae5f68..1b361ce 100644 --- a/src/app/_components/DownloadedScriptsTab.tsx +++ b/src/app/_components/DownloadedScriptsTab.tsx @@ -320,9 +320,9 @@ export function DownloadedScriptsTab() {
-
+
{/* Category Sidebar */} -
+
{/* Main Content */} -
+
{/* Enhanced Filter Bar */} -
+
+
{/* Header */}

Execution Mode

diff --git a/src/app/_components/FilterBar.tsx b/src/app/_components/FilterBar.tsx index 7574146..895a13a 100644 --- a/src/app/_components/FilterBar.tsx +++ b/src/app/_components/FilterBar.tsx @@ -2,7 +2,7 @@ import React, { useState } from "react"; import { Button } from "./ui/button"; -import { Package, Monitor, Wrench, Server, FileText, Calendar } from "lucide-react"; +import { Package, Monitor, Wrench, Server, FileText, Calendar, RefreshCw, Filter } from "lucide-react"; export interface FilterState { searchQuery: string; @@ -35,6 +35,7 @@ export function FilterBar({ updatableCount = 0, }: FilterBarProps) { const [isTypeDropdownOpen, setIsTypeDropdownOpen] = useState(false); + const [isSortDropdownOpen, setIsSortDropdownOpen] = useState(false); const updateFilters = (updates: Partial) => { onFiltersChange({ ...filters, ...updates }); @@ -76,10 +77,10 @@ export function FilterBar({ }; return ( -
+
{/* Search Bar */}
-
+
{/* Filter Buttons */} -
+
{/* Updateable Filter */} {/* Type Dropdown */} -
+
+ + {isSortDropdownOpen && ( +
+
+ + +
+
+ )}
+ + {/* Sort Order Button */} +
{/* Filter Summary and Clear All */} -
+
{filteredCount === totalScripts ? ( Showing all {totalScripts} scripts @@ -336,7 +376,7 @@ export function FilterBar({ onClick={clearAllFilters} variant="ghost" size="sm" - className="flex items-center space-x-1 text-red-600 hover:bg-red-50 hover:text-red-800" + className="flex items-center space-x-1 text-red-600 hover:bg-red-50 hover:text-red-800 w-full sm:w-auto justify-center sm:justify-start" > - {/* Click outside to close dropdown */} - {isTypeDropdownOpen && ( + {/* Click outside to close dropdowns */} + {(isTypeDropdownOpen || isSortDropdownOpen) && (
setIsTypeDropdownOpen(false)} + onClick={() => { + setIsTypeDropdownOpen(false); + setIsSortDropdownOpen(false); + }} /> )}
diff --git a/src/app/_components/ScriptDetailModal.tsx b/src/app/_components/ScriptDetailModal.tsx index 6e8f2ce..7b85b87 100644 --- a/src/app/_components/ScriptDetailModal.tsx +++ b/src/app/_components/ScriptDetailModal.tsx @@ -136,38 +136,63 @@ export function ScriptDetailModal({ className="fixed inset-0 z-50 flex items-center justify-center p-4 backdrop-blur-sm bg-black/50" onClick={handleBackdropClick} > -
+
{/* Header */} -
-
+
+
{script.logo && !imageError ? ( {`${script.name} ) : ( -
- +
+ {script.name.charAt(0).toUpperCase()}
)} -
-

+
+

{script.name}

-
+
{script.updateable && } {script.privileged && }
-
+ + {/* Close Button */} + +
+ + {/* Action Buttons */} +
{/* Install Button - only show if script files exist */} {scriptFilesData?.success && scriptFilesData.ctExists && @@ -176,7 +201,7 @@ export function ScriptDetailModal({ onClick={handleInstallScript} variant="outline" size="default" - className="flex items-center space-x-2" + className="w-full sm:w-auto flex items-center justify-center space-x-2" > - - - - -

{/* Load Message */} {loadMessage && ( -
+
{loadMessage}
)} {/* Script Files Status */} {(scriptFilesLoading || comparisonLoading) && ( -
+
Loading script status... @@ -392,8 +396,8 @@ export function ScriptDetailModal({ } return ( -
-
+
+
{scriptFilesData.files.length > 0 && ( -
+
Files: {scriptFilesData.files.join(", ")}
)} @@ -442,21 +446,21 @@ export function ScriptDetailModal({ })()} {/* Content */} -
+
{/* Description */}
-

+

Description

-

+

{script.description}

{/* Basic Information */} -
+
-

+

Basic Information

@@ -508,7 +512,7 @@ export function ScriptDetailModal({
-

+

Links

@@ -555,24 +559,24 @@ export function ScriptDetailModal({ script.type !== "pve" && script.type !== "addon" && (
-

+

Install Methods

{script.install_methods.map((method, index) => (
-
-

+
+

{method.type}

- + {method.script}
-
+
CPU @@ -616,7 +620,7 @@ export function ScriptDetailModal({ {(script.default_credentials.username ?? script.default_credentials.password) && (
-

+

Default Credentials

diff --git a/src/app/_components/ScriptsGrid.tsx b/src/app/_components/ScriptsGrid.tsx index b79a1aa..475a0a2 100644 --- a/src/app/_components/ScriptsGrid.tsx +++ b/src/app/_components/ScriptsGrid.tsx @@ -316,9 +316,9 @@ export function ScriptsGrid({ onInstallScript }: ScriptsGridProps) { } return ( -
+
{/* Category Sidebar */} -
+
{/* Main Content */} -
+
{/* Enhanced Filter Bar */} -
+
-
+
{isEditing && onCancel && ( @@ -159,6 +160,7 @@ export function ServerForm({ onSubmit, initialData, isEditing = false, onCancel type="submit" variant="default" size="default" + className="w-full sm:w-auto order-1 sm:order-2" > {isEditing ? 'Update Server' : 'Add Server'} diff --git a/src/app/_components/ServerList.tsx b/src/app/_components/ServerList.tsx index 9d40c39..f870602 100644 --- a/src/app/_components/ServerList.tsx +++ b/src/app/_components/ServerList.tsx @@ -102,30 +102,30 @@ export function ServerList({ servers, onUpdate, onDelete }: ServerListProps) { />
) : ( -
-
-
+
+
+
-
- +
+
-

{server.name}

-
+

{server.name}

+
- + - {server.ip} + {server.ip} - + - {server.user} + {server.user}
@@ -162,51 +162,58 @@ export function ServerList({ servers, onUpdate, onDelete }: ServerListProps) {
-
+
- - +
+ + +
)} diff --git a/src/app/_components/SettingsModal.tsx b/src/app/_components/SettingsModal.tsx index 051e24a..5a9edf6 100644 --- a/src/app/_components/SettingsModal.tsx +++ b/src/app/_components/SettingsModal.tsx @@ -99,18 +99,18 @@ export function SettingsModal({ isOpen, onClose }: SettingsModalProps) { if (!isOpen) return null; return ( -
-
+
+
{/* Header */} -
-

Settings

+
+

Settings

@@ -118,12 +118,12 @@ export function SettingsModal({ isOpen, onClose }: SettingsModalProps) { {/* Tabs */}
-
{/* Content */} -
+
{error && ( -
+
- +
-
-

Error

-
{error}
+
+

Error

+
{error}
)} {activeTab === 'servers' && ( -
+
-

Server Configurations

+

Server Configurations

-

Saved Servers

+

Saved Servers

{loading ? (
@@ -191,8 +191,8 @@ export function SettingsModal({ isOpen, onClose }: SettingsModalProps) { {activeTab === 'general' && (
-

General Settings

-

General settings will be available in a future update.

+

General Settings

+

General settings will be available in a future update.

)}
diff --git a/src/app/_components/Terminal.tsx b/src/app/_components/Terminal.tsx index a15e2be..ff3d5fd 100644 --- a/src/app/_components/Terminal.tsx +++ b/src/app/_components/Terminal.tsx @@ -317,21 +317,21 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate return (
{/* Terminal Header */} -
-
-
-
-
-
+
+
+
+
+
+
- + {scriptName} {mode === 'ssh' && server && `(SSH: ${server.name})`}
-
+
- + {isConnected ? 'Connected' : 'Disconnected'}
@@ -340,22 +340,23 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate {/* Terminal Output */}
{/* Terminal Controls */} -
-
+
+
@@ -384,9 +387,9 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate onClick={onClose} variant="secondary" size="sm" - className="bg-gray-600 text-white hover:bg-gray-700" + className="text-xs sm:text-sm bg-gray-600 text-white hover:bg-gray-700 w-full sm:w-auto" > - + Close
diff --git a/src/app/_components/TextViewer.tsx b/src/app/_components/TextViewer.tsx index 3d9c889..2ea10f3 100644 --- a/src/app/_components/TextViewer.tsx +++ b/src/app/_components/TextViewer.tsx @@ -103,7 +103,7 @@ export function TextViewer({ scriptName, isOpen, onClose }: TextViewerProps) { className="fixed inset-0 backdrop-blur-sm bg-black/50 flex items-center justify-center p-4 z-50" onClick={handleBackdropClick} > -
+
{/* Header */}
diff --git a/src/app/_components/VersionDisplay.tsx b/src/app/_components/VersionDisplay.tsx index c194b79..bbe5e9c 100644 --- a/src/app/_components/VersionDisplay.tsx +++ b/src/app/_components/VersionDisplay.tsx @@ -229,18 +229,18 @@ export function VersionDisplay() { {/* Loading overlay */} {isUpdating && } -
- +
+ v{currentVersion} {updateAvailable && releaseInfo && ( -
+
- + Update Available -
+
How to update:
Click the button to update, when installed via the helper script
@@ -255,38 +255,42 @@ export function VersionDisplay() {
- - - - - +
+ + + + + +
{updateResult && ( -
-
+
{/* Header */} -
-

- - PVE Scripts Management +
+

+ + PVE Scripts Management

-

+

Manage and execute Proxmox helper scripts locally with live output streaming

-
+
{/* Controls */} -
-
+
+
@@ -54,44 +54,47 @@ export default function Home() {
{/* Tab Navigation */} -
+
-
diff --git a/src/styles/globals.css b/src/styles/globals.css index a68b83e..d352c7f 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -141,3 +141,62 @@ color: inherit; background-color: inherit; } + +/* Mobile-specific improvements */ +@media (max-width: 640px) { + /* Improve touch targets */ + button, .cursor-pointer { + min-height: 44px; + min-width: 44px; + } + + /* Better text sizing on mobile */ + .text-xs { + font-size: 0.75rem; + line-height: 1rem; + } + + /* Improve form elements on mobile */ + input, select, textarea { + font-size: 16px; /* Prevents zoom on iOS */ + } + + /* Better spacing for mobile */ + .space-y-2 > * + * { + margin-top: 0.5rem; + } + + .space-y-4 > * + * { + margin-top: 1rem; + } + + /* Improve modal and overlay positioning */ + .fixed.inset-0 { + padding: 1rem; + } + + /* Better scroll behavior */ + .overflow-x-auto { + -webkit-overflow-scrolling: touch; + } +} + +/* Tablet improvements */ +@media (min-width: 641px) and (max-width: 1024px) { + /* Better spacing for tablets */ + .container { + padding-left: 1.5rem; + padding-right: 1.5rem; + } +} + +/* Ensure proper viewport handling */ +html { + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; +} + +body { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} From f4fded4e3f6510b66b91c7d5d2946c89a7fa40e4 Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 11:39:28 +0200 Subject: [PATCH 02/33] fix: improve mobile terminal input handling for SSH processes - Updated mobile input controls to use up/down arrows instead of numbered buttons - Fixed WebSocket input handling to support both regular processes and pty processes (SSH) - Added comprehensive debugging logs for input handling - Added visual feedback showing when inputs are sent - Improved error handling and user feedback for input failures The mobile terminal input should now work properly with SSH-executed scripts. --- src/app/_components/Terminal.tsx | 134 +++++++++++++++++++++++++++- src/server/api/websocket/handler.ts | 71 ++++++++++++++- 2 files changed, 201 insertions(+), 4 deletions(-) diff --git a/src/app/_components/Terminal.tsx b/src/app/_components/Terminal.tsx index ff3d5fd..ddfec74 100644 --- a/src/app/_components/Terminal.tsx +++ b/src/app/_components/Terminal.tsx @@ -3,7 +3,7 @@ import { useCallback, useEffect, useRef, useState } from 'react'; import '@xterm/xterm/css/xterm.css'; import { Button } from './ui/button'; -import { Play, Square, Trash2, X } from 'lucide-react'; +import { Play, Square, Trash2, X, Send, Keyboard, ChevronUp, ChevronDown } from 'lucide-react'; interface TerminalProps { scriptPath: string; @@ -24,6 +24,9 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate const [isConnected, setIsConnected] = useState(false); const [isRunning, setIsRunning] = useState(false); const [isClient, setIsClient] = useState(false); + const [mobileInput, setMobileInput] = useState(''); + const [showMobileInput, setShowMobileInput] = useState(false); + const [lastInputSent, setLastInputSent] = useState(null); const terminalRef = useRef(null); const xtermRef = useRef(null); const fitAddonRef = useRef(null); @@ -229,6 +232,7 @@ 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); @@ -291,6 +295,35 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate } }; + const sendInput = (input: string) => { + console.log('Sending input:', input, 'to execution:', executionId); + setLastInputSent(input); + if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) { + wsRef.current.send(JSON.stringify({ + action: 'input', + executionId, + data: input + })); + // Clear the feedback after 2 seconds + setTimeout(() => setLastInputSent(null), 2000); + } else { + console.warn('WebSocket not connected, cannot send input'); + } + }; + + const handleMobileInput = (input: string) => { + sendInput(input); + setMobileInput(''); + }; + + const handleNumberInput = (number: number) => { + sendInput(number.toString()); + }; + + const handleEnterKey = () => { + sendInput('\r'); + }; + // Don't render on server side if (!isClient) { return ( @@ -344,6 +377,105 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate style={{ minHeight: '320px' }} /> + {/* Mobile Input Controls - Only show on mobile */} +
+
+
+ Mobile Input + {lastInputSent && ( + + Sent: {lastInputSent === '\r' ? 'Enter' : lastInputSent === '\x1b[A' ? 'Up' : lastInputSent === '\x1b[B' ? 'Down' : lastInputSent} + + )} +
+ +
+ + {showMobileInput && ( +
+ {/* Navigation and Action Buttons */} +
+ + +
+ + {/* Action Buttons */} +
+ + +
+ + {/* Custom Input */} +
+ setMobileInput(e.target.value)} + placeholder="Type command..." + className="flex-1 px-3 py-2 text-sm border border-border rounded-md bg-background text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary" + onKeyPress={(e) => { + if (e.key === 'Enter') { + handleMobileInput(mobileInput); + } + }} + disabled={!isConnected} + /> + +
+
+ )} +
+ {/* Terminal Controls */}
diff --git a/src/server/api/websocket/handler.ts b/src/server/api/websocket/handler.ts index 6f60d2f..ccb5b7a 100644 --- a/src/server/api/websocket/handler.ts +++ b/src/server/api/websocket/handler.ts @@ -50,10 +50,10 @@ export class ScriptExecutionHandler { }); } - private async handleMessage(ws: WebSocket, message: { action: string; scriptPath?: string; executionId?: string; mode?: 'local' | 'ssh'; server?: any }) { - const { action, scriptPath, executionId, mode, server } = message; + private async handleMessage(ws: WebSocket, message: { action: string; scriptPath?: string; executionId?: string; mode?: 'local' | 'ssh'; server?: any; data?: string }) { + const { action, scriptPath, executionId, mode, server, data } = message; - console.log('WebSocket message received:', { action, scriptPath, executionId, mode, server: server ? { name: server.name, ip: server.ip } : null }); + console.log('WebSocket message received:', { action, scriptPath, executionId, mode, server: server ? { name: server.name, ip: server.ip } : null, hasData: !!data }); switch (action) { case 'start': @@ -74,6 +74,18 @@ export class ScriptExecutionHandler { } break; + case 'input': + if (executionId && data !== undefined) { + this.sendInputToExecution(executionId, data); + } else { + this.sendMessage(ws, { + type: 'error', + data: 'Missing executionId or input data', + timestamp: Date.now() + }); + } + break; + default: this.sendMessage(ws, { type: 'error', @@ -249,6 +261,59 @@ export class ScriptExecutionHandler { } } + private sendInputToExecution(executionId: string, input: string) { + console.log('sendInputToExecution called:', { executionId, input, inputLength: input.length }); + const execution = this.activeExecutions.get(executionId); + console.log('Active executions:', Array.from(this.activeExecutions.keys())); + + if (execution && execution.process) { + console.log('Execution found, process details:', { + hasStdin: !!execution.process.stdin, + stdinDestroyed: execution.process.stdin?.destroyed, + processPid: execution.process.pid, + processKilled: execution.process.killed, + hasWrite: typeof execution.process.write === 'function', + isPty: !!(execution.process as any).write && !execution.process.stdin + }); + + try { + // Check if it's a pty process (SSH) or regular process + if (typeof execution.process.write === 'function' && !execution.process.stdin) { + // This is a pty process (SSH execution) + console.log('Writing to pty process:', input); + execution.process.write(input); + console.log('Successfully wrote to pty process'); + } else if (execution.process.stdin && !execution.process.stdin.destroyed) { + // This is a regular process (local execution) + console.log('Writing to stdin:', input); + execution.process.stdin.write(input); + console.log('Successfully wrote to stdin'); + } else { + console.warn('Process input not available or destroyed'); + this.sendMessage(execution.ws, { + type: 'error', + data: 'Process input not available', + timestamp: Date.now() + }); + } + } catch (error) { + console.error('Error sending input to execution:', error); + this.sendMessage(execution.ws, { + type: 'error', + data: `Failed to send input: ${error instanceof Error ? error.message : 'Unknown error'}`, + timestamp: Date.now() + }); + } + } else { + console.warn('No active execution found for input:', executionId); + this.sendMessage(execution.ws, { + type: 'error', + data: `No active execution found for ID: ${executionId}`, + timestamp: Date.now() + }); + } + } + private sendMessage(ws: WebSocket, message: ScriptExecutionMessage) { if (ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify(message)); From 82638b2a287b59e49286682dcf399004162e249e Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 11:43:17 +0200 Subject: [PATCH 03/33] debug: add comprehensive debugging for mobile terminal input - Added byte-level debugging to see exact input being sent - Added test button to verify basic input works - Enhanced server-side logging to track input processing - Improved escape sequence handling for arrow keys This will help identify why mobile inputs aren't working while keyboard works. --- src/app/_components/Terminal.tsx | 12 +++++++++++- src/server/api/websocket/handler.ts | 2 ++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/app/_components/Terminal.tsx b/src/app/_components/Terminal.tsx index ddfec74..5b66055 100644 --- a/src/app/_components/Terminal.tsx +++ b/src/app/_components/Terminal.tsx @@ -297,6 +297,7 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate const sendInput = (input: string) => { console.log('Sending input:', input, 'to execution:', executionId); + console.log('Input bytes:', Array.from(input).map(c => c.charCodeAt(0))); setLastInputSent(input); if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) { wsRef.current.send(JSON.stringify({ @@ -426,7 +427,7 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate
{/* Action Buttons */} -
+
+
{/* Custom Input */} diff --git a/src/server/api/websocket/handler.ts b/src/server/api/websocket/handler.ts index ccb5b7a..b215a53 100644 --- a/src/server/api/websocket/handler.ts +++ b/src/server/api/websocket/handler.ts @@ -281,11 +281,13 @@ export class ScriptExecutionHandler { if (typeof execution.process.write === 'function' && !execution.process.stdin) { // This is a pty process (SSH execution) console.log('Writing to pty process:', input); + console.log('Input bytes for pty:', Array.from(input).map(c => c.charCodeAt(0))); execution.process.write(input); console.log('Successfully wrote to pty process'); } else if (execution.process.stdin && !execution.process.stdin.destroyed) { // This is a regular process (local execution) console.log('Writing to stdin:', input); + console.log('Input bytes for stdin:', Array.from(input).map(c => c.charCodeAt(0))); execution.process.stdin.write(input); console.log('Successfully wrote to stdin'); } else { From abb000a711be75b6c983126f7773a34a757d3d6e Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 11:45:07 +0200 Subject: [PATCH 04/33] debug: add comprehensive server-side debugging for mobile input - Added detailed logging for mobile input processing - Added confirmation messages sent back to client - Enhanced debugging to track input flow from client to server - Added JSON string representation of inputs for better debugging This will help identify why mobile inputs aren't working while keyboard works. --- src/server/api/websocket/handler.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/server/api/websocket/handler.ts b/src/server/api/websocket/handler.ts index b215a53..e9b22a7 100644 --- a/src/server/api/websocket/handler.ts +++ b/src/server/api/websocket/handler.ts @@ -262,9 +262,13 @@ export class ScriptExecutionHandler { } private sendInputToExecution(executionId: string, input: string) { + console.log('=== MOBILE INPUT DEBUG ==='); console.log('sendInputToExecution called:', { executionId, input, inputLength: input.length }); + console.log('Input string representation:', JSON.stringify(input)); + console.log('Input char codes:', Array.from(input).map(c => c.charCodeAt(0))); const execution = this.activeExecutions.get(executionId); console.log('Active executions:', Array.from(this.activeExecutions.keys())); + console.log('Found execution:', !!execution); if (execution && execution.process) { console.log('Execution found, process details:', { @@ -284,12 +288,26 @@ export class ScriptExecutionHandler { console.log('Input bytes for pty:', Array.from(input).map(c => c.charCodeAt(0))); execution.process.write(input); console.log('Successfully wrote to pty process'); + + // Send confirmation back to client + this.sendMessage(execution.ws, { + type: 'output', + data: `[MOBILE INPUT SENT: ${JSON.stringify(input)}]`, + timestamp: Date.now() + }); } else if (execution.process.stdin && !execution.process.stdin.destroyed) { // This is a regular process (local execution) console.log('Writing to stdin:', input); console.log('Input bytes for stdin:', Array.from(input).map(c => c.charCodeAt(0))); execution.process.stdin.write(input); console.log('Successfully wrote to stdin'); + + // Send confirmation back to client + this.sendMessage(execution.ws, { + type: 'output', + data: `[MOBILE INPUT SENT: ${JSON.stringify(input)}]`, + timestamp: Date.now() + }); } else { console.warn('Process input not available or destroyed'); this.sendMessage(execution.ws, { From cefdb30ce8c9e314d6180b807c0ec8856256e4c8 Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 12:32:03 +0200 Subject: [PATCH 05/33] debug: add WebSocket message routing debugging - Added comprehensive logging for all WebSocket messages - Added specific debugging for input action handling - Added full message object logging to identify routing issues - Enhanced input action validation logging This will help identify if input messages are reaching the server at all. --- src/server/api/websocket/handler.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/server/api/websocket/handler.ts b/src/server/api/websocket/handler.ts index e9b22a7..a9d6047 100644 --- a/src/server/api/websocket/handler.ts +++ b/src/server/api/websocket/handler.ts @@ -53,7 +53,9 @@ export class ScriptExecutionHandler { private async handleMessage(ws: WebSocket, message: { action: string; scriptPath?: string; executionId?: string; mode?: 'local' | 'ssh'; server?: any; data?: string }) { const { action, scriptPath, executionId, mode, server, data } = message; + console.log('=== WEBSOCKET MESSAGE DEBUG ==='); console.log('WebSocket message received:', { action, scriptPath, executionId, mode, server: server ? { name: server.name, ip: server.ip } : null, hasData: !!data }); + console.log('Full message object:', JSON.stringify(message, null, 2)); switch (action) { case 'start': @@ -75,9 +77,13 @@ export class ScriptExecutionHandler { break; case 'input': + console.log('=== INPUT ACTION DEBUG ==='); + console.log('Input action received:', { executionId, data, hasExecutionId: !!executionId, hasData: data !== undefined }); if (executionId && data !== undefined) { + console.log('Calling sendInputToExecution...'); this.sendInputToExecution(executionId, data); } else { + console.log('Input action failed - missing executionId or data'); this.sendMessage(ws, { type: 'error', data: 'Missing executionId or input data', From f75f763a165551e85deb9e5852bbf4fedde22e44 Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 12:34:02 +0200 Subject: [PATCH 06/33] debug: add comprehensive client-side debugging for mobile input - Added detailed logging for button click events - Added WebSocket connection state debugging - Added message sending confirmation logging - Enhanced sendInput function with complete debugging This will help identify if mobile buttons are being clicked and if WebSocket messages are being sent. --- src/app/_components/Terminal.tsx | 39 ++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/src/app/_components/Terminal.tsx b/src/app/_components/Terminal.tsx index 5b66055..6b3bcfc 100644 --- a/src/app/_components/Terminal.tsx +++ b/src/app/_components/Terminal.tsx @@ -296,19 +296,29 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate }; const sendInput = (input: string) => { + console.log('=== MOBILE INPUT SEND DEBUG ==='); console.log('Sending input:', input, 'to execution:', executionId); console.log('Input bytes:', Array.from(input).map(c => c.charCodeAt(0))); + console.log('WebSocket ref exists:', !!wsRef.current); + console.log('WebSocket readyState:', wsRef.current?.readyState); + console.log('WebSocket OPEN constant:', WebSocket.OPEN); + console.log('Is connected:', wsRef.current?.readyState === WebSocket.OPEN); + setLastInputSent(input); if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) { - wsRef.current.send(JSON.stringify({ + const message = { action: 'input', executionId, data: input - })); + }; + console.log('Sending WebSocket message:', JSON.stringify(message, null, 2)); + wsRef.current.send(JSON.stringify(message)); + console.log('WebSocket message sent successfully'); // Clear the feedback after 2 seconds setTimeout(() => setLastInputSent(null), 2000); } else { console.warn('WebSocket not connected, cannot send input'); + console.warn('WebSocket state:', wsRef.current?.readyState); } }; @@ -405,7 +415,10 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate {/* Navigation and Action Buttons */}
@@ -430,7 +435,7 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate {showMobileInput && (
- {/* Navigation and Action Buttons */} + {/* Navigation Buttons */}
+ {/* Left/Right Navigation Buttons */} +
+ + +
+ {/* Action Buttons */}
{/* Action Buttons */} -
+
-
From 77b6724f18fec59e28de5c915358e610f33977c6 Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 12:44:48 +0200 Subject: [PATCH 13/33] feat: add backspace button to mobile terminal controls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added backspace button (⌫ Backspace) to action buttons - Sends \b character for backspace functionality - Changed action buttons from 2-column to 3-column grid - Updated visual feedback to show 'Backspace' for backspace input - Mobile users now have complete text editing capabilities This completes the essential mobile terminal input controls with navigation, text input, and editing functions. --- src/app/_components/Terminal.tsx | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/app/_components/Terminal.tsx b/src/app/_components/Terminal.tsx index b1809dd..1e3cfa4 100644 --- a/src/app/_components/Terminal.tsx +++ b/src/app/_components/Terminal.tsx @@ -415,6 +415,7 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate Sent: {lastInputSent === '\r' ? 'Enter' : lastInputSent === ' ' ? 'Space' : + lastInputSent === '\b' ? 'Backspace' : lastInputSent === '\x1b[A' ? 'Up' : lastInputSent === '\x1b[B' ? 'Down' : lastInputSent === '\x1b[C' ? 'Right' : @@ -497,7 +498,7 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate
{/* Action Buttons */} -
+
+
{/* Custom Input */} From 9a378e5bc5b3d56d6d5016ec16a581428e0419e4 Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 12:46:57 +0200 Subject: [PATCH 14/33] feat: improve mobile terminal scaling and responsiveness - Reduced font size from 14px to 10px on mobile devices (< 768px width) - Set mobile-specific terminal dimensions (20 rows, 60 cols) for better fit - Reduced mobile terminal height from 20rem to 16rem (256px min-height) - Added responsive resize listener to adjust terminal size on orientation changes - Improved mobile terminal display to prevent cramped text and odd appearance - Better balance between terminal content and mobile input controls This makes the terminal much more readable and usable on mobile devices. --- src/app/_components/Terminal.tsx | 41 ++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/src/app/_components/Terminal.tsx b/src/app/_components/Terminal.tsx index 1e3cfa4..6ac2b7d 100644 --- a/src/app/_components/Terminal.tsx +++ b/src/app/_components/Terminal.tsx @@ -109,13 +109,16 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate const { FitAddon } = await import('@xterm/addon-fit'); const { WebLinksAddon } = await import('@xterm/addon-web-links'); + // Check if we're on mobile + const isMobile = window.innerWidth < 768; + const terminal = new XTerm({ theme: { background: '#000000', foreground: '#00ff00', cursor: '#00ff00', }, - fontSize: 14, + fontSize: isMobile ? 10 : 14, fontFamily: 'JetBrains Mono, Fira Code, Cascadia Code, Monaco, Menlo, Ubuntu Mono, monospace', cursorBlink: true, cursorStyle: 'block', @@ -127,6 +130,11 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate macOptionIsMeta: false, rightClickSelectsWord: false, wordSeparator: ' ()[]{}\'"`<>|', + // Mobile-specific settings + ...(isMobile && { + rows: 20, + cols: 60, + }), }); // Add addons @@ -143,6 +151,20 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate fitAddon.fit(); }, 100); + // Add resize listener for mobile responsiveness + const handleResize = () => { + if (fitAddonRef.current) { + setTimeout(() => { + fitAddonRef.current.fit(); + }, 50); + } + }; + + window.addEventListener('resize', handleResize); + + // Store the handler for cleanup + (terminalRef.current as any).resizeHandler = handleResize; + // Store references xtermRef.current = terminal; fitAddonRef.current = fitAddon; @@ -173,17 +195,7 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate } }); - // Handle terminal resize - const handleResize = () => { - if (fitAddonRef.current) { - fitAddonRef.current.fit(); - } - }; - - window.addEventListener('resize', handleResize); - return () => { - window.removeEventListener('resize', handleResize); terminal.dispose(); }; }; @@ -195,6 +207,9 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate return () => { clearTimeout(timeoutId); + if (terminalRef.current && (terminalRef.current as any).resizeHandler) { + window.removeEventListener('resize', (terminalRef.current as any).resizeHandler); + } if (xtermRef.current) { xtermRef.current.dispose(); xtermRef.current = null; @@ -402,8 +417,8 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate {/* Terminal Output */}
{/* Mobile Input Controls - Only show on mobile */} From 5e7168a7a7f8f8e96ec63ce38eabe37cd2fa8b52 Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 12:50:11 +0200 Subject: [PATCH 15/33] fix: improve ANSI escape sequence handling for whiptail dialogs - Added better ANSI handling configuration to terminal - Added detection and logging for screen clearing sequences (\x1b[2J, \x1b[H\x1b[2J) - Added detection and logging for cursor positioning sequences - Enabled allowProposedApi for better terminal compatibility - Added debugging to identify when clear screen operations occur This should fix the issue where whiptail dialogs duplicate content and don't properly clear the screen on mobile input. --- src/app/_components/Terminal.tsx | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/app/_components/Terminal.tsx b/src/app/_components/Terminal.tsx index 6ac2b7d..37f78b7 100644 --- a/src/app/_components/Terminal.tsx +++ b/src/app/_components/Terminal.tsx @@ -50,7 +50,18 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate break; case 'output': // Write directly to terminal - xterm.js handles ANSI codes natively - xtermRef.current.write(message.data); + // Check for screen clearing sequences and handle them properly + if (message.data.includes('\x1b[2J') || message.data.includes('\x1b[H\x1b[2J')) { + // This is a clear screen sequence, ensure it's processed correctly + console.log('Clear screen detected:', message.data); + xtermRef.current.write(message.data); + } else if (message.data.includes('\x1b[') && message.data.includes('H')) { + // This might be a cursor positioning sequence + console.log('Cursor positioning detected:', message.data); + xtermRef.current.write(message.data); + } else { + xtermRef.current.write(message.data); + } break; case 'error': // Check if this looks like ANSI terminal output (contains escape codes) @@ -130,6 +141,8 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate macOptionIsMeta: false, rightClickSelectsWord: false, wordSeparator: ' ()[]{}\'"`<>|', + // Better ANSI handling + allowProposedApi: true, // Mobile-specific settings ...(isMobile && { rows: 20, @@ -142,6 +155,9 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate const webLinksAddon = new WebLinksAddon(); terminal.loadAddon(fitAddon); terminal.loadAddon(webLinksAddon); + + // Enable better ANSI handling + terminal.options.allowProposedApi = true; // Open terminal terminal.open(terminalRef.current); From 04b73b2ca012d8198d7cdacdc4718a37b71c5e42 Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 12:50:27 +0200 Subject: [PATCH 16/33] debug: add whiptail/dialog detection and logging - Added detection for whiptail and dialog output in terminal messages - Added logging to track when whiptail content is being processed - This will help identify if the issue is with ANSI sequence processing - Console logs will show when clear screen, cursor positioning, and whiptail content is detected This debugging will help identify the root cause of the terminal rerendering issue. --- src/app/_components/Terminal.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/app/_components/Terminal.tsx b/src/app/_components/Terminal.tsx index 37f78b7..ce9bbbb 100644 --- a/src/app/_components/Terminal.tsx +++ b/src/app/_components/Terminal.tsx @@ -59,6 +59,10 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate // This might be a cursor positioning sequence console.log('Cursor positioning detected:', message.data); xtermRef.current.write(message.data); + } else if (message.data.includes('whiptail') || message.data.includes('dialog')) { + // This might be a whiptail/dialog output, ensure clean rendering + console.log('Whiptail/dialog output detected:', message.data.substring(0, 100)); + xtermRef.current.write(message.data); } else { xtermRef.current.write(message.data); } From 27d569e3c2ec82aed091a636740669006338a8f7 Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 12:52:10 +0200 Subject: [PATCH 17/33] fix: force screen clear on cursor positioning to prevent whiptail duplication - Modified output handling to force screen clear (\x1b[2J\x1b[H) when cursor positioning is detected - Removed whiptail-specific detection in favor of broader cursor positioning approach - This should prevent content duplication when whiptail redraws its interface - Cursor positioning sequences often indicate a full screen redraw is intended This aggressive approach should finally fix the terminal rerendering issue. --- src/app/_components/Terminal.tsx | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/app/_components/Terminal.tsx b/src/app/_components/Terminal.tsx index ce9bbbb..e311113 100644 --- a/src/app/_components/Terminal.tsx +++ b/src/app/_components/Terminal.tsx @@ -56,12 +56,10 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate console.log('Clear screen detected:', message.data); xtermRef.current.write(message.data); } else if (message.data.includes('\x1b[') && message.data.includes('H')) { - // This might be a cursor positioning sequence - console.log('Cursor positioning detected:', message.data); - xtermRef.current.write(message.data); - } else if (message.data.includes('whiptail') || message.data.includes('dialog')) { - // This might be a whiptail/dialog output, ensure clean rendering - console.log('Whiptail/dialog output detected:', message.data.substring(0, 100)); + // This is a cursor positioning sequence, often implies a redraw of the entire screen + console.log('Cursor positioning detected (implies redraw):', message.data); + // Explicitly clear the screen before writing new content to prevent duplication + xtermRef.current.write('\x1b[2J\x1b[H'); // Clear screen and move cursor to home xtermRef.current.write(message.data); } else { xtermRef.current.write(message.data); From 12af43628c3216e655e8963f20a252cf70f950ad Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 12:53:11 +0200 Subject: [PATCH 18/33] debug: add comprehensive output debugging and try terminal.clear() - Added detailed logging for all output messages including length, preview, and ANSI detection - Changed cursor positioning handling to use xtermRef.current.clear() for more aggressive clearing - This will help identify exactly what data is being received and how it's being processed - The terminal.clear() method should completely reset the terminal buffer This debugging will help us understand why the duplication is still occurring. --- src/app/_components/Terminal.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/app/_components/Terminal.tsx b/src/app/_components/Terminal.tsx index e311113..635b627 100644 --- a/src/app/_components/Terminal.tsx +++ b/src/app/_components/Terminal.tsx @@ -50,6 +50,13 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate break; case 'output': // Write directly to terminal - xterm.js handles ANSI codes natively + console.log('=== OUTPUT DEBUG ==='); + console.log('Output data length:', message.data.length); + console.log('Output preview:', message.data.substring(0, 200)); + console.log('Contains ANSI codes:', message.data.includes('\x1b[') || message.data.includes('\u001b[')); + console.log('Contains clear screen:', message.data.includes('\x1b[2J') || message.data.includes('\x1b[H\x1b[2J')); + console.log('Contains cursor positioning:', message.data.includes('\x1b[') && message.data.includes('H')); + // Check for screen clearing sequences and handle them properly if (message.data.includes('\x1b[2J') || message.data.includes('\x1b[H\x1b[2J')) { // This is a clear screen sequence, ensure it's processed correctly @@ -58,7 +65,8 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate } else if (message.data.includes('\x1b[') && message.data.includes('H')) { // This is a cursor positioning sequence, often implies a redraw of the entire screen console.log('Cursor positioning detected (implies redraw):', message.data); - // Explicitly clear the screen before writing new content to prevent duplication + // Try a more aggressive clear - clear entire buffer and reset + xtermRef.current.clear(); xtermRef.current.write('\x1b[2J\x1b[H'); // Clear screen and move cursor to home xtermRef.current.write(message.data); } else { From 986c5ecb8ef60679c2ddd68f8e4ccb1563af4792 Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 12:54:11 +0200 Subject: [PATCH 19/33] debug: add whiptail session detection and enhanced debugging - Added inWhiptailSession state to track when we're in a whiptail dialog - Added detection for whiptail/dialog content to set session flag - Enhanced output debugging with comprehensive logging - Added aggressive terminal clearing specifically for whiptail sessions - Reset whiptail session when script ends - Set explicit terminal dimensions (cols/rows) for better behavior This should provide much more detailed debugging information and better handling of whiptail sessions. --- src/app/_components/Terminal.tsx | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/src/app/_components/Terminal.tsx b/src/app/_components/Terminal.tsx index 635b627..76dc888 100644 --- a/src/app/_components/Terminal.tsx +++ b/src/app/_components/Terminal.tsx @@ -27,6 +27,7 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate const [mobileInput, setMobileInput] = useState(''); const [showMobileInput, setShowMobileInput] = useState(false); const [lastInputSent, setLastInputSent] = useState(null); + const [inWhiptailSession, setInWhiptailSession] = useState(false); const terminalRef = useRef(null); const xtermRef = useRef(null); const fitAddonRef = useRef(null); @@ -56,6 +57,13 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate console.log('Contains ANSI codes:', message.data.includes('\x1b[') || message.data.includes('\u001b[')); console.log('Contains clear screen:', message.data.includes('\x1b[2J') || message.data.includes('\x1b[H\x1b[2J')); console.log('Contains cursor positioning:', message.data.includes('\x1b[') && message.data.includes('H')); + console.log('In whiptail session:', inWhiptailSession); + + // Detect whiptail sessions + if (message.data.includes('whiptail') || message.data.includes('dialog') || message.data.includes('Choose an option')) { + setInWhiptailSession(true); + console.log('Whiptail session detected!'); + } // Check for screen clearing sequences and handle them properly if (message.data.includes('\x1b[2J') || message.data.includes('\x1b[H\x1b[2J')) { @@ -65,9 +73,12 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate } else if (message.data.includes('\x1b[') && message.data.includes('H')) { // This is a cursor positioning sequence, often implies a redraw of the entire screen console.log('Cursor positioning detected (implies redraw):', message.data); - // Try a more aggressive clear - clear entire buffer and reset - xtermRef.current.clear(); - xtermRef.current.write('\x1b[2J\x1b[H'); // Clear screen and move cursor to home + if (inWhiptailSession) { + // In whiptail session, be more aggressive about clearing + console.log('Clearing terminal for whiptail redraw'); + xtermRef.current.clear(); + xtermRef.current.write('\x1b[2J\x1b[H'); // Clear screen and move cursor to home + } xtermRef.current.write(message.data); } else { xtermRef.current.write(message.data); @@ -90,6 +101,9 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate } break; case 'end': + // Reset whiptail session + setInWhiptailSession(false); + // Check if this is an LXC creation script const isLxcCreation = scriptPath.includes('ct/') || scriptPath.includes('create_lxc') || @@ -153,11 +167,9 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate wordSeparator: ' ()[]{}\'"`<>|', // Better ANSI handling allowProposedApi: true, - // Mobile-specific settings - ...(isMobile && { - rows: 20, - cols: 60, - }), + // Force proper terminal behavior for interactive applications + cols: isMobile ? 60 : 80, + rows: isMobile ? 20 : 24, }); // Add addons From 09436ea736cf82c8b97ad3cc223a4f5845717f8e Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 12:56:05 +0200 Subject: [PATCH 20/33] feat: improve whiptail centering on mobile devices - Reduced mobile terminal dimensions to 50 cols x 18 rows for better whiptail centering - Reduced mobile terminal height from 16rem to 14rem (224px min-height) - Added isMobile state to component level for consistent mobile detection - Added horizontal padding on mobile to help center terminal content - Smaller terminal dimensions should help whiptail dialogs appear more centered This should improve the visual positioning of whiptail dialogs on mobile devices. --- src/app/_components/Terminal.tsx | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/app/_components/Terminal.tsx b/src/app/_components/Terminal.tsx index 76dc888..fd7bbf1 100644 --- a/src/app/_components/Terminal.tsx +++ b/src/app/_components/Terminal.tsx @@ -28,6 +28,7 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate const [showMobileInput, setShowMobileInput] = useState(false); const [lastInputSent, setLastInputSent] = useState(null); const [inWhiptailSession, setInWhiptailSession] = useState(false); + const [isMobile, setIsMobile] = useState(false); const terminalRef = useRef(null); const xtermRef = useRef(null); const fitAddonRef = useRef(null); @@ -129,6 +130,8 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate // Ensure we're on the client side useEffect(() => { setIsClient(true); + // Detect mobile on mount + setIsMobile(window.innerWidth < 768); }, []); useEffect(() => { @@ -144,8 +147,7 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate const { FitAddon } = await import('@xterm/addon-fit'); const { WebLinksAddon } = await import('@xterm/addon-web-links'); - // Check if we're on mobile - const isMobile = window.innerWidth < 768; + // Use the mobile state const terminal = new XTerm({ theme: { @@ -168,8 +170,9 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate // Better ANSI handling allowProposedApi: true, // Force proper terminal behavior for interactive applications - cols: isMobile ? 60 : 80, - rows: isMobile ? 20 : 24, + // Use smaller dimensions on mobile to help center whiptail dialogs + cols: isMobile ? 50 : 80, + rows: isMobile ? 18 : 24, }); // Add addons @@ -455,8 +458,12 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate {/* Terminal Output */}
{/* Mobile Input Controls - Only show on mobile */} From 811fc4e66a9aa96b9a614703458ec2502286b3af Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 12:57:40 +0200 Subject: [PATCH 21/33] feat: improve whiptail horizontal centering on mobile - Reduced mobile terminal dimensions to 40 cols x 16 rows for better centering - Added mobile-terminal CSS class with flex centering - Added CSS rules to center xterm content horizontally on mobile - Added transform translateX for fine-tuning horizontal position - Increased horizontal padding to 2rem on mobile This should better center the whiptail dialog horizontally on mobile devices. --- src/app/_components/Terminal.tsx | 9 +++++---- src/styles/globals.css | 12 ++++++++++++ 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/app/_components/Terminal.tsx b/src/app/_components/Terminal.tsx index fd7bbf1..19b012c 100644 --- a/src/app/_components/Terminal.tsx +++ b/src/app/_components/Terminal.tsx @@ -171,8 +171,8 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate allowProposedApi: true, // Force proper terminal behavior for interactive applications // Use smaller dimensions on mobile to help center whiptail dialogs - cols: isMobile ? 50 : 80, - rows: isMobile ? 18 : 24, + cols: isMobile ? 40 : 80, + rows: isMobile ? 16 : 24, }); // Add addons @@ -458,10 +458,11 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate {/* Terminal Output */}
diff --git a/src/styles/globals.css b/src/styles/globals.css index d352c7f..61afd35 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -200,3 +200,15 @@ body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } + +/* Mobile terminal centering */ +.mobile-terminal { + display: flex !important; + justify-content: center !important; + align-items: center !important; +} + +.mobile-terminal .xterm { + margin: 0 auto !important; + transform: translateX(0.5rem) !important; +} From 1db1c0f1b0de6abd3a6fa56ac0a8cfb40474577c Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 13:00:55 +0200 Subject: [PATCH 22/33] fix: resolve text wrapping and overflow issues in mobile terminal - Increased mobile terminal dimensions to 60 cols x 20 rows to prevent text cutoff - Increased mobile terminal height back to 16rem (256px) for better content display - Added overflow: hidden to mobile terminal CSS to prevent content overflow - Added width and max-width constraints to xterm elements - Reduced horizontal padding and transform to better fit content - Cleaned up duplicate terminal configuration options This should fix the weird text wrapping and cutoff issues on mobile devices. --- src/app/_components/Terminal.tsx | 14 +++++++------- src/styles/globals.css | 12 ++++++++++++ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/app/_components/Terminal.tsx b/src/app/_components/Terminal.tsx index 19b012c..8619e39 100644 --- a/src/app/_components/Terminal.tsx +++ b/src/app/_components/Terminal.tsx @@ -170,9 +170,9 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate // Better ANSI handling allowProposedApi: true, // Force proper terminal behavior for interactive applications - // Use smaller dimensions on mobile to help center whiptail dialogs - cols: isMobile ? 40 : 80, - rows: isMobile ? 16 : 24, + // Use appropriate dimensions on mobile to prevent text wrapping issues + cols: isMobile ? 60 : 80, + rows: isMobile ? 20 : 24, }); // Add addons @@ -458,12 +458,12 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate {/* Terminal Output */}
diff --git a/src/styles/globals.css b/src/styles/globals.css index 61afd35..cdb9e65 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -206,9 +206,21 @@ body { display: flex !important; justify-content: center !important; align-items: center !important; + overflow: hidden !important; } .mobile-terminal .xterm { margin: 0 auto !important; transform: translateX(0.5rem) !important; + width: 100% !important; + max-width: 100% !important; +} + +.mobile-terminal .xterm-viewport { + overflow: hidden !important; +} + +.mobile-terminal .xterm-screen { + width: 100% !important; + max-width: 100% !important; } From 32dc1b1438bd1b3ec780668e4658f445794e7d4d Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 13:02:34 +0200 Subject: [PATCH 23/33] fix: try auto-fit approach to resolve mobile terminal text wrapping - Removed fixed terminal dimensions on mobile to let it auto-fit - Added double fit calls for mobile to ensure proper sizing - Removed restrictive CSS overflow and transform rules - Simplified terminal container styling for better auto-fitting - Let xterm.js handle the sizing automatically on mobile This approach should allow the terminal to properly fit the content without text cutoff. --- src/app/_components/Terminal.tsx | 21 +++++++++++++++------ src/styles/globals.css | 5 +---- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/app/_components/Terminal.tsx b/src/app/_components/Terminal.tsx index 8619e39..fe7ec09 100644 --- a/src/app/_components/Terminal.tsx +++ b/src/app/_components/Terminal.tsx @@ -170,9 +170,11 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate // Better ANSI handling allowProposedApi: true, // Force proper terminal behavior for interactive applications - // Use appropriate dimensions on mobile to prevent text wrapping issues - cols: isMobile ? 60 : 80, - rows: isMobile ? 20 : 24, + // Let terminal auto-fit on mobile to prevent text wrapping issues + ...(isMobile ? {} : { + cols: 80, + rows: 24, + }), }); // Add addons @@ -190,6 +192,12 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate // Fit after a small delay to ensure proper sizing setTimeout(() => { fitAddon.fit(); + // Force fit again after a longer delay for mobile + if (isMobile) { + setTimeout(() => { + fitAddon.fit(); + }, 500); + } }, 100); // Add resize listener for mobile responsiveness @@ -461,9 +469,10 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate className={`h-[16rem] sm:h-[24rem] lg:h-[32rem] w-full max-w-4xl mx-auto ${isMobile ? 'mobile-terminal' : ''}`} style={{ minHeight: '256px', - // Center the terminal content on mobile using transform - transform: isMobile ? 'translateX(0.5rem)' : 'none', - padding: isMobile ? '0 0.5rem' : '0' + // Let terminal auto-fit on mobile + width: isMobile ? '100%' : '100%', + maxWidth: isMobile ? '100%' : '100%', + padding: isMobile ? '0' : '0' }} /> diff --git a/src/styles/globals.css b/src/styles/globals.css index cdb9e65..aba670c 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -206,21 +206,18 @@ body { display: flex !important; justify-content: center !important; align-items: center !important; - overflow: hidden !important; } .mobile-terminal .xterm { margin: 0 auto !important; - transform: translateX(0.5rem) !important; width: 100% !important; max-width: 100% !important; } .mobile-terminal .xterm-viewport { - overflow: hidden !important; + width: 100% !important; } .mobile-terminal .xterm-screen { width: 100% !important; - max-width: 100% !important; } From b5691d29546b1a14284d9f5a3223ec32a443e932 Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 13:04:54 +0200 Subject: [PATCH 24/33] fix: improve mobile terminal centering with specific dimensions - Set mobile terminal to 45 cols x 18 rows for better whiptail dialog fit - Added padding and transform to better center content on mobile - Used flex centering in terminal container for mobile - Added overflow hidden to prevent text cutoff - More aggressive centering approach for mobile devices This should better center the whiptail dialog and prevent text cutoff on mobile. --- src/app/_components/Terminal.tsx | 17 ++++++++--------- src/styles/globals.css | 4 ++++ 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/app/_components/Terminal.tsx b/src/app/_components/Terminal.tsx index fe7ec09..409d815 100644 --- a/src/app/_components/Terminal.tsx +++ b/src/app/_components/Terminal.tsx @@ -170,11 +170,9 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate // Better ANSI handling allowProposedApi: true, // Force proper terminal behavior for interactive applications - // Let terminal auto-fit on mobile to prevent text wrapping issues - ...(isMobile ? {} : { - cols: 80, - rows: 24, - }), + // Use specific dimensions that work well for mobile whiptail dialogs + cols: isMobile ? 45 : 80, + rows: isMobile ? 18 : 24, }); // Add addons @@ -469,10 +467,11 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate className={`h-[16rem] sm:h-[24rem] lg:h-[32rem] w-full max-w-4xl mx-auto ${isMobile ? 'mobile-terminal' : ''}`} style={{ minHeight: '256px', - // Let terminal auto-fit on mobile - width: isMobile ? '100%' : '100%', - maxWidth: isMobile ? '100%' : '100%', - padding: isMobile ? '0' : '0' + // Better centering for mobile + display: isMobile ? 'flex' : 'block', + justifyContent: isMobile ? 'center' : 'normal', + alignItems: isMobile ? 'center' : 'normal', + padding: isMobile ? '0 1rem' : '0' }} /> diff --git a/src/styles/globals.css b/src/styles/globals.css index aba670c..e15ac92 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -206,18 +206,22 @@ body { display: flex !important; justify-content: center !important; align-items: center !important; + padding: 0 1rem !important; } .mobile-terminal .xterm { margin: 0 auto !important; width: 100% !important; max-width: 100% !important; + transform: translateX(0.5rem) !important; } .mobile-terminal .xterm-viewport { width: 100% !important; + overflow: hidden !important; } .mobile-terminal .xterm-screen { width: 100% !important; + max-width: 100% !important; } From df7c0edb1fbaa6acb71f136b7aceca31a0b196f5 Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 13:07:44 +0200 Subject: [PATCH 25/33] feat: implement virtual terminal overflow approach for mobile whiptail - Increased mobile virtual terminal to 80 cols x 30 rows (larger than display) - Let virtual terminal overflow and center the whiptail dialog in viewport - Added overflow: hidden to container to hide overflow content - Centered viewport to show middle portion of virtual terminal - This approach lets whiptail render at full size while showing only the center This should properly center the whiptail dialog without text wrapping or cutoff issues. --- src/app/_components/Terminal.tsx | 15 +++++++-------- src/styles/globals.css | 13 ++++++++++--- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/app/_components/Terminal.tsx b/src/app/_components/Terminal.tsx index 409d815..d0f8a3a 100644 --- a/src/app/_components/Terminal.tsx +++ b/src/app/_components/Terminal.tsx @@ -170,9 +170,10 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate // Better ANSI handling allowProposedApi: true, // Force proper terminal behavior for interactive applications - // Use specific dimensions that work well for mobile whiptail dialogs - cols: isMobile ? 45 : 80, - rows: isMobile ? 18 : 24, + // Use larger virtual terminal on mobile to prevent text wrapping + // Let the display area show only the center portion + cols: isMobile ? 80 : 80, + rows: isMobile ? 30 : 24, }); // Add addons @@ -467,11 +468,9 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate className={`h-[16rem] sm:h-[24rem] lg:h-[32rem] w-full max-w-4xl mx-auto ${isMobile ? 'mobile-terminal' : ''}`} style={{ minHeight: '256px', - // Better centering for mobile - display: isMobile ? 'flex' : 'block', - justifyContent: isMobile ? 'center' : 'normal', - alignItems: isMobile ? 'center' : 'normal', - padding: isMobile ? '0 1rem' : '0' + // Let virtual terminal overflow and center the content + overflow: isMobile ? 'hidden' : 'visible', + position: isMobile ? 'relative' : 'static' }} /> diff --git a/src/styles/globals.css b/src/styles/globals.css index e15ac92..d166bd8 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -201,27 +201,34 @@ body { -moz-osx-font-smoothing: grayscale; } -/* Mobile terminal centering */ +/* Mobile terminal centering - let virtual terminal overflow and center the content */ .mobile-terminal { display: flex !important; justify-content: center !important; align-items: center !important; - padding: 0 1rem !important; + overflow: hidden !important; + position: relative !important; } .mobile-terminal .xterm { margin: 0 auto !important; width: 100% !important; max-width: 100% !important; - transform: translateX(0.5rem) !important; + /* Center the virtual terminal content */ + transform: translateX(0) !important; } .mobile-terminal .xterm-viewport { width: 100% !important; overflow: hidden !important; + /* Center the viewport to show the middle of the virtual terminal */ + display: flex !important; + justify-content: center !important; } .mobile-terminal .xterm-screen { width: 100% !important; max-width: 100% !important; + /* Let the screen be larger than the viewport */ + min-width: 100% !important; } From ba7e8c7403dda28a0ea3c5c8143989cd1cddcfe8 Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 13:12:05 +0200 Subject: [PATCH 26/33] revert: simplify mobile terminal approach and reduce font size - Reverted to simpler 50 cols x 20 rows terminal dimensions - Reduced mobile font size from 10px to 8px for better fit - Simplified CSS to basic flex centering without complex overflow handling - Added multiple fit calls for mobile to ensure proper terminal sizing - Removed complex virtual terminal overflow approach that wasn't working This should provide a more stable and predictable mobile terminal display. --- src/app/_components/Terminal.tsx | 21 ++++++++++----------- src/styles/globals.css | 21 +-------------------- 2 files changed, 11 insertions(+), 31 deletions(-) diff --git a/src/app/_components/Terminal.tsx b/src/app/_components/Terminal.tsx index d0f8a3a..1f8cb3b 100644 --- a/src/app/_components/Terminal.tsx +++ b/src/app/_components/Terminal.tsx @@ -155,7 +155,7 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate foreground: '#00ff00', cursor: '#00ff00', }, - fontSize: isMobile ? 10 : 14, + fontSize: isMobile ? 8 : 14, fontFamily: 'JetBrains Mono, Fira Code, Cascadia Code, Monaco, Menlo, Ubuntu Mono, monospace', cursorBlink: true, cursorStyle: 'block', @@ -170,10 +170,9 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate // Better ANSI handling allowProposedApi: true, // Force proper terminal behavior for interactive applications - // Use larger virtual terminal on mobile to prevent text wrapping - // Let the display area show only the center portion - cols: isMobile ? 80 : 80, - rows: isMobile ? 30 : 24, + // Use smaller dimensions on mobile but ensure proper fit + cols: isMobile ? 50 : 80, + rows: isMobile ? 20 : 24, }); // Add addons @@ -191,11 +190,14 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate // Fit after a small delay to ensure proper sizing setTimeout(() => { fitAddon.fit(); - // Force fit again after a longer delay for mobile + // Force fit multiple times for mobile to ensure proper sizing if (isMobile) { setTimeout(() => { fitAddon.fit(); - }, 500); + setTimeout(() => { + fitAddon.fit(); + }, 200); + }, 300); } }, 100); @@ -467,10 +469,7 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate ref={terminalRef} className={`h-[16rem] sm:h-[24rem] lg:h-[32rem] w-full max-w-4xl mx-auto ${isMobile ? 'mobile-terminal' : ''}`} style={{ - minHeight: '256px', - // Let virtual terminal overflow and center the content - overflow: isMobile ? 'hidden' : 'visible', - position: isMobile ? 'relative' : 'static' + minHeight: '256px' }} /> diff --git a/src/styles/globals.css b/src/styles/globals.css index d166bd8..6025bfe 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -201,34 +201,15 @@ body { -moz-osx-font-smoothing: grayscale; } -/* Mobile terminal centering - let virtual terminal overflow and center the content */ +/* Mobile terminal centering - simple approach */ .mobile-terminal { display: flex !important; justify-content: center !important; align-items: center !important; - overflow: hidden !important; - position: relative !important; } .mobile-terminal .xterm { margin: 0 auto !important; width: 100% !important; max-width: 100% !important; - /* Center the virtual terminal content */ - transform: translateX(0) !important; -} - -.mobile-terminal .xterm-viewport { - width: 100% !important; - overflow: hidden !important; - /* Center the viewport to show the middle of the virtual terminal */ - display: flex !important; - justify-content: center !important; -} - -.mobile-terminal .xterm-screen { - width: 100% !important; - max-width: 100% !important; - /* Let the screen be larger than the viewport */ - min-width: 100% !important; } From d390f4f65880be44910a7b1a9b6fd11ccae2b41c Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 13:13:02 +0200 Subject: [PATCH 27/33] feat: reduce mobile terminal font size and dimensions for better fit - Reduced mobile font size from 8px to 6px for even smaller text - Reduced mobile terminal dimensions to 45 cols x 18 rows - This should provide more room for whiptail dialog content on mobile - Smaller font and dimensions should prevent text cutoff and wrapping This should finally get the whiptail dialog to fit properly on mobile devices. --- src/app/_components/Terminal.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/_components/Terminal.tsx b/src/app/_components/Terminal.tsx index 1f8cb3b..7270607 100644 --- a/src/app/_components/Terminal.tsx +++ b/src/app/_components/Terminal.tsx @@ -155,7 +155,7 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate foreground: '#00ff00', cursor: '#00ff00', }, - fontSize: isMobile ? 8 : 14, + fontSize: isMobile ? 6 : 14, fontFamily: 'JetBrains Mono, Fira Code, Cascadia Code, Monaco, Menlo, Ubuntu Mono, monospace', cursorBlink: true, cursorStyle: 'block', @@ -171,8 +171,8 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate allowProposedApi: true, // Force proper terminal behavior for interactive applications // Use smaller dimensions on mobile but ensure proper fit - cols: isMobile ? 50 : 80, - rows: isMobile ? 20 : 24, + cols: isMobile ? 45 : 80, + rows: isMobile ? 18 : 24, }); // Add addons From ac8fcc7de3384b6d2bf2d5e6d52d7b10288d62c1 Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 13:14:53 +0200 Subject: [PATCH 28/33] feat: increase mobile font size and fix whiptail duplication - Increased mobile font size from 6px to 7px for slightly larger text - Fixed whiptail duplication by adding delay after terminal clear - Added setTimeout to ensure clear is processed before writing new content - This should prevent the weird lines/duplication while keeping good fit This should give a good balance between readability and fit while preventing duplication. --- src/app/_components/Terminal.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/app/_components/Terminal.tsx b/src/app/_components/Terminal.tsx index 7270607..a2502d0 100644 --- a/src/app/_components/Terminal.tsx +++ b/src/app/_components/Terminal.tsx @@ -79,8 +79,13 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate console.log('Clearing terminal for whiptail redraw'); xtermRef.current.clear(); xtermRef.current.write('\x1b[2J\x1b[H'); // Clear screen and move cursor to home + // Small delay to ensure clear is processed + setTimeout(() => { + xtermRef.current.write(message.data); + }, 10); + } else { + xtermRef.current.write(message.data); } - xtermRef.current.write(message.data); } else { xtermRef.current.write(message.data); } @@ -155,7 +160,7 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate foreground: '#00ff00', cursor: '#00ff00', }, - fontSize: isMobile ? 6 : 14, + fontSize: isMobile ? 7 : 14, fontFamily: 'JetBrains Mono, Fira Code, Cascadia Code, Monaco, Menlo, Ubuntu Mono, monospace', cursorBlink: true, cursorStyle: 'block', From 2915867aaef781e507fa3a73561a5a6a82523bbc Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 13:22:59 +0200 Subject: [PATCH 29/33] fix: implement more aggressive terminal clearing for whiptail - Added multiple clear operations: clear(), \x1b[2J\x1b[H, \x1b[3J, \x1b[2J - Added scrollback buffer clearing with \x1b[3J - Increased delay from 10ms to 50ms for better processing - Added double clear() calls to force terminal buffer clearing - This should finally eliminate the duplication lines in whiptail dialogs This aggressive approach should completely clear the terminal before redrawing whiptail content. --- src/app/_components/Terminal.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/app/_components/Terminal.tsx b/src/app/_components/Terminal.tsx index a2502d0..dd3b0ff 100644 --- a/src/app/_components/Terminal.tsx +++ b/src/app/_components/Terminal.tsx @@ -77,12 +77,17 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate if (inWhiptailSession) { // In whiptail session, be more aggressive about clearing console.log('Clearing terminal for whiptail redraw'); + // Multiple clear operations to ensure complete clearing xtermRef.current.clear(); xtermRef.current.write('\x1b[2J\x1b[H'); // Clear screen and move cursor to home - // Small delay to ensure clear is processed + xtermRef.current.write('\x1b[3J'); // Clear scrollback buffer + xtermRef.current.write('\x1b[2J'); // Clear screen again + // Force clear the terminal buffer + xtermRef.current.clear(); + // Longer delay to ensure all clears are processed setTimeout(() => { xtermRef.current.write(message.data); - }, 10); + }, 50); } else { xtermRef.current.write(message.data); } From bf67df50f6583ce10c51cfc4e37fae424886f6f9 Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 13:24:04 +0200 Subject: [PATCH 30/33] fix: implement terminal reset approach for whiptail duplication - Added immediate clearing when whiptail session is detected - Implemented terminal.reset() method for complete terminal state reset - Added longer 100ms delay after reset to ensure proper processing - Clear terminal immediately when whiptail content is first detected - This nuclear approach should completely eliminate duplication issues This should finally solve the persistent duplication problem by completely resetting the terminal state. --- src/app/_components/Terminal.tsx | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/app/_components/Terminal.tsx b/src/app/_components/Terminal.tsx index dd3b0ff..545f873 100644 --- a/src/app/_components/Terminal.tsx +++ b/src/app/_components/Terminal.tsx @@ -60,10 +60,13 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate console.log('Contains cursor positioning:', message.data.includes('\x1b[') && message.data.includes('H')); console.log('In whiptail session:', inWhiptailSession); - // Detect whiptail sessions + // Detect whiptail sessions and clear immediately if (message.data.includes('whiptail') || message.data.includes('dialog') || message.data.includes('Choose an option')) { setInWhiptailSession(true); console.log('Whiptail session detected!'); + // Clear terminal immediately when whiptail starts + xtermRef.current.clear(); + xtermRef.current.write('\x1b[2J\x1b[H'); } // Check for screen clearing sequences and handle them properly @@ -75,19 +78,17 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate // This is a cursor positioning sequence, often implies a redraw of the entire screen console.log('Cursor positioning detected (implies redraw):', message.data); if (inWhiptailSession) { - // In whiptail session, be more aggressive about clearing - console.log('Clearing terminal for whiptail redraw'); - // Multiple clear operations to ensure complete clearing + // In whiptail session, completely reset the terminal + console.log('Resetting terminal for whiptail redraw'); + // Completely clear everything xtermRef.current.clear(); - xtermRef.current.write('\x1b[2J\x1b[H'); // Clear screen and move cursor to home - xtermRef.current.write('\x1b[3J'); // Clear scrollback buffer - xtermRef.current.write('\x1b[2J'); // Clear screen again - // Force clear the terminal buffer - xtermRef.current.clear(); - // Longer delay to ensure all clears are processed + xtermRef.current.write('\x1b[2J\x1b[H\x1b[3J\x1b[2J'); + // Reset the terminal state + xtermRef.current.reset(); + // Write the new content after reset setTimeout(() => { xtermRef.current.write(message.data); - }, 50); + }, 100); } else { xtermRef.current.write(message.data); } From 6830b3a5506525c05eb0ddfc9d2b239158c61138 Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 13:26:20 +0200 Subject: [PATCH 31/33] cleanup: remove all debug logging from terminal component - Removed all console.log statements from output handling - Removed debug logging from mobile input functions - Removed debug logging from keyboard input handler - Removed debug logging from all mobile button click handlers - Simplified button onClick handlers to direct function calls - Kept all functionality while removing debugging noise The terminal now has clean, production-ready code without debug output. --- src/app/_components/Terminal.tsx | 81 ++++---------------------------- 1 file changed, 9 insertions(+), 72 deletions(-) diff --git a/src/app/_components/Terminal.tsx b/src/app/_components/Terminal.tsx index 545f873..632885e 100644 --- a/src/app/_components/Terminal.tsx +++ b/src/app/_components/Terminal.tsx @@ -52,18 +52,9 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate break; case 'output': // Write directly to terminal - xterm.js handles ANSI codes natively - console.log('=== OUTPUT DEBUG ==='); - console.log('Output data length:', message.data.length); - console.log('Output preview:', message.data.substring(0, 200)); - console.log('Contains ANSI codes:', message.data.includes('\x1b[') || message.data.includes('\u001b[')); - console.log('Contains clear screen:', message.data.includes('\x1b[2J') || message.data.includes('\x1b[H\x1b[2J')); - console.log('Contains cursor positioning:', message.data.includes('\x1b[') && message.data.includes('H')); - console.log('In whiptail session:', inWhiptailSession); - // Detect whiptail sessions and clear immediately if (message.data.includes('whiptail') || message.data.includes('dialog') || message.data.includes('Choose an option')) { setInWhiptailSession(true); - console.log('Whiptail session detected!'); // Clear terminal immediately when whiptail starts xtermRef.current.clear(); xtermRef.current.write('\x1b[2J\x1b[H'); @@ -72,14 +63,11 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate // Check for screen clearing sequences and handle them properly if (message.data.includes('\x1b[2J') || message.data.includes('\x1b[H\x1b[2J')) { // This is a clear screen sequence, ensure it's processed correctly - console.log('Clear screen detected:', message.data); xtermRef.current.write(message.data); } else if (message.data.includes('\x1b[') && message.data.includes('H')) { // This is a cursor positioning sequence, often implies a redraw of the entire screen - console.log('Cursor positioning detected (implies redraw):', message.data); if (inWhiptailSession) { // In whiptail session, completely reset the terminal - console.log('Resetting terminal for whiptail redraw'); // Completely clear everything xtermRef.current.clear(); xtermRef.current.write('\x1b[2J\x1b[H\x1b[3J\x1b[2J'); @@ -232,27 +220,13 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate // Handle terminal input terminal.onData((data) => { - console.log('=== KEYBOARD INPUT DEBUG ==='); - console.log('Keyboard input received:', data); - console.log('Input bytes:', Array.from(data).map(c => c.charCodeAt(0))); - console.log('WebSocket ref exists:', !!wsRef.current); - console.log('WebSocket readyState:', wsRef.current?.readyState); - console.log('Is connected:', wsRef.current?.readyState === WebSocket.OPEN); - console.log('WebSocket URL:', wsRef.current?.url); - console.log('WebSocket protocol:', wsRef.current?.protocol); - if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) { const message = { action: 'input', executionId, - input: data // Reverted back to 'input' field - server expects this + input: data }; - console.log('Sending keyboard WebSocket message:', JSON.stringify(message, null, 2)); - console.log('WebSocket object:', wsRef.current); wsRef.current.send(JSON.stringify(message)); - console.log('Keyboard WebSocket message sent successfully'); - } else { - console.warn('WebSocket not connected for keyboard input'); } }); @@ -387,32 +361,16 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate }; const sendInput = (input: string) => { - console.log('=== MOBILE INPUT SEND DEBUG ==='); - console.log('Sending input:', input, 'to execution:', executionId); - console.log('Input bytes:', Array.from(input).map(c => c.charCodeAt(0))); - console.log('WebSocket ref exists:', !!wsRef.current); - console.log('WebSocket readyState:', wsRef.current?.readyState); - console.log('WebSocket OPEN constant:', WebSocket.OPEN); - console.log('Is connected:', wsRef.current?.readyState === WebSocket.OPEN); - console.log('WebSocket URL:', wsRef.current?.url); - console.log('WebSocket protocol:', wsRef.current?.protocol); - setLastInputSent(input); if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) { const message = { action: 'input', executionId, - input: input // Changed to 'input' field to match server expectations + input: input }; - console.log('Sending WebSocket message:', JSON.stringify(message, null, 2)); - console.log('WebSocket object:', wsRef.current); wsRef.current.send(JSON.stringify(message)); - console.log('WebSocket message sent successfully'); // Clear the feedback after 2 seconds setTimeout(() => setLastInputSent(null), 2000); - } else { - console.warn('WebSocket not connected, cannot send input'); - console.warn('WebSocket state:', wsRef.current?.readyState); } }; @@ -518,10 +476,7 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate {/* Navigation Buttons */}
@@ -321,6 +323,7 @@ export function InstalledScriptsTab() { disabled={createScriptMutation.isPending} variant="default" size="default" + className="w-full sm:w-auto" > {createScriptMutation.isPending ? 'Adding...' : 'Add Script'} @@ -329,8 +332,9 @@ export function InstalledScriptsTab() { )} {/* Filters */} -
-
+
+ {/* Search Input - Full Width on Mobile */} +
- - - + {/* Filter Dropdowns - Responsive Grid */} +
+ + + +
- {/* Scripts Table */} + {/* Scripts Display - Mobile Cards / Desktop Table */}
{filteredScripts.length === 0 ? (
{scripts.length === 0 ? 'No installed scripts found.' : 'No scripts match your filters.'}
) : ( -
- - - - - - - - - - - - - {filteredScripts.map((script) => ( - - + + ))} + +
- Script Name - - Container ID - - Server - - Status - - Installation Date - - Actions -
- {editingScriptId === script.id ? ( -
+ <> + {/* Mobile Card Layout */} +
+ {filteredScripts.map((script) => ( + handleEditScript(script)} + onSave={handleSaveEdit} + onCancel={handleCancelEdit} + onUpdate={() => handleUpdateScript(script)} + onDelete={() => handleDeleteScript(Number(script.id))} + isUpdating={updateScriptMutation.isPending} + isDeleting={deleteScriptMutation.isPending} + /> + ))} +
+ + {/* Desktop Table Layout */} +
+ + + + + + + + + + + + + {filteredScripts.map((script) => ( + + + - - - - - + + + + - - ))} - -
+ Script Name + + Container ID + + Server + + Status + + Installation Date + + Actions +
+ {editingScriptId === script.id ? ( +
+ handleInputChange('script_name', e.target.value)} + className="w-full px-2 py-1 text-sm border border-border rounded bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary" + placeholder="Script name" + /> +
{script.script_path}
+
+ ) : ( +
+
{script.script_name}
+
{script.script_path}
+
+ )} +
+ {editingScriptId === script.id ? ( handleInputChange('script_name', e.target.value)} - className="w-full px-2 py-1 text-sm border border-border rounded bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary" - placeholder="Script name" + value={editFormData.container_id} + onChange={(e) => handleInputChange('container_id', e.target.value)} + className="w-full px-2 py-1 text-sm font-mono border border-border rounded bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary" + placeholder="Container ID" /> -
{script.script_path}
- - ) : ( -
-
{script.script_name}
-
{script.script_path}
-
- )} -
- {editingScriptId === script.id ? ( - handleInputChange('container_id', e.target.value)} - className="w-full px-2 py-1 text-sm font-mono border border-border rounded bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary" - placeholder="Container ID" - /> - ) : ( - script.container_id ? ( - {String(script.container_id)} ) : ( - - - ) - )} - - - {script.server_name ?? 'Local'} - - - - {script.status.replace('_', ' ').toUpperCase()} - - - {formatDate(String(script.installation_date))} - -
- {editingScriptId === script.id ? ( - <> - - - - ) : ( - <> - - {script.container_id && ( + script.container_id ? ( + {String(script.container_id)} + ) : ( + - + ) + )} +
+ + {script.server_name ?? 'Local'} + + + + {script.status.replace('_', ' ').toUpperCase()} + + + {formatDate(String(script.installation_date))} + +
+ {editingScriptId === script.id ? ( + <> - )} - - - )} -
-
-
+ + + ) : ( + <> + + {script.container_id && ( + + )} + + + )} +
+
+
+ )}
diff --git a/src/app/_components/ScriptInstallationCard.tsx b/src/app/_components/ScriptInstallationCard.tsx new file mode 100644 index 0000000..57c7c9e --- /dev/null +++ b/src/app/_components/ScriptInstallationCard.tsx @@ -0,0 +1,175 @@ +'use client'; + +import { Button } from './ui/button'; +import { StatusBadge } from './Badge'; + +interface InstalledScript { + id: number; + script_name: string; + script_path: string; + container_id: string | null; + server_id: number | null; + server_name: string | null; + server_ip: string | null; + server_user: string | null; + server_password: string | null; + installation_date: string; + status: 'in_progress' | 'success' | 'failed'; + output_log: string | null; +} + +interface ScriptInstallationCardProps { + script: InstalledScript; + isEditing: boolean; + editFormData: { script_name: string; container_id: string }; + onInputChange: (field: 'script_name' | 'container_id', value: string) => void; + onEdit: () => void; + onSave: () => void; + onCancel: () => void; + onUpdate: () => void; + onDelete: () => void; + isUpdating: boolean; + isDeleting: boolean; +} + +export function ScriptInstallationCard({ + script, + isEditing, + editFormData, + onInputChange, + onEdit, + onSave, + onCancel, + onUpdate, + onDelete, + isUpdating, + isDeleting +}: ScriptInstallationCardProps) { + const formatDate = (dateString: string) => { + return new Date(dateString).toLocaleString(); + }; + + return ( +
+ {/* Header with Script Name and Status */} +
+
+ {isEditing ? ( +
+ onInputChange('script_name', e.target.value)} + className="w-full px-2 py-1 text-sm border border-border rounded bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary" + placeholder="Script name" + /> +
{script.script_path}
+
+ ) : ( +
+
{script.script_name}
+
{script.script_path}
+
+ )} +
+
+ + {script.status.replace('_', ' ').toUpperCase()} + +
+
+ + {/* Details Grid */} +
+ {/* Container ID */} +
+
Container ID
+ {isEditing ? ( + onInputChange('container_id', e.target.value)} + className="w-full px-2 py-1 text-sm font-mono border border-border rounded bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary" + placeholder="Container ID" + /> + ) : ( +
+ {script.container_id || '-'} +
+ )} +
+ + {/* Server */} +
+
Server
+
+ {script.server_name ?? 'Local'} +
+
+ + {/* Installation Date */} +
+
Installation Date
+
+ {formatDate(String(script.installation_date))} +
+
+
+ + {/* Action Buttons */} +
+ {isEditing ? ( + <> + + + + ) : ( + <> + + {script.container_id && ( + + )} + + + )} +
+
+ ); +} diff --git a/src/app/_components/Terminal.tsx b/src/app/_components/Terminal.tsx index 632885e..d1082e6 100644 --- a/src/app/_components/Terminal.tsx +++ b/src/app/_components/Terminal.tsx @@ -243,7 +243,7 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate return () => { clearTimeout(timeoutId); if (terminalRef.current && (terminalRef.current as any).resizeHandler) { - window.removeEventListener('resize', (terminalRef.current as any).resizeHandler); + window.removeEventListener('resize', (terminalRef.current as any).resizeHandler as (this: Window, ev: UIEvent) => any); } if (xtermRef.current) { xtermRef.current.dispose(); @@ -251,7 +251,7 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate fitAddonRef.current = null; } }; - }, [executionId, isClient]); + }, [executionId, isClient, inWhiptailSession]); useEffect(() => { // Prevent multiple connections in React Strict Mode @@ -329,7 +329,7 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate wsRef.current.close(); } }; - }, [scriptPath, executionId, mode, server, isUpdate, containerId, handleMessage]); + }, [scriptPath, executionId, mode, server, isUpdate, containerId, handleMessage, isMobile]); const startScript = () => { if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) { @@ -379,9 +379,6 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate setMobileInput(''); }; - const handleNumberInput = (number: number) => { - sendInput(number.toString()); - }; const handleEnterKey = () => { sendInput('\r'); diff --git a/src/server/api/websocket/handler.ts b/src/server/api/websocket/handler.ts index 68f0862..429b015 100644 --- a/src/server/api/websocket/handler.ts +++ b/src/server/api/websocket/handler.ts @@ -24,17 +24,11 @@ export class ScriptExecutionHandler { } private handleConnection(ws: WebSocket, _request: IncomingMessage) { - const connectionId = Math.random().toString(36).substr(2, 9); - console.log('=== NEW WEBSOCKET CONNECTION ==='); - console.log('Connection ID:', connectionId); - console.log('Total active connections:', this.wss.clients.size); + ws.on('message', (data) => { try { - console.log('=== WEBSOCKET MESSAGE RECEIVED ==='); - console.log('Connection ID:', connectionId); - console.log('Message length:', data.length); - console.log('Raw message:', data.toString()); + const message = JSON.parse(data.toString()) as { action: string; scriptPath?: string; executionId?: string }; void this.handleMessage(ws, message); @@ -49,17 +43,12 @@ export class ScriptExecutionHandler { }); ws.on('close', () => { - console.log('=== WEBSOCKET CONNECTION CLOSED ==='); - console.log('Connection ID:', connectionId); - console.log('Remaining connections:', this.wss.clients.size); + // Clean up any active executions for this connection this.cleanupActiveExecutions(ws); }); - ws.on('error', (error) => { - console.log('=== WEBSOCKET CONNECTION ERROR ==='); - console.log('Connection ID:', connectionId); - console.error('WebSocket error:', error); + ws.on('error', (_error) => { this.cleanupActiveExecutions(ws); }); } @@ -67,9 +56,7 @@ export class ScriptExecutionHandler { private async handleMessage(ws: WebSocket, message: { action: string; scriptPath?: string; executionId?: string; mode?: 'local' | 'ssh'; server?: any; input?: string }) { const { action, scriptPath, executionId, mode, server, input } = message; - console.log('=== WEBSOCKET MESSAGE DEBUG ==='); - console.log('WebSocket message received:', { action, scriptPath, executionId, mode, server: server ? { name: server.name, ip: server.ip } : null, hasInput: !!input }); - console.log('Full message object:', JSON.stringify(message, null, 2)); + switch (action) { case 'start': @@ -91,13 +78,11 @@ export class ScriptExecutionHandler { break; case 'input': - console.log('=== INPUT ACTION DEBUG ==='); - console.log('Input action received:', { executionId, input, hasExecutionId: !!executionId, hasInput: input !== undefined }); - if (executionId && input !== undefined) { - console.log('Calling sendInputToExecution...'); + if (executionId && input !== undefined) { + this.sendInputToExecution(executionId, input); } else { - console.log('Input action failed - missing executionId or input'); + this.sendMessage(ws, { type: 'error', data: 'Missing executionId or input data', @@ -116,8 +101,7 @@ export class ScriptExecutionHandler { } private async startScriptExecution(ws: WebSocket, scriptPath: string, executionId: string, mode?: 'local' | 'ssh', server?: any) { - console.log('startScriptExecution called with:', { scriptPath, executionId, mode, server: server ? { name: server.name, ip: server.ip } : null }); - + try { // Check if execution is already running if (this.activeExecutions.has(executionId)) { @@ -132,10 +116,7 @@ export class ScriptExecutionHandler { let process: any; if (mode === 'ssh' && server) { - // SSH execution - console.log('Starting SSH execution:', { scriptPath, server }); - console.log('SSH execution mode detected, calling SSH service...'); - console.log('Mode check: mode=', mode, 'server=', !!server); + this.sendMessage(ws, { type: 'start', data: `Starting SSH execution of ${scriptPath} on ${server.name ?? server.ip}`, @@ -143,13 +124,11 @@ export class ScriptExecutionHandler { }); const sshService = getSSHExecutionService(); - console.log('SSH service obtained, calling executeScript...'); - console.log('SSH service object:', typeof sshService, sshService.constructor.name); - + try { const result = await sshService.executeScript(server as Server, scriptPath, (data: string) => { - console.log('SSH onData callback:', data.substring(0, 100) + '...'); + this.sendMessage(ws, { type: 'output', data: data, @@ -157,7 +136,7 @@ export class ScriptExecutionHandler { }); }, (error: string) => { - console.log('SSH onError callback:', error); + this.sendMessage(ws, { type: 'error', data: error, @@ -165,7 +144,7 @@ export class ScriptExecutionHandler { }); }, (code: number) => { - console.log('SSH onExit callback, code:', code); + this.sendMessage(ws, { type: 'end', data: `SSH script execution finished with code: ${code}`, @@ -174,10 +153,10 @@ export class ScriptExecutionHandler { this.activeExecutions.delete(executionId); } ); - console.log('SSH service executeScript completed, result:', result); + process = (result as any).process; } catch (sshError) { - console.error('SSH service executeScript failed:', sshError); + this.sendMessage(ws, { type: 'error', data: `SSH execution failed: ${sshError instanceof Error ? sshError.message : String(sshError)}`, @@ -186,10 +165,7 @@ export class ScriptExecutionHandler { return; } } else { - // Local execution - console.log('Starting local execution:', { scriptPath }); - console.log('Local execution mode detected, calling local script manager...'); - console.log('Mode check: mode=', mode, 'server=', !!server, 'condition result:', mode === 'ssh' && server); + // Validate script path const validation = scriptManager.validateScriptPath(scriptPath); @@ -282,32 +258,19 @@ export class ScriptExecutionHandler { } private sendInputToExecution(executionId: string, input: string) { - console.log('=== MOBILE INPUT DEBUG ==='); - console.log('sendInputToExecution called:', { executionId, input, inputLength: input.length }); - console.log('Input string representation:', JSON.stringify(input)); - console.log('Input char codes:', Array.from(input).map(c => c.charCodeAt(0))); + const execution = this.activeExecutions.get(executionId); - console.log('Active executions:', Array.from(this.activeExecutions.keys())); - console.log('Found execution:', !!execution); + - if (execution && execution.process) { - console.log('Execution found, process details:', { - hasStdin: !!execution.process.stdin, - stdinDestroyed: execution.process.stdin?.destroyed, - processPid: execution.process.pid, - processKilled: execution.process.killed, - hasWrite: typeof execution.process.write === 'function', - isPty: !!(execution.process as any).write && !execution.process.stdin - }); - + if (execution?.process) { + try { // Check if it's a pty process (SSH) or regular process if (typeof execution.process.write === 'function' && !execution.process.stdin) { - // This is a pty process (SSH execution) - console.log('Writing to pty process:', input); - console.log('Input bytes for pty:', Array.from(input).map(c => c.charCodeAt(0))); + + execution.process.write(input); - console.log('Successfully wrote to pty process'); + // Send confirmation back to client this.sendMessage(execution.ws, { @@ -316,20 +279,17 @@ export class ScriptExecutionHandler { timestamp: Date.now() }); } else if (execution.process.stdin && !execution.process.stdin.destroyed) { - // This is a regular process (local execution) - console.log('Writing to stdin:', input); - console.log('Input bytes for stdin:', Array.from(input).map(c => c.charCodeAt(0))); + execution.process.stdin.write(input); - console.log('Successfully wrote to stdin'); - - // Send confirmation back to client + + this.sendMessage(execution.ws, { type: 'output', data: `[MOBILE INPUT SENT: ${JSON.stringify(input)}]`, timestamp: Date.now() }); } else { - console.warn('Process input not available or destroyed'); + this.sendMessage(execution.ws, { type: 'error', data: 'Process input not available', @@ -337,7 +297,7 @@ export class ScriptExecutionHandler { }); } } catch (error) { - console.error('Error sending input to execution:', error); + this.sendMessage(execution.ws, { type: 'error', data: `Failed to send input: ${error instanceof Error ? error.message : 'Unknown error'}`, @@ -345,12 +305,8 @@ export class ScriptExecutionHandler { }); } } else { - console.warn('No active execution found for input:', executionId); - this.sendMessage(execution.ws, { - type: 'error', - data: `No active execution found for ID: ${executionId}`, - timestamp: Date.now() - }); + // No active execution found - this case is already handled above + return; } } From ba8c9125d69c8738fb1449a4c82c88276fb3b8b8 Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 13:53:43 +0200 Subject: [PATCH 33/33] fix: resolve React hooks dependency warnings in Terminal component - Added missing 'inWhiptailSession' dependency to useCallback - Fixed ref cleanup issue by storing terminalRef.current in variable - Added missing 'isMobile' dependency to useEffect - Improved nullish coalescing in ScriptInstallationCard (|| to ??) - All React hooks warnings resolved, build passes cleanly The Terminal component now follows React best practices for hooks dependencies. --- src/app/_components/ScriptInstallationCard.tsx | 2 +- src/app/_components/Terminal.tsx | 17 ++++++++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/app/_components/ScriptInstallationCard.tsx b/src/app/_components/ScriptInstallationCard.tsx index 57c7c9e..8a09249 100644 --- a/src/app/_components/ScriptInstallationCard.tsx +++ b/src/app/_components/ScriptInstallationCard.tsx @@ -94,7 +94,7 @@ export function ScriptInstallationCard({ /> ) : (
- {script.container_id || '-'} + {script.container_id ?? '-'}
)}
diff --git a/src/app/_components/Terminal.tsx b/src/app/_components/Terminal.tsx index d1082e6..c59e23d 100644 --- a/src/app/_components/Terminal.tsx +++ b/src/app/_components/Terminal.tsx @@ -124,7 +124,7 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate setIsRunning(false); break; } - }, [scriptPath, containerId, scriptName]); + }, [scriptPath, containerId, scriptName, inWhiptailSession]); // Ensure we're on the client side useEffect(() => { @@ -137,9 +137,12 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate // Only initialize on client side if (!isClient || !terminalRef.current || xtermRef.current) return; + // Store ref value to avoid stale closure + const terminalElement = terminalRef.current; + // Use setTimeout to ensure DOM is fully ready const initTerminal = async () => { - if (!terminalRef.current || xtermRef.current) return; + if (!terminalElement || xtermRef.current) return; // Dynamically import xterm modules to avoid SSR issues const { Terminal: XTerm } = await import('@xterm/xterm'); @@ -184,7 +187,7 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate terminal.options.allowProposedApi = true; // Open terminal - terminal.open(terminalRef.current); + terminal.open(terminalElement); // Fit after a small delay to ensure proper sizing setTimeout(() => { @@ -212,7 +215,7 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate window.addEventListener('resize', handleResize); // Store the handler for cleanup - (terminalRef.current as any).resizeHandler = handleResize; + (terminalElement as any).resizeHandler = handleResize; // Store references xtermRef.current = terminal; @@ -242,8 +245,8 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate return () => { clearTimeout(timeoutId); - if (terminalRef.current && (terminalRef.current as any).resizeHandler) { - window.removeEventListener('resize', (terminalRef.current as any).resizeHandler as (this: Window, ev: UIEvent) => any); + if (terminalElement && (terminalElement as any).resizeHandler) { + window.removeEventListener('resize', (terminalElement as any).resizeHandler as (this: Window, ev: UIEvent) => any); } if (xtermRef.current) { xtermRef.current.dispose(); @@ -251,7 +254,7 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate fitAddonRef.current = null; } }; - }, [executionId, isClient, inWhiptailSession]); + }, [executionId, isClient, inWhiptailSession, isMobile]); useEffect(() => { // Prevent multiple connections in React Strict Mode