Update fonts, upadte logging, and auth.

This commit is contained in:
LukeGus
2025-08-08 00:40:32 -05:00
parent d0b139e388
commit 08f9143993
7 changed files with 160 additions and 63 deletions

View File

@@ -35,8 +35,8 @@ function App() {
} }
return ( return (
<div className="flex"> <div className="flex min-h-svh w-full">
<main> <main className="flex-1 w-full">
{renderActiveView()} {renderActiveView()}
</main> </main>
</div> </div>

View File

@@ -68,16 +68,14 @@ export function Homepage({onSelectView}: HomepageProps): React.ReactElement {
}, []); }, []);
return ( return (
<div className="flex min-h-screen"> <HomepageSidebar
<HomepageSidebar onSelectView={onSelectView}
onSelectView={onSelectView} disabled={!loggedIn || authLoading}
disabled={!loggedIn || authLoading} isAdmin={isAdmin}
isAdmin={isAdmin} username={loggedIn ? username : null}
username={loggedIn ? username : null} >
/> <div className="w-full min-h-svh grid place-items-center">
<div className="flex-1 bg-background grid grid-cols-3 items-center"> <div className="flex flex-row items-center justify-center gap-8">
<div className="col-span-1"></div>
<div className="col-span-1 flex justify-center">
<HomepageAuth <HomepageAuth
setLoggedIn={setLoggedIn} setLoggedIn={setLoggedIn}
setIsAdmin={setIsAdmin} setIsAdmin={setIsAdmin}
@@ -87,13 +85,11 @@ export function Homepage({onSelectView}: HomepageProps): React.ReactElement {
dbError={dbError} dbError={dbError}
setDbError={setDbError} setDbError={setDbError}
/> />
</div>
<div className="col-span-1 flex justify-center">
<HomepageUpdateLog <HomepageUpdateLog
loggedIn={loggedIn} loggedIn={loggedIn}
/> />
</div> </div>
</div> </div>
</div> </HomepageSidebar>
); );
} }

View File

@@ -249,50 +249,50 @@ export function HomepageAuth({
)} )}
{(internalLoggedIn || (authLoading && getCookie("jwt"))) && ( {(internalLoggedIn || (authLoading && getCookie("jwt"))) && (
<div className="flex flex-col items-center gap-4"> <div className="flex flex-col items-center gap-4">
<Alert className="my-2"> <Alert className="my-2">
<AlertTitle>Logged in!</AlertTitle> <AlertTitle>Logged in!</AlertTitle>
<AlertDescription> <AlertDescription>
You are logged in! Use the sidebar to access all available tools. To get started, 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 create an SSH Host in the SSH Manager tab. Once created, you can connect to that
host using the other apps in the sidebar. host using the other apps in the sidebar.
</AlertDescription> </AlertDescription>
</Alert> </Alert>
<div className="flex flex-row items-center gap-2"> <div className="flex flex-row items-center gap-2">
<Button <Button
variant="link" variant="link"
className="text-sm" className="text-sm"
onClick={() => window.open('https://github.com/LukeGus/Termix', '_blank')} onClick={() => window.open('https://github.com/LukeGus/Termix', '_blank')}
> >
GitHub GitHub
</Button> </Button>
<div className="w-px h-4 bg-border"></div> <div className="w-px h-4 bg-border"></div>
<Button <Button
variant="link" variant="link"
className="text-sm" className="text-sm"
onClick={() => window.open('https://github.com/LukeGus/Termix/issues/new', '_blank')} onClick={() => window.open('https://github.com/LukeGus/Termix/issues/new', '_blank')}
> >
Feedback Feedback
</Button> </Button>
<div className="w-px h-4 bg-border"></div> <div className="w-px h-4 bg-border"></div>
<Button <Button
variant="link" variant="link"
className="text-sm" className="text-sm"
onClick={() => window.open('https://discord.com/invite/jVQGdvHDrf', '_blank')} onClick={() => window.open('https://discord.com/invite/jVQGdvHDrf', '_blank')}
> >
Discord Discord
</Button> </Button>
<div className="w-px h-4 bg-border"></div> <div className="w-px h-4 bg-border"></div>
<Button <Button
variant="link" variant="link"
className="text-sm" className="text-sm"
onClick={() => window.open('https://github.com/sponsors/LukeGus', '_blank')} onClick={() => window.open('https://github.com/sponsors/LukeGus', '_blank')}
> >
Fund Fund
</Button> </Button>
</div> </div>
</div> </div>
)} )}
{(!internalLoggedIn && (!authLoading || !getCookie("jwt"))) && ( {(!internalLoggedIn && (!authLoading || !getCookie("jwt"))) && (
<> <>
<div className="flex gap-2 mb-6"> <div className="flex gap-2 mb-6">

View File

@@ -14,7 +14,7 @@ import {
SidebarGroupLabel, SidebarGroupLabel,
SidebarMenu, SidebarMenu,
SidebarMenuButton, SidebarMenuButton,
SidebarMenuItem, SidebarProvider, SidebarMenuItem, SidebarProvider, SidebarInset,
} from "@/components/ui/sidebar.tsx" } from "@/components/ui/sidebar.tsx"
import { import {
@@ -44,6 +44,7 @@ interface SidebarProps {
disabled?: boolean; disabled?: boolean;
isAdmin?: boolean; isAdmin?: boolean;
username?: string | null; username?: string | null;
children?: React.ReactNode;
} }
function handleLogout() { function handleLogout() {
@@ -72,7 +73,8 @@ export function HomepageSidebar({
getView, getView,
disabled, disabled,
isAdmin, isAdmin,
username username,
children,
}: SidebarProps): React.ReactElement { }: SidebarProps): React.ReactElement {
const [adminSheetOpen, setAdminSheetOpen] = React.useState(false); const [adminSheetOpen, setAdminSheetOpen] = React.useState(false);
const [allowRegistration, setAllowRegistration] = React.useState(true); const [allowRegistration, setAllowRegistration] = React.useState(true);
@@ -160,7 +162,7 @@ export function HomepageSidebar({
}; };
return ( return (
<div> <div className="min-h-svh">
<SidebarProvider> <SidebarProvider>
<Sidebar> <Sidebar>
<SidebarContent> <SidebarContent>
@@ -430,6 +432,9 @@ export function HomepageSidebar({
</Sheet> </Sheet>
)} )}
</Sidebar> </Sidebar>
<SidebarInset>
{children}
</SidebarInset>
</SidebarProvider> </SidebarProvider>
</div> </div>
) )

View File

@@ -98,7 +98,7 @@ export function HomepageUpdateLog({loggedIn}: HomepageUpdateLogProps) {
}; };
return ( return (
<div className="w-[400px] h-[600px] mr-8 flex flex-col border border-border rounded-lg bg-card p-4"> <div className="w-[400px] h-[600px] flex flex-col border border-border rounded-lg bg-card p-4">
<div> <div>
<h3 className="text-lg font-semibold mb-3">Updates & Releases</h3> <h3 className="text-lg font-semibold mb-3">Updates & Releases</h3>

View File

@@ -38,6 +38,7 @@ import {
import {ScrollArea} from "@/components/ui/scroll-area.tsx"; import {ScrollArea} from "@/components/ui/scroll-area.tsx";
import {Input} from "@/components/ui/input.tsx"; import {Input} from "@/components/ui/input.tsx";
import {getSSHHosts} from "@/apps/SSH/ssh-axios"; import {getSSHHosts} from "@/apps/SSH/ssh-axios";
import {Checkbox} from "@/components/ui/checkbox.tsx";
interface SSHHost { interface SSHHost {
id: number; 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 ( return (
<SidebarProvider> <SidebarProvider>
<Sidebar className="h-full flex flex-col overflow-hidden"> <Sidebar className="h-full flex flex-col overflow-hidden">
@@ -332,6 +344,19 @@ export function SSHSidebar({onSelectView, onHostConnect, allTabs, runCommandOnTa
</AccordionContent> </AccordionContent>
</AccordionItem> </AccordionItem>
</Accordion> </Accordion>
<Separator className="p-0.25"/>
<div className="flex items-center space-x-2 mt-5">
<Checkbox id="enable-copy-paste" onCheckedChange={updateRightClickCopyPaste}
defaultChecked={getCookie("rightClickCopyPaste") === "true"}/>
<label
htmlFor="enable-paste"
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
Enable rightclick copy/paste
</label>
</div>
</div> </div>
</SheetContent> </SheetContent>
</Sheet> </Sheet>

View File

@@ -52,6 +52,49 @@ export const SSHTerminal = forwardRef<any, SSHTerminalProps>(function SSHTermina
fitAddonRef.current?.fit(); 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<void> {
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<string> {
try {
if (navigator.clipboard && navigator.clipboard.readText) {
return await navigator.clipboard.readText();
}
} catch (_) {
}
return '';
}
useEffect(() => { useEffect(() => {
if (!terminal || !xtermRef.current || !hostConfig) return; if (!terminal || !xtermRef.current || !hostConfig) return;
@@ -59,7 +102,7 @@ export const SSHTerminal = forwardRef<any, SSHTerminalProps>(function SSHTermina
cursorBlink: true, cursorBlink: true,
cursorStyle: 'bar', cursorStyle: 'bar',
scrollback: 10000, scrollback: 10000,
fontSize: 15, fontSize: 14,
fontFamily: '"JetBrains Mono Nerd Font", "MesloLGS NF", "FiraCode Nerd Font", "Cascadia Code", "JetBrains Mono", Consolas, "Courier New", monospace', fontFamily: '"JetBrains Mono Nerd Font", "MesloLGS NF", "FiraCode Nerd Font", "Cascadia Code", "JetBrains Mono", Consolas, "Courier New", monospace',
theme: { theme: {
background: '#09090b', background: '#09090b',
@@ -88,6 +131,31 @@ export const SSHTerminal = forwardRef<any, SSHTerminalProps>(function SSHTermina
terminal.loadAddon(webLinksAddon); terminal.loadAddon(webLinksAddon);
terminal.open(xtermRef.current); 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(() => { const resizeObserver = new ResizeObserver(() => {
if (resizeTimeout.current) clearTimeout(resizeTimeout.current); if (resizeTimeout.current) clearTimeout(resizeTimeout.current);
resizeTimeout.current = setTimeout(() => { resizeTimeout.current = setTimeout(() => {
@@ -158,6 +226,9 @@ export const SSHTerminal = forwardRef<any, SSHTerminalProps>(function SSHTermina
return () => { return () => {
resizeObserver.disconnect(); resizeObserver.disconnect();
if (element) {
element.removeEventListener('contextmenu', handleContextMenu);
}
if (resizeTimeout.current) clearTimeout(resizeTimeout.current); if (resizeTimeout.current) clearTimeout(resizeTimeout.current);
if (pingIntervalRef.current) { if (pingIntervalRef.current) {
clearInterval(pingIntervalRef.current); clearInterval(pingIntervalRef.current);