feat: add txt export
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { API_URL, authInstance, Modal } from "@shared";
|
import { API_URL, authInstance, Modal } from "@shared";
|
||||||
import { CircularProgress, TextField } from "@mui/material";
|
import { Button, CircularProgress, TextField } from "@mui/material";
|
||||||
import { useEffect, useState, useMemo } from "react";
|
import { useEffect, useState, useMemo } from "react";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
|
|
||||||
@@ -82,6 +82,7 @@ const parseJsonLogLine = (line: string) => {
|
|||||||
return { ts, level, msg, extraStr };
|
return { ts, level, msg, extraStr };
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
@@ -180,6 +181,49 @@ export const DeviceLogsModal = ({
|
|||||||
return parsed;
|
return parsed;
|
||||||
}, [chunks]);
|
}, [chunks]);
|
||||||
|
|
||||||
|
const logsText = useMemo(
|
||||||
|
() =>
|
||||||
|
logs
|
||||||
|
.map((log) => {
|
||||||
|
const level = log.level === "unknown" ? "LOG" : log.level.toUpperCase();
|
||||||
|
const time = log.time ? `[${log.time}] ` : "";
|
||||||
|
return `${time}${level}: ${log.text}`;
|
||||||
|
})
|
||||||
|
.join("\n"),
|
||||||
|
[logs]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleDownloadLogs = () => {
|
||||||
|
if (!logsText) {
|
||||||
|
toast.info("Нет логов для сохранения");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const safeDeviceUuid = (deviceUuid ?? "device").replace(
|
||||||
|
/[^a-zA-Z0-9_-]/g,
|
||||||
|
"_"
|
||||||
|
);
|
||||||
|
const fileName = `logs_${safeDeviceUuid}_${dateFrom}_${dateTo}.txt`;
|
||||||
|
const blob = new Blob([`\uFEFF${logsText}`], {
|
||||||
|
type: "text/plain;charset=utf-8",
|
||||||
|
});
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const link = document.createElement("a");
|
||||||
|
|
||||||
|
link.href = url;
|
||||||
|
link.download = fileName;
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
|
||||||
|
toast.success("Логи сохранены в .txt");
|
||||||
|
} catch {
|
||||||
|
toast.error("Не удалось сохранить логи");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal open={open} onClose={onClose} sx={{ width: "80vw", p: 3 }}>
|
<Modal open={open} onClose={onClose} sx={{ width: "80vw", p: 3 }}>
|
||||||
<div className="flex flex-col gap-6 h-[85vh]">
|
<div className="flex flex-col gap-6 h-[85vh]">
|
||||||
@@ -188,7 +232,7 @@ export const DeviceLogsModal = ({
|
|||||||
<div className="flex gap-4 items-center">
|
<div className="flex gap-4 items-center">
|
||||||
<TextField
|
<TextField
|
||||||
type="date"
|
type="date"
|
||||||
label="С"
|
label="От"
|
||||||
size="small"
|
size="small"
|
||||||
value={dateFrom}
|
value={dateFrom}
|
||||||
onChange={(e) => setDateFrom(e.target.value)}
|
onChange={(e) => setDateFrom(e.target.value)}
|
||||||
@@ -196,12 +240,20 @@ export const DeviceLogsModal = ({
|
|||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
type="date"
|
type="date"
|
||||||
label="По"
|
label="До"
|
||||||
size="small"
|
size="small"
|
||||||
value={dateTo}
|
value={dateTo}
|
||||||
onChange={(e) => setDateTo(e.target.value)}
|
onChange={(e) => setDateTo(e.target.value)}
|
||||||
slotProps={{ inputLabel: { shrink: true } }}
|
slotProps={{ inputLabel: { shrink: true } }}
|
||||||
/>
|
/>
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
onClick={handleDownloadLogs}
|
||||||
|
disabled={isLoading || Boolean(error) || logs.length === 0}
|
||||||
|
>
|
||||||
|
Скачать .txt
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user