feat: big major update

This commit is contained in:
2026-02-02 04:00:37 +03:00
parent bbab6fc46a
commit d557664b25
34 changed files with 1801 additions and 665 deletions

View File

@@ -0,0 +1,132 @@
import { API_URL, authInstance, Modal } from "@shared";
import { CircularProgress } from "@mui/material";
import { useEffect, useState } from "react";
import { toast } from "react-toastify";
interface DeviceLogChunk {
date?: string;
lines?: string[];
}
interface DeviceLogsModalProps {
open: boolean;
deviceUuid: string | null;
onClose: () => void;
}
const formatDate = (date: Date) => {
return new Intl.DateTimeFormat("ru-RU", {
day: "2-digit",
month: "2-digit",
year: "numeric",
}).format(date);
};
const formatLogTimestamp = (timestampStr: string) => {
return timestampStr.replace(/^\[|\]$/g, "").trim();
};
const parseLogLine = (line: string, index: number) => {
const match = line.match(/^(\[[^\]]+\])\s*(.*)$/);
if (match) {
return { id: index, time: match[1], text: match[2].trim() || line };
}
return { id: index, time: "", text: line };
};
const toYYYYMMDD = (d: Date) => d.toISOString().slice(0, 10);
export const DeviceLogsModal = ({
open,
deviceUuid,
onClose,
}: DeviceLogsModalProps) => {
const [chunks, setChunks] = useState<DeviceLogChunk[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const today = new Date();
const yesterday = new Date(today.getTime() - 24 * 60 * 60 * 1000);
const dateStr = toYYYYMMDD(today);
const dateStrYesterday = toYYYYMMDD(yesterday);
useEffect(() => {
if (!open || !deviceUuid) return;
const fetchLogs = async () => {
setIsLoading(true);
setError(null);
try {
const { data } = await authInstance.get<DeviceLogChunk[]>(
`${API_URL}/devices/${deviceUuid}/logs`,
{ params: { from: dateStrYesterday, to: dateStr } }
);
setChunks(Array.isArray(data) ? data : []);
} catch (err: unknown) {
const msg =
err && typeof err === "object" && "message" in err
? String((err as { message?: string }).message)
: "Ошибка загрузки логов";
setError(msg);
toast.error(msg);
} finally {
setIsLoading(false);
}
};
fetchLogs();
}, [open, deviceUuid, dateStr]);
const logs = chunks.flatMap((chunk, chunkIdx) =>
(chunk.lines ?? []).map((line, i) =>
parseLogLine(line, chunkIdx * 10000 + i)
)
);
return (
<Modal open={open} onClose={onClose} sx={{ width: "80vw", p: 1.5 }}>
<div className="flex flex-col gap-6 h-[85vh]">
<div className="flex gap-3 items-center justify-between w-full">
<h2 className="text-2xl font-semibold text-[#000000BF]">Логи</h2>
<span className="text-lg text-[#00000040]">{formatDate(today)}</span>
</div>
<div className="flex-1 flex flex-col min-h-0 w-full">
{isLoading && (
<div className="w-full h-full flex items-center justify-center bg-white rounded-xl shadow-inner">
<CircularProgress />
</div>
)}
{!isLoading && error && (
<div className="w-full h-full flex items-center justify-center text-red-500">
{error}
</div>
)}
{!isLoading && !error && (
<div className="w-full bg-white p-4 h-full overflow-y-auto rounded-xl shadow-inner">
<div className="flex flex-col gap-2 font-mono">
{logs.length > 0 ? (
logs.map((log) => (
<div key={log.id} className="flex gap-3 items-start p-2">
<div className="text-sm text-[#00000050] shrink-0 whitespace-nowrap pt-px">
{log.time ? `[${formatLogTimestamp(log.time)}]` : null}
</div>
<div className="text-sm text-[#000000BF] break-words w-full">
{log.text}
</div>
</div>
))
) : (
<div className="h-full flex items-center justify-center text-gray-500">
Логи отсутствуют.
</div>
)}
</div>
</div>
)}
</div>
</div>
</Modal>
);
};