fix: replace explicit any types with proper TypeScript types
- Replace 'any' with 'unknown' in catch blocks and add type assertions - Create explicit interfaces for complex objects (HostConfig, TabData, TerminalHandle) - Fix window/document object type extensions - Update Electron API type definitions - Improve type safety in database routes and utilities - Add proper types to Terminal components (Desktop & Mobile) - Fix navigation component types (TopNavbar, LeftSidebar, AppView) Reduces TypeScript lint errors from 394 to 358 (-36 errors) Fixes 45 @typescript-eslint/no-explicit-any violations
This commit is contained in:
@@ -107,7 +107,8 @@ export function AdminSettings({
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isElectron()) {
|
||||
const serverUrl = (window as any).configuredServerUrl;
|
||||
const serverUrl = (window as { configuredServerUrl?: string })
|
||||
.configuredServerUrl;
|
||||
if (!serverUrl) {
|
||||
return;
|
||||
}
|
||||
@@ -127,7 +128,8 @@ export function AdminSettings({
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isElectron()) {
|
||||
const serverUrl = (window as any).configuredServerUrl;
|
||||
const serverUrl = (window as { configuredServerUrl?: string })
|
||||
.configuredServerUrl;
|
||||
if (!serverUrl) {
|
||||
return;
|
||||
}
|
||||
@@ -148,7 +150,8 @@ export function AdminSettings({
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isElectron()) {
|
||||
const serverUrl = (window as any).configuredServerUrl;
|
||||
const serverUrl = (window as { configuredServerUrl?: string })
|
||||
.configuredServerUrl;
|
||||
if (!serverUrl) {
|
||||
return;
|
||||
}
|
||||
@@ -169,7 +172,8 @@ export function AdminSettings({
|
||||
|
||||
const fetchUsers = async () => {
|
||||
if (isElectron()) {
|
||||
const serverUrl = (window as any).configuredServerUrl;
|
||||
const serverUrl = (window as { configuredServerUrl?: string })
|
||||
.configuredServerUrl;
|
||||
if (!serverUrl) {
|
||||
return;
|
||||
}
|
||||
@@ -234,9 +238,10 @@ export function AdminSettings({
|
||||
try {
|
||||
await updateOIDCConfig(oidcConfig);
|
||||
toast.success(t("admin.oidcConfigurationUpdated"));
|
||||
} catch (err: any) {
|
||||
} catch (err: unknown) {
|
||||
setOidcError(
|
||||
err?.response?.data?.error || t("admin.failedToUpdateOidcConfig"),
|
||||
(err as { response?: { data?: { error?: string } } })?.response?.data
|
||||
?.error || t("admin.failedToUpdateOidcConfig"),
|
||||
);
|
||||
} finally {
|
||||
setOidcLoading(false);
|
||||
@@ -257,9 +262,10 @@ export function AdminSettings({
|
||||
toast.success(t("admin.userIsNowAdmin", { username: newAdminUsername }));
|
||||
setNewAdminUsername("");
|
||||
fetchUsers();
|
||||
} catch (err: any) {
|
||||
} catch (err: unknown) {
|
||||
setMakeAdminError(
|
||||
err?.response?.data?.error || t("admin.failedToMakeUserAdmin"),
|
||||
(err as { response?: { data?: { error?: string } } })?.response?.data
|
||||
?.error || t("admin.failedToMakeUserAdmin"),
|
||||
);
|
||||
} finally {
|
||||
setMakeAdminLoading(false);
|
||||
@@ -272,7 +278,7 @@ export function AdminSettings({
|
||||
await removeAdminStatus(username);
|
||||
toast.success(t("admin.adminStatusRemoved", { username }));
|
||||
fetchUsers();
|
||||
} catch (err: any) {
|
||||
} catch (err: unknown) {
|
||||
toast.error(t("admin.failedToRemoveAdminStatus"));
|
||||
}
|
||||
});
|
||||
@@ -286,7 +292,7 @@ export function AdminSettings({
|
||||
await deleteUser(username);
|
||||
toast.success(t("admin.userDeletedSuccessfully", { username }));
|
||||
fetchUsers();
|
||||
} catch (err: any) {
|
||||
} catch (err: unknown) {
|
||||
toast.error(t("admin.failedToDeleteUser"));
|
||||
}
|
||||
},
|
||||
@@ -316,7 +322,7 @@ export function AdminSettings({
|
||||
window.location.hostname === "127.0.0.1");
|
||||
|
||||
const apiUrl = isElectron()
|
||||
? `${(window as any).configuredServerUrl}/database/export`
|
||||
? `${(window as { configuredServerUrl?: string }).configuredServerUrl}/database/export`
|
||||
: isDev
|
||||
? `http://localhost:30001/database/export`
|
||||
: `${window.location.protocol}//${window.location.host}/database/export`;
|
||||
@@ -386,7 +392,7 @@ export function AdminSettings({
|
||||
window.location.hostname === "127.0.0.1");
|
||||
|
||||
const apiUrl = isElectron()
|
||||
? `${(window as any).configuredServerUrl}/database/import`
|
||||
? `${(window as { configuredServerUrl?: string }).configuredServerUrl}/database/import`
|
||||
: isDev
|
||||
? `http://localhost:30001/database/import`
|
||||
: `${window.location.protocol}//${window.location.host}/database/import`;
|
||||
@@ -713,9 +719,13 @@ export function AdminSettings({
|
||||
try {
|
||||
await disableOIDCConfig();
|
||||
toast.success(t("admin.oidcConfigurationDisabled"));
|
||||
} catch (err: any) {
|
||||
} catch (err: unknown) {
|
||||
setOidcError(
|
||||
err?.response?.data?.error ||
|
||||
(
|
||||
err as {
|
||||
response?: { data?: { error?: string } };
|
||||
}
|
||||
)?.response?.data?.error ||
|
||||
t("admin.failedToDisableOidcConfig"),
|
||||
);
|
||||
} finally {
|
||||
|
||||
@@ -311,10 +311,10 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
|
||||
setFiles(files);
|
||||
clearSelection();
|
||||
initialLoadDoneRef.current = true;
|
||||
} catch (dirError: any) {
|
||||
} catch (dirError: unknown) {
|
||||
console.error("Failed to load initial directory:", dirError);
|
||||
}
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
console.error("SSH connection failed:", error);
|
||||
handleCloseWithError(
|
||||
t("fileManager.failedToConnect") + ": " + (error.message || error),
|
||||
@@ -353,7 +353,7 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
|
||||
|
||||
setFiles(files);
|
||||
clearSelection();
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
if (currentLoadingPathRef.current === path) {
|
||||
console.error("Failed to load directory:", error);
|
||||
|
||||
@@ -535,7 +535,7 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
|
||||
t("fileManager.fileUploadedSuccessfully", { name: file.name }),
|
||||
);
|
||||
handleRefreshDirectory();
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
toast.dismiss(progressToast);
|
||||
|
||||
if (
|
||||
@@ -584,7 +584,7 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
|
||||
t("fileManager.fileDownloadedSuccessfully", { name: file.name }),
|
||||
);
|
||||
}
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
if (
|
||||
error.message?.includes("connection") ||
|
||||
error.message?.includes("established")
|
||||
@@ -665,7 +665,7 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
|
||||
);
|
||||
handleRefreshDirectory();
|
||||
clearSelection();
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
if (
|
||||
error.message?.includes("connection") ||
|
||||
error.message?.includes("established")
|
||||
@@ -775,7 +775,7 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
|
||||
component: createWindowComponent,
|
||||
});
|
||||
}
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
toast.error(
|
||||
error?.response?.data?.error ||
|
||||
error?.message ||
|
||||
@@ -914,7 +914,7 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
|
||||
successCount++;
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
console.error(`Failed to ${operation} file ${file.name}:`, error);
|
||||
toast.error(
|
||||
t("fileManager.operationFailed", {
|
||||
@@ -1015,7 +1015,7 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
|
||||
if (operation === "cut") {
|
||||
setClipboard(null);
|
||||
}
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
toast.error(
|
||||
`${t("fileManager.pasteFailed")}: ${error.message || t("fileManager.unknownError")}`,
|
||||
);
|
||||
@@ -1050,7 +1050,7 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
|
||||
currentHost?.userId?.toString(),
|
||||
);
|
||||
successCount++;
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
console.error(
|
||||
`Failed to delete copied file ${copiedFile.targetName}:`,
|
||||
error,
|
||||
@@ -1092,7 +1092,7 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
|
||||
currentHost?.userId?.toString(),
|
||||
);
|
||||
successCount++;
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
console.error(
|
||||
`Failed to move back file ${movedFile.targetName}:`,
|
||||
error,
|
||||
@@ -1132,7 +1132,7 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
|
||||
}
|
||||
|
||||
handleRefreshDirectory();
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
toast.error(
|
||||
`${t("fileManager.undoOperationFailed")}: ${error.message || t("fileManager.unknownError")}`,
|
||||
);
|
||||
@@ -1204,7 +1204,7 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
|
||||
|
||||
setCreateIntent(null);
|
||||
handleRefreshDirectory();
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
console.error("Create failed:", error);
|
||||
toast.error(t("fileManager.failedToCreateItem"));
|
||||
}
|
||||
@@ -1233,7 +1233,7 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
|
||||
);
|
||||
setEditingFile(null);
|
||||
handleRefreshDirectory();
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
console.error("Rename failed:", error);
|
||||
toast.error(t("fileManager.failedToRenameItem"));
|
||||
}
|
||||
@@ -1269,11 +1269,11 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
|
||||
clearSelection();
|
||||
initialLoadDoneRef.current = true;
|
||||
toast.success(t("fileManager.connectedSuccessfully"));
|
||||
} catch (dirError: any) {
|
||||
} catch (dirError: unknown) {
|
||||
console.error("Failed to load initial directory:", dirError);
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
console.error("TOTP verification failed:", error);
|
||||
toast.error(t("fileManager.totpVerificationFailed"));
|
||||
} finally {
|
||||
@@ -1340,7 +1340,7 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
|
||||
movedItems.push(file.name);
|
||||
successCount++;
|
||||
}
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
console.error(`Failed to move file ${file.name}:`, error);
|
||||
toast.error(
|
||||
t("fileManager.moveFileFailed", { name: file.name }) +
|
||||
@@ -1388,7 +1388,7 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
|
||||
handleRefreshDirectory();
|
||||
clearSelection();
|
||||
}
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
console.error("Drag move operation failed:", error);
|
||||
toast.error(t("fileManager.moveOperationFailed") + ": " + error.message);
|
||||
}
|
||||
@@ -1459,7 +1459,7 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
|
||||
await dragToDesktop.dragFilesToDesktop(files);
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
console.error("Drag to desktop failed:", error);
|
||||
toast.error(
|
||||
t("fileManager.dragFailed") +
|
||||
@@ -1554,7 +1554,9 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
|
||||
|
||||
try {
|
||||
const pinnedData = await getPinnedFiles(currentHost.id);
|
||||
const pinnedPaths = new Set(pinnedData.map((item: any) => item.path));
|
||||
const pinnedPaths = new Set(
|
||||
pinnedData.map((item: Record<string, unknown>) => item.path),
|
||||
);
|
||||
setPinnedFiles(pinnedPaths);
|
||||
} catch (error) {
|
||||
console.error("Failed to load pinned files:", error);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -18,6 +18,21 @@ import {
|
||||
} from "lucide-react";
|
||||
import { Button } from "@/components/ui/button.tsx";
|
||||
|
||||
interface TabData {
|
||||
id: number;
|
||||
type: string;
|
||||
title: string;
|
||||
terminalRef?: {
|
||||
current?: {
|
||||
fit?: () => void;
|
||||
notifyResize?: () => void;
|
||||
refresh?: () => void;
|
||||
};
|
||||
};
|
||||
hostConfig?: unknown;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
interface TerminalViewProps {
|
||||
isTopbarOpen?: boolean;
|
||||
}
|
||||
@@ -25,11 +40,16 @@ interface TerminalViewProps {
|
||||
export function AppView({
|
||||
isTopbarOpen = true,
|
||||
}: TerminalViewProps): React.ReactElement {
|
||||
const { tabs, currentTab, allSplitScreenTab, removeTab } = useTabs() as any;
|
||||
const { tabs, currentTab, allSplitScreenTab, removeTab } = useTabs() as {
|
||||
tabs: TabData[];
|
||||
currentTab: number;
|
||||
allSplitScreenTab: number[];
|
||||
removeTab: (id: number) => void;
|
||||
};
|
||||
const { state: sidebarState } = useSidebar();
|
||||
|
||||
const terminalTabs = tabs.filter(
|
||||
(tab: any) =>
|
||||
(tab: TabData) =>
|
||||
tab.type === "terminal" ||
|
||||
tab.type === "server" ||
|
||||
tab.type === "file_manager",
|
||||
@@ -59,7 +79,7 @@ export function AppView({
|
||||
const splitIds = allSplitScreenTab as number[];
|
||||
visibleIds.push(currentTab, ...splitIds.filter((i) => i !== currentTab));
|
||||
}
|
||||
terminalTabs.forEach((t: any) => {
|
||||
terminalTabs.forEach((t: TabData) => {
|
||||
if (visibleIds.includes(t.id)) {
|
||||
const ref = t.terminalRef?.current;
|
||||
if (ref?.fit) ref.fit();
|
||||
@@ -125,16 +145,16 @@ export function AppView({
|
||||
|
||||
const renderTerminalsLayer = () => {
|
||||
const styles: Record<number, React.CSSProperties> = {};
|
||||
const splitTabs = terminalTabs.filter((tab: any) =>
|
||||
const splitTabs = terminalTabs.filter((tab: TabData) =>
|
||||
allSplitScreenTab.includes(tab.id),
|
||||
);
|
||||
const mainTab = terminalTabs.find((tab: any) => tab.id === currentTab);
|
||||
const mainTab = terminalTabs.find((tab: TabData) => tab.id === currentTab);
|
||||
const layoutTabs = [
|
||||
mainTab,
|
||||
...splitTabs.filter(
|
||||
(t: any) => t && t.id !== (mainTab && (mainTab as any).id),
|
||||
(t: TabData) => t && t.id !== (mainTab && (mainTab as TabData).id),
|
||||
),
|
||||
].filter(Boolean) as any[];
|
||||
].filter((t): t is TabData => t !== null && t !== undefined);
|
||||
|
||||
if (allSplitScreenTab.length === 0 && mainTab) {
|
||||
const isFileManagerTab = mainTab.type === "file_manager";
|
||||
@@ -150,7 +170,7 @@ export function AppView({
|
||||
opacity: ready ? 1 : 0,
|
||||
};
|
||||
} else {
|
||||
layoutTabs.forEach((t: any) => {
|
||||
layoutTabs.forEach((t: TabData) => {
|
||||
const rect = panelRects[String(t.id)];
|
||||
const parentRect = containerRef.current?.getBoundingClientRect();
|
||||
if (rect && parentRect) {
|
||||
@@ -171,7 +191,7 @@ export function AppView({
|
||||
|
||||
return (
|
||||
<div className="absolute inset-0 z-[1]">
|
||||
{terminalTabs.map((t: any) => {
|
||||
{terminalTabs.map((t: TabData) => {
|
||||
const hasStyle = !!styles[t.id];
|
||||
const isVisible =
|
||||
hasStyle || (allSplitScreenTab.length === 0 && t.id === currentTab);
|
||||
@@ -241,16 +261,16 @@ export function AppView({
|
||||
};
|
||||
|
||||
const renderSplitOverlays = () => {
|
||||
const splitTabs = terminalTabs.filter((tab: any) =>
|
||||
const splitTabs = terminalTabs.filter((tab: TabData) =>
|
||||
allSplitScreenTab.includes(tab.id),
|
||||
);
|
||||
const mainTab = terminalTabs.find((tab: any) => tab.id === currentTab);
|
||||
const mainTab = terminalTabs.find((tab: TabData) => tab.id === currentTab);
|
||||
const layoutTabs = [
|
||||
mainTab,
|
||||
...splitTabs.filter(
|
||||
(t: any) => t && t.id !== (mainTab && (mainTab as any).id),
|
||||
(t: TabData) => t && t.id !== (mainTab && (mainTab as TabData).id),
|
||||
),
|
||||
].filter(Boolean) as any[];
|
||||
].filter((t): t is TabData => t !== null && t !== undefined);
|
||||
if (allSplitScreenTab.length === 0) return null;
|
||||
|
||||
const handleStyle = {
|
||||
@@ -258,13 +278,16 @@ export function AppView({
|
||||
zIndex: 12,
|
||||
background: "var(--color-dark-border)",
|
||||
} as React.CSSProperties;
|
||||
const commonGroupProps = {
|
||||
const commonGroupProps: {
|
||||
onLayout: () => void;
|
||||
onResize: () => void;
|
||||
} = {
|
||||
onLayout: scheduleMeasureAndFit,
|
||||
onResize: scheduleMeasureAndFit,
|
||||
} as any;
|
||||
};
|
||||
|
||||
if (layoutTabs.length === 2) {
|
||||
const [a, b] = layoutTabs as any[];
|
||||
const [a, b] = layoutTabs;
|
||||
return (
|
||||
<div className="absolute inset-0 z-[10] pointer-events-none">
|
||||
<ResizablePrimitive.PanelGroup
|
||||
@@ -316,7 +339,7 @@ export function AppView({
|
||||
);
|
||||
}
|
||||
if (layoutTabs.length === 3) {
|
||||
const [a, b, c] = layoutTabs as any[];
|
||||
const [a, b, c] = layoutTabs;
|
||||
return (
|
||||
<div className="absolute inset-0 z-[10] pointer-events-none">
|
||||
<ResizablePrimitive.PanelGroup
|
||||
@@ -404,7 +427,7 @@ export function AppView({
|
||||
);
|
||||
}
|
||||
if (layoutTabs.length === 4) {
|
||||
const [a, b, c, d] = layoutTabs as any[];
|
||||
const [a, b, c, d] = layoutTabs;
|
||||
return (
|
||||
<div className="absolute inset-0 z-[10] pointer-events-none">
|
||||
<ResizablePrimitive.PanelGroup
|
||||
@@ -529,7 +552,7 @@ export function AppView({
|
||||
return null;
|
||||
};
|
||||
|
||||
const currentTabData = tabs.find((tab: any) => tab.id === currentTab);
|
||||
const currentTabData = tabs.find((tab: TabData) => tab.id === currentTab);
|
||||
const isFileManager = currentTabData?.type === "file_manager";
|
||||
const isSplitScreen = allSplitScreenTab.length > 0;
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ interface SSHHost {
|
||||
enableTunnel: boolean;
|
||||
enableFileManager: boolean;
|
||||
defaultPath: string;
|
||||
tunnelConnections: any[];
|
||||
tunnelConnections: unknown[];
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
@@ -112,13 +112,19 @@ export function LeftSidebar({
|
||||
setCurrentTab,
|
||||
allSplitScreenTab,
|
||||
updateHostConfig,
|
||||
} = useTabs() as any;
|
||||
} = useTabs() as {
|
||||
tabs: Array<{ id: number; type: string; [key: string]: unknown }>;
|
||||
addTab: (tab: { type: string; [key: string]: unknown }) => number;
|
||||
setCurrentTab: (id: number) => void;
|
||||
allSplitScreenTab: number[];
|
||||
updateHostConfig: (id: number, config: unknown) => void;
|
||||
};
|
||||
const isSplitScreenActive =
|
||||
Array.isArray(allSplitScreenTab) && allSplitScreenTab.length > 0;
|
||||
const sshManagerTab = tabList.find((t) => t.type === "ssh_manager");
|
||||
const openSshManagerTab = () => {
|
||||
if (sshManagerTab || isSplitScreenActive) return;
|
||||
const id = addTab({ type: "ssh_manager" } as any);
|
||||
const id = addTab({ type: "ssh_manager" });
|
||||
setCurrentTab(id);
|
||||
};
|
||||
const adminTab = tabList.find((t) => t.type === "admin");
|
||||
@@ -128,7 +134,7 @@ export function LeftSidebar({
|
||||
setCurrentTab(adminTab.id);
|
||||
return;
|
||||
}
|
||||
const id = addTab({ type: "admin" } as any);
|
||||
const id = addTab({ type: "admin" });
|
||||
setCurrentTab(id);
|
||||
};
|
||||
const userProfileTab = tabList.find((t) => t.type === "user_profile");
|
||||
@@ -138,7 +144,7 @@ export function LeftSidebar({
|
||||
setCurrentTab(userProfileTab.id);
|
||||
return;
|
||||
}
|
||||
const id = addTab({ type: "user_profile" } as any);
|
||||
const id = addTab({ type: "user_profile" });
|
||||
setCurrentTab(id);
|
||||
};
|
||||
|
||||
@@ -206,7 +212,7 @@ export function LeftSidebar({
|
||||
});
|
||||
}, 50);
|
||||
}
|
||||
} catch (err: any) {
|
||||
} catch (err: unknown) {
|
||||
setHostsError(t("leftSidebar.failedToLoadHosts"));
|
||||
}
|
||||
}, [updateHostConfig]);
|
||||
@@ -319,9 +325,10 @@ export function LeftSidebar({
|
||||
await deleteAccount(deletePassword);
|
||||
|
||||
handleLogout();
|
||||
} catch (err: any) {
|
||||
} catch (err: unknown) {
|
||||
setDeleteError(
|
||||
err?.response?.data?.error || t("leftSidebar.failedToDeleteAccount"),
|
||||
(err as { response?: { data?: { error?: string } } })?.response?.data
|
||||
?.error || t("leftSidebar.failedToDeleteAccount"),
|
||||
);
|
||||
setDeleteLoading(false);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,18 @@ import { TabDropdown } from "@/ui/Desktop/Navigation/Tabs/TabDropdown.tsx";
|
||||
import { getCookie, setCookie } from "@/ui/main-axios.ts";
|
||||
import { SnippetsSidebar } from "@/ui/Desktop/Apps/Terminal/SnippetsSidebar.tsx";
|
||||
|
||||
interface TabData {
|
||||
id: number;
|
||||
type: string;
|
||||
title: string;
|
||||
terminalRef?: {
|
||||
current?: {
|
||||
sendInput?: (data: string) => void;
|
||||
};
|
||||
};
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
interface TopNavbarProps {
|
||||
isTopbarOpen: boolean;
|
||||
setIsTopbarOpen: (open: boolean) => void;
|
||||
@@ -35,7 +47,14 @@ export function TopNavbar({
|
||||
setSplitScreenTab,
|
||||
removeTab,
|
||||
allSplitScreenTab,
|
||||
} = useTabs() as any;
|
||||
} = useTabs() as {
|
||||
tabs: TabData[];
|
||||
currentTab: number;
|
||||
setCurrentTab: (id: number) => void;
|
||||
setSplitScreenTab: (id: number) => void;
|
||||
removeTab: (id: number) => void;
|
||||
allSplitScreenTab: number[];
|
||||
};
|
||||
const leftPosition = state === "collapsed" ? "26px" : "264px";
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -192,7 +211,7 @@ export function TopNavbar({
|
||||
|
||||
if (commandToSend) {
|
||||
selectedTabIds.forEach((tabId) => {
|
||||
const tab = tabs.find((t: any) => t.id === tabId);
|
||||
const tab = tabs.find((t: TabData) => t.id === tabId);
|
||||
if (tab?.terminalRef?.current?.sendInput) {
|
||||
tab.terminalRef.current.sendInput(commandToSend);
|
||||
}
|
||||
@@ -206,7 +225,7 @@ export function TopNavbar({
|
||||
if (e.key.length === 1 && !e.ctrlKey && !e.metaKey) {
|
||||
const char = e.key;
|
||||
selectedTabIds.forEach((tabId) => {
|
||||
const tab = tabs.find((t: any) => t.id === tabId);
|
||||
const tab = tabs.find((t: TabData) => t.id === tabId);
|
||||
if (tab?.terminalRef?.current?.sendInput) {
|
||||
tab.terminalRef.current.sendInput(char);
|
||||
}
|
||||
@@ -215,7 +234,7 @@ export function TopNavbar({
|
||||
};
|
||||
|
||||
const handleSnippetExecute = (content: string) => {
|
||||
const tab = tabs.find((t: any) => t.id === currentTab);
|
||||
const tab = tabs.find((t: TabData) => t.id === currentTab);
|
||||
if (tab?.terminalRef?.current?.sendInput) {
|
||||
tab.terminalRef.current.sendInput(content + "\n");
|
||||
}
|
||||
@@ -223,13 +242,13 @@ export function TopNavbar({
|
||||
|
||||
const isSplitScreenActive =
|
||||
Array.isArray(allSplitScreenTab) && allSplitScreenTab.length > 0;
|
||||
const currentTabObj = tabs.find((t: any) => t.id === currentTab);
|
||||
const currentTabObj = tabs.find((t: TabData) => t.id === currentTab);
|
||||
const currentTabIsHome = currentTabObj?.type === "home";
|
||||
const currentTabIsSshManager = currentTabObj?.type === "ssh_manager";
|
||||
const currentTabIsAdmin = currentTabObj?.type === "admin";
|
||||
const currentTabIsUserProfile = currentTabObj?.type === "user_profile";
|
||||
|
||||
const terminalTabs = tabs.filter((tab: any) => tab.type === "terminal");
|
||||
const terminalTabs = tabs.filter((tab: TabData) => tab.type === "terminal");
|
||||
|
||||
const updateRightClickCopyPaste = (checked: boolean) => {
|
||||
setCookie("rightClickCopyPaste", checked.toString());
|
||||
@@ -246,7 +265,7 @@ export function TopNavbar({
|
||||
}}
|
||||
>
|
||||
<div className="h-full p-1 pr-2 border-r-2 border-dark-border w-[calc(100%-6rem)] flex items-center overflow-x-auto overflow-y-hidden gap-2 thin-scrollbar">
|
||||
{tabs.map((tab: any) => {
|
||||
{tabs.map((tab: TabData) => {
|
||||
const isActive = tab.id === currentTab;
|
||||
const isSplit =
|
||||
Array.isArray(allSplitScreenTab) &&
|
||||
|
||||
@@ -14,379 +14,412 @@ import { useTranslation } from "react-i18next";
|
||||
import { isElectron, getCookie } from "@/ui/main-axios.ts";
|
||||
import { toast } from "sonner";
|
||||
|
||||
interface HostConfig {
|
||||
id?: number;
|
||||
ip: string;
|
||||
port: number;
|
||||
username: string;
|
||||
password?: string;
|
||||
key?: string;
|
||||
keyPassword?: string;
|
||||
keyType?: string;
|
||||
authType?: string;
|
||||
credentialId?: number;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
interface TerminalHandle {
|
||||
disconnect: () => void;
|
||||
fit: () => void;
|
||||
sendInput: (data: string) => void;
|
||||
notifyResize: () => void;
|
||||
refresh: () => void;
|
||||
}
|
||||
|
||||
interface SSHTerminalProps {
|
||||
hostConfig: any;
|
||||
hostConfig: HostConfig;
|
||||
isVisible: boolean;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export const Terminal = forwardRef<any, SSHTerminalProps>(function SSHTerminal(
|
||||
{ hostConfig, isVisible },
|
||||
ref,
|
||||
) {
|
||||
const { t } = useTranslation();
|
||||
const { instance: terminal, ref: xtermRef } = useXTerm();
|
||||
const fitAddonRef = useRef<FitAddon | null>(null);
|
||||
const webSocketRef = useRef<WebSocket | null>(null);
|
||||
const resizeTimeout = useRef<NodeJS.Timeout | null>(null);
|
||||
const wasDisconnectedBySSH = useRef(false);
|
||||
const pingIntervalRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [isConnected, setIsConnected] = useState(false);
|
||||
const [isConnecting, setIsConnecting] = useState(false);
|
||||
const [connectionError, setConnectionError] = useState<string | null>(null);
|
||||
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
||||
const isVisibleRef = useRef<boolean>(false);
|
||||
const isConnectingRef = useRef(false);
|
||||
export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
|
||||
function SSHTerminal({ hostConfig, isVisible }, ref) {
|
||||
const { t } = useTranslation();
|
||||
const { instance: terminal, ref: xtermRef } = useXTerm();
|
||||
const fitAddonRef = useRef<FitAddon | null>(null);
|
||||
const webSocketRef = useRef<WebSocket | null>(null);
|
||||
const resizeTimeout = useRef<NodeJS.Timeout | null>(null);
|
||||
const wasDisconnectedBySSH = useRef(false);
|
||||
const pingIntervalRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [isConnected, setIsConnected] = useState(false);
|
||||
const [isConnecting, setIsConnecting] = useState(false);
|
||||
const [connectionError, setConnectionError] = useState<string | null>(null);
|
||||
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
||||
const isVisibleRef = useRef<boolean>(false);
|
||||
const isConnectingRef = useRef(false);
|
||||
|
||||
const lastSentSizeRef = useRef<{ cols: number; rows: number } | null>(null);
|
||||
const pendingSizeRef = useRef<{ cols: number; rows: number } | null>(null);
|
||||
const notifyTimerRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const DEBOUNCE_MS = 140;
|
||||
const lastSentSizeRef = useRef<{ cols: number; rows: number } | null>(null);
|
||||
const pendingSizeRef = useRef<{ cols: number; rows: number } | null>(null);
|
||||
const notifyTimerRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const DEBOUNCE_MS = 140;
|
||||
|
||||
useEffect(() => {
|
||||
isVisibleRef.current = isVisible;
|
||||
}, [isVisible]);
|
||||
useEffect(() => {
|
||||
isVisibleRef.current = isVisible;
|
||||
}, [isVisible]);
|
||||
|
||||
useEffect(() => {
|
||||
const checkAuth = () => {
|
||||
const jwtToken = getCookie("jwt");
|
||||
const isAuth = !!(jwtToken && jwtToken.trim() !== "");
|
||||
useEffect(() => {
|
||||
const checkAuth = () => {
|
||||
const jwtToken = getCookie("jwt");
|
||||
const isAuth = !!(jwtToken && jwtToken.trim() !== "");
|
||||
|
||||
setIsAuthenticated((prev) => {
|
||||
if (prev !== isAuth) {
|
||||
return isAuth;
|
||||
setIsAuthenticated((prev) => {
|
||||
if (prev !== isAuth) {
|
||||
return isAuth;
|
||||
}
|
||||
return prev;
|
||||
});
|
||||
};
|
||||
|
||||
checkAuth();
|
||||
|
||||
const authCheckInterval = setInterval(checkAuth, 5000);
|
||||
|
||||
return () => clearInterval(authCheckInterval);
|
||||
}, []);
|
||||
|
||||
function hardRefresh() {
|
||||
try {
|
||||
if (
|
||||
terminal &&
|
||||
typeof (
|
||||
terminal as { refresh?: (start: number, end: number) => void }
|
||||
).refresh === "function"
|
||||
) {
|
||||
(
|
||||
terminal as { refresh?: (start: number, end: number) => void }
|
||||
).refresh(0, terminal.rows - 1);
|
||||
}
|
||||
return prev;
|
||||
});
|
||||
};
|
||||
|
||||
checkAuth();
|
||||
|
||||
const authCheckInterval = setInterval(checkAuth, 5000);
|
||||
|
||||
return () => clearInterval(authCheckInterval);
|
||||
}, []);
|
||||
|
||||
function hardRefresh() {
|
||||
try {
|
||||
if (terminal && typeof (terminal as any).refresh === "function") {
|
||||
(terminal as any).refresh(0, terminal.rows - 1);
|
||||
} catch {
|
||||
// Ignore terminal refresh errors
|
||||
}
|
||||
} catch {
|
||||
// Ignore terminal refresh errors
|
||||
}
|
||||
}
|
||||
|
||||
function scheduleNotify(cols: number, rows: number) {
|
||||
if (!(cols > 0 && rows > 0)) return;
|
||||
pendingSizeRef.current = { cols, rows };
|
||||
if (notifyTimerRef.current) clearTimeout(notifyTimerRef.current);
|
||||
notifyTimerRef.current = setTimeout(() => {
|
||||
const next = pendingSizeRef.current;
|
||||
const last = lastSentSizeRef.current;
|
||||
if (!next) return;
|
||||
if (last && last.cols === next.cols && last.rows === next.rows) return;
|
||||
if (webSocketRef.current?.readyState === WebSocket.OPEN) {
|
||||
webSocketRef.current.send(
|
||||
JSON.stringify({ type: "resize", data: next }),
|
||||
function scheduleNotify(cols: number, rows: number) {
|
||||
if (!(cols > 0 && rows > 0)) return;
|
||||
pendingSizeRef.current = { cols, rows };
|
||||
if (notifyTimerRef.current) clearTimeout(notifyTimerRef.current);
|
||||
notifyTimerRef.current = setTimeout(() => {
|
||||
const next = pendingSizeRef.current;
|
||||
const last = lastSentSizeRef.current;
|
||||
if (!next) return;
|
||||
if (last && last.cols === next.cols && last.rows === next.rows) return;
|
||||
if (webSocketRef.current?.readyState === WebSocket.OPEN) {
|
||||
webSocketRef.current.send(
|
||||
JSON.stringify({ type: "resize", data: next }),
|
||||
);
|
||||
lastSentSizeRef.current = next;
|
||||
}
|
||||
}, DEBOUNCE_MS);
|
||||
}
|
||||
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
() => ({
|
||||
disconnect: () => {
|
||||
if (pingIntervalRef.current) {
|
||||
clearInterval(pingIntervalRef.current);
|
||||
pingIntervalRef.current = null;
|
||||
}
|
||||
webSocketRef.current?.close();
|
||||
},
|
||||
fit: () => {
|
||||
fitAddonRef.current?.fit();
|
||||
if (terminal) scheduleNotify(terminal.cols, terminal.rows);
|
||||
hardRefresh();
|
||||
},
|
||||
sendInput: (data: string) => {
|
||||
if (webSocketRef.current?.readyState === 1) {
|
||||
webSocketRef.current.send(JSON.stringify({ type: "input", data }));
|
||||
}
|
||||
},
|
||||
notifyResize: () => {
|
||||
try {
|
||||
const cols = terminal?.cols ?? undefined;
|
||||
const rows = terminal?.rows ?? undefined;
|
||||
if (typeof cols === "number" && typeof rows === "number") {
|
||||
scheduleNotify(cols, rows);
|
||||
hardRefresh();
|
||||
}
|
||||
} catch {
|
||||
// Ignore resize notification errors
|
||||
}
|
||||
},
|
||||
refresh: () => hardRefresh(),
|
||||
}),
|
||||
[terminal],
|
||||
);
|
||||
|
||||
function handleWindowResize() {
|
||||
if (!isVisibleRef.current) return;
|
||||
fitAddonRef.current?.fit();
|
||||
if (terminal) scheduleNotify(terminal.cols, terminal.rows);
|
||||
hardRefresh();
|
||||
}
|
||||
|
||||
function setupWebSocketListeners(
|
||||
ws: WebSocket,
|
||||
cols: number,
|
||||
rows: number,
|
||||
) {
|
||||
ws.addEventListener("open", () => {
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
type: "connectToHost",
|
||||
data: { cols, rows, hostConfig },
|
||||
}),
|
||||
);
|
||||
lastSentSizeRef.current = next;
|
||||
}
|
||||
}, DEBOUNCE_MS);
|
||||
}
|
||||
terminal.onData((data) => {
|
||||
ws.send(JSON.stringify({ type: "input", data }));
|
||||
});
|
||||
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
() => ({
|
||||
disconnect: () => {
|
||||
pingIntervalRef.current = setInterval(() => {
|
||||
if (ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(JSON.stringify({ type: "ping" }));
|
||||
}
|
||||
}, 30000);
|
||||
});
|
||||
|
||||
ws.addEventListener("message", (event) => {
|
||||
try {
|
||||
const msg = JSON.parse(event.data);
|
||||
if (msg.type === "data") {
|
||||
if (typeof msg.data === "string") {
|
||||
terminal.write(msg.data);
|
||||
} else {
|
||||
terminal.write(String(msg.data));
|
||||
}
|
||||
} else if (msg.type === "error")
|
||||
terminal.writeln(`\r\n[${t("terminal.error")}] ${msg.message}`);
|
||||
else if (msg.type === "connected") {
|
||||
isConnectingRef.current = false;
|
||||
} else if (msg.type === "disconnected") {
|
||||
wasDisconnectedBySSH.current = true;
|
||||
isConnectingRef.current = false;
|
||||
terminal.writeln(
|
||||
`\r\n[${msg.message || t("terminal.disconnected")}]`,
|
||||
);
|
||||
}
|
||||
} catch {
|
||||
// Ignore message parsing errors
|
||||
}
|
||||
});
|
||||
|
||||
ws.addEventListener("close", (event) => {
|
||||
isConnectingRef.current = false;
|
||||
|
||||
if (event.code === 1008) {
|
||||
console.error("WebSocket authentication failed:", event.reason);
|
||||
terminal.writeln(`\r\n[Authentication failed - please re-login]`);
|
||||
|
||||
localStorage.removeItem("jwt");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!wasDisconnectedBySSH.current) {
|
||||
terminal.writeln(`\r\n[${t("terminal.connectionClosed")}]`);
|
||||
}
|
||||
});
|
||||
|
||||
ws.addEventListener("error", () => {
|
||||
isConnectingRef.current = false;
|
||||
terminal.writeln(`\r\n[${t("terminal.connectionError")}]`);
|
||||
});
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!terminal || !xtermRef.current || !hostConfig) return;
|
||||
|
||||
if (!isAuthenticated) {
|
||||
return;
|
||||
}
|
||||
|
||||
terminal.options = {
|
||||
cursorBlink: false,
|
||||
cursorStyle: "bar",
|
||||
scrollback: 10000,
|
||||
fontSize: 14,
|
||||
fontFamily:
|
||||
'"Caskaydia Cove Nerd Font Mono", "SF Mono", Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
|
||||
theme: { background: "#09090b", foreground: "#f7f7f7" },
|
||||
allowTransparency: true,
|
||||
convertEol: true,
|
||||
windowsMode: false,
|
||||
macOptionIsMeta: false,
|
||||
macOptionClickForcesSelection: false,
|
||||
rightClickSelectsWord: false,
|
||||
fastScrollModifier: "alt",
|
||||
fastScrollSensitivity: 5,
|
||||
allowProposedApi: true,
|
||||
disableStdin: true,
|
||||
cursorInactiveStyle: "bar",
|
||||
minimumContrastRatio: 1,
|
||||
letterSpacing: 0,
|
||||
lineHeight: 1.2,
|
||||
};
|
||||
|
||||
const fitAddon = new FitAddon();
|
||||
const clipboardAddon = new ClipboardAddon();
|
||||
const unicode11Addon = new Unicode11Addon();
|
||||
const webLinksAddon = new WebLinksAddon();
|
||||
|
||||
fitAddonRef.current = fitAddon;
|
||||
terminal.loadAddon(fitAddon);
|
||||
terminal.loadAddon(clipboardAddon);
|
||||
terminal.loadAddon(unicode11Addon);
|
||||
terminal.loadAddon(webLinksAddon);
|
||||
|
||||
terminal.unicode.activeVersion = "11";
|
||||
|
||||
terminal.open(xtermRef.current);
|
||||
|
||||
const textarea = xtermRef.current.querySelector(
|
||||
".xterm-helper-textarea",
|
||||
) as HTMLTextAreaElement | null;
|
||||
if (textarea) {
|
||||
textarea.readOnly = true;
|
||||
textarea.blur();
|
||||
}
|
||||
|
||||
terminal.focus = () => {};
|
||||
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
if (resizeTimeout.current) clearTimeout(resizeTimeout.current);
|
||||
resizeTimeout.current = setTimeout(() => {
|
||||
if (!isVisibleRef.current) return;
|
||||
fitAddonRef.current?.fit();
|
||||
if (terminal) scheduleNotify(terminal.cols, terminal.rows);
|
||||
hardRefresh();
|
||||
}, 150);
|
||||
});
|
||||
|
||||
resizeObserver.observe(xtermRef.current);
|
||||
|
||||
const readyFonts =
|
||||
(document as { fonts?: { ready?: Promise<unknown> } }).fonts
|
||||
?.ready instanceof Promise
|
||||
? (document as { fonts?: { ready?: Promise<unknown> } }).fonts.ready
|
||||
: Promise.resolve();
|
||||
setVisible(true);
|
||||
|
||||
readyFonts.then(() => {
|
||||
setTimeout(() => {
|
||||
fitAddon.fit();
|
||||
if (terminal) scheduleNotify(terminal.cols, terminal.rows);
|
||||
hardRefresh();
|
||||
|
||||
const jwtToken = getCookie("jwt");
|
||||
if (!jwtToken || jwtToken.trim() === "") {
|
||||
setIsConnected(false);
|
||||
setIsConnecting(false);
|
||||
setConnectionError("Authentication required");
|
||||
return;
|
||||
}
|
||||
|
||||
const cols = terminal.cols;
|
||||
const rows = terminal.rows;
|
||||
|
||||
const isDev =
|
||||
process.env.NODE_ENV === "development" &&
|
||||
(window.location.port === "3000" ||
|
||||
window.location.port === "5173" ||
|
||||
window.location.port === "");
|
||||
|
||||
const baseWsUrl = isDev
|
||||
? `${window.location.protocol === "https:" ? "wss" : "ws"}://localhost:30002`
|
||||
: isElectron()
|
||||
? (() => {
|
||||
const baseUrl =
|
||||
(window as { configuredServerUrl?: string })
|
||||
.configuredServerUrl || "http://127.0.0.1:30001";
|
||||
const wsProtocol = baseUrl.startsWith("https://")
|
||||
? "wss://"
|
||||
: "ws://";
|
||||
const wsHost = baseUrl.replace(/^https?:\/\//, "");
|
||||
return `${wsProtocol}${wsHost}/ssh/websocket/`;
|
||||
})()
|
||||
: `${window.location.protocol === "https:" ? "wss" : "ws"}://${window.location.host}/ssh/websocket/`;
|
||||
|
||||
if (isConnectingRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
isConnectingRef.current = true;
|
||||
|
||||
if (
|
||||
webSocketRef.current &&
|
||||
webSocketRef.current.readyState !== WebSocket.CLOSED
|
||||
) {
|
||||
webSocketRef.current.close();
|
||||
}
|
||||
|
||||
if (pingIntervalRef.current) {
|
||||
clearInterval(pingIntervalRef.current);
|
||||
pingIntervalRef.current = null;
|
||||
}
|
||||
|
||||
const wsUrl = `${baseWsUrl}?token=${encodeURIComponent(jwtToken)}`;
|
||||
|
||||
setIsConnecting(true);
|
||||
setConnectionError(null);
|
||||
|
||||
const ws = new WebSocket(wsUrl);
|
||||
webSocketRef.current = ws;
|
||||
wasDisconnectedBySSH.current = false;
|
||||
|
||||
setupWebSocketListeners(ws, cols, rows);
|
||||
}, 200);
|
||||
});
|
||||
|
||||
return () => {
|
||||
resizeObserver.disconnect();
|
||||
if (notifyTimerRef.current) clearTimeout(notifyTimerRef.current);
|
||||
if (resizeTimeout.current) clearTimeout(resizeTimeout.current);
|
||||
if (pingIntervalRef.current) {
|
||||
clearInterval(pingIntervalRef.current);
|
||||
pingIntervalRef.current = null;
|
||||
}
|
||||
webSocketRef.current?.close();
|
||||
},
|
||||
fit: () => {
|
||||
fitAddonRef.current?.fit();
|
||||
if (terminal) scheduleNotify(terminal.cols, terminal.rows);
|
||||
hardRefresh();
|
||||
},
|
||||
sendInput: (data: string) => {
|
||||
if (webSocketRef.current?.readyState === 1) {
|
||||
webSocketRef.current.send(JSON.stringify({ type: "input", data }));
|
||||
}
|
||||
},
|
||||
notifyResize: () => {
|
||||
try {
|
||||
const cols = terminal?.cols ?? undefined;
|
||||
const rows = terminal?.rows ?? undefined;
|
||||
if (typeof cols === "number" && typeof rows === "number") {
|
||||
scheduleNotify(cols, rows);
|
||||
hardRefresh();
|
||||
}
|
||||
} catch {
|
||||
// Ignore resize notification errors
|
||||
}
|
||||
},
|
||||
refresh: () => hardRefresh(),
|
||||
}),
|
||||
[terminal],
|
||||
);
|
||||
};
|
||||
}, [xtermRef, terminal, hostConfig]);
|
||||
|
||||
function handleWindowResize() {
|
||||
if (!isVisibleRef.current) return;
|
||||
fitAddonRef.current?.fit();
|
||||
if (terminal) scheduleNotify(terminal.cols, terminal.rows);
|
||||
hardRefresh();
|
||||
}
|
||||
|
||||
function setupWebSocketListeners(ws: WebSocket, cols: number, rows: number) {
|
||||
ws.addEventListener("open", () => {
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
type: "connectToHost",
|
||||
data: { cols, rows, hostConfig },
|
||||
}),
|
||||
);
|
||||
terminal.onData((data) => {
|
||||
ws.send(JSON.stringify({ type: "input", data }));
|
||||
});
|
||||
|
||||
pingIntervalRef.current = setInterval(() => {
|
||||
if (ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(JSON.stringify({ type: "ping" }));
|
||||
}
|
||||
}, 30000);
|
||||
});
|
||||
|
||||
ws.addEventListener("message", (event) => {
|
||||
try {
|
||||
const msg = JSON.parse(event.data);
|
||||
if (msg.type === "data") {
|
||||
if (typeof msg.data === "string") {
|
||||
terminal.write(msg.data);
|
||||
} else {
|
||||
terminal.write(String(msg.data));
|
||||
}
|
||||
} else if (msg.type === "error")
|
||||
terminal.writeln(`\r\n[${t("terminal.error")}] ${msg.message}`);
|
||||
else if (msg.type === "connected") {
|
||||
isConnectingRef.current = false;
|
||||
} else if (msg.type === "disconnected") {
|
||||
wasDisconnectedBySSH.current = true;
|
||||
isConnectingRef.current = false;
|
||||
terminal.writeln(
|
||||
`\r\n[${msg.message || t("terminal.disconnected")}]`,
|
||||
);
|
||||
}
|
||||
} catch {
|
||||
// Ignore message parsing errors
|
||||
useEffect(() => {
|
||||
if (isVisible && fitAddonRef.current) {
|
||||
setTimeout(() => {
|
||||
fitAddonRef.current?.fit();
|
||||
if (terminal) scheduleNotify(terminal.cols, terminal.rows);
|
||||
hardRefresh();
|
||||
}, 0);
|
||||
}
|
||||
});
|
||||
}, [isVisible, terminal]);
|
||||
|
||||
ws.addEventListener("close", (event) => {
|
||||
isConnectingRef.current = false;
|
||||
|
||||
if (event.code === 1008) {
|
||||
console.error("WebSocket authentication failed:", event.reason);
|
||||
terminal.writeln(`\r\n[Authentication failed - please re-login]`);
|
||||
|
||||
localStorage.removeItem("jwt");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!wasDisconnectedBySSH.current) {
|
||||
terminal.writeln(`\r\n[${t("terminal.connectionClosed")}]`);
|
||||
}
|
||||
});
|
||||
|
||||
ws.addEventListener("error", () => {
|
||||
isConnectingRef.current = false;
|
||||
terminal.writeln(`\r\n[${t("terminal.connectionError")}]`);
|
||||
});
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!terminal || !xtermRef.current || !hostConfig) return;
|
||||
|
||||
if (!isAuthenticated) {
|
||||
return;
|
||||
}
|
||||
|
||||
terminal.options = {
|
||||
cursorBlink: false,
|
||||
cursorStyle: "bar",
|
||||
scrollback: 10000,
|
||||
fontSize: 14,
|
||||
fontFamily:
|
||||
'"Caskaydia Cove Nerd Font Mono", "SF Mono", Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
|
||||
theme: { background: "#09090b", foreground: "#f7f7f7" },
|
||||
allowTransparency: true,
|
||||
convertEol: true,
|
||||
windowsMode: false,
|
||||
macOptionIsMeta: false,
|
||||
macOptionClickForcesSelection: false,
|
||||
rightClickSelectsWord: false,
|
||||
fastScrollModifier: "alt",
|
||||
fastScrollSensitivity: 5,
|
||||
allowProposedApi: true,
|
||||
disableStdin: true,
|
||||
cursorInactiveStyle: "bar",
|
||||
minimumContrastRatio: 1,
|
||||
letterSpacing: 0,
|
||||
lineHeight: 1.2,
|
||||
};
|
||||
|
||||
const fitAddon = new FitAddon();
|
||||
const clipboardAddon = new ClipboardAddon();
|
||||
const unicode11Addon = new Unicode11Addon();
|
||||
const webLinksAddon = new WebLinksAddon();
|
||||
|
||||
fitAddonRef.current = fitAddon;
|
||||
terminal.loadAddon(fitAddon);
|
||||
terminal.loadAddon(clipboardAddon);
|
||||
terminal.loadAddon(unicode11Addon);
|
||||
terminal.loadAddon(webLinksAddon);
|
||||
|
||||
terminal.unicode.activeVersion = "11";
|
||||
|
||||
terminal.open(xtermRef.current);
|
||||
|
||||
const textarea = xtermRef.current.querySelector(
|
||||
".xterm-helper-textarea",
|
||||
) as HTMLTextAreaElement | null;
|
||||
if (textarea) {
|
||||
textarea.readOnly = true;
|
||||
textarea.blur();
|
||||
}
|
||||
|
||||
terminal.focus = () => {};
|
||||
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
if (resizeTimeout.current) clearTimeout(resizeTimeout.current);
|
||||
resizeTimeout.current = setTimeout(() => {
|
||||
if (!isVisibleRef.current) return;
|
||||
fitAddonRef.current?.fit();
|
||||
if (terminal) scheduleNotify(terminal.cols, terminal.rows);
|
||||
hardRefresh();
|
||||
}, 150);
|
||||
});
|
||||
|
||||
resizeObserver.observe(xtermRef.current);
|
||||
|
||||
const readyFonts =
|
||||
(document as any).fonts?.ready instanceof Promise
|
||||
? (document as any).fonts.ready
|
||||
: Promise.resolve();
|
||||
setVisible(true);
|
||||
|
||||
readyFonts.then(() => {
|
||||
setTimeout(() => {
|
||||
fitAddon.fit();
|
||||
if (terminal) scheduleNotify(terminal.cols, terminal.rows);
|
||||
hardRefresh();
|
||||
|
||||
const jwtToken = getCookie("jwt");
|
||||
if (!jwtToken || jwtToken.trim() === "") {
|
||||
setIsConnected(false);
|
||||
setIsConnecting(false);
|
||||
setConnectionError("Authentication required");
|
||||
return;
|
||||
}
|
||||
|
||||
const cols = terminal.cols;
|
||||
const rows = terminal.rows;
|
||||
|
||||
const isDev =
|
||||
process.env.NODE_ENV === "development" &&
|
||||
(window.location.port === "3000" ||
|
||||
window.location.port === "5173" ||
|
||||
window.location.port === "");
|
||||
|
||||
const baseWsUrl = isDev
|
||||
? `${window.location.protocol === "https:" ? "wss" : "ws"}://localhost:30002`
|
||||
: isElectron()
|
||||
? (() => {
|
||||
const baseUrl =
|
||||
(window as any).configuredServerUrl ||
|
||||
"http://127.0.0.1:30001";
|
||||
const wsProtocol = baseUrl.startsWith("https://")
|
||||
? "wss://"
|
||||
: "ws://";
|
||||
const wsHost = baseUrl.replace(/^https?:\/\//, "");
|
||||
return `${wsProtocol}${wsHost}/ssh/websocket/`;
|
||||
})()
|
||||
: `${window.location.protocol === "https:" ? "wss" : "ws"}://${window.location.host}/ssh/websocket/`;
|
||||
|
||||
if (isConnectingRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
isConnectingRef.current = true;
|
||||
|
||||
if (
|
||||
webSocketRef.current &&
|
||||
webSocketRef.current.readyState !== WebSocket.CLOSED
|
||||
) {
|
||||
webSocketRef.current.close();
|
||||
}
|
||||
|
||||
if (pingIntervalRef.current) {
|
||||
clearInterval(pingIntervalRef.current);
|
||||
pingIntervalRef.current = null;
|
||||
}
|
||||
|
||||
const wsUrl = `${baseWsUrl}?token=${encodeURIComponent(jwtToken)}`;
|
||||
|
||||
setIsConnecting(true);
|
||||
setConnectionError(null);
|
||||
|
||||
const ws = new WebSocket(wsUrl);
|
||||
webSocketRef.current = ws;
|
||||
wasDisconnectedBySSH.current = false;
|
||||
|
||||
setupWebSocketListeners(ws, cols, rows);
|
||||
}, 200);
|
||||
});
|
||||
|
||||
return () => {
|
||||
resizeObserver.disconnect();
|
||||
if (notifyTimerRef.current) clearTimeout(notifyTimerRef.current);
|
||||
if (resizeTimeout.current) clearTimeout(resizeTimeout.current);
|
||||
if (pingIntervalRef.current) {
|
||||
clearInterval(pingIntervalRef.current);
|
||||
pingIntervalRef.current = null;
|
||||
}
|
||||
webSocketRef.current?.close();
|
||||
};
|
||||
}, [xtermRef, terminal, hostConfig]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isVisible && fitAddonRef.current) {
|
||||
useEffect(() => {
|
||||
if (!fitAddonRef.current) return;
|
||||
setTimeout(() => {
|
||||
fitAddonRef.current?.fit();
|
||||
if (terminal) scheduleNotify(terminal.cols, terminal.rows);
|
||||
hardRefresh();
|
||||
}, 0);
|
||||
}
|
||||
}, [isVisible, terminal]);
|
||||
}, [isVisible, terminal]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!fitAddonRef.current) return;
|
||||
setTimeout(() => {
|
||||
fitAddonRef.current?.fit();
|
||||
if (terminal) scheduleNotify(terminal.cols, terminal.rows);
|
||||
hardRefresh();
|
||||
}, 0);
|
||||
}, [isVisible, terminal]);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={xtermRef}
|
||||
className={`h-full w-full m-1 transition-opacity duration-200 ${visible && isVisible ? "opacity-100" : "opacity-0"} overflow-hidden`}
|
||||
/>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<div
|
||||
ref={xtermRef}
|
||||
className={`h-full w-full m-1 transition-opacity duration-200 ${visible && isVisible ? "opacity-100" : "opacity-0"} overflow-hidden`}
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
const style = document.createElement("style");
|
||||
style.innerHTML = `
|
||||
|
||||
@@ -95,8 +95,22 @@ interface OIDCAuthorize {
|
||||
|
||||
export function isElectron(): boolean {
|
||||
return (
|
||||
(window as any).IS_ELECTRON === true ||
|
||||
(window as any).electronAPI?.isElectron === true
|
||||
(
|
||||
window as Window &
|
||||
typeof globalThis & {
|
||||
IS_ELECTRON?: boolean;
|
||||
electronAPI?: unknown;
|
||||
configuredServerUrl?: string;
|
||||
}
|
||||
).IS_ELECTRON === true ||
|
||||
(
|
||||
window as Window &
|
||||
typeof globalThis & {
|
||||
IS_ELECTRON?: boolean;
|
||||
electronAPI?: unknown;
|
||||
configuredServerUrl?: string;
|
||||
}
|
||||
).electronAPI?.isElectron === true
|
||||
);
|
||||
}
|
||||
|
||||
@@ -154,8 +168,8 @@ function createApiInstance(
|
||||
const startTime = performance.now();
|
||||
const requestId = `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
|
||||
(config as any).startTime = startTime;
|
||||
(config as any).requestId = requestId;
|
||||
(config as Record<string, unknown>).startTime = startTime;
|
||||
(config as Record<string, unknown>).requestId = requestId;
|
||||
|
||||
const method = config.method?.toUpperCase() || "UNKNOWN";
|
||||
const url = config.url || "UNKNOWN";
|
||||
@@ -189,8 +203,8 @@ function createApiInstance(
|
||||
instance.interceptors.response.use(
|
||||
(response) => {
|
||||
const endTime = performance.now();
|
||||
const startTime = (response.config as any).startTime;
|
||||
const requestId = (response.config as any).requestId;
|
||||
const startTime = (response.config as Record<string, unknown>).startTime;
|
||||
const requestId = (response.config as Record<string, unknown>).requestId;
|
||||
const responseTime = Math.round(endTime - startTime);
|
||||
|
||||
const method = response.config.method?.toUpperCase() || "UNKNOWN";
|
||||
@@ -227,8 +241,10 @@ function createApiInstance(
|
||||
},
|
||||
(error: AxiosError) => {
|
||||
const endTime = performance.now();
|
||||
const startTime = (error.config as any)?.startTime;
|
||||
const requestId = (error.config as any)?.requestId;
|
||||
const startTime = (error.config as Record<string, unknown> | undefined)
|
||||
?.startTime;
|
||||
const requestId = (error.config as Record<string, unknown> | undefined)
|
||||
?.requestId;
|
||||
const responseTime = startTime
|
||||
? Math.round(endTime - startTime)
|
||||
: undefined;
|
||||
@@ -238,10 +254,11 @@ function createApiInstance(
|
||||
const fullUrl = error.config ? `${error.config.baseURL}${url}` : url;
|
||||
const status = error.response?.status;
|
||||
const message =
|
||||
(error.response?.data as any)?.error ||
|
||||
(error.response?.data as Record<string, unknown>)?.error ||
|
||||
(error as Error).message ||
|
||||
"Unknown error";
|
||||
const errorCode = (error.response?.data as any)?.code || error.code;
|
||||
const errorCode =
|
||||
(error.response?.data as Record<string, unknown>)?.code || error.code;
|
||||
|
||||
const context: LogContext = {
|
||||
requestId,
|
||||
@@ -274,7 +291,8 @@ function createApiInstance(
|
||||
}
|
||||
|
||||
if (status === 401) {
|
||||
const errorCode = (error.response?.data as any)?.code;
|
||||
const errorCode = (error.response?.data as Record<string, unknown>)
|
||||
?.code;
|
||||
const isSessionExpired = errorCode === "SESSION_EXPIRED";
|
||||
|
||||
if (isElectron()) {
|
||||
@@ -337,9 +355,14 @@ export async function getServerConfig(): Promise<ServerConfig | null> {
|
||||
if (!isElectron()) return null;
|
||||
|
||||
try {
|
||||
const result = await (window as any).electronAPI?.invoke(
|
||||
"get-server-config",
|
||||
);
|
||||
const result = await (
|
||||
window as Window &
|
||||
typeof globalThis & {
|
||||
IS_ELECTRON?: boolean;
|
||||
electronAPI?: unknown;
|
||||
configuredServerUrl?: string;
|
||||
}
|
||||
).electronAPI?.invoke("get-server-config");
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error("Failed to get server config:", error);
|
||||
@@ -351,13 +374,24 @@ export async function saveServerConfig(config: ServerConfig): Promise<boolean> {
|
||||
if (!isElectron()) return false;
|
||||
|
||||
try {
|
||||
const result = await (window as any).electronAPI?.invoke(
|
||||
"save-server-config",
|
||||
config,
|
||||
);
|
||||
const result = await (
|
||||
window as Window &
|
||||
typeof globalThis & {
|
||||
IS_ELECTRON?: boolean;
|
||||
electronAPI?: unknown;
|
||||
configuredServerUrl?: string;
|
||||
}
|
||||
).electronAPI?.invoke("save-server-config", config);
|
||||
if (result?.success) {
|
||||
configuredServerUrl = config.serverUrl;
|
||||
(window as any).configuredServerUrl = configuredServerUrl;
|
||||
(
|
||||
window as Window &
|
||||
typeof globalThis & {
|
||||
IS_ELECTRON?: boolean;
|
||||
electronAPI?: unknown;
|
||||
configuredServerUrl?: string;
|
||||
}
|
||||
).configuredServerUrl = configuredServerUrl;
|
||||
updateApiInstances();
|
||||
return true;
|
||||
}
|
||||
@@ -375,10 +409,14 @@ export async function testServerConnection(
|
||||
return { success: false, error: "Not in Electron environment" };
|
||||
|
||||
try {
|
||||
const result = await (window as any).electronAPI?.invoke(
|
||||
"test-server-connection",
|
||||
serverUrl,
|
||||
);
|
||||
const result = await (
|
||||
window as Window &
|
||||
typeof globalThis & {
|
||||
IS_ELECTRON?: boolean;
|
||||
electronAPI?: unknown;
|
||||
configuredServerUrl?: string;
|
||||
}
|
||||
).electronAPI?.invoke("test-server-connection", serverUrl);
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error("Failed to test server connection:", error);
|
||||
@@ -406,9 +444,14 @@ export async function checkElectronUpdate(): Promise<{
|
||||
return { success: false, error: "Not in Electron environment" };
|
||||
|
||||
try {
|
||||
const result = await (window as any).electronAPI?.invoke(
|
||||
"check-electron-update",
|
||||
);
|
||||
const result = await (
|
||||
window as Window &
|
||||
typeof globalThis & {
|
||||
IS_ELECTRON?: boolean;
|
||||
electronAPI?: unknown;
|
||||
configuredServerUrl?: string;
|
||||
}
|
||||
).electronAPI?.invoke("check-electron-update");
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error("Failed to check Electron update:", error);
|
||||
@@ -472,7 +515,14 @@ if (isElectron()) {
|
||||
.then((config) => {
|
||||
if (config?.serverUrl) {
|
||||
configuredServerUrl = config.serverUrl;
|
||||
(window as any).configuredServerUrl = configuredServerUrl;
|
||||
(
|
||||
window as Window &
|
||||
typeof globalThis & {
|
||||
IS_ELECTRON?: boolean;
|
||||
electronAPI?: unknown;
|
||||
configuredServerUrl?: string;
|
||||
}
|
||||
).configuredServerUrl = configuredServerUrl;
|
||||
}
|
||||
initializeApiInstances();
|
||||
})
|
||||
@@ -495,7 +545,14 @@ function updateApiInstances() {
|
||||
|
||||
initializeApiInstances();
|
||||
|
||||
(window as any).configuredServerUrl = configuredServerUrl;
|
||||
(
|
||||
window as Window &
|
||||
typeof globalThis & {
|
||||
IS_ELECTRON?: boolean;
|
||||
electronAPI?: unknown;
|
||||
configuredServerUrl?: string;
|
||||
}
|
||||
).configuredServerUrl = configuredServerUrl;
|
||||
|
||||
systemLogger.success("All API instances updated successfully", {
|
||||
operation: "api_instance_update_complete",
|
||||
@@ -564,7 +621,7 @@ function handleApiError(error: unknown, operation: string): never {
|
||||
403,
|
||||
code || "ACCESS_DENIED",
|
||||
);
|
||||
(apiError as any).response = error.response;
|
||||
(apiError as ApiError & { response?: unknown }).response = error.response;
|
||||
throw apiError;
|
||||
} else if (status === 404) {
|
||||
apiLogger.warn(`Not found: ${method} ${url}`, errorContext);
|
||||
@@ -788,7 +845,9 @@ export async function bulkImportSSHHosts(hosts: SSHHostData[]): Promise<{
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteSSHHost(hostId: number): Promise<any> {
|
||||
export async function deleteSSHHost(
|
||||
hostId: number,
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await sshHostApi.delete(`/db/host/${hostId}`);
|
||||
return response.data;
|
||||
@@ -821,7 +880,9 @@ export async function exportSSHHostWithCredentials(
|
||||
// SSH AUTOSTART MANAGEMENT
|
||||
// ============================================================================
|
||||
|
||||
export async function enableAutoStart(sshConfigId: number): Promise<any> {
|
||||
export async function enableAutoStart(
|
||||
sshConfigId: number,
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await sshHostApi.post("/autostart/enable", {
|
||||
sshConfigId,
|
||||
@@ -832,7 +893,9 @@ export async function enableAutoStart(sshConfigId: number): Promise<any> {
|
||||
}
|
||||
}
|
||||
|
||||
export async function disableAutoStart(sshConfigId: number): Promise<any> {
|
||||
export async function disableAutoStart(
|
||||
sshConfigId: number,
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await sshHostApi.delete("/autostart/disable", {
|
||||
data: { sshConfigId },
|
||||
@@ -883,7 +946,9 @@ export async function getTunnelStatusByName(
|
||||
return statuses[tunnelName];
|
||||
}
|
||||
|
||||
export async function connectTunnel(tunnelConfig: TunnelConfig): Promise<any> {
|
||||
export async function connectTunnel(
|
||||
tunnelConfig: TunnelConfig,
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await tunnelApi.post("/tunnel/connect", tunnelConfig);
|
||||
return response.data;
|
||||
@@ -892,7 +957,9 @@ export async function connectTunnel(tunnelConfig: TunnelConfig): Promise<any> {
|
||||
}
|
||||
}
|
||||
|
||||
export async function disconnectTunnel(tunnelName: string): Promise<any> {
|
||||
export async function disconnectTunnel(
|
||||
tunnelName: string,
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await tunnelApi.post("/tunnel/disconnect", { tunnelName });
|
||||
return response.data;
|
||||
@@ -901,7 +968,9 @@ export async function disconnectTunnel(tunnelName: string): Promise<any> {
|
||||
}
|
||||
}
|
||||
|
||||
export async function cancelTunnel(tunnelName: string): Promise<any> {
|
||||
export async function cancelTunnel(
|
||||
tunnelName: string,
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await tunnelApi.post("/tunnel/cancel", { tunnelName });
|
||||
return response.data;
|
||||
@@ -929,7 +998,7 @@ export async function getFileManagerRecent(
|
||||
|
||||
export async function addFileManagerRecent(
|
||||
file: FileManagerOperation,
|
||||
): Promise<any> {
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await sshHostApi.post("/file_manager/recent", file);
|
||||
return response.data;
|
||||
@@ -940,7 +1009,7 @@ export async function addFileManagerRecent(
|
||||
|
||||
export async function removeFileManagerRecent(
|
||||
file: FileManagerOperation,
|
||||
): Promise<any> {
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await sshHostApi.delete("/file_manager/recent", {
|
||||
data: file,
|
||||
@@ -966,7 +1035,7 @@ export async function getFileManagerPinned(
|
||||
|
||||
export async function addFileManagerPinned(
|
||||
file: FileManagerOperation,
|
||||
): Promise<any> {
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await sshHostApi.post("/file_manager/pinned", file);
|
||||
return response.data;
|
||||
@@ -977,7 +1046,7 @@ export async function addFileManagerPinned(
|
||||
|
||||
export async function removeFileManagerPinned(
|
||||
file: FileManagerOperation,
|
||||
): Promise<any> {
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await sshHostApi.delete("/file_manager/pinned", {
|
||||
data: file,
|
||||
@@ -1003,7 +1072,7 @@ export async function getFileManagerShortcuts(
|
||||
|
||||
export async function addFileManagerShortcut(
|
||||
shortcut: FileManagerOperation,
|
||||
): Promise<any> {
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await sshHostApi.post("/file_manager/shortcuts", shortcut);
|
||||
return response.data;
|
||||
@@ -1014,7 +1083,7 @@ export async function addFileManagerShortcut(
|
||||
|
||||
export async function removeFileManagerShortcut(
|
||||
shortcut: FileManagerOperation,
|
||||
): Promise<any> {
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await sshHostApi.delete("/file_manager/shortcuts", {
|
||||
data: shortcut,
|
||||
@@ -1043,7 +1112,7 @@ export async function connectSSH(
|
||||
credentialId?: number;
|
||||
userId?: string;
|
||||
},
|
||||
): Promise<any> {
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await fileManagerApi.post("/ssh/connect", {
|
||||
sessionId,
|
||||
@@ -1055,7 +1124,9 @@ export async function connectSSH(
|
||||
}
|
||||
}
|
||||
|
||||
export async function disconnectSSH(sessionId: string): Promise<any> {
|
||||
export async function disconnectSSH(
|
||||
sessionId: string,
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await fileManagerApi.post("/ssh/disconnect", {
|
||||
sessionId,
|
||||
@@ -1069,7 +1140,7 @@ export async function disconnectSSH(sessionId: string): Promise<any> {
|
||||
export async function verifySSHTOTP(
|
||||
sessionId: string,
|
||||
totpCode: string,
|
||||
): Promise<any> {
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await fileManagerApi.post("/ssh/connect-totp", {
|
||||
sessionId,
|
||||
@@ -1094,7 +1165,9 @@ export async function getSSHStatus(
|
||||
}
|
||||
}
|
||||
|
||||
export async function keepSSHAlive(sessionId: string): Promise<any> {
|
||||
export async function keepSSHAlive(
|
||||
sessionId: string,
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await fileManagerApi.post("/ssh/keepalive", {
|
||||
sessionId,
|
||||
@@ -1108,7 +1181,7 @@ export async function keepSSHAlive(sessionId: string): Promise<any> {
|
||||
export async function listSSHFiles(
|
||||
sessionId: string,
|
||||
path: string,
|
||||
): Promise<{ files: any[]; path: string }> {
|
||||
): Promise<{ files: unknown[]; path: string }> {
|
||||
try {
|
||||
const response = await fileManagerApi.get("/ssh/listFiles", {
|
||||
params: { sessionId, path },
|
||||
@@ -1143,12 +1216,15 @@ export async function readSSHFile(
|
||||
params: { sessionId, path },
|
||||
});
|
||||
return response.data;
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
if (error.response?.status === 404) {
|
||||
const customError = new Error("File not found");
|
||||
(customError as any).response = error.response;
|
||||
(customError as any).isFileNotFound =
|
||||
error.response.data?.fileNotFound || true;
|
||||
(
|
||||
customError as Error & { response?: unknown; isFileNotFound?: boolean }
|
||||
).response = error.response;
|
||||
(
|
||||
customError as Error & { response?: unknown; isFileNotFound?: boolean }
|
||||
).isFileNotFound = error.response.data?.fileNotFound || true;
|
||||
throw customError;
|
||||
}
|
||||
handleApiError(error, "read SSH file");
|
||||
@@ -1161,7 +1237,7 @@ export async function writeSSHFile(
|
||||
content: string,
|
||||
hostId?: number,
|
||||
userId?: string,
|
||||
): Promise<any> {
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await fileManagerApi.post("/ssh/writeFile", {
|
||||
sessionId,
|
||||
@@ -1192,7 +1268,7 @@ export async function uploadSSHFile(
|
||||
content: string,
|
||||
hostId?: number,
|
||||
userId?: string,
|
||||
): Promise<any> {
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await fileManagerApi.post("/ssh/uploadFile", {
|
||||
sessionId,
|
||||
@@ -1213,7 +1289,7 @@ export async function downloadSSHFile(
|
||||
filePath: string,
|
||||
hostId?: number,
|
||||
userId?: string,
|
||||
): Promise<any> {
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await fileManagerApi.post("/ssh/downloadFile", {
|
||||
sessionId,
|
||||
@@ -1234,7 +1310,7 @@ export async function createSSHFile(
|
||||
content: string = "",
|
||||
hostId?: number,
|
||||
userId?: string,
|
||||
): Promise<any> {
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await fileManagerApi.post("/ssh/createFile", {
|
||||
sessionId,
|
||||
@@ -1256,7 +1332,7 @@ export async function createSSHFolder(
|
||||
folderName: string,
|
||||
hostId?: number,
|
||||
userId?: string,
|
||||
): Promise<any> {
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await fileManagerApi.post("/ssh/createFolder", {
|
||||
sessionId,
|
||||
@@ -1277,7 +1353,7 @@ export async function deleteSSHItem(
|
||||
isDirectory: boolean,
|
||||
hostId?: number,
|
||||
userId?: string,
|
||||
): Promise<any> {
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await fileManagerApi.delete("/ssh/deleteItem", {
|
||||
data: {
|
||||
@@ -1300,7 +1376,7 @@ export async function copySSHItem(
|
||||
targetDir: string,
|
||||
hostId?: number,
|
||||
userId?: string,
|
||||
): Promise<any> {
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await fileManagerApi.post(
|
||||
"/ssh/copyItem",
|
||||
@@ -1328,7 +1404,7 @@ export async function renameSSHItem(
|
||||
newName: string,
|
||||
hostId?: number,
|
||||
userId?: string,
|
||||
): Promise<any> {
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await fileManagerApi.put("/ssh/renameItem", {
|
||||
sessionId,
|
||||
@@ -1350,7 +1426,7 @@ export async function moveSSHItem(
|
||||
newPath: string,
|
||||
hostId?: number,
|
||||
userId?: string,
|
||||
): Promise<any> {
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await fileManagerApi.put(
|
||||
"/ssh/moveItem",
|
||||
@@ -1377,7 +1453,9 @@ export async function moveSSHItem(
|
||||
// ============================================================================
|
||||
|
||||
// Recent Files
|
||||
export async function getRecentFiles(hostId: number): Promise<any> {
|
||||
export async function getRecentFiles(
|
||||
hostId: number,
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await authApi.get("/ssh/file_manager/recent", {
|
||||
params: { hostId },
|
||||
@@ -1393,7 +1471,7 @@ export async function addRecentFile(
|
||||
hostId: number,
|
||||
path: string,
|
||||
name?: string,
|
||||
): Promise<any> {
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await authApi.post("/ssh/file_manager/recent", {
|
||||
hostId,
|
||||
@@ -1410,7 +1488,7 @@ export async function addRecentFile(
|
||||
export async function removeRecentFile(
|
||||
hostId: number,
|
||||
path: string,
|
||||
): Promise<any> {
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await authApi.delete("/ssh/file_manager/recent", {
|
||||
data: { hostId, path },
|
||||
@@ -1422,7 +1500,9 @@ export async function removeRecentFile(
|
||||
}
|
||||
}
|
||||
|
||||
export async function getPinnedFiles(hostId: number): Promise<any> {
|
||||
export async function getPinnedFiles(
|
||||
hostId: number,
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await authApi.get("/ssh/file_manager/pinned", {
|
||||
params: { hostId },
|
||||
@@ -1438,7 +1518,7 @@ export async function addPinnedFile(
|
||||
hostId: number,
|
||||
path: string,
|
||||
name?: string,
|
||||
): Promise<any> {
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await authApi.post("/ssh/file_manager/pinned", {
|
||||
hostId,
|
||||
@@ -1455,7 +1535,7 @@ export async function addPinnedFile(
|
||||
export async function removePinnedFile(
|
||||
hostId: number,
|
||||
path: string,
|
||||
): Promise<any> {
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await authApi.delete("/ssh/file_manager/pinned", {
|
||||
data: { hostId, path },
|
||||
@@ -1467,7 +1547,9 @@ export async function removePinnedFile(
|
||||
}
|
||||
}
|
||||
|
||||
export async function getFolderShortcuts(hostId: number): Promise<any> {
|
||||
export async function getFolderShortcuts(
|
||||
hostId: number,
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await authApi.get("/ssh/file_manager/shortcuts", {
|
||||
params: { hostId },
|
||||
@@ -1483,7 +1565,7 @@ export async function addFolderShortcut(
|
||||
hostId: number,
|
||||
path: string,
|
||||
name?: string,
|
||||
): Promise<any> {
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await authApi.post("/ssh/file_manager/shortcuts", {
|
||||
hostId,
|
||||
@@ -1500,7 +1582,7 @@ export async function addFolderShortcut(
|
||||
export async function removeFolderShortcut(
|
||||
hostId: number,
|
||||
path: string,
|
||||
): Promise<any> {
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await authApi.delete("/ssh/file_manager/shortcuts", {
|
||||
data: { hostId, path },
|
||||
@@ -1552,7 +1634,7 @@ export async function getServerMetricsById(id: number): Promise<ServerMetrics> {
|
||||
export async function registerUser(
|
||||
username: string,
|
||||
password: string,
|
||||
): Promise<any> {
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await authApi.post("/users/create", {
|
||||
username,
|
||||
@@ -1638,11 +1720,11 @@ export async function getPasswordLoginAllowed(): Promise<{ allowed: boolean }> {
|
||||
}
|
||||
}
|
||||
|
||||
export async function getOIDCConfig(): Promise<any> {
|
||||
export async function getOIDCConfig(): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await authApi.get("/users/oidc-config");
|
||||
return response.data;
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
console.warn(
|
||||
"Failed to fetch OIDC config:",
|
||||
error.response?.data?.error || error.message,
|
||||
@@ -1669,7 +1751,9 @@ export async function getUserCount(): Promise<UserCount> {
|
||||
}
|
||||
}
|
||||
|
||||
export async function initiatePasswordReset(username: string): Promise<any> {
|
||||
export async function initiatePasswordReset(
|
||||
username: string,
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await authApi.post("/users/initiate-reset", { username });
|
||||
return response.data;
|
||||
@@ -1681,7 +1765,7 @@ export async function initiatePasswordReset(username: string): Promise<any> {
|
||||
export async function verifyPasswordResetCode(
|
||||
username: string,
|
||||
resetCode: string,
|
||||
): Promise<any> {
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await authApi.post("/users/verify-reset-code", {
|
||||
username,
|
||||
@@ -1697,7 +1781,7 @@ export async function completePasswordReset(
|
||||
username: string,
|
||||
tempToken: string,
|
||||
newPassword: string,
|
||||
): Promise<any> {
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await authApi.post("/users/complete-reset", {
|
||||
username,
|
||||
@@ -1732,7 +1816,9 @@ export async function getUserList(): Promise<{ users: UserInfo[] }> {
|
||||
}
|
||||
}
|
||||
|
||||
export async function makeUserAdmin(username: string): Promise<any> {
|
||||
export async function makeUserAdmin(
|
||||
username: string,
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await authApi.post("/users/make-admin", { username });
|
||||
return response.data;
|
||||
@@ -1741,7 +1827,9 @@ export async function makeUserAdmin(username: string): Promise<any> {
|
||||
}
|
||||
}
|
||||
|
||||
export async function removeAdminStatus(username: string): Promise<any> {
|
||||
export async function removeAdminStatus(
|
||||
username: string,
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await authApi.post("/users/remove-admin", { username });
|
||||
return response.data;
|
||||
@@ -1750,7 +1838,9 @@ export async function removeAdminStatus(username: string): Promise<any> {
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteUser(username: string): Promise<any> {
|
||||
export async function deleteUser(
|
||||
username: string,
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await authApi.delete("/users/delete-user", {
|
||||
data: { username },
|
||||
@@ -1761,7 +1851,9 @@ export async function deleteUser(username: string): Promise<any> {
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteAccount(password: string): Promise<any> {
|
||||
export async function deleteAccount(
|
||||
password: string,
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await authApi.delete("/users/delete-account", {
|
||||
data: { password },
|
||||
@@ -1774,7 +1866,7 @@ export async function deleteAccount(password: string): Promise<any> {
|
||||
|
||||
export async function updateRegistrationAllowed(
|
||||
allowed: boolean,
|
||||
): Promise<any> {
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await authApi.patch("/users/registration-allowed", {
|
||||
allowed,
|
||||
@@ -1798,7 +1890,9 @@ export async function updatePasswordLoginAllowed(
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateOIDCConfig(config: any): Promise<any> {
|
||||
export async function updateOIDCConfig(
|
||||
config: Record<string, unknown>,
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await authApi.post("/users/oidc-config", config);
|
||||
return response.data;
|
||||
@@ -1807,7 +1901,7 @@ export async function updateOIDCConfig(config: any): Promise<any> {
|
||||
}
|
||||
}
|
||||
|
||||
export async function disableOIDCConfig(): Promise<any> {
|
||||
export async function disableOIDCConfig(): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await authApi.delete("/users/oidc-config");
|
||||
return response.data;
|
||||
@@ -1893,7 +1987,9 @@ export async function generateBackupCodes(
|
||||
}
|
||||
}
|
||||
|
||||
export async function getUserAlerts(): Promise<{ alerts: any[] }> {
|
||||
export async function getUserAlerts(): Promise<{
|
||||
alerts: Array<Record<string, unknown>>;
|
||||
}> {
|
||||
try {
|
||||
const response = await authApi.get(`/alerts`);
|
||||
return response.data;
|
||||
@@ -1902,7 +1998,9 @@ export async function getUserAlerts(): Promise<{ alerts: any[] }> {
|
||||
}
|
||||
}
|
||||
|
||||
export async function dismissAlert(alertId: string): Promise<any> {
|
||||
export async function dismissAlert(
|
||||
alertId: string,
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await authApi.post("/alerts/dismiss", { alertId });
|
||||
return response.data;
|
||||
@@ -1915,7 +2013,9 @@ export async function dismissAlert(alertId: string): Promise<any> {
|
||||
// UPDATES & RELEASES
|
||||
// ============================================================================
|
||||
|
||||
export async function getReleasesRSS(perPage: number = 100): Promise<any> {
|
||||
export async function getReleasesRSS(
|
||||
perPage: number = 100,
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await authApi.get(`/releases/rss?per_page=${perPage}`);
|
||||
return response.data;
|
||||
@@ -1924,7 +2024,7 @@ export async function getReleasesRSS(perPage: number = 100): Promise<any> {
|
||||
}
|
||||
}
|
||||
|
||||
export async function getVersionInfo(): Promise<any> {
|
||||
export async function getVersionInfo(): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await authApi.get("/version");
|
||||
return response.data;
|
||||
@@ -1937,7 +2037,7 @@ export async function getVersionInfo(): Promise<any> {
|
||||
// DATABASE HEALTH
|
||||
// ============================================================================
|
||||
|
||||
export async function getDatabaseHealth(): Promise<any> {
|
||||
export async function getDatabaseHealth(): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await authApi.get("/users/db-health");
|
||||
return response.data;
|
||||
@@ -1950,7 +2050,7 @@ export async function getDatabaseHealth(): Promise<any> {
|
||||
// SSH CREDENTIALS MANAGEMENT
|
||||
// ============================================================================
|
||||
|
||||
export async function getCredentials(): Promise<any> {
|
||||
export async function getCredentials(): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await authApi.get("/credentials");
|
||||
return response.data;
|
||||
@@ -1959,7 +2059,9 @@ export async function getCredentials(): Promise<any> {
|
||||
}
|
||||
}
|
||||
|
||||
export async function getCredentialDetails(credentialId: number): Promise<any> {
|
||||
export async function getCredentialDetails(
|
||||
credentialId: number,
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await authApi.get(`/credentials/${credentialId}`);
|
||||
return response.data;
|
||||
@@ -1968,7 +2070,9 @@ export async function getCredentialDetails(credentialId: number): Promise<any> {
|
||||
}
|
||||
}
|
||||
|
||||
export async function createCredential(credentialData: any): Promise<any> {
|
||||
export async function createCredential(
|
||||
credentialData: Record<string, unknown>,
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await authApi.post("/credentials", credentialData);
|
||||
return response.data;
|
||||
@@ -1979,8 +2083,8 @@ export async function createCredential(credentialData: any): Promise<any> {
|
||||
|
||||
export async function updateCredential(
|
||||
credentialId: number,
|
||||
credentialData: any,
|
||||
): Promise<any> {
|
||||
credentialData: Record<string, unknown>,
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await authApi.put(
|
||||
`/credentials/${credentialId}`,
|
||||
@@ -1992,7 +2096,9 @@ export async function updateCredential(
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteCredential(credentialId: number): Promise<any> {
|
||||
export async function deleteCredential(
|
||||
credentialId: number,
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await authApi.delete(`/credentials/${credentialId}`);
|
||||
return response.data;
|
||||
@@ -2001,7 +2107,9 @@ export async function deleteCredential(credentialId: number): Promise<any> {
|
||||
}
|
||||
}
|
||||
|
||||
export async function getCredentialHosts(credentialId: number): Promise<any> {
|
||||
export async function getCredentialHosts(
|
||||
credentialId: number,
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await authApi.get(`/credentials/${credentialId}/hosts`);
|
||||
return response.data;
|
||||
@@ -2010,7 +2118,7 @@ export async function getCredentialHosts(credentialId: number): Promise<any> {
|
||||
}
|
||||
}
|
||||
|
||||
export async function getCredentialFolders(): Promise<any> {
|
||||
export async function getCredentialFolders(): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await authApi.get("/credentials/folders");
|
||||
return response.data;
|
||||
@@ -2019,7 +2127,9 @@ export async function getCredentialFolders(): Promise<any> {
|
||||
}
|
||||
}
|
||||
|
||||
export async function getSSHHostWithCredentials(hostId: number): Promise<any> {
|
||||
export async function getSSHHostWithCredentials(
|
||||
hostId: number,
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await sshHostApi.get(
|
||||
`/db/host/${hostId}/with-credentials`,
|
||||
@@ -2033,7 +2143,7 @@ export async function getSSHHostWithCredentials(hostId: number): Promise<any> {
|
||||
export async function applyCredentialToHost(
|
||||
hostId: number,
|
||||
credentialId: number,
|
||||
): Promise<any> {
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await sshHostApi.post(
|
||||
`/db/host/${hostId}/apply-credential`,
|
||||
@@ -2045,7 +2155,9 @@ export async function applyCredentialToHost(
|
||||
}
|
||||
}
|
||||
|
||||
export async function removeCredentialFromHost(hostId: number): Promise<any> {
|
||||
export async function removeCredentialFromHost(
|
||||
hostId: number,
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await sshHostApi.delete(`/db/host/${hostId}/credential`);
|
||||
return response.data;
|
||||
@@ -2057,7 +2169,7 @@ export async function removeCredentialFromHost(hostId: number): Promise<any> {
|
||||
export async function migrateHostToCredential(
|
||||
hostId: number,
|
||||
credentialName: string,
|
||||
): Promise<any> {
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await sshHostApi.post(
|
||||
`/db/host/${hostId}/migrate-to-credential`,
|
||||
@@ -2073,7 +2185,7 @@ export async function migrateHostToCredential(
|
||||
// SSH FOLDER MANAGEMENT
|
||||
// ============================================================================
|
||||
|
||||
export async function getFoldersWithStats(): Promise<any> {
|
||||
export async function getFoldersWithStats(): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await authApi.get("/ssh/db/folders/with-stats");
|
||||
return response.data;
|
||||
@@ -2085,7 +2197,7 @@ export async function getFoldersWithStats(): Promise<any> {
|
||||
export async function renameFolder(
|
||||
oldName: string,
|
||||
newName: string,
|
||||
): Promise<any> {
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await authApi.put("/ssh/folders/rename", {
|
||||
oldName,
|
||||
@@ -2100,7 +2212,7 @@ export async function renameFolder(
|
||||
export async function renameCredentialFolder(
|
||||
oldName: string,
|
||||
newName: string,
|
||||
): Promise<any> {
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await authApi.put("/credentials/folders/rename", {
|
||||
oldName,
|
||||
@@ -2115,7 +2227,7 @@ export async function renameCredentialFolder(
|
||||
export async function detectKeyType(
|
||||
privateKey: string,
|
||||
keyPassword?: string,
|
||||
): Promise<any> {
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await authApi.post("/credentials/detect-key-type", {
|
||||
privateKey,
|
||||
@@ -2127,7 +2239,9 @@ export async function detectKeyType(
|
||||
}
|
||||
}
|
||||
|
||||
export async function detectPublicKeyType(publicKey: string): Promise<any> {
|
||||
export async function detectPublicKeyType(
|
||||
publicKey: string,
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await authApi.post("/credentials/detect-public-key-type", {
|
||||
publicKey,
|
||||
@@ -2142,7 +2256,7 @@ export async function validateKeyPair(
|
||||
privateKey: string,
|
||||
publicKey: string,
|
||||
keyPassword?: string,
|
||||
): Promise<any> {
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await authApi.post("/credentials/validate-key-pair", {
|
||||
privateKey,
|
||||
@@ -2158,7 +2272,7 @@ export async function validateKeyPair(
|
||||
export async function generatePublicKeyFromPrivate(
|
||||
privateKey: string,
|
||||
keyPassword?: string,
|
||||
): Promise<any> {
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await authApi.post("/credentials/generate-public-key", {
|
||||
privateKey,
|
||||
@@ -2174,7 +2288,7 @@ export async function generateKeyPair(
|
||||
keyType: "ssh-ed25519" | "ssh-rsa" | "ecdsa-sha2-nistp256",
|
||||
keySize?: number,
|
||||
passphrase?: string,
|
||||
): Promise<any> {
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await authApi.post("/credentials/generate-key-pair", {
|
||||
keyType,
|
||||
@@ -2190,7 +2304,7 @@ export async function generateKeyPair(
|
||||
export async function deployCredentialToHost(
|
||||
credentialId: number,
|
||||
targetHostId: number,
|
||||
): Promise<any> {
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await authApi.post(
|
||||
`/credentials/${credentialId}/deploy-to-host`,
|
||||
@@ -2206,7 +2320,7 @@ export async function deployCredentialToHost(
|
||||
// SNIPPETS API
|
||||
// ============================================================================
|
||||
|
||||
export async function getSnippets(): Promise<any> {
|
||||
export async function getSnippets(): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await authApi.get("/snippets");
|
||||
return response.data;
|
||||
@@ -2215,7 +2329,9 @@ export async function getSnippets(): Promise<any> {
|
||||
}
|
||||
}
|
||||
|
||||
export async function createSnippet(snippetData: any): Promise<any> {
|
||||
export async function createSnippet(
|
||||
snippetData: Record<string, unknown>,
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await authApi.post("/snippets", snippetData);
|
||||
return response.data;
|
||||
@@ -2226,8 +2342,8 @@ export async function createSnippet(snippetData: any): Promise<any> {
|
||||
|
||||
export async function updateSnippet(
|
||||
snippetId: number,
|
||||
snippetData: any,
|
||||
): Promise<any> {
|
||||
snippetData: Record<string, unknown>,
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await authApi.put(`/snippets/${snippetId}`, snippetData);
|
||||
return response.data;
|
||||
@@ -2236,7 +2352,9 @@ export async function updateSnippet(
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteSnippet(snippetId: number): Promise<any> {
|
||||
export async function deleteSnippet(
|
||||
snippetId: number,
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const response = await authApi.delete(`/snippets/${snippetId}`);
|
||||
return response.data;
|
||||
|
||||
Reference in New Issue
Block a user