fix: 安全性与代码质量加固(异常保护、外键级联、竞态修复、优雅关机)
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { useState } from "react";
|
||||
import { subtractHours } from "../utils/time";
|
||||
|
||||
interface TimeRangePickerProps {
|
||||
from: string;
|
||||
@@ -18,12 +19,6 @@ function toLocalDatetimeInput(date: Date): string {
|
||||
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
||||
}
|
||||
|
||||
function subtractHours(date: Date, hours: number): Date {
|
||||
const result = new Date(date);
|
||||
result.setTime(result.getTime() - hours * 60 * 60 * 1000);
|
||||
return result;
|
||||
}
|
||||
|
||||
export function TimeRangePicker({ from, to, onChange }: TimeRangePickerProps) {
|
||||
const [activeShortcut, setActiveShortcut] = useState<string | null>("24h");
|
||||
|
||||
|
||||
@@ -1,21 +1,27 @@
|
||||
import { useCallback, useState } from "react";
|
||||
import { useCallback, useRef, useState } from "react";
|
||||
import type { HistoryResponse } from "../../shared/api";
|
||||
|
||||
export function useHistory(targetId: number | null) {
|
||||
const [data, setData] = useState<HistoryResponse>({ items: [], total: 0, page: 1, pageSize: 15 });
|
||||
const [data, setData] = useState<HistoryResponse>({ items: [], total: 0, page: 1, pageSize: 20 });
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const abortRef = useRef<AbortController | null>(null);
|
||||
|
||||
const fetchHistory = useCallback(
|
||||
async (from: string, to: string, page = 1, pageSize = 15) => {
|
||||
async (from: string, to: string, page = 1, pageSize = 20) => {
|
||||
if (targetId === null) return;
|
||||
|
||||
abortRef.current?.abort();
|
||||
const controller = new AbortController();
|
||||
abortRef.current = controller;
|
||||
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/targets/${targetId}/history?from=${encodeURIComponent(from)}&to=${encodeURIComponent(to)}&page=${page}&pageSize=${pageSize}`,
|
||||
{ signal: controller.signal },
|
||||
);
|
||||
|
||||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||||
@@ -23,6 +29,7 @@ export function useHistory(targetId: number | null) {
|
||||
const result = (await response.json()) as HistoryResponse;
|
||||
setData(result);
|
||||
} catch (err) {
|
||||
if (err instanceof DOMException && err.name === "AbortError") return;
|
||||
setError(err instanceof Error ? err.message : "请求失败");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import type { TargetStatus } from "../../shared/api";
|
||||
import { useTrend } from "./useTrend";
|
||||
import { useHistory } from "./useHistory";
|
||||
import { subtractHours } from "../utils/time";
|
||||
|
||||
export function useTargetDetail() {
|
||||
const [selectedTarget, setSelectedTarget] = useState<TargetStatus | null>(null);
|
||||
@@ -69,9 +70,3 @@ export function useTargetDetail() {
|
||||
handlePageChange,
|
||||
};
|
||||
}
|
||||
|
||||
function subtractHours(date: Date, hours: number): Date {
|
||||
const result = new Date(date);
|
||||
result.setTime(result.getTime() - hours * 60 * 60 * 1000);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1,21 +1,27 @@
|
||||
import { useCallback, useState } from "react";
|
||||
import { useCallback, useRef, useState } from "react";
|
||||
import type { TrendPoint } from "../../shared/api";
|
||||
|
||||
export function useTrend(targetId: number | null) {
|
||||
const [data, setData] = useState<TrendPoint[]>([]);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const abortRef = useRef<AbortController | null>(null);
|
||||
|
||||
const fetchTrend = useCallback(
|
||||
async (from: string, to: string) => {
|
||||
if (targetId === null) return;
|
||||
|
||||
abortRef.current?.abort();
|
||||
const controller = new AbortController();
|
||||
abortRef.current = controller;
|
||||
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/targets/${targetId}/trend?from=${encodeURIComponent(from)}&to=${encodeURIComponent(to)}`,
|
||||
{ signal: controller.signal },
|
||||
);
|
||||
|
||||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||||
@@ -23,6 +29,7 @@ export function useTrend(targetId: number | null) {
|
||||
const result = (await response.json()) as TrendPoint[];
|
||||
setData(result);
|
||||
} catch (err) {
|
||||
if (err instanceof DOMException && err.name === "AbortError") return;
|
||||
setError(err instanceof Error ? err.message : "请求失败");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
|
||||
5
src/web/utils/time.ts
Normal file
5
src/web/utils/time.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export function subtractHours(date: Date, hours: number): Date {
|
||||
const result = new Date(date);
|
||||
result.setTime(result.getTime() - hours * 60 * 60 * 1000);
|
||||
return result;
|
||||
}
|
||||
Reference in New Issue
Block a user