mirror of
https://github.com/Buriburizaem0n/admin-frontend-domain.git
synced 2026-09-19 09:40:13 +00:00
fix: migrate to tailwind v4 and TS6, add oauth2 dashboard_host setting
- migrate Tailwind v3->v4 (postcss @tailwindcss/postcss, CSS-first @theme/@plugin/@custom-variant) - fix TS6 build: ignoreDeprecations for baseUrl, react-day-picker v10 classNames, test type fixes - add dashboard_host setting field with i18n
This commit is contained in:
+1
-2
@@ -1,6 +1,5 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
"@tailwindcss/postcss": {},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ function Calendar({
|
||||
: "[&>svg]:text-muted-foreground flex h-8 items-center gap-1 rounded-md pl-2 pr-1 text-sm [&>svg]:size-3.5",
|
||||
defaultClassNames.caption_label,
|
||||
),
|
||||
table: "w-full border-collapse",
|
||||
month_grid: "w-full border-collapse",
|
||||
weekdays: cn("flex", defaultClassNames.weekdays),
|
||||
weekday: cn(
|
||||
"text-muted-foreground flex-1 select-none rounded-md text-[0.8rem] font-normal",
|
||||
|
||||
+38
-5
@@ -1,8 +1,42 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@import "tailwindcss";
|
||||
@plugin "tailwindcss-animate";
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
@theme inline {
|
||||
--radius-lg: var(--radius);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
|
||||
--color-background: hsl(var(--background));
|
||||
--color-foreground: hsl(var(--foreground));
|
||||
--color-card: hsl(var(--card));
|
||||
--color-card-foreground: hsl(var(--card-foreground));
|
||||
--color-popover: hsl(var(--popover));
|
||||
--color-popover-foreground: hsl(var(--popover-foreground));
|
||||
--color-primary: hsl(var(--primary));
|
||||
--color-primary-foreground: hsl(var(--primary-foreground));
|
||||
--color-secondary: hsl(var(--secondary));
|
||||
--color-secondary-foreground: hsl(var(--secondary-foreground));
|
||||
--color-muted: hsl(var(--muted));
|
||||
--color-muted-foreground: hsl(var(--muted-foreground));
|
||||
--color-accent: hsl(var(--accent));
|
||||
--color-accent-foreground: hsl(var(--accent-foreground));
|
||||
--color-destructive: hsl(var(--destructive));
|
||||
--color-destructive-foreground: hsl(var(--destructive-foreground));
|
||||
--color-border: hsl(var(--border));
|
||||
--color-input: hsl(var(--input));
|
||||
--color-ring: hsl(var(--ring));
|
||||
--color-chart-1: hsl(var(--chart-1));
|
||||
--color-chart-2: hsl(var(--chart-2));
|
||||
--color-chart-3: hsl(var(--chart-3));
|
||||
--color-chart-4: hsl(var(--chart-4));
|
||||
--color-chart-5: hsl(var(--chart-5));
|
||||
|
||||
--text-xsm: 0.825rem;
|
||||
--text-xsm--line-height: 1.25rem;
|
||||
}
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--radius: 0.5rem;
|
||||
--background: 0 0% 100%;
|
||||
@@ -57,7 +91,6 @@
|
||||
--chart-4: 280 65% 60%;
|
||||
--chart-5: 340 75% 55%;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
|
||||
@@ -148,6 +148,8 @@
|
||||
"Loading": "Laden",
|
||||
"Services": "Services",
|
||||
"DashboardOriginalHost": "Agent Verbindungsadresse [Domainname/IP:port]",
|
||||
"DashboardHost": "Dashboard-Host für OAuth2-Callback [Domainname/IP:port]",
|
||||
"DashboardHostHint": "Der öffentliche Host des Dashboards zum Erstellen der OAuth2-Callback-URL. Setzen Sie diesen Wert, wenn das Dashboard über eine andere Domain als die Agent-Verbindungsadresse erreichbar ist. Leer lassen, um dem Request-Host unverändert zu vertrauen.",
|
||||
"ReservedHosts": "Reservierte Hosts (kommagetrennt; öffentliche/Reverse-Proxy-Domains, die Mitglieder nicht als NAT-Domain nutzen dürfen)",
|
||||
"ReservedHostsHint": "Hinter einem Reverse-Proxy sieht das Dashboard seine eigene öffentliche Domain nicht und ist daher nicht automatisch geschützt. Tragen Sie hier jeden öffentlichen Eingangs-Hostnamen ein, sonst könnte ein Mitglied ihn als NAT-Domain registrieren und das Dashboard-Routing übernehmen.",
|
||||
"NavigateTo": "Navigieren",
|
||||
|
||||
@@ -141,6 +141,8 @@
|
||||
"WAF": "Web Application Firewall",
|
||||
"SiteName": "Site Name",
|
||||
"DashboardOriginalHost": "Agent connecting address [domain name/IP:port]",
|
||||
"DashboardHost": "Dashboard host for OAuth2 callback [domain name/IP:port]",
|
||||
"DashboardHostHint": "The dashboard's public host used to build the OAuth2 callback URL. Set this when the dashboard is reached on a different domain than the agent connecting address. Leave empty to trust the request Host as-is.",
|
||||
"ReservedHosts": "Reserved hosts (comma-separated; public/reverse-proxy domains members cannot use as NAT domains)",
|
||||
"ReservedHostsHint": "Behind a reverse proxy the dashboard cannot see its own public domain, so it is not auto-protected. List every public entry hostname here, or a member could register it as a NAT domain and hijack dashboard routing.",
|
||||
"ConfigTLS": "Use TLS to connect Agent",
|
||||
|
||||
@@ -157,6 +157,8 @@
|
||||
"RequestType": "Tipo de solicitud",
|
||||
"RequestBody": "Cuerpo de la solicitud",
|
||||
"DashboardOriginalHost": "Dirección de conexión del Agent [nombre de dominio/IP:puerto]",
|
||||
"DashboardHost": "Host del panel para el callback de OAuth2 [nombre de dominio/IP:puerto]",
|
||||
"DashboardHostHint": "Host público del panel usado para construir la URL de callback de OAuth2. Configúralo cuando se acceda al panel en un dominio distinto al de la dirección de conexión del Agent. Déjalo vacío para confiar en el Host de la petición tal cual.",
|
||||
"ReservedHosts": "Hosts reservados (separados por comas; dominios públicos/proxy inverso que los miembros no pueden usar como dominio NAT)",
|
||||
"ReservedHostsHint": "Detrás de un proxy inverso, el panel no ve su propio dominio público, por lo que no se protege automáticamente. Indique aquí todos los nombres de host de entrada públicos; de lo contrario, un miembro podría registrarlo como dominio NAT y secuestrar el enrutamiento del panel.",
|
||||
"ConfigTLS": "Usar TLS para conectar el Agent",
|
||||
|
||||
@@ -138,6 +138,8 @@
|
||||
"WAF": "Firewall Aplikasi Web",
|
||||
"SiteName": "Nama Situs",
|
||||
"DashboardOriginalHost": "Alamat koneksi Agen [nama domain/IP:port]",
|
||||
"DashboardHost": "Host dashboard untuk callback OAuth2 [nama domain/IP:port]",
|
||||
"DashboardHostHint": "Host publik dashboard yang dipakai untuk membangun URL callback OAuth2. Atur ini bila dashboard diakses pada domain yang berbeda dari alamat koneksi Agen. Kosongkan untuk mempercayai Host permintaan apa adanya.",
|
||||
"ReservedHosts": "Host khusus (dipisahkan koma; domain publik/reverse-proxy yang tidak boleh dipakai anggota sebagai domain NAT)",
|
||||
"ReservedHostsHint": "Di belakang reverse proxy, dasbor tidak melihat domain publiknya sendiri sehingga tidak terlindungi otomatis. Cantumkan setiap host entri publik di sini, atau anggota bisa mendaftarkannya sebagai domain NAT dan membajak perutean dasbor.",
|
||||
"ConfigTLS": "Gunakan TLS untuk menghubungkan Agen",
|
||||
|
||||
@@ -141,6 +141,8 @@
|
||||
"CustomCodes": "Codice personalizzato (stili e script)",
|
||||
"CustomCodesDashboard": "Codice personalizzato per dashboard",
|
||||
"DashboardOriginalHost": "Indirizzo di ancoraggio dell'agente [nome dominio/IP:porta]",
|
||||
"DashboardHost": "Host della dashboard per il callback OAuth2 [nome dominio/IP:porta]",
|
||||
"DashboardHostHint": "Host pubblico della dashboard usato per costruire l'URL di callback OAuth2. Impostalo quando la dashboard è raggiungibile su un dominio diverso dall'indirizzo di connessione dell'agente. Lascia vuoto per fidarti dell'Host della richiesta così com'è.",
|
||||
"ReservedHosts": "Host riservati (separati da virgola; domini pubblici/reverse-proxy che i membri non possono usare come dominio NAT)",
|
||||
"ReservedHostsHint": "Dietro un reverse proxy il pannello non vede il proprio dominio pubblico, quindi non è protetto automaticamente. Elenca qui ogni hostname di ingresso pubblico, altrimenti un membro potrebbe registrarlo come dominio NAT e dirottare il routing del pannello.",
|
||||
"ConfigTLS": "Usa TLS per connettere Agent",
|
||||
|
||||
@@ -140,6 +140,8 @@
|
||||
"WAF": "Web Application Firewall",
|
||||
"SiteName": "Numele site-ului",
|
||||
"DashboardOriginalHost": "Adresa de conectare a agentului [numele domeniului/IP:port]",
|
||||
"DashboardHost": "Host-ul panoului pentru callback-ul OAuth2 [numele domeniului/IP:port]",
|
||||
"DashboardHostHint": "Host-ul public al panoului folosit pentru a construi URL-ul de callback OAuth2. Setați-l când panoul este accesat pe un domeniu diferit de adresa de conectare a agentului. Lăsați gol pentru a avea încredere în Host-ul cererii ca atare.",
|
||||
"ReservedHosts": "Host-uri reservate (separate prin virgulă; domeniile publice sau ale reverse proxy-urilor nu pot fi utilizate ca domenii NAT)",
|
||||
"ReservedHostsHint": "În spatele unui reverse proxy, dashboard-ul nu își poate vedea propriul domeniu public, prin urmare nu este protejat automat. Trebuie să menționați aici toate hostname-urile ale intrărilor publice; în caz contrar, un membru ar putea să le înregistreze ca domenii NAT și să deturneze rutarea către dashboard.",
|
||||
"ConfigTLS": "Folosește TLS pentru a conecta agentul",
|
||||
|
||||
@@ -42,6 +42,8 @@
|
||||
"Credential": "Учетные данные",
|
||||
"CommunityThemeDescription": "Эта тема предоставлена сообществом, используйте на свой страх и риск",
|
||||
"DashboardOriginalHost": "Адрес подключения агента [домен/IP:порт]",
|
||||
"DashboardHost": "Хост панели для OAuth2 callback [домен/IP:порт]",
|
||||
"DashboardHostHint": "Публичный хост панели, используемый для формирования URL обратного вызова OAuth2. Укажите его, если панель доступна на домене, отличном от адреса подключения агента. Оставьте пустым, чтобы доверять Host из запроса как есть.",
|
||||
"ReservedHosts": "Зарезервированные хосты (через запятую; публичные/реверс-прокси домены, которые участники не могут использовать как NAT-домен)",
|
||||
"ReservedHostsHint": "За реверс-прокси панель не видит собственный публичный домен и не защищается автоматически. Укажите здесь все публичные входные хосты, иначе участник сможет зарегистрировать такой домен как NAT-домен и перехватить маршрутизацию панели.",
|
||||
"Error": "Ошибка",
|
||||
|
||||
@@ -134,6 +134,8 @@
|
||||
"WAF": "வலை பயன்பாடு ஃபயர்வால்",
|
||||
"SiteName": "தளத்தின் பெயர்",
|
||||
"DashboardOriginalHost": "முகவரியை இணைக்கும் முகவரி [டொமைன் பெயர்/ஐபி: போர்ட்]",
|
||||
"DashboardHost": "OAuth2 கால்பேக்கிற்கான டாஷ்போர்டு ஹோஸ்ட் [டொமைன் பெயர்/ஐபி: போர்ட்]",
|
||||
"DashboardHostHint": "OAuth2 கால்பேக் URL-ஐ உருவாக்கப் பயன்படும் டாஷ்போர்டின் பொது ஹோஸ்ட். ஏஜெண்ட் இணைப்பு முகவரியிலிருந்து வேறுபட்ட டொமைனில் டாஷ்போர்டு அணுகப்பட்டால் இதை அமைக்கவும். கோரிக்கையின் Host-ஐ அப்படியே நம்ப காலியாக விடவும்.",
|
||||
"ReservedHosts": "ஒதுக்கப்பட்ட ஹோஸ்ட்கள் (கமாவால் பிரிக்கப்பட்டது; உறுப்பினர்கள் NAT டொமைனாகப் பயன்படுத்த முடியாத பொது/ரிவர்ஸ்-ப்ராக்ஸி டொமைன்கள்)",
|
||||
"ReservedHostsHint": "ரிவர்ஸ்-ப்ராக்ஸிக்குப் பின்னால் டாஷ்போர்டு அதன் சொந்த பொது டொமைனைப் பார்க்க முடியாது, எனவே அது தானாகப் பாதுகாக்கப்படாது. அனைத்து பொது நுழைவு ஹோஸ்ட்களையும் இங்கே பட்டியலிடுங்கள்; இல்லையெனில் ஒரு உறுப்பினர் அதை NAT டொமைனாகப் பதிவுசெய்து டாஷ்போர்டு வழித்தடத்தைக் கடத்தலாம்.",
|
||||
"ConfigTLS": "முகவரை இணைக்க TLS ஐப் பயன்படுத்தவும்",
|
||||
|
||||
@@ -154,6 +154,8 @@
|
||||
"CustomCodes": "自定义代码(样式和脚本)",
|
||||
"CustomCodesDashboard": "仪表板的自定义代码",
|
||||
"DashboardOriginalHost": "Agent对接地址【域名/IP:端口】",
|
||||
"DashboardHost": "面板 OAuth2 回调域名【域名/IP:端口】",
|
||||
"DashboardHostHint": "用于生成 OAuth2 回调地址的面板对外域名。当面板访问域名与 Agent 对接地址不同时设置此项。留空则信任请求 Host,不做强制重写。",
|
||||
"ReservedHosts": "保留 Host(逗号分隔;反代/公网域名,成员不可注册为 NAT 域名)",
|
||||
"ReservedHostsHint": "反代部署下面板看不到自身的对外域名,无法自动保护。请在此列出所有公网入口域名,否则成员可将其注册为 NAT 域名抢占面板路由。",
|
||||
"ConfigTLS": "Agent 使用 TLS 连接",
|
||||
|
||||
@@ -146,6 +146,8 @@
|
||||
"CustomCodes": "自訂程式碼(樣式和腳本)",
|
||||
"CustomCodesDashboard": "儀表板的自訂程式碼",
|
||||
"DashboardOriginalHost": "Agent對接位址【網域名稱/IP:連接埠】",
|
||||
"DashboardHost": "面板 OAuth2 回呼網域【網域名稱/IP:連接埠】",
|
||||
"DashboardHostHint": "用於產生 OAuth2 回呼位址的面板對外網域。當面板存取網域與 Agent 對接位址不同時設定此項。留空則信任請求 Host,不做強制重寫。",
|
||||
"ReservedHosts": "保留 Host(逗號分隔;反代/公網網域,成員不可註冊為 NAT 網域)",
|
||||
"ReservedHostsHint": "反代部署下面板看不到自身的對外網域,無法自動保護。請在此列出所有公網入口網域,否則成員可將其註冊為 NAT 網域搶佔面板路由。",
|
||||
"ConfigTLS": "Agent 使用 TLS 連線",
|
||||
|
||||
@@ -45,6 +45,7 @@ const settingFormSchema = z.object({
|
||||
language: z.string().min(2),
|
||||
user_template: z.string().min(1),
|
||||
install_host: asOptionalField(z.string()),
|
||||
dashboard_host: asOptionalField(z.string()),
|
||||
reserved_hosts: asOptionalField(z.string()),
|
||||
custom_code: asOptionalField(z.string()),
|
||||
custom_code_dashboard: asOptionalField(z.string()),
|
||||
@@ -298,6 +299,20 @@ export default function SettingsPage() {
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="dashboard_host"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("DashboardHost")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>{t("DashboardHostHint")}</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="reserved_hosts"
|
||||
|
||||
@@ -2,10 +2,10 @@ import { afterEach, beforeEach, expect, test, vi } from "vitest"
|
||||
|
||||
import { createApiToken, deleteApiToken, listApiTokens } from "../api/api-tokens"
|
||||
|
||||
const realFetch = global.fetch
|
||||
const realFetch = globalThis.fetch
|
||||
|
||||
function mockFetch(payload: unknown, ok = true, success = true) {
|
||||
global.fetch = vi.fn(async () => {
|
||||
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" },
|
||||
@@ -19,13 +19,13 @@ beforeEach(() => {
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers()
|
||||
global.fetch = realFetch
|
||||
globalThis.fetch = realFetch
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
test("listApiTokens GETs /api/v1/api-tokens and returns parsed array", async () => {
|
||||
const calls: Array<{ url: string; method: string }> = []
|
||||
global.fetch = vi.fn(async (input: any, init?: any) => {
|
||||
globalThis.fetch = vi.fn(async (input: any, init?: any) => {
|
||||
calls.push({ url: String(input), method: String(init?.method ?? "GET") })
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
@@ -54,7 +54,7 @@ test("listApiTokens GETs /api/v1/api-tokens and returns parsed array", async ()
|
||||
|
||||
test("createApiToken POSTs /api/v1/api-tokens and serializes scopes / server_ids / expires_in_days", async () => {
|
||||
let captured: { url: string; method: string; body: any } | null = null
|
||||
global.fetch = vi.fn(async (input: any, init?: any) => {
|
||||
globalThis.fetch = vi.fn(async (input: any, init?: any) => {
|
||||
captured = {
|
||||
url: String(input),
|
||||
method: String(init?.method ?? ""),
|
||||
@@ -103,7 +103,7 @@ test("listApiTokens normalizes null scopes to an empty array so the table cannot
|
||||
// Backend APIToken.Scopes() returns nil for ScopesCSV=="" which JSON-encodes
|
||||
// as null; migrated/legacy/hand-edited rows hit this. Without normalization
|
||||
// the list page does tok.scopes.map(...) on null and the whole page crashes.
|
||||
global.fetch = vi.fn(async () => {
|
||||
globalThis.fetch = vi.fn(async () => {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: true,
|
||||
@@ -122,7 +122,7 @@ test("listApiTokens normalizes null scopes to an empty array so the table cannot
|
||||
|
||||
test("deleteApiToken DELETEs /api/v1/api-tokens/:id", async () => {
|
||||
const calls: Array<{ url: string; method: string }> = []
|
||||
global.fetch = vi.fn(async (input: any, init?: any) => {
|
||||
globalThis.fetch = vi.fn(async (input: any, init?: any) => {
|
||||
calls.push({ url: String(input), method: String(init?.method ?? "GET") })
|
||||
return new Response(JSON.stringify({ success: true, data: null }), {
|
||||
status: 200,
|
||||
|
||||
@@ -2,7 +2,7 @@ import { afterEach, beforeEach, expect, test, vi } from "vitest"
|
||||
|
||||
import { FetcherMethod, fetcher } from "../api/api"
|
||||
|
||||
const realFetch = global.fetch
|
||||
const realFetch = globalThis.fetch
|
||||
|
||||
function setCookie(value: string) {
|
||||
Object.defineProperty(document, "cookie", { value, configurable: true })
|
||||
@@ -13,7 +13,7 @@ beforeEach(() => {
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
global.fetch = realFetch
|
||||
globalThis.fetch = realFetch
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
@@ -40,7 +40,7 @@ function headerOf(init: RequestInit | undefined, name: string): string | null {
|
||||
test("POST sends X-CSRF-Token mirrored from nz-csrf cookie", async () => {
|
||||
setCookie("nz-csrf=abc123; other=1")
|
||||
const seen: { init?: RequestInit }[] = []
|
||||
global.fetch = vi.fn(async (_input: any, init?: RequestInit) => {
|
||||
globalThis.fetch = vi.fn(async (_input: any, init?: RequestInit) => {
|
||||
seen.push({ init })
|
||||
return jsonOk()
|
||||
}) as unknown as typeof fetch
|
||||
@@ -53,7 +53,7 @@ test("POST sends X-CSRF-Token mirrored from nz-csrf cookie", async () => {
|
||||
test("DELETE sends X-CSRF-Token mirrored from nz-csrf cookie", async () => {
|
||||
setCookie("nz-csrf=del-token")
|
||||
const seen: { init?: RequestInit }[] = []
|
||||
global.fetch = vi.fn(async (_input: any, init?: RequestInit) => {
|
||||
globalThis.fetch = vi.fn(async (_input: any, init?: RequestInit) => {
|
||||
seen.push({ init })
|
||||
return jsonOk()
|
||||
}) as unknown as typeof fetch
|
||||
@@ -68,7 +68,7 @@ test("auto refresh-token uses POST (backend route is POST)", async () => {
|
||||
const { FetcherMethod: M, fetcher: f } = await import("../api/api")
|
||||
setCookie("nz-jwt=sess; nz-csrf=c")
|
||||
const seen: { url: string; init?: RequestInit }[] = []
|
||||
global.fetch = vi.fn(async (input: any, init?: RequestInit) => {
|
||||
globalThis.fetch = vi.fn(async (input: any, init?: RequestInit) => {
|
||||
seen.push({ url: String(input), init })
|
||||
return jsonOk()
|
||||
}) as unknown as typeof fetch
|
||||
@@ -83,13 +83,13 @@ 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 () => {
|
||||
global.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()
|
||||
})
|
||||
|
||||
test("empty 200 body does not throw", async () => {
|
||||
global.fetch = vi.fn(
|
||||
globalThis.fetch = vi.fn(
|
||||
async () => new Response("", { status: 200 }),
|
||||
) as unknown as typeof fetch
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { afterEach, beforeEach, expect, test, vi } from "vitest"
|
||||
|
||||
import { FetcherMethod, fetcher } from "../api/api"
|
||||
|
||||
const realFetch = global.fetch
|
||||
const realFetch = globalThis.fetch
|
||||
|
||||
beforeEach(() => {
|
||||
// Avoid the auto refresh-token branch interfering with assertions.
|
||||
@@ -10,7 +10,7 @@ beforeEach(() => {
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
global.fetch = realFetch
|
||||
globalThis.fetch = realFetch
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
@@ -19,7 +19,7 @@ afterEach(() => {
|
||||
// DELETE route. The wire-level method must match the requested FetcherMethod.
|
||||
test("fetcher uses HTTP DELETE for FetcherMethod.DELETE", async () => {
|
||||
const seen: { url: string; init?: RequestInit }[] = []
|
||||
global.fetch = vi.fn(async (input: any, init?: RequestInit) => {
|
||||
globalThis.fetch = vi.fn(async (input: any, init?: RequestInit) => {
|
||||
seen.push({ url: String(input), init })
|
||||
return new Response(JSON.stringify({ success: true, data: null }), {
|
||||
status: 200,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { afterEach, beforeEach, expect, test, vi } from "vitest"
|
||||
|
||||
const realFetch = global.fetch
|
||||
const realFetch = globalThis.fetch
|
||||
|
||||
function setCookie(value: string) {
|
||||
Object.defineProperty(document, "cookie", { value, configurable: true })
|
||||
@@ -19,7 +19,7 @@ beforeEach(() => {
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
global.fetch = realFetch
|
||||
globalThis.fetch = realFetch
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
@@ -31,7 +31,7 @@ test("auto refresh is deferred while nz-csrf cookie is missing", async () => {
|
||||
const { FetcherMethod, fetcher } = await import("../api/api")
|
||||
setCookie("nz-jwt=session") // jwt present, but no nz-csrf yet
|
||||
const urls: string[] = []
|
||||
global.fetch = vi.fn(async (input: any) => {
|
||||
globalThis.fetch = vi.fn(async (input: any) => {
|
||||
urls.push(String(input))
|
||||
return jsonOk()
|
||||
}) as unknown as typeof fetch
|
||||
@@ -46,7 +46,7 @@ test("auto refresh is deferred while nz-csrf cookie is missing", async () => {
|
||||
test("auto refresh fires after nz-csrf cookie becomes available", async () => {
|
||||
const { FetcherMethod, fetcher } = await import("../api/api")
|
||||
const urls: string[] = []
|
||||
global.fetch = vi.fn(async (input: any) => {
|
||||
globalThis.fetch = vi.fn(async (input: any) => {
|
||||
urls.push(String(input))
|
||||
return jsonOk()
|
||||
}) as unknown as typeof fetch
|
||||
|
||||
@@ -130,7 +130,7 @@ class MockWebSocket extends EventTarget implements WebSocket {
|
||||
onerror: ((this: WebSocket, ev: Event) => unknown) | null = null
|
||||
onmessage: ((this: WebSocket, ev: MessageEvent) => unknown) | null = null
|
||||
onopen: ((this: WebSocket, ev: Event) => unknown) | null = null
|
||||
readyState = MockWebSocket.CONNECTING
|
||||
readyState: 0 | 1 | 2 | 3 = MockWebSocket.CONNECTING
|
||||
sent: SentWebSocketData[] = []
|
||||
|
||||
constructor(url: string | URL) {
|
||||
|
||||
@@ -21,6 +21,7 @@ class TestResizeObserver implements ResizeObserver {
|
||||
class TestIntersectionObserver implements IntersectionObserver {
|
||||
readonly root: Element | Document | null = null
|
||||
readonly rootMargin = ""
|
||||
readonly scrollMargin = ""
|
||||
readonly thresholds: ReadonlyArray<number> = []
|
||||
|
||||
observe(): void {}
|
||||
|
||||
@@ -719,6 +719,7 @@ export interface ModelSetting {
|
||||
ignored_ip_notification: string
|
||||
ignored_ip_notification_server_ids: Record<string, boolean>
|
||||
install_host: string
|
||||
dashboard_host: string
|
||||
ip_change_notification_group_id: number
|
||||
/** 系统语言,默认 zh_CN */
|
||||
language: string
|
||||
@@ -745,6 +746,7 @@ export interface ModelSettingForm {
|
||||
enable_mcp?: boolean
|
||||
ignored_ip_notification?: string
|
||||
install_host?: string
|
||||
dashboard_host?: string
|
||||
reserved_hosts?: string
|
||||
/** IP变更提醒的通知组 */
|
||||
ip_change_notification_group_id: number
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"ignoreDeprecations": "6.0",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
}
|
||||
],
|
||||
"compilerOptions": {
|
||||
"ignoreDeprecations": "6.0",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
|
||||
Reference in New Issue
Block a user