Update fonts, upadte logging, and auth.
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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="flex-1 bg-background grid grid-cols-3 items-center">
|
<div className="w-full min-h-svh grid place-items-center">
|
||||||
<div className="col-span-1"></div>
|
<div className="flex flex-row items-center justify-center gap-8">
|
||||||
<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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -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>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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 right‑click copy/paste
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</SheetContent>
|
</SheetContent>
|
||||||
</Sheet>
|
</Sheet>
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user