mirror of
https://github.com/Telecominfraproject/wlan-cloud-ucentralgw-ui.git
synced 2025-10-30 18:27:53 +00:00
Compare commits
25 Commits
v3.2.0-RC1
...
v4.1.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1d594dc625 | ||
|
|
0b738e4e6a | ||
|
|
c4aff418ed | ||
|
|
dd5c894b03 | ||
|
|
c3256b93c7 | ||
|
|
932f1f4a12 | ||
|
|
db3cbb0b35 | ||
|
|
c895274ebf | ||
|
|
a3647bca08 | ||
|
|
5fbf421d77 | ||
|
|
e09b3ee5f4 | ||
|
|
855960559d | ||
|
|
4cecfc6fc4 | ||
|
|
e62d1e4a98 | ||
|
|
6dddba0848 | ||
|
|
30fffdfe52 | ||
|
|
c8d6540ca6 | ||
|
|
2b2f08c231 | ||
|
|
0cfed90a7b | ||
|
|
01008dc1aa | ||
|
|
26b90cfdba | ||
|
|
b218051104 | ||
|
|
a2fa93938f | ||
|
|
c220d11dd0 | ||
|
|
40d533ecc5 |
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -20,7 +20,7 @@ defaults:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
docker:
|
docker:
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-latest
|
||||||
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-20.04
|
runs-on: ubuntu-latest
|
||||||
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: main
|
tag: v4.1.0
|
||||||
pullPolicy: Always
|
pullPolicy: Always
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "ucentral-client",
|
"name": "ucentral-client",
|
||||||
"version": "3.1.0(5)",
|
"version": "4.1.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "ucentral-client",
|
"name": "ucentral-client",
|
||||||
"version": "3.1.0(5)",
|
"version": "4.1.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@chakra-ui/anatomy": "^2.1.1",
|
"@chakra-ui/anatomy": "^2.1.1",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ucentral-client",
|
"name": "ucentral-client",
|
||||||
"version": "3.2.0",
|
"version": "4.1.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "index.tsx",
|
"main": "index.tsx",
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -32,6 +32,7 @@ interface Props {
|
|||||||
onOpenTelemetryModal: (serialNumber: string) => void;
|
onOpenTelemetryModal: (serialNumber: string) => void;
|
||||||
onOpenScriptModal: (device: GatewayDevice) => void;
|
onOpenScriptModal: (device: GatewayDevice) => void;
|
||||||
onOpenRebootModal: (serialNumber: string) => void;
|
onOpenRebootModal: (serialNumber: string) => void;
|
||||||
|
onOpenReEnrollModal?: (serialNumber: string) => void;
|
||||||
size?: 'sm' | 'md' | 'lg';
|
size?: 'sm' | 'md' | 'lg';
|
||||||
isCompact?: boolean;
|
isCompact?: boolean;
|
||||||
}
|
}
|
||||||
@@ -49,6 +50,7 @@ const DeviceActionDropdown = ({
|
|||||||
onOpenConfigureModal,
|
onOpenConfigureModal,
|
||||||
onOpenScriptModal,
|
onOpenScriptModal,
|
||||||
onOpenRebootModal,
|
onOpenRebootModal,
|
||||||
|
onOpenReEnrollModal,
|
||||||
size,
|
size,
|
||||||
isCompact,
|
isCompact,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
@@ -234,6 +236,11 @@ const DeviceActionDropdown = ({
|
|||||||
<MenuItem onClick={handleRebootClick} hidden={!isCompact}>
|
<MenuItem onClick={handleRebootClick} hidden={!isCompact}>
|
||||||
{t('commands.reboot')}
|
{t('commands.reboot')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
{onOpenReEnrollModal && (
|
||||||
|
<MenuItem onClick={() => onOpenReEnrollModal(device.serialNumber)}>
|
||||||
|
{t('controller.devices.re_enroll')}
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
<MenuItem onClick={handleOpenTelemetry}>{t('controller.telemetry.title')}</MenuItem>
|
<MenuItem onClick={handleOpenTelemetry}>{t('controller.telemetry.title')}</MenuItem>
|
||||||
<MenuItem onClick={handleOpenScript}>{t('script.one')}</MenuItem>
|
<MenuItem onClick={handleOpenScript}>{t('script.one')}</MenuItem>
|
||||||
<MenuItem onClick={handleOpenTrace}>{t('controller.devices.trace')}</MenuItem>
|
<MenuItem onClick={handleOpenTrace}>{t('controller.devices.trace')}</MenuItem>
|
||||||
|
|||||||
257
src/components/Modals/CableDiagnosticsModal/index.tsx
Normal file
257
src/components/Modals/CableDiagnosticsModal/index.tsx
Normal file
@@ -0,0 +1,257 @@
|
|||||||
|
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>
|
||||||
|
);
|
||||||
|
};
|
||||||
50
src/components/Modals/ReEnrollModal/index.tsx
Normal file
50
src/components/Modals/ReEnrollModal/index.tsx
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Center, Spinner, Alert, Button } from '@chakra-ui/react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { Modal } from '../Modal';
|
||||||
|
import { useReEnroll } from 'hooks/Network/ReEnroll';
|
||||||
|
import { ModalProps } from 'models/Modal';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
modalProps: ModalProps;
|
||||||
|
serialNumber: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ReEnrollModal = ({ modalProps: { isOpen, onClose }, serialNumber }: Props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { mutate: reEnroll, isLoading } = useReEnroll({ serialNumber });
|
||||||
|
|
||||||
|
const submit = () => {
|
||||||
|
reEnroll(
|
||||||
|
{ serialNumber, when: 0 },
|
||||||
|
{
|
||||||
|
onSuccess: () => {
|
||||||
|
onClose();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal isOpen={isOpen} onClose={onClose} title={t('controller.devices.re_enroll')}>
|
||||||
|
{isLoading ? (
|
||||||
|
<Center>
|
||||||
|
<Spinner size="lg" />
|
||||||
|
</Center>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Alert colorScheme="blue" mb={6}>
|
||||||
|
{t('controller.devices.re_enroll_warning', { serialNumber })}
|
||||||
|
</Alert>
|
||||||
|
<Center mb={6}>
|
||||||
|
<Button size="lg" colorScheme="blue" onClick={submit} fontWeight="bold">
|
||||||
|
{t('controller.devices.confirm_re_enroll', { serialNumber })}
|
||||||
|
</Button>
|
||||||
|
</Center>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ReEnrollModal;
|
||||||
@@ -166,6 +166,7 @@ export type DeviceStatus = {
|
|||||||
connected: boolean;
|
connected: boolean;
|
||||||
connectReason?: string;
|
connectReason?: string;
|
||||||
certificateExpiryDate: number;
|
certificateExpiryDate: number;
|
||||||
|
certificateIssuerName?: string;
|
||||||
connectionCompletionTime: number;
|
connectionCompletionTime: number;
|
||||||
firmware: string;
|
firmware: string;
|
||||||
ipAddress: string;
|
ipAddress: string;
|
||||||
@@ -377,6 +378,40 @@ 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();
|
||||||
|
|||||||
78
src/hooks/Network/ReEnroll.ts
Normal file
78
src/hooks/Network/ReEnroll.ts
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import { useToast } from '@chakra-ui/react';
|
||||||
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { axiosGw } from 'constants/axiosInstances';
|
||||||
|
|
||||||
|
export type ReEnrollRequest = {
|
||||||
|
serialNumber: string;
|
||||||
|
when?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ReEnrollResponse = {
|
||||||
|
UUID: string;
|
||||||
|
command: 're-enroll' | 'reenroll';
|
||||||
|
completed: number;
|
||||||
|
custom: number;
|
||||||
|
details: {
|
||||||
|
serial: string;
|
||||||
|
when: number;
|
||||||
|
};
|
||||||
|
errorCode: number;
|
||||||
|
errorText: string;
|
||||||
|
executed: number;
|
||||||
|
executionTime: number;
|
||||||
|
results: {
|
||||||
|
serial: string;
|
||||||
|
status: {
|
||||||
|
error: number;
|
||||||
|
resultCode: number;
|
||||||
|
resultText: string;
|
||||||
|
text: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
serialNumber: string;
|
||||||
|
status: string;
|
||||||
|
submitted: number;
|
||||||
|
submittedBy: string;
|
||||||
|
when: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const reEnrollDevice = async ({ serialNumber, when = 0 }: ReEnrollRequest) =>
|
||||||
|
axiosGw.post<ReEnrollResponse>(`device/${serialNumber}/reenroll`, {
|
||||||
|
serial: serialNumber,
|
||||||
|
when,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const useReEnroll = ({ serialNumber }: { serialNumber: string }) => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const toast = useToast();
|
||||||
|
|
||||||
|
return useMutation(reEnrollDevice, {
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries(['commands', serialNumber]);
|
||||||
|
queryClient.invalidateQueries(['device', serialNumber]);
|
||||||
|
queryClient.invalidateQueries(['device-status', serialNumber]);
|
||||||
|
toast({
|
||||||
|
id: `re-enroll-success-${serialNumber}`,
|
||||||
|
title: t('common.success'),
|
||||||
|
description: t('controller.devices.re_enroll_initiated', { serialNumber }),
|
||||||
|
status: 'success',
|
||||||
|
duration: 5000,
|
||||||
|
isClosable: true,
|
||||||
|
position: 'top-right',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onError: (error: any) => {
|
||||||
|
toast({
|
||||||
|
id: `re-enroll-error-${serialNumber}`,
|
||||||
|
title: t('common.error'),
|
||||||
|
description: error?.response?.data?.ErrorDescription || t('common.error'),
|
||||||
|
status: 'error',
|
||||||
|
duration: 5000,
|
||||||
|
isClosable: true,
|
||||||
|
position: 'top-right',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -171,6 +171,12 @@ const DeviceSummary = ({ serialNumber }: Props) => {
|
|||||||
'-'
|
'-'
|
||||||
)}
|
)}
|
||||||
</GridItem>
|
</GridItem>
|
||||||
|
<GridItem colSpan={1} alignContent="center" alignItems="center">
|
||||||
|
<Heading size="sm">{t('devices.certificate_issuer')}:</Heading>
|
||||||
|
</GridItem>
|
||||||
|
<GridItem colSpan={1}>
|
||||||
|
{getStatus.data?.certificateIssuerName ? getStatus.data.certificateIssuerName.split('CN=')[1] : '-'}
|
||||||
|
</GridItem>
|
||||||
<GridItem colSpan={1} alignContent="center" alignItems="center">
|
<GridItem colSpan={1} alignContent="center" alignItems="center">
|
||||||
<Heading size="sm">Connect Reason:</Heading>
|
<Heading size="sm">Connect Reason:</Heading>
|
||||||
</GridItem>
|
</GridItem>
|
||||||
|
|||||||
@@ -1,17 +1,19 @@
|
|||||||
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 } from '@phosphor-icons/react';
|
import { Power, PlugsConnected } 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 }: Props) => {
|
const LinkStateTableActions = ({ state, deviceSerialNumber, onOpenCableDiagnostics }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const powerCycle = usePowerCycle();
|
const powerCycle = usePowerCycle();
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
@@ -54,16 +56,27 @@ const LinkStateTableActions = ({ state, deviceSerialNumber }: Props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip label="Power Cycle" placement="auto-start">
|
<>
|
||||||
<IconButton
|
<Tooltip label="Power Cycle" placement="auto-start">
|
||||||
aria-label="Power Cycle"
|
<IconButton
|
||||||
icon={<Power size={20} />}
|
aria-label="Power Cycle"
|
||||||
colorScheme="green"
|
icon={<Power size={20} />}
|
||||||
onClick={onPowerCycle}
|
colorScheme="green"
|
||||||
isLoading={powerCycle.isLoading}
|
onClick={onPowerCycle}
|
||||||
size="xs"
|
isLoading={powerCycle.isLoading}
|
||||||
/>
|
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,8 +9,12 @@ 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) => (
|
const actionCell = (row: Row, serialNumber: string, onOpenCableDiagnostics: (port: string) => void) => (
|
||||||
<LinkStateTableActions state={row} deviceSerialNumber={serialNumber} />
|
<LinkStateTableActions
|
||||||
|
state={row}
|
||||||
|
deviceSerialNumber={serialNumber}
|
||||||
|
onOpenCableDiagnostics={onOpenCableDiagnostics}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -19,9 +23,10 @@ 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 }: Props) => {
|
const LinkStateTable = ({ statistics, refetch, isFetching, type, serialNumber, onOpenCableDiagnostics }: Props) => {
|
||||||
const tableController = useDataGrid({
|
const tableController = useDataGrid({
|
||||||
tableSettingsId: 'switch.link-state.table',
|
tableSettingsId: 'switch.link-state.table',
|
||||||
defaultOrder: [
|
defaultOrder: [
|
||||||
@@ -157,10 +162,16 @@ const LinkStateTable = ({ statistics, refetch, isFetching, type, serialNumber }:
|
|||||||
id: 'actions',
|
id: 'actions',
|
||||||
header: '',
|
header: '',
|
||||||
accessorKey: '',
|
accessorKey: '',
|
||||||
cell: ({ cell }) => actionCell(cell.row.original, serialNumber),
|
cell: ({ cell }) => (
|
||||||
|
<LinkStateTableActions
|
||||||
|
state={cell.row.original}
|
||||||
|
deviceSerialNumber={serialNumber}
|
||||||
|
onOpenCableDiagnostics={onOpenCableDiagnostics}
|
||||||
|
/>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[],
|
[onOpenCableDiagnostics],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!statistics || statistics?.length === 0) {
|
if (!statistics || statistics?.length === 0) {
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ 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;
|
||||||
@@ -12,6 +14,8 @@ 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);
|
||||||
@@ -35,63 +39,73 @@ 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}>
|
<>
|
||||||
<CardBody p={0} display="block">
|
<Card p={0} mb={4}>
|
||||||
<Tabs index={tabIndex} onChange={handleTabsChange} variant="enclosed" w="100%">
|
<CardBody p={0} display="block">
|
||||||
<TabList>
|
<Tabs index={tabIndex} onChange={handleTabsChange} variant="enclosed" w="100%">
|
||||||
<Tab fontSize="lg" fontWeight="bold">
|
<TabList>
|
||||||
Interfaces
|
<Tab fontSize="lg" fontWeight="bold">
|
||||||
</Tab>
|
Interfaces
|
||||||
<Tab fontSize="lg" fontWeight="bold">
|
</Tab>
|
||||||
Link-State (Up)
|
<Tab fontSize="lg" fontWeight="bold">
|
||||||
</Tab>
|
Link-State (Up)
|
||||||
<Tab fontSize="lg" fontWeight="bold">
|
</Tab>
|
||||||
Link-State (Down)
|
<Tab fontSize="lg" fontWeight="bold">
|
||||||
</Tab>
|
Link-State (Down)
|
||||||
</TabList>
|
</Tab>
|
||||||
<TabPanels>
|
</TabList>
|
||||||
<TabPanel>
|
<TabPanels>
|
||||||
{getStats.data ? (
|
<TabPanel>
|
||||||
<SwitchInterfaceTable
|
{getStats.data ? (
|
||||||
statistics={getStats.data}
|
<SwitchInterfaceTable
|
||||||
refetch={getStats.refetch}
|
statistics={getStats.data}
|
||||||
isFetching={getStats.isFetching}
|
refetch={getStats.refetch}
|
||||||
/>
|
isFetching={getStats.isFetching}
|
||||||
) : (
|
/>
|
||||||
<Spinner size="xl" />
|
) : (
|
||||||
)}
|
<Spinner size="xl" />
|
||||||
</TabPanel>
|
)}
|
||||||
<TabPanel>
|
</TabPanel>
|
||||||
{getStats.data ? (
|
<TabPanel>
|
||||||
<LinkStateTable
|
{getStats.data ? (
|
||||||
statistics={upLinkStates}
|
<LinkStateTable
|
||||||
refetch={getStats.refetch}
|
statistics={upLinkStates}
|
||||||
isFetching={getStats.isFetching}
|
refetch={getStats.refetch}
|
||||||
type="upstream"
|
isFetching={getStats.isFetching}
|
||||||
serialNumber={serialNumber}
|
type="upstream"
|
||||||
/>
|
serialNumber={serialNumber}
|
||||||
) : (
|
onOpenCableDiagnostics={handleOpenCableDiagnostics}
|
||||||
<Spinner size="xl" />
|
/>
|
||||||
)}
|
) : (
|
||||||
</TabPanel>
|
<Spinner size="xl" />
|
||||||
<TabPanel>
|
)}
|
||||||
{getStats.data ? (
|
</TabPanel>
|
||||||
<LinkStateTable
|
<TabPanel>
|
||||||
statistics={downLinkStates}
|
{getStats.data ? (
|
||||||
refetch={getStats.refetch}
|
<LinkStateTable
|
||||||
isFetching={getStats.isFetching}
|
statistics={downLinkStates}
|
||||||
type="downstream"
|
refetch={getStats.refetch}
|
||||||
serialNumber={serialNumber}
|
isFetching={getStats.isFetching}
|
||||||
/>
|
type="downstream"
|
||||||
) : (
|
serialNumber={serialNumber}
|
||||||
<Spinner size="xl" />
|
onOpenCableDiagnostics={handleOpenCableDiagnostics}
|
||||||
)}
|
/>
|
||||||
</TabPanel>
|
) : (
|
||||||
</TabPanels>
|
<Spinner size="xl" />
|
||||||
</Tabs>
|
)}
|
||||||
</CardBody>
|
</TabPanel>
|
||||||
</Card>
|
</TabPanels>
|
||||||
|
</Tabs>
|
||||||
|
</CardBody>
|
||||||
|
</Card>
|
||||||
|
<CableDiagnosticsModal modalProps={cableDiagnosticsModalProps} serialNumber={serialNumber} port={selectedPort} />
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ import { EventQueueModal } from 'components/Modals/EventQueueModal';
|
|||||||
import FactoryResetModal from 'components/Modals/FactoryResetModal';
|
import FactoryResetModal from 'components/Modals/FactoryResetModal';
|
||||||
import { FirmwareUpgradeModal } from 'components/Modals/FirmwareUpgradeModal';
|
import { FirmwareUpgradeModal } from 'components/Modals/FirmwareUpgradeModal';
|
||||||
import { RebootModal } from 'components/Modals/RebootModal';
|
import { RebootModal } from 'components/Modals/RebootModal';
|
||||||
|
import ReEnrollModal from 'components/Modals/ReEnrollModal';
|
||||||
import { useScriptModal } from 'components/Modals/ScriptModal/useScriptModal';
|
import { useScriptModal } from 'components/Modals/ScriptModal/useScriptModal';
|
||||||
import ethernetConnected from './ethernetIconConnected.svg?react';
|
import ethernetConnected from './ethernetIconConnected.svg?react';
|
||||||
import ethernetDisconnected from './ethernetIconDisconnected.svg?react';
|
import ethernetDisconnected from './ethernetIconDisconnected.svg?react';
|
||||||
@@ -68,6 +69,7 @@ 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();
|
||||||
@@ -75,6 +77,7 @@ const DevicePageWrapper = ({ serialNumber }: Props) => {
|
|||||||
const telemetryModalProps = useDisclosure();
|
const telemetryModalProps = useDisclosure();
|
||||||
const traceModalProps = useDisclosure();
|
const traceModalProps = useDisclosure();
|
||||||
const rebootModalProps = useDisclosure();
|
const rebootModalProps = useDisclosure();
|
||||||
|
const reEnrollModalProps = useDisclosure();
|
||||||
const scriptModal = useScriptModal();
|
const scriptModal = useScriptModal();
|
||||||
// Sticky-top styles
|
// Sticky-top styles
|
||||||
const isCompact = breakpoint === 'base' || breakpoint === 'sm' || breakpoint === 'md';
|
const isCompact = breakpoint === 'base' || breakpoint === 'sm' || breakpoint === 'md';
|
||||||
@@ -215,6 +218,7 @@ const DevicePageWrapper = ({ serialNumber }: Props) => {
|
|||||||
onOpenTelemetryModal={telemetryModalProps.onOpen}
|
onOpenTelemetryModal={telemetryModalProps.onOpen}
|
||||||
onOpenScriptModal={scriptModal.openModal}
|
onOpenScriptModal={scriptModal.openModal}
|
||||||
onOpenRebootModal={rebootModalProps.onOpen}
|
onOpenRebootModal={rebootModalProps.onOpen}
|
||||||
|
onOpenReEnrollModal={reEnrollModalProps.onOpen}
|
||||||
size="md"
|
size="md"
|
||||||
isCompact
|
isCompact
|
||||||
/>
|
/>
|
||||||
@@ -267,6 +271,7 @@ const DevicePageWrapper = ({ serialNumber }: Props) => {
|
|||||||
onOpenTelemetryModal={telemetryModalProps.onOpen}
|
onOpenTelemetryModal={telemetryModalProps.onOpen}
|
||||||
onOpenRebootModal={rebootModalProps.onOpen}
|
onOpenRebootModal={rebootModalProps.onOpen}
|
||||||
onOpenScriptModal={scriptModal.openModal}
|
onOpenScriptModal={scriptModal.openModal}
|
||||||
|
onOpenReEnrollModal={reEnrollModalProps.onOpen}
|
||||||
size="md"
|
size="md"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -310,6 +315,7 @@ const DevicePageWrapper = ({ serialNumber }: Props) => {
|
|||||||
<ConfigureModal serialNumber={serialNumber} modalProps={configureModalProps} />
|
<ConfigureModal serialNumber={serialNumber} modalProps={configureModalProps} />
|
||||||
<TelemetryModal serialNumber={serialNumber} modalProps={telemetryModalProps} />
|
<TelemetryModal serialNumber={serialNumber} modalProps={telemetryModalProps} />
|
||||||
<RebootModal serialNumber={serialNumber} modalProps={rebootModalProps} />
|
<RebootModal serialNumber={serialNumber} modalProps={rebootModalProps} />
|
||||||
|
<ReEnrollModal serialNumber={serialNumber} modalProps={reEnrollModalProps} />
|
||||||
{scriptModal.modal}
|
{scriptModal.modal}
|
||||||
<Box mt={isCompact ? '0px' : '68px'}>
|
<Box mt={isCompact ? '0px' : '68px'}>
|
||||||
<Masonry
|
<Masonry
|
||||||
|
|||||||
Reference in New Issue
Block a user