mirror of
				https://github.com/Telecominfraproject/wlan-cloud-ucentralgw-ui.git
				synced 2025-10-30 18:27:53 +00:00 
			
		
		
		
	Compare commits
	
		
			15 Commits
		
	
	
		
			dev
			...
			feature/do
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 1dfd8d8fb1 | ||
|   | 93c57ecfb6 | ||
|   | 666f1a0483 | ||
|   | 1b8abac84b | ||
|   | 3de2ffb1a1 | ||
|   | 419b8c84cb | ||
|   | c9ef0b9046 | ||
|   | 7dc2ba9264 | ||
|   | a2fc111d8c | ||
|   | 1b47daced3 | ||
|   | ad2be40718 | ||
|   | 0f1d251c3a | ||
|   | 1bdb30ef01 | ||
|   | dcc37a113f | ||
|   | 8d4d90197d | 
							
								
								
									
										27
									
								
								.dockerignore
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								.dockerignore
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. | ||||
|  | ||||
| # dependencies | ||||
| /node_modules | ||||
| /.pnp | ||||
| .pnp.js | ||||
|  | ||||
| # testing | ||||
| /coverage | ||||
|  | ||||
| # production | ||||
| /build | ||||
|  | ||||
| # misc | ||||
| .DS_Store | ||||
| .env.local | ||||
| .env.development.local | ||||
| .env.test.local | ||||
| .env.production.local | ||||
|  | ||||
| npm-debug.log* | ||||
| yarn-debug.log* | ||||
| yarn-error.log* | ||||
|  | ||||
| .git | ||||
| .github | ||||
| Dockerfile | ||||
							
								
								
									
										2
									
								
								.env
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								.env
									
									
									
									
									
								
							| @@ -1,2 +0,0 @@ | ||||
| REACT_APP_DEFAULT_GATEWAY_URL=https://ucentral.dpaas.arilia.com:16001 | ||||
| REACT_APP_ALLOW_GATEWAY_CHANGE=false | ||||
							
								
								
									
										14
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| FROM node:16-alpine3.11 AS build | ||||
|  | ||||
| COPY package.json package-lock.json / | ||||
|  | ||||
| RUN npm install | ||||
|  | ||||
| COPY . . | ||||
|  | ||||
| RUN echo '{"DEFAULT_GATEWAY_URL": "https://ucentral.dpaas.arilia.com:16001","ALLOW_GATEWAY_CHANGE": true}' > public/config.json \ | ||||
|     && npm run build | ||||
|  | ||||
| FROM nginx:1.20.1-alpine AS runtime | ||||
|  | ||||
| COPY --from=build /build/ /usr/share/nginx/html/ | ||||
							
								
								
									
										14
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								README.md
									
									
									
									
									
								
							| @@ -4,6 +4,8 @@ | ||||
| The uCentralGW Client is a user interface that lets you monitor and manage devices connected to the [uCentral gateway](https://github.com/Telecominfraproject/wlan-cloud-ucentralgw). To use the interface, | ||||
| you either need to run it on your machine for [development](#development) or build it for [production](#production). | ||||
|  | ||||
| NOTE: This UI will be evolving as micro services are added to the uCentral program most notably with provisioning, base dashboard, firmware, device management | ||||
|  | ||||
| ## Running the solution | ||||
|  | ||||
| ### Development | ||||
| @@ -22,15 +24,3 @@ cd wlan-cloud-ucentralgw-ui | ||||
| npm run build | ||||
| ``` | ||||
| Once the build is done, you can move the `build` folder on your server. | ||||
|  | ||||
| ### Environment variables | ||||
| There are two environment variables currently used to control the gateway URL and also controlling if the users can modify the gateway URL. You can modify these values in the `.env` file located in the root of the project.  | ||||
|  | ||||
| During development, you will need to stop and start the project again to see those changes come into effect. | ||||
| ```asm | ||||
| REACT_APP_DEFAULT_GATEWAY_URL=https://ucentral.dpaas.arilia.com:16001 | ||||
| REACT_APP_ALLOW_GATEWAY_CHANGE=false | ||||
| ``` | ||||
| - `REACT_APP_DEFAULT_GATEWAY_URL` points to the actual uCentral gateway, including the port. | ||||
| - `REACT_APP_ALLOW_GATEWAY_CHANGE` : when set to `true` will allow a user to change the gateway name she wants to use. When set to `false`, will not show a text field for the gateway and will only allow users to go to the gateway speficied in `REACT_APP_DEFAULT_GATEWAY_URL`. | ||||
|  | ||||
|   | ||||
							
								
								
									
										20
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										20
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -1,11 +1,12 @@ | ||||
| { | ||||
|   "name": "ucentral-client", | ||||
|   "version": "0.9.0", | ||||
|   "version": "0.9.3", | ||||
|   "lockfileVersion": 2, | ||||
|   "requires": true, | ||||
|   "packages": { | ||||
|     "": { | ||||
|       "version": "0.9.0", | ||||
|       "name": "ucentral-client", | ||||
|       "version": "0.9.2", | ||||
|       "dependencies": { | ||||
|         "@coreui/coreui": "^3.4.0", | ||||
|         "@coreui/icons": "^2.0.1", | ||||
| @@ -37,6 +38,7 @@ | ||||
|         "react-select": "^4.3.1", | ||||
|         "react-widgets": "^5.1.1", | ||||
|         "redux": "^4.1.0", | ||||
|         "sass": "^1.35.1", | ||||
|         "uuid": "^8.3.2" | ||||
|       }, | ||||
|       "devDependencies": { | ||||
| @@ -5248,7 +5250,6 @@ | ||||
|       "version": "2.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", | ||||
|       "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", | ||||
|       "optional": true, | ||||
|       "engines": { | ||||
|         "node": ">=8" | ||||
|       } | ||||
| @@ -5737,7 +5738,6 @@ | ||||
|       "version": "3.5.1", | ||||
|       "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", | ||||
|       "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", | ||||
|       "optional": true, | ||||
|       "dependencies": { | ||||
|         "anymatch": "~3.1.1", | ||||
|         "braces": "~3.0.2", | ||||
| @@ -10569,7 +10569,6 @@ | ||||
|       "version": "2.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", | ||||
|       "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", | ||||
|       "optional": true, | ||||
|       "dependencies": { | ||||
|         "binary-extensions": "^2.0.0" | ||||
|       }, | ||||
| @@ -17453,7 +17452,6 @@ | ||||
|       "version": "3.5.0", | ||||
|       "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", | ||||
|       "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", | ||||
|       "optional": true, | ||||
|       "dependencies": { | ||||
|         "picomatch": "^2.2.1" | ||||
|       }, | ||||
| @@ -18343,8 +18341,6 @@ | ||||
|       "version": "1.35.1", | ||||
|       "resolved": "https://registry.npmjs.org/sass/-/sass-1.35.1.tgz", | ||||
|       "integrity": "sha512-oCisuQJstxMcacOPmxLNiLlj4cUyN2+8xJnG7VanRoh2GOLr9RqkvI4AxA4a6LHVg/rsu+PmxXeGhrdSF9jCiQ==", | ||||
|       "optional": true, | ||||
|       "peer": true, | ||||
|       "dependencies": { | ||||
|         "chokidar": ">=3.0.0 <4.0.0" | ||||
|       }, | ||||
| @@ -26490,8 +26486,7 @@ | ||||
|     "binary-extensions": { | ||||
|       "version": "2.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", | ||||
|       "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", | ||||
|       "optional": true | ||||
|       "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==" | ||||
|     }, | ||||
|     "bindings": { | ||||
|       "version": "1.5.0", | ||||
| @@ -26896,7 +26891,6 @@ | ||||
|       "version": "3.5.1", | ||||
|       "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", | ||||
|       "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", | ||||
|       "optional": true, | ||||
|       "requires": { | ||||
|         "anymatch": "~3.1.1", | ||||
|         "braces": "~3.0.2", | ||||
| @@ -30706,7 +30700,6 @@ | ||||
|       "version": "2.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", | ||||
|       "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", | ||||
|       "optional": true, | ||||
|       "requires": { | ||||
|         "binary-extensions": "^2.0.0" | ||||
|       } | ||||
| @@ -36047,7 +36040,6 @@ | ||||
|       "version": "3.5.0", | ||||
|       "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", | ||||
|       "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", | ||||
|       "optional": true, | ||||
|       "requires": { | ||||
|         "picomatch": "^2.2.1" | ||||
|       } | ||||
| @@ -36756,8 +36748,6 @@ | ||||
|       "version": "1.35.1", | ||||
|       "resolved": "https://registry.npmjs.org/sass/-/sass-1.35.1.tgz", | ||||
|       "integrity": "sha512-oCisuQJstxMcacOPmxLNiLlj4cUyN2+8xJnG7VanRoh2GOLr9RqkvI4AxA4a6LHVg/rsu+PmxXeGhrdSF9jCiQ==", | ||||
|       "optional": true, | ||||
|       "peer": true, | ||||
|       "requires": { | ||||
|         "chokidar": ">=3.0.0 <4.0.0" | ||||
|       } | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|   "name": "ucentral-client", | ||||
|   "version": "0.9.0", | ||||
|   "version": "0.9.3", | ||||
|   "private": true, | ||||
|   "dependencies": { | ||||
|     "@coreui/coreui": "^3.4.0", | ||||
| @@ -33,6 +33,7 @@ | ||||
|     "react-select": "^4.3.1", | ||||
|     "react-widgets": "^5.1.1", | ||||
|     "redux": "^4.1.0", | ||||
|     "sass": "^1.35.1", | ||||
|     "uuid": "^8.3.2" | ||||
|   }, | ||||
|   "scripts": { | ||||
|   | ||||
							
								
								
									
										4
									
								
								public/config.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								public/config.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| { | ||||
|   "DEFAULT_GATEWAY_URL": "https://ucentral.dpaas.arilia.com:16001", | ||||
|   "ALLOW_GATEWAY_CHANGE": false | ||||
| } | ||||
| @@ -103,6 +103,12 @@ | ||||
| 		"explanation": "Möchten Sie diesen Befehl wirklich löschen? Diese Aktion ist nicht umkehrbar.", | ||||
| 		"title": "Befehl löschen" | ||||
| 	}, | ||||
| 	"delete_logs": { | ||||
| 		"date": "Wählen Sie das Datum des ältesten Protokolls aus, das Sie behalten möchten", | ||||
| 		"device_logs_title": "Geräteprotokolle löschen", | ||||
| 		"explanation": "Dadurch werden alle {{object}} vor dem von Ihnen gewählten Datum gelöscht. Seien Sie vorsichtig, diese Aktion ist nicht umkehrbar.", | ||||
| 		"healthchecks_title": "Healthchecks löschen" | ||||
| 	}, | ||||
| 	"device_logs": { | ||||
| 		"log": "Log", | ||||
| 		"severity": "Schwere", | ||||
|   | ||||
| @@ -103,6 +103,12 @@ | ||||
| 		"explanation": "Are you sure you want to delete this command? This action is not reversible.", | ||||
| 		"title": "Delete Command" | ||||
| 	}, | ||||
| 	"delete_logs": { | ||||
| 		"date": "Select the date of the oldest log you would like to keep", | ||||
| 		"device_logs_title": "Delete Device Logs", | ||||
| 		"explanation": "This will delete all of the {{object}} before the date you choose. Be careful, this action is not reversible.", | ||||
| 		"healthchecks_title": "Delete Healthchecks" | ||||
| 	}, | ||||
| 	"device_logs": { | ||||
| 		"log": "Log", | ||||
| 		"severity": "Severity", | ||||
|   | ||||
| @@ -103,6 +103,12 @@ | ||||
| 		"explanation": "¿Está seguro de que desea eliminar este comando? Esta acción no es reversible.", | ||||
| 		"title": "Eliminar comando" | ||||
| 	}, | ||||
| 	"delete_logs": { | ||||
| 		"date": "Seleccione la fecha del registro más antiguo que le gustaría conservar", | ||||
| 		"device_logs_title": "Eliminar registros de dispositivos", | ||||
| 		"explanation": "Esto eliminará todos los {{object}} antes de la fecha que elija. Tenga cuidado, esta acción no es reversible.", | ||||
| 		"healthchecks_title": "Eliminar comprobaciones de estado" | ||||
| 	}, | ||||
| 	"device_logs": { | ||||
| 		"log": "Iniciar sesión", | ||||
| 		"severity": "Gravedad", | ||||
|   | ||||
| @@ -103,6 +103,12 @@ | ||||
| 		"explanation": "Êtes-vous sûr de vouloir supprimer cette commande ? Cette action n'est pas réversible.", | ||||
| 		"title": "Supprimer la commande" | ||||
| 	}, | ||||
| 	"delete_logs": { | ||||
| 		"date": "Sélectionnez la date du plus ancien journal que vous souhaitez conserver", | ||||
| 		"device_logs_title": "Supprimer les journaux de l'appareil", | ||||
| 		"explanation": "Cela supprimera tous les {{object}} avant la date que vous choisissez. Attention, cette action n'est pas réversible.", | ||||
| 		"healthchecks_title": "Supprimer les vérifications d'état" | ||||
| 	}, | ||||
| 	"device_logs": { | ||||
| 		"log": "Bûche", | ||||
| 		"severity": "Gravité", | ||||
|   | ||||
| @@ -103,6 +103,12 @@ | ||||
| 		"explanation": "Tem certeza de que deseja excluir este comando? esta ação não é reversível.", | ||||
| 		"title": "Apagar Comando" | ||||
| 	}, | ||||
| 	"delete_logs": { | ||||
| 		"date": "Selecione a data do registro mais antigo que você gostaria de manter", | ||||
| 		"device_logs_title": "Excluir registros do dispositivo", | ||||
| 		"explanation": "Isso excluirá todos os {{object}} antes da data que você escolheu. Cuidado, esta ação não é reversível.", | ||||
| 		"healthchecks_title": "Excluir verificações de saúde" | ||||
| 	}, | ||||
| 	"device_logs": { | ||||
| 		"log": "Registro", | ||||
| 		"severity": "Gravidade", | ||||
|   | ||||
							
								
								
									
										64
									
								
								src/components/ConfirmFooter/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								src/components/ConfirmFooter/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | ||||
| import React, { useState, useEffect } from 'react'; | ||||
| import { useTranslation } from 'react-i18next'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import { CButton, CSpinner, CModalFooter } from '@coreui/react'; | ||||
|  | ||||
| const ConfirmFooter = ({ isShown, isLoading, action, color, variant, block, toggleParent }) => { | ||||
|   const { t } = useTranslation(); | ||||
|   const [askingIfSure, setAskingIfSure] = useState(false); | ||||
|  | ||||
|   const confirmingIfSure = () => { | ||||
|     setAskingIfSure(true); | ||||
|   }; | ||||
|  | ||||
|   useEffect(() => { | ||||
|     setAskingIfSure(false); | ||||
|   }, [isShown]); | ||||
|  | ||||
|   return ( | ||||
|     <CModalFooter> | ||||
|       <div hidden={!askingIfSure}>{t('common.are_you_sure')}</div> | ||||
|       <CButton | ||||
|         disabled={isLoading} | ||||
|         hidden={askingIfSure} | ||||
|         color={color} | ||||
|         variant={variant} | ||||
|         onClick={() => confirmingIfSure()} | ||||
|         block={block} | ||||
|       > | ||||
|         {t('common.submit')} | ||||
|       </CButton> | ||||
|       <CButton | ||||
|         disabled={isLoading} | ||||
|         hidden={!askingIfSure} | ||||
|         color={color} | ||||
|         onClick={() => action()} | ||||
|         block={block} | ||||
|       > | ||||
|         {isLoading ? t('common.loading_ellipsis') : t('common.yes')} | ||||
|         <CSpinner color="light" hidden={!isLoading} component="span" size="sm" /> | ||||
|       </CButton> | ||||
|       <CButton color="secondary" onClick={toggleParent}> | ||||
|         {t('common.cancel')} | ||||
|       </CButton> | ||||
|     </CModalFooter> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
| ConfirmFooter.propTypes = { | ||||
|   isLoading: PropTypes.bool.isRequired, | ||||
|   block: PropTypes.bool, | ||||
|   action: PropTypes.func.isRequired, | ||||
|   color: PropTypes.string, | ||||
|   variant: PropTypes.string, | ||||
|   toggleParent: PropTypes.func.isRequired, | ||||
|   isShown: PropTypes.bool.isRequired, | ||||
| }; | ||||
|  | ||||
| ConfirmFooter.defaultProps = { | ||||
|   color: 'primary', | ||||
|   variant: '', | ||||
|   block: false, | ||||
| }; | ||||
|  | ||||
| export default ConfirmFooter; | ||||
							
								
								
									
										100
									
								
								src/components/DeleteLogModal/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								src/components/DeleteLogModal/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,100 @@ | ||||
| import React, { useState, useEffect } from 'react'; | ||||
| import { useTranslation } from 'react-i18next'; | ||||
| import { CModal, CModalHeader, CModalTitle, CModalBody, CCol, CRow } from '@coreui/react'; | ||||
| import DatePicker from 'react-widgets/DatePicker'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import ConfirmFooter from 'components/ConfirmFooter'; | ||||
| import { dateToUnix } from 'utils/helper'; | ||||
| import axiosInstance from 'utils/axiosInstance'; | ||||
| import { getToken } from 'utils/authHelper'; | ||||
| import eventBus from 'utils/eventBus'; | ||||
| import styles from './index.module.scss'; | ||||
|  | ||||
| const DeleteLogModal = ({ serialNumber, show, toggle, object }) => { | ||||
|   const { t } = useTranslation(); | ||||
|   const [loading, setLoading] = useState(false); | ||||
|   const [maxDate, setMaxDate] = useState(new Date().toString()); | ||||
|  | ||||
|   const setDate = (date) => { | ||||
|     if (date) { | ||||
|       setMaxDate(date.toString()); | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   const deleteLog = async () => { | ||||
|     setLoading(true); | ||||
|  | ||||
|     const options = { | ||||
|       headers: { | ||||
|         Accept: 'application/json', | ||||
|         Authorization: `Bearer ${getToken()}`, | ||||
|       }, | ||||
|       params: { | ||||
|         endDate: dateToUnix(maxDate), | ||||
|       }, | ||||
|     }; | ||||
|     return axiosInstance | ||||
|       .delete(`/device/${serialNumber}/${object}`, options) | ||||
|       .then(() => {}) | ||||
|       .catch(() => {}) | ||||
|       .finally(() => { | ||||
|         if (object === 'healthchecks') | ||||
|           eventBus.dispatch('deletedHealth', { message: 'Healthcheck was deleted' }); | ||||
|         else if (object === 'logs') | ||||
|           eventBus.dispatch('deletedLogs', { message: 'Deleted device logs' }); | ||||
|         setLoading(false); | ||||
|         toggle(); | ||||
|       }); | ||||
|   }; | ||||
|  | ||||
|   useEffect(() => { | ||||
|     setLoading(false); | ||||
|     setMaxDate(new Date().toString()); | ||||
|   }, [show]); | ||||
|  | ||||
|   return ( | ||||
|     <CModal className={styles.modal} show={show} onClose={toggle}> | ||||
|       <CModalHeader closeButton> | ||||
|         <CModalTitle> | ||||
|           {object === 'healthchecks' | ||||
|             ? t('delete_logs.healthchecks_title') | ||||
|             : t('delete_logs.device_logs_title')} | ||||
|         </CModalTitle> | ||||
|       </CModalHeader> | ||||
|       <CModalBody> | ||||
|         <h6>{t('delete_logs.explanation', { object })}</h6> | ||||
|         <CRow className={styles.spacedRow}> | ||||
|           <CCol md="4" className={styles.spacedDate}> | ||||
|             <p>{t('common.date')}:</p> | ||||
|           </CCol> | ||||
|           <CCol xs="12" md="8"> | ||||
|             <DatePicker | ||||
|               selected={new Date(maxDate)} | ||||
|               includeTime | ||||
|               value={new Date(maxDate)} | ||||
|               placeholder="Select custom date" | ||||
|               disabled={loading} | ||||
|               onChange={(date) => setDate(date)} | ||||
|             /> | ||||
|           </CCol> | ||||
|         </CRow> | ||||
|       </CModalBody> | ||||
|       <ConfirmFooter | ||||
|         isShown={show} | ||||
|         isLoading={loading} | ||||
|         action={deleteLog} | ||||
|         color="primary" | ||||
|         toggleParent={toggle} | ||||
|       /> | ||||
|     </CModal> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
| DeleteLogModal.propTypes = { | ||||
|   show: PropTypes.bool.isRequired, | ||||
|   toggle: PropTypes.func.isRequired, | ||||
|   object: PropTypes.string.isRequired, | ||||
|   serialNumber: PropTypes.string.isRequired, | ||||
| }; | ||||
|  | ||||
| export default DeleteLogModal; | ||||
							
								
								
									
										11
									
								
								src/components/DeleteLogModal/index.module.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/components/DeleteLogModal/index.module.scss
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| .modal { | ||||
|   color: #3c4b64; | ||||
| } | ||||
|  | ||||
| .spacedRow { | ||||
|   margin-top: 20px; | ||||
| } | ||||
|  | ||||
| .spacedColumn { | ||||
|   margin-top: 7px; | ||||
| } | ||||
| @@ -10,6 +10,7 @@ import { | ||||
|   CRow, | ||||
|   CCol, | ||||
|   CProgress, | ||||
|   CPopover, | ||||
| } from '@coreui/react'; | ||||
| import CIcon from '@coreui/icons-react'; | ||||
| import { useTranslation } from 'react-i18next'; | ||||
| @@ -18,7 +19,9 @@ import PropTypes from 'prop-types'; | ||||
| import { prettyDate, dateToUnix } from 'utils/helper'; | ||||
| import axiosInstance from 'utils/axiosInstance'; | ||||
| import { getToken } from 'utils/authHelper'; | ||||
| import eventBus from 'utils/eventBus'; | ||||
| import LoadingButton from 'components/LoadingButton'; | ||||
| import DeleteLogModal from 'components/DeleteLogModal'; | ||||
| import styles from './index.module.scss'; | ||||
|  | ||||
| const DeviceHealth = ({ selectedDeviceId }) => { | ||||
| @@ -34,6 +37,11 @@ const DeviceHealth = ({ selectedDeviceId }) => { | ||||
|   const [showLoadingMore, setShowLoadingMore] = useState(true); | ||||
|   const [sanityLevel, setSanityLevel] = useState(null); | ||||
|   const [barColor, setBarColor] = useState('gradient-dark'); | ||||
|   const [showDeleteModal, setShowDeleteModal] = useState(false); | ||||
|  | ||||
|   const toggleDeleteModal = () => { | ||||
|     setShowDeleteModal(!showDeleteModal); | ||||
|   }; | ||||
|  | ||||
|   const toggle = (e) => { | ||||
|     setCollapse(!collapse); | ||||
| @@ -167,6 +175,14 @@ const DeviceHealth = ({ selectedDeviceId }) => { | ||||
|     } | ||||
|   }, [start, end, selectedDeviceId]); | ||||
|  | ||||
|   useEffect(() => { | ||||
|     eventBus.on('deletedHealth', () => getDeviceHealth()); | ||||
|  | ||||
|     return () => { | ||||
|       eventBus.remove('deletedHealth'); | ||||
|     }; | ||||
|   }, []); | ||||
|  | ||||
|   return ( | ||||
|     <CWidgetDropdown | ||||
|       header={sanityLevel ? `${sanityLevel}%` : t('common.unknown')} | ||||
| @@ -178,6 +194,20 @@ const DeviceHealth = ({ selectedDeviceId }) => { | ||||
|         <div className={styles.footer}> | ||||
|           <CProgress className={styles.progressBar} color="white" value={sanityLevel ?? 0} /> | ||||
|           <CCollapse show={collapse}> | ||||
|             <div className={styles.alignRight}> | ||||
|               <CPopover content={t('common.delete')}> | ||||
|                 <CButton | ||||
|                   color="light" | ||||
|                   shape="square" | ||||
|                   size="sm" | ||||
|                   onClick={() => { | ||||
|                     toggleDeleteModal(); | ||||
|                   }} | ||||
|                 > | ||||
|                   <CIcon name="cilTrash" size="lg" /> | ||||
|                 </CButton> | ||||
|               </CPopover> | ||||
|             </div> | ||||
|             <CRow className={styles.spacedRow}> | ||||
|               <CCol> | ||||
|                 {t('common.from')}: | ||||
| @@ -250,6 +280,12 @@ const DeviceHealth = ({ selectedDeviceId }) => { | ||||
|               size="lg" | ||||
|             /> | ||||
|           </CButton> | ||||
|           <DeleteLogModal | ||||
|             serialNumber={selectedDeviceId} | ||||
|             object="healthchecks" | ||||
|             show={showDeleteModal} | ||||
|             toggle={toggleDeleteModal} | ||||
|           /> | ||||
|         </div> | ||||
|       } | ||||
|     /> | ||||
|   | ||||
| @@ -25,3 +25,7 @@ | ||||
| .scrollable { | ||||
|   height: 250px; | ||||
| } | ||||
|  | ||||
| .alignRight { | ||||
|   float: right; | ||||
| } | ||||
|   | ||||
| @@ -9,6 +9,7 @@ import { | ||||
|   CDataTable, | ||||
|   CCard, | ||||
|   CCardBody, | ||||
|   CPopover, | ||||
| } from '@coreui/react'; | ||||
| import CIcon from '@coreui/icons-react'; | ||||
| import { useTranslation } from 'react-i18next'; | ||||
| @@ -17,7 +18,9 @@ import PropTypes from 'prop-types'; | ||||
| import { prettyDate, dateToUnix } from 'utils/helper'; | ||||
| import axiosInstance from 'utils/axiosInstance'; | ||||
| import { getToken } from 'utils/authHelper'; | ||||
| import eventBus from 'utils/eventBus'; | ||||
| import LoadingButton from 'components/LoadingButton'; | ||||
| import DeleteLogModal from 'components/DeleteLogModal'; | ||||
| import styles from './index.module.scss'; | ||||
|  | ||||
| const DeviceLogs = ({ selectedDeviceId }) => { | ||||
| @@ -31,6 +34,11 @@ const DeviceLogs = ({ selectedDeviceId }) => { | ||||
|   const [logLimit, setLogLimit] = useState(25); | ||||
|   const [loadingMore, setLoadingMore] = useState(false); | ||||
|   const [showLoadingMore, setShowLoadingMore] = useState(true); | ||||
|   const [showDeleteModal, setShowDeleteModal] = useState(false); | ||||
|  | ||||
|   const toggleDeleteModal = () => { | ||||
|     setShowDeleteModal(!showDeleteModal); | ||||
|   }; | ||||
|  | ||||
|   const toggle = (e) => { | ||||
|     setCollapse(!collapse); | ||||
| @@ -149,7 +157,16 @@ const DeviceLogs = ({ selectedDeviceId }) => { | ||||
|     } | ||||
|   }, [start, end, selectedDeviceId]); | ||||
|  | ||||
|   useEffect(() => { | ||||
|     eventBus.on('deletedLogs', () => getLogs()); | ||||
|  | ||||
|     return () => { | ||||
|       eventBus.remove('deletedLogs'); | ||||
|     }; | ||||
|   }, []); | ||||
|  | ||||
|   return ( | ||||
|     <div> | ||||
|       <CWidgetDropdown | ||||
|         inverse="true" | ||||
|         color="gradient-info" | ||||
| @@ -157,6 +174,20 @@ const DeviceLogs = ({ selectedDeviceId }) => { | ||||
|         footerSlot={ | ||||
|           <div className={styles.footer}> | ||||
|             <CCollapse show={collapse}> | ||||
|               <div className={styles.alignRight}> | ||||
|                 <CPopover content={t('common.delete')}> | ||||
|                   <CButton | ||||
|                     color="light" | ||||
|                     shape="square" | ||||
|                     size="sm" | ||||
|                     onClick={() => { | ||||
|                       toggleDeleteModal(); | ||||
|                     }} | ||||
|                   > | ||||
|                     <CIcon name="cilTrash" size="lg" /> | ||||
|                   </CButton> | ||||
|                 </CPopover> | ||||
|               </div> | ||||
|               <CRow className={styles.datepickerRow}> | ||||
|                 <CCol> | ||||
|                   {t('common.from')} | ||||
| @@ -228,6 +259,13 @@ const DeviceLogs = ({ selectedDeviceId }) => { | ||||
|       > | ||||
|         <CIcon name="cilList" className={styles.whiteIcon} size="lg" /> | ||||
|       </CWidgetDropdown> | ||||
|       <DeleteLogModal | ||||
|         serialNumber={selectedDeviceId} | ||||
|         object="logs" | ||||
|         show={showDeleteModal} | ||||
|         toggle={toggleDeleteModal} | ||||
|       /> | ||||
|     </div> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -17,3 +17,7 @@ | ||||
| .loadMoreRow { | ||||
|   margin-bottom: 1%; | ||||
| } | ||||
|  | ||||
| .alignRight { | ||||
|   float: right; | ||||
| } | ||||
|   | ||||
| @@ -1,10 +1,9 @@ | ||||
| import React from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import Chart from 'react-apexcharts'; | ||||
| import styles from './index.module.scss'; | ||||
|  | ||||
| const DeviceStatisticsChart = ({ data, options }) => ( | ||||
|   <div className={styles.chart}> | ||||
|   <div style={{ height: '360px' }}> | ||||
|     <Chart series={data} options={options} type="line" height="100%" /> | ||||
|   </div> | ||||
| ); | ||||
|   | ||||
| @@ -1,3 +0,0 @@ | ||||
| .chart { | ||||
|   height: 360px; | ||||
| } | ||||
| @@ -10,8 +10,10 @@ import DeviceStatisticsChart from '../DeviceStatisticsChart'; | ||||
| const StatisticsChartList = ({ selectedDeviceId, lastRefresh }) => { | ||||
|   const { t } = useTranslation(); | ||||
|   const [loading, setLoading] = useState(false); | ||||
|   const [deviceStats, setStats] = useState([]); | ||||
|   const [statOptions, setStatOptions] = useState({}); | ||||
|   const [statOptions, setStatOptions] = useState({ | ||||
|     interfaceList: [], | ||||
|     settings: {}, | ||||
|   }); | ||||
|  | ||||
|   const transformIntoDataset = (data) => { | ||||
|     const sortedData = data.sort((a, b) => { | ||||
| @@ -60,7 +62,9 @@ const StatisticsChartList = ({ selectedDeviceId, lastRefresh }) => { | ||||
|         interfaceList[interfaceTypes[inter.name]][0].data.push( | ||||
|           Math.floor(inter.counters.tx_bytes / 1024), | ||||
|         ); | ||||
|         interfaceList[interfaceTypes[inter.name]][1].data.push(Math.floor(inter.counters.rx_bytes)); | ||||
|         interfaceList[interfaceTypes[inter.name]][1].data.push( | ||||
|           Math.floor(inter.counters.rx_bytes / 1024), | ||||
|         ); | ||||
|       } | ||||
|     } | ||||
|  | ||||
| @@ -97,8 +101,14 @@ const StatisticsChartList = ({ selectedDeviceId, lastRefresh }) => { | ||||
|       }, | ||||
|     }; | ||||
|  | ||||
|     setStatOptions(options); | ||||
|     setStats(interfaceList); | ||||
|     const newOptions = { | ||||
|       interfaceList, | ||||
|       settings: options, | ||||
|     }; | ||||
|  | ||||
|     if (statOptions !== newOptions) { | ||||
|       setStatOptions(newOptions); | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   const getStatistics = () => { | ||||
| @@ -134,20 +144,20 @@ const StatisticsChartList = ({ selectedDeviceId, lastRefresh }) => { | ||||
|   }, [selectedDeviceId]); | ||||
|  | ||||
|   useEffect(() => { | ||||
|     if (lastRefresh !== '' && selectedDeviceId) { | ||||
|     if (!loading && lastRefresh !== '' && selectedDeviceId) { | ||||
|       getStatistics(); | ||||
|     } | ||||
|   }, [lastRefresh]); | ||||
|  | ||||
|   return ( | ||||
|     <div> | ||||
|       {deviceStats.map((data) => ( | ||||
|       {statOptions.interfaceList.map((data) => ( | ||||
|         <div key={createUuid()}> | ||||
|           <DeviceStatisticsChart | ||||
|             key={createUuid()} | ||||
|             data={data} | ||||
|             options={{ | ||||
|               ...statOptions, | ||||
|               ...statOptions.settings, | ||||
|               title: { | ||||
|                 text: capitalizeFirstLetter(data[0].titleName), | ||||
|                 align: 'left', | ||||
|   | ||||
| @@ -9,7 +9,7 @@ import styles from './index.module.scss'; | ||||
|  | ||||
| const DeviceStatisticsCard = ({ selectedDeviceId }) => { | ||||
|   const { t } = useTranslation(); | ||||
|   const [lastRefresh, setLastRefresh] = useState(''); | ||||
|   const [lastRefresh, setLastRefresh] = useState(new Date().toString()); | ||||
|  | ||||
|   const refresh = () => { | ||||
|     setLastRefresh(new Date().toString()); | ||||
|   | ||||
| @@ -1,3 +0,0 @@ | ||||
| { | ||||
|   "REACT_APP_BASE_URL": "https://ucentral.dpaas.arilia.com:16001/api/v1" | ||||
| } | ||||
| @@ -6,7 +6,7 @@ const TheFooter = () => ( | ||||
|   <Translation> | ||||
|     {(t) => ( | ||||
|       <CFooter fixed={false}> | ||||
|         <div>{t('footer.version')} 0.9.0</div> | ||||
|         <div>{t('footer.version')} 0.9.3</div> | ||||
|         <div className="mfs-auto"> | ||||
|           <span className="mr-1">{t('footer.powered_by')}</span> | ||||
|           <a href="https://coreui.io/react" target="_blank" rel="noopener noreferrer"> | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| .sidebarImgFull { | ||||
|   height: 75px; | ||||
|   width: 75px; | ||||
| } | ||||
|  | ||||
| .sidebarImgMinimized { | ||||
|   | ||||
| @@ -30,15 +30,30 @@ const Login = () => { | ||||
|   const dispatch = useDispatch(); | ||||
|   const [userId, setUsername] = useState(''); | ||||
|   const [password, setPassword] = useState(''); | ||||
|   const [gatewayUrl, setGatewayUrl] = useState(process.env.REACT_APP_DEFAULT_GATEWAY_URL); | ||||
|   const [gatewayUrl, setGatewayUrl] = useState(''); | ||||
|   const [hadError, setHadError] = useState(false); | ||||
|   const [emptyUsername, setEmptyUsername] = useState(false); | ||||
|   const [emptyPassword, setEmptyPassword] = useState(false); | ||||
|   const [emptyGateway, setEmptyGateway] = useState(false); | ||||
|   const placeholderUrl = 'Gateway URL (ex: https://ucentral.dpaas.arilia.com:16001)'; | ||||
|   const defaultGatewayUrl = process.env.REACT_APP_DEFAULT_GATEWAY_URL; | ||||
|   const allowUrlChange = process.env.REACT_APP_ALLOW_GATEWAY_CHANGE === 'true'; | ||||
|   const loginErrorText = t('login.login_error'); | ||||
|   const [defaultConfig, setDefaultConfig] = useState({ | ||||
|     DEFAULT_GATEWAY_URL: '', | ||||
|     ALLOW_GATEWAY_CHANGE: true, | ||||
|   }); | ||||
|   const placeholderUrl = 'Gateway URL (ex: https://your-url:port)'; | ||||
|  | ||||
|   const getDefaultConfig = async () => { | ||||
|     fetch('./config.json', { | ||||
|       headers: { | ||||
|         'Content-Type': 'application/json', | ||||
|         Accept: 'application/json', | ||||
|       }, | ||||
|     }) | ||||
|       .then((response) => response.json()) | ||||
|       .then((json) => { | ||||
|         setDefaultConfig(json); | ||||
|       }) | ||||
|       .catch(); | ||||
|   }; | ||||
|  | ||||
|   const formValidation = () => { | ||||
|     setHadError(false); | ||||
| @@ -59,12 +74,13 @@ const Login = () => { | ||||
|       setEmptyGateway(true); | ||||
|       isSuccessful = false; | ||||
|     } | ||||
|  | ||||
|     return isSuccessful; | ||||
|   }; | ||||
|  | ||||
|   const SignIn = (credentials) => { | ||||
|     const gatewayUrlToUse = allowUrlChange ? gatewayUrl : defaultGatewayUrl; | ||||
|     const gatewayUrlToUse = defaultConfig.ALLOW_GATEWAY_CHANGE | ||||
|       ? gatewayUrl | ||||
|       : defaultConfig.DEFAULT_GATEWAY_URL; | ||||
|  | ||||
|     axiosInstance | ||||
|       .post(`${gatewayUrlToUse}/api/v1/oauth2`, credentials) | ||||
| @@ -93,6 +109,12 @@ const Login = () => { | ||||
|   useEffect(() => { | ||||
|     if (emptyGateway) setEmptyGateway(false); | ||||
|   }, [gatewayUrl]); | ||||
|   useEffect(() => { | ||||
|     getDefaultConfig(); | ||||
|   }, []); | ||||
|   useEffect(() => { | ||||
|     setGatewayUrl(defaultConfig.DEFAULT_GATEWAY_URL); | ||||
|   }, [defaultConfig]); | ||||
|  | ||||
|   return ( | ||||
|     <div className="c-app c-default-layout flex-row align-items-center"> | ||||
| @@ -151,7 +173,7 @@ const Login = () => { | ||||
|                         {t('login.please_enter_password')} | ||||
|                       </CInvalidFeedback> | ||||
|                     </CInputGroup> | ||||
|                     <CInputGroup className="mb-4" hidden={!allowUrlChange}> | ||||
|                     <CInputGroup className="mb-4" hidden={!defaultConfig.ALLOW_GATEWAY_CHANGE}> | ||||
|                       <CPopover content="Gateway URL"> | ||||
|                         <CInputGroupPrepend> | ||||
|                           <CInputGroupText> | ||||
| @@ -175,7 +197,7 @@ const Login = () => { | ||||
|                     <CRow> | ||||
|                       <CCol> | ||||
|                         <CAlert show={hadError} color="danger"> | ||||
|                           {loginErrorText} | ||||
|                           {t('login.login_error')} | ||||
|                         </CAlert> | ||||
|                       </CCol> | ||||
|                     </CRow> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user