From 03e31d66a7aea307a13fbf7645103387fb4e0bf4 Mon Sep 17 00:00:00 2001 From: CanbiZ <47820557+MickLesk@users.noreply.github.com> Date: Fri, 28 Nov 2025 12:47:09 +0100 Subject: [PATCH] Refactor type usage and improve data normalization Updated several components to use explicit TypeScript types for better type safety. Normalized appriseUrls to always be an array in auto-sync settings API. Improved handling of optional server_id in BackupsTab and adjusted IP detection logic in InstalledScriptsTab. Removed unnecessary eslint-disable comments and improved code clarity in various places. --- eslint.config.js | 20 +- src/app/_components/AuthProvider.tsx | 2 +- src/app/_components/BackupsTab.tsx | 5 +- src/app/_components/FilterBar.tsx | 2 +- src/app/_components/GeneralSettingsModal.tsx | 235 ++++++++++--------- src/app/_components/InstalledScriptsTab.tsx | 11 +- src/app/_components/ServerForm.tsx | 2 +- src/app/_components/Terminal.tsx | 2 +- src/app/api/settings/auto-sync/route.ts | 11 +- src/server/api/routers/installedScripts.ts | 6 +- src/server/api/routers/scripts.ts | 2 +- 11 files changed, 160 insertions(+), 138 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index 917cfd2..c9c0470 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,15 +1,23 @@ -import { FlatCompat } from "@eslint/eslintrc"; +import eslintPluginNext from "@next/eslint-plugin-next"; import tseslint from "typescript-eslint"; - -const compat = new FlatCompat({ - baseDirectory: import.meta.dirname, -}); +import reactPlugin from "eslint-plugin-react"; +import reactHooksPlugin from "eslint-plugin-react-hooks"; export default tseslint.config( { ignores: [".next", "next-env.d.ts", "postcss.config.js", "prettier.config.js"], }, - ...compat.extends("next/core-web-vitals"), + { + plugins: { + "@next/next": eslintPluginNext, + "react": reactPlugin, + "react-hooks": reactHooksPlugin, + }, + rules: { + ...eslintPluginNext.configs.recommended.rules, + ...eslintPluginNext.configs["core-web-vitals"].rules, + }, + }, { files: ["**/*.ts", "**/*.tsx"], extends: [ diff --git a/src/app/_components/AuthProvider.tsx b/src/app/_components/AuthProvider.tsx index 7a28f8f..76e81fc 100644 --- a/src/app/_components/AuthProvider.tsx +++ b/src/app/_components/AuthProvider.tsx @@ -97,7 +97,7 @@ export function AuthProvider({ children }: AuthProviderProps) { const checkAuth = useCallback(() => { return checkAuthInternal(0); - // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); const login = async ( diff --git a/src/app/_components/BackupsTab.tsx b/src/app/_components/BackupsTab.tsx index 05c0fc2..5048e8a 100644 --- a/src/app/_components/BackupsTab.tsx +++ b/src/app/_components/BackupsTab.tsx @@ -32,7 +32,7 @@ interface Backup { storage_name: string; storage_type: string; discovered_at: Date; - server_id: number; + server_id?: number; server_name: string | null; server_color: string | null; } @@ -163,7 +163,6 @@ export function BackupsTab() { } setHasAutoDiscovered(true); } - // eslint-disable-next-line react-hooks/exhaustive-deps }, [hasAutoDiscovered, isLoading, backupsData]); const handleDiscoverBackups = () => { @@ -188,7 +187,7 @@ export function BackupsTab() { restoreMutation.mutate({ backupId: selectedBackup.backup.id, containerId: selectedBackup.containerId, - serverId: selectedBackup.backup.server_id, + serverId: selectedBackup.backup.server_id ?? 0, }); }; diff --git a/src/app/_components/FilterBar.tsx b/src/app/_components/FilterBar.tsx index 03dd11e..eb55b47 100644 --- a/src/app/_components/FilterBar.tsx +++ b/src/app/_components/FilterBar.tsx @@ -321,7 +321,7 @@ export function FilterBar({ {/* Repository Filter Buttons - Only show if more than one enabled repo */} {enabledRepos.length > 1 && - enabledRepos.map((repo) => { + enabledRepos.map((repo: { id: number; url: string }) => { const repoUrl = String(repo.url); const isSelected = filters.selectedRepositories.includes(repoUrl); diff --git a/src/app/_components/GeneralSettingsModal.tsx b/src/app/_components/GeneralSettingsModal.tsx index 86a641e..023325b 100644 --- a/src/app/_components/GeneralSettingsModal.tsx +++ b/src/app/_components/GeneralSettingsModal.tsx @@ -1704,134 +1704,143 @@ export function GeneralSettingsModal({ {repositoriesData?.success && repositoriesData.repositories.length > 0 ? (
- {repositoriesData.repositories.map((repo) => ( -
-
-
- - {repo.url} - - - {repo.is_default && ( - - {repo.priority === 1 ? "Main" : "Dev"} - - )} + {repositoriesData.repositories.map( + (repo: { + id: number; + url: string; + enabled: boolean; + is_default: boolean; + is_removable: boolean; + priority: number; + }) => ( +
+
+
+ + {repo.url} + + + {repo.is_default && ( + + {repo.priority === 1 ? "Main" : "Dev"} + + )} +
+

+ Priority: {repo.priority}{" "} + {repo.enabled ? "• Enabled" : "• Disabled"} +

-

- Priority: {repo.priority}{" "} - {repo.enabled ? "• Enabled" : "• Disabled"} -

-
-
- { - setMessage(null); - try { - const result = - await updateRepoMutation.mutateAsync({ - id: repo.id, - enabled, - }); - if (result.success) { - setMessage({ - type: "success", - text: `Repository ${enabled ? "enabled" : "disabled"} successfully!`, - }); - await refetchRepositories(); - } else { +
+ { + setMessage(null); + try { + const result = + await updateRepoMutation.mutateAsync({ + id: repo.id, + enabled, + }); + if (result.success) { + setMessage({ + type: "success", + text: `Repository ${enabled ? "enabled" : "disabled"} successfully!`, + }); + await refetchRepositories(); + } else { + setMessage({ + type: "error", + text: + result.error ?? + "Failed to update repository", + }); + } + } catch (error) { setMessage({ type: "error", text: - result.error ?? - "Failed to update repository", + error instanceof Error + ? error.message + : "Failed to update repository", }); } - } catch (error) { - setMessage({ - type: "error", - text: - error instanceof Error - ? error.message - : "Failed to update repository", - }); - } - }} - disabled={updateRepoMutation.isPending} - label={repo.enabled ? "Disable" : "Enable"} - /> - + variant="ghost" + size="icon" + className="text-error hover:text-error/80 hover:bg-error/10" + > + + +
-
- ))} + ), + )}
) : (

diff --git a/src/app/_components/InstalledScriptsTab.tsx b/src/app/_components/InstalledScriptsTab.tsx index 9bf501d..4799a31 100644 --- a/src/app/_components/InstalledScriptsTab.tsx +++ b/src/app/_components/InstalledScriptsTab.tsx @@ -331,7 +331,7 @@ export function InstalledScriptsTab() { setAutoDetectStatus({ type: "success", message: data.success - ? `Detected IP: ${data.ip}` + ? `Detected IP: ${data.detectedIp ?? "unknown"}` : (data.error ?? "Failed to detect Web UI"), }); setTimeout( @@ -359,15 +359,10 @@ export function InstalledScriptsTab() { { enabled: false }, // Only fetch when explicitly called ); - const fetchStorages = async (serverId: number, forceRefresh = false) => { + const fetchStorages = async (serverId: number, _forceRefresh = false) => { setIsLoadingStorages(true); try { - const result = await getBackupStoragesQuery.refetch({ - queryKey: [ - "installedScripts.getBackupStorages", - { serverId, forceRefresh }, - ], - }); + const result = await getBackupStoragesQuery.refetch(); if (result.data?.success) { setBackupStorages(result.data.storages); } else { diff --git a/src/app/_components/ServerForm.tsx b/src/app/_components/ServerForm.tsx index 290b259..5709bf4 100644 --- a/src/app/_components/ServerForm.tsx +++ b/src/app/_components/ServerForm.tsx @@ -74,7 +74,7 @@ export function ServerForm({ // Check for IPv6 with zone identifier (link-local addresses like fe80::...%eth0) let ipv6Address = trimmed; const zoneIdMatch = /^(.+)%([a-zA-Z0-9_\-]+)$/.exec(trimmed); - if (zoneIdMatch) { + if (zoneIdMatch?.[1] && zoneIdMatch[2]) { ipv6Address = zoneIdMatch[1]; // Zone identifier should be a valid interface name (alphanumeric, underscore, hyphen) const zoneId = zoneIdMatch[2]; diff --git a/src/app/_components/Terminal.tsx b/src/app/_components/Terminal.tsx index 84c0ec1..4412760 100644 --- a/src/app/_components/Terminal.tsx +++ b/src/app/_components/Terminal.tsx @@ -380,7 +380,7 @@ export function Terminal({ scriptPath, onClose, mode = 'local', server, isUpdate wsRef.current.close(); } }; - }, [scriptPath, mode, server, isUpdate, isShell, containerId, isMobile]); // eslint-disable-line react-hooks/exhaustive-deps + }, [scriptPath, mode, server, isUpdate, isShell, containerId, isMobile]); const startScript = () => { if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN && !isRunning) { diff --git a/src/app/api/settings/auto-sync/route.ts b/src/app/api/settings/auto-sync/route.ts index 1fafcf1..b8870d5 100644 --- a/src/app/api/settings/auto-sync/route.ts +++ b/src/app/api/settings/auto-sync/route.ts @@ -187,7 +187,16 @@ export async function POST(request: NextRequest) { } // Update the global service instance with new settings - autoSyncService.saveSettings(settings); + // Normalize appriseUrls to always be an array + const normalizedSettings = { + ...settings, + appriseUrls: Array.isArray(settings.appriseUrls) + ? settings.appriseUrls + : settings.appriseUrls + ? [settings.appriseUrls] + : undefined + }; + autoSyncService.saveSettings(normalizedSettings); if (settings.autoSyncEnabled) { autoSyncService.scheduleAutoSync(); diff --git a/src/server/api/routers/installedScripts.ts b/src/server/api/routers/installedScripts.ts index 03dbe65..96c516f 100644 --- a/src/server/api/routers/installedScripts.ts +++ b/src/server/api/routers/installedScripts.ts @@ -476,7 +476,8 @@ export const installedScriptsRouter = createTRPCRouter({ const scripts = await db.getAllInstalledScripts(); // Transform scripts to flatten server data for frontend compatibility - const transformedScripts = await Promise.all(scripts.map(async (script) => { + + const transformedScripts = await Promise.all(scripts.map(async (script: any) => { // Determine if it's a VM or LXC let is_vm = false; if (script.container_id && script.server_id) { @@ -522,7 +523,8 @@ export const installedScriptsRouter = createTRPCRouter({ const scripts = await db.getInstalledScriptsByServer(input.serverId); // Transform scripts to flatten server data for frontend compatibility - const transformedScripts = await Promise.all(scripts.map(async (script) => { + + const transformedScripts = await Promise.all(scripts.map(async (script: any) => { // Determine if it's a VM or LXC let is_vm = false; if (script.container_id && script.server_id) { diff --git a/src/server/api/routers/scripts.ts b/src/server/api/routers/scripts.ts index b33b4a1..f8c34bc 100644 --- a/src/server/api/routers/scripts.ts +++ b/src/server/api/routers/scripts.ts @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ import { z } from "zod"; import { createTRPCRouter, publicProcedure } from "~/server/api/trpc"; import { scriptManager } from "~/server/lib/scripts";