fix: resolve TypeScript and ESLint errors across the codebase

- Fixed @typescript-eslint/no-unused-vars errors (31 instances)
- Fixed @typescript-eslint/no-explicit-any errors in backend (~22 instances)
- Fixed @typescript-eslint/no-explicit-any errors in frontend (~60 instances)
- Fixed prefer-const errors (5 instances)
- Fixed no-empty-object-type and rules-of-hooks errors
- Added proper type assertions for database operations
- Improved type safety in authentication and encryption modules
- Enhanced type definitions for API routes and SSH operations

All TypeScript compilation errors resolved. Application builds and runs successfully.
This commit is contained in:
ZacharyZcR
2025-10-09 23:05:55 +08:00
parent eb76f416bf
commit 8f102bf971
45 changed files with 494 additions and 217 deletions

View File

@@ -702,24 +702,22 @@ router.get("/oidc/callback", async (req, res) => {
const getNestedValue = (
obj: Record<string, unknown>,
path: string,
): any => {
): unknown => {
if (!path || !obj) return null;
return path.split(".").reduce((current, key) => current?.[key], obj);
};
const identifier =
getNestedValue(userInfo, config.identifier_path) ||
const identifier = (getNestedValue(userInfo, config.identifier_path) ||
userInfo[config.identifier_path] ||
userInfo.sub ||
userInfo.email ||
userInfo.preferred_username;
userInfo.preferred_username) as string;
const name =
getNestedValue(userInfo, config.name_path) ||
const name = (getNestedValue(userInfo, config.name_path) ||
userInfo[config.name_path] ||
userInfo.name ||
userInfo.given_name ||
identifier;
identifier) as string;
if (!identifier) {
authLogger.error(
@@ -753,14 +751,14 @@ router.get("/oidc/callback", async (req, res) => {
is_admin: isFirstUser,
is_oidc: true,
oidc_identifier: identifier,
client_id: config.client_id,
client_secret: config.client_secret,
issuer_url: config.issuer_url,
authorization_url: config.authorization_url,
token_url: config.token_url,
identifier_path: config.identifier_path,
name_path: config.name_path,
scopes: config.scopes,
client_id: String(config.client_id),
client_secret: String(config.client_secret),
issuer_url: String(config.issuer_url),
authorization_url: String(config.authorization_url),
token_url: String(config.token_url),
identifier_path: String(config.identifier_path),
name_path: String(config.name_path),
scopes: String(config.scopes),
});
try {

View File

@@ -8,6 +8,7 @@ import { eq, and } from "drizzle-orm";
import { fileLogger } from "../utils/logger.js";
import { SimpleDBOps } from "../utils/simple-db-ops.js";
import { AuthManager } from "../utils/auth-manager.js";
import type { AuthenticatedRequest } from "../../types/index.js";
function isExecutableFile(permissions: string, fileName: string): boolean {
const hasExecutePermission =
@@ -166,7 +167,7 @@ app.post("/ssh/file_manager/ssh/connect", async (req, res) => {
credentialId,
} = req.body;
const userId = (req as any).userId;
const userId = (req as AuthenticatedRequest).userId;
if (!userId) {
fileLogger.error("SSH connection rejected: no authenticated user", {
@@ -246,7 +247,7 @@ app.post("/ssh/file_manager/ssh/connect", async (req, res) => {
);
}
const config: any = {
const config: Record<string, unknown> = {
host: ip,
port: port || 22,
username,
@@ -417,7 +418,9 @@ app.post("/ssh/file_manager/ssh/connect", async (req, res) => {
});
} else {
if (resolvedCredentials.password) {
const responses = prompts.map(() => resolvedCredentials.password || "");
const responses = prompts.map(
() => resolvedCredentials.password || "",
);
finish(responses);
} else {
finish(prompts.map(() => ""));
@@ -432,7 +435,7 @@ app.post("/ssh/file_manager/ssh/connect", async (req, res) => {
app.post("/ssh/file_manager/ssh/connect-totp", async (req, res) => {
const { sessionId, totpCode } = req.body;
const userId = (req as any).userId;
const userId = (req as AuthenticatedRequest).userId;
if (!userId) {
fileLogger.error("TOTP verification rejected: no authenticated user", {
@@ -454,7 +457,9 @@ app.post("/ssh/file_manager/ssh/connect-totp", async (req, res) => {
sessionId,
userId,
});
return res.status(404).json({ error: "TOTP session expired. Please reconnect." });
return res
.status(404)
.json({ error: "TOTP session expired. Please reconnect." });
}
delete pendingTOTPSessions[sessionId];
@@ -462,8 +467,12 @@ app.post("/ssh/file_manager/ssh/connect-totp", async (req, res) => {
if (Date.now() - session.createdAt > 120000) {
try {
session.client.end();
} catch {}
return res.status(408).json({ error: "TOTP session timeout. Please reconnect." });
} catch {
// Ignore errors when closing timed out session
}
return res
.status(408)
.json({ error: "TOTP session timeout. Please reconnect." });
}
session.finish([totpCode]);
@@ -487,7 +496,10 @@ app.post("/ssh/file_manager/ssh/connect-totp", async (req, res) => {
userId,
});
res.json({ status: "success", message: "TOTP verified, SSH connection established" });
res.json({
status: "success",
message: "TOTP verified, SSH connection established",
});
});
session.client.on("error", (err) => {

View File

@@ -194,7 +194,7 @@ class SSHConnectionPool {
}
class RequestQueue {
private queues = new Map<number, Array<() => Promise<any>>>();
private queues = new Map<number, Array<() => Promise<unknown>>>();
private processing = new Set<number>();
async queueRequest<T>(hostId: number, request: () => Promise<T>): Promise<T> {

View File

@@ -903,7 +903,7 @@ async function connectSSHTunnel(
});
});
const connOptions: any = {
const connOptions: Record<string, unknown> = {
host: tunnelConfig.sourceIP,
port: tunnelConfig.sourceSSHPort,
username: tunnelConfig.sourceUsername,
@@ -1065,7 +1065,7 @@ async function killRemoteTunnelByMarker(
}
const conn = new Client();
const connOptions: any = {
const connOptions: Record<string, unknown> = {
host: tunnelConfig.sourceIP,
port: tunnelConfig.sourceSSHPort,
username: tunnelConfig.sourceUsername,
@@ -1461,10 +1461,10 @@ async function initializeAutoStartTunnels(): Promise<void> {
});
}, 1000);
}
} catch (error: any) {
} catch (error) {
tunnelLogger.error(
"Failed to initialize auto-start tunnels:",
error.message,
error instanceof Error ? error.message : "Unknown error",
);
}
}

View File

@@ -30,7 +30,11 @@ class DatabaseFileEncryption {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv(this.ALGORITHM, key, iv) as any;
const cipher = crypto.createCipheriv(
this.ALGORITHM,
key,
iv,
) as crypto.CipherGCM;
const encrypted = Buffer.concat([cipher.update(buffer), cipher.final()]);
const tag = cipher.getAuthTag();
@@ -78,7 +82,11 @@ class DatabaseFileEncryption {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv(this.ALGORITHM, key, iv) as any;
const cipher = crypto.createCipheriv(
this.ALGORITHM,
key,
iv,
) as crypto.CipherGCM;
const encrypted = Buffer.concat([
cipher.update(sourceData),
cipher.final(),
@@ -163,7 +171,7 @@ class DatabaseFileEncryption {
metadata.algorithm,
key,
Buffer.from(metadata.iv, "hex"),
) as any;
) as crypto.DecipherGCM;
decipher.setAuthTag(Buffer.from(metadata.tag, "hex"));
const decryptedBuffer = Buffer.concat([
@@ -233,7 +241,7 @@ class DatabaseFileEncryption {
metadata.algorithm,
key,
Buffer.from(metadata.iv, "hex"),
) as any;
) as crypto.DecipherGCM;
decipher.setAuthTag(Buffer.from(metadata.tag, "hex"));
const decrypted = Buffer.concat([

View File

@@ -234,7 +234,9 @@ export class DatabaseMigration {
memoryDb.exec("PRAGMA foreign_keys = OFF");
for (const table of tables) {
const rows = originalDb.prepare(`SELECT * FROM ${table.name}`).all();
const rows = originalDb
.prepare(`SELECT * FROM ${table.name}`)
.all() as Record<string, unknown>[];
if (rows.length > 0) {
const columns = Object.keys(rows[0]);
@@ -244,7 +246,7 @@ export class DatabaseMigration {
);
const insertTransaction = memoryDb.transaction(
(dataRows: any[]) => {
(dataRows: Record<string, unknown>[]) => {
for (const row of dataRows) {
const values = columns.map((col) => row[col]);
insertStmt.run(values);

View File

@@ -1,6 +1,14 @@
import { FieldCrypto } from "./field-crypto.js";
import { databaseLogger } from "./logger.js";
interface DatabaseInstance {
prepare: (sql: string) => {
all: (param?: unknown) => unknown[];
get: (param?: unknown) => unknown;
run: (...params: unknown[]) => unknown;
};
}
export class LazyFieldEncryption {
private static readonly LEGACY_FIELD_NAME_MAP: Record<string, string> = {
key_password: "keyPassword",
@@ -182,12 +190,12 @@ export class LazyFieldEncryption {
}
static migrateRecordSensitiveFields(
record: any,
record: Record<string, unknown>,
sensitiveFields: string[],
userKEK: Buffer,
recordId: string,
): {
updatedRecord: any;
updatedRecord: Record<string, unknown>;
migratedFields: string[];
needsUpdate: boolean;
} {
@@ -202,7 +210,7 @@ export class LazyFieldEncryption {
try {
const { encrypted, wasPlaintext, wasLegacyEncryption } =
this.migrateFieldToEncrypted(
fieldValue,
fieldValue as string,
userKEK,
recordId,
fieldName,
@@ -279,7 +287,7 @@ export class LazyFieldEncryption {
static async checkUserNeedsMigration(
userId: string,
userKEK: Buffer,
db: any,
db: DatabaseInstance,
): Promise<{
needsMigration: boolean;
plaintextFields: Array<{
@@ -298,7 +306,9 @@ export class LazyFieldEncryption {
try {
const sshHosts = db
.prepare("SELECT * FROM ssh_data WHERE user_id = ?")
.all(userId);
.all(userId) as Array<
Record<string, unknown> & { id: string | number }
>;
for (const host of sshHosts) {
const sensitiveFields = this.getSensitiveFieldsForTable("ssh_data");
const hostPlaintextFields: string[] = [];
@@ -307,7 +317,7 @@ export class LazyFieldEncryption {
if (
host[field] &&
this.fieldNeedsMigration(
host[field],
host[field] as string,
userKEK,
host.id.toString(),
field,
@@ -329,7 +339,9 @@ export class LazyFieldEncryption {
const sshCredentials = db
.prepare("SELECT * FROM ssh_credentials WHERE user_id = ?")
.all(userId);
.all(userId) as Array<
Record<string, unknown> & { id: string | number }
>;
for (const credential of sshCredentials) {
const sensitiveFields =
this.getSensitiveFieldsForTable("ssh_credentials");
@@ -339,7 +351,7 @@ export class LazyFieldEncryption {
if (
credential[field] &&
this.fieldNeedsMigration(
credential[field],
credential[field] as string,
userKEK,
credential.id.toString(),
field,

View File

@@ -11,7 +11,7 @@ export interface LogContext {
sessionId?: string;
requestId?: string;
duration?: number;
[key: string]: any;
[key: string]: unknown;
}
const SENSITIVE_FIELDS = [

View File

@@ -89,7 +89,7 @@ class UserDataImport {
) {
const importStats = await this.importSshHosts(
targetUserId,
exportData.userData.sshHosts,
exportData.userData.sshHosts as Record<string, unknown>[],
{ replaceExisting, dryRun, userDataKey },
);
result.summary.sshHostsImported = importStats.imported;
@@ -104,7 +104,7 @@ class UserDataImport {
) {
const importStats = await this.importSshCredentials(
targetUserId,
exportData.userData.sshCredentials,
exportData.userData.sshCredentials as Record<string, unknown>[],
{ replaceExisting, dryRun, userDataKey },
);
result.summary.sshCredentialsImported = importStats.imported;
@@ -129,7 +129,7 @@ class UserDataImport {
) {
const importStats = await this.importDismissedAlerts(
targetUserId,
exportData.userData.dismissedAlerts,
exportData.userData.dismissedAlerts as Record<string, unknown>[],
{ replaceExisting, dryRun },
);
result.summary.dismissedAlertsImported = importStats.imported;
@@ -159,7 +159,7 @@ class UserDataImport {
private static async importSshHosts(
targetUserId: string,
sshHosts: any[],
sshHosts: Record<string, unknown>[],
options: {
replaceExisting: boolean;
dryRun: boolean;
@@ -198,7 +198,9 @@ class UserDataImport {
delete processedHostData.id;
await getDb().insert(sshData).values(processedHostData);
await getDb()
.insert(sshData)
.values(processedHostData as unknown as typeof sshData.$inferInsert);
imported++;
} catch (error) {
errors.push(
@@ -213,7 +215,7 @@ class UserDataImport {
private static async importSshCredentials(
targetUserId: string,
credentials: any[],
credentials: Record<string, unknown>[],
options: {
replaceExisting: boolean;
dryRun: boolean;
@@ -254,7 +256,11 @@ class UserDataImport {
delete processedCredentialData.id;
await getDb().insert(sshCredentials).values(processedCredentialData);
await getDb()
.insert(sshCredentials)
.values(
processedCredentialData as unknown as typeof sshCredentials.$inferInsert,
);
imported++;
} catch (error) {
errors.push(
@@ -269,7 +275,7 @@ class UserDataImport {
private static async importFileManagerData(
targetUserId: string,
fileManagerData: any,
fileManagerData: Record<string, unknown>,
options: { replaceExisting: boolean; dryRun: boolean },
) {
let imported = 0;
@@ -356,7 +362,7 @@ class UserDataImport {
private static async importDismissedAlerts(
targetUserId: string,
alerts: any[],
alerts: Record<string, unknown>[],
options: { replaceExisting: boolean; dryRun: boolean },
) {
let imported = 0;
@@ -376,7 +382,7 @@ class UserDataImport {
.where(
and(
eq(dismissedAlerts.userId, targetUserId),
eq(dismissedAlerts.alertId, alert.alertId),
eq(dismissedAlerts.alertId, alert.alertId as string),
),
);
@@ -395,10 +401,12 @@ class UserDataImport {
if (existing.length > 0 && options.replaceExisting) {
await getDb()
.update(dismissedAlerts)
.set(newAlert)
.set(newAlert as typeof dismissedAlerts.$inferInsert)
.where(eq(dismissedAlerts.id, existing[0].id));
} else {
await getDb().insert(dismissedAlerts).values(newAlert);
await getDb()
.insert(dismissedAlerts)
.values(newAlert as typeof dismissedAlerts.$inferInsert);
}
imported++;

View File

@@ -5,8 +5,7 @@ import { Eye, EyeOff } from "lucide-react";
import { Input } from "@/components/ui/input";
import { cn } from "@/lib/utils";
interface PasswordInputProps
extends React.InputHTMLAttributes<HTMLInputElement> {}
type PasswordInputProps = React.InputHTMLAttributes<HTMLInputElement>;
export const PasswordInput = React.forwardRef<
HTMLInputElement,

View File

@@ -8,7 +8,10 @@ const Toaster = ({ ...props }: ToasterProps) => {
const originalToast = toast;
const rateLimitedToast = (message: string, options?: any) => {
const rateLimitedToast = (
message: string,
options?: Record<string, unknown>,
) => {
const now = Date.now();
const lastToast = lastToastRef.current;
@@ -25,13 +28,13 @@ const Toaster = ({ ...props }: ToasterProps) => {
};
Object.assign(toast, {
success: (message: string, options?: any) =>
success: (message: string, options?: Record<string, unknown>) =>
rateLimitedToast(message, { ...options, type: "success" }),
error: (message: string, options?: any) =>
error: (message: string, options?: Record<string, unknown>) =>
rateLimitedToast(message, { ...options, type: "error" }),
warning: (message: string, options?: any) =>
warning: (message: string, options?: Record<string, unknown>) =>
rateLimitedToast(message, { ...options, type: "warning" }),
info: (message: string, options?: any) =>
info: (message: string, options?: Record<string, unknown>) =>
rateLimitedToast(message, { ...options, type: "info" }),
message: rateLimitedToast,
});

View File

@@ -2,8 +2,7 @@ import * as React from "react";
import { cn } from "../../lib/utils";
export interface TextareaProps
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
export type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>;
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
({ className, ...props }, ref) => {

View File

@@ -14,7 +14,10 @@ export function VersionCheckModal({
isAuthenticated = false,
}: VersionCheckModalProps) {
const { t } = useTranslation();
const [versionInfo, setVersionInfo] = useState<any>(null);
const [versionInfo, setVersionInfo] = useState<Record<
string,
unknown
> | null>(null);
const [versionChecking, setVersionChecking] = useState(false);
const [versionDismissed] = useState(false);

View File

@@ -17,7 +17,7 @@ export interface LogContext {
errorCode?: string;
errorMessage?: string;
[key: string]: any;
[key: string]: unknown;
}
class FrontendLogger {

View File

@@ -155,7 +155,9 @@ export function CredentialEditor({
type FormData = z.infer<typeof formSchema>;
const form = useForm<FormData>({
resolver: zodResolver(formSchema) as any,
resolver: zodResolver(formSchema) as unknown as Parameters<
typeof useForm<FormData>
>[0]["resolver"],
defaultValues: {
name: "",
description: "",
@@ -198,7 +200,7 @@ export function CredentialEditor({
formData.publicKey = fullCredentialDetails.publicKey || "";
formData.keyPassword = fullCredentialDetails.keyPassword || "";
formData.keyType =
(fullCredentialDetails.keyType as any) || ("auto" as const);
(fullCredentialDetails.keyType as string) || ("auto" as const);
}
form.reset(formData);

View File

@@ -71,7 +71,15 @@ export function CredentialsManager({
const [showDeployDialog, setShowDeployDialog] = useState(false);
const [deployingCredential, setDeployingCredential] =
useState<Credential | null>(null);
const [availableHosts, setAvailableHosts] = useState<any[]>([]);
const [availableHosts, setAvailableHosts] = useState<
Array<{
id: number;
name: string;
ip: string;
port: number;
username: string;
}>
>([]);
const [selectedHostId, setSelectedHostId] = useState<string>("");
const [deployLoading, setDeployLoading] = useState(false);
const [hostSearchQuery, setHostSearchQuery] = useState("");
@@ -207,10 +215,13 @@ export function CredentialsManager({
);
await fetchCredentials();
window.dispatchEvent(new CustomEvent("credentials:changed"));
} catch (err: any) {
if (err.response?.data?.details) {
} catch (err: unknown) {
const error = err as {
response?: { data?: { error?: string; details?: string } };
};
if (error.response?.data?.details) {
toast.error(
`${err.response.data.error}\n${err.response.data.details}`,
`${error.response.data.error}\n${error.response.data.details}`,
);
} else {
toast.error(t("credentials.failedToDeleteCredential"));

View File

@@ -96,15 +96,19 @@ export function DiffViewer({
setContent1(response1.content || "");
setContent2(response2.content || "");
} catch (error: any) {
} catch (error: unknown) {
console.error("Failed to load files for diff:", error);
const errorData = error?.response?.data;
const err = error as {
message?: string;
response?: { data?: { tooLarge?: boolean; error?: string } };
};
const errorData = err?.response?.data;
if (errorData?.tooLarge) {
setError(t("fileManager.fileTooLarge", { error: errorData.error }));
} else if (
error.message?.includes("connection") ||
error.message?.includes("established")
err.message?.includes("connection") ||
err.message?.includes("established")
) {
setError(
t("fileManager.sshConnectionFailed", {
@@ -117,9 +121,7 @@ export function DiffViewer({
setError(
t("fileManager.loadFileFailed", {
error:
error.message ||
errorData?.error ||
t("fileManager.unknownError"),
err.message || errorData?.error || t("fileManager.unknownError"),
}),
);
}
@@ -157,12 +159,13 @@ export function DiffViewer({
t("fileManager.downloadFileSuccess", { name: file.name }),
);
}
} catch (error: any) {
} catch (error: unknown) {
console.error("Failed to download file:", error);
const err = error as { message?: string };
toast.error(
t("fileManager.downloadFileFailed") +
": " +
(error.message || t("fileManager.unknownError")),
(err.message || t("fileManager.unknownError")),
);
}
};

View File

@@ -289,7 +289,7 @@ function getLanguageExtension(filename: string) {
return language ? loadLanguage(language) : null;
}
function formatFileSize(bytes?: number, t?: any): string {
function formatFileSize(bytes?: number, t?: (key: string) => string): string {
if (!bytes) return t ? t("fileManager.unknownSize") : "Unknown size";
const sizes = ["B", "KB", "MB", "GB"];
const i = Math.floor(Math.log(bytes) / Math.log(1024));
@@ -323,7 +323,9 @@ export function FileViewer({
const [pdfScale, setPdfScale] = useState(1.2);
const [pdfError, setPdfError] = useState(false);
const [markdownEditMode, setMarkdownEditMode] = useState(false);
const editorRef = useRef<any>(null);
const editorRef = useRef<{
view?: { dispatch: (transaction: unknown) => void };
} | null>(null);
const fileTypeInfo = getFileType(file.name);

View File

@@ -157,28 +157,40 @@ export function FileWindow({
const extension = file.name.split(".").pop()?.toLowerCase();
setIsEditable(!mediaExtensions.includes(extension || ""));
} catch (error: any) {
} catch (error: unknown) {
console.error("Failed to load file:", error);
const errorData = error?.response?.data;
const err = error as {
message?: string;
isFileNotFound?: boolean;
response?: {
status?: number;
data?: {
tooLarge?: boolean;
error?: string;
fileNotFound?: boolean;
};
};
};
const errorData = err?.response?.data;
if (errorData?.tooLarge) {
toast.error(`File too large: ${errorData.error}`, {
duration: 10000,
});
} else if (
error.message?.includes("connection") ||
error.message?.includes("established")
err.message?.includes("connection") ||
err.message?.includes("established")
) {
toast.error(
`SSH connection failed. Please check your connection to ${sshHost.name} (${sshHost.ip}:${sshHost.port})`,
);
} else {
const errorMessage =
errorData?.error || error.message || "Unknown error";
errorData?.error || err.message || "Unknown error";
const isFileNotFound =
(error as any).isFileNotFound ||
err.isFileNotFound ||
errorData?.fileNotFound ||
error.response?.status === 404 ||
err.response?.status === 404 ||
errorMessage.includes("File not found") ||
errorMessage.includes("No such file or directory") ||
errorMessage.includes("cannot access") ||
@@ -229,10 +241,11 @@ export function FileWindow({
const contentSize = new Blob([fileContent]).size;
file.size = contentSize;
}
} catch (error: any) {
} catch (error: unknown) {
console.error("Failed to load file content:", error);
const err = error as { message?: string };
toast.error(
`${t("fileManager.failedToLoadFile")}: ${error.message || t("fileManager.unknownError")}`,
`${t("fileManager.failedToLoadFile")}: ${err.message || t("fileManager.unknownError")}`,
);
} finally {
setIsLoading(false);
@@ -258,19 +271,20 @@ export function FileWindow({
}
toast.success(t("fileManager.fileSavedSuccessfully"));
} catch (error: any) {
} catch (error: unknown) {
console.error("Failed to save file:", error);
const err = error as { message?: string };
if (
error.message?.includes("connection") ||
error.message?.includes("established")
err.message?.includes("connection") ||
err.message?.includes("established")
) {
toast.error(
`SSH connection failed. Please check your connection to ${sshHost.name} (${sshHost.ip}:${sshHost.port})`,
);
} else {
toast.error(
`${t("fileManager.failedToSaveFile")}: ${error.message || t("fileManager.unknownError")}`,
`${t("fileManager.failedToSaveFile")}: ${err.message || t("fileManager.unknownError")}`,
);
}
} finally {
@@ -335,19 +349,20 @@ export function FileWindow({
toast.success(t("fileManager.fileDownloadedSuccessfully"));
}
} catch (error: any) {
} catch (error: unknown) {
console.error("Failed to download file:", error);
const err = error as { message?: string };
if (
error.message?.includes("connection") ||
error.message?.includes("established")
err.message?.includes("connection") ||
err.message?.includes("established")
) {
toast.error(
`SSH connection failed. Please check your connection to ${sshHost.name} (${sshHost.ip}:${sshHost.port})`,
);
} else {
toast.error(
`Failed to download file: ${error.message || "Unknown error"}`,
`Failed to download file: ${err.message || "Unknown error"}`,
);
}
}

View File

@@ -38,9 +38,17 @@ export function TerminalWindow({
const { t } = useTranslation();
const { closeWindow, maximizeWindow, focusWindow, windows } =
useWindowManager();
const terminalRef = React.useRef<any>(null);
const terminalRef = React.useRef<{ fit?: () => void } | null>(null);
const resizeTimeoutRef = React.useRef<NodeJS.Timeout | null>(null);
React.useEffect(() => {
return () => {
if (resizeTimeoutRef.current) {
clearTimeout(resizeTimeoutRef.current);
}
};
}, []);
const currentWindow = windows.find((w) => w.id === windowId);
if (!currentWindow) {
return null;
@@ -70,14 +78,6 @@ export function TerminalWindow({
}, 100);
};
React.useEffect(() => {
return () => {
if (resizeTimeoutRef.current) {
clearTimeout(resizeTimeoutRef.current);
}
};
}, []);
const terminalTitle = executeCommand
? t("terminal.runTitle", { host: hostConfig.name, command: executeCommand })
: initialPath

View File

@@ -21,7 +21,11 @@ export function HostManager({
const [activeTab, setActiveTab] = useState("host_viewer");
const [editingHost, setEditingHost] = useState<SSHHost | null>(null);
const [editingCredential, setEditingCredential] = useState<any | null>(null);
const [editingCredential, setEditingCredential] = useState<{
id: number;
name?: string;
username: string;
} | null>(null);
const { state: sidebarState } = useSidebar();
const handleEditHost = (host: SSHHost) => {
@@ -34,7 +38,11 @@ export function HostManager({
setActiveTab("host_viewer");
};
const handleEditCredential = (credential: any) => {
const handleEditCredential = (credential: {
id: number;
name?: string;
username: string;
}) => {
setEditingCredential(credential);
setActiveTab("add_credential");
};

View File

@@ -60,7 +60,14 @@ interface SSHHost {
enableTunnel: boolean;
enableFileManager: boolean;
defaultPath: string;
tunnelConnections: any[];
tunnelConnections: Array<{
sourcePort: number;
endpointPort: number;
endpointHost: string;
maxRetries: number;
retryInterval: number;
autoStart: boolean;
}>;
statsConfig?: StatsConfig;
createdAt: string;
updatedAt: string;
@@ -79,7 +86,9 @@ export function HostManagerEditor({
const { t } = useTranslation();
const [folders, setFolders] = useState<string[]>([]);
const [sshConfigurations, setSshConfigurations] = useState<string[]>([]);
const [credentials, setCredentials] = useState<any[]>([]);
const [credentials, setCredentials] = useState<
Array<{ id: number; username: string; authType: string }>
>([]);
const [authTab, setAuthTab] = useState<"password" | "key" | "credential">(
"password",
@@ -292,7 +301,7 @@ export function HostManagerEditor({
type FormData = z.infer<typeof formSchema>;
const form = useForm<FormData>({
resolver: zodResolver(formSchema) as any,
resolver: zodResolver(formSchema),
defaultValues: {
name: "",
ip: "",
@@ -377,7 +386,17 @@ export function HostManagerEditor({
} else if (defaultAuthType === "key") {
formData.key = editingHost.id ? "existing_key" : editingHost.key;
formData.keyPassword = cleanedHost.keyPassword || "";
formData.keyType = (cleanedHost.keyType as any) || "auto";
formData.keyType =
(cleanedHost.keyType as
| "auto"
| "ssh-rsa"
| "ssh-ed25519"
| "ecdsa-sha2-nistp256"
| "ecdsa-sha2-nistp384"
| "ecdsa-sha2-nistp521"
| "ssh-dss"
| "ssh-rsa-sha2-256"
| "ssh-rsa-sha2-512") || "auto";
} else if (defaultAuthType === "credential") {
formData.credentialId =
cleanedHost.credentialId || "existing_credential";
@@ -430,7 +449,7 @@ export function HostManagerEditor({
data.name = `${data.username}@${data.ip}`;
}
const submitData: any = {
const submitData: Record<string, unknown> = {
name: data.name,
ip: data.ip,
port: data.port,

View File

@@ -11,7 +11,16 @@ interface NetworkWidgetProps {
export function NetworkWidget({ metrics }: NetworkWidgetProps) {
const { t } = useTranslation();
const network = (metrics as any)?.network;
const metricsWithNetwork = metrics as ServerMetrics & {
network?: {
interfaces?: Array<{
name: string;
state: string;
ip: string;
}>;
};
};
const network = metricsWithNetwork?.network;
const interfaces = network?.interfaces || [];
return (
@@ -30,7 +39,7 @@ export function NetworkWidget({ metrics }: NetworkWidgetProps) {
<p className="text-sm">{t("serverStats.noInterfacesFound")}</p>
</div>
) : (
interfaces.map((iface: any, index: number) => (
interfaces.map((iface, index: number) => (
<div
key={index}
className="p-3 rounded-lg bg-dark-bg/50 border border-dark-border/30 hover:bg-dark-bg/60 transition-colors"

View File

@@ -11,7 +11,20 @@ interface ProcessesWidgetProps {
export function ProcessesWidget({ metrics }: ProcessesWidgetProps) {
const { t } = useTranslation();
const processes = (metrics as any)?.processes;
const metricsWithProcesses = metrics as ServerMetrics & {
processes?: {
total?: number;
running?: number;
top?: Array<{
pid: number;
cpu: number;
mem: number;
command: string;
user: string;
}>;
};
};
const processes = metricsWithProcesses?.processes;
const topProcesses = processes?.top || [];
return (
@@ -46,7 +59,7 @@ export function ProcessesWidget({ metrics }: ProcessesWidgetProps) {
</div>
) : (
<div className="space-y-2">
{topProcesses.map((proc: any, index: number) => (
{topProcesses.map((proc, index: number) => (
<div
key={index}
className="p-2.5 rounded-lg bg-dark-bg/30 hover:bg-dark-bg/50 transition-colors border border-dark-border/20"

View File

@@ -11,7 +11,14 @@ interface SystemWidgetProps {
export function SystemWidget({ metrics }: SystemWidgetProps) {
const { t } = useTranslation();
const system = (metrics as any)?.system;
const metricsWithSystem = metrics as ServerMetrics & {
system?: {
hostname?: string;
os?: string;
kernel?: string;
};
};
const system = metricsWithSystem?.system;
return (
<div className="h-full w-full p-4 rounded-lg bg-dark-bg/50 border border-dark-border/50 hover:bg-dark-bg/70 transition-colors duration-200 flex flex-col overflow-hidden">

View File

@@ -11,7 +11,13 @@ interface UptimeWidgetProps {
export function UptimeWidget({ metrics }: UptimeWidgetProps) {
const { t } = useTranslation();
const uptime = (metrics as any)?.uptime;
const metricsWithUptime = metrics as ServerMetrics & {
uptime?: {
formatted?: string;
seconds?: number;
};
};
const uptime = metricsWithUptime?.uptime;
return (
<div className="h-full w-full p-4 rounded-lg bg-dark-bg/50 border border-dark-border/50 hover:bg-dark-bg/70 transition-colors duration-200 flex flex-col overflow-hidden">

View File

@@ -11,7 +11,7 @@ interface SSHTunnelViewerProps {
action: "connect" | "disconnect" | "cancel",
host: SSHHost,
tunnelIndex: number,
) => Promise<any>;
) => Promise<unknown>;
}
export function TunnelViewer({

View File

@@ -164,7 +164,7 @@ export function HomepageAuth({
}
try {
let res, meRes;
let res;
if (tab === "login") {
res = await loginUser(localUsername, password);
} else {
@@ -194,7 +194,7 @@ export function HomepageAuth({
throw new Error(t("errors.loginFailed"));
}
[meRes] = await Promise.all([getUserInfo()]);
const [meRes] = await Promise.all([getUserInfo()]);
setInternalLoggedIn(true);
setLoggedIn(true);
@@ -217,16 +217,22 @@ export function HomepageAuth({
setTotpRequired(false);
setTotpCode("");
setTotpTempToken("");
} catch (err: any) {
} catch (err: unknown) {
const error = err as {
message?: string;
response?: { data?: { error?: string } };
};
const errorMessage =
err?.response?.data?.error || err?.message || t("errors.unknownError");
error?.response?.data?.error ||
error?.message ||
t("errors.unknownError");
toast.error(errorMessage);
setInternalLoggedIn(false);
setLoggedIn(false);
setIsAdmin(false);
setUsername(null);
setUserId(null);
if (err?.response?.data?.error?.includes("Database")) {
if (error?.response?.data?.error?.includes("Database")) {
setDbConnectionFailed(true);
} else {
setDbError(null);
@@ -242,10 +248,14 @@ export function HomepageAuth({
await initiatePasswordReset(localUsername);
setResetStep("verify");
toast.success(t("messages.resetCodeSent"));
} catch (err: any) {
} catch (err: unknown) {
const error = err as {
message?: string;
response?: { data?: { error?: string } };
};
toast.error(
err?.response?.data?.error ||
err?.message ||
error?.response?.data?.error ||
error?.message ||
t("errors.failedPasswordReset"),
);
} finally {
@@ -260,8 +270,9 @@ export function HomepageAuth({
setTempToken(response.tempToken);
setResetStep("newPassword");
toast.success(t("messages.codeVerified"));
} catch (err: any) {
toast.error(err?.response?.data?.error || t("errors.failedVerifyCode"));
} catch (err: unknown) {
const error = err as { response?: { data?: { error?: string } } };
toast.error(error?.response?.data?.error || t("errors.failedVerifyCode"));
} finally {
setResetLoading(false);
}
@@ -296,9 +307,10 @@ export function HomepageAuth({
setTab("login");
resetPasswordState();
} catch (err: any) {
} catch (err: unknown) {
const error = err as { response?: { data?: { error?: string } } };
toast.error(
err?.response?.data?.error || t("errors.failedCompleteReset"),
error?.response?.data?.error || t("errors.failedCompleteReset"),
);
} finally {
setResetLoading(false);
@@ -359,11 +371,15 @@ export function HomepageAuth({
setTotpCode("");
setTotpTempToken("");
toast.success(t("messages.loginSuccess"));
} catch (err: any) {
const errorCode = err?.response?.data?.code;
} catch (err: unknown) {
const error = err as {
message?: string;
response?: { data?: { code?: string; error?: string } };
};
const errorCode = error?.response?.data?.code;
const errorMessage =
err?.response?.data?.error ||
err?.message ||
error?.response?.data?.error ||
error?.message ||
t("errors.invalidTotpCode");
if (errorCode === "SESSION_EXPIRED") {
@@ -391,10 +407,14 @@ export function HomepageAuth({
}
window.location.replace(authUrl);
} catch (err: any) {
} catch (err: unknown) {
const error = err as {
message?: string;
response?: { data?: { error?: string } };
};
const errorMessage =
err?.response?.data?.error ||
err?.message ||
error?.response?.data?.error ||
error?.message ||
t("errors.failedOidcLogin");
toast.error(errorMessage);
setOidcLoading(false);

View File

@@ -23,7 +23,14 @@ interface SSHHost {
enableTunnel: boolean;
enableFileManager: boolean;
defaultPath: string;
tunnelConnections: any[];
tunnelConnections: Array<{
sourcePort: number;
endpointPort: number;
endpointHost: string;
maxRetries: number;
retryInterval: number;
autoStart: boolean;
}>;
createdAt: string;
updatedAt: string;
}

View File

@@ -20,7 +20,6 @@ export function Host({ host }: HostProps): React.ReactElement {
: `${host.username}@${host.ip}:${host.port}`;
useEffect(() => {
let intervalId: number | undefined;
let cancelled = false;
const fetchStatus = async () => {
@@ -29,13 +28,14 @@ export function Host({ host }: HostProps): React.ReactElement {
if (!cancelled) {
setServerStatus(res?.status === "online" ? "online" : "offline");
}
} catch (error: any) {
} catch (error: unknown) {
if (!cancelled) {
if (error?.response?.status === 503) {
const err = error as { response?: { status?: number } };
if (err?.response?.status === 503) {
setServerStatus("offline");
} else if (error?.response?.status === 504) {
} else if (err?.response?.status === 504) {
setServerStatus("degraded");
} else if (error?.response?.status === 404) {
} else if (err?.response?.status === 404) {
setServerStatus("offline");
} else {
setServerStatus("offline");
@@ -46,7 +46,7 @@ export function Host({ host }: HostProps): React.ReactElement {
fetchStatus();
intervalId = window.setInterval(fetchStatus, 30000);
const intervalId = window.setInterval(fetchStatus, 30000);
return () => {
cancelled = true;

View File

@@ -19,7 +19,16 @@ interface TabContextType {
setCurrentTab: (tabId: number) => void;
setSplitScreenTab: (tabId: number) => void;
getTab: (tabId: number) => Tab | undefined;
updateHostConfig: (hostId: number, newHostConfig: any) => void;
updateHostConfig: (
hostId: number,
newHostConfig: {
id: number;
name?: string;
username: string;
ip: string;
port: number;
},
) => void;
}
const TabContext = createContext<TabContextType | undefined>(undefined);
@@ -98,7 +107,9 @@ export function TabProvider({ children }: TabProviderProps) {
id,
title: effectiveTitle,
terminalRef:
tabData.type === "terminal" ? React.createRef<any>() : undefined,
tabData.type === "terminal"
? React.createRef<{ disconnect?: () => void }>()
: undefined,
};
setTabs((prev) => [...prev, newTab]);
setCurrentTab(id);
@@ -140,7 +151,16 @@ export function TabProvider({ children }: TabProviderProps) {
return tabs.find((tab) => tab.id === tabId);
};
const updateHostConfig = (hostId: number, newHostConfig: any) => {
const updateHostConfig = (
hostId: number,
newHostConfig: {
id: number;
name?: string;
username: string;
ip: string;
port: number;
},
) => {
setTabs((prev) =>
prev.map((tab) => {
if (tab.hostConfig && tab.hostConfig.id === hostId) {

View File

@@ -49,10 +49,14 @@ export function PasswordReset({ userInfo }: PasswordResetProps) {
await initiatePasswordReset(userInfo.username);
setResetStep("verify");
setError(null);
} catch (err: any) {
} catch (err: unknown) {
const error = err as {
message?: string;
response?: { data?: { error?: string } };
};
setError(
err?.response?.data?.error ||
err?.message ||
error?.response?.data?.error ||
error?.message ||
t("common.failedToInitiatePasswordReset"),
);
} finally {
@@ -80,9 +84,10 @@ export function PasswordReset({ userInfo }: PasswordResetProps) {
setTempToken(response.tempToken);
setResetStep("newPassword");
setError(null);
} catch (err: any) {
} catch (err: unknown) {
const error = err as { response?: { data?: { error?: string } } };
setError(
err?.response?.data?.error || t("common.failedToVerifyResetCode"),
error?.response?.data?.error || t("common.failedToVerifyResetCode"),
);
} finally {
setResetLoading(false);
@@ -110,9 +115,11 @@ export function PasswordReset({ userInfo }: PasswordResetProps) {
toast.success(t("common.passwordResetSuccess"));
resetPasswordState();
} catch (err: any) {
} catch (err: unknown) {
const error = err as { response?: { data?: { error?: string } } };
setError(
err?.response?.data?.error || t("common.failedToCompletePasswordReset"),
error?.response?.data?.error ||
t("common.failedToCompletePasswordReset"),
);
} finally {
setResetLoading(false);

View File

@@ -66,8 +66,9 @@ export function TOTPSetup({
setSecret(response.secret);
setSetupStep("qr");
setIsSettingUp(true);
} catch (err: any) {
setError(err?.response?.data?.error || "Failed to start TOTP setup");
} catch (err: unknown) {
const error = err as { response?: { data?: { error?: string } } };
setError(error?.response?.data?.error || "Failed to start TOTP setup");
} finally {
setLoading(false);
}
@@ -86,8 +87,9 @@ export function TOTPSetup({
setBackupCodes(response.backup_codes);
setSetupStep("backup");
toast.success(t("auth.twoFactorEnabledSuccess"));
} catch (err: any) {
setError(err?.response?.data?.error || "Invalid verification code");
} catch (err: unknown) {
const error = err as { response?: { data?: { error?: string } } };
setError(error?.response?.data?.error || "Invalid verification code");
} finally {
setLoading(false);
}
@@ -105,8 +107,9 @@ export function TOTPSetup({
setDisableCode("");
onStatusChange?.(false);
toast.success(t("auth.twoFactorDisabled"));
} catch (err: any) {
setError(err?.response?.data?.error || "Failed to disable TOTP");
} catch (err: unknown) {
const error = err as { response?: { data?: { error?: string } } };
setError(error?.response?.data?.error || "Failed to disable TOTP");
} finally {
setLoading(false);
}
@@ -122,8 +125,11 @@ export function TOTPSetup({
);
setBackupCodes(response.backup_codes);
toast.success(t("auth.newBackupCodesGenerated"));
} catch (err: any) {
setError(err?.response?.data?.error || "Failed to generate backup codes");
} catch (err: unknown) {
const error = err as { response?: { data?: { error?: string } } };
setError(
error?.response?.data?.error || "Failed to generate backup codes",
);
} finally {
setLoading(false);
}

View File

@@ -62,8 +62,9 @@ export function UserProfile({ isTopbarOpen = true }: UserProfileProps) {
is_oidc: info.is_oidc,
totp_enabled: info.totp_enabled || false,
});
} catch (err: any) {
setError(err?.response?.data?.error || t("errors.loadFailed"));
} catch (err: unknown) {
const error = err as { response?: { data?: { error?: string } } };
setError(error?.response?.data?.error || t("errors.loadFailed"));
} finally {
setLoading(false);
}

View File

@@ -23,7 +23,14 @@ interface SSHHost {
enableTunnel: boolean;
enableFileManager: boolean;
defaultPath: string;
tunnelConnections: any[];
tunnelConnections: Array<{
sourcePort: number;
endpointPort: number;
endpointHost: string;
maxRetries: number;
retryInterval: number;
autoStart: boolean;
}>;
createdAt: string;
updatedAt: string;
}

View File

@@ -20,7 +20,6 @@ export function Host({ host, onHostConnect }: HostProps): React.ReactElement {
: `${host.username}@${host.ip}:${host.port}`;
useEffect(() => {
let intervalId: number | undefined;
let cancelled = false;
const fetchStatus = async () => {
@@ -29,13 +28,14 @@ export function Host({ host, onHostConnect }: HostProps): React.ReactElement {
if (!cancelled) {
setServerStatus(res?.status === "online" ? "online" : "offline");
}
} catch (error: any) {
} catch (error: unknown) {
if (!cancelled) {
if (error?.response?.status === 503) {
const err = error as { response?: { status?: number } };
if (err?.response?.status === 503) {
setServerStatus("offline");
} else if (error?.response?.status === 504) {
} else if (err?.response?.status === 504) {
setServerStatus("degraded");
} else if (error?.response?.status === 404) {
} else if (err?.response?.status === 404) {
setServerStatus("offline");
} else {
setServerStatus("offline");
@@ -46,7 +46,7 @@ export function Host({ host, onHostConnect }: HostProps): React.ReactElement {
fetchStatus();
intervalId = window.setInterval(fetchStatus, 30000);
const intervalId = window.setInterval(fetchStatus, 30000);
return () => {
cancelled = true;

View File

@@ -42,7 +42,14 @@ interface SSHHost {
enableTunnel: boolean;
enableFileManager: boolean;
defaultPath: string;
tunnelConnections: any[];
tunnelConnections: Array<{
sourcePort: number;
endpointPort: number;
endpointHost: string;
maxRetries: number;
retryInterval: number;
autoStart: boolean;
}>;
createdAt: string;
updatedAt: string;
}

View File

@@ -56,7 +56,7 @@ export function TabProvider({ children }: TabProviderProps) {
...tabData,
id,
title: computeUniqueTitle(tabData.title),
terminalRef: React.createRef<any>(),
terminalRef: React.createRef<{ disconnect?: () => void }>(),
};
setTabs((prev) => [...prev, newTab]);
setCurrentTab(id);

View File

@@ -162,7 +162,7 @@ export function HomepageAuth({
}
try {
let res, meRes;
let res;
if (tab === "login") {
res = await loginUser(localUsername, password);
} else {
@@ -192,7 +192,7 @@ export function HomepageAuth({
throw new Error(t("errors.loginFailed"));
}
[meRes] = await Promise.all([getUserInfo()]);
const [meRes] = await Promise.all([getUserInfo()]);
setInternalLoggedIn(true);
setLoggedIn(true);
@@ -215,16 +215,22 @@ export function HomepageAuth({
setTotpRequired(false);
setTotpCode("");
setTotpTempToken("");
} catch (err: any) {
} catch (err: unknown) {
const error = err as {
message?: string;
response?: { data?: { error?: string } };
};
const errorMessage =
err?.response?.data?.error || err?.message || t("errors.unknownError");
error?.response?.data?.error ||
error?.message ||
t("errors.unknownError");
toast.error(errorMessage);
setInternalLoggedIn(false);
setLoggedIn(false);
setIsAdmin(false);
setUsername(null);
setUserId(null);
if (err?.response?.data?.error?.includes("Database")) {
if (error?.response?.data?.error?.includes("Database")) {
setDbError(t("errors.databaseConnection"));
} else {
setDbError(null);
@@ -241,10 +247,14 @@ export function HomepageAuth({
await initiatePasswordReset(localUsername);
setResetStep("verify");
toast.success(t("messages.resetCodeSent"));
} catch (err: any) {
} catch (err: unknown) {
const error = err as {
message?: string;
response?: { data?: { error?: string } };
};
toast.error(
err?.response?.data?.error ||
err?.message ||
error?.response?.data?.error ||
error?.message ||
t("errors.failedPasswordReset"),
);
} finally {
@@ -260,8 +270,9 @@ export function HomepageAuth({
setTempToken(response.tempToken);
setResetStep("newPassword");
toast.success(t("messages.codeVerified"));
} catch (err: any) {
toast.error(err?.response?.data?.error || t("errors.failedVerifyCode"));
} catch (err: unknown) {
const error = err as { response?: { data?: { error?: string } } };
toast.error(error?.response?.data?.error || t("errors.failedVerifyCode"));
} finally {
setResetLoading(false);
}
@@ -298,9 +309,10 @@ export function HomepageAuth({
setTab("login");
resetPasswordState();
} catch (err: any) {
} catch (err: unknown) {
const error = err as { response?: { data?: { error?: string } } };
toast.error(
err?.response?.data?.error || t("errors.failedCompleteReset"),
error?.response?.data?.error || t("errors.failedCompleteReset"),
);
} finally {
setResetLoading(false);
@@ -364,11 +376,15 @@ export function HomepageAuth({
setTotpCode("");
setTotpTempToken("");
toast.success(t("messages.loginSuccess"));
} catch (err: any) {
const errorCode = err?.response?.data?.code;
} catch (err: unknown) {
const error = err as {
message?: string;
response?: { data?: { code?: string; error?: string } };
};
const errorCode = error?.response?.data?.code;
const errorMessage =
err?.response?.data?.error ||
err?.message ||
error?.response?.data?.error ||
error?.message ||
t("errors.invalidTotpCode");
if (errorCode === "SESSION_EXPIRED") {
@@ -397,10 +413,14 @@ export function HomepageAuth({
}
window.location.replace(authUrl);
} catch (err: any) {
} catch (err: unknown) {
const error = err as {
message?: string;
response?: { data?: { error?: string } };
};
const errorMessage =
err?.response?.data?.error ||
err?.message ||
error?.response?.data?.error ||
error?.message ||
t("errors.failedOidcLogin");
toast.error(errorMessage);
setOidcLoading(false);

View File

@@ -23,7 +23,14 @@ interface SSHHost {
enableTunnel: boolean;
enableFileManager: boolean;
defaultPath: string;
tunnelConnections: any[];
tunnelConnections: Array<{
sourcePort: number;
endpointPort: number;
endpointHost: string;
maxRetries: number;
retryInterval: number;
autoStart: boolean;
}>;
createdAt: string;
updatedAt: string;
}

View File

@@ -20,7 +20,6 @@ export function Host({ host, onHostConnect }: HostProps): React.ReactElement {
: `${host.username}@${host.ip}:${host.port}`;
useEffect(() => {
let intervalId: number | undefined;
let cancelled = false;
const fetchStatus = async () => {
@@ -29,13 +28,14 @@ export function Host({ host, onHostConnect }: HostProps): React.ReactElement {
if (!cancelled) {
setServerStatus(res?.status === "online" ? "online" : "offline");
}
} catch (error: any) {
} catch (error: unknown) {
if (!cancelled) {
if (error?.response?.status === 503) {
const err = error as { response?: { status?: number } };
if (err?.response?.status === 503) {
setServerStatus("offline");
} else if (error?.response?.status === 504) {
} else if (err?.response?.status === 504) {
setServerStatus("degraded");
} else if (error?.response?.status === 404) {
} else if (err?.response?.status === 404) {
setServerStatus("offline");
} else {
setServerStatus("offline");
@@ -46,7 +46,7 @@ export function Host({ host, onHostConnect }: HostProps): React.ReactElement {
fetchStatus();
intervalId = window.setInterval(fetchStatus, 30000);
const intervalId = window.setInterval(fetchStatus, 30000);
return () => {
cancelled = true;

View File

@@ -43,7 +43,14 @@ interface SSHHost {
enableTunnel: boolean;
enableFileManager: boolean;
defaultPath: string;
tunnelConnections: any[];
tunnelConnections: Array<{
sourcePort: number;
endpointPort: number;
endpointHost: string;
maxRetries: number;
retryInterval: number;
autoStart: boolean;
}>;
createdAt: string;
updatedAt: string;
}

View File

@@ -56,7 +56,7 @@ export function TabProvider({ children }: TabProviderProps) {
...tabData,
id,
title: computeUniqueTitle(tabData.title),
terminalRef: React.createRef<any>(),
terminalRef: React.createRef<{ disconnect?: () => void }>(),
};
setTabs((prev) => [...prev, newTab]);
setCurrentTab(id);

View File

@@ -114,9 +114,10 @@ export function useDragToDesktop({ sshSessionId }: UseDragToDesktopProps) {
}, 10000);
return true;
} catch (error: any) {
} catch (error: unknown) {
console.error("Failed to drag to desktop:", error);
const errorMessage = error.message || "Drag failed";
const err = error as { message?: string };
const errorMessage = err.message || "Drag failed";
setState((prev) => ({
...prev,
@@ -223,9 +224,10 @@ export function useDragToDesktop({ sshSessionId }: UseDragToDesktopProps) {
}));
}, 15000);
return true;
} catch (error: any) {
} catch (error: unknown) {
console.error("Failed to batch drag to desktop:", error);
const errorMessage = error.message || "Batch drag failed";
const err = error as { message?: string };
const errorMessage = err.message || "Batch drag failed";
setState((prev) => ({
...prev,

View File

@@ -34,7 +34,9 @@ export function useDragToSystemDesktop({ sshSessionId }: UseDragToSystemProps) {
options: DragToSystemOptions;
} | null>(null);
const saveLastDirectory = async (fileHandle: any) => {
const saveLastDirectory = async (fileHandle: {
getParent?: () => Promise<unknown>;
}) => {
try {
if ("indexedDB" in window && fileHandle.getParent) {
const dirHandle = await fileHandle.getParent();
@@ -133,10 +135,33 @@ export function useDragToSystemDesktop({ sshSessionId }: UseDragToSystemProps) {
const fileName =
fileList.length === 1 ? fileList[0].name : `files_${Date.now()}.zip`;
let fileHandle: any = null;
let fileHandle: {
createWritable?: () => Promise<{
write: (data: Blob) => Promise<void>;
close: () => Promise<void>;
}>;
getParent?: () => Promise<unknown>;
} | null = null;
if (isFileSystemAPISupported()) {
try {
fileHandle = await (window as any).showSaveFilePicker({
fileHandle = await (
window as Window & {
showSaveFilePicker?: (options: {
suggestedName: string;
startIn: string;
types: Array<{
description: string;
accept: Record<string, string[]>;
}>;
}) => Promise<{
createWritable?: () => Promise<{
write: (data: Blob) => Promise<void>;
close: () => Promise<void>;
}>;
getParent?: () => Promise<unknown>;
}>;
}
).showSaveFilePicker!({
suggestedName: fileName,
startIn: "desktop",
types: [
@@ -156,8 +181,9 @@ export function useDragToSystemDesktop({ sshSessionId }: UseDragToSystemProps) {
},
],
});
} catch (error: any) {
if (error.name === "AbortError") {
} catch (error: unknown) {
const err = error as { name?: string };
if (err.name === "AbortError") {
setState((prev) => ({
...prev,
isDownloading: false,
@@ -211,8 +237,9 @@ export function useDragToSystemDesktop({ sshSessionId }: UseDragToSystemProps) {
}, 1000);
return true;
} catch (error: any) {
const errorMessage = error.message || "Save failed";
} catch (error: unknown) {
const err = error as { message?: string };
const errorMessage = err.message || "Save failed";
setState((prev) => ({
...prev,