mirror of
https://github.com/optim-enterprises-bv/OptimCloud-gw-ui.git
synced 2025-11-02 11:17:46 +00:00
Merge pull request #156 from stephb9959/main
[WIFI-12067] Added crash logs to device details page
This commit is contained in:
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "ucentral-client",
|
"name": "ucentral-client",
|
||||||
"version": "2.9.0(1)",
|
"version": "2.9.0(2)",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "ucentral-client",
|
"name": "ucentral-client",
|
||||||
"version": "2.9.0(1)",
|
"version": "2.9.0(2)",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@chakra-ui/icons": "^2.0.11",
|
"@chakra-ui/icons": "^2.0.11",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ucentral-client",
|
"name": "ucentral-client",
|
||||||
"version": "2.9.0(1)",
|
"version": "2.9.0(2)",
|
||||||
"description": "",
|
"description": "",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "index.tsx",
|
"main": "index.tsx",
|
||||||
|
|||||||
@@ -392,6 +392,7 @@
|
|||||||
"warning_pushes_one": "Warten auf Geräteverbindung: {{count}}",
|
"warning_pushes_one": "Warten auf Geräteverbindung: {{count}}",
|
||||||
"warning_pushes_other": "Warten auf Geräteverbindung: {{count}}",
|
"warning_pushes_other": "Warten auf Geräteverbindung: {{count}}",
|
||||||
"weight": "Gewicht",
|
"weight": "Gewicht",
|
||||||
|
"wifi_bands_max": "Es können nicht mehr als 8 SSIDs dieses WLAN-Band verwenden",
|
||||||
"wifi_frames": "WiFi-Frames"
|
"wifi_frames": "WiFi-Frames"
|
||||||
},
|
},
|
||||||
"contacts": {
|
"contacts": {
|
||||||
@@ -601,6 +602,7 @@
|
|||||||
"certificate_expires_in": "Zertifikat läuft ab in",
|
"certificate_expires_in": "Zertifikat läuft ab in",
|
||||||
"certificate_expiry": "Zert. Läuft ab in",
|
"certificate_expiry": "Zert. Läuft ab in",
|
||||||
"connected": "In Verbindung gebracht",
|
"connected": "In Verbindung gebracht",
|
||||||
|
"crash_logs": "Absturzprotokolle",
|
||||||
"create_errors": "Fehler beim Versuch, Geräte zu erstellen",
|
"create_errors": "Fehler beim Versuch, Geräte zu erstellen",
|
||||||
"create_success": " Geräte erfolgreich erstellt",
|
"create_success": " Geräte erfolgreich erstellt",
|
||||||
"current_firmware": "Aktuelle Firmware",
|
"current_firmware": "Aktuelle Firmware",
|
||||||
@@ -614,6 +616,7 @@
|
|||||||
"import_device_warning": "Bitte stellen Sie sicher, dass am Anfang oder Ende von Werten keine zusätzlichen Leerzeichen stehen, es sei denn, es handelt sich um einen Teil des gewünschten Werts",
|
"import_device_warning": "Bitte stellen Sie sicher, dass am Anfang oder Ende von Werten keine zusätzlichen Leerzeichen stehen, es sei denn, es handelt sich um einen Teil des gewünschten Werts",
|
||||||
"import_explanation": "Für den Massenimport von Geräten müssen Sie eine CSV-Datei mit den folgenden Spalten verwenden: SerialNumber, DeviceType, Name, Description, Note",
|
"import_explanation": "Für den Massenimport von Geräten müssen Sie eine CSV-Datei mit den folgenden Spalten verwenden: SerialNumber, DeviceType, Name, Description, Note",
|
||||||
"invalid_serial_number": "Ungültige Seriennummer (muss 12 HEX-Zeichen lang sein)",
|
"invalid_serial_number": "Ungültige Seriennummer (muss 12 HEX-Zeichen lang sein)",
|
||||||
|
"logs_one": "Log",
|
||||||
"new_devices": "Neue Geräte",
|
"new_devices": "Neue Geräte",
|
||||||
"no_model_image": "Kein Modellbild gefunden",
|
"no_model_image": "Kein Modellbild gefunden",
|
||||||
"not_connected": "Nicht verbunden",
|
"not_connected": "Nicht verbunden",
|
||||||
|
|||||||
@@ -392,6 +392,7 @@
|
|||||||
"warning_pushes_one": "Waiting for devices to connect: {{count}}",
|
"warning_pushes_one": "Waiting for devices to connect: {{count}}",
|
||||||
"warning_pushes_other": "Waiting for devices to connect: {{count}}",
|
"warning_pushes_other": "Waiting for devices to connect: {{count}}",
|
||||||
"weight": "Weight",
|
"weight": "Weight",
|
||||||
|
"wifi_bands_max": "There cannot be more than 8 SSIDs using this wifi-band",
|
||||||
"wifi_frames": "WiFi Frames"
|
"wifi_frames": "WiFi Frames"
|
||||||
},
|
},
|
||||||
"contacts": {
|
"contacts": {
|
||||||
@@ -601,6 +602,7 @@
|
|||||||
"certificate_expires_in": "Certificate Expiry",
|
"certificate_expires_in": "Certificate Expiry",
|
||||||
"certificate_expiry": "Cert. Expires In",
|
"certificate_expiry": "Cert. Expires In",
|
||||||
"connected": "Connected",
|
"connected": "Connected",
|
||||||
|
"crash_logs": "Crash Logs",
|
||||||
"create_errors": "errors while trying to create devices",
|
"create_errors": "errors while trying to create devices",
|
||||||
"create_success": " devices successfully created",
|
"create_success": " devices successfully created",
|
||||||
"current_firmware": "Current Firmware",
|
"current_firmware": "Current Firmware",
|
||||||
@@ -614,6 +616,7 @@
|
|||||||
"import_device_warning": "Please make sure there are no extra spaces at the start or end of any values unless it is part of the value desired",
|
"import_device_warning": "Please make sure there are no extra spaces at the start or end of any values unless it is part of the value desired",
|
||||||
"import_explanation": "To bulk import devices, you need to use a CSV file with the following columns: SerialNumber, DeviceType, Name, Description, Note",
|
"import_explanation": "To bulk import devices, you need to use a CSV file with the following columns: SerialNumber, DeviceType, Name, Description, Note",
|
||||||
"invalid_serial_number": "Invalid Serial Number (needs to be 12 HEX chars)",
|
"invalid_serial_number": "Invalid Serial Number (needs to be 12 HEX chars)",
|
||||||
|
"logs_one": "Log",
|
||||||
"new_devices": "new devices",
|
"new_devices": "new devices",
|
||||||
"no_model_image": "No Model Image Found",
|
"no_model_image": "No Model Image Found",
|
||||||
"not_connected": "Not Connected",
|
"not_connected": "Not Connected",
|
||||||
|
|||||||
@@ -392,6 +392,7 @@
|
|||||||
"warning_pushes_one": "Esperando a que los dispositivos se conecten: {{count}}",
|
"warning_pushes_one": "Esperando a que los dispositivos se conecten: {{count}}",
|
||||||
"warning_pushes_other": "Esperando a que los dispositivos se conecten: {{count}}",
|
"warning_pushes_other": "Esperando a que los dispositivos se conecten: {{count}}",
|
||||||
"weight": "Peso",
|
"weight": "Peso",
|
||||||
|
"wifi_bands_max": "No puede haber más de 8 SSID usando esta banda wifi",
|
||||||
"wifi_frames": "Marcos WiFi"
|
"wifi_frames": "Marcos WiFi"
|
||||||
},
|
},
|
||||||
"contacts": {
|
"contacts": {
|
||||||
@@ -601,6 +602,7 @@
|
|||||||
"certificate_expires_in": "El certificado caduca en",
|
"certificate_expires_in": "El certificado caduca en",
|
||||||
"certificate_expiry": "Cert. Expira en",
|
"certificate_expiry": "Cert. Expira en",
|
||||||
"connected": "Conectado",
|
"connected": "Conectado",
|
||||||
|
"crash_logs": "Registros de fallas",
|
||||||
"create_errors": "errores al intentar crear dispositivos",
|
"create_errors": "errores al intentar crear dispositivos",
|
||||||
"create_success": " dispositivos creados con éxito",
|
"create_success": " dispositivos creados con éxito",
|
||||||
"current_firmware": "Firmware actual",
|
"current_firmware": "Firmware actual",
|
||||||
@@ -614,6 +616,7 @@
|
|||||||
"import_device_warning": "Asegúrese de que no haya espacios adicionales al principio o al final de ningún valor a menos que sea parte del valor deseado",
|
"import_device_warning": "Asegúrese de que no haya espacios adicionales al principio o al final de ningún valor a menos que sea parte del valor deseado",
|
||||||
"import_explanation": "Para importar dispositivos de forma masiva, debe usar un archivo CSV con las siguientes columnas: Número de serie, Tipo de dispositivo, Nombre, Descripción, Nota",
|
"import_explanation": "Para importar dispositivos de forma masiva, debe usar un archivo CSV con las siguientes columnas: Número de serie, Tipo de dispositivo, Nombre, Descripción, Nota",
|
||||||
"invalid_serial_number": "Número de serie no válido (debe tener 12 caracteres HEX)",
|
"invalid_serial_number": "Número de serie no válido (debe tener 12 caracteres HEX)",
|
||||||
|
"logs_one": "Iniciar sesión",
|
||||||
"new_devices": "Nuevos dispositivos",
|
"new_devices": "Nuevos dispositivos",
|
||||||
"no_model_image": "No se encontró ninguna imagen de modelo",
|
"no_model_image": "No se encontró ninguna imagen de modelo",
|
||||||
"not_connected": "No conectado",
|
"not_connected": "No conectado",
|
||||||
|
|||||||
@@ -392,6 +392,7 @@
|
|||||||
"warning_pushes_one": "En attente de connexion des appareils : {{count}}",
|
"warning_pushes_one": "En attente de connexion des appareils : {{count}}",
|
||||||
"warning_pushes_other": "En attente de connexion des appareils : {{count}}",
|
"warning_pushes_other": "En attente de connexion des appareils : {{count}}",
|
||||||
"weight": "Poids",
|
"weight": "Poids",
|
||||||
|
"wifi_bands_max": "Il ne peut y avoir plus de 8 SSID utilisant cette bande wifi",
|
||||||
"wifi_frames": "Cadres Wi-Fi"
|
"wifi_frames": "Cadres Wi-Fi"
|
||||||
},
|
},
|
||||||
"contacts": {
|
"contacts": {
|
||||||
@@ -601,6 +602,7 @@
|
|||||||
"certificate_expires_in": "Le certificat expire dans",
|
"certificate_expires_in": "Le certificat expire dans",
|
||||||
"certificate_expiry": "Cert. Expire dans",
|
"certificate_expiry": "Cert. Expire dans",
|
||||||
"connected": "Connecté",
|
"connected": "Connecté",
|
||||||
|
"crash_logs": "Journaux des plantages",
|
||||||
"create_errors": "erreurs lors de la tentative de création d'appareils",
|
"create_errors": "erreurs lors de la tentative de création d'appareils",
|
||||||
"create_success": " appareils créés avec succès",
|
"create_success": " appareils créés avec succès",
|
||||||
"current_firmware": "Firmware actuel",
|
"current_firmware": "Firmware actuel",
|
||||||
@@ -614,6 +616,7 @@
|
|||||||
"import_device_warning": "Veuillez vous assurer qu'il n'y a pas d'espaces supplémentaires au début ou à la fin des valeurs, sauf si cela fait partie de la valeur souhaitée",
|
"import_device_warning": "Veuillez vous assurer qu'il n'y a pas d'espaces supplémentaires au début ou à la fin des valeurs, sauf si cela fait partie de la valeur souhaitée",
|
||||||
"import_explanation": "Pour importer en masse des appareils, vous devez utiliser un fichier CSV avec les colonnes suivantes : SerialNumber, DeviceType, Name, Description, Note",
|
"import_explanation": "Pour importer en masse des appareils, vous devez utiliser un fichier CSV avec les colonnes suivantes : SerialNumber, DeviceType, Name, Description, Note",
|
||||||
"invalid_serial_number": "Numéro de série non valide (doit être composé de 12 caractères HEX)",
|
"invalid_serial_number": "Numéro de série non valide (doit être composé de 12 caractères HEX)",
|
||||||
|
"logs_one": "Bûche",
|
||||||
"new_devices": "nouveaux appareils",
|
"new_devices": "nouveaux appareils",
|
||||||
"no_model_image": "Aucune image de modèle trouvée",
|
"no_model_image": "Aucune image de modèle trouvée",
|
||||||
"not_connected": "Pas connecté",
|
"not_connected": "Pas connecté",
|
||||||
|
|||||||
@@ -392,6 +392,7 @@
|
|||||||
"warning_pushes_one": "Aguardando a conexão dos dispositivos: {{count}}",
|
"warning_pushes_one": "Aguardando a conexão dos dispositivos: {{count}}",
|
||||||
"warning_pushes_other": "Aguardando a conexão dos dispositivos: {{count}}",
|
"warning_pushes_other": "Aguardando a conexão dos dispositivos: {{count}}",
|
||||||
"weight": "Peso",
|
"weight": "Peso",
|
||||||
|
"wifi_bands_max": "Não pode haver mais de 8 SSIDs usando esta banda wi-fi",
|
||||||
"wifi_frames": "Quadros WiFi"
|
"wifi_frames": "Quadros WiFi"
|
||||||
},
|
},
|
||||||
"contacts": {
|
"contacts": {
|
||||||
@@ -601,6 +602,7 @@
|
|||||||
"certificate_expires_in": "Certificado expira em",
|
"certificate_expires_in": "Certificado expira em",
|
||||||
"certificate_expiry": "Certificado expira em",
|
"certificate_expiry": "Certificado expira em",
|
||||||
"connected": "Conectado",
|
"connected": "Conectado",
|
||||||
|
"crash_logs": "Registros de falhas",
|
||||||
"create_errors": "erros ao tentar criar dispositivos",
|
"create_errors": "erros ao tentar criar dispositivos",
|
||||||
"create_success": " dispositivos criados com sucesso",
|
"create_success": " dispositivos criados com sucesso",
|
||||||
"current_firmware": "Firmware atual",
|
"current_firmware": "Firmware atual",
|
||||||
@@ -614,6 +616,7 @@
|
|||||||
"import_device_warning": "Certifique-se de que não há espaços extras no início ou no final de nenhum valor, a menos que faça parte do valor desejado",
|
"import_device_warning": "Certifique-se de que não há espaços extras no início ou no final de nenhum valor, a menos que faça parte do valor desejado",
|
||||||
"import_explanation": "Para importar dispositivos em massa, você precisa usar um arquivo CSV com as seguintes colunas: SerialNumber, DeviceType, Name, Description, Note",
|
"import_explanation": "Para importar dispositivos em massa, você precisa usar um arquivo CSV com as seguintes colunas: SerialNumber, DeviceType, Name, Description, Note",
|
||||||
"invalid_serial_number": "Número de série inválido (precisa ter 12 caracteres HEX)",
|
"invalid_serial_number": "Número de série inválido (precisa ter 12 caracteres HEX)",
|
||||||
|
"logs_one": "Registro",
|
||||||
"new_devices": "novos dispositivos",
|
"new_devices": "novos dispositivos",
|
||||||
"no_model_image": "Nenhuma imagem de modelo encontrada",
|
"no_model_image": "Nenhuma imagem de modelo encontrada",
|
||||||
"not_connected": "Não conectado",
|
"not_connected": "Não conectado",
|
||||||
|
|||||||
@@ -11,8 +11,10 @@ export type DeviceLog = {
|
|||||||
severity: number;
|
severity: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getDeviceLogs = (limit: number, serialNumber?: string) => async () =>
|
const getDeviceLogs = (limit: number, serialNumber?: string, logType?: 0 | 1) => async () =>
|
||||||
axiosGw.get(`device/${serialNumber}/logs?newest=true&limit=${limit}`).then((response) => response.data) as Promise<{
|
axiosGw
|
||||||
|
.get(`device/${serialNumber}/logs?newest=true&limit=${limit}&logType=${logType}`)
|
||||||
|
.then((response) => response.data) as Promise<{
|
||||||
values: DeviceLog[];
|
values: DeviceLog[];
|
||||||
serialNumber: string;
|
serialNumber: string;
|
||||||
}>;
|
}>;
|
||||||
@@ -21,20 +23,29 @@ export const useGetDeviceLogs = ({
|
|||||||
serialNumber,
|
serialNumber,
|
||||||
limit,
|
limit,
|
||||||
onError,
|
onError,
|
||||||
|
logType,
|
||||||
}: {
|
}: {
|
||||||
serialNumber?: string;
|
serialNumber?: string;
|
||||||
limit: number;
|
limit: number;
|
||||||
onError?: (e: AxiosError) => void;
|
onError?: (e: AxiosError) => void;
|
||||||
|
logType?: 0 | 1;
|
||||||
}) =>
|
}) =>
|
||||||
useQuery(['devicelogs', serialNumber, { limit }], getDeviceLogs(limit, serialNumber), {
|
useQuery(['devicelogs', serialNumber, { limit, logType }], getDeviceLogs(limit, serialNumber, logType ?? 0), {
|
||||||
keepPreviousData: true,
|
keepPreviousData: true,
|
||||||
enabled: serialNumber !== undefined && serialNumber !== '',
|
enabled: serialNumber !== undefined && serialNumber !== '',
|
||||||
staleTime: 30000,
|
staleTime: 30000,
|
||||||
onError,
|
onError,
|
||||||
});
|
});
|
||||||
|
|
||||||
const deleteLogs = async ({ serialNumber, endDate }: { serialNumber: string; endDate: number }) =>
|
const deleteLogs = async ({
|
||||||
axiosGw.delete(`device/${serialNumber}/logs?endDate=${endDate}`);
|
serialNumber,
|
||||||
|
endDate,
|
||||||
|
logType,
|
||||||
|
}: {
|
||||||
|
serialNumber: string;
|
||||||
|
endDate: number;
|
||||||
|
logType: 0 | 1;
|
||||||
|
}) => axiosGw.delete(`device/${serialNumber}/logs?endDate=${endDate}&logType=${logType}`);
|
||||||
export const useDeleteLogs = () => {
|
export const useDeleteLogs = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
@@ -45,46 +56,62 @@ export const useDeleteLogs = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const getLogsBatch = (serialNumber?: string, start?: number, end?: number, limit?: number, offset?: number) =>
|
const getLogsBatch = (
|
||||||
|
serialNumber?: string,
|
||||||
|
start?: number,
|
||||||
|
end?: number,
|
||||||
|
limit?: number,
|
||||||
|
offset?: number,
|
||||||
|
logType?: 0 | 1,
|
||||||
|
) =>
|
||||||
axiosGw
|
axiosGw
|
||||||
.get(`device/${serialNumber}/logs?startDate=${start}&endDate=${end}&limit=${limit}&offset=${offset}`)
|
.get(
|
||||||
|
`device/${serialNumber}/logs?startDate=${start}&endDate=${end}&limit=${limit}&offset=${offset}&logType=${logType}`,
|
||||||
|
)
|
||||||
.then((response) => response.data) as Promise<{
|
.then((response) => response.data) as Promise<{
|
||||||
values: DeviceLog[];
|
values: DeviceLog[];
|
||||||
serialNumber: string;
|
serialNumber: string;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
const getDeviceLogsWithTimestamps = (serialNumber?: string, start?: number, end?: number) => async () => {
|
const getDeviceLogsWithTimestamps =
|
||||||
let offset = 0;
|
(serialNumber?: string, start?: number, end?: number, logType?: 0 | 1) => async () => {
|
||||||
const limit = 100;
|
let offset = 0;
|
||||||
let logs: DeviceLog[] = [];
|
const limit = 100;
|
||||||
let latestResponse: {
|
let logs: DeviceLog[] = [];
|
||||||
values: DeviceLog[];
|
let latestResponse: {
|
||||||
serialNumber: string;
|
values: DeviceLog[];
|
||||||
|
serialNumber: string;
|
||||||
|
};
|
||||||
|
do {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
latestResponse = await getLogsBatch(serialNumber, start, end, limit, offset, logType);
|
||||||
|
logs = logs.concat(latestResponse.values);
|
||||||
|
offset += limit;
|
||||||
|
} while (latestResponse.values.length === limit);
|
||||||
|
return {
|
||||||
|
values: logs,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
do {
|
|
||||||
// eslint-disable-next-line no-await-in-loop
|
|
||||||
latestResponse = await getLogsBatch(serialNumber, start, end, limit, offset);
|
|
||||||
logs = logs.concat(latestResponse.values);
|
|
||||||
offset += limit;
|
|
||||||
} while (latestResponse.values.length === limit);
|
|
||||||
return {
|
|
||||||
values: logs,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useGetDeviceLogsWithTimestamps = ({
|
export const useGetDeviceLogsWithTimestamps = ({
|
||||||
serialNumber,
|
serialNumber,
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
onError,
|
onError,
|
||||||
|
logType,
|
||||||
}: {
|
}: {
|
||||||
serialNumber?: string;
|
serialNumber?: string;
|
||||||
start?: number;
|
start?: number;
|
||||||
end?: number;
|
end?: number;
|
||||||
onError?: (e: AxiosError) => void;
|
onError?: (e: AxiosError) => void;
|
||||||
|
logType?: 0 | 1;
|
||||||
}) =>
|
}) =>
|
||||||
useQuery(['devicelogs', serialNumber, { start, end }], getDeviceLogsWithTimestamps(serialNumber, start, end), {
|
useQuery(
|
||||||
enabled: serialNumber !== undefined && serialNumber !== '' && start !== undefined && end !== undefined,
|
['devicelogs', serialNumber, { start, end, logType }],
|
||||||
staleTime: 1000 * 60,
|
getDeviceLogsWithTimestamps(serialNumber, start, end, logType ?? 0),
|
||||||
onError,
|
{
|
||||||
});
|
enabled: serialNumber !== undefined && serialNumber !== '' && start !== undefined && end !== undefined,
|
||||||
|
staleTime: 1000 * 60,
|
||||||
|
onError,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|||||||
94
src/pages/Device/LogsCard/LogHistory/CrashLogs.tsx
Normal file
94
src/pages/Device/LogsCard/LogHistory/CrashLogs.tsx
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { Box, Button, Center, Flex, Heading, HStack, Spacer } from '@chakra-ui/react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import HistoryDatePickers from '../DatePickers';
|
||||||
|
import DeleteLogModal from './DeleteModal';
|
||||||
|
import useDeviceLogsTable from './useDeviceLogsTable';
|
||||||
|
import { RefreshButton } from 'components/Buttons/RefreshButton';
|
||||||
|
import { ColumnPicker } from 'components/DataTables/ColumnPicker';
|
||||||
|
import { DataTable } from 'components/DataTables/DataTable';
|
||||||
|
import { Column } from 'models/Table';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
serialNumber: string;
|
||||||
|
};
|
||||||
|
const CrashLogs = ({ serialNumber }: Props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [limit, setLimit] = React.useState(25);
|
||||||
|
const [hiddenColumns, setHiddenColumns] = React.useState<string[]>([]);
|
||||||
|
const { time, setTime, getCustomLogs, getLogs, columns, modal } = useDeviceLogsTable({
|
||||||
|
serialNumber,
|
||||||
|
limit,
|
||||||
|
logType: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
const setNewTime = (start: Date, end: Date) => {
|
||||||
|
setTime({ start, end });
|
||||||
|
};
|
||||||
|
const onClear = () => {
|
||||||
|
setTime(undefined);
|
||||||
|
};
|
||||||
|
const raiseLimit = () => {
|
||||||
|
setLimit(limit + 25);
|
||||||
|
};
|
||||||
|
|
||||||
|
const noMoreAvailable = getLogs.data !== undefined && getLogs.data.values.length < limit;
|
||||||
|
|
||||||
|
const data = React.useMemo(() => {
|
||||||
|
if (getCustomLogs.data) return getCustomLogs.data.values.sort((a, b) => b.recorded - a.recorded);
|
||||||
|
if (getLogs.data) return getLogs.data.values;
|
||||||
|
return [];
|
||||||
|
}, [getLogs.data, getCustomLogs.data]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Flex>
|
||||||
|
<Spacer />
|
||||||
|
<HStack>
|
||||||
|
<HistoryDatePickers defaults={time} setTime={setNewTime} onClear={onClear} />
|
||||||
|
<ColumnPicker
|
||||||
|
columns={columns as Column<unknown>[]}
|
||||||
|
hiddenColumns={hiddenColumns}
|
||||||
|
setHiddenColumns={setHiddenColumns}
|
||||||
|
preference="gateway.device.logs.hiddenColumns"
|
||||||
|
/>
|
||||||
|
<DeleteLogModal serialNumber={serialNumber} logType={0} />
|
||||||
|
<RefreshButton isCompact isFetching={getLogs.isFetching} onClick={getLogs.refetch} colorScheme="blue" />
|
||||||
|
</HStack>
|
||||||
|
</Flex>
|
||||||
|
<Box overflowY="auto" h="300px">
|
||||||
|
<DataTable
|
||||||
|
columns={
|
||||||
|
columns as {
|
||||||
|
id: string;
|
||||||
|
Header: string;
|
||||||
|
Footer: string;
|
||||||
|
accessor: string;
|
||||||
|
}[]
|
||||||
|
}
|
||||||
|
data={data}
|
||||||
|
isLoading={getLogs.isFetching || getCustomLogs.isFetching}
|
||||||
|
hiddenColumns={hiddenColumns}
|
||||||
|
obj={t('controller.devices.logs')}
|
||||||
|
// @ts-ignore
|
||||||
|
hideControls
|
||||||
|
showAllRows
|
||||||
|
/>
|
||||||
|
{getLogs.data !== undefined && (
|
||||||
|
<Center mt={1} hidden={getCustomLogs.data !== undefined}>
|
||||||
|
{!noMoreAvailable || getLogs.isFetching ? (
|
||||||
|
<Button colorScheme="blue" onClick={raiseLimit} isLoading={getLogs.isFetching}>
|
||||||
|
{t('controller.devices.show_more')}
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Heading size="sm">{t('controller.devices.no_more_available')}!</Heading>
|
||||||
|
)}
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
{modal}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CrashLogs;
|
||||||
@@ -16,8 +16,8 @@ const CustomInputButton = React.forwardRef(
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
type Props = { serialNumber: string };
|
type Props = { serialNumber: string; logType: 0 | 1 };
|
||||||
const DeleteLogModal = ({ serialNumber }: Props) => {
|
const DeleteLogModal = ({ serialNumber, logType }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const modalProps = useDisclosure();
|
const modalProps = useDisclosure();
|
||||||
@@ -26,7 +26,7 @@ const DeleteLogModal = ({ serialNumber }: Props) => {
|
|||||||
|
|
||||||
const onDeleteClick = () => {
|
const onDeleteClick = () => {
|
||||||
deleteLogs.mutate(
|
deleteLogs.mutate(
|
||||||
{ endDate: Math.floor(date.getTime() / 1000), serialNumber },
|
{ endDate: Math.floor(date.getTime() / 1000), serialNumber, logType },
|
||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
modalProps.onClose();
|
modalProps.onClose();
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { Box, Button, Code, Heading, useClipboard } from '@chakra-ui/react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import FormattedDate from 'components/InformationDisplays/FormattedDate';
|
||||||
|
import { Modal } from 'components/Modals/Modal';
|
||||||
|
import { DeviceLog } from 'hooks/Network/DeviceLogs';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
modalProps: {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
};
|
||||||
|
log?: DeviceLog;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DetailedLogViewModal = ({ modalProps, log }: Props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { hasCopied, onCopy, setValue } = useClipboard(JSON.stringify(log?.log ?? {}, null, 2));
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setValue(JSON.stringify(log?.log ?? {}, null, 2));
|
||||||
|
}, [log]);
|
||||||
|
|
||||||
|
if (!log) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
isOpen={modalProps.isOpen}
|
||||||
|
onClose={modalProps.onClose}
|
||||||
|
title={t('devices.logs_one')}
|
||||||
|
topRightButtons={
|
||||||
|
<Button onClick={onCopy} size="md" colorScheme="teal">
|
||||||
|
{hasCopied ? `${t('common.copied')}!` : t('common.copy')}
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Box>
|
||||||
|
<Heading size="sm">
|
||||||
|
<FormattedDate date={log.recorded} />
|
||||||
|
</Heading>
|
||||||
|
<Heading size="sm">
|
||||||
|
{t('controller.devices.severity')}: {log.severity}
|
||||||
|
</Heading>
|
||||||
|
<Heading size="sm">
|
||||||
|
{t('controller.devices.config_id')}: {log.UUID}
|
||||||
|
</Heading>
|
||||||
|
<Code whiteSpace="pre-line" mt={2}>
|
||||||
|
{log.log}
|
||||||
|
</Code>
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DetailedLogViewModal;
|
||||||
@@ -16,7 +16,7 @@ const LogHistory = ({ serialNumber }: Props) => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [limit, setLimit] = React.useState(25);
|
const [limit, setLimit] = React.useState(25);
|
||||||
const [hiddenColumns, setHiddenColumns] = React.useState<string[]>([]);
|
const [hiddenColumns, setHiddenColumns] = React.useState<string[]>([]);
|
||||||
const { time, setTime, getCustomLogs, getLogs, columns } = useDeviceLogsTable({ serialNumber, limit });
|
const { time, setTime, getCustomLogs, getLogs, columns } = useDeviceLogsTable({ serialNumber, limit, logType: 0 });
|
||||||
|
|
||||||
const setNewTime = (start: Date, end: Date) => {
|
const setNewTime = (start: Date, end: Date) => {
|
||||||
setTime({ start, end });
|
setTime({ start, end });
|
||||||
@@ -48,7 +48,7 @@ const LogHistory = ({ serialNumber }: Props) => {
|
|||||||
setHiddenColumns={setHiddenColumns}
|
setHiddenColumns={setHiddenColumns}
|
||||||
preference="gateway.device.logs.hiddenColumns"
|
preference="gateway.device.logs.hiddenColumns"
|
||||||
/>
|
/>
|
||||||
<DeleteLogModal serialNumber={serialNumber} />
|
<DeleteLogModal serialNumber={serialNumber} logType={0} />
|
||||||
<RefreshButton isCompact isFetching={getLogs.isFetching} onClick={getLogs.refetch} colorScheme="blue" />
|
<RefreshButton isCompact isFetching={getLogs.isFetching} onClick={getLogs.refetch} colorScheme="blue" />
|
||||||
</HStack>
|
</HStack>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { Box } from '@chakra-ui/react';
|
import { Box, IconButton, Text, useDisclosure } from '@chakra-ui/react';
|
||||||
|
import { MagnifyingGlass } from 'phosphor-react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import DetailedLogViewModal from './DetailedLogViewModal';
|
||||||
import FormattedDate from 'components/InformationDisplays/FormattedDate';
|
import FormattedDate from 'components/InformationDisplays/FormattedDate';
|
||||||
import { DeviceLog, useGetDeviceLogs, useGetDeviceLogsWithTimestamps } from 'hooks/Network/DeviceLogs';
|
import { DeviceLog, useGetDeviceLogs, useGetDeviceLogsWithTimestamps } from 'hooks/Network/DeviceLogs';
|
||||||
import { Column } from 'models/Table';
|
import { Column } from 'models/Table';
|
||||||
@@ -8,18 +10,49 @@ import { Column } from 'models/Table';
|
|||||||
type Props = {
|
type Props = {
|
||||||
serialNumber: string;
|
serialNumber: string;
|
||||||
limit: number;
|
limit: number;
|
||||||
|
logType: 0 | 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
const useDeviceLogsTable = ({ serialNumber, limit }: Props) => {
|
const useDeviceLogsTable = ({ serialNumber, limit, logType }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const getLogs = useGetDeviceLogs({ serialNumber, limit });
|
const getLogs = useGetDeviceLogs({ serialNumber, limit, logType });
|
||||||
|
const modalProps = useDisclosure();
|
||||||
|
const [log, setLog] = React.useState<DeviceLog | undefined>();
|
||||||
const [time, setTime] = React.useState<{ start: Date; end: Date } | undefined>();
|
const [time, setTime] = React.useState<{ start: Date; end: Date } | undefined>();
|
||||||
const getCustomLogs = useGetDeviceLogsWithTimestamps({
|
const getCustomLogs = useGetDeviceLogsWithTimestamps({
|
||||||
serialNumber,
|
serialNumber,
|
||||||
start: time ? Math.floor(time.start.getTime() / 1000) : undefined,
|
start: time ? Math.floor(time.start.getTime() / 1000) : undefined,
|
||||||
end: time ? Math.floor(time.end.getTime() / 1000) : undefined,
|
end: time ? Math.floor(time.end.getTime() / 1000) : undefined,
|
||||||
|
logType,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const onOpen = React.useCallback((v: DeviceLog) => {
|
||||||
|
setLog(v);
|
||||||
|
modalProps.onOpen();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const logCell = React.useCallback(
|
||||||
|
(v: DeviceLog) =>
|
||||||
|
logType === 1 ? (
|
||||||
|
<Box display="flex">
|
||||||
|
<IconButton
|
||||||
|
aria-label="Open Log Details"
|
||||||
|
onClick={() => onOpen(v)}
|
||||||
|
colorScheme="blue"
|
||||||
|
icon={<MagnifyingGlass size={16} />}
|
||||||
|
size="xs"
|
||||||
|
mr={2}
|
||||||
|
/>
|
||||||
|
<Text my="auto" maxW="calc(20vw)" textOverflow="ellipsis" overflow="hidden" whiteSpace="nowrap">
|
||||||
|
{v.log}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
v.log
|
||||||
|
),
|
||||||
|
[onOpen],
|
||||||
|
);
|
||||||
|
|
||||||
const dateCell = React.useCallback(
|
const dateCell = React.useCallback(
|
||||||
(v: number) => (
|
(v: number) => (
|
||||||
<Box>
|
<Box>
|
||||||
@@ -65,6 +98,7 @@ const useDeviceLogsTable = ({ serialNumber, limit }: Props) => {
|
|||||||
Footer: '',
|
Footer: '',
|
||||||
accessor: 'log',
|
accessor: 'log',
|
||||||
customWidth: '35px',
|
customWidth: '35px',
|
||||||
|
Cell: (v) => logCell(v.cell.row.original),
|
||||||
disableSortBy: true,
|
disableSortBy: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -85,6 +119,7 @@ const useDeviceLogsTable = ({ serialNumber, limit }: Props) => {
|
|||||||
getCustomLogs,
|
getCustomLogs,
|
||||||
time,
|
time,
|
||||||
setTime,
|
setTime,
|
||||||
|
modal: <DetailedLogViewModal modalProps={modalProps} log={log} />,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import CommandHistory from './CommandHistory';
|
import CommandHistory from './CommandHistory';
|
||||||
import HealthCheckHistory from './HealthCheckHistory';
|
import HealthCheckHistory from './HealthCheckHistory';
|
||||||
import LogHistory from './LogHistory';
|
import LogHistory from './LogHistory';
|
||||||
|
import CrashLogs from './LogHistory/CrashLogs';
|
||||||
import { Card } from 'components/Containers/Card';
|
import { Card } from 'components/Containers/Card';
|
||||||
import { CardBody } from 'components/Containers/Card/CardBody';
|
import { CardBody } from 'components/Containers/Card/CardBody';
|
||||||
|
|
||||||
@@ -32,6 +33,9 @@ const DeviceLogsCard = ({ serialNumber }: Props) => {
|
|||||||
<Tab fontSize="lg" fontWeight="bold">
|
<Tab fontSize="lg" fontWeight="bold">
|
||||||
{t('controller.devices.logs')}
|
{t('controller.devices.logs')}
|
||||||
</Tab>
|
</Tab>
|
||||||
|
<Tab fontSize="lg" fontWeight="bold">
|
||||||
|
{t('devices.crash_logs')}
|
||||||
|
</Tab>
|
||||||
</TabList>
|
</TabList>
|
||||||
<TabPanels>
|
<TabPanels>
|
||||||
<TabPanel p={0}>
|
<TabPanel p={0}>
|
||||||
@@ -51,10 +55,12 @@ const DeviceLogsCard = ({ serialNumber }: Props) => {
|
|||||||
<TabPanel>
|
<TabPanel>
|
||||||
<HealthCheckHistory serialNumber={serialNumber} />
|
<HealthCheckHistory serialNumber={serialNumber} />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
<LogHistory serialNumber={serialNumber} />
|
<LogHistory serialNumber={serialNumber} />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
<TabPanel>
|
||||||
|
<CrashLogs serialNumber={serialNumber} />
|
||||||
|
</TabPanel>
|
||||||
</TabPanels>
|
</TabPanels>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
|
|||||||
Reference in New Issue
Block a user