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