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/
|
||||||
16
README.md
16
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,
|
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).
|
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
|
## Running the solution
|
||||||
|
|
||||||
### Development
|
### Development
|
||||||
@@ -21,16 +23,4 @@ git clone https://github.com/Telecominfraproject/wlan-cloud-ucentralgw-ui
|
|||||||
cd wlan-cloud-ucentralgw-ui
|
cd wlan-cloud-ucentralgw-ui
|
||||||
npm run build
|
npm run build
|
||||||
```
|
```
|
||||||
Once the build is done, you can move the `build` folder on your server.
|
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",
|
"name": "ucentral-client",
|
||||||
"version": "0.9.0",
|
"version": "0.9.3",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"version": "0.9.0",
|
"name": "ucentral-client",
|
||||||
|
"version": "0.9.2",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@coreui/coreui": "^3.4.0",
|
"@coreui/coreui": "^3.4.0",
|
||||||
"@coreui/icons": "^2.0.1",
|
"@coreui/icons": "^2.0.1",
|
||||||
@@ -37,6 +38,7 @@
|
|||||||
"react-select": "^4.3.1",
|
"react-select": "^4.3.1",
|
||||||
"react-widgets": "^5.1.1",
|
"react-widgets": "^5.1.1",
|
||||||
"redux": "^4.1.0",
|
"redux": "^4.1.0",
|
||||||
|
"sass": "^1.35.1",
|
||||||
"uuid": "^8.3.2"
|
"uuid": "^8.3.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -5248,7 +5250,6 @@
|
|||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
|
||||||
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
|
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
|
||||||
"optional": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
@@ -5737,7 +5738,6 @@
|
|||||||
"version": "3.5.1",
|
"version": "3.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz",
|
||||||
"integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==",
|
"integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==",
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"anymatch": "~3.1.1",
|
"anymatch": "~3.1.1",
|
||||||
"braces": "~3.0.2",
|
"braces": "~3.0.2",
|
||||||
@@ -10569,7 +10569,6 @@
|
|||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
||||||
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
|
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"binary-extensions": "^2.0.0"
|
"binary-extensions": "^2.0.0"
|
||||||
},
|
},
|
||||||
@@ -17453,7 +17452,6 @@
|
|||||||
"version": "3.5.0",
|
"version": "3.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz",
|
||||||
"integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==",
|
"integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==",
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"picomatch": "^2.2.1"
|
"picomatch": "^2.2.1"
|
||||||
},
|
},
|
||||||
@@ -18343,8 +18341,6 @@
|
|||||||
"version": "1.35.1",
|
"version": "1.35.1",
|
||||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.35.1.tgz",
|
"resolved": "https://registry.npmjs.org/sass/-/sass-1.35.1.tgz",
|
||||||
"integrity": "sha512-oCisuQJstxMcacOPmxLNiLlj4cUyN2+8xJnG7VanRoh2GOLr9RqkvI4AxA4a6LHVg/rsu+PmxXeGhrdSF9jCiQ==",
|
"integrity": "sha512-oCisuQJstxMcacOPmxLNiLlj4cUyN2+8xJnG7VanRoh2GOLr9RqkvI4AxA4a6LHVg/rsu+PmxXeGhrdSF9jCiQ==",
|
||||||
"optional": true,
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chokidar": ">=3.0.0 <4.0.0"
|
"chokidar": ">=3.0.0 <4.0.0"
|
||||||
},
|
},
|
||||||
@@ -26490,8 +26486,7 @@
|
|||||||
"binary-extensions": {
|
"binary-extensions": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
|
||||||
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
|
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA=="
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"version": "1.5.0",
|
"version": "1.5.0",
|
||||||
@@ -26896,7 +26891,6 @@
|
|||||||
"version": "3.5.1",
|
"version": "3.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz",
|
||||||
"integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==",
|
"integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==",
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"anymatch": "~3.1.1",
|
"anymatch": "~3.1.1",
|
||||||
"braces": "~3.0.2",
|
"braces": "~3.0.2",
|
||||||
@@ -30706,7 +30700,6 @@
|
|||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
||||||
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
|
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"binary-extensions": "^2.0.0"
|
"binary-extensions": "^2.0.0"
|
||||||
}
|
}
|
||||||
@@ -36047,7 +36040,6 @@
|
|||||||
"version": "3.5.0",
|
"version": "3.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz",
|
||||||
"integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==",
|
"integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==",
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"picomatch": "^2.2.1"
|
"picomatch": "^2.2.1"
|
||||||
}
|
}
|
||||||
@@ -36756,8 +36748,6 @@
|
|||||||
"version": "1.35.1",
|
"version": "1.35.1",
|
||||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.35.1.tgz",
|
"resolved": "https://registry.npmjs.org/sass/-/sass-1.35.1.tgz",
|
||||||
"integrity": "sha512-oCisuQJstxMcacOPmxLNiLlj4cUyN2+8xJnG7VanRoh2GOLr9RqkvI4AxA4a6LHVg/rsu+PmxXeGhrdSF9jCiQ==",
|
"integrity": "sha512-oCisuQJstxMcacOPmxLNiLlj4cUyN2+8xJnG7VanRoh2GOLr9RqkvI4AxA4a6LHVg/rsu+PmxXeGhrdSF9jCiQ==",
|
||||||
"optional": true,
|
|
||||||
"peer": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"chokidar": ">=3.0.0 <4.0.0"
|
"chokidar": ">=3.0.0 <4.0.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ucentral-client",
|
"name": "ucentral-client",
|
||||||
"version": "0.9.0",
|
"version": "0.9.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@coreui/coreui": "^3.4.0",
|
"@coreui/coreui": "^3.4.0",
|
||||||
@@ -33,6 +33,7 @@
|
|||||||
"react-select": "^4.3.1",
|
"react-select": "^4.3.1",
|
||||||
"react-widgets": "^5.1.1",
|
"react-widgets": "^5.1.1",
|
||||||
"redux": "^4.1.0",
|
"redux": "^4.1.0",
|
||||||
|
"sass": "^1.35.1",
|
||||||
"uuid": "^8.3.2"
|
"uuid": "^8.3.2"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"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.",
|
"explanation": "Möchten Sie diesen Befehl wirklich löschen? Diese Aktion ist nicht umkehrbar.",
|
||||||
"title": "Befehl löschen"
|
"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": {
|
"device_logs": {
|
||||||
"log": "Log",
|
"log": "Log",
|
||||||
"severity": "Schwere",
|
"severity": "Schwere",
|
||||||
|
|||||||
@@ -103,6 +103,12 @@
|
|||||||
"explanation": "Are you sure you want to delete this command? This action is not reversible.",
|
"explanation": "Are you sure you want to delete this command? This action is not reversible.",
|
||||||
"title": "Delete Command"
|
"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": {
|
"device_logs": {
|
||||||
"log": "Log",
|
"log": "Log",
|
||||||
"severity": "Severity",
|
"severity": "Severity",
|
||||||
|
|||||||
@@ -103,6 +103,12 @@
|
|||||||
"explanation": "¿Está seguro de que desea eliminar este comando? Esta acción no es reversible.",
|
"explanation": "¿Está seguro de que desea eliminar este comando? Esta acción no es reversible.",
|
||||||
"title": "Eliminar comando"
|
"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": {
|
"device_logs": {
|
||||||
"log": "Iniciar sesión",
|
"log": "Iniciar sesión",
|
||||||
"severity": "Gravedad",
|
"severity": "Gravedad",
|
||||||
|
|||||||
@@ -103,6 +103,12 @@
|
|||||||
"explanation": "Êtes-vous sûr de vouloir supprimer cette commande ? Cette action n'est pas réversible.",
|
"explanation": "Êtes-vous sûr de vouloir supprimer cette commande ? Cette action n'est pas réversible.",
|
||||||
"title": "Supprimer la commande"
|
"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": {
|
"device_logs": {
|
||||||
"log": "Bûche",
|
"log": "Bûche",
|
||||||
"severity": "Gravité",
|
"severity": "Gravité",
|
||||||
|
|||||||
@@ -103,6 +103,12 @@
|
|||||||
"explanation": "Tem certeza de que deseja excluir este comando? esta ação não é reversível.",
|
"explanation": "Tem certeza de que deseja excluir este comando? esta ação não é reversível.",
|
||||||
"title": "Apagar Comando"
|
"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": {
|
"device_logs": {
|
||||||
"log": "Registro",
|
"log": "Registro",
|
||||||
"severity": "Gravidade",
|
"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,
|
CRow,
|
||||||
CCol,
|
CCol,
|
||||||
CProgress,
|
CProgress,
|
||||||
|
CPopover,
|
||||||
} from '@coreui/react';
|
} from '@coreui/react';
|
||||||
import CIcon from '@coreui/icons-react';
|
import CIcon from '@coreui/icons-react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@@ -18,7 +19,9 @@ import PropTypes from 'prop-types';
|
|||||||
import { prettyDate, dateToUnix } from 'utils/helper';
|
import { prettyDate, dateToUnix } from 'utils/helper';
|
||||||
import axiosInstance from 'utils/axiosInstance';
|
import axiosInstance from 'utils/axiosInstance';
|
||||||
import { getToken } from 'utils/authHelper';
|
import { getToken } from 'utils/authHelper';
|
||||||
|
import eventBus from 'utils/eventBus';
|
||||||
import LoadingButton from 'components/LoadingButton';
|
import LoadingButton from 'components/LoadingButton';
|
||||||
|
import DeleteLogModal from 'components/DeleteLogModal';
|
||||||
import styles from './index.module.scss';
|
import styles from './index.module.scss';
|
||||||
|
|
||||||
const DeviceHealth = ({ selectedDeviceId }) => {
|
const DeviceHealth = ({ selectedDeviceId }) => {
|
||||||
@@ -34,6 +37,11 @@ const DeviceHealth = ({ selectedDeviceId }) => {
|
|||||||
const [showLoadingMore, setShowLoadingMore] = useState(true);
|
const [showLoadingMore, setShowLoadingMore] = useState(true);
|
||||||
const [sanityLevel, setSanityLevel] = useState(null);
|
const [sanityLevel, setSanityLevel] = useState(null);
|
||||||
const [barColor, setBarColor] = useState('gradient-dark');
|
const [barColor, setBarColor] = useState('gradient-dark');
|
||||||
|
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||||
|
|
||||||
|
const toggleDeleteModal = () => {
|
||||||
|
setShowDeleteModal(!showDeleteModal);
|
||||||
|
};
|
||||||
|
|
||||||
const toggle = (e) => {
|
const toggle = (e) => {
|
||||||
setCollapse(!collapse);
|
setCollapse(!collapse);
|
||||||
@@ -167,6 +175,14 @@ const DeviceHealth = ({ selectedDeviceId }) => {
|
|||||||
}
|
}
|
||||||
}, [start, end, selectedDeviceId]);
|
}, [start, end, selectedDeviceId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
eventBus.on('deletedHealth', () => getDeviceHealth());
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
eventBus.remove('deletedHealth');
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CWidgetDropdown
|
<CWidgetDropdown
|
||||||
header={sanityLevel ? `${sanityLevel}%` : t('common.unknown')}
|
header={sanityLevel ? `${sanityLevel}%` : t('common.unknown')}
|
||||||
@@ -178,6 +194,20 @@ const DeviceHealth = ({ selectedDeviceId }) => {
|
|||||||
<div className={styles.footer}>
|
<div className={styles.footer}>
|
||||||
<CProgress className={styles.progressBar} color="white" value={sanityLevel ?? 0} />
|
<CProgress className={styles.progressBar} color="white" value={sanityLevel ?? 0} />
|
||||||
<CCollapse show={collapse}>
|
<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}>
|
<CRow className={styles.spacedRow}>
|
||||||
<CCol>
|
<CCol>
|
||||||
{t('common.from')}:
|
{t('common.from')}:
|
||||||
@@ -250,6 +280,12 @@ const DeviceHealth = ({ selectedDeviceId }) => {
|
|||||||
size="lg"
|
size="lg"
|
||||||
/>
|
/>
|
||||||
</CButton>
|
</CButton>
|
||||||
|
<DeleteLogModal
|
||||||
|
serialNumber={selectedDeviceId}
|
||||||
|
object="healthchecks"
|
||||||
|
show={showDeleteModal}
|
||||||
|
toggle={toggleDeleteModal}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -25,3 +25,7 @@
|
|||||||
.scrollable {
|
.scrollable {
|
||||||
height: 250px;
|
height: 250px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.alignRight {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
CDataTable,
|
CDataTable,
|
||||||
CCard,
|
CCard,
|
||||||
CCardBody,
|
CCardBody,
|
||||||
|
CPopover,
|
||||||
} from '@coreui/react';
|
} from '@coreui/react';
|
||||||
import CIcon from '@coreui/icons-react';
|
import CIcon from '@coreui/icons-react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@@ -17,7 +18,9 @@ import PropTypes from 'prop-types';
|
|||||||
import { prettyDate, dateToUnix } from 'utils/helper';
|
import { prettyDate, dateToUnix } from 'utils/helper';
|
||||||
import axiosInstance from 'utils/axiosInstance';
|
import axiosInstance from 'utils/axiosInstance';
|
||||||
import { getToken } from 'utils/authHelper';
|
import { getToken } from 'utils/authHelper';
|
||||||
|
import eventBus from 'utils/eventBus';
|
||||||
import LoadingButton from 'components/LoadingButton';
|
import LoadingButton from 'components/LoadingButton';
|
||||||
|
import DeleteLogModal from 'components/DeleteLogModal';
|
||||||
import styles from './index.module.scss';
|
import styles from './index.module.scss';
|
||||||
|
|
||||||
const DeviceLogs = ({ selectedDeviceId }) => {
|
const DeviceLogs = ({ selectedDeviceId }) => {
|
||||||
@@ -31,6 +34,11 @@ const DeviceLogs = ({ selectedDeviceId }) => {
|
|||||||
const [logLimit, setLogLimit] = useState(25);
|
const [logLimit, setLogLimit] = useState(25);
|
||||||
const [loadingMore, setLoadingMore] = useState(false);
|
const [loadingMore, setLoadingMore] = useState(false);
|
||||||
const [showLoadingMore, setShowLoadingMore] = useState(true);
|
const [showLoadingMore, setShowLoadingMore] = useState(true);
|
||||||
|
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||||
|
|
||||||
|
const toggleDeleteModal = () => {
|
||||||
|
setShowDeleteModal(!showDeleteModal);
|
||||||
|
};
|
||||||
|
|
||||||
const toggle = (e) => {
|
const toggle = (e) => {
|
||||||
setCollapse(!collapse);
|
setCollapse(!collapse);
|
||||||
@@ -149,85 +157,115 @@ const DeviceLogs = ({ selectedDeviceId }) => {
|
|||||||
}
|
}
|
||||||
}, [start, end, selectedDeviceId]);
|
}, [start, end, selectedDeviceId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
eventBus.on('deletedLogs', () => getLogs());
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
eventBus.remove('deletedLogs');
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CWidgetDropdown
|
<div>
|
||||||
inverse="true"
|
<CWidgetDropdown
|
||||||
color="gradient-info"
|
inverse="true"
|
||||||
header={t('device_logs.title')}
|
color="gradient-info"
|
||||||
footerSlot={
|
header={t('device_logs.title')}
|
||||||
<div className={styles.footer}>
|
footerSlot={
|
||||||
<CCollapse show={collapse}>
|
<div className={styles.footer}>
|
||||||
<CRow className={styles.datepickerRow}>
|
<CCollapse show={collapse}>
|
||||||
<CCol>
|
<div className={styles.alignRight}>
|
||||||
{t('common.from')}
|
<CPopover content={t('common.delete')}>
|
||||||
<DatePicker includeTime onChange={(date) => modifyStart(date)} />
|
<CButton
|
||||||
</CCol>
|
color="light"
|
||||||
<CCol>
|
shape="square"
|
||||||
{t('common.to')}
|
size="sm"
|
||||||
<DatePicker includeTime onChange={(date) => modifyEnd(date)} />
|
onClick={() => {
|
||||||
</CCol>
|
toggleDeleteModal();
|
||||||
</CRow>
|
}}
|
||||||
<CCard>
|
>
|
||||||
<div className={[styles.scrollableCard, 'overflow-auto'].join(' ')}>
|
<CIcon name="cilTrash" size="lg" />
|
||||||
<CDataTable
|
</CButton>
|
||||||
items={logs ?? []}
|
</CPopover>
|
||||||
fields={columns}
|
|
||||||
loading={loading}
|
|
||||||
className={styles.whiteIcon}
|
|
||||||
sorterValue={{ column: 'recorded', desc: 'true' }}
|
|
||||||
scopedSlots={{
|
|
||||||
recorded: (item) => <td>{prettyDate(item.recorded)}</td>,
|
|
||||||
show_details: (item, index) => (
|
|
||||||
<td className="py-2">
|
|
||||||
<CButton
|
|
||||||
color="primary"
|
|
||||||
variant={details.includes(index) ? '' : 'outline'}
|
|
||||||
shape="square"
|
|
||||||
size="sm"
|
|
||||||
onClick={() => {
|
|
||||||
toggleDetails(index);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CIcon name="cilList" size="lg" />
|
|
||||||
</CButton>
|
|
||||||
</td>
|
|
||||||
),
|
|
||||||
details: (item, index) => (
|
|
||||||
<CCollapse show={details.includes(index)}>
|
|
||||||
<CCardBody>
|
|
||||||
<h5>{t('common.details')}</h5>
|
|
||||||
<div>{getDetails(index, item)}</div>
|
|
||||||
</CCardBody>
|
|
||||||
</CCollapse>
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<CRow className={styles.loadMoreRow}>
|
|
||||||
{showLoadingMore && (
|
|
||||||
<LoadingButton
|
|
||||||
label={t('common.view_more')}
|
|
||||||
isLoadingLabel={t('common.loading_more_ellipsis')}
|
|
||||||
isLoading={loadingMore}
|
|
||||||
action={showMoreLogs}
|
|
||||||
variant="outline"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</CRow>
|
|
||||||
</div>
|
</div>
|
||||||
</CCard>
|
<CRow className={styles.datepickerRow}>
|
||||||
</CCollapse>
|
<CCol>
|
||||||
<CButton show={collapse ? 'true' : 'false'} color="transparent" onClick={toggle} block>
|
{t('common.from')}
|
||||||
<CIcon
|
<DatePicker includeTime onChange={(date) => modifyStart(date)} />
|
||||||
name={collapse ? 'cilChevronTop' : 'cilChevronBottom'}
|
</CCol>
|
||||||
className={styles.whiteIcon}
|
<CCol>
|
||||||
size="lg"
|
{t('common.to')}
|
||||||
/>
|
<DatePicker includeTime onChange={(date) => modifyEnd(date)} />
|
||||||
</CButton>
|
</CCol>
|
||||||
</div>
|
</CRow>
|
||||||
}
|
<CCard>
|
||||||
>
|
<div className={[styles.scrollableCard, 'overflow-auto'].join(' ')}>
|
||||||
<CIcon name="cilList" className={styles.whiteIcon} size="lg" />
|
<CDataTable
|
||||||
</CWidgetDropdown>
|
items={logs ?? []}
|
||||||
|
fields={columns}
|
||||||
|
loading={loading}
|
||||||
|
className={styles.whiteIcon}
|
||||||
|
sorterValue={{ column: 'recorded', desc: 'true' }}
|
||||||
|
scopedSlots={{
|
||||||
|
recorded: (item) => <td>{prettyDate(item.recorded)}</td>,
|
||||||
|
show_details: (item, index) => (
|
||||||
|
<td className="py-2">
|
||||||
|
<CButton
|
||||||
|
color="primary"
|
||||||
|
variant={details.includes(index) ? '' : 'outline'}
|
||||||
|
shape="square"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => {
|
||||||
|
toggleDetails(index);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CIcon name="cilList" size="lg" />
|
||||||
|
</CButton>
|
||||||
|
</td>
|
||||||
|
),
|
||||||
|
details: (item, index) => (
|
||||||
|
<CCollapse show={details.includes(index)}>
|
||||||
|
<CCardBody>
|
||||||
|
<h5>{t('common.details')}</h5>
|
||||||
|
<div>{getDetails(index, item)}</div>
|
||||||
|
</CCardBody>
|
||||||
|
</CCollapse>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<CRow className={styles.loadMoreRow}>
|
||||||
|
{showLoadingMore && (
|
||||||
|
<LoadingButton
|
||||||
|
label={t('common.view_more')}
|
||||||
|
isLoadingLabel={t('common.loading_more_ellipsis')}
|
||||||
|
isLoading={loadingMore}
|
||||||
|
action={showMoreLogs}
|
||||||
|
variant="outline"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</CRow>
|
||||||
|
</div>
|
||||||
|
</CCard>
|
||||||
|
</CCollapse>
|
||||||
|
<CButton show={collapse ? 'true' : 'false'} color="transparent" onClick={toggle} block>
|
||||||
|
<CIcon
|
||||||
|
name={collapse ? 'cilChevronTop' : 'cilChevronBottom'}
|
||||||
|
className={styles.whiteIcon}
|
||||||
|
size="lg"
|
||||||
|
/>
|
||||||
|
</CButton>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<CIcon name="cilList" className={styles.whiteIcon} size="lg" />
|
||||||
|
</CWidgetDropdown>
|
||||||
|
<DeleteLogModal
|
||||||
|
serialNumber={selectedDeviceId}
|
||||||
|
object="logs"
|
||||||
|
show={showDeleteModal}
|
||||||
|
toggle={toggleDeleteModal}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -17,3 +17,7 @@
|
|||||||
.loadMoreRow {
|
.loadMoreRow {
|
||||||
margin-bottom: 1%;
|
margin-bottom: 1%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.alignRight {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Chart from 'react-apexcharts';
|
import Chart from 'react-apexcharts';
|
||||||
import styles from './index.module.scss';
|
|
||||||
|
|
||||||
const DeviceStatisticsChart = ({ data, options }) => (
|
const DeviceStatisticsChart = ({ data, options }) => (
|
||||||
<div className={styles.chart}>
|
<div style={{ height: '360px' }}>
|
||||||
<Chart series={data} options={options} type="line" height="100%" />
|
<Chart series={data} options={options} type="line" height="100%" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
.chart {
|
|
||||||
height: 360px;
|
|
||||||
}
|
|
||||||
@@ -10,8 +10,10 @@ import DeviceStatisticsChart from '../DeviceStatisticsChart';
|
|||||||
const StatisticsChartList = ({ selectedDeviceId, lastRefresh }) => {
|
const StatisticsChartList = ({ selectedDeviceId, lastRefresh }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [deviceStats, setStats] = useState([]);
|
const [statOptions, setStatOptions] = useState({
|
||||||
const [statOptions, setStatOptions] = useState({});
|
interfaceList: [],
|
||||||
|
settings: {},
|
||||||
|
});
|
||||||
|
|
||||||
const transformIntoDataset = (data) => {
|
const transformIntoDataset = (data) => {
|
||||||
const sortedData = data.sort((a, b) => {
|
const sortedData = data.sort((a, b) => {
|
||||||
@@ -60,7 +62,9 @@ const StatisticsChartList = ({ selectedDeviceId, lastRefresh }) => {
|
|||||||
interfaceList[interfaceTypes[inter.name]][0].data.push(
|
interfaceList[interfaceTypes[inter.name]][0].data.push(
|
||||||
Math.floor(inter.counters.tx_bytes / 1024),
|
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);
|
const newOptions = {
|
||||||
setStats(interfaceList);
|
interfaceList,
|
||||||
|
settings: options,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (statOptions !== newOptions) {
|
||||||
|
setStatOptions(newOptions);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getStatistics = () => {
|
const getStatistics = () => {
|
||||||
@@ -134,20 +144,20 @@ const StatisticsChartList = ({ selectedDeviceId, lastRefresh }) => {
|
|||||||
}, [selectedDeviceId]);
|
}, [selectedDeviceId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (lastRefresh !== '' && selectedDeviceId) {
|
if (!loading && lastRefresh !== '' && selectedDeviceId) {
|
||||||
getStatistics();
|
getStatistics();
|
||||||
}
|
}
|
||||||
}, [lastRefresh]);
|
}, [lastRefresh]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{deviceStats.map((data) => (
|
{statOptions.interfaceList.map((data) => (
|
||||||
<div key={createUuid()}>
|
<div key={createUuid()}>
|
||||||
<DeviceStatisticsChart
|
<DeviceStatisticsChart
|
||||||
key={createUuid()}
|
key={createUuid()}
|
||||||
data={data}
|
data={data}
|
||||||
options={{
|
options={{
|
||||||
...statOptions,
|
...statOptions.settings,
|
||||||
title: {
|
title: {
|
||||||
text: capitalizeFirstLetter(data[0].titleName),
|
text: capitalizeFirstLetter(data[0].titleName),
|
||||||
align: 'left',
|
align: 'left',
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import styles from './index.module.scss';
|
|||||||
|
|
||||||
const DeviceStatisticsCard = ({ selectedDeviceId }) => {
|
const DeviceStatisticsCard = ({ selectedDeviceId }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [lastRefresh, setLastRefresh] = useState('');
|
const [lastRefresh, setLastRefresh] = useState(new Date().toString());
|
||||||
|
|
||||||
const refresh = () => {
|
const refresh = () => {
|
||||||
setLastRefresh(new Date().toString());
|
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>
|
<Translation>
|
||||||
{(t) => (
|
{(t) => (
|
||||||
<CFooter fixed={false}>
|
<CFooter fixed={false}>
|
||||||
<div>{t('footer.version')} 0.9.0</div>
|
<div>{t('footer.version')} 0.9.3</div>
|
||||||
<div className="mfs-auto">
|
<div className="mfs-auto">
|
||||||
<span className="mr-1">{t('footer.powered_by')}</span>
|
<span className="mr-1">{t('footer.powered_by')}</span>
|
||||||
<a href="https://coreui.io/react" target="_blank" rel="noopener noreferrer">
|
<a href="https://coreui.io/react" target="_blank" rel="noopener noreferrer">
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
.sidebarImgFull {
|
.sidebarImgFull {
|
||||||
height: 75px;
|
height: 75px;
|
||||||
|
width: 75px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebarImgMinimized {
|
.sidebarImgMinimized {
|
||||||
|
|||||||
@@ -30,15 +30,30 @@ const Login = () => {
|
|||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const [userId, setUsername] = useState('');
|
const [userId, setUsername] = useState('');
|
||||||
const [password, setPassword] = 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 [hadError, setHadError] = useState(false);
|
||||||
const [emptyUsername, setEmptyUsername] = useState(false);
|
const [emptyUsername, setEmptyUsername] = useState(false);
|
||||||
const [emptyPassword, setEmptyPassword] = useState(false);
|
const [emptyPassword, setEmptyPassword] = useState(false);
|
||||||
const [emptyGateway, setEmptyGateway] = useState(false);
|
const [emptyGateway, setEmptyGateway] = useState(false);
|
||||||
const placeholderUrl = 'Gateway URL (ex: https://ucentral.dpaas.arilia.com:16001)';
|
const [defaultConfig, setDefaultConfig] = useState({
|
||||||
const defaultGatewayUrl = process.env.REACT_APP_DEFAULT_GATEWAY_URL;
|
DEFAULT_GATEWAY_URL: '',
|
||||||
const allowUrlChange = process.env.REACT_APP_ALLOW_GATEWAY_CHANGE === 'true';
|
ALLOW_GATEWAY_CHANGE: true,
|
||||||
const loginErrorText = t('login.login_error');
|
});
|
||||||
|
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 = () => {
|
const formValidation = () => {
|
||||||
setHadError(false);
|
setHadError(false);
|
||||||
@@ -59,12 +74,13 @@ const Login = () => {
|
|||||||
setEmptyGateway(true);
|
setEmptyGateway(true);
|
||||||
isSuccessful = false;
|
isSuccessful = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return isSuccessful;
|
return isSuccessful;
|
||||||
};
|
};
|
||||||
|
|
||||||
const SignIn = (credentials) => {
|
const SignIn = (credentials) => {
|
||||||
const gatewayUrlToUse = allowUrlChange ? gatewayUrl : defaultGatewayUrl;
|
const gatewayUrlToUse = defaultConfig.ALLOW_GATEWAY_CHANGE
|
||||||
|
? gatewayUrl
|
||||||
|
: defaultConfig.DEFAULT_GATEWAY_URL;
|
||||||
|
|
||||||
axiosInstance
|
axiosInstance
|
||||||
.post(`${gatewayUrlToUse}/api/v1/oauth2`, credentials)
|
.post(`${gatewayUrlToUse}/api/v1/oauth2`, credentials)
|
||||||
@@ -93,6 +109,12 @@ const Login = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (emptyGateway) setEmptyGateway(false);
|
if (emptyGateway) setEmptyGateway(false);
|
||||||
}, [gatewayUrl]);
|
}, [gatewayUrl]);
|
||||||
|
useEffect(() => {
|
||||||
|
getDefaultConfig();
|
||||||
|
}, []);
|
||||||
|
useEffect(() => {
|
||||||
|
setGatewayUrl(defaultConfig.DEFAULT_GATEWAY_URL);
|
||||||
|
}, [defaultConfig]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="c-app c-default-layout flex-row align-items-center">
|
<div className="c-app c-default-layout flex-row align-items-center">
|
||||||
@@ -151,7 +173,7 @@ const Login = () => {
|
|||||||
{t('login.please_enter_password')}
|
{t('login.please_enter_password')}
|
||||||
</CInvalidFeedback>
|
</CInvalidFeedback>
|
||||||
</CInputGroup>
|
</CInputGroup>
|
||||||
<CInputGroup className="mb-4" hidden={!allowUrlChange}>
|
<CInputGroup className="mb-4" hidden={!defaultConfig.ALLOW_GATEWAY_CHANGE}>
|
||||||
<CPopover content="Gateway URL">
|
<CPopover content="Gateway URL">
|
||||||
<CInputGroupPrepend>
|
<CInputGroupPrepend>
|
||||||
<CInputGroupText>
|
<CInputGroupText>
|
||||||
@@ -175,7 +197,7 @@ const Login = () => {
|
|||||||
<CRow>
|
<CRow>
|
||||||
<CCol>
|
<CCol>
|
||||||
<CAlert show={hadError} color="danger">
|
<CAlert show={hadError} color="danger">
|
||||||
{loginErrorText}
|
{t('login.login_error')}
|
||||||
</CAlert>
|
</CAlert>
|
||||||
</CCol>
|
</CCol>
|
||||||
</CRow>
|
</CRow>
|
||||||
|
|||||||
Reference in New Issue
Block a user