diff --git a/src/App.tsx b/src/App.tsx index 46efa03f..99d1319c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -35,8 +35,8 @@ function App() { } return ( -
-
+
+
{renderActiveView()}
diff --git a/src/apps/Homepage/Homepage.tsx b/src/apps/Homepage/Homepage.tsx index 60aa9464..0279f175 100644 --- a/src/apps/Homepage/Homepage.tsx +++ b/src/apps/Homepage/Homepage.tsx @@ -68,16 +68,14 @@ export function Homepage({onSelectView}: HomepageProps): React.ReactElement { }, []); return ( -
- -
-
-
+ +
+
-
-
-
+ ); } \ No newline at end of file diff --git a/src/apps/Homepage/HomepageAuth.tsx b/src/apps/Homepage/HomepageAuth.tsx index a078cfcf..185ac43d 100644 --- a/src/apps/Homepage/HomepageAuth.tsx +++ b/src/apps/Homepage/HomepageAuth.tsx @@ -249,50 +249,50 @@ export function HomepageAuth({ )} {(internalLoggedIn || (authLoading && getCookie("jwt"))) && (
- - Logged in! - - You are logged in! Use the sidebar to access all available tools. To get started, - create an SSH Host in the SSH Manager tab. Once created, you can connect to that - host using the other apps in the sidebar. - - + + Logged in! + + You are logged in! Use the sidebar to access all available tools. To get started, + create an SSH Host in the SSH Manager tab. Once created, you can connect to that + host using the other apps in the sidebar. + + -
- -
- -
- -
- -
-
- )} +
+ +
+ +
+ +
+ +
+
+ )} {(!internalLoggedIn && (!authLoading || !getCookie("jwt"))) && ( <>
diff --git a/src/apps/Homepage/HomepageSidebar.tsx b/src/apps/Homepage/HomepageSidebar.tsx index 8608b8db..30f4e524 100644 --- a/src/apps/Homepage/HomepageSidebar.tsx +++ b/src/apps/Homepage/HomepageSidebar.tsx @@ -14,7 +14,7 @@ import { SidebarGroupLabel, SidebarMenu, SidebarMenuButton, - SidebarMenuItem, SidebarProvider, + SidebarMenuItem, SidebarProvider, SidebarInset, } from "@/components/ui/sidebar.tsx" import { @@ -44,6 +44,7 @@ interface SidebarProps { disabled?: boolean; isAdmin?: boolean; username?: string | null; + children?: React.ReactNode; } function handleLogout() { @@ -72,7 +73,8 @@ export function HomepageSidebar({ getView, disabled, isAdmin, - username + username, + children, }: SidebarProps): React.ReactElement { const [adminSheetOpen, setAdminSheetOpen] = React.useState(false); const [allowRegistration, setAllowRegistration] = React.useState(true); @@ -160,7 +162,7 @@ export function HomepageSidebar({ }; return ( -
+
@@ -430,6 +432,9 @@ export function HomepageSidebar({ )} + + {children} +
) diff --git a/src/apps/Homepage/HompageUpdateLog.tsx b/src/apps/Homepage/HompageUpdateLog.tsx index 7453dd8c..3a72c18f 100644 --- a/src/apps/Homepage/HompageUpdateLog.tsx +++ b/src/apps/Homepage/HompageUpdateLog.tsx @@ -98,7 +98,7 @@ export function HomepageUpdateLog({loggedIn}: HomepageUpdateLogProps) { }; return ( -
+

Updates & Releases

diff --git a/src/apps/SSH/Terminal/SSHSidebar.tsx b/src/apps/SSH/Terminal/SSHSidebar.tsx index 634db0d2..f890d4fc 100644 --- a/src/apps/SSH/Terminal/SSHSidebar.tsx +++ b/src/apps/SSH/Terminal/SSHSidebar.tsx @@ -38,6 +38,7 @@ import { import {ScrollArea} from "@/components/ui/scroll-area.tsx"; import {Input} from "@/components/ui/input.tsx"; import {getSSHHosts} from "@/apps/SSH/ssh-axios"; +import {Checkbox} from "@/components/ui/checkbox.tsx"; interface SSHHost { id: number; @@ -186,6 +187,17 @@ export function SSHSidebar({onSelectView, onHostConnect, allTabs, runCommandOnTa } }; + function getCookie(name: string) { + return document.cookie.split('; ').reduce((r, v) => { + const parts = v.split('='); + return parts[0] === name ? decodeURIComponent(parts[1]) : r; + }, ""); + } + + const updateRightClickCopyPaste = (checked) => { + document.cookie = `rightClickCopyPaste=${checked}; expires=2147483647; path=/`; + } + return ( @@ -332,6 +344,19 @@ export function SSHSidebar({onSelectView, onHostConnect, allTabs, runCommandOnTa + + + +
+ + +
diff --git a/src/apps/SSH/Terminal/SSHTerminal.tsx b/src/apps/SSH/Terminal/SSHTerminal.tsx index c198469c..e26a1236 100644 --- a/src/apps/SSH/Terminal/SSHTerminal.tsx +++ b/src/apps/SSH/Terminal/SSHTerminal.tsx @@ -52,6 +52,49 @@ export const SSHTerminal = forwardRef(function SSHTermina fitAddonRef.current?.fit(); } + function getCookie(name: string) { + return document.cookie.split('; ').reduce((r, v) => { + const parts = v.split('='); + return parts[0] === name ? decodeURIComponent(parts[1]) : r; + }, ""); + } + + function getUseRightClickCopyPaste() { + return getCookie("rightClickCopyPaste") === "true" + } + + async function writeTextToClipboard(text: string): Promise { + try { + if (navigator.clipboard && navigator.clipboard.writeText) { + await navigator.clipboard.writeText(text); + return; + } + } catch (_) { + } + const textarea = document.createElement('textarea'); + textarea.value = text; + textarea.style.position = 'fixed'; + textarea.style.left = '-9999px'; + document.body.appendChild(textarea); + textarea.focus(); + textarea.select(); + try { + document.execCommand('copy'); + } finally { + document.body.removeChild(textarea); + } + } + + async function readTextFromClipboard(): Promise { + try { + if (navigator.clipboard && navigator.clipboard.readText) { + return await navigator.clipboard.readText(); + } + } catch (_) { + } + return ''; + } + useEffect(() => { if (!terminal || !xtermRef.current || !hostConfig) return; @@ -59,7 +102,7 @@ export const SSHTerminal = forwardRef(function SSHTermina cursorBlink: true, cursorStyle: 'bar', scrollback: 10000, - fontSize: 15, + fontSize: 14, fontFamily: '"JetBrains Mono Nerd Font", "MesloLGS NF", "FiraCode Nerd Font", "Cascadia Code", "JetBrains Mono", Consolas, "Courier New", monospace', theme: { background: '#09090b', @@ -88,6 +131,31 @@ export const SSHTerminal = forwardRef(function SSHTermina terminal.loadAddon(webLinksAddon); terminal.open(xtermRef.current); + const element = xtermRef.current; + const handleContextMenu = async (e: MouseEvent) => { + if (!getUseRightClickCopyPaste()) return; + e.preventDefault(); + e.stopPropagation(); + try { + if (terminal.hasSelection()) { + const selection = terminal.getSelection(); + if (selection) { + await writeTextToClipboard(selection); + terminal.clearSelection(); + } + } else { + const pasteText = await readTextFromClipboard(); + if (pasteText) { + terminal.paste(pasteText); + } + } + } catch (_) { + } + }; + if (element) { + element.addEventListener('contextmenu', handleContextMenu); + } + const resizeObserver = new ResizeObserver(() => { if (resizeTimeout.current) clearTimeout(resizeTimeout.current); resizeTimeout.current = setTimeout(() => { @@ -158,6 +226,9 @@ export const SSHTerminal = forwardRef(function SSHTermina return () => { resizeObserver.disconnect(); + if (element) { + element.removeEventListener('contextmenu', handleContextMenu); + } if (resizeTimeout.current) clearTimeout(resizeTimeout.current); if (pingIntervalRef.current) { clearInterval(pingIntervalRef.current);