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:
ZacharyZcR
2025-10-09 18:06:17 +08:00
parent 1decac481e
commit d7e98cda04
22 changed files with 2002 additions and 1540 deletions

View File

@@ -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 {

View File

@@ -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

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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) &&

View File

@@ -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 = `

View File

@@ -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;