From 04fbddb979901ce7b9d5a993541ea685bb4afe41 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Aug 2026 10:58:33 +0800 Subject: [PATCH] Stabilize CI runtime for updated deps and unblock Dependabot auto-format flow (#185) * chore: apply ci and dependabot remediation changes * chore: auto-fix linting and formatting issues --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> --- .github/dependabot.yml | 36 +++--- .github/workflows/frontend-ci.yml | 2 +- .prettierignore | 1 + package-lock.json | 4 +- playwright.config.ts | 10 +- src/api/api-tokens.ts | 117 +++++++++++++++---- src/api/server.ts | 6 +- src/components/alert-rule.tsx | 61 +++++----- src/components/cron.tsx | 36 +++--- src/components/ddns.tsx | 60 +++++----- src/components/fm.tsx | 54 +++++---- src/components/install-commands.tsx | 20 ++-- src/components/nat.tsx | 24 ++-- src/components/notification-group.tsx | 12 +- src/components/notifier.tsx | 48 ++++---- src/components/server-group.tsx | 12 +- src/components/server.tsx | 24 ++-- src/components/service.tsx | 42 +++---- src/components/ui/badge.tsx | 3 +- src/components/ui/button.tsx | 3 +- src/components/ui/form.tsx | 4 +- src/components/ui/icon.tsx | 9 +- src/components/xui/icon-button.tsx | 108 ++++++++--------- src/components/xui/multi-select.tsx | 3 +- src/components/xui/overlayless-sheet.tsx | 3 +- src/components/xui/virtulized-data-table.tsx | 6 +- src/hooks/useAuth.tsx | 43 +++---- src/index.css | 2 +- src/lib/fm.ts | 84 ++++++------- src/routes/alert-rule.tsx | 6 +- src/routes/api-tokens.tsx | 24 +++- src/routes/cron.tsx | 24 ++-- src/routes/ddns.tsx | 6 +- src/routes/login.tsx | 14 +-- src/routes/nat.tsx | 6 +- src/routes/notification-group.tsx | 6 +- src/routes/notification.tsx | 6 +- src/routes/online-user.tsx | 6 +- src/routes/server-group.tsx | 6 +- src/routes/server.tsx | 6 +- src/routes/service.tsx | 18 +-- src/routes/settings.tsx | 30 ++--- src/routes/transfer.tsx | 11 +- src/routes/user.tsx | 6 +- src/routes/waf.tsx | 6 +- src/test/api-tokens.test.tsx | 21 ++-- src/test/auth-login-loading.test.tsx | 3 +- src/test/auth-login-race.test.tsx | 3 +- src/test/batch-move-server-icon.test.tsx | 3 +- src/test/fetcher-csrf.test.ts | 4 +- src/test/fm.test.tsx | 9 +- src/test/protect-auth-loading.test.tsx | 23 +--- src/test/settings-submit-error.test.tsx | 4 +- src/test/transfer.test.tsx | 8 +- tests/e2e/api-tokens.spec.ts | 5 +- tests/e2e/auth.spec.ts | 14 ++- tests/e2e/fixtures.ts | 5 +- tests/e2e/fm-csrf.spec.ts | 5 +- tests/e2e/visibility.spec.ts | 18 ++- 59 files changed, 627 insertions(+), 516 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index cf87225..4b40039 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,21 +1,21 @@ version: 2 updates: - - package-ecosystem: "npm" - directory: "/" - schedule: - interval: "weekly" - open-pull-requests-limit: 10 - groups: - npm-dependencies: - patterns: - - "*" + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 10 + groups: + npm-dependencies: + patterns: + - "*" - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "weekly" - open-pull-requests-limit: 5 - groups: - github-actions: - patterns: - - "*" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 5 + groups: + github-actions: + patterns: + - "*" diff --git a/.github/workflows/frontend-ci.yml b/.github/workflows/frontend-ci.yml index 4431ac6..720f81f 100644 --- a/.github/workflows/frontend-ci.yml +++ b/.github/workflows/frontend-ci.yml @@ -20,7 +20,7 @@ jobs: - name: Setup Node uses: actions/setup-node@v7 with: - node-version: 20 + node-version: 22.19.0 cache: npm cache-dependency-path: package-lock.json diff --git a/.prettierignore b/.prettierignore index 2ee4c22..5a73fd5 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1 +1,2 @@ src/main.tsx +.github/workflows/* diff --git a/package-lock.json b/package-lock.json index 0d6c685..e632d51 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3460,7 +3460,9 @@ } }, "node_modules/brace-expansion": { - "version": "2.1.1", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", + "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" diff --git a/playwright.config.ts b/playwright.config.ts index 17e226d..b17910d 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -24,9 +24,9 @@ export default defineConfig({ webServer: process.env.E2E_SKIP_WEBSERVER ? undefined : { - command: "npm run dev -- --host 127.0.0.1", - url: baseURL + "/dashboard/login", - reuseExistingServer: !process.env.CI, - timeout: 120_000, - }, + command: "npm run dev -- --host 127.0.0.1", + url: baseURL + "/dashboard/login", + reuseExistingServer: !process.env.CI, + timeout: 120_000, + }, }) diff --git a/src/api/api-tokens.ts b/src/api/api-tokens.ts index 9fcb9b9..da84b1e 100644 --- a/src/api/api-tokens.ts +++ b/src/api/api-tokens.ts @@ -47,20 +47,56 @@ export const deleteApiToken = async (id: number): Promise => { } export const SCOPE_OPTIONS = [ - { value: "nezha:inventory:read", label: "Inventory: read", desc: "List servers & groups (server.list)" }, - { value: "nezha:inventory:delete", label: "Inventory: delete", desc: "Delete servers & server groups" }, + { + value: "nezha:inventory:read", + label: "Inventory: read", + desc: "List servers & groups (server.list)", + }, + { + value: "nezha:inventory:delete", + label: "Inventory: delete", + desc: "Delete servers & server groups", + }, { value: "nezha:inventory:*", label: "Inventory: all", desc: "List + delete servers & groups" }, - { value: "nezha:server:read", label: "Server: read", desc: "Inspect a server, read files & metrics (needs server id)" }, + { + value: "nezha:server:read", + label: "Server: read", + desc: "Inspect a server, read files & metrics (needs server id)", + }, { value: "nezha:server:write", label: "Server: write", desc: "Edit a server, push files" }, { value: "nezha:server:delete", label: "Server: delete", desc: "Delete files on a server" }, { value: "nezha:server:exec", label: "Server: exec", desc: "Run shell commands on servers" }, - { value: "nezha:server:*", label: "Server: all", desc: "Every server operation (read+write+delete+exec)" }, - { value: "nezha:service:read", label: "Service monitor: read", desc: "List service monitors & history" }, - { value: "nezha:service:write", label: "Service monitor: write", desc: "Create / edit service monitors" }, - { value: "nezha:service:delete", label: "Service monitor: delete", desc: "Delete service monitors" }, - { value: "nezha:service:*", label: "Service monitor: all", desc: "Every service monitor permission" }, + { + value: "nezha:server:*", + label: "Server: all", + desc: "Every server operation (read+write+delete+exec)", + }, + { + value: "nezha:service:read", + label: "Service monitor: read", + desc: "List service monitors & history", + }, + { + value: "nezha:service:write", + label: "Service monitor: write", + desc: "Create / edit service monitors", + }, + { + value: "nezha:service:delete", + label: "Service monitor: delete", + desc: "Delete service monitors", + }, + { + value: "nezha:service:*", + label: "Service monitor: all", + desc: "Every service monitor permission", + }, { value: "nezha:alertrule:read", label: "Alert rule: read", desc: "List alert rules" }, - { value: "nezha:alertrule:write", label: "Alert rule: write", desc: "Create / edit alert rules" }, + { + value: "nezha:alertrule:write", + label: "Alert rule: write", + desc: "Create / edit alert rules", + }, { value: "nezha:alertrule:delete", label: "Alert rule: delete", desc: "Delete alert rules" }, { value: "nezha:alertrule:*", label: "Alert rule: all", desc: "Every alert-rule permission" }, { value: "nezha:cron:read", label: "Cron: read", desc: "List scheduled tasks" }, @@ -69,13 +105,41 @@ export const SCOPE_OPTIONS = [ { value: "nezha:cron:exec", label: "Cron: trigger", desc: "Manually trigger scheduled tasks" }, { value: "nezha:cron:*", label: "Cron: all", desc: "Every cron permission" }, { value: "nezha:notification:read", label: "Notification: read", desc: "List notifications" }, - { value: "nezha:notification:write", label: "Notification: write", desc: "Create / edit notifications" }, - { value: "nezha:notification:delete", label: "Notification: delete", desc: "Delete notifications" }, - { value: "nezha:notification:*", label: "Notification: all", desc: "Every notification permission" }, - { value: "nezha:notification-group:read", label: "Notification group: read", desc: "List notification groups" }, - { value: "nezha:notification-group:write", label: "Notification group: write", desc: "Create / edit groups" }, - { value: "nezha:notification-group:delete", label: "Notification group: delete", desc: "Delete groups" }, - { value: "nezha:notification-group:*", label: "Notification group: all", desc: "Every notification-group permission" }, + { + value: "nezha:notification:write", + label: "Notification: write", + desc: "Create / edit notifications", + }, + { + value: "nezha:notification:delete", + label: "Notification: delete", + desc: "Delete notifications", + }, + { + value: "nezha:notification:*", + label: "Notification: all", + desc: "Every notification permission", + }, + { + value: "nezha:notification-group:read", + label: "Notification group: read", + desc: "List notification groups", + }, + { + value: "nezha:notification-group:write", + label: "Notification group: write", + desc: "Create / edit groups", + }, + { + value: "nezha:notification-group:delete", + label: "Notification group: delete", + desc: "Delete groups", + }, + { + value: "nezha:notification-group:*", + label: "Notification group: all", + desc: "Every notification-group permission", + }, { value: "nezha:ddns:read", label: "DDNS: read", desc: "List DDNS profiles" }, { value: "nezha:ddns:write", label: "DDNS: write", desc: "Create / edit DDNS profiles" }, { value: "nezha:ddns:delete", label: "DDNS: delete", desc: "Delete DDNS profiles" }, @@ -86,21 +150,27 @@ export const SCOPE_OPTIONS = [ { value: "nezha:nat:*", label: "NAT: all", desc: "Every NAT permission" }, { value: "nezha:transfer:read", label: "Transfer: read", desc: "Read server transfer state" }, { value: "nezha:transfer:write", label: "Transfer: write", desc: "Cancel / retry transfers" }, - { value: "nezha:transfer:delete", label: "Transfer: delete", desc: "Delete server transfer records" }, + { + value: "nezha:transfer:delete", + label: "Transfer: delete", + desc: "Delete server transfer records", + }, { value: "nezha:transfer:*", label: "Transfer: all", desc: "Every transfer permission" }, - { value: "nezha:admin:*", label: "Admin: all (admin only)", desc: "User / WAF / Setting / Online-user management" }, + { + value: "nezha:admin:*", + label: "Admin: all (admin only)", + desc: "User / WAF / Setting / Online-user management", + }, { value: "nezha:*", label: "Everything (admin only)", desc: "Full access to all resources" }, ] as const export type Scope = (typeof SCOPE_OPTIONS)[number]["value"] export type ParseServerIDsResult = - | { ok: true; value: number[] | undefined } - | { ok: false; error: string } + { ok: true; value: number[] | undefined } | { ok: false; error: string } export type ParseExpiresInDaysResult = - | { ok: true; value: number | undefined } - | { ok: false; error: string } + { ok: true; value: number | undefined } | { ok: false; error: string } // Validates the "expires in days" field before it is sent to the backend. // The backend model field is `ExpiresInDays int`, so a fractional value would @@ -130,7 +200,8 @@ export function parseServerIDsInput(raw: string): ParseServerIDsResult { // Server IDs are uint64 on the backend; reject values that lose // precision as a JS number, otherwise the PAT could bind to a // different server than the operator typed. - if (!Number.isSafeInteger(n) || n <= 0) return { ok: false, error: `invalid server id: ${p}` } + if (!Number.isSafeInteger(n) || n <= 0) + return { ok: false, error: `invalid server id: ${p}` } if (seen.has(n)) continue seen.add(n) out.push(n) diff --git a/src/api/server.ts b/src/api/server.ts index 6a16733..4a2d5ca 100644 --- a/src/api/server.ts +++ b/src/api/server.ts @@ -26,7 +26,11 @@ export const deleteServer = async (id: number[]): Promise => { export const batchMoveServer = async ( data: ModelBatchMoveServerForm, ): Promise => { - return fetcher(FetcherMethod.POST, "/api/v1/batch-move/server", data) + return fetcher( + FetcherMethod.POST, + "/api/v1/batch-move/server", + data, + ) } export const forceUpdateServer = async (id: number[]): Promise => { diff --git a/src/components/alert-rule.tsx b/src/components/alert-rule.tsx index a399eb9..c081c29 100644 --- a/src/components/alert-rule.tsx +++ b/src/components/alert-rule.tsx @@ -102,22 +102,22 @@ export const AlertRuleCard: React.FC = ({ data, mutate }) => resolver: zodResolver(alertRuleFormSchema), defaultValues: data ? { - ...data, - rules_raw: JSON.stringify(data.rules), - fail_trigger_tasks_raw: conv.arrToStr(data.fail_trigger_tasks), - recover_trigger_tasks_raw: conv.arrToStr(data.recover_trigger_tasks), - } + ...data, + rules_raw: JSON.stringify(data.rules), + fail_trigger_tasks_raw: conv.arrToStr(data.fail_trigger_tasks), + recover_trigger_tasks_raw: conv.arrToStr(data.recover_trigger_tasks), + } : { - name: "", - rules_raw: "", - rules: [], - fail_trigger_tasks: [], - fail_trigger_tasks_raw: "", - recover_trigger_tasks: [], - recover_trigger_tasks_raw: "", - notification_group_id: 0, - trigger_mode: 0, - }, + name: "", + rules_raw: "", + rules: [], + fail_trigger_tasks: [], + fail_trigger_tasks_raw: "", + recover_trigger_tasks: [], + recover_trigger_tasks_raw: "", + notification_group_id: 0, + trigger_mode: 0, + }, resetOptions: { keepDefaultValues: false, }, @@ -195,10 +195,7 @@ export const AlertRuleCard: React.FC = ({ data, mutate }) =>
- + = ({ data, mutate }) => ...next[idx], min: e.target.value ? Number( - e.target - .value, - ) + e.target + .value, + ) : undefined, } setRulesUI(next) @@ -392,9 +389,9 @@ export const AlertRuleCard: React.FC = ({ data, mutate }) => ...next[idx], max: e.target.value ? Number( - e.target - .value, - ) + e.target + .value, + ) : undefined, } setRulesUI(next) @@ -456,8 +453,8 @@ export const AlertRuleCard: React.FC = ({ data, mutate }) => try { return r.ignore ? JSON.stringify( - r.ignore, - ) + r.ignore, + ) : "" } catch { return "" @@ -468,8 +465,8 @@ export const AlertRuleCard: React.FC = ({ data, mutate }) => try { const obj = e.target.value ? JSON.parse( - e.target.value, - ) + e.target.value, + ) : undefined next[idx] = { ...next[idx], @@ -523,9 +520,9 @@ export const AlertRuleCard: React.FC = ({ data, mutate }) => cycle_interval: e.target .value ? Number( - e.target - .value, - ) + e.target + .value, + ) : undefined, } setRulesUI(next) diff --git a/src/components/cron.tsx b/src/components/cron.tsx index 61035cb..07be50b 100644 --- a/src/components/cron.tsx +++ b/src/components/cron.tsx @@ -72,25 +72,25 @@ export const CronCard: React.FC = ({ data, mutate }) => { resolver: zodResolver(cronFormSchema), defaultValues: data ? { - task_type: data.task_type ?? 0, - name: data.name ?? "", - scheduler: data.scheduler ?? "", - command: data.command ?? "", - servers: data.servers ?? [], - cover: data.cover ?? 0, - push_successful: data.push_successful ?? false, - notification_group_id: data.notification_group_id ?? 0, - } + task_type: data.task_type ?? 0, + name: data.name ?? "", + scheduler: data.scheduler ?? "", + command: data.command ?? "", + servers: data.servers ?? [], + cover: data.cover ?? 0, + push_successful: data.push_successful ?? false, + notification_group_id: data.notification_group_id ?? 0, + } : { - task_type: 0, - name: "", - scheduler: "", - command: "", - servers: [], - cover: 0, - push_successful: false, - notification_group_id: 0, - }, + task_type: 0, + name: "", + scheduler: "", + command: "", + servers: [], + cover: 0, + push_successful: false, + notification_group_id: 0, + }, resetOptions: { keepDefaultValues: false, }, diff --git a/src/components/ddns.tsx b/src/components/ddns.tsx index 43157c1..7d26106 100644 --- a/src/components/ddns.tsx +++ b/src/components/ddns.tsx @@ -76,37 +76,37 @@ export const DDNSCard: React.FC = ({ data, providers, mutate }) = resolver: zodResolver(ddnsFormSchema), defaultValues: data ? { - max_retries: data.max_retries ?? 3, - enable_ipv4: data.enable_ipv4 ?? false, - enable_ipv6: data.enable_ipv6 ?? false, - name: data.name ?? "", - provider: data.provider ?? "dummy", - domains: data.domains ?? [], - domains_raw: conv.arrToStr(data.domains ?? []), - access_id: data.access_id ?? "", - access_secret: data.access_secret ?? "", - webhook_url: data.webhook_url ?? "", - webhook_method: data.webhook_method, - webhook_request_type: data.webhook_request_type, - webhook_request_body: data.webhook_request_body ?? "", - webhook_headers: data.webhook_headers ?? "", - } + max_retries: data.max_retries ?? 3, + enable_ipv4: data.enable_ipv4 ?? false, + enable_ipv6: data.enable_ipv6 ?? false, + name: data.name ?? "", + provider: data.provider ?? "dummy", + domains: data.domains ?? [], + domains_raw: conv.arrToStr(data.domains ?? []), + access_id: data.access_id ?? "", + access_secret: data.access_secret ?? "", + webhook_url: data.webhook_url ?? "", + webhook_method: data.webhook_method, + webhook_request_type: data.webhook_request_type, + webhook_request_body: data.webhook_request_body ?? "", + webhook_headers: data.webhook_headers ?? "", + } : { - max_retries: 3, - enable_ipv4: false, - enable_ipv6: false, - name: "", - provider: "dummy", - domains: [], - domains_raw: "", - access_id: "", - access_secret: "", - webhook_url: "", - webhook_method: undefined, - webhook_request_type: undefined, - webhook_request_body: "", - webhook_headers: "", - }, + max_retries: 3, + enable_ipv4: false, + enable_ipv6: false, + name: "", + provider: "dummy", + domains: [], + domains_raw: "", + access_id: "", + access_secret: "", + webhook_url: "", + webhook_method: undefined, + webhook_request_type: undefined, + webhook_request_body: "", + webhook_headers: "", + }, resetOptions: { keepDefaultValues: false, }, diff --git a/src/components/fm.tsx b/src/components/fm.tsx index 0286c3f..9b87147 100644 --- a/src/components/fm.tsx +++ b/src/components/fm.tsx @@ -71,7 +71,10 @@ const arraysEqual = (a: Uint8Array, b: Uint8Array) => { return true } -export const FMComponent: React.FC = ({ wsUrl, ...props }) => { +export const FMComponent: React.FC = ({ + wsUrl, + ...props +}) => { const { t } = useTranslation() const fmRef = useRef(null) const wsRef = useRef(null) @@ -162,30 +165,30 @@ export const FMComponent: React.FC = ({ useEffect(() => { worker.onmessage = async (event: MessageEvent) => { switch (event.data.type) { - case FMWorkerOpcode.Error: { - console.error("Error from worker", event.data.error) - break - } - case FMWorkerOpcode.Progress: { - handleReady.current = true - break - } - case FMWorkerOpcode.Result: { - handleReady.current = false - - if (event.data.blob && event.data.fileName) { - const url = URL.createObjectURL(event.data.blob) - const anchor = document.createElement("a") - anchor.href = url - anchor.download = event.data.fileName - anchor.click() - URL.revokeObjectURL(url) + case FMWorkerOpcode.Error: { + console.error("Error from worker", event.data.error) + break } + case FMWorkerOpcode.Progress: { + handleReady.current = true + break + } + case FMWorkerOpcode.Result: { + handleReady.current = false - firstChunk.current = true - if (dOpen) setdOpen(false) - break - } + if (event.data.blob && event.data.fileName) { + const url = URL.createObjectURL(event.data.blob) + const anchor = document.createElement("a") + anchor.href = url + anchor.download = event.data.fileName + anchor.click() + URL.revokeObjectURL(url) + } + + firstChunk.current = true + if (dOpen) setdOpen(false) + break + } } } @@ -342,7 +345,10 @@ export const FMComponent: React.FC = ({ try { await copyToClipboard(formatPath(currentPath)) } catch (error) { - const description = error instanceof Error ? error.message : t("Results.UnExpectedError") + const description = + error instanceof Error + ? error.message + : t("Results.UnExpectedError") toast("FM" + " " + t("Error"), { description, }) diff --git a/src/components/install-commands.tsx b/src/components/install-commands.tsx index c33f6fb..d11fc92 100644 --- a/src/components/install-commands.tsx +++ b/src/components/install-commands.tsx @@ -154,15 +154,15 @@ const generateCommand = ( const env_win = envWinParts.join("") switch (type) { - case OSTypes.Linux: - case OSTypes.macOS: { - return `curl -L https://raw.githubusercontent.com/nezhahq/scripts/main/agent/install.sh -o agent.sh && chmod +x agent.sh && env ${env} ./agent.sh` - } - case OSTypes.Windows: { - return `${env_win} [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Ssl3 -bor [Net.SecurityProtocolType]::Tls -bor [Net.SecurityProtocolType]::Tls11 -bor [Net.SecurityProtocolType]::Tls12;set-ExecutionPolicy RemoteSigned;Invoke-WebRequest https://raw.githubusercontent.com/nezhahq/scripts/main/agent/install.ps1 -OutFile C:\\install.ps1;powershell.exe C:\\install.ps1` - } - default: { - throw new Error(`Unknown OS: ${type}`) - } + case OSTypes.Linux: + case OSTypes.macOS: { + return `curl -L https://raw.githubusercontent.com/nezhahq/scripts/main/agent/install.sh -o agent.sh && chmod +x agent.sh && env ${env} ./agent.sh` + } + case OSTypes.Windows: { + return `${env_win} [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Ssl3 -bor [Net.SecurityProtocolType]::Tls -bor [Net.SecurityProtocolType]::Tls11 -bor [Net.SecurityProtocolType]::Tls12;set-ExecutionPolicy RemoteSigned;Invoke-WebRequest https://raw.githubusercontent.com/nezhahq/scripts/main/agent/install.ps1 -OutFile C:\\install.ps1;powershell.exe C:\\install.ps1` + } + default: { + throw new Error(`Unknown OS: ${type}`) + } } } diff --git a/src/components/nat.tsx b/src/components/nat.tsx index f8daa82..d843966 100644 --- a/src/components/nat.tsx +++ b/src/components/nat.tsx @@ -55,19 +55,19 @@ export const NATCard: React.FC = ({ data, mutate }) => { resolver: zodResolver(natFormSchema), defaultValues: data ? { - name: data.name ?? "", - enabled: data.enabled ?? false, - server_id: data.server_id ?? 0, - host: data.host ?? "", - domain: data.domain ?? "", - } + name: data.name ?? "", + enabled: data.enabled ?? false, + server_id: data.server_id ?? 0, + host: data.host ?? "", + domain: data.domain ?? "", + } : { - name: "", - enabled: false, - server_id: 0, - host: "", - domain: "", - }, + name: "", + enabled: false, + server_id: 0, + host: "", + domain: "", + }, resetOptions: { keepDefaultValues: false, }, diff --git a/src/components/notification-group.tsx b/src/components/notification-group.tsx index 6657daf..f9d3a73 100644 --- a/src/components/notification-group.tsx +++ b/src/components/notification-group.tsx @@ -48,13 +48,13 @@ export const NotificationGroupCard: React.FC = ({ da resolver: zodResolver(notificationGroupFormSchema), defaultValues: data ? { - name: data.group.name, - notifications: data.notifications, - } + name: data.group.name, + notifications: data.notifications, + } : { - name: "", - notifications: [], - }, + name: "", + notifications: [], + }, resetOptions: { keepDefaultValues: false, }, diff --git a/src/components/notifier.tsx b/src/components/notifier.tsx index fd371e2..fb38f05 100644 --- a/src/components/notifier.tsx +++ b/src/components/notifier.tsx @@ -64,34 +64,35 @@ export const NotifierCard: React.FC = ({ data, mutate }) => { const { t } = useTranslation() type NotificationFormInput = z.input type NotificationFormData = z.output - type NotificationDefaults = ModelNotification & Partial> + type NotificationDefaults = ModelNotification & + Partial> const notificationDefaults: NotificationDefaults | undefined = data const form = useForm({ resolver: zodResolver(notificationFormSchema), defaultValues: data ? { - name: data.name ?? "", - url: data.url ?? "", - request_method: data.request_method ?? 1, - request_type: data.request_type ?? 1, - request_header: data.request_header ?? "", - request_body: data.request_body ?? "", - verify_tls: data.verify_tls ?? false, - skip_check: notificationDefaults?.skip_check ?? false, - format_metric_units: data.format_metric_units ?? false, - } + name: data.name ?? "", + url: data.url ?? "", + request_method: data.request_method ?? 1, + request_type: data.request_type ?? 1, + request_header: data.request_header ?? "", + request_body: data.request_body ?? "", + verify_tls: data.verify_tls ?? false, + skip_check: notificationDefaults?.skip_check ?? false, + format_metric_units: data.format_metric_units ?? false, + } : { - name: "", - url: "", - request_method: 1, - request_type: 1, - request_header: "", - request_body: "", - verify_tls: false, - skip_check: false, - format_metric_units: false, - }, + name: "", + url: "", + request_method: 1, + request_type: 1, + request_header: "", + request_body: "", + verify_tls: false, + skip_check: false, + format_metric_units: false, + }, resetOptions: { keepDefaultValues: false, }, @@ -133,10 +134,7 @@ export const NotifierCard: React.FC = ({ data, mutate }) => { - + = ({ data, mutate } resolver: zodResolver(serverGroupFormSchema), defaultValues: data ? { - name: data.group.name, - servers: data.servers, - } + name: data.group.name, + servers: data.servers, + } : { - name: "", - servers: [], - }, + name: "", + servers: [], + }, resetOptions: { keepDefaultValues: false, }, diff --git a/src/components/server.tsx b/src/components/server.tsx index d73003c..dd81e64 100644 --- a/src/components/server.tsx +++ b/src/components/server.tsx @@ -467,8 +467,10 @@ export const ServerCard: React.FC = ({ data, mutate }) => { {publicNoteObj.billingDataMod ?.startDate ? new Date( - publicNoteObj.billingDataMod!.startDate!, - ).toLocaleDateString() + publicNoteObj + .billingDataMod! + .startDate!, + ).toLocaleDateString() : "YYYY-MM-DD"} @@ -492,8 +494,10 @@ export const ServerCard: React.FC = ({ data, mutate }) => { .billingDataMod ?.startDate ? new Date( - publicNoteObj.billingDataMod!.startDate!, - ) + publicNoteObj + .billingDataMod! + .startDate!, + ) : undefined } onSelect={(d) => { @@ -560,17 +564,17 @@ export const ServerCard: React.FC = ({ data, mutate }) => { {publicNoteObj.billingDataMod ?.endDate ? publicNoteObj - .billingDataMod - ?.endDate === + .billingDataMod + ?.endDate === "0000-00-00T23:59:59+08:00" ? t( - "PublicNote.NoExpiry", - ) + "PublicNote.NoExpiry", + ) : new Date( publicNoteObj .billingDataMod ?.endDate as string, - ).toLocaleDateString() + ).toLocaleDateString() : "YYYY-MM-DD"} @@ -601,7 +605,7 @@ export const ServerCard: React.FC = ({ data, mutate }) => { publicNoteObj .billingDataMod ?.endDate as string, - ) + ) : undefined } onSelect={(d) => { diff --git a/src/components/service.tsx b/src/components/service.tsx index 4bdd296..978c7c6 100644 --- a/src/components/service.tsx +++ b/src/components/service.tsx @@ -80,28 +80,28 @@ export const ServiceCard: React.FC = ({ data, mutate }) => { resolver: zodResolver(serviceFormSchema) as any, defaultValues: data ? { - ...data, - fail_trigger_tasks_raw: conv.arrToStr(data.fail_trigger_tasks), - recover_trigger_tasks_raw: conv.arrToStr(data.recover_trigger_tasks), - skip_servers_raw: conv.recordToStrArr(data.skip_servers ? data.skip_servers : {}), - } + ...data, + fail_trigger_tasks_raw: conv.arrToStr(data.fail_trigger_tasks), + recover_trigger_tasks_raw: conv.arrToStr(data.recover_trigger_tasks), + skip_servers_raw: conv.recordToStrArr(data.skip_servers ? data.skip_servers : {}), + } : { - type: 1, - cover: 0, - display_index: 0, - name: "", - target: "", - max_latency: 0.0, - min_latency: 0.0, - duration: 30, - notification_group_id: 0, - fail_trigger_tasks: [], - fail_trigger_tasks_raw: "", - recover_trigger_tasks: [], - recover_trigger_tasks_raw: "", - skip_servers: {}, - skip_servers_raw: [], - }, + type: 1, + cover: 0, + display_index: 0, + name: "", + target: "", + max_latency: 0.0, + min_latency: 0.0, + duration: 30, + notification_group_id: 0, + fail_trigger_tasks: [], + fail_trigger_tasks_raw: "", + recover_trigger_tasks: [], + recover_trigger_tasks_raw: "", + skip_servers: {}, + skip_servers_raw: [], + }, resetOptions: { keepDefaultValues: false, }, diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx index f7c1d40..658a142 100644 --- a/src/components/ui/badge.tsx +++ b/src/components/ui/badge.tsx @@ -23,8 +23,7 @@ const badgeVariants = cva( ) export interface BadgeProps - extends HTMLAttributes, - VariantProps {} + extends HTMLAttributes, VariantProps {} function Badge({ className, variant, ...props }: BadgeProps) { return
diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx index f09e440..2571352 100644 --- a/src/components/ui/button.tsx +++ b/src/components/ui/button.tsx @@ -31,8 +31,7 @@ const buttonVariants = cva( ) export interface ButtonProps - extends ButtonHTMLAttributes, - VariantProps { + extends ButtonHTMLAttributes, VariantProps { asChild?: boolean } diff --git a/src/components/ui/form.tsx b/src/components/ui/form.tsx index c1d8135..8c5c71c 100644 --- a/src/components/ui/form.tsx +++ b/src/components/ui/form.tsx @@ -35,8 +35,8 @@ const FormField = < TFieldValues extends FieldValues = FieldValues, TName extends FieldPath = FieldPath, >({ - ...props - }: ControllerProps) => { + ...props +}: ControllerProps) => { return ( diff --git a/src/components/ui/icon.tsx b/src/components/ui/icon.tsx index 1ef9e0b..5f1709a 100644 --- a/src/components/ui/icon.tsx +++ b/src/components/ui/icon.tsx @@ -1,9 +1,7 @@ import { useEffect, useState } from "react" type SVGProps = React.ComponentPropsWithoutRef<"svg"> -type IconMarkupState = - | { status: "ready"; icon: ParsedIconMarkup } - | { status: "missing" } +type IconMarkupState = { status: "ready"; icon: ParsedIconMarkup } | { status: "missing" } type ParsedIconMarkup = { title?: string @@ -26,7 +24,10 @@ const iconLoadersBySlug = Object.fromEntries( const iconMarkupCache = new Map() function toProviderSlug(provider: string) { - return provider.trim().replace(/[^a-z0-9]+/gi, "").toLowerCase() + return provider + .trim() + .replace(/[^a-z0-9]+/gi, "") + .toLowerCase() } function loadProviderIconMarkup(provider: string) { diff --git a/src/components/xui/icon-button.tsx b/src/components/xui/icon-button.tsx index 13b64a7..4c413e5 100644 --- a/src/components/xui/icon-button.tsx +++ b/src/components/xui/icon-button.tsx @@ -53,60 +53,60 @@ export const IconButton = forwardRef((props, > {(() => { switch (props.icon) { - case "clipboard": { - return - } - case "check": { - return - } - case "edit": { - return - } - case "trash": { - return - } - case "plus": { - return - } - case "terminal": { - return - } - case "update": { - return - } - case "folder-closed": { - return - } - case "play": { - return - } - case "download": { - return - } - case "upload": { - return - } - case "menu": { - return - } - case "ban": { - return - } - case "expand": { - return - } - case "cog": { - return - } - case "minus": { - return - } - case "user-pen": { - return - } - case "more": { - return - } + case "clipboard": { + return + } + case "check": { + return + } + case "edit": { + return + } + case "trash": { + return + } + case "plus": { + return + } + case "terminal": { + return + } + case "update": { + return + } + case "folder-closed": { + return + } + case "play": { + return + } + case "download": { + return + } + case "upload": { + return + } + case "menu": { + return + } + case "ban": { + return + } + case "expand": { + return + } + case "cog": { + return + } + case "minus": { + return + } + case "user-pen": { + return + } + case "more": { + return + } } })()} diff --git a/src/components/xui/multi-select.tsx b/src/components/xui/multi-select.tsx index 5dcb922..97b33d5 100644 --- a/src/components/xui/multi-select.tsx +++ b/src/components/xui/multi-select.tsx @@ -67,7 +67,8 @@ const multiSelectVariants = cva( * Props for MultiSelect component */ interface MultiSelectProps - extends React.ButtonHTMLAttributes, + extends + React.ButtonHTMLAttributes, VariantProps { /** * An array of option objects to be displayed in the multi-select component. diff --git a/src/components/xui/overlayless-sheet.tsx b/src/components/xui/overlayless-sheet.tsx index 765edb1..e31b188 100644 --- a/src/components/xui/overlayless-sheet.tsx +++ b/src/components/xui/overlayless-sheet.tsx @@ -37,7 +37,8 @@ const sheetVariants = cva( ) interface SheetContentProps - extends ComponentPropsWithoutRef, + extends + ComponentPropsWithoutRef, VariantProps { setOpen: Dispatch> } diff --git a/src/components/xui/virtulized-data-table.tsx b/src/components/xui/virtulized-data-table.tsx index 9467932..ab1f719 100644 --- a/src/components/xui/virtulized-data-table.tsx +++ b/src/components/xui/virtulized-data-table.tsx @@ -158,9 +158,9 @@ export function DataTable({ {...{ style: header.column.getCanSort() ? { - cursor: "pointer", - userSelect: "none", - } + cursor: "pointer", + userSelect: "none", + } : {}, onClick: header.column.getToggleSortingHandler(), diff --git a/src/hooks/useAuth.tsx b/src/hooks/useAuth.tsx index 1beec60..64485a7 100644 --- a/src/hooks/useAuth.tsx +++ b/src/hooks/useAuth.tsx @@ -53,27 +53,30 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => { const navigate = useNavigate() - const login = useCallback(async (username: string, password: string) => { - try { - await loginRequest(username, password) - const user = await getProfile() - authEpoch.current++ - user.role = normalizeRole(user.role) - setProfile(user) - navigate("/dashboard") - } catch (error: any) { - const msg = error?.message - if (msg === "ApiErrorUnauthorized" || msg === "Unauthorized") { - toast(t("InvalidUsernameOrPassword")) - } else { - toast(msg || t("NetworkError")) + const login = useCallback( + async (username: string, password: string) => { + try { + await loginRequest(username, password) + const user = await getProfile() + authEpoch.current++ + user.role = normalizeRole(user.role) + setProfile(user) + navigate("/dashboard") + } catch (error: any) { + const msg = error?.message + if (msg === "ApiErrorUnauthorized" || msg === "Unauthorized") { + toast(t("InvalidUsernameOrPassword")) + } else { + toast(msg || t("NetworkError")) + } + } finally { + // An explicit login resolves auth regardless of the still-pending + // mount probe; clear loading so ProtectedRoute stops blanking. + setLoading(false) } - } finally { - // An explicit login resolves auth regardless of the still-pending - // mount probe; clear loading so ProtectedRoute stops blanking. - setLoading(false) - } - }, [navigate, setProfile, t]) + }, + [navigate, setProfile, t], + ) const loginOauth2 = useCallback(async () => { try { diff --git a/src/index.css b/src/index.css index 9e2c894..23c3a94 100644 --- a/src/index.css +++ b/src/index.css @@ -117,5 +117,5 @@ body, } ::-webkit-scrollbar-thumb { - @apply rounded-full border-[1px] border-solid border-transparent bg-border bg-clip-padding; + @apply bg-border rounded-full border-[1px] border-solid border-transparent bg-clip-padding; } diff --git a/src/lib/fm.ts b/src/lib/fm.ts index a2aaaf6..11a13eb 100644 --- a/src/lib/fm.ts +++ b/src/lib/fm.ts @@ -15,53 +15,53 @@ onmessage = async function (event) { const { operation, arrayBuffer, fileName } = event.data switch (operation) { - case Operation.WriteHeader: { - const dataView = new DataView(arrayBuffer) - expectedLength = Number(dataView.getBigUint64(4, false)) - receivedLength = 0 + case Operation.WriteHeader: { + const dataView = new DataView(arrayBuffer) + expectedLength = Number(dataView.getBigUint64(4, false)) + receivedLength = 0 - // Create a new temporary file - root = await navigator.storage.getDirectory() - draftHandle = await root.getFileHandle(fileName, { create: true }) - accessHandle = await draftHandle.createSyncAccessHandle() + // Create a new temporary file + root = await navigator.storage.getDirectory() + draftHandle = await root.getFileHandle(fileName, { create: true }) + accessHandle = await draftHandle.createSyncAccessHandle() - // Inform that file handle is created - const dataChunk = arrayBuffer.slice(12) - receivedLength += dataChunk.byteLength - accessHandle.write(dataChunk, { at: 0 }) - const progress = "got handle" - postMessage({ type: 1, progress: progress }) - break - } - case Operation.WriteChunks: { - if (!accessHandle) { - throw new Error("accessHandle is undefined") + // Inform that file handle is created + const dataChunk = arrayBuffer.slice(12) + receivedLength += dataChunk.byteLength + accessHandle.write(dataChunk, { at: 0 }) + const progress = "got handle" + postMessage({ type: 1, progress: progress }) + break } - - accessHandle.write(arrayBuffer, { at: receivedLength }) - receivedLength += arrayBuffer.byteLength - - if (receivedLength === expectedLength) { - accessHandle.flush() - accessHandle.close() - - const fileBlob = await draftHandle.getFile() - const blob = new Blob([fileBlob], { type: "application/octet-stream" }) - - postMessage({ type: 2, blob: blob, fileName: fileName }) - } - break - } - case Operation.DeleteFiles: { - for await (const [name, handle] of root.entries()) { - if (handle.kind === "file") { - await root.removeEntry(name) - } else if (handle.kind === "directory") { - await root.removeEntry(name, { recursive: true }) + case Operation.WriteChunks: { + if (!accessHandle) { + throw new Error("accessHandle is undefined") } + + accessHandle.write(arrayBuffer, { at: receivedLength }) + receivedLength += arrayBuffer.byteLength + + if (receivedLength === expectedLength) { + accessHandle.flush() + accessHandle.close() + + const fileBlob = await draftHandle.getFile() + const blob = new Blob([fileBlob], { type: "application/octet-stream" }) + + postMessage({ type: 2, blob: blob, fileName: fileName }) + } + break + } + case Operation.DeleteFiles: { + for await (const [name, handle] of root.entries()) { + if (handle.kind === "file") { + await root.removeEntry(name) + } else if (handle.kind === "directory") { + await root.removeEntry(name, { recursive: true }) + } + } + break } - break - } } } catch (error) { if (error instanceof Error) postMessage({ type: 0, error: error.message }) diff --git a/src/routes/alert-rule.tsx b/src/routes/alert-rule.tsx index 7c47548..11ac8f4 100644 --- a/src/routes/alert-rule.tsx +++ b/src/routes/alert-rule.tsx @@ -165,9 +165,9 @@ export default function AlertRulePage() { {header.isPlaceholder ? null : flexRender( - header.column.columnDef.header, - header.getContext(), - )} + header.column.columnDef.header, + header.getContext(), + )} ) })} diff --git a/src/routes/api-tokens.tsx b/src/routes/api-tokens.tsx index 0a31537..c44c7c2 100644 --- a/src/routes/api-tokens.tsx +++ b/src/routes/api-tokens.tsx @@ -98,7 +98,10 @@ export default function ApiTokensPage() { ) : !data || data.length === 0 ? ( - + {t("NoResults")} @@ -119,10 +122,14 @@ export default function ApiTokensPage() {
- {tok.server_ids?.length ? tok.server_ids.join(", ") : t("ApiTokenAllServers")} + {tok.server_ids?.length + ? tok.server_ids.join(", ") + : t("ApiTokenAllServers")} - {tok.expires_at ? new Date(tok.expires_at).toLocaleString() : t("ApiTokenNever")} + {tok.expires_at + ? new Date(tok.expires_at).toLocaleString() + : t("ApiTokenNever")} {tok.last_used_at @@ -239,7 +246,8 @@ function CreateApiTokenDialog({
{SCOPE_OPTIONS.map((s) => { - const adminOnly = s.value === "nezha:*" || s.value === "nezha:admin:*" + const adminOnly = + s.value === "nezha:*" || s.value === "nezha:admin:*" const disabled = adminOnly && !isAdmin return ( ) diff --git a/src/routes/cron.tsx b/src/routes/cron.tsx index 6278a84..e37a4e7 100644 --- a/src/routes/cron.tsx +++ b/src/routes/cron.tsx @@ -110,15 +110,15 @@ export default function CronPage() {
{(() => { switch (s.cover) { - case 0: { - return {t("IgnoreAll")} - } - case 1: { - return {t("CoverAll")} - } - case 2: { - return {t("OnAlert")} - } + case 0: { + return {t("IgnoreAll")} + } + case 1: { + return {t("CoverAll")} + } + case 2: { + return {t("OnAlert")} + } } })()}
@@ -233,9 +233,9 @@ export default function CronPage() { {header.isPlaceholder ? null : flexRender( - header.column.columnDef.header, - header.getContext(), - )} + header.column.columnDef.header, + header.getContext(), + )} ) })} diff --git a/src/routes/ddns.tsx b/src/routes/ddns.tsx index a9fb833..313f35c 100644 --- a/src/routes/ddns.tsx +++ b/src/routes/ddns.tsx @@ -170,9 +170,9 @@ export default function DDNSPage() { {header.isPlaceholder ? null : flexRender( - header.column.columnDef.header, - header.getContext(), - )} + header.column.columnDef.header, + header.getContext(), + )} ) })} diff --git a/src/routes/login.tsx b/src/routes/login.tsx index b341429..a0e6563 100644 --- a/src/routes/login.tsx +++ b/src/routes/login.tsx @@ -108,14 +108,14 @@ function Login() { {settingData?.config?.oauth2_providers && settingData?.config?.oauth2_providers.length > 0 && ( -
- -
+
+ +
OAuth2 -
- -
- )} +
+ +
+ )}
{settingData?.config?.oauth2_providers?.map((p: string) => ( diff --git a/src/routes/nat.tsx b/src/routes/nat.tsx index ede8c5d..9d35dfa 100644 --- a/src/routes/nat.tsx +++ b/src/routes/nat.tsx @@ -153,9 +153,9 @@ export default function NATPage() { {header.isPlaceholder ? null : flexRender( - header.column.columnDef.header, - header.getContext(), - )} + header.column.columnDef.header, + header.getContext(), + )} ) })} diff --git a/src/routes/notification-group.tsx b/src/routes/notification-group.tsx index b278da7..523e0ac 100644 --- a/src/routes/notification-group.tsx +++ b/src/routes/notification-group.tsx @@ -146,9 +146,9 @@ export default function NotificationGroupPage() { {header.isPlaceholder ? null : flexRender( - header.column.columnDef.header, - header.getContext(), - )} + header.column.columnDef.header, + header.getContext(), + )} ) })} diff --git a/src/routes/notification.tsx b/src/routes/notification.tsx index c0ccb87..d4bf20a 100644 --- a/src/routes/notification.tsx +++ b/src/routes/notification.tsx @@ -161,9 +161,9 @@ export default function NotificationPage() { {header.isPlaceholder ? null : flexRender( - header.column.columnDef.header, - header.getContext(), - )} + header.column.columnDef.header, + header.getContext(), + )} ) })} diff --git a/src/routes/online-user.tsx b/src/routes/online-user.tsx index 3b5461f..850f3ae 100644 --- a/src/routes/online-user.tsx +++ b/src/routes/online-user.tsx @@ -264,9 +264,9 @@ export default function OnlineUserPage() { {header.isPlaceholder ? null : flexRender( - header.column.columnDef.header, - header.getContext(), - )} + header.column.columnDef.header, + header.getContext(), + )} ) })} diff --git a/src/routes/server-group.tsx b/src/routes/server-group.tsx index 10131b0..98fec61 100644 --- a/src/routes/server-group.tsx +++ b/src/routes/server-group.tsx @@ -145,9 +145,9 @@ export default function ServerGroupPage() { {header.isPlaceholder ? null : flexRender( - header.column.columnDef.header, - header.getContext(), - )} + header.column.columnDef.header, + header.getContext(), + )} ) })} diff --git a/src/routes/server.tsx b/src/routes/server.tsx index ccfd600..3f3aa20 100644 --- a/src/routes/server.tsx +++ b/src/routes/server.tsx @@ -292,9 +292,9 @@ export default function ServerPage() { {header.isPlaceholder ? null : flexRender( - header.column.columnDef.header, - header.getContext(), - )} + header.column.columnDef.header, + header.getContext(), + )} ) })} diff --git a/src/routes/service.tsx b/src/routes/service.tsx index 79a4fb9..27d711a 100644 --- a/src/routes/service.tsx +++ b/src/routes/service.tsx @@ -90,12 +90,12 @@ export default function ServicePage() {
{(() => { switch (s.cover) { - case 0: { - return {t("CoverAll")} - } - case 1: { - return {t("IgnoreAll")} - } + case 0: { + return {t("CoverAll")} + } + case 1: { + return {t("IgnoreAll")} + } } })()}
@@ -199,9 +199,9 @@ export default function ServicePage() { {header.isPlaceholder ? null : flexRender( - header.column.columnDef.header, - header.getContext(), - )} + header.column.columnDef.header, + header.getContext(), + )} ) })} diff --git a/src/routes/settings.tsx b/src/routes/settings.tsx index 829f594..c53ac46 100644 --- a/src/routes/settings.tsx +++ b/src/routes/settings.tsx @@ -76,19 +76,19 @@ export default function SettingsPage() { resolver: zodResolver(settingFormSchema) as any, defaultValues: config ? { - ...config.config, - user_template: + ...config.config, + user_template: config.config?.user_template || Object.keys(config.frontend_templates?.filter((t) => !t.is_admin) || {})[0] || "user-dist", - } + } : { - ip_change_notification_group_id: 0, - cover: 1, - site_name: "", - language: "", - user_template: "user-dist", - }, + ip_change_notification_group_id: 0, + cover: 1, + site_name: "", + language: "", + user_template: "user-dist", + }, resetOptions: { keepDefaultValues: false, }, @@ -224,9 +224,13 @@ export default function SettingsPage() {
- {safeExternalHref(template.repository) ? ( + {safeExternalHref( + template.repository, + ) ? ( - +
diff --git a/src/routes/transfer.tsx b/src/routes/transfer.tsx index bb191a4..3e8fb6e 100644 --- a/src/routes/transfer.tsx +++ b/src/routes/transfer.tsx @@ -1,7 +1,7 @@ import { swrFetcher } from "@/api/api" import { cancelServerTransfer, retryServerTransfer } from "@/api/transfer" -import { Button } from "@/components/ui/button" import { Badge } from "@/components/ui/badge" +import { Button } from "@/components/ui/button" import { Table, TableBody, @@ -45,10 +45,7 @@ export default function TransferPage() { const { profile } = useAuth() const isAdmin = profile?.role === 0 const callerID = profile?.id - const { data, mutate, error } = useSWR( - "/api/v1/transfer", - swrFetcher, - ) + const { data, mutate, error } = useSWR("/api/v1/transfer", swrFetcher) useEffect(() => { if (error) { @@ -182,9 +179,7 @@ export default function TransferPage() { return (

{t("Transfer.Title")}

-

- {t("Transfer.PageHint")} -

+

{t("Transfer.PageHint")}

diff --git a/src/routes/user.tsx b/src/routes/user.tsx index 8c0fb70..e05c274 100644 --- a/src/routes/user.tsx +++ b/src/routes/user.tsx @@ -139,9 +139,9 @@ export default function UserPage() { {header.isPlaceholder ? null : flexRender( - header.column.columnDef.header, - header.getContext(), - )} + header.column.columnDef.header, + header.getContext(), + )} ) })} diff --git a/src/routes/waf.tsx b/src/routes/waf.tsx index d1c96de..c547ded 100644 --- a/src/routes/waf.tsx +++ b/src/routes/waf.tsx @@ -283,9 +283,9 @@ export default function WAFPage() { {header.isPlaceholder ? null : flexRender( - header.column.columnDef.header, - header.getContext(), - )} + header.column.columnDef.header, + header.getContext(), + )} ) })} diff --git a/src/test/api-tokens.test.tsx b/src/test/api-tokens.test.tsx index 7b48f46..79a507f 100644 --- a/src/test/api-tokens.test.tsx +++ b/src/test/api-tokens.test.tsx @@ -6,10 +6,13 @@ const realFetch = globalThis.fetch function mockFetch(payload: unknown, ok = true, success = true) { globalThis.fetch = vi.fn(async () => { - return new Response(JSON.stringify({ success, error: success ? "" : "boom", data: payload }), { - status: ok ? 200 : 500, - headers: { "Content-Type": "application/json" }, - }) + return new Response( + JSON.stringify({ success, error: success ? "" : "boom", data: payload }), + { + status: ok ? 200 : 500, + headers: { "Content-Type": "application/json" }, + }, + ) }) as unknown as typeof fetch } @@ -94,9 +97,9 @@ test("createApiToken POSTs /api/v1/api-tokens and serializes scopes / server_ids test("createApiToken surfaces server error via thrown Error", async () => { mockFetch(null, true, false) - await expect( - createApiToken({ name: "x", scopes: ["nezha:server:read"] }), - ).rejects.toThrow("boom") + await expect(createApiToken({ name: "x", scopes: ["nezha:server:read"] })).rejects.toThrow( + "boom", + ) }) test("listApiTokens normalizes null scopes to an empty array so the table cannot crash", async () => { @@ -107,9 +110,7 @@ test("listApiTokens normalizes null scopes to an empty array so the table cannot return new Response( JSON.stringify({ success: true, - data: [ - { id: 1, name: "legacy", scopes: null, created_at: "2025-01-01T00:00:00Z" }, - ], + data: [{ id: 1, name: "legacy", scopes: null, created_at: "2025-01-01T00:00:00Z" }], }), { status: 200, headers: { "Content-Type": "application/json" } }, ) diff --git a/src/test/auth-login-loading.test.tsx b/src/test/auth-login-loading.test.tsx index ef79291..32e7014 100644 --- a/src/test/auth-login-loading.test.tsx +++ b/src/test/auth-login-loading.test.tsx @@ -9,8 +9,7 @@ const setProfileSpy = vi.fn((p: any) => { vi.mock("./useMainStore", () => ({})) vi.mock("@/hooks/useMainStore", () => ({ - useMainStore: (selector: any) => - selector({ profile: profileStore, setProfile: setProfileSpy }), + useMainStore: (selector: any) => selector({ profile: profileStore, setProfile: setProfileSpy }), })) vi.mock("react-i18next", () => ({ diff --git a/src/test/auth-login-race.test.tsx b/src/test/auth-login-race.test.tsx index 8f7ca53..abe58e2 100644 --- a/src/test/auth-login-race.test.tsx +++ b/src/test/auth-login-race.test.tsx @@ -9,8 +9,7 @@ const setProfileSpy = vi.fn((p: any) => { vi.mock("./useMainStore", () => ({})) vi.mock("@/hooks/useMainStore", () => ({ - useMainStore: (selector: any) => - selector({ profile: profileStore, setProfile: setProfileSpy }), + useMainStore: (selector: any) => selector({ profile: profileStore, setProfile: setProfileSpy }), })) vi.mock("react-i18next", () => ({ diff --git a/src/test/batch-move-server-icon.test.tsx b/src/test/batch-move-server-icon.test.tsx index e41f924..2a0e9b8 100644 --- a/src/test/batch-move-server-icon.test.tsx +++ b/src/test/batch-move-server-icon.test.tsx @@ -1,3 +1,4 @@ +import { BatchMoveServerIcon } from "@/components/batch-move-server-icon" import { act, fireEvent, render, screen, waitFor } from "@testing-library/react" import { afterEach, beforeEach, expect, test, vi } from "vitest" @@ -26,8 +27,6 @@ vi.mock("@/api/server", () => ({ batchMoveServer: (...args: unknown[]) => batchMoveServer(...args), })) -import { BatchMoveServerIcon } from "@/components/batch-move-server-icon" - beforeEach(() => { toastCalls.length = 0 batchMoveServer.mockReset() diff --git a/src/test/fetcher-csrf.test.ts b/src/test/fetcher-csrf.test.ts index 20e6e0e..c537a96 100644 --- a/src/test/fetcher-csrf.test.ts +++ b/src/test/fetcher-csrf.test.ts @@ -83,7 +83,9 @@ test("auto refresh-token uses POST (backend route is POST)", async () => { // Revoke (DELETE) commonly returns 204 / empty body. The fetcher must not // blow up on response.json() of an empty body and must resolve successfully. test("DELETE tolerates an empty 204 response body", async () => { - globalThis.fetch = vi.fn(async () => new Response(null, { status: 204 })) as unknown as typeof fetch + globalThis.fetch = vi.fn( + async () => new Response(null, { status: 204 }), + ) as unknown as typeof fetch await expect(fetcher(FetcherMethod.DELETE, "/api/v1/api-tokens/9")).resolves.toBeUndefined() }) diff --git a/src/test/fm.test.tsx b/src/test/fm.test.tsx index 4dabb7d..82d35ec 100644 --- a/src/test/fm.test.tsx +++ b/src/test/fm.test.tsx @@ -1,6 +1,8 @@ import { act, render, waitFor } from "@testing-library/react" import { afterEach, beforeEach, expect, test, vi } from "vitest" +import { FMComponent } from "../components/fm" + type DivProps = React.ComponentPropsWithoutRef<"div"> & { asChild?: boolean } type ButtonProps = React.ComponentPropsWithoutRef<"button"> & { asChild?: boolean @@ -106,8 +108,6 @@ vi.mock("@/lib/utils", () => ({ formatPath: (path: string) => path, })) -import { FMComponent } from "../components/fm" - const webSockets: MockWebSocket[] = [] class MockWebSocket extends EventTarget implements WebSocket { @@ -235,7 +235,10 @@ test("FM websocket lifecycle reuses the socket on path changes", async () => { socket.sent.length = 0 await act(async () => { - await socket.onmessage?.call(socket, new MessageEvent("message", { data: encodeCompleteMessage() })) + await socket.onmessage?.call( + socket, + new MessageEvent("message", { data: encodeCompleteMessage() }), + ) }) expect(webSockets).toHaveLength(1) diff --git a/src/test/protect-auth-loading.test.tsx b/src/test/protect-auth-loading.test.tsx index 0b3ea7b..926e18f 100644 --- a/src/test/protect-auth-loading.test.tsx +++ b/src/test/protect-auth-loading.test.tsx @@ -32,15 +32,11 @@ function renderAtPath(path: string, child: React.ReactNode) { {child} - } + element={{child}} /> {child} - } + element={{child}} /> , @@ -50,18 +46,12 @@ function renderAtPath(path: string, child: React.ReactNode) { } test("ProtectedRoute does not mount children for protected paths while auth is loading", async () => { - await renderAtPath( - "/dashboard", -
protected
, - ) + await renderAtPath("/dashboard",
protected
) expect(document.querySelector("[data-testid='protected-child']")).toBeNull() }) test("ProtectedRoute renders children on the login page even while auth is loading", async () => { - await renderAtPath( - "/dashboard/login", -
login
, - ) + await renderAtPath("/dashboard/login",
login
) expect(document.querySelector("[data-testid='login-child']")).not.toBeNull() }) @@ -100,9 +90,6 @@ test("ProtectedRoute redirects unauthenticated users without mounting protected test("ProtectedRoute renders children once an authenticated profile resolves", async () => { mockLoading = false mockProfile = { id: 1, role: 0 } - await renderAtPath( - "/dashboard", -
protected
, - ) + await renderAtPath("/dashboard",
protected
) expect(document.querySelector("[data-testid='protected-child']")).not.toBeNull() }) diff --git a/src/test/settings-submit-error.test.tsx b/src/test/settings-submit-error.test.tsx index 6e0fb71..45f8704 100644 --- a/src/test/settings-submit-error.test.tsx +++ b/src/test/settings-submit-error.test.tsx @@ -47,7 +47,9 @@ vi.mock("@/hooks/useNotfication", () => ({ })) const updateSettings = vi.fn() -vi.mock("@/api/settings", () => ({ updateSettings: (...args: unknown[]) => updateSettings(...args) })) +vi.mock("@/api/settings", () => ({ + updateSettings: (...args: unknown[]) => updateSettings(...args), +})) beforeEach(() => { toastCalls.length = 0 diff --git a/src/test/transfer.test.tsx b/src/test/transfer.test.tsx index a8e11ff..e79fda8 100644 --- a/src/test/transfer.test.tsx +++ b/src/test/transfer.test.tsx @@ -1,3 +1,4 @@ +import type { ModelServerTransfer } from "@/types" import { act, render, screen, waitFor } from "@testing-library/react" import { afterEach, beforeEach, expect, test, vi } from "vitest" @@ -47,8 +48,6 @@ vi.mock("@/hooks/useMainStore", () => ({ }, })) -import type { ModelServerTransfer } from "@/types" - let mockedRows: ModelServerTransfer[] = [] beforeEach(() => { @@ -127,10 +126,7 @@ test("non-admin member who is only the ToUserID or InitiatorID sees neither Canc test("admin sees both Cancel for pending and Retry for terminal", async () => { mockProfile = { id: 1, role: 0 } - mockedRows = [ - makeRow({ id: 1, status: 0 }), - makeRow({ id: 2, status: 2 }), - ] + mockedRows = [makeRow({ id: 1, status: 0 }), makeRow({ id: 2, status: 2 })] await renderPage() diff --git a/tests/e2e/api-tokens.spec.ts b/tests/e2e/api-tokens.spec.ts index f5ce45d..033fc6e 100644 --- a/tests/e2e/api-tokens.spec.ts +++ b/tests/e2e/api-tokens.spec.ts @@ -85,7 +85,10 @@ test("an API token can authenticate /mcp", async ({ page }) => { headers: await csrfHeaders(page), data: { ...baseSettings, enable_mcp: true }, }) - expect(enableResp.ok(), "PATCH /api/v1/setting must succeed to enable MCP for the test").toBeTruthy() + expect( + enableResp.ok(), + "PATCH /api/v1/setting must succeed to enable MCP for the test", + ).toBeTruthy() try { const apiResp = await page.request.post("/api/v1/api-tokens", { diff --git a/tests/e2e/auth.spec.ts b/tests/e2e/auth.spec.ts index 1e3aae7..17aca38 100644 --- a/tests/e2e/auth.spec.ts +++ b/tests/e2e/auth.spec.ts @@ -1,6 +1,13 @@ import { expect } from "@playwright/test" -import { csrfRequest, defaultAdmin, expectAuthenticated, expectUnauthenticated, loginAs, test } from "./fixtures" +import { + csrfRequest, + defaultAdmin, + expectAuthenticated, + expectUnauthenticated, + loginAs, + test, +} from "./fixtures" test("login persists session via cookie and getProfile succeeds", async ({ page }) => { await loginAs(page, defaultAdmin) @@ -51,7 +58,10 @@ test("password change rotates TokenVersion and revokes existing session", async reject_password: false, }, }) - expect(restoreResp.ok(), "password restore must succeed so other suites can still log in").toBeTruthy() + expect( + restoreResp.ok(), + "password restore must succeed so other suites can still log in", + ).toBeTruthy() } } }) diff --git a/tests/e2e/fixtures.ts b/tests/e2e/fixtures.ts index 24ee9a3..dfdbedc 100644 --- a/tests/e2e/fixtures.ts +++ b/tests/e2e/fixtures.ts @@ -1,4 +1,4 @@ -import { Page, Request, expect, test as base } from "@playwright/test" +import { Page, Request, test as base, expect } from "@playwright/test" export type LoginContext = { username: string @@ -39,7 +39,8 @@ export async function csrfHeaders(page: Page): Promise> { await expect .poll( async () => { - value = (await page.context().cookies()).find((c) => c.name === "nz-csrf")?.value ?? "" + value = + (await page.context().cookies()).find((c) => c.name === "nz-csrf")?.value ?? "" return value }, { timeout: 10_000 }, diff --git a/tests/e2e/fm-csrf.spec.ts b/tests/e2e/fm-csrf.spec.ts index a0e569e..2762223 100644 --- a/tests/e2e/fm-csrf.spec.ts +++ b/tests/e2e/fm-csrf.spec.ts @@ -17,6 +17,9 @@ test("file manager creation only accepts POST", async ({ adminPage: page }) => { }) expect(postResp.status()).toBe(200) const body = await postResp.json() - expect(body.success, "without a connected agent server the POST surfaces a Service error, but the route is reachable").not.toBe(true) + expect( + body.success, + "without a connected agent server the POST surfaces a Service error, but the route is reachable", + ).not.toBe(true) expect(body.error).toBeTruthy() }) diff --git a/tests/e2e/visibility.spec.ts b/tests/e2e/visibility.spec.ts index 3c51339..36bb850 100644 --- a/tests/e2e/visibility.spec.ts +++ b/tests/e2e/visibility.spec.ts @@ -2,7 +2,10 @@ import { expect } from "@playwright/test" import { csrfHeaders, test } from "./fixtures" -test("server-group hides guest-empty groups from anonymous callers", async ({ adminPage: page, browser }) => { +test("server-group hides guest-empty groups from anonymous callers", async ({ + adminPage: page, + browser, +}) => { const tag = Date.now().toString(36) const visibleName = `e2e-visible-${tag}` const hiddenName = `e2e-hidden-${tag}` @@ -12,7 +15,9 @@ test("server-group hides guest-empty groups from anonymous callers", async ({ ad // scenario boils down to "no group is guest-visible", which the assertion below still covers. const serversResp = await page.request.get("/api/v1/server") expect(serversResp.ok()).toBeTruthy() - const serversBody = (await serversResp.json()) as { data: Array<{ id: number; hide_for_guest?: boolean }> } + const serversBody = (await serversResp.json()) as { + data: Array<{ id: number; hide_for_guest?: boolean }> + } const publicServer = serversBody.data?.find((s) => !s.hide_for_guest) const createdGroupIDs: number[] = [] @@ -40,9 +45,14 @@ test("server-group hides guest-empty groups from anonymous callers", async ({ ad data: Array<{ group: { name: string }; servers: number[] }> } const names = (guestBody.data || []).map((it) => it.group.name) - expect(names, "guest must NOT see groups with zero visible servers").not.toContain(hiddenName) + expect(names, "guest must NOT see groups with zero visible servers").not.toContain( + hiddenName, + ) if (publicServer) { - expect(names, "guest still sees groups that contain a guest-visible server").toContain(visibleName) + expect( + names, + "guest still sees groups that contain a guest-visible server", + ).toContain(visibleName) } } finally { await guestCtx.close()