enhancement/dashboardFeedback #11
@@ -0,0 +1,50 @@
|
|||||||
|
import { faDownload } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { useDownloadLogFiles } from "../../../hooks/useDownloadLogFiles";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
|
||||||
|
const DownloadLogButton = () => {
|
||||||
|
const { downloadLogFilesQuery } = useDownloadLogFiles();
|
||||||
|
const isLoading = downloadLogFilesQuery?.isFetching;
|
||||||
|
|
||||||
|
const handleDownloadClick = async () => {
|
||||||
|
try {
|
||||||
|
const blob = await downloadLogFilesQuery?.refetch().then((res) => res.data);
|
||||||
|
if (!blob) {
|
||||||
|
throw new Error("No log file data received");
|
||||||
|
}
|
||||||
|
const url = window.URL.createObjectURL(new Blob([blob]));
|
||||||
|
const link = document.createElement("a");
|
||||||
|
if (!link) {
|
||||||
|
throw new Error("Failed to create download link");
|
||||||
|
} else {
|
||||||
|
link.href = url;
|
||||||
|
link.setAttribute("download", "FlexiAI-0.log");
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
link.parentNode?.removeChild(link);
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
}
|
||||||
|
} catch (error: unknown) {
|
||||||
|
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
||||||
|
toast.error(errorMessage);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className="p-3 border border-gray-700 rounded-lg hover:bg-[#233241] hover:cursor-pointer"
|
||||||
|
onClick={handleDownloadClick}
|
||||||
|
>
|
||||||
|
<div className="flex flex-row gap-2 items-center">
|
||||||
|
<span className="font-bold text-xl bg-slate-700 p-1 px-2 rounded-md">
|
||||||
|
<FontAwesomeIcon icon={faDownload} />
|
||||||
|
</span>
|
||||||
|
<p className="text-lg">{"Download Log Files"}</p>
|
||||||
|
</div>
|
||||||
|
<p className="text-slate-400 italic text-start">{isLoading ? "Downloading..." : "View logs"}</p>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DownloadLogButton;
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { faChartSimple } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
|
||||||
|
type StatusReadsProps = {
|
||||||
|
reads: {
|
||||||
|
totalPending: number;
|
||||||
|
totalActive: number;
|
||||||
|
totalSent: number;
|
||||||
|
totalReceived: number;
|
||||||
|
totalLost: number;
|
||||||
|
sanityCheck: boolean;
|
||||||
|
sanityCheckFormula: string;
|
||||||
|
};
|
||||||
|
isReadsLoading?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const StatusReads = ({ reads, isReadsLoading }: StatusReadsProps) => {
|
||||||
|
const totalPending = reads?.totalPending ?? 0;
|
||||||
|
const totalActive = reads?.totalActive ?? 0;
|
||||||
|
const totalSent = reads?.totalSent ?? 0;
|
||||||
|
const totalLost = reads?.totalLost ?? 0;
|
||||||
|
const totalReceived = reads?.totalReceived ?? 0;
|
||||||
|
|
||||||
|
if (isReadsLoading) {
|
||||||
|
return <p className="text-slate-400">Loading reads…</p>;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className="p-3 border border-gray-700 rounded-lg hover:bg-[#233241]">
|
||||||
|
<div className="flex flex-row gap-2 items-center">
|
||||||
|
<span className="font-bold text-xl bg-slate-700 p-1 px-2 rounded-md">
|
||||||
|
<FontAwesomeIcon icon={faChartSimple} />
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<p className="text-lg">Reads</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="text-slate-400 mt-1">
|
||||||
|
Pending: <span className="text-yellow-500">{totalPending}</span> | Active:{" "}
|
||||||
|
<span className="text-cyan-500">{totalActive}</span> | Lost: <span className="text-red-500">{totalLost}</span>
|
||||||
|
<br />
|
||||||
|
Sent / Received: <span className="text-blue-500">{totalSent}</span> |{" "}
|
||||||
|
<span className="text-green-500">{totalReceived}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StatusReads;
|
||||||
@@ -1,13 +1,23 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
import { useInfoSocket } from "../../../../app/context/WebSocketContext";
|
import { useInfoSocket } from "../../../../app/context/WebSocketContext";
|
||||||
import Card from "../../../../ui/Card";
|
import Card from "../../../../ui/Card";
|
||||||
import CardHeader from "../../../../ui/CardHeader";
|
import CardHeader from "../../../../ui/CardHeader";
|
||||||
import StatusItemCPU from "./StatusItems/StatusItemCPU";
|
import DownloadLogButton from "./StatusItems/DownloadLogButton";
|
||||||
import StatusItemLocal from "./StatusItems/StatusItemLocal";
|
import StatusItemLocal from "./StatusItems/StatusItemLocal";
|
||||||
import StatusItemThreads from "./StatusItems/StatusItemThreads";
|
|
||||||
import StatusItemUTC from "./StatusItems/StatusItemUTC";
|
import StatusItemUTC from "./StatusItems/StatusItemUTC";
|
||||||
|
import StatusReads from "./StatusItems/StatusReads";
|
||||||
|
import { useGetStore } from "../../hooks/useGetStore";
|
||||||
|
|
||||||
const SystemStatusCard = () => {
|
const SystemStatusCard = () => {
|
||||||
const { data: stats } = useInfoSocket();
|
const { data: stats } = useInfoSocket();
|
||||||
|
const { storeQuery } = useGetStore();
|
||||||
|
|
||||||
|
const reads = storeQuery?.data;
|
||||||
|
const isReadsLoading = storeQuery.isFetching;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
storeQuery.refetch();
|
||||||
|
}, [reads]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="p-4">
|
<Card className="p-4">
|
||||||
@@ -16,8 +26,8 @@ const SystemStatusCard = () => {
|
|||||||
<div className="grid grid-cols-2 grid-rows-2 gap-4 col-span-2">
|
<div className="grid grid-cols-2 grid-rows-2 gap-4 col-span-2">
|
||||||
<StatusItemUTC statusInfoItem={stats["system-clock-utc"]} description={"UTC Time"} />
|
<StatusItemUTC statusInfoItem={stats["system-clock-utc"]} description={"UTC Time"} />
|
||||||
<StatusItemLocal statusInfoItem={stats["system-clock-local"]} description={"Local Time"} />
|
<StatusItemLocal statusInfoItem={stats["system-clock-local"]} description={"Local Time"} />
|
||||||
<StatusItemCPU statusInfoItem={stats["memory-cpu-status"]} description={"CPU"} />
|
<DownloadLogButton />
|
||||||
<StatusItemThreads statusInfoItem={stats["thread-count"]} description={"Threads"} />
|
<StatusReads reads={reads} isReadsLoading={isReadsLoading} />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<span className="text-slate-500">Loading system status…</span>
|
<span className="text-slate-500">Loading system status…</span>
|
||||||
|
|||||||
20
src/features/dashboard/hooks/useDownloadLogFiles.ts
Normal file
20
src/features/dashboard/hooks/useDownloadLogFiles.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { CAMBASE } from "../../../utils/config";
|
||||||
|
|
||||||
|
const getDownloadLogFiles = async () => {
|
||||||
|
const response = await fetch(`${CAMBASE}/LogView/download?filename=FlexiAI-0.log`);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Failed to download log files");
|
||||||
|
}
|
||||||
|
return response.blob();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useDownloadLogFiles = () => {
|
||||||
|
const downloadLogFilesQuery = useQuery({
|
||||||
|
queryKey: ["downloadLogFiles"],
|
||||||
|
queryFn: getDownloadLogFiles,
|
||||||
|
enabled: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
return { downloadLogFilesQuery };
|
||||||
|
};
|
||||||
19
src/features/dashboard/hooks/useGetStore.ts
Normal file
19
src/features/dashboard/hooks/useGetStore.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { CAMBASE } from "../../../utils/config";
|
||||||
|
|
||||||
|
const getStoreData = async () => {
|
||||||
|
const response = await fetch(`${CAMBASE}/Store0/diagnostics-json`);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Network response was not ok");
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useGetStore = () => {
|
||||||
|
const storeQuery = useQuery({
|
||||||
|
queryKey: ["storeData"],
|
||||||
|
queryFn: getStoreData,
|
||||||
|
// refetchInterval: 10 * 60 * 1000,
|
||||||
|
});
|
||||||
|
return { storeQuery };
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user