mirror of
https://github.com/optim-enterprises-bv/OptimCloud-gw-ui.git
synced 2025-10-29 17:32:20 +00:00
[WIFI-12360] Custom script run fix
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.9.0(13)",
|
"version": "2.9.0(16)",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "ucentral-client",
|
"name": "ucentral-client",
|
||||||
"version": "2.9.0(13)",
|
"version": "2.9.0(16)",
|
||||||
"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(13)",
|
"version": "2.9.0(16)",
|
||||||
"description": "",
|
"description": "",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "index.tsx",
|
"main": "index.tsx",
|
||||||
|
|||||||
@@ -44,12 +44,14 @@ const defaultProps = {
|
|||||||
sortBy: [],
|
sortBy: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DataTableProps = {
|
export type DataTableProps<TValue> = {
|
||||||
columns: readonly Column<object>[];
|
columns: Column<TValue>[];
|
||||||
data: object[];
|
data: TValue[];
|
||||||
count?: number;
|
count?: number;
|
||||||
setPageInfo?: React.Dispatch<React.SetStateAction<PageInfo | undefined>>;
|
setPageInfo?: React.Dispatch<React.SetStateAction<PageInfo | undefined>>;
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
|
onRowClick?: (row: TValue) => void;
|
||||||
|
isRowClickable?: (row: TValue) => boolean;
|
||||||
obj?: string;
|
obj?: string;
|
||||||
sortBy?: { id: string; desc: boolean }[];
|
sortBy?: { id: string; desc: boolean }[];
|
||||||
hiddenColumns?: string[];
|
hiddenColumns?: string[];
|
||||||
@@ -68,7 +70,7 @@ type TableInstanceWithHooks<T extends object> = TableInstance<T> &
|
|||||||
state: UsePaginationState<T>;
|
state: UsePaginationState<T>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const _DataTable = ({
|
const _DataTable = <TValue extends object>({
|
||||||
columns,
|
columns,
|
||||||
data,
|
data,
|
||||||
isLoading,
|
isLoading,
|
||||||
@@ -84,7 +86,9 @@ const _DataTable = ({
|
|||||||
isManual,
|
isManual,
|
||||||
saveSettingsId,
|
saveSettingsId,
|
||||||
showAllRows,
|
showAllRows,
|
||||||
}: DataTableProps) => {
|
onRowClick,
|
||||||
|
isRowClickable,
|
||||||
|
}: DataTableProps<TValue>) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const breakpoint = useBreakpoint();
|
const breakpoint = useBreakpoint();
|
||||||
const textColor = useColorModeValue('gray.700', 'white');
|
const textColor = useColorModeValue('gray.700', 'white');
|
||||||
@@ -143,7 +147,7 @@ const _DataTable = ({
|
|||||||
},
|
},
|
||||||
useSortBy,
|
useSortBy,
|
||||||
usePagination,
|
usePagination,
|
||||||
) as TableInstanceWithHooks<object>;
|
) as TableInstanceWithHooks<TValue>;
|
||||||
|
|
||||||
const handleGoToPage = (newPage: number) => {
|
const handleGoToPage = (newPage: number) => {
|
||||||
if (saveSettingsId) localStorage.setItem(`${saveSettingsId}.page`, String(newPage));
|
if (saveSettingsId) localStorage.setItem(`${saveSettingsId}.page`, String(newPage));
|
||||||
@@ -260,8 +264,10 @@ const _DataTable = ({
|
|||||||
</Thead>
|
</Thead>
|
||||||
{data.length > 0 && (
|
{data.length > 0 && (
|
||||||
<Tbody {...getTableBodyProps()}>
|
<Tbody {...getTableBodyProps()}>
|
||||||
{page.map((row: Row) => {
|
{page.map((row: Row<TValue>) => {
|
||||||
prepareRow(row);
|
prepareRow(row);
|
||||||
|
const rowIsClickable = isRowClickable ? isRowClickable(row.original) : true;
|
||||||
|
const onClick = rowIsClickable && onRowClick ? () => onRowClick(row.original) : undefined;
|
||||||
return (
|
return (
|
||||||
<Tr
|
<Tr
|
||||||
{...row.getRowProps()}
|
{...row.getRowProps()}
|
||||||
@@ -269,6 +275,7 @@ const _DataTable = ({
|
|||||||
_hover={{
|
_hover={{
|
||||||
backgroundColor: hoveredRowBg,
|
backgroundColor: hoveredRowBg,
|
||||||
}}
|
}}
|
||||||
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@@ -288,8 +295,26 @@ const _DataTable = ({
|
|||||||
fontSize="14px"
|
fontSize="14px"
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
textAlign={cell.column.isCentered ? 'center' : undefined}
|
textAlign={cell.column.isCentered ? 'center' : undefined}
|
||||||
|
fontFamily={
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
fontFamily={cell.column.isMonospace ? 'monospace' : undefined}
|
cell.column.isMonospace
|
||||||
|
? 'Inter, SFMono-Regular, Menlo, Monaco, Consolas, monospace'
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
onClick={
|
||||||
|
// @ts-ignore
|
||||||
|
cell.column.stopPropagation || (cell.column.id === 'actions' && onRowClick)
|
||||||
|
? (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
cursor={
|
||||||
|
// @ts-ignore
|
||||||
|
!cell.column.stopPropagation && cell.column.id !== 'actions' && onRowClick
|
||||||
|
? 'pointer'
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{cell.render('Cell')}
|
{cell.render('Cell')}
|
||||||
</Td>
|
</Td>
|
||||||
@@ -414,4 +439,4 @@ const _DataTable = ({
|
|||||||
|
|
||||||
_DataTable.defaultProps = defaultProps;
|
_DataTable.defaultProps = defaultProps;
|
||||||
|
|
||||||
export const DataTable = React.memo(_DataTable);
|
export const DataTable = React.memo(_DataTable) as unknown as typeof _DataTable;
|
||||||
|
|||||||
@@ -249,8 +249,12 @@ const SortableDataTable: React.FC<Props> = ({
|
|||||||
fontSize="14px"
|
fontSize="14px"
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
textAlign={cell.column.isCentered ? 'center' : undefined}
|
textAlign={cell.column.isCentered ? 'center' : undefined}
|
||||||
|
fontFamily={
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
fontFamily={cell.column.isMonospace ? 'monospace' : undefined}
|
cell.column.isMonospace
|
||||||
|
? 'Inter, SFMono-Regular, Menlo, Monaco, Consolas, monospace'
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{cell.render('Cell')}
|
{cell.render('Cell')}
|
||||||
</Td>
|
</Td>
|
||||||
|
|||||||
@@ -5,17 +5,22 @@ import {
|
|||||||
AlertIcon,
|
AlertIcon,
|
||||||
AlertTitle,
|
AlertTitle,
|
||||||
Box,
|
Box,
|
||||||
|
Button,
|
||||||
|
Flex,
|
||||||
FormControl,
|
FormControl,
|
||||||
FormErrorMessage,
|
FormErrorMessage,
|
||||||
FormLabel,
|
FormLabel,
|
||||||
Textarea,
|
Textarea,
|
||||||
useToast,
|
useToast,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
|
import { ClipboardText } from 'phosphor-react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { SaveButton } from '../../Buttons/SaveButton';
|
import { SaveButton } from '../../Buttons/SaveButton';
|
||||||
import { Modal } from '../Modal';
|
import { Modal } from '../Modal';
|
||||||
import { FileInputButton } from 'components/Buttons/FileInputButton';
|
import { FileInputButton } from 'components/Buttons/FileInputButton';
|
||||||
import { useConfigureDevice } from 'hooks/Network/Commands';
|
import { useConfigureDevice } from 'hooks/Network/Commands';
|
||||||
|
import { useGetDevice } from 'hooks/Network/Devices';
|
||||||
|
import { AxiosError } from 'models/Axios';
|
||||||
|
|
||||||
export type ConfigureModalProps = {
|
export type ConfigureModalProps = {
|
||||||
serialNumber: string;
|
serialNumber: string;
|
||||||
@@ -29,11 +34,17 @@ export const ConfigureModal = ({ serialNumber, modalProps }: ConfigureModalProps
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const configure = useConfigureDevice({ serialNumber });
|
const configure = useConfigureDevice({ serialNumber });
|
||||||
|
const getDevice = useGetDevice({ serialNumber });
|
||||||
|
|
||||||
const [newConfig, setNewConfig] = React.useState('');
|
const [newConfig, setNewConfig] = React.useState('');
|
||||||
|
|
||||||
const onChange = React.useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
const onChange = React.useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
setNewConfig(e.target.value);
|
setNewConfig(e.target.value);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const onImportConfiguration = () => {
|
||||||
|
setNewConfig(getDevice.data?.configuration ? JSON.stringify(getDevice.data.configuration, null, 4) : '');
|
||||||
|
};
|
||||||
const isValid = React.useMemo(() => {
|
const isValid = React.useMemo(() => {
|
||||||
try {
|
try {
|
||||||
JSON.parse(newConfig);
|
JSON.parse(newConfig);
|
||||||
@@ -60,9 +71,7 @@ export const ConfigureModal = ({ serialNumber, modalProps }: ConfigureModalProps
|
|||||||
modalProps.onClose();
|
modalProps.onClose();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {}
|
||||||
// console.log(e);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -79,10 +88,7 @@ export const ConfigureModal = ({ serialNumber, modalProps }: ConfigureModalProps
|
|||||||
<AlertIcon />
|
<AlertIcon />
|
||||||
<Box>
|
<Box>
|
||||||
<AlertTitle>{t('common.error')}</AlertTitle>
|
<AlertTitle>{t('common.error')}</AlertTitle>
|
||||||
{
|
<AlertDescription>{(configure.error as AxiosError)?.response?.data?.ErrorDescription}</AlertDescription>
|
||||||
// @ts-ignore
|
|
||||||
<AlertDescription>{configure.error?.response?.data?.ErrorDescription}</AlertDescription>
|
|
||||||
}
|
|
||||||
</Box>
|
</Box>
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
@@ -92,7 +98,8 @@ export const ConfigureModal = ({ serialNumber, modalProps }: ConfigureModalProps
|
|||||||
</Alert>
|
</Alert>
|
||||||
<FormControl isInvalid={!isValid && newConfig.length > 0}>
|
<FormControl isInvalid={!isValid && newConfig.length > 0}>
|
||||||
<FormLabel>{t('configurations.one')}</FormLabel>
|
<FormLabel>{t('configurations.one')}</FormLabel>
|
||||||
<Box mb={2} w="240px">
|
<Flex mb={2}>
|
||||||
|
<Box w="240px">
|
||||||
<FileInputButton
|
<FileInputButton
|
||||||
value={newConfig}
|
value={newConfig}
|
||||||
setValue={(v) => setNewConfig(v)}
|
setValue={(v) => setNewConfig(v)}
|
||||||
@@ -101,6 +108,15 @@ export const ConfigureModal = ({ serialNumber, modalProps }: ConfigureModalProps
|
|||||||
isStringFile
|
isStringFile
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
<Button
|
||||||
|
rightIcon={<ClipboardText size={20} />}
|
||||||
|
onClick={onImportConfiguration}
|
||||||
|
hidden={!getDevice.data}
|
||||||
|
ml={2}
|
||||||
|
>
|
||||||
|
Current Configuration
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
<Textarea height="auto" minH="600px" value={newConfig} onChange={onChange} />
|
<Textarea height="auto" minH="600px" value={newConfig} onChange={onChange} />
|
||||||
<FormErrorMessage>{t('controller.configure.invalid')}</FormErrorMessage>
|
<FormErrorMessage>{t('controller.configure.invalid')}</FormErrorMessage>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|||||||
@@ -60,8 +60,14 @@ export const ScriptModal = ({ device, modalProps }: ScriptModalProps) => {
|
|||||||
let requestData: {
|
let requestData: {
|
||||||
[k: string]: unknown;
|
[k: string]: unknown;
|
||||||
serialNumber: string;
|
serialNumber: string;
|
||||||
|
script?: string;
|
||||||
timeout?: number | undefined;
|
timeout?: number | undefined;
|
||||||
} = data;
|
} = data;
|
||||||
|
|
||||||
|
if (requestData.script) {
|
||||||
|
requestData.script = btoa(requestData.script);
|
||||||
|
}
|
||||||
|
|
||||||
if (selectedScript === 'diagnostics') {
|
if (selectedScript === 'diagnostics') {
|
||||||
requestData = {
|
requestData = {
|
||||||
serialNumber: device?.serialNumber ?? '',
|
serialNumber: device?.serialNumber ?? '',
|
||||||
@@ -88,6 +94,19 @@ export const ScriptModal = ({ device, modalProps }: ScriptModalProps) => {
|
|||||||
setValue(response.results?.status?.result ?? JSON.stringify(response.results ?? {}, null, 2));
|
setValue(response.results?.status?.result ?? JSON.stringify(response.results ?? {}, null, 2));
|
||||||
queryClient.invalidateQueries(['commands', device?.serialNumber ?? '']);
|
queryClient.invalidateQueries(['commands', device?.serialNumber ?? '']);
|
||||||
},
|
},
|
||||||
|
onError: (e) => {
|
||||||
|
if (axios.isAxiosError(e) && e.response?.data?.ErrorDescription) {
|
||||||
|
toast({
|
||||||
|
id: 'script-update-error',
|
||||||
|
title: t('common.error'),
|
||||||
|
description: e.response?.data?.ErrorDescription,
|
||||||
|
status: 'error',
|
||||||
|
duration: 5000,
|
||||||
|
isClosable: true,
|
||||||
|
position: 'top-right',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
});
|
});
|
||||||
if (!waitForResponse) {
|
if (!waitForResponse) {
|
||||||
toast({
|
toast({
|
||||||
|
|||||||
@@ -110,6 +110,18 @@ export const useDeleteCommand = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useGetSingleCommandHistory = ({ serialNumber, commandId }: { serialNumber: string; commandId: string }) =>
|
||||||
|
useQuery(
|
||||||
|
['commands', serialNumber, commandId],
|
||||||
|
() =>
|
||||||
|
axiosGw
|
||||||
|
.get(`command/${commandId}?serialNumber=${serialNumber}`)
|
||||||
|
.then((response) => response.data as DeviceCommandHistory),
|
||||||
|
{
|
||||||
|
enabled: serialNumber !== undefined && serialNumber !== '' && commandId !== undefined && commandId !== '',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export type EventQueueResponse = {
|
export type EventQueueResponse = {
|
||||||
UUID: string;
|
UUID: string;
|
||||||
attachFile: number;
|
attachFile: number;
|
||||||
@@ -245,6 +257,7 @@ export const useDeviceScript = ({ serialNumber }: { serialNumber: string }) => {
|
|||||||
queryClient.invalidateQueries(['commands', serialNumber]);
|
queryClient.invalidateQueries(['commands', serialNumber]);
|
||||||
},
|
},
|
||||||
onError: (e) => {
|
onError: (e) => {
|
||||||
|
queryClient.invalidateQueries(['commands', serialNumber]);
|
||||||
if (axios.isAxiosError(e)) {
|
if (axios.isAxiosError(e)) {
|
||||||
toast({
|
toast({
|
||||||
id: 'script-error',
|
id: 'script-error',
|
||||||
|
|||||||
@@ -1,6 +1,18 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { Flex, Heading, Tooltip, VStack } from '@chakra-ui/react';
|
import {
|
||||||
|
Box,
|
||||||
|
CircularProgress,
|
||||||
|
CircularProgressLabel,
|
||||||
|
Flex,
|
||||||
|
Heading,
|
||||||
|
Icon,
|
||||||
|
Text,
|
||||||
|
Tooltip,
|
||||||
|
VStack,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import { ArrowSquareDown, ArrowSquareUp, Clock } from 'phosphor-react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { Card } from 'components/Containers/Card';
|
||||||
import { compactSecondsToDetailed, minimalSecondsToDetailed } from 'helpers/dateFormatting';
|
import { compactSecondsToDetailed, minimalSecondsToDetailed } from 'helpers/dateFormatting';
|
||||||
import { bytesString } from 'helpers/stringHelper';
|
import { bytesString } from 'helpers/stringHelper';
|
||||||
import { useGetDevicesStats } from 'hooks/Network/Devices';
|
import { useGetDevicesStats } from 'hooks/Network/Devices';
|
||||||
@@ -11,18 +23,19 @@ const SidebarDevices = () => {
|
|||||||
const [lastTime, setLastTime] = React.useState<Date | undefined>();
|
const [lastTime, setLastTime] = React.useState<Date | undefined>();
|
||||||
const [lastUpdate, setLastUpdate] = React.useState<Date | undefined>();
|
const [lastUpdate, setLastUpdate] = React.useState<Date | undefined>();
|
||||||
|
|
||||||
const getTime = () => {
|
const time = React.useMemo(() => {
|
||||||
if (lastTime === undefined || lastUpdate === undefined) return null;
|
if (lastTime === undefined || lastUpdate === undefined) return null;
|
||||||
|
|
||||||
const seconds = lastTime.getTime() - lastUpdate.getTime();
|
const seconds = lastTime.getTime() - lastUpdate.getTime();
|
||||||
|
|
||||||
return Math.max(0, Math.floor(seconds / 1000));
|
return Math.max(0, Math.floor(seconds / 1000));
|
||||||
};
|
}, [lastTime, lastUpdate]);
|
||||||
|
|
||||||
const refresh = () => {
|
const circleColor = () => {
|
||||||
if (document.visibilityState !== 'hidden') {
|
if (time === null) return 'gray.300';
|
||||||
getStats.refetch();
|
if (time < 10) return 'green.300';
|
||||||
}
|
if (time < 30) return 'yellow.300';
|
||||||
|
return 'red.300';
|
||||||
};
|
};
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
@@ -38,47 +51,60 @@ const SidebarDevices = () => {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
document.addEventListener('visibilitychange', refresh);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener('visibilitychange', refresh);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
if (!getStats.data) return null;
|
if (!getStats.data) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<Card borderWidth="2px">
|
||||||
|
<Tooltip hasArrow label={t('controller.stats.seconds_ago', { s: time })}>
|
||||||
|
<CircularProgress
|
||||||
|
isIndeterminate
|
||||||
|
color={circleColor()}
|
||||||
|
position="absolute"
|
||||||
|
right="6px"
|
||||||
|
top="6px"
|
||||||
|
w="unset"
|
||||||
|
size={6}
|
||||||
|
thickness="14px"
|
||||||
|
>
|
||||||
|
<CircularProgressLabel fontSize="1.9em">{time}s</CircularProgressLabel>
|
||||||
|
</CircularProgress>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip hasArrow label={t('controller.stats.seconds_ago', { s: time })}>
|
||||||
|
<Box position="absolute" right="8px" top="8px" w="unset" hidden>
|
||||||
|
<Clock size={16} />
|
||||||
|
</Box>
|
||||||
|
</Tooltip>
|
||||||
<VStack mb={-1}>
|
<VStack mb={-1}>
|
||||||
<Flex flexDir="column" textAlign="center">
|
<Flex flexDir="column" textAlign="center">
|
||||||
<Heading size="md">{getStats.data.connectedDevices}</Heading>
|
<Heading size="md">{getStats.data.connectedDevices}</Heading>
|
||||||
<Heading size="xs">
|
<Heading size="xs" display="flex" justifyContent="center">
|
||||||
{t('common.connected')} {t('devices.title')}
|
<Text>
|
||||||
</Heading>
|
{t('common.connected')} {t('devices.title')}{' '}
|
||||||
<Heading size="xs" mt={1} fontStyle="italic" fontWeight="normal" color="gray.400">
|
</Text>{' '}
|
||||||
({getStats.data.connectingDevices} {t('controller.devices.connecting')})
|
|
||||||
</Heading>
|
|
||||||
<Heading
|
|
||||||
size="xs"
|
|
||||||
mt={1}
|
|
||||||
fontStyle="italic"
|
|
||||||
fontWeight="normal"
|
|
||||||
color="gray.400"
|
|
||||||
hidden={getStats.data.rx === undefined || getStats.data.tx === undefined}
|
|
||||||
>
|
|
||||||
Rx: {bytesString(getStats.data.rx)}, Tx: {bytesString(getStats.data.tx)}
|
|
||||||
</Heading>
|
</Heading>
|
||||||
<Tooltip hasArrow label={compactSecondsToDetailed(getStats.data.averageConnectionTime, t)}>
|
<Tooltip hasArrow label={compactSecondsToDetailed(getStats.data.averageConnectionTime, t)}>
|
||||||
<Heading size="md" textAlign="center" mt={2}>
|
<Heading size="md" textAlign="center" mt={1}>
|
||||||
{minimalSecondsToDetailed(getStats.data.averageConnectionTime, t)}
|
{minimalSecondsToDetailed(getStats.data.averageConnectionTime, t)}
|
||||||
</Heading>
|
</Heading>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Heading size="xs">{t('controller.devices.average_uptime')}</Heading>
|
<Heading size="xs">{t('controller.devices.average_uptime')}</Heading>
|
||||||
<Heading size="xs" mt={2} fontStyle="italic" fontWeight="normal" color="gray.400">
|
<Flex fontSize="sm" fontWeight="bold" alignItems="center" justifyContent="center" mt={1}>
|
||||||
{t('controller.stats.seconds_ago', { s: getTime() })}
|
<Tooltip hasArrow label="Rx">
|
||||||
</Heading>
|
<Flex alignItems="center" mr={1}>
|
||||||
|
<Icon as={ArrowSquareUp} weight="bold" boxSize={5} mt="1px" color="blue.400" />{' '}
|
||||||
|
{getStats.data.rx !== undefined ? bytesString(getStats.data.rx, 0) : '-'}
|
||||||
|
</Flex>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip hasArrow label="Tx">
|
||||||
|
<Flex alignItems="center">
|
||||||
|
<Icon as={ArrowSquareDown} weight="bold" boxSize={5} mt="1px" color="purple.400" />{' '}
|
||||||
|
{getStats.data.tx !== undefined ? bytesString(getStats.data.tx, 0) : '-'}
|
||||||
|
</Flex>
|
||||||
|
</Tooltip>
|
||||||
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
</VStack>
|
</VStack>
|
||||||
|
</Card>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export interface Column<T> {
|
|||||||
alwaysShow?: boolean;
|
alwaysShow?: boolean;
|
||||||
Footer?: string;
|
Footer?: string;
|
||||||
accessor?: string;
|
accessor?: string;
|
||||||
|
stopPropagation?: boolean;
|
||||||
disableSortBy?: boolean;
|
disableSortBy?: boolean;
|
||||||
hasPopover?: boolean;
|
hasPopover?: boolean;
|
||||||
customMaxWidth?: string;
|
customMaxWidth?: string;
|
||||||
|
|||||||
@@ -99,13 +99,14 @@ const DefaultConfigurationsList = () => {
|
|||||||
<CardBody>
|
<CardBody>
|
||||||
<Box overflowX="auto" w="100%">
|
<Box overflowX="auto" w="100%">
|
||||||
<LoadingOverlay isLoading={getConfigs.isFetching}>
|
<LoadingOverlay isLoading={getConfigs.isFetching}>
|
||||||
<DataTable
|
<DataTable<DefaultConfigurationResponse>
|
||||||
columns={columns as Column<object>[]}
|
columns={columns}
|
||||||
saveSettingsId="firmware.table"
|
saveSettingsId="firmware.table"
|
||||||
data={getConfigs.data ?? []}
|
data={getConfigs.data ?? []}
|
||||||
obj={t('controller.configurations.title')}
|
obj={t('controller.configurations.title')}
|
||||||
minHeight="200px"
|
minHeight="200px"
|
||||||
sortBy={[{ id: 'name', desc: true }]}
|
sortBy={[{ id: 'name', desc: true }]}
|
||||||
|
onRowClick={onViewDetails}
|
||||||
/>
|
/>
|
||||||
</LoadingOverlay>
|
</LoadingOverlay>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -35,7 +35,10 @@ export const useStatisticsCard = ({ serialNumber }: Props) => {
|
|||||||
const parsedData = React.useMemo(() => {
|
const parsedData = React.useMemo(() => {
|
||||||
if (!getStats.data && !getCustomStats.data) return undefined;
|
if (!getStats.data && !getCustomStats.data) return undefined;
|
||||||
|
|
||||||
const data: Record<string, { tx: number[]; rx: number[]; recorded: number[]; maxRx: number; maxTx: number }> = {};
|
const data: Record<
|
||||||
|
string,
|
||||||
|
{ tx: number[]; rx: number[]; recorded: number[]; maxRx: number; maxTx: number; removed?: boolean }
|
||||||
|
> = {};
|
||||||
const memoryData = {
|
const memoryData = {
|
||||||
used: [] as number[],
|
used: [] as number[],
|
||||||
buffered: [] as number[],
|
buffered: [] as number[],
|
||||||
@@ -100,6 +103,18 @@ export const useStatisticsCard = ({ serialNumber }: Props) => {
|
|||||||
maxRx: rxDelta,
|
maxRx: rxDelta,
|
||||||
};
|
};
|
||||||
else {
|
else {
|
||||||
|
if (data[inter.name] && !data[inter.name]?.removed && data[inter.name]?.recorded.length === 1) {
|
||||||
|
data[inter.name]?.tx.shift();
|
||||||
|
data[inter.name]?.rx.shift();
|
||||||
|
data[inter.name]?.recorded.shift();
|
||||||
|
// @ts-ignore
|
||||||
|
data[inter.name].maxRx = rxDelta;
|
||||||
|
// @ts-ignore
|
||||||
|
data[inter.name].maxTx = txDelta;
|
||||||
|
// @ts-ignore
|
||||||
|
data[inter.name].removed = true;
|
||||||
|
}
|
||||||
|
|
||||||
data[inter.name]?.rx.push(rxDelta);
|
data[inter.name]?.rx.push(rxDelta);
|
||||||
data[inter.name]?.tx.push(txDelta);
|
data[inter.name]?.tx.push(txDelta);
|
||||||
data[inter.name]?.recorded.push(stat.recorded);
|
data[inter.name]?.recorded.push(stat.recorded);
|
||||||
|
|||||||
@@ -1,8 +1,18 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
|
Button,
|
||||||
|
Center,
|
||||||
Heading,
|
Heading,
|
||||||
HStack,
|
HStack,
|
||||||
|
Popover,
|
||||||
|
PopoverArrow,
|
||||||
|
PopoverBody,
|
||||||
|
PopoverCloseButton,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverFooter,
|
||||||
|
PopoverHeader,
|
||||||
|
PopoverTrigger,
|
||||||
Portal,
|
Portal,
|
||||||
Spacer,
|
Spacer,
|
||||||
Tag,
|
Tag,
|
||||||
@@ -12,10 +22,13 @@ import {
|
|||||||
useBreakpoint,
|
useBreakpoint,
|
||||||
useColorModeValue,
|
useColorModeValue,
|
||||||
useDisclosure,
|
useDisclosure,
|
||||||
|
useToast,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
|
import axios from 'axios';
|
||||||
import { Heart, HeartBreak, LockSimple, LockSimpleOpen, WifiHigh, WifiSlash } from 'phosphor-react';
|
import { Heart, HeartBreak, LockSimple, LockSimpleOpen, WifiHigh, WifiSlash } from 'phosphor-react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import Masonry from 'react-masonry-css';
|
import Masonry from 'react-masonry-css';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
import DeviceDetails from './Details';
|
import DeviceDetails from './Details';
|
||||||
import DeviceLogsCard from './LogsCard';
|
import DeviceLogsCard from './LogsCard';
|
||||||
import DeviceNotes from './Notes';
|
import DeviceNotes from './Notes';
|
||||||
@@ -23,6 +36,7 @@ import RestrictionsCard from './RestrictionsCard';
|
|||||||
import DeviceStatisticsCard from './StatisticsCard';
|
import DeviceStatisticsCard from './StatisticsCard';
|
||||||
import DeviceSummary from './Summary';
|
import DeviceSummary from './Summary';
|
||||||
import WifiAnalysisCard from './WifiAnalysis';
|
import WifiAnalysisCard from './WifiAnalysis';
|
||||||
|
import { DeleteButton } from 'components/Buttons/DeleteButton';
|
||||||
import DeviceActionDropdown from 'components/Buttons/DeviceActionDropdown';
|
import DeviceActionDropdown from 'components/Buttons/DeviceActionDropdown';
|
||||||
import { RefreshButton } from 'components/Buttons/RefreshButton';
|
import { RefreshButton } from 'components/Buttons/RefreshButton';
|
||||||
import { Card } from 'components/Containers/Card';
|
import { Card } from 'components/Containers/Card';
|
||||||
@@ -38,7 +52,7 @@ import { useScriptModal } from 'components/Modals/ScriptModal/useScriptModal';
|
|||||||
import { TelemetryModal } from 'components/Modals/TelemetryModal';
|
import { TelemetryModal } from 'components/Modals/TelemetryModal';
|
||||||
import { TraceModal } from 'components/Modals/TraceModal';
|
import { TraceModal } from 'components/Modals/TraceModal';
|
||||||
import { WifiScanModal } from 'components/Modals/WifiScanModal';
|
import { WifiScanModal } from 'components/Modals/WifiScanModal';
|
||||||
import { useGetDevice, useGetDeviceHealthChecks, useGetDeviceStatus } from 'hooks/Network/Devices';
|
import { useDeleteDevice, useGetDevice, useGetDeviceHealthChecks, useGetDeviceStatus } from 'hooks/Network/Devices';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
serialNumber: string;
|
serialNumber: string;
|
||||||
@@ -46,10 +60,16 @@ type Props = {
|
|||||||
|
|
||||||
const DevicePageWrapper = ({ serialNumber }: Props) => {
|
const DevicePageWrapper = ({ serialNumber }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const toast = useToast();
|
||||||
const breakpoint = useBreakpoint();
|
const breakpoint = useBreakpoint();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { mutateAsync: deleteDevice, isLoading: isDeleting } = useDeleteDevice({
|
||||||
|
serialNumber,
|
||||||
|
});
|
||||||
const getDevice = useGetDevice({ serialNumber });
|
const getDevice = useGetDevice({ serialNumber });
|
||||||
const getStatus = useGetDeviceStatus({ serialNumber });
|
const getStatus = useGetDeviceStatus({ serialNumber });
|
||||||
const getHealth = useGetDeviceHealthChecks({ serialNumber, limit: 1 });
|
const getHealth = useGetDeviceHealthChecks({ serialNumber, limit: 1 });
|
||||||
|
const { isOpen: isDeleteOpen, onOpen: onDeleteOpen, onClose: onDeleteClose } = useDisclosure();
|
||||||
const scanModalProps = useDisclosure();
|
const scanModalProps = useDisclosure();
|
||||||
const resetModalProps = useDisclosure();
|
const resetModalProps = useDisclosure();
|
||||||
const eventQueueProps = useDisclosure();
|
const eventQueueProps = useDisclosure();
|
||||||
@@ -62,6 +82,35 @@ const DevicePageWrapper = ({ serialNumber }: Props) => {
|
|||||||
// Sticky-top styles
|
// Sticky-top styles
|
||||||
const isCompact = breakpoint === 'base' || breakpoint === 'sm' || breakpoint === 'md';
|
const isCompact = breakpoint === 'base' || breakpoint === 'sm' || breakpoint === 'md';
|
||||||
const boxShadow = useColorModeValue('0px 7px 23px rgba(0, 0, 0, 0.05)', 'none');
|
const boxShadow = useColorModeValue('0px 7px 23px rgba(0, 0, 0, 0.05)', 'none');
|
||||||
|
|
||||||
|
const handleDeleteClick = () =>
|
||||||
|
deleteDevice(serialNumber, {
|
||||||
|
onSuccess: () => {
|
||||||
|
toast({
|
||||||
|
id: `delete-device-success-${serialNumber}`,
|
||||||
|
title: t('common.success'),
|
||||||
|
status: 'success',
|
||||||
|
duration: 5000,
|
||||||
|
isClosable: true,
|
||||||
|
position: 'top-right',
|
||||||
|
});
|
||||||
|
navigate('/devices');
|
||||||
|
},
|
||||||
|
onError: (e) => {
|
||||||
|
if (axios.isAxiosError(e)) {
|
||||||
|
toast({
|
||||||
|
id: `delete-device-error-${serialNumber}`,
|
||||||
|
title: t('common.error'),
|
||||||
|
description: e.response?.data?.ErrorDescription,
|
||||||
|
status: 'error',
|
||||||
|
duration: 5000,
|
||||||
|
isClosable: true,
|
||||||
|
position: 'top-right',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const connectedTag = React.useMemo(() => {
|
const connectedTag = React.useMemo(() => {
|
||||||
if (!getStatus.data) return null;
|
if (!getStatus.data) return null;
|
||||||
|
|
||||||
@@ -148,6 +197,7 @@ const DevicePageWrapper = ({ serialNumber }: Props) => {
|
|||||||
<Spacer />
|
<Spacer />
|
||||||
<HStack spacing={2}>
|
<HStack spacing={2}>
|
||||||
{breakpoint !== 'base' && breakpoint !== 'md' && <DeviceSearchBar />}
|
{breakpoint !== 'base' && breakpoint !== 'md' && <DeviceSearchBar />}
|
||||||
|
|
||||||
{getDevice?.data && (
|
{getDevice?.data && (
|
||||||
<DeviceActionDropdown
|
<DeviceActionDropdown
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@@ -166,6 +216,33 @@ const DevicePageWrapper = ({ serialNumber }: Props) => {
|
|||||||
isCompact
|
isCompact
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
<Popover isOpen={isDeleteOpen} onOpen={onDeleteOpen} onClose={onDeleteClose}>
|
||||||
|
<Tooltip hasArrow label={t('crud.delete')} placement="top" isDisabled={isDeleteOpen}>
|
||||||
|
<Box>
|
||||||
|
<PopoverTrigger>
|
||||||
|
<DeleteButton isCompact onClick={() => {}} />
|
||||||
|
</PopoverTrigger>
|
||||||
|
</Box>
|
||||||
|
</Tooltip>
|
||||||
|
<PopoverContent>
|
||||||
|
<PopoverArrow />
|
||||||
|
<PopoverCloseButton />
|
||||||
|
<PopoverHeader>
|
||||||
|
{t('crud.delete')} {serialNumber}
|
||||||
|
</PopoverHeader>
|
||||||
|
<PopoverBody>{t('crud.delete_confirm', { obj: t('devices.one') })}</PopoverBody>
|
||||||
|
<PopoverFooter>
|
||||||
|
<Center>
|
||||||
|
<Button colorScheme="gray" mr="1" onClick={onDeleteClose}>
|
||||||
|
{t('common.cancel')}
|
||||||
|
</Button>
|
||||||
|
<Button colorScheme="red" ml="1" onClick={handleDeleteClick} isLoading={isDeleting}>
|
||||||
|
{t('common.yes')}
|
||||||
|
</Button>
|
||||||
|
</Center>
|
||||||
|
</PopoverFooter>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
<RefreshButton
|
<RefreshButton
|
||||||
onClick={refresh}
|
onClick={refresh}
|
||||||
isFetching={getDevice.isFetching || getHealth.isFetching || getStatus.isFetching}
|
isFetching={getDevice.isFetching || getHealth.isFetching || getStatus.isFetching}
|
||||||
@@ -214,6 +291,33 @@ const DevicePageWrapper = ({ serialNumber }: Props) => {
|
|||||||
size="md"
|
size="md"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
<Popover isOpen={isDeleteOpen} onOpen={onDeleteOpen} onClose={onDeleteClose}>
|
||||||
|
<Tooltip hasArrow label={t('crud.delete')} placement="top" isDisabled={isDeleteOpen}>
|
||||||
|
<Box>
|
||||||
|
<PopoverTrigger>
|
||||||
|
<DeleteButton isCompact onClick={() => {}} />
|
||||||
|
</PopoverTrigger>
|
||||||
|
</Box>
|
||||||
|
</Tooltip>
|
||||||
|
<PopoverContent>
|
||||||
|
<PopoverArrow />
|
||||||
|
<PopoverCloseButton />
|
||||||
|
<PopoverHeader>
|
||||||
|
{t('crud.delete')} {serialNumber}
|
||||||
|
</PopoverHeader>
|
||||||
|
<PopoverBody>{t('crud.delete_confirm', { obj: t('devices.one') })}</PopoverBody>
|
||||||
|
<PopoverFooter>
|
||||||
|
<Center>
|
||||||
|
<Button colorScheme="gray" mr="1" onClick={onDeleteClose}>
|
||||||
|
{t('common.cancel')}
|
||||||
|
</Button>
|
||||||
|
<Button colorScheme="red" ml="1" onClick={handleDeleteClick} isLoading={isDeleting}>
|
||||||
|
{t('common.yes')}
|
||||||
|
</Button>
|
||||||
|
</Center>
|
||||||
|
</PopoverFooter>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
<RefreshButton
|
<RefreshButton
|
||||||
onClick={refresh}
|
onClick={refresh}
|
||||||
isFetching={getDevice.isFetching || getHealth.isFetching || getStatus.isFetching}
|
isFetching={getDevice.isFetching || getHealth.isFetching || getStatus.isFetching}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { Box, Heading, Image, Link, Spacer, Tooltip, useDisclosure } from '@chak
|
|||||||
import { LockSimple } from 'phosphor-react';
|
import { LockSimple } from 'phosphor-react';
|
||||||
import ReactCountryFlag from 'react-country-flag';
|
import ReactCountryFlag from 'react-country-flag';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
import Actions from './Actions';
|
import Actions from './Actions';
|
||||||
import DeviceListFirmwareButton from './FirmwareButton';
|
import DeviceListFirmwareButton from './FirmwareButton';
|
||||||
import AP from './icons/AP.png';
|
import AP from './icons/AP.png';
|
||||||
@@ -49,6 +50,7 @@ const BADGE_COLORS: Record<string, string> = {
|
|||||||
|
|
||||||
const DeviceListCard = () => {
|
const DeviceListCard = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const navigate = useNavigate();
|
||||||
const [serialNumber, setSerialNumber] = React.useState<string>('');
|
const [serialNumber, setSerialNumber] = React.useState<string>('');
|
||||||
const [hiddenColumns, setHiddenColumns] = React.useState<string[]>([]);
|
const [hiddenColumns, setHiddenColumns] = React.useState<string[]>([]);
|
||||||
const [pageInfo, setPageInfo] = React.useState<PageInfo | undefined>(undefined);
|
const [pageInfo, setPageInfo] = React.useState<PageInfo | undefined>(undefined);
|
||||||
@@ -252,6 +254,7 @@ const DeviceListCard = () => {
|
|||||||
Footer: '',
|
Footer: '',
|
||||||
accessor: 'firmware',
|
accessor: 'firmware',
|
||||||
Cell: (v) => firmwareCell(v.cell.row.original),
|
Cell: (v) => firmwareCell(v.cell.row.original),
|
||||||
|
stopPropagation: true,
|
||||||
customWidth: '50px',
|
customWidth: '50px',
|
||||||
disableSortBy: true,
|
disableSortBy: true,
|
||||||
},
|
},
|
||||||
@@ -389,7 +392,7 @@ const DeviceListCard = () => {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardBody p={4}>
|
<CardBody p={4}>
|
||||||
<Box overflowX="auto" w="100%">
|
<Box overflowX="auto" w="100%">
|
||||||
<DataTable
|
<DataTable<DeviceWithStatus>
|
||||||
columns={
|
columns={
|
||||||
columns.filter(({ id }) => !hiddenColumns.find((hidden) => hidden === id)) as {
|
columns.filter(({ id }) => !hiddenColumns.find((hidden) => hidden === id)) as {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -407,6 +410,8 @@ const DeviceListCard = () => {
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
setPageInfo={setPageInfo}
|
setPageInfo={setPageInfo}
|
||||||
saveSettingsId="gateway.devices.table"
|
saveSettingsId="gateway.devices.table"
|
||||||
|
onRowClick={(device) => navigate(`devices/${device.serialNumber}`)}
|
||||||
|
isRowClickable={() => true}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
|
|||||||
@@ -11,7 +11,15 @@ const UriCell = ({ uri }: Props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box display="flex">
|
<Box display="flex">
|
||||||
<Button onClick={copy.onCopy} size="xs" colorScheme="teal" mr={2}>
|
<Button
|
||||||
|
onClick={(e) => {
|
||||||
|
copy.onCopy();
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
size="xs"
|
||||||
|
colorScheme="teal"
|
||||||
|
mr={2}
|
||||||
|
>
|
||||||
{copy.hasCopied ? `${t('common.copied')}!` : t('common.copy')}
|
{copy.hasCopied ? `${t('common.copied')}!` : t('common.copy')}
|
||||||
</Button>
|
</Button>
|
||||||
<Text my="auto">{uri}</Text>
|
<Text my="auto">{uri}</Text>
|
||||||
|
|||||||
@@ -158,13 +158,14 @@ const FirmwareListTable = () => {
|
|||||||
<CardBody p={4}>
|
<CardBody p={4}>
|
||||||
<Box overflowX="auto" w="100%">
|
<Box overflowX="auto" w="100%">
|
||||||
<LoadingOverlay isLoading={getDeviceTypes.isFetching || getFirmware.isFetching}>
|
<LoadingOverlay isLoading={getDeviceTypes.isFetching || getFirmware.isFetching}>
|
||||||
<DataTable
|
<DataTable<Firmware>
|
||||||
columns={columns as Column<object>[]}
|
columns={columns}
|
||||||
saveSettingsId="firmware.table"
|
saveSettingsId="firmware.table"
|
||||||
data={getFirmware.data?.filter((firmw) => showDevFirmware || !firmw.revision.includes('devel')) ?? []}
|
data={getFirmware.data?.filter((firmw) => showDevFirmware || !firmw.revision.includes('devel')) ?? []}
|
||||||
obj={t('analytics.firmware')}
|
obj={t('analytics.firmware')}
|
||||||
minHeight="200px"
|
minHeight="200px"
|
||||||
sortBy={[{ id: 'imageDate', desc: true }]}
|
sortBy={[{ id: 'imageDate', desc: true }]}
|
||||||
|
onRowClick={(firmw) => handleViewDetailsClick(firmw)()}
|
||||||
/>
|
/>
|
||||||
</LoadingOverlay>
|
</LoadingOverlay>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { Box, Button, Heading, HStack, Spacer } from '@chakra-ui/react';
|
import { Box, Button, Heading, HStack, Spacer } from '@chakra-ui/react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useParams } from 'react-router-dom';
|
||||||
import ScriptTableActions from './Actions';
|
import ScriptTableActions from './Actions';
|
||||||
import CreateScriptButton from './CreateButton';
|
import CreateScriptButton from './CreateButton';
|
||||||
import useScriptsTable from './useScriptsTable';
|
import useScriptsTable from './useScriptsTable';
|
||||||
@@ -21,6 +22,7 @@ type Props = {
|
|||||||
const ScriptTableCard = ({ onIdSelect }: Props) => {
|
const ScriptTableCard = ({ onIdSelect }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { query, hiddenColumns } = useScriptsTable();
|
const { query, hiddenColumns } = useScriptsTable();
|
||||||
|
const { id } = useParams();
|
||||||
|
|
||||||
const dateCell = React.useCallback((date: number) => <FormattedDate date={date} />, []);
|
const dateCell = React.useCallback((date: number) => <FormattedDate date={date} />, []);
|
||||||
const actionCell = React.useCallback(
|
const actionCell = React.useCallback(
|
||||||
@@ -108,8 +110,8 @@ const ScriptTableCard = ({ onIdSelect }: Props) => {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardBody>
|
<CardBody>
|
||||||
<Box w="100%" h="300px" overflowY="auto">
|
<Box w="100%" h="300px" overflowY="auto">
|
||||||
<DataTable
|
<DataTable<Script>
|
||||||
columns={columns as Column<object>[]}
|
columns={columns}
|
||||||
saveSettingsId="apiKeys.profile.table"
|
saveSettingsId="apiKeys.profile.table"
|
||||||
data={query.data ?? []}
|
data={query.data ?? []}
|
||||||
obj={t('script.other')}
|
obj={t('script.other')}
|
||||||
@@ -118,6 +120,8 @@ const ScriptTableCard = ({ onIdSelect }: Props) => {
|
|||||||
hiddenColumns={hiddenColumns[0]}
|
hiddenColumns={hiddenColumns[0]}
|
||||||
showAllRows
|
showAllRows
|
||||||
hideControls
|
hideControls
|
||||||
|
onRowClick={(script) => onIdSelect(script.id)}
|
||||||
|
isRowClickable={(script) => script.id !== id}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
|
|||||||
@@ -25,10 +25,10 @@ const UserTable = () => {
|
|||||||
const { isOpen: editOpen, onOpen: openEdit, onClose: closeEdit } = useDisclosure();
|
const { isOpen: editOpen, onOpen: openEdit, onClose: closeEdit } = useDisclosure();
|
||||||
const { data: users, refetch: refreshUsers, isFetching } = useGetUsers();
|
const { data: users, refetch: refreshUsers, isFetching } = useGetUsers();
|
||||||
|
|
||||||
const openEditModal = (editUser: User) => {
|
const openEditModal = React.useCallback((editUser: User) => {
|
||||||
setEditId(editUser.id);
|
setEditId(editUser.id);
|
||||||
openEdit();
|
openEdit();
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
const memoizedActions = useCallback(
|
const memoizedActions = useCallback(
|
||||||
(userActions: User) => (
|
(userActions: User) => (
|
||||||
@@ -99,7 +99,7 @@ const UserTable = () => {
|
|||||||
];
|
];
|
||||||
if (user?.userRole !== 'csr')
|
if (user?.userRole !== 'csr')
|
||||||
baseColumns.push({
|
baseColumns.push({
|
||||||
id: 'user',
|
id: 'actions',
|
||||||
Header: t('common.actions'),
|
Header: t('common.actions'),
|
||||||
Footer: '',
|
Footer: '',
|
||||||
accessor: 'Id',
|
accessor: 'Id',
|
||||||
@@ -139,14 +139,15 @@ const UserTable = () => {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardBody>
|
<CardBody>
|
||||||
<Box overflowX="auto" w="100%">
|
<Box overflowX="auto" w="100%">
|
||||||
<DataTable
|
<DataTable<User>
|
||||||
columns={columns as Column<object>[]}
|
columns={columns}
|
||||||
data={users ?? []}
|
data={users ?? []}
|
||||||
isLoading={isFetching}
|
isLoading={isFetching}
|
||||||
obj={t('users.title')}
|
obj={t('users.title')}
|
||||||
sortBy={[{ id: 'email', desc: false }]}
|
sortBy={[{ id: 'email', desc: false }]}
|
||||||
hiddenColumns={hiddenColumns}
|
hiddenColumns={hiddenColumns}
|
||||||
fullScreen
|
fullScreen
|
||||||
|
onRowClick={openEditModal}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
|
|||||||
Reference in New Issue
Block a user