diff --git a/src/App.tsx b/src/App.tsx index fce95e0d..3856a26b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -87,7 +87,7 @@ function AppContent() { // Determine what to show based on current tab const currentTabData = tabs.find(tab => tab.id === currentTab); - const showTerminalView = currentTabData?.type === 'terminal'; + const showTerminalView = currentTabData?.type === 'terminal' || currentTabData?.type === 'server'; const showHome = currentTabData?.type === 'home'; const showSshManager = currentTabData?.type === 'ssh_manager'; diff --git a/src/components/ui/button-group.tsx b/src/components/ui/button-group.tsx index e8029346..e3215e1b 100644 --- a/src/components/ui/button-group.tsx +++ b/src/components/ui/button-group.tsx @@ -1,4 +1,4 @@ -import { Children, ReactElement, cloneElement } from 'react'; +import { Children, ReactElement, cloneElement, isValidElement } from 'react'; import { ButtonProps } from '@/components/ui/button'; import { cn } from '@/lib/utils'; @@ -6,7 +6,7 @@ import { cn } from '@/lib/utils'; interface ButtonGroupProps { className?: string; orientation?: 'horizontal' | 'vertical'; - children: ReactElement[]; + children: ReactElement[] | React.ReactNode; } export const ButtonGroup = ({ @@ -14,10 +14,15 @@ export const ButtonGroup = ({ orientation = 'horizontal', children, }: ButtonGroupProps) => { - const totalButtons = Children.count(children); const isHorizontal = orientation === 'horizontal'; const isVertical = orientation === 'vertical'; + // Normalize and filter only valid React elements + const childArray = Children.toArray(children).filter((child): child is ReactElement => + isValidElement(child) + ); + const totalButtons = childArray.length; + return (
- {Children.map(children, (child, index) => { + {childArray.map((child, index) => { const isFirst = index === 0; const isLast = index === totalButtons - 1; diff --git a/src/contexts/TabContext.tsx b/src/contexts/TabContext.tsx index b2c8bd95..a1ef802c 100644 --- a/src/contexts/TabContext.tsx +++ b/src/contexts/TabContext.tsx @@ -2,7 +2,7 @@ import React, { createContext, useContext, useState, useRef, ReactNode } from 'r export interface Tab { id: number; - type: 'home' | 'terminal' | 'ssh_manager'; + type: 'home' | 'terminal' | 'ssh_manager' | 'server'; title: string; hostConfig?: any; terminalRef?: React.RefObject; diff --git a/src/ui/Navigation/Hosts/Host.tsx b/src/ui/Navigation/Hosts/Host.tsx index 63fa1ff5..7f7a757e 100644 --- a/src/ui/Navigation/Hosts/Host.tsx +++ b/src/ui/Navigation/Hosts/Host.tsx @@ -37,16 +37,14 @@ export function Host({ host }: HostProps): React.ReactElement { const tags = Array.isArray(host.tags) ? host.tags : []; const hasTags = tags.length > 0; + const title = host.name?.trim() ? host.name : `${host.username}@${host.ip}:${host.port}`; + const handleTerminalClick = () => { - console.log('Terminal button clicked for host:', host); - const title = host.name?.trim() ? host.name : `${host.username}@${host.ip}:${host.port}`; - console.log('Creating terminal tab with title:', title); - const tabId = addTab({ - type: 'terminal', - title, - hostConfig: host, - }); - console.log('Created terminal tab with ID:', tabId); + addTab({ type: 'terminal', title, hostConfig: host }); + }; + + const handleServerClick = () => { + addTab({ type: 'server', title, hostConfig: host }); }; return ( @@ -59,7 +57,7 @@ export function Host({ host }: HostProps): React.ReactElement { {host.name || host.ip}

- {canSplit && ( diff --git a/src/ui/SSH/Server/Server.tsx b/src/ui/SSH/Server/Server.tsx new file mode 100644 index 00000000..2885b9f3 --- /dev/null +++ b/src/ui/SSH/Server/Server.tsx @@ -0,0 +1,43 @@ +import React from "react"; +import { useSidebar } from "@/components/ui/sidebar"; + +interface ServerProps { + hostConfig?: any; + title?: string; + isVisible?: boolean; + isTopbarOpen?: boolean; + embedded?: boolean; // when rendered inside a pane in TerminalView +} + +export function Server({ hostConfig, title, isVisible = true, isTopbarOpen = true, embedded = false }: ServerProps): React.ReactElement { + const { state: sidebarState } = useSidebar(); + + const topMarginPx = isTopbarOpen ? 74 : 16; + const leftMarginPx = sidebarState === 'collapsed' ? 16 : 8; + const bottomMarginPx = 16; + + const wrapperStyle: React.CSSProperties = embedded + ? { opacity: isVisible ? 1 : 0, height: '100%', width: '100%' } + : { + opacity: isVisible ? 1 : 0, + marginLeft: leftMarginPx, + marginRight: 17, + marginTop: topMarginPx, + marginBottom: bottomMarginPx, + height: `calc(100vh - ${topMarginPx + bottomMarginPx}px)`, + }; + + const containerClass = embedded + ? "h-full w-full text-white overflow-hidden bg-transparent" + : "bg-[#18181b] text-white rounded-lg border-2 border-[#303032] overflow-hidden"; + + return ( +
+
+
+
{title || 'Server'}
+
+
+
+ ); +} diff --git a/src/ui/SSH/Terminal/TerminalView.tsx b/src/ui/SSH/Terminal/TerminalView.tsx index c4545915..2be3c6ab 100644 --- a/src/ui/SSH/Terminal/TerminalView.tsx +++ b/src/ui/SSH/Terminal/TerminalView.tsx @@ -1,9 +1,12 @@ import React, { useEffect, useRef, useState } from "react"; import {TerminalComponent} from "./TerminalComponent.tsx"; +import {Server as ServerView} from "@/ui/SSH/Server/Server.tsx"; import {useTabs} from "@/contexts/TabContext"; import {ResizablePanelGroup, ResizablePanel, ResizableHandle} from '@/components/ui/resizable.tsx'; import * as ResizablePrimitive from "react-resizable-panels"; import { useSidebar } from "@/components/ui/sidebar"; +import {LucideRefreshCcw, LucideRefreshCw, RefreshCcw, RefreshCcwDot} from "lucide-react"; +import { Button } from "@/components/ui/button.tsx"; interface TerminalViewProps { isTopbarOpen?: boolean; @@ -13,12 +16,13 @@ export function TerminalView({ isTopbarOpen = true }: TerminalViewProps): React. const {tabs, currentTab, allSplitScreenTab} = useTabs() as any; const { state: sidebarState } = useSidebar(); - const terminalTabs = tabs.filter((tab: any) => tab.type === 'terminal'); + const terminalTabs = tabs.filter((tab: any) => tab.type === 'terminal' || tab.type === 'server'); const containerRef = useRef(null); const panelRefs = useRef>({}); const [panelRects, setPanelRects] = useState>({}); const [ready, setReady] = useState(true); + const [resetKey, setResetKey] = useState(0); const updatePanelRects = () => { const next: Record = {}; @@ -79,7 +83,7 @@ export function TerminalView({ isTopbarOpen = true }: TerminalViewProps): React. useEffect(() => { scheduleMeasureAndFit(); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [allSplitScreenTab.length, isTopbarOpen, sidebarState]); + }, [allSplitScreenTab.length, isTopbarOpen, sidebarState, resetKey]); useEffect(() => { const roContainer = containerRef.current ? new ResizeObserver(() => { @@ -145,14 +149,24 @@ export function TerminalView({ isTopbarOpen = true }: TerminalViewProps): React. return (
- 0} - /> + {t.type === 'terminal' ? ( + 0} + /> + ) : ( + + )}
); @@ -161,6 +175,23 @@ export function TerminalView({ isTopbarOpen = true }: TerminalViewProps): React. ); }; + const ResetButton = ({ onClick }: { onClick: () => void }) => ( + + ); + + const handleReset = () => { + setResetKey((k) => k + 1); + requestAnimationFrame(() => scheduleMeasureAndFit()); + }; + const renderSplitOverlays = () => { const splitTabs = terminalTabs.filter((tab: any) => allSplitScreenTab.includes(tab.id)); const mainTab = terminalTabs.find((tab: any) => tab.id === currentTab); @@ -174,16 +205,19 @@ export function TerminalView({ isTopbarOpen = true }: TerminalViewProps): React. const [a,b] = layoutTabs as any[]; return (
- +
{ panelRefs.current[String(a.id)] = el; }} style={{height:'100%',width:'100%',display:'flex',flexDirection:'column',background:'transparent',position:'relative'}}> -
{a.title}
+
{a.title}
{ panelRefs.current[String(b.id)] = el; }} style={{height:'100%',width:'100%',display:'flex',flexDirection:'column',background:'transparent',position:'relative'}}> -
{b.title}
+
+ {b.title} + +
@@ -194,18 +228,21 @@ export function TerminalView({ isTopbarOpen = true }: TerminalViewProps): React. const [a,b,c] = layoutTabs as any[]; return (
- + - +
{ panelRefs.current[String(a.id)] = el; }} style={{height:'100%',width:'100%',display:'flex',flexDirection:'column',position:'relative'}}> -
{a.title}
+
{a.title}
{ panelRefs.current[String(b.id)] = el; }} style={{height:'100%',width:'100%',display:'flex',flexDirection:'column',position:'relative'}}> -
{b.title}
+
+ {b.title} + +
@@ -213,7 +250,7 @@ export function TerminalView({ isTopbarOpen = true }: TerminalViewProps): React.
{ panelRefs.current[String(c.id)] = el; }} style={{height:'100%',width:'100%',display:'flex',flexDirection:'column',position:'relative'}}> -
{c.title}
+
{c.title}
@@ -224,34 +261,37 @@ export function TerminalView({ isTopbarOpen = true }: TerminalViewProps): React. const [a,b,c,d] = layoutTabs as any[]; return (
- + - - + +
{ panelRefs.current[String(a.id)] = el; }} style={{height:'100%',width:'100%',display:'flex',flexDirection:'column',position:'relative'}}> -
{a.title}
+
{a.title}
- +
{ panelRefs.current[String(b.id)] = el; }} style={{height:'100%',width:'100%',display:'flex',flexDirection:'column',position:'relative'}}> -
{b.title}
+
+ {b.title} + +
- +
{ panelRefs.current[String(c.id)] = el; }} style={{height:'100%',width:'100%',display:'flex',flexDirection:'column',position:'relative'}}> -
{c.title}
+
{c.title}
{ panelRefs.current[String(d.id)] = el; }} style={{height:'100%',width:'100%',display:'flex',flexDirection:'column',position:'relative'}}> -
{d.title}
+
{d.title}