v1.10.0 #471
No files matched your search
@@ -9,7 +9,7 @@ import {
|
|||||||
} from "@/ui/desktop/navigation/tabs/TabContext.tsx";
|
} from "@/ui/desktop/navigation/tabs/TabContext.tsx";
|
||||||
import { TopNavbar } from "@/ui/desktop/navigation/TopNavbar.tsx";
|
import { TopNavbar } from "@/ui/desktop/navigation/TopNavbar.tsx";
|
||||||
import { CommandHistoryProvider } from "@/ui/desktop/apps/features/terminal/command-history/CommandHistoryContext.tsx";
|
import { CommandHistoryProvider } from "@/ui/desktop/apps/features/terminal/command-history/CommandHistoryContext.tsx";
|
||||||
import { AdminSettings } from "@/ui/desktop/admin/AdminSettings.tsx";
|
import { AdminSettings } from "@/ui/desktop/apps/admin/AdminSettings.tsx";
|
||||||
import { UserProfile } from "@/ui/desktop/user/UserProfile.tsx";
|
import { UserProfile } from "@/ui/desktop/user/UserProfile.tsx";
|
||||||
import { Toaster } from "@/components/ui/sonner.tsx";
|
import { Toaster } from "@/components/ui/sonner.tsx";
|
||||||
import { CommandPalette } from "@/ui/desktop/apps/command-palette/CommandPalette.tsx";
|
import { CommandPalette } from "@/ui/desktop/apps/command-palette/CommandPalette.tsx";
|
||||||
|
|||||||
+7
-7
@@ -1,5 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { useSidebar } from "@/components/ui/sidebar";
|
import { useSidebar } from "@/components/ui/sidebar.tsx";
|
||||||
import { Separator } from "@/components/ui/separator.tsx";
|
import { Separator } from "@/components/ui/separator.tsx";
|
||||||
import {
|
import {
|
||||||
Tabs,
|
Tabs,
|
||||||
@@ -21,12 +21,12 @@ import {
|
|||||||
getSessions,
|
getSessions,
|
||||||
unlinkOIDCFromPasswordAccount,
|
unlinkOIDCFromPasswordAccount,
|
||||||
} from "@/ui/main-axios.ts";
|
} from "@/ui/main-axios.ts";
|
||||||
import { RolesTab } from "./widgets/RolesTab.tsx";
|
import { RolesTab } from "@/ui/desktop/apps/admin/tabs/RolesTab.tsx";
|
||||||
import { GeneralSettingsTab } from "./widgets/GeneralSettingsTab.tsx";
|
import { GeneralSettingsTab } from "@/ui/desktop/apps/admin/tabs/GeneralSettingsTab.tsx";
|
||||||
import { OIDCSettingsTab } from "./widgets/OIDCSettingsTab.tsx";
|
import { OIDCSettingsTab } from "@/ui/desktop/apps/admin/tabs/OIDCSettingsTab.tsx";
|
||||||
import { UserManagementTab } from "./widgets/UserManagementTab.tsx";
|
import { UserManagementTab } from "@/ui/desktop/apps/admin/tabs/UserManagementTab.tsx";
|
||||||
import { SessionManagementTab } from "./widgets/SessionManagementTab.tsx";
|
import { SessionManagementTab } from "@/ui/desktop/apps/admin/tabs/SessionManagementTab.tsx";
|
||||||
import { DatabaseSecurityTab } from "./widgets/DatabaseSecurityTab.tsx";
|
import { DatabaseSecurityTab } from "@/ui/desktop/apps/admin/tabs/DatabaseSecurityTab.tsx";
|
||||||
import { CreateUserDialog } from "./dialogs/CreateUserDialog.tsx";
|
import { CreateUserDialog } from "./dialogs/CreateUserDialog.tsx";
|
||||||
import { UserEditDialog } from "./dialogs/UserEditDialog.tsx";
|
import { UserEditDialog } from "./dialogs/UserEditDialog.tsx";
|
||||||
import { LinkAccountDialog } from "./dialogs/LinkAccountDialog.tsx";
|
import { LinkAccountDialog } from "./dialogs/LinkAccountDialog.tsx";
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -123,8 +123,6 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
|
|||||||
const resizeTimeout = useRef<NodeJS.Timeout | null>(null);
|
const resizeTimeout = useRef<NodeJS.Timeout | null>(null);
|
||||||
const wasDisconnectedBySSH = useRef(false);
|
const wasDisconnectedBySSH = useRef(false);
|
||||||
const pingIntervalRef = useRef<NodeJS.Timeout | null>(null);
|
const pingIntervalRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
const [visible, setVisible] = useState(false);
|
|
||||||
const [isReady, setIsReady] = useState(false);
|
|
||||||
const [isConnected, setIsConnected] = useState(false);
|
const [isConnected, setIsConnected] = useState(false);
|
||||||
const [isConnecting, setIsConnecting] = useState(false);
|
const [isConnecting, setIsConnecting] = useState(false);
|
||||||
const [isFitted, setIsFitted] = useState(true);
|
const [isFitted, setIsFitted] = useState(true);
|
||||||
@@ -342,7 +340,7 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
|
|||||||
if (
|
if (
|
||||||
!fitAddonRef.current ||
|
!fitAddonRef.current ||
|
||||||
!terminal ||
|
!terminal ||
|
||||||
!isVisibleRef.current ||
|
!isVisible ||
|
||||||
isFittingRef.current
|
isFittingRef.current
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
@@ -1144,6 +1142,15 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
|
|||||||
|
|
||||||
terminal.open(xtermRef.current);
|
terminal.open(xtermRef.current);
|
||||||
|
|
||||||
|
// Immediately fit to establish correct dimensions
|
||||||
|
fitAddonRef.current?.fit();
|
||||||
|
if (terminal.cols < 10 || terminal.rows < 3) {
|
||||||
|
// Terminal opened with invalid dimensions, retry fit in next frame
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
fitAddonRef.current?.fit();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const element = xtermRef.current;
|
const element = xtermRef.current;
|
||||||
const handleContextMenu = async (e: MouseEvent) => {
|
const handleContextMenu = async (e: MouseEvent) => {
|
||||||
if (!getUseRightClickCopyPaste()) return;
|
if (!getUseRightClickCopyPaste()) return;
|
||||||
@@ -1225,22 +1232,19 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
|
|||||||
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(() => {
|
||||||
if (!isVisibleRef.current || !isReady) return;
|
if (isVisible && terminal?.cols > 0) {
|
||||||
performFit();
|
performFit();
|
||||||
|
}
|
||||||
}, 50);
|
}, 50);
|
||||||
});
|
});
|
||||||
|
|
||||||
resizeObserver.observe(xtermRef.current);
|
resizeObserver.observe(xtermRef.current);
|
||||||
|
|
||||||
setVisible(true);
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
isUnmountingRef.current = true;
|
isUnmountingRef.current = true;
|
||||||
shouldNotReconnectRef.current = true;
|
shouldNotReconnectRef.current = true;
|
||||||
isReconnectingRef.current = false;
|
isReconnectingRef.current = false;
|
||||||
setIsConnecting(false);
|
setIsConnecting(false);
|
||||||
setVisible(false);
|
|
||||||
setIsReady(false);
|
|
||||||
isFittingRef.current = false;
|
isFittingRef.current = false;
|
||||||
resizeObserver.disconnect();
|
resizeObserver.disconnect();
|
||||||
element?.removeEventListener("contextmenu", handleContextMenu);
|
element?.removeEventListener("contextmenu", handleContextMenu);
|
||||||
@@ -1444,75 +1448,48 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
|
|||||||
terminal.attachCustomKeyEventHandler(handleCustomKey);
|
terminal.attachCustomKeyEventHandler(handleCustomKey);
|
||||||
}, [terminal]);
|
}, [terminal]);
|
||||||
|
|
||||||
|
// Connection initialization effect
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!terminal || !hostConfig || !visible) return;
|
if (!terminal || !hostConfig || !isVisible) return;
|
||||||
|
|
||||||
if (isConnected || isConnecting) return;
|
if (isConnected || isConnecting) return;
|
||||||
|
|
||||||
setIsConnecting(true);
|
// Ensure terminal has valid dimensions before connecting
|
||||||
|
if (terminal.cols < 10 || terminal.rows < 3) {
|
||||||
// Start connection immediately without waiting for fonts
|
// Wait for next frame when dimensions will be valid
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
fitAddonRef.current?.fit();
|
if (terminal.cols > 0 && terminal.rows > 0) {
|
||||||
if (terminal && terminal.cols > 0 && terminal.rows > 0) {
|
setIsConnecting(true);
|
||||||
scheduleNotify(terminal.cols, terminal.rows);
|
fitAddonRef.current?.fit();
|
||||||
}
|
scheduleNotify(terminal.cols, terminal.rows);
|
||||||
hardRefresh();
|
connectToHost(terminal.cols, terminal.rows);
|
||||||
|
}
|
||||||
setVisible(true);
|
});
|
||||||
setIsReady(true);
|
|
||||||
|
|
||||||
if (terminal && !splitScreen) {
|
|
||||||
terminal.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
const jwtToken = getCookie("jwt");
|
|
||||||
|
|
||||||
if (!jwtToken || jwtToken.trim() === "") {
|
|
||||||
setIsConnected(false);
|
|
||||||
setIsConnecting(false);
|
|
||||||
setConnectionError("Authentication required");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const cols = terminal.cols;
|
|
||||||
const rows = terminal.rows;
|
|
||||||
|
|
||||||
connectToHost(cols, rows);
|
|
||||||
});
|
|
||||||
}, [terminal, hostConfig, visible, isConnected, isConnecting, splitScreen]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isVisible || !isReady || !fitAddonRef.current || !terminal) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let rafId: number;
|
setIsConnecting(true);
|
||||||
|
fitAddonRef.current?.fit();
|
||||||
rafId = requestAnimationFrame(() => {
|
if (terminal.cols > 0 && terminal.rows > 0) {
|
||||||
performFit();
|
scheduleNotify(terminal.cols, terminal.rows);
|
||||||
});
|
connectToHost(terminal.cols, terminal.rows);
|
||||||
|
|
||||||
return () => {
|
|
||||||
if (rafId) cancelAnimationFrame(rafId);
|
|
||||||
};
|
|
||||||
}, [isVisible, isReady, splitScreen, terminal]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (
|
|
||||||
isFitted &&
|
|
||||||
isVisible &&
|
|
||||||
isReady &&
|
|
||||||
!isConnecting &&
|
|
||||||
terminal &&
|
|
||||||
!splitScreen
|
|
||||||
) {
|
|
||||||
const rafId = requestAnimationFrame(() => {
|
|
||||||
terminal.focus();
|
|
||||||
});
|
|
||||||
return () => cancelAnimationFrame(rafId);
|
|
||||||
}
|
}
|
||||||
}, [isFitted, isVisible, isReady, isConnecting, terminal, splitScreen]);
|
}, [terminal, hostConfig, isVisible, isConnected, isConnecting]);
|
||||||
|
|
||||||
|
// Consolidated fitting and focus effect
|
||||||
|
useEffect(() => {
|
||||||
|
if (!terminal || !fitAddonRef.current || !isVisible) return;
|
||||||
|
|
||||||
|
const fitTimeoutId = setTimeout(() => {
|
||||||
|
if (!isFittingRef.current && terminal.cols > 0 && terminal.rows > 0) {
|
||||||
|
performFit();
|
||||||
|
if (!splitScreen && !isConnecting) {
|
||||||
|
requestAnimationFrame(() => terminal.focus());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
return () => clearTimeout(fitTimeoutId);
|
||||||
|
}, [terminal, isVisible, splitScreen, isConnecting]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full w-full relative" style={{ backgroundColor }}>
|
<div className="h-full w-full relative" style={{ backgroundColor }}>
|
||||||
@@ -1520,8 +1497,7 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
|
|||||||
ref={xtermRef}
|
ref={xtermRef}
|
||||||
className="h-full w-full"
|
className="h-full w-full"
|
||||||
style={{
|
style={{
|
||||||
visibility: isReady ? "visible" : "hidden",
|
pointerEvents: isVisible ? "auto" : "none",
|
||||||
pointerEvents: isReady ? "auto" : "none",
|
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (terminal && !splitScreen) {
|
if (terminal && !splitScreen) {
|
||||||
|
|||||||
File renamed without changes.
@@ -38,7 +38,7 @@ import {
|
|||||||
} from "@/ui/main-axios.ts";
|
} from "@/ui/main-axios.ts";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { CredentialSelector } from "@/ui/desktop/apps/host-manager/credentials/CredentialSelector.tsx";
|
import { CredentialSelector } from "@/ui/desktop/apps/host-manager/credentials/CredentialSelector.tsx";
|
||||||
import { HostSharingTab } from "./HostSharingTab.tsx";
|
import { HostSharingTab } from "./tabs/HostSharingTab.tsx";
|
||||||
import CodeMirror from "@uiw/react-codemirror";
|
import CodeMirror from "@uiw/react-codemirror";
|
||||||
import { oneDark } from "@codemirror/theme-one-dark";
|
import { oneDark } from "@codemirror/theme-one-dark";
|
||||||
import { githubLight } from "@uiw/codemirror-theme-github";
|
import { githubLight } from "@uiw/codemirror-theme-github";
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ import type {
|
|||||||
SSHManagerHostViewerProps,
|
SSHManagerHostViewerProps,
|
||||||
} from "../../../../../types";
|
} from "../../../../../types";
|
||||||
import { DEFAULT_STATS_CONFIG } from "@/types/stats-widgets.ts";
|
import { DEFAULT_STATS_CONFIG } from "@/types/stats-widgets.ts";
|
||||||
import { FolderEditDialog } from "../components/FolderEditDialog.tsx";
|
import { FolderEditDialog } from "@/ui/desktop/apps/host-manager/dialogs/FolderEditDialog.tsx";
|
||||||
import { useTabs } from "@/ui/desktop/navigation/tabs/TabContext.tsx";
|
import { useTabs } from "@/ui/desktop/navigation/tabs/TabContext.tsx";
|
||||||
|
|
||||||
export function HostManagerViewer({ onEditHost }: SSHManagerHostViewerProps) {
|
export function HostManagerViewer({ onEditHost }: SSHManagerHostViewerProps) {
|
||||||
|
|||||||
File renamed without changes.
@@ -101,13 +101,18 @@ export function AppView({
|
|||||||
const splitIds = allSplitScreenTab as number[];
|
const splitIds = allSplitScreenTab as number[];
|
||||||
visibleIds.push(currentTab, ...splitIds.filter((i) => i !== currentTab));
|
visibleIds.push(currentTab, ...splitIds.filter((i) => i !== currentTab));
|
||||||
}
|
}
|
||||||
terminalTabs.forEach((t: TabData) => {
|
|
||||||
if (visibleIds.includes(t.id)) {
|
const operations = terminalTabs
|
||||||
const ref = t.terminalRef?.current;
|
.filter((t: TabData) => visibleIds.includes(t.id))
|
||||||
if (ref?.fit) ref.fit();
|
.map((t: TabData) => t.terminalRef?.current)
|
||||||
if (ref?.notifyResize) ref.notifyResize();
|
.filter((ref) => ref?.fit);
|
||||||
if (ref?.refresh) ref.refresh();
|
|
||||||
}
|
requestAnimationFrame(() => {
|
||||||
|
operations.forEach((ref) => {
|
||||||
|
ref.fit?.();
|
||||||
|
ref.notifyResize?.();
|
||||||
|
ref.refresh?.();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}, [allSplitScreenTab, currentTab, terminalTabs]);
|
}, [allSplitScreenTab, currentTab, terminalTabs]);
|
||||||
|
|
||||||
@@ -117,18 +122,14 @@ export function AppView({
|
|||||||
cancelAnimationFrame(layoutScheduleRef.current);
|
cancelAnimationFrame(layoutScheduleRef.current);
|
||||||
layoutScheduleRef.current = requestAnimationFrame(() => {
|
layoutScheduleRef.current = requestAnimationFrame(() => {
|
||||||
updatePanelRects();
|
updatePanelRects();
|
||||||
layoutScheduleRef.current = requestAnimationFrame(() => {
|
fitActiveAndNotify();
|
||||||
fitActiveAndNotify();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}, [updatePanelRects, fitActiveAndNotify]);
|
}, [updatePanelRects, fitActiveAndNotify]);
|
||||||
|
|
||||||
const hideThenFit = React.useCallback(() => {
|
const hideThenFit = React.useCallback(() => {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
updatePanelRects();
|
updatePanelRects();
|
||||||
requestAnimationFrame(() => {
|
fitActiveAndNotify();
|
||||||
fitActiveAndNotify();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}, [updatePanelRects, fitActiveAndNotify]);
|
}, [updatePanelRects, fitActiveAndNotify]);
|
||||||
|
|
||||||
@@ -237,7 +238,6 @@ export function AppView({
|
|||||||
display: "block" as const,
|
display: "block" as const,
|
||||||
pointerEvents: "auto" as const,
|
pointerEvents: "auto" as const,
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
transition: "opacity 150ms ease-in-out",
|
|
||||||
};
|
};
|
||||||
styles[mainTab.id] = newStyle;
|
styles[mainTab.id] = newStyle;
|
||||||
previousStylesRef.current[mainTab.id] = newStyle;
|
previousStylesRef.current[mainTab.id] = newStyle;
|
||||||
@@ -256,7 +256,6 @@ export function AppView({
|
|||||||
display: "block" as const,
|
display: "block" as const,
|
||||||
pointerEvents: "auto" as const,
|
pointerEvents: "auto" as const,
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
transition: "opacity 150ms ease-in-out",
|
|
||||||
};
|
};
|
||||||
styles[t.id] = newStyle;
|
styles[t.id] = newStyle;
|
||||||
previousStylesRef.current[t.id] = newStyle;
|
previousStylesRef.current[t.id] = newStyle;
|
||||||
@@ -298,7 +297,6 @@ export function AppView({
|
|||||||
pointerEvents: "auto",
|
pointerEvents: "auto",
|
||||||
zIndex: 20,
|
zIndex: 20,
|
||||||
display: "block",
|
display: "block",
|
||||||
transition: "opacity 150ms ease-in-out",
|
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
}
|
}
|
||||||
: ({
|
: ({
|
||||||
@@ -306,7 +304,7 @@ export function AppView({
|
|||||||
opacity: 0,
|
opacity: 0,
|
||||||
pointerEvents: "none",
|
pointerEvents: "none",
|
||||||
zIndex: 0,
|
zIndex: 0,
|
||||||
transition: "opacity 150ms ease-in-out",
|
display: "none",
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
} as React.CSSProperties);
|
} as React.CSSProperties);
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user