mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-05 20:54:14 +00:00
refactor(frontend): tighten HttpUtil generics from any to unknown
Switch the class-level default on Msg<T> and the per-method defaults on HttpUtil.get/post/postWithModal from `any` to `unknown`, so callers that don't pass an explicit T get a narrowed response that must be schema- checked or type-cast before its shape is trusted. Drops the four file-level eslint-disable comments these defaults required. Fixes the nine direct `.obj.field` consumers that surfaced (IndexPage, XrayMetricsModal, NordModal, WarpModal, LogModal, VersionModal, XrayLogModal, CustomGeoSection) by giving each call site the explicit T it should have had from the start — typically a small ad-hoc shape, sometimes a string for the JSON-text-in-Msg.obj pattern used by NordModal/WarpModal/Xray nord/warp endpoints. PR3 of the planned Zod end-to-end rollout — schemas/inbound.ts and schemas/client.ts loose() removal stays parked until the protocol schemas land in Phase 3 to avoid silently dropping fields.
This commit is contained in:
parent
7bd281d26d
commit
31845fa8f6
9 changed files with 36 additions and 39 deletions
|
|
@ -116,7 +116,7 @@ export default function CustomGeoSection({ active }: CustomGeoSectionProps) {
|
||||||
async function updateAll() {
|
async function updateAll() {
|
||||||
setUpdatingAll(true);
|
setUpdatingAll(true);
|
||||||
try {
|
try {
|
||||||
const msg = await HttpUtil.post('/panel/api/custom-geo/update-all');
|
const msg = await HttpUtil.post<{ succeeded?: unknown[]; failed?: unknown[] }>('/panel/api/custom-geo/update-all');
|
||||||
const ok = msg?.obj?.succeeded?.length || 0;
|
const ok = msg?.obj?.succeeded?.length || 0;
|
||||||
const failed = msg?.obj?.failed?.length || 0;
|
const failed = msg?.obj?.failed?.length || 0;
|
||||||
if (msg?.success || ok > 0) {
|
if (msg?.success || ok > 0) {
|
||||||
|
|
|
||||||
|
|
@ -86,10 +86,10 @@ export default function IndexPage() {
|
||||||
const [loadingTip, setLoadingTip] = useState(t('loading'));
|
const [loadingTip, setLoadingTip] = useState(t('loading'));
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
HttpUtil.post('/panel/setting/defaultSettings').then((msg) => {
|
HttpUtil.post<{ ipLimitEnable?: boolean }>('/panel/setting/defaultSettings').then((msg) => {
|
||||||
if (msg?.success && msg.obj) setIpLimitEnable(!!msg.obj.ipLimitEnable);
|
if (msg?.success && msg.obj) setIpLimitEnable(!!msg.obj.ipLimitEnable);
|
||||||
});
|
});
|
||||||
HttpUtil.get('/panel/api/server/getPanelUpdateInfo').then((msg) => {
|
HttpUtil.get<PanelUpdateInfo>('/panel/api/server/getPanelUpdateInfo').then((msg) => {
|
||||||
if (msg?.success && msg.obj) setPanelUpdateInfo(msg.obj);
|
if (msg?.success && msg.obj) setPanelUpdateInfo(msg.obj);
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ export default function LogModal({ open, onClose }: LogModalProps) {
|
||||||
const refresh = useCallback(async () => {
|
const refresh = useCallback(async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const msg = await HttpUtil.post(`/panel/api/server/logs/${rows}`, {
|
const msg = await HttpUtil.post<string[]>(`/panel/api/server/logs/${rows}`, {
|
||||||
level,
|
level,
|
||||||
syslog,
|
syslog,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ export default function VersionModal({ open, status, onClose, onBusy }: VersionM
|
||||||
const fetchVersions = useCallback(async () => {
|
const fetchVersions = useCallback(async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const msg = await HttpUtil.get('/panel/api/server/getXrayVersion');
|
const msg = await HttpUtil.get<string[]>('/panel/api/server/getXrayVersion');
|
||||||
if (msg?.success) setVersions(msg.obj || []);
|
if (msg?.success) setVersions(msg.obj || []);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ export default function XrayLogModal({ open, onClose }: XrayLogModalProps) {
|
||||||
const refresh = useCallback(async () => {
|
const refresh = useCallback(async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const msg = await HttpUtil.post(`/panel/api/server/xraylogs/${rows}`, {
|
const msg = await HttpUtil.post<XrayLogEntry[]>(`/panel/api/server/xraylogs/${rows}`, {
|
||||||
filter,
|
filter,
|
||||||
showDirect,
|
showDirect,
|
||||||
showBlocked,
|
showBlocked,
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Alert, Modal, Select, Tabs, Tag } from 'antd';
|
import { Alert, Modal, Select, Tabs, Tag } from 'antd';
|
||||||
|
|
||||||
import { HttpUtil, SizeFormatter } from '@/utils';
|
import { HttpUtil, Msg, SizeFormatter } from '@/utils';
|
||||||
import Sparkline from '@/components/Sparkline';
|
import Sparkline from '@/components/Sparkline';
|
||||||
import { useMediaQuery } from '@/hooks/useMediaQuery';
|
import { useMediaQuery } from '@/hooks/useMediaQuery';
|
||||||
import './XrayMetricsModal.css';
|
import './XrayMetricsModal.css';
|
||||||
|
|
@ -90,7 +90,7 @@ export default function XrayMetricsModal({ open, onClose }: XrayMetricsModalProp
|
||||||
|
|
||||||
const activeObsTag = obsTags.find((tg) => tg.tag === obsActiveTag) || null;
|
const activeObsTag = obsTags.find((tg) => tg.tag === obsActiveTag) || null;
|
||||||
|
|
||||||
const applyHistory = useCallback((msg: { success?: boolean; obj?: { t: number; v: number }[] }, currentBucket: number) => {
|
const applyHistory = useCallback((msg: Msg<{ t: number; v: number }[]> | null | undefined, currentBucket: number) => {
|
||||||
if (msg?.success && Array.isArray(msg.obj)) {
|
if (msg?.success && Array.isArray(msg.obj)) {
|
||||||
const vals: number[] = [];
|
const vals: number[] = [];
|
||||||
const labs: string[] = [];
|
const labs: string[] = [];
|
||||||
|
|
@ -112,7 +112,7 @@ export default function XrayMetricsModal({ open, onClose }: XrayMetricsModalProp
|
||||||
|
|
||||||
const fetchState = useCallback(async () => {
|
const fetchState = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
const msg = await HttpUtil.get('/panel/api/server/xrayMetricsState');
|
const msg = await HttpUtil.get<XrayState>('/panel/api/server/xrayMetricsState');
|
||||||
if (msg?.success && msg.obj) setState(msg.obj);
|
if (msg?.success && msg.obj) setState(msg.obj);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to fetch xray metrics state', e);
|
console.error('Failed to fetch xray metrics state', e);
|
||||||
|
|
@ -121,12 +121,13 @@ export default function XrayMetricsModal({ open, onClose }: XrayMetricsModalProp
|
||||||
|
|
||||||
const fetchObservatory = useCallback(async () => {
|
const fetchObservatory = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
const msg = await HttpUtil.get('/panel/api/server/xrayObservatory');
|
const msg = await HttpUtil.get<ObservatoryTag[]>('/panel/api/server/xrayObservatory');
|
||||||
if (msg?.success && Array.isArray(msg.obj)) {
|
if (msg?.success && Array.isArray(msg.obj)) {
|
||||||
setObsTags(msg.obj);
|
const tags = msg.obj;
|
||||||
|
setObsTags(tags);
|
||||||
setObsActiveTag((prev) => {
|
setObsActiveTag((prev) => {
|
||||||
if (msg.obj.find((tg: ObservatoryTag) => tg.tag === prev)) return prev;
|
if (tags.find((tg) => tg.tag === prev)) return prev;
|
||||||
return msg.obj[0]?.tag || '';
|
return tags[0]?.tag || '';
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setObsTags([]);
|
setObsTags([]);
|
||||||
|
|
@ -141,7 +142,7 @@ export default function XrayMetricsModal({ open, onClose }: XrayMetricsModalProp
|
||||||
if (!activeMetric) return;
|
if (!activeMetric) return;
|
||||||
try {
|
try {
|
||||||
const url = `/panel/api/server/xrayMetricsHistory/${activeMetric.key}/${bucket}`;
|
const url = `/panel/api/server/xrayMetricsHistory/${activeMetric.key}/${bucket}`;
|
||||||
const msg = await HttpUtil.get(url);
|
const msg = await HttpUtil.get<{ t: number; v: number }[]>(url);
|
||||||
applyHistory(msg, bucket);
|
applyHistory(msg, bucket);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to fetch xray metrics bucket', e);
|
console.error('Failed to fetch xray metrics bucket', e);
|
||||||
|
|
@ -158,7 +159,7 @@ export default function XrayMetricsModal({ open, onClose }: XrayMetricsModalProp
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const url = `/panel/api/server/xrayObservatoryHistory/${encodeURIComponent(obsActiveTag)}/${bucket}`;
|
const url = `/panel/api/server/xrayObservatoryHistory/${encodeURIComponent(obsActiveTag)}/${bucket}`;
|
||||||
const msg = await HttpUtil.get(url);
|
const msg = await HttpUtil.get<{ t: number; v: number }[]>(url);
|
||||||
applyHistory(msg, bucket);
|
applyHistory(msg, bucket);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to fetch observatory bucket', e);
|
console.error('Failed to fetch observatory bucket', e);
|
||||||
|
|
|
||||||
|
|
@ -86,14 +86,14 @@ export default function NordModal({
|
||||||
}, [filteredServers]);
|
}, [filteredServers]);
|
||||||
|
|
||||||
const fetchCountries = useCallback(async () => {
|
const fetchCountries = useCallback(async () => {
|
||||||
const msg = await HttpUtil.post('/panel/xray/nord/countries');
|
const msg = await HttpUtil.post<string>('/panel/xray/nord/countries');
|
||||||
if (msg?.success) setCountries(JSON.parse(msg.obj));
|
if (msg?.success && msg.obj) setCountries(JSON.parse(msg.obj));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const fetchData = useCallback(async () => {
|
const fetchData = useCallback(async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const msg = await HttpUtil.post('/panel/xray/nord/data');
|
const msg = await HttpUtil.post<string>('/panel/xray/nord/data');
|
||||||
if (msg?.success) {
|
if (msg?.success) {
|
||||||
const next = msg.obj ? JSON.parse(msg.obj) : null;
|
const next = msg.obj ? JSON.parse(msg.obj) : null;
|
||||||
setNordData(next);
|
setNordData(next);
|
||||||
|
|
@ -111,8 +111,8 @@ export default function NordModal({
|
||||||
async function login() {
|
async function login() {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const msg = await HttpUtil.post('/panel/xray/nord/reg', { token });
|
const msg = await HttpUtil.post<string>('/panel/xray/nord/reg', { token });
|
||||||
if (msg?.success) {
|
if (msg?.success && msg.obj) {
|
||||||
setNordData(JSON.parse(msg.obj));
|
setNordData(JSON.parse(msg.obj));
|
||||||
await fetchCountries();
|
await fetchCountries();
|
||||||
}
|
}
|
||||||
|
|
@ -124,8 +124,8 @@ export default function NordModal({
|
||||||
async function saveKey() {
|
async function saveKey() {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const msg = await HttpUtil.post('/panel/xray/nord/setKey', { key: manualKey });
|
const msg = await HttpUtil.post<string>('/panel/xray/nord/setKey', { key: manualKey });
|
||||||
if (msg?.success) {
|
if (msg?.success && msg.obj) {
|
||||||
setNordData(JSON.parse(msg.obj));
|
setNordData(JSON.parse(msg.obj));
|
||||||
await fetchCountries();
|
await fetchCountries();
|
||||||
}
|
}
|
||||||
|
|
@ -164,8 +164,8 @@ export default function NordModal({
|
||||||
setServerId(null);
|
setServerId(null);
|
||||||
setCityId(null);
|
setCityId(null);
|
||||||
try {
|
try {
|
||||||
const msg = await HttpUtil.post('/panel/xray/nord/servers', { countryId: newCountryId });
|
const msg = await HttpUtil.post<string>('/panel/xray/nord/servers', { countryId: newCountryId });
|
||||||
if (!msg?.success) return;
|
if (!msg?.success || !msg.obj) return;
|
||||||
const data = JSON.parse(msg.obj);
|
const data = JSON.parse(msg.obj);
|
||||||
const locations = data.locations || [];
|
const locations = data.locations || [];
|
||||||
const locToCity: Record<number, City> = {};
|
const locToCity: Record<number, City> = {};
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,7 @@ export default function WarpModal({
|
||||||
const fetchData = useCallback(async () => {
|
const fetchData = useCallback(async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const msg = await HttpUtil.post('/panel/xray/warp/data');
|
const msg = await HttpUtil.post<string>('/panel/xray/warp/data');
|
||||||
if (msg?.success) {
|
if (msg?.success) {
|
||||||
const raw = msg.obj;
|
const raw = msg.obj;
|
||||||
setWarpData(raw && raw.length > 0 ? JSON.parse(raw) : null);
|
setWarpData(raw && raw.length > 0 ? JSON.parse(raw) : null);
|
||||||
|
|
@ -130,8 +130,8 @@ export default function WarpModal({
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const keys = Wireguard.generateKeypair();
|
const keys = Wireguard.generateKeypair();
|
||||||
const msg = await HttpUtil.post('/panel/xray/warp/reg', keys);
|
const msg = await HttpUtil.post<string>('/panel/xray/warp/reg', keys);
|
||||||
if (msg?.success) {
|
if (msg?.success && msg.obj) {
|
||||||
const resp = JSON.parse(msg.obj);
|
const resp = JSON.parse(msg.obj);
|
||||||
setWarpData(resp.data);
|
setWarpData(resp.data);
|
||||||
setWarpConfig(resp.config);
|
setWarpConfig(resp.config);
|
||||||
|
|
@ -145,8 +145,8 @@ export default function WarpModal({
|
||||||
async function getConfig() {
|
async function getConfig() {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const msg = await HttpUtil.post('/panel/xray/warp/config');
|
const msg = await HttpUtil.post<string>('/panel/xray/warp/config');
|
||||||
if (msg?.success) {
|
if (msg?.success && msg.obj) {
|
||||||
const parsed = JSON.parse(msg.obj);
|
const parsed = JSON.parse(msg.obj);
|
||||||
setWarpConfig(parsed);
|
setWarpConfig(parsed);
|
||||||
collectConfig(warpData, parsed);
|
collectConfig(warpData, parsed);
|
||||||
|
|
@ -161,8 +161,8 @@ export default function WarpModal({
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setLicenseError('');
|
setLicenseError('');
|
||||||
try {
|
try {
|
||||||
const msg = await HttpUtil.post('/panel/xray/warp/license', { license: warpPlus });
|
const msg = await HttpUtil.post<string>('/panel/xray/warp/license', { license: warpPlus });
|
||||||
if (msg?.success) {
|
if (msg?.success && msg.obj) {
|
||||||
setWarpData(JSON.parse(msg.obj));
|
setWarpData(JSON.parse(msg.obj));
|
||||||
setWarpConfig(null);
|
setWarpConfig(null);
|
||||||
setWarpPlus('');
|
setWarpPlus('');
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,7 @@ import { getMessage } from './messageBus';
|
||||||
|
|
||||||
type RespEnvelope = { success?: unknown; msg?: unknown; obj?: unknown };
|
type RespEnvelope = { success?: unknown; msg?: unknown; obj?: unknown };
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
export class Msg<T = unknown> {
|
||||||
export class Msg<T = any> {
|
|
||||||
success: boolean;
|
success: boolean;
|
||||||
msg: string;
|
msg: string;
|
||||||
obj: T | null;
|
obj: T | null;
|
||||||
|
|
@ -50,8 +49,7 @@ export class HttpUtil {
|
||||||
return typeof data === 'object' ? (data as Msg) : new Msg(false, 'unknown data:', data);
|
return typeof data === 'object' ? (data as Msg) : new Msg(false, 'unknown data:', data);
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
static async get<T = unknown>(url: string, params?: unknown, options: HttpOptions = {}): Promise<Msg<T>> {
|
||||||
static async get<T = any>(url: string, params?: unknown, options: HttpOptions = {}): Promise<Msg<T>> {
|
|
||||||
const { silent, ...axiosOpts } = options;
|
const { silent, ...axiosOpts } = options;
|
||||||
try {
|
try {
|
||||||
const resp = await axios.get(url, { params, ...axiosOpts });
|
const resp = await axios.get(url, { params, ...axiosOpts });
|
||||||
|
|
@ -67,8 +65,7 @@ export class HttpUtil {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
static async post<T = unknown>(url: string, data?: unknown, options: HttpOptions = {}): Promise<Msg<T>> {
|
||||||
static async post<T = any>(url: string, data?: unknown, options: HttpOptions = {}): Promise<Msg<T>> {
|
|
||||||
const { silent, ...axiosOpts } = options;
|
const { silent, ...axiosOpts } = options;
|
||||||
try {
|
try {
|
||||||
const resp = await axios.post(url, data, axiosOpts);
|
const resp = await axios.post(url, data, axiosOpts);
|
||||||
|
|
@ -84,8 +81,7 @@ export class HttpUtil {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
static async postWithModal<T = unknown>(url: string, data?: unknown, modal?: HttpModal | null): Promise<Msg<T>> {
|
||||||
static async postWithModal<T = any>(url: string, data?: unknown, modal?: HttpModal | null): Promise<Msg<T>> {
|
|
||||||
if (modal) {
|
if (modal) {
|
||||||
modal.loading(true);
|
modal.loading(true);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue