diff --git a/package-lock.json b/package-lock.json index 2c5c3cb..ea9f614 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ucentral-client", - "version": "2.9.0(18)", + "version": "2.9.0(22)", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "ucentral-client", - "version": "2.9.0(18)", + "version": "2.9.0(22)", "license": "ISC", "dependencies": { "@chakra-ui/icons": "^2.0.11", diff --git a/package.json b/package.json index 86e1ce9..ce4c2ce 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,13 @@ { "name": "ucentral-client", - "version": "2.9.0(18)", + "version": "2.9.0(22)", "description": "", "private": true, "main": "index.tsx", "scripts": { "dev": "vite", "build": "vite build", - "format": "prettier --write \"src/**/*.js\"", + "format": "prettier --write \"src/**/*x.{ts,tsx,js,jsx}\"", "analyze": "source-map-explorer 'build/static/js/*.js'", "lint": "TIMING=1 eslint \"src/**/*.{ts,tsx,js,jsx}\" --fix", "clean": "rm -rf node_modules && rm -rf build" diff --git a/src/components/Buttons/AlertButton/index.tsx b/src/components/Buttons/AlertButton/index.tsx index 835a4ed..6ee8f08 100644 --- a/src/components/Buttons/AlertButton/index.tsx +++ b/src/components/Buttons/AlertButton/index.tsx @@ -12,7 +12,14 @@ export interface AlertButtonProps extends ThemeProps { label?: string; } -const _AlertButton: React.FC = ({ onClick, isDisabled, isLoading, isCompact, label, ...props }) => { +const _AlertButton: React.FC = ({ + onClick, + isDisabled, + isLoading, + isCompact = true, + label, + ...props +}) => { const { t } = useTranslation(); const breakpoint = useBreakpoint(); diff --git a/src/components/Buttons/CreateButton/index.tsx b/src/components/Buttons/CreateButton/index.tsx index aba82f7..9a576a3 100644 --- a/src/components/Buttons/CreateButton/index.tsx +++ b/src/components/Buttons/CreateButton/index.tsx @@ -11,7 +11,14 @@ export interface CreateButtonProps extends SpaceProps { label?: string; } -const _CreateButton: React.FC = ({ onClick, isDisabled, isLoading, isCompact, label, ...props }) => { +const _CreateButton: React.FC = ({ + onClick, + isDisabled, + isLoading, + isCompact = true, + label, + ...props +}) => { const { t } = useTranslation(); const breakpoint = useBreakpoint(); diff --git a/src/components/Buttons/DeleteButton/index.tsx b/src/components/Buttons/DeleteButton/index.tsx index 0c8c82c..280eb56 100644 --- a/src/components/Buttons/DeleteButton/index.tsx +++ b/src/components/Buttons/DeleteButton/index.tsx @@ -16,7 +16,7 @@ const _DeleteButton: React.FC = ({ onClick, isDisabled, isLoading, - isCompact, + isCompact = true, label, ml, ...props diff --git a/src/components/Buttons/DeviceActionDropdown/index.tsx b/src/components/Buttons/DeviceActionDropdown/index.tsx index f11e94f..c9cfafa 100644 --- a/src/components/Buttons/DeviceActionDropdown/index.tsx +++ b/src/components/Buttons/DeviceActionDropdown/index.tsx @@ -51,7 +51,7 @@ const DeviceActionDropdown = ({ onOpenScriptModal, onOpenRebootModal, size, - isCompact, + isCompact = true, }: Props) => { const { t } = useTranslation(); const toast = useToast(); @@ -163,7 +163,7 @@ const DeviceActionDropdown = ({ return ( - + {size === undefined || isCompact ? ( - {t('commands.other')} + {t('common.actions')} )} diff --git a/src/components/Buttons/EditButton/index.tsx b/src/components/Buttons/EditButton/index.tsx index eb60255..49c0998 100644 --- a/src/components/Buttons/EditButton/index.tsx +++ b/src/components/Buttons/EditButton/index.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { IconButton, Button, Tooltip, useBreakpoint } from '@chakra-ui/react'; import { Pen } from 'phosphor-react'; +import { useTranslation } from 'react-i18next'; export interface EditButtonProps { onClick: () => void; @@ -11,7 +12,15 @@ export interface EditButtonProps { ml?: string | number; } -const _EditButton: React.FC = ({ onClick, label, isDisabled, isLoading, isCompact, ...props }) => { +const _EditButton: React.FC = ({ + onClick, + label, + isDisabled, + isLoading, + isCompact = true, + ...props +}) => { + const { t } = useTranslation(); const breakpoint = useBreakpoint(); if (!isCompact && breakpoint !== 'base' && breakpoint !== 'sm') { @@ -24,12 +33,12 @@ const _EditButton: React.FC = ({ onClick, label, isDisabled, is isDisabled={isDisabled} {...props} > - {label} + {label ?? t('common.edit')} ); } return ( - + = ({ onClick, isDisabled, isFetching, - isCompact, + isCompact = true, ml, size, ...props diff --git a/src/components/Buttons/ResponsiveButton/index.tsx b/src/components/Buttons/ResponsiveButton/index.tsx index d1fc7ce..d633d01 100644 --- a/src/components/Buttons/ResponsiveButton/index.tsx +++ b/src/components/Buttons/ResponsiveButton/index.tsx @@ -15,7 +15,7 @@ const _ResponsiveButton: React.FC = ({ onClick, isDisabled, isLoading, - isCompact, + isCompact = true, color, label, icon, diff --git a/src/components/Buttons/SaveButton/index.tsx b/src/components/Buttons/SaveButton/index.tsx index 85eda6e..ed08c4f 100644 --- a/src/components/Buttons/SaveButton/index.tsx +++ b/src/components/Buttons/SaveButton/index.tsx @@ -18,7 +18,7 @@ const _SaveButton: React.FC = ({ onClick, isDisabled, isLoading, - isCompact, + isCompact = true, isDirty, dirtyCheck, ...props diff --git a/src/components/Buttons/ToggleEditButton/index.tsx b/src/components/Buttons/ToggleEditButton/index.tsx index b6e1667..5767038 100644 --- a/src/components/Buttons/ToggleEditButton/index.tsx +++ b/src/components/Buttons/ToggleEditButton/index.tsx @@ -20,7 +20,7 @@ const _ToggleEditButton: React.FC = ({ isDirty, isDisabled, isLoading, - isCompact, + isCompact = true, ml, ...props }) => { diff --git a/src/components/Buttons/WarningButton/index.tsx b/src/components/Buttons/WarningButton/index.tsx index 83436d3..f702f0b 100644 --- a/src/components/Buttons/WarningButton/index.tsx +++ b/src/components/Buttons/WarningButton/index.tsx @@ -16,7 +16,7 @@ const _WarningButton: React.FC = ({ onClick, isDisabled, isLoading, - isCompact, + isCompact = true, label, ...props }) => { diff --git a/src/components/DataTables/ColumnPicker/index.tsx b/src/components/DataTables/ColumnPicker/index.tsx index 6af99f6..23f9ea1 100644 --- a/src/components/DataTables/ColumnPicker/index.tsx +++ b/src/components/DataTables/ColumnPicker/index.tsx @@ -21,7 +21,7 @@ export const ColumnPicker = ({ hiddenColumns, setHiddenColumns, size, - isCompact, + isCompact = true, }: ColumnPickerProps) => { const { t } = useTranslation(); const { getPref, setPref } = useAuth(); diff --git a/src/components/Modals/WifiScanModal/index.tsx b/src/components/Modals/WifiScanModal/index.tsx index 4257012..6dc057b 100644 --- a/src/components/Modals/WifiScanModal/index.tsx +++ b/src/components/Modals/WifiScanModal/index.tsx @@ -57,7 +57,7 @@ export const WifiScanModal = ({ modalProps: { isOpen, onClose }, serialNumber }: if (isOpen) resetData(); }, [isOpen]); return ( - ( + {csvData ? ( // @ts-ignore - ( @@ -77,7 +77,7 @@ export const WifiScanModal = ({ modalProps: { isOpen, onClose }, serialNumber }: label={t('common.download')} onClick={() => {}} /> - ) + ) : ( - ) + ); }; diff --git a/src/hooks/Network/Commands.ts b/src/hooks/Network/Commands.ts index 541bcce..99696ad 100644 --- a/src/hooks/Network/Commands.ts +++ b/src/hooks/Network/Commands.ts @@ -276,8 +276,11 @@ export const useDeviceScript = ({ serialNumber }: { serialNumber: string }) => { const downloadScript = (serialNumber: string, commandId: string) => axiosGw.get(`file/${commandId}?serialNumber=${serialNumber}`, { responseType: 'arraybuffer' }); -export const useDownloadScriptResult = ({ serialNumber, commandId }: { serialNumber: string; commandId: string }) => - useQuery(['download-script', serialNumber, commandId], () => downloadScript(serialNumber, commandId), { +export const useDownloadScriptResult = ({ serialNumber, commandId }: { serialNumber: string; commandId: string }) => { + const { t } = useTranslation(); + const toast = useToast(); + + return useQuery(['download-script', serialNumber, commandId], () => downloadScript(serialNumber, commandId), { enabled: false, onSuccess: (response) => { const blob = new Blob([response.data], { type: 'application/octet-stream' }); @@ -290,4 +293,27 @@ export const useDownloadScriptResult = ({ serialNumber, commandId }: { serialNum link.download = filename; link.click(); }, + onError: (e) => { + if (axios.isAxiosError(e)) { + const bufferResponse = e.response?.data; + let errorMessage = ''; + // If the response is a buffer, parse to JSON object + if (bufferResponse instanceof ArrayBuffer) { + const decoder = new TextDecoder('utf-8'); + const json = JSON.parse(decoder.decode(bufferResponse)); + errorMessage = json.ErrorDescription; + } + + toast({ + id: `script-download-error-${serialNumber}`, + title: t('common.error'), + description: errorMessage, + status: 'error', + duration: 5000, + isClosable: true, + position: 'top-right', + }); + } + }, }); +}; diff --git a/src/hooks/Network/Trace.ts b/src/hooks/Network/Trace.ts index bc77ce5..2b79a62 100644 --- a/src/hooks/Network/Trace.ts +++ b/src/hooks/Network/Trace.ts @@ -1,5 +1,6 @@ import { useToast } from '@chakra-ui/react'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import axios from 'axios'; import { useTranslation } from 'react-i18next'; import { axiosGw } from 'constants/axiosInstances'; @@ -85,8 +86,11 @@ export const useTrace = ({ serialNumber, alertOnCompletion }: { serialNumber: st export const downloadTrace = (serialNumber: string, commandId: string) => axiosGw.get(`file/${commandId}?serialNumber=${serialNumber}`, { responseType: 'arraybuffer' }); -export const useDownloadTrace = ({ serialNumber, commandId }: { serialNumber: string; commandId: string }) => - useQuery(['download-trace', serialNumber, commandId], () => downloadTrace(serialNumber, commandId), { +export const useDownloadTrace = ({ serialNumber, commandId }: { serialNumber: string; commandId: string }) => { + const { t } = useTranslation(); + const toast = useToast(); + + return useQuery(['download-trace', serialNumber, commandId], () => downloadTrace(serialNumber, commandId), { enabled: false, onSuccess: (response) => { const blob = new Blob([response.data], { type: 'application/octet-stream' }); @@ -99,4 +103,27 @@ export const useDownloadTrace = ({ serialNumber, commandId }: { serialNumber: st link.download = filename; link.click(); }, + onError: (e) => { + if (axios.isAxiosError(e)) { + const bufferResponse = e.response?.data; + let errorMessage = ''; + // If the response is a buffer, parse to JSON object + if (bufferResponse instanceof ArrayBuffer) { + const decoder = new TextDecoder('utf-8'); + const json = JSON.parse(decoder.decode(bufferResponse)); + errorMessage = json.ErrorDescription; + } + + toast({ + id: `trace-download-error-${serialNumber}`, + title: t('common.error'), + description: errorMessage, + status: 'error', + duration: 5000, + isClosable: true, + position: 'top-right', + }); + } + }, }); +}; diff --git a/src/pages/Device/LogsCard/CommandHistory/index.tsx b/src/pages/Device/LogsCard/CommandHistory/index.tsx index 8fb26a9..a556710 100644 --- a/src/pages/Device/LogsCard/CommandHistory/index.tsx +++ b/src/pages/Device/LogsCard/CommandHistory/index.tsx @@ -44,13 +44,13 @@ const CommandHistory = ({ serialNumber }: Props) => { - []} hiddenColumns={hiddenColumns} setHiddenColumns={setHiddenColumns} preference="gateway.device.commandshistory.hiddenColumns" /> + { const actionCell = React.useCallback( (command: DeviceCommandHistory) => ( - - } - size="sm" - isLoading={loadingDeleteSerial === command.UUID} - /> - { isLoading={loadingDeleteSerial === command.UUID} /> + + } + size="sm" + isLoading={loadingDeleteSerial === command.UUID} + /> + ), [loadingDeleteSerial], diff --git a/src/pages/Device/StatisticsCard/index.tsx b/src/pages/Device/StatisticsCard/index.tsx index 9f1a58e..c8d321e 100644 --- a/src/pages/Device/StatisticsCard/index.tsx +++ b/src/pages/Device/StatisticsCard/index.tsx @@ -52,8 +52,6 @@ const DeviceStatisticsCard = ({ serialNumber }: Props) => { {t('configurations.statistics')} - - + + { return ( <> - + + } + onClick={onOpen} + colorScheme="pink" + mr={2} + /> + - + + } + onClick={onOpen} + colorScheme="purple" + /> + { const { t } = useTranslation(); const toast = useToast(); const breakpoint = useBreakpoint(); + const cancelRef = React.useRef(null); const navigate = useNavigate(); const { mutateAsync: deleteDevice, isLoading: isDeleting } = useDeleteDevice({ serialNumber, @@ -197,7 +195,7 @@ const DevicePageWrapper = ({ serialNumber }: Props) => { {breakpoint !== 'base' && breakpoint !== 'md' && } - + {getDevice?.data && ( { isCompact /> )} - - - - - {}} /> - - - - - - - - {t('crud.delete')} {serialNumber} - - {t('crud.delete_confirm', { obj: t('devices.one') })} - -
- - -
-
-
-
{ + {getDevice?.data && ( { onOpenRebootModal={rebootModalProps.onOpen} onOpenScriptModal={scriptModal.openModal} size="md" + isCompact /> )} - - - - - {}} /> - - - - - - - - {t('crud.delete')} {serialNumber} - - {t('crud.delete_confirm', { obj: t('devices.one') })} - -
- - -
-
-
-
{ )} + + + + + {t('crud.delete')} {serialNumber} + + {t('crud.delete_confirm', { obj: t('devices.one') })} + + + + + + + diff --git a/src/pages/Firmware/List/UpdateDbButton.tsx b/src/pages/Firmware/List/UpdateDbButton.tsx index 6f9cbf0..3bfd383 100644 --- a/src/pages/Firmware/List/UpdateDbButton.tsx +++ b/src/pages/Firmware/List/UpdateDbButton.tsx @@ -7,9 +7,11 @@ import { Box, Button, Center, + IconButton, Tag, TagLabel, Text, + Tooltip, useDisclosure, useToast, } from '@chakra-ui/react'; @@ -58,9 +60,14 @@ const UpdateDbButton = () => { return ( <> - + + } + onClick={onOpen} + /> + { filename={`logs_${dateForFilename(new Date().getTime() / 1000)}.csv`} data={downloadableLogs as object[]} > - + + } colorScheme="blue" /> +
diff --git a/src/pages/Notifications/FmsLogs/index.tsx b/src/pages/Notifications/FmsLogs/index.tsx index 48f9edd..661858a 100644 --- a/src/pages/Notifications/FmsLogs/index.tsx +++ b/src/pages/Notifications/FmsLogs/index.tsx @@ -1,5 +1,19 @@ import * as React from 'react'; -import { Badge, Box, Button, Flex, HStack, Select, Spacer, Table, Text, Th, Thead, Tr } from '@chakra-ui/react'; +import { + Badge, + Box, + Flex, + HStack, + IconButton, + Select, + Spacer, + Table, + Text, + Th, + Thead, + Tooltip, + Tr, +} from '@chakra-ui/react'; import { Download } from 'phosphor-react'; import { CSVLink } from 'react-csv'; import { useTranslation } from 'react-i18next'; @@ -128,9 +142,9 @@ const FmsLogsCard = () => { filename={`logs_${dateForFilename(new Date().getTime() / 1000)}.csv`} data={downloadableLogs as object[]} > - + + } colorScheme="blue" /> +
diff --git a/src/pages/Notifications/GeneralLogs/index.tsx b/src/pages/Notifications/GeneralLogs/index.tsx index dbce496..14585f9 100644 --- a/src/pages/Notifications/GeneralLogs/index.tsx +++ b/src/pages/Notifications/GeneralLogs/index.tsx @@ -1,5 +1,19 @@ import * as React from 'react'; -import { Badge, Box, Button, Flex, HStack, Select, Spacer, Table, Text, Th, Thead, Tr } from '@chakra-ui/react'; +import { + Badge, + Box, + Flex, + HStack, + IconButton, + Select, + Spacer, + Table, + Text, + Th, + Thead, + Tooltip, + Tr, +} from '@chakra-ui/react'; import { Download } from 'phosphor-react'; import { CSVLink } from 'react-csv'; import { useTranslation } from 'react-i18next'; @@ -128,9 +142,9 @@ const GeneralLogsCard = () => { filename={`logs_${dateForFilename(new Date().getTime() / 1000)}.csv`} data={downloadableLogs as object[]} > - + + } colorScheme="blue" /> +
diff --git a/src/pages/Notifications/SecLogs/index.tsx b/src/pages/Notifications/SecLogs/index.tsx index 1ed983d..9f78bf6 100644 --- a/src/pages/Notifications/SecLogs/index.tsx +++ b/src/pages/Notifications/SecLogs/index.tsx @@ -1,5 +1,19 @@ import * as React from 'react'; -import { Badge, Box, Button, Flex, HStack, Select, Spacer, Table, Text, Th, Thead, Tr } from '@chakra-ui/react'; +import { + Badge, + Box, + Flex, + HStack, + IconButton, + Select, + Spacer, + Table, + Text, + Th, + Thead, + Tooltip, + Tr, +} from '@chakra-ui/react'; import { Download } from 'phosphor-react'; import { CSVLink } from 'react-csv'; import { useTranslation } from 'react-i18next'; @@ -128,9 +142,9 @@ const SecLogsCard = () => { filename={`logs_${dateForFilename(new Date().getTime() / 1000)}.csv`} data={downloadableLogs as object[]} > - + + } colorScheme="blue" /> +
diff --git a/src/pages/Profile/ApiKeys/index.tsx b/src/pages/Profile/ApiKeys/index.tsx index d44d0b7..fac9261 100644 --- a/src/pages/Profile/ApiKeys/index.tsx +++ b/src/pages/Profile/ApiKeys/index.tsx @@ -1,4 +1,5 @@ import * as React from 'react'; +import { Box } from '@chakra-ui/react'; import ApiKeyTable from './Table'; import { Card } from 'components/Containers/Card'; import { CardBody } from 'components/Containers/Card/CardBody'; @@ -10,7 +11,9 @@ const ApiKeysCard = () => { return ( - + + + ); diff --git a/src/pages/SystemPage/SystemTile/LoggingButton/index.tsx b/src/pages/SystemPage/SystemTile/LoggingButton/index.tsx index f0d1c93..fa39e23 100644 --- a/src/pages/SystemPage/SystemTile/LoggingButton/index.tsx +++ b/src/pages/SystemPage/SystemTile/LoggingButton/index.tsx @@ -1,5 +1,6 @@ import * as React from 'react'; -import { Button, useDisclosure } from '@chakra-ui/react'; +import { IconButton, Tooltip, useDisclosure } from '@chakra-ui/react'; +import { Article } from 'phosphor-react'; import { useTranslation } from 'react-i18next'; import SystemLoggingModal from './Modal'; import { EndpointApiResponse } from 'hooks/Network/Endpoints'; @@ -15,9 +16,17 @@ const SystemLoggingButton = ({ endpoint, token }: Props) => { return ( <> - + + } + mr={2} + /> + ); diff --git a/src/pages/SystemPage/SystemTile/index.tsx b/src/pages/SystemPage/SystemTile/index.tsx index e9528d5..0ecdc4f 100644 --- a/src/pages/SystemPage/SystemTile/index.tsx +++ b/src/pages/SystemPage/SystemTile/index.tsx @@ -22,6 +22,7 @@ import { useTranslation } from 'react-i18next'; import FormattedDate from '../../../components/InformationDisplays/FormattedDate'; import SystemLoggingButton from './LoggingButton'; import SystemCertificatesTable from './SystemCertificatesTable'; +import { RefreshButton } from 'components/Buttons/RefreshButton'; import { Card } from 'components/Containers/Card'; import { CardBody } from 'components/Containers/Card/CardBody'; import { compactSecondsToDetailed } from 'helpers/dateFormatting'; @@ -70,16 +71,7 @@ const SystemTile = ({ endpoint, token }: Props) => { {endpoint.type} - +
diff --git a/src/pages/UsersPage/Table/ActionsDropdown.tsx b/src/pages/UsersPage/Table/ActionsDropdown.tsx index d87d6c8..1a7b58f 100644 --- a/src/pages/UsersPage/Table/ActionsDropdown.tsx +++ b/src/pages/UsersPage/Table/ActionsDropdown.tsx @@ -77,7 +77,7 @@ const UserActions = ({ id, isSuspended, isWaitingForCheck, refresh, size = 'sm', return ( - + { return ( <> - + {user?.userRole === 'CSR' ? null : } { preference="provisioning.userTable.hiddenColumns" /> - + diff --git a/src/theme/theme.ts b/src/theme/theme.ts index 5aeea4f..d2c5fc6 100644 --- a/src/theme/theme.ts +++ b/src/theme/theme.ts @@ -1,4 +1,4 @@ -import { extendTheme, type ThemeConfig } from '@chakra-ui/react'; +import { extendTheme, Tooltip, type ThemeConfig } from '@chakra-ui/react'; import CardComponent from './additions/card/Card'; import CardBodyComponent from './additions/card/CardBody'; import CardHeaderComponent from './additions/card/CardHeader'; @@ -37,4 +37,6 @@ const theme = extendTheme({ }, }); +Tooltip.defaultProps = { ...Tooltip.defaultProps, hasArrow: true }; + export default theme; diff --git a/vite.config.ts b/vite.config.ts index 815a3c5..9c1e774 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,49 +1,9 @@ import { defineConfig } from 'vite'; import tsconfigPaths from 'vite-tsconfig-paths'; -import { VitePWA } from 'vite-plugin-pwa'; import react from '@vitejs/plugin-react'; export default defineConfig({ - plugins: [ - tsconfigPaths(), - react(), - VitePWA({ - registerType: 'autoUpdate', - devOptions: { - enabled: true, - /* other options */ - }, - manifest: { - name: 'OpenWiFi Controller App', - short_name: 'OpenWiFiController', - description: 'OpenWiFi Controller App', - theme_color: '#000000', - icons: [ - { - src: 'android-chrome-192x192.png', - sizes: '192x192', - type: 'image/png', - }, - { - src: 'android-chrome-384x384.png', - sizes: '384x384', - type: 'image/png', - }, - { - src: 'android-chrome-512x512.png', - sizes: '512x512', - type: 'image/png', - }, - { - src: 'android-chrome-512x512.png', - sizes: '512x512', - type: 'image/png', - purpose: 'any maskable', - }, - ], - }, - }), - ], + plugins: [tsconfigPaths(), react()], build: { outDir: './build', chunkSizeWarningLimit: 1000,