From dc37f9b7310df388ab7f18a6ca754dc85c1ee90c Mon Sep 17 00:00:00 2001 From: Sanaei Date: Mon, 25 May 2026 14:34:53 +0200 Subject: [PATCH] Migrate frontend models/api/utils to TypeScript and modernize AntD theming (#4563) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor(frontend): port api/* and reality-targets to TypeScript Phase 1 of the JS→TS migration: convert three small, isolated files (axios-init, websocket, reality-targets) to typed sources so future phases can lean on their interfaces. - api/axios-init.ts: typed CSRF cache, interceptors, request retry - api/websocket.ts: typed listener map, message envelope guard, reconnect timer - models/reality-targets.ts: RealityTarget interface, readonly list - env.d.ts: minimal qs module shim (stringify/parse) - consumers: drop ".js" extension from @/api imports * refactor(frontend): port utils/index to TypeScript Phase 2 of the JS→TS migration: convert the 858-line utility module that 30+ pages and hooks depend on. - Msg generic with success/msg/obj shape preserved - HttpUtil get/post/postWithModal generic over response shape - RandomUtil, Wireguard, Base64 fully typed - SizeFormatter/CPUFormatter/TimeFormatter/NumberFormatter typed - ColorUtils.usageColor returns 'green'|'orange'|'red'|'purple' union - LanguageManager.supportedLanguages readonly typed - IntlUtil.formatDate/formatRelativeTime accept null/undefined - ObjectUtil.clone/deepClone/cloneProps/equals kept as `any`-shaped to preserve the prior JS contract used by class-instance callers (AllSetting.cloneProps(this, data), etc.) * refactor(frontend): port models/outbound to TypeScript (hybrid typing) Phase 4 of the JS→TS migration: rename outbound.js to outbound.ts and make it compile under strict mode with a minimal hybrid type pass. - Enum-like constants kept as typed objects (Protocols, SSMethods, …) - Top-level DNS helpers strictly typed - CommonClass gets [key: string]: any so all subclasses can keep their loose this.foo = bar assignments without per-field declarations - Constructor / fromJson / toJson signatures typed as any to preserve the prior JS contract used by consumers and parsers - Outbound declares static fields for the dynamically-attached Settings subclasses (Settings, FreedomSettings, VmessSettings, …) - urlParams.get() results that feed parseInt now use the non-null assertion since the surrounding has() check already guards them - File-level eslint-disable for no-explicit-any/no-var/prefer-const to keep the JS-derived code building without churn * refactor(frontend): port models/inbound to TypeScript (hybrid typing) Phase 5 of the JS→TS migration. Same hybrid approach as outbound.ts: constants typed strictly, classes get [key: string]: any from XrayCommonClass, constructor / fromJson / toJson signatures use any. - XrayCommonClass gains [key: string]: any plus typed static helpers (toJsonArray, fallbackToJson, toHeaders, toV2Headers) - TcpStreamSettings/TlsStreamSettings/RealityStreamSettings/Inbound declare static fields for their dynamically-attached subclasses (TcpRequest, TcpResponse, Cert, Settings, ClientBase, Vmess/VLESS/ Trojan/Shadowsocks/Hysteria/Tunnel/Mixed/Http/Wireguard/TunSettings) - All gen*Link, applyXhttpExtra*, applyExternalProxyTLS*, applyFinalMask* and related helpers explicitly any-typed - Constructor positional client-args (email, limitIp, totalGB, …) typed as optional any across Vmess/VLESS/Trojan/Shadowsocks/Hysteria.VMESS| VLESS|Trojan|Shadowsocks|Hysteria - File-level eslint-disable for no-explicit-any/prefer-const/ no-case-declarations/no-array-constructor to silence churn without changing behavior * refactor(frontend): port models/dbinbound to TypeScript Phase 6 — final phase of the JS→TS migration. Frontend src/ no longer contains any *.js files. - DBInbound declares all fields explicitly (id, userId, up, down, total, …, nodeId, fallbackParent) with proper types - _expiryTime getter/setter typed against dayjs.Dayjs - coerceInboundJsonField takes unknown, returns any - Private cache fields (_cachedInbound, _clientStatsMap) declared - Consumers (InboundFormModal, InboundsPage, useInbounds): drop ".js" extension from @/models/dbinbound imports * refactor(frontend): drop .js extensions from TS-resolved imports Cleanup after the JS→TS migration: - All consumers that imported @/models/{inbound,outbound,dbinbound}.js now drop the .js extension (TS module resolution lands on the .ts file automatically) - eslint.config.js: remove the **/*.js block since the only remaining JS file under src/ is endpoints.js (build-script consumed only) and js.configs.recommended already covers it correctly * refactor(frontend): tighten inbound.ts cleanup wins Checkpoint before the full any → typed pass: - Wrap 15 case bodies in braces (no-case-declarations) - Convert 14 let → const in genLink helpers (prefer-const) - new Array() → [] for shadowsocks passwords (no-array-constructor) - XrayCommonClass: HeaderEntry, FallbackEntry, JsonObject interfaces; fromJson/toV2Headers/toHeaders typed against them; static methods return JsonObject / HeaderEntry[] instead of any - Reduce file-level eslint-disable scope from 4 rules to just no-explicit-any (the only one still needed) * refactor(frontend): drop eslint-disable from models/dbinbound Replace `any` with explicit domain types: - `coerceInboundJsonField` returns `Record` (settings/streamSettings/sniffing are always objects). - Add `RawJsonField`, `ClientStats`, `FallbackParentRef`, `DBInboundInit` types. - `_cachedInbound: Inbound | null`, `toInbound(): Inbound`. - `getClientStats(email): ClientStats | undefined`. - `genInboundLinks(): string` (matches actual return from Inbound.genInboundLinks). - Constructor now accepts `DBInboundInit`. * refactor(frontend): drop eslint-disable from InboundsPage Type all callbacks against DBInbound from @/models/dbinbound: - state setters use DBInbound | null - helpers (projectChildThroughMaster, checkFallback, findClientIndex, exportInboundLinks, etc.) take DBInbound - drop `(dbInbounds as any[])` casts; useInbounds already returns DBInbound[] - introduce ClientMatchTarget for findClientIndex's `client` param - tighten DBInbound.clientStats to ClientStats[] (default []) - single boundary cast at to bridge InboundList's narrower DBInboundRecord (cleanup belongs with InboundList) * refactor(frontend): drop file-level eslint-disable from utils/index - ObjectUtil.clone/deepClone become generic - cloneProps/delProps accept `object` (cast internally to AnyRecord) - equals accepts `unknown` with proper narrowing - ColorUtils.usageColor narrows data/threshold to `number`; total widened to `number | { valueOf(): number } | null | undefined` so Dayjs works - Utils.debounce replaces `const self = this` with lexical arrow closure (no-this-alias clean) - InboundList._expiryTime narrowed from `unknown` to `{ valueOf(): number } | null` - Single-line eslint-disable remains on `Msg` and HttpUtil generic defaults (idiomatic API envelope; changing default to unknown cascades through 34 consumer files) * refactor(frontend): drop eslint-disable from OutboundFormModal field section Replace `type OB = any` with `type OB = Outbound`. Body code still sees protocol fields as `any` via Outbound's inherited [key: string]: any index signature (CommonClass) — that escape hatch will narrow as Phase 6 tightens outbound.ts itself. The intentional `// eslint-disable-next-line` on `useRef(null)` at line 72 stays — out of scope per plan. * refactor(frontend): drop file-level eslint-disable from InboundFormModal Add minimal local interfaces for protocol-specific shapes the form reads: - StreamLike, TlsCert, VlessClient, ShadowsocksClient, HttpAccount, WireguardPeer (replace with real exports from inbound.ts as Phase 7 exports them). - Props typed as DBInbound | null + DBInbound[]. - Drop unnecessary `(Inbound as any).X`, `(RandomUtil as any).X`, `(Wireguard as any).X`, `(DBInbound as any)(...)` casts — they are already typed classes; only `Inbound.Settings`/`Inbound.HttpSettings` remain `any` via static field on Inbound (will tighten in Phase 7). - inboundRef/dbFormRef retain single-line `// eslint-disable-next-line` for `useRef(null)` — nullable narrowing across ~30 callsites exceeds Phase 5 scope. - payload locals typed Record; setAdvancedAllValue parses JSON into a narrowed object instead of `let parsed: any`. * refactor(frontend): narrow outbound.ts eslint-disable to no-explicit-any only - Fix all 36 prefer-const violations: convert never-reassigned `let` to `const`; for mixed-mutability destructuring (fromParamLink, fromHysteriaLink) split into separate `const`/`let` declarations by index instead of destructuring. - Fix both no-var violations: `var stream` / `var settings` → `let`. - File still carries `/* eslint-disable @typescript-eslint/no-explicit-any */` because tightening 223 `any` uses requires removing CommonClass's `[key: string]: any` escape hatch and reshaping ~30 dynamically-attached subclass patterns into named classes — multi-hour architectural work tracked as Phase 7's twin for outbound. * refactor(frontend): align sub page chrome with login + AntD defaults - Theme + language buttons now both use AntD ` ); } diff --git a/frontend/src/components/CustomStatistic.css b/frontend/src/components/CustomStatistic.css deleted file mode 100644 index 9cb3e065..00000000 --- a/frontend/src/components/CustomStatistic.css +++ /dev/null @@ -1,52 +0,0 @@ -.ant-statistic-content { - font-size: 17px !important; - line-height: 1.4 !important; - font-weight: 600; -} - -.ant-statistic-content-value, -.ant-statistic-content-prefix, -.ant-statistic-content-suffix { - font-size: 17px !important; -} - -.ant-statistic-content-prefix { - margin-inline-end: 8px !important; - opacity: 0.7; -} - -.ant-statistic-content-prefix .anticon { - font-size: 17px !important; -} - -.ant-statistic-content-suffix { - font-size: 12px !important; - opacity: 0.55; - margin-inline-start: 4px; - font-weight: 500; -} - -.ant-statistic-title { - font-size: 11px !important; - margin-bottom: 6px !important; - letter-spacing: 0.6px; - text-transform: uppercase; - color: rgba(0, 0, 0, 0.55); - font-weight: 500; -} - -body.dark .ant-statistic-content { - color: rgba(255, 255, 255, 0.92); -} - -body.dark .ant-statistic-title { - color: rgba(255, 255, 255, 0.72); -} - -html[data-theme='ultra-dark'] .ant-statistic-content { - color: rgba(255, 255, 255, 0.95); -} - -html[data-theme='ultra-dark'] .ant-statistic-title { - color: rgba(255, 255, 255, 0.70); -} diff --git a/frontend/src/components/CustomStatistic.tsx b/frontend/src/components/CustomStatistic.tsx deleted file mode 100644 index 6089637f..00000000 --- a/frontend/src/components/CustomStatistic.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import type { ReactNode } from 'react'; -import { Statistic } from 'antd'; -import './CustomStatistic.css'; - -interface CustomStatisticProps { - title?: string; - value?: string | number; - prefix?: ReactNode; - suffix?: ReactNode; -} - -export default function CustomStatistic({ title = '', value = '', prefix, suffix }: CustomStatisticProps) { - return ; -} diff --git a/frontend/src/components/FinalMaskForm.tsx b/frontend/src/components/FinalMaskForm.tsx index 07039cca..b66709da 100644 --- a/frontend/src/components/FinalMaskForm.tsx +++ b/frontend/src/components/FinalMaskForm.tsx @@ -3,7 +3,7 @@ import { Button, Divider, Form, Input, InputNumber, Select, Switch } from 'antd' import { DeleteOutlined, PlusOutlined, ReloadOutlined } from '@ant-design/icons'; import { RandomUtil } from '@/utils'; -import { Protocols } from '@/models/outbound.js'; +import { Protocols } from '@/models/outbound'; interface StreamShape { network?: string; @@ -138,7 +138,7 @@ export default function FinalMaskForm({ stream, protocol, onChange }: FinalMaskF TCP Mask {mIdx + 1} { stream.delTcpMask(mIdx); notify(); @@ -238,7 +238,7 @@ export default function FinalMaskForm({ stream, protocol, onChange }: FinalMaskF UDP Mask {mIdx + 1} { stream.delUdpMask(mIdx); notify(); @@ -403,7 +403,7 @@ function HeaderCustomGroups({ {groupKey === 'clients' ? 'Clients' : 'Servers'} Group {gi + 1} { (settings[groupKey] as ItemRow[][]).splice(gi, 1); onChange(); @@ -445,7 +445,7 @@ function UdpHeaderCustom({ mask, onChange }: { mask: MaskRow; onChange: () => vo {groupKey === 'client' ? 'Client' : 'Server'} {ci + 1} { (settings[groupKey] as ItemRow[]).splice(ci, 1); onChange(); @@ -493,7 +493,7 @@ function NoiseItems({ mask, onChange }: { mask: MaskRow; onChange: () => void }) Noise {ni + 1} { (settings.noise as ItemRow[]).splice(ni, 1); onChange(); diff --git a/frontend/src/components/InputAddon.css b/frontend/src/components/InputAddon.css index e8544941..10a6dca0 100644 --- a/frontend/src/components/InputAddon.css +++ b/frontend/src/components/InputAddon.css @@ -5,22 +5,15 @@ height: 32px; font-size: 14px; line-height: 30px; - background-color: rgba(0, 0, 0, 0.02); - border: 1px solid #d9d9d9; + background-color: var(--ant-color-fill-tertiary); + border: 1px solid var(--ant-color-border); border-radius: 6px; position: relative; z-index: 1; - color: rgba(0, 0, 0, 0.88); + color: var(--ant-color-text); white-space: nowrap; } -body.dark .input-addon, -html[data-theme='ultra-dark'] .input-addon { - background-color: rgba(255, 255, 255, 0.04); - border-color: #424242; - color: rgba(255, 255, 255, 0.85); -} - .ant-space-compact > .input-addon:not(:first-child) { margin-inline-start: -1px; } diff --git a/frontend/src/components/JsonEditor.css b/frontend/src/components/JsonEditor.css index e7e0c320..c449c0ab 100644 --- a/frontend/src/components/JsonEditor.css +++ b/frontend/src/components/JsonEditor.css @@ -1,8 +1,8 @@ .json-editor-host { - border: 1px solid var(--ant-color-border, #d9d9d9); + border: 1px solid var(--ant-color-border); border-radius: 6px; overflow: hidden; - background: var(--ant-color-bg-container, #fff); + background: var(--ant-color-bg-container); } .json-editor-host .cm-editor, @@ -11,16 +11,6 @@ } .json-editor-host:focus-within { - border-color: var(--ant-color-primary, #1677ff); - box-shadow: 0 0 0 2px rgba(22, 119, 255, 0.1); -} - -body.dark .json-editor-host { - border-color: #3a3a3c; - background: #1e1e1e; -} - -html[data-theme="ultra-dark"] .json-editor-host { - border-color: #1f1f1f; - background: #0a0a0a; + border-color: var(--ant-color-primary); + box-shadow: 0 0 0 2px color-mix(in srgb, var(--ant-color-primary) 10%, transparent); } diff --git a/frontend/src/components/SettingListItem.css b/frontend/src/components/SettingListItem.css index 2f024eda..df9b4dfe 100644 --- a/frontend/src/components/SettingListItem.css +++ b/frontend/src/components/SettingListItem.css @@ -2,18 +2,13 @@ display: flex; align-items: center; justify-content: space-between; - border-bottom: 1px solid rgba(5, 5, 5, 0.06); + border-bottom: 1px solid var(--ant-color-border-secondary); } .setting-list-item:last-child { border-bottom: 0; } -body.dark .setting-list-item, -html[data-theme='ultra-dark'] .setting-list-item { - border-bottom-color: rgba(255, 255, 255, 0.08); -} - .setting-list-meta { display: flex; flex-direction: column; @@ -22,22 +17,12 @@ html[data-theme='ultra-dark'] .setting-list-item { .setting-list-title { font-size: 14px; - color: rgba(0, 0, 0, 0.88); + color: var(--ant-color-text); font-weight: 500; } .setting-list-description { font-size: 14px; - color: rgba(0, 0, 0, 0.45); + color: var(--ant-color-text-tertiary); line-height: 1.5715; } - -body.dark .setting-list-title, -html[data-theme='ultra-dark'] .setting-list-title { - color: rgba(255, 255, 255, 0.85); -} - -body.dark .setting-list-description, -html[data-theme='ultra-dark'] .setting-list-description { - color: rgba(255, 255, 255, 0.45); -} diff --git a/frontend/src/components/Sparkline.css b/frontend/src/components/Sparkline.css index ad0e012c..2aece7ca 100644 --- a/frontend/src/components/Sparkline.css +++ b/frontend/src/components/Sparkline.css @@ -2,43 +2,3 @@ display: block; width: 100%; } - -.sparkline-svg .cpu-grid-y-text, -.sparkline-svg .cpu-grid-x-text { - fill: rgba(0, 0, 0, 0.55); - font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; - letter-spacing: 0.2px; -} - -.sparkline-svg .cpu-grid-text { - fill: rgba(0, 0, 0, 0.88); -} - -.sparkline-svg .cpu-grid-line { - stroke: rgba(0, 0, 0, 0.08); -} - -.sparkline-svg .cpu-tooltip-text { - pointer-events: none; -} - -.sparkline-svg .cpu-tooltip-pill { - filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.18)); -} - -body.dark .sparkline-svg .cpu-grid-y-text, -body.dark .sparkline-svg .cpu-grid-x-text { - fill: rgba(255, 255, 255, 0.7); -} - -body.dark .sparkline-svg .cpu-grid-text { - fill: rgba(255, 255, 255, 0.95); -} - -body.dark .sparkline-svg .cpu-grid-line { - stroke: rgba(255, 255, 255, 0.10); -} - -body.dark .sparkline-svg .cpu-tooltip-pill { - filter: drop-shadow(0 2px 8px rgba(0, 0, 0, 0.6)); -} diff --git a/frontend/src/components/Sparkline.tsx b/frontend/src/components/Sparkline.tsx index c49db262..15a9055f 100644 --- a/frontend/src/components/Sparkline.tsx +++ b/frontend/src/components/Sparkline.tsx @@ -1,27 +1,29 @@ -import { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react'; -import type { MouseEvent } from 'react'; +import { useId, useMemo } from 'react'; +import { + Area, + AreaChart, + CartesianGrid, + ResponsiveContainer, + Tooltip, + XAxis, + YAxis, +} from 'recharts'; import './Sparkline.css'; interface SparklineProps { data: number[]; labels?: (string | number)[]; - vbWidth?: number; height?: number; stroke?: string; strokeWidth?: number; maxPoints?: number; showGrid?: boolean; - gridColor?: string; fillOpacity?: number; showMarker?: boolean; markerRadius?: number; showAxes?: boolean; yTickStep?: number; tickCountX?: number; - paddingLeft?: number; - paddingRight?: number; - paddingTop?: number; - paddingBottom?: number; showTooltip?: boolean; valueMin?: number; valueMax?: number | null; @@ -29,340 +31,136 @@ interface SparklineProps { tooltipFormatter?: ((v: number) => string) | null; } +interface ChartPoint { + index: number; + value: number; + label: string; +} + export default function Sparkline({ data, labels = [], - vbWidth = 320, height = 80, stroke = '#008771', strokeWidth = 2, maxPoints = 120, showGrid = true, - gridColor = 'rgba(0,0,0,0.08)', fillOpacity = 0.22, showMarker = true, markerRadius = 3, showAxes = false, yTickStep = 25, tickCountX = 4, - paddingLeft = 56, - paddingRight = 6, - paddingTop = 6, - paddingBottom = 20, showTooltip = false, valueMin = 0, valueMax = 100, yFormatter = (v: number) => `${Math.round(v)}%`, tooltipFormatter = null, }: SparklineProps) { - const svgRef = useRef(null); - const [measuredWidth, setMeasuredWidth] = useState(0); - const [hoverIdx, setHoverIdx] = useState(-1); - const reactId = useId(); const safeId = reactId.replace(/[^a-zA-Z0-9]/g, ''); const gradId = `spkGrad-${safeId}`; - const shadowId = `spkShadow-${safeId}`; - const glowId = `spkGlow-${safeId}`; - useEffect(() => { - const el = svgRef.current; - if (!el) return; - const measure = () => { - const w = el.getBoundingClientRect?.().width || 0; - if (w > 0) setMeasuredWidth(Math.round(w)); - }; - measure(); - if (typeof ResizeObserver !== 'undefined') { - const ro = new ResizeObserver(measure); - ro.observe(el); - return () => ro.disconnect(); + const points = useMemo(() => { + const n = Math.min(data.length, maxPoints); + if (n === 0) return []; + const sliceStart = data.length - n; + const labelStart = Math.max(0, labels.length - n); + return data.slice(sliceStart).map((value, i) => ({ + index: i, + value: Number(value) || 0, + label: String(labels[labelStart + i] ?? i + 1), + })); + }, [data, labels, maxPoints]); + + const yDomain = useMemo<[number, number]>(() => { + if (valueMax != null) return [valueMin, valueMax]; + let max = valueMin; + for (const p of points) { + if (Number.isFinite(p.value) && p.value > max) max = p.value; } - window.addEventListener('resize', measure); - return () => window.removeEventListener('resize', measure); - }, []); - - const effectiveVbWidth = measuredWidth > 0 ? measuredWidth : vbWidth; - const drawWidth = Math.max(1, effectiveVbWidth - paddingLeft - paddingRight); - const drawHeight = Math.max(1, height - paddingTop - paddingBottom); - const nPoints = Math.min(data.length, maxPoints); - - const dataSlice = useMemo( - () => (nPoints === 0 ? [] : data.slice(data.length - nPoints)), - [data, nPoints], - ); - - const labelsSlice = useMemo(() => { - if (!labels?.length || nPoints === 0) return [] as (string | number)[]; - const start = Math.max(0, labels.length - nPoints); - return labels.slice(start); - }, [labels, nPoints]); - - const yDomain = useMemo(() => { - const min = valueMin; - if (valueMax != null) return { min, max: valueMax }; - let max = min; - for (const v of dataSlice) { - const n = Number(v); - if (Number.isFinite(n) && n > max) max = n; - } - if (max <= min) max = min + 1; - return { min, max: max * 1.1 }; - }, [dataSlice, valueMin, valueMax]); - - const project = useCallback( - (v: number) => { - const { min, max } = yDomain; - const span = max - min; - if (span <= 0) return paddingTop + drawHeight; - const clipped = Math.max(min, Math.min(max, Number(v) || 0)); - const ratio = (clipped - min) / span; - return Math.round(paddingTop + (drawHeight - ratio * drawHeight)); - }, - [yDomain, paddingTop, drawHeight], - ); - - const pointsArr = useMemo<[number, number][]>(() => { - if (nPoints === 0) return []; - const w = drawWidth; - const dx = nPoints > 1 ? w / (nPoints - 1) : 0; - return dataSlice.map((v, i) => { - const x = Math.round(paddingLeft + i * dx); - return [x, project(v)]; - }); - }, [dataSlice, nPoints, drawWidth, paddingLeft, project]); - - const pointsStr = useMemo(() => pointsArr.map((p) => `${p[0]},${p[1]}`).join(' '), [pointsArr]); - - const areaPath = useMemo(() => { - if (pointsArr.length === 0) return ''; - const first = pointsArr[0]; - const last = pointsArr[pointsArr.length - 1]; - const baseY = paddingTop + drawHeight; - const line = pointsStr.replace(/ /g, ' L '); - return `M ${first[0]},${baseY} L ${line} L ${last[0]},${baseY} Z`; - }, [pointsArr, pointsStr, paddingTop, drawHeight]); - - const gridLines = useMemo(() => { - if (!showGrid) return []; - const h = drawHeight; - const w = drawWidth; - return [0, 0.25, 0.5, 0.75, 1].map((r) => { - const y = Math.round(paddingTop + h * r); - return { x1: paddingLeft, y1: y, x2: paddingLeft + w, y2: y }; - }); - }, [showGrid, drawHeight, drawWidth, paddingTop, paddingLeft]); - - const lastPoint = pointsArr.length === 0 ? null : pointsArr[pointsArr.length - 1]; + if (max <= valueMin) max = valueMin + 1; + return [valueMin, max * 1.1]; + }, [points, valueMin, valueMax]); const yTicks = useMemo(() => { - if (!showAxes) return []; - const { min, max } = yDomain; - const out: { y: number; label: string }[] = []; + if (!showAxes) return undefined; + const [min, max] = yDomain; if (valueMax === 100 && valueMin === 0 && yTickStep > 0) { - for (let p = min; p <= max; p += yTickStep) { - out.push({ y: project(p), label: yFormatter(p) }); - } + const out: number[] = []; + for (let v = min; v <= max; v += yTickStep) out.push(v); return out; } - const ticks = 5; - for (let i = 0; i < ticks; i++) { - const v = min + ((max - min) * i) / (ticks - 1); - out.push({ y: project(v), label: yFormatter(v) }); - } - return out; - }, [showAxes, yDomain, valueMax, valueMin, yTickStep, project, yFormatter]); + const n = 5; + return Array.from({ length: n }, (_, i) => min + ((max - min) * i) / (n - 1)); + }, [showAxes, yDomain, valueMin, valueMax, yTickStep]); - const xTicks = useMemo(() => { - if (!showAxes) return []; - if (nPoints === 0) return []; + const xTickIndexes = useMemo(() => { + if (!showAxes || points.length === 0) return undefined; const m = Math.max(2, tickCountX); - const w = drawWidth; - const dx = nPoints > 1 ? w / (nPoints - 1) : 0; - const out: { x: number; label: string }[] = []; - for (let i = 0; i < m; i++) { - const idx = Math.round((i * (nPoints - 1)) / (m - 1)); - const label = labelsSlice[idx] != null ? String(labelsSlice[idx]) : String(idx); - const x = Math.round(paddingLeft + idx * dx); - out.push({ x, label }); - } - return out; - }, [showAxes, labelsSlice, nPoints, tickCountX, drawWidth, paddingLeft]); + return Array.from({ length: m }, (_, i) => Math.round((i * (points.length - 1)) / (m - 1))); + }, [showAxes, tickCountX, points.length]); - const onMouseMove = useCallback( - (evt: MouseEvent) => { - if (!showTooltip || pointsArr.length === 0) return; - const rect = evt.currentTarget.getBoundingClientRect(); - const px = evt.clientX - rect.left; - const x = (px / rect.width) * effectiveVbWidth; - const dx = nPoints > 1 ? drawWidth / (nPoints - 1) : 0; - const idx = Math.max(0, Math.min(nPoints - 1, Math.round((x - paddingLeft) / (dx || 1)))); - setHoverIdx(idx); - }, - [showTooltip, pointsArr.length, effectiveVbWidth, nPoints, drawWidth, paddingLeft], - ); - - const onMouseLeave = useCallback(() => setHoverIdx(-1), []); - - const hoverText = useMemo(() => { - const idx = hoverIdx; - if (idx < 0 || idx >= dataSlice.length) return ''; - const raw = Number(dataSlice[idx] || 0); - const fmt = tooltipFormatter || yFormatter; - const val = fmt(Number.isFinite(raw) ? raw : 0); - const lab = labelsSlice[idx] != null ? labelsSlice[idx] : ''; - return `${val}${lab ? ' • ' + lab : ''}`; - }, [hoverIdx, dataSlice, labelsSlice, tooltipFormatter, yFormatter]); - - const tooltipPillWidth = Math.max(48, hoverText.length * 6.2 + 14); - const hoverPoint = hoverIdx >= 0 ? pointsArr[hoverIdx] : null; - const tooltipX = hoverPoint - ? Math.max( - paddingLeft + 2, - Math.min(effectiveVbWidth - paddingRight - tooltipPillWidth - 2, hoverPoint[0] - tooltipPillWidth / 2), - ) - : 0; + const fmtTooltip = tooltipFormatter ?? yFormatter; return ( - - - - - - - - - - - - - - - - - - - - - - - - - {showGrid && ( - - {gridLines.map((g, i) => ( - - ))} - - )} - - {showAxes && ( - - {yTicks.map((tk, i) => ( - - {tk.label} - - ))} - {xTicks.map((tk, i) => ( - - {tk.label} - - ))} - - )} - - {areaPath && } - - {showMarker && lastPoint && ( - <> - - - - - - - )} - - {showTooltip && hoverIdx >= 0 && pointsArr[hoverIdx] && ( - - + + + + + + + + {showGrid && ( + + )} + points[i]?.label).filter(Boolean) as string[] | undefined} + /> + + {showTooltip && ( + [fmtTooltip(Number(v) || 0), '']} + separator="" /> - - - - - {hoverText} - - - )} - + )} + + + ); } diff --git a/frontend/src/entries/login.tsx b/frontend/src/entries/login.tsx index eecec4bd..24b28cd2 100644 --- a/frontend/src/entries/login.tsx +++ b/frontend/src/entries/login.tsx @@ -2,7 +2,7 @@ import { createRoot } from 'react-dom/client'; import { message } from 'antd'; import 'antd/dist/reset.css'; -import { setupAxios } from '@/api/axios-init.js'; +import { setupAxios } from '@/api/axios-init'; import { applyDocumentTitle } from '@/utils'; import { readyI18n } from '@/i18n/react'; import { ThemeProvider } from '@/hooks/useTheme'; diff --git a/frontend/src/env.d.ts b/frontend/src/env.d.ts index eb3c1cf7..6a2d2216 100644 --- a/frontend/src/env.d.ts +++ b/frontend/src/env.d.ts @@ -28,6 +28,28 @@ interface Window { __SUB_PAGE_DATA__?: SubPageData; } +declare module 'qs' { + interface StringifyOptions { + arrayFormat?: 'indices' | 'brackets' | 'repeat' | 'comma'; + encode?: boolean; + encoder?: (str: unknown, defaultEncoder: (s: unknown) => string, charset: string, type: 'key' | 'value') => string; + allowDots?: boolean; + skipNulls?: boolean; + addQueryPrefix?: boolean; + } + interface ParseOptions { + depth?: number; + arrayLimit?: number; + allowDots?: boolean; + parseArrays?: boolean; + ignoreQueryPrefix?: boolean; + } + export function stringify(obj: unknown, options?: StringifyOptions): string; + export function parse(str: string, options?: ParseOptions): Record; + const qs: { stringify: typeof stringify; parse: typeof parse }; + export default qs; +} + declare module 'persian-calendar-suite' { import type { ComponentType, ReactNode } from 'react'; diff --git a/frontend/src/hooks/useTheme.tsx b/frontend/src/hooks/useTheme.tsx index 12c33252..665e46f1 100644 --- a/frontend/src/hooks/useTheme.tsx +++ b/frontend/src/hooks/useTheme.tsx @@ -68,10 +68,25 @@ const ULTRA_DARK_MENU_TOKENS = { darkSubMenuItemBg: '#000', darkPopupBg: '#101013', }; +const DARK_CARD_TOKENS = { + colorBorderSecondary: 'rgba(255, 255, 255, 0.06)', +}; +const ULTRA_DARK_CARD_TOKENS = { + colorBorderSecondary: 'rgba(255, 255, 255, 0.04)', +}; +const STATISTIC_TOKENS = { + contentFontSize: 17, + titleFontSize: 11, +}; export function buildAntdThemeConfig(isDark: boolean, isUltra: boolean): ThemeConfig { if (!isDark) { - return { algorithm: antdTheme.defaultAlgorithm }; + return { + algorithm: antdTheme.defaultAlgorithm, + components: { + Statistic: STATISTIC_TOKENS, + }, + }; } return { algorithm: antdTheme.darkAlgorithm, @@ -79,6 +94,8 @@ export function buildAntdThemeConfig(isDark: boolean, isUltra: boolean): ThemeCo components: { Layout: isUltra ? ULTRA_DARK_LAYOUT_TOKENS : DARK_LAYOUT_TOKENS, Menu: isUltra ? ULTRA_DARK_MENU_TOKENS : DARK_MENU_TOKENS, + Card: isUltra ? ULTRA_DARK_CARD_TOKENS : DARK_CARD_TOKENS, + Statistic: STATISTIC_TOKENS, }, }; } diff --git a/frontend/src/hooks/useWebSocket.ts b/frontend/src/hooks/useWebSocket.ts index 02ddd0be..4a5e036e 100644 --- a/frontend/src/hooks/useWebSocket.ts +++ b/frontend/src/hooks/useWebSocket.ts @@ -1,5 +1,5 @@ import { useEffect } from 'react'; -import { WebSocketClient } from '@/api/websocket.js'; +import { WebSocketClient } from '@/api/websocket'; type Handler = (payload: unknown) => void; diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index a4da5109..91131eed 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -2,8 +2,11 @@ import { createRoot } from 'react-dom/client'; import { RouterProvider } from 'react-router-dom'; import { message } from 'antd'; import 'antd/dist/reset.css'; +import '@/styles/utils.css'; +import '@/styles/page-shell.css'; +import '@/styles/page-cards.css'; -import { setupAxios } from '@/api/axios-init.js'; +import { setupAxios } from '@/api/axios-init'; import { readyI18n } from '@/i18n/react'; import { ThemeProvider } from '@/hooks/useTheme'; import { QueryProvider } from '@/api/QueryProvider'; diff --git a/frontend/src/models/dbinbound.js b/frontend/src/models/dbinbound.ts similarity index 59% rename from frontend/src/models/dbinbound.js rename to frontend/src/models/dbinbound.ts index 49c19eaf..391838c2 100644 --- a/frontend/src/models/dbinbound.js +++ b/frontend/src/models/dbinbound.ts @@ -1,23 +1,94 @@ -import dayjs from 'dayjs'; +import dayjs, { type Dayjs } from 'dayjs'; import { ObjectUtil, NumberFormatter, SizeFormatter } from '@/utils'; -import { Inbound, Protocols } from './inbound.js'; +import { Inbound, Protocols } from './inbound'; -export function coerceInboundJsonField(value) { +export type RawJsonField = string | Record | unknown[]; + +export interface ClientStats { + email: string; + up: number; + down: number; + total: number; + expiryTime: number; + enable?: boolean; + inboundId?: number; + reset?: number; +} + +export interface FallbackParentRef { + masterId: number; + path: string; +} + +export type DBInboundInit = Partial<{ + id: number; + userId: number; + up: number; + down: number; + total: number; + remark: string; + enable: boolean; + expiryTime: number; + trafficReset: string; + lastTrafficResetTime: number; + listen: string; + port: number; + protocol: string; + settings: RawJsonField; + streamSettings: RawJsonField; + tag: string; + sniffing: RawJsonField; + clientStats: ClientStats[]; + nodeId: number | null; + fallbackParent: FallbackParentRef | null; +}>; + +export function coerceInboundJsonField(value: unknown): Record { if (value == null) return {}; - if (typeof value === 'object') return value; + if (typeof value === 'object' && !Array.isArray(value)) { + return value as Record; + } if (typeof value !== 'string') return {}; const trimmed = value.trim(); if (trimmed === '') return {}; try { - return JSON.parse(trimmed); - } catch (_e) { + const parsed = JSON.parse(trimmed); + if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) { + return parsed as Record; + } + return {}; + } catch { return {}; } } export class DBInbound { + id: number; + userId: number; + up: number; + down: number; + total: number; + remark: string; + enable: boolean; + expiryTime: number; + trafficReset: string; + lastTrafficResetTime: number; - constructor(data) { + listen: string; + port: number; + protocol: string; + settings: RawJsonField; + streamSettings: RawJsonField; + tag: string; + sniffing: RawJsonField; + clientStats: ClientStats[]; + nodeId: number | null; + fallbackParent: FallbackParentRef | null; + + private _cachedInbound: Inbound | null = null; + private _clientStatsMap: Map | null = null; + + constructor(data?: DBInboundInit) { this.id = 0; this.userId = 0; this.up = 0; @@ -36,12 +107,8 @@ export class DBInbound { this.streamSettings = ""; this.tag = ""; this.sniffing = ""; - this.clientStats = "" - // Optional FK to web/runtime registered Node. null/undefined = - // local panel; otherwise the inbound lives on the named node. + this.clientStats = []; this.nodeId = null; - // Populated by the API when this inbound is a fallback child of - // a VLESS/Trojan TCP-TLS master. Shape: { masterId, path }. this.fallbackParent = null; if (data == null) { return; @@ -49,11 +116,11 @@ export class DBInbound { ObjectUtil.cloneProps(this, data); } - get totalGB() { + get totalGB(): number { return NumberFormatter.toFixed(this.total / SizeFormatter.ONE_GB, 2); } - set totalGB(gb) { + set totalGB(gb: number) { this.total = NumberFormatter.toFixed(gb * SizeFormatter.ONE_GB, 0); } @@ -89,7 +156,7 @@ export class DBInbound { return this.protocol === Protocols.HYSTERIA; } - get address() { + get address(): string { let address = location.hostname; if (!ObjectUtil.isEmpty(this.listen) && this.listen !== "0.0.0.0") { address = this.listen; @@ -97,14 +164,14 @@ export class DBInbound { return address; } - get _expiryTime() { + get _expiryTime(): Dayjs | null { if (this.expiryTime === 0) { return null; } return dayjs(this.expiryTime); } - set _expiryTime(t) { + set _expiryTime(t: Dayjs | null | undefined) { if (t == null) { this.expiryTime = 0; } else { @@ -112,16 +179,16 @@ export class DBInbound { } } - get isExpiry() { + get isExpiry(): boolean { return this.expiryTime < new Date().getTime(); } - invalidateCache() { + invalidateCache(): void { this._cachedInbound = null; this._clientStatsMap = null; } - toInbound() { + toInbound(): Inbound { if (this._cachedInbound) { return this._cachedInbound; } @@ -145,19 +212,21 @@ export class DBInbound { return this._cachedInbound; } - getClientStats(email) { + getClientStats(email: string): ClientStats | undefined { if (!this._clientStatsMap) { this._clientStatsMap = new Map(); - if (this.clientStats && Array.isArray(this.clientStats)) { + if (Array.isArray(this.clientStats)) { for (const stats of this.clientStats) { - this._clientStatsMap.set(stats.email, stats); + if (stats && stats.email) { + this._clientStatsMap.set(stats.email, stats); + } } } } return this._clientStatsMap.get(email); } - isMultiUser() { + isMultiUser(): boolean { switch (this.protocol) { case Protocols.VMESS: case Protocols.VLESS: @@ -171,7 +240,7 @@ export class DBInbound { } } - hasLink() { + hasLink(): boolean { switch (this.protocol) { case Protocols.VMESS: case Protocols.VLESS: @@ -184,8 +253,8 @@ export class DBInbound { } } - genInboundLinks(remarkModel, hostOverride = '') { + genInboundLinks(remarkModel: string, hostOverride: string = ''): string { const inbound = this.toInbound(); return inbound.genInboundLinks(this.remark, remarkModel, hostOverride); } -} \ No newline at end of file +} diff --git a/frontend/src/models/inbound.js b/frontend/src/models/inbound.ts similarity index 85% rename from frontend/src/models/inbound.js rename to frontend/src/models/inbound.ts index d98dd8ba..e1342faa 100644 --- a/frontend/src/models/inbound.js +++ b/frontend/src/models/inbound.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import dayjs from 'dayjs'; import { ObjectUtil, RandomUtil, Base64, NumberFormatter, SizeFormatter, Wireguard } from '@/utils'; import { getRandomRealityTarget } from '@/models/reality-targets'; @@ -136,25 +137,33 @@ Object.freeze(TCP_CONGESTION_OPTION); Object.freeze(USERS_SECURITY); Object.freeze(MODE_OPTION); -export class XrayCommonClass { +export type JsonObject = Record; +export interface HeaderEntry { name: string; value: string } +export interface FallbackEntry { + dest?: string | number; + name?: string; + alpn?: string; + path?: string; + xver?: number | string; +} - static toJsonArray(arr) { - return arr.map(obj => obj.toJson()); +export class XrayCommonClass { + [key: string]: any; + + static toJsonArray(arr: T[]): unknown[] { + return arr.map((obj) => obj.toJson()); } - static fromJson() { + static fromJson(..._args: unknown[]): XrayCommonClass | undefined { return new XrayCommonClass(); } - toJson() { + toJson(): unknown { return this; } - // Build a clean Xray fallback entry. Per docs, name/alpn/path empty = "any", - // and xver=0 means PROXY protocol off — omit them so the generated config - // stays minimal and readable. dest is required and always emitted. - static fallbackToJson(fb) { - const out = { dest: fb.dest }; + static fallbackToJson(fb: FallbackEntry): JsonObject { + const out: JsonObject = { dest: fb.dest }; if (fb.name) out.name = fb.name; if (fb.alpn) out.alpn = fb.alpn; if (fb.path) out.path = fb.path; @@ -163,18 +172,19 @@ export class XrayCommonClass { return out; } - toString(format = true) { + toString(format: boolean = true): string { return format ? JSON.stringify(this.toJson(), null, 2) : JSON.stringify(this.toJson()); } - static toHeaders(v2Headers) { - let newHeaders = []; - if (v2Headers) { - Object.keys(v2Headers).forEach(key => { - let values = v2Headers[key]; - if (typeof (values) === 'string') { + static toHeaders(v2Headers: unknown): HeaderEntry[] { + const newHeaders: HeaderEntry[] = []; + if (v2Headers && typeof v2Headers === 'object') { + const map = v2Headers as Record; + Object.keys(map).forEach((key: string) => { + const values = map[key]; + if (typeof values === 'string') { newHeaders.push({ name: key, value: values }); - } else { + } else if (Array.isArray(values)) { for (let i = 0; i < values.length; ++i) { newHeaders.push({ name: key, value: values[i] }); } @@ -184,19 +194,20 @@ export class XrayCommonClass { return newHeaders; } - static toV2Headers(headers, arr = true) { - let v2Headers = {}; + static toV2Headers(headers: HeaderEntry[], arr: boolean = true): Record { + const v2Headers: Record = {}; for (let i = 0; i < headers.length; ++i) { - let name = headers[i].name; - let value = headers[i].value; + const name = headers[i].name; + const value = headers[i].value; if (ObjectUtil.isEmpty(name) || ObjectUtil.isEmpty(value)) { continue; } if (!(name in v2Headers)) { v2Headers[name] = arr ? [value] : value; } else { - if (arr) { - v2Headers[name].push(value); + const existing = v2Headers[name]; + if (arr && Array.isArray(existing)) { + existing.push(value); } else { v2Headers[name] = value; } @@ -207,10 +218,13 @@ export class XrayCommonClass { } export class TcpStreamSettings extends XrayCommonClass { + static TcpRequest: any; + static TcpResponse: any; + constructor( - acceptProxyProtocol = false, - type = 'none', - request = new TcpStreamSettings.TcpRequest(), + acceptProxyProtocol: any = false, + type: any = 'none', + request: any = new TcpStreamSettings.TcpRequest(), response = new TcpStreamSettings.TcpResponse(), ) { super(); @@ -220,7 +234,7 @@ export class TcpStreamSettings extends XrayCommonClass { this.response = response; } - static fromJson(json = {}) { + static fromJson(json: any = {}) { let header = json.header; if (!header) { header = {}; @@ -233,7 +247,7 @@ export class TcpStreamSettings extends XrayCommonClass { } toJson() { - const json = {}; + const json: any = {}; if (this.acceptProxyProtocol) { json.acceptProxyProtocol = true; } @@ -255,7 +269,7 @@ TcpStreamSettings.TcpRequest = class extends XrayCommonClass { version = '1.1', method = 'GET', path = ['/'], - headers = [], + headers: any[] = [], ) { super(); this.version = version; @@ -264,23 +278,23 @@ TcpStreamSettings.TcpRequest = class extends XrayCommonClass { this.headers = headers; } - addPath(path) { + addPath(path: any) { this.path.push(path); } - removePath(index) { + removePath(index: number) { this.path.splice(index, 1); } - addHeader(name, value) { + addHeader(name: any, value: any) { this.headers.push({ name: name, value: value }); } - removeHeader(index) { + removeHeader(index: number) { this.headers.splice(index, 1); } - static fromJson(json = {}) { + static fromJson(json: any = {}) { return new TcpStreamSettings.TcpRequest( json.version, json.method, @@ -304,7 +318,7 @@ TcpStreamSettings.TcpResponse = class extends XrayCommonClass { version = '1.1', status = '200', reason = 'OK', - headers = [], + headers: any[] = [], ) { super(); this.version = version; @@ -313,15 +327,15 @@ TcpStreamSettings.TcpResponse = class extends XrayCommonClass { this.headers = headers; } - addHeader(name, value) { + addHeader(name: any, value: any) { this.headers.push({ name: name, value: value }); } - removeHeader(index) { + removeHeader(index: number) { this.headers.splice(index, 1); } - static fromJson(json = {}) { + static fromJson(json: any = {}) { return new TcpStreamSettings.TcpResponse( json.version, json.status, @@ -358,7 +372,7 @@ export class KcpStreamSettings extends XrayCommonClass { this.maxSendingWindow = maxSendingWindow; } - static fromJson(json = {}) { + static fromJson(json: any = {}) { return new KcpStreamSettings( json.mtu, json.tti, @@ -383,10 +397,10 @@ export class KcpStreamSettings extends XrayCommonClass { export class WsStreamSettings extends XrayCommonClass { constructor( - acceptProxyProtocol = false, + acceptProxyProtocol: any = false, path = '/', host = '', - headers = [], + headers: any[] = [], heartbeatPeriod = 0, ) { super(); @@ -397,15 +411,15 @@ export class WsStreamSettings extends XrayCommonClass { this.heartbeatPeriod = heartbeatPeriod; } - addHeader(name, value) { + addHeader(name: any, value: any) { this.headers.push({ name: name, value: value }); } - removeHeader(index) { + removeHeader(index: number) { this.headers.splice(index, 1); } - static fromJson(json = {}) { + static fromJson(json: any = {}) { return new WsStreamSettings( json.acceptProxyProtocol, json.path, @@ -438,7 +452,7 @@ export class GrpcStreamSettings extends XrayCommonClass { this.multiMode = multiMode; } - static fromJson(json = {}) { + static fromJson(json: any = {}) { return new GrpcStreamSettings( json.serviceName, json.authority, @@ -457,10 +471,10 @@ export class GrpcStreamSettings extends XrayCommonClass { export class HTTPUpgradeStreamSettings extends XrayCommonClass { constructor( - acceptProxyProtocol = false, + acceptProxyProtocol: any = false, path = '/', host = '', - headers = [] + headers: any[] = [] ) { super(); this.acceptProxyProtocol = acceptProxyProtocol; @@ -469,15 +483,15 @@ export class HTTPUpgradeStreamSettings extends XrayCommonClass { this.headers = headers; } - addHeader(name, value) { + addHeader(name: any, value: any) { this.headers.push({ name: name, value: value }); } - removeHeader(index) { + removeHeader(index: number) { this.headers.splice(index, 1); } - static fromJson(json = {}) { + static fromJson(json: any = {}) { return new HTTPUpgradeStreamSettings( json.acceptProxyProtocol, json.path, @@ -533,7 +547,7 @@ export class xHTTPStreamSettings extends XrayCommonClass { // URL-share only — embedded in the link's `extra` blob so clients // pick them up; xray's listener ignores them at runtime. uplinkHTTPMethod = '', - headers = [], + headers: any[] = [], ) { super(); this.path = path; @@ -560,15 +574,15 @@ export class xHTTPStreamSettings extends XrayCommonClass { this.headers = headers; } - addHeader(name, value) { + addHeader(name: any, value: any) { this.headers.push({ name: name, value: value }); } - removeHeader(index) { + removeHeader(index: number) { this.headers.splice(index, 1); } - static fromJson(json = {}) { + static fromJson(json: any = {}) { return new xHTTPStreamSettings( json.path, json.host, @@ -625,20 +639,21 @@ export class xHTTPStreamSettings extends XrayCommonClass { export class HysteriaStreamSettings extends XrayCommonClass { constructor( - protocol, - version = 2, - auth = '', - udpIdleTimeout = 60, - masquerade, + protocol?: any, + version: any = 2, + auth: any = '', + udpIdleTimeout: any = 60, + masquerade?: any, ) { - super(protocol); + super(); + this.protocol = protocol; this.version = version; this.auth = auth; this.udpIdleTimeout = udpIdleTimeout; this.masquerade = masquerade; } - static fromJson(json = {}) { + static fromJson(json: any = {}) { return new HysteriaStreamSettings( json.protocol, json.version ?? 2, @@ -675,7 +690,7 @@ export class HysteriaMasquerade extends XrayCommonClass { rewriteHost = false, insecure = false, content = '', - headers = [], + headers: any[] = [], statusCode = 0, ) { super(); @@ -689,15 +704,15 @@ export class HysteriaMasquerade extends XrayCommonClass { this.statusCode = statusCode; } - addHeader(name, value) { + addHeader(name: any, value: any) { this.headers.push({ name: name, value: value }); } - removeHeader(index) { + removeHeader(index: number) { this.headers.splice(index, 1); } - static fromJson(json = {}) { + static fromJson(json: any = {}) { const type = ['proxy', 'file', 'string'].includes(json.type) ? json.type : 'proxy'; return new HysteriaMasquerade( type, @@ -725,8 +740,11 @@ export class HysteriaMasquerade extends XrayCommonClass { } }; export class TlsStreamSettings extends XrayCommonClass { + static Cert: any; + static Settings: any; + constructor( - serverName = '', + serverName: any = '', minVersion = TLS_VERSION_OPTION.TLS12, maxVersion = TLS_VERSION_OPTION.TLS13, cipherSuites = '', @@ -756,15 +774,15 @@ export class TlsStreamSettings extends XrayCommonClass { this.certs.push(new TlsStreamSettings.Cert()); } - removeCert(index) { + removeCert(index: number) { this.certs.splice(index, 1); } - static fromJson(json = {}) { + static fromJson(json: any = {}) { let certs; let settings; if (!ObjectUtil.isEmpty(json.certificates)) { - certs = json.certificates.map(cert => TlsStreamSettings.Cert.fromJson(cert)); + certs = json.certificates.map((cert: any) => TlsStreamSettings.Cert.fromJson(cert)); } if (!ObjectUtil.isEmpty(json.settings)) { @@ -824,7 +842,7 @@ TlsStreamSettings.Cert = class extends XrayCommonClass { this.buildChain = buildChain } - static fromJson(json = {}) { + static fromJson(json: any = {}) { if ('certificateFile' in json && 'keyFile' in json) { return new TlsStreamSettings.Cert( true, @@ -876,7 +894,7 @@ TlsStreamSettings.Settings = class extends XrayCommonClass { this.fingerprint = fingerprint; this.echConfigList = echConfigList; } - static fromJson(json = {}) { + static fromJson(json: any = {}) { return new TlsStreamSettings.Settings( json.fingerprint, json.echConfigList, @@ -892,8 +910,10 @@ TlsStreamSettings.Settings = class extends XrayCommonClass { export class RealityStreamSettings extends XrayCommonClass { + static Settings: any; + constructor( - show = false, + show: any = false, xver = 0, target = '', serverNames = '', @@ -925,7 +945,7 @@ export class RealityStreamSettings extends XrayCommonClass { this.settings = settings; } - static fromJson(json = {}) { + static fromJson(json: any = {}) { let settings; if (!ObjectUtil.isEmpty(json.settings)) { settings = new RealityStreamSettings.Settings( @@ -983,7 +1003,7 @@ RealityStreamSettings.Settings = class extends XrayCommonClass { this.spiderX = spiderX; this.mldsa65Verify = mldsa65Verify; } - static fromJson(json = {}) { + static fromJson(json: any = {}) { return new RealityStreamSettings.Settings( json.publicKey, json.fingerprint, @@ -1005,7 +1025,7 @@ RealityStreamSettings.Settings = class extends XrayCommonClass { export class SockoptStreamSettings extends XrayCommonClass { constructor( - acceptProxyProtocol = false, + acceptProxyProtocol: any = false, tcpFastOpen = false, mark = 0, tproxy = "off", @@ -1043,7 +1063,7 @@ export class SockoptStreamSettings extends XrayCommonClass { this.trustedXForwardedFor = trustedXForwardedFor; } - static fromJson(json = {}) { + static fromJson(json: any = {}) { if (Object.keys(json).length === 0) return undefined; return new SockoptStreamSettings( json.acceptProxyProtocol, @@ -1067,7 +1087,7 @@ export class SockoptStreamSettings extends XrayCommonClass { } toJson() { - const result = { + const result: any = { acceptProxyProtocol: this.acceptProxyProtocol, tcpFastOpen: this.tcpFastOpen, mark: this.mark, @@ -1093,13 +1113,13 @@ export class SockoptStreamSettings extends XrayCommonClass { } export class UdpMask extends XrayCommonClass { - constructor(type = 'salamander', settings = {}) { + constructor(type: any = 'salamander', settings: any = {}) { super(); this.type = type; this.settings = this._getDefaultSettings(type, settings); } - _getDefaultSettings(type, settings = {}) { + _getDefaultSettings(type: any, settings: any = {}): any { switch (type) { case 'salamander': case 'mkcp-aes128gcm': @@ -1132,7 +1152,7 @@ export class UdpMask extends XrayCommonClass { } } - static fromJson(json = {}) { + static fromJson(json: any = {}) { return new UdpMask( json.type || 'salamander', json.settings || {} @@ -1140,7 +1160,7 @@ export class UdpMask extends XrayCommonClass { } toJson() { - const cleanItem = item => { + const cleanItem = (item: any) => { const out = { ...item }; if (out.type === 'array') { delete out.packet; @@ -1170,13 +1190,13 @@ export class UdpMask extends XrayCommonClass { } export class TcpMask extends XrayCommonClass { - constructor(type = 'fragment', settings = {}) { + constructor(type: any = 'fragment', settings: any = {}) { super(); this.type = type; this.settings = this._getDefaultSettings(type, settings); } - _getDefaultSettings(type, settings = {}) { + _getDefaultSettings(type: any, settings: any = {}): any { switch (type) { case 'fragment': return { @@ -1204,7 +1224,7 @@ export class TcpMask extends XrayCommonClass { } } - static fromJson(json = {}) { + static fromJson(json: any = {}) { return new TcpMask( json.type || 'fragment', json.settings || {} @@ -1212,7 +1232,7 @@ export class TcpMask extends XrayCommonClass { } toJson() { - const cleanItem = item => { + const cleanItem = (item: any) => { const out = { ...item }; if (out.type === 'array') { delete out.packet; @@ -1225,7 +1245,7 @@ export class TcpMask extends XrayCommonClass { let settings = this.settings; if (this.type === 'header-custom' && settings) { - const cleanGroup = group => Array.isArray(group) ? group.map(cleanItem) : group; + const cleanGroup = (group: any) => Array.isArray(group) ? group.map(cleanItem) : group; settings = { ...settings, clients: Array.isArray(settings.clients) ? settings.clients.map(cleanGroup) : settings.clients, @@ -1242,18 +1262,18 @@ export class TcpMask extends XrayCommonClass { export class QuicParams extends XrayCommonClass { constructor( - congestion = 'bbr', - debug = false, - brutalUp = 65537, - brutalDown = 65537, - udpHop = undefined, - initStreamReceiveWindow = 8388608, - maxStreamReceiveWindow = 8388608, - initConnectionReceiveWindow = 20971520, - maxConnectionReceiveWindow = 20971520, - maxIdleTimeout = 30, - keepAlivePeriod = 5, - disablePathMTUDiscovery = false, + congestion: any = 'bbr', + debug: any = false, + brutalUp: any = 65537, + brutalDown: any = 65537, + udpHop: any = undefined, + initStreamReceiveWindow: any = 8388608, + maxStreamReceiveWindow: any = 8388608, + initConnectionReceiveWindow: any = 20971520, + maxConnectionReceiveWindow: any = 20971520, + maxIdleTimeout: any = 30, + keepAlivePeriod: any = 5, + disablePathMTUDiscovery: any = false, maxIncomingStreams = 1024, ) { super(); @@ -1280,7 +1300,7 @@ export class QuicParams extends XrayCommonClass { this.udpHop = value ? (this.udpHop || { ports: '20000-50000', interval: '5-10' }) : undefined; } - static fromJson(json = {}) { + static fromJson(json: any = {}) { if (!json || Object.keys(json).length === 0) return undefined; return new QuicParams( json.congestion, @@ -1300,7 +1320,7 @@ export class QuicParams extends XrayCommonClass { } toJson() { - const result = { congestion: this.congestion }; + const result: any = { congestion: this.congestion }; if (this.debug) result.debug = this.debug; if (['brutal', 'force-brutal'].includes(this.congestion)) { if (this.brutalUp) result.brutalUp = this.brutalUp; @@ -1320,10 +1340,10 @@ export class QuicParams extends XrayCommonClass { } export class FinalMaskStreamSettings extends XrayCommonClass { - constructor(tcp = [], udp = [], quicParams = undefined) { + constructor(tcp: any[] = [], udp: any[] = [], quicParams: any = undefined) { super(); - this.tcp = Array.isArray(tcp) ? tcp.map(t => t instanceof TcpMask ? t : new TcpMask(t.type, t.settings)) : []; - this.udp = Array.isArray(udp) ? udp.map(u => new UdpMask(u.type, u.settings)) : [new UdpMask(udp.type, udp.settings)]; + this.tcp = Array.isArray(tcp) ? tcp.map((t: any) => t instanceof TcpMask ? t : new TcpMask(t.type, t.settings)) : []; + this.udp = Array.isArray(udp) ? udp.map((u: any) => new UdpMask(u.type, u.settings)) : [new UdpMask((udp as any).type, (udp as any).settings)]; this.quicParams = quicParams instanceof QuicParams ? quicParams : (quicParams ? QuicParams.fromJson(quicParams) : undefined); } @@ -1335,7 +1355,7 @@ export class FinalMaskStreamSettings extends XrayCommonClass { this.quicParams = value ? (this.quicParams || new QuicParams()) : undefined; } - static fromJson(json = {}) { + static fromJson(json: any = {}) { return new FinalMaskStreamSettings( json.tcp || [], json.udp || [], @@ -1344,12 +1364,12 @@ export class FinalMaskStreamSettings extends XrayCommonClass { } toJson() { - const result = {}; + const result: any = {} as any; if (this.tcp && this.tcp.length > 0) { - result.tcp = this.tcp.map(t => t.toJson()); + result.tcp = this.tcp.map((t: any) => t.toJson()); } if (this.udp && this.udp.length > 0) { - result.udp = this.udp.map(udp => udp.toJson()); + result.udp = this.udp.map((udp: any) => udp.toJson()); } if (this.quicParams) { result.quicParams = this.quicParams.toJson(); @@ -1372,7 +1392,7 @@ export class StreamSettings extends XrayCommonClass { xhttpSettings = new xHTTPStreamSettings(), hysteriaSettings = new HysteriaStreamSettings(), finalmask = new FinalMaskStreamSettings(), - sockopt = undefined, + sockopt: any = undefined, ) { super(); this.network = network; @@ -1395,7 +1415,7 @@ export class StreamSettings extends XrayCommonClass { this.finalmask.tcp.push(new TcpMask(type)); } - delTcpMask(index) { + delTcpMask(index: number) { if (this.finalmask.tcp) { this.finalmask.tcp.splice(index, 1); } @@ -1405,7 +1425,7 @@ export class StreamSettings extends XrayCommonClass { this.finalmask.udp.push(new UdpMask(type)); } - delUdpMask(index) { + delUdpMask(index: number) { if (this.finalmask.udp) { this.finalmask.udp.splice(index, 1); } @@ -1451,7 +1471,7 @@ export class StreamSettings extends XrayCommonClass { this.sockopt = value ? new SockoptStreamSettings() : undefined; } - static fromJson(json = {}) { + static fromJson(json: any = {}) { return new StreamSettings( json.network, json.security, @@ -1510,7 +1530,7 @@ export class Sniffing extends XrayCommonClass { this.domainsExcluded = Array.isArray(domainsExcluded) ? domainsExcluded : []; } - static fromJson(json = {}) { + static fromJson(json: any = {}) { let destOverride = ObjectUtil.clone(json.destOverride); if (ObjectUtil.isEmpty(destOverride) || ObjectUtil.isArrEmpty(destOverride) || ObjectUtil.isEmpty(destOverride[0])) { destOverride = ['http', 'tls', 'quic', 'fakedns']; @@ -1541,8 +1561,21 @@ export class Sniffing extends XrayCommonClass { } export class Inbound extends XrayCommonClass { + static Settings: any; + static ClientBase: any; + static VmessSettings: any; + static VLESSSettings: any; + static TrojanSettings: any; + static ShadowsocksSettings: any; + static HysteriaSettings: any; + static TunnelSettings: any; + static MixedSettings: any; + static HttpSettings: any; + static WireguardSettings: any; + static TunSettings: any; + constructor( - port = RandomUtil.randomInteger(10000, 60000), + port: any = RandomUtil.randomInteger(10000, 60000), listen = '', protocol = Protocols.VLESS, settings = null, @@ -1568,7 +1601,7 @@ export class Inbound extends XrayCommonClass { // Looks for a "host"-named entry in xhttp.headers and returns its value, // or '' if not found. Used as a fallback when xhttp.host is empty so the // share URL still carries a usable Host hint. - static xhttpHostFallback(xhttp) { + static xhttpHostFallback(xhttp: any): string { if (!xhttp || !Array.isArray(xhttp.headers)) return ''; for (const h of xhttp.headers) { if (h && typeof h.name === 'string' && h.name.toLowerCase() === 'host') { @@ -1593,16 +1626,16 @@ export class Inbound extends XrayCommonClass { // // Truthy-only guards keep default inbounds emitting the same compact // URL they did before this helper grew. - static buildXhttpExtra(xhttp) { + static buildXhttpExtra(xhttp: any): any { if (!xhttp) return null; - const extra = {}; + const extra: any = {}; if (typeof xhttp.xPaddingBytes === 'string' && xhttp.xPaddingBytes.length > 0) { extra.xPaddingBytes = xhttp.xPaddingBytes; } if (xhttp.xPaddingObfsMode === true) { extra.xPaddingObfsMode = true; - ["xPaddingKey", "xPaddingHeader", "xPaddingPlacement", "xPaddingMethod"].forEach(k => { + ["xPaddingKey", "xPaddingHeader", "xPaddingPlacement", "xPaddingMethod"].forEach((k: string) => { if (typeof xhttp[k] === 'string' && xhttp[k].length > 0) { extra[k] = xhttp[k]; } @@ -1642,7 +1675,7 @@ export class Inbound extends XrayCommonClass { // expects. The server runtime ignores this field, but the client // (consuming the share link) honors it. if (Array.isArray(xhttp.headers) && xhttp.headers.length > 0) { - const headersMap = {}; + const headersMap: any = {}; for (const h of xhttp.headers) { if (h && h.name && h.name.toLowerCase() !== 'host') { headersMap[h.name] = h.value || ''; @@ -1663,7 +1696,7 @@ export class Inbound extends XrayCommonClass { // Without this, the admin's custom xPaddingBytes / sessionKey / etc. // never reach the client and handshakes are silently rejected with // `invalid padding (...) length: 0`. - static applyXhttpExtraToParams(xhttp, params) { + static applyXhttpExtraToParams(xhttp: any, params: any): void { if (!xhttp) return; params.set("path", xhttp.path); const host = xhttp.host?.length > 0 ? xhttp.host : Inbound.xhttpHostFallback(xhttp); @@ -1684,7 +1717,7 @@ export class Inbound extends XrayCommonClass { // of building a query string. (The base VMess link generator already // sets net/type/path/host, so we only contribute the SplitHTTPConfig // extra side here.) - static applyXhttpExtraToObj(xhttp, obj) { + static applyXhttpExtraToObj(xhttp: any, obj: any): void { if (!xhttp || !obj) return; if (typeof xhttp.xPaddingBytes === 'string' && xhttp.xPaddingBytes.length > 0) { obj.x_padding_bytes = xhttp.xPaddingBytes; @@ -1696,12 +1729,12 @@ export class Inbound extends XrayCommonClass { } } - static externalProxyAlpn(value) { + static externalProxyAlpn(value: any): any { if (Array.isArray(value)) return value.filter(Boolean).join(','); return typeof value === 'string' ? value : ''; } - static applyExternalProxyTLSParams(externalProxy, params, security) { + static applyExternalProxyTLSParams(externalProxy: any, params: any, security: any): void { if (!externalProxy || security !== 'tls') return; const sni = externalProxy.sni?.length > 0 ? externalProxy.sni : externalProxy.dest; if (sni?.length > 0) params.set("sni", sni); @@ -1710,7 +1743,7 @@ export class Inbound extends XrayCommonClass { if (alpn.length > 0) params.set("alpn", alpn); } - static applyExternalProxyTLSObj(externalProxy, obj, security) { + static applyExternalProxyTLSObj(externalProxy: any, obj: any, security: any): void { if (!externalProxy || !obj || security !== 'tls') return; const sni = externalProxy.sni?.length > 0 ? externalProxy.sni : externalProxy.dest; if (sni?.length > 0) obj.sni = sni; @@ -1719,15 +1752,15 @@ export class Inbound extends XrayCommonClass { if (alpn.length > 0) obj.alpn = alpn; } - static hasShareableFinalMaskValue(value) { + static hasShareableFinalMaskValue(value: any): boolean { if (value == null) { return false; } if (Array.isArray(value)) { - return value.some(item => Inbound.hasShareableFinalMaskValue(item)); + return value.some((item: any) => Inbound.hasShareableFinalMaskValue(item)); } if (typeof value === 'object') { - return Object.values(value).some(item => Inbound.hasShareableFinalMaskValue(item)); + return Object.values(value).some((item: any) => Inbound.hasShareableFinalMaskValue(item)); } if (typeof value === 'string') { return value.length > 0; @@ -1735,7 +1768,7 @@ export class Inbound extends XrayCommonClass { return true; } - static serializeFinalMask(finalmask) { + static serializeFinalMask(finalmask: any): any { if (!finalmask) { return ''; } @@ -1745,7 +1778,7 @@ export class Inbound extends XrayCommonClass { // Export finalmask with the same compact JSON payload shape that // v2rayN-compatible share links use: fm=. - static applyFinalMaskToParams(finalmask, params) { + static applyFinalMaskToParams(finalmask: any, params: any): void { if (!params) return; const payload = Inbound.serializeFinalMask(finalmask); if (payload.length > 0) { @@ -1755,7 +1788,7 @@ export class Inbound extends XrayCommonClass { // VMess links are a base64 JSON object, so keep the same fm payload // under a flat property instead of a URL query string. - static applyFinalMaskToObj(finalmask, obj) { + static applyFinalMaskToObj(finalmask: any, obj: any): void { if (!obj) return; const payload = Inbound.serializeFinalMask(finalmask); if (payload.length > 0) { @@ -1847,7 +1880,7 @@ export class Inbound extends XrayCommonClass { return ""; } - getHeader(obj, name) { + getHeader(obj: any, name: any) { for (const header of obj.headers) { if (header.name.toLowerCase() === name.toLowerCase()) { return header.value; @@ -1886,8 +1919,8 @@ export class Inbound extends XrayCommonClass { return this.stream.grpc.serviceName; } - isExpiry(index) { - let exp = this.clients[index].expiryTime; + isExpiry(index: number) { + const exp = this.clients[index].expiryTime; return exp > 0 ? exp < new Date().getTime() : false; } @@ -1911,7 +1944,7 @@ export class Inbound extends XrayCommonClass { if (!this.canEnableTlsFlow()) return false; const clients = this.settings?.vlesses; if (!Array.isArray(clients)) return false; - return clients.some(c => c?.flow === TLS_FLOW_CONTROL.VISION); + return clients.some((c: any) => c?.flow === TLS_FLOW_CONTROL.VISION); } canEnableReality() { @@ -1933,12 +1966,12 @@ export class Inbound extends XrayCommonClass { this.sniffing = new Sniffing(); } - genVmessLink(address = '', port = this.port, forceTls, remark = '', clientId, security, externalProxy = null) { + genVmessLink(address: any = '', port: any = this.port, forceTls?: any, remark: any = '', clientId?: any, security?: any, externalProxy: any = null) { if (this.protocol !== Protocols.VMESS) { return ''; } const tls = forceTls == 'same' ? this.stream.security : forceTls; - let obj = { + const obj: any = { v: '2', ps: remark, add: address, @@ -2002,7 +2035,7 @@ export class Inbound extends XrayCommonClass { return 'vmess://' + Base64.encode(JSON.stringify(obj, null, 2)); } - genVLESSLink(address = '', port = this.port, forceTls, remark = '', clientId, flow, externalProxy = null) { + genVLESSLink(address: any = '', port: any = this.port, forceTls?: any, remark: any = '', clientId?: any, flow?: any, externalProxy: any = null) { const uuid = clientId; const type = this.stream.network; const security = forceTls == 'same' ? this.stream.security : forceTls; @@ -2010,12 +2043,12 @@ export class Inbound extends XrayCommonClass { params.set("type", this.stream.network); params.set("encryption", this.settings.encryption); switch (type) { - case "tcp": + case "tcp": { const tcp = this.stream.tcp; if (tcp.type === 'http') { const request = tcp.request; params.set("path", request.path.join(',')); - const index = request.headers.findIndex(header => header.name.toLowerCase() === 'host'); + const index = request.headers.findIndex((header: any) => header.name.toLowerCase() === 'host'); if (index >= 0) { const host = request.headers[index].value; params.set("host", host); @@ -2023,17 +2056,20 @@ export class Inbound extends XrayCommonClass { params.set("headerType", 'http'); } break; - case "kcp": + } + case "kcp": { const kcp = this.stream.kcp; params.set("mtu", kcp.mtu); params.set("tti", kcp.tti); break; - case "ws": + } + case "ws": { const ws = this.stream.ws; params.set("path", ws.path); params.set("host", ws.host?.length > 0 ? ws.host : this.getHeader(ws, 'host')); break; - case "grpc": + } + case "grpc": { const grpc = this.stream.grpc; params.set("serviceName", grpc.serviceName); params.set("authority", grpc.authority); @@ -2041,11 +2077,13 @@ export class Inbound extends XrayCommonClass { params.set("mode", "multi"); } break; - case "httpupgrade": + } + case "httpupgrade": { const httpupgrade = this.stream.httpupgrade; params.set("path", httpupgrade.path); params.set("host", httpupgrade.host?.length > 0 ? httpupgrade.host : this.getHeader(httpupgrade, 'host')); break; + } case "xhttp": Inbound.applyXhttpExtraToParams(this.stream.xhttp, params); break; @@ -2105,19 +2143,19 @@ export class Inbound extends XrayCommonClass { return url.toString(); } - genSSLink(address = '', port = this.port, forceTls, remark = '', clientPassword, externalProxy = null) { - let settings = this.settings; + genSSLink(address: any = '', port: any = this.port, forceTls?: any, remark: any = '', clientPassword?: any, externalProxy: any = null) { + const settings = this.settings; const type = this.stream.network; const security = forceTls == 'same' ? this.stream.security : forceTls; const params = new Map(); params.set("type", this.stream.network); switch (type) { - case "tcp": + case "tcp": { const tcp = this.stream.tcp; if (tcp.type === 'http') { const request = tcp.request; params.set("path", request.path.join(',')); - const index = request.headers.findIndex(header => header.name.toLowerCase() === 'host'); + const index = request.headers.findIndex((header: any) => header.name.toLowerCase() === 'host'); if (index >= 0) { const host = request.headers[index].value; params.set("host", host); @@ -2125,17 +2163,20 @@ export class Inbound extends XrayCommonClass { params.set("headerType", 'http'); } break; - case "kcp": + } + case "kcp": { const kcp = this.stream.kcp; params.set("mtu", kcp.mtu); params.set("tti", kcp.tti); break; - case "ws": + } + case "ws": { const ws = this.stream.ws; params.set("path", ws.path); params.set("host", ws.host?.length > 0 ? ws.host : this.getHeader(ws, 'host')); break; - case "grpc": + } + case "grpc": { const grpc = this.stream.grpc; params.set("serviceName", grpc.serviceName); params.set("authority", grpc.authority); @@ -2143,11 +2184,13 @@ export class Inbound extends XrayCommonClass { params.set("mode", "multi"); } break; - case "httpupgrade": + } + case "httpupgrade": { const httpupgrade = this.stream.httpupgrade; params.set("path", httpupgrade.path); params.set("host", httpupgrade.host?.length > 0 ? httpupgrade.host : this.getHeader(httpupgrade, 'host')); break; + } case "xhttp": Inbound.applyXhttpExtraToParams(this.stream.xhttp, params); break; @@ -2171,11 +2214,11 @@ export class Inbound extends XrayCommonClass { } - let password = new Array(); + const password: string[] = []; if (this.isSS2022) password.push(settings.password); if (this.isSSMultiUser) password.push(clientPassword); - let link = `ss://${Base64.encode(`${settings.method}:${password.join(':')}`, true)}@${address}:${port}`; + const link = `ss://${Base64.encode(`${settings.method}:${password.join(':')}`, true)}@${address}:${port}`; const url = new URL(link); for (const [key, value] of params) { url.searchParams.set(key, value) @@ -2184,18 +2227,18 @@ export class Inbound extends XrayCommonClass { return url.toString(); } - genTrojanLink(address = '', port = this.port, forceTls, remark = '', clientPassword, externalProxy = null) { + genTrojanLink(address: any = '', port: any = this.port, forceTls?: any, remark: any = '', clientPassword?: any, externalProxy: any = null) { const security = forceTls == 'same' ? this.stream.security : forceTls; const type = this.stream.network; const params = new Map(); params.set("type", this.stream.network); switch (type) { - case "tcp": + case "tcp": { const tcp = this.stream.tcp; if (tcp.type === 'http') { const request = tcp.request; params.set("path", request.path.join(',')); - const index = request.headers.findIndex(header => header.name.toLowerCase() === 'host'); + const index = request.headers.findIndex((header: any) => header.name.toLowerCase() === 'host'); if (index >= 0) { const host = request.headers[index].value; params.set("host", host); @@ -2203,17 +2246,20 @@ export class Inbound extends XrayCommonClass { params.set("headerType", 'http'); } break; - case "kcp": + } + case "kcp": { const kcp = this.stream.kcp; params.set("mtu", kcp.mtu); params.set("tti", kcp.tti); break; - case "ws": + } + case "ws": { const ws = this.stream.ws; params.set("path", ws.path); params.set("host", ws.host?.length > 0 ? ws.host : this.getHeader(ws, 'host')); break; - case "grpc": + } + case "grpc": { const grpc = this.stream.grpc; params.set("serviceName", grpc.serviceName); params.set("authority", grpc.authority); @@ -2221,11 +2267,13 @@ export class Inbound extends XrayCommonClass { params.set("mode", "multi"); } break; - case "httpupgrade": + } + case "httpupgrade": { const httpupgrade = this.stream.httpupgrade; params.set("path", httpupgrade.path); params.set("host", httpupgrade.host?.length > 0 ? httpupgrade.host : this.getHeader(httpupgrade, 'host')); break; + } case "xhttp": Inbound.applyXhttpExtraToParams(this.stream.xhttp, params); break; @@ -2279,7 +2327,7 @@ export class Inbound extends XrayCommonClass { return url.toString(); } - genHysteriaLink(address = '', port = this.port, remark = '', clientAuth) { + genHysteriaLink(address: any = '', port: any = this.port, remark: any = '', clientAuth?: any) { const protocol = this.settings.version == 2 ? "hysteria2" : "hysteria"; const link = `${protocol}://${clientAuth}@${address}:${port}`; @@ -2293,7 +2341,7 @@ export class Inbound extends XrayCommonClass { const udpMasks = this.stream?.finalmask?.udp; if (Array.isArray(udpMasks)) { - const salamanderMask = udpMasks.find(mask => mask?.type === 'salamander'); + const salamanderMask = udpMasks.find((mask: any) => mask?.type === 'salamander'); const obfsPassword = salamanderMask?.settings?.password; if (typeof obfsPassword === 'string' && obfsPassword.length > 0) { params.set("obfs", "salamander"); @@ -2311,7 +2359,7 @@ export class Inbound extends XrayCommonClass { return url.toString(); } - getWireguardTxt(address, port, remark, peerId) { + getWireguardTxt(address: any, port: any, remark: any, peerId: any) { let txt = `[Interface]\n` txt += `PrivateKey = ${this.settings.peers[peerId].privateKey}\n` txt += `Address = ${this.settings.peers[peerId].allowedIPs[0]}\n` @@ -2333,7 +2381,7 @@ export class Inbound extends XrayCommonClass { return txt; } - getWireguardLink(address, port, remark, peerId) { + getWireguardLink(address: any, port: any, remark: any, peerId: any) { const peer = this.settings?.peers?.[peerId]; if (!peer) return ''; @@ -2370,8 +2418,8 @@ export class Inbound extends XrayCommonClass { genWireguardLinks(remark = '', remarkModel = '-ieo', hostOverride = '') { const addr = this._resolveAddr(hostOverride); const separationChar = remarkModel.charAt(0); - let links = []; - this.settings.peers.forEach((p, index) => { + const links: any[] = []; + this.settings.peers.forEach((_p: any, index: number) => { links.push(this.getWireguardLink(addr, this.port, remark + separationChar + (index + 1), index)); }); return links.join('\r\n'); @@ -2380,14 +2428,14 @@ export class Inbound extends XrayCommonClass { genWireguardConfigs(remark = '', remarkModel = '-ieo', hostOverride = '') { const addr = this._resolveAddr(hostOverride); const separationChar = remarkModel.charAt(0); - let links = []; - this.settings.peers.forEach((p, index) => { + const links: any[] = []; + this.settings.peers.forEach((_p: any, index: number) => { links.push(this.getWireguardTxt(addr, this.port, remark + separationChar + (index + 1), index)); }); return links.join('\r\n'); } - genLink(address = '', port = this.port, forceTls = 'same', remark = '', client, externalProxy = null) { + genLink(address: any = '', port: any = this.port, forceTls: any = 'same', remark: any = '', client?: any, externalProxy: any = null) { switch (this.protocol) { case Protocols.VMESS: return this.genVmessLink(address, port, forceTls, remark, client.id, client.security, externalProxy); @@ -2403,28 +2451,28 @@ export class Inbound extends XrayCommonClass { } } - genAllLinks(remark = '', remarkModel = '-ieo', client, hostOverride = '') { - let result = []; - let email = client ? client.email : ''; - let addr = this._resolveAddr(hostOverride); - let port = this.port; + genAllLinks(remark: any = '', remarkModel: any = '-ieo', client?: any, hostOverride: any = '') { + const result: any[] = []; + const email = client ? client.email : ''; + const addr = this._resolveAddr(hostOverride); + const port = this.port; const separationChar = remarkModel.charAt(0); const orderChars = remarkModel.slice(1); - let orders = { + const orders: any = { 'i': remark, 'e': email, 'o': '', }; if (ObjectUtil.isArrEmpty(this.stream.externalProxy)) { - let r = orderChars.split('').map(char => orders[char]).filter(x => x.length > 0).join(separationChar); + const r = orderChars.split('').map((char: string) => orders[char]).filter((x: any) => x.length > 0).join(separationChar); result.push({ remark: r, link: this.genLink(addr, port, 'same', r, client) }); } else { - this.stream.externalProxy.forEach((ep) => { + this.stream.externalProxy.forEach((ep: any) => { orders['o'] = ep.remark; - let r = orderChars.split('').map(char => orders[char]).filter(x => x.length > 0).join(separationChar); + const r = orderChars.split('').map((char: string) => orders[char]).filter((x: any) => x.length > 0).join(separationChar); result.push({ remark: r, link: this.genLink(ep.dest, ep.port, ep.forceTls, r, client, ep) @@ -2435,11 +2483,11 @@ export class Inbound extends XrayCommonClass { } genInboundLinks(remark = '', remarkModel = '-ieo', hostOverride = '') { - let addr = this._resolveAddr(hostOverride); + const addr = this._resolveAddr(hostOverride); if (this.clients) { - let links = []; - this.clients.forEach((client) => { - this.genAllLinks(remark, remarkModel, client, hostOverride).forEach(l => { + const links: any[] = []; + this.clients.forEach((client: any) => { + this.genAllLinks(remark, remarkModel, client, hostOverride).forEach((l: any) => { links.push(l.link); }) }); @@ -2453,7 +2501,7 @@ export class Inbound extends XrayCommonClass { } } - static fromJson(json = {}) { + static fromJson(json: any = {}) { return new Inbound( json.port, json.listen, @@ -2470,7 +2518,7 @@ export class Inbound extends XrayCommonClass { // Only these protocols use streamSettings const streamProtocols = [Protocols.VLESS, Protocols.VMESS, Protocols.TROJAN, Protocols.SHADOWSOCKS, Protocols.HYSTERIA]; - const result = { + const result: any = { port: this.port, listen: this.listen, protocol: this.protocol, @@ -2490,12 +2538,12 @@ export class Inbound extends XrayCommonClass { } Inbound.Settings = class extends XrayCommonClass { - constructor(protocol) { + constructor(protocol: any) { super(); this.protocol = protocol; } - static getSettings(protocol) { + static getSettings(protocol: any): any { switch (protocol) { case Protocols.VMESS: return new Inbound.VmessSettings(protocol); case Protocols.VLESS: return new Inbound.VLESSSettings(protocol); @@ -2511,7 +2559,7 @@ Inbound.Settings = class extends XrayCommonClass { } } - static fromJson(protocol, json) { + static fromJson(protocol: any, json: any): any { switch (protocol) { case Protocols.VMESS: return Inbound.VmessSettings.fromJson(json); case Protocols.VLESS: return Inbound.VLESSSettings.fromJson(json); @@ -2535,17 +2583,17 @@ Inbound.Settings = class extends XrayCommonClass { /** Shared user-quota fields and UI helpers for multi-user protocol clients. */ Inbound.ClientBase = class extends XrayCommonClass { constructor( - email = RandomUtil.randomLowerAndNum(8), - limitIp = 0, - totalGB = 0, - expiryTime = 0, - enable = true, - tgId = '', - subId = RandomUtil.randomLowerAndNum(16), - comment = '', - reset = 0, - created_at = undefined, - updated_at = undefined, + email: any = RandomUtil.randomLowerAndNum(8), + limitIp: any = 0, + totalGB: any = 0, + expiryTime: any = 0, + enable: any = true, + tgId: any = '', + subId: any = RandomUtil.randomLowerAndNum(16), + comment: any = '', + reset: any = 0, + created_at: any = undefined, + updated_at: any = undefined, ) { super(); this.email = email; @@ -2561,7 +2609,7 @@ Inbound.ClientBase = class extends XrayCommonClass { this.updated_at = updated_at; } - static commonArgsFromJson(json = {}) { + static commonArgsFromJson(json: any = {}) { return [ json.email, json.limitIp, @@ -2603,7 +2651,7 @@ Inbound.ClientBase = class extends XrayCommonClass { return dayjs(this.expiryTime); } - set _expiryTime(t) { + set _expiryTime(t: any) { if (t == null || t === '') { this.expiryTime = 0; } else { @@ -2621,34 +2669,34 @@ Inbound.ClientBase = class extends XrayCommonClass { }; Inbound.VmessSettings = class extends Inbound.Settings { - constructor(protocol, - vmesses = []) { + constructor(protocol: any, + vmesses: any[] = []) { super(protocol); this.vmesses = vmesses; } - indexOfVmessById(id) { - return this.vmesses.findIndex(VMESS => VMESS.id === id); + indexOfVmessById(id: any) { + return this.vmesses.findIndex((VMESS: any) => VMESS.id === id); } - addVmess(VMESS) { + addVmess(VMESS: any) { if (this.indexOfVmessById(VMESS.id) >= 0) { return false; } this.vmesses.push(VMESS); } - delVmess(VMESS) { + delVmess(VMESS: any) { const i = this.indexOfVmessById(VMESS.id); if (i >= 0) { this.vmesses.splice(i, 1); } } - static fromJson(json = {}) { + static fromJson(json: any = {}) { return new Inbound.VmessSettings( Protocols.VMESS, - (json.clients || []).map(client => Inbound.VmessSettings.VMESS.fromJson(client)), + (json.clients || []).map((client: any) => Inbound.VmessSettings.VMESS.fromJson(client)), ); } @@ -2661,16 +2709,16 @@ Inbound.VmessSettings = class extends Inbound.Settings { Inbound.VmessSettings.VMESS = class extends Inbound.ClientBase { constructor( - id = RandomUtil.randomUUID(), - security = USERS_SECURITY.AUTO, - email, limitIp, totalGB, expiryTime, enable, tgId, subId, comment, reset, created_at, updated_at, + id: any = RandomUtil.randomUUID(), + security: any = USERS_SECURITY.AUTO, + email?: any, limitIp?: any, totalGB?: any, expiryTime?: any, enable?: any, tgId?: any, subId?: any, comment?: any, reset?: any, created_at?: any, updated_at?: any, ) { super(email, limitIp, totalGB, expiryTime, enable, tgId, subId, comment, reset, created_at, updated_at); this.id = id; this.security = security; } - static fromJson(json = {}) { + static fromJson(json: any = {}) { return new Inbound.VmessSettings.VMESS( json.id, json.security, @@ -2689,12 +2737,12 @@ Inbound.VmessSettings.VMESS = class extends Inbound.ClientBase { Inbound.VLESSSettings = class extends Inbound.Settings { constructor( - protocol, - vlesses = [], - decryption = "none", - encryption = "none", - fallbacks = [], - testseed = [], + protocol: any, + vlesses: any[] = [], + decryption: any = "none", + encryption: any = "none", + fallbacks: any[] = [], + testseed: any[] = [], ) { super(protocol); this.vlesses = vlesses; @@ -2708,31 +2756,31 @@ Inbound.VLESSSettings = class extends Inbound.Settings { this.fallbacks.push(new Inbound.VLESSSettings.Fallback()); } - delFallback(index) { + delFallback(index: number) { this.fallbacks.splice(index, 1); } // Empty array means "use server defaults" (won't be sent). // Anything else must be exactly 4 positive integers. - static isValidTestseed(arr) { + static isValidTestseed(arr: any): boolean { if (!Array.isArray(arr) || arr.length === 0) return true; if (arr.length !== 4) return false; - return arr.every(v => Number.isInteger(v) && v > 0); + return arr.every((v: any) => Number.isInteger(v) && v > 0); } - static fromJson(json = {}) { + static fromJson(json: any = {}) { // Preserve a saved testseed only if it's a valid 4-positive-int array; otherwise leave empty // so toJson omits it and the form falls back to placeholder defaults. const saved = json.testseed; const testseed = (Array.isArray(saved) && saved.length === 4 - && saved.every(v => Number.isInteger(v) && v > 0)) + && saved.every((v: any) => Number.isInteger(v) && v > 0)) ? saved : []; const obj = new Inbound.VLESSSettings( Protocols.VLESS, - (json.clients || []).map(client => Inbound.VLESSSettings.VLESS.fromJson(client)), + (json.clients || []).map((client: any) => Inbound.VLESSSettings.VLESS.fromJson(client)), json.decryption, json.encryption, Inbound.VLESSSettings.Fallback.fromJson(json.fallbacks || []), @@ -2743,7 +2791,7 @@ Inbound.VLESSSettings = class extends Inbound.Settings { toJson() { - const json = { + const json: any = { clients: Inbound.VLESSSettings.toJsonArray(this.vlesses), }; @@ -2762,11 +2810,11 @@ Inbound.VLESSSettings = class extends Inbound.Settings { // testseed is only meaningful for the exact xtls-rprx-vision flow, and only when // the user supplied a complete 4-positive-int array. Otherwise omit and let the // backend fall back to its safe defaults. - const hasVisionFlow = this.vlesses && this.vlesses.some(v => v.flow === TLS_FLOW_CONTROL.VISION); + const hasVisionFlow = this.vlesses && this.vlesses.some((v: any) => v.flow === TLS_FLOW_CONTROL.VISION); if (hasVisionFlow && Array.isArray(this.testseed) && this.testseed.length === 4 - && this.testseed.every(v => Number.isInteger(v) && v > 0)) { + && this.testseed.every((v: any) => Number.isInteger(v) && v > 0)) { json.testseed = this.testseed; } @@ -2776,11 +2824,11 @@ Inbound.VLESSSettings = class extends Inbound.Settings { Inbound.VLESSSettings.VLESS = class extends Inbound.ClientBase { constructor( - id = RandomUtil.randomUUID(), - flow = '', - reverseTag = '', - reverseSniffing = new Sniffing(), - email, limitIp, totalGB, expiryTime, enable, tgId, subId, comment, reset, created_at, updated_at, + id: any = RandomUtil.randomUUID(), + flow: any = '', + reverseTag: any = '', + reverseSniffing: any = new Sniffing(), + email?: any, limitIp?: any, totalGB?: any, expiryTime?: any, enable?: any, tgId?: any, subId?: any, comment?: any, reset?: any, created_at?: any, updated_at?: any, ) { super(email, limitIp, totalGB, expiryTime, enable, tgId, subId, comment, reset, created_at, updated_at); this.id = id; @@ -2789,7 +2837,7 @@ Inbound.VLESSSettings.VLESS = class extends Inbound.ClientBase { this.reverseSniffing = reverseSniffing; } - static fromJson(json = {}) { + static fromJson(json: any = {}) { return new Inbound.VLESSSettings.VLESS( json.id, json.flow, @@ -2800,7 +2848,7 @@ Inbound.VLESSSettings.VLESS = class extends Inbound.ClientBase { } toJson() { - const json = { + const json: any = { id: this.id, flow: this.flow, ...this._clientBaseToJson(), @@ -2825,20 +2873,20 @@ Inbound.VLESSSettings.Fallback = class extends XrayCommonClass { } toJson() { - return XrayCommonClass.fallbackToJson(this); + return XrayCommonClass.fallbackToJson(this as unknown as FallbackEntry); } - static fromJson(json = []) { - return (json || []).map(f => new Inbound.VLESSSettings.Fallback( + static fromJson(json: any = []) { + return (json || []).map((f: any) => new Inbound.VLESSSettings.Fallback( f.name, f.alpn, f.path, f.dest, f.xver, )); } }; Inbound.TrojanSettings = class extends Inbound.Settings { - constructor(protocol, - trojans = [], - fallbacks = [],) { + constructor(protocol: any, + trojans: any[] = [], + fallbacks: any[] = [],) { super(protocol); this.trojans = trojans; this.fallbacks = fallbacks; @@ -2848,19 +2896,19 @@ Inbound.TrojanSettings = class extends Inbound.Settings { this.fallbacks.push(new Inbound.TrojanSettings.Fallback()); } - delFallback(index) { + delFallback(index: number) { this.fallbacks.splice(index, 1); } - static fromJson(json = {}) { + static fromJson(json: any = {}) { return new Inbound.TrojanSettings( Protocols.TROJAN, - (json.clients || []).map(client => Inbound.TrojanSettings.Trojan.fromJson(client)), + (json.clients || []).map((client: any) => Inbound.TrojanSettings.Trojan.fromJson(client)), Inbound.TrojanSettings.Fallback.fromJson(json.fallbacks),); } toJson() { - const json = { + const json: any = { clients: Inbound.TrojanSettings.toJsonArray(this.trojans), }; if (this.fallbacks && this.fallbacks.length > 0) { @@ -2873,7 +2921,7 @@ Inbound.TrojanSettings = class extends Inbound.Settings { Inbound.TrojanSettings.Trojan = class extends Inbound.ClientBase { constructor( password = RandomUtil.randomSeq(10), - email, limitIp, totalGB, expiryTime, enable, tgId, subId, comment, reset, created_at, updated_at, + email?: any, limitIp?: any, totalGB?: any, expiryTime?: any, enable?: any, tgId?: any, subId?: any, comment?: any, reset?: any, created_at?: any, updated_at?: any, ) { super(email, limitIp, totalGB, expiryTime, enable, tgId, subId, comment, reset, created_at, updated_at); this.password = password; @@ -2886,7 +2934,7 @@ Inbound.TrojanSettings.Trojan = class extends Inbound.ClientBase { }; } - static fromJson(json = {}) { + static fromJson(json: any = {}) { return new Inbound.TrojanSettings.Trojan( json.password, ...Inbound.ClientBase.commonArgsFromJson(json), @@ -2905,22 +2953,22 @@ Inbound.TrojanSettings.Fallback = class extends XrayCommonClass { } toJson() { - return XrayCommonClass.fallbackToJson(this); + return XrayCommonClass.fallbackToJson(this as unknown as FallbackEntry); } - static fromJson(json = []) { - return (json || []).map(f => new Inbound.TrojanSettings.Fallback( + static fromJson(json: any = []) { + return (json || []).map((f: any) => new Inbound.TrojanSettings.Fallback( f.name, f.alpn, f.path, f.dest, f.xver, )); } }; Inbound.ShadowsocksSettings = class extends Inbound.Settings { - constructor(protocol, - method = SSMethods.BLAKE3_AES_256_GCM, - password = RandomUtil.randomShadowsocksPassword(), - network = 'tcp', - shadowsockses = [], + constructor(protocol: any, + method: any = SSMethods.BLAKE3_AES_256_GCM, + password: any = RandomUtil.randomShadowsocksPassword(), + network: any = 'tcp', + shadowsockses: any[] = [], ivCheck = false, ) { super(protocol); @@ -2931,13 +2979,13 @@ Inbound.ShadowsocksSettings = class extends Inbound.Settings { this.ivCheck = ivCheck; } - static fromJson(json = {}) { + static fromJson(json: any = {}) { return new Inbound.ShadowsocksSettings( Protocols.SHADOWSOCKS, json.method, json.password, json.network, - (json.clients || []).map(client => Inbound.ShadowsocksSettings.Shadowsocks.fromJson(client)), + (json.clients || []).map((client: any) => Inbound.ShadowsocksSettings.Shadowsocks.fromJson(client)), json.ivCheck, ); } @@ -2957,7 +3005,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends Inbound.ClientBase { constructor( method = '', password = RandomUtil.randomShadowsocksPassword(), - email, limitIp, totalGB, expiryTime, enable, tgId, subId, comment, reset, created_at, updated_at, + email?: any, limitIp?: any, totalGB?: any, expiryTime?: any, enable?: any, tgId?: any, subId?: any, comment?: any, reset?: any, created_at?: any, updated_at?: any, ) { super(email, limitIp, totalGB, expiryTime, enable, tgId, subId, comment, reset, created_at, updated_at); this.method = method; @@ -2972,7 +3020,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends Inbound.ClientBase { }; } - static fromJson(json = {}) { + static fromJson(json: any = {}) { return new Inbound.ShadowsocksSettings.Shadowsocks( json.method, json.password, @@ -2982,17 +3030,17 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends Inbound.ClientBase { }; Inbound.HysteriaSettings = class extends Inbound.Settings { - constructor(protocol, version = 2, hysterias = []) { + constructor(protocol: any, version: any = 2, hysterias: any[] = []) { super(protocol); this.version = version; this.hysterias = hysterias; } - static fromJson(json = {}) { + static fromJson(json: any = {}) { return new Inbound.HysteriaSettings( Protocols.HYSTERIA, json.version ?? 2, - (json.clients || []).map(client => Inbound.HysteriaSettings.Hysteria.fromJson(client)), + (json.clients || []).map((client: any) => Inbound.HysteriaSettings.Hysteria.fromJson(client)), ); } @@ -3007,7 +3055,7 @@ Inbound.HysteriaSettings = class extends Inbound.Settings { Inbound.HysteriaSettings.Hysteria = class extends Inbound.ClientBase { constructor( auth = RandomUtil.randomSeq(10), - email, limitIp, totalGB, expiryTime, enable, tgId, subId, comment, reset, created_at, updated_at, + email?: any, limitIp?: any, totalGB?: any, expiryTime?: any, enable?: any, tgId?: any, subId?: any, comment?: any, reset?: any, created_at?: any, updated_at?: any, ) { super(email, limitIp, totalGB, expiryTime, enable, tgId, subId, comment, reset, created_at, updated_at); this.auth = auth; @@ -3020,7 +3068,7 @@ Inbound.HysteriaSettings.Hysteria = class extends Inbound.ClientBase { }; } - static fromJson(json = {}) { + static fromJson(json: any = {}) { return new Inbound.HysteriaSettings.Hysteria( json.auth, ...Inbound.ClientBase.commonArgsFromJson(json), @@ -3030,12 +3078,12 @@ Inbound.HysteriaSettings.Hysteria = class extends Inbound.ClientBase { Inbound.TunnelSettings = class extends Inbound.Settings { constructor( - protocol, - rewriteAddress, - rewritePort, - portMap = [], - allowedNetwork = 'tcp,udp', - followRedirect = false + protocol: any, + rewriteAddress?: any, + rewritePort?: any, + portMap: any[] = [], + allowedNetwork: any = 'tcp,udp', + followRedirect: any = false ) { super(protocol); this.rewriteAddress = rewriteAddress; @@ -3049,11 +3097,11 @@ Inbound.TunnelSettings = class extends Inbound.Settings { this.portMap.push({ name: port, value: target }); } - removePortMap(index) { + removePortMap(index: number) { this.portMap.splice(index, 1); } - static fromJson(json = {}) { + static fromJson(json: any = {}) { return new Inbound.TunnelSettings( Protocols.TUNNEL, json.rewriteAddress, @@ -3076,7 +3124,7 @@ Inbound.TunnelSettings = class extends Inbound.Settings { }; Inbound.MixedSettings = class extends Inbound.Settings { - constructor(protocol, auth = 'password', accounts = [new Inbound.MixedSettings.SocksAccount()], udp = false, ip = '127.0.0.1') { + constructor(protocol: any, auth: any = 'password', accounts: any[] = [new Inbound.MixedSettings.SocksAccount()], udp: any = false, ip: any = '127.0.0.1') { super(protocol); this.auth = auth; this.accounts = accounts; @@ -3084,19 +3132,19 @@ Inbound.MixedSettings = class extends Inbound.Settings { this.ip = ip; } - addAccount(account) { + addAccount(account: any) { this.accounts.push(account); } - delAccount(index) { + delAccount(index: number) { this.accounts.splice(index, 1); } - static fromJson(json = {}) { + static fromJson(json: any = {}) { let accounts; if (json.auth === 'password') { accounts = json.accounts.map( - account => Inbound.MixedSettings.SocksAccount.fromJson(account) + (account: any) => Inbound.MixedSettings.SocksAccount.fromJson(account) ) } return new Inbound.MixedSettings( @@ -3111,7 +3159,7 @@ Inbound.MixedSettings = class extends Inbound.Settings { toJson() { return { auth: this.auth, - accounts: this.auth === 'password' ? this.accounts.map(account => account.toJson()) : undefined, + accounts: this.auth === 'password' ? this.accounts.map((account: any) => account.toJson()) : undefined, udp: this.udp, ip: this.ip, }; @@ -3124,34 +3172,34 @@ Inbound.MixedSettings.SocksAccount = class extends XrayCommonClass { this.pass = pass; } - static fromJson(json = {}) { + static fromJson(json: any = {}) { return new Inbound.MixedSettings.SocksAccount(json.user, json.pass); } }; Inbound.HttpSettings = class extends Inbound.Settings { constructor( - protocol, - accounts = [new Inbound.HttpSettings.HttpAccount()], - allowTransparent = false, + protocol: any, + accounts: any[] = [new Inbound.HttpSettings.HttpAccount()], + allowTransparent: any = false, ) { super(protocol); this.accounts = accounts; this.allowTransparent = allowTransparent; } - addAccount(account) { + addAccount(account: any) { this.accounts.push(account); } - delAccount(index) { + delAccount(index: number) { this.accounts.splice(index, 1); } - static fromJson(json = {}) { + static fromJson(json: any = {}) { return new Inbound.HttpSettings( Protocols.HTTP, - json.accounts.map(account => Inbound.HttpSettings.HttpAccount.fromJson(account)), + json.accounts.map((account: any) => Inbound.HttpSettings.HttpAccount.fromJson(account)), json.allowTransparent, ); } @@ -3171,20 +3219,21 @@ Inbound.HttpSettings.HttpAccount = class extends XrayCommonClass { this.pass = pass; } - static fromJson(json = {}) { + static fromJson(json: any = {}) { return new Inbound.HttpSettings.HttpAccount(json.user, json.pass); } }; Inbound.WireguardSettings = class extends XrayCommonClass { constructor( - protocol, - mtu = 1420, - secretKey = Wireguard.generateKeypair().privateKey, - peers = [new Inbound.WireguardSettings.Peer()], - noKernelTun = false + protocol?: any, + mtu: any = 1420, + secretKey: any = Wireguard.generateKeypair().privateKey, + peers: any[] = [new Inbound.WireguardSettings.Peer()], + noKernelTun: any = false ) { - super(protocol); + super(); + this.protocol = protocol; this.mtu = mtu; this.secretKey = secretKey; this.pubKey = secretKey.length > 0 ? Wireguard.generateKeypair(secretKey).publicKey : ''; @@ -3196,16 +3245,16 @@ Inbound.WireguardSettings = class extends XrayCommonClass { this.peers.push(new Inbound.WireguardSettings.Peer(null, null, '', ['10.0.0.' + (this.peers.length + 2)])); } - delPeer(index) { + delPeer(index: number) { this.peers.splice(index, 1); } - static fromJson(json = {}) { + static fromJson(json: any = {}) { return new Inbound.WireguardSettings( Protocols.WIREGUARD, json.mtu, json.secretKey, - json.peers.map(peer => Inbound.WireguardSettings.Peer.fromJson(peer)), + json.peers.map((peer: any) => Inbound.WireguardSettings.Peer.fromJson(peer)), json.noKernelTun, ); } @@ -3221,7 +3270,7 @@ Inbound.WireguardSettings = class extends XrayCommonClass { }; Inbound.WireguardSettings.Peer = class extends XrayCommonClass { - constructor(privateKey, publicKey, psk = '', allowedIPs = ['10.0.0.2/32'], keepAlive = 0) { + constructor(privateKey?: any, publicKey?: any, psk: any = '', allowedIPs: any[] = ['10.0.0.2/32'], keepAlive: any = 0) { super(); this.privateKey = privateKey this.publicKey = publicKey; @@ -3229,14 +3278,14 @@ Inbound.WireguardSettings.Peer = class extends XrayCommonClass { [this.publicKey, this.privateKey] = Object.values(Wireguard.generateKeypair()) } this.psk = psk; - allowedIPs.forEach((a, index) => { + allowedIPs.forEach((a: any, index: number) => { if (a.length > 0 && !a.includes('/')) allowedIPs[index] += '/32'; }) this.allowedIPs = allowedIPs; this.keepAlive = keepAlive; } - static fromJson(json = {}) { + static fromJson(json: any = {}) { return new Inbound.WireguardSettings.Peer( json.privateKey, json.publicKey, @@ -3247,7 +3296,7 @@ Inbound.WireguardSettings.Peer = class extends XrayCommonClass { } toJson() { - this.allowedIPs.forEach((a, index) => { + this.allowedIPs.forEach((a: any, index: number) => { if (a.length > 0 && !a.includes('/')) this.allowedIPs[index] += '/32'; }); return { @@ -3262,13 +3311,13 @@ Inbound.WireguardSettings.Peer = class extends XrayCommonClass { Inbound.TunSettings = class extends Inbound.Settings { constructor( - protocol, - name = 'xray0', - mtu = 1500, - gateway = [], - dns = [], - userLevel = 0, - autoSystemRoutingTable = [], + protocol: any, + name: any = 'xray0', + mtu: any = 1500, + gateway: any[] = [], + dns: any[] = [], + userLevel: any = 0, + autoSystemRoutingTable: any[] = [], autoOutboundsInterface = 'auto' ) { super(protocol); @@ -3281,7 +3330,7 @@ Inbound.TunSettings = class extends Inbound.Settings { this.autoOutboundsInterface = autoOutboundsInterface; } - static fromJson(json = {}) { + static fromJson(json: any = {}) { const rawMtu = json.mtu ?? json.MTU; const mtu = Array.isArray(rawMtu) ? rawMtu[0] : rawMtu; return new Inbound.TunSettings( diff --git a/frontend/src/models/outbound.js b/frontend/src/models/outbound.ts similarity index 88% rename from frontend/src/models/outbound.js rename to frontend/src/models/outbound.ts index 79af2682..5a8ab442 100644 --- a/frontend/src/models/outbound.js +++ b/frontend/src/models/outbound.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { ObjectUtil, Base64, Wireguard } from '@/utils'; export const Protocols = { @@ -109,30 +110,30 @@ export const Address_Port_Strategy = { export const DNSRuleActions = ['direct', 'drop', 'reject', 'hijack']; -export function normalizeDNSRuleField(value) { +export function normalizeDNSRuleField(value: any): string { if (value === null || value === undefined) { return ''; } if (Array.isArray(value)) { - return value.map(item => item.toString().trim()).filter(item => item.length > 0).join(','); + return value.map((item: any) => item.toString().trim()).filter((item: any) => item.length > 0).join(','); } return value.toString().trim(); } -export function normalizeDNSRuleAction(action) { +export function normalizeDNSRuleAction(action: any): string { action = ObjectUtil.isEmpty(action) ? 'direct' : action.toString().toLowerCase().trim(); return DNSRuleActions.includes(action) ? action : 'direct'; } -export function parseLegacyDNSBlockTypes(blockTypes) { +export function parseLegacyDNSBlockTypes(blockTypes: any): number[] { if (blockTypes === null || blockTypes === undefined || blockTypes === '') { return []; } if (Array.isArray(blockTypes)) { return blockTypes - .map(item => Number(item)) - .filter(item => Number.isInteger(item) && item >= 0 && item <= 65535); + .map((item: any) => Number(item)) + .filter((item: any) => Number.isInteger(item) && item >= 0 && item <= 65535); } if (typeof blockTypes === 'number') { @@ -142,13 +143,13 @@ export function parseLegacyDNSBlockTypes(blockTypes) { return blockTypes .toString() .split(',') - .map(item => item.trim()) - .filter(item => /^\d+$/.test(item)) - .map(item => Number(item)) - .filter(item => item >= 0 && item <= 65535); + .map((item: any) => item.trim()) + .filter((item: any) => /^\d+$/.test(item)) + .map((item: any) => Number(item)) + .filter((item: any) => item >= 0 && item <= 65535); } -export function buildLegacyDNSRules(nonIPQuery, blockTypes) { +export function buildLegacyDNSRules(nonIPQuery: any, blockTypes: any): any[] { const mode = ['reject', 'drop', 'skip'].includes(nonIPQuery) ? nonIPQuery : 'reject'; const rules = []; const parsedBlockTypes = parseLegacyDNSBlockTypes(blockTypes); @@ -163,9 +164,9 @@ export function buildLegacyDNSRules(nonIPQuery, blockTypes) { return rules; } -export function getDNSRulesFromJson(json = {}) { +export function getDNSRulesFromJson(json: any = {}): any[] { if (Array.isArray(json.rules) && json.rules.length > 0) { - return json.rules.map(rule => Outbound.DNSRule.fromJson(rule)); + return json.rules.map((rule: any) => Outbound.DNSRule.fromJson(rule)); } if (json.nonIPQuery !== undefined || json.blockTypes !== undefined) { @@ -189,20 +190,21 @@ Object.freeze(Address_Port_Strategy); Object.freeze(DNSRuleActions); export class CommonClass { + [key: string]: any; - static toJsonArray(arr) { + static toJsonArray(arr: any[]): any[] { return arr.map(obj => obj.toJson()); } - static fromJson() { + static fromJson(..._args: any[]): any { return new CommonClass(); } - toJson() { + toJson(): any { return this; } - toString(format = true) { + toString(format: boolean = true): string { return format ? JSON.stringify(this.toJson(), null, 2) : JSON.stringify(this.toJson()); } } @@ -225,7 +227,7 @@ export class ReverseSniffing extends CommonClass { this.domainsExcluded = Array.isArray(domainsExcluded) ? domainsExcluded : []; } - static fromJson(json = {}) { + static fromJson(json: any = {}): any { if (!json || Object.keys(json).length === 0) { return new ReverseSniffing(); } @@ -252,15 +254,15 @@ export class ReverseSniffing extends CommonClass { } export class TcpStreamSettings extends CommonClass { - constructor(type = 'none', host, path) { + constructor(type: any = 'none', host?: any, path?: any) { super(); this.type = type; this.host = host; this.path = path; } - static fromJson(json = {}) { - let header = json.header; + static fromJson(json: any = {}): any { + const header = json.header; if (!header) return new TcpStreamSettings(); if (header.type == 'http' && header.request) { return new TcpStreamSettings( @@ -305,7 +307,7 @@ export class KcpStreamSettings extends CommonClass { this.maxSendingWindow = maxSendingWindow; } - static fromJson(json = {}) { + static fromJson(json: any = {}): any { return new KcpStreamSettings( json.mtu, json.tti, @@ -341,7 +343,7 @@ export class WsStreamSettings extends CommonClass { this.heartbeatPeriod = heartbeatPeriod; } - static fromJson(json = {}) { + static fromJson(json: any = {}): any { return new WsStreamSettings( json.path, json.host, @@ -370,7 +372,7 @@ export class GrpcStreamSettings extends CommonClass { this.multiMode = multiMode; } - static fromJson(json = {}) { + static fromJson(json: any = {}): any { return new GrpcStreamSettings(json.serviceName, json.authority, json.multiMode); } @@ -390,7 +392,7 @@ export class HttpUpgradeStreamSettings extends CommonClass { this.host = host; } - static fromJson(json = {}) { + static fromJson(json: any = {}): any { return new HttpUpgradeStreamSettings( json.path, json.host, @@ -414,10 +416,10 @@ export class HttpUpgradeStreamSettings extends CommonClass { export class xHTTPStreamSettings extends CommonClass { constructor( // Bidirectional — must match the inbound side - path = '/', - host = '', - mode = '', - xPaddingBytes = "100-1000", + path: any = '/', + host: any = '', + mode: any = '', + xPaddingBytes: any = "100-1000", xPaddingObfsMode = false, xPaddingKey = '', xPaddingHeader = '', @@ -429,9 +431,9 @@ export class xHTTPStreamSettings extends CommonClass { seqKey = '', uplinkDataPlacement = '', uplinkDataKey = '', - scMaxEachPostBytes = "1000000", + scMaxEachPostBytes: any = "1000000", // Client-side only - headers = [], + headers: any[] = [], uplinkHTTPMethod = '', uplinkChunkSize = 0, noGRPCHeader = false, @@ -475,17 +477,17 @@ export class xHTTPStreamSettings extends CommonClass { this.enableXmux = enableXmux; } - addHeader(name, value) { + addHeader(name: any, value: any): void { this.headers.push({ name: name, value: value }); } - removeHeader(index) { + removeHeader(index: number): void { this.headers.splice(index, 1); } - static fromJson(json = {}) { + static fromJson(json: any = {}): any { const headersInput = json.headers; - let headers = []; + let headers: any[] = []; if (Array.isArray(headersInput)) { headers = headersInput; } else if (headersInput && typeof headersInput === 'object') { @@ -524,7 +526,7 @@ export class xHTTPStreamSettings extends CommonClass { toJson() { // Upstream expects headers as a {name: value} map, not a list of entries. - const headersMap = {}; + const headersMap: any = {}; if (Array.isArray(this.headers)) { for (const h of this.headers) { if (h && h.name) headersMap[h.name] = h.value || ''; @@ -566,9 +568,9 @@ export class xHTTPStreamSettings extends CommonClass { export class TlsStreamSettings extends CommonClass { constructor( - serverName = '', - alpn = [], - fingerprint = '', + serverName: any = '', + alpn: any[] = [], + fingerprint: any = '', echConfigList = '', verifyPeerCertByName = '', pinnedPeerCertSha256 = '', @@ -582,7 +584,7 @@ export class TlsStreamSettings extends CommonClass { this.pinnedPeerCertSha256 = pinnedPeerCertSha256; } - static fromJson(json = {}) { + static fromJson(json: any = {}): any { return new TlsStreamSettings( json.serverName, json.alpn, @@ -607,12 +609,12 @@ export class TlsStreamSettings extends CommonClass { export class RealityStreamSettings extends CommonClass { constructor( - publicKey = '', - fingerprint = '', - serverName = '', - shortId = '', - spiderX = '', - mldsa65Verify = '' + publicKey: any = '', + fingerprint: any = '', + serverName: any = '', + shortId: any = '', + spiderX: any = '', + mldsa65Verify: any = '' ) { super(); this.publicKey = publicKey; @@ -622,7 +624,7 @@ export class RealityStreamSettings extends CommonClass { this.spiderX = spiderX; this.mldsa65Verify = mldsa65Verify; } - static fromJson(json = {}) { + static fromJson(json: any = {}): any { return new RealityStreamSettings( json.publicKey, json.fingerprint, @@ -680,7 +682,7 @@ export class HysteriaStreamSettings extends CommonClass { this.disablePathMTUDiscovery = disablePathMTUDiscovery; } - static fromJson(json = {}) { + static fromJson(json: any = {}): any { let udphopPort = ''; let udphopIntervalMin = 30; let udphopIntervalMax = 30; @@ -715,7 +717,7 @@ export class HysteriaStreamSettings extends CommonClass { } toJson() { - const result = { + const result: any = { version: this.version, auth: this.auth, congestion: this.congestion, @@ -765,7 +767,7 @@ export class SockoptStreamSettings extends CommonClass { } - static fromJson(json = {}) { + static fromJson(json: any = {}): any { if (Object.keys(json).length === 0) return undefined; return new SockoptStreamSettings( json.dialerProxy, @@ -781,7 +783,7 @@ export class SockoptStreamSettings extends CommonClass { } toJson() { - const result = { + const result: any = { dialerProxy: this.dialerProxy, tcpFastOpen: this.tcpFastOpen, tcpKeepAliveInterval: this.tcpKeepAliveInterval, @@ -799,13 +801,13 @@ export class SockoptStreamSettings extends CommonClass { } export class UdpMask extends CommonClass { - constructor(type = 'salamander', settings = {}) { + constructor(type: any = 'salamander', settings: any = {}) { super(); this.type = type; this.settings = this._getDefaultSettings(type, settings); } - _getDefaultSettings(type, settings = {}) { + _getDefaultSettings(type: any, settings: any = {}): any { switch (type) { case 'salamander': case 'mkcp-aes128gcm': @@ -846,7 +848,7 @@ export class UdpMask extends CommonClass { } } - static fromJson(json = {}) { + static fromJson(json: any = {}): any { return new UdpMask( json.type || 'salamander', json.settings || {} @@ -854,7 +856,7 @@ export class UdpMask extends CommonClass { } toJson() { - const cleanItem = item => { + const cleanItem = (item: any) => { const out = { ...item }; if (out.type === 'array') { delete out.packet; @@ -884,13 +886,13 @@ export class UdpMask extends CommonClass { } export class TcpMask extends CommonClass { - constructor(type = 'fragment', settings = {}) { + constructor(type: any = 'fragment', settings: any = {}) { super(); this.type = type; this.settings = this._getDefaultSettings(type, settings); } - _getDefaultSettings(type, settings = {}) { + _getDefaultSettings(type: any, settings: any = {}): any { switch (type) { case 'fragment': return { @@ -918,7 +920,7 @@ export class TcpMask extends CommonClass { } } - static fromJson(json = {}) { + static fromJson(json: any = {}): any { return new TcpMask( json.type || 'fragment', json.settings || {} @@ -926,7 +928,7 @@ export class TcpMask extends CommonClass { } toJson() { - const cleanItem = item => { + const cleanItem = (item: any) => { const out = { ...item }; if (out.type === 'array') { delete out.packet; @@ -939,7 +941,7 @@ export class TcpMask extends CommonClass { let settings = this.settings; if (this.type === 'header-custom' && settings) { - const cleanGroup = group => Array.isArray(group) ? group.map(cleanItem) : group; + const cleanGroup = (group: any) => Array.isArray(group) ? group.map(cleanItem) : group; settings = { ...settings, clients: Array.isArray(settings.clients) ? settings.clients.map(cleanGroup) : settings.clients, @@ -956,11 +958,11 @@ export class TcpMask extends CommonClass { export class QuicParams extends CommonClass { constructor( - congestion = 'bbr', - debug = false, - brutalUp = 65537, - brutalDown = 65537, - udpHop = undefined, + congestion: any = 'bbr', + debug: any = false, + brutalUp: any = 65537, + brutalDown: any = 65537, + udpHop: any = undefined, initStreamReceiveWindow = 8388608, maxStreamReceiveWindow = 8388608, initConnectionReceiveWindow = 20971520, @@ -994,7 +996,7 @@ export class QuicParams extends CommonClass { this.udpHop = value ? (this.udpHop || { ports: '20000-50000', interval: '5-10' }) : undefined; } - static fromJson(json = {}) { + static fromJson(json: any = {}): any { if (!json || Object.keys(json).length === 0) return undefined; return new QuicParams( json.congestion, @@ -1014,7 +1016,7 @@ export class QuicParams extends CommonClass { } toJson() { - const result = { congestion: this.congestion }; + const result: any = { congestion: this.congestion } as any; if (this.debug) result.debug = this.debug; if (['brutal', 'force-brutal'].includes(this.congestion)) { if (this.brutalUp) result.brutalUp = this.brutalUp; @@ -1034,10 +1036,10 @@ export class QuicParams extends CommonClass { } export class FinalMaskStreamSettings extends CommonClass { - constructor(tcp = [], udp = [], quicParams = undefined) { + constructor(tcp: any[] = [], udp: any[] = [], quicParams: any = undefined) { super(); - this.tcp = Array.isArray(tcp) ? tcp.map(t => t instanceof TcpMask ? t : new TcpMask(t.type, t.settings)) : []; - this.udp = Array.isArray(udp) ? udp.map(u => new UdpMask(u.type, u.settings)) : [new UdpMask(udp.type, udp.settings)]; + this.tcp = Array.isArray(tcp) ? tcp.map((t: any) => t instanceof TcpMask ? t : new TcpMask(t.type, t.settings)) : []; + this.udp = Array.isArray(udp) ? udp.map((u: any) => new UdpMask(u.type, u.settings)) : [new UdpMask((udp as any).type, (udp as any).settings)]; this.quicParams = quicParams instanceof QuicParams ? quicParams : (quicParams ? QuicParams.fromJson(quicParams) : undefined); } @@ -1049,7 +1051,7 @@ export class FinalMaskStreamSettings extends CommonClass { this.quicParams = value ? (this.quicParams || new QuicParams()) : undefined; } - static fromJson(json = {}) { + static fromJson(json: any = {}): any { return new FinalMaskStreamSettings( json.tcp || [], json.udp || [], @@ -1058,12 +1060,12 @@ export class FinalMaskStreamSettings extends CommonClass { } toJson() { - const result = {}; + const result: any = {} as any; if (this.tcp && this.tcp.length > 0) { - result.tcp = this.tcp.map(t => t.toJson()); + result.tcp = this.tcp.map((t: any) => t.toJson()); } if (this.udp && this.udp.length > 0) { - result.udp = this.udp.map(udp => udp.toJson()); + result.udp = this.udp.map((udp: any) => udp.toJson()); } if (this.quicParams) { result.quicParams = this.quicParams.toJson(); @@ -1108,7 +1110,7 @@ export class StreamSettings extends CommonClass { this.finalmask.tcp.push(new TcpMask(type)); } - delTcpMask(index) { + delTcpMask(index: number) { if (this.finalmask.tcp) { this.finalmask.tcp.splice(index, 1); } @@ -1118,7 +1120,7 @@ export class StreamSettings extends CommonClass { this.finalmask.udp.push(new UdpMask(type)); } - delUdpMask(index) { + delUdpMask(index: number) { if (this.finalmask.udp) { this.finalmask.udp.splice(index, 1); } @@ -1147,7 +1149,7 @@ export class StreamSettings extends CommonClass { this.sockopt = value ? new SockoptStreamSettings() : undefined; } - static fromJson(json = {}) { + static fromJson(json: any = {}): any { // Xray-core supports both "xhttpSettings" and "splithttpSettings" (backward-compat alias) const xhttpJson = json.xhttpSettings ?? json.splithttpSettings; // Normalize "splithttp" network name to "xhttp" for internal consistency @@ -1198,7 +1200,7 @@ export class Mux extends CommonClass { this.xudpProxyUDP443 = xudpProxyUDP443; } - static fromJson(json = {}) { + static fromJson(json: any = {}): any { if (Object.keys(json).length === 0) return undefined; return new Mux( json.enabled, @@ -1219,13 +1221,28 @@ export class Mux extends CommonClass { } export class Outbound extends CommonClass { + static Settings: any; + static FreedomSettings: any; + static BlackholeSettings: any; + static LoopbackSettings: any; + static DNSRule: any; + static DNSSettings: any; + static VmessSettings: any; + static VLESSSettings: any; + static TrojanSettings: any; + static ShadowsocksSettings: any; + static SocksSettings: any; + static HttpSettings: any; + static WireguardSettings: any; + static HysteriaSettings: any; + constructor( - tag = '', - protocol = Protocols.VLESS, - settings = null, - streamSettings = new StreamSettings(), - sendThrough, - mux = new Mux(), + tag: any = '', + protocol: any = Protocols.VLESS, + settings: any = null, + streamSettings: any = new StreamSettings(), + sendThrough?: any, + mux: any = new Mux(), ) { super(); this.tag = tag; @@ -1320,7 +1337,7 @@ export class Outbound extends CommonClass { return [Protocols.Socks, Protocols.HTTP].includes(this.protocol); } - static fromJson(json = {}) { + static fromJson(json: any = {}): any { return new Outbound( json.tag, json.protocol, @@ -1332,14 +1349,14 @@ export class Outbound extends CommonClass { } toJson() { - var stream; + let stream; if (this.canEnableStream()) { stream = this.stream.toJson(); } else { if (this.stream?.sockopt) stream = { sockopt: this.stream.sockopt.toJson() }; } - let settingsOut = this.settings instanceof CommonClass ? this.settings.toJson() : this.settings; + const settingsOut = this.settings instanceof CommonClass ? this.settings.toJson() : this.settings; return { protocol: this.protocol, settings: settingsOut, @@ -1351,7 +1368,7 @@ export class Outbound extends CommonClass { }; } - static fromLink(link) { + static fromLink(link: any) { const data = link.split('://'); if (data.length != 2) return null; switch (data[0].toLowerCase()) { @@ -1369,10 +1386,10 @@ export class Outbound extends CommonClass { } } - static fromVmessLink(json = {}) { - let stream = new StreamSettings(json.net, json.tls); + static fromVmessLink(json: any = {}) { + const stream = new StreamSettings(json.net, json.tls); - let network = json.net; + const network = json.net; if (network === 'tcp') { stream.tcp = new TcpStreamSettings( json.type, @@ -1402,7 +1419,7 @@ export class Outbound extends CommonClass { if (typeof json.xPaddingBytes === 'string' && json.xPaddingBytes) xh.xPaddingBytes = json.xPaddingBytes; if (json.xPaddingObfsMode === true) { xh.xPaddingObfsMode = true; - ["xPaddingKey", "xPaddingHeader", "xPaddingPlacement", "xPaddingMethod"].forEach(k => { + ["xPaddingKey", "xPaddingHeader", "xPaddingPlacement", "xPaddingMethod"].forEach((k: string) => { if (typeof json[k] === 'string' && json[k]) xh[k] = json[k]; }); } @@ -1414,7 +1431,7 @@ export class Outbound extends CommonClass { "uplinkDataPlacement", "uplinkDataKey", "scMaxEachPostBytes", "scMinPostsIntervalMs", ]; - xFields.forEach(k => { + xFields.forEach((k: string) => { if (typeof json[k] === 'string' && json[k]) xh[k] = json[k]; }); if (typeof json.uplinkChunkSize === 'number' && json.uplinkChunkSize !== 0) xh.uplinkChunkSize = json.uplinkChunkSize; @@ -1451,17 +1468,17 @@ export class Outbound extends CommonClass { return new Outbound(json.ps, Protocols.VMess, new Outbound.VmessSettings(json.add, port, json.id, json.scy), stream); } - static fromParamLink(link) { + static fromParamLink(link: any) { const url = new URL(link); - let type = url.searchParams.get('type') ?? 'tcp'; - let security = url.searchParams.get('security') ?? 'none'; - let stream = new StreamSettings(type, security); + const type = url.searchParams.get('type') ?? 'tcp'; + const security = url.searchParams.get('security') ?? 'none'; + const stream = new StreamSettings(type, security); - let headerType = url.searchParams.get('headerType') ?? undefined; - let host = url.searchParams.get('host') ?? undefined; - let path = url.searchParams.get('path') ?? undefined; - let seed = url.searchParams.get('seed') ?? path ?? undefined; - let mode = url.searchParams.get('mode') ?? undefined; + const headerType = url.searchParams.get('headerType') ?? undefined; + const host = url.searchParams.get('host') ?? undefined; + const path = url.searchParams.get('path') ?? undefined; + const seed = url.searchParams.get('seed') ?? path ?? undefined; + const mode = url.searchParams.get('mode') ?? undefined; if (type === 'tcp' || type === 'none') { stream.tcp = new TcpStreamSettings(headerType ?? 'none', host, path); @@ -1496,7 +1513,7 @@ export class Outbound extends CommonClass { const extra = JSON.parse(extraRaw); if (typeof extra.xPaddingBytes === 'string' && extra.xPaddingBytes) xh.xPaddingBytes = extra.xPaddingBytes; if (extra.xPaddingObfsMode === true) xh.xPaddingObfsMode = true; - ["xPaddingKey", "xPaddingHeader", "xPaddingPlacement", "xPaddingMethod"].forEach(k => { + ["xPaddingKey", "xPaddingHeader", "xPaddingPlacement", "xPaddingMethod"].forEach((k: string) => { if (typeof extra[k] === 'string' && extra[k]) xh[k] = extra[k]; }); if (!xh.mode && typeof extra.mode === 'string' && extra.mode) xh.mode = extra.mode; @@ -1508,7 +1525,7 @@ export class Outbound extends CommonClass { "uplinkDataPlacement", "uplinkDataKey", "scMaxEachPostBytes", "scMinPostsIntervalMs", ]; - xFields.forEach(k => { + xFields.forEach((k: string) => { if (typeof extra[k] === 'string' && extra[k]) xh[k] = extra[k]; }); if (typeof extra.uplinkChunkSize === 'number' && extra.uplinkChunkSize !== 0) xh.uplinkChunkSize = extra.uplinkChunkSize; @@ -1529,20 +1546,20 @@ export class Outbound extends CommonClass { } if (security == 'tls') { - let fp = url.searchParams.get('fp') ?? 'none'; - let alpn = url.searchParams.get('alpn'); - let sni = url.searchParams.get('sni') ?? ''; - let ech = url.searchParams.get('ech') ?? ''; + const fp = url.searchParams.get('fp') ?? 'none'; + const alpn = url.searchParams.get('alpn'); + const sni = url.searchParams.get('sni') ?? ''; + const ech = url.searchParams.get('ech') ?? ''; stream.tls = new TlsStreamSettings(sni, alpn ? alpn.split(',') : [], fp, ech); } if (security == 'reality') { - let pbk = url.searchParams.get('pbk'); - let fp = url.searchParams.get('fp'); - let sni = url.searchParams.get('sni') ?? ''; - let sid = url.searchParams.get('sid') ?? ''; - let spx = url.searchParams.get('spx') ?? ''; - let pqv = url.searchParams.get('pqv') ?? ''; + const pbk = url.searchParams.get('pbk'); + const fp = url.searchParams.get('fp'); + const sni = url.searchParams.get('sni') ?? ''; + const sid = url.searchParams.get('sid') ?? ''; + const spx = url.searchParams.get('spx') ?? ''; + const pqv = url.searchParams.get('pqv') ?? ''; stream.reality = new RealityStreamSettings(pbk, fp, sni, sid, spx, pqv); } @@ -1550,13 +1567,16 @@ export class Outbound extends CommonClass { const match = link.match(regex); if (!match) return null; - let [, protocol, userData, address, port,] = match; + const address = match[3]; + let protocol = match[1]; + let userData: any = match[2]; + let port: any = match[4]; port *= 1; if (protocol == 'ss') { protocol = 'shadowsocks'; userData = atob(userData).split(':'); } - var settings; + let settings; switch (protocol) { case Protocols.VLESS: settings = new Outbound.VLESSSettings(address, port, userData, url.searchParams.get('flow') ?? '', url.searchParams.get('encryption') ?? 'none'); @@ -1564,10 +1584,11 @@ export class Outbound extends CommonClass { case Protocols.Trojan: settings = new Outbound.TrojanSettings(address, port, userData); break; - case Protocols.Shadowsocks: - let method = userData.splice(0, 1)[0]; + case Protocols.Shadowsocks: { + const method = userData.splice(0, 1)[0]; settings = new Outbound.ShadowsocksSettings(address, port, userData.join(":"), method, true); break; + } default: return null; } @@ -1585,29 +1606,30 @@ export class Outbound extends CommonClass { return new Outbound(remark, protocol, settings, stream); } - static fromHysteriaLink(link) { + static fromHysteriaLink(link: any) { // Parse hysteria2://password@address:port[?param1=value1¶m2=value2...][#remarks] const regex = /^hysteria2?:\/\/([^@]+)@([^:?#]+):(\d+)([^#]*)(#.*)?$/; const match = link.match(regex); if (!match) return null; - let [, password, address, port, params, hash] = match; + const password = match[1]; + const address = match[2]; + let port: any = match[3]; + const params = match[4]; + const hash = match[5]; port = parseInt(port); - // Parse URL parameters if present - let urlParams = new URLSearchParams(params); + const urlParams = new URLSearchParams(params); - // Create stream settings with hysteria network - let security = urlParams.get('security') ?? 'none'; - let stream = new StreamSettings('hysteria', security); + const security = urlParams.get('security') ?? 'none'; + const stream = new StreamSettings('hysteria', security); - // Parse TLS settings when security=tls if (security === 'tls') { - let fp = urlParams.get('fp') ?? 'none'; - let alpn = urlParams.get('alpn'); - let sni = urlParams.get('sni') ?? ''; - let ech = urlParams.get('ech') ?? ''; + const fp = urlParams.get('fp') ?? 'none'; + const alpn = urlParams.get('alpn'); + const sni = urlParams.get('sni') ?? ''; + const ech = urlParams.get('ech') ?? ''; stream.tls = new TlsStreamSettings(sni, alpn ? alpn.split(',') : [], fp, ech); } @@ -1619,7 +1641,7 @@ export class Outbound extends CommonClass { stream.hysteria.udphopPort = urlParams.get('udphopPort') ?? ''; // Support both old single interval and new min/max range if (urlParams.has('udphopInterval')) { - const interval = parseInt(urlParams.get('udphopInterval')); + const interval = parseInt(urlParams.get('udphopInterval')!); stream.hysteria.udphopIntervalMin = interval; stream.hysteria.udphopIntervalMax = interval; } else { @@ -1629,22 +1651,22 @@ export class Outbound extends CommonClass { // Optional QUIC parameters for FinalMask support and hysteria2 share links if (urlParams.has('initStreamReceiveWindow')) { - stream.hysteria.initStreamReceiveWindow = parseInt(urlParams.get('initStreamReceiveWindow')); + stream.hysteria.initStreamReceiveWindow = parseInt(urlParams.get('initStreamReceiveWindow')!); } if (urlParams.has('maxStreamReceiveWindow')) { - stream.hysteria.maxStreamReceiveWindow = parseInt(urlParams.get('maxStreamReceiveWindow')); + stream.hysteria.maxStreamReceiveWindow = parseInt(urlParams.get('maxStreamReceiveWindow')!); } if (urlParams.has('initConnectionReceiveWindow')) { - stream.hysteria.initConnectionReceiveWindow = parseInt(urlParams.get('initConnectionReceiveWindow')); + stream.hysteria.initConnectionReceiveWindow = parseInt(urlParams.get('initConnectionReceiveWindow')!); } if (urlParams.has('maxConnectionReceiveWindow')) { - stream.hysteria.maxConnectionReceiveWindow = parseInt(urlParams.get('maxConnectionReceiveWindow')); + stream.hysteria.maxConnectionReceiveWindow = parseInt(urlParams.get('maxConnectionReceiveWindow')!); } if (urlParams.has('maxIdleTimeout')) { - stream.hysteria.maxIdleTimeout = parseInt(urlParams.get('maxIdleTimeout')); + stream.hysteria.maxIdleTimeout = parseInt(urlParams.get('maxIdleTimeout')!); } if (urlParams.has('keepAlivePeriod')) { - stream.hysteria.keepAlivePeriod = parseInt(urlParams.get('keepAlivePeriod')); + stream.hysteria.keepAlivePeriod = parseInt(urlParams.get('keepAlivePeriod')!); } if (urlParams.has('disablePathMTUDiscovery')) { stream.hysteria.disablePathMTUDiscovery = urlParams.get('disablePathMTUDiscovery') === 'true'; @@ -1682,23 +1704,21 @@ export class Outbound extends CommonClass { } catch (_) { /* ignore malformed fm */ } } - // Create settings - let settings = new Outbound.HysteriaSettings(address, port, 2); + const settings = new Outbound.HysteriaSettings(address, port, 2); - // Extract remark from hash - let remark = hash ? decodeURIComponent(hash.substring(1)) : `out-hysteria-${port}`; + const remark = hash ? decodeURIComponent(hash.substring(1)) : `out-hysteria-${port}`; return new Outbound(remark, Protocols.Hysteria, settings, stream); } } Outbound.Settings = class extends CommonClass { - constructor(protocol) { + constructor(protocol: any) { super(); this.protocol = protocol; } - static getSettings(protocol) { + static getSettings(protocol: any): any { switch (protocol) { case Protocols.Freedom: return new Outbound.FreedomSettings(); case Protocols.Blackhole: return new Outbound.BlackholeSettings(); @@ -1716,7 +1736,7 @@ Outbound.Settings = class extends CommonClass { } } - static fromJson(protocol, json) { + static fromJson(protocol: any, json: any): any { switch (protocol) { case Protocols.Freedom: return Outbound.FreedomSettings.fromJson(json); case Protocols.Blackhole: return Outbound.BlackholeSettings.fromJson(json); @@ -1752,7 +1772,7 @@ Outbound.FreedomSettings = class extends CommonClass { this.fragment = fragment || {}; this.noises = Array.isArray(noises) ? noises : []; this.finalRules = Array.isArray(finalRules) - ? finalRules.map(rule => rule instanceof Outbound.FreedomSettings.FinalRule ? rule : Outbound.FreedomSettings.FinalRule.fromJson(rule)) + ? finalRules.map((rule: any) => rule instanceof Outbound.FreedomSettings.FinalRule ? rule : Outbound.FreedomSettings.FinalRule.fromJson(rule)) : []; } @@ -1760,7 +1780,7 @@ Outbound.FreedomSettings = class extends CommonClass { this.noises.push(new Outbound.FreedomSettings.Noise()); } - delNoise(index) { + delNoise(index: number) { this.noises.splice(index, 1); } @@ -1768,13 +1788,13 @@ Outbound.FreedomSettings = class extends CommonClass { this.finalRules.push(new Outbound.FreedomSettings.FinalRule(action)); } - delFinalRule(index) { + delFinalRule(index: number) { this.finalRules.splice(index, 1); } - static fromJson(json = {}) { + static fromJson(json: any = {}): any { const finalRules = Array.isArray(json.finalRules) - ? json.finalRules.map(rule => Outbound.FreedomSettings.FinalRule.fromJson(rule)) + ? json.finalRules.map((rule: any) => Outbound.FreedomSettings.FinalRule.fromJson(rule)) : []; // Backward compatibility: map legacy ipsBlocked entries to blocking finalRules. @@ -1786,7 +1806,7 @@ Outbound.FreedomSettings = class extends CommonClass { json.domainStrategy, json.redirect, json.fragment ? Outbound.FreedomSettings.Fragment.fromJson(json.fragment) : {}, - json.noises ? json.noises.map(noise => Outbound.FreedomSettings.Noise.fromJson(noise)) : [], + json.noises ? json.noises.map((noise: any) => Outbound.FreedomSettings.Noise.fromJson(noise)) : [], finalRules, ); } @@ -1816,7 +1836,7 @@ Outbound.FreedomSettings.Fragment = class extends CommonClass { this.maxSplit = maxSplit; } - static fromJson(json = {}) { + static fromJson(json: any = {}): any { return new Outbound.FreedomSettings.Fragment( json.packets, json.length, @@ -1840,7 +1860,7 @@ Outbound.FreedomSettings.Noise = class extends CommonClass { this.applyTo = applyTo; } - static fromJson(json = {}) { + static fromJson(json: any = {}): any { return new Outbound.FreedomSettings.Noise( json.type, json.packet, @@ -1869,7 +1889,7 @@ Outbound.FreedomSettings.FinalRule = class extends CommonClass { this.blockDelay = blockDelay; } - static fromJson(json = {}) { + static fromJson(json: any = {}): any { return new Outbound.FreedomSettings.FinalRule( json.action, Array.isArray(json.network) ? json.network.join(',') : json.network, @@ -1891,12 +1911,12 @@ Outbound.FreedomSettings.FinalRule = class extends CommonClass { }; Outbound.BlackholeSettings = class extends CommonClass { - constructor(type) { + constructor(type?: any) { super(); this.type = type; } - static fromJson(json = {}) { + static fromJson(json: any = {}): any { return new Outbound.BlackholeSettings( json.response ? json.response.type : undefined, ); @@ -1915,7 +1935,7 @@ Outbound.LoopbackSettings = class extends CommonClass { this.inboundTag = inboundTag; } - static fromJson(json = {}) { + static fromJson(json: any = {}): any { return new Outbound.LoopbackSettings(json.inboundTag || ''); } @@ -1934,7 +1954,7 @@ Outbound.DNSRule = class extends CommonClass { this.domain = domain; } - static fromJson(json = {}) { + static fromJson(json: any = {}): any { return new Outbound.DNSRule( json.action, normalizeDNSRuleField(json.qtype), @@ -1943,7 +1963,7 @@ Outbound.DNSRule = class extends CommonClass { } toJson() { - const rule = { + const rule: any = { action: normalizeDNSRuleAction(this.action), }; @@ -1981,18 +2001,18 @@ Outbound.DNSSettings = class extends CommonClass { this.rewriteAddress = rewriteAddress; this.rewritePort = rewritePort; this.userLevel = userLevel; - this.rules = Array.isArray(rules) ? rules.map(rule => rule instanceof Outbound.DNSRule ? rule : Outbound.DNSRule.fromJson(rule)) : []; + this.rules = Array.isArray(rules) ? rules.map((rule: any) => rule instanceof Outbound.DNSRule ? rule : Outbound.DNSRule.fromJson(rule)) : []; } addRule(action = 'direct') { this.rules.push(new Outbound.DNSRule(action)); } - delRule(index) { + delRule(index: number) { this.rules.splice(index, 1); } - static fromJson(json = {}) { + static fromJson(json: any = {}): any { // Spec uses rewrite{Network,Address,Port}; older configs used the // bare network/address/port keys — accept both so existing saved // configs keep working after the migration. @@ -2006,7 +2026,7 @@ Outbound.DNSSettings = class extends CommonClass { } toJson() { - const json = {}; + const json: any = {}; if (!ObjectUtil.isEmpty(this.rewriteNetwork)) json.rewriteNetwork = this.rewriteNetwork; if (!ObjectUtil.isEmpty(this.rewriteAddress)) json.rewriteAddress = this.rewriteAddress; if (this.rewritePort > 0) json.rewritePort = this.rewritePort; @@ -2016,7 +2036,7 @@ Outbound.DNSSettings = class extends CommonClass { } }; Outbound.VmessSettings = class extends CommonClass { - constructor(address, port, id, security) { + constructor(address?: any, port?: any, id?: any, security?: any) { super(); this.address = address; this.port = port; @@ -2024,7 +2044,7 @@ Outbound.VmessSettings = class extends CommonClass { this.security = security; } - static fromJson(json = {}) { + static fromJson(json: any = {}): any { if (!ObjectUtil.isArrEmpty(json.vnext)) { const v = json.vnext[0] || {}; const u = ObjectUtil.isArrEmpty(v.users) ? {} : v.users[0]; @@ -2051,7 +2071,7 @@ Outbound.VmessSettings = class extends CommonClass { } }; Outbound.VLESSSettings = class extends CommonClass { - constructor(address, port, id, flow, encryption = 'none', reverseTag = '', reverseSniffing = new ReverseSniffing(), testpre = 0, testseed = []) { + constructor(address?: any, port?: any, id?: any, flow?: any, encryption: any = 'none', reverseTag: any = '', reverseSniffing: any = new ReverseSniffing(), testpre: any = 0, testseed: any[] = []) { super(); this.address = address; this.port = port; @@ -2064,7 +2084,7 @@ Outbound.VLESSSettings = class extends CommonClass { this.testseed = testseed; } - static fromJson(json = {}) { + static fromJson(json: any = {}): any { // Handle v2rayN-style nested vnext array (standard Xray JSON format) if (!ObjectUtil.isArrEmpty(json.vnext)) { const v = json.vnext[0] || {}; @@ -2072,7 +2092,7 @@ Outbound.VLESSSettings = class extends CommonClass { const saved = json.testseed; const testseed = (Array.isArray(saved) && saved.length === 4 - && saved.every(v => Number.isInteger(v) && v > 0)) + && saved.every((v: any) => Number.isInteger(v) && v > 0)) ? saved : []; return new Outbound.VLESSSettings( @@ -2091,7 +2111,7 @@ Outbound.VLESSSettings = class extends CommonClass { const saved = json.testseed; const testseed = (Array.isArray(saved) && saved.length === 4 - && saved.every(v => Number.isInteger(v) && v > 0)) + && saved.every((v: any) => Number.isInteger(v) && v > 0)) ? saved : []; return new Outbound.VLESSSettings( @@ -2108,7 +2128,7 @@ Outbound.VLESSSettings = class extends CommonClass { } toJson() { - const result = { + const result: any = { address: this.address, port: this.port, id: this.id, @@ -2130,7 +2150,7 @@ Outbound.VLESSSettings = class extends CommonClass { } if (Array.isArray(this.testseed) && this.testseed.length === 4 - && this.testseed.every(v => Number.isInteger(v) && v > 0)) { + && this.testseed.every((v: any) => Number.isInteger(v) && v > 0)) { result.testseed = this.testseed; } } @@ -2138,14 +2158,14 @@ Outbound.VLESSSettings = class extends CommonClass { } }; Outbound.TrojanSettings = class extends CommonClass { - constructor(address, port, password) { + constructor(address?: any, port?: any, password?: any) { super(); this.address = address; this.port = port; this.password = password; } - static fromJson(json = {}) { + static fromJson(json: any = {}): any { if (ObjectUtil.isArrEmpty(json.servers)) return new Outbound.TrojanSettings(); return new Outbound.TrojanSettings( json.servers[0].address, @@ -2165,7 +2185,7 @@ Outbound.TrojanSettings = class extends CommonClass { } }; Outbound.ShadowsocksSettings = class extends CommonClass { - constructor(address, port, password, method, uot, UoTVersion) { + constructor(address?: any, port?: any, password?: any, method?: any, uot?: any, UoTVersion?: any) { super(); this.address = address; this.port = port; @@ -2175,7 +2195,7 @@ Outbound.ShadowsocksSettings = class extends CommonClass { this.UoTVersion = UoTVersion; } - static fromJson(json = {}) { + static fromJson(json: any = {}): any { let servers = json.servers; if (ObjectUtil.isArrEmpty(servers)) servers = [{}]; return new Outbound.ShadowsocksSettings( @@ -2203,7 +2223,7 @@ Outbound.ShadowsocksSettings = class extends CommonClass { }; Outbound.SocksSettings = class extends CommonClass { - constructor(address, port, user, pass) { + constructor(address?: any, port?: any, user?: any, pass?: any) { super(); this.address = address; this.port = port; @@ -2211,7 +2231,7 @@ Outbound.SocksSettings = class extends CommonClass { this.pass = pass; } - static fromJson(json = {}) { + static fromJson(json: any = {}): any { let servers = json.servers; if (ObjectUtil.isArrEmpty(servers)) servers = [{ users: [{}] }]; return new Outbound.SocksSettings( @@ -2233,7 +2253,7 @@ Outbound.SocksSettings = class extends CommonClass { } }; Outbound.HttpSettings = class extends CommonClass { - constructor(address, port, user, pass) { + constructor(address?: any, port?: any, user?: any, pass?: any) { super(); this.address = address; this.port = port; @@ -2241,7 +2261,7 @@ Outbound.HttpSettings = class extends CommonClass { this.pass = pass; } - static fromJson(json = {}) { + static fromJson(json: any = {}): any { let servers = json.servers; if (ObjectUtil.isArrEmpty(servers)) servers = [{ users: [{}] }]; return new Outbound.HttpSettings( @@ -2290,11 +2310,11 @@ Outbound.WireguardSettings = class extends CommonClass { this.peers.push(new Outbound.WireguardSettings.Peer()); } - delPeer(index) { + delPeer(index: number) { this.peers.splice(index, 1); } - static fromJson(json = {}) { + static fromJson(json: any = {}): any { return new Outbound.WireguardSettings( json.mtu, json.secretKey, @@ -2302,7 +2322,7 @@ Outbound.WireguardSettings = class extends CommonClass { json.workers, json.domainStrategy, json.reserved, - json.peers.map(peer => Outbound.WireguardSettings.Peer.fromJson(peer)), + json.peers.map((peer: any) => Outbound.WireguardSettings.Peer.fromJson(peer)), json.noKernelTun, ); } @@ -2337,7 +2357,7 @@ Outbound.WireguardSettings.Peer = class extends CommonClass { this.keepAlive = keepAlive; } - static fromJson(json = {}) { + static fromJson(json: any = {}): any { return new Outbound.WireguardSettings.Peer( json.publicKey, json.preSharedKey, @@ -2366,7 +2386,7 @@ Outbound.HysteriaSettings = class extends CommonClass { this.version = version; } - static fromJson(json = {}) { + static fromJson(json: any = {}): any { if (Object.keys(json).length === 0) return new Outbound.HysteriaSettings(); return new Outbound.HysteriaSettings( json.address, diff --git a/frontend/src/models/reality-targets.js b/frontend/src/models/reality-targets.js deleted file mode 100644 index 85a3e881..00000000 --- a/frontend/src/models/reality-targets.js +++ /dev/null @@ -1,24 +0,0 @@ -// List of popular services for VLESS Reality Target/SNI randomization -export const REALITY_TARGETS = [ - { target: 'www.amazon.com:443', sni: 'www.amazon.com' }, - { target: 'aws.amazon.com:443', sni: 'aws.amazon.com' }, - { target: 'www.oracle.com:443', sni: 'www.oracle.com' }, - { target: 'www.nvidia.com:443', sni: 'www.nvidia.com' }, - { target: 'www.amd.com:443', sni: 'www.amd.com' }, - { target: 'www.intel.com:443', sni: 'www.intel.com' }, - { target: 'www.sony.com:443', sni: 'www.sony.com' } -]; - -/** - * Returns a random Reality target configuration from the predefined list - * @returns {Object} Object with target and sni properties - */ -export function getRandomRealityTarget() { - const randomIndex = Math.floor(Math.random() * REALITY_TARGETS.length); - const selected = REALITY_TARGETS[randomIndex]; - // Return a copy to avoid reference issues - return { - target: selected.target, - sni: selected.sni - }; -} diff --git a/frontend/src/models/reality-targets.ts b/frontend/src/models/reality-targets.ts new file mode 100644 index 00000000..518c836e --- /dev/null +++ b/frontend/src/models/reality-targets.ts @@ -0,0 +1,23 @@ +export interface RealityTarget { + target: string; + sni: string; +} + +export const REALITY_TARGETS: readonly RealityTarget[] = [ + { target: 'www.amazon.com:443', sni: 'www.amazon.com' }, + { target: 'aws.amazon.com:443', sni: 'aws.amazon.com' }, + { target: 'www.oracle.com:443', sni: 'www.oracle.com' }, + { target: 'www.nvidia.com:443', sni: 'www.nvidia.com' }, + { target: 'www.amd.com:443', sni: 'www.amd.com' }, + { target: 'www.intel.com:443', sni: 'www.intel.com' }, + { target: 'www.sony.com:443', sni: 'www.sony.com' }, +]; + +export function getRandomRealityTarget(): RealityTarget { + const randomIndex = Math.floor(Math.random() * REALITY_TARGETS.length); + const selected = REALITY_TARGETS[randomIndex]; + return { + target: selected.target, + sni: selected.sni, + }; +} diff --git a/frontend/src/pages/api-docs/ApiDocsPage.css b/frontend/src/pages/api-docs/ApiDocsPage.css index f6ac4f7b..c6cb6daf 100644 --- a/frontend/src/pages/api-docs/ApiDocsPage.css +++ b/frontend/src/pages/api-docs/ApiDocsPage.css @@ -1,13 +1,4 @@ -.api-docs-page { - --bg-page: #e6e8ec; - --bg-card: #ffffff; - min-height: 100vh; - background: var(--bg-page); -} - .api-docs-page.is-dark { - --bg-page: #1a1b1f; - --bg-card: #23252b; --sw-bg: #1f2026; --sw-bg-soft: #25272e; --sw-bg-input: #15161a; @@ -22,8 +13,6 @@ } .api-docs-page.is-dark.is-ultra { - --bg-page: #000; - --bg-card: #101013; --sw-bg: #0a0a0d; --sw-bg-soft: #131316; --sw-bg-input: #050507; @@ -51,7 +40,7 @@ .api-docs-page .docs-wrapper { background: var(--bg-card); border-radius: 8px; - border: 1px solid rgba(128, 128, 128, 0.12); + border: 1px solid var(--ant-color-border-secondary); overflow: hidden; } diff --git a/frontend/src/pages/api-docs/ApiDocsPage.tsx b/frontend/src/pages/api-docs/ApiDocsPage.tsx index e708b45c..8841c707 100644 --- a/frontend/src/pages/api-docs/ApiDocsPage.tsx +++ b/frontend/src/pages/api-docs/ApiDocsPage.tsx @@ -5,7 +5,6 @@ import 'swagger-ui-react/swagger-ui.css'; import { useTheme } from '@/hooks/useTheme'; import AppSidebar from '@/components/AppSidebar'; -import '@/styles/page-cards.css'; import './ApiDocsPage.css'; const basePath = window.X_UI_BASE_PATH || ''; diff --git a/frontend/src/pages/clients/ClientBulkAddModal.css b/frontend/src/pages/clients/ClientBulkAddModal.css deleted file mode 100644 index e49ef577..00000000 --- a/frontend/src/pages/clients/ClientBulkAddModal.css +++ /dev/null @@ -1,5 +0,0 @@ -.random-icon { - margin-left: 4px; - cursor: pointer; - color: var(--ant-color-primary, #1677ff); -} diff --git a/frontend/src/pages/clients/ClientBulkAddModal.tsx b/frontend/src/pages/clients/ClientBulkAddModal.tsx index 1b15267f..264b3229 100644 --- a/frontend/src/pages/clients/ClientBulkAddModal.tsx +++ b/frontend/src/pages/clients/ClientBulkAddModal.tsx @@ -9,7 +9,6 @@ import { HttpUtil, RandomUtil, SizeFormatter } from '@/utils'; import { TLS_FLOW_CONTROL } from '@/models/inbound'; import DateTimePicker from '@/components/DateTimePicker'; import type { InboundOption } from '@/hooks/useClients'; -import './ClientBulkAddModal.css'; const FLOW_OPTIONS = Object.values(TLS_FLOW_CONTROL); const JSON_HEADERS = { headers: { 'Content-Type': 'application/json' } } as const; diff --git a/frontend/src/pages/clients/ClientInfoModal.css b/frontend/src/pages/clients/ClientInfoModal.css index 9cf9c9bc..1320fd98 100644 --- a/frontend/src/pages/clients/ClientInfoModal.css +++ b/frontend/src/pages/clients/ClientInfoModal.css @@ -40,7 +40,7 @@ } .link-panel { - border: 1px solid rgba(128, 128, 128, 0.2); + border: 1px solid var(--ant-color-border); border-radius: 8px; padding: 10px; margin-bottom: 10px; @@ -62,37 +62,25 @@ word-break: break-all; white-space: pre-wrap; padding: 6px 8px; - background: rgba(0, 0, 0, 0.04); + background: var(--ant-color-fill-tertiary); border-radius: 4px; user-select: all; } -body.dark .link-panel-text { - background: rgba(255, 255, 255, 0.05); -} - .link-panel-anchor { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-size: 11px; word-break: break-all; padding: 6px 8px; - background: rgba(0, 0, 0, 0.04); + background: var(--ant-color-fill-tertiary); border-radius: 4px; - color: var(--ant-color-primary, #1677ff); + color: var(--ant-color-primary); text-decoration: underline; - text-decoration-color: rgba(22, 119, 255, 0.4); + text-decoration-color: color-mix(in srgb, var(--ant-color-primary) 40%, transparent); transition: background 120ms ease, text-decoration-color 120ms ease; } .link-panel-anchor:hover { - background: rgba(22, 119, 255, 0.08); - text-decoration-color: var(--ant-color-primary, #1677ff); -} - -body.dark .link-panel-anchor { - background: rgba(255, 255, 255, 0.05); -} - -body.dark .link-panel-anchor:hover { - background: rgba(22, 119, 255, 0.16); + background: color-mix(in srgb, var(--ant-color-primary) 12%, transparent); + text-decoration-color: var(--ant-color-primary); } diff --git a/frontend/src/pages/clients/ClientsPage.css b/frontend/src/pages/clients/ClientsPage.css index ab1ce76c..8153b279 100644 --- a/frontend/src/pages/clients/ClientsPage.css +++ b/frontend/src/pages/clients/ClientsPage.css @@ -1,56 +1,6 @@ -.clients-page { - --bg-page: #e6e8ec; - --bg-card: #ffffff; - min-height: 100vh; - background: var(--bg-page); -} - -.clients-page.is-dark { - --bg-page: #1a1b1f; - --bg-card: #23252b; -} - -.clients-page.is-dark.is-ultra { - --bg-page: #000; - --bg-card: #101013; -} - -.clients-page .ant-layout, -.clients-page .ant-layout-content { - background: transparent; -} - -.clients-page .content-shell { - background: transparent; -} - -.clients-page .content-area { - padding: 24px; -} - -@media (max-width: 768px) { - .clients-page .content-area { - padding: 8px; - } -} - .clients-page .ant-pagination-options-size-changer, .clients-page .ant-pagination-options-size-changer .ant-select-selector { - min-width: 100px !important; -} - -.clients-page .loading-spacer { - min-height: calc(100vh - 120px); -} - -.clients-page .summary-card { - padding: 16px; -} - -@media (max-width: 768px) { - .clients-page .summary-card { - padding: 8px; - } + min-width: 100px; } .client-email-list { @@ -92,11 +42,11 @@ vertical-align: middle; } -.dot-green { background: #52c41a; } -.dot-blue { background: #1677ff; } -.dot-red { background: #ff4d4f; } -.dot-orange { background: #fa8c16; } -.dot-gray { background: rgba(128, 128, 128, 0.6); } +.dot-green { background: var(--ant-color-success); } +.dot-blue { background: var(--ant-color-primary); } +.dot-red { background: var(--ant-color-error); } +.dot-orange { background: var(--ant-color-warning); } +.dot-gray { background: var(--ant-color-text-quaternary); } .status-tag { margin: 0 0 0 4px; @@ -154,32 +104,27 @@ .card-pagination .ant-pagination-options-size-changer, .card-pagination .ant-pagination-options-size-changer .ant-select-selector { - min-width: 88px !important; + min-width: 88px; } .bulk-count { font-size: 12px; - background: rgba(22, 119, 255, 0.12); - color: var(--ant-color-primary, #1677ff); + background: color-mix(in srgb, var(--ant-color-primary) 12%, transparent); + color: var(--ant-color-primary); padding: 1px 8px; border-radius: 10px; } .client-card { - border: 1px solid rgba(128, 128, 128, 0.2); + border: 1px solid var(--ant-color-border-secondary); border-radius: 10px; padding: 10px 12px; - background: rgba(255, 255, 255, 0.02); + background: var(--ant-color-fill-quaternary); } .client-card.is-selected { - border-color: var(--ant-color-primary, #1677ff); - background: rgba(22, 119, 255, 0.06); -} - -body.dark .client-card { - background: rgba(255, 255, 255, 0.03); - border-color: rgba(255, 255, 255, 0.1); + border-color: var(--ant-color-primary); + background: color-mix(in srgb, var(--ant-color-primary) 6%, transparent); } .card-head { diff --git a/frontend/src/pages/clients/ClientsPage.tsx b/frontend/src/pages/clients/ClientsPage.tsx index 0a024536..872f87b4 100644 --- a/frontend/src/pages/clients/ClientsPage.tsx +++ b/frontend/src/pages/clients/ClientsPage.tsx @@ -18,6 +18,7 @@ import { Select, Space, Spin, + Statistic, Switch, Table, Tag, @@ -49,7 +50,6 @@ import { useClients } from '@/hooks/useClients'; import { useDatepicker } from '@/hooks/useDatepicker'; import type { ClientRecord, InboundOption } from '@/hooks/useClients'; import AppSidebar from '@/components/AppSidebar'; -import CustomStatistic from '@/components/CustomStatistic'; import { IntlUtil, SizeFormatter } from '@/utils'; import { setMessageInstance } from '@/utils/messageBus'; import LazyMount from '@/components/LazyMount'; @@ -58,7 +58,6 @@ const ClientInfoModal = lazy(() => import('./ClientInfoModal')); const ClientQrModal = lazy(() => import('./ClientQrModal')); const ClientBulkAddModal = lazy(() => import('./ClientBulkAddModal')); const ClientBulkAdjustModal = lazy(() => import('./ClientBulkAdjustModal')); -import '@/styles/page-cards.css'; import './ClientsPage.css'; const FILTER_STATE_KEY = 'clientsFilterState'; @@ -216,13 +215,12 @@ export default function ClientsPage() { return 'active'; }, [expireDiff, trafficDiff]); - function bucketBadgeColor(bucket: Bucket | null): string { + function bucketBadgeStatus(bucket: Bucket | null): 'success' | 'warning' | 'error' | 'default' { switch (bucket) { - case 'depleted': return '#ff4d4f'; - case 'expiring': return '#fa8c16'; - case 'deactive': return 'rgba(128,128,128,0.6)'; - case 'active': return '#52c41a'; - default: return 'rgba(128,128,128,0.6)'; + case 'depleted': return 'error'; + case 'expiring': return 'warning'; + case 'active': return 'success'; + default: return 'default'; } } @@ -624,7 +622,7 @@ export default function ClientsPage() { - } /> + } /> {summary.online.map((e) =>
{e}
)}} > - } /> + } />
@@ -641,7 +639,7 @@ export default function ClientsPage() { open={summary.depleted.length ? undefined : false} content={
{summary.depleted.map((e) =>
{e}
)}
} > - } /> + } /> @@ -650,7 +648,7 @@ export default function ClientsPage() { open={summary.expiring.length ? undefined : false} content={
{summary.expiring.map((e) =>
{e}
)}
} > - } /> + } /> @@ -659,11 +657,11 @@ export default function ClientsPage() { open={summary.deactive.length ? undefined : false} content={
{summary.deactive.map((e) =>
{e}
)}
} > - } /> + } /> - } /> + } />
@@ -838,7 +836,7 @@ export default function ClientsPage() { checked={selectedRowKeys.includes(row.email)} onChange={(e) => toggleSelect(row.email, e.target.checked)} /> - + {row.email} {bucket === 'depleted' && {t('depleted')}} {bucket === 'expiring' && {t('depletingSoon')}} diff --git a/frontend/src/pages/inbounds/InboundFormModal.css b/frontend/src/pages/inbounds/InboundFormModal.css index 505ef12f..ab149e81 100644 --- a/frontend/src/pages/inbounds/InboundFormModal.css +++ b/frontend/src/pages/inbounds/InboundFormModal.css @@ -1,22 +1,3 @@ -.mt-4 { margin-top: 4px; } -.mt-8 { margin-top: 8px; } -.mt-12 { margin-top: 12px; } -.mb-4 { margin-bottom: 4px; } -.mb-8 { margin-bottom: 8px; } -.mb-12 { margin-bottom: 12px; } - -.random-icon { - margin-left: 4px; - cursor: pointer; - color: var(--ant-color-primary, #1890ff); -} - -.danger-icon { - margin-left: 6px; - cursor: pointer; - color: #ff4d4f; -} - .vless-auth-state { display: block; margin-top: 6px; @@ -34,9 +15,9 @@ .advanced-panel { padding: 14px; - border: 1px solid rgba(128, 128, 128, 0.18); + border: 1px solid var(--ant-color-border-secondary); border-radius: 12px; - background: rgba(128, 128, 128, 0.04); + background: var(--ant-color-fill-quaternary); } .advanced-panel__header { @@ -79,9 +60,3 @@ padding-inline: 10px; } } - -body.dark .advanced-panel, -html[data-theme='ultra-dark'] .advanced-panel { - border-color: rgba(255, 255, 255, 0.12); - background: rgba(255, 255, 255, 0.03); -} diff --git a/frontend/src/pages/inbounds/InboundFormModal.tsx b/frontend/src/pages/inbounds/InboundFormModal.tsx index 369a1945..dd1c43c4 100644 --- a/frontend/src/pages/inbounds/InboundFormModal.tsx +++ b/frontend/src/pages/inbounds/InboundFormModal.tsx @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import dayjs, { type Dayjs } from 'dayjs'; @@ -55,8 +54,8 @@ import { DOMAIN_STRATEGY_OPTION, TCP_CONGESTION_OPTION, MODE_OPTION, -} from '@/models/inbound.js'; -import { DBInbound } from '@/models/dbinbound.js'; +} from '@/models/inbound'; +import { DBInbound } from '@/models/dbinbound'; import FinalMaskForm from '@/components/FinalMaskForm'; import DateTimePicker from '@/components/DateTimePicker'; import JsonEditor from '@/components/JsonEditor'; @@ -71,11 +70,75 @@ interface InboundFormModalProps { onClose: () => void; onSaved: () => void; mode: 'add' | 'edit'; - dbInbound: any; - dbInbounds: any[]; + dbInbound: DBInbound | null; + dbInbounds: DBInbound[]; availableNodes?: NodeRecord[]; } +interface StreamLike { + network?: string; + tcp?: { type?: string; request?: { path?: string[] }; acceptProxyProtocol?: boolean }; + ws?: { path?: string; acceptProxyProtocol?: boolean }; + grpc?: { serviceName?: string; multiMode?: boolean }; + httpupgrade?: { path?: string; acceptProxyProtocol?: boolean }; + xhttp?: { path?: string }; + security?: string; + tls?: { certs?: TlsCert[] }; + reality?: unknown; + externalProxy?: unknown; +} + +interface TlsCert { + useFile?: boolean; + certFile?: string; + keyFile?: string; + cert?: string; + key?: string; + ocspStapling?: number; + oneTimeLoading?: boolean; + usage?: string; + buildChain?: boolean; +} + +interface VlessClient { + id?: string; + email?: string; + flow?: string; + enable?: boolean; + subId?: string; + totalGB?: number; + expiryTime?: number; + limitIp?: number; + comment?: string; + tgId?: string; +} + +interface ShadowsocksClient { + email?: string; + password?: string; + method?: string; + enable?: boolean; + subId?: string; + totalGB?: number; + expiryTime?: number; + limitIp?: number; + comment?: string; + tgId?: string; +} + +interface HttpAccount { + user?: string; + pass?: string; +} + +interface WireguardPeer { + privateKey?: string; + publicKey?: string; + psk?: string; + allowedIPs: string[]; + keepAlive?: number; +} + const TRAFFIC_RESETS = ['never', 'hourly', 'daily', 'weekly', 'monthly']; const PROTOCOLS = Object.values(Protocols) as string[]; const TLS_VERSIONS = Object.values(TLS_VERSION_OPTION) as string[]; @@ -107,12 +170,12 @@ interface FallbackRow { xver: number; } -function deriveFallbackDefaults(childDb: any): Omit { +function deriveFallbackDefaults(childDb: DBInbound | null | undefined): Omit { const out = { name: '', alpn: '', path: '', xver: 0 }; if (!childDb) return out; - let stream: any; + let stream: StreamLike | undefined; try { - stream = childDb.toInbound()?.stream; + stream = childDb.toInbound()?.stream as StreamLike | undefined; } catch { return out; } @@ -166,7 +229,9 @@ export default function InboundFormModal({ [availableNodes], ); + // eslint-disable-next-line @typescript-eslint/no-explicit-any const inboundRef = useRef(null); + // eslint-disable-next-line @typescript-eslint/no-explicit-any const dbFormRef = useRef(null); const fallbackKeyRef = useRef(0); const advancedTextRef = useRef({ stream: '', sniffing: '', settings: '' }); @@ -279,9 +344,9 @@ export default function InboundFormModal({ if (!open) return; setFallbackEditing(new Set()); if (mode === 'edit' && dbInbound) { - const parsed = (Inbound as any).fromJson(dbInbound.toInbound().toJson()); + const parsed = Inbound.fromJson(dbInbound.toInbound().toJson()); inboundRef.current = parsed; - dbFormRef.current = new (DBInbound as any)(dbInbound); + dbFormRef.current = new DBInbound(dbInbound); primeAdvancedJson(); if (dbInbound.protocol === Protocols.VLESS || dbInbound.protocol === Protocols.TROJAN) { loadFallbacks(dbInbound.id); @@ -289,12 +354,12 @@ export default function InboundFormModal({ setFallbacks([]); } } else { - const ib = new (Inbound as any)(); + const ib = new Inbound(); ib.protocol = Protocols.VLESS; - ib.settings = (Inbound as any).Settings.getSettings(Protocols.VLESS); + ib.settings = Inbound.Settings.getSettings(Protocols.VLESS); ib.port = RandomUtil.randomInteger(10000, 60000); inboundRef.current = ib; - const form = new (DBInbound as any)(); + const form = new DBInbound(); form.enable = true; form.remark = ''; form.total = 0; @@ -333,7 +398,7 @@ export default function InboundFormModal({ const ib = inboundRef.current; if (mode === 'edit' || !ib) return; ib.protocol = next; - ib.settings = (Inbound as any).Settings.getSettings(next); + ib.settings = Inbound.Settings.getSettings(next); if (!NODE_ELIGIBLE_PROTOCOLS.has(next) && dbFormRef.current) { dbFormRef.current.nodeId = null; } @@ -352,7 +417,7 @@ export default function InboundFormModal({ && !ib.canEnableTlsFlow() && Array.isArray(ib.settings.vlesses) ) { - ib.settings.vlesses.forEach((c: any) => { c.flow = ''; }); + ib.settings.vlesses.forEach((c: VlessClient) => { c.flow = ''; }); } if (next !== 'kcp' && ib.stream.finalmask) { ib.stream.finalmask.udp = []; @@ -379,7 +444,7 @@ export default function InboundFormModal({ xver: 0, }; if (childId) { - const child = (dbInbounds || []).find((ib: any) => ib.id === childId); + const child = (dbInbounds || []).find((ib) => ib.id === childId); Object.assign(row, deriveFallbackDefaults(child)); } setFallbacks((prev) => [...prev, row]); @@ -402,7 +467,7 @@ export default function InboundFormModal({ const onFallbackChildPicked = useCallback((rowKey: string, childId: number) => { setFallbacks((prev) => prev.map((row) => { if (row.rowKey !== rowKey) return row; - const child = (dbInbounds || []).find((ib: any) => ib.id === childId); + const child = (dbInbounds || []).find((ib) => ib.id === childId); const defaults = deriveFallbackDefaults(child); return { ...row, childId, ...defaults }; })); @@ -415,7 +480,7 @@ export default function InboundFormModal({ const rederiveFallback = useCallback((rowKey: string) => { setFallbacks((prev) => prev.map((row) => { if (row.rowKey !== rowKey || !row.childId) return row; - const child = (dbInbounds || []).find((ib: any) => ib.id === row.childId); + const child = (dbInbounds || []).find((ib) => ib.id === row.childId); const defaults = deriveFallbackDefaults(child); return { ...row, ...defaults }; })); @@ -432,9 +497,9 @@ export default function InboundFormModal({ for (const ib of list) { if (ib.id === masterId) continue; if (existing.has(ib.id)) continue; - let stream: any; - try { stream = ib.toInbound()?.stream; } catch { continue; } - if (!stream || !FALLBACK_ELIGIBLE_TRANSPORTS.has(stream.network)) continue; + let stream: StreamLike | undefined; + try { stream = ib.toInbound()?.stream as StreamLike | undefined; } catch { continue; } + if (!stream || !FALLBACK_ELIGIBLE_TRANSPORTS.has(stream.network ?? '')) continue; const row: FallbackRow = { rowKey: `fb-${++fallbackKeyRef.current}`, childId: ib.id, @@ -456,8 +521,8 @@ export default function InboundFormModal({ const list = dbInbounds || []; const masterId = dbInbound?.id; return list - .filter((ib: any) => ib.id !== masterId) - .map((ib: any) => ({ + .filter((ib) => ib.id !== masterId) + .map((ib) => ({ label: `${ib.remark || `#${ib.id}`} · ${ib.protocol}:${ib.port}`, value: ib.id, })); @@ -488,22 +553,22 @@ export default function InboundFormModal({ try { return await fn(); } finally { setSaving(false); } }, []); - const randomSSPassword = useCallback((target: any) => { + const randomSSPassword = useCallback((target: ShadowsocksClient) => { if (target) { - target.password = (RandomUtil as any).randomShadowsocksPassword(inboundRef.current.settings.method); + target.password = RandomUtil.randomShadowsocksPassword(inboundRef.current.settings.method); refresh(); } }, [refresh]); - const regenWgKeypair = useCallback((target: any) => { - const kp = (Wireguard as any).generateKeypair(); + const regenWgKeypair = useCallback((target: WireguardPeer) => { + const kp = Wireguard.generateKeypair(); target.publicKey = kp.publicKey; target.privateKey = kp.privateKey; refresh(); }, [refresh]); const regenInboundWg = useCallback(() => { - const kp = (Wireguard as any).generateKeypair(); + const kp = Wireguard.generateKeypair(); inboundRef.current.settings.pubKey = kp.publicKey; inboundRef.current.settings.secretKey = kp.privateKey; refresh(); @@ -557,7 +622,7 @@ export default function InboundFormModal({ const randomizeShortIds = useCallback(() => { if (!inboundRef.current?.stream?.reality) return; - inboundRef.current.stream.reality.shortIds = (RandomUtil as any).randomShortIds(); + inboundRef.current.stream.reality.shortIds = RandomUtil.randomShortIds(); refresh(); }, [refresh]); @@ -590,7 +655,7 @@ export default function InboundFormModal({ refresh(); }, [defaultCert, defaultKey, refresh]); - const matchesVlessAuth = useCallback((block: any, authId: string) => { + const matchesVlessAuth = useCallback((block: { id?: string; label?: string } | undefined | null, authId: string) => { if (block?.id === authId) return true; const label = (block?.label || '').toLowerCase().replace(/[-_\s]/g, ''); if (authId === 'mlkem768') return label.includes('mlkem768'); @@ -633,11 +698,11 @@ export default function InboundFormModal({ const onSSMethodChange = useCallback(() => { const ib = inboundRef.current; - ib.settings.password = (RandomUtil as any).randomShadowsocksPassword(ib.settings.method); + ib.settings.password = RandomUtil.randomShadowsocksPassword(ib.settings.method); if (ib.isSSMultiUser) { - ib.settings.shadowsockses.forEach((c: any) => { + ib.settings.shadowsockses.forEach((c: ShadowsocksClient) => { c.method = ib.isSS2022 ? '' : ib.settings.method; - c.password = (RandomUtil as any).randomShadowsocksPassword(ib.settings.method); + c.password = RandomUtil.randomShadowsocksPassword(ib.settings.method); }); } else { ib.settings.shadowsockses = []; @@ -686,7 +751,7 @@ export default function InboundFormModal({ return false; } try { - inboundRef.current = (Inbound as any).fromJson({ + inboundRef.current = Inbound.fromJson({ port: ib.port, listen: ib.listen, protocol: ib.protocol, @@ -781,17 +846,26 @@ export default function InboundFormModal({ })(); const setAdvancedAllValue = (next: string) => { - let parsed: any; + let parsedRaw: unknown; try { - parsed = JSON.parse(next); + parsedRaw = JSON.parse(next); } catch (e) { messageApi.error(`All JSON invalid: ${(e as Error).message}`); return; } - if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) { + if (!parsedRaw || typeof parsedRaw !== 'object' || Array.isArray(parsedRaw)) { messageApi.error('All JSON must be an inbound object.'); return; } + const parsed = parsedRaw as { + listen?: string; + port?: number | string; + protocol?: string; + tag?: string; + settings?: unknown; + sniffing?: unknown; + streamSettings?: unknown; + }; const ib = inboundRef.current; try { if (typeof parsed.listen === 'string') ib.listen = parsed.listen; @@ -857,7 +931,7 @@ export default function InboundFormModal({ settings = compactAdvancedJson(advancedTextRef.current.settings, ib.settings.toString(), t('pages.inbounds.advanced.settings')); } catch { return; } - const payload: any = { + const payload: Record = { up: form.up || 0, down: form.down || 0, total: form.total, @@ -876,14 +950,15 @@ export default function InboundFormModal({ if (form.nodeId != null) payload.nodeId = form.nodeId; const url = mode === 'edit' - ? `/panel/api/inbounds/update/${dbInbound.id}` + ? `/panel/api/inbounds/update/${dbInbound!.id}` : '/panel/api/inbounds/add'; const msg = await HttpUtil.post(url, payload); if (msg?.success) { if (isFallbackHost) { + const obj = msg.obj as { id?: number; Id?: number } | null; const masterId = mode === 'edit' - ? dbInbound.id - : ((msg.obj as any)?.id || (msg.obj as any)?.Id); + ? dbInbound!.id + : (obj?.id || obj?.Id); if (masterId) await saveFallbacks(masterId); } onSaved(); @@ -1155,8 +1230,8 @@ export default function InboundFormModal({ - {(ib.settings.accounts || []).map((account: any, idx: number) => ( + {(ib.settings.accounts || []).map((account: HttpAccount, idx: number) => ( {String(idx + 1)} Add peer - {(ib.settings.peers || []).map((peer: any, idx: number) => ( + {(ib.settings.peers || []).map((peer: WireguardPeer, idx: number) => (
Peer {idx + 1} @@ -1906,7 +1981,7 @@ export default function InboundFormModal({ { ib.stream.tls.disableSystemRoot = v; refresh(); }} /> { ib.stream.tls.enableSessionResumption = v; refresh(); }} /> - {(ib.stream.tls.certs || []).map((cert: any, idx: number) => ( + {(ib.stream.tls.certs || []).map((cert: TlsCert, idx: number) => (
{ cert.useFile = e.target.value; refresh(); }}> diff --git a/frontend/src/pages/inbounds/InboundInfoModal.css b/frontend/src/pages/inbounds/InboundInfoModal.css index 44dce4e2..2240375b 100644 --- a/frontend/src/pages/inbounds/InboundInfoModal.css +++ b/frontend/src/pages/inbounds/InboundInfoModal.css @@ -39,7 +39,7 @@ align-items: center; gap: 12px; padding: 6px 0; - border-bottom: 1px solid rgba(128, 128, 128, 0.12); + border-bottom: 1px solid var(--ant-color-border-secondary); } .info-row:last-child { @@ -95,16 +95,12 @@ word-break: break-all; white-space: pre-wrap; padding: 4px 8px; - background: rgba(0, 0, 0, 0.04); + background: var(--ant-color-fill-tertiary); border-radius: 4px; user-select: all; min-width: 0; } -body.dark .value-code { - background: rgba(255, 255, 255, 0.05); -} - .value-copy { flex-shrink: 0; } @@ -112,7 +108,7 @@ body.dark .value-code { .share-buttons { margin-inline-start: 4px; padding-inline-start: 8px; - border-inline-start: 1px solid rgba(128, 128, 128, 0.25); + border-inline-start: 1px solid var(--ant-color-border); } .summary-table { @@ -157,7 +153,7 @@ body.dark .value-code { } .link-panel { - border: 1px solid rgba(128, 128, 128, 0.2); + border: 1px solid var(--ant-color-border); border-radius: 8px; padding: 10px; margin-bottom: 10px; @@ -179,37 +175,25 @@ body.dark .value-code { word-break: break-all; white-space: pre-wrap; padding: 6px 8px; - background: rgba(0, 0, 0, 0.04); + background: var(--ant-color-fill-tertiary); border-radius: 4px; user-select: all; } -body.dark .link-panel-text { - background: rgba(255, 255, 255, 0.05); -} - .link-panel-anchor { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-size: 11px; word-break: break-all; padding: 6px 8px; - background: rgba(0, 0, 0, 0.04); + background: var(--ant-color-fill-tertiary); border-radius: 4px; - color: var(--ant-color-primary, #1677ff); + color: var(--ant-color-primary); text-decoration: underline; - text-decoration-color: rgba(22, 119, 255, 0.4); + text-decoration-color: color-mix(in srgb, var(--ant-color-primary) 40%, transparent); transition: background 120ms ease, text-decoration-color 120ms ease; } .link-panel-anchor:hover { - background: rgba(22, 119, 255, 0.08); - text-decoration-color: var(--ant-color-primary, #1677ff); -} - -body.dark .link-panel-anchor { - background: rgba(255, 255, 255, 0.05); -} - -body.dark .link-panel-anchor:hover { - background: rgba(22, 119, 255, 0.16); + background: color-mix(in srgb, var(--ant-color-primary) 12%, transparent); + text-decoration-color: var(--ant-color-primary); } diff --git a/frontend/src/pages/inbounds/InboundInfoModal.tsx b/frontend/src/pages/inbounds/InboundInfoModal.tsx index f8d8f059..6f3a0599 100644 --- a/frontend/src/pages/inbounds/InboundInfoModal.tsx +++ b/frontend/src/pages/inbounds/InboundInfoModal.tsx @@ -12,7 +12,7 @@ import { ClipboardManager, FileManager, } from '@/utils'; -import { Protocols } from '@/models/inbound.js'; +import { Protocols } from '@/models/inbound'; import InfinityIcon from '@/components/InfinityIcon'; import { useDatepicker } from '@/hooks/useDatepicker'; import type { SubSettings } from './useInbounds'; diff --git a/frontend/src/pages/inbounds/InboundList.css b/frontend/src/pages/inbounds/InboundList.css index 2a9f28ba..7246719e 100644 --- a/frontend/src/pages/inbounds/InboundList.css +++ b/frontend/src/pages/inbounds/InboundList.css @@ -32,29 +32,29 @@ font-size: 12px; } -.ant-table { +.inbounds-page .ant-table { border-radius: 8px; overflow: hidden; } -.ant-table-container { +.inbounds-page .ant-table-container { border-radius: 8px; overflow: hidden; } -.ant-table-thead > tr:first-child > *:first-child { +.inbounds-page .ant-table-thead > tr:first-child > *:first-child { border-start-start-radius: 8px; } -.ant-table-thead > tr:first-child > *:last-child { +.inbounds-page .ant-table-thead > tr:first-child > *:last-child { border-start-end-radius: 8px; } -.ant-table-tbody > tr:last-child > *:first-child { +.inbounds-page .ant-table-tbody > tr:last-child > *:first-child { border-end-start-radius: 8px; } -.ant-table-tbody > tr:last-child > *:last-child { +.inbounds-page .ant-table-tbody > tr:last-child > *:last-child { border-end-end-radius: 8px; } @@ -66,20 +66,15 @@ } .inbound-card { - border: 1px solid rgba(128, 128, 128, 0.2); + border: 1px solid var(--ant-color-border-secondary); border-radius: 10px; padding: 12px; - background: rgba(255, 255, 255, 0.02); + background: var(--ant-color-fill-quaternary); display: flex; flex-direction: column; gap: 8px; } -body.dark .inbound-card { - background: rgba(255, 255, 255, 0.03); - border-color: rgba(255, 255, 255, 0.1); -} - .card-head { display: flex; align-items: center; @@ -142,21 +137,21 @@ body.dark .inbound-card { } @media (max-width: 768px) { - .ant-card-head { + .inbounds-page .ant-card-head { padding: 0 12px; min-height: 44px; } - .ant-card-head-title, - .ant-card-extra { + .inbounds-page .ant-card-head-title, + .inbounds-page .ant-card-extra { padding: 8px 0; } - .ant-card-body { + .inbounds-page .ant-card-body { padding: 8px; } - .row-action-trigger { + .inbounds-page .row-action-trigger { font-size: 22px; padding: 4px; } diff --git a/frontend/src/pages/inbounds/InboundList.tsx b/frontend/src/pages/inbounds/InboundList.tsx index c73db4d3..690b204f 100644 --- a/frontend/src/pages/inbounds/InboundList.tsx +++ b/frontend/src/pages/inbounds/InboundList.tsx @@ -57,7 +57,7 @@ interface DBInboundRecord extends ProtocolFlags { down: number; total: number; expiryTime: number; - _expiryTime: unknown; + _expiryTime: { valueOf(): number } | null; nodeId?: number | null; toInbound: () => { stream?: { network?: string; isTls?: boolean; isReality?: boolean }; diff --git a/frontend/src/pages/inbounds/InboundsPage.css b/frontend/src/pages/inbounds/InboundsPage.css deleted file mode 100644 index 2f3bc461..00000000 --- a/frontend/src/pages/inbounds/InboundsPage.css +++ /dev/null @@ -1,50 +0,0 @@ -.inbounds-page { - --bg-page: #e6e8ec; - --bg-card: #ffffff; - - min-height: 100vh; - background: var(--bg-page); -} - -.inbounds-page.is-dark { - --bg-page: #1a1b1f; - --bg-card: #23252b; -} - -.inbounds-page.is-dark.is-ultra { - --bg-page: #000; - --bg-card: #101013; -} - -.inbounds-page .ant-layout, -.inbounds-page .ant-layout-content { - background: transparent; -} - -.content-shell { - background: transparent; -} - -.content-area { - padding: 24px; -} - -@media (max-width: 768px) { - .content-area { - padding: 8px; - } -} - -.loading-spacer { - min-height: calc(100vh - 120px); -} - -.summary-card { - padding: 16px; -} - -@media (max-width: 768px) { - .summary-card { - padding: 8px; - } -} diff --git a/frontend/src/pages/inbounds/InboundsPage.tsx b/frontend/src/pages/inbounds/InboundsPage.tsx index 7cd570c5..c72b078b 100644 --- a/frontend/src/pages/inbounds/InboundsPage.tsx +++ b/frontend/src/pages/inbounds/InboundsPage.tsx @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ import { lazy, useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { @@ -9,6 +8,7 @@ import { Modal, Row, Spin, + Statistic, message, } from 'antd'; @@ -20,14 +20,13 @@ import { } from '@ant-design/icons'; import { HttpUtil, SizeFormatter, RandomUtil } from '@/utils'; -import { Inbound } from '@/models/inbound.js'; -import { coerceInboundJsonField } from '@/models/dbinbound.js'; +import { Inbound } from '@/models/inbound'; +import { coerceInboundJsonField, type DBInbound } from '@/models/dbinbound'; import { useTheme } from '@/hooks/useTheme'; import { useMediaQuery } from '@/hooks/useMediaQuery'; import { useWebSocket } from '@/hooks/useWebSocket'; import { useNodesQuery } from '@/api/queries/useNodesQuery'; import AppSidebar from '@/components/AppSidebar'; -import CustomStatistic from '@/components/CustomStatistic'; const TextModal = lazy(() => import('@/components/TextModal')); const PromptModal = lazy(() => import('@/components/PromptModal')); @@ -37,8 +36,6 @@ import LazyMount from '@/components/LazyMount'; const InboundFormModal = lazy(() => import('./InboundFormModal')); const InboundInfoModal = lazy(() => import('./InboundInfoModal')); const QrCodeModal = lazy(() => import('./QrCodeModal')); -import '@/styles/page-cards.css'; -import './InboundsPage.css'; type RowAction = | 'edit' @@ -53,6 +50,12 @@ type RowAction = type GeneralAction = 'import' | 'export' | 'subs' | 'resetInbounds'; +interface ClientMatchTarget { + id?: string; + email?: string; + password?: string; +} + export default function InboundsPage() { const { t } = useTranslation(); const { isDark, isUltra, antdThemeConfig } = useTheme(); @@ -94,7 +97,7 @@ export default function InboundsPage() { [nodesList], ); const hasNodeAttachedInbound = useMemo( - () => (dbInbounds || []).some((ib: any) => ib?.nodeId != null), + () => (dbInbounds || []).some((ib) => ib?.nodeId != null), [dbInbounds], ); const showNodeInfo = hasNodeAttachedInbound || hasActiveNode; @@ -106,14 +109,14 @@ export default function InboundsPage() { const [formOpen, setFormOpen] = useState(false); const [formMode, setFormMode] = useState<'add' | 'edit'>('add'); - const [formDbInbound, setFormDbInbound] = useState(null); + const [formDbInbound, setFormDbInbound] = useState(null); const [infoOpen, setInfoOpen] = useState(false); - const [infoDbInbound, setInfoDbInbound] = useState(null); + const [infoDbInbound, setInfoDbInbound] = useState(null); const [infoClientIndex, setInfoClientIndex] = useState(0); const [qrOpen, setQrOpen] = useState(false); - const [qrDbInbound, setQrDbInbound] = useState(null); + const [qrDbInbound, setQrDbInbound] = useState(null); const [textOpen, setTextOpen] = useState(false); const [textTitle, setTextTitle] = useState(''); @@ -128,7 +131,7 @@ export default function InboundsPage() { const [promptLoading, setPromptLoading] = useState(false); const [promptHandler, setPromptHandler] = useState<((value: string) => Promise | boolean | void) | null>(null); - const hostOverrideFor = useCallback((dbInbound: any) => { + const hostOverrideFor = useCallback((dbInbound: DBInbound | null) => { if (!dbInbound || dbInbound.nodeId == null) return ''; return nodesById.get(dbInbound.nodeId)?.address || ''; }, [nodesById]); @@ -172,8 +175,8 @@ export default function InboundsPage() { } }, [promptHandler]); - const projectChildThroughMaster = useCallback((child: any, master: any) => { - const projected = JSON.parse(JSON.stringify(child)); + const projectChildThroughMaster = useCallback((child: DBInbound, master: DBInbound): DBInbound => { + const projected = JSON.parse(JSON.stringify(child)) as DBInbound; projected.listen = master.listen; projected.port = master.port; const masterStream = master.toInbound().stream; @@ -183,17 +186,18 @@ export default function InboundsPage() { childInbound.stream.reality = masterStream.reality; childInbound.stream.externalProxy = masterStream.externalProxy; projected.streamSettings = childInbound.stream.toString(); - return new child.constructor(projected); + const Ctor = child.constructor as new (data: DBInbound) => DBInbound; + return new Ctor(projected); }, []); - const checkFallback = useCallback((dbInbound: any) => { + const checkFallback = useCallback((dbInbound: DBInbound): DBInbound => { const parent = dbInbound?.fallbackParent; if (parent?.masterId) { - const master = (dbInbounds as any[]).find((ib: any) => ib.id === parent.masterId); + const master = dbInbounds.find((ib) => ib.id === parent.masterId); if (master) return projectChildThroughMaster(dbInbound, master); } - if (!(dbInbound?.listen as string | undefined)?.startsWith?.('@')) return dbInbound; - for (const candidate of dbInbounds as any[]) { + if (!dbInbound?.listen?.startsWith?.('@')) return dbInbound; + for (const candidate of dbInbounds) { if (candidate.id === dbInbound.id) continue; const parsed = candidate.toInbound(); if (!parsed.isTcp) continue; @@ -205,11 +209,11 @@ export default function InboundsPage() { return dbInbound; }, [dbInbounds, projectChildThroughMaster]); - const findClientIndex = useCallback((dbInbound: any, client: any) => { + const findClientIndex = useCallback((dbInbound: DBInbound, client: ClientMatchTarget | null) => { if (!client) return 0; const inbound = dbInbound.toInbound(); - const clients = inbound?.clients || []; - const idx = clients.findIndex((c: any) => { + const clients = (inbound?.clients || []) as ClientMatchTarget[]; + const idx = clients.findIndex((c) => { if (!c) return false; switch (dbInbound.protocol) { case 'trojan': @@ -222,7 +226,7 @@ export default function InboundsPage() { return idx >= 0 ? idx : 0; }, []); - const exportInboundLinks = useCallback((dbInbound: any) => { + const exportInboundLinks = useCallback((dbInbound: DBInbound) => { const projected = checkFallback(dbInbound); openText({ title: t('pages.inbounds.exportLinksTitle'), @@ -231,13 +235,13 @@ export default function InboundsPage() { }); }, [checkFallback, remarkModel, hostOverrideFor, openText, t]); - const exportInboundClipboard = useCallback((dbInbound: any) => { + const exportInboundClipboard = useCallback((dbInbound: DBInbound) => { openText({ title: t('pages.inbounds.inboundJsonTitle'), content: JSON.stringify(dbInbound, null, 2) }); }, [openText, t]); - const exportInboundSubs = useCallback((dbInbound: any) => { + const exportInboundSubs = useCallback((dbInbound: DBInbound) => { const inbound = dbInbound.toInbound(); - const clients = inbound?.clients || []; + const clients = (inbound?.clients || []) as { subId?: string }[]; const subLinks: string[] = []; for (const c of clients) { if (c.subId && subSettings.subURI) { @@ -253,7 +257,7 @@ export default function InboundsPage() { const exportAllLinks = useCallback(async () => { const hydrated = await Promise.all( - (dbInbounds as any[]).map((ib) => hydrateInbound(ib.id).then((r) => r ?? ib)), + dbInbounds.map((ib) => hydrateInbound(ib.id).then((r) => r ?? ib)), ); const out: string[] = []; for (const ib of hydrated) { @@ -265,12 +269,12 @@ export default function InboundsPage() { const exportAllSubs = useCallback(async () => { const hydrated = await Promise.all( - (dbInbounds as any[]).map((ib) => hydrateInbound(ib.id).then((r) => r ?? ib)), + dbInbounds.map((ib) => hydrateInbound(ib.id).then((r) => r ?? ib)), ); const out: string[] = []; for (const ib of hydrated) { const inbound = ib.toInbound(); - const clients = inbound?.clients || []; + const clients = (inbound?.clients || []) as { subId?: string }[]; for (const c of clients) { if (c.subId && subSettings.subURI) { out.push(subSettings.subURI + c.subId); @@ -303,13 +307,13 @@ export default function InboundsPage() { setFormOpen(true); }, []); - const openEdit = useCallback((dbInbound: any) => { + const openEdit = useCallback((dbInbound: DBInbound) => { setFormMode('edit'); setFormDbInbound(dbInbound); setFormOpen(true); }, []); - const confirmDelete = useCallback((dbInbound: any) => { + const confirmDelete = useCallback((dbInbound: DBInbound) => { modal.confirm({ title: t('pages.inbounds.deleteConfirmTitle', { remark: dbInbound.remark }), content: t('pages.inbounds.deleteConfirmContent'), @@ -323,7 +327,7 @@ export default function InboundsPage() { }); }, [modal, refresh, t]); - const confirmResetTraffic = useCallback((dbInbound: any) => { + const confirmResetTraffic = useCallback((dbInbound: DBInbound) => { modal.confirm({ title: t('pages.inbounds.resetConfirmTitle', { remark: dbInbound.remark }), content: t('pages.inbounds.resetConfirmContent'), @@ -336,7 +340,7 @@ export default function InboundsPage() { }); }, [modal, refresh, t]); - const confirmClone = useCallback((dbInbound: any) => { + const confirmClone = useCallback((dbInbound: DBInbound) => { modal.confirm({ title: t('pages.inbounds.cloneConfirmTitle', { remark: dbInbound.remark }), content: t('pages.inbounds.cloneConfirmContent'), @@ -350,7 +354,7 @@ export default function InboundsPage() { raw.clients = []; clonedSettings = JSON.stringify(raw); } catch { - clonedSettings = (Inbound as any).Settings.getSettings(baseInbound.protocol).toString(); + clonedSettings = Inbound.Settings.getSettings(baseInbound.protocol).toString(); } const data = { up: 0, @@ -393,7 +397,7 @@ export default function InboundsPage() { } }, [modal, importInbound, exportAllLinks, exportAllSubs, refresh, messageApi]); - const onRowAction = useCallback(async ({ key, dbInbound }: { key: RowAction; dbInbound: any }) => { + const onRowAction = useCallback(async ({ key, dbInbound }: { key: RowAction; dbInbound: DBInbound }) => { // Actions that touch per-client secrets (uuid, password, flow, ...) need // the full payload that the slim list view does not ship. Hydrate first // and then operate on the rehydrated record. @@ -457,21 +461,21 @@ export default function InboundsPage() { - } /> - } /> - } @@ -483,7 +487,7 @@ export default function InboundsPage() { onRowAction({ key, dbInbound: dbInbound as unknown as DBInbound })} /> @@ -512,7 +516,7 @@ export default function InboundsPage() { onSaved={refresh} mode={formMode} dbInbound={formDbInbound} - dbInbounds={dbInbounds as any[]} + dbInbounds={dbInbounds} availableNodes={nodesList} /> diff --git a/frontend/src/pages/inbounds/QrCodeModal.tsx b/frontend/src/pages/inbounds/QrCodeModal.tsx index f4fae390..720944e5 100644 --- a/frontend/src/pages/inbounds/QrCodeModal.tsx +++ b/frontend/src/pages/inbounds/QrCodeModal.tsx @@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next'; import { Collapse, Modal } from 'antd'; import type { CollapseProps } from 'antd'; -import { Protocols } from '@/models/inbound.js'; +import { Protocols } from '@/models/inbound'; import QrPanel from './QrPanel'; import type { SubSettings } from './useInbounds'; diff --git a/frontend/src/pages/inbounds/QrPanel.css b/frontend/src/pages/inbounds/QrPanel.css index 8f739c9c..b2e2c1d8 100644 --- a/frontend/src/pages/inbounds/QrPanel.css +++ b/frontend/src/pages/inbounds/QrPanel.css @@ -1,5 +1,5 @@ .qr-panel { - border: 1px solid rgba(128, 128, 128, 0.2); + border: 1px solid var(--ant-color-border-secondary); border-radius: 8px; padding: 10px; margin-bottom: 10px; diff --git a/frontend/src/pages/inbounds/useInbounds.ts b/frontend/src/pages/inbounds/useInbounds.ts index 6a5ff89a..37e18088 100644 --- a/frontend/src/pages/inbounds/useInbounds.ts +++ b/frontend/src/pages/inbounds/useInbounds.ts @@ -2,8 +2,8 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useQuery, useQueryClient } from '@tanstack/react-query'; import { HttpUtil } from '@/utils'; -import { DBInbound } from '@/models/dbinbound.js'; -import { Protocols } from '@/models/inbound.js'; +import { DBInbound } from '@/models/dbinbound'; +import { Protocols } from '@/models/inbound'; import { setDatepicker } from '@/hooks/useDatepicker'; import { keys } from '@/api/queryKeys'; diff --git a/frontend/src/pages/index/BackupModal.css b/frontend/src/pages/index/BackupModal.css index e5d8147b..838aa85d 100644 --- a/frontend/src/pages/index/BackupModal.css +++ b/frontend/src/pages/index/BackupModal.css @@ -1,32 +1,22 @@ .backup-list { width: 100%; - border: 1px solid rgba(5, 5, 5, 0.06); + border: 1px solid var(--ant-color-border-secondary); border-radius: 8px; overflow: hidden; } -body.dark .backup-list, -html[data-theme='ultra-dark'] .backup-list { - border-color: rgba(255, 255, 255, 0.12); -} - .backup-item { display: flex; align-items: center; gap: 16px; padding: 12px 24px; - border-bottom: 1px solid rgba(5, 5, 5, 0.06); + border-bottom: 1px solid var(--ant-color-border-secondary); } .backup-item:last-child { border-bottom: 0; } -body.dark .backup-item, -html[data-theme='ultra-dark'] .backup-item { - border-bottom-color: rgba(255, 255, 255, 0.08); -} - .backup-meta { flex: 1; display: flex; @@ -37,21 +27,11 @@ html[data-theme='ultra-dark'] .backup-item { .backup-title { font-size: 14px; font-weight: 500; - color: rgba(0, 0, 0, 0.88); + color: var(--ant-color-text); } .backup-description { font-size: 14px; - color: rgba(0, 0, 0, 0.45); + color: var(--ant-color-text-tertiary); line-height: 1.5715; } - -body.dark .backup-title, -html[data-theme='ultra-dark'] .backup-title { - color: rgba(255, 255, 255, 0.85); -} - -body.dark .backup-description, -html[data-theme='ultra-dark'] .backup-description { - color: rgba(255, 255, 255, 0.45); -} diff --git a/frontend/src/pages/index/CustomGeoSection.css b/frontend/src/pages/index/CustomGeoSection.css index 3b2bbc44..dc58cce7 100644 --- a/frontend/src/pages/index/CustomGeoSection.css +++ b/frontend/src/pages/index/CustomGeoSection.css @@ -1,7 +1,3 @@ -.mb-10 { - margin-bottom: 10px; -} - .toolbar { display: flex; align-items: center; @@ -14,15 +10,11 @@ margin-left: 4px; padding: 2px 8px; border-radius: 10px; - background: rgba(0, 0, 0, 0.05); + background: var(--ant-color-fill-tertiary); font-size: 12px; opacity: 0.75; } -body.dark .custom-geo-count { - background: rgba(255, 255, 255, 0.08); -} - .custom-geo-alias-cell { display: flex; align-items: center; @@ -48,20 +40,12 @@ body.dark .custom-geo-count { font-size: 12px; padding: 2px 6px; border-radius: 4px; - background: rgba(0, 0, 0, 0.05); + background: var(--ant-color-fill-tertiary); user-select: all; } .custom-geo-copyable:hover { - background: rgba(0, 0, 0, 0.1); -} - -body.dark .custom-geo-ext-code { - background: rgba(255, 255, 255, 0.08); -} - -body.dark .custom-geo-copyable:hover { - background: rgba(255, 255, 255, 0.14); + background: var(--ant-color-fill-secondary); } .custom-geo-muted { diff --git a/frontend/src/pages/index/IndexPage.css b/frontend/src/pages/index/IndexPage.css index 889ed959..5efca336 100644 --- a/frontend/src/pages/index/IndexPage.css +++ b/frontend/src/pages/index/IndexPage.css @@ -1,34 +1,3 @@ -.index-page { - --bg-page: #e6e8ec; - --bg-card: #ffffff; - - min-height: 100vh; - background: var(--bg-page); -} - -.index-page.is-dark { - --bg-page: #1a1b1f; - --bg-card: #23252b; -} - -.index-page.is-dark.is-ultra { - --bg-page: #000; - --bg-card: #101013; -} - -.index-page .ant-layout, -.index-page .ant-layout-content { - background: transparent; -} - -.index-page .content-shell { - background: transparent; -} - -.index-page .content-area { - padding: 24px; -} - @media (max-width: 768px) { .index-page .content-area { padding: 12px; @@ -36,156 +5,11 @@ } } -.index-page .loading-spacer { - min-height: calc(100vh - 120px); -} - -.index-page .ant-card { - border-radius: 12px; - border: 1px solid rgba(0, 0, 0, 0.06); - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04); - transition: transform 0.2s ease, box-shadow 0.25s ease, border-color 0.2s ease; -} - -body.dark .index-page .ant-card { - border-color: rgba(255, 255, 255, 0.06); - box-shadow: - 0 1px 2px rgba(0, 0, 0, 0.4), - inset 0 1px 0 rgba(255, 255, 255, 0.03); -} - -html[data-theme='ultra-dark'] .index-page .ant-card { - border-color: rgba(255, 255, 255, 0.04); - box-shadow: - 0 1px 2px rgba(0, 0, 0, 0.6), - inset 0 1px 0 rgba(255, 255, 255, 0.025); -} - -.index-page .ant-card.ant-card-hoverable:hover { - transform: translateY(-2px); - border-color: rgba(0, 0, 0, 0.10); - box-shadow: 0 6px 18px rgba(0, 0, 0, 0.08); -} - -body.dark .index-page .ant-card.ant-card-hoverable:hover { - border-color: rgba(255, 255, 255, 0.12); - box-shadow: - 0 8px 24px rgba(0, 0, 0, 0.5), - inset 0 1px 0 rgba(255, 255, 255, 0.04); -} - -html[data-theme='ultra-dark'] .index-page .ant-card.ant-card-hoverable:hover { - border-color: rgba(255, 255, 255, 0.08); - box-shadow: - 0 8px 24px rgba(0, 0, 0, 0.75), - inset 0 1px 0 rgba(255, 255, 255, 0.03); -} - -.index-page .ant-card .ant-card-head { - min-height: 44px; - padding-inline: 16px; -} - -.index-page .ant-card .ant-card-head-title { - font-size: 13px; - font-weight: 600; - letter-spacing: 0.5px; - text-transform: uppercase; - opacity: 0.75; -} - -.index-page .ant-card .ant-card-body { - padding: 18px 20px; -} - -.index-page .ant-card .ant-card-body > .ant-row > .ant-col { - position: relative; - padding: 4px 6px; -} - -@media (min-width: 769px) { - .index-page .ant-card .ant-card-body > .ant-row > .ant-col + .ant-col::before { - content: ''; - position: absolute; - left: 0; - top: 10%; - bottom: 10%; - width: 1px; - background: linear-gradient(180deg, transparent, rgba(0, 0, 0, 0.10), transparent); - pointer-events: none; - } -} - -body.dark .index-page .ant-card .ant-card-body > .ant-row > .ant-col + .ant-col::before { - background: linear-gradient(180deg, transparent, rgba(255, 255, 255, 0.12), transparent); -} - -.index-page .ant-card .ant-card-head { - border-bottom-color: rgba(0, 0, 0, 0.06); -} - -.index-page .ant-card .ant-card-actions { - border-top-color: rgba(0, 0, 0, 0.06); - background: transparent; -} - -.index-page .ant-card .ant-card-actions > li { - border-inline-end-color: rgba(0, 0, 0, 0.06); -} - -body.dark .index-page .ant-card .ant-card-head { - border-bottom-color: rgba(255, 255, 255, 0.06); -} - -body.dark .index-page .ant-card .ant-card-actions { - border-top-color: rgba(255, 255, 255, 0.06); -} - -body.dark .index-page .ant-card .ant-card-actions > li { - border-inline-end-color: rgba(255, 255, 255, 0.06); -} - -html[data-theme='ultra-dark'] .index-page .ant-card .ant-card-head { - border-bottom-color: rgba(255, 255, 255, 0.04); -} - -html[data-theme='ultra-dark'] .index-page .ant-card .ant-card-actions { - border-top-color: rgba(255, 255, 255, 0.04); -} - -html[data-theme='ultra-dark'] .index-page .ant-card .ant-card-actions > li { - border-inline-end-color: rgba(255, 255, 255, 0.04); -} - .index-page .action { cursor: pointer; justify-content: center; max-width: 100%; - padding: 0 8px; flex-wrap: nowrap; - color: rgba(0, 0, 0, 0.78); - font-weight: 500; - transition: opacity 0.15s ease, transform 0.15s ease, color 0.2s ease; -} - -.index-page .action .anticon { - color: rgba(0, 0, 0, 0.72); -} - -body.dark .index-page .action { - color: rgba(255, 255, 255, 0.82); -} - -body.dark .index-page .action .anticon { - color: rgba(255, 255, 255, 0.75); -} - -html[data-theme='ultra-dark'] .index-page .action { - color: rgba(255, 255, 255, 0.86); -} - -html[data-theme='ultra-dark'] .index-page .action .anticon { - color: rgba(255, 255, 255, 0.78); } .index-page .action > span:not(.anticon):not(.tg-icon) { @@ -195,23 +19,13 @@ html[data-theme='ultra-dark'] .index-page .action .anticon { min-width: 0; } -.index-page .action:hover { - opacity: 0.75; - transform: translateY(-1px); -} - -.index-page .ant-card-actions > li { - margin: 8px 0; - min-width: 0; -} - .index-page .action-update { - color: #fa8c16; + color: var(--ant-color-warning); font-weight: 600; } .index-page .action-update .anticon { - color: #fa8c16; + color: var(--ant-color-warning); } .index-page .history-tag { diff --git a/frontend/src/pages/index/IndexPage.tsx b/frontend/src/pages/index/IndexPage.tsx index 21bfcb3a..c3a58105 100644 --- a/frontend/src/pages/index/IndexPage.tsx +++ b/frontend/src/pages/index/IndexPage.tsx @@ -11,6 +11,7 @@ import { Row, Space, Spin, + Statistic, Tag, Tooltip, } from 'antd'; @@ -39,7 +40,6 @@ import { useTheme } from '@/hooks/useTheme'; import { useStatusQuery } from '@/api/queries/useStatusQuery'; import { useMediaQuery } from '@/hooks/useMediaQuery'; import AppSidebar from '@/components/AppSidebar'; -import CustomStatistic from '@/components/CustomStatistic'; import LazyMount from '@/components/LazyMount'; import { setMessageInstance } from '@/utils/messageBus'; import StatusCard from './StatusCard'; @@ -53,7 +53,6 @@ const SystemHistoryModal = lazy(() => import('./SystemHistoryModal')); const XrayMetricsModal = lazy(() => import('./XrayMetricsModal')); const XrayLogModal = lazy(() => import('./XrayLogModal')); const VersionModal = lazy(() => import('./VersionModal')); -import '@/styles/page-cards.css'; import './IndexPage.css'; export default function IndexPage() { @@ -285,14 +284,14 @@ export default function IndexPage() { - } /> - } @@ -306,14 +305,14 @@ export default function IndexPage() { - } /> - } @@ -327,7 +326,7 @@ export default function IndexPage() { - } @@ -335,7 +334,7 @@ export default function IndexPage() { /> - } @@ -350,14 +349,14 @@ export default function IndexPage() { - } /> - } @@ -392,14 +391,14 @@ export default function IndexPage() { > - } /> - } @@ -413,14 +412,14 @@ export default function IndexPage() { - } /> - } diff --git a/frontend/src/pages/index/LogModal.css b/frontend/src/pages/index/LogModal.css index 1fc31322..86858812 100644 --- a/frontend/src/pages/index/LogModal.css +++ b/frontend/src/pages/index/LogModal.css @@ -32,9 +32,10 @@ word-break: break-word; max-height: 60vh; overflow-y: auto; - border: 1px solid rgba(128, 128, 128, 0.25); + border: 1px solid var(--ant-color-border); border-radius: 6px; - background: rgba(0, 0, 0, 0.04); + background: var(--ant-color-fill-tertiary); + color: var(--ant-color-text); } .log-stamp { @@ -140,10 +141,6 @@ } body.dark .log-container { - background: rgba(255, 255, 255, 0.03); - border-color: rgba(255, 255, 255, 0.1); - color: rgba(255, 255, 255, 0.88); - --log-stamp: #6aa6ee; --log-debug: #6aa6ee; --log-info: #4ed3a6; @@ -165,12 +162,6 @@ html[data-theme="ultra-dark"] .log-container { --log-divider: rgba(255, 255, 255, 0.12); } -.logmodal-mobile { - top: 0 !important; - padding-bottom: 0 !important; - max-width: 100vw !important; -} - .logmodal-mobile .ant-modal-content { border-radius: 0; height: 100vh; diff --git a/frontend/src/pages/index/LogModal.tsx b/frontend/src/pages/index/LogModal.tsx index ef31ad34..1f074017 100644 --- a/frontend/src/pages/index/LogModal.tsx +++ b/frontend/src/pages/index/LogModal.tsx @@ -109,6 +109,7 @@ export default function LogModal({ open, onClose }: LogModalProps) { open={open} footer={null} width={isMobile ? '100vw' : 800} + style={isMobile ? { top: 0, paddingBottom: 0, maxWidth: '100vw' } : undefined} className={isMobile ? 'logmodal-mobile' : undefined} onCancel={onClose} title={titleNode} diff --git a/frontend/src/pages/index/PanelUpdateModal.css b/frontend/src/pages/index/PanelUpdateModal.css index 4c676c74..9c8190dc 100644 --- a/frontend/src/pages/index/PanelUpdateModal.css +++ b/frontend/src/pages/index/PanelUpdateModal.css @@ -1,36 +1,22 @@ -.mb-12 { - margin-bottom: 12px; -} - .version-list { width: 100%; - border: 1px solid rgba(5, 5, 5, 0.06); + border: 1px solid var(--ant-color-border-secondary); border-radius: 8px; overflow: hidden; } -body.dark .version-list, -html[data-theme='ultra-dark'] .version-list { - border-color: rgba(255, 255, 255, 0.12); -} - .version-list-item { display: flex; align-items: center; justify-content: space-between; padding: 12px 24px; - border-bottom: 1px solid rgba(5, 5, 5, 0.06); + border-bottom: 1px solid var(--ant-color-border-secondary); } .version-list-item:last-child { border-bottom: 0; } -body.dark .version-list-item, -html[data-theme='ultra-dark'] .version-list-item { - border-bottom-color: rgba(255, 255, 255, 0.08); -} - .actions-row { display: flex; justify-content: flex-end; diff --git a/frontend/src/pages/index/SystemHistoryModal.css b/frontend/src/pages/index/SystemHistoryModal.css index fc5a11e1..445a8ea3 100644 --- a/frontend/src/pages/index/SystemHistoryModal.css +++ b/frontend/src/pages/index/SystemHistoryModal.css @@ -11,20 +11,9 @@ margin: 8px 8px 16px; padding: 16px 18px 18px; border-radius: 14px; - background: linear-gradient(180deg, rgba(99, 102, 241, 0.05), rgba(99, 102, 241, 0)); - border: 1px solid rgba(99, 102, 241, 0.12); - box-shadow: 0 2px 12px rgba(99, 102, 241, 0.06); -} - -body.dark .cpu-chart-wrap { - background: linear-gradient(180deg, rgba(129, 140, 248, 0.08), rgba(129, 140, 248, 0)); - border-color: rgba(129, 140, 248, 0.16); - box-shadow: 0 2px 16px rgba(0, 0, 0, 0.25); -} - -html[data-theme='ultra-dark'] .cpu-chart-wrap { - background: linear-gradient(180deg, rgba(129, 140, 248, 0.05), rgba(129, 140, 248, 0)); - border-color: rgba(129, 140, 248, 0.10); + background: linear-gradient(180deg, color-mix(in srgb, var(--ant-color-primary) 6%, transparent), transparent); + border: 1px solid var(--ant-color-border-secondary); + box-shadow: 0 2px 12px var(--ant-color-fill-quaternary); } .cpu-chart-meta { diff --git a/frontend/src/pages/index/SystemHistoryModal.tsx b/frontend/src/pages/index/SystemHistoryModal.tsx index 84b00643..69592550 100644 --- a/frontend/src/pages/index/SystemHistoryModal.tsx +++ b/frontend/src/pages/index/SystemHistoryModal.tsx @@ -142,7 +142,6 @@ export default function SystemHistoryModal({ open, status, onClose }: SystemHist = { error: 'pages.index.xrayStatusError', }; -function badgeAnimationClass(color: string): string { - if (color === 'green') return 'xray-running-animation'; - if (color === 'orange') return 'xray-stop-animation'; - if (color === 'red') return 'xray-error-animation'; - return 'xray-processing-animation'; -} - export default function XrayStatusCard({ status, isMobile, @@ -65,12 +58,7 @@ export default function XrayStatusCard({ const extra = status.xray.state !== 'error' ? ( - + ) : ( } > - + ); diff --git a/frontend/src/pages/login/LoginPage.css b/frontend/src/pages/login/LoginPage.css index ed2b7e77..d78ffae8 100644 --- a/frontend/src/pages/login/LoginPage.css +++ b/frontend/src/pages/login/LoginPage.css @@ -228,36 +228,6 @@ font-size: 18px; } -.theme-cycle { - width: 40px; - height: 40px; - border-radius: 50%; - border: 1px solid var(--color-border); - background: var(--bg-card); - color: var(--color-text); - display: inline-flex; - align-items: center; - justify-content: center; - cursor: pointer; - padding: 0; - -webkit-backdrop-filter: blur(20px); - backdrop-filter: blur(20px); - transition: background-color 0.2s, transform 0.15s, color 0.2s; -} - -.theme-cycle:hover, -.theme-cycle:focus-visible { - background-color: rgba(99, 102, 241, 0.15); - color: var(--color-accent); - transform: scale(1.05); - outline: none; -} - -.theme-cycle svg { - width: 18px; - height: 18px; -} - .login-wrapper { position: relative; min-height: 100vh; @@ -402,44 +372,3 @@ margin-bottom: 0; } -.lang-list { - list-style: none; - margin: 0; - padding: 0; - min-width: 160px; - display: flex; - flex-direction: column; - gap: 2px; -} - -.lang-item { - display: flex; - align-items: center; - gap: 10px; - width: 100%; - padding: 8px 12px; - border: none; - border-radius: 8px; - background: transparent; - color: inherit; - font: inherit; - text-align: start; - cursor: pointer; - transition: background-color 0.15s, color 0.15s; -} - -.lang-item:hover, -.lang-item:focus-visible { - background-color: rgba(99, 102, 241, 0.12); - outline: none; -} - -.lang-item.is-active { - color: var(--color-accent); - font-weight: 600; -} - -.lang-item-icon { - font-size: 16px; - line-height: 1; -} diff --git a/frontend/src/pages/login/LoginPage.tsx b/frontend/src/pages/login/LoginPage.tsx index 46d8c7a3..fe2ab6d9 100644 --- a/frontend/src/pages/login/LoginPage.tsx +++ b/frontend/src/pages/login/LoginPage.tsx @@ -6,13 +6,18 @@ import { Form, Input, Layout, + Menu, Popover, + Space, Spin, message, } from 'antd'; import { KeyOutlined, LockOutlined, + MoonFilled, + MoonOutlined, + SunOutlined, TranslationOutlined, UserOutlined, } from '@ant-design/icons'; @@ -105,26 +110,20 @@ export default function LoginPage() { return classes.join(' '); }, [isDark, isUltra]); - const langList = useMemo( - () => LanguageManager.supportedLanguages as { value: string; name: string; icon: string }[], + const langMenuItems = useMemo( + () => (LanguageManager.supportedLanguages as { value: string; name: string; icon: string }[]).map((l) => ({ + key: l.value, + label: ( + + + {l.name} + + ), + })), [], ); - const themeIcon = !isDark ? ( - - ) : !isUltra ? ( - - ) : ( - - ); + const themeIcon = !isDark ? : !isUltra ? : ; return ( @@ -132,35 +131,30 @@ export default function LoginPage() {
- + /> - {langList.map((l) => ( -
  • - -
  • - ))} - + onLangChange(key)} + style={{ border: 'none', minWidth: 160 }} + /> } >
    diff --git a/frontend/src/pages/nodes/NodesPage.css b/frontend/src/pages/nodes/NodesPage.css deleted file mode 100644 index e8800311..00000000 --- a/frontend/src/pages/nodes/NodesPage.css +++ /dev/null @@ -1,49 +0,0 @@ -.nodes-page { - --bg-page: #e6e8ec; - --bg-card: #ffffff; - min-height: 100vh; - background: var(--bg-page); -} - -.nodes-page.is-dark { - --bg-page: #1a1b1f; - --bg-card: #23252b; -} - -.nodes-page.is-dark.is-ultra { - --bg-page: #000; - --bg-card: #101013; -} - -.nodes-page .ant-layout, -.nodes-page .ant-layout-content { - background: transparent; -} - -.nodes-page .content-shell { - background: transparent; -} - -.nodes-page .content-area { - padding: 24px; -} - -@media (max-width: 768px) { - .nodes-page .content-area { - padding: 8px; - } -} - -.nodes-page .loading-spacer { - min-height: calc(100vh - 120px); -} - -.nodes-page .summary-card { - padding: 16px; -} - -@media (max-width: 768px) { - .nodes-page .summary-card { - padding: 8px; - } -} diff --git a/frontend/src/pages/nodes/NodesPage.tsx b/frontend/src/pages/nodes/NodesPage.tsx index 2f5b7a79..966b9649 100644 --- a/frontend/src/pages/nodes/NodesPage.tsx +++ b/frontend/src/pages/nodes/NodesPage.tsx @@ -1,6 +1,6 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { Card, Col, ConfigProvider, Layout, Modal, Row, Spin, message } from 'antd'; +import { Card, Col, ConfigProvider, Layout, Modal, Row, Spin, Statistic, message } from 'antd'; import { CheckCircleOutlined, CloseCircleOutlined, @@ -14,12 +14,9 @@ import { useNodesQuery } from '@/api/queries/useNodesQuery'; import type { NodeRecord } from '@/api/queries/useNodesQuery'; import { useNodeMutations } from '@/api/queries/useNodeMutations'; import AppSidebar from '@/components/AppSidebar'; -import CustomStatistic from '@/components/CustomStatistic'; import NodeList from './NodeList'; import NodeFormModal from './NodeFormModal'; import { setMessageInstance } from '@/utils/messageBus'; -import '@/styles/page-cards.css'; -import './NodesPage.css'; export default function NodesPage() { const { t } = useTranslation(); @@ -109,28 +106,28 @@ export default function NodesPage() { - } /> - } + prefix={} /> - } + prefix={} /> - 0 ? `${totals.avgLatency} ms` : '-'} prefix={} diff --git a/frontend/src/pages/settings/SecurityTab.css b/frontend/src/pages/settings/SecurityTab.css index 158f45ed..e078c081 100644 --- a/frontend/src/pages/settings/SecurityTab.css +++ b/frontend/src/pages/settings/SecurityTab.css @@ -22,7 +22,7 @@ } .api-token-row { - border: 1px solid rgba(128, 128, 128, 0.18); + border: 1px solid var(--ant-color-border-secondary); border-radius: 8px; padding: 10px 12px; display: flex; @@ -78,7 +78,7 @@ font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12.5px; padding: 4px 8px; - background: rgba(128, 128, 128, 0.08); + background: var(--ant-color-fill-tertiary); border-radius: 4px; word-break: break-all; } diff --git a/frontend/src/pages/settings/SettingsPage.css b/frontend/src/pages/settings/SettingsPage.css index d5d638ab..7bacdcbe 100644 --- a/frontend/src/pages/settings/SettingsPage.css +++ b/frontend/src/pages/settings/SettingsPage.css @@ -1,87 +1,9 @@ -.settings-page { - --bg-page: #e6e8ec; - --bg-card: #ffffff; - min-height: 100vh; - background: var(--bg-page); -} - -.settings-page.is-dark { - --bg-page: #1a1b1f; - --bg-card: #23252b; -} - -.settings-page.is-dark.is-ultra { - --bg-page: #000; - --bg-card: #101013; -} - -.settings-page .ant-layout, -.settings-page .ant-layout-content { - background: transparent; -} - -.settings-page .content-shell { - background: transparent; -} - -.settings-page .content-area { - padding: 24px; -} - -.settings-page .loading-spacer { - min-height: calc(100vh - 120px); -} - .settings-page .conf-alert { margin-bottom: 10px; } -.settings-page .header-row { - display: flex; - flex-wrap: wrap; - align-items: center; -} - -.settings-page .header-actions { - padding: 4px; -} - -.settings-page .header-info { - display: flex; - justify-content: flex-end; -} - -.icons-only .ant-tabs-nav { - margin-bottom: 8px; -} - -.icons-only .ant-tabs-nav-wrap { - width: 100%; -} - -.icons-only .ant-tabs-nav-list { - display: flex; - width: 100%; -} - -.icons-only .ant-tabs-tab { - flex: 1 1 0; - justify-content: center; - margin: 0; - padding: 10px 0; -} - -.icons-only .ant-tabs-tab .anticon { - margin: 0; - font-size: 18px; -} - -.icons-only .ant-tabs-nav-operations { - display: none; -} - .ldap-no-inbounds { margin-top: 6px; - color: #999; + color: var(--ant-color-text-tertiary); font-size: 12px; } diff --git a/frontend/src/pages/settings/SettingsPage.tsx b/frontend/src/pages/settings/SettingsPage.tsx index 8442f27b..af77f6b1 100644 --- a/frontend/src/pages/settings/SettingsPage.tsx +++ b/frontend/src/pages/settings/SettingsPage.tsx @@ -35,7 +35,6 @@ import SecurityTab from './SecurityTab'; import TelegramTab from './TelegramTab'; import SubscriptionGeneralTab from './SubscriptionGeneralTab'; import SubscriptionFormatsTab from './SubscriptionFormatsTab'; -import '@/styles/page-cards.css'; import './SettingsPage.css'; interface ApiMsg { diff --git a/frontend/src/pages/settings/SubscriptionFormatsTab.css b/frontend/src/pages/settings/SubscriptionFormatsTab.css index 318f341f..730aeb68 100644 --- a/frontend/src/pages/settings/SubscriptionFormatsTab.css +++ b/frontend/src/pages/settings/SubscriptionFormatsTab.css @@ -1,4 +1,3 @@ .nested-block { padding: 10px 20px; - display: block !important; } diff --git a/frontend/src/pages/settings/TwoFactorModal.css b/frontend/src/pages/settings/TwoFactorModal.css index e72f272f..23a401d9 100644 --- a/frontend/src/pages/settings/TwoFactorModal.css +++ b/frontend/src/pages/settings/TwoFactorModal.css @@ -7,9 +7,6 @@ .qr-code { cursor: pointer; - padding: 0 !important; - background: #fff; - border-radius: 6px; } .qr-token { diff --git a/frontend/src/pages/sub/SubPage.css b/frontend/src/pages/sub/SubPage.css index 62292db4..fabeaac2 100644 --- a/frontend/src/pages/sub/SubPage.css +++ b/frontend/src/pages/sub/SubPage.css @@ -53,49 +53,12 @@ .qr-code { cursor: pointer; - padding: 0 !important; - background: #fff; - border-radius: 4px; } .info-table { margin-top: 12px; } -.info-table .ant-descriptions-view, -.info-table .ant-descriptions-view table, -.info-table .ant-descriptions-view th, -.info-table .ant-descriptions-view td { - border-color: rgba(0, 0, 0, 0.18) !important; -} - -.info-table tbody > tr > th, -.info-table tbody > tr > td { - border-bottom: 1px solid rgba(0, 0, 0, 0.18) !important; -} - -.info-table tbody > tr:last-child > th, -.info-table tbody > tr:last-child > td { - border-bottom: none !important; -} - -.is-dark .info-table .ant-descriptions-view, -.is-dark .info-table .ant-descriptions-view table, -.is-dark .info-table .ant-descriptions-view th, -.is-dark .info-table .ant-descriptions-view td { - border-color: rgba(255, 255, 255, 0.18) !important; -} - -.is-dark .info-table tbody > tr > th, -.is-dark .info-table tbody > tr > td { - border-bottom: 1px solid rgba(255, 255, 255, 0.18) !important; -} - -.is-dark .info-table tbody > tr:last-child > th, -.is-dark .info-table tbody > tr:last-child > td { - border-bottom: none !important; -} - .links-section { margin-top: 16px; } @@ -158,49 +121,15 @@ text-align: center; } -.settings-popover { - min-width: 220px; -} - -.theme-cycle { - width: 32px; - height: 32px; +.toolbar-btn { + width: 40px; + height: 40px; + min-width: 40px; border-radius: 50%; - border: 1px solid rgba(0, 0, 0, 0.08); - background: var(--bg-card); - color: rgba(0, 0, 0, 0.65); - display: inline-flex; - align-items: center; - justify-content: center; - cursor: pointer; padding: 0; - transition: background-color 0.2s, transform 0.15s, color 0.2s; } -.theme-cycle:hover, -.theme-cycle:focus-visible { - background-color: rgba(64, 150, 255, 0.1); - color: #4096ff; - transform: scale(1.05); - outline: none; +.toolbar-btn .anticon { + font-size: 18px; } -.theme-cycle svg { - width: 16px; - height: 16px; -} - -.is-dark .theme-cycle { - border-color: rgba(255, 255, 255, 0.08); - color: rgba(255, 255, 255, 0.85); -} - -.is-dark .theme-cycle:hover, -.is-dark .theme-cycle:focus-visible { - background-color: rgba(64, 150, 255, 0.1); - color: #4096ff; -} - -.lang-select { - width: 100%; -} diff --git a/frontend/src/pages/sub/SubPage.tsx b/frontend/src/pages/sub/SubPage.tsx index b0eeabc6..ebea13fd 100644 --- a/frontend/src/pages/sub/SubPage.tsx +++ b/frontend/src/pages/sub/SubPage.tsx @@ -8,11 +8,11 @@ import { Descriptions, Dropdown, Layout, + Menu, message, Popover, QRCode, Row, - Select, Space, Tag, } from 'antd'; @@ -21,7 +21,10 @@ import { AppleOutlined, CopyOutlined, DownOutlined, - SettingOutlined, + MoonFilled, + MoonOutlined, + SunOutlined, + TranslationOutlined, } from '@ant-design/icons'; import { ClipboardManager, IntlUtil, LanguageManager } from '@/utils'; @@ -206,34 +209,20 @@ export default function SubPage() { { key: 'ios-happ', label: 'Happ', onClick: () => open(happUrl) }, ], [copy, open, shadowrocketUrl, v2boxUrl, streisandUrl, happUrl]); - const langOptions = useMemo( - () => LanguageManager.supportedLanguages.map((l: { value: string; name: string; icon: string }) => ({ - value: l.value, + const langMenuItems = useMemo( + () => (LanguageManager.supportedLanguages as { value: string; name: string; icon: string }[]).map((l) => ({ + key: l.value, label: ( - <> - {l.icon} -   {l.name} - + + + {l.name} + ), })), [], ); - const themeIcon = !isDark ? ( - - ) : !isUltra ? ( - - ) : ( - - ); + const themeIcon = !isDark ? : !isUltra ? : ; const cardTitle = ( @@ -244,32 +233,38 @@ export default function SubPage() { const cardExtra = ( - + /> -