mirror of
https://github.com/optim-enterprises-bv/OptimCloud-gw-ui.git
synced 2025-11-03 03:37:45 +00:00
[WIFI-11875] Added timestamps selection for device commands, logs and healthchecks
Signed-off-by: Charles <charles.bourque96@gmail.com>
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.8.0(36)",
|
"version": "2.8.0(37)",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "ucentral-client",
|
"name": "ucentral-client",
|
||||||
"version": "2.8.0(36)",
|
"version": "2.8.0(37)",
|
||||||
"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.8.0(36)",
|
"version": "2.8.0(37)",
|
||||||
"description": "",
|
"description": "",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "index.tsx",
|
"main": "index.tsx",
|
||||||
|
|||||||
@@ -57,6 +57,29 @@ export const useGetCommandHistory = ({
|
|||||||
onError,
|
onError,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const getCommandsWithTimestamps = (serialNumber?: string, start?: number, end?: number) => async () =>
|
||||||
|
axiosGw
|
||||||
|
.get(`commands?serialNumber=${serialNumber}&startDate=${start}&endDate=${end}`)
|
||||||
|
.then((response) => response.data) as Promise<{
|
||||||
|
commands: DeviceCommandHistory[];
|
||||||
|
}>;
|
||||||
|
export const useGetCommandHistoryWithTimestamps = ({
|
||||||
|
serialNumber,
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
onError,
|
||||||
|
}: {
|
||||||
|
serialNumber?: string;
|
||||||
|
start?: number;
|
||||||
|
end?: number;
|
||||||
|
onError?: (e: AxiosError) => void;
|
||||||
|
}) =>
|
||||||
|
useQuery(['commands', serialNumber, { start, end }], getCommandsWithTimestamps(serialNumber, start, end), {
|
||||||
|
enabled: serialNumber !== undefined && serialNumber !== '' && start !== undefined && end !== undefined,
|
||||||
|
staleTime: 1000 * 60,
|
||||||
|
onError,
|
||||||
|
});
|
||||||
|
|
||||||
const deleteCommandHistory = async (id: string) => axiosGw.delete(`command/${id}`);
|
const deleteCommandHistory = async (id: string) => axiosGw.delete(`command/${id}`);
|
||||||
export const useDeleteCommand = () => {
|
export const useDeleteCommand = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|||||||
@@ -40,7 +40,31 @@ export const useDeleteLogs = () => {
|
|||||||
|
|
||||||
return useMutation(deleteLogs, {
|
return useMutation(deleteLogs, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries('devicelogs');
|
queryClient.invalidateQueries(['devicelogs']);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getDeviceLogsWithTimestamps = (serialNumber?: string, start?: number, end?: number) => async () =>
|
||||||
|
axiosGw
|
||||||
|
.get(`device/${serialNumber}/logs?startDate=${start}&endDate=${end}`)
|
||||||
|
.then((response) => response.data) as Promise<{
|
||||||
|
values: DeviceLog[];
|
||||||
|
serialNumber: string;
|
||||||
|
}>;
|
||||||
|
export const useGetDeviceLogsWithTimestamps = ({
|
||||||
|
serialNumber,
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
onError,
|
||||||
|
}: {
|
||||||
|
serialNumber?: string;
|
||||||
|
start?: number;
|
||||||
|
end?: number;
|
||||||
|
onError?: (e: AxiosError) => void;
|
||||||
|
}) =>
|
||||||
|
useQuery(['devicelogs', serialNumber, { start, end }], getDeviceLogsWithTimestamps(serialNumber, start, end), {
|
||||||
|
enabled: serialNumber !== undefined && serialNumber !== '' && start !== undefined && end !== undefined,
|
||||||
|
staleTime: 1000 * 60,
|
||||||
|
onError,
|
||||||
|
});
|
||||||
|
|||||||
@@ -33,6 +33,30 @@ export const useGetHealthChecks = ({
|
|||||||
onError,
|
onError,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const getHealthChecksWithTimestamps = (serialNumber?: string, start?: number, end?: number) => async () =>
|
||||||
|
axiosGw
|
||||||
|
.get(`device/${serialNumber}/healthchecks?startDate=${start}&endDate=${end}`)
|
||||||
|
.then((response) => response.data) as Promise<{
|
||||||
|
values: HealthCheck[];
|
||||||
|
serialNumber: string;
|
||||||
|
}>;
|
||||||
|
export const useGetHealthChecksWithTimestamps = ({
|
||||||
|
serialNumber,
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
onError,
|
||||||
|
}: {
|
||||||
|
serialNumber?: string;
|
||||||
|
start?: number;
|
||||||
|
end?: number;
|
||||||
|
onError?: (e: AxiosError) => void;
|
||||||
|
}) =>
|
||||||
|
useQuery(['healthchecks', serialNumber, { start, end }], getHealthChecksWithTimestamps(serialNumber, start, end), {
|
||||||
|
enabled: serialNumber !== undefined && serialNumber !== '' && start !== undefined && end !== undefined,
|
||||||
|
staleTime: 1000 * 60,
|
||||||
|
onError,
|
||||||
|
});
|
||||||
|
|
||||||
const deleteHealthChecks = async ({ serialNumber, endDate }: { serialNumber: string; endDate: number }) =>
|
const deleteHealthChecks = async ({ serialNumber, endDate }: { serialNumber: string; endDate: number }) =>
|
||||||
axiosGw.delete(`device/${serialNumber}/healthchecks?endDate=${endDate}`);
|
axiosGw.delete(`device/${serialNumber}/healthchecks?endDate=${endDate}`);
|
||||||
export const useDeleteHealthChecks = () => {
|
export const useDeleteHealthChecks = () => {
|
||||||
@@ -40,7 +64,7 @@ export const useDeleteHealthChecks = () => {
|
|||||||
|
|
||||||
return useMutation(deleteHealthChecks, {
|
return useMutation(deleteHealthChecks, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries('healthchecks');
|
queryClient.invalidateQueries(['healthchecks']);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { Box, Button, Center, Heading } from '@chakra-ui/react';
|
import { Box, Button, Center, Heading, HStack, Spacer } from '@chakra-ui/react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import HistoryDatePickers from '../DatePickers';
|
||||||
import CommandResultModal from './ResultModal';
|
import CommandResultModal from './ResultModal';
|
||||||
import useCommandHistoryTable from './useCommandHistoryTable';
|
import useCommandHistoryTable from './useCommandHistoryTable';
|
||||||
import { RefreshButton } from 'components/Buttons/RefreshButton';
|
import { RefreshButton } from 'components/Buttons/RefreshButton';
|
||||||
@@ -15,24 +16,48 @@ const CommandHistory = ({ 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 { getCommands, columns, selectedCommand, detailsModalProps } = useCommandHistoryTable({ serialNumber, limit });
|
const { time, setTime, getCustomCommands, getCommands, columns, selectedCommand, detailsModalProps } =
|
||||||
|
useCommandHistoryTable({ serialNumber, limit });
|
||||||
|
|
||||||
const raiseLimit = () => {
|
const raiseLimit = () => {
|
||||||
setLimit(limit + 25);
|
setLimit(limit + 25);
|
||||||
};
|
};
|
||||||
|
|
||||||
const noMoreAvailable = getCommands.data !== undefined && getCommands.data.commands.length < limit;
|
const setNewTime = (start: Date, end: Date) => {
|
||||||
|
setTime({ start, end });
|
||||||
|
};
|
||||||
|
const onClear = () => {
|
||||||
|
setTime(undefined);
|
||||||
|
};
|
||||||
|
|
||||||
|
const noMoreAvailable =
|
||||||
|
getCustomCommands.data || (getCommands.data !== undefined && getCommands.data.commands.length < limit);
|
||||||
|
|
||||||
|
const data = React.useMemo(() => {
|
||||||
|
if (getCustomCommands.data) return getCustomCommands.data.commands.sort((a, b) => b.submitted - a.submitted);
|
||||||
|
if (getCommands.data) return getCommands.data.commands;
|
||||||
|
return [];
|
||||||
|
}, [getCustomCommands.data, getCommands.data]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Box textAlign="right">
|
<Box textAlign="right" display="flex">
|
||||||
<ColumnPicker
|
<Spacer />
|
||||||
columns={columns as Column<unknown>[]}
|
<HStack>
|
||||||
hiddenColumns={hiddenColumns}
|
<HistoryDatePickers defaults={time} setTime={setNewTime} onClear={onClear} />
|
||||||
setHiddenColumns={setHiddenColumns}
|
<ColumnPicker
|
||||||
preference="gateway.device.commandshistory.hiddenColumns"
|
columns={columns as Column<unknown>[]}
|
||||||
/>
|
hiddenColumns={hiddenColumns}
|
||||||
<RefreshButton isCompact isFetching={getCommands.isFetching} onClick={getCommands.refetch} ml={2} />
|
setHiddenColumns={setHiddenColumns}
|
||||||
|
preference="gateway.device.commandshistory.hiddenColumns"
|
||||||
|
/>
|
||||||
|
<RefreshButton
|
||||||
|
isCompact
|
||||||
|
isFetching={getCommands.isFetching}
|
||||||
|
onClick={getCommands.refetch}
|
||||||
|
colorScheme="blue"
|
||||||
|
/>
|
||||||
|
</HStack>
|
||||||
</Box>
|
</Box>
|
||||||
<Box overflowY="auto" h="300px">
|
<Box overflowY="auto" h="300px">
|
||||||
<DataTable
|
<DataTable
|
||||||
@@ -44,16 +69,16 @@ const CommandHistory = ({ serialNumber }: Props) => {
|
|||||||
accessor: string;
|
accessor: string;
|
||||||
}[]
|
}[]
|
||||||
}
|
}
|
||||||
data={getCommands.data?.commands ?? []}
|
data={data}
|
||||||
isLoading={getCommands.isFetching}
|
isLoading={getCommands.isFetching || getCustomCommands.isFetching}
|
||||||
hiddenColumns={hiddenColumns}
|
hiddenColumns={hiddenColumns}
|
||||||
obj={t('controller.devices.commands')}
|
obj={t('controller.devices.commands')}
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
hideControls
|
hideControls
|
||||||
showAllRows
|
showAllRows
|
||||||
/>
|
/>
|
||||||
{getCommands.data !== undefined && (
|
{data !== undefined && (
|
||||||
<Center mt={2}>
|
<Center mt={2} hidden={getCustomCommands.data !== undefined}>
|
||||||
{!noMoreAvailable || getCommands.isFetching ? (
|
{!noMoreAvailable || getCommands.isFetching ? (
|
||||||
<Button colorScheme="blue" onClick={raiseLimit} isLoading={getCommands.isFetching}>
|
<Button colorScheme="blue" onClick={raiseLimit} isLoading={getCommands.isFetching}>
|
||||||
{t('controller.devices.show_more')}
|
{t('controller.devices.show_more')}
|
||||||
|
|||||||
@@ -4,7 +4,12 @@ import { MagnifyingGlass, Trash } from 'phosphor-react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import FormattedDate from 'components/InformationDisplays/FormattedDate';
|
import FormattedDate from 'components/InformationDisplays/FormattedDate';
|
||||||
import { uppercaseFirstLetter } from 'helpers/stringHelper';
|
import { uppercaseFirstLetter } from 'helpers/stringHelper';
|
||||||
import { DeviceCommandHistory, useDeleteCommand, useGetCommandHistory } from 'hooks/Network/Commands';
|
import {
|
||||||
|
DeviceCommandHistory,
|
||||||
|
useDeleteCommand,
|
||||||
|
useGetCommandHistory,
|
||||||
|
useGetCommandHistoryWithTimestamps,
|
||||||
|
} from 'hooks/Network/Commands';
|
||||||
import { AxiosError } from 'models/Axios';
|
import { AxiosError } from 'models/Axios';
|
||||||
import { Column } from 'models/Table';
|
import { Column } from 'models/Table';
|
||||||
|
|
||||||
@@ -15,6 +20,12 @@ type Props = {
|
|||||||
|
|
||||||
const useCommandHistoryTable = ({ serialNumber, limit }: Props) => {
|
const useCommandHistoryTable = ({ serialNumber, limit }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const [time, setTime] = React.useState<{ start: Date; end: Date } | undefined>();
|
||||||
|
const getCustomCommands = useGetCommandHistoryWithTimestamps({
|
||||||
|
serialNumber,
|
||||||
|
start: time ? Math.floor(time.start.getTime() / 1000) : undefined,
|
||||||
|
end: time ? Math.floor(time.end.getTime() / 1000) : undefined,
|
||||||
|
});
|
||||||
const getCommands = useGetCommandHistory({ serialNumber, limit });
|
const getCommands = useGetCommandHistory({ serialNumber, limit });
|
||||||
const deleteCommand = useDeleteCommand();
|
const deleteCommand = useDeleteCommand();
|
||||||
const [selectedCommand, setSelectedCommand] = React.useState<DeviceCommandHistory | undefined>();
|
const [selectedCommand, setSelectedCommand] = React.useState<DeviceCommandHistory | undefined>();
|
||||||
@@ -185,8 +196,11 @@ const useCommandHistoryTable = ({ serialNumber, limit }: Props) => {
|
|||||||
return {
|
return {
|
||||||
columns,
|
columns,
|
||||||
getCommands,
|
getCommands,
|
||||||
|
getCustomCommands,
|
||||||
selectedCommand,
|
selectedCommand,
|
||||||
detailsModalProps,
|
detailsModalProps,
|
||||||
|
time,
|
||||||
|
setTime,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
188
src/pages/Device/LogsCard/DatePickers.tsx
Normal file
188
src/pages/Device/LogsCard/DatePickers.tsx
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Flex,
|
||||||
|
Heading,
|
||||||
|
HStack,
|
||||||
|
IconButton,
|
||||||
|
Popover,
|
||||||
|
PopoverArrow,
|
||||||
|
PopoverBody,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverHeader,
|
||||||
|
PopoverTrigger,
|
||||||
|
Spacer,
|
||||||
|
Tooltip,
|
||||||
|
useBreakpoint,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import { Clock, Prohibit } from 'phosphor-react';
|
||||||
|
import ReactDatePicker from 'react-datepicker';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { CloseButton } from 'components/Buttons/CloseButton';
|
||||||
|
import { SaveButton } from 'components/Buttons/SaveButton';
|
||||||
|
|
||||||
|
const CustomInputButton = React.forwardRef(
|
||||||
|
({ value, onClick }: { value: string; onClick: () => void }, ref: React.LegacyRef<HTMLButtonElement>) => (
|
||||||
|
<Button colorScheme="gray" size="sm" onClick={onClick} ref={ref} mt={1}>
|
||||||
|
{value}
|
||||||
|
</Button>
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const getStart = () => {
|
||||||
|
const date = new Date();
|
||||||
|
date.setHours(date.getHours() - 1);
|
||||||
|
return date;
|
||||||
|
};
|
||||||
|
type Props = {
|
||||||
|
defaults?: { start: Date; end: Date };
|
||||||
|
setTime: (start: Date, end: Date) => void;
|
||||||
|
onClear: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const HistoryDatePickers = ({ defaults, setTime, onClear }: Props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [start, setStart] = React.useState<Date>(defaults?.start ?? getStart());
|
||||||
|
const [end, setEnd] = React.useState<Date>(defaults?.end ?? new Date());
|
||||||
|
const breakpoint = useBreakpoint();
|
||||||
|
|
||||||
|
const onStartChange = (newDate: Date) => {
|
||||||
|
setStart(newDate);
|
||||||
|
};
|
||||||
|
const onEndChange = (newDate: Date) => {
|
||||||
|
setEnd(newDate);
|
||||||
|
};
|
||||||
|
const clear = (onClose: () => void) => () => {
|
||||||
|
onClear();
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
const onSave = (onClose: () => void) => () => {
|
||||||
|
onClose();
|
||||||
|
setTime(start, end);
|
||||||
|
};
|
||||||
|
|
||||||
|
const width = (isOpen: boolean) => {
|
||||||
|
if (isOpen) {
|
||||||
|
return breakpoint === 'base' ? '360px' : '460px';
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setStart(defaults?.start ?? getStart());
|
||||||
|
setEnd(defaults?.end ?? new Date());
|
||||||
|
}, [defaults]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover>
|
||||||
|
{({ isOpen, onClose }) => (
|
||||||
|
<>
|
||||||
|
<PopoverTrigger>
|
||||||
|
<Box>
|
||||||
|
<Tooltip label={t('controller.crud.choose_time')}>
|
||||||
|
<IconButton aria-label={t('controller.crud.choose_time')} icon={<Clock />} />
|
||||||
|
</Tooltip>
|
||||||
|
</Box>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent w={width(isOpen)}>
|
||||||
|
<PopoverArrow />
|
||||||
|
<PopoverHeader display="flex">
|
||||||
|
<Heading size="sm" my="auto">
|
||||||
|
{t('controller.crud.choose_time')}
|
||||||
|
</Heading>
|
||||||
|
<Spacer />
|
||||||
|
<HStack>
|
||||||
|
<Tooltip label={t('controller.crud.clear_time')}>
|
||||||
|
<IconButton
|
||||||
|
colorScheme="red"
|
||||||
|
aria-label={t('controller.crud.clear_time')}
|
||||||
|
onClick={clear(onClose)}
|
||||||
|
icon={<Prohibit />}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<SaveButton onClick={onSave(onClose)} isCompact />
|
||||||
|
<CloseButton onClick={onClose} />
|
||||||
|
</HStack>
|
||||||
|
</PopoverHeader>
|
||||||
|
<PopoverBody>
|
||||||
|
{breakpoint === 'base' ? (
|
||||||
|
<Box>
|
||||||
|
<Flex>
|
||||||
|
<Heading size="sm" my="auto" mr={2}>
|
||||||
|
{t('system.start')}:{' '}
|
||||||
|
</Heading>
|
||||||
|
<Box w="170px">
|
||||||
|
<ReactDatePicker
|
||||||
|
selected={start}
|
||||||
|
onChange={onStartChange}
|
||||||
|
timeInputLabel={`${t('common.time')}: `}
|
||||||
|
dateFormat="dd/MM/yyyy hh:mm aa"
|
||||||
|
timeFormat="p"
|
||||||
|
showTimeSelect
|
||||||
|
// @ts-ignore
|
||||||
|
customInput={<CustomInputButton />}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
<Flex>
|
||||||
|
<Heading size="sm" my="auto" mr={4}>
|
||||||
|
{t('common.end')}:{' '}
|
||||||
|
</Heading>
|
||||||
|
<Box w="170px">
|
||||||
|
<ReactDatePicker
|
||||||
|
selected={end}
|
||||||
|
onChange={onEndChange}
|
||||||
|
timeInputLabel={`${t('common.time')}: `}
|
||||||
|
dateFormat="dd/MM/yyyy hh:mm aa"
|
||||||
|
timeFormat="p"
|
||||||
|
showTimeSelect
|
||||||
|
// @ts-ignore
|
||||||
|
customInput={<CustomInputButton />}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<Flex>
|
||||||
|
<Heading size="sm" my="auto" mr={2}>
|
||||||
|
{t('system.start')}:{' '}
|
||||||
|
</Heading>
|
||||||
|
<Box w="170px">
|
||||||
|
<ReactDatePicker
|
||||||
|
selected={start}
|
||||||
|
onChange={onStartChange}
|
||||||
|
timeInputLabel={`${t('common.time')}: `}
|
||||||
|
dateFormat="dd/MM/yyyy hh:mm aa"
|
||||||
|
timeFormat="p"
|
||||||
|
showTimeSelect
|
||||||
|
// @ts-ignore
|
||||||
|
customInput={<CustomInputButton />}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Heading size="sm" my="auto" mr={2}>
|
||||||
|
{t('common.end')}:{' '}
|
||||||
|
</Heading>
|
||||||
|
<Box w="170px">
|
||||||
|
<ReactDatePicker
|
||||||
|
selected={end}
|
||||||
|
onChange={onEndChange}
|
||||||
|
timeInputLabel={`${t('common.time')}: `}
|
||||||
|
dateFormat="dd/MM/yyyy hh:mm aa"
|
||||||
|
timeFormat="p"
|
||||||
|
showTimeSelect
|
||||||
|
// @ts-ignore
|
||||||
|
customInput={<CustomInputButton />}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
</PopoverBody>
|
||||||
|
</PopoverContent>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HistoryDatePickers;
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { Box, Button, Center, Flex, Heading, HStack, Spacer } from '@chakra-ui/react';
|
import { Box, Button, Center, Flex, Heading, HStack, Spacer } from '@chakra-ui/react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import HistoryDatePickers from '../DatePickers';
|
||||||
import DeleteHealthChecksModal from './DeleteModal';
|
import DeleteHealthChecksModal from './DeleteModal';
|
||||||
import useHealthCheckTable from './useHealthCheckTable';
|
import useHealthCheckTable from './useHealthCheckTable';
|
||||||
import { RefreshButton } from 'components/Buttons/RefreshButton';
|
import { RefreshButton } from 'components/Buttons/RefreshButton';
|
||||||
@@ -15,19 +16,35 @@ const HealthCheckHistory = ({ 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 { getHealthChecks, columns } = useHealthCheckTable({ serialNumber, limit });
|
const { time, setTime, getCustomHealthChecks, getHealthChecks, columns } = useHealthCheckTable({
|
||||||
|
serialNumber,
|
||||||
|
limit,
|
||||||
|
});
|
||||||
|
|
||||||
|
const setNewTime = (start: Date, end: Date) => {
|
||||||
|
setTime({ start, end });
|
||||||
|
};
|
||||||
|
const onClear = () => {
|
||||||
|
setTime(undefined);
|
||||||
|
};
|
||||||
const raiseLimit = () => {
|
const raiseLimit = () => {
|
||||||
setLimit(limit + 25);
|
setLimit(limit + 25);
|
||||||
};
|
};
|
||||||
|
|
||||||
const noMoreAvailable = getHealthChecks.data !== undefined && getHealthChecks.data.values.length < limit;
|
const noMoreAvailable = getHealthChecks.data !== undefined && getHealthChecks.data.values.length < limit;
|
||||||
|
|
||||||
|
const data = React.useMemo(() => {
|
||||||
|
if (getCustomHealthChecks.data) return getCustomHealthChecks.data.values.sort((a, b) => b.recorded - a.recorded);
|
||||||
|
if (getHealthChecks.data) return getHealthChecks.data.values;
|
||||||
|
return [];
|
||||||
|
}, [getHealthChecks.data, getCustomHealthChecks.data]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Flex>
|
<Flex>
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<HStack>
|
<HStack>
|
||||||
|
<HistoryDatePickers defaults={time} setTime={setNewTime} onClear={onClear} />
|
||||||
<ColumnPicker
|
<ColumnPicker
|
||||||
columns={columns as Column<unknown>[]}
|
columns={columns as Column<unknown>[]}
|
||||||
hiddenColumns={hiddenColumns}
|
hiddenColumns={hiddenColumns}
|
||||||
@@ -35,7 +52,13 @@ const HealthCheckHistory = ({ serialNumber }: Props) => {
|
|||||||
preference="gateway.device.healthchecks.hiddenColumns"
|
preference="gateway.device.healthchecks.hiddenColumns"
|
||||||
/>
|
/>
|
||||||
<DeleteHealthChecksModal serialNumber={serialNumber} />
|
<DeleteHealthChecksModal serialNumber={serialNumber} />
|
||||||
<RefreshButton isCompact isFetching={getHealthChecks.isFetching} onClick={getHealthChecks.refetch} ml={2} />
|
<RefreshButton
|
||||||
|
isCompact
|
||||||
|
isFetching={getHealthChecks.isFetching || getCustomHealthChecks.isFetching}
|
||||||
|
onClick={getHealthChecks.refetch}
|
||||||
|
ml={2}
|
||||||
|
colorScheme="blue"
|
||||||
|
/>
|
||||||
</HStack>
|
</HStack>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Box overflowY="auto" h="300px">
|
<Box overflowY="auto" h="300px">
|
||||||
@@ -48,8 +71,8 @@ const HealthCheckHistory = ({ serialNumber }: Props) => {
|
|||||||
accessor: string;
|
accessor: string;
|
||||||
}[]
|
}[]
|
||||||
}
|
}
|
||||||
data={getHealthChecks.data?.values ?? []}
|
data={data}
|
||||||
isLoading={getHealthChecks.isFetching}
|
isLoading={getHealthChecks.isFetching || getCustomHealthChecks.isFetching}
|
||||||
hiddenColumns={hiddenColumns}
|
hiddenColumns={hiddenColumns}
|
||||||
obj={t('controller.devices.healthchecks')}
|
obj={t('controller.devices.healthchecks')}
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@@ -57,7 +80,7 @@ const HealthCheckHistory = ({ serialNumber }: Props) => {
|
|||||||
showAllRows
|
showAllRows
|
||||||
/>
|
/>
|
||||||
{getHealthChecks.data !== undefined && (
|
{getHealthChecks.data !== undefined && (
|
||||||
<Center mt={2}>
|
<Center mt={2} hidden={getCustomHealthChecks.data !== undefined}>
|
||||||
{!noMoreAvailable || getHealthChecks.isFetching ? (
|
{!noMoreAvailable || getHealthChecks.isFetching ? (
|
||||||
<Button colorScheme="blue" onClick={raiseLimit} isLoading={getHealthChecks.isFetching}>
|
<Button colorScheme="blue" onClick={raiseLimit} isLoading={getHealthChecks.isFetching}>
|
||||||
{t('controller.devices.show_more')}
|
{t('controller.devices.show_more')}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import * as React from 'react';
|
|||||||
import { Badge, Box } from '@chakra-ui/react';
|
import { Badge, Box } from '@chakra-ui/react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import FormattedDate from 'components/InformationDisplays/FormattedDate';
|
import FormattedDate from 'components/InformationDisplays/FormattedDate';
|
||||||
import { HealthCheck, useGetHealthChecks } from 'hooks/Network/HealthChecks';
|
import { HealthCheck, useGetHealthChecks, useGetHealthChecksWithTimestamps } from 'hooks/Network/HealthChecks';
|
||||||
import { Column } from 'models/Table';
|
import { Column } from 'models/Table';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -13,6 +13,12 @@ type Props = {
|
|||||||
const useHealthCheckTable = ({ serialNumber, limit }: Props) => {
|
const useHealthCheckTable = ({ serialNumber, limit }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const getHealthChecks = useGetHealthChecks({ serialNumber, limit });
|
const getHealthChecks = useGetHealthChecks({ serialNumber, limit });
|
||||||
|
const [time, setTime] = React.useState<{ start: Date; end: Date } | undefined>();
|
||||||
|
const getCustomHealthChecks = useGetHealthChecksWithTimestamps({
|
||||||
|
serialNumber,
|
||||||
|
start: time ? Math.floor(time.start.getTime() / 1000) : undefined,
|
||||||
|
end: time ? Math.floor(time.end.getTime() / 1000) : undefined,
|
||||||
|
});
|
||||||
|
|
||||||
const dateCell = React.useCallback(
|
const dateCell = React.useCallback(
|
||||||
(v: number) => (
|
(v: number) => (
|
||||||
@@ -80,6 +86,9 @@ const useHealthCheckTable = ({ serialNumber, limit }: Props) => {
|
|||||||
return {
|
return {
|
||||||
columns,
|
columns,
|
||||||
getHealthChecks,
|
getHealthChecks,
|
||||||
|
getCustomHealthChecks,
|
||||||
|
time,
|
||||||
|
setTime,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { Box, Button, Center, Flex, Heading, HStack, Spacer } from '@chakra-ui/react';
|
import { Box, Button, Center, Flex, Heading, HStack, Spacer } from '@chakra-ui/react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import HistoryDatePickers from '../DatePickers';
|
||||||
import DeleteLogModal from './DeleteModal';
|
import DeleteLogModal from './DeleteModal';
|
||||||
import useDeviceLogsTable from './useDeviceLogsTable';
|
import useDeviceLogsTable from './useDeviceLogsTable';
|
||||||
import { RefreshButton } from 'components/Buttons/RefreshButton';
|
import { RefreshButton } from 'components/Buttons/RefreshButton';
|
||||||
@@ -15,19 +16,32 @@ 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 { getLogs, columns } = useDeviceLogsTable({ serialNumber, limit });
|
const { time, setTime, getCustomLogs, getLogs, columns } = useDeviceLogsTable({ serialNumber, limit });
|
||||||
|
|
||||||
|
const setNewTime = (start: Date, end: Date) => {
|
||||||
|
setTime({ start, end });
|
||||||
|
};
|
||||||
|
const onClear = () => {
|
||||||
|
setTime(undefined);
|
||||||
|
};
|
||||||
const raiseLimit = () => {
|
const raiseLimit = () => {
|
||||||
setLimit(limit + 25);
|
setLimit(limit + 25);
|
||||||
};
|
};
|
||||||
|
|
||||||
const noMoreAvailable = getLogs.data !== undefined && getLogs.data.values.length < limit;
|
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 (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Flex>
|
<Flex>
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<HStack>
|
<HStack>
|
||||||
|
<HistoryDatePickers defaults={time} setTime={setNewTime} onClear={onClear} />
|
||||||
<ColumnPicker
|
<ColumnPicker
|
||||||
columns={columns as Column<unknown>[]}
|
columns={columns as Column<unknown>[]}
|
||||||
hiddenColumns={hiddenColumns}
|
hiddenColumns={hiddenColumns}
|
||||||
@@ -35,7 +49,7 @@ const LogHistory = ({ serialNumber }: Props) => {
|
|||||||
preference="gateway.device.logs.hiddenColumns"
|
preference="gateway.device.logs.hiddenColumns"
|
||||||
/>
|
/>
|
||||||
<DeleteLogModal serialNumber={serialNumber} />
|
<DeleteLogModal serialNumber={serialNumber} />
|
||||||
<RefreshButton isCompact isFetching={getLogs.isFetching} onClick={getLogs.refetch} ml={2} />
|
<RefreshButton isCompact isFetching={getLogs.isFetching} onClick={getLogs.refetch} colorScheme="blue" />
|
||||||
</HStack>
|
</HStack>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Box overflowY="auto" h="300px">
|
<Box overflowY="auto" h="300px">
|
||||||
@@ -48,8 +62,8 @@ const LogHistory = ({ serialNumber }: Props) => {
|
|||||||
accessor: string;
|
accessor: string;
|
||||||
}[]
|
}[]
|
||||||
}
|
}
|
||||||
data={getLogs.data?.values ?? []}
|
data={data}
|
||||||
isLoading={getLogs.isFetching}
|
isLoading={getLogs.isFetching || getCustomLogs.isFetching}
|
||||||
hiddenColumns={hiddenColumns}
|
hiddenColumns={hiddenColumns}
|
||||||
obj={t('controller.devices.logs')}
|
obj={t('controller.devices.logs')}
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@@ -57,7 +71,7 @@ const LogHistory = ({ serialNumber }: Props) => {
|
|||||||
showAllRows
|
showAllRows
|
||||||
/>
|
/>
|
||||||
{getLogs.data !== undefined && (
|
{getLogs.data !== undefined && (
|
||||||
<Center mt={2}>
|
<Center mt={2} hidden={getCustomLogs.data !== undefined}>
|
||||||
{!noMoreAvailable || getLogs.isFetching ? (
|
{!noMoreAvailable || getLogs.isFetching ? (
|
||||||
<Button colorScheme="blue" onClick={raiseLimit} isLoading={getLogs.isFetching}>
|
<Button colorScheme="blue" onClick={raiseLimit} isLoading={getLogs.isFetching}>
|
||||||
{t('controller.devices.show_more')}
|
{t('controller.devices.show_more')}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import * as React from 'react';
|
|||||||
import { Box } from '@chakra-ui/react';
|
import { Box } from '@chakra-ui/react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import FormattedDate from 'components/InformationDisplays/FormattedDate';
|
import FormattedDate from 'components/InformationDisplays/FormattedDate';
|
||||||
import { DeviceLog, useGetDeviceLogs } from 'hooks/Network/DeviceLogs';
|
import { DeviceLog, useGetDeviceLogs, useGetDeviceLogsWithTimestamps } from 'hooks/Network/DeviceLogs';
|
||||||
import { Column } from 'models/Table';
|
import { Column } from 'models/Table';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -13,6 +13,12 @@ type Props = {
|
|||||||
const useDeviceLogsTable = ({ serialNumber, limit }: Props) => {
|
const useDeviceLogsTable = ({ serialNumber, limit }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const getLogs = useGetDeviceLogs({ serialNumber, limit });
|
const getLogs = useGetDeviceLogs({ serialNumber, limit });
|
||||||
|
const [time, setTime] = React.useState<{ start: Date; end: Date } | undefined>();
|
||||||
|
const getCustomLogs = useGetDeviceLogsWithTimestamps({
|
||||||
|
serialNumber,
|
||||||
|
start: time ? Math.floor(time.start.getTime() / 1000) : undefined,
|
||||||
|
end: time ? Math.floor(time.end.getTime() / 1000) : undefined,
|
||||||
|
});
|
||||||
|
|
||||||
const dateCell = React.useCallback(
|
const dateCell = React.useCallback(
|
||||||
(v: number) => (
|
(v: number) => (
|
||||||
@@ -76,6 +82,9 @@ const useDeviceLogsTable = ({ serialNumber, limit }: Props) => {
|
|||||||
return {
|
return {
|
||||||
columns,
|
columns,
|
||||||
getLogs,
|
getLogs,
|
||||||
|
getCustomLogs,
|
||||||
|
time,
|
||||||
|
setTime,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user