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

View File

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

View File

@@ -194,7 +194,7 @@ class SSHConnectionPool {
} }
class RequestQueue { class RequestQueue {
private queues = new Map<number, Array<() => Promise<any>>>(); private queues = new Map<number, Array<() => Promise<unknown>>>();
private processing = new Set<number>(); private processing = new Set<number>();
async queueRequest<T>(hostId: number, request: () => Promise<T>): Promise<T> { 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, host: tunnelConfig.sourceIP,
port: tunnelConfig.sourceSSHPort, port: tunnelConfig.sourceSSHPort,
username: tunnelConfig.sourceUsername, username: tunnelConfig.sourceUsername,
@@ -1065,7 +1065,7 @@ async function killRemoteTunnelByMarker(
} }
const conn = new Client(); const conn = new Client();
const connOptions: any = { const connOptions: Record<string, unknown> = {
host: tunnelConfig.sourceIP, host: tunnelConfig.sourceIP,
port: tunnelConfig.sourceSSHPort, port: tunnelConfig.sourceSSHPort,
username: tunnelConfig.sourceUsername, username: tunnelConfig.sourceUsername,
@@ -1461,10 +1461,10 @@ async function initializeAutoStartTunnels(): Promise<void> {
}); });
}, 1000); }, 1000);
} }
} catch (error: any) { } catch (error) {
tunnelLogger.error( tunnelLogger.error(
"Failed to initialize auto-start tunnels:", "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 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 encrypted = Buffer.concat([cipher.update(buffer), cipher.final()]);
const tag = cipher.getAuthTag(); const tag = cipher.getAuthTag();
@@ -78,7 +82,11 @@ class DatabaseFileEncryption {
const iv = crypto.randomBytes(16); 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([ const encrypted = Buffer.concat([
cipher.update(sourceData), cipher.update(sourceData),
cipher.final(), cipher.final(),
@@ -163,7 +171,7 @@ class DatabaseFileEncryption {
metadata.algorithm, metadata.algorithm,
key, key,
Buffer.from(metadata.iv, "hex"), Buffer.from(metadata.iv, "hex"),
) as any; ) as crypto.DecipherGCM;
decipher.setAuthTag(Buffer.from(metadata.tag, "hex")); decipher.setAuthTag(Buffer.from(metadata.tag, "hex"));
const decryptedBuffer = Buffer.concat([ const decryptedBuffer = Buffer.concat([
@@ -233,7 +241,7 @@ class DatabaseFileEncryption {
metadata.algorithm, metadata.algorithm,
key, key,
Buffer.from(metadata.iv, "hex"), Buffer.from(metadata.iv, "hex"),
) as any; ) as crypto.DecipherGCM;
decipher.setAuthTag(Buffer.from(metadata.tag, "hex")); decipher.setAuthTag(Buffer.from(metadata.tag, "hex"));
const decrypted = Buffer.concat([ const decrypted = Buffer.concat([

View File

@@ -234,7 +234,9 @@ export class DatabaseMigration {
memoryDb.exec("PRAGMA foreign_keys = OFF"); memoryDb.exec("PRAGMA foreign_keys = OFF");
for (const table of tables) { 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) { if (rows.length > 0) {
const columns = Object.keys(rows[0]); const columns = Object.keys(rows[0]);
@@ -244,7 +246,7 @@ export class DatabaseMigration {
); );
const insertTransaction = memoryDb.transaction( const insertTransaction = memoryDb.transaction(
(dataRows: any[]) => { (dataRows: Record<string, unknown>[]) => {
for (const row of dataRows) { for (const row of dataRows) {
const values = columns.map((col) => row[col]); const values = columns.map((col) => row[col]);
insertStmt.run(values); insertStmt.run(values);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -96,15 +96,19 @@ export function DiffViewer({
setContent1(response1.content || ""); setContent1(response1.content || "");
setContent2(response2.content || ""); setContent2(response2.content || "");
} catch (error: any) { } catch (error: unknown) {
console.error("Failed to load files for diff:", error); 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) { if (errorData?.tooLarge) {
setError(t("fileManager.fileTooLarge", { error: errorData.error })); setError(t("fileManager.fileTooLarge", { error: errorData.error }));
} else if ( } else if (
error.message?.includes("connection") || err.message?.includes("connection") ||
error.message?.includes("established") err.message?.includes("established")
) { ) {
setError( setError(
t("fileManager.sshConnectionFailed", { t("fileManager.sshConnectionFailed", {
@@ -117,9 +121,7 @@ export function DiffViewer({
setError( setError(
t("fileManager.loadFileFailed", { t("fileManager.loadFileFailed", {
error: error:
error.message || err.message || errorData?.error || t("fileManager.unknownError"),
errorData?.error ||
t("fileManager.unknownError"),
}), }),
); );
} }
@@ -157,12 +159,13 @@ export function DiffViewer({
t("fileManager.downloadFileSuccess", { name: file.name }), t("fileManager.downloadFileSuccess", { name: file.name }),
); );
} }
} catch (error: any) { } catch (error: unknown) {
console.error("Failed to download file:", error); console.error("Failed to download file:", error);
const err = error as { message?: string };
toast.error( toast.error(
t("fileManager.downloadFileFailed") + 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; 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"; if (!bytes) return t ? t("fileManager.unknownSize") : "Unknown size";
const sizes = ["B", "KB", "MB", "GB"]; const sizes = ["B", "KB", "MB", "GB"];
const i = Math.floor(Math.log(bytes) / Math.log(1024)); const i = Math.floor(Math.log(bytes) / Math.log(1024));
@@ -323,7 +323,9 @@ export function FileViewer({
const [pdfScale, setPdfScale] = useState(1.2); const [pdfScale, setPdfScale] = useState(1.2);
const [pdfError, setPdfError] = useState(false); const [pdfError, setPdfError] = useState(false);
const [markdownEditMode, setMarkdownEditMode] = 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); const fileTypeInfo = getFileType(file.name);

View File

@@ -157,28 +157,40 @@ export function FileWindow({
const extension = file.name.split(".").pop()?.toLowerCase(); const extension = file.name.split(".").pop()?.toLowerCase();
setIsEditable(!mediaExtensions.includes(extension || "")); setIsEditable(!mediaExtensions.includes(extension || ""));
} catch (error: any) { } catch (error: unknown) {
console.error("Failed to load file:", error); 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) { if (errorData?.tooLarge) {
toast.error(`File too large: ${errorData.error}`, { toast.error(`File too large: ${errorData.error}`, {
duration: 10000, duration: 10000,
}); });
} else if ( } else if (
error.message?.includes("connection") || err.message?.includes("connection") ||
error.message?.includes("established") err.message?.includes("established")
) { ) {
toast.error( toast.error(
`SSH connection failed. Please check your connection to ${sshHost.name} (${sshHost.ip}:${sshHost.port})`, `SSH connection failed. Please check your connection to ${sshHost.name} (${sshHost.ip}:${sshHost.port})`,
); );
} else { } else {
const errorMessage = const errorMessage =
errorData?.error || error.message || "Unknown error"; errorData?.error || err.message || "Unknown error";
const isFileNotFound = const isFileNotFound =
(error as any).isFileNotFound || err.isFileNotFound ||
errorData?.fileNotFound || errorData?.fileNotFound ||
error.response?.status === 404 || err.response?.status === 404 ||
errorMessage.includes("File not found") || errorMessage.includes("File not found") ||
errorMessage.includes("No such file or directory") || errorMessage.includes("No such file or directory") ||
errorMessage.includes("cannot access") || errorMessage.includes("cannot access") ||
@@ -229,10 +241,11 @@ export function FileWindow({
const contentSize = new Blob([fileContent]).size; const contentSize = new Blob([fileContent]).size;
file.size = contentSize; file.size = contentSize;
} }
} catch (error: any) { } catch (error: unknown) {
console.error("Failed to load file content:", error); console.error("Failed to load file content:", error);
const err = error as { message?: string };
toast.error( toast.error(
`${t("fileManager.failedToLoadFile")}: ${error.message || t("fileManager.unknownError")}`, `${t("fileManager.failedToLoadFile")}: ${err.message || t("fileManager.unknownError")}`,
); );
} finally { } finally {
setIsLoading(false); setIsLoading(false);
@@ -258,19 +271,20 @@ export function FileWindow({
} }
toast.success(t("fileManager.fileSavedSuccessfully")); toast.success(t("fileManager.fileSavedSuccessfully"));
} catch (error: any) { } catch (error: unknown) {
console.error("Failed to save file:", error); console.error("Failed to save file:", error);
const err = error as { message?: string };
if ( if (
error.message?.includes("connection") || err.message?.includes("connection") ||
error.message?.includes("established") err.message?.includes("established")
) { ) {
toast.error( toast.error(
`SSH connection failed. Please check your connection to ${sshHost.name} (${sshHost.ip}:${sshHost.port})`, `SSH connection failed. Please check your connection to ${sshHost.name} (${sshHost.ip}:${sshHost.port})`,
); );
} else { } else {
toast.error( toast.error(
`${t("fileManager.failedToSaveFile")}: ${error.message || t("fileManager.unknownError")}`, `${t("fileManager.failedToSaveFile")}: ${err.message || t("fileManager.unknownError")}`,
); );
} }
} finally { } finally {
@@ -335,19 +349,20 @@ export function FileWindow({
toast.success(t("fileManager.fileDownloadedSuccessfully")); toast.success(t("fileManager.fileDownloadedSuccessfully"));
} }
} catch (error: any) { } catch (error: unknown) {
console.error("Failed to download file:", error); console.error("Failed to download file:", error);
const err = error as { message?: string };
if ( if (
error.message?.includes("connection") || err.message?.includes("connection") ||
error.message?.includes("established") err.message?.includes("established")
) { ) {
toast.error( toast.error(
`SSH connection failed. Please check your connection to ${sshHost.name} (${sshHost.ip}:${sshHost.port})`, `SSH connection failed. Please check your connection to ${sshHost.name} (${sshHost.ip}:${sshHost.port})`,
); );
} else { } else {
toast.error( 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 { t } = useTranslation();
const { closeWindow, maximizeWindow, focusWindow, windows } = const { closeWindow, maximizeWindow, focusWindow, windows } =
useWindowManager(); useWindowManager();
const terminalRef = React.useRef<any>(null); const terminalRef = React.useRef<{ fit?: () => void } | null>(null);
const resizeTimeoutRef = React.useRef<NodeJS.Timeout | 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); const currentWindow = windows.find((w) => w.id === windowId);
if (!currentWindow) { if (!currentWindow) {
return null; return null;
@@ -70,14 +78,6 @@ export function TerminalWindow({
}, 100); }, 100);
}; };
React.useEffect(() => {
return () => {
if (resizeTimeoutRef.current) {
clearTimeout(resizeTimeoutRef.current);
}
};
}, []);
const terminalTitle = executeCommand const terminalTitle = executeCommand
? t("terminal.runTitle", { host: hostConfig.name, command: executeCommand }) ? t("terminal.runTitle", { host: hostConfig.name, command: executeCommand })
: initialPath : initialPath

View File

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

View File

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

View File

@@ -11,7 +11,16 @@ interface NetworkWidgetProps {
export function NetworkWidget({ metrics }: NetworkWidgetProps) { export function NetworkWidget({ metrics }: NetworkWidgetProps) {
const { t } = useTranslation(); 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 || []; const interfaces = network?.interfaces || [];
return ( return (
@@ -30,7 +39,7 @@ export function NetworkWidget({ metrics }: NetworkWidgetProps) {
<p className="text-sm">{t("serverStats.noInterfacesFound")}</p> <p className="text-sm">{t("serverStats.noInterfacesFound")}</p>
</div> </div>
) : ( ) : (
interfaces.map((iface: any, index: number) => ( interfaces.map((iface, index: number) => (
<div <div
key={index} key={index}
className="p-3 rounded-lg bg-dark-bg/50 border border-dark-border/30 hover:bg-dark-bg/60 transition-colors" 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) { export function ProcessesWidget({ metrics }: ProcessesWidgetProps) {
const { t } = useTranslation(); 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 || []; const topProcesses = processes?.top || [];
return ( return (
@@ -46,7 +59,7 @@ export function ProcessesWidget({ metrics }: ProcessesWidgetProps) {
</div> </div>
) : ( ) : (
<div className="space-y-2"> <div className="space-y-2">
{topProcesses.map((proc: any, index: number) => ( {topProcesses.map((proc, index: number) => (
<div <div
key={index} key={index}
className="p-2.5 rounded-lg bg-dark-bg/30 hover:bg-dark-bg/50 transition-colors border border-dark-border/20" 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) { export function SystemWidget({ metrics }: SystemWidgetProps) {
const { t } = useTranslation(); 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 ( 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"> <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) { export function UptimeWidget({ metrics }: UptimeWidgetProps) {
const { t } = useTranslation(); const { t } = useTranslation();
const uptime = (metrics as any)?.uptime; const metricsWithUptime = metrics as ServerMetrics & {
uptime?: {
formatted?: string;
seconds?: number;
};
};
const uptime = metricsWithUptime?.uptime;
return ( 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"> <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", action: "connect" | "disconnect" | "cancel",
host: SSHHost, host: SSHHost,
tunnelIndex: number, tunnelIndex: number,
) => Promise<any>; ) => Promise<unknown>;
} }
export function TunnelViewer({ export function TunnelViewer({

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -114,9 +114,10 @@ export function useDragToDesktop({ sshSessionId }: UseDragToDesktopProps) {
}, 10000); }, 10000);
return true; return true;
} catch (error: any) { } catch (error: unknown) {
console.error("Failed to drag to desktop:", error); 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) => ({ setState((prev) => ({
...prev, ...prev,
@@ -223,9 +224,10 @@ export function useDragToDesktop({ sshSessionId }: UseDragToDesktopProps) {
})); }));
}, 15000); }, 15000);
return true; return true;
} catch (error: any) { } catch (error: unknown) {
console.error("Failed to batch drag to desktop:", error); 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) => ({ setState((prev) => ({
...prev, ...prev,

View File

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