diff --git a/.env b/.env deleted file mode 100644 index 2aabddb..0000000 --- a/.env +++ /dev/null @@ -1,2 +0,0 @@ -REACT_APP_DEFAULT_GATEWAY_URL=https://ucentral.dpaas.arilia.com:16001 -REACT_APP_ALLOW_GATEWAY_CHANGE=false \ No newline at end of file diff --git a/README.md b/README.md index 9b577d9..6ff8af1 100644 --- a/README.md +++ b/README.md @@ -23,16 +23,4 @@ git clone https://github.com/Telecominfraproject/wlan-cloud-ucentralgw-ui 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`. - +Once the build is done, you can move the `build` folder on your server. \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 4f04f59..f9dad06 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "ucentral-client", - "version": "0.9.1", + "version": "0.9.3", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 3dad34a..7482592 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ucentral-client", - "version": "0.9.2", + "version": "0.9.3", "private": true, "dependencies": { "@coreui/coreui": "^3.4.0", diff --git a/public/config.json b/public/config.json new file mode 100644 index 0000000..55aa90d --- /dev/null +++ b/public/config.json @@ -0,0 +1,4 @@ +{ + "DEFAULT_GATEWAY_URL": "https://ucentral.dpaas.arilia.com:16001", + "ALLOW_GATEWAY_CHANGE": false +} \ No newline at end of file diff --git a/public/locales/de/translation.json b/public/locales/de/translation.json index c512754..bbfddae 100644 --- a/public/locales/de/translation.json +++ b/public/locales/de/translation.json @@ -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", diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index b07a66c..33ee808 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -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", diff --git a/public/locales/es/translation.json b/public/locales/es/translation.json index 39bb9cd..9f2dd31 100644 --- a/public/locales/es/translation.json +++ b/public/locales/es/translation.json @@ -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", diff --git a/public/locales/fr/translation.json b/public/locales/fr/translation.json index 7e495fa..b564b5d 100644 --- a/public/locales/fr/translation.json +++ b/public/locales/fr/translation.json @@ -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é", diff --git a/public/locales/pt/translation.json b/public/locales/pt/translation.json index fc2cc69..9d16d4d 100644 --- a/public/locales/pt/translation.json +++ b/public/locales/pt/translation.json @@ -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", diff --git a/src/components/ConfirmFooter/index.js b/src/components/ConfirmFooter/index.js new file mode 100644 index 0000000..922f1b2 --- /dev/null +++ b/src/components/ConfirmFooter/index.js @@ -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 ( + + + + + + {t('common.cancel')} + + + ); +}; + +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; diff --git a/src/components/DeleteLogModal/index.js b/src/components/DeleteLogModal/index.js new file mode 100644 index 0000000..8f2b704 --- /dev/null +++ b/src/components/DeleteLogModal/index.js @@ -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 ( + + + + {object === 'healthchecks' + ? t('delete_logs.healthchecks_title') + : t('delete_logs.device_logs_title')} + + + +
{t('delete_logs.explanation', { object })}
+ + +

{t('common.date')}:

+
+ + setDate(date)} + /> + +
+
+ +
+ ); +}; + +DeleteLogModal.propTypes = { + show: PropTypes.bool.isRequired, + toggle: PropTypes.func.isRequired, + object: PropTypes.string.isRequired, + serialNumber: PropTypes.string.isRequired, +}; + +export default DeleteLogModal; diff --git a/src/components/DeleteLogModal/index.module.scss b/src/components/DeleteLogModal/index.module.scss new file mode 100644 index 0000000..100e640 --- /dev/null +++ b/src/components/DeleteLogModal/index.module.scss @@ -0,0 +1,11 @@ +.modal { + color: #3c4b64; +} + +.spacedRow { + margin-top: 20px; +} + +.spacedColumn { + margin-top: 7px; +} diff --git a/src/components/DeviceHealth/index.js b/src/components/DeviceHealth/index.js index b022035..9fd47fd 100644 --- a/src/components/DeviceHealth/index.js +++ b/src/components/DeviceHealth/index.js @@ -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 ( {
+
+ + { + toggleDeleteModal(); + }} + > + + + +
{t('common.from')}: @@ -250,6 +280,12 @@ const DeviceHealth = ({ selectedDeviceId }) => { size="lg" /> +
} /> diff --git a/src/components/DeviceHealth/index.module.scss b/src/components/DeviceHealth/index.module.scss index 73da2b3..ac10e83 100644 --- a/src/components/DeviceHealth/index.module.scss +++ b/src/components/DeviceHealth/index.module.scss @@ -25,3 +25,7 @@ .scrollable { height: 250px; } + +.alignRight { + float: right; +} diff --git a/src/components/DeviceLogs/index.js b/src/components/DeviceLogs/index.js index 60efc22..8ab4d3e 100644 --- a/src/components/DeviceLogs/index.js +++ b/src/components/DeviceLogs/index.js @@ -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,85 +157,115 @@ const DeviceLogs = ({ selectedDeviceId }) => { } }, [start, end, selectedDeviceId]); + useEffect(() => { + eventBus.on('deletedLogs', () => getLogs()); + + return () => { + eventBus.remove('deletedLogs'); + }; + }, []); + return ( - - - - - {t('common.from')} - modifyStart(date)} /> - - - {t('common.to')} - modifyEnd(date)} /> - - - -
- {prettyDate(item.recorded)}, - show_details: (item, index) => ( - - { - toggleDetails(index); - }} - > - - - - ), - details: (item, index) => ( - - -
{t('common.details')}
-
{getDetails(index, item)}
-
-
- ), - }} - /> - - {showLoadingMore && ( - - )} - +
+ + +
+ + { + toggleDeleteModal(); + }} + > + + +
- -
- - - -
- } - > - - + + + {t('common.from')} + modifyStart(date)} /> + + + {t('common.to')} + modifyEnd(date)} /> + + + +
+ {prettyDate(item.recorded)}, + show_details: (item, index) => ( + + { + toggleDetails(index); + }} + > + + + + ), + details: (item, index) => ( + + +
{t('common.details')}
+
{getDetails(index, item)}
+
+
+ ), + }} + /> + + {showLoadingMore && ( + + )} + +
+
+ + + + +
+ } + > + +
+ + ); }; diff --git a/src/components/DeviceLogs/index.module.scss b/src/components/DeviceLogs/index.module.scss index bf7f76d..3962864 100644 --- a/src/components/DeviceLogs/index.module.scss +++ b/src/components/DeviceLogs/index.module.scss @@ -17,3 +17,7 @@ .loadMoreRow { margin-bottom: 1%; } + +.alignRight { + float: right; +} diff --git a/src/components/InterfaceStatistics/containers/StatisticsChartList/index.js b/src/components/InterfaceStatistics/containers/StatisticsChartList/index.js index 6f5b398..07f262b 100644 --- a/src/components/InterfaceStatistics/containers/StatisticsChartList/index.js +++ b/src/components/InterfaceStatistics/containers/StatisticsChartList/index.js @@ -12,7 +12,7 @@ const StatisticsChartList = ({ selectedDeviceId, lastRefresh }) => { const [loading, setLoading] = useState(false); const [statOptions, setStatOptions] = useState({ interfaceList: [], - settings: {} + settings: {}, }); const transformIntoDataset = (data) => { @@ -62,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 / 1024)); + interfaceList[interfaceTypes[inter.name]][1].data.push( + Math.floor(inter.counters.rx_bytes / 1024), + ); } } @@ -101,10 +103,10 @@ const StatisticsChartList = ({ selectedDeviceId, lastRefresh }) => { const newOptions = { interfaceList, - settings: options + settings: options, }; - if(statOptions !== newOptions){ + if (statOptions !== newOptions) { setStatOptions(newOptions); } }; diff --git a/src/config.json b/src/config.json deleted file mode 100644 index fdd6ff4..0000000 --- a/src/config.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "REACT_APP_BASE_URL": "https://ucentral.dpaas.arilia.com:16001/api/v1" -} diff --git a/src/layout/Footer/index.js b/src/layout/Footer/index.js index badf9fc..aa79a3d 100644 --- a/src/layout/Footer/index.js +++ b/src/layout/Footer/index.js @@ -6,7 +6,7 @@ const TheFooter = () => ( {(t) => ( -
{t('footer.version')} 0.9.2
+
{t('footer.version')} 0.9.3
{t('footer.powered_by')} diff --git a/src/layout/Sidebar/index.module.scss b/src/layout/Sidebar/index.module.scss index f2917af..7a8ab70 100644 --- a/src/layout/Sidebar/index.module.scss +++ b/src/layout/Sidebar/index.module.scss @@ -1,5 +1,6 @@ .sidebarImgFull { height: 75px; + width: 75px; } .sidebarImgMinimized { diff --git a/src/pages/LoginPage/index.js b/src/pages/LoginPage/index.js index 68394d6..a94a0ff 100644 --- a/src/pages/LoginPage/index.js +++ b/src/pages/LoginPage/index.js @@ -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 (
@@ -151,7 +173,7 @@ const Login = () => { {t('login.please_enter_password')} -