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
This commit is contained in:
MHSanaei 2026-05-25 01:09:16 +02:00
parent 2c8c30681b
commit 91ade9dfec
No known key found for this signature in database
GPG key ID: 7E4060F2FBE5AB7A
4 changed files with 46 additions and 25 deletions

View file

@ -1,8 +1,9 @@
import dayjs from 'dayjs'; /* eslint-disable @typescript-eslint/no-explicit-any */
import dayjs, { type Dayjs } from 'dayjs';
import { ObjectUtil, NumberFormatter, SizeFormatter } from '@/utils'; import { ObjectUtil, NumberFormatter, SizeFormatter } from '@/utils';
import { Inbound, Protocols } from './inbound.js'; import { Inbound, Protocols } from './inbound';
export function coerceInboundJsonField(value) { export function coerceInboundJsonField(value: unknown): any {
if (value == null) return {}; if (value == null) return {};
if (typeof value === 'object') return value; if (typeof value === 'object') return value;
if (typeof value !== 'string') return {}; if (typeof value !== 'string') return {};
@ -10,14 +11,38 @@ export function coerceInboundJsonField(value) {
if (trimmed === '') return {}; if (trimmed === '') return {};
try { try {
return JSON.parse(trimmed); return JSON.parse(trimmed);
} catch (_e) { } catch {
return {}; return {};
} }
} }
export class DBInbound { 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: any;
streamSettings: any;
tag: string;
sniffing: any;
clientStats: any;
nodeId: number | null;
fallbackParent: { masterId: number; path: string } | null;
private _cachedInbound: any = null;
private _clientStatsMap: Map<string, any> | null = null;
constructor(data?: any) {
this.id = 0; this.id = 0;
this.userId = 0; this.userId = 0;
this.up = 0; this.up = 0;
@ -36,12 +61,8 @@ export class DBInbound {
this.streamSettings = ""; this.streamSettings = "";
this.tag = ""; this.tag = "";
this.sniffing = ""; this.sniffing = "";
this.clientStats = "" this.clientStats = "";
// Optional FK to web/runtime registered Node. null/undefined =
// local panel; otherwise the inbound lives on the named node.
this.nodeId = null; 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; this.fallbackParent = null;
if (data == null) { if (data == null) {
return; return;
@ -49,11 +70,11 @@ export class DBInbound {
ObjectUtil.cloneProps(this, data); ObjectUtil.cloneProps(this, data);
} }
get totalGB() { get totalGB(): number {
return NumberFormatter.toFixed(this.total / SizeFormatter.ONE_GB, 2); 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); this.total = NumberFormatter.toFixed(gb * SizeFormatter.ONE_GB, 0);
} }
@ -89,7 +110,7 @@ export class DBInbound {
return this.protocol === Protocols.HYSTERIA; return this.protocol === Protocols.HYSTERIA;
} }
get address() { get address(): string {
let address = location.hostname; let address = location.hostname;
if (!ObjectUtil.isEmpty(this.listen) && this.listen !== "0.0.0.0") { if (!ObjectUtil.isEmpty(this.listen) && this.listen !== "0.0.0.0") {
address = this.listen; address = this.listen;
@ -97,14 +118,14 @@ export class DBInbound {
return address; return address;
} }
get _expiryTime() { get _expiryTime(): Dayjs | null {
if (this.expiryTime === 0) { if (this.expiryTime === 0) {
return null; return null;
} }
return dayjs(this.expiryTime); return dayjs(this.expiryTime);
} }
set _expiryTime(t) { set _expiryTime(t: Dayjs | null | undefined) {
if (t == null) { if (t == null) {
this.expiryTime = 0; this.expiryTime = 0;
} else { } else {
@ -112,16 +133,16 @@ export class DBInbound {
} }
} }
get isExpiry() { get isExpiry(): boolean {
return this.expiryTime < new Date().getTime(); return this.expiryTime < new Date().getTime();
} }
invalidateCache() { invalidateCache(): void {
this._cachedInbound = null; this._cachedInbound = null;
this._clientStatsMap = null; this._clientStatsMap = null;
} }
toInbound() { toInbound(): any {
if (this._cachedInbound) { if (this._cachedInbound) {
return this._cachedInbound; return this._cachedInbound;
} }
@ -145,7 +166,7 @@ export class DBInbound {
return this._cachedInbound; return this._cachedInbound;
} }
getClientStats(email) { getClientStats(email: string): any {
if (!this._clientStatsMap) { if (!this._clientStatsMap) {
this._clientStatsMap = new Map(); this._clientStatsMap = new Map();
if (this.clientStats && Array.isArray(this.clientStats)) { if (this.clientStats && Array.isArray(this.clientStats)) {
@ -157,7 +178,7 @@ export class DBInbound {
return this._clientStatsMap.get(email); return this._clientStatsMap.get(email);
} }
isMultiUser() { isMultiUser(): boolean {
switch (this.protocol) { switch (this.protocol) {
case Protocols.VMESS: case Protocols.VMESS:
case Protocols.VLESS: case Protocols.VLESS:
@ -171,7 +192,7 @@ export class DBInbound {
} }
} }
hasLink() { hasLink(): boolean {
switch (this.protocol) { switch (this.protocol) {
case Protocols.VMESS: case Protocols.VMESS:
case Protocols.VLESS: case Protocols.VLESS:
@ -184,7 +205,7 @@ export class DBInbound {
} }
} }
genInboundLinks(remarkModel, hostOverride = '') { genInboundLinks(remarkModel: string, hostOverride: string = ''): any[] {
const inbound = this.toInbound(); const inbound = this.toInbound();
return inbound.genInboundLinks(this.remark, remarkModel, hostOverride); return inbound.genInboundLinks(this.remark, remarkModel, hostOverride);
} }

View file

@ -56,7 +56,7 @@ import {
TCP_CONGESTION_OPTION, TCP_CONGESTION_OPTION,
MODE_OPTION, MODE_OPTION,
} from '@/models/inbound.js'; } from '@/models/inbound.js';
import { DBInbound } from '@/models/dbinbound.js'; import { DBInbound } from '@/models/dbinbound';
import FinalMaskForm from '@/components/FinalMaskForm'; import FinalMaskForm from '@/components/FinalMaskForm';
import DateTimePicker from '@/components/DateTimePicker'; import DateTimePicker from '@/components/DateTimePicker';
import JsonEditor from '@/components/JsonEditor'; import JsonEditor from '@/components/JsonEditor';

View file

@ -21,7 +21,7 @@ import {
import { HttpUtil, SizeFormatter, RandomUtil } from '@/utils'; import { HttpUtil, SizeFormatter, RandomUtil } from '@/utils';
import { Inbound } from '@/models/inbound.js'; import { Inbound } from '@/models/inbound.js';
import { coerceInboundJsonField } from '@/models/dbinbound.js'; import { coerceInboundJsonField } from '@/models/dbinbound';
import { useTheme } from '@/hooks/useTheme'; import { useTheme } from '@/hooks/useTheme';
import { useMediaQuery } from '@/hooks/useMediaQuery'; import { useMediaQuery } from '@/hooks/useMediaQuery';
import { useWebSocket } from '@/hooks/useWebSocket'; import { useWebSocket } from '@/hooks/useWebSocket';

View file

@ -2,7 +2,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useQuery, useQueryClient } from '@tanstack/react-query'; import { useQuery, useQueryClient } from '@tanstack/react-query';
import { HttpUtil } from '@/utils'; import { HttpUtil } from '@/utils';
import { DBInbound } from '@/models/dbinbound.js'; import { DBInbound } from '@/models/dbinbound';
import { Protocols } from '@/models/inbound.js'; import { Protocols } from '@/models/inbound.js';
import { setDatepicker } from '@/hooks/useDatepicker'; import { setDatepicker } from '@/hooks/useDatepicker';
import { keys } from '@/api/queryKeys'; import { keys } from '@/api/queryKeys';