mirror of
https://github.com/Telecominfraproject/wlan-cloud-ucentralgw-ui.git
synced 2025-10-30 18:27:53 +00:00
Compare commits
5 Commits
v4.0.0
...
release/v3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa2b923111 | ||
|
|
9b56a5ea87 | ||
|
|
2ec6837dce | ||
|
|
9e3df09fb2 | ||
|
|
2445935627 |
1
.env
Normal file
1
.env
Normal file
@@ -0,0 +1 @@
|
|||||||
|
VITE_UCENTRALSEC_URL="https://ucentral.dpaas.arilia.com:16001"
|
||||||
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -20,7 +20,7 @@ defaults:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
docker:
|
docker:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-20.04
|
||||||
env:
|
env:
|
||||||
DOCKER_REGISTRY_URL: tip-tip-wlan-cloud-ucentral.jfrog.io
|
DOCKER_REGISTRY_URL: tip-tip-wlan-cloud-ucentral.jfrog.io
|
||||||
DOCKER_REGISTRY_USERNAME: ucentral
|
DOCKER_REGISTRY_USERNAME: ucentral
|
||||||
|
|||||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -11,7 +11,7 @@ defaults:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
helm-package:
|
helm-package:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-20.04
|
||||||
env:
|
env:
|
||||||
HELM_REPO_URL: https://tip.jfrog.io/artifactory/tip-wlan-cloud-ucentral-helm/
|
HELM_REPO_URL: https://tip.jfrog.io/artifactory/tip-wlan-cloud-ucentral-helm/
|
||||||
HELM_REPO_USERNAME: ucentral
|
HELM_REPO_USERNAME: ucentral
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ fullnameOverride: ""
|
|||||||
images:
|
images:
|
||||||
owgwui:
|
owgwui:
|
||||||
repository: tip-tip-wlan-cloud-ucentral.jfrog.io/owgw-ui
|
repository: tip-tip-wlan-cloud-ucentral.jfrog.io/owgw-ui
|
||||||
tag: v4.0.0
|
tag: v3.0.2-RC1
|
||||||
pullPolicy: Always
|
pullPolicy: Always
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
|||||||
49
package-lock.json
generated
49
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "ucentral-client",
|
"name": "ucentral-client",
|
||||||
"version": "3.2.0",
|
"version": "3.0.2(9)",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "ucentral-client",
|
"name": "ucentral-client",
|
||||||
"version": "3.2.0",
|
"version": "3.0.2(9)",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@chakra-ui/anatomy": "^2.1.1",
|
"@chakra-ui/anatomy": "^2.1.1",
|
||||||
@@ -3540,7 +3540,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/@jridgewell/resolve-uri": {
|
"node_modules/@jridgewell/resolve-uri": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.0.0"
|
"node": ">=6.0.0"
|
||||||
@@ -3548,7 +3548,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/@jridgewell/set-array": {
|
"node_modules/@jridgewell/set-array": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.0.0"
|
"node": ">=6.0.0"
|
||||||
@@ -3556,7 +3556,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/@jridgewell/source-map": {
|
"node_modules/@jridgewell/source-map": {
|
||||||
"version": "0.3.2",
|
"version": "0.3.2",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/gen-mapping": "^0.3.0",
|
"@jridgewell/gen-mapping": "^0.3.0",
|
||||||
@@ -3565,7 +3565,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/@jridgewell/source-map/node_modules/@jridgewell/gen-mapping": {
|
"node_modules/@jridgewell/source-map/node_modules/@jridgewell/gen-mapping": {
|
||||||
"version": "0.3.2",
|
"version": "0.3.2",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/set-array": "^1.0.1",
|
"@jridgewell/set-array": "^1.0.1",
|
||||||
@@ -3578,12 +3578,12 @@
|
|||||||
},
|
},
|
||||||
"node_modules/@jridgewell/sourcemap-codec": {
|
"node_modules/@jridgewell/sourcemap-codec": {
|
||||||
"version": "1.4.14",
|
"version": "1.4.14",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@jridgewell/trace-mapping": {
|
"node_modules/@jridgewell/trace-mapping": {
|
||||||
"version": "0.3.17",
|
"version": "0.3.17",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/resolve-uri": "3.1.0",
|
"@jridgewell/resolve-uri": "3.1.0",
|
||||||
@@ -4374,7 +4374,7 @@
|
|||||||
"version": "18.15.11",
|
"version": "18.15.11",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.11.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.11.tgz",
|
||||||
"integrity": "sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q==",
|
"integrity": "sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q==",
|
||||||
"dev": true
|
"devOptional": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/parse-json": {
|
"node_modules/@types/parse-json": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
@@ -4419,7 +4419,7 @@
|
|||||||
"version": "18.0.11",
|
"version": "18.0.11",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.11.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.11.tgz",
|
||||||
"integrity": "sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw==",
|
"integrity": "sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/react": "*"
|
"@types/react": "*"
|
||||||
}
|
}
|
||||||
@@ -4753,7 +4753,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/acorn": {
|
"node_modules/acorn": {
|
||||||
"version": "8.8.0",
|
"version": "8.8.0",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
@@ -5174,7 +5174,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/buffer-from": {
|
"node_modules/buffer-from": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/builtin-modules": {
|
"node_modules/builtin-modules": {
|
||||||
@@ -5671,9 +5671,8 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/ejs": {
|
"node_modules/ejs": {
|
||||||
"version": "3.1.10",
|
"version": "3.1.8",
|
||||||
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
|
"license": "Apache-2.0",
|
||||||
"integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"jake": "^10.8.5"
|
"jake": "^10.8.5"
|
||||||
},
|
},
|
||||||
@@ -6683,9 +6682,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/follow-redirects": {
|
"node_modules/follow-redirects": {
|
||||||
"version": "1.15.6",
|
"version": "1.15.4",
|
||||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
|
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz",
|
||||||
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
|
"integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "individual",
|
"type": "individual",
|
||||||
@@ -9781,7 +9780,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/source-map-support": {
|
"node_modules/source-map-support": {
|
||||||
"version": "0.5.21",
|
"version": "0.5.21",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"buffer-from": "^1.0.0",
|
"buffer-from": "^1.0.0",
|
||||||
@@ -9790,7 +9789,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/source-map-support/node_modules/source-map": {
|
"node_modules/source-map-support/node_modules/source-map": {
|
||||||
"version": "0.6.1",
|
"version": "0.6.1",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
@@ -10080,7 +10079,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/terser": {
|
"node_modules/terser": {
|
||||||
"version": "5.15.1",
|
"version": "5.15.1",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/source-map": "^0.3.2",
|
"@jridgewell/source-map": "^0.3.2",
|
||||||
@@ -10097,7 +10096,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/terser/node_modules/commander": {
|
"node_modules/terser/node_modules/commander": {
|
||||||
"version": "2.20.3",
|
"version": "2.20.3",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/text-table": {
|
"node_modules/text-table": {
|
||||||
@@ -10443,9 +10442,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "4.5.3",
|
"version": "4.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.2.tgz",
|
||||||
"integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==",
|
"integrity": "sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.18.10",
|
"esbuild": "^0.18.10",
|
||||||
"postcss": "^8.4.27",
|
"postcss": "^8.4.27",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ucentral-client",
|
"name": "ucentral-client",
|
||||||
"version": "4.0.0",
|
"version": "3.0.2(9)",
|
||||||
"description": "",
|
"description": "",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "index.tsx",
|
"main": "index.tsx",
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 294 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 394 KiB |
@@ -173,7 +173,7 @@ const DeviceActionDropdown = ({
|
|||||||
isLoading={isRtty}
|
isLoading={isRtty}
|
||||||
onClick={handleConnectClick}
|
onClick={handleConnectClick}
|
||||||
colorScheme={connectColor}
|
colorScheme={connectColor}
|
||||||
hidden={isCompact}
|
hidden={isCompact || deviceType !== 'ap'}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip label={t('controller.configure.title')}>
|
<Tooltip label={t('controller.configure.title')}>
|
||||||
|
|||||||
@@ -1,257 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import {
|
|
||||||
Modal,
|
|
||||||
Text,
|
|
||||||
ModalOverlay,
|
|
||||||
ModalContent,
|
|
||||||
ModalBody,
|
|
||||||
Center,
|
|
||||||
Spinner,
|
|
||||||
Checkbox,
|
|
||||||
Stack,
|
|
||||||
Table,
|
|
||||||
Thead,
|
|
||||||
Tbody,
|
|
||||||
Tr,
|
|
||||||
Th,
|
|
||||||
Td,
|
|
||||||
} from '@chakra-ui/react';
|
|
||||||
import { PlugsConnected } from '@phosphor-icons/react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { CloseButton } from 'components/Buttons/CloseButton';
|
|
||||||
import { ResponsiveButton } from 'components/Buttons/ResponsiveButton';
|
|
||||||
import { ModalHeader } from 'components/Containers/Modal/ModalHeader';
|
|
||||||
import { useCableDiagnostics } from 'hooks/Network/Devices';
|
|
||||||
import { ModalProps } from 'models/Modal';
|
|
||||||
import Button from 'theme/components/button';
|
|
||||||
import { DataGridColumn, useDataGrid } from 'components/DataTables/DataGrid/useDataGrid';
|
|
||||||
import { DataGrid } from 'components/DataTables/DataGrid';
|
|
||||||
|
|
||||||
export type CableDiagnosticsModalProps = {
|
|
||||||
modalProps: ModalProps;
|
|
||||||
serialNumber: string;
|
|
||||||
port: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type DiagnosticsRow = {
|
|
||||||
port: string;
|
|
||||||
linkStatus: string;
|
|
||||||
pairA: string;
|
|
||||||
pairB: string;
|
|
||||||
pairC: string;
|
|
||||||
pairD: string;
|
|
||||||
type: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type OpticalRow = {
|
|
||||||
port: string;
|
|
||||||
vendorName: string;
|
|
||||||
formFactor: string;
|
|
||||||
partNumber: string;
|
|
||||||
serialNumber: string;
|
|
||||||
temperature: string;
|
|
||||||
txPower: string;
|
|
||||||
rxPower: string;
|
|
||||||
revision: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const CableDiagnosticsModal = ({
|
|
||||||
modalProps: { isOpen, onClose },
|
|
||||||
serialNumber,
|
|
||||||
port,
|
|
||||||
}: CableDiagnosticsModalProps) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [selectedPorts, setSelectedPorts] = React.useState<string[]>([]);
|
|
||||||
const [diagnosticsResult, setDiagnosticsResult] = React.useState<any>(null);
|
|
||||||
const { mutateAsync: diagnose, isLoading } = useCableDiagnostics({ serialNumber });
|
|
||||||
|
|
||||||
const handlePortToggle = (port: string) => {
|
|
||||||
setSelectedPorts((prev) => (prev.includes(port) ? prev.filter((p) => p !== port) : [...prev, port]));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDiagnose = async () => {
|
|
||||||
if (port) {
|
|
||||||
try {
|
|
||||||
const result = await diagnose([port]);
|
|
||||||
setDiagnosticsResult(result);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error diagnosing cable:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const tableController = useDataGrid({
|
|
||||||
tableSettingsId: 'cable.diagnostics.table',
|
|
||||||
defaultOrder: ['port', 'linkStatus', 'pairA', 'pairB', 'pairC', 'pairD', 'type'],
|
|
||||||
showAllRows: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const columns: DataGridColumn<DiagnosticsRow | OpticalRow>[] = React.useMemo(() => {
|
|
||||||
const data = diagnosticsResult?.results?.status?.text?.[port];
|
|
||||||
const isOpticalData = data && 'form-factor' in data;
|
|
||||||
|
|
||||||
return isOpticalData
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
id: 'vendorName',
|
|
||||||
header: 'Vendor Name',
|
|
||||||
accessorKey: 'vendorName',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'formFactor',
|
|
||||||
header: 'Form Factor',
|
|
||||||
accessorKey: 'formFactor',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'partNumber',
|
|
||||||
header: 'Part Number',
|
|
||||||
accessorKey: 'partNumber',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'serialNumber',
|
|
||||||
header: 'Serial Number',
|
|
||||||
accessorKey: 'serialNumber',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'temperature',
|
|
||||||
header: 'Temperature',
|
|
||||||
accessorKey: 'temperature',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'txPower',
|
|
||||||
header: 'TX Power',
|
|
||||||
accessorKey: 'txPower',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'rxPower',
|
|
||||||
header: 'RX Power',
|
|
||||||
accessorKey: 'rxPower',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'revision',
|
|
||||||
header: 'Revision',
|
|
||||||
accessorKey: 'revision',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: [
|
|
||||||
{
|
|
||||||
id: 'port',
|
|
||||||
header: 'Port',
|
|
||||||
accessorKey: 'port',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'linkStatus',
|
|
||||||
header: 'Link Status',
|
|
||||||
accessorKey: 'linkStatus',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'pairA',
|
|
||||||
header: 'Pair A',
|
|
||||||
accessorKey: 'pairA',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'pairB',
|
|
||||||
header: 'Pair B',
|
|
||||||
accessorKey: 'pairB',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'pairC',
|
|
||||||
header: 'Pair C',
|
|
||||||
accessorKey: 'pairC',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'pairD',
|
|
||||||
header: 'Pair D',
|
|
||||||
accessorKey: 'pairD',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'type',
|
|
||||||
header: 'Type',
|
|
||||||
accessorKey: 'type',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}, [diagnosticsResult]);
|
|
||||||
|
|
||||||
const formatDiagnosticsData = (result: any): (DiagnosticsRow | OpticalRow)[] => {
|
|
||||||
if (!result?.results?.status?.text?.[port]) return [];
|
|
||||||
|
|
||||||
const data = result.results.status.text[port];
|
|
||||||
|
|
||||||
if (data['form-factor']) {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
port,
|
|
||||||
vendorName: data['vendor-name'] || 'N/A',
|
|
||||||
formFactor: data['form-factor'] || 'N/A',
|
|
||||||
partNumber: data['part-number'] || 'N/A',
|
|
||||||
serialNumber: data['serial-number'] || 'N/A',
|
|
||||||
temperature: data.temperature ? `${data.temperature.toFixed(2)}` : 'N/A',
|
|
||||||
txPower: data['tx-optical-power'] ? `${data['tx-optical-power']}` : 'N/A',
|
|
||||||
rxPower: data['rx-optical-power'] ? `${data['rx-optical-power']}` : 'N/A',
|
|
||||||
revision: data.revision || 'N/A',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
port,
|
|
||||||
linkStatus: data['link-status'],
|
|
||||||
pairA: `${data['pair-A'].meters} (${data['pair-A'].status})`,
|
|
||||||
pairB: `${data['pair-B'].meters} (${data['pair-B'].status})`,
|
|
||||||
pairC: `${data['pair-C'].meters} (${data['pair-C'].status})`,
|
|
||||||
pairD: `${data['pair-D'].meters} (${data['pair-D'].status})`,
|
|
||||||
type: data.type,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal onClose={onClose} isOpen={isOpen} size="xl">
|
|
||||||
<ModalOverlay />
|
|
||||||
<ModalContent maxW="50vw">
|
|
||||||
<ModalHeader title={t('commands.cable_diagnostics')} right={<CloseButton onClick={onClose} />} />
|
|
||||||
<ModalBody pb={6}>
|
|
||||||
{isLoading ? (
|
|
||||||
<Center my={4} flexDirection="column" gap={4}>
|
|
||||||
<Spinner size="lg" />
|
|
||||||
<Text>Please wait...</Text>
|
|
||||||
<Text fontSize="sm" color="gray.500">
|
|
||||||
Please do not close this window. This may take a few seconds.
|
|
||||||
</Text>
|
|
||||||
</Center>
|
|
||||||
) : (
|
|
||||||
<Center flexDirection="column" gap={4}>
|
|
||||||
<ResponsiveButton
|
|
||||||
color="blue"
|
|
||||||
icon={<PlugsConnected size={20} />}
|
|
||||||
label={`${
|
|
||||||
diagnosticsResult && formatDiagnosticsData(diagnosticsResult).length > 0 ? 'Retake' : 'Start'
|
|
||||||
} Test for Port ${port}`}
|
|
||||||
onClick={handleDiagnose}
|
|
||||||
isLoading={isLoading}
|
|
||||||
isDisabled={!port}
|
|
||||||
isCompact={false}
|
|
||||||
/>
|
|
||||||
{diagnosticsResult && formatDiagnosticsData(diagnosticsResult).length > 0 && (
|
|
||||||
<DataGrid<DiagnosticsRow | OpticalRow>
|
|
||||||
controller={tableController}
|
|
||||||
header={{
|
|
||||||
title: '',
|
|
||||||
objectListed: 'Cable Diagnostics',
|
|
||||||
}}
|
|
||||||
columns={columns}
|
|
||||||
isLoading={isLoading}
|
|
||||||
data={formatDiagnosticsData(diagnosticsResult)}
|
|
||||||
options={{
|
|
||||||
isHidingControls: true,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Center>
|
|
||||||
)}
|
|
||||||
</ModalBody>
|
|
||||||
</ModalContent>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -25,7 +25,6 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { Modal } from '../Modal';
|
import { Modal } from '../Modal';
|
||||||
import { lowercaseFirstLetter } from 'helpers/stringHelper';
|
import { lowercaseFirstLetter } from 'helpers/stringHelper';
|
||||||
import { useTelemetry } from 'hooks/Network/Telemetry';
|
import { useTelemetry } from 'hooks/Network/Telemetry';
|
||||||
import { secondsDuration } from 'helpers/dateFormatting';
|
|
||||||
|
|
||||||
export type TelemetryModalProps = {
|
export type TelemetryModalProps = {
|
||||||
serialNumber: string;
|
serialNumber: string;
|
||||||
@@ -147,7 +146,8 @@ const _TelemetryModal = ({ serialNumber, modalProps }: TelemetryModalProps) => {
|
|||||||
{t('controller.telemetry.interval')}: {form.interval} {lowercaseFirstLetter(t('common.seconds'))}
|
{t('controller.telemetry.interval')}: {form.interval} {lowercaseFirstLetter(t('common.seconds'))}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
{t('controller.telemetry.duration')}: {secondsDuration(form.lifetime, t)}
|
{t('controller.telemetry.duration')}: {form.interval}{' '}
|
||||||
|
{lowercaseFirstLetter(t('controller.telemetry.minutes'))}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
{t('controller.telemetry.types')}: {form.types.join(', ')}
|
{t('controller.telemetry.types')}: {form.types.join(', ')}
|
||||||
|
|||||||
@@ -377,40 +377,6 @@ export const useWifiScanDevice = ({ serialNumber }: { serialNumber: string }) =>
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useCableDiagnostics = ({ serialNumber }: { serialNumber: string }) => {
|
|
||||||
const toast = useToast();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
return useMutation(
|
|
||||||
(ports: string[]): Promise<unknown> =>
|
|
||||||
axiosGw
|
|
||||||
.post(`device/${serialNumber}/cable-diagnostics`, {
|
|
||||||
serial: serialNumber,
|
|
||||||
ports,
|
|
||||||
when: 0,
|
|
||||||
})
|
|
||||||
.then(({ data }) => data),
|
|
||||||
{
|
|
||||||
onSuccess: (data) => {
|
|
||||||
console.log('Success data: ', data);
|
|
||||||
},
|
|
||||||
onError: (e: AxiosError) => {
|
|
||||||
toast({
|
|
||||||
id: uuid(),
|
|
||||||
title: t('common.error'),
|
|
||||||
description: t('commands.cablediagnostics_error', {
|
|
||||||
e: e?.response?.data?.ErrorDescription,
|
|
||||||
}),
|
|
||||||
status: 'error',
|
|
||||||
duration: 5000,
|
|
||||||
isClosable: true,
|
|
||||||
position: 'top-right',
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useGetDeviceRtty = ({ serialNumber, extraId }: { serialNumber: string; extraId: string | number }) => {
|
export const useGetDeviceRtty = ({ serialNumber, extraId }: { serialNumber: string; extraId: string | number }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
|||||||
@@ -59,7 +59,6 @@ export type DeviceInterfaceStatistics = {
|
|||||||
dynamic_vlan?: number;
|
dynamic_vlan?: number;
|
||||||
inactive: number;
|
inactive: number;
|
||||||
ipaddr_v4: string;
|
ipaddr_v4: string;
|
||||||
fingerprint?: object;
|
|
||||||
rssi: number;
|
rssi: number;
|
||||||
rx_bytes: number;
|
rx_bytes: number;
|
||||||
rx_duration: number;
|
rx_duration: number;
|
||||||
|
|||||||
@@ -65,8 +65,6 @@ const DeviceSummary = ({ serialNumber }: Props) => {
|
|||||||
const getDeviceCompatible = () => {
|
const getDeviceCompatible = () => {
|
||||||
if (!getDevice.data?.compatible) return undefined;
|
if (!getDevice.data?.compatible) return undefined;
|
||||||
|
|
||||||
if (getDevice.data.compatible.includes(' ')) return getDevice.data.compatible.replaceAll(' ', '_');
|
|
||||||
|
|
||||||
return getDevice.data?.compatible;
|
return getDevice.data?.compatible;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,17 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { IconButton, Tooltip, useToast } from '@chakra-ui/react';
|
import { IconButton, Tooltip, useToast } from '@chakra-ui/react';
|
||||||
import { Power, PlugsConnected } from '@phosphor-icons/react';
|
import { Power } from '@phosphor-icons/react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { usePowerCycle } from 'hooks/Network/Devices';
|
import { usePowerCycle } from 'hooks/Network/Devices';
|
||||||
import { useNotification } from 'hooks/useNotification';
|
import { useNotification } from 'hooks/useNotification';
|
||||||
import { DeviceLinkState } from 'hooks/Network/Statistics';
|
import { DeviceLinkState } from 'hooks/Network/Statistics';
|
||||||
import { CableDiagnosticsModalProps } from 'components/Modals/CableDiagnosticsModal';
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
state: DeviceLinkState & { name: string };
|
state: DeviceLinkState & { name: string };
|
||||||
deviceSerialNumber: string;
|
deviceSerialNumber: string;
|
||||||
onOpenCableDiagnostics: (port: string) => void;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const LinkStateTableActions = ({ state, deviceSerialNumber, onOpenCableDiagnostics }: Props) => {
|
const LinkStateTableActions = ({ state, deviceSerialNumber }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const powerCycle = usePowerCycle();
|
const powerCycle = usePowerCycle();
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
@@ -56,27 +54,16 @@ const LinkStateTableActions = ({ state, deviceSerialNumber, onOpenCableDiagnosti
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Tooltip label="Power Cycle" placement="auto-start">
|
||||||
<Tooltip label="Power Cycle" placement="auto-start">
|
<IconButton
|
||||||
<IconButton
|
aria-label="Power Cycle"
|
||||||
aria-label="Power Cycle"
|
icon={<Power size={20} />}
|
||||||
icon={<Power size={20} />}
|
colorScheme="green"
|
||||||
colorScheme="green"
|
onClick={onPowerCycle}
|
||||||
onClick={onPowerCycle}
|
isLoading={powerCycle.isLoading}
|
||||||
isLoading={powerCycle.isLoading}
|
size="xs"
|
||||||
size="xs"
|
/>
|
||||||
/>
|
</Tooltip>
|
||||||
</Tooltip>
|
|
||||||
<Tooltip label="Cable Diagnostics" placement="auto-start">
|
|
||||||
<IconButton
|
|
||||||
aria-label="Cable Diagnostics"
|
|
||||||
icon={<PlugsConnected size={20} />}
|
|
||||||
colorScheme="blue"
|
|
||||||
onClick={() => onOpenCableDiagnostics(state.name)}
|
|
||||||
size="xs"
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -9,12 +9,8 @@ import LinkStateTableActions from './Actions';
|
|||||||
|
|
||||||
type Row = DeviceLinkState & { name: string };
|
type Row = DeviceLinkState & { name: string };
|
||||||
const dataCell = (v: number) => <DataCell bytes={v} />;
|
const dataCell = (v: number) => <DataCell bytes={v} />;
|
||||||
const actionCell = (row: Row, serialNumber: string, onOpenCableDiagnostics: (port: string) => void) => (
|
const actionCell = (row: Row, serialNumber: string) => (
|
||||||
<LinkStateTableActions
|
<LinkStateTableActions state={row} deviceSerialNumber={serialNumber} />
|
||||||
state={row}
|
|
||||||
deviceSerialNumber={serialNumber}
|
|
||||||
onOpenCableDiagnostics={onOpenCableDiagnostics}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -23,10 +19,9 @@ type Props = {
|
|||||||
isFetching: boolean;
|
isFetching: boolean;
|
||||||
type: 'upstream' | 'downstream';
|
type: 'upstream' | 'downstream';
|
||||||
serialNumber: string;
|
serialNumber: string;
|
||||||
onOpenCableDiagnostics: (port: string) => void;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const LinkStateTable = ({ statistics, refetch, isFetching, type, serialNumber, onOpenCableDiagnostics }: Props) => {
|
const LinkStateTable = ({ statistics, refetch, isFetching, type, serialNumber }: Props) => {
|
||||||
const tableController = useDataGrid({
|
const tableController = useDataGrid({
|
||||||
tableSettingsId: 'switch.link-state.table',
|
tableSettingsId: 'switch.link-state.table',
|
||||||
defaultOrder: [
|
defaultOrder: [
|
||||||
@@ -162,16 +157,10 @@ const LinkStateTable = ({ statistics, refetch, isFetching, type, serialNumber, o
|
|||||||
id: 'actions',
|
id: 'actions',
|
||||||
header: '',
|
header: '',
|
||||||
accessorKey: '',
|
accessorKey: '',
|
||||||
cell: ({ cell }) => (
|
cell: ({ cell }) => actionCell(cell.row.original, serialNumber),
|
||||||
<LinkStateTableActions
|
|
||||||
state={cell.row.original}
|
|
||||||
deviceSerialNumber={serialNumber}
|
|
||||||
onOpenCableDiagnostics={onOpenCableDiagnostics}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[onOpenCableDiagnostics],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!statistics || statistics?.length === 0) {
|
if (!statistics || statistics?.length === 0) {
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ import SwitchInterfaceTable from './SwitchInterfaceTable';
|
|||||||
import { DeviceLinkState, useGetDeviceLastStats } from 'hooks/Network/Statistics';
|
import { DeviceLinkState, useGetDeviceLastStats } from 'hooks/Network/Statistics';
|
||||||
import { Card } from 'components/Containers/Card';
|
import { Card } from 'components/Containers/Card';
|
||||||
import { CardBody } from 'components/Containers/Card/CardBody';
|
import { CardBody } from 'components/Containers/Card/CardBody';
|
||||||
import { CableDiagnosticsModal } from 'components/Modals/CableDiagnosticsModal';
|
|
||||||
import { useDisclosure } from '@chakra-ui/react';
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
serialNumber: string;
|
serialNumber: string;
|
||||||
@@ -14,8 +12,6 @@ type Props = {
|
|||||||
|
|
||||||
const SwitchPortExamination = ({ serialNumber }: Props) => {
|
const SwitchPortExamination = ({ serialNumber }: Props) => {
|
||||||
const [tabIndex, setTabIndex] = React.useState(0);
|
const [tabIndex, setTabIndex] = React.useState(0);
|
||||||
const [selectedPort, setSelectedPort] = React.useState<string>('');
|
|
||||||
const cableDiagnosticsModalProps = useDisclosure();
|
|
||||||
|
|
||||||
const handleTabsChange = React.useCallback((index: number) => {
|
const handleTabsChange = React.useCallback((index: number) => {
|
||||||
setTabIndex(index);
|
setTabIndex(index);
|
||||||
@@ -39,73 +35,63 @@ const SwitchPortExamination = ({ serialNumber }: Props) => {
|
|||||||
}));
|
}));
|
||||||
}, [getStats.data]);
|
}, [getStats.data]);
|
||||||
|
|
||||||
const handleOpenCableDiagnostics = React.useCallback((port: string) => {
|
|
||||||
setSelectedPort(port);
|
|
||||||
cableDiagnosticsModalProps.onOpen();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Card p={0} mb={4}>
|
||||||
<Card p={0} mb={4}>
|
<CardBody p={0} display="block">
|
||||||
<CardBody p={0} display="block">
|
<Tabs index={tabIndex} onChange={handleTabsChange} variant="enclosed" w="100%">
|
||||||
<Tabs index={tabIndex} onChange={handleTabsChange} variant="enclosed" w="100%">
|
<TabList>
|
||||||
<TabList>
|
<Tab fontSize="lg" fontWeight="bold">
|
||||||
<Tab fontSize="lg" fontWeight="bold">
|
Interfaces
|
||||||
Interfaces
|
</Tab>
|
||||||
</Tab>
|
<Tab fontSize="lg" fontWeight="bold">
|
||||||
<Tab fontSize="lg" fontWeight="bold">
|
Link-State (Up)
|
||||||
Link-State (Up)
|
</Tab>
|
||||||
</Tab>
|
<Tab fontSize="lg" fontWeight="bold">
|
||||||
<Tab fontSize="lg" fontWeight="bold">
|
Link-State (Down)
|
||||||
Link-State (Down)
|
</Tab>
|
||||||
</Tab>
|
</TabList>
|
||||||
</TabList>
|
<TabPanels>
|
||||||
<TabPanels>
|
<TabPanel>
|
||||||
<TabPanel>
|
{getStats.data ? (
|
||||||
{getStats.data ? (
|
<SwitchInterfaceTable
|
||||||
<SwitchInterfaceTable
|
statistics={getStats.data}
|
||||||
statistics={getStats.data}
|
refetch={getStats.refetch}
|
||||||
refetch={getStats.refetch}
|
isFetching={getStats.isFetching}
|
||||||
isFetching={getStats.isFetching}
|
/>
|
||||||
/>
|
) : (
|
||||||
) : (
|
<Spinner size="xl" />
|
||||||
<Spinner size="xl" />
|
)}
|
||||||
)}
|
</TabPanel>
|
||||||
</TabPanel>
|
<TabPanel>
|
||||||
<TabPanel>
|
{getStats.data ? (
|
||||||
{getStats.data ? (
|
<LinkStateTable
|
||||||
<LinkStateTable
|
statistics={upLinkStates}
|
||||||
statistics={upLinkStates}
|
refetch={getStats.refetch}
|
||||||
refetch={getStats.refetch}
|
isFetching={getStats.isFetching}
|
||||||
isFetching={getStats.isFetching}
|
type="upstream"
|
||||||
type="upstream"
|
serialNumber={serialNumber}
|
||||||
serialNumber={serialNumber}
|
/>
|
||||||
onOpenCableDiagnostics={handleOpenCableDiagnostics}
|
) : (
|
||||||
/>
|
<Spinner size="xl" />
|
||||||
) : (
|
)}
|
||||||
<Spinner size="xl" />
|
</TabPanel>
|
||||||
)}
|
<TabPanel>
|
||||||
</TabPanel>
|
{getStats.data ? (
|
||||||
<TabPanel>
|
<LinkStateTable
|
||||||
{getStats.data ? (
|
statistics={downLinkStates}
|
||||||
<LinkStateTable
|
refetch={getStats.refetch}
|
||||||
statistics={downLinkStates}
|
isFetching={getStats.isFetching}
|
||||||
refetch={getStats.refetch}
|
type="downstream"
|
||||||
isFetching={getStats.isFetching}
|
serialNumber={serialNumber}
|
||||||
type="downstream"
|
/>
|
||||||
serialNumber={serialNumber}
|
) : (
|
||||||
onOpenCableDiagnostics={handleOpenCableDiagnostics}
|
<Spinner size="xl" />
|
||||||
/>
|
)}
|
||||||
) : (
|
</TabPanel>
|
||||||
<Spinner size="xl" />
|
</TabPanels>
|
||||||
)}
|
</Tabs>
|
||||||
</TabPanel>
|
</CardBody>
|
||||||
</TabPanels>
|
</Card>
|
||||||
</Tabs>
|
|
||||||
</CardBody>
|
|
||||||
</Card>
|
|
||||||
<CableDiagnosticsModal modalProps={cableDiagnosticsModalProps} serialNumber={serialNumber} port={selectedPort} />
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ export type ParsedAssociation = {
|
|||||||
txNss: number | string;
|
txNss: number | string;
|
||||||
recorded: number;
|
recorded: number;
|
||||||
dynamicVlan?: number;
|
dynamicVlan?: number;
|
||||||
fingerprint?: object;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -78,14 +77,6 @@ const WifiAnalysisAssociationsTable = ({ data, ouis, isSingle }: Props) => {
|
|||||||
isMonospace: true,
|
isMonospace: true,
|
||||||
alwaysShow: true,
|
alwaysShow: true,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: 'ssid',
|
|
||||||
Header: 'SSID',
|
|
||||||
Footer: '',
|
|
||||||
accessor: 'ssid',
|
|
||||||
customWidth: '35px',
|
|
||||||
alwaysShow: true,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: 'ips',
|
id: 'ips',
|
||||||
Header: 'IPs',
|
Header: 'IPs',
|
||||||
@@ -93,13 +84,6 @@ const WifiAnalysisAssociationsTable = ({ data, ouis, isSingle }: Props) => {
|
|||||||
Cell: (v) => ipCell(v.cell.row.original),
|
Cell: (v) => ipCell(v.cell.row.original),
|
||||||
disableSortBy: true,
|
disableSortBy: true,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: 'fingerprint',
|
|
||||||
Header: 'Fingerprint',
|
|
||||||
Footer: '',
|
|
||||||
Cell: (v) => Object.values(v.cell.row.original.fingerprint ?? {}).join(', '),
|
|
||||||
disableSortBy: true,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: 'vendor',
|
id: 'vendor',
|
||||||
Header: t('controller.wifi.vendor'),
|
Header: t('controller.wifi.vendor'),
|
||||||
|
|||||||
@@ -105,7 +105,6 @@ const parseAssociations = (data: { data: DeviceStatistics; recorded: number }, r
|
|||||||
txNss: association.tx_rate.nss ?? '-',
|
txNss: association.tx_rate.nss ?? '-',
|
||||||
recorded: data.recorded,
|
recorded: data.recorded,
|
||||||
dynamicVlan: association.dynamic_vlan,
|
dynamicVlan: association.dynamic_vlan,
|
||||||
fingerprint: association.fingerprint,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,7 +68,6 @@ const DevicePageWrapper = ({ serialNumber }: Props) => {
|
|||||||
const getHealth = useGetDeviceHealthChecks({ serialNumber, limit: 1 });
|
const getHealth = useGetDeviceHealthChecks({ serialNumber, limit: 1 });
|
||||||
const { isOpen: isDeleteOpen, onOpen: onDeleteOpen, onClose: onDeleteClose } = useDisclosure();
|
const { isOpen: isDeleteOpen, onOpen: onDeleteOpen, onClose: onDeleteClose } = useDisclosure();
|
||||||
const scanModalProps = useDisclosure();
|
const scanModalProps = useDisclosure();
|
||||||
const cableDiagnosticsModalProps = useDisclosure();
|
|
||||||
const resetModalProps = useDisclosure();
|
const resetModalProps = useDisclosure();
|
||||||
const eventQueueProps = useDisclosure();
|
const eventQueueProps = useDisclosure();
|
||||||
const configureModalProps = useDisclosure();
|
const configureModalProps = useDisclosure();
|
||||||
|
|||||||
Reference in New Issue
Block a user