mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-20 18:11:00 +00:00
chore(frontend): update dependencies and adapt to oxlint 1.79
npm install was failing with ERESOLVE: the lockfile pinned storybook 10.5.7 and vitest 4.1.10 as peers while package.json asked for ^10.5.9 and ^4.1.11, and npm would not move either. Neither npm update, a targeted install, nor --package-lock-only broke the cycle, so node_modules and package-lock.json were regenerated from scratch (601 packages, 0 vulnerabilities). oxlint 1.79.0 then promoted five React Compiler rules into the correctness category, flagging 101 pre-existing sites. 1.78.0 exits 0 on the same tree, so nothing in our code changed - the rule set grew. They are fixed rather than suppressed: - refs (31): latest-value ref writes moved out of render into an effect. onlineClientsRef turned out to be write-only and is gone; expireDiffRef and trafficDiffRef were replaced by reading the values directly. - set-state-in-effect (55): reset-on-open modals now adjust state during render; where an effect mixed a synchronous reset with an async fetch, the reset moved to render and the effect kept only the request. useMediaQuery became useSyncExternalStore. - preserve-manual-memoization (11): optional-chained deps the compiler cannot match, hoisted to locals or dropped where the memo wrapped a string concat. - purity (3): Date.now() in render replaced by a state-backed clock, which also refreshes the expiry tag every 60s instead of freezing it until the next unrelated re-render. - immutability (1): applyClientStatsEvent merged websocket traffic into DBInbound rows in place; it now rebuilds only the rows it touches. Two things fell out of that. clientCount is derived with useMemo instead of an imperative rebuildClientCount() called from five sites, which also fixes a staleness bug where changing the expiry or traffic threshold left the counts alone until some later rebuild. statsVersion existed only to force a re-render after an in-place mutation, is meaningless now that rows are replaced, and nothing read it, so it is removed. Also adds a lint:fix script - oxlint --fix was previously only reachable through the lint-staged hook.
This commit is contained in:
@@ -200,16 +200,74 @@ function formatFullTimestamp(unixSec: number): string {
|
||||
return `${MM}-${DD} ${time}`;
|
||||
}
|
||||
|
||||
interface HistoryChart {
|
||||
points: number[];
|
||||
points2: number[];
|
||||
points3: number[];
|
||||
labels: string[];
|
||||
timestamps: number[];
|
||||
}
|
||||
|
||||
const EMPTY_CHART: HistoryChart = {
|
||||
points: [],
|
||||
points2: [],
|
||||
points3: [],
|
||||
labels: [],
|
||||
timestamps: [],
|
||||
};
|
||||
|
||||
async function loadBucket(metric: (typeof METRICS)[number], bucket: number): Promise<HistoryChart> {
|
||||
try {
|
||||
const msg = await HttpUtil.get(`/panel/api/server/history/${metric.key}/${bucket}`);
|
||||
if (!msg?.success || !Array.isArray(msg.obj)) return EMPTY_CHART;
|
||||
const points: number[] = [];
|
||||
const labels: string[] = [];
|
||||
const timestamps: number[] = [];
|
||||
for (const p of msg.obj) {
|
||||
const d = new Date(p.t * 1000);
|
||||
const MM = String(d.getMonth() + 1).padStart(2, '0');
|
||||
const DD = String(d.getDate()).padStart(2, '0');
|
||||
const hh = String(d.getHours()).padStart(2, '0');
|
||||
const mm = String(d.getMinutes()).padStart(2, '0');
|
||||
const ss = String(d.getSeconds()).padStart(2, '0');
|
||||
labels.push(
|
||||
bucket >= 2880
|
||||
? `${MM}-${DD} ${hh}:${mm}`
|
||||
: bucket >= 60
|
||||
? `${hh}:${mm}`
|
||||
: `${hh}:${mm}:${ss}`,
|
||||
);
|
||||
points.push(Number(p.v) || 0);
|
||||
timestamps.push(Number(p.t) || 0);
|
||||
}
|
||||
const fetchAligned = async (key?: string): Promise<number[]> => {
|
||||
if (!key) return [];
|
||||
const m = await HttpUtil.get(`/panel/api/server/history/${key}/${bucket}`);
|
||||
if (!m?.success || !Array.isArray(m.obj)) return [];
|
||||
const byTs = new Map<number, number>();
|
||||
for (const p of m.obj) byTs.set(Number(p.t) || 0, Number(p.v) || 0);
|
||||
return timestamps.map((ts) => byTs.get(ts) ?? 0);
|
||||
};
|
||||
return {
|
||||
labels,
|
||||
points,
|
||||
timestamps,
|
||||
points2: await fetchAligned(metric.key2),
|
||||
points3: await fetchAligned(metric.key3),
|
||||
};
|
||||
} catch (e) {
|
||||
console.error('Failed to fetch history bucket', e);
|
||||
return EMPTY_CHART;
|
||||
}
|
||||
}
|
||||
|
||||
export default function SystemHistoryModal({ open, status, onClose }: SystemHistoryModalProps) {
|
||||
const { t } = useTranslation();
|
||||
const { isMobile } = useMediaQuery();
|
||||
const [activeKey, setActiveKey] = useState('cpu');
|
||||
const [bucket, setBucket] = useState(2);
|
||||
const [points, setPoints] = useState<number[]>([]);
|
||||
const [points2, setPoints2] = useState<number[]>([]);
|
||||
const [points3, setPoints3] = useState<number[]>([]);
|
||||
const [labels, setLabels] = useState<string[]>([]);
|
||||
const [timestamps, setTimestamps] = useState<number[]>([]);
|
||||
const [{ points, points2, points3, labels, timestamps }, setChart] =
|
||||
useState<HistoryChart>(EMPTY_CHART);
|
||||
|
||||
const activeMetric = useMemo(() => METRICS.find((m) => m.key === activeKey), [activeKey]);
|
||||
const trName = (n?: string) => (n && n.startsWith('pages.') ? t(n) : n);
|
||||
@@ -237,70 +295,27 @@ export default function SystemHistoryModal({ open, status, onClose }: SystemHist
|
||||
|
||||
const fetchBucket = useCallback(async () => {
|
||||
if (!activeMetric) return;
|
||||
try {
|
||||
const url = `/panel/api/server/history/${activeMetric.key}/${bucket}`;
|
||||
const msg = await HttpUtil.get(url);
|
||||
if (msg?.success && Array.isArray(msg.obj)) {
|
||||
const vals: number[] = [];
|
||||
const labs: string[] = [];
|
||||
const tss: number[] = [];
|
||||
for (const p of msg.obj) {
|
||||
const d = new Date(p.t * 1000);
|
||||
const MM = String(d.getMonth() + 1).padStart(2, '0');
|
||||
const DD = String(d.getDate()).padStart(2, '0');
|
||||
const hh = String(d.getHours()).padStart(2, '0');
|
||||
const mm = String(d.getMinutes()).padStart(2, '0');
|
||||
const ss = String(d.getSeconds()).padStart(2, '0');
|
||||
const lab =
|
||||
bucket >= 2880
|
||||
? `${MM}-${DD} ${hh}:${mm}`
|
||||
: bucket >= 60
|
||||
? `${hh}:${mm}`
|
||||
: `${hh}:${mm}:${ss}`;
|
||||
labs.push(lab);
|
||||
vals.push(Number(p.v) || 0);
|
||||
tss.push(Number(p.t) || 0);
|
||||
}
|
||||
setLabels(labs);
|
||||
setPoints(vals);
|
||||
setTimestamps(tss);
|
||||
|
||||
const fetchAligned = async (key?: string): Promise<number[]> => {
|
||||
if (!key) return [];
|
||||
const m = await HttpUtil.get(`/panel/api/server/history/${key}/${bucket}`);
|
||||
if (m?.success && Array.isArray(m.obj)) {
|
||||
const byTs = new Map<number, number>();
|
||||
for (const p of m.obj) byTs.set(Number(p.t) || 0, Number(p.v) || 0);
|
||||
return tss.map((ts) => byTs.get(ts) ?? 0);
|
||||
}
|
||||
return [];
|
||||
};
|
||||
setPoints2(await fetchAligned(activeMetric.key2));
|
||||
setPoints3(await fetchAligned(activeMetric.key3));
|
||||
} else {
|
||||
setLabels([]);
|
||||
setPoints([]);
|
||||
setPoints2([]);
|
||||
setPoints3([]);
|
||||
setTimestamps([]);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to fetch history bucket', e);
|
||||
setLabels([]);
|
||||
setPoints([]);
|
||||
setPoints2([]);
|
||||
setPoints3([]);
|
||||
setTimestamps([]);
|
||||
}
|
||||
const next = await loadBucket(activeMetric, bucket);
|
||||
setChart(next);
|
||||
}, [activeMetric, bucket]);
|
||||
|
||||
useEffect(() => {
|
||||
const [wasOpen, setWasOpen] = useState(false);
|
||||
if (open !== wasOpen) {
|
||||
setWasOpen(open);
|
||||
if (open) setActiveKey('cpu');
|
||||
}, [open]);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (open) fetchBucket();
|
||||
}, [open, activeKey, bucket, fetchBucket]);
|
||||
if (!open || !activeMetric) return;
|
||||
let cancelled = false;
|
||||
void (async () => {
|
||||
const next = await loadBucket(activeMetric, bucket);
|
||||
if (!cancelled) setChart(next);
|
||||
})();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [open, activeMetric, bucket]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return undefined;
|
||||
|
||||
Reference in New Issue
Block a user