|
@@ -1,4 +1,4 @@
|
|
|
-import { useState, useEffect } from "react";
|
|
|
|
|
|
|
+import { useState } from "react";
|
|
|
import { useNavigate } from "react-router-dom";
|
|
import { useNavigate } from "react-router-dom";
|
|
|
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
|
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
|
|
import { domainApi } from "../api/domains";
|
|
import { domainApi } from "../api/domains";
|
|
@@ -6,84 +6,7 @@ import { monitoringApi } from "../api/monitoring";
|
|
|
import { licenseApi } from "../api/license";
|
|
import { licenseApi } from "../api/license";
|
|
|
import type { MonitoredDomain } from "../types/domain";
|
|
import type { MonitoredDomain } from "../types/domain";
|
|
|
import { T } from "../theme";
|
|
import { T } from "../theme";
|
|
|
-import { Card, StatCard, LoadingDots, Toast } from "../components/Shared";
|
|
|
|
|
-
|
|
|
|
|
-function FetchControls() {
|
|
|
|
|
- const queryClient = useQueryClient();
|
|
|
|
|
- const [autoFetch, setAutoFetch] = useState(false);
|
|
|
|
|
- const [scheduleTime, setScheduleTime] = useState("02:00");
|
|
|
|
|
- const [fetchDate, setFetchDate] = useState("");
|
|
|
|
|
- const [fetchingByDate, setFetchingByDate] = useState(false);
|
|
|
|
|
- const [toast, setToast] = useState<{ message: string; type: "success" | "error" } | null>(null);
|
|
|
|
|
-
|
|
|
|
|
- useEffect(() => {
|
|
|
|
|
- domainApi.getSchedule().then(res => {
|
|
|
|
|
- setAutoFetch(res.data.enabled);
|
|
|
|
|
- setScheduleTime(res.data.schedule_time);
|
|
|
|
|
- }).catch(() => {});
|
|
|
|
|
- }, []);
|
|
|
|
|
-
|
|
|
|
|
- const showToast = (message: string, type: "success" | "error") => {
|
|
|
|
|
- setToast({ message, type });
|
|
|
|
|
- setTimeout(() => setToast(null), 4000);
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- const handleSaveSchedule = () => {
|
|
|
|
|
- domainApi.saveSchedule({ enabled: autoFetch, schedule_time: scheduleTime }).then(() => {
|
|
|
|
|
- showToast("配置已保存", "success");
|
|
|
|
|
- }).catch(() => showToast("保存失败", "error"));
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- const batchMutation = useMutation({
|
|
|
|
|
- mutationFn: () => domainApi.fetchAll(),
|
|
|
|
|
- onSuccess: (res: any) => {
|
|
|
|
|
- queryClient.invalidateQueries({ queryKey: ["dashboard"] });
|
|
|
|
|
- const data = res.data;
|
|
|
|
|
- if (data.errors && data.errors.length > 0) showToast(`部分失败: ${data.errors.length}/${data.total} 个域名出错`, "error");
|
|
|
|
|
- else if (data.total === 0) showToast("没有启用中的域名", "error");
|
|
|
|
|
- else showToast(`全部爬取成功,共 ${data.total} 个域名`, "success");
|
|
|
|
|
- },
|
|
|
|
|
- onError: () => showToast("爬取请求失败,请稍后重试", "error"),
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- const fetchByDateMutation = useMutation({
|
|
|
|
|
- mutationFn: (date: string) => domainApi.fetchAll(date),
|
|
|
|
|
- onSuccess: (res: any) => {
|
|
|
|
|
- queryClient.invalidateQueries({ queryKey: ["dashboard"] });
|
|
|
|
|
- setFetchingByDate(false);
|
|
|
|
|
- const data = res.data;
|
|
|
|
|
- if (data.errors && data.errors.length > 0) showToast(`部分失败: ${data.errors.length}/${data.total} 个域名出错`, "error");
|
|
|
|
|
- else if (data.total === 0) showToast("没有启用中的域名", "error");
|
|
|
|
|
- else showToast(`按日期爬取成功,共 ${data.total} 个域名`, "success");
|
|
|
|
|
- },
|
|
|
|
|
- onError: () => {
|
|
|
|
|
- setFetchingByDate(false);
|
|
|
|
|
- showToast("爬取请求失败,请稍后重试", "error");
|
|
|
|
|
- },
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- return (
|
|
|
|
|
- <>
|
|
|
|
|
- {toast && <Toast message={toast.message} type={toast.type} />}
|
|
|
|
|
- <div style={{ display: "flex", alignItems: "center", gap: 16, flexWrap: "wrap" }}>
|
|
|
|
|
- <Toggle checked={autoFetch} onChange={setAutoFetch} label="每日定时爬取" />
|
|
|
|
|
- {autoFetch && (
|
|
|
|
|
- <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
|
|
|
|
|
- <span style={{ fontSize: 13, color: T.textSec }}>时间</span>
|
|
|
|
|
- <input type="time" value={scheduleTime} onChange={(e) => setScheduleTime(e.target.value)} style={{ padding: "4px 8px", borderRadius: 6, border: `1px solid ${T.border}`, fontSize: 13, outline: "none" }} />
|
|
|
|
|
- </div>
|
|
|
|
|
- )}
|
|
|
|
|
- <button onClick={handleSaveSchedule} style={{ padding: "6px 14px", borderRadius: 6, fontSize: 13, cursor: "pointer", border: `1px solid ${T.primary}`, background: "transparent", color: T.primary, fontWeight: 500, whiteSpace: "nowrap" }}>保存配置</button>
|
|
|
|
|
- <button onClick={() => batchMutation.mutate()} disabled={batchMutation.isPending} style={{ padding: "6px 14px", borderRadius: 6, fontSize: 13, cursor: "pointer", border: "none", background: batchMutation.isPending ? "#f1f5f9" : T.primary, color: batchMutation.isPending ? T.textSec : "#fff", fontWeight: 500, whiteSpace: "nowrap" }}>{batchMutation.isPending ? "爬取中..." : "全部爬取"}</button>
|
|
|
|
|
- <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
|
|
|
|
|
- <span style={{ fontSize: 13, color: T.textSec }}>按日期爬取</span>
|
|
|
|
|
- <input type="date" value={fetchDate} onChange={(e) => setFetchDate(e.target.value)} style={{ padding: "4px 8px", borderRadius: 6, border: `1px solid ${T.border}`, fontSize: 13, outline: "none" }} />
|
|
|
|
|
- <button onClick={() => { if (fetchDate) { setFetchingByDate(true); fetchByDateMutation.mutate(fetchDate); } }} disabled={fetchingByDate || !fetchDate} style={{ padding: "6px 14px", borderRadius: 6, fontSize: 13, cursor: fetchingByDate || !fetchDate ? "default" : "pointer", border: `1px solid ${T.primary}`, background: fetchingByDate || !fetchDate ? "#f1f5f9" : "transparent", color: fetchingByDate || !fetchDate ? T.textSec : T.primary, fontWeight: 500, whiteSpace: "nowrap" }}>{fetchingByDate ? "爬取中..." : "爬取"}</button>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </>
|
|
|
|
|
- );
|
|
|
|
|
-}
|
|
|
|
|
|
|
+import { Card, StatCard } from "../components/Shared";
|
|
|
|
|
|
|
|
export function DashboardPage() {
|
|
export function DashboardPage() {
|
|
|
const navigate = useNavigate();
|
|
const navigate = useNavigate();
|