From 8f57d73547e6bc5f679af8a4f264c6e22bc20241 Mon Sep 17 00:00:00 2001 From: Charles Date: Fri, 10 Mar 2023 14:14:56 +0100 Subject: [PATCH 1/3] [WIFI-12363] Missing AP event-related configuration values in configuration editor Signed-off-by: Charles --- package-lock.json | 4 +- package.json | 2 +- public/locales/de/translation.json | 2 + public/locales/en/translation.json | 2 + public/locales/es/translation.json | 2 + public/locales/fr/translation.json | 2 + public/locales/pt/translation.json | 2 + src/components/ColumnPicker/index.tsx | 43 +++-- .../RrmFormField/AlgorithmPicker.tsx | 1 + .../CustomFields/RrmFormField/Algorithms.tsx | 7 +- .../CustomFields/RrmFormField/Form.tsx | 83 +++------- .../RrmFormField/ProviderPicker.tsx | 42 +++-- .../CustomFields/RrmFormField/TypePicker.tsx | 67 ++++++++ .../CustomFields/RrmFormField/index.tsx | 10 +- src/components/DataTable/index.tsx | 51 ++++-- src/components/SortableDataTable/index.tsx | 128 +++++++++------ .../Tables/ConfigurationTable/index.jsx | 39 +++-- src/components/Tables/ContactTable/index.jsx | 11 +- .../InventoryTable/EditTagModal/index.jsx | 56 ++++++- .../Tables/InventoryTable/index.jsx | 21 ++- src/components/Tables/LocationTable/index.jsx | 7 +- src/components/Tables/ResourceTable/index.jsx | 22 ++- .../Tables/SubscriberDeviceTable/index.tsx | 12 +- .../Tables/SubscriberTable/index.jsx | 7 +- src/hooks/Network/Rrm.ts | 76 +++++++-- src/hooks/useRrm.ts | 26 +++ src/models/Table.ts | 9 ++ .../AddSubsectionModal.jsx | 8 +- .../ExpertModeButton/index.tsx | 149 ++++++++++++++++++ .../ImportConfigurationButton/index.jsx | 27 +--- .../InterfaceExpertModal/Form.jsx | 75 --------- .../InterfaceExpertModal/index.jsx | 31 ---- .../InterfaceSection/index.jsx | 8 +- .../MetricsSection/Realtime.tsx | 34 ++++ .../MetricsSection/Telemetry.tsx | 44 ++++++ .../MetricsSection/WifiScan.tsx | 35 ++++ .../MetricsSection/index.jsx | 22 ++- .../MetricsSection/metricsConstants.js | 71 +++++++++ .../ServicesSection/Captive/index.tsx | 7 +- .../ServicesSection/servicesConstants.js | 1 + .../ViewJsonConfig/index.tsx | 40 +---- .../ConfigurationSectionsCard/index.jsx | 19 ++- .../EntityContactTableWrapper /index.jsx | 8 +- .../EntityDeviceTableWrapper/index.tsx | 1 + .../EntityLocationTableWrapper /index.jsx | 8 +- .../EntityResourcesTableWrapper/index.jsx | 12 +- src/pages/InventoryPage/Table/index.tsx | 18 ++- .../ChildrenCard/DevicesTab/index.tsx | 10 +- .../ChildrenCard/ServiceClassTab/Table.jsx | 5 +- .../ChildrenCard/ServiceClassTab/index.jsx | 2 +- src/pages/OperatorsPage/Table/index.tsx | 24 +-- .../DevicesTab/index.tsx | 3 +- src/pages/SystemPage/SystemSecrets/Table.tsx | 2 +- src/pages/UsersPage/Table/index.jsx | 3 +- .../VenueContactTableWrapper/index.tsx | 1 + .../VenueDeviceTableWrapper/index.tsx | 1 + .../VenueResourcesTableWrapper/index.jsx | 8 +- 57 files changed, 998 insertions(+), 413 deletions(-) create mode 100644 src/components/CustomFields/RrmFormField/TypePicker.tsx create mode 100644 src/hooks/useRrm.ts create mode 100644 src/pages/ConfigurationPage/ConfigurationCard/ConfigurationSectionsCard/ExpertModeButton/index.tsx delete mode 100644 src/pages/ConfigurationPage/ConfigurationCard/ConfigurationSectionsCard/InterfaceSection/InterfaceExpertModal/Form.jsx delete mode 100644 src/pages/ConfigurationPage/ConfigurationCard/ConfigurationSectionsCard/InterfaceSection/InterfaceExpertModal/index.jsx create mode 100644 src/pages/ConfigurationPage/ConfigurationCard/ConfigurationSectionsCard/MetricsSection/Realtime.tsx create mode 100644 src/pages/ConfigurationPage/ConfigurationCard/ConfigurationSectionsCard/MetricsSection/Telemetry.tsx create mode 100644 src/pages/ConfigurationPage/ConfigurationCard/ConfigurationSectionsCard/MetricsSection/WifiScan.tsx diff --git a/package-lock.json b/package-lock.json index ac21b7f..3743ae1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "wlan-cloud-owprov-ui", - "version": "2.9.0(10)", + "version": "2.9.0(15)", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "wlan-cloud-owprov-ui", - "version": "2.9.0(10)", + "version": "2.9.0(15)", "license": "ISC", "dependencies": { "@chakra-ui/icons": "^2.0.11", diff --git a/package.json b/package.json index 2eb8811..b87c67c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wlan-cloud-owprov-ui", - "version": "2.9.0(10)", + "version": "2.9.0(15)", "description": "", "main": "index.tsx", "scripts": { diff --git a/public/locales/de/translation.json b/public/locales/de/translation.json index 23684e5..8c512bd 100644 --- a/public/locales/de/translation.json +++ b/public/locales/de/translation.json @@ -356,6 +356,7 @@ "error_pushes_one": "Fehler (könnte an einer fehlerhaften Konfiguration liegen): {{count}}", "error_pushes_other": "Fehler (können auf eine fehlerhafte Konfiguration zurückzuführen sein): {{count}}", "expert_name": "Expertenmodus", + "expert_name_explanation": "Sie können den Expertenmodus verwenden, um Ihre Konfiguration direkt zu ändern, einschließlich des Hinzufügens von Werten, die derzeit nicht von der Benutzeroberfläche unterstützt werden. Bitte verwenden Sie dieses Format: { \"interfaces\": [ ... ], \"globals\": { ... }, ...etc }", "explanation": "Erläuterung", "firewall": "Firewall", "firmware_upgrade": "Firmware-Aktualisierung", @@ -522,6 +523,7 @@ "ouis_explanation": "OUIs von Geräten, die sich mit diesem Firmware-Server verbunden haben", "outdated_one": "Firmware {{count}} Tag alt", "outdated_other": "Firmware {{count}} Tage alt", + "outdated_unknown": "Firmware unbekannten Alters", "release": "Veröffentlichung", "show_dev_releases": "Entwicklerversionen", "status_explanation": "Verbindungsstatus von Geräten, die sich mit diesem Firmware-Server verbunden haben", diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 92ad3e3..342589c 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -356,6 +356,7 @@ "error_pushes_one": "Error (could be because of bad configuration): {{count}}", "error_pushes_other": "Errors (could be because of bad configuration): {{count}}", "expert_name": "Expert Mode", + "expert_name_explanation": "You can use expert mode to directly modify your configuration, including adding values that are not currently supported by the UI. Please use this format: { \"interfaces\": [ ... ], \"globals\": { ... }, ...etc }", "explanation": "Explanation", "firewall": "Firewall", "firmware_upgrade": "Firmware Upgrade", @@ -522,6 +523,7 @@ "ouis_explanation": "OUIs of devices that have connected to this firmware server", "outdated_one": "Firmware {{count}} day old", "outdated_other": "Firmware {{count}} days old", + "outdated_unknown": "Firmware of unknown age", "release": "Release", "show_dev_releases": "Dev Releases", "status_explanation": "Connection status of devices that have connected to this firmware server", diff --git a/public/locales/es/translation.json b/public/locales/es/translation.json index 8727e57..ac2871e 100644 --- a/public/locales/es/translation.json +++ b/public/locales/es/translation.json @@ -356,6 +356,7 @@ "error_pushes_one": "Error (podría deberse a una mala configuración): {{count}}", "error_pushes_other": "Errores (pueden deberse a una mala configuración): {{count}}", "expert_name": "Modo experto", + "expert_name_explanation": "Puede usar el modo experto para modificar directamente su configuración, incluida la adición de valores que actualmente no son compatibles con la interfaz de usuario. Utilice este formato: { \"interfaces\": [ ... ], \"globals\": { ... }, ...etc }", "explanation": "Explicación", "firewall": "cortafuegos", "firmware_upgrade": "Actualización de firmware", @@ -522,6 +523,7 @@ "ouis_explanation": "OUI de dispositivos que se han conectado a este servidor de firmware", "outdated_one": "Firmware {{count}} día de antigüedad", "outdated_other": "Firmware de {{count}} días de antigüedad", + "outdated_unknown": "Firmware de antigüedad desconocida", "release": "Lanzamiento", "show_dev_releases": "Lanzamientos de desarrollo", "status_explanation": "Estado de conexión de los dispositivos que se han conectado a este servidor de firmware", diff --git a/public/locales/fr/translation.json b/public/locales/fr/translation.json index a11bdf2..7ce21e0 100644 --- a/public/locales/fr/translation.json +++ b/public/locales/fr/translation.json @@ -356,6 +356,7 @@ "error_pushes_one": "Erreur (peut être due à une mauvaise configuration) : {{count}}", "error_pushes_other": "Erreurs (peut-être dues à une mauvaise configuration) : {{count}}", "expert_name": "Mode expert", + "expert_name_explanation": "Vous pouvez utiliser le mode expert pour modifier directement votre configuration, notamment en ajoutant des valeurs qui ne sont pas actuellement prises en charge par l'interface utilisateur. Veuillez utiliser ce format : { \"interfaces\": [ ... ], \"globals\": { ... }, ...etc }", "explanation": "Explication", "firewall": "Pare-feu", "firmware_upgrade": "Mise à jour du firmware", @@ -522,6 +523,7 @@ "ouis_explanation": "OUI des appareils qui se sont connectés à ce serveur de firmware", "outdated_one": "Micrologiciel vieux de {{count}} jours", "outdated_other": "Micrologiciel vieux de {{count}} jours", + "outdated_unknown": "Firmware d'âge inconnu", "release": "libération", "show_dev_releases": "Versions de développement", "status_explanation": "État de connexion des appareils qui se sont connectés à ce serveur de micrologiciel", diff --git a/public/locales/pt/translation.json b/public/locales/pt/translation.json index 14c9c8f..8a2266c 100644 --- a/public/locales/pt/translation.json +++ b/public/locales/pt/translation.json @@ -356,6 +356,7 @@ "error_pushes_one": "Erro (pode ser devido à configuração incorreta): {{count}}", "error_pushes_other": "Erros (podem ser devido à configuração incorreta): {{count}}", "expert_name": "MODO EXPERT", + "expert_name_explanation": "Você pode usar o modo especialista para modificar diretamente sua configuração, incluindo a adição de valores que não são atualmente suportados pela interface do usuário. Use este formato: { \"interfaces\": [ ... ], \"globals\": { ... }, ...etc }", "explanation": "Explicação", "firewall": "Firewall", "firmware_upgrade": "Atualização de firmware", @@ -522,6 +523,7 @@ "ouis_explanation": "OUIs de dispositivos que se conectaram a este servidor de firmware", "outdated_one": "Firmware com {{count}} dias", "outdated_other": "Firmware com {{count}} dias", + "outdated_unknown": "Firmware de idade desconhecida", "release": "LANÇAMENTO", "show_dev_releases": "Lançamentos do desenvolvedor", "status_explanation": "Status da conexão dos dispositivos que se conectaram a este servidor de firmware", diff --git a/src/components/ColumnPicker/index.tsx b/src/components/ColumnPicker/index.tsx index 593232c..d8ef3c2 100644 --- a/src/components/ColumnPicker/index.tsx +++ b/src/components/ColumnPicker/index.tsx @@ -1,19 +1,40 @@ import React, { useEffect } from 'react'; -import { Button, Checkbox, IconButton, Menu, MenuButton, MenuItem, MenuList, useBreakpoint } from '@chakra-ui/react'; +import { + Button, + Checkbox, + IconButton, + Menu, + MenuButton, + MenuItem, + MenuList, + Tooltip, + useBreakpoint, +} from '@chakra-ui/react'; import { FunnelSimple } from 'phosphor-react'; import { useTranslation } from 'react-i18next'; import { v4 as uuid } from 'uuid'; import { useAuth } from 'contexts/AuthProvider'; import { Column } from 'models/Table'; -interface Props { +type ColumnPickerProps = { preference: string; - columns: Column[]; + columns: Column[]; hiddenColumns: string[]; setHiddenColumns: (str: string[]) => void; -} + defaultHiddenColumns?: string[]; + size?: 'sm' | 'md' | 'lg' | 'xl' | '2xl'; + isCompact?: boolean; +}; -const ColumnPicker = ({ preference, columns, hiddenColumns, setHiddenColumns }: Props) => { +const ColumnPicker = ({ + preference, + columns, + hiddenColumns, + setHiddenColumns, + defaultHiddenColumns = [], + size, + isCompact, +}: ColumnPickerProps) => { const { t } = useTranslation(); const { getPref, setPref } = useAuth(); const breakpoint = useBreakpoint(); @@ -28,14 +49,16 @@ const ColumnPicker = ({ preference, columns, hiddenColumns, setHiddenColumns }: useEffect(() => { const savedPrefs = getPref(preference); - setHiddenColumns(savedPrefs ? savedPrefs.split(',') : []); + setHiddenColumns(savedPrefs ? savedPrefs.split(',') : defaultHiddenColumns); }, []); - if (breakpoint === 'base' || breakpoint === 'sm') { + if (isCompact || breakpoint === 'base' || breakpoint === 'sm') { return ( - } /> - + + } /> + + {columns.map((column) => ( handleColumnClick(column.id)}> - } minWidth="120px"> + } minWidth="120px"> {t('common.columns')} diff --git a/src/components/CustomFields/RrmFormField/AlgorithmPicker.tsx b/src/components/CustomFields/RrmFormField/AlgorithmPicker.tsx index b9557dd..e871b1c 100644 --- a/src/components/CustomFields/RrmFormField/AlgorithmPicker.tsx +++ b/src/components/CustomFields/RrmFormField/AlgorithmPicker.tsx @@ -98,6 +98,7 @@ const AlgorithmPicker = ({ algorithms, value, onChange, onRemove, isDisabled, op colorScheme="red" onClick={onRemove} icon={} + isDisabled={isDisabled} mt={1} /> diff --git a/src/components/CustomFields/RrmFormField/Algorithms.tsx b/src/components/CustomFields/RrmFormField/Algorithms.tsx index 8236478..59fa54e 100644 --- a/src/components/CustomFields/RrmFormField/Algorithms.tsx +++ b/src/components/CustomFields/RrmFormField/Algorithms.tsx @@ -23,9 +23,10 @@ const DeviceRulesAlgorithms = ({ algorithms, value, setValue, isDisabled }: Prop } }; const onAdd = () => { - if (algorithms?.[0]) { + const unusedAlgos = algorithms?.filter((a) => !value?.find((v) => v.name === a.shortName)); + if (unusedAlgos?.[0]) { const newValues = value ? [...value] : []; - newValues.push({ name: algorithms[0].shortName, parameters: '' }); + newValues.push({ name: unusedAlgos[0].shortName, parameters: '' }); setValue([...newValues]); } }; @@ -62,7 +63,7 @@ const DeviceRulesAlgorithms = ({ algorithms, value, setValue, isDisabled }: Prop /> ))}
-
diff --git a/src/components/CustomFields/RrmFormField/Form.tsx b/src/components/CustomFields/RrmFormField/Form.tsx index 598125f..d662b56 100644 --- a/src/components/CustomFields/RrmFormField/Form.tsx +++ b/src/components/CustomFields/RrmFormField/Form.tsx @@ -1,14 +1,14 @@ import * as React from 'react'; -import { Alert, Box, Flex, FormControl, FormLabel, Select, UseDisclosureReturn } from '@chakra-ui/react'; +import { Alert, Box, Flex, UseDisclosureReturn } from '@chakra-ui/react'; import { useTranslation } from 'react-i18next'; -import { v4 as uuid } from 'uuid'; import DeviceRulesAlgorithms from './Algorithms'; -import { CUSTOM_RRM, DEFAULT_RRM_CRON, isCustomRrm, isValidCustomRrm, RRM_VALUE } from './helper'; +import { CUSTOM_RRM, isCustomRrm, isValidCustomRrm, RRM_VALUE } from './helper'; import RrmProviderPicker from './ProviderPicker'; import RrmScheduler from './Scheduler'; +import RrmTypePicker from './TypePicker'; import SaveButton from 'components/Buttons/SaveButton'; import { Modal } from 'components/Modals/Modal'; -import { RrmAlgorithm, RrmProvider } from 'hooks/Network/Rrm'; +import { RrmProviderCompleteInformation } from 'hooks/Network/Rrm'; const extractValueFromProps: (value: unknown) => RRM_VALUE = (value: unknown) => { try { @@ -36,26 +36,16 @@ type Props = { modalProps: UseDisclosureReturn; value: unknown; onChange: (v: RRM_VALUE) => void; - algorithms?: RrmAlgorithm[]; - provider?: RrmProvider; + providers?: RrmProviderCompleteInformation[]; isDisabled?: boolean; }; -const EditRrmForm = ({ value, modalProps, onChange, algorithms, provider, isDisabled }: Props) => { +const EditRrmForm = ({ value, modalProps, onChange, providers, isDisabled }: Props) => { const { t } = useTranslation(); const [newValue, setNewValue] = React.useState(extractValueFromProps(value)); - const options = [ - { label: t('common.custom'), value: 'custom' }, - { label: t('common.no'), value: 'no' }, - { label: t('common.inherit'), value: 'inherit' }, - ]; - const isCustom = isCustomRrm(newValue); - const onVendorChange = (vendor: string) => { - if (isCustomRrm(newValue)) setNewValue({ ...newValue, vendor }); - }; const onAlgoChange = (v: { name: string; parameters: string }[]) => { if (isCustomRrm(newValue)) setNewValue({ ...newValue, algorithms: v }); }; @@ -63,23 +53,6 @@ const EditRrmForm = ({ value, modalProps, onChange, algorithms, provider, isDisa if (isCustomRrm(newValue)) setNewValue({ ...newValue, schedule }); }; - const onSelectChange = (e: React.ChangeEvent) => { - if (e.target.value === 'custom') { - setNewValue({ - vendor: provider?.vendorShortname ?? '', - schedule: DEFAULT_RRM_CRON, - algorithms: [ - { - name: algorithms?.[0]?.shortName ?? '', - parameters: '', - }, - ], - }); - } else if (e.target.value === 'no' || e.target.value === 'inherit') { - setNewValue(e.target.value); - } - }; - const onSave = () => { if (isCustomRrm(newValue)) { onChange({ ...newValue, schedule: `0 ${newValue.schedule}` }); @@ -109,11 +82,7 @@ const EditRrmForm = ({ value, modalProps, onChange, algorithms, provider, isDisa onClose={modalProps.onClose} topRightButtons={ - + } options={{ @@ -121,52 +90,38 @@ const EditRrmForm = ({ value, modalProps, onChange, algorithms, provider, isDisa }} > - {isCustom && (!provider || !algorithms) && {t('rrm.cant_save_custom')}} + {isCustom && !providers && {t('rrm.cant_save_custom')}} - - - {t('common.mode')} - - - + {isCustomRrm(newValue) && ( <> p.rrm.vendorShortname === newValue.vendor)?.algorithms ?? []} value={newValue.algorithms} setValue={onAlgoChange} - isDisabled={isDisabled || !provider} + isDisabled={isDisabled || !providers} /> diff --git a/src/components/CustomFields/RrmFormField/ProviderPicker.tsx b/src/components/CustomFields/RrmFormField/ProviderPicker.tsx index 89ddcc7..0a2adab 100644 --- a/src/components/CustomFields/RrmFormField/ProviderPicker.tsx +++ b/src/components/CustomFields/RrmFormField/ProviderPicker.tsx @@ -2,26 +2,40 @@ import * as React from 'react'; import { Alert, Box, Flex, FormControl, FormLabel, Link, Select, Text } from '@chakra-ui/react'; import { useTranslation } from 'react-i18next'; import { v4 as uuid } from 'uuid'; +import { DEFAULT_RRM_CRON, RRM_VALUE } from './helper'; import { InfoPopover } from 'components/InfoPopover'; -import { RrmProvider } from 'hooks/Network/Rrm'; +import { RrmProviderCompleteInformation } from 'hooks/Network/Rrm'; type Props = { - providers: RrmProvider[]; - setValue: (v: string) => void; + setValue: (v: RRM_VALUE) => void; value?: string; isDisabled?: boolean; + providers?: RrmProviderCompleteInformation[]; }; + const RrmProviderPicker = ({ providers, value, setValue, isDisabled }: Props) => { const { t } = useTranslation(); - const options = providers.map((p) => ({ label: p.vendor, value: p.vendorShortname })); + const options = providers?.map((p) => ({ label: p.rrm.vendor, value: p.rrm.vendorShortname })) ?? []; - const provider = providers.find((p) => p.vendorShortname === value); + const provider = providers?.find((p) => p.rrm.vendorShortname === value); const onSelectChange = (e: React.ChangeEvent) => { - setValue(e.target.value); + const found = providers?.find((p) => p.rrm.vendorShortname === e.target.value); + if (found) { + setValue({ + vendor: found.rrm.vendorShortname ?? '', + schedule: DEFAULT_RRM_CRON, + algorithms: [ + { + name: found.algorithms?.[0]?.shortName ?? '', + parameters: '', + }, + ], + }); + } }; - if (providers.length === 0 || !value) { + if (providers?.length === 0 || !value || !providers) { return ( @@ -61,13 +75,19 @@ const RrmProviderPicker = ({ providers, value, setValue, isDisabled }: Props) => > - {t('rrm.version')}: {provider?.version} + {t('rrm.version')}: {provider?.rrm.version} {t('common.details')}: - - {provider?.about} - + {provider?.rrm.about.includes('http') ? ( + + {provider?.rrm.about} + + ) : ( + + {provider?.rrm.about} + + )} diff --git a/src/components/CustomFields/RrmFormField/TypePicker.tsx b/src/components/CustomFields/RrmFormField/TypePicker.tsx new file mode 100644 index 0000000..e6b6754 --- /dev/null +++ b/src/components/CustomFields/RrmFormField/TypePicker.tsx @@ -0,0 +1,67 @@ +import * as React from 'react'; +import { FormControl, FormLabel, Select } from '@chakra-ui/react'; +import { useTranslation } from 'react-i18next'; +import { v4 as uuid } from 'uuid'; +import { DEFAULT_RRM_CRON, RRM_VALUE } from './helper'; +import { RrmProviderCompleteInformation } from 'hooks/Network/Rrm'; + +type Props = { + value: 'custom' | 'no' | 'inherit'; + onChange: (v: RRM_VALUE) => void; + providers?: RrmProviderCompleteInformation[]; + isDisabled?: boolean; +}; + +const RrmTypePicker = ({ value, onChange, providers, isDisabled }: Props) => { + const { t } = useTranslation(); + + const options = [ + { label: t('common.custom'), value: 'custom' }, + { label: t('common.no'), value: 'no' }, + { label: t('common.inherit'), value: 'inherit' }, + ]; + + const onRrmTypeChange = (e: React.ChangeEvent) => { + if (e.target.value === 'custom' && providers?.[0]) { + onChange({ + vendor: providers?.[0]?.rrm.vendorShortname ?? '', + schedule: DEFAULT_RRM_CRON, + algorithms: [ + { + name: providers?.[0]?.algorithms?.[0]?.shortName ?? '', + parameters: '', + }, + ], + }); + } else if (e.target.value === 'no' || e.target.value === 'inherit') { + onChange(e.target.value); + } else { + onChange('no'); + } + }; + + return ( + + + {t('common.mode')} + + + + ); +}; + +export default RrmTypePicker; diff --git a/src/components/CustomFields/RrmFormField/index.tsx b/src/components/CustomFields/RrmFormField/index.tsx index a681f6f..afabf6f 100644 --- a/src/components/CustomFields/RrmFormField/index.tsx +++ b/src/components/CustomFields/RrmFormField/index.tsx @@ -3,8 +3,8 @@ import { Button, FormControl, FormErrorMessage, FormLabel, useDisclosure } from import { useTranslation } from 'react-i18next'; import EditRrmForm from './Form'; import { isCustomRrm } from './helper'; -import { useGetRrmAlgorithms, useGetRrmProvider } from 'hooks/Network/Rrm'; import useFastField from 'hooks/useFastField'; +import { useRrm } from 'hooks/useRrm'; type Props = { namePrefix?: string; @@ -16,8 +16,7 @@ const RrmFormField = ({ namePrefix = 'deviceRules', isDisabled }: Props) => { const name = `${namePrefix}.rrm`; const { value, isError, error, onChange } = useFastField({ name }); const modalProps = useDisclosure(); - const { data: provider, isLoading: isLoadingProvider } = useGetRrmProvider(); - const { data: algos, isLoading: isLoadingAlgos } = useGetRrmAlgorithms(); + const rrm = useRrm(); const displayedValue = React.useMemo(() => { try { @@ -47,7 +46,7 @@ const RrmFormField = ({ namePrefix = 'deviceRules', isDisabled }: Props) => { colorScheme="blue" mt={2} ml={1} - isLoading={isLoadingProvider || isLoadingAlgos} + isLoading={rrm.getProviders.isFetching} > {displayedValue} @@ -56,8 +55,7 @@ const RrmFormField = ({ namePrefix = 'deviceRules', isDisabled }: Props) => { value={value} modalProps={modalProps} onChange={onChange} - algorithms={algos} - provider={provider} + providers={rrm.getProviders.data} isDisabled={isDisabled} /> diff --git a/src/components/DataTable/index.tsx b/src/components/DataTable/index.tsx index 8fc2b8a..8960b5c 100644 --- a/src/components/DataTable/index.tsx +++ b/src/components/DataTable/index.tsx @@ -45,15 +45,18 @@ const defaultProps = { sortBy: [], }; -type DataTableProps = { - columns: readonly Column[]; - data: object[]; +type DataTableProps = { + columns: Column[]; + data: TValue[]; count?: number; setPageInfo?: React.Dispatch>; isLoading?: boolean; + onRowClick?: (row: TValue) => void; + isRowClickable?: (row: TValue) => boolean; obj?: string; sortBy?: { id: string; desc: boolean }[]; hiddenColumns?: string[]; + hideEmptyListText?: boolean; hideControls?: boolean; minHeight?: string | number; fullScreen?: boolean; @@ -68,7 +71,7 @@ type TableInstanceWithHooks = TableInstance & state: UsePaginationState; }; -const DataTable = ({ +const DataTable = ({ columns, data, isLoading, @@ -78,16 +81,19 @@ const DataTable = ({ sortBy, hiddenColumns, hideControls, + hideEmptyListText, count, setPageInfo, isManual, saveSettingsId, showAllRows, -}: DataTableProps) => { + onRowClick, + isRowClickable, +}: DataTableProps) => { const { t } = useTranslation(); const breakpoint = useBreakpoint(); - const hoveredRowBg = useColorModeValue('gray.100', 'gray.600'); const textColor = useColorModeValue('gray.700', 'white'); + const hoveredRowBg = useColorModeValue('gray.100', 'gray.600'); const getPageSize = () => { try { if (showAllRows) return 1000000; @@ -142,7 +148,7 @@ const DataTable = ({ }, useSortBy, usePagination, - ) as TableInstanceWithHooks; + ) as TableInstanceWithHooks; const handleGoToPage = (newPage: number) => { if (saveSettingsId) localStorage.setItem(`${saveSettingsId}.page`, String(newPage)); @@ -259,8 +265,10 @@ const DataTable = ({ {data.length > 0 && ( - {page.map((row: Row) => { + {page.map((row: Row) => { prepareRow(row); + const rowIsClickable = isRowClickable ? isRowClickable(row.original) : true; + const onClick = rowIsClickable && onRowClick ? () => onRowClick(row.original) : undefined; return ( { // @ts-ignore @@ -287,8 +296,26 @@ const DataTable = ({ fontSize="14px" // @ts-ignore textAlign={cell.column.isCentered ? 'center' : undefined} - // @ts-ignore - fontFamily={cell.column.isMonospace ? 'monospace' : undefined} + fontFamily={ + // @ts-ignore + 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')} @@ -300,7 +327,7 @@ const DataTable = ({ )} - {!isLoading && data.length === 0 && ( + {!isLoading && data.length === 0 && !hideEmptyListText && (
{obj ? ( @@ -413,4 +440,4 @@ const DataTable = ({ DataTable.defaultProps = defaultProps; -export default React.memo(DataTable); +export default DataTable; diff --git a/src/components/SortableDataTable/index.tsx b/src/components/SortableDataTable/index.tsx index a4cabf4..795fcc7 100644 --- a/src/components/SortableDataTable/index.tsx +++ b/src/components/SortableDataTable/index.tsx @@ -26,24 +26,33 @@ import { Heading, useBreakpoint, } from '@chakra-ui/react'; -// @ts-ignore import { useTranslation } from 'react-i18next'; -import { useTable, usePagination, useSortBy, Row } from 'react-table'; +import { + useTable, + usePagination, + useSortBy, + Row, + UsePaginationInstanceProps, + UseSortByInstanceProps, + UsePaginationState, +} from 'react-table'; import { v4 as uuid } from 'uuid'; import SortIcon from './SortIcon'; import { isColumnSorted, isSortedDesc, onSortClick } from './utils'; import LoadingOverlay from 'components/LoadingOverlay'; import { Column, PageInfo, SortInfo } from 'models/Table'; -interface Props { - columns: Column[]; - data: object[]; +interface Props { + columns: Column[]; + data: TValue[]; count?: number; setPageInfo?: React.Dispatch>; sortInfo: SortInfo; setSortInfo: React.Dispatch>; isLoading?: boolean; obj: string; + onRowClick?: (row: TValue) => void; + isRowClickable?: (row: TValue) => boolean; sortBy?: { id: string; desc: boolean }[]; hiddenColumns?: string[]; hideControls?: boolean; @@ -53,6 +62,12 @@ interface Props { saveSettingsId?: string; } +type TableInstanceWithHooks = TableInstance & + UsePaginationInstanceProps & + UseSortByInstanceProps & { + state: UsePaginationState; + }; + const defaultProps = { count: undefined, setPageInfo: undefined, @@ -66,7 +81,7 @@ const defaultProps = { saveSettingsId: undefined, }; -const SortableDataTable = ({ +const SortableDataTable = ({ columns, data, isLoading, @@ -82,7 +97,9 @@ const SortableDataTable = ({ setPageInfo, isManual, saveSettingsId, -}: Props) => { + onRowClick, + isRowClickable, +}: Props) => { const { t } = useTranslation(); const breakpoint = useBreakpoint(); const hoveredRowBg = useColorModeValue('gray.100', 'gray.600'); @@ -112,21 +129,23 @@ const SortableDataTable = ({ state: { pageIndex, pageSize }, } = useTable( { + // @ts-ignore columns, - data, + data, // @ts-ignore initialState: { sortBy, pagination: !hideControls, pageSize: queryPageSize }, manualPagination: isManual, pageCount: isManual && count !== undefined ? Math.ceil(count / queryPageSize) : undefined, }, useSortBy, usePagination, - ); + ) as TableInstanceWithHooks; useEffect(() => { if (setPageInfo && pageIndex !== undefined) setPageInfo({ index: pageIndex, limit: queryPageSize }); }, [queryPageSize, pageIndex]); useEffect(() => { + // @ts-ignore if (saveSettingsId) localStorage.setItem(saveSettingsId, pageSize); setQueryPageSize(pageSize); }, [pageSize]); @@ -170,48 +189,44 @@ const SortableDataTable = ({ - { - // @ts-ignore - headerGroups.map((group) => ( - - { + {headerGroups.map((group) => ( + + {group.headers.map((column) => ( + - )) - } - - )) - } + canSort={column.canSort} + /> + + + ))} + + ))} {data.length > 0 && ( - {page.map((row: Row) => { + {page.map((row: Row) => { prepareRow(row); + const rowIsClickable = isRowClickable ? isRowClickable(row.original) : true; + const onClick = rowIsClickable && onRowClick ? () => onRowClick(row.original) : undefined; return ( { // @ts-ignore @@ -238,8 +254,26 @@ const SortableDataTable = ({ fontSize="14px" // @ts-ignore textAlign={cell.column.isCentered ? 'center' : undefined} - // @ts-ignore - fontFamily={cell.column.isMonospace ? 'monospace' : undefined} + fontFamily={ + // @ts-ignore + 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')} @@ -300,7 +334,7 @@ const SortableDataTable = ({ w={28} min={1} max={pageOptions.length} - onChange={(_, numberValue) => { + onChange={(_: unknown, numberValue: number) => { const newPage = numberValue ? numberValue - 1 : 0; gotoPage(newPage); }} @@ -317,7 +351,7 @@ const SortableDataTable = ({
( - +
onSortClick(column.id, sortInfo, setSortInfo)} + style={{ alignContent: 'center', overflow: 'hidden', whiteSpace: 'nowrap' }} + > + {column.render('Header')} + -
onSortClick(column.id, sortInfo, setSortInfo)} - style={{ alignContent: 'center', overflow: 'hidden', whiteSpace: 'nowrap' }} - > - {column.render('Header')} - -
-