diff --git a/package-lock.json b/package-lock.json index 21fdddf9..44db4c5b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,7 +7,6 @@ "": { "name": "termix", "version": "1.6.0", - "hasInstallScript": true, "dependencies": { "@hookform/resolvers": "^5.1.1", "@radix-ui/react-accordion": "^1.2.11", @@ -5152,26 +5151,6 @@ "node": ">=0.10.0" } }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/compare-version": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz", - "integrity": "sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", diff --git a/src/ui/Desktop/Navigation/LeftSidebar.tsx b/src/ui/Desktop/Navigation/LeftSidebar.tsx index 28e35d97..ae217896 100644 --- a/src/ui/Desktop/Navigation/LeftSidebar.tsx +++ b/src/ui/Desktop/Navigation/LeftSidebar.tsx @@ -388,6 +388,7 @@ export function LeftSidebar({ + {t('common.logout')} ) => { - }; - const handleKeyDown = (e: React.KeyboardEvent) => { if (selectedTabIds.length === 0) return; - const value = e.currentTarget.value; let commandToSend = ''; if (e.ctrlKey || e.metaKey) { diff --git a/src/ui/Mobile/Apps/Navigation/BottomNavbar.tsx b/src/ui/Mobile/Apps/Navigation/BottomNavbar.tsx new file mode 100644 index 00000000..3016c11f --- /dev/null +++ b/src/ui/Mobile/Apps/Navigation/BottomNavbar.tsx @@ -0,0 +1,16 @@ +import { Button } from "@/components/ui/button"; +import {Menu} from "lucide-react"; + +interface MenuProps { + onSidebarOpenClick?: () => void; +} + +export function BottomNavbar({onSidebarOpenClick}: MenuProps) { + return ( + + + + + + ) +} \ No newline at end of file diff --git a/src/ui/Mobile/Apps/Navigation/Hosts/FolderCard.tsx b/src/ui/Mobile/Apps/Navigation/Hosts/FolderCard.tsx new file mode 100644 index 00000000..611b24b9 --- /dev/null +++ b/src/ui/Mobile/Apps/Navigation/Hosts/FolderCard.tsx @@ -0,0 +1,80 @@ +import React, {useState} from "react"; +import {CardTitle} from "@/components/ui/card.tsx"; +import {ChevronDown, Folder} from "lucide-react"; +import {Button} from "@/components/ui/button.tsx"; +import {Separator} from "@/components/ui/separator.tsx"; +import {Host} from "@/ui/Mobile/Apps/Navigation/Hosts/Host.tsx"; + +interface SSHHost { + id: number; + name: string; + ip: string; + port: number; + username: string; + folder: string; + tags: string[]; + pin: boolean; + authType: string; + password?: string; + key?: string; + keyPassword?: string; + keyType?: string; + enableTerminal: boolean; + enableTunnel: boolean; + enableFileManager: boolean; + defaultPath: string; + tunnelConnections: any[]; + createdAt: string; + updatedAt: string; +} + +interface FolderCardProps { + folderName: string, + hosts: SSHHost[], +} + +export function FolderCard({folderName, hosts}: FolderCardProps): React.ReactElement { + const [isExpanded, setIsExpanded] = useState(true); + + const toggleExpanded = () => { + setIsExpanded(!isExpanded); + }; + + return ( + + + + + + + + {folderName} + + + + + + + {isExpanded && ( + + {hosts.map((host, index) => ( + + + + {index < hosts.length - 1 && ( + + + + )} + + ))} + + )} + + ) +} \ No newline at end of file diff --git a/src/ui/Mobile/Apps/Navigation/Hosts/Host.tsx b/src/ui/Mobile/Apps/Navigation/Hosts/Host.tsx new file mode 100644 index 00000000..856176b6 --- /dev/null +++ b/src/ui/Mobile/Apps/Navigation/Hosts/Host.tsx @@ -0,0 +1,107 @@ +import React, {useEffect, useState} from "react"; +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 {getServerStatusById} from "@/ui/main-axios.ts"; + +interface SSHHost { + id: number; + name: string; + ip: string; + port: number; + username: string; + folder: string; + tags: string[]; + pin: boolean; + authType: string; + password?: string; + key?: string; + keyPassword?: string; + keyType?: string; + enableTerminal: boolean; + enableTunnel: boolean; + enableFileManager: boolean; + defaultPath: string; + tunnelConnections: any[]; + createdAt: string; + updatedAt: string; +} + +interface HostProps { + host: SSHHost; +} + +export function Host({host}: HostProps): React.ReactElement { + const [serverStatus, setServerStatus] = useState<'online' | 'offline' | 'degraded'>('degraded'); + 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}`; + + useEffect(() => { + let intervalId: number | undefined; + let cancelled = false; + + const fetchStatus = async () => { + try { + const res = await getServerStatusById(host.id); + if (!cancelled) { + setServerStatus(res?.status === 'online' ? 'online' : 'offline'); + } + } catch { + if (!cancelled) setServerStatus('offline'); + } + }; + + fetchStatus(); + + intervalId = window.setInterval(fetchStatus, 10000); + + return () => { + cancelled = true; + if (intervalId) window.clearInterval(intervalId); + }; + }, [host.id]); + + const handleTerminalClick = () => { + + }; + + const handleServerClick = () => { + + }; + + return ( + + + + + + + {host.name || host.ip} + + + {host.enableTerminal && ( + + + + )} + + + {hasTags && ( + + {tags.map((tag: string) => ( + + {tag} + + ))} + + )} + + ) +} \ No newline at end of file diff --git a/src/ui/Mobile/Apps/Navigation/LeftSidebar.tsx b/src/ui/Mobile/Apps/Navigation/LeftSidebar.tsx new file mode 100644 index 00000000..45a5539d --- /dev/null +++ b/src/ui/Mobile/Apps/Navigation/LeftSidebar.tsx @@ -0,0 +1,63 @@ +import {Sidebar, SidebarContent, SidebarGroupLabel, SidebarHeader, SidebarProvider} from "@/components/ui/sidebar.tsx"; +import {Button} from "@/components/ui/button.tsx"; +import {Menu} from "lucide-react"; +import React from "react"; +import {Separator} from "@/components/ui/separator.tsx"; +import {FolderCard} from "@/ui/Mobile/Apps/Navigation/Hosts/FolderCard.tsx"; +import {Host} from "@/ui/Mobile/Apps/Navigation/Hosts/Host.tsx"; + +interface LeftSidebarProps { + isSidebarOpen: boolean; + setIsSidebarOpen: (type: boolean) => void; +} + +export function LeftSidebar({ isSidebarOpen, setIsSidebarOpen }: LeftSidebarProps) { + return ( + + + + + + Termix + setIsSidebarOpen(!isSidebarOpen)} + className="w-[28px] h-[28px] absolute right-5" + > + + + + + + + + + + + + ) +} \ No newline at end of file diff --git a/src/ui/Mobile/MobileApp.tsx b/src/ui/Mobile/MobileApp.tsx index b5b78ade..ca4438e2 100644 --- a/src/ui/Mobile/MobileApp.tsx +++ b/src/ui/Mobile/MobileApp.tsx @@ -1,9 +1,12 @@ -import {useRef, FC} from "react"; +import React, {useRef, FC} from "react"; import {Terminal} from "@/ui/Mobile/Apps/Terminal/Terminal.tsx"; import {TerminalKeyboard} from "@/ui/Mobile/Apps/Terminal/TerminalKeyboard.tsx"; +import {BottomNavbar} from "@/ui/Mobile/Apps/Navigation/BottomNavbar.tsx"; +import {LeftSidebar} from "@/ui/Mobile/Apps/Navigation/LeftSidebar.tsx"; export const MobileApp: FC = () => { const terminalRef = useRef(null); + const [isSidebarOpen, setIsSidebarOpen] = React.useState(false); function handleKeyboardInput(input: string) { if (!terminalRef.current?.sendInput) return; @@ -12,7 +15,7 @@ export const MobileApp: FC = () => { "{backspace}": "\x7f", "{space}": " ", "{tab}": "\t", - "{enter}": "\r", + "{enter}": "", "{escape}": "\x1b", "{arrowUp}": "\x1b[A", "{arrowDown}": "\x1b[B", @@ -22,7 +25,7 @@ export const MobileApp: FC = () => { "{home}": "\x1b[H", "{end}": "\x1b[F", "{pageUp}": "\x1b[5~", - "{pageDown}": "\x1b[6~", + "{pageDown}": "\x1b[6~" }; if (input in keyMap) { @@ -33,7 +36,7 @@ export const MobileApp: FC = () => { } return ( - + { - + setIsSidebarOpen(true)} + /> + {isSidebarOpen && ( + setIsSidebarOpen(false)} + /> + )} + + + { e.stopPropagation(); }} className="pointer-events-auto"> + + - ) -} + ); +} \ No newline at end of file
+ {host.name || host.ip} +
{tag}