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
			...
			release/v4
		
	
	| 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: | ||||
|   docker: | ||||
|     runs-on: ubuntu-20.04 | ||||
|     runs-on: ubuntu-latest | ||||
|     env: | ||||
|       DOCKER_REGISTRY_URL: tip-tip-wlan-cloud-ucentral.jfrog.io | ||||
|       DOCKER_REGISTRY_USERNAME: ucentral | ||||
|   | ||||
							
								
								
									
										2
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							| @@ -11,7 +11,7 @@ defaults: | ||||
|  | ||||
| jobs: | ||||
|   helm-package: | ||||
|     runs-on: ubuntu-20.04 | ||||
|     runs-on: ubuntu-latest | ||||
|     env: | ||||
|       HELM_REPO_URL: https://tip.jfrog.io/artifactory/tip-wlan-cloud-ucentral-helm/ | ||||
|       HELM_REPO_USERNAME: ucentral | ||||
|   | ||||
| @@ -8,7 +8,7 @@ fullnameOverride: "" | ||||
| images: | ||||
|   owgwui: | ||||
|     repository: tip-tip-wlan-cloud-ucentral.jfrog.io/owgw-ui | ||||
|     tag: main | ||||
|     tag: v4.1.0 | ||||
|     pullPolicy: Always | ||||
|  | ||||
| services: | ||||
|   | ||||
							
								
								
									
										4
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										4
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -1,12 +1,12 @@ | ||||
| { | ||||
|   "name": "ucentral-client", | ||||
|   "version": "3.1.0(5)", | ||||
|   "version": "4.1.0", | ||||
|   "lockfileVersion": 3, | ||||
|   "requires": true, | ||||
|   "packages": { | ||||
|     "": { | ||||
|       "name": "ucentral-client", | ||||
|       "version": "3.1.0(5)", | ||||
|       "version": "4.1.0", | ||||
|       "license": "ISC", | ||||
|       "dependencies": { | ||||
|         "@chakra-ui/anatomy": "^2.1.1", | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|   "name": "ucentral-client", | ||||
|   "version": "3.2.0", | ||||
|   "version": "4.1.0", | ||||
|   "description": "", | ||||
|   "private": true, | ||||
|   "main": "index.tsx", | ||||
|   | ||||
| @@ -514,6 +514,10 @@ | ||||
|       "started_upgrade": "{{serialNumber}} just shut down to start the upgrade!", | ||||
|       "trace": "Trace", | ||||
|       "trace_description": "Launch a remote trace of this device for either a specific duration or a number of packets", | ||||
|       "re_enroll": "Re-enroll", | ||||
|       "re_enroll_initiated": "Re-enrollment initiated for device {{serialNumber}}", | ||||
|       "re_enroll_warning": "This will renew the operational certificate for device {{serialNumber}}.", | ||||
|       "confirm_re_enroll": "Renew Certificate for {{serialNumber}}", | ||||
|       "update_success": "Device updated!", | ||||
|       "updated_blacklist": "Updated Blacklist!" | ||||
|     }, | ||||
| @@ -622,6 +626,7 @@ | ||||
|     "all": "All", | ||||
|     "associations": "Associations", | ||||
|     "certificate_expires_in": "Certificate Expiry", | ||||
|     "certificate_issuer": "Certificate Issuer", | ||||
|     "certificate_expiry": "Cert. Expires In", | ||||
|     "connected": "Connected", | ||||
|     "crash_logs": "Crash Logs", | ||||
|   | ||||
| @@ -32,6 +32,7 @@ interface Props { | ||||
|   onOpenTelemetryModal: (serialNumber: string) => void; | ||||
|   onOpenScriptModal: (device: GatewayDevice) => void; | ||||
|   onOpenRebootModal: (serialNumber: string) => void; | ||||
|   onOpenReEnrollModal?: (serialNumber: string) => void; | ||||
|   size?: 'sm' | 'md' | 'lg'; | ||||
|   isCompact?: boolean; | ||||
| } | ||||
| @@ -49,6 +50,7 @@ const DeviceActionDropdown = ({ | ||||
|   onOpenConfigureModal, | ||||
|   onOpenScriptModal, | ||||
|   onOpenRebootModal, | ||||
|   onOpenReEnrollModal, | ||||
|   size, | ||||
|   isCompact, | ||||
| }: Props) => { | ||||
| @@ -234,6 +236,11 @@ const DeviceActionDropdown = ({ | ||||
|             <MenuItem onClick={handleRebootClick} hidden={!isCompact}> | ||||
|               {t('commands.reboot')} | ||||
|             </MenuItem> | ||||
|             {onOpenReEnrollModal && ( | ||||
|               <MenuItem onClick={() => onOpenReEnrollModal(device.serialNumber)}> | ||||
|                 {t('controller.devices.re_enroll')} | ||||
|               </MenuItem> | ||||
|             )} | ||||
|             <MenuItem onClick={handleOpenTelemetry}>{t('controller.telemetry.title')}</MenuItem> | ||||
|             <MenuItem onClick={handleOpenScript}>{t('script.one')}</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; | ||||
|   connectReason?: string; | ||||
|   certificateExpiryDate: number; | ||||
|   certificateIssuerName?: string; | ||||
|   connectionCompletionTime: number; | ||||
|   firmware: 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 }) => { | ||||
|   const { t } = useTranslation(); | ||||
|   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 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"> | ||||
|               <Heading size="sm">Connect Reason:</Heading> | ||||
|             </GridItem> | ||||
|   | ||||
| @@ -1,17 +1,19 @@ | ||||
| import * as React from '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 { usePowerCycle } from 'hooks/Network/Devices'; | ||||
| import { useNotification } from 'hooks/useNotification'; | ||||
| import { DeviceLinkState } from 'hooks/Network/Statistics'; | ||||
| import { CableDiagnosticsModalProps } from 'components/Modals/CableDiagnosticsModal'; | ||||
|  | ||||
| type Props = { | ||||
|   state: DeviceLinkState & { name: string }; | ||||
|   deviceSerialNumber: string; | ||||
|   onOpenCableDiagnostics: (port: string) => void; | ||||
| }; | ||||
|  | ||||
| const LinkStateTableActions = ({ state, deviceSerialNumber }: Props) => { | ||||
| const LinkStateTableActions = ({ state, deviceSerialNumber, onOpenCableDiagnostics }: Props) => { | ||||
|   const { t } = useTranslation(); | ||||
|   const powerCycle = usePowerCycle(); | ||||
|   const toast = useToast(); | ||||
| @@ -54,6 +56,7 @@ const LinkStateTableActions = ({ state, deviceSerialNumber }: Props) => { | ||||
|   }; | ||||
|  | ||||
|   return ( | ||||
|     <> | ||||
|       <Tooltip label="Power Cycle" placement="auto-start"> | ||||
|         <IconButton | ||||
|           aria-label="Power Cycle" | ||||
| @@ -64,6 +67,16 @@ const LinkStateTableActions = ({ state, deviceSerialNumber }: Props) => { | ||||
|           size="xs" | ||||
|         /> | ||||
|       </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 }; | ||||
| const dataCell = (v: number) => <DataCell bytes={v} />; | ||||
| const actionCell = (row: Row, serialNumber: string) => ( | ||||
|   <LinkStateTableActions state={row} deviceSerialNumber={serialNumber} /> | ||||
| const actionCell = (row: Row, serialNumber: string, onOpenCableDiagnostics: (port: string) => void) => ( | ||||
|   <LinkStateTableActions | ||||
|     state={row} | ||||
|     deviceSerialNumber={serialNumber} | ||||
|     onOpenCableDiagnostics={onOpenCableDiagnostics} | ||||
|   /> | ||||
| ); | ||||
|  | ||||
| type Props = { | ||||
| @@ -19,9 +23,10 @@ type Props = { | ||||
|   isFetching: boolean; | ||||
|   type: 'upstream' | 'downstream'; | ||||
|   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({ | ||||
|     tableSettingsId: 'switch.link-state.table', | ||||
|     defaultOrder: [ | ||||
| @@ -157,10 +162,16 @@ const LinkStateTable = ({ statistics, refetch, isFetching, type, serialNumber }: | ||||
|         id: 'actions', | ||||
|         header: '', | ||||
|         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) { | ||||
|   | ||||
| @@ -5,6 +5,8 @@ import SwitchInterfaceTable from './SwitchInterfaceTable'; | ||||
| import { DeviceLinkState, useGetDeviceLastStats } from 'hooks/Network/Statistics'; | ||||
| import { Card } from 'components/Containers/Card'; | ||||
| import { CardBody } from 'components/Containers/Card/CardBody'; | ||||
| import { CableDiagnosticsModal } from 'components/Modals/CableDiagnosticsModal'; | ||||
| import { useDisclosure } from '@chakra-ui/react'; | ||||
|  | ||||
| type Props = { | ||||
|   serialNumber: string; | ||||
| @@ -12,6 +14,8 @@ type Props = { | ||||
|  | ||||
| const SwitchPortExamination = ({ serialNumber }: Props) => { | ||||
|   const [tabIndex, setTabIndex] = React.useState(0); | ||||
|   const [selectedPort, setSelectedPort] = React.useState<string>(''); | ||||
|   const cableDiagnosticsModalProps = useDisclosure(); | ||||
|  | ||||
|   const handleTabsChange = React.useCallback((index: number) => { | ||||
|     setTabIndex(index); | ||||
| @@ -35,7 +39,13 @@ const SwitchPortExamination = ({ serialNumber }: Props) => { | ||||
|     })); | ||||
|   }, [getStats.data]); | ||||
|  | ||||
|   const handleOpenCableDiagnostics = React.useCallback((port: string) => { | ||||
|     setSelectedPort(port); | ||||
|     cableDiagnosticsModalProps.onOpen(); | ||||
|   }, []); | ||||
|  | ||||
|   return ( | ||||
|     <> | ||||
|       <Card p={0} mb={4}> | ||||
|         <CardBody p={0} display="block"> | ||||
|           <Tabs index={tabIndex} onChange={handleTabsChange} variant="enclosed" w="100%"> | ||||
| @@ -70,6 +80,7 @@ const SwitchPortExamination = ({ serialNumber }: Props) => { | ||||
|                     isFetching={getStats.isFetching} | ||||
|                     type="upstream" | ||||
|                     serialNumber={serialNumber} | ||||
|                     onOpenCableDiagnostics={handleOpenCableDiagnostics} | ||||
|                   /> | ||||
|                 ) : ( | ||||
|                   <Spinner size="xl" /> | ||||
| @@ -83,6 +94,7 @@ const SwitchPortExamination = ({ serialNumber }: Props) => { | ||||
|                     isFetching={getStats.isFetching} | ||||
|                     type="downstream" | ||||
|                     serialNumber={serialNumber} | ||||
|                     onOpenCableDiagnostics={handleOpenCableDiagnostics} | ||||
|                   /> | ||||
|                 ) : ( | ||||
|                   <Spinner size="xl" /> | ||||
| @@ -92,6 +104,8 @@ const SwitchPortExamination = ({ serialNumber }: Props) => { | ||||
|           </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 { FirmwareUpgradeModal } from 'components/Modals/FirmwareUpgradeModal'; | ||||
| import { RebootModal } from 'components/Modals/RebootModal'; | ||||
| import ReEnrollModal from 'components/Modals/ReEnrollModal'; | ||||
| import { useScriptModal } from 'components/Modals/ScriptModal/useScriptModal'; | ||||
| import ethernetConnected from './ethernetIconConnected.svg?react'; | ||||
| import ethernetDisconnected from './ethernetIconDisconnected.svg?react'; | ||||
| @@ -68,6 +69,7 @@ const DevicePageWrapper = ({ serialNumber }: Props) => { | ||||
|   const getHealth = useGetDeviceHealthChecks({ serialNumber, limit: 1 }); | ||||
|   const { isOpen: isDeleteOpen, onOpen: onDeleteOpen, onClose: onDeleteClose } = useDisclosure(); | ||||
|   const scanModalProps = useDisclosure(); | ||||
|   const cableDiagnosticsModalProps = useDisclosure(); | ||||
|   const resetModalProps = useDisclosure(); | ||||
|   const eventQueueProps = useDisclosure(); | ||||
|   const configureModalProps = useDisclosure(); | ||||
| @@ -75,6 +77,7 @@ const DevicePageWrapper = ({ serialNumber }: Props) => { | ||||
|   const telemetryModalProps = useDisclosure(); | ||||
|   const traceModalProps = useDisclosure(); | ||||
|   const rebootModalProps = useDisclosure(); | ||||
|   const reEnrollModalProps = useDisclosure(); | ||||
|   const scriptModal = useScriptModal(); | ||||
|   // Sticky-top styles | ||||
|   const isCompact = breakpoint === 'base' || breakpoint === 'sm' || breakpoint === 'md'; | ||||
| @@ -215,6 +218,7 @@ const DevicePageWrapper = ({ serialNumber }: Props) => { | ||||
|                   onOpenTelemetryModal={telemetryModalProps.onOpen} | ||||
|                   onOpenScriptModal={scriptModal.openModal} | ||||
|                   onOpenRebootModal={rebootModalProps.onOpen} | ||||
|                   onOpenReEnrollModal={reEnrollModalProps.onOpen} | ||||
|                   size="md" | ||||
|                   isCompact | ||||
|                 /> | ||||
| @@ -267,6 +271,7 @@ const DevicePageWrapper = ({ serialNumber }: Props) => { | ||||
|                     onOpenTelemetryModal={telemetryModalProps.onOpen} | ||||
|                     onOpenRebootModal={rebootModalProps.onOpen} | ||||
|                     onOpenScriptModal={scriptModal.openModal} | ||||
|                     onOpenReEnrollModal={reEnrollModalProps.onOpen} | ||||
|                     size="md" | ||||
|                   /> | ||||
|                 )} | ||||
| @@ -310,6 +315,7 @@ const DevicePageWrapper = ({ serialNumber }: Props) => { | ||||
|       <ConfigureModal serialNumber={serialNumber} modalProps={configureModalProps} /> | ||||
|       <TelemetryModal serialNumber={serialNumber} modalProps={telemetryModalProps} /> | ||||
|       <RebootModal serialNumber={serialNumber} modalProps={rebootModalProps} /> | ||||
|       <ReEnrollModal serialNumber={serialNumber} modalProps={reEnrollModalProps} /> | ||||
|       {scriptModal.modal} | ||||
|       <Box mt={isCompact ? '0px' : '68px'}> | ||||
|         <Masonry | ||||
|   | ||||
		Reference in New Issue
	
	Block a user