From 880907cc934d6b0a727b887806c3b33a8f4e4763 Mon Sep 17 00:00:00 2001 From: LukeGus Date: Sun, 17 Aug 2025 15:57:46 -0500 Subject: [PATCH] Config editor rename to file manager + fixed up file manager UI --- docker/nginx.conf | 2 +- src/App.tsx | 6 +- src/backend/database/db/index.ts | 16 +-- src/backend/database/db/schema.ts | 8 +- src/backend/database/routes/ssh.ts | 36 +++--- src/backend/database/routes/users.ts | 6 +- .../ssh/{config_editor.ts => file-manager.ts} | 12 +- src/backend/ssh/{ssh.ts => terminal.ts} | 0 src/backend/ssh/{ssh_tunnel.ts => tunnel.ts} | 0 src/backend/starter.ts | 6 +- src/ui/Admin/AdminSettings.tsx | 2 +- src/ui/Navigation/AppView.tsx | 10 +- src/ui/Navigation/Hosts/Host.tsx | 20 ++-- src/ui/Navigation/LeftSidebar.tsx | 15 +-- .../Navigation/Tabs}/TabContext.tsx | 0 src/ui/Navigation/TopNavbar.tsx | 2 +- src/ui/SSH/Config Editor/ConfigTopbar.tsx | 8 -- .../File Manager/FIleManagerTopNavbar.tsx | 8 ++ .../File Manager/FileManager.tsx} | 24 ++-- .../File Manager/FileManagerFileEditor.tsx} | 2 +- .../File Manager/FileManagerHomeView.tsx} | 6 +- .../File Manager/FileManagerLeftSidebar.tsx} | 22 ++-- .../FileManagerLeftSidebarFileViewer.tsx} | 2 +- .../File Manager/FileManagerTabList.tsx} | 2 +- .../Host Manager/HostManager.tsx} | 12 +- .../Host Manager/HostManagerHostEditor.tsx} | 14 +-- .../Host Manager/HostManagerHostViewer.tsx} | 4 +- src/ui/{SSH => apps}/Server/Server.tsx | 105 +++++++++++++----- .../Terminal/TerminalComponent.tsx | 0 .../SSHTunnel.tsx => apps/Tunnel/Tunnel.tsx} | 8 +- .../Tunnel/TunnelObject.tsx} | 2 +- .../Tunnel/TunnelViewer.tsx} | 6 +- src/ui/{SSH/ssh-axios.ts => main-axios.ts} | 32 +++--- 33 files changed, 223 insertions(+), 175 deletions(-) rename src/backend/ssh/{config_editor.ts => file-manager.ts} (97%) rename src/backend/ssh/{ssh.ts => terminal.ts} (100%) rename src/backend/ssh/{ssh_tunnel.ts => tunnel.ts} (100%) rename src/{contexts => ui/Navigation/Tabs}/TabContext.tsx (100%) delete mode 100644 src/ui/SSH/Config Editor/ConfigTopbar.tsx create mode 100644 src/ui/apps/File Manager/FIleManagerTopNavbar.tsx rename src/ui/{SSH/Config Editor/ConfigEditor.tsx => apps/File Manager/FileManager.tsx} (96%) rename src/ui/{SSH/Config Editor/ConfigCodeEditor.tsx => apps/File Manager/FileManagerFileEditor.tsx} (99%) rename src/ui/{SSH/Config Editor/ConfigHomeView.tsx => apps/File Manager/FileManagerHomeView.tsx} (98%) rename src/ui/{SSH/Config Editor/ConfigEditorSidebar.tsx => apps/File Manager/FileManagerLeftSidebar.tsx} (95%) rename src/ui/{SSH/Config Editor/ConfigFileSidebarViewer.tsx => apps/File Manager/FileManagerLeftSidebarFileViewer.tsx} (99%) rename src/ui/{SSH/Config Editor/ConfigTabList.tsx => apps/File Manager/FileManagerTabList.tsx} (95%) rename src/ui/{SSH/Manager/SSHManager.tsx => apps/Host Manager/HostManager.tsx} (85%) rename src/ui/{SSH/Manager/SSHManagerHostEditor.tsx => apps/Host Manager/HostManagerHostEditor.tsx} (99%) rename src/ui/{SSH/Manager/SSHManagerHostViewer.tsx => apps/Host Manager/HostManagerHostViewer.tsx} (99%) rename src/ui/{SSH => apps}/Server/Server.tsx (67%) rename src/ui/{SSH => apps}/Terminal/TerminalComponent.tsx (100%) rename src/ui/{SSH/Tunnel/SSHTunnel.tsx => apps/Tunnel/Tunnel.tsx} (97%) rename src/ui/{SSH/Tunnel/SSHTunnelObject.tsx => apps/Tunnel/TunnelObject.tsx} (99%) rename src/ui/{SSH/Tunnel/SSHTunnelViewer.tsx => apps/Tunnel/TunnelViewer.tsx} (96%) rename src/ui/{SSH/ssh-axios.ts => main-axios.ts} (91%) diff --git a/docker/nginx.conf b/docker/nginx.conf index 4fca1184..32446739 100644 --- a/docker/nginx.conf +++ b/docker/nginx.conf @@ -85,7 +85,7 @@ http { proxy_set_header X-Forwarded-Proto $scheme; } - location /ssh/config_editor/ { + location /ssh/file_manager/ { proxy_pass http://127.0.0.1:8084; proxy_http_version 1.1; proxy_set_header Host $host; diff --git a/src/App.tsx b/src/App.tsx index 3814169c..bbd89038 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,8 +4,8 @@ import {Homepage} from "@/ui/Homepage/Homepage.tsx" import {AppView} from "@/ui/Navigation/AppView.tsx" // import {SSHTunnel} from "@/ui/SSH/Tunnel/SSHTunnel.tsx" // import {ConfigEditor} from "@/ui/SSH/Config Editor/ConfigEditor.tsx" -import {SSHManager} from "@/ui/SSH/Manager/SSHManager.tsx" -import {TabProvider, useTabs} from "@/contexts/TabContext" +import {HostManager} from "@/ui/apps/Host Manager/HostManager.tsx" +import {TabProvider, useTabs} from "@/ui/Navigation/Tabs/TabContext.tsx" import axios from "axios" import {TopNavbar} from "@/ui/Navigation/TopNavbar.tsx"; import { AdminSettings } from "@/ui/Admin/AdminSettings"; @@ -202,7 +202,7 @@ function AppContent() { overflow: "hidden", }} > - + {/* Admin Settings tab */} diff --git a/src/backend/database/db/index.ts b/src/backend/database/db/index.ts index b41e4729..fb6af420 100644 --- a/src/backend/database/db/index.ts +++ b/src/backend/database/db/index.ts @@ -82,14 +82,14 @@ sqlite.exec(` enable_terminal INTEGER NOT NULL DEFAULT 1, enable_tunnel INTEGER NOT NULL DEFAULT 1, tunnel_connections TEXT, - enable_config_editor INTEGER NOT NULL DEFAULT 1, + enable_file_manager INTEGER NOT NULL DEFAULT 1, default_path TEXT, created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ); - CREATE TABLE IF NOT EXISTS config_editor_recent ( + CREATE TABLE IF NOT EXISTS file_manager_recent ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id TEXT NOT NULL, host_id INTEGER NOT NULL, @@ -100,7 +100,7 @@ sqlite.exec(` FOREIGN KEY (host_id) REFERENCES ssh_data(id) ); - CREATE TABLE IF NOT EXISTS config_editor_pinned ( + CREATE TABLE IF NOT EXISTS file_manager_pinned ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id TEXT NOT NULL, host_id INTEGER NOT NULL, @@ -111,7 +111,7 @@ sqlite.exec(` FOREIGN KEY (host_id) REFERENCES ssh_data(id) ); - CREATE TABLE IF NOT EXISTS config_editor_shortcuts ( + CREATE TABLE IF NOT EXISTS file_manager_shortcuts ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id TEXT NOT NULL, host_id INTEGER NOT NULL, @@ -179,14 +179,14 @@ const migrateSchema = () => { addColumnIfNotExists('ssh_data', 'enable_terminal', 'INTEGER NOT NULL DEFAULT 1'); addColumnIfNotExists('ssh_data', 'enable_tunnel', 'INTEGER NOT NULL DEFAULT 1'); addColumnIfNotExists('ssh_data', 'tunnel_connections', 'TEXT'); - addColumnIfNotExists('ssh_data', 'enable_config_editor', 'INTEGER NOT NULL DEFAULT 1'); + addColumnIfNotExists('ssh_data', 'enable_file_manager', 'INTEGER NOT NULL DEFAULT 1'); addColumnIfNotExists('ssh_data', 'default_path', 'TEXT'); addColumnIfNotExists('ssh_data', 'created_at', 'TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP'); addColumnIfNotExists('ssh_data', 'updated_at', 'TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP'); - addColumnIfNotExists('config_editor_recent', 'host_id', 'INTEGER NOT NULL'); - addColumnIfNotExists('config_editor_pinned', 'host_id', 'INTEGER NOT NULL'); - addColumnIfNotExists('config_editor_shortcuts', 'host_id', 'INTEGER NOT NULL'); + addColumnIfNotExists('file_manager_recent', 'host_id', 'INTEGER NOT NULL'); + addColumnIfNotExists('file_manager_pinned', 'host_id', 'INTEGER NOT NULL'); + addColumnIfNotExists('file_manager_shortcuts', 'host_id', 'INTEGER NOT NULL'); logger.success('Schema migration completed'); }; diff --git a/src/backend/database/db/schema.ts b/src/backend/database/db/schema.ts index b7ecc68f..2672c140 100644 --- a/src/backend/database/db/schema.ts +++ b/src/backend/database/db/schema.ts @@ -42,13 +42,13 @@ export const sshData = sqliteTable('ssh_data', { enableTerminal: integer('enable_terminal', {mode: 'boolean'}).notNull().default(true), enableTunnel: integer('enable_tunnel', {mode: 'boolean'}).notNull().default(true), tunnelConnections: text('tunnel_connections'), - enableConfigEditor: integer('enable_config_editor', {mode: 'boolean'}).notNull().default(true), + enableConfigEditor: integer('enable_file_manager', {mode: 'boolean'}).notNull().default(true), defaultPath: text('default_path'), createdAt: text('created_at').notNull().default(sql`CURRENT_TIMESTAMP`), updatedAt: text('updated_at').notNull().default(sql`CURRENT_TIMESTAMP`), }); -export const configEditorRecent = sqliteTable('config_editor_recent', { +export const configEditorRecent = sqliteTable('file_manager_recent', { id: integer('id').primaryKey({autoIncrement: true}), userId: text('user_id').notNull().references(() => users.id), hostId: integer('host_id').notNull().references(() => sshData.id), @@ -57,7 +57,7 @@ export const configEditorRecent = sqliteTable('config_editor_recent', { lastOpened: text('last_opened').notNull().default(sql`CURRENT_TIMESTAMP`), }); -export const configEditorPinned = sqliteTable('config_editor_pinned', { +export const configEditorPinned = sqliteTable('file_manager_pinned', { id: integer('id').primaryKey({autoIncrement: true}), userId: text('user_id').notNull().references(() => users.id), hostId: integer('host_id').notNull().references(() => sshData.id), @@ -66,7 +66,7 @@ export const configEditorPinned = sqliteTable('config_editor_pinned', { pinnedAt: text('pinned_at').notNull().default(sql`CURRENT_TIMESTAMP`), }); -export const configEditorShortcuts = sqliteTable('config_editor_shortcuts', { +export const configEditorShortcuts = sqliteTable('file_manager_shortcuts', { id: integer('id').primaryKey({autoIncrement: true}), userId: text('user_id').notNull().references(() => users.id), hostId: integer('host_id').notNull().references(() => sshData.id), diff --git a/src/backend/database/routes/ssh.ts b/src/backend/database/routes/ssh.ts index 2d1bec89..b8b66e18 100644 --- a/src/backend/database/routes/ssh.ts +++ b/src/backend/database/routes/ssh.ts @@ -406,8 +406,8 @@ router.delete('/db/host/:id', authenticateJWT, async (req: Request, res: Respons }); // Route: Get recent files (requires JWT) -// GET /ssh/config_editor/recent -router.get('/config_editor/recent', authenticateJWT, async (req: Request, res: Response) => { +// GET /ssh/file_manager/recent +router.get('/file_manager/recent', authenticateJWT, async (req: Request, res: Response) => { const userId = (req as any).userId; const hostId = req.query.hostId ? parseInt(req.query.hostId as string) : null; @@ -438,8 +438,8 @@ router.get('/config_editor/recent', authenticateJWT, async (req: Request, res: R }); // Route: Add file to recent (requires JWT) -// POST /ssh/config_editor/recent -router.post('/config_editor/recent', authenticateJWT, async (req: Request, res: Response) => { +// POST /ssh/file_manager/recent +router.post('/file_manager/recent', authenticateJWT, async (req: Request, res: Response) => { const userId = (req as any).userId; const {name, path, hostId} = req.body; if (!isNonEmptyString(userId) || !name || !path || !hostId) { @@ -480,8 +480,8 @@ router.post('/config_editor/recent', authenticateJWT, async (req: Request, res: }); // Route: Remove file from recent (requires JWT) -// DELETE /ssh/config_editor/recent -router.delete('/config_editor/recent', authenticateJWT, async (req: Request, res: Response) => { +// DELETE /ssh/file_manager/recent +router.delete('/file_manager/recent', authenticateJWT, async (req: Request, res: Response) => { const userId = (req as any).userId; const {name, path, hostId} = req.body; if (!isNonEmptyString(userId) || !name || !path || !hostId) { @@ -506,8 +506,8 @@ router.delete('/config_editor/recent', authenticateJWT, async (req: Request, res }); // Route: Get pinned files (requires JWT) -// GET /ssh/config_editor/pinned -router.get('/config_editor/pinned', authenticateJWT, async (req: Request, res: Response) => { +// GET /ssh/file_manager/pinned +router.get('/file_manager/pinned', authenticateJWT, async (req: Request, res: Response) => { const userId = (req as any).userId; const hostId = req.query.hostId ? parseInt(req.query.hostId as string) : null; @@ -538,8 +538,8 @@ router.get('/config_editor/pinned', authenticateJWT, async (req: Request, res: R }); // Route: Add file to pinned (requires JWT) -// POST /ssh/config_editor/pinned -router.post('/config_editor/pinned', authenticateJWT, async (req: Request, res: Response) => { +// POST /ssh/file_manager/pinned +router.post('/file_manager/pinned', authenticateJWT, async (req: Request, res: Response) => { const userId = (req as any).userId; const {name, path, hostId} = req.body; if (!isNonEmptyString(userId) || !name || !path || !hostId) { @@ -575,8 +575,8 @@ router.post('/config_editor/pinned', authenticateJWT, async (req: Request, res: }); // Route: Remove file from pinned (requires JWT) -// DELETE /ssh/config_editor/pinned -router.delete('/config_editor/pinned', authenticateJWT, async (req: Request, res: Response) => { +// DELETE /ssh/file_manager/pinned +router.delete('/file_manager/pinned', authenticateJWT, async (req: Request, res: Response) => { const userId = (req as any).userId; const {name, path, hostId} = req.body; if (!isNonEmptyString(userId) || !name || !path || !hostId) { @@ -601,8 +601,8 @@ router.delete('/config_editor/pinned', authenticateJWT, async (req: Request, res }); // Route: Get folder shortcuts (requires JWT) -// GET /ssh/config_editor/shortcuts -router.get('/config_editor/shortcuts', authenticateJWT, async (req: Request, res: Response) => { +// GET /ssh/file_manager/shortcuts +router.get('/file_manager/shortcuts', authenticateJWT, async (req: Request, res: Response) => { const userId = (req as any).userId; const hostId = req.query.hostId ? parseInt(req.query.hostId as string) : null; @@ -631,8 +631,8 @@ router.get('/config_editor/shortcuts', authenticateJWT, async (req: Request, res }); // Route: Add folder shortcut (requires JWT) -// POST /ssh/config_editor/shortcuts -router.post('/config_editor/shortcuts', authenticateJWT, async (req: Request, res: Response) => { +// POST /ssh/file_manager/shortcuts +router.post('/file_manager/shortcuts', authenticateJWT, async (req: Request, res: Response) => { const userId = (req as any).userId; const {name, path, hostId} = req.body; if (!isNonEmptyString(userId) || !name || !path || !hostId) { @@ -667,8 +667,8 @@ router.post('/config_editor/shortcuts', authenticateJWT, async (req: Request, re }); // Route: Remove folder shortcut (requires JWT) -// DELETE /ssh/config_editor/shortcuts -router.delete('/config_editor/shortcuts', authenticateJWT, async (req: Request, res: Response) => { +// DELETE /ssh/file_manager/shortcuts +router.delete('/file_manager/shortcuts', authenticateJWT, async (req: Request, res: Response) => { const userId = (req as any).userId; const {name, path, hostId} = req.body; if (!isNonEmptyString(userId) || !name || !path || !hostId) { diff --git a/src/backend/database/routes/users.ts b/src/backend/database/routes/users.ts index 98985d5e..790ac7be 100644 --- a/src/backend/database/routes/users.ts +++ b/src/backend/database/routes/users.ts @@ -959,9 +959,9 @@ router.delete('/delete-user', authenticateJWT, async (req, res) => { const targetUserId = targetUser[0].id; try { - db.$client.prepare('DELETE FROM config_editor_recent WHERE user_id = ?').run(targetUserId); - db.$client.prepare('DELETE FROM config_editor_pinned WHERE user_id = ?').run(targetUserId); - db.$client.prepare('DELETE FROM config_editor_shortcuts WHERE user_id = ?').run(targetUserId); + db.$client.prepare('DELETE FROM file_manager_recent WHERE user_id = ?').run(targetUserId); + db.$client.prepare('DELETE FROM file_manager_pinned WHERE user_id = ?').run(targetUserId); + db.$client.prepare('DELETE FROM file_manager_shortcuts WHERE user_id = ?').run(targetUserId); db.$client.prepare('DELETE FROM ssh_data WHERE user_id = ?').run(targetUserId); } catch (cleanupError) { logger.error(`Cleanup failed for user ${username}:`, cleanupError); diff --git a/src/backend/ssh/config_editor.ts b/src/backend/ssh/file-manager.ts similarity index 97% rename from src/backend/ssh/config_editor.ts rename to src/backend/ssh/file-manager.ts index 7c4126bc..e0c21fb9 100644 --- a/src/backend/ssh/config_editor.ts +++ b/src/backend/ssh/file-manager.ts @@ -68,7 +68,7 @@ function scheduleSessionCleanup(sessionId: string) { } } -app.post('/ssh/config_editor/ssh/connect', (req, res) => { +app.post('/ssh/file_manager/ssh/connect', (req, res) => { const {sessionId, ip, port, username, password, sshKey, keyPassword} = req.body; if (!sessionId || !ip || !username || !port) { return res.status(400).json({error: 'Missing SSH connection parameters'}); @@ -153,19 +153,19 @@ app.post('/ssh/config_editor/ssh/connect', (req, res) => { client.connect(config); }); -app.post('/ssh/config_editor/ssh/disconnect', (req, res) => { +app.post('/ssh/file_manager/ssh/disconnect', (req, res) => { const {sessionId} = req.body; cleanupSession(sessionId); res.json({status: 'success', message: 'SSH connection disconnected'}); }); -app.get('/ssh/config_editor/ssh/status', (req, res) => { +app.get('/ssh/file_manager/ssh/status', (req, res) => { const sessionId = req.query.sessionId as string; const isConnected = !!sshSessions[sessionId]?.isConnected; res.json({status: 'success', connected: isConnected}); }); -app.get('/ssh/config_editor/ssh/listFiles', (req, res) => { +app.get('/ssh/file_manager/ssh/listFiles', (req, res) => { const sessionId = req.query.sessionId as string; const sshConn = sshSessions[sessionId]; const sshPath = decodeURIComponent((req.query.path as string) || '/'); @@ -231,7 +231,7 @@ app.get('/ssh/config_editor/ssh/listFiles', (req, res) => { }); }); -app.get('/ssh/config_editor/ssh/readFile', (req, res) => { +app.get('/ssh/file_manager/ssh/readFile', (req, res) => { const sessionId = req.query.sessionId as string; const sshConn = sshSessions[sessionId]; const filePath = decodeURIComponent(req.query.path as string); @@ -280,7 +280,7 @@ app.get('/ssh/config_editor/ssh/readFile', (req, res) => { }); }); -app.post('/ssh/config_editor/ssh/writeFile', (req, res) => { +app.post('/ssh/file_manager/ssh/writeFile', (req, res) => { const {sessionId, path: filePath, content} = req.body; const sshConn = sshSessions[sessionId]; diff --git a/src/backend/ssh/ssh.ts b/src/backend/ssh/terminal.ts similarity index 100% rename from src/backend/ssh/ssh.ts rename to src/backend/ssh/terminal.ts diff --git a/src/backend/ssh/ssh_tunnel.ts b/src/backend/ssh/tunnel.ts similarity index 100% rename from src/backend/ssh/ssh_tunnel.ts rename to src/backend/ssh/tunnel.ts diff --git a/src/backend/starter.ts b/src/backend/starter.ts index 83fb86c3..8da9b1d8 100644 --- a/src/backend/starter.ts +++ b/src/backend/starter.ts @@ -2,9 +2,9 @@ // node ./dist/backend/starter.js import './database/database.js' -import './ssh/ssh.js'; -import './ssh/ssh_tunnel.js'; -import './ssh/config_editor.js'; +import './ssh/terminal.js'; +import './ssh/tunnel.js'; +import './ssh/file-manager.ts'; import './ssh/server-stats.js'; import chalk from 'chalk'; diff --git a/src/ui/Admin/AdminSettings.tsx b/src/ui/Admin/AdminSettings.tsx index 954cc7ed..ff1b1ae0 100644 --- a/src/ui/Admin/AdminSettings.tsx +++ b/src/ui/Admin/AdminSettings.tsx @@ -194,7 +194,7 @@ export function AdminSettings({ isTopbarOpen = true }: AdminSettingsProps): Reac
- + General diff --git a/src/ui/Navigation/AppView.tsx b/src/ui/Navigation/AppView.tsx index 61263867..1d01c7f0 100644 --- a/src/ui/Navigation/AppView.tsx +++ b/src/ui/Navigation/AppView.tsx @@ -1,8 +1,8 @@ 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 {TerminalComponent} from "@/ui/apps/Terminal/TerminalComponent.tsx"; +import {Server as ServerView} from "@/ui/apps/Server/Server.tsx"; +import {FileManager} from "@/ui/apps/File Manager/FileManager.tsx"; +import {useTabs} from "@/ui/Navigation/Tabs/TabContext.tsx"; import {ResizablePanelGroup, ResizablePanel, ResizableHandle} from '@/components/ui/resizable.tsx'; import * as ResizablePrimitive from "react-resizable-panels"; import { useSidebar } from "@/components/ui/sidebar.tsx"; @@ -168,7 +168,7 @@ export function AppView({ isTopbarOpen = true }: TerminalViewProps): React.React embedded /> ) : ( - diff --git a/src/ui/Navigation/Hosts/Host.tsx b/src/ui/Navigation/Hosts/Host.tsx index 4ddaa246..952c61f4 100644 --- a/src/ui/Navigation/Hosts/Host.tsx +++ b/src/ui/Navigation/Hosts/Host.tsx @@ -3,8 +3,8 @@ import {Status, StatusIndicator} from "@/components/ui/shadcn-io/status"; import {Button} from "@/components/ui/button.tsx"; import {ButtonGroup} from "@/components/ui/button-group.tsx"; import {Server, Terminal} from "lucide-react"; -import {useTabs} from "@/contexts/TabContext"; -import { getServerStatusById } from "@/ui/SSH/ssh-axios"; +import {useTabs} from "@/ui/Navigation/Tabs/TabContext.tsx"; +import { getServerStatusById } from "@/ui/main-axios.ts"; interface SSHHost { id: number; @@ -86,13 +86,15 @@ export function Host({ host }: HostProps): React.ReactElement { - + {host.enableTerminal && ( + + )}
{hasTags && ( diff --git a/src/ui/Navigation/LeftSidebar.tsx b/src/ui/Navigation/LeftSidebar.tsx index f22a430a..ff5a762a 100644 --- a/src/ui/Navigation/LeftSidebar.tsx +++ b/src/ui/Navigation/LeftSidebar.tsx @@ -48,8 +48,8 @@ import { import axios from "axios"; import {Card} from "@/components/ui/card.tsx"; import {FolderCard} from "@/ui/Navigation/Hosts/FolderCard.tsx"; -import {getSSHHosts} from "@/ui/SSH/ssh-axios"; -import { useTabs } from "@/contexts/TabContext"; +import {getSSHHosts} from "@/ui/main-axios.ts"; +import { useTabs } from "@/ui/Navigation/Tabs/TabContext.tsx"; interface SSHHost { id: number; @@ -206,19 +206,20 @@ export function LeftSidebar({ const fetchHosts = React.useCallback(async () => { try { const newHosts = await getSSHHosts(); - const terminalHosts = newHosts.filter(host => host.enableTerminal); + // Show all hosts in sidebar, regardless of terminal setting + // Terminal visibility is handled in the UI components const prevHosts = prevHostsRef.current; // Create a stable map of existing hosts by ID for comparison const existingHostsMap = new Map(prevHosts.map(h => [h.id, h])); - const newHostsMap = new Map(terminalHosts.map(h => [h.id, h])); + const newHostsMap = new Map(newHosts.map(h => [h.id, h])); // Check if there are any meaningful changes let hasChanges = false; // Check for new hosts, removed hosts, or changed hosts - if (terminalHosts.length !== prevHosts.length) { + if (newHosts.length !== prevHosts.length) { hasChanges = true; } else { for (const [id, newHost] of newHostsMap) { @@ -248,8 +249,8 @@ export function LeftSidebar({ if (hasChanges) { // Use a small delay to batch updates and reduce jittering setTimeout(() => { - setHosts(terminalHosts); - prevHostsRef.current = terminalHosts; + setHosts(newHosts); + prevHostsRef.current = newHosts; }, 50); } } catch (err: any) { diff --git a/src/contexts/TabContext.tsx b/src/ui/Navigation/Tabs/TabContext.tsx similarity index 100% rename from src/contexts/TabContext.tsx rename to src/ui/Navigation/Tabs/TabContext.tsx diff --git a/src/ui/Navigation/TopNavbar.tsx b/src/ui/Navigation/TopNavbar.tsx index da962cfd..c052ed9e 100644 --- a/src/ui/Navigation/TopNavbar.tsx +++ b/src/ui/Navigation/TopNavbar.tsx @@ -3,7 +3,7 @@ import {useSidebar} from "@/components/ui/sidebar"; import {Button} from "@/components/ui/button.tsx"; import {ChevronDown, ChevronUpIcon} from "lucide-react"; import {Tab} from "@/ui/Navigation/Tabs/Tab.tsx"; -import {useTabs} from "@/contexts/TabContext"; +import {useTabs} from "@/ui/Navigation/Tabs/TabContext.tsx"; interface TopNavbarProps { isTopbarOpen: boolean; diff --git a/src/ui/SSH/Config Editor/ConfigTopbar.tsx b/src/ui/SSH/Config Editor/ConfigTopbar.tsx deleted file mode 100644 index 62637cde..00000000 --- a/src/ui/SSH/Config Editor/ConfigTopbar.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import React from "react"; -import { ConfigTabList } from "./ConfigTabList.tsx"; - -export function ConfigTopbar(props: any): React.ReactElement { - return ( - - ) -} \ No newline at end of file diff --git a/src/ui/apps/File Manager/FIleManagerTopNavbar.tsx b/src/ui/apps/File Manager/FIleManagerTopNavbar.tsx new file mode 100644 index 00000000..4c127031 --- /dev/null +++ b/src/ui/apps/File Manager/FIleManagerTopNavbar.tsx @@ -0,0 +1,8 @@ +import React from "react"; +import { FileManagerTabList } from "./FileManagerTabList.tsx"; + +export function FIleManagerTopNavbar(props: any): React.ReactElement { + return ( + + ) +} \ No newline at end of file diff --git a/src/ui/SSH/Config Editor/ConfigEditor.tsx b/src/ui/apps/File Manager/FileManager.tsx similarity index 96% rename from src/ui/SSH/Config Editor/ConfigEditor.tsx rename to src/ui/apps/File Manager/FileManager.tsx index 69029a3b..0ebd9274 100644 --- a/src/ui/SSH/Config Editor/ConfigEditor.tsx +++ b/src/ui/apps/File Manager/FileManager.tsx @@ -1,10 +1,10 @@ import React, {useState, useEffect, useRef} from "react"; -import {ConfigEditorSidebar} from "@/ui/SSH/Config Editor/ConfigEditorSidebar.tsx"; -import {ConfigTabList} from "@/ui/SSH/Config Editor/ConfigTabList.tsx"; -import {ConfigHomeView} from "@/ui/SSH/Config Editor/ConfigHomeView.tsx"; -import {ConfigCodeEditor} from "@/ui/SSH/Config Editor/ConfigCodeEditor.tsx"; +import {FileManagerLeftSidebar} from "@/ui/apps/File Manager/FileManagerLeftSidebar.tsx"; +import {FileManagerTabList} from "@/ui/apps/File Manager/FileManagerTabList.tsx"; +import {FileManagerHomeView} from "@/ui/apps/File Manager/FileManagerHomeView.tsx"; +import {FileManagerFileEditor} from "@/ui/apps/File Manager/FileManagerFileEditor.tsx"; import {Button} from '@/components/ui/button.tsx'; -import {ConfigTopbar} from "@/ui/SSH/Config Editor/ConfigTopbar.tsx"; +import {FIleManagerTopNavbar} from "@/ui/apps/File Manager/FIleManagerTopNavbar.tsx"; import {cn} from '@/lib/utils.ts'; import { getConfigEditorRecent, @@ -20,7 +20,7 @@ import { writeSSHFile, getSSHStatus, connectSSH -} from '@/ui/SSH/ssh-axios.ts'; +} from '@/ui/main-axios.ts'; interface Tab { id: string | number; @@ -59,7 +59,7 @@ interface SSHHost { updatedAt: string; } -export function ConfigEditor({onSelectView, embedded = false, initialHost = null}: { onSelectView?: (view: string) => void, embedded?: boolean, initialHost?: SSHHost | null }): React.ReactElement { +export function FileManager({onSelectView, embedded = false, initialHost = null}: { onSelectView?: (view: string) => void, embedded?: boolean, initialHost?: SSHHost | null }): React.ReactElement { const [tabs, setTabs] = useState([]); const [activeTab, setActiveTab] = useState('home'); const [recent, setRecent] = useState([]); @@ -451,7 +451,7 @@ export function ConfigEditor({onSelectView, embedded = false, initialHost = null return (
- {})} onOpenFile={handleOpenFile} tabs={tabs} @@ -482,7 +482,7 @@ export function ConfigEditor({onSelectView, embedded = false, initialHost = null return (
- {})} onOpenFile={handleOpenFile} tabs={tabs} @@ -498,7 +498,7 @@ export function ConfigEditor({onSelectView, embedded = false, initialHost = null
- ({id: t.id, title: t.title}))} activeTab={activeTab} setActiveTab={setActiveTab} @@ -544,7 +544,7 @@ export function ConfigEditor({onSelectView, embedded = false, initialHost = null flexDirection: 'column' }}> {activeTab === 'home' ? ( - )}
- setTabContent(tab.id, content)} diff --git a/src/ui/SSH/Config Editor/ConfigCodeEditor.tsx b/src/ui/apps/File Manager/FileManagerFileEditor.tsx similarity index 99% rename from src/ui/SSH/Config Editor/ConfigCodeEditor.tsx rename to src/ui/apps/File Manager/FileManagerFileEditor.tsx index c216bf16..8ea6429f 100644 --- a/src/ui/SSH/Config Editor/ConfigCodeEditor.tsx +++ b/src/ui/apps/File Manager/FileManagerFileEditor.tsx @@ -11,7 +11,7 @@ interface ConfigCodeEditorProps { onContentChange: (value: string) => void; } -export function ConfigCodeEditor({content, fileName, onContentChange}: ConfigCodeEditorProps) { +export function FileManagerFileEditor({content, fileName, onContentChange}: ConfigCodeEditorProps) { function getLanguageName(filename: string): string { if (!filename || typeof filename !== 'string') { return 'text'; diff --git a/src/ui/SSH/Config Editor/ConfigHomeView.tsx b/src/ui/apps/File Manager/FileManagerHomeView.tsx similarity index 98% rename from src/ui/SSH/Config Editor/ConfigHomeView.tsx rename to src/ui/apps/File Manager/FileManagerHomeView.tsx index 4fb20a8d..eaf4b0ce 100644 --- a/src/ui/SSH/Config Editor/ConfigHomeView.tsx +++ b/src/ui/apps/File Manager/FileManagerHomeView.tsx @@ -31,7 +31,7 @@ interface ConfigHomeViewProps { onAddShortcut: (path: string) => void; } -export function ConfigHomeView({ +export function FileManagerHomeView({ recent, pinned, shortcuts, @@ -177,8 +177,8 @@ export function ConfigHomeView({ /> 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]" + className="flex-1 bg-[#18181b] border-2 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 ? ( @@ -388,4 +382,4 @@ const ConfigEditorSidebar = forwardRef(function ConfigEditorSidebar(
); }); -export {ConfigEditorSidebar}; \ No newline at end of file +export {FileManagerLeftSidebar}; \ No newline at end of file diff --git a/src/ui/SSH/Config Editor/ConfigFileSidebarViewer.tsx b/src/ui/apps/File Manager/FileManagerLeftSidebarFileViewer.tsx similarity index 99% rename from src/ui/SSH/Config Editor/ConfigFileSidebarViewer.tsx rename to src/ui/apps/File Manager/FileManagerLeftSidebarFileViewer.tsx index 251df258..083e795d 100644 --- a/src/ui/SSH/Config Editor/ConfigFileSidebarViewer.tsx +++ b/src/ui/apps/File Manager/FileManagerLeftSidebarFileViewer.tsx @@ -41,7 +41,7 @@ interface ConfigFileSidebarViewerProps { currentSSH?: SSHConnection; } -export function ConfigFileSidebarViewer({ +export function FileManagerLeftSidebarFileViewer({ sshConnections, onAddSSH, onConnectSSH, diff --git a/src/ui/SSH/Config Editor/ConfigTabList.tsx b/src/ui/apps/File Manager/FileManagerTabList.tsx similarity index 95% rename from src/ui/SSH/Config Editor/ConfigTabList.tsx rename to src/ui/apps/File Manager/FileManagerTabList.tsx index cfaa13c6..5ec4a405 100644 --- a/src/ui/SSH/Config Editor/ConfigTabList.tsx +++ b/src/ui/apps/File Manager/FileManagerTabList.tsx @@ -15,7 +15,7 @@ interface ConfigTabListProps { onHomeClick: () => void; } -export function ConfigTabList({tabs, activeTab, setActiveTab, closeTab, onHomeClick}: ConfigTabListProps) { +export function FileManagerTabList({tabs, activeTab, setActiveTab, closeTab, onHomeClick}: ConfigTabListProps) { return (
+ {currentHostConfig?.enableConfigEditor && ( + + )}
@@ -181,9 +232,9 @@ export function Server({ hostConfig, title, isVisible = true, isTopbarOpen = tru
{/* SSH Tunnels */} - {(hostConfig?.tunnelConnections && hostConfig.tunnelConnections.length > 0) && ( + {(currentHostConfig?.tunnelConnections && currentHostConfig.tunnelConnections.length > 0) && (
- +
)} diff --git a/src/ui/SSH/Terminal/TerminalComponent.tsx b/src/ui/apps/Terminal/TerminalComponent.tsx similarity index 100% rename from src/ui/SSH/Terminal/TerminalComponent.tsx rename to src/ui/apps/Terminal/TerminalComponent.tsx diff --git a/src/ui/SSH/Tunnel/SSHTunnel.tsx b/src/ui/apps/Tunnel/Tunnel.tsx similarity index 97% rename from src/ui/SSH/Tunnel/SSHTunnel.tsx rename to src/ui/apps/Tunnel/Tunnel.tsx index a8d757e9..619cf9e1 100644 --- a/src/ui/SSH/Tunnel/SSHTunnel.tsx +++ b/src/ui/apps/Tunnel/Tunnel.tsx @@ -1,6 +1,6 @@ import React, {useState, useEffect, useCallback} from "react"; -import {SSHTunnelViewer} from "@/ui/SSH/Tunnel/SSHTunnelViewer.tsx"; -import {getSSHHosts, getTunnelStatuses, connectTunnel, disconnectTunnel, cancelTunnel} from "@/ui/SSH/ssh-axios"; +import {TunnelViewer} from "@/ui/apps/Tunnel/TunnelViewer.tsx"; +import {getSSHHosts, getTunnelStatuses, connectTunnel, disconnectTunnel, cancelTunnel} from "@/ui/main-axios.ts"; interface TunnelConnection { sourcePort: number; @@ -48,7 +48,7 @@ interface SSHTunnelProps { filterHostKey?: string; } -export function SSHTunnel({ filterHostKey }: SSHTunnelProps): React.ReactElement { +export function Tunnel({ filterHostKey }: SSHTunnelProps): React.ReactElement { // Keep full list for endpoint lookups; keep a separate visible list for UI const [allHosts, setAllHosts] = useState([]); const [visibleHosts, setVisibleHosts] = useState([]); @@ -197,7 +197,7 @@ export function SSHTunnel({ filterHostKey }: SSHTunnelProps): React.ReactElement }; return ( - Promise; } -export function SSHTunnelViewer({ +export function TunnelViewer({ hosts = [], tunnelStatuses = {}, tunnelActions = {}, @@ -74,7 +74,7 @@ export function SSHTunnelViewer({
{activeHost.tunnelConnections.map((t, idx) => ( - { export async function getConfigEditorRecent(hostId: number): Promise { try { - const response = await sshHostApi.get(`/ssh/config_editor/recent?hostId=${hostId}`); + const response = await sshHostApi.get(`/ssh/file_manager/recent?hostId=${hostId}`); return response.data || []; } catch (error) { return []; @@ -379,7 +379,7 @@ export async function addConfigEditorRecent(file: { hostId: number }): Promise { try { - const response = await sshHostApi.post('/ssh/config_editor/recent', file); + const response = await sshHostApi.post('/ssh/file_manager/recent', file); return response.data; } catch (error) { throw error; @@ -394,7 +394,7 @@ export async function removeConfigEditorRecent(file: { hostId: number }): Promise { try { - const response = await sshHostApi.delete('/ssh/config_editor/recent', {data: file}); + const response = await sshHostApi.delete('/ssh/file_manager/recent', {data: file}); return response.data; } catch (error) { throw error; @@ -403,7 +403,7 @@ export async function removeConfigEditorRecent(file: { export async function getConfigEditorPinned(hostId: number): Promise { try { - const response = await sshHostApi.get(`/ssh/config_editor/pinned?hostId=${hostId}`); + const response = await sshHostApi.get(`/ssh/file_manager/pinned?hostId=${hostId}`); return response.data || []; } catch (error) { return []; @@ -418,7 +418,7 @@ export async function addConfigEditorPinned(file: { hostId: number }): Promise { try { - const response = await sshHostApi.post('/ssh/config_editor/pinned', file); + const response = await sshHostApi.post('/ssh/file_manager/pinned', file); return response.data; } catch (error) { throw error; @@ -433,7 +433,7 @@ export async function removeConfigEditorPinned(file: { hostId: number }): Promise { try { - const response = await sshHostApi.delete('/ssh/config_editor/pinned', {data: file}); + const response = await sshHostApi.delete('/ssh/file_manager/pinned', {data: file}); return response.data; } catch (error) { throw error; @@ -442,7 +442,7 @@ export async function removeConfigEditorPinned(file: { export async function getConfigEditorShortcuts(hostId: number): Promise { try { - const response = await sshHostApi.get(`/ssh/config_editor/shortcuts?hostId=${hostId}`); + const response = await sshHostApi.get(`/ssh/file_manager/shortcuts?hostId=${hostId}`); return response.data || []; } catch (error) { return []; @@ -457,7 +457,7 @@ export async function addConfigEditorShortcut(shortcut: { hostId: number }): Promise { try { - const response = await sshHostApi.post('/ssh/config_editor/shortcuts', shortcut); + const response = await sshHostApi.post('/ssh/file_manager/shortcuts', shortcut); return response.data; } catch (error) { throw error; @@ -472,7 +472,7 @@ export async function removeConfigEditorShortcut(shortcut: { hostId: number }): Promise { try { - const response = await sshHostApi.delete('/ssh/config_editor/shortcuts', {data: shortcut}); + const response = await sshHostApi.delete('/ssh/file_manager/shortcuts', {data: shortcut}); return response.data; } catch (error) { throw error; @@ -488,7 +488,7 @@ export async function connectSSH(sessionId: string, config: { keyPassword?: string; }): Promise { try { - const response = await configEditorApi.post('/ssh/config_editor/ssh/connect', { + const response = await configEditorApi.post('/ssh/file_manager/ssh/connect', { sessionId, ...config }); @@ -500,7 +500,7 @@ export async function connectSSH(sessionId: string, config: { export async function disconnectSSH(sessionId: string): Promise { try { - const response = await configEditorApi.post('/ssh/config_editor/ssh/disconnect', {sessionId}); + const response = await configEditorApi.post('/ssh/file_manager/ssh/disconnect', {sessionId}); return response.data; } catch (error) { throw error; @@ -509,7 +509,7 @@ export async function disconnectSSH(sessionId: string): Promise { export async function getSSHStatus(sessionId: string): Promise<{ connected: boolean }> { try { - const response = await configEditorApi.get('/ssh/config_editor/ssh/status', { + const response = await configEditorApi.get('/ssh/file_manager/ssh/status', { params: {sessionId} }); return response.data; @@ -520,7 +520,7 @@ export async function getSSHStatus(sessionId: string): Promise<{ connected: bool export async function listSSHFiles(sessionId: string, path: string): Promise { try { - const response = await configEditorApi.get('/ssh/config_editor/ssh/listFiles', { + const response = await configEditorApi.get('/ssh/file_manager/ssh/listFiles', { params: {sessionId, path} }); return response.data || []; @@ -531,7 +531,7 @@ export async function listSSHFiles(sessionId: string, path: string): Promise { try { - const response = await configEditorApi.get('/ssh/config_editor/ssh/readFile', { + const response = await configEditorApi.get('/ssh/file_manager/ssh/readFile', { params: {sessionId, path} }); return response.data; @@ -542,7 +542,7 @@ export async function readSSHFile(sessionId: string, path: string): Promise<{ co export async function writeSSHFile(sessionId: string, path: string, content: string): Promise { try { - const response = await configEditorApi.post('/ssh/config_editor/ssh/writeFile', { + const response = await configEditorApi.post('/ssh/file_manager/ssh/writeFile', { sessionId, path, content