Merge pull request #156 from stephb9959/main

[WIFI-12067] Added crash logs to device details page
This commit is contained in:
Charles Bourque
2023-01-06 14:55:06 -05:00
committed by GitHub
14 changed files with 273 additions and 41 deletions

4
package-lock.json generated
View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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é",

View File

@@ -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",

View File

@@ -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,
},
);

View 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;

View File

@@ -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();

View File

@@ -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;

View File

@@ -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>

View File

@@ -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} />,
}; };
}; };

View File

@@ -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>