diff --git a/src/App.tsx b/src/App.tsx index 649d4965..3814169c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -88,7 +88,7 @@ function AppContent() { // Determine what to show based on current tab const currentTabData = tabs.find(tab => tab.id === currentTab); - const showTerminalView = currentTabData?.type === 'terminal' || currentTabData?.type === 'server'; + const showTerminalView = currentTabData?.type === 'terminal' || currentTabData?.type === 'server' || currentTabData?.type === 'config'; const showHome = currentTabData?.type === 'home'; const showSshManager = currentTabData?.type === 'ssh_manager'; const showAdmin = currentTabData?.type === 'admin'; diff --git a/src/contexts/TabContext.tsx b/src/contexts/TabContext.tsx index 7cf13dcd..24bd0b22 100644 --- a/src/contexts/TabContext.tsx +++ b/src/contexts/TabContext.tsx @@ -2,7 +2,7 @@ import React, { createContext, useContext, useState, useRef, type ReactNode } fr export interface Tab { id: number; - type: 'home' | 'terminal' | 'ssh_manager' | 'server' | 'admin'; + type: 'home' | 'terminal' | 'ssh_manager' | 'server' | 'admin' | 'config'; title: string; hostConfig?: any; terminalRef?: React.RefObject; @@ -42,7 +42,7 @@ export function TabProvider({ children }: TabProviderProps) { const nextTabId = useRef(2); function computeUniqueTitle(tabType: Tab['type'], desiredTitle: string | undefined): string { - const defaultTitle = tabType === 'server' ? 'Server' : 'Terminal'; + const defaultTitle = tabType === 'server' ? 'Server' : (tabType === 'config' ? 'Config' : 'Terminal'); const baseTitle = (desiredTitle || defaultTitle).trim(); // Extract base name without trailing " (n)" const match = baseTitle.match(/^(.*) \((\d+)\)$/); @@ -72,7 +72,7 @@ export function TabProvider({ children }: TabProviderProps) { const addTab = (tabData: Omit): number => { const id = nextTabId.current++; - const needsUniqueTitle = tabData.type === 'terminal' || tabData.type === 'server'; + const needsUniqueTitle = tabData.type === 'terminal' || tabData.type === 'server' || tabData.type === 'config'; const effectiveTitle = needsUniqueTitle ? computeUniqueTitle(tabData.type, tabData.title) : (tabData.title || ''); const newTab: Tab = { ...tabData, diff --git a/src/ui/Navigation/AppView.tsx b/src/ui/Navigation/AppView.tsx index 8c03e230..61263867 100644 --- a/src/ui/Navigation/AppView.tsx +++ b/src/ui/Navigation/AppView.tsx @@ -1,6 +1,7 @@ import React, { useEffect, useRef, useState } from "react"; import {TerminalComponent} from "../SSH/Terminal/TerminalComponent.tsx"; import {Server as ServerView} from "@/ui/SSH/Server/Server.tsx"; +import {ConfigEditor} from "@/ui/SSH/Config Editor/ConfigEditor.tsx"; import {useTabs} from "@/contexts/TabContext.tsx"; import {ResizablePanelGroup, ResizablePanel, ResizableHandle} from '@/components/ui/resizable.tsx'; import * as ResizablePrimitive from "react-resizable-panels"; @@ -16,7 +17,7 @@ export function AppView({ isTopbarOpen = true }: TerminalViewProps): React.React const {tabs, currentTab, allSplitScreenTab} = useTabs() as any; const { state: sidebarState } = useSidebar(); - const terminalTabs = tabs.filter((tab: any) => tab.type === 'terminal' || tab.type === 'server'); + const terminalTabs = tabs.filter((tab: any) => tab.type === 'terminal' || tab.type === 'server' || tab.type === 'config'); const containerRef = useRef(null); const panelRefs = useRef>({}); @@ -158,7 +159,7 @@ export function AppView({ isTopbarOpen = true }: TerminalViewProps): React.React showTitle={false} splitScreen={allSplitScreenTab.length>0} /> - ) : ( + ) : t.type === 'server' ? ( + ) : ( + )} diff --git a/src/ui/Navigation/Tabs/Tab.tsx b/src/ui/Navigation/Tabs/Tab.tsx index 558136f5..f9b793f0 100644 --- a/src/ui/Navigation/Tabs/Tab.tsx +++ b/src/ui/Navigation/Tabs/Tab.tsx @@ -1,7 +1,7 @@ import React from "react"; import {ButtonGroup} from "@/components/ui/button-group.tsx"; import {Button} from "@/components/ui/button.tsx"; -import {Home, SeparatorVertical, X, Terminal as TerminalIcon, Server as ServerIcon} from "lucide-react"; +import {Home, SeparatorVertical, X, Terminal as TerminalIcon, Server as ServerIcon, Folder as FolderIcon} from "lucide-react"; interface TabProps { tabType: string; @@ -31,8 +31,9 @@ export function Tab({tabType, title, isActive, onActivate, onClose, onSplit, can ); } - if (tabType === "terminal" || tabType === "server") { + if (tabType === "terminal" || tabType === "server" || tabType === "config") { const isServer = tabType === 'server'; + const isConfig = tabType === 'config'; return ( {canSplit && ( - - - -
- {view === 'servers' && ( - <> -
- setSearch(e.target.value)} - placeholder="Search hosts by name, username, IP, folder, tags..." - className="w-full h-8 text-sm bg-[#18181b] border border-[#23232a] text-white placeholder:text-muted-foreground rounded" - autoComplete="off" - /> -
- -
-
-
- -
-
-
- - {sortedFolders.map((folder, idx) => ( - - - {folder} - - {filteredSshByFolder[folder].map(conn => ( - - ))} - - - {idx < sortedFolders.length - 1 && ( -
- -
- )} -
- ))} -
-
-
-
-
-
- - )} - {view === 'files' && activeServer && ( -
-
- - setCurrentPath(e.target.value)} - className="flex-1 bg-[#18181b] border border-[#434345] text-white truncate rounded-md px-2 py-1 focus:outline-none focus:ring-2 focus:ring-ring hover:border-[#5a5a5d]" - /> -
-
- setFileSearch(e.target.value)} - /> -
-
- -
- {connectingSSH || filesLoading ? ( -
Loading...
- ) : filesError ? ( -
{filesError}
- ) : filteredFiles.length === 0 ? ( -
No files or - folders found.
- ) : ( -
- {filteredFiles.map((item: any) => { - const isOpen = (tabs || []).some((t: any) => t.id === item.path); - return ( -
-
!isOpen && (item.type === 'directory' ? setCurrentPath(item.path) : onOpenFile({ - name: item.name, - path: item.path, - isSSH: item.isSSH, - sshSessionId: item.sshSessionId - }))} - > - {item.type === 'directory' ? - : - } - {item.name} -
-
- {item.type === 'file' && ( - - )} -
-
- ); - })} -
- )} -
-
-
-
- )} +
+
+
+ {host && ( +
+
+ + setCurrentPath(e.target.value)} + className="flex-1 bg-[#18181b] border border-[#434345] text-white truncate rounded-md px-2 py-1 focus:outline-none focus:ring-2 focus:ring-ring hover:border-[#5a5a5d]" + />
- - - - - +
+ setFileSearch(e.target.value)} + /> +
+
+ +
+ {connectingSSH || filesLoading ? ( +
Loading...
+ ) : filesError ? ( +
{filesError}
+ ) : filteredFiles.length === 0 ? ( +
No files or folders found.
+ ) : ( +
+ {filteredFiles.map((item: any) => { + const isOpen = (tabs || []).some((t: any) => t.id === item.path); + return ( +
+
!isOpen && (item.type === 'directory' ? setCurrentPath(item.path) : onOpenFile({ + name: item.name, + path: item.path, + isSSH: item.isSSH, + sshSessionId: item.sshSessionId + }))} + > + {item.type === 'directory' ? + : + } + {item.name} +
+
+ {item.type === 'file' && ( + + )} +
+
+ ); + })} +
+ )} +
+
+
+
+ )} +
+
+
); }); export {ConfigEditorSidebar}; \ No newline at end of file diff --git a/src/ui/SSH/Config Editor/ConfigFileSidebarViewer.tsx b/src/ui/SSH/Config Editor/ConfigFileSidebarViewer.tsx index 99d3e7d8..251df258 100644 --- a/src/ui/SSH/Config Editor/ConfigFileSidebarViewer.tsx +++ b/src/ui/SSH/Config Editor/ConfigFileSidebarViewer.tsx @@ -64,7 +64,7 @@ export function ConfigFileSidebarViewer({ return (
{/* SSH Connections */} -
+
SSH Connections @@ -36,7 +36,7 @@ export function ConfigTabList({tabs, activeTab, setActiveTab, closeTab, onHomeCl diff --git a/src/ui/SSH/Server/Server.tsx b/src/ui/SSH/Server/Server.tsx index 72019f6d..e2f22aa4 100644 --- a/src/ui/SSH/Server/Server.tsx +++ b/src/ui/SSH/Server/Server.tsx @@ -7,6 +7,7 @@ import { Progress } from "@/components/ui/progress" import {Cpu, HardDrive, MemoryStick} from "lucide-react"; import {SSHTunnel} from "@/ui/SSH/Tunnel/SSHTunnel.tsx"; import { getServerStatusById, getServerMetricsById, ServerMetrics } from "@/ui/SSH/ssh-axios"; +import { useTabs } from "@/contexts/TabContext"; interface ServerProps { hostConfig?: any; @@ -18,6 +19,7 @@ interface ServerProps { export function Server({ hostConfig, title, isVisible = true, isTopbarOpen = true, embedded = false }: ServerProps): React.ReactElement { const { state: sidebarState } = useSidebar(); + const { addTab } = useTabs() as any; const [serverStatus, setServerStatus] = React.useState<'online' | 'offline'>('offline'); const [metrics, setMetrics] = React.useState(null); @@ -95,7 +97,22 @@ export function Server({ hostConfig, title, isVisible = true, isTopbarOpen = tru
- +