mirror of
https://github.com/Telecominfraproject/wlan-cloud-ucentralgw-ui.git
synced 2025-10-30 02:12:33 +00:00
Compare commits
254 Commits
dev
...
v2.1.0-RC1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
63b1fef875 | ||
|
|
671057e507 | ||
|
|
78fe66a155 | ||
|
|
1900a2cc1e | ||
|
|
d202938370 | ||
|
|
6f66d7134d | ||
|
|
a70d363b62 | ||
|
|
3799d6d187 | ||
|
|
1fd56085f2 | ||
|
|
61de7f7abf | ||
|
|
bd37b67ab8 | ||
|
|
6c0a5c0806 | ||
|
|
5be16c47be | ||
|
|
ebe37afa4a | ||
|
|
4ec232f35c | ||
|
|
53e2909690 | ||
|
|
092653c838 | ||
|
|
d204861c56 | ||
|
|
1494dd5ee9 | ||
|
|
4a3e697d78 | ||
|
|
f234ca2985 | ||
|
|
b64bbb4262 | ||
|
|
41f139fdf1 | ||
|
|
386b94c126 | ||
|
|
36d2d31878 | ||
|
|
3e0dc52376 | ||
|
|
aef48107fe | ||
|
|
9fb2c92a15 | ||
|
|
6dbe24e34f | ||
|
|
656d407ade | ||
|
|
133af50067 | ||
|
|
bb03f0d250 | ||
|
|
4d373fb1c7 | ||
|
|
c21453861f | ||
|
|
587a0aac68 | ||
|
|
58a083109e | ||
|
|
97d326ac88 | ||
|
|
909616efbe | ||
|
|
fa566c2101 | ||
|
|
8a7d740c95 | ||
|
|
69e5c2ef48 | ||
|
|
f202cd7327 | ||
|
|
f6861ec122 | ||
|
|
76a10f3dd7 | ||
|
|
6c16125fa4 | ||
|
|
d6b9d445aa | ||
|
|
5403ce690e | ||
|
|
8bb3f64e70 | ||
|
|
bcbdf3441e | ||
|
|
4c355d8a68 | ||
|
|
44c32f364a | ||
|
|
439bb9667e | ||
|
|
d0daf64ae9 | ||
|
|
6e90ed67ad | ||
|
|
ebc44fa1ea | ||
|
|
5ba716a437 | ||
|
|
b7a427fc6f | ||
|
|
6067fcbc9e | ||
|
|
8b767a8aff | ||
|
|
3c6cf48d9f | ||
|
|
c6b77aecb9 | ||
|
|
3119c8f9a6 | ||
|
|
ada2dea463 | ||
|
|
9001214462 | ||
|
|
77bc5eaabd | ||
|
|
c4263299b9 | ||
|
|
633b59f99f | ||
|
|
35f1ddcbae | ||
|
|
7279acff7e | ||
|
|
91dc8f4c92 | ||
|
|
dfba26b76a | ||
|
|
4954f11c93 | ||
|
|
40f752f48f | ||
|
|
5ccf299003 | ||
|
|
b5f8b3c1eb | ||
|
|
cfe2199354 | ||
|
|
074c973aeb | ||
|
|
2166001174 | ||
|
|
98b82b9fc6 | ||
|
|
a56d92e4eb | ||
|
|
283c8361f2 | ||
|
|
3743fc5cfa | ||
|
|
08d2154489 | ||
|
|
449ce0a927 | ||
|
|
5dff6f76d1 | ||
|
|
5ae181cd89 | ||
|
|
09887a439e | ||
|
|
b1d999a42e | ||
|
|
c857796318 | ||
|
|
68b3693531 | ||
|
|
79fb4c550d | ||
|
|
b02e495028 | ||
|
|
acdd51ffdf | ||
|
|
69f3d96be2 | ||
|
|
c7e3c9d8d0 | ||
|
|
d80002afc8 | ||
|
|
5ade42ca7c | ||
|
|
8636215314 | ||
|
|
4f4fe972d3 | ||
|
|
38a39a2aaa | ||
|
|
1d40cef5d5 | ||
|
|
d9160aac2d | ||
|
|
5125be8094 | ||
|
|
cadf862c7b | ||
|
|
13639b3a01 | ||
|
|
f0c8a4053c | ||
|
|
e47d8b02d3 | ||
|
|
de7faed4c3 | ||
|
|
fbd03c1fc5 | ||
|
|
e23b77c400 | ||
|
|
df6ec36515 | ||
|
|
192836dd2c | ||
|
|
3510f6f90b | ||
|
|
db7394d86c | ||
|
|
772e7cd07d | ||
|
|
15403befc0 | ||
|
|
4d0f7f2de2 | ||
|
|
8cdb1865bf | ||
|
|
c5843a55c3 | ||
|
|
54bc4ad403 | ||
|
|
8fe26a08b3 | ||
|
|
75b2dd2e27 | ||
|
|
83b7560d4d | ||
|
|
946fbbc053 | ||
|
|
54174dd4f2 | ||
|
|
017d1719a6 | ||
|
|
efdf5b2b30 | ||
|
|
4a58852ebd | ||
|
|
cf18a96900 | ||
|
|
d1f4e77e6d | ||
|
|
757b09f031 | ||
|
|
6f78768459 | ||
|
|
c3c31ec4ac | ||
|
|
3dce199b79 | ||
|
|
403e8bc185 | ||
|
|
0f15ae7bef | ||
|
|
112b01afff | ||
|
|
4a45ad0025 | ||
|
|
02385c5544 | ||
|
|
a422d5d446 | ||
|
|
5fe05e2890 | ||
|
|
d9f3406ae7 | ||
|
|
28ec0482c8 | ||
|
|
273a91f357 | ||
|
|
2c7df5abd1 | ||
|
|
09232c5640 | ||
|
|
fe84f6bdb1 | ||
|
|
e1727d2f91 | ||
|
|
74cac90696 | ||
|
|
4b2bde3c3a | ||
|
|
c4b19a3b24 | ||
|
|
7384558ad0 | ||
|
|
cc8be05594 | ||
|
|
bff117e45a | ||
|
|
62b1c23b3c | ||
|
|
340d21edac | ||
|
|
73a824330a | ||
|
|
96cc7dc78b | ||
|
|
b71b4cd1a5 | ||
|
|
a54acfd347 | ||
|
|
4d4af822d9 | ||
|
|
3bc028fc3b | ||
|
|
e1e74cb29a | ||
|
|
d9a8a02cd7 | ||
|
|
4c96158f11 | ||
|
|
7162303e62 | ||
|
|
26ef27f251 | ||
|
|
f2f370602f | ||
|
|
2056482179 | ||
|
|
e8960368a5 | ||
|
|
c017fe0774 | ||
|
|
3ea22477ae | ||
|
|
258ba197bb | ||
|
|
908a034433 | ||
|
|
101509edd6 | ||
|
|
ae7200815d | ||
|
|
f42b3d48d3 | ||
|
|
e60a703f19 | ||
|
|
281a84f19c | ||
|
|
563b94765e | ||
|
|
9695a3ba71 | ||
|
|
5b6f0e8f78 | ||
|
|
405e8eb944 | ||
|
|
1d771a2bea | ||
|
|
895301edd3 | ||
|
|
568961c4d9 | ||
|
|
ce5c25f169 | ||
|
|
0b142b0658 | ||
|
|
87b3450221 | ||
|
|
97b3716dc9 | ||
|
|
c83b8eb8aa | ||
|
|
d5244f8626 | ||
|
|
d5156bf000 | ||
|
|
ae36c31dea | ||
|
|
77224477b3 | ||
|
|
80fa5e7d43 | ||
|
|
84de2595b9 | ||
|
|
c0023436e8 | ||
|
|
6593be8bdf | ||
|
|
fb344db120 | ||
|
|
6cf081527f | ||
|
|
dc4dca417b | ||
|
|
be7a55fbc0 | ||
|
|
7b476cf38a | ||
|
|
236e2ced62 | ||
|
|
e99fd9f6b2 | ||
|
|
78eb883fef | ||
|
|
16608c5fcf | ||
|
|
657f61b43e | ||
|
|
3e279aff58 | ||
|
|
6628c69d19 | ||
|
|
cdcd29a47c | ||
|
|
d8ab92531f | ||
|
|
f6876d13fe | ||
|
|
5253009bad | ||
|
|
d74e1f4172 | ||
|
|
0ad81c85f2 | ||
|
|
ec8ea71dcc | ||
|
|
cd4be5c7b1 | ||
|
|
e2433024d7 | ||
|
|
ddc877ee3d | ||
|
|
60b03d49c6 | ||
|
|
853590376b | ||
|
|
cccd7b7cec | ||
|
|
1dfd8d8fb1 | ||
|
|
68f7570720 | ||
|
|
59e1709cff | ||
|
|
39b05192fe | ||
|
|
fa377132a8 | ||
|
|
b3e5cd79ed | ||
|
|
6f7b05649b | ||
|
|
4861a746b4 | ||
|
|
e75c163248 | ||
|
|
d8b501fff0 | ||
|
|
9de6c27c69 | ||
|
|
80811a6071 | ||
|
|
814cb09e44 | ||
|
|
050a4a3b78 | ||
|
|
ced06d1391 | ||
|
|
9876c98628 | ||
|
|
93c57ecfb6 | ||
|
|
666f1a0483 | ||
|
|
1b8abac84b | ||
|
|
3de2ffb1a1 | ||
|
|
419b8c84cb | ||
|
|
c9ef0b9046 | ||
|
|
7dc2ba9264 | ||
|
|
a2fc111d8c | ||
|
|
1b47daced3 | ||
|
|
ad2be40718 | ||
|
|
0f1d251c3a | ||
|
|
1bdb30ef01 | ||
|
|
dcc37a113f | ||
|
|
8d4d90197d |
28
.dockerignore
Normal file
28
.dockerignore
Normal file
@@ -0,0 +1,28 @@
|
||||
# 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
|
||||
helm
|
||||
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
|
||||
@@ -1 +1,4 @@
|
||||
/src/assets
|
||||
/src/assets
|
||||
/build
|
||||
/node_modules
|
||||
.github
|
||||
|
||||
34
.eslintrc
34
.eslintrc
@@ -1,16 +1,11 @@
|
||||
{
|
||||
"parser": "babel-eslint",
|
||||
"parserOptions": {
|
||||
"sourceType": "module",
|
||||
"allowImportExportEverywhere": false,
|
||||
"codeFrame": false
|
||||
},
|
||||
"extends": ["airbnb", "prettier"],
|
||||
"env": {
|
||||
"browser": true,
|
||||
"jest": true
|
||||
},
|
||||
"rules": {
|
||||
"extends": ["airbnb", "prettier"],
|
||||
"plugins": ["prettier"],
|
||||
"env": {
|
||||
"browser": true,
|
||||
"jest": true
|
||||
},
|
||||
"rules": {
|
||||
"max-len": ["error", {"code": 150}],
|
||||
"prefer-promise-reject-errors": ["off"],
|
||||
"react/jsx-filename-extension": ["off"],
|
||||
@@ -18,13 +13,22 @@
|
||||
"no-return-assign": ["off"],
|
||||
"react/jsx-props-no-spreading": ["off"],
|
||||
"react/destructuring-assignment": ["off"],
|
||||
"no-restricted-syntax": ["error", "ForInStatement", "LabeledStatement", "WithStatement"]
|
||||
"no-restricted-syntax": ["error", "ForInStatement", "LabeledStatement", "WithStatement"],
|
||||
"react/jsx-one-expression-per-line": "off",
|
||||
"react/jsx-wrap-multilines": "off",
|
||||
"react/jsx-curly-newline": "off"
|
||||
},
|
||||
"settings": {
|
||||
"import/resolver": {
|
||||
"node": {
|
||||
"paths": ["src"]
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
"parser": "babel-eslint",
|
||||
"parserOptions": {
|
||||
"sourceType": "module",
|
||||
"allowImportExportEverywhere": false,
|
||||
"codeFrame": false
|
||||
}
|
||||
}
|
||||
|
||||
68
.github/workflows/ci.yml
vendored
Normal file
68
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
name: Build Docker image
|
||||
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
branches:
|
||||
- main
|
||||
- 'release/*'
|
||||
tags:
|
||||
- 'v*'
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
runs-on: ubuntu-20.04
|
||||
env:
|
||||
DOCKER_REGISTRY_URL: tip-tip-wlan-cloud-ucentral.jfrog.io
|
||||
DOCKER_REGISTRY_USERNAME: ucentral
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Build Docker image
|
||||
run: docker build -t wlan-cloud-ucentralgw-ui:${{ github.sha }} .
|
||||
|
||||
- name: Tag Docker image
|
||||
run: |
|
||||
TAGS="${{ github.sha }}"
|
||||
|
||||
if [[ ${GITHUB_REF} == "refs/heads/"* ]]
|
||||
then
|
||||
CURRENT_TAG=$(echo ${GITHUB_REF#refs/heads/} | tr '/' '-')
|
||||
TAGS="$TAGS $CURRENT_TAG"
|
||||
else
|
||||
if [[ ${GITHUB_REF} == "refs/tags/"* ]]
|
||||
then
|
||||
CURRENT_TAG=$(echo ${GITHUB_REF#refs/tags/} | tr '/' '-')
|
||||
TAGS="$TAGS $CURRENT_TAG"
|
||||
else # PR build
|
||||
CURRENT_TAG=$(echo ${GITHUB_HEAD_REF#refs/heads/} | tr '/' '-')
|
||||
TAGS="$TAGS $CURRENT_TAG"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Result tags: $TAGS"
|
||||
|
||||
for tag in $TAGS; do
|
||||
docker tag wlan-cloud-ucentralgw-ui:${{ github.sha }} ${{ env.DOCKER_REGISTRY_URL }}/ucentralgw-ui:$tag
|
||||
done
|
||||
|
||||
- name: Log into Docker registry
|
||||
if: startsWith(github.ref, 'refs/tags/') || startsWith(github.ref, 'refs/pull/') || github.ref == 'refs/heads/main'
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
registry: ${{ env.DOCKER_REGISTRY_URL }}
|
||||
username: ${{ env.DOCKER_REGISTRY_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_REGISTRY_PASSWORD }}
|
||||
|
||||
- name: Push Docker images
|
||||
if: startsWith(github.ref, 'refs/tags/') || startsWith(github.ref, 'refs/pull/') || github.ref == 'refs/heads/main'
|
||||
run: |
|
||||
docker images | grep ${{ env.DOCKER_REGISTRY_URL }}/ucentralgw-ui | awk -F ' ' '{print $1":"$2}' | xargs -I {} docker push {}
|
||||
19
.github/workflows/cleanup.yml
vendored
Normal file
19
.github/workflows/cleanup.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
name: Clean up PR Docker images
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
types: [ closed ]
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
cleanup:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: |
|
||||
export PR_BRANCH_TAG=$(echo ${GITHUB_HEAD_REF#refs/heads/} | tr '/' '-')
|
||||
curl -uucentral:${{ secrets.DOCKER_REGISTRY_PASSWORD }} -X DELETE "https://tip.jfrog.io/artifactory/tip-wlan-cloud-ucentral/ucentralgw-ui/$PR_BRANCH_TAG"
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,4 +1,4 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
||||
4
.prettierignore
Normal file
4
.prettierignore
Normal file
@@ -0,0 +1,4 @@
|
||||
/src/assets
|
||||
build
|
||||
node_modules
|
||||
.github
|
||||
15
Dockerfile
Normal file
15
Dockerfile
Normal file
@@ -0,0 +1,15 @@
|
||||
FROM node:14-alpine3.11 AS build
|
||||
|
||||
COPY package.json package-lock.json /
|
||||
|
||||
RUN npm install
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN npm run build
|
||||
|
||||
FROM nginx:1.20.1-alpine AS runtime
|
||||
|
||||
COPY --from=build /build/ /usr/share/nginx/html/
|
||||
|
||||
COPY --from=build docker-entrypoint.d/40-generate-config.sh /docker-entrypoint.d/40-generate-config.sh
|
||||
34
README.md
34
README.md
@@ -4,33 +4,45 @@
|
||||
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
|
||||
Here are the instructions to run the solution on your machine for development purposes. You need to run these in the root folder of the project and also have npm installed on your machine. Please install `npm` for the platform you are using.
|
||||
You need to run these commands in the root folder of the project and also have npm installed on your machine.
|
||||
```
|
||||
git clone https://github.com/Telecominfraproject/wlan-cloud-ucentralgw-ui
|
||||
cd wlan-cloud-ucentralgw-ui
|
||||
npm install
|
||||
npm start
|
||||
```
|
||||
|
||||
Run these commands if you want to run the solution on your machine while also doing development on the [uCentral UI Library](https://github.com/Telecominfraproject/wlan-cloud-ucentral-ui-libs).
|
||||
```
|
||||
git clone https://github.com/Telecominfraproject/wlan-cloud-ucentralgw-ui
|
||||
git clone https://github.com/Telecominfraproject/wlan-cloud-ucentral-ui-libs
|
||||
cd wlan-cloud-ucentralgw-ui
|
||||
npm link ../wlan-cloud-ucentral-ui-libs // Add sudo at the start of this command if it fails because of permissions
|
||||
npm start
|
||||
```
|
||||
|
||||
### Production
|
||||
Here are the instructions to build the production veresion of the application. You need to run this in the root folder of the project and also have npm installed on your machine.
|
||||
You need to run this in the root folder of the project and also have npm installed on your machine.
|
||||
```
|
||||
git clone https://github.com/Telecominfraproject/wlan-cloud-ucentralgw-ui
|
||||
cd wlan-cloud-ucentralgw-ui
|
||||
npm install
|
||||
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.
|
||||
### Configuration
|
||||
You must change the `config.json` file in `public` directory to point to your uCentral Security Service URL (uCentralSec). You may also limit the ability for users to change the default uCentralSec. If you do not allow a uCentralSec change, the uCentralSec URL will not appear on the login screen.
|
||||
|
||||
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
|
||||
Here are the current default values:
|
||||
```
|
||||
{
|
||||
"DEFAULT_UCENTRALSEC_URL": "https://ucentral.dpaas.arilia.com:16001",
|
||||
"ALLOW_UCENTRALSEC_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`.
|
||||
|
||||
|
||||
25
babel.config.json
Normal file
25
babel.config.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"presets": [
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
"modules": false
|
||||
}
|
||||
],
|
||||
"@babel/preset-react"
|
||||
],
|
||||
"env": {
|
||||
"production": {
|
||||
"plugins": [
|
||||
"@babel/plugin-transform-react-inline-elements",
|
||||
"@babel/plugin-transform-react-constant-elements",
|
||||
[
|
||||
"transform-react-remove-prop-types",
|
||||
{
|
||||
"removeImport": true
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
12
config/paths.js
Normal file
12
config/paths.js
Normal file
@@ -0,0 +1,12 @@
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
// Source files
|
||||
src: path.resolve(__dirname, '../src'),
|
||||
|
||||
// Production build files
|
||||
build: path.resolve(__dirname, '../build'),
|
||||
|
||||
// Static files that get copied to build folder
|
||||
public: path.resolve(__dirname, '../public'),
|
||||
};
|
||||
75
config/webpack.common.js
Normal file
75
config/webpack.common.js
Normal file
@@ -0,0 +1,75 @@
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
/* eslint-disable prefer-template */
|
||||
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const path = require('path');
|
||||
const paths = require('./paths');
|
||||
|
||||
module.exports = {
|
||||
entry: [paths.src + '/index.js'],
|
||||
output: {
|
||||
path: paths.build,
|
||||
filename: '[name].bundle.js',
|
||||
publicPath: '/',
|
||||
},
|
||||
resolve: {
|
||||
modules: [path.resolve('./node_modules'), path.resolve('./src')],
|
||||
preferRelative: true,
|
||||
},
|
||||
plugins: [
|
||||
new MiniCssExtractPlugin({
|
||||
filename: 'styles/[name].[contenthash].css',
|
||||
chunkFilename: '[id].[contenthash].css',
|
||||
}),
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [
|
||||
{
|
||||
from: paths.src + '/assets',
|
||||
to: 'assets',
|
||||
globOptions: {
|
||||
ignore: ['*.DS_Store'],
|
||||
},
|
||||
},
|
||||
{
|
||||
from: paths.public + '/locales',
|
||||
to: 'locales',
|
||||
globOptions: {
|
||||
ignore: ['*.DS_Store'],
|
||||
},
|
||||
},
|
||||
{
|
||||
from: paths.public + '/config.json',
|
||||
to: 'config.json',
|
||||
},
|
||||
],
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
title: 'uCentralGW',
|
||||
favicon: paths.public + '/favicon.ico',
|
||||
template: paths.public + '/index.html',
|
||||
filename: 'index.html',
|
||||
}),
|
||||
new CleanWebpackPlugin(),
|
||||
],
|
||||
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(js|jsx)$/,
|
||||
exclude: /node_modules/,
|
||||
use: ['babel-loader'],
|
||||
},
|
||||
{
|
||||
test: /\.(css|scss)$/,
|
||||
use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'],
|
||||
},
|
||||
{
|
||||
test: /\.svg$/,
|
||||
use: ['@svgr/webpack'],
|
||||
},
|
||||
{ test: /\.(?:ico|gif|png|jpg|jpeg)$/i, type: 'asset/resource' },
|
||||
],
|
||||
},
|
||||
};
|
||||
54
config/webpack.dev.js
Normal file
54
config/webpack.dev.js
Normal file
@@ -0,0 +1,54 @@
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
/* eslint-disable prefer-template */
|
||||
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
|
||||
const { merge } = require('webpack-merge');
|
||||
const path = require('path');
|
||||
const paths = require('./paths');
|
||||
const common = require('./webpack.common');
|
||||
|
||||
module.exports = merge(common, {
|
||||
mode: 'development',
|
||||
|
||||
target: 'web',
|
||||
|
||||
devtool: 'inline-source-map',
|
||||
|
||||
devServer: {
|
||||
historyApiFallback: true,
|
||||
contentBase: paths.build,
|
||||
open: true,
|
||||
compress: false,
|
||||
hot: true,
|
||||
port: 3000,
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.[js]sx?$/,
|
||||
exclude: /node_modules/,
|
||||
use: [
|
||||
{
|
||||
loader: require.resolve('babel-loader'),
|
||||
options: {
|
||||
plugins: [require.resolve('react-refresh/babel')],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
resolve: {
|
||||
modules: [
|
||||
'node_modules',
|
||||
'src',
|
||||
path.resolve(__dirname, '../', 'node_modules', 'ucentral-libs', 'src'),
|
||||
],
|
||||
alias: {
|
||||
react: path.resolve(__dirname, '../', 'node_modules', 'react'),
|
||||
'react-router-dom': path.resolve('./node_modules/react-router-dom'),
|
||||
'ucentral-libs': path.resolve(__dirname, '../', 'node_modules', 'ucentral-libs', 'src'),
|
||||
graphlib: path.resolve(__dirname, '../', 'node_modules', 'graphlib'),
|
||||
},
|
||||
},
|
||||
plugins: [new ReactRefreshWebpackPlugin()],
|
||||
});
|
||||
86
config/webpack.prod.js
Normal file
86
config/webpack.prod.js
Normal file
@@ -0,0 +1,86 @@
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
/* eslint-disable prefer-template */
|
||||
const { merge } = require('webpack-merge');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
|
||||
const TerserPlugin = require('terser-webpack-plugin');
|
||||
const CompressionPlugin = require('compression-webpack-plugin');
|
||||
const path = require('path');
|
||||
const paths = require('./paths');
|
||||
const common = require('./webpack.common');
|
||||
|
||||
module.exports = merge(common, {
|
||||
mode: 'production',
|
||||
devtool: false,
|
||||
output: {
|
||||
path: paths.build,
|
||||
publicPath: '/',
|
||||
filename: 'js/[name].[contenthash].bundle.js',
|
||||
},
|
||||
plugins: [
|
||||
// new BundleAnalyzerPlugin(),
|
||||
new MiniCssExtractPlugin({
|
||||
filename: 'styles/[name].[contenthash].css',
|
||||
chunkFilename: '[contenthash].css',
|
||||
}),
|
||||
new CompressionPlugin({
|
||||
filename: '[path]/[name].gz[query]',
|
||||
algorithm: 'gzip',
|
||||
test: /\.js$|\.css$|\.html$|\.eot?.+$|\.ttf?.+$|\.woff?.+$|\.svg?.+$/,
|
||||
threshold: 10240,
|
||||
minRatio: 0.8,
|
||||
}),
|
||||
],
|
||||
module: {
|
||||
rules: [],
|
||||
},
|
||||
optimization: {
|
||||
minimize: true,
|
||||
minimizer: [
|
||||
'...',
|
||||
new TerserPlugin({
|
||||
terserOptions: {
|
||||
warnings: false,
|
||||
compress: {
|
||||
comparisons: false,
|
||||
},
|
||||
parse: {},
|
||||
mangle: true,
|
||||
output: {
|
||||
ascii_only: true,
|
||||
},
|
||||
},
|
||||
parallel: true,
|
||||
}),
|
||||
new CssMinimizerPlugin(),
|
||||
],
|
||||
nodeEnv: 'production',
|
||||
sideEffects: true,
|
||||
runtimeChunk: 'single',
|
||||
splitChunks: {
|
||||
chunks: 'all',
|
||||
maxInitialRequests: 10,
|
||||
minSize: 0,
|
||||
cacheGroups: {
|
||||
vendor: {
|
||||
test: /[\\/]node_modules[\\/]/,
|
||||
name(module) {
|
||||
const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];
|
||||
return `npm.${packageName.replace('@', '')}`;
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
resolve: {
|
||||
modules: [],
|
||||
alias: {
|
||||
graphlib: path.resolve(__dirname, '../', 'node_modules', 'graphlib'),
|
||||
},
|
||||
},
|
||||
performance: {
|
||||
hints: false,
|
||||
maxEntrypointSize: 512000,
|
||||
maxAssetSize: 512000,
|
||||
},
|
||||
});
|
||||
6
docker-entrypoint.d/40-generate-config.sh
Executable file
6
docker-entrypoint.d/40-generate-config.sh
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/bin/ash
|
||||
# Check if variables are set
|
||||
export DEFAULT_UCENTRALSEC_URL="${DEFAULT_UCENTRALSEC_URL:-https://ucentral.dpaas.arilia.com:16001}"
|
||||
export ALLOW_UCENTRALSEC_CHANGE="${ALLOW_UCENTRALSEC_CHANGE:-false}"
|
||||
|
||||
echo '{"DEFAULT_UCENTRALSEC_URL": "'$DEFAULT_UCENTRALSEC_URL'","ALLOW_UCENTRALSEC_CHANGE": '$ALLOW_UCENTRALSEC_CHANGE'}' > /usr/share/nginx/html/config.json
|
||||
1
helm/.gitignore
vendored
Normal file
1
helm/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.swp
|
||||
22
helm/.helmignore
Normal file
22
helm/.helmignore
Normal file
@@ -0,0 +1,22 @@
|
||||
# Patterns to ignore when building packages.
|
||||
# This supports shell glob matching, relative path matching, and
|
||||
# negation (prefixed with !). Only one pattern per line.
|
||||
.DS_Store
|
||||
# Common VCS dirs
|
||||
.git/
|
||||
.gitignore
|
||||
.bzr/
|
||||
.bzrignore
|
||||
.hg/
|
||||
.hgignore
|
||||
.svn/
|
||||
# Common backup files
|
||||
*.swp
|
||||
*.bak
|
||||
*.tmp
|
||||
*~
|
||||
# Various IDEs
|
||||
.project
|
||||
.idea/
|
||||
*.tmproj
|
||||
.vscode/
|
||||
5
helm/Chart.yaml
Normal file
5
helm/Chart.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
apiVersion: v1
|
||||
appVersion: "1.0"
|
||||
description: A Helm chart for Kubernetes
|
||||
name: ucentralgwui
|
||||
version: 0.1.0
|
||||
82
helm/README.md
Normal file
82
helm/README.md
Normal file
@@ -0,0 +1,82 @@
|
||||
# ucentralgwui
|
||||
|
||||
This Helm chart helps to deploy uCentralGW-UI to the Kubernetes clusters. It is mainly used in [assembly chart](https://github.com/Telecominfraproject/wlan-cloud-ucentral-deploy/tree/main/chart) as uCentralGW-UI requires other services as dependencies that are considered in that Helm chart. This chart is purposed to define deployment logic close to the application code itself and define default values that could be overriden during deployment.
|
||||
|
||||
|
||||
## TL;DR;
|
||||
|
||||
```bash
|
||||
$ helm install .
|
||||
```
|
||||
|
||||
## Introduction
|
||||
|
||||
This chart bootstraps an ucentralgwui on a [Kubernetes](http://kubernetes.io) cluster using the [Helm](https://helm.sh) package manager.
|
||||
|
||||
## Installing the Chart
|
||||
|
||||
Currently this chart is not assembled in charts archives, so [helm-git](https://github.com/aslafy-z/helm-git) is required for remote the installation
|
||||
|
||||
To install the chart with the release name `my-release`:
|
||||
|
||||
```bash
|
||||
$ helm install --name my-release git+https://github.com/Telecominfraproject/wlan-cloud-ucentralgw-ui@helm?ref=main
|
||||
```
|
||||
|
||||
The command deploys ucentralgwui on the Kubernetes cluster in the default configuration. The [configuration](#configuration) section lists the parameters that can be configured during installation.
|
||||
|
||||
> **Tip**: List all releases using `helm list`
|
||||
|
||||
## Uninstalling the Chart
|
||||
|
||||
To uninstall/delete the `my-release` deployment:
|
||||
|
||||
```bash
|
||||
$ helm delete my-release
|
||||
```
|
||||
|
||||
The command removes all the Kubernetes components associated with the chart and deletes the release.
|
||||
|
||||
## Configuration
|
||||
|
||||
The following table lists the configurable parameters of the chart and their default values. If Default value is not listed in the table, please refer to the [Values](values.yaml) files for details.
|
||||
|
||||
| Parameter | Type | Description | Default |
|
||||
|-----------|------|-------------|---------|
|
||||
| replicaCount | number | Amount of replicas to be deployed | `1` |
|
||||
| nameOverride | string | Override to be used for application deployment | |
|
||||
| fullnameOverride | string | Override to be used for application deployment (has priority over nameOverride) | |
|
||||
| images.ucentralgwui.repository | string | Docker image repository | |
|
||||
| images.ucentralgwui.tag | string | Docker image tag | `'master'` |
|
||||
| images.ucentralgwui.pullPolicy | string | Docker image pull policy | `'Always'` |
|
||||
| services.ucentralgwui.type | string | uCentralGW-UI service type | `'ClusterIP'` |
|
||||
| services.ucentralgwui.ports.http.servicePort | number | Websocket endpoint port to be exposed on service | `80` |
|
||||
| services.ucentralgwui.ports.http.targetPort | number | Websocket endpoint port to be targeted by service | `80` |
|
||||
| services.ucentralgwui.ports.http.protocol | string | Websocket endpoint protocol | `'TCP'` |
|
||||
| checks.ucentralgwui.liveness.httpGet.path | string | Liveness check path to be used | `'/'` |
|
||||
| checks.ucentralgwui.liveness.httpGet.port | number | Liveness check port to be used (should be pointint to ALB endpoint) | `http` |
|
||||
| checks.ucentralgwui.readiness.httpGet.path | string | Readiness check path to be used | `'/'` |
|
||||
| checks.ucentralgwui.readiness.httpGet.port | number | Readiness check port to be used | `http` |
|
||||
| ingresses.default.enabled | boolean | Defines if uCentralGW-UI should be exposed via Ingress controller | `False` |
|
||||
| ingresses.default.hosts | array | List of hosts for exposed uCentralGW-UI | |
|
||||
| ingresses.default.paths | array | List of paths to be exposed for uCentralGW-UI | |
|
||||
| public_env_variables | hash | Defines list of environment variables to be passed to uCentralGW-UI (required for application configuration) | |
|
||||
|
||||
|
||||
Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example,
|
||||
|
||||
```bash
|
||||
$ helm install --name my-release \
|
||||
--set replicaCount=1 \
|
||||
.
|
||||
```
|
||||
|
||||
The above command sets that only 1 instance of your app should be running
|
||||
|
||||
Alternatively, a YAML file that specifies the values for the parameters can be provided while installing the chart. For example,
|
||||
|
||||
```bash
|
||||
$ helm install --name my-release -f values.yaml .
|
||||
```
|
||||
|
||||
> **Tip**: You can use the default [values.yaml](values.yaml) as a base for customization.
|
||||
32
helm/templates/_helpers.tpl
Normal file
32
helm/templates/_helpers.tpl
Normal file
@@ -0,0 +1,32 @@
|
||||
{{/* vim: set filetype=mustache: */}}
|
||||
{{/*
|
||||
Expand the name of the chart.
|
||||
*/}}
|
||||
{{- define "ucentralgwui.name" -}}
|
||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Create a default fully qualified app name.
|
||||
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||
If release name contains chart name it will be used as a full name.
|
||||
*/}}
|
||||
{{- define "ucentralgwui.fullname" -}}
|
||||
{{- if .Values.fullnameOverride -}}
|
||||
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
|
||||
{{- else -}}
|
||||
{{- $name := default .Chart.Name .Values.nameOverride -}}
|
||||
{{- if contains $name .Release.Name -}}
|
||||
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
|
||||
{{- else -}}
|
||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Create chart name and version as used by the chart label.
|
||||
*/}}
|
||||
{{- define "ucentralgwui.chart" -}}
|
||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
86
helm/templates/deployment.yaml
Normal file
86
helm/templates/deployment.yaml
Normal file
@@ -0,0 +1,86 @@
|
||||
{{- $root := . -}}
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "ucentralgwui.fullname" . }}
|
||||
labels:
|
||||
app.kubernetes.io/name: {{ include "ucentralgwui.name" . }}
|
||||
helm.sh/chart: {{ include "ucentralgwui.chart" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
spec:
|
||||
replicas: {{ .Values.replicaCount }}
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: {{ include "ucentralgwui.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
{{- with .Values.services.ucentralgwui.labels }}
|
||||
{{- toYaml . | nindent 6 }}
|
||||
{{- end }}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: {{ include "ucentralgwui.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
{{- with .Values.services.ucentralgwui.labels }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
|
||||
containers:
|
||||
|
||||
- name: ucentralgwui
|
||||
image: "{{ .Values.images.ucentralgwui.repository }}:{{ .Values.images.ucentralgwui.tag }}"
|
||||
imagePullPolicy: {{ .Values.images.ucentralgwui.pullPolicy }}
|
||||
|
||||
env:
|
||||
- name: KUBERNETES_DEPLOYED
|
||||
value: "{{ now }}"
|
||||
{{- range $key, $value := .Values.public_env_variables }}
|
||||
- name: {{ $key }}
|
||||
value: {{ $value | quote }}
|
||||
{{- end }}
|
||||
|
||||
ports:
|
||||
{{- range $key, $value := .Values.services.ucentralgwui.ports }}
|
||||
- name: {{ $key }}
|
||||
containerPort: {{ $value.targetPort }}
|
||||
protocol: {{ $value.protocol }}
|
||||
{{- end }}
|
||||
|
||||
{{- if .Values.checks.ucentralgwui.liveness }}
|
||||
livenessProbe:
|
||||
{{- toYaml .Values.checks.ucentralgwui.liveness | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- if .Values.checks.ucentralgwui.readiness }}
|
||||
readinessProbe:
|
||||
{{- toYaml .Values.checks.ucentralgwui.readiness | nindent 12 }}
|
||||
{{- end }}
|
||||
|
||||
{{- with .Values.resources }}
|
||||
resources:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
|
||||
imagePullSecrets:
|
||||
{{- range $image, $imageValue := .Values.images }}
|
||||
{{- if $imageValue.regcred }}
|
||||
- name: {{ include "ucentralgwui.fullname" $root }}-{{ $image }}-regcred
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{- with .Values.nodeSelector }}
|
||||
nodeSelector:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
|
||||
{{- with .Values.affinity }}
|
||||
affinity:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
|
||||
{{- with .Values.tolerations }}
|
||||
tolerations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
47
helm/templates/ingress.yaml
Normal file
47
helm/templates/ingress.yaml
Normal file
@@ -0,0 +1,47 @@
|
||||
{{- $root := . -}}
|
||||
{{- range $ingress, $ingressValue := .Values.ingresses }}
|
||||
{{- if $ingressValue.enabled }}
|
||||
---
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{ include "ucentralgwui.fullname" $root }}-{{ $ingress }}
|
||||
labels:
|
||||
app.kubernetes.io/name: {{ include "ucentralgwui.name" $root }}
|
||||
helm.sh/chart: {{ include "ucentralgwui.chart" $root }}
|
||||
app.kubernetes.io/instance: {{ $root.Release.Name }}
|
||||
app.kubernetes.io/managed-by: {{ $root.Release.Service }}
|
||||
{{- with $ingressValue.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
|
||||
spec:
|
||||
|
||||
{{- if $ingressValue.tls }}
|
||||
tls:
|
||||
{{- range $ingressValue.tls }}
|
||||
- hosts:
|
||||
{{- range .hosts }}
|
||||
- {{ . | quote }}
|
||||
{{- end }}
|
||||
secretName: {{ tpl .secretName $root }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
rules:
|
||||
{{- range $ingressValue.hosts }}
|
||||
- host: {{ . | quote }}
|
||||
http:
|
||||
paths:
|
||||
{{- range $ingressValue.paths }}
|
||||
- path: {{ .path }}
|
||||
backend:
|
||||
serviceName: {{ include "ucentralgwui.fullname" $root }}-{{ .serviceName }}
|
||||
servicePort: {{ .servicePort }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{- end }}
|
||||
|
||||
{{- end }}
|
||||
21
helm/templates/secret-regcred.yaml
Normal file
21
helm/templates/secret-regcred.yaml
Normal file
@@ -0,0 +1,21 @@
|
||||
{{- define "imagePullSecret" }}
|
||||
{{- printf "{\"auths\": {\"%s\": {\"auth\": \"%s\"}}}" .registry (printf "%s:%s" .username .password | b64enc) | b64enc }}
|
||||
{{- end }}
|
||||
{{- $root := . -}}
|
||||
{{- range $image, $imageValue := .Values.images }}
|
||||
{{- if $imageValue.regcred }}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
type: kubernetes.io/dockerconfigjson
|
||||
metadata:
|
||||
labels:
|
||||
app.kuberentes.io/name: {{ include "ucentralgwui.name" $root }}
|
||||
helm.sh/chart: {{ include "ucentralgwui.chart" $root }}
|
||||
app.kubernetes.io/instance: {{ $root.Release.Name }}
|
||||
app.kubernetes.io/managed-by: {{ $root.Release.Service }}
|
||||
name: {{ include "ucentralgwui.fullname" $root }}-{{ $image }}-regcred
|
||||
data:
|
||||
.dockerconfigjson: {{ template "imagePullSecret" $imageValue.regcred }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
48
helm/templates/service.yaml
Normal file
48
helm/templates/service.yaml
Normal file
@@ -0,0 +1,48 @@
|
||||
{{- $root := . -}}
|
||||
{{- range $service, $serviceValue := .Values.services }}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "ucentralgwui.fullname" $root }}-{{ $service }}
|
||||
{{- with $serviceValue.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
labels:
|
||||
app.kubernetes.io/name: {{ include "ucentralgwui.name" $root }}
|
||||
helm.sh/chart: {{ include "ucentralgwui.chart" $root }}
|
||||
app.kubernetes.io/instance: {{ $root.Release.Name }}
|
||||
app.kubernetes.io/managed-by: {{ $root.Release.Service }}
|
||||
|
||||
{{- with $serviceValue.labels }}
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
|
||||
{{- if $serviceValue.serviceMonitor }}
|
||||
|
||||
{{- range $selector, $selectorValue := $serviceValue.serviceMonitor.serviceSelector }}
|
||||
{{ $selector }}: {{ tpl $selectorValue $root }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
spec:
|
||||
type: {{ $serviceValue.type }}
|
||||
ports:
|
||||
|
||||
{{- range $service_service, $service_value := $serviceValue.ports }}
|
||||
- name: {{ $service_service }}
|
||||
targetPort: {{ $service_value.targetPort }}
|
||||
protocol: {{ $service_value.protocol }}
|
||||
port: {{ $service_value.servicePort }}
|
||||
{{- if and (eq "NodePort" $serviceValue.type) $service_value.nodePort }}
|
||||
nodePort: {{ $service_value.nodePort }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
selector:
|
||||
app.kubernetes.io/name: {{ include "ucentralgwui.name" $root }}
|
||||
app.kubernetes.io/instance: {{ $root.Release.Name }}
|
||||
{{- with $serviceValue.labels }}
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
|
||||
{{- end }}
|
||||
75
helm/values.yaml
Normal file
75
helm/values.yaml
Normal file
@@ -0,0 +1,75 @@
|
||||
# System
|
||||
replicaCount: 1
|
||||
|
||||
nameOverride: ""
|
||||
fullnameOverride: ""
|
||||
|
||||
images:
|
||||
ucentralgwui:
|
||||
repository: tip-tip-wlan-cloud-ucentral.jfrog.io/ucentralgw-ui
|
||||
tag: v2.1.0-RC1
|
||||
pullPolicy: Always
|
||||
|
||||
services:
|
||||
ucentralgwui:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
http:
|
||||
servicePort: 80
|
||||
targetPort: 80
|
||||
protocol: TCP
|
||||
|
||||
checks:
|
||||
ucentralgwui:
|
||||
liveness:
|
||||
httpGet:
|
||||
path: /
|
||||
port: http
|
||||
readiness:
|
||||
httpGet:
|
||||
path: /
|
||||
port: http
|
||||
|
||||
ingresses:
|
||||
default:
|
||||
enabled: false
|
||||
annotations: {}
|
||||
# kubernetes.io/ingress.class: nginx
|
||||
# kubernetes.io/tls-acme: "true"
|
||||
# tls:
|
||||
# - secretName: '{{ include "ucentralgwui.fullname" . }}-default-tls' # template may be used
|
||||
# cert: |
|
||||
# CERT_HERE_IN_PEM
|
||||
# key: |
|
||||
# KEY_HERE_IN_PEM
|
||||
# hosts:
|
||||
# - chart-example.local
|
||||
hosts:
|
||||
- chart-example.local
|
||||
paths:
|
||||
- path: /
|
||||
serviceName: ucentralgwui
|
||||
servicePort: http
|
||||
|
||||
resources: {}
|
||||
# We usually recommend not to specify default resources and to leave this as a conscious
|
||||
# choice for the user. This also increases chances charts run on environments with little
|
||||
# resources, such as Minikube. If you do want to specify resources, uncomment the following
|
||||
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
|
||||
# requests:
|
||||
# cpu: 100m
|
||||
# memory: 128Mi
|
||||
# limits:
|
||||
# cpu: 100m
|
||||
# memory: 128Mi
|
||||
|
||||
nodeSelector: {}
|
||||
|
||||
tolerations: []
|
||||
|
||||
affinity: {}
|
||||
|
||||
# Application
|
||||
public_env_variables:
|
||||
DEFAULT_UCENTRALSEC_URL: https://ucentral.dpaas.arilia.com:16001
|
||||
ALLOW_UCENTRALSEC_CHANGE: false
|
||||
@@ -4,6 +4,6 @@
|
||||
"paths": {
|
||||
"*": ["*"]
|
||||
}
|
||||
},
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
|
||||
32882
package-lock.json
generated
32882
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
91
package.json
91
package.json
@@ -1,23 +1,16 @@
|
||||
{
|
||||
"name": "ucentral-client",
|
||||
"version": "0.9.0",
|
||||
"private": true,
|
||||
"version": "2.1.0",
|
||||
"dependencies": {
|
||||
"@coreui/coreui": "^3.4.0",
|
||||
"@coreui/icons": "^2.0.1",
|
||||
"@coreui/icons-react": "^1.1.0",
|
||||
"@coreui/react": "^3.4.6",
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.35",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.15.3",
|
||||
"@fortawesome/react-fontawesome": "^0.1.14",
|
||||
"@testing-library/jest-dom": "^5.14.1",
|
||||
"@testing-library/react": "^11.2.7",
|
||||
"@testing-library/user-event": "^13.1.9",
|
||||
"@coreui/react-chartjs": "^1.1.0",
|
||||
"apexcharts": "^3.27.1",
|
||||
"axios": "^0.21.1",
|
||||
"axios-retry": "^3.1.9",
|
||||
"http": "^0.0.1-security",
|
||||
"https": "^1.0.0",
|
||||
"dagre": "^0.8.5",
|
||||
"i18next": "^20.3.1",
|
||||
"i18next-browser-languagedetector": "^6.1.2",
|
||||
"i18next-http-backend": "^1.2.6",
|
||||
@@ -25,21 +18,23 @@
|
||||
"react": "^17.0.2",
|
||||
"react-apexcharts": "^1.3.9",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-flow-renderer": "^9.6.6",
|
||||
"react-i18next": "^11.11.0",
|
||||
"react-paginate": "^7.1.3",
|
||||
"react-redux": "^7.2.4",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-scripts": "^4.0.3",
|
||||
"react-select": "^4.3.1",
|
||||
"react-tooltip": "^4.2.21",
|
||||
"react-widgets": "^5.1.1",
|
||||
"redux": "^4.1.0",
|
||||
"sass": "^1.35.1",
|
||||
"ucentral-libs": "^0.8.82",
|
||||
"uuid": "^8.3.2"
|
||||
},
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
"start": "webpack serve --config config/webpack.dev.js",
|
||||
"build": "webpack --config config/webpack.prod.js",
|
||||
"format": "prettier --write 'src/**/*.js'",
|
||||
"eslint-fix": "eslint --fix 'src/**/*.js'"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "react-app"
|
||||
@@ -50,12 +45,56 @@
|
||||
}
|
||||
},
|
||||
"lint-staged": {
|
||||
"src/**/*.{js,jsx}": [
|
||||
"*.{js,jsx}": [
|
||||
"eslint",
|
||||
"pretty-quick — staged",
|
||||
"git add"
|
||||
"prettier --write"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.14.6",
|
||||
"@babel/plugin-proposal-class-properties": "^7.14.5",
|
||||
"@babel/plugin-transform-runtime": "^7.14.5",
|
||||
"@babel/polyfill": "^7.12.1",
|
||||
"@babel/preset-env": "^7.14.7",
|
||||
"@babel/preset-react": "^7.14.5",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.4.3",
|
||||
"@svgr/webpack": "^5.5.0",
|
||||
"autoprefixer": "^10.2.6",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-loader": "^8.2.2",
|
||||
"clean-webpack-plugin": "^3.0.0",
|
||||
"compression-webpack-plugin": "^8.0.1",
|
||||
"copy-webpack-plugin": "^7.0.0",
|
||||
"css-loader": "^5.2.6",
|
||||
"css-minimizer-webpack-plugin": "^2.0.0",
|
||||
"dotenv-webpack": "^6.0.4",
|
||||
"eslint": "^7.29.0",
|
||||
"eslint-config-airbnb": "^18.2.1",
|
||||
"eslint-config-prettier": "^7.2.0",
|
||||
"eslint-import-resolver-alias": "^1.1.2",
|
||||
"eslint-loader": "^4.0.2",
|
||||
"eslint-plugin-babel": "^5.3.1",
|
||||
"eslint-plugin-import": "^2.23.4",
|
||||
"eslint-plugin-prettier": "^3.4.0",
|
||||
"eslint-plugin-react": "^7.24.0",
|
||||
"eslint-plugin-react-hooks": "^4.2.0",
|
||||
"html-webpack-plugin": "^5.3.2",
|
||||
"husky": "^4.3.8",
|
||||
"lint-staged": "^11.0.0",
|
||||
"mini-css-extract-plugin": "^1.6.1",
|
||||
"node-sass": "^5.0.0",
|
||||
"path": "^0.12.7",
|
||||
"prettier": "^2.3.2",
|
||||
"react-refresh": "^0.9.0",
|
||||
"sass-loader": "^11.1.1",
|
||||
"style-loader": "^2.0.0",
|
||||
"terser-webpack-plugin": "^5.1.4",
|
||||
"webpack": "^5.40.0",
|
||||
"webpack-bundle-analyzer": "^4.4.2",
|
||||
"webpack-cli": "^4.7.2",
|
||||
"webpack-dev-server": "^3.11.2",
|
||||
"webpack-merge": "^5.8.0"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
@@ -67,17 +106,5 @@
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-eslint": "^10.1.0",
|
||||
"eslint": "^7.28.0",
|
||||
"eslint-config-airbnb": "^18.2.1",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-import": "^2.23.4",
|
||||
"eslint-plugin-react": "^7.24.0",
|
||||
"husky": "^6.0.0",
|
||||
"lint-staged": "^11.0.0",
|
||||
"prettier": "^2.3.1",
|
||||
"pretty-quick": "^3.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
4
public/config.json
Normal file
4
public/config.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"DEFAULT_UCENTRALSEC_URL": "https://ucentral.dpaas.arilia.com:16001",
|
||||
"ALLOW_UCENTRALSEC_CHANGE": false
|
||||
}
|
||||
@@ -2,42 +2,13 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<link rel="icon" href="favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>uCentralGW</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,82 +1,157 @@
|
||||
{
|
||||
"actions": {
|
||||
"blink": "Blinken",
|
||||
"actions": "Aktionen",
|
||||
"blink": "LEDs Blinken",
|
||||
"configure": "Konfigurieren",
|
||||
"connect": "Verbinden",
|
||||
"connecting": "Verbindung wird hergestellt ...",
|
||||
"factory_reset": "Werkseinstellungen zurückgesetzt",
|
||||
"firmware_upgrade": "Firmware-Aktualisierung",
|
||||
"reboot": "Starten Sie neu",
|
||||
"title": "Geräteaktionen",
|
||||
"trace": "Spur",
|
||||
"wifi_scan": "WLAN-Scan"
|
||||
"connect": "Konsole öffnen",
|
||||
"connecting": "Die Verbindung wird hergestellt ...",
|
||||
"factory_reset": "Auf Werkseinstellungen zurückgesetzt",
|
||||
"firmware_upgrade": "Firmware Aktualisierung",
|
||||
"reboot": "Gerät neustarten",
|
||||
"title": "Geräte Administrations",
|
||||
"trace": "Tcpdump starten",
|
||||
"wifi_scan": "Wi-Fi Scan"
|
||||
},
|
||||
"blink": {
|
||||
"blink": "Blinken",
|
||||
"device_leds": "Geräte-LEDs",
|
||||
"blink": "LEDs Blinken",
|
||||
"device_leds": "LEDs",
|
||||
"execute_now": "Möchten Sie dieses Muster jetzt einstellen?",
|
||||
"pattern": "Wählen Sie das Muster, das Sie verwenden möchten:",
|
||||
"when_blink_leds": "Wann möchten Sie die Geräte-LEDs blinken lassen?"
|
||||
"set_leds": "LEDs einstellen",
|
||||
"when_blink_leds": "Wann möchten Sie die LEDs blinken lassen?"
|
||||
},
|
||||
"commands": {
|
||||
"error": "Fehler beim Senden des Befehls!",
|
||||
"success": "Befehl erfolgreich übermittelt",
|
||||
"title": "Gerätebefehle"
|
||||
"event_queue": "Ereigniswarteschlange",
|
||||
"success": "Befehl wurde erfolgreich übermittelt",
|
||||
"title": "Gerätebefehle",
|
||||
"unable_queue": "Anfrage für Ereigniswarteschlange kann nicht abgeschlossen werden"
|
||||
},
|
||||
"common": {
|
||||
"access_policy": "Zugangsrichtlinien",
|
||||
"add": "Hinzufügen",
|
||||
"adding_ellipsis": "Hinzufügen ...",
|
||||
"are_you_sure": "Bist du sicher?",
|
||||
"cancel": "Stornieren",
|
||||
"back_to_login": "Zurück zur Anmeldung",
|
||||
"cancel": "Abbrechen",
|
||||
"certificate": "Zertifikat",
|
||||
"clear": "klar",
|
||||
"certificates": "Zertifikate",
|
||||
"clear": "Löschen",
|
||||
"close": "Schließen",
|
||||
"command": "Befehl",
|
||||
"commands": "Befehle",
|
||||
"commands_executed": "Ausgeführte Befehle",
|
||||
"compatible": "kompatibel",
|
||||
"completed": "Abgeschlossen",
|
||||
"config_id": "Konfig. Ich würde",
|
||||
"config_id": "Konfigurations ID",
|
||||
"confirm": "Bestätigen",
|
||||
"connected": "In Verbindung gebracht",
|
||||
"connected": "Verbindung wurde hergestellt",
|
||||
"copied": "kopiert!",
|
||||
"copy_to_clipboard": "In die Zwischenablage kopieren",
|
||||
"create": "Erstellen",
|
||||
"created": "Erstellt",
|
||||
"created_by": "Erstellt von",
|
||||
"current": "Aktuell",
|
||||
"custom_date": "Benutzerdefiniertes Datum",
|
||||
"dashboard": "Instrumententafel",
|
||||
"date": "Datum",
|
||||
"day": "tag",
|
||||
"days": "tage",
|
||||
"delete": "Löschen",
|
||||
"delete_device": "Gerät löschen",
|
||||
"details": "Einzelheiten",
|
||||
"device": "Gerät #{{serialNumber}}",
|
||||
"device_dashboard": "Geräte-Dashboard",
|
||||
"device_delete": "Gerät Nr.{{serialNumber}}löschen",
|
||||
"device_deleted": "Gerät erfolgreich gelöscht",
|
||||
"device_health": "Gerätezustand",
|
||||
"device_list": "Liste der Geräte",
|
||||
"device_page": "Geräteseite",
|
||||
"device_page": "Aussicht",
|
||||
"device_status": "Gerätestatus",
|
||||
"devices": "Geräte",
|
||||
"devices_using_latest": "Geräte mit der neuesten Firmware",
|
||||
"devices_using_unknown": "Geräte mit unbekannter Firmware",
|
||||
"dismiss": "entlassen",
|
||||
"do_now": "Jetzt tun!",
|
||||
"do_now": "Sofort",
|
||||
"download": "Herunterladen",
|
||||
"duration": "Dauer",
|
||||
"error": "Error",
|
||||
"executed": "Hingerichtet",
|
||||
"edit": "Bearbeiten",
|
||||
"edit_user": "Bearbeiten",
|
||||
"email_address": "E-Mail-Addresse",
|
||||
"endpoint": "Endpunkt",
|
||||
"endpoints": "Endpunkte",
|
||||
"error": "Fehler",
|
||||
"execute_now": "Möchten Sie diesen Befehl jetzt ausführen?",
|
||||
"executed": "Ausgeführt",
|
||||
"exit": "Ausgang",
|
||||
"firmware": "Firmware",
|
||||
"firmware_dashboard": "Firmware-Dashboard",
|
||||
"firmware_installed": "Firmware installiert",
|
||||
"forgot_password": "Haben Sie Ihr Passwort vergessen?",
|
||||
"forgot_password_title": "Passwort vergessen",
|
||||
"from": "Von",
|
||||
"id": "Ich würde",
|
||||
"general_error": "API-Fehler, wenden Sie sich bitte an Ihren Administrator",
|
||||
"hide": "verbergen",
|
||||
"hour": "stunde",
|
||||
"hours": "std",
|
||||
"id": "ID",
|
||||
"ip_address": "IP Adresse",
|
||||
"later_tonight": "Heute Abend später",
|
||||
"last_dashboard_refresh": "Letzte Dashboard-Aktualisierung",
|
||||
"later_tonight": "Später am Abend",
|
||||
"latest": "Neueste",
|
||||
"list": "Liste",
|
||||
"loading_ellipsis": "Wird geladen...",
|
||||
"loading_more_ellipsis": "Mehr laden ...",
|
||||
"logout": "Ausloggen",
|
||||
"mac": "MAC-Adresse",
|
||||
"manufacturer": "Hersteller",
|
||||
"na": "N / A",
|
||||
"memory_used": "Verwendeter Speicher",
|
||||
"minute": "Minute",
|
||||
"minutes": "protokoll",
|
||||
"na": "(unbekannt)",
|
||||
"need_date": "Du brauchst ein Datum...",
|
||||
"no": "Nein",
|
||||
"no_devices_found": "Keine Geräte gefunden",
|
||||
"no_items": "Keine Gegenstände",
|
||||
"not_connected": "Nicht verbunden",
|
||||
"off": "aus",
|
||||
"on": "auf",
|
||||
"of_connected": "% der Geräte",
|
||||
"off": "Aus",
|
||||
"on": "An",
|
||||
"optional": "Wahlweise",
|
||||
"overall_health": "Allgemeine Gesundheit",
|
||||
"password_policy": "Kennwortrichtlinie",
|
||||
"recorded": "Verzeichnet",
|
||||
"refresh": "Aktualisierung",
|
||||
"refresh_device": "Gerät aktualisieren",
|
||||
"required": "Erforderlich",
|
||||
"result": "Ergebnis",
|
||||
"save": "Sparen",
|
||||
"saved": "Gerettet!",
|
||||
"saving": "Speichern ...",
|
||||
"schedule": "Zeitplan",
|
||||
"serial_number": "Ordnungsnummer",
|
||||
"search": "Geräte suchen",
|
||||
"second": "zweite",
|
||||
"seconds": "sekunden",
|
||||
"seconds_elapsed": "Sekunden verstrichen",
|
||||
"serial_number": "Seriennummer",
|
||||
"show_all": "Zeige alles",
|
||||
"start": "Start",
|
||||
"submit": "einreichen",
|
||||
"submit": "Absenden",
|
||||
"submitted": "Eingereicht",
|
||||
"success": "Erfolg",
|
||||
"system": "System",
|
||||
"table": "Tabelle",
|
||||
"timestamp": "Zeit",
|
||||
"to": "zu",
|
||||
"type": "Art",
|
||||
"unable_to_connect": "Keine Verbindung zum Gerät möglich",
|
||||
"unable_to_delete": "Löschen nicht möglich",
|
||||
"unknown": "unbekannte",
|
||||
"up_to_date": "Aktuelle Geräte",
|
||||
"uptimes": "Betriebszeiten",
|
||||
"uuid": "UUID",
|
||||
"view_more": "Mehr sehen",
|
||||
"vendors": "Anbieter",
|
||||
"view_more": "Mehr anzeigen",
|
||||
"yes": "Ja"
|
||||
},
|
||||
"configuration": {
|
||||
@@ -86,77 +161,257 @@
|
||||
"last_configuration_change": "Letzte Konfigurationsänderung",
|
||||
"last_configuration_download": "Letzter Konfigurations-Download",
|
||||
"location": "Ort",
|
||||
"note": "Hinweis",
|
||||
"notes": "Anmerkungen",
|
||||
"owner": "Inhaber",
|
||||
"title": "Gerätekonfiguration",
|
||||
"type": "Gerätetyp",
|
||||
"view_json": "Rohes JSON anzeigen"
|
||||
"uuid": "Konfigurations-ID",
|
||||
"view_json": "Rohe Konfiguration anzeigen"
|
||||
},
|
||||
"configure": {
|
||||
"choose_file": "Sie müssen eine gültige .json-Datei auswählen:",
|
||||
"enter_new": "Geben Sie die JSON für die neue Gerätekonfiguration ein:",
|
||||
"placeholder": "JSON konfigurieren",
|
||||
"placeholder": "Konfiguration",
|
||||
"title": "Gerät konfigurieren",
|
||||
"valid_json": "Sie müssen gültiges JSON eingeben"
|
||||
"valid_json": "Sie müssen ein gültiges JSON eingeben"
|
||||
},
|
||||
"delete_command": {
|
||||
"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",
|
||||
"log": "Protokoll",
|
||||
"severity": "Wichtigkeit",
|
||||
"title": "Geräteprotokolle"
|
||||
},
|
||||
"entity": {
|
||||
"add_child": "Untergeordnete Entität zu {{entityName}}hinzufügen",
|
||||
"add_failure": "Fehler, der Server hat zurückgegeben: {{error}}",
|
||||
"add_root": "Root-Entität hinzufügen",
|
||||
"add_success": "Entität erfolgreich erstellt!",
|
||||
"cannot_delete": "Entitäten mit untergeordneten Elementen können nicht gelöscht werden. Löschen Sie die untergeordneten Elemente dieser Entität, um sie löschen zu können.",
|
||||
"delete_success": "Entität erfolgreich gelöscht",
|
||||
"delete_warning": "Achtung: Dieser Vorgang kann nicht rückgängig gemacht werden",
|
||||
"edit_failure": "Aktualisierung fehlgeschlagen : {{error}}",
|
||||
"entities": "Entitäten",
|
||||
"entity": "Entität",
|
||||
"only_unassigned": "Nur nicht zugewiesen",
|
||||
"valid_serial": "Muss eine gültige Seriennummer sein (12 HEX-Zeichen)"
|
||||
},
|
||||
"factory_reset": {
|
||||
"redirector": "Weiterleitung beibehalten:",
|
||||
"redirector": "Gatewaykonfiguration beibehalten:",
|
||||
"reset": "Zurücksetzen",
|
||||
"resetting": "Zurücksetzen...",
|
||||
"title": "Gerät auf Werkseinstellungen zurücksetzen",
|
||||
"warning": "Achtung: Nach dem Absenden kann dies nicht rückgängig gemacht werden"
|
||||
},
|
||||
"firmware": {
|
||||
"average_age": "Durchschnittliches Firmware-Alter",
|
||||
"choose_custom": "Wählen",
|
||||
"details_title": "Bild #{{image}} Details",
|
||||
"device_type": "Gerätetyp",
|
||||
"device_types": "Gerätetypen",
|
||||
"downloads": "Downloads",
|
||||
"error_fetching_latest": "Fehler beim Abrufen der neuesten Firmware",
|
||||
"from_release": "Von",
|
||||
"history_title": "Geschichte",
|
||||
"image": "Bild",
|
||||
"image_date": "Bilddatum",
|
||||
"installed_firmware": "Installierte Firmware",
|
||||
"latest_version_installed": "Neueste Version installiert Version",
|
||||
"newer_firmware_available": "Neuere Versionen verfügbar",
|
||||
"reinstall_latest": "Neu installieren",
|
||||
"revision": "Revision",
|
||||
"show_dev": "Dev-Releases anzeigen",
|
||||
"size": "Größe",
|
||||
"status": "Firmware-Status",
|
||||
"title": "Firmware",
|
||||
"to_release": "Zu",
|
||||
"unknown_firmware_status": "Unbekannter Firmware-Status",
|
||||
"upgrade": "Aktualisierung",
|
||||
"upgrade_command_submitted": "Upgrade-Befehl erfolgreich gesendet",
|
||||
"upgrade_to_latest": "Neueste",
|
||||
"upgrade_to_version": "Upgrade auf diese Revision",
|
||||
"upgrading": "Upgrade durchführen..."
|
||||
},
|
||||
"footer": {
|
||||
"coreui_for_react": "CoreUI für React",
|
||||
"powered_by": "Unterstützt von",
|
||||
"version": "Ausführung"
|
||||
"version": "Version"
|
||||
},
|
||||
"health": {
|
||||
"sanity": "Gesundheit",
|
||||
"title": "Gerätezustand"
|
||||
"sanity": "Gesundheitzustand",
|
||||
"title": "Gesundheitzustand"
|
||||
},
|
||||
"inventory": {
|
||||
"add_child_venue": "Untergeordneten Veranstaltungsort zu {{entityName}}hinzufügen",
|
||||
"add_tag": "Tag hinzufügen",
|
||||
"add_tag_to": "Inventar-Tag zu {{name}}hinzufügen",
|
||||
"assign_error": "Fehler beim Versuch, Tag zuzuweisen",
|
||||
"assign_to_entity": "Zu Entität zuweisen",
|
||||
"error_retrieving": "Beim Abrufen von Inventar-Tags ist ein Fehler aufgetreten",
|
||||
"error_unassign": "Fehler beim Aufheben der Zuweisung",
|
||||
"subscriber": "Teilnehmer",
|
||||
"successful_assign": "Tag erfolgreich zugewiesen",
|
||||
"successful_tag_update": "Tag erfolgreich aktualisiert",
|
||||
"successful_unassign": "Vorgang zum Aufheben der Zuweisung war erfolgreich",
|
||||
"tag_created": "Inventar-Tag erfolgreich erstellt",
|
||||
"tag_creation_error": "Fehler beim Versuch, Inventar-Tag zu erstellen",
|
||||
"tag_update_error": "Fehler beim Aktualisieren des Tags",
|
||||
"tags_assigned_to": "Inventar-Tags {{name}}zugewiesen",
|
||||
"unassign": "Zuordnung aufheben",
|
||||
"unassign_tag": "Tag von Entität zuweisen",
|
||||
"unassigned_tags": "Nicht zugewiesene Tags",
|
||||
"venue": "Tagungsort"
|
||||
},
|
||||
"login": {
|
||||
"change_password": "Ändere das Passwort",
|
||||
"change_password_error": "Fehler beim Ändern des Passworts. Stellen Sie sicher, dass das neue Passwort gültig ist, indem Sie die Seite \"Passwortrichtlinie\" besuchen",
|
||||
"change_password_instructions": "Geben Sie Ihr neues Passwort ein und bestätigen Sie es",
|
||||
"changing_password": "Passwort ändern...",
|
||||
"confirm_new_password": "Bestätige neues Passwort",
|
||||
"different_passwords": "Sie müssen das gleiche Passwort zweimal eingeben",
|
||||
"forgot_password_error": "Fehler beim Versuch, eine E-Mail mit vergessenem Passwort zu senden. Stellen Sie sicher, dass diese Benutzer-ID mit einem Konto verknüpft ist.",
|
||||
"forgot_password_explanation": "Geben Sie Ihren Benutzernamen ein, um eine E-Mail mit Anweisungen zum Zurücksetzen Ihres Passworts zu erhalten",
|
||||
"forgot_password_success": "Sie sollten in Kürze eine E-Mail mit Anweisungen zum Zurücksetzen Ihres Passworts erhalten. Bitte überprüfen Sie Ihren Spam, wenn Sie die E-Mail nicht finden können",
|
||||
"logging_in": "Einloggen...",
|
||||
"login": "Anmeldung",
|
||||
"login_error": "Anmeldefehler, bestätigen Sie, dass Ihr Benutzername, Ihr Passwort und Ihre Gateway-URL gültig sind",
|
||||
"login_error": "Anmeldefehler, stellen Sie sicher, dass die von Ihnen angegebenen Informationen gültig sind",
|
||||
"new_password": "Neues Kennwort",
|
||||
"password": "Passwort",
|
||||
"please_enter_gateway": "Bitte geben Sie eine Gateway-URL ein",
|
||||
"please_enter_gateway": "Bitte geben Sie eine uCentralSec-URL ein",
|
||||
"please_enter_password": "Bitte geben Sie Ihr Passwort ein",
|
||||
"please_enter_username": "Bitte geben Sie Ihren Benutzernamen ein",
|
||||
"previously_used": "Passwort wurde zuvor verwendet",
|
||||
"send_forgot": "E-Mail senden",
|
||||
"sending_ellipsis": "Senden…",
|
||||
"sign_in_to_account": "Melden Sie sich bei Ihrem Konto an",
|
||||
"username": "Nutzername"
|
||||
"url": "uCentralSec-URL",
|
||||
"username": "Benutzername"
|
||||
},
|
||||
"reboot": {
|
||||
"directions": "Wann möchten Sie dieses Gerät neu starten?",
|
||||
"now": "Möchten Sie dieses Gerät jetzt neu starten?",
|
||||
"title": "Gerät neustarten"
|
||||
},
|
||||
"scan": {
|
||||
"active": "Aktiven Scan aktivieren",
|
||||
"channel": "Kanal",
|
||||
"directions": "Starten Sie einen WLAN-Scan dieses Geräts, der ungefähr 25 Sekunden dauern sollte.",
|
||||
"results": "Ergebnisse des WLAN-Scans"
|
||||
"directions": "Starten Sie einen WiFi-Scan dieses Geräts, der ungefähr 25 Sekunden dauern sollte.",
|
||||
"re_scan": "Erneut scannen",
|
||||
"result_directions": "Bitte klicken Sie auf die Schaltfläche '$t(scan.re_scan)', wenn Sie einen Scan mit derselben Konfiguration wie beim letzten Scan durchführen möchten.",
|
||||
"results": "Ergebnisse des WiFi-Scans",
|
||||
"scan": "Scan",
|
||||
"scanning": "Scannen... ",
|
||||
"waiting_directions": "Bitte warten Sie auf das Scanergebnis. Dies kann bis zu 25 Sekunden dauern. Sie können den Vorgang später beenden und die Ergebnisse aus der Befehlstabelle anzeigen."
|
||||
},
|
||||
"settings": {
|
||||
"title": "die Einstellungen"
|
||||
},
|
||||
"statistics": {
|
||||
"data": "Daten (KB)",
|
||||
"latest_statistics": "Neueste Statistiken",
|
||||
"lifetime_stats": "Lifetime-Statistik",
|
||||
"no_interfaces": "Keine Statistiken zur Schnittstellenlebensdauer verfügbar",
|
||||
"show_latest": "Letzte Statistik",
|
||||
"title": "Statistiken"
|
||||
},
|
||||
"status": {
|
||||
"connection_status": "Verbindungsstatus",
|
||||
"error": "Statusdaten sind nicht verfügbar",
|
||||
"last_contact": "Letzter Kontakt",
|
||||
"load_averages": "Belastung (Durchschnitt 1 / 5 / 15 Minuten)",
|
||||
"localtime": "Ortszeit",
|
||||
"memory": "Verwendeter Speicher",
|
||||
"percentage_free": "{{percentage}}% von {{total}} kostenlos",
|
||||
"percentage_used": "{{percentage}}% von {{total}} verwendet",
|
||||
"title": "#{{serialNumber}} Status",
|
||||
"uptime": "Betriebszeit",
|
||||
"used_total_memory": "{{used}} verwendet / {{total}} insgesamt"
|
||||
},
|
||||
"system": {
|
||||
"error_fetching": "Fehler beim Abrufen von Systeminformationen"
|
||||
},
|
||||
"trace": {
|
||||
"choose_network": "Netzwerk auswählen",
|
||||
"directions": "Starten Sie eine Fernverfolgung dieses Geräts für eine bestimmte Dauer oder eine Anzahl von Paketen",
|
||||
"directions": "Starten Sie eine Tcpdump auf diesem Geräts für eine bestimmte Dauer oder eine Anzahl von Paketen",
|
||||
"download_trace": "Trace-Datei herunterladen",
|
||||
"packets": "Pakete",
|
||||
"title": "Trace-Gerät"
|
||||
"title": "Tcpdump",
|
||||
"trace": "Spur",
|
||||
"trace_not_successful": "Trace nicht erfolgreich: Gateway hat folgenden Fehler gemeldet: {{error}}",
|
||||
"wait_for_file": "Möchten Sie warten, bis die Trace-Datei fertig ist?",
|
||||
"waiting_directions": "Bitte warten Sie auf die Trace-Datendatei. Dies könnte eine Weile dauern. Sie können das Warten beenden und die Ablaufverfolgungsdatei später aus der Befehlstabelle abrufen.",
|
||||
"waiting_seconds": "Verstrichene Zeit: {{seconds}} Sekunden"
|
||||
},
|
||||
"upgrade": {
|
||||
"command_submitted": "Befehl gesendet",
|
||||
"device_disconnected": "Gerät getrennt",
|
||||
"device_reconnected": "Gerät wieder verbunden",
|
||||
"device_upgrading_firmware": "Geräte-Firmware-Upgrade",
|
||||
"directions": "Wählen Sie eine Uhrzeit und eine Firmware-Version für dieses Gerät",
|
||||
"firmware_uri": "Firmware-URI:",
|
||||
"need_uri": "Du brauchst eine URI...",
|
||||
"time": "Zeitpunkt der Aktualisierung:",
|
||||
"title": "Firmware-Aktualisierung"
|
||||
"firmware_uri": "Firmware-URL:",
|
||||
"need_uri": "Sie brauchen eine URL...",
|
||||
"new_version": "Neue Version ist",
|
||||
"offline_device": "Diese Option ist gesperrt, da dieses Gerät nicht verbunden ist",
|
||||
"time": "Zeitpunkt für Aktualisierung:",
|
||||
"title": "Firmware Aktualisieren",
|
||||
"upgrade": "Aktualisierung",
|
||||
"wait_for_upgrade": "Möchten Sie warten, bis das Upgrade abgeschlossen ist?",
|
||||
"waiting_for_device": "Warten, bis das Gerät wieder verbunden ist"
|
||||
},
|
||||
"user": {
|
||||
"avatar": "Dein Avatar",
|
||||
"avatar_file": "Dein Avatar (max. 2 MB)",
|
||||
"create": "Benutzer erstellen",
|
||||
"create_failure": "Fehler beim Erstellen des Benutzers. Bitte stellen Sie sicher, dass diese E-Mail-Adresse nicht bereits mit einem Konto verknüpft ist.",
|
||||
"create_success": "Benutzer erfolgreich erstellt",
|
||||
"creating": "Benutzer erstellen ...",
|
||||
"delete_avatar": "Avatar löschen",
|
||||
"delete_failure": "Fehler beim Versuch, den Benutzer zu löschen",
|
||||
"delete_success": "Benutzer erfolgreich gelöscht!",
|
||||
"delete_title": "Benutzer löschen",
|
||||
"delete_warning": "Warnung: Sobald Sie einen Benutzer gelöscht haben, können Sie ihn nicht wiederherstellen",
|
||||
"deleting": "Löschen ...",
|
||||
"description": "Beschreibung",
|
||||
"edit": "Benutzer bearbeiten",
|
||||
"email_address": "E-Mail-Addresse",
|
||||
"force_password_change": "Passwortänderung bei der Anmeldung erzwingen",
|
||||
"id": "Benutzeridentifikation.",
|
||||
"last_login": "Letzte Anmeldung",
|
||||
"login_id": "Anmelde-ID.",
|
||||
"my_profile": "Mein Profil",
|
||||
"name": "Name",
|
||||
"nickname": "Spitzname",
|
||||
"nickname_explanation": "Spitzname (optional)",
|
||||
"not_validated": "Nicht validiert",
|
||||
"note": "Hinweis",
|
||||
"password": "Passwort",
|
||||
"provide_email": "Bitte geben Sie eine gültige E-Mail Adresse an",
|
||||
"provide_password": "Bitte geben Sie ein gültiges Passwort ein",
|
||||
"save_avatar": "Avatar speichern",
|
||||
"show_hide_password": "Passwort anzeigen/verbergen",
|
||||
"update_failure": "Stellen Sie sicher, dass alle Ihre Daten gültig sind. Wenn Sie das Kennwort ändern, stellen Sie sicher, dass es sich nicht um ein altes handelt.",
|
||||
"update_failure_title": "Update fehlgeschlagen",
|
||||
"update_success": "Benutzer erfolgreich aktualisiert",
|
||||
"update_success_title": "Erfolg",
|
||||
"user_role": "Rolle",
|
||||
"users": "Benutzer",
|
||||
"validated": "Bestätigt"
|
||||
},
|
||||
"wifi_analysis": {
|
||||
"association": "Verband",
|
||||
"associations": "Verbände",
|
||||
"mode": "Modus",
|
||||
"network_diagram": "Netzwerkdiagramm",
|
||||
"radios": "Radios",
|
||||
"title": "WLAN-Analyse"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"actions": {
|
||||
"actions": "Actions",
|
||||
"blink": "Blink",
|
||||
"configure": "Configure",
|
||||
"connect": "Connect",
|
||||
@@ -7,28 +8,39 @@
|
||||
"factory_reset": "Factory Reset",
|
||||
"firmware_upgrade": "Firmware Upgrade",
|
||||
"reboot": "Reboot",
|
||||
"title": "Device Actions",
|
||||
"title": "Commands",
|
||||
"trace": "Trace",
|
||||
"wifi_scan": "Wifi Scan"
|
||||
"wifi_scan": "Wi-Fi Scan"
|
||||
},
|
||||
"blink": {
|
||||
"blink": "Blink",
|
||||
"device_leds": "Device LEDs",
|
||||
"pattern": "Choose the pattern you would like to use: ",
|
||||
"execute_now": "Would you like to set this pattern now?",
|
||||
"pattern": "LEDs pattern: ",
|
||||
"set_leds": "Set LEDs",
|
||||
"when_blink_leds": "When would you like to make the device LEDs blink?"
|
||||
},
|
||||
"commands": {
|
||||
"error": "Error while submitting command!",
|
||||
"success": "Command submitted successfully",
|
||||
"title": "Device Commands"
|
||||
"event_queue": "Event Queue",
|
||||
"success": "Command submitted successfully, you can look at the Commands log for the result",
|
||||
"title": "Command History",
|
||||
"unable_queue": "Unable to complete event queue request"
|
||||
},
|
||||
"common": {
|
||||
"access_policy": "Access Policy",
|
||||
"add": "Add",
|
||||
"adding_ellipsis": "Adding...",
|
||||
"are_you_sure": "Are you sure?",
|
||||
"back_to_login": "Back to Login",
|
||||
"cancel": "Cancel",
|
||||
"certificate": "Certificate",
|
||||
"certificates": "Certificates",
|
||||
"clear": "Clear",
|
||||
"close": "Close",
|
||||
"command": "Command",
|
||||
"commands": "Commands",
|
||||
"commands_executed": "Commands Executed",
|
||||
"compatible": "Compatible",
|
||||
"completed": "Completed",
|
||||
"config_id": "Config. Id",
|
||||
@@ -36,83 +48,199 @@
|
||||
"connected": "Connected",
|
||||
"copied": "Copied!",
|
||||
"copy_to_clipboard": "Copy to clipboard",
|
||||
"create": "Create",
|
||||
"created": "Created",
|
||||
"created_by": "Created By",
|
||||
"current": "Current ",
|
||||
"custom_date": "Custom Date",
|
||||
"dashboard": "Dashboard",
|
||||
"date": "Date",
|
||||
"day": "day",
|
||||
"days": "days",
|
||||
"delete": "Delete",
|
||||
"delete_device": "Delete Device",
|
||||
"details": "Details",
|
||||
"device": "Device #{{serialNumber}}",
|
||||
"device_dashboard": "Device Dashboard",
|
||||
"device_delete": "Delete Device #{{serialNumber}}",
|
||||
"device_deleted": "Device Successfully Deleted",
|
||||
"device_health": "Device Health",
|
||||
"device_list": "List of Devices",
|
||||
"device_page": "Device Page",
|
||||
"device_page": "View",
|
||||
"device_status": "Device Status",
|
||||
"devices": "Devices",
|
||||
"devices_using_latest": "Devices Using Latest Firmware",
|
||||
"devices_using_unknown": "Devices Using Unknown Firmware",
|
||||
"dismiss": "Dismiss",
|
||||
"do_now": "Do Now!",
|
||||
"download": "Download",
|
||||
"duration": "Duration",
|
||||
"edit": "Edit",
|
||||
"edit_user": "Edit",
|
||||
"email_address": "Email Address",
|
||||
"endpoint": "Endpoint",
|
||||
"endpoints": "Endpoints",
|
||||
"error": "Error",
|
||||
"execute_now": "Would you like to execute this command now?",
|
||||
"executed": "Executed",
|
||||
"exit": "Exit",
|
||||
"firmware": "Firmware",
|
||||
"firmware_dashboard": "Firmware Dashboard",
|
||||
"firmware_installed": "Firmware Installed",
|
||||
"forgot_password": "Forgot your Password?",
|
||||
"forgot_password_title": "Forgot Password",
|
||||
"from": "From",
|
||||
"general_error": "API Error, please consult your administrator",
|
||||
"hide": "Hide",
|
||||
"hour": "hour",
|
||||
"hours": "hours",
|
||||
"id": "Id",
|
||||
"ip_address": "Ip Address",
|
||||
"ip_address": "IP Address",
|
||||
"last_dashboard_refresh": "Last Dashboard Refresh",
|
||||
"later_tonight": "Later tonight",
|
||||
"latest": "Latest",
|
||||
"list": "List",
|
||||
"loading_ellipsis": "Loading...",
|
||||
"loading_more_ellipsis": "Loading more...",
|
||||
"logout": "Logout",
|
||||
"mac": "MAC Address",
|
||||
"manufacturer": "Manufacturer",
|
||||
"memory_used": "Memory Used",
|
||||
"minute": "minute",
|
||||
"minutes": "minutes",
|
||||
"na": "N/A",
|
||||
"need_date": "You need a date...",
|
||||
"no": "No",
|
||||
"no_devices_found": "No Devices Found",
|
||||
"no_items": "No Items",
|
||||
"not_connected": "Not Connected",
|
||||
"of_connected": "% of devices",
|
||||
"off": "Off",
|
||||
"on": "On",
|
||||
"optional": "Optional",
|
||||
"overall_health": "Overall Health",
|
||||
"password_policy": "Password Policy",
|
||||
"recorded": "Recorded",
|
||||
"refresh": "Refresh",
|
||||
"refresh_device": "Refresh Device",
|
||||
"required": "Required",
|
||||
"result": "Result",
|
||||
"save": "Save",
|
||||
"saved": "Saved!",
|
||||
"saving": "Saving... ",
|
||||
"schedule": "Schedule",
|
||||
"search": "Search Devices",
|
||||
"second": "second",
|
||||
"seconds": "seconds",
|
||||
"seconds_elapsed": "Seconds elapsed",
|
||||
"serial_number": "Serial Number",
|
||||
"show_all": "Show All",
|
||||
"start": "Start",
|
||||
"submit": "Submit",
|
||||
"submitted": "Submitted",
|
||||
"success": "Success",
|
||||
"system": "System",
|
||||
"table": "Table",
|
||||
"timestamp": "Time",
|
||||
"to": "To",
|
||||
"type": "Type",
|
||||
"unable_to_connect": "Unable to Connect to Device",
|
||||
"unable_to_delete": "Unable to Delete",
|
||||
"unknown": "Unknown",
|
||||
"up_to_date": "Up to Date Devices",
|
||||
"uptimes": "Uptimes",
|
||||
"uuid": "UUID",
|
||||
"vendors": "Vendors",
|
||||
"view_more": "View more",
|
||||
"yes": "Yes"
|
||||
},
|
||||
"configuration": {
|
||||
"created": "Created",
|
||||
"details": "Device Details",
|
||||
"details": "Details",
|
||||
"device_password": "Password",
|
||||
"last_configuration_change": "Last Configuration Change",
|
||||
"last_configuration_download": "Last Configuration Download",
|
||||
"location": "Location",
|
||||
"note": "Note",
|
||||
"notes": "Notes",
|
||||
"owner": "Owner",
|
||||
"title": "Device Configuration",
|
||||
"title": "Configuration",
|
||||
"type": "Device Type",
|
||||
"uuid": "Config ID",
|
||||
"view_json": "View raw JSON"
|
||||
},
|
||||
"configure": {
|
||||
"choose_file": "You need to choose a valid .json file: ",
|
||||
"enter_new": "Enter new device configuration JSON: ",
|
||||
"placeholder": "Config JSON",
|
||||
"title": "Configure Device",
|
||||
"title": "Configure",
|
||||
"valid_json": "You need to enter valid JSON"
|
||||
},
|
||||
"delete_command": {
|
||||
"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",
|
||||
"title": "Device Logs"
|
||||
"title": "Logs"
|
||||
},
|
||||
"entity": {
|
||||
"add_child": "Add Child Entity to {{entityName}}",
|
||||
"add_failure": "Error, the server returned : {{error}}",
|
||||
"add_root": "Add Root Entity",
|
||||
"add_success": "Entity Successfully Created!",
|
||||
"cannot_delete": "You cannot delete entities which have children. Delete this entity's children to be able to delete it.",
|
||||
"delete_success": "Entity Successfully Deleted",
|
||||
"delete_warning": "Warning: this operation cannot be reverted",
|
||||
"edit_failure": "Update unsuccessful : {{error}}",
|
||||
"entities": "Entities",
|
||||
"entity": "Entity",
|
||||
"only_unassigned": "Only Unassigned",
|
||||
"valid_serial": "Needs to be a valid serial number (12 HEX characters)"
|
||||
},
|
||||
"factory_reset": {
|
||||
"redirector": "Keep redirector: ",
|
||||
"title": "Factory Reset Device",
|
||||
"reset": "Reset",
|
||||
"resetting": "Resetting... ",
|
||||
"title": "Factory Reset",
|
||||
"warning": "Warning: Once you submit this cannot be reverted"
|
||||
},
|
||||
"firmware": {
|
||||
"average_age": "Average Firmware Age",
|
||||
"choose_custom": "Choose",
|
||||
"details_title": "Image #{{image}} Details",
|
||||
"device_type": "Device Type",
|
||||
"device_types": "Device Types",
|
||||
"downloads": "Downloads",
|
||||
"error_fetching_latest": "Error while fetching latest firmware",
|
||||
"from_release": "From",
|
||||
"history_title": "History",
|
||||
"image": "Image",
|
||||
"image_date": "Image Date",
|
||||
"installed_firmware": "Installed Firmware",
|
||||
"latest_version_installed": "Latest Version Installed",
|
||||
"newer_firmware_available": "Newer Revisions Available",
|
||||
"reinstall_latest": "Reinstall ",
|
||||
"revision": "Revision",
|
||||
"show_dev": "Show Dev Releases",
|
||||
"size": "Size",
|
||||
"status": "Firmware Status",
|
||||
"title": "Firmware",
|
||||
"to_release": "To",
|
||||
"unknown_firmware_status": "Unknown Firmware Status",
|
||||
"upgrade": "Upgrade",
|
||||
"upgrade_command_submitted": "Upgrade Command Submitted Successfully",
|
||||
"upgrade_to_latest": "Latest",
|
||||
"upgrade_to_version": "Upgrade to this Revision",
|
||||
"upgrading": "Upgrading..."
|
||||
},
|
||||
"footer": {
|
||||
"coreui_for_react": "CoreUI for React",
|
||||
"powered_by": "Powered by",
|
||||
@@ -120,43 +248,170 @@
|
||||
},
|
||||
"health": {
|
||||
"sanity": "Sanity",
|
||||
"title": "Device Health"
|
||||
"title": "Health"
|
||||
},
|
||||
"inventory": {
|
||||
"add_child_venue": "Add Child Venue to {{entityName}}",
|
||||
"add_tag": "Add Tag",
|
||||
"add_tag_to": "Add Inventory Tag to {{name}}",
|
||||
"assign_error": "Error while trying to assign tag",
|
||||
"assign_to_entity": "Assign to Entity",
|
||||
"error_retrieving": "Error occurred while retrieving inventory tags",
|
||||
"error_unassign": "Error during unassign operation",
|
||||
"subscriber": "Subscriber",
|
||||
"successful_assign": "Tag successfully assigned",
|
||||
"successful_tag_update": "Successfully updated tag",
|
||||
"successful_unassign": "Unassign operation was successful",
|
||||
"tag_created": "Inventory tag successfully created",
|
||||
"tag_creation_error": "Error while trying to create inventory tag",
|
||||
"tag_update_error": "Error while updating tag",
|
||||
"tags_assigned_to": "Inventory tags assigned to {{name}}",
|
||||
"unassign": "Unassign",
|
||||
"unassign_tag": "Unassign Tag from Entity",
|
||||
"unassigned_tags": "Unassigned tags",
|
||||
"venue": "Venue"
|
||||
},
|
||||
"login": {
|
||||
"change_password": "Change Password",
|
||||
"change_password_error": "Error while changing password. Make sure the new password is valid by visiting the 'Password Policy' page",
|
||||
"change_password_instructions": "Enter and confirm your new password",
|
||||
"changing_password": "Changing Password... ",
|
||||
"confirm_new_password": "Confirm New Password",
|
||||
"different_passwords": "You need to enter the same password twice",
|
||||
"forgot_password_error": "Error while trying to send Forgot Password email. Please make sure this userId is associated to an account.",
|
||||
"forgot_password_explanation": "Enter your username to receive an email containing the instructions to reset your password",
|
||||
"forgot_password_success": "You should soon receive an email containing the instructions to reset your password. Please make sure to check your spam if you can't find the email",
|
||||
"logging_in": "Logging In... ",
|
||||
"login": "Login",
|
||||
"login_error": "Login error, confirm that your username, password and gateway url are valid",
|
||||
"login_error": "Login error, make sure the information you are providing is valid",
|
||||
"new_password": "New Password",
|
||||
"password": "Password",
|
||||
"please_enter_gateway": "Please enter a gateway URL",
|
||||
"please_enter_gateway": "Please enter a uCentralSec URL",
|
||||
"please_enter_password": "Please enter your password",
|
||||
"please_enter_username": "Please enter your username",
|
||||
"previously_used": "Password was previously used",
|
||||
"send_forgot": "Send Email",
|
||||
"sending_ellipsis": "Sending... ",
|
||||
"sign_in_to_account": "Sign in to your account",
|
||||
"url": "uCentralSec URL",
|
||||
"username": "Username"
|
||||
},
|
||||
"reboot": {
|
||||
"directions": "When would you like to reboot this device?",
|
||||
"title": "Reboot Device"
|
||||
"now": "Would you like to reboot this device now?",
|
||||
"title": "Reboot"
|
||||
},
|
||||
"scan": {
|
||||
"active": "Enable active scan",
|
||||
"channel": "Channel",
|
||||
"directions": "Launch a wifi scan of this device, which should take approximately 25 seconds.",
|
||||
"results": "Wifi Scan Results"
|
||||
"re_scan": "Re-Scan",
|
||||
"result_directions": "Please click the '$t(scan.re_scan)' button if you would like to do a scan with the same configuration as the last.",
|
||||
"results": "Wi-Fi Scan Results",
|
||||
"scan": "Scan",
|
||||
"scanning": "Scanning... ",
|
||||
"waiting_directions": "Please wait for the scan result. This may take up to 25 seconds. You can exit and look at the results from the commands table later."
|
||||
},
|
||||
"settings": {
|
||||
"title": "Settings"
|
||||
},
|
||||
"statistics": {
|
||||
"data": "Data (KB)",
|
||||
"latest_statistics": "Latest Statistics",
|
||||
"lifetime_stats": "Lifetime Statistics",
|
||||
"no_interfaces": "No interface lifetime statistics available",
|
||||
"show_latest": "Last Statistics",
|
||||
"title": "Statistics"
|
||||
},
|
||||
"status": {
|
||||
"connection_status": "Connection Status",
|
||||
"error": "Status data is unavailable",
|
||||
"last_contact": "Last Contact",
|
||||
"load_averages": "Load ( 1 / 5 / 15 minute average)",
|
||||
"localtime": "Localtime",
|
||||
"memory": "Memory Used",
|
||||
"percentage_free": "{{percentage}}% of {{total}} free",
|
||||
"percentage_used": "{{percentage}}% of {{total}} used",
|
||||
"title": "#{{serialNumber}} Status",
|
||||
"uptime": "Uptime",
|
||||
"used_total_memory": "{{used}} used / {{total}} total "
|
||||
},
|
||||
"system": {
|
||||
"error_fetching": "Error while fetching system information"
|
||||
},
|
||||
"trace": {
|
||||
"choose_network": "Choose network",
|
||||
"directions": "Launch a remote trace of this device for either a specific duration or a number of packets",
|
||||
"download_trace": "Download Trace File",
|
||||
"packets": "Packets",
|
||||
"title": "Trace Device"
|
||||
"title": "Trace",
|
||||
"trace": "Trace",
|
||||
"trace_not_successful": "Trace not successful: gateway reported the following error : {{error}}",
|
||||
"wait_for_file": "Would you like to wait until the trace file is ready?",
|
||||
"waiting_directions": "Please wait for the trace data file. This may take some time. You can exit the wait and retrieve the trace file from the commands table later.",
|
||||
"waiting_seconds": "Time Elapsed: {{seconds}} seconds"
|
||||
},
|
||||
"upgrade": {
|
||||
"command_submitted": "Command submitted",
|
||||
"device_disconnected": "Device disconnected",
|
||||
"device_reconnected": "Device reconnected",
|
||||
"device_upgrading_firmware": "Device upgrading firmware",
|
||||
"directions": "Choose a time and a firmware version for this device",
|
||||
"firmware_uri": "Firmware URI:",
|
||||
"need_uri": "You need a URI...",
|
||||
"new_version": "New version is ",
|
||||
"offline_device": "This option is blocked because this device is not connected",
|
||||
"time": "Time of upgrade:",
|
||||
"title": "Firmware Upgrade"
|
||||
"title": "Firmware Upgrade",
|
||||
"upgrade": "Upgrade",
|
||||
"wait_for_upgrade": "Would you like to wait for the upgrade to finish?",
|
||||
"waiting_for_device": "Waiting for device to reconnect"
|
||||
},
|
||||
"user": {
|
||||
"avatar": "Your Avatar",
|
||||
"avatar_file": "Your Avatar (max. of 2 MB)",
|
||||
"create": "Create User",
|
||||
"create_failure": "Error while creating user. Please make sure this email address is not already linked to an account.",
|
||||
"create_success": "User Created Successfully",
|
||||
"creating": "Creating User...",
|
||||
"delete_avatar": "Delete Avatar",
|
||||
"delete_failure": "Error while trying to delete user",
|
||||
"delete_success": "User successfully deleted!",
|
||||
"delete_title": "Delete User",
|
||||
"delete_warning": "Warning: Once you delete a user you cannot revert",
|
||||
"deleting": "Deleting... ",
|
||||
"description": "Description",
|
||||
"edit": "Edit User",
|
||||
"email_address": "Email Address",
|
||||
"force_password_change": "Force Password Change on Login",
|
||||
"id": "User Id.",
|
||||
"last_login": "Last Login",
|
||||
"login_id": "Login Id.",
|
||||
"my_profile": "My Profile",
|
||||
"name": "Name",
|
||||
"nickname": "Nickname",
|
||||
"nickname_explanation": "Nickname (optional)",
|
||||
"not_validated": "Not Validated",
|
||||
"note": "Note",
|
||||
"password": "Password",
|
||||
"provide_email": "Please provide a valid email address",
|
||||
"provide_password": "Please provide a valid password",
|
||||
"save_avatar": "Save Avatar",
|
||||
"show_hide_password": "Show/Hide Password",
|
||||
"update_failure": "Make sure all of your data is valid. If you are modifying the password, make sure it is not an old one.",
|
||||
"update_failure_title": "Update Failed",
|
||||
"update_success": "User Updated Successfully",
|
||||
"update_success_title": "Success",
|
||||
"user_role": "Role",
|
||||
"users": "Users",
|
||||
"validated": "Validated"
|
||||
},
|
||||
"wifi_analysis": {
|
||||
"association": "Association",
|
||||
"associations": "Associations",
|
||||
"mode": "Mode",
|
||||
"network_diagram": "Network Diagram",
|
||||
"radios": "Radios",
|
||||
"title": "Wi-Fi Analysis"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"actions": {
|
||||
"actions": "Comportamiento",
|
||||
"blink": "Parpadeo",
|
||||
"configure": "Configurar",
|
||||
"connect": "Conectar",
|
||||
@@ -7,28 +8,39 @@
|
||||
"factory_reset": "Restablecimiento De Fábrica",
|
||||
"firmware_upgrade": "Actualización de firmware",
|
||||
"reboot": "Reiniciar",
|
||||
"title": "Acciones del dispositivo",
|
||||
"title": "Comandos",
|
||||
"trace": "Rastro",
|
||||
"wifi_scan": "Escaneo Wifi"
|
||||
"wifi_scan": "Escaneo Wi-Fi "
|
||||
},
|
||||
"blink": {
|
||||
"blink": "Parpadeo",
|
||||
"device_leds": "LED de dispositivo",
|
||||
"execute_now": "¿Le gustaría establecer este patrón ahora?",
|
||||
"pattern": "Elija el patrón que le gustaría usar:",
|
||||
"set_leds": "Establecer LED",
|
||||
"when_blink_leds": "¿Cuándo desea que los LED del dispositivo parpadeen?"
|
||||
},
|
||||
"commands": {
|
||||
"error": "¡Error al enviar el comando!",
|
||||
"success": "Comando enviado con éxito",
|
||||
"title": "Comandos del dispositivo"
|
||||
"event_queue": "Cola de eventos",
|
||||
"success": "Comando enviado con éxito, puede consultar el registro de Comandos para ver el resultado",
|
||||
"title": "Historial de Comandos",
|
||||
"unable_queue": "No se pudo completar la solicitud de cola de eventos"
|
||||
},
|
||||
"common": {
|
||||
"access_policy": "Política de acceso",
|
||||
"add": "Añadir",
|
||||
"adding_ellipsis": "Añadiendo ...",
|
||||
"are_you_sure": "¿Estás seguro?",
|
||||
"back_to_login": "Atrás para iniciar sesión",
|
||||
"cancel": "Cancelar",
|
||||
"certificate": "Certificado",
|
||||
"certificates": "Certificados",
|
||||
"clear": "Claro",
|
||||
"close": "Cerrar",
|
||||
"command": "Mando",
|
||||
"commands": "comandos",
|
||||
"commands_executed": "Comandos ejecutados",
|
||||
"compatible": "Compatible",
|
||||
"completed": "terminado",
|
||||
"config_id": "Config. Identificación",
|
||||
@@ -36,83 +48,199 @@
|
||||
"connected": "Conectado",
|
||||
"copied": "Copiado!",
|
||||
"copy_to_clipboard": "Copiar al portapapeles",
|
||||
"create": "Crear",
|
||||
"created": "creado",
|
||||
"created_by": "Creado por",
|
||||
"current": "Corriente",
|
||||
"custom_date": "Fecha personalizada",
|
||||
"dashboard": "Tablero",
|
||||
"date": "Fecha",
|
||||
"day": "día",
|
||||
"days": "días",
|
||||
"delete": "Borrar",
|
||||
"delete_device": "Eliminar dispositivo",
|
||||
"details": "Detalles",
|
||||
"device": "Dispositivo n.º{{serialNumber}}",
|
||||
"device_dashboard": "Panel de control del dispositivo",
|
||||
"device_delete": "Eliminar dispositivo n.º{{serialNumber}}",
|
||||
"device_deleted": "Dispositivo eliminado correctamente",
|
||||
"device_health": "Salud del dispositivo",
|
||||
"device_list": "Listado de dispositivos",
|
||||
"device_page": "Página del dispositivo",
|
||||
"device_page": "Ver",
|
||||
"device_status": "Estado del dispositivo",
|
||||
"devices": "Dispositivos",
|
||||
"devices_using_latest": "Dispositivos que utilizan el firmware más reciente",
|
||||
"devices_using_unknown": "Dispositivos que utilizan firmware desconocido",
|
||||
"dismiss": "Despedir",
|
||||
"do_now": "¡Hagan ahora!",
|
||||
"download": "Descargar",
|
||||
"duration": "Duración",
|
||||
"edit": "Editar",
|
||||
"edit_user": "Editar",
|
||||
"email_address": "Dirección de correo electrónico",
|
||||
"endpoint": "punto final",
|
||||
"endpoints": "Puntos finales",
|
||||
"error": "Error",
|
||||
"execute_now": "¿Le gustaría ejecutar este comando ahora?",
|
||||
"executed": "ejecutado",
|
||||
"exit": "salida",
|
||||
"firmware": "Firmware",
|
||||
"firmware_dashboard": "Panel de firmware",
|
||||
"firmware_installed": "Firmware instalado",
|
||||
"forgot_password": "¿Olvidaste tu contraseña?",
|
||||
"forgot_password_title": "Se te olvidó tu contraseña",
|
||||
"from": "Desde",
|
||||
"general_error": "Error de API, consulte a su administrador",
|
||||
"hide": "Esconder",
|
||||
"hour": "hora",
|
||||
"hours": "horas",
|
||||
"id": "Carné de identidad",
|
||||
"ip_address": "Dirección IP",
|
||||
"last_dashboard_refresh": "Última actualización del panel",
|
||||
"later_tonight": "Más tarde esta noche",
|
||||
"latest": "último",
|
||||
"list": "Lista",
|
||||
"loading_ellipsis": "Cargando...",
|
||||
"loading_more_ellipsis": "Cargando más ...",
|
||||
"logout": "Cerrar sesión",
|
||||
"mac": "Dirección MAC",
|
||||
"manufacturer": "Fabricante",
|
||||
"memory_used": "Memoria usada",
|
||||
"minute": "minuto",
|
||||
"minutes": "minutos",
|
||||
"na": "N / A",
|
||||
"need_date": "Necesitas una cita ...",
|
||||
"no": "No",
|
||||
"no_devices_found": "No se encontraron dispositivos",
|
||||
"no_items": "No hay articulos",
|
||||
"not_connected": "No conectado",
|
||||
"of_connected": "% de dispositivos",
|
||||
"off": "Apagado",
|
||||
"on": "en",
|
||||
"optional": "Opcional",
|
||||
"overall_health": "Salud en general",
|
||||
"password_policy": "Política de contraseñas",
|
||||
"recorded": "Grabado",
|
||||
"refresh": "Refrescar",
|
||||
"refresh_device": "Actualizar dispositivo",
|
||||
"required": "Necesario",
|
||||
"result": "Resultado",
|
||||
"save": "Salvar",
|
||||
"saved": "¡Salvado!",
|
||||
"saving": "Ahorro...",
|
||||
"schedule": "Programar",
|
||||
"search": "Dispositivos de búsqueda",
|
||||
"second": "segundo",
|
||||
"seconds": "segundos",
|
||||
"seconds_elapsed": "Segundos transcurridos",
|
||||
"serial_number": "Número de serie",
|
||||
"show_all": "Mostrar todo",
|
||||
"start": "comienzo",
|
||||
"submit": "Enviar",
|
||||
"submitted": "Presentado",
|
||||
"success": "Éxito",
|
||||
"system": "Sistema",
|
||||
"table": "Mesa",
|
||||
"timestamp": "hora",
|
||||
"to": "a",
|
||||
"type": "Tipo",
|
||||
"unable_to_connect": "No se puede conectar al dispositivo",
|
||||
"unable_to_delete": "No se puede eliminar",
|
||||
"unknown": "Desconocido",
|
||||
"up_to_date": "Dispositivos actualizados",
|
||||
"uptimes": "Tiempos de actividad",
|
||||
"uuid": "UUID",
|
||||
"vendors": "Vendedores",
|
||||
"view_more": "Ver más",
|
||||
"yes": "Sí"
|
||||
},
|
||||
"configuration": {
|
||||
"created": "creado",
|
||||
"details": "Detalles del dispositivo",
|
||||
"details": "Detalles",
|
||||
"device_password": "Contraseña",
|
||||
"last_configuration_change": "Último cambio de configuración",
|
||||
"last_configuration_download": "Descarga de la última configuración",
|
||||
"location": "Ubicación",
|
||||
"note": "Nota",
|
||||
"notes": "Notas",
|
||||
"owner": "Propietario",
|
||||
"title": "Configuración del dispositivo",
|
||||
"title": "Configuración",
|
||||
"type": "Tipo de dispositivo",
|
||||
"uuid": "ID de configuración",
|
||||
"view_json": "Ver JSON sin procesar"
|
||||
},
|
||||
"configure": {
|
||||
"choose_file": "Debe elegir un archivo .json válido:",
|
||||
"enter_new": "Ingrese la nueva configuración del dispositivo JSON:",
|
||||
"placeholder": "Configurar JSON",
|
||||
"title": "Configurar dispositivo",
|
||||
"title": "Configurar",
|
||||
"valid_json": "Debes ingresar un JSON válido"
|
||||
},
|
||||
"delete_command": {
|
||||
"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",
|
||||
"title": "Registros de dispositivos"
|
||||
"title": "Registros"
|
||||
},
|
||||
"entity": {
|
||||
"add_child": "Agregar entidad secundaria a {{entityName}}",
|
||||
"add_failure": "Error, el servidor devolvió: {{error}}",
|
||||
"add_root": "Agregar entidad raíz",
|
||||
"add_success": "¡Entidad creada con éxito!",
|
||||
"cannot_delete": "No puede eliminar entidades que tienen hijos. Elimina los hijos de esta entidad para poder eliminarla.",
|
||||
"delete_success": "Entidad eliminada correctamente",
|
||||
"delete_warning": "Advertencia: esta operación no se puede revertir",
|
||||
"edit_failure": "Actualización fallida: {{error}}",
|
||||
"entities": "entidades",
|
||||
"entity": "Entidad",
|
||||
"only_unassigned": "Solo sin asignar",
|
||||
"valid_serial": "Debe ser un número de serie válido (12 caracteres HEX)"
|
||||
},
|
||||
"factory_reset": {
|
||||
"redirector": "Mantener el redirector:",
|
||||
"title": "Dispositivo de restablecimiento de fábrica",
|
||||
"reset": "Reiniciar",
|
||||
"resetting": "Restableciendo…",
|
||||
"title": "Restablecimiento De Fábrica",
|
||||
"warning": "Advertencia: una vez que envíe, esto no se podrá revertir"
|
||||
},
|
||||
"firmware": {
|
||||
"average_age": "Edad promedio del firmware",
|
||||
"choose_custom": "Escoger",
|
||||
"details_title": "Detalles de la imagen n. °{{image}} ",
|
||||
"device_type": "Tipo de dispositivo",
|
||||
"device_types": "Tipos de dispositivos",
|
||||
"downloads": "Descargas",
|
||||
"error_fetching_latest": "Error al obtener el firmware más reciente",
|
||||
"from_release": "Desde",
|
||||
"history_title": "Historia",
|
||||
"image": "Imagen",
|
||||
"image_date": "Fecha de la imagen",
|
||||
"installed_firmware": "Firmware instalado",
|
||||
"latest_version_installed": "Última versión instalada",
|
||||
"newer_firmware_available": "Nuevas revisiones disponibles",
|
||||
"reinstall_latest": "Reinstalar",
|
||||
"revision": "Revisión",
|
||||
"show_dev": "Mostrar lanzamientos para desarrolladores",
|
||||
"size": "Tamaño",
|
||||
"status": "Estado del firmware",
|
||||
"title": "Firmware",
|
||||
"to_release": "A",
|
||||
"unknown_firmware_status": "Estado de firmware desconocido",
|
||||
"upgrade": "Mejorar",
|
||||
"upgrade_command_submitted": "El comando de actualización se envió correctamente",
|
||||
"upgrade_to_latest": "último",
|
||||
"upgrade_to_version": "Actualizar a esta revisión",
|
||||
"upgrading": "Actualizando ..."
|
||||
},
|
||||
"footer": {
|
||||
"coreui_for_react": "CoreUI para React",
|
||||
"powered_by": "energizado por",
|
||||
@@ -120,43 +248,170 @@
|
||||
},
|
||||
"health": {
|
||||
"sanity": "Cordura",
|
||||
"title": "Salud del dispositivo"
|
||||
"title": "Salud"
|
||||
},
|
||||
"inventory": {
|
||||
"add_child_venue": "Agregar lugar infantil a {{entityName}}",
|
||||
"add_tag": "Añadir etiqueta",
|
||||
"add_tag_to": "Agregar etiqueta de inventario a {{name}}",
|
||||
"assign_error": "Error al intentar asignar la etiqueta",
|
||||
"assign_to_entity": "Asignar a entidad",
|
||||
"error_retrieving": "Se produjo un error al recuperar las etiquetas de inventario",
|
||||
"error_unassign": "Error durante la operación de anulación de asignación",
|
||||
"subscriber": "Abonado",
|
||||
"successful_assign": "Etiqueta asignada correctamente",
|
||||
"successful_tag_update": "Etiqueta actualizada correctamente",
|
||||
"successful_unassign": "La operación de anulación de asignación se realizó correctamente",
|
||||
"tag_created": "Etiqueta de inventario creada correctamente",
|
||||
"tag_creation_error": "Error al intentar crear una etiqueta de inventario",
|
||||
"tag_update_error": "Error al actualizar la etiqueta",
|
||||
"tags_assigned_to": "Etiquetas de inventario asignadas a {{name}}",
|
||||
"unassign": "Anular asignación",
|
||||
"unassign_tag": "Anular asignación de etiqueta de entidad",
|
||||
"unassigned_tags": "Etiquetas sin asignar",
|
||||
"venue": "Lugar de encuentro"
|
||||
},
|
||||
"login": {
|
||||
"change_password": "Cambia la contraseña",
|
||||
"change_password_error": "Error al cambiar la contraseña. Asegúrese de que la nueva contraseña sea válida visitando la página 'Política de contraseñas'",
|
||||
"change_password_instructions": "Ingrese y confirme su nueva contraseña",
|
||||
"changing_password": "Cambio de contraseña ...",
|
||||
"confirm_new_password": "confirmar nueva contraseña",
|
||||
"different_passwords": "Debes ingresar la misma contraseña dos veces",
|
||||
"forgot_password_error": "Error al intentar enviar el correo electrónico de Olvidé mi contraseña. Asegúrese de que este ID de usuario esté asociado a una cuenta.",
|
||||
"forgot_password_explanation": "Ingrese su nombre de usuario para recibir un correo electrónico con las instrucciones para restablecer su contraseña",
|
||||
"forgot_password_success": "Pronto debería recibir un correo electrónico con las instrucciones para restablecer su contraseña. Asegúrese de verificar su correo no deseado si no puede encontrar el correo electrónico",
|
||||
"logging_in": "Iniciar sesión...",
|
||||
"login": "Iniciar sesión",
|
||||
"login_error": "Error de inicio de sesión, confirme que su nombre de usuario, contraseña y URL de puerta de enlace son válidos",
|
||||
"login_error": "Error de inicio de sesión, asegúrese de que la información que proporciona sea válida",
|
||||
"new_password": "Nueva contraseña",
|
||||
"password": "Contraseña",
|
||||
"please_enter_gateway": "Ingrese una URL de puerta de enlace",
|
||||
"please_enter_gateway": "Ingrese una URL de uCentralSec",
|
||||
"please_enter_password": "Por favor, introduzca su contraseña",
|
||||
"please_enter_username": "Por favor, ingrese su nombre de usuario",
|
||||
"previously_used": "La contraseña se usó anteriormente",
|
||||
"send_forgot": "Enviar correo electrónico",
|
||||
"sending_ellipsis": "Enviando...",
|
||||
"sign_in_to_account": "Iniciar sesión en su cuenta",
|
||||
"url": "URL de uCentralSec",
|
||||
"username": "Nombre de usuario"
|
||||
},
|
||||
"reboot": {
|
||||
"directions": "¿Cuándo le gustaría reiniciar este dispositivo?",
|
||||
"title": "Reiniciar dispositivo"
|
||||
"now": "¿Le gustaría reiniciar este dispositivo ahora?",
|
||||
"title": "Reiniciar"
|
||||
},
|
||||
"scan": {
|
||||
"active": "Habilitar escaneo activo",
|
||||
"channel": "Canal",
|
||||
"directions": "Ejecute un escaneo wifi de este dispositivo, que debería tomar aproximadamente 25 segundos.",
|
||||
"results": "Resultados de escaneo Wifi"
|
||||
"re_scan": "Vuelva a escanear",
|
||||
"result_directions": "Haga clic en el botón '$ t (scan.re_scan)' si desea realizar un escaneo con la misma configuración que el anterior.",
|
||||
"results": "Resultados de escaneo Wi-Fi",
|
||||
"scan": "Escanear",
|
||||
"scanning": "Exploración... ",
|
||||
"waiting_directions": "Espere el resultado del escaneo. Esto puede tardar hasta 25 segundos. Puede salir y ver los resultados de la tabla de comandos más adelante."
|
||||
},
|
||||
"settings": {
|
||||
"title": "Ajustes"
|
||||
},
|
||||
"statistics": {
|
||||
"data": "Datos (KB)",
|
||||
"latest_statistics": "Últimas estadísticas",
|
||||
"lifetime_stats": "Estadísticas de por vida",
|
||||
"no_interfaces": "No hay estadísticas de vida útil de la interfaz disponibles",
|
||||
"show_latest": "Últimas estadísticas",
|
||||
"title": "estadística"
|
||||
},
|
||||
"status": {
|
||||
"connection_status": "Estado de conexión",
|
||||
"error": "Los datos de estado no están disponibles",
|
||||
"last_contact": "Último contacto",
|
||||
"load_averages": "Carga (promedio de 1/5/15 minutos)",
|
||||
"localtime": "Hora local",
|
||||
"memory": "Memoria usada",
|
||||
"percentage_free": "{{percentage}}% de {{total}} gratis",
|
||||
"percentage_used": "{{percentage}}% de {{total}} utilizado",
|
||||
"title": "#{{serialNumber}} Estado",
|
||||
"uptime": "Tiempo de actividad",
|
||||
"used_total_memory": "{{used}} usado / {{total}} total"
|
||||
},
|
||||
"system": {
|
||||
"error_fetching": "Error al obtener información del sistema"
|
||||
},
|
||||
"trace": {
|
||||
"choose_network": "Elija la red",
|
||||
"directions": "Lanzar un rastreo remoto de este dispositivo por una duración específica o por una cantidad de paquetes",
|
||||
"download_trace": "Descargar archivo de seguimiento",
|
||||
"packets": "Paquetes",
|
||||
"title": "Dispositivo de seguimiento"
|
||||
"title": "Rastro",
|
||||
"trace": "Rastro",
|
||||
"trace_not_successful": "Seguimiento fallido: la puerta de enlace informó el siguiente error: {{error}}",
|
||||
"wait_for_file": "¿Le gustaría esperar hasta que el archivo de seguimiento esté listo?",
|
||||
"waiting_directions": "Espere el archivo de datos de seguimiento. Esto puede tomar algo de tiempo. Puede salir de la espera y recuperar el archivo de seguimiento de la tabla de comandos más tarde.",
|
||||
"waiting_seconds": "Tiempo transcurrido: {{seconds}} segundos"
|
||||
},
|
||||
"upgrade": {
|
||||
"command_submitted": "Comando enviado",
|
||||
"device_disconnected": "Dispositivo desconectado",
|
||||
"device_reconnected": "Dispositivo reconectado",
|
||||
"device_upgrading_firmware": "Firmware de actualización del dispositivo",
|
||||
"directions": "Elija una hora y una versión de firmware para este dispositivo",
|
||||
"firmware_uri": "URI de firmware:",
|
||||
"need_uri": "Necesitas un URI ...",
|
||||
"new_version": "La nueva versión es",
|
||||
"offline_device": "Esta opción está bloqueada porque este dispositivo no está conectado",
|
||||
"time": "Hora de actualización:",
|
||||
"title": "Actualización de firmware"
|
||||
"title": "Actualización de firmware",
|
||||
"upgrade": "Mejorar",
|
||||
"wait_for_upgrade": "¿Le gustaría esperar a que finalice la actualización?",
|
||||
"waiting_for_device": "Esperando que el dispositivo se vuelva a conectar"
|
||||
},
|
||||
"user": {
|
||||
"avatar": "Tu avatar",
|
||||
"avatar_file": "Tu avatar (máx. De 2 MB)",
|
||||
"create": "Crear usuario",
|
||||
"create_failure": "Error al crear usuario. Asegúrese de que esta dirección de correo electrónico no esté vinculada a una cuenta.",
|
||||
"create_success": "Usuario creado con éxito",
|
||||
"creating": "Creando usuario ...",
|
||||
"delete_avatar": "Eliminar avatar",
|
||||
"delete_failure": "Error al intentar eliminar al usuario",
|
||||
"delete_success": "¡Usuario eliminado correctamente!",
|
||||
"delete_title": "Borrar usuario",
|
||||
"delete_warning": "Advertencia: una vez que elimina un usuario, no puede revertir",
|
||||
"deleting": "Eliminando ...",
|
||||
"description": "Descripción",
|
||||
"edit": "editar usuario",
|
||||
"email_address": "Dirección de correo electrónico",
|
||||
"force_password_change": "Forzar cambio de contraseña al iniciar sesión",
|
||||
"id": "Id. De usuario",
|
||||
"last_login": "Último acceso",
|
||||
"login_id": "Ingresar identificación.",
|
||||
"my_profile": "Mi perfil",
|
||||
"name": "Nombre",
|
||||
"nickname": "Apodo",
|
||||
"nickname_explanation": "Apodo (opcional)",
|
||||
"not_validated": "No validado",
|
||||
"note": "Nota",
|
||||
"password": "Contraseña",
|
||||
"provide_email": "Por favor ingrese su dirección de correo electrónico válida",
|
||||
"provide_password": "Proporcione una contraseña válida",
|
||||
"save_avatar": "Guardar avatar",
|
||||
"show_hide_password": "Mostrar / Ocultar contraseña",
|
||||
"update_failure": "Asegúrese de que todos sus datos sean válidos. Si está modificando la contraseña, asegúrese de que no sea antigua.",
|
||||
"update_failure_title": "Actualización fallida",
|
||||
"update_success": "Usuario actualizado con éxito",
|
||||
"update_success_title": "Éxito",
|
||||
"user_role": "papel",
|
||||
"users": "Usuarios",
|
||||
"validated": "Validado"
|
||||
},
|
||||
"wifi_analysis": {
|
||||
"association": "Asociación",
|
||||
"associations": "Asociaciones",
|
||||
"mode": "Modo",
|
||||
"network_diagram": "Diagrama de Red",
|
||||
"radios": "Radios",
|
||||
"title": "Análisis de Wi-Fi"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"actions": {
|
||||
"actions": "actes",
|
||||
"blink": "Cligner",
|
||||
"configure": "Configurer",
|
||||
"connect": "Relier",
|
||||
@@ -7,28 +8,39 @@
|
||||
"factory_reset": "Retour aux paramètres d'usine",
|
||||
"firmware_upgrade": "Mise à jour du firmware",
|
||||
"reboot": "Redémarrer",
|
||||
"title": "Actions de l'appareil",
|
||||
"title": "Les commandes",
|
||||
"trace": "Trace",
|
||||
"wifi_scan": "Balayage Wi-Fi"
|
||||
},
|
||||
"blink": {
|
||||
"blink": "Cligner",
|
||||
"device_leds": "LED de l'appareil",
|
||||
"execute_now": "Souhaitez-vous définir ce modèle maintenant ?",
|
||||
"pattern": "Choisissez le modèle que vous souhaitez utiliser :",
|
||||
"set_leds": "Définir les LED",
|
||||
"when_blink_leds": "Quand souhaitez-vous faire clignoter les LED de l'appareil ?"
|
||||
},
|
||||
"commands": {
|
||||
"error": "Erreur lors de la soumission de la commande !",
|
||||
"success": "Commande soumise avec succès",
|
||||
"title": "Commandes de l'appareil"
|
||||
"event_queue": "File d'attente d'événements",
|
||||
"success": "Commande soumise avec succès, vous pouvez consulter le journal des commandes pour le résultat",
|
||||
"title": "Historique des commandes",
|
||||
"unable_queue": "Impossible de terminer la demande de file d'attente d'événements"
|
||||
},
|
||||
"common": {
|
||||
"access_policy": "Politique d'accès",
|
||||
"add": "Ajouter",
|
||||
"adding_ellipsis": "Ajouter...",
|
||||
"are_you_sure": "Êtes-vous sûr?",
|
||||
"back_to_login": "Retour connexion",
|
||||
"cancel": "annuler",
|
||||
"certificate": "Certificat",
|
||||
"certificates": "Certificats",
|
||||
"clear": "Clair",
|
||||
"close": "Fermer",
|
||||
"command": "Commander",
|
||||
"commands": "Les commandes",
|
||||
"commands_executed": "commandes exécutées",
|
||||
"compatible": "Compatible",
|
||||
"completed": "Terminé",
|
||||
"config_id": "Config. Identifiant",
|
||||
@@ -36,83 +48,199 @@
|
||||
"connected": "Connecté",
|
||||
"copied": "Copié!",
|
||||
"copy_to_clipboard": "Copier dans le presse-papier",
|
||||
"create": "Créer",
|
||||
"created": "Créé",
|
||||
"created_by": "Créé par",
|
||||
"current": "Actuel",
|
||||
"custom_date": "Date personnalisée",
|
||||
"dashboard": "Tableau de bord",
|
||||
"date": "Rendez-vous amoureux",
|
||||
"day": "journée",
|
||||
"days": "journées",
|
||||
"delete": "Effacer",
|
||||
"delete_device": "Supprimer le périphérique",
|
||||
"details": "Détails",
|
||||
"device": "N° d'appareil{{serialNumber}}",
|
||||
"device_dashboard": "Tableau de bord de l'appareil",
|
||||
"device_delete": "Supprimer l'appareil n°{{serialNumber}}",
|
||||
"device_deleted": "Appareil supprimé avec succès",
|
||||
"device_health": "Santé de l'appareil",
|
||||
"device_list": "Liste des appareils",
|
||||
"device_page": "Page de l'appareil",
|
||||
"device_page": "Vue",
|
||||
"device_status": "Statut du périphérique",
|
||||
"devices": "Dispositifs",
|
||||
"devices_using_latest": "Appareils utilisant le dernier micrologiciel",
|
||||
"devices_using_unknown": "Périphériques utilisant un micrologiciel inconnu",
|
||||
"dismiss": "Rejeter",
|
||||
"do_now": "Faire maintenant!",
|
||||
"download": "Télécharger",
|
||||
"duration": "Durée",
|
||||
"error": "erreur",
|
||||
"edit": "modifier",
|
||||
"edit_user": "Modifier",
|
||||
"email_address": "Adresse électronique",
|
||||
"endpoint": "Point final",
|
||||
"endpoints": "Points de terminaison",
|
||||
"error": "Erreur",
|
||||
"execute_now": "Souhaitez-vous exécuter cette commande maintenant ?",
|
||||
"executed": "réalisé",
|
||||
"exit": "Sortie",
|
||||
"firmware": "Micrologiciel",
|
||||
"firmware_dashboard": "Tableau de bord du micrologiciel",
|
||||
"firmware_installed": "Micrologiciel installé",
|
||||
"forgot_password": "Mot de passe oublié?",
|
||||
"forgot_password_title": "Mot de passe oublié",
|
||||
"from": "De",
|
||||
"general_error": "Erreur API, veuillez consulter votre administrateur",
|
||||
"hide": "Cacher",
|
||||
"hour": "heure",
|
||||
"hours": "heures",
|
||||
"id": "Id",
|
||||
"ip_address": "Adresse IP",
|
||||
"last_dashboard_refresh": "Dernière actualisation du tableau de bord",
|
||||
"later_tonight": "Plus tard ce soir",
|
||||
"latest": "Dernier",
|
||||
"list": "liste",
|
||||
"loading_ellipsis": "Chargement...",
|
||||
"loading_more_ellipsis": "Chargement plus ...",
|
||||
"logout": "Connectez - Out",
|
||||
"mac": "ADRESSE MAC",
|
||||
"manufacturer": "fabricant",
|
||||
"memory_used": "Mémoire utilisée",
|
||||
"minute": "minute",
|
||||
"minutes": "minutes",
|
||||
"na": "N / A",
|
||||
"need_date": "Vous avez besoin d'un rendez-vous...",
|
||||
"no": "Non",
|
||||
"no_devices_found": "Aucun périphérique trouvé",
|
||||
"no_items": "Pas d'objet",
|
||||
"not_connected": "Pas connecté",
|
||||
"of_connected": "% d'appareils",
|
||||
"off": "De",
|
||||
"on": "sur",
|
||||
"optional": "Optionnel",
|
||||
"overall_health": "Santé globale",
|
||||
"password_policy": "Politique de mot de passe",
|
||||
"recorded": "Enregistré",
|
||||
"refresh": "Rafraîchir",
|
||||
"refresh_device": "Actualiser l'appareil",
|
||||
"required": "Champs obligatoires",
|
||||
"result": "Résultat",
|
||||
"save": "Sauvegarder",
|
||||
"saved": "Enregistré!",
|
||||
"saving": "Économie...",
|
||||
"schedule": "Programme",
|
||||
"search": "Rechercher des appareils",
|
||||
"second": "seconde",
|
||||
"seconds": "secondes",
|
||||
"seconds_elapsed": "Secondes écoulées",
|
||||
"serial_number": "Numéro de série",
|
||||
"show_all": "Montre tout",
|
||||
"start": "Début",
|
||||
"submit": "Soumettre",
|
||||
"submitted": "Soumis",
|
||||
"success": "Succès",
|
||||
"system": "Système",
|
||||
"table": "Table",
|
||||
"timestamp": "Temps",
|
||||
"to": "à",
|
||||
"type": "Type",
|
||||
"unable_to_connect": "Impossible de se connecter à l'appareil",
|
||||
"unable_to_delete": "Impossible de supprimer",
|
||||
"unknown": "Inconnu",
|
||||
"up_to_date": "Appareils à jour",
|
||||
"uptimes": "Disponibilités",
|
||||
"uuid": "UUID",
|
||||
"vendors": "Vendeurs",
|
||||
"view_more": "Afficher plus",
|
||||
"yes": "Oui"
|
||||
},
|
||||
"configuration": {
|
||||
"created": "Créé",
|
||||
"details": "Détails de l'appareil",
|
||||
"details": "Détails",
|
||||
"device_password": "Mot de passe",
|
||||
"last_configuration_change": "Dernière modification de configuration",
|
||||
"last_configuration_download": "Téléchargement de la dernière configuration",
|
||||
"location": "Emplacement",
|
||||
"note": "Remarque",
|
||||
"notes": "Remarques",
|
||||
"owner": "Propriétaire",
|
||||
"title": "Configuration de l'appareil",
|
||||
"title": "Configuration",
|
||||
"type": "Type d'appareil",
|
||||
"uuid": "Identifiant de configuration",
|
||||
"view_json": "Afficher le JSON brut"
|
||||
},
|
||||
"configure": {
|
||||
"choose_file": "Vous devez choisir un fichier .json valide :",
|
||||
"enter_new": "Entrez la nouvelle configuration de l'appareil JSON :",
|
||||
"placeholder": "Configurer JSON",
|
||||
"title": "Configurer l'appareil",
|
||||
"title": "Configurer",
|
||||
"valid_json": "Vous devez entrer un JSON valide"
|
||||
},
|
||||
"delete_command": {
|
||||
"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é",
|
||||
"title": "Journaux de l'appareil"
|
||||
"title": "Journaux"
|
||||
},
|
||||
"entity": {
|
||||
"add_child": "Ajouter une entité enfant à {{entityName}}",
|
||||
"add_failure": "Erreur, le serveur a renvoyé : {{error}}",
|
||||
"add_root": "Ajouter une entité racine",
|
||||
"add_success": "Entité créée avec succès !",
|
||||
"cannot_delete": "Vous ne pouvez pas supprimer des entités qui ont des enfants. Supprimez les enfants de cette entité pour pouvoir la supprimer.",
|
||||
"delete_success": "Entité supprimée avec succès",
|
||||
"delete_warning": "Attention : cette opération ne peut pas être annulée",
|
||||
"edit_failure": "Échec de la mise à jour : {{error}}",
|
||||
"entities": "Entités",
|
||||
"entity": "Entité",
|
||||
"only_unassigned": "Uniquement non attribué",
|
||||
"valid_serial": "Doit être un numéro de série valide (12 caractères HEX)"
|
||||
},
|
||||
"factory_reset": {
|
||||
"redirector": "Conserver le redirecteur :",
|
||||
"title": "Dispositif de réinitialisation d'usine",
|
||||
"reset": "Réinitialiser",
|
||||
"resetting": "Réinitialisation…",
|
||||
"title": "Retour aux paramètres d'usine",
|
||||
"warning": "Avertissement : Une fois que vous avez soumis, cela ne peut pas être annulé"
|
||||
},
|
||||
"firmware": {
|
||||
"average_age": "Âge moyen du micrologiciel",
|
||||
"choose_custom": "Choisir",
|
||||
"details_title": "Image #{{image}} Détails",
|
||||
"device_type": "Type d'appareil",
|
||||
"device_types": "Types d'appareils",
|
||||
"downloads": "Téléchargements",
|
||||
"error_fetching_latest": "Erreur lors de la récupération du dernier firmware",
|
||||
"from_release": "De",
|
||||
"history_title": "Historique",
|
||||
"image": "Image",
|
||||
"image_date": "Date de l'image",
|
||||
"installed_firmware": "Micrologiciel installé",
|
||||
"latest_version_installed": "Dernière version installée",
|
||||
"newer_firmware_available": "Révisions plus récentes disponibles",
|
||||
"reinstall_latest": "Réinstaller",
|
||||
"revision": "Révision",
|
||||
"show_dev": "Afficher les versions des développeurs",
|
||||
"size": "Taille",
|
||||
"status": "État du micrologiciel",
|
||||
"title": "Micrologiciel",
|
||||
"to_release": "à",
|
||||
"unknown_firmware_status": "État du micrologiciel inconnu",
|
||||
"upgrade": "Améliorer",
|
||||
"upgrade_command_submitted": "Commande de mise à niveau soumise avec succès",
|
||||
"upgrade_to_latest": "Dernier",
|
||||
"upgrade_to_version": "Mettre à niveau vers cette révision",
|
||||
"upgrading": "Mise à niveau..."
|
||||
},
|
||||
"footer": {
|
||||
"coreui_for_react": "CoreUI pour React",
|
||||
"powered_by": "Alimenté par",
|
||||
@@ -120,43 +248,170 @@
|
||||
},
|
||||
"health": {
|
||||
"sanity": "Santé mentale",
|
||||
"title": "Santé de l'appareil"
|
||||
"title": "Santé"
|
||||
},
|
||||
"inventory": {
|
||||
"add_child_venue": "Ajouter un lieu enfant à {{entityName}}",
|
||||
"add_tag": "Ajouter une étiquette",
|
||||
"add_tag_to": "Ajouter une balise d'inventaire à {{name}}",
|
||||
"assign_error": "Erreur lors de la tentative d'attribution de balise",
|
||||
"assign_to_entity": "Affecter à l'entité",
|
||||
"error_retrieving": "Une erreur s'est produite lors de la récupération des balises d'inventaire",
|
||||
"error_unassign": "Erreur lors de l'opération de désaffectation",
|
||||
"subscriber": "Abonné",
|
||||
"successful_assign": "Tag attribué avec succès",
|
||||
"successful_tag_update": "Balise mise à jour avec succès",
|
||||
"successful_unassign": "L'opération de désaffectation a réussi",
|
||||
"tag_created": "Tag d'inventaire créé avec succès",
|
||||
"tag_creation_error": "Erreur lors de la tentative de création d'une balise d'inventaire",
|
||||
"tag_update_error": "Erreur lors de la mise à jour de la balise",
|
||||
"tags_assigned_to": "Balises d'inventaire attribuées à {{name}}",
|
||||
"unassign": "Annuler l'attribution",
|
||||
"unassign_tag": "Désaffecter la balise de l'entité",
|
||||
"unassigned_tags": "Balises non attribuées",
|
||||
"venue": "Lieu"
|
||||
},
|
||||
"login": {
|
||||
"change_password": "Changer le mot de passe",
|
||||
"change_password_error": "Erreur lors du changement de mot de passe. Assurez-vous que le nouveau mot de passe est valide en visitant la page « Politique de mot de passe »",
|
||||
"change_password_instructions": "Saisissez et confirmez votre nouveau mot de passe",
|
||||
"changing_password": "Modification du mot de passe...",
|
||||
"confirm_new_password": "Confirmer le nouveau mot de passe",
|
||||
"different_passwords": "Vous devez saisir deux fois le même mot de passe",
|
||||
"forgot_password_error": "Erreur lors de la tentative d'envoi de l'e-mail Mot de passe oublié. Veuillez vous assurer que cet identifiant est associé à un compte.",
|
||||
"forgot_password_explanation": "Entrez votre nom d'utilisateur pour recevoir un e-mail contenant les instructions pour réinitialiser votre mot de passe",
|
||||
"forgot_password_success": "Vous devriez bientôt recevoir un e-mail contenant les instructions pour réinitialiser votre mot de passe. S'il vous plaît assurez-vous de vérifier vos spams si vous ne trouvez pas l'e-mail",
|
||||
"logging_in": "Se connecter...",
|
||||
"login": "S'identifier",
|
||||
"login_error": "Erreur de connexion, confirmez que votre nom d'utilisateur, mot de passe et URL de passerelle sont valides",
|
||||
"login_error": "Erreur de connexion, assurez-vous que les informations que vous fournissez sont valides",
|
||||
"new_password": "Nouveau mot de passe",
|
||||
"password": "Mot de passe",
|
||||
"please_enter_gateway": "Veuillez saisir une URL de passerelle",
|
||||
"please_enter_gateway": "Veuillez saisir une URL uCentralSec",
|
||||
"please_enter_password": "s'il vous plait entrez votre mot de passe",
|
||||
"please_enter_username": "s'il vous plaît entrez votre nom d'utilisateur",
|
||||
"previously_used": "Le mot de passe a déjà été utilisé",
|
||||
"send_forgot": "Envoyer un email",
|
||||
"sending_ellipsis": "Envoi...",
|
||||
"sign_in_to_account": "Connectez-vous à votre compte",
|
||||
"url": "URL uCentralSec",
|
||||
"username": "Nom d'utilisateur"
|
||||
},
|
||||
"reboot": {
|
||||
"directions": "Quand souhaitez-vous redémarrer cet appareil ?",
|
||||
"title": "Redémarrez l'appareil"
|
||||
"now": "Souhaitez-vous redémarrer cet appareil maintenant ?",
|
||||
"title": "Redémarrer"
|
||||
},
|
||||
"scan": {
|
||||
"active": "Activer l'analyse active",
|
||||
"channel": "Canal",
|
||||
"directions": "Lancez une analyse wifi de cet appareil, ce qui devrait prendre environ 25 secondes.",
|
||||
"results": "Résultats de l'analyse Wi-Fi"
|
||||
"re_scan": "Nouvelle analyse",
|
||||
"result_directions": "Veuillez cliquer sur le bouton '$t(scan.re_scan)' si vous souhaitez effectuer un scan avec la même configuration que le précédent.",
|
||||
"results": "Résultats de l'analyse Wi-Fi",
|
||||
"scan": "Balayage",
|
||||
"scanning": "Balayage... ",
|
||||
"waiting_directions": "Veuillez attendre le résultat de l'analyse. Cela peut prendre jusqu'à 25 secondes. Vous pouvez quitter et consulter les résultats du tableau des commandes plus tard."
|
||||
},
|
||||
"settings": {
|
||||
"title": "Réglages"
|
||||
},
|
||||
"statistics": {
|
||||
"data": "Données (Ko)",
|
||||
"latest_statistics": "Dernières statistiques",
|
||||
"lifetime_stats": "Statistiques à vie",
|
||||
"no_interfaces": "Aucune statistique de durée de vie de l'interface disponible",
|
||||
"show_latest": "Dernières statistiques",
|
||||
"title": "statistiques"
|
||||
},
|
||||
"status": {
|
||||
"connection_status": "Statut de connexion",
|
||||
"error": "Les données d'état ne sont pas disponibles",
|
||||
"last_contact": "Dernier contact",
|
||||
"load_averages": "Charge (moyenne 1 / 5 / 15 minutes)",
|
||||
"localtime": "heure locale",
|
||||
"memory": "Mémoire utilisée",
|
||||
"percentage_free": "{{percentage}}% de {{total}} gratuit",
|
||||
"percentage_used": "{{percentage}}% de {{total}} utilisé",
|
||||
"title": "#{{serialNumber}} état",
|
||||
"uptime": "La disponibilité",
|
||||
"used_total_memory": "{{used}} utilisé / {{total}} total"
|
||||
},
|
||||
"system": {
|
||||
"error_fetching": "Erreur lors de la récupération des informations système"
|
||||
},
|
||||
"trace": {
|
||||
"choose_network": "Choisir le réseau",
|
||||
"directions": "Lancer une trace à distance de cet appareil pour une durée spécifique ou un nombre de paquets",
|
||||
"download_trace": "Télécharger le fichier de trace",
|
||||
"packets": "Paquets",
|
||||
"title": "Dispositif de traçage"
|
||||
"title": "Trace",
|
||||
"trace": "Trace",
|
||||
"trace_not_successful": "Trace non réussie : la passerelle a signalé l'erreur suivante : {{error}}",
|
||||
"wait_for_file": "Souhaitez-vous attendre que le fichier de trace soit prêt ?",
|
||||
"waiting_directions": "Veuillez attendre le fichier de données de trace. Cela peut prendre un certain temps. Vous pouvez quitter l'attente et récupérer le fichier de trace de la table des commandes plus tard.",
|
||||
"waiting_seconds": "Temps écoulé : {{seconds}} secondes"
|
||||
},
|
||||
"upgrade": {
|
||||
"command_submitted": "Commande soumise",
|
||||
"device_disconnected": "Appareil déconnecté",
|
||||
"device_reconnected": "Appareil reconnecté",
|
||||
"device_upgrading_firmware": "Mise à niveau du micrologiciel de l'appareil",
|
||||
"directions": "Choisissez une heure et une version de firmware pour cet appareil",
|
||||
"firmware_uri": "URI du micrologiciel :",
|
||||
"need_uri": "Vous avez besoin d'un URI...",
|
||||
"new_version": "La nouvelle version est",
|
||||
"offline_device": "Cette option est bloquée car cet appareil n'est pas connecté",
|
||||
"time": "Heure de la mise à niveau :",
|
||||
"title": "Mise à jour du firmware"
|
||||
"title": "Mise à jour du firmware",
|
||||
"upgrade": "Améliorer",
|
||||
"wait_for_upgrade": "Souhaitez-vous attendre la fin de la mise à niveau ?",
|
||||
"waiting_for_device": "En attente de la reconnexion de l'appareil"
|
||||
},
|
||||
"user": {
|
||||
"avatar": "Votre avatar",
|
||||
"avatar_file": "Votre Avatar (max. de 2 Mo)",
|
||||
"create": "Créer un utilisateur",
|
||||
"create_failure": "Erreur lors de la création de l'utilisateur. Veuillez vous assurer que cette adresse e-mail n'est pas déjà liée à un compte.",
|
||||
"create_success": "L'utilisateur a été créé avec succès",
|
||||
"creating": "Création de l'utilisateur...",
|
||||
"delete_avatar": "Supprimer l'avatar",
|
||||
"delete_failure": "Erreur lors de la tentative de suppression de l'utilisateur",
|
||||
"delete_success": "Utilisateur supprimé avec succès !",
|
||||
"delete_title": "Supprimer l'utilisateur",
|
||||
"delete_warning": "Avertissement : Une fois que vous avez supprimé un utilisateur, vous ne pouvez plus revenir en arrière",
|
||||
"deleting": "Suppression ...",
|
||||
"description": "La description",
|
||||
"edit": "Modifier l'utilisateur",
|
||||
"email_address": "Adresse électronique",
|
||||
"force_password_change": "Forcer le changement de mot de passe lors de la connexion",
|
||||
"id": "Identifiant d'utilisateur.",
|
||||
"last_login": "Dernière connexion",
|
||||
"login_id": "Identifiant de connexion.",
|
||||
"my_profile": "Mon profil",
|
||||
"name": "Prénom",
|
||||
"nickname": "Surnom",
|
||||
"nickname_explanation": "Surnom (optionnel)",
|
||||
"not_validated": "Pas valide",
|
||||
"note": "Remarque",
|
||||
"password": "Mot de passe",
|
||||
"provide_email": "Veuillez fournir une adresse email valide",
|
||||
"provide_password": "Veuillez fournir un mot de passe valide",
|
||||
"save_avatar": "Enregistrer l'avatar",
|
||||
"show_hide_password": "Afficher/Masquer le mot de passe",
|
||||
"update_failure": "Assurez-vous que toutes vos données sont valides. Si vous modifiez le mot de passe, assurez-vous qu'il ne s'agit pas d'un ancien.",
|
||||
"update_failure_title": "mise à jour a échoué",
|
||||
"update_success": "L'utilisateur a bien été mis à jour",
|
||||
"update_success_title": "Succès",
|
||||
"user_role": "Rôle",
|
||||
"users": "Utilisateurs",
|
||||
"validated": "Validé"
|
||||
},
|
||||
"wifi_analysis": {
|
||||
"association": "Association",
|
||||
"associations": "Les associations",
|
||||
"mode": "Mode",
|
||||
"network_diagram": "Diagramme de réseau",
|
||||
"radios": "Radios",
|
||||
"title": "Analyse Wi-Fi"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"actions": {
|
||||
"actions": "Ações",
|
||||
"blink": "Piscar",
|
||||
"configure": "Configurar",
|
||||
"connect": "Conectar",
|
||||
@@ -7,28 +8,39 @@
|
||||
"factory_reset": "Restauração de fábrica",
|
||||
"firmware_upgrade": "Atualização de firmware",
|
||||
"reboot": "Reiniciar",
|
||||
"title": "Ações do dispositivo",
|
||||
"title": "Comandos",
|
||||
"trace": "Vestígio",
|
||||
"wifi_scan": "Wifi Scan"
|
||||
"wifi_scan": "Wi-Fi Scan"
|
||||
},
|
||||
"blink": {
|
||||
"blink": "Piscar",
|
||||
"device_leds": "LEDs do dispositivo",
|
||||
"execute_now": "Você gostaria de definir este padrão agora?",
|
||||
"pattern": "Escolha o padrão que deseja usar:",
|
||||
"set_leds": "Definir LEDs",
|
||||
"when_blink_leds": "Quando você gostaria de fazer os LEDs do dispositivo piscarem?"
|
||||
},
|
||||
"commands": {
|
||||
"error": "Erro ao enviar comando!",
|
||||
"success": "Comando enviado com sucesso",
|
||||
"title": "Comandos de dispositivo"
|
||||
"event_queue": "Fila de Eventos",
|
||||
"success": "Comando enviado com sucesso, você pode consultar o log de Comandos para ver o resultado",
|
||||
"title": "Histórico de Comandos",
|
||||
"unable_queue": "Incapaz de completar o pedido de fila de eventos"
|
||||
},
|
||||
"common": {
|
||||
"access_policy": "Política de Acesso",
|
||||
"add": "Adicionar",
|
||||
"adding_ellipsis": "Adicionando ...",
|
||||
"are_you_sure": "Você tem certeza?",
|
||||
"back_to_login": "Volte ao login",
|
||||
"cancel": "Cancelar",
|
||||
"certificate": "Certificado",
|
||||
"certificates": "Certificados",
|
||||
"clear": "Claro",
|
||||
"close": "Perto",
|
||||
"command": "Comando",
|
||||
"commands": "comandos",
|
||||
"commands_executed": "Comandos Executados",
|
||||
"compatible": "Compatível",
|
||||
"completed": "Concluído",
|
||||
"config_id": "Config. Identidade",
|
||||
@@ -36,83 +48,199 @@
|
||||
"connected": "Conectado",
|
||||
"copied": "Copiado!",
|
||||
"copy_to_clipboard": "Copiar para área de transferência",
|
||||
"create": "Crio",
|
||||
"created": "Criado",
|
||||
"created_by": "Criado Por",
|
||||
"current": "Atual",
|
||||
"custom_date": "Data personalizada",
|
||||
"dashboard": "painel de controle",
|
||||
"date": "Encontro",
|
||||
"day": "dia",
|
||||
"days": "dias",
|
||||
"delete": "Excluir",
|
||||
"delete_device": "Apagar dispositivo",
|
||||
"details": "Detalhes",
|
||||
"device": "Dispositivo nº{{serialNumber}}",
|
||||
"device_dashboard": "Painel do dispositivo",
|
||||
"device_delete": "Excluir dispositivo nº{{serialNumber}}",
|
||||
"device_deleted": "Dispositivo excluído com sucesso",
|
||||
"device_health": "Saúde do Dispositivo",
|
||||
"device_list": "Lista de Dispositivos",
|
||||
"device_page": "Página do dispositivo",
|
||||
"device_page": "Visão",
|
||||
"device_status": "Status do dispositivo",
|
||||
"devices": "Devices",
|
||||
"devices_using_latest": "Dispositivos que usam o firmware mais recente",
|
||||
"devices_using_unknown": "Dispositivos que usam firmware desconhecido",
|
||||
"dismiss": "Dispensar",
|
||||
"do_now": "Faça agora!",
|
||||
"download": "Baixar",
|
||||
"duration": "Duração",
|
||||
"edit": "Editar",
|
||||
"edit_user": "Editar",
|
||||
"email_address": "Endereço de e-mail",
|
||||
"endpoint": "Ponto final",
|
||||
"endpoints": "Pontos finais",
|
||||
"error": "Erro",
|
||||
"execute_now": "Você gostaria de executar este comando agora?",
|
||||
"executed": "Executado",
|
||||
"exit": "Saída",
|
||||
"firmware": "Firmware",
|
||||
"firmware_dashboard": "Painel de Firmware",
|
||||
"firmware_installed": "Firmware Instalado",
|
||||
"forgot_password": "Esqueceu sua senha?",
|
||||
"forgot_password_title": "Esqueceu a senha",
|
||||
"from": "De",
|
||||
"general_error": "Erro de API, consulte o seu administrador",
|
||||
"hide": "Ocultar",
|
||||
"hour": "hora",
|
||||
"hours": "horas",
|
||||
"id": "identidade",
|
||||
"ip_address": "Endereço de IP",
|
||||
"last_dashboard_refresh": "Última atualização do painel",
|
||||
"later_tonight": "Logo à noite",
|
||||
"latest": "Mais recentes",
|
||||
"list": "Lista",
|
||||
"loading_ellipsis": "Carregando...",
|
||||
"loading_more_ellipsis": "Carregando mais ...",
|
||||
"logout": "Sair",
|
||||
"mac": "Endereço MAC",
|
||||
"manufacturer": "Fabricante",
|
||||
"memory_used": "Memória Usada",
|
||||
"minute": "minuto",
|
||||
"minutes": "minutos",
|
||||
"na": "N / D",
|
||||
"need_date": "Você precisa de um encontro ...",
|
||||
"no": "Não",
|
||||
"no_devices_found": "Nenhum dispositivo encontrado",
|
||||
"no_items": "Nenhum item",
|
||||
"not_connected": "Não conectado",
|
||||
"of_connected": "% de dispositivos",
|
||||
"off": "Fora",
|
||||
"on": "em",
|
||||
"optional": "Opcional",
|
||||
"overall_health": "Saúde geral",
|
||||
"password_policy": "Política de Senha",
|
||||
"recorded": "Gravado",
|
||||
"refresh": "REFRESH",
|
||||
"refresh_device": "Atualizar dispositivo",
|
||||
"required": "Requeridos",
|
||||
"result": "Resultado",
|
||||
"save": "Salve",
|
||||
"saved": "Salvou!",
|
||||
"saving": "Salvando ...",
|
||||
"schedule": "Cronograma",
|
||||
"search": "Dispositivos de pesquisa",
|
||||
"second": "segundo",
|
||||
"seconds": "segundos",
|
||||
"seconds_elapsed": "Segundos decorridos",
|
||||
"serial_number": "Número de série",
|
||||
"show_all": "mostre tudo",
|
||||
"start": "Começar",
|
||||
"submit": "Enviar",
|
||||
"submitted": "Submetido",
|
||||
"success": "Sucesso",
|
||||
"system": "Sistema",
|
||||
"table": "Mesa",
|
||||
"timestamp": "tempo",
|
||||
"to": "Para",
|
||||
"type": "Tipo",
|
||||
"unable_to_connect": "Incapaz de conectar ao dispositivo",
|
||||
"unable_to_delete": "Incapaz de deletar",
|
||||
"unknown": "Desconhecido",
|
||||
"up_to_date": "Dispositivos atualizados",
|
||||
"uptimes": "Uptimes",
|
||||
"uuid": "UUID",
|
||||
"vendors": "Vendedores",
|
||||
"view_more": "Veja mais",
|
||||
"yes": "sim"
|
||||
},
|
||||
"configuration": {
|
||||
"created": "Criado",
|
||||
"details": "Detalhes do dispositivo",
|
||||
"details": "Detalhes",
|
||||
"device_password": "Senha",
|
||||
"last_configuration_change": "Última Mudança de Configuração",
|
||||
"last_configuration_download": "Último download da configuração",
|
||||
"location": "Localização",
|
||||
"note": "Nota",
|
||||
"notes": "notas",
|
||||
"owner": "Proprietário",
|
||||
"title": "Configuração do dispositivo",
|
||||
"title": "Configuração",
|
||||
"type": "Tipo de dispositivo",
|
||||
"uuid": "ID de configuração",
|
||||
"view_json": "Exibir JSON bruto"
|
||||
},
|
||||
"configure": {
|
||||
"choose_file": "Você precisa escolher um arquivo .json válido:",
|
||||
"enter_new": "Insira a nova configuração do dispositivo JSON:",
|
||||
"placeholder": "Config JSON",
|
||||
"title": "Configurar dispositivo",
|
||||
"title": "Configurar",
|
||||
"valid_json": "Você precisa inserir um JSON válido"
|
||||
},
|
||||
"delete_command": {
|
||||
"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",
|
||||
"title": "Registros de dispositivos"
|
||||
"title": "Toras"
|
||||
},
|
||||
"entity": {
|
||||
"add_child": "Adicionar Entidade Filha a {{entityName}}",
|
||||
"add_failure": "Erro, o servidor retornou: {{error}}",
|
||||
"add_root": "Adicionar Entidade Raiz",
|
||||
"add_success": "Entidade criada com sucesso!",
|
||||
"cannot_delete": "Você não pode excluir entidades que têm filhos. Exclua os filhos desta entidade para poder excluí-la.",
|
||||
"delete_success": "Entidade excluída com sucesso",
|
||||
"delete_warning": "Aviso: esta operação não pode ser revertida",
|
||||
"edit_failure": "Atualização malsucedida: {{error}}",
|
||||
"entities": "Entidades",
|
||||
"entity": "Entidade",
|
||||
"only_unassigned": "Apenas não atribuídos",
|
||||
"valid_serial": "Precisa ser um número de série válido (12 caracteres HEX)"
|
||||
},
|
||||
"factory_reset": {
|
||||
"redirector": "Manter redirecionador:",
|
||||
"title": "Dispositivo de redefinição de fábrica",
|
||||
"reset": "Restabelecer",
|
||||
"resetting": "Reiniciando ...",
|
||||
"title": "Restauração de fábrica",
|
||||
"warning": "Aviso: depois de enviar, isso não pode ser revertido"
|
||||
},
|
||||
"firmware": {
|
||||
"average_age": "Idade Média do Firmware",
|
||||
"choose_custom": "Escolher",
|
||||
"details_title": "Detalhes da imagem #{{image}} ",
|
||||
"device_type": "Tipo de dispositivo",
|
||||
"device_types": "Tipos de dispositivos",
|
||||
"downloads": "Transferências",
|
||||
"error_fetching_latest": "Erro ao buscar o firmware mais recente",
|
||||
"from_release": "De",
|
||||
"history_title": "História",
|
||||
"image": "Imagem",
|
||||
"image_date": "Data da Imagem",
|
||||
"installed_firmware": "Firmware Instalado",
|
||||
"latest_version_installed": "Última versão instalada",
|
||||
"newer_firmware_available": "Novas revisões disponíveis",
|
||||
"reinstall_latest": "Reinstalar",
|
||||
"revision": "Revisão",
|
||||
"show_dev": "Mostrar lançamentos de desenvolvimento",
|
||||
"size": "Tamanho",
|
||||
"status": "Status do firmware",
|
||||
"title": "Firmware",
|
||||
"to_release": "Para",
|
||||
"unknown_firmware_status": "Status de firmware desconhecido",
|
||||
"upgrade": "Melhorar",
|
||||
"upgrade_command_submitted": "Comando de atualização enviado com sucesso",
|
||||
"upgrade_to_latest": "Mais recentes",
|
||||
"upgrade_to_version": "Atualize para esta revisão",
|
||||
"upgrading": "Atualizando ..."
|
||||
},
|
||||
"footer": {
|
||||
"coreui_for_react": "CoreUI para React",
|
||||
"powered_by": "Distribuído por",
|
||||
@@ -120,43 +248,170 @@
|
||||
},
|
||||
"health": {
|
||||
"sanity": "Sanidade",
|
||||
"title": "Saúde do Dispositivo"
|
||||
"title": "Saúde"
|
||||
},
|
||||
"inventory": {
|
||||
"add_child_venue": "Adicionar Local Infantil a {{entityName}}",
|
||||
"add_tag": "Adicionar etiqueta",
|
||||
"add_tag_to": "Adicionar tag de estoque a {{name}}",
|
||||
"assign_error": "Erro ao tentar atribuir tag",
|
||||
"assign_to_entity": "Atribuir à Entidade",
|
||||
"error_retrieving": "Ocorreu um erro ao recuperar as tags de inventário",
|
||||
"error_unassign": "Erro durante operação de cancelamento de atribuição",
|
||||
"subscriber": "Assinante",
|
||||
"successful_assign": "Tag atribuída com sucesso",
|
||||
"successful_tag_update": "Tag atualizada com sucesso",
|
||||
"successful_unassign": "A operação de cancelamento da atribuição foi bem-sucedida",
|
||||
"tag_created": "Tag de inventário criada com sucesso",
|
||||
"tag_creation_error": "Erro ao tentar criar etiqueta de inventário",
|
||||
"tag_update_error": "Erro ao atualizar tag",
|
||||
"tags_assigned_to": "Tags de inventário atribuídas a {{name}}",
|
||||
"unassign": "Cancelar atribuição",
|
||||
"unassign_tag": "Cancelar a atribuição de tag da entidade",
|
||||
"unassigned_tags": "Tags não atribuídas",
|
||||
"venue": "Local"
|
||||
},
|
||||
"login": {
|
||||
"change_password": "Mudar senha",
|
||||
"change_password_error": "Erro ao alterar a senha. Certifique-se de que a nova senha é válida visitando a página 'Política de senha'",
|
||||
"change_password_instructions": "Digite e confirme sua nova senha",
|
||||
"changing_password": "Alterando senha ...",
|
||||
"confirm_new_password": "confirme a nova senha",
|
||||
"different_passwords": "Você precisa inserir a mesma senha duas vezes",
|
||||
"forgot_password_error": "Erro ao tentar enviar e-mail Esqueci a senha. Certifique-se de que este userId esteja associado a uma conta.",
|
||||
"forgot_password_explanation": "Digite seu nome de usuário para receber um e-mail contendo as instruções para redefinir sua senha",
|
||||
"forgot_password_success": "Em breve, você receberá um e-mail com as instruções para redefinir sua senha. Certifique-se de verificar o seu spam se você não conseguir encontrar o e-mail",
|
||||
"logging_in": "Fazendo login ...",
|
||||
"login": "Entrar",
|
||||
"login_error": "Erro de login, confirme se seu nome de usuário, senha e url de gateway são válidos",
|
||||
"login_error": "Erro de login, certifique-se de que as informações que você está fornecendo são válidas",
|
||||
"new_password": "Nova senha",
|
||||
"password": "Senha",
|
||||
"please_enter_gateway": "Insira um URL de gateway",
|
||||
"please_enter_gateway": "Insira um URL uCentralSec",
|
||||
"please_enter_password": "Por favor, insira sua senha",
|
||||
"please_enter_username": "Por favor insira seu nome de usuário",
|
||||
"previously_used": "A senha foi usada anteriormente",
|
||||
"send_forgot": "ENVIAR EMAIL",
|
||||
"sending_ellipsis": "Enviando ...",
|
||||
"sign_in_to_account": "Faça login em sua conta",
|
||||
"url": "URL uCentralSec",
|
||||
"username": "Nome de usuário"
|
||||
},
|
||||
"reboot": {
|
||||
"directions": "Quando você gostaria de reinicializar este dispositivo?",
|
||||
"title": "Reiniciar dispositivo"
|
||||
"now": "Você gostaria de reiniciar este dispositivo agora?",
|
||||
"title": "Reiniciar"
|
||||
},
|
||||
"scan": {
|
||||
"active": "Habilitar varredura ativa",
|
||||
"channel": "Canal",
|
||||
"directions": "Inicie uma verificação de wi-fi deste dispositivo, o que deve levar aproximadamente 25 segundos.",
|
||||
"results": "Resultados da verificação de wi-fi"
|
||||
"re_scan": "Verificar novamente",
|
||||
"result_directions": "Clique no botão '$ t (scan.re_scan)' se desejar fazer uma varredura com a mesma configuração da anterior.",
|
||||
"results": "Resultados da verificação de Wi-Fi",
|
||||
"scan": "Varredura",
|
||||
"scanning": "Scanning... ",
|
||||
"waiting_directions": "Por favor, aguarde o resultado da verificação. Isso pode levar até 25 segundos. Você pode sair e ver os resultados da tabela de comandos mais tarde."
|
||||
},
|
||||
"settings": {
|
||||
"title": "Definições"
|
||||
},
|
||||
"statistics": {
|
||||
"data": "Dados (KB)",
|
||||
"latest_statistics": "Estatísticas mais recentes",
|
||||
"lifetime_stats": "Estatísticas de vida",
|
||||
"no_interfaces": "Nenhuma estatística de tempo de vida da interface disponível",
|
||||
"show_latest": "Últimas estatísticas",
|
||||
"title": "Estatisticas"
|
||||
},
|
||||
"status": {
|
||||
"connection_status": "Status da conexão",
|
||||
"error": "Dados de status indisponíveis",
|
||||
"last_contact": "Último contato",
|
||||
"load_averages": "Carga (1/5/15 minutos em média)",
|
||||
"localtime": "Horário local",
|
||||
"memory": "Memória Usada",
|
||||
"percentage_free": "{{percentage}}% de {{total}} grátis",
|
||||
"percentage_used": "{{percentage}}% de {{total}} usado",
|
||||
"title": "#{{serialNumber}} status",
|
||||
"uptime": "Tempo de atividade",
|
||||
"used_total_memory": "{{used}} usado / {{total}} total"
|
||||
},
|
||||
"system": {
|
||||
"error_fetching": "Erro ao buscar informações do sistema"
|
||||
},
|
||||
"trace": {
|
||||
"choose_network": "Escolha a rede",
|
||||
"directions": "Lançar um rastreamento remoto deste dispositivo para uma duração específica ou um número de pacotes",
|
||||
"download_trace": "Baixar arquivo de rastreamento",
|
||||
"packets": "Pacotes",
|
||||
"title": "Dispositivo de rastreamento"
|
||||
"title": "Vestígio",
|
||||
"trace": "Vestígio",
|
||||
"trace_not_successful": "O rastreamento não foi bem-sucedido: o gateway relatou o seguinte erro: {{error}}",
|
||||
"wait_for_file": "Você gostaria de esperar até que o arquivo de rastreamento esteja pronto?",
|
||||
"waiting_directions": "Aguarde o arquivo de dados de rastreamento. Isto pode tomar algum tempo. Você pode sair da espera e recuperar o arquivo de rastreamento da tabela de comandos mais tarde.",
|
||||
"waiting_seconds": "Tempo decorrido: {{seconds}} segundos"
|
||||
},
|
||||
"upgrade": {
|
||||
"command_submitted": "Comando enviado",
|
||||
"device_disconnected": "Dispositivo desconectado",
|
||||
"device_reconnected": "Dispositivo reconectado",
|
||||
"device_upgrading_firmware": "Firmware de atualização de dispositivo",
|
||||
"directions": "Escolha um horário e uma versão de firmware para este dispositivo",
|
||||
"firmware_uri": "URI de firmware:",
|
||||
"need_uri": "Você precisa de um URI ...",
|
||||
"new_version": "Nova versão é",
|
||||
"offline_device": "Esta opção está bloqueada porque este dispositivo não está conectado",
|
||||
"time": "Tempo de atualização:",
|
||||
"title": "Atualização de firmware"
|
||||
"title": "Atualização de firmware",
|
||||
"upgrade": "Melhorar",
|
||||
"wait_for_upgrade": "Você gostaria de esperar a conclusão da atualização?",
|
||||
"waiting_for_device": "Esperando que o dispositivo se reconecte"
|
||||
},
|
||||
"user": {
|
||||
"avatar": "Seu avatar",
|
||||
"avatar_file": "Seu avatar (máx. De 2 MB)",
|
||||
"create": "Criar usuário",
|
||||
"create_failure": "Erro ao criar usuário. Certifique-se de que este endereço de e-mail ainda não esteja vinculado a uma conta.",
|
||||
"create_success": "Usuário criado com sucesso",
|
||||
"creating": "Criando usuário ...",
|
||||
"delete_avatar": "Apagar Avatar",
|
||||
"delete_failure": "Erro ao tentar excluir usuário",
|
||||
"delete_success": "Usuário excluído com sucesso!",
|
||||
"delete_title": "Deletar usuário",
|
||||
"delete_warning": "Aviso: depois de excluir um usuário, você não pode reverter",
|
||||
"deleting": "Excluindo ...",
|
||||
"description": "Descrição",
|
||||
"edit": "Editar usuário",
|
||||
"email_address": "Endereço de e-mail",
|
||||
"force_password_change": "Forçar mudança de senha no login",
|
||||
"id": "ID do usuário.",
|
||||
"last_login": "Último login",
|
||||
"login_id": "Identificação de usuário.",
|
||||
"my_profile": "Meu perfil",
|
||||
"name": "Nome",
|
||||
"nickname": "Apelido",
|
||||
"nickname_explanation": "Apelido (opcional)",
|
||||
"not_validated": "Não validado",
|
||||
"note": "Nota",
|
||||
"password": "Senha",
|
||||
"provide_email": "Por favor, forneça um endereço de e-mail válido",
|
||||
"provide_password": "Forneça uma senha válida",
|
||||
"save_avatar": "Salvar Avatar",
|
||||
"show_hide_password": "Mostrar / ocultar senha",
|
||||
"update_failure": "Certifique-se de que todos os seus dados são válidos. Se você estiver modificando a senha, certifique-se de que não seja uma senha antiga.",
|
||||
"update_failure_title": "Atualização falhou",
|
||||
"update_success": "Usuário atualizado com sucesso",
|
||||
"update_success_title": "Sucesso",
|
||||
"user_role": "Função",
|
||||
"users": "Comercial",
|
||||
"validated": "Validado"
|
||||
},
|
||||
"wifi_analysis": {
|
||||
"association": "Associação",
|
||||
"associations": "Associações",
|
||||
"mode": "Modo",
|
||||
"network_diagram": "Diagrama de rede",
|
||||
"radios": "Rádios",
|
||||
"title": "Análise de Wi-Fi"
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 4.1 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 12 KiB |
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "favicon.svg",
|
||||
"type": "image/svg",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "favicon.svg",
|
||||
"type": "image/svg",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
@@ -1,165 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.2.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 141.5 185.6" style="enable-background:new 0 0 141.5 185.6;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#414141;}
|
||||
.st1{fill:#FFFFFF;}
|
||||
.st2{fill:#FED206;}
|
||||
.st3{fill:#EB6F53;}
|
||||
.st4{fill:#3BA9B6;}
|
||||
</style>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st0" d="M120.7,183.9H21.5c-10.8,0-19.5-8.7-19.5-19.5V20.5c0-10.8,8.7-19.5,19.5-19.5h99.2
|
||||
c10.8,0,19.5,8.7,19.5,19.5v143.9C140.2,175.2,131.5,183.9,120.7,183.9z"/>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st1" d="M46.3,166.2v-3.4h-1.2v-0.6h3.1v0.6H47v3.4H46.3z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st1" d="M49,166.2v-4h2.7v0.6h-2v1h2v0.6h-2v1.1h2v0.6H49z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st1" d="M52.6,166.2v-4h0.7v3.4h1.8v0.6H52.6z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st1" d="M55.7,166.2v-4h2.7v0.6h-2v1h2v0.6h-2v1.1h2v0.6H55.7z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st1" d="M59.1,164.2c0-1.2,0.9-2.1,2.1-2.1c0.8,0,1.3,0.4,1.6,0.9l-0.6,0.3c-0.2-0.3-0.6-0.6-1-0.6
|
||||
c-0.8,0-1.4,0.6-1.4,1.4c0,0.8,0.6,1.4,1.4,1.4c0.4,0,0.8-0.3,1-0.6l0.6,0.3c-0.3,0.5-0.8,0.9-1.6,0.9
|
||||
C60,166.3,59.1,165.5,59.1,164.2z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st1" d="M63.2,164.2c0-1.2,0.8-2.1,2-2.1c1.2,0,2,0.9,2,2.1c0,1.2-0.8,2.1-2,2.1C64,166.3,63.2,165.4,63.2,164.2z
|
||||
M66.5,164.2c0-0.8-0.5-1.4-1.3-1.4c-0.8,0-1.3,0.6-1.3,1.4c0,0.8,0.5,1.4,1.3,1.4C66,165.7,66.5,165,66.5,164.2z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st1" d="M71.3,166.2v-3.1l-1.2,3.1h-0.3l-1.2-3.1v3.1h-0.7v-4h1l1.1,2.7l1.1-2.7h1v4H71.3z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st1" d="M75.7,166.2v-4h0.7v4H75.7z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st1" d="M80.4,166.2l-2.1-2.8v2.8h-0.7v-4h0.7l2,2.8v-2.8h0.7v4H80.4z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st1" d="M82.3,166.2v-4H85v0.6h-2v1h2v0.6h-2v1.7H82.3z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st1" d="M87.9,166.2l-0.9-1.5h-0.7v1.5h-0.7v-4h1.7c0.8,0,1.3,0.5,1.3,1.2c0,0.7-0.5,1.1-0.9,1.2l1,1.6H87.9z
|
||||
M88,163.5c0-0.4-0.3-0.6-0.7-0.6h-1v1.3h1C87.7,164.1,88,163.9,88,163.5z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st1" d="M92.4,166.2l-0.3-0.8h-1.8l-0.3,0.8h-0.8l1.6-4h0.9l1.6,4H92.4z M91.2,162.9l-0.7,1.9h1.4L91.2,162.9z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st1" d="M95.8,166.2v-4h1.5c0.8,0,1.2,0.5,1.2,1.2c0,0.6-0.4,1.2-1.2,1.2h-1.2v1.7H95.8z M98.2,163.4
|
||||
c0-0.5-0.3-0.9-0.9-0.9h-1.1v1.7h1.1C97.8,164.3,98.2,163.9,98.2,163.4z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st1" d="M101.5,166.2l-1.1-1.6h-0.9v1.6h-0.3v-4h1.5c0.7,0,1.2,0.4,1.2,1.2c0,0.7-0.5,1.1-1.1,1.1l1.2,1.7H101.5z
|
||||
M101.6,163.4c0-0.5-0.4-0.9-0.9-0.9h-1.1v1.7h1.1C101.2,164.3,101.6,163.9,101.6,163.4z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st1" d="M102.8,164.2c0-1.2,0.8-2.1,1.9-2.1c1.2,0,1.9,0.9,1.9,2.1c0,1.2-0.8,2.1-1.9,2.1
|
||||
C103.6,166.3,102.8,165.4,102.8,164.2z M106.3,164.2c0-1-0.6-1.7-1.6-1.7c-1,0-1.6,0.7-1.6,1.7c0,1,0.6,1.7,1.6,1.7
|
||||
C105.7,166,106.3,165.2,106.3,164.2z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st1" d="M106.9,165.8l0.2-0.3c0.2,0.2,0.4,0.4,0.8,0.4c0.5,0,0.9-0.4,0.9-0.9v-2.8h0.3v2.8c0,0.8-0.5,1.2-1.2,1.2
|
||||
C107.5,166.3,107.2,166.1,106.9,165.8z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st1" d="M110.4,166.2v-4h2.5v0.3h-2.2v1.5h2.1v0.3h-2.1v1.6h2.2v0.3H110.4z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st1" d="M113.5,164.2c0-1.2,0.9-2.1,2-2.1c0.6,0,1.1,0.3,1.5,0.7l-0.3,0.2c-0.3-0.3-0.7-0.6-1.2-0.6
|
||||
c-0.9,0-1.7,0.7-1.7,1.7c0,1,0.7,1.7,1.7,1.7c0.5,0,0.9-0.2,1.2-0.6l0.3,0.2c-0.4,0.4-0.8,0.7-1.5,0.7
|
||||
C114.4,166.3,113.5,165.5,113.5,164.2z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st1" d="M118.7,166.2v-3.7h-1.3v-0.3h2.9v0.3H119v3.7H118.7z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<polygon class="st1" points="26.3,163.8 31.6,158.5 36.9,163.8 37.7,163.8 31.6,157.6 25.5,163.8 "/>
|
||||
<polygon class="st1" points="36.9,164.7 31.6,170 26.3,164.7 25.5,164.7 31.6,170.8 37.7,164.7 "/>
|
||||
<polygon class="st1" points="31,163.8 36.3,158.5 41.6,163.8 42.5,163.8 36.3,157.6 30.2,163.8 "/>
|
||||
<polygon class="st1" points="41.6,164.7 36.3,170 31,164.7 30.2,164.7 36.3,170.8 42.5,164.7 "/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st1" d="M33.2,100.7c-4.6,0-8.3,3.7-8.3,8.3s3.7,8.3,8.3,8.3s8.3-3.7,8.3-8.3S37.8,100.7,33.2,100.7z"/>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st2" d="M33.2,35.2c40.7,0,73.8,33.1,73.8,73.8c0,0.7,0,1.4,0,2.1c0,1.7,0.6,3.3,1.7,4.6c1.2,1.2,2.8,1.9,4.5,2
|
||||
l0.2,0c3.5,0,6.3-2.7,6.4-6.2c0-0.8,0-1.7,0-2.5c0-47.7-38.8-86.6-86.6-86.6c-0.8,0-1.7,0-2.5,0c-1.7,0-3.3,0.8-4.5,2
|
||||
c-1.2,1.2-1.8,2.9-1.7,4.6c0.1,3.5,3,6.3,6.6,6.2C31.8,35.2,32.5,35.2,33.2,35.2z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st3" d="M33.2,60.5c26.7,0,48.5,21.7,48.5,48.5c0,0.6,0,1.3,0,2c-0.1,1.7,0.5,3.3,1.7,4.6c1.2,1.3,2.7,2,4.4,2.1
|
||||
c1.7,0.1,3.3-0.5,4.6-1.7c1.2-1.2,2-2.7,2-4.4c0-0.9,0.1-1.8,0.1-2.6c0-33.8-27.5-61.2-61.2-61.2c-0.8,0-1.6,0-2.6,0.1
|
||||
c-1.7,0.1-3.3,0.8-4.4,2.1c-1.2,1.3-1.8,2.9-1.7,4.6s0.8,3.3,2.1,4.4c1.3,1.2,2.9,1.8,4.6,1.7C31.9,60.5,32.6,60.5,33.2,60.5z"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st4" d="M33.2,86.7c12.3,0,22.3,10,22.3,22.3c0,0.5,0,1.1-0.1,1.8c-0.3,3.5,2.3,6.6,5.8,6.9
|
||||
c3.5,0.3,6.6-2.3,6.9-5.8c0.1-1,0.1-1.9,0.1-2.8c0-19.3-15.7-35.1-35.1-35.1c-0.9,0-1.8,0-2.8,0.1c-1.7,0.1-3.2,0.9-4.3,2.2
|
||||
c-1.1,1.3-1.6,2.9-1.5,4.6c0.1,1.7,0.9,3.2,2.2,4.3c1.3,1.1,2.9,1.6,4.6,1.5C32.1,86.7,32.7,86.7,33.2,86.7z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st1" d="M35.8,130.4c1.1,0.6,2.1,1.5,2.7,2.6c0.7,1.1,1,2.3,1,3.7s-0.3,2.6-1,3.7c-0.7,1.1-1.6,2-2.7,2.6
|
||||
c-1.1,0.6-2.4,1-3.8,1s-2.7-0.3-3.8-1c-1.1-0.6-2.1-1.5-2.7-2.6c-0.7-1.1-1-2.3-1-3.7c0-1.3,0.3-2.6,1-3.7c0.7-1.1,1.6-2,2.7-2.6
|
||||
c1.1-0.6,2.4-0.9,3.8-0.9C33.4,129.5,34.7,129.8,35.8,130.4z M29.9,132.9c-0.7,0.4-1.2,0.9-1.6,1.6s-0.6,1.4-0.6,2.2
|
||||
c0,0.8,0.2,1.6,0.6,2.3c0.4,0.7,0.9,1.2,1.6,1.6c0.7,0.4,1.4,0.6,2.1,0.6c0.8,0,1.5-0.2,2.1-0.6c0.6-0.4,1.2-0.9,1.5-1.6
|
||||
c0.4-0.7,0.6-1.4,0.6-2.3c0-0.8-0.2-1.6-0.6-2.2s-0.9-1.2-1.5-1.6c-0.6-0.4-1.4-0.6-2.1-0.6C31.3,132.3,30.6,132.5,29.9,132.9z"/>
|
||||
<path class="st1" d="M50.6,133.6c0.8,0.5,1.4,1.1,1.8,2c0.4,0.8,0.6,1.8,0.6,2.9c0,1.1-0.2,2-0.6,2.8c-0.4,0.8-1,1.5-1.8,1.9
|
||||
c-0.8,0.5-1.6,0.7-2.6,0.7c-0.7,0-1.4-0.1-2-0.4s-1.1-0.7-1.5-1.2v5.4h-3.1V133h3.1v1.6c0.4-0.5,0.9-1,1.4-1.2s1.2-0.4,2-0.4
|
||||
C48.9,132.9,49.8,133.1,50.6,133.6z M49.1,140.5c0.5-0.6,0.7-1.3,0.7-2.2c0-0.9-0.2-1.6-0.7-2.1c-0.5-0.6-1.1-0.8-1.9-0.8
|
||||
s-1.4,0.3-1.9,0.8c-0.5,0.6-0.8,1.3-0.8,2.1c0,0.9,0.2,1.6,0.8,2.2s1.1,0.8,1.9,0.8S48.6,141,49.1,140.5z"/>
|
||||
<path class="st1" d="M63.4,134.4c0.9,1,1.4,2.4,1.4,4.2c0,0.3,0,0.6,0,0.7H57c0.2,0.7,0.5,1.2,1,1.6c0.5,0.4,1.1,0.6,1.8,0.6
|
||||
c0.5,0,1-0.1,1.5-0.3s0.9-0.5,1.3-0.9l1.6,1.6c-0.5,0.6-1.2,1.1-2,1.4c-0.8,0.3-1.6,0.5-2.6,0.5c-1.1,0-2.1-0.2-3-0.7
|
||||
s-1.5-1.1-2-1.9c-0.5-0.8-0.7-1.8-0.7-2.9c0-1.1,0.2-2.1,0.7-2.9s1.1-1.5,2-1.9c0.8-0.5,1.8-0.7,2.9-0.7
|
||||
C61.2,132.9,62.5,133.4,63.4,134.4z M61.8,137.5c0-0.7-0.3-1.3-0.7-1.7s-1-0.6-1.7-0.6c-0.7,0-1.2,0.2-1.7,0.6
|
||||
c-0.4,0.4-0.7,1-0.9,1.7H61.8z"/>
|
||||
<path class="st1" d="M76.2,134c0.7,0.7,1.1,1.7,1.1,3v6.8h-3.1v-5.9c0-0.7-0.2-1.2-0.6-1.6s-0.9-0.6-1.5-0.6
|
||||
c-0.8,0-1.4,0.3-1.8,0.8c-0.4,0.5-0.7,1.2-0.7,2v5.3h-3.1V133h3.1v1.9c0.7-1.3,2-2,3.7-2C74.6,132.8,75.5,133.2,76.2,134z"/>
|
||||
<path class="st1" d="M96,129.7h3.3l-4.7,14h-3.3l-2.9-10.1l-3,10.1h-3.2l-4.7-14h3.4l3,10.7l3-10.7H90l3.1,10.7L96,129.7z"/>
|
||||
<path class="st1" d="M103.3,128.7c0.3,0.3,0.5,0.7,0.5,1.2s-0.2,0.9-0.5,1.2c-0.3,0.3-0.7,0.5-1.2,0.5c-0.5,0-0.9-0.2-1.2-0.5
|
||||
c-0.3-0.3-0.5-0.7-0.5-1.2c0-0.5,0.2-0.9,0.5-1.2c0.3-0.3,0.7-0.5,1.2-0.5C102.6,128.2,103,128.3,103.3,128.7z M100.6,133h3.1
|
||||
v10.8h-3.1V133z"/>
|
||||
<path class="st1" d="M106.5,129.7h10.1l0,2.6h-6.9v3.4h6.3v2.6h-6.3v5.3h-3.2V129.7z"/>
|
||||
<path class="st1" d="M120.9,128.7c0.3,0.3,0.5,0.7,0.5,1.2s-0.2,0.9-0.5,1.2c-0.3,0.3-0.7,0.5-1.2,0.5c-0.5,0-0.9-0.2-1.2-0.5
|
||||
c-0.3-0.3-0.5-0.7-0.5-1.2c0-0.5,0.2-0.9,0.5-1.2c0.3-0.3,0.7-0.5,1.2-0.5C120.1,128.2,120.5,128.3,120.9,128.7z M118.1,133h3.1
|
||||
v10.8h-3.1V133z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 8.0 KiB |
@@ -1,2 +0,0 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
49
src/App.js
49
src/App.js
@@ -1,7 +1,10 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { HashRouter, Route, Switch } from 'react-router-dom';
|
||||
import React from 'react';
|
||||
import { HashRouter, Switch } from 'react-router-dom';
|
||||
import 'scss/style.scss';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import Router from 'router';
|
||||
import { AuthProvider } from 'ucentral-libs';
|
||||
import { checkIfJson } from 'utils/helper';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
|
||||
const loading = (
|
||||
<div className="pt-3 text-center">
|
||||
@@ -9,32 +12,26 @@ const loading = (
|
||||
</div>
|
||||
);
|
||||
|
||||
const TheLayout = React.lazy(() => import('layout'));
|
||||
const Login = React.lazy(() => import('pages/LoginPage'));
|
||||
|
||||
const App = () => {
|
||||
const isLoggedIn = useSelector((state) => state.connected);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
const token = sessionStorage.getItem('access_token');
|
||||
if (token !== undefined && token !== null) {
|
||||
dispatch({ type: 'set', connected: true });
|
||||
}
|
||||
}, [dispatch]);
|
||||
const storageToken = sessionStorage.getItem('access_token');
|
||||
const apiEndpoints = checkIfJson(sessionStorage.getItem('gateway_endpoints'))
|
||||
? JSON.parse(sessionStorage.getItem('gateway_endpoints'))
|
||||
: {};
|
||||
|
||||
return (
|
||||
<HashRouter>
|
||||
<React.Suspense fallback={loading}>
|
||||
<Switch>
|
||||
<Route
|
||||
path="/"
|
||||
name="Devices"
|
||||
render={(props) => (isLoggedIn ? <TheLayout {...props} /> : <Login {...props} />)}
|
||||
/>
|
||||
</Switch>
|
||||
</React.Suspense>
|
||||
</HashRouter>
|
||||
<AuthProvider
|
||||
axiosInstance={axiosInstance}
|
||||
token={storageToken ?? ''}
|
||||
apiEndpoints={apiEndpoints}
|
||||
>
|
||||
<HashRouter>
|
||||
<React.Suspense fallback={loading}>
|
||||
<Switch>
|
||||
<Router />
|
||||
</Switch>
|
||||
</React.Suspense>
|
||||
</HashRouter>
|
||||
</AuthProvider>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 24 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 24 KiB |
@@ -1,28 +1,4 @@
|
||||
import {
|
||||
cibSkype,
|
||||
cibFacebook,
|
||||
cibTwitter,
|
||||
cibLinkedin,
|
||||
cibFlickr,
|
||||
cibTumblr,
|
||||
cibXing,
|
||||
cibGithub,
|
||||
cibStackoverflow,
|
||||
cibYoutube,
|
||||
cibDribbble,
|
||||
cibInstagram,
|
||||
cibPinterest,
|
||||
cibVk,
|
||||
cibYahoo,
|
||||
cibBehance,
|
||||
cibReddit,
|
||||
cibVimeo,
|
||||
cibCcMastercard,
|
||||
cibCcVisa,
|
||||
cibStripe,
|
||||
cibPaypal,
|
||||
cibGooglePay,
|
||||
cibCcAmex,
|
||||
cifUs,
|
||||
cifBr,
|
||||
cifIn,
|
||||
@@ -122,14 +98,8 @@ import {
|
||||
cilXCircle,
|
||||
cilWarning,
|
||||
} from '@coreui/icons';
|
||||
import { sygnet } from './sygnet';
|
||||
import { logo } from './logo';
|
||||
import { logoNegative } from './logo-negative';
|
||||
|
||||
export const icons = {
|
||||
sygnet,
|
||||
logo,
|
||||
logoNegative,
|
||||
cilAlignCenter,
|
||||
cilAlignLeft,
|
||||
cilAlignRight,
|
||||
@@ -228,28 +198,4 @@ export const icons = {
|
||||
cifFr,
|
||||
cifEs,
|
||||
cifPl,
|
||||
cibSkype,
|
||||
cibFacebook,
|
||||
cibTwitter,
|
||||
cibLinkedin,
|
||||
cibFlickr,
|
||||
cibTumblr,
|
||||
cibXing,
|
||||
cibGithub,
|
||||
cibStackoverflow,
|
||||
cibYoutube,
|
||||
cibDribbble,
|
||||
cibInstagram,
|
||||
cibPinterest,
|
||||
cibVk,
|
||||
cibYahoo,
|
||||
cibBehance,
|
||||
cibReddit,
|
||||
cibVimeo,
|
||||
cibCcMastercard,
|
||||
cibCcVisa,
|
||||
cibStripe,
|
||||
cibPaypal,
|
||||
cibGooglePay,
|
||||
cibCcAmex,
|
||||
};
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
export const logoNegative = [
|
||||
'608 134',
|
||||
`
|
||||
<title>coreui react pro logo</title>
|
||||
<g>
|
||||
<g style="fill:#80d0ff;">
|
||||
<path d="M362.0177,90.1512,353.25,69.4149a.2507.2507,0,0,0-.2559-.1914H343.01a.2263.2263,0,0,0-.2559.2559V90.0233a.5657.5657,0,0,1-.64.64h-1.2163a.5652.5652,0,0,1-.64-.64V46.5028a.5655.5655,0,0,1,.64-.64H353.442a9.9792,9.9792,0,0,1,7.7437,3.2324A12.2,12.2,0,0,1,364.13,57.64a12.4389,12.4389,0,0,1-2.24,7.584,9.37,9.37,0,0,1-6.08,3.7441c-.1709.086-.2139.1915-.128.3194l8.7041,20.6084.064.2558q0,.5127-.5757.5118h-1.1523A.703.703,0,0,1,362.0177,90.1512ZM342.754,48.3593v18.496a.2259.2259,0,0,0,.2559.2559h10.3037a7.6713,7.6713,0,0,0,6.0166-2.5918,9.8807,9.8807,0,0,0,2.3037-6.8164,10.2875,10.2875,0,0,0-2.272-6.9756,7.6033,7.6033,0,0,0-6.0483-2.624H343.01A.2263.2263,0,0,0,342.754,48.3593Z"/>
|
||||
<path d="M401.3263,48.1034H381.2945a.2262.2262,0,0,0-.2558.2559v18.496a.2259.2259,0,0,0,.2558.2559h13.8238a.5664.5664,0,0,1,.6406.64v.96a.5663.5663,0,0,1-.6406.6406H381.2945a.2263.2263,0,0,0-.2558.2559v18.56a.2258.2258,0,0,0,.2558.2558h20.0318a.5671.5671,0,0,1,.6406.6407v.96a.566.566,0,0,1-.6406.64H379.1827a.5653.5653,0,0,1-.64-.64V46.5028a.5656.5656,0,0,1,.64-.64h22.1436a.5664.5664,0,0,1,.6406.64v.96A.5663.5663,0,0,1,401.3263,48.1034Z"/>
|
||||
<path d="M439.047,90.1512l-2.4317-8.832a.2971.2971,0,0,0-.32-.1924H419.5274a.2957.2957,0,0,0-.32.1924l-2.3681,8.7676a.6577.6577,0,0,1-.7036.5762H414.919a.5385.5385,0,0,1-.5756-.7041l12.0317-43.584a.6436.6436,0,0,1,.7041-.5117h1.6a.6442.6442,0,0,1,.7041.5117l12.16,43.584.0644.1923q0,.5127-.64.5118h-1.2163A.6428.6428,0,0,1,439.047,90.1512ZM419.9435,78.9188a.3031.3031,0,0,0,.2236.0967h15.4883a.3048.3048,0,0,0,.2236-.0967c.0645-.0635.0742-.1162.0322-.1592l-7.872-28.9287c-.043-.0849-.086-.1279-.128-.1279s-.0859.043-.1279.1279L419.9112,78.76C419.8683,78.8026,419.879,78.8553,419.9435,78.9188Z"/>
|
||||
<path d="M456.6017,87.911a11.6372,11.6372,0,0,1-3.3277-8.7041V57.1913a11.4158,11.4158,0,0,1,3.36-8.5762,12.0941,12.0941,0,0,1,8.8-3.2637,12.2566,12.2566,0,0,1,8.8643,3.2315,11.3927,11.3927,0,0,1,3.36,8.6084v.64a.5663.5663,0,0,1-.6406.6407l-1.28.0634q-.6408,0-.64-.5761v-.8321a9.289,9.289,0,0,0-2.6558-6.9121,10.6734,10.6734,0,0,0-14.0161,0,9.2854,9.2854,0,0,0-2.6563,6.9121V79.3993a9.2808,9.2808,0,0,0,2.6563,6.9121,10.67,10.67,0,0,0,14.0161,0,9.2843,9.2843,0,0,0,2.6558-6.9121v-.7686q0-.5757.64-.5752l1.28.0635a.5667.5667,0,0,1,.6406.6406v.5118a11.4952,11.4952,0,0,1-3.36,8.64,13.6227,13.6227,0,0,1-17.6963,0Z"/>
|
||||
<path d="M514.4376,46.5028v.96a.5658.5658,0,0,1-.64.6406H503.046a.2263.2263,0,0,0-.2559.2559v41.664a.566.566,0,0,1-.6406.64h-1.2158a.5652.5652,0,0,1-.64-.64V48.3593a.2266.2266,0,0,0-.2558-.2559H489.8619a.5656.5656,0,0,1-.64-.6406v-.96a.5656.5656,0,0,1,.64-.64H513.798A.5658.5658,0,0,1,514.4376,46.5028Z"/>
|
||||
<path d="M522.0665,89.5116a2.8385,2.8385,0,0,1-.8-2.0488,2.9194,2.9194,0,0,1,.8-2.1114,2.7544,2.7544,0,0,1,2.08-.832,2.8465,2.8465,0,0,1,2.9438,2.9434,2.7541,2.7541,0,0,1-.832,2.08,2.9221,2.9221,0,0,1-2.1118.8008A2.754,2.754,0,0,1,522.0665,89.5116Z"/>
|
||||
<path d="M542.4054,88.0077a11.3123,11.3123,0,0,1-3.2-8.416v-5.44a.5656.5656,0,0,1,.64-.64h1.2158a.5661.5661,0,0,1,.64.64v5.5039a9.1424,9.1424,0,0,0,2.5283,6.72,8.9745,8.9745,0,0,0,6.6875,2.5605,8.7908,8.7908,0,0,0,9.28-9.28V46.5028a.5655.5655,0,0,1,.64-.64h1.2163a.566.566,0,0,1,.64.64V79.5917a11.2545,11.2545,0,0,1-3.2325,8.416,13.0618,13.0618,0,0,1-17.0556,0Z"/>
|
||||
<path d="M580.35,88.1034a10.4859,10.4859,0,0,1-3.36-8.1279v-1.792a.5663.5663,0,0,1,.64-.6407h1.0884a.5668.5668,0,0,1,.64.6407v1.6a8.5459,8.5459,0,0,0,2.752,6.6562,10.5353,10.5353,0,0,0,7.36,2.4961,9.8719,9.8719,0,0,0,6.9761-2.3681,8.2161,8.2161,0,0,0,2.56-6.336,8.4,8.4,0,0,0-1.12-4.416,11.3812,11.3812,0,0,0-3.3281-3.3926,71.6714,71.6714,0,0,0-6.1763-3.7119,71.0479,71.0479,0,0,1-6.24-3.84,12.1711,12.1711,0,0,1-3.4238-3.68,10.2614,10.2614,0,0,1-1.28-5.3438,9.8579,9.8579,0,0,1,3.0718-7.7441,12.0122,12.0122,0,0,1,8.32-2.752q5.6954,0,8.96,3.1036a10.8251,10.8251,0,0,1,3.2642,8.2246v1.6a.5658.5658,0,0,1-.64.64h-1.1519a.5652.5652,0,0,1-.64-.64V56.8075a8.8647,8.8647,0,0,0-2.624-6.6885,9.9933,9.9933,0,0,0-7.232-2.5273,9.37,9.37,0,0,0-6.5278,2.1435,7.8224,7.8224,0,0,0-2.3682,6.1123,7.8006,7.8006,0,0,0,1.0244,4.16,10.387,10.387,0,0,0,3.0078,3.0391,62.8714,62.8714,0,0,0,5.9522,3.4882,71.0575,71.0575,0,0,1,6.72,4.2559,13.4674,13.4674,0,0,1,3.648,3.9365,10.049,10.049,0,0,1,1.28,5.1836,10.7177,10.7177,0,0,1-3.2637,8.1924q-3.2637,3.0717-8.832,3.0723Q583.71,91.1757,580.35,88.1034Z"/>
|
||||
</g>
|
||||
|
||||
<g style="fill:#fff;">
|
||||
<g>
|
||||
<path d="M99.835,36.0577l-39-22.5167a12,12,0,0,0-12,0l-39,22.5166a12.0339,12.0339,0,0,0-6,10.3924V91.4833a12.0333,12.0333,0,0,0,6,10.3923l39,22.5167a12,12,0,0,0,12,0l39-22.5167a12.0331,12.0331,0,0,0,6-10.3923V46.45A12.0334,12.0334,0,0,0,99.835,36.0577Zm-2,55.4256a4,4,0,0,1-2,3.4641l-39,22.5167a4.0006,4.0006,0,0,1-4,0l-39-22.5167a4,4,0,0,1-2-3.4641V46.45a4,4,0,0,1,2-3.4642l39-22.5166a4,4,0,0,1,4,0l39,22.5166a4,4,0,0,1,2,3.4642Z"/>
|
||||
<path d="M77.8567,82.0046h-2.866a4,4,0,0,0-1.9247.4934L55.7852,91.9833,35.835,80.4648V57.4872l19.95-11.5185,17.2893,9.4549a3.9993,3.9993,0,0,0,1.9192.4906h2.8632a2,2,0,0,0,2-2V51.2024a2,2,0,0,0-1.04-1.7547L59.628,38.9521a8.0391,8.0391,0,0,0-7.8428.09L31.8346,50.56a8.0246,8.0246,0,0,0-4,6.9287v22.976a8,8,0,0,0,4,6.9283l19.95,11.5186a8.0429,8.0429,0,0,0,7.8433.0879l19.19-10.5312a2,2,0,0,0,1.0378-1.7533v-2.71A2,2,0,0,0,77.8567,82.0046Z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path d="M172.58,45.3618a15.0166,15.0166,0,0,0-15,14.9995V77.6387a15,15,0,0,0,30,0V60.3613A15.0166,15.0166,0,0,0,172.58,45.3618Zm7,32.2769a7,7,0,0,1-14,0V60.3613a7,7,0,0,1,14,0Z"/>
|
||||
<path d="M135.9138,53.4211a7.01,7.01,0,0,1,7.8681,6.0752.9894.9894,0,0,0,.9843.865h6.03a1.0108,1.0108,0,0,0,.9987-1.0971,15.0182,15.0182,0,0,0-15.7162-13.8837,15.2881,15.2881,0,0,0-14.2441,15.4163V77.2037A15.288,15.288,0,0,0,136.0792,92.62a15.0183,15.0183,0,0,0,15.7162-13.8842,1.0107,1.0107,0,0,0-.9987-1.0971h-6.03a.9894.9894,0,0,0-.9843.865,7.01,7.01,0,0,1-7.8679,6.0757,7.1642,7.1642,0,0,1-6.0789-7.1849V60.6057A7.1638,7.1638,0,0,1,135.9138,53.4211Z"/>
|
||||
<path d="M218.7572,72.9277a12.1585,12.1585,0,0,0,7.1843-11.0771V58.1494A12.1494,12.1494,0,0,0,213.7921,46H196.835a1,1,0,0,0-1,1V91a1,1,0,0,0,1,1h6a1,1,0,0,0,1-1V74h6.6216l7.9154,17.4138a1,1,0,0,0,.91.5862h6.5911a1,1,0,0,0,.91-1.4138Zm-.8157-11.0771A4.1538,4.1538,0,0,1,213.7926,66h-9.8511V54h9.8511a4.1538,4.1538,0,0,1,4.1489,4.1494Z"/>
|
||||
<path d="M260.835,46h-26a1,1,0,0,0-1,1V91a1,1,0,0,0,1,1h26a1,1,0,0,0,1-1V85a1,1,0,0,0-1-1h-19V72h13a1,1,0,0,0,1-1V65a1,1,0,0,0-1-1h-13V54h19a1,1,0,0,0,1-1V47A1,1,0,0,0,260.835,46Z"/>
|
||||
<path d="M298.835,46h-6a1,1,0,0,0-1,1V69.6475a7.0066,7.0066,0,1,1-14,0V47a1,1,0,0,0-1-1h-6a1,1,0,0,0-1,1V69.6475a15.0031,15.0031,0,1,0,30,0V47A1,1,0,0,0,298.835,46Z"/>
|
||||
<rect x="307.835" y="46" width="8" height="38" rx="1"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
`,
|
||||
];
|
||||
@@ -1,32 +0,0 @@
|
||||
export const logo = [
|
||||
'608 134',
|
||||
`
|
||||
<title>coreui react pro</title>
|
||||
<g>
|
||||
<g style="fill:#00a1ff">
|
||||
<path d="M362.0177,90.1512,353.25,69.4149a.2507.2507,0,0,0-.2559-.1914H343.01a.2263.2263,0,0,0-.2559.2559V90.0233a.5657.5657,0,0,1-.64.64h-1.2163a.5652.5652,0,0,1-.64-.64V46.5028a.5655.5655,0,0,1,.64-.64H353.442a9.9792,9.9792,0,0,1,7.7437,3.2324A12.2,12.2,0,0,1,364.13,57.64a12.4389,12.4389,0,0,1-2.24,7.584,9.37,9.37,0,0,1-6.08,3.7441c-.1709.086-.2139.1915-.128.3194l8.7041,20.6084.064.2558q0,.5127-.5757.5118h-1.1523A.703.703,0,0,1,362.0177,90.1512ZM342.754,48.3593v18.496a.2259.2259,0,0,0,.2559.2559h10.3037a7.6713,7.6713,0,0,0,6.0166-2.5918,9.8807,9.8807,0,0,0,2.3037-6.8164,10.2875,10.2875,0,0,0-2.272-6.9756,7.6033,7.6033,0,0,0-6.0483-2.624H343.01A.2263.2263,0,0,0,342.754,48.3593Z"/>
|
||||
<path d="M401.3263,48.1034H381.2945a.2262.2262,0,0,0-.2558.2559v18.496a.2259.2259,0,0,0,.2558.2559h13.8238a.5664.5664,0,0,1,.6406.64v.96a.5663.5663,0,0,1-.6406.6406H381.2945a.2263.2263,0,0,0-.2558.2559v18.56a.2258.2258,0,0,0,.2558.2558h20.0318a.5671.5671,0,0,1,.6406.6407v.96a.566.566,0,0,1-.6406.64H379.1827a.5653.5653,0,0,1-.64-.64V46.5028a.5656.5656,0,0,1,.64-.64h22.1436a.5664.5664,0,0,1,.6406.64v.96A.5663.5663,0,0,1,401.3263,48.1034Z"/>
|
||||
<path d="M439.047,90.1512l-2.4317-8.832a.2971.2971,0,0,0-.32-.1924H419.5274a.2957.2957,0,0,0-.32.1924l-2.3681,8.7676a.6577.6577,0,0,1-.7036.5762H414.919a.5385.5385,0,0,1-.5756-.7041l12.0317-43.584a.6436.6436,0,0,1,.7041-.5117h1.6a.6442.6442,0,0,1,.7041.5117l12.16,43.584.0644.1923q0,.5127-.64.5118h-1.2163A.6428.6428,0,0,1,439.047,90.1512ZM419.9435,78.9188a.3031.3031,0,0,0,.2236.0967h15.4883a.3048.3048,0,0,0,.2236-.0967c.0645-.0635.0742-.1162.0322-.1592l-7.872-28.9287c-.043-.0849-.086-.1279-.128-.1279s-.0859.043-.1279.1279L419.9112,78.76C419.8683,78.8026,419.879,78.8553,419.9435,78.9188Z"/>
|
||||
<path d="M456.6017,87.911a11.6372,11.6372,0,0,1-3.3277-8.7041V57.1913a11.4158,11.4158,0,0,1,3.36-8.5762,12.0941,12.0941,0,0,1,8.8-3.2637,12.2566,12.2566,0,0,1,8.8643,3.2315,11.3927,11.3927,0,0,1,3.36,8.6084v.64a.5663.5663,0,0,1-.6406.6407l-1.28.0634q-.6408,0-.64-.5761v-.8321a9.289,9.289,0,0,0-2.6558-6.9121,10.6734,10.6734,0,0,0-14.0161,0,9.2854,9.2854,0,0,0-2.6563,6.9121V79.3993a9.2808,9.2808,0,0,0,2.6563,6.9121,10.67,10.67,0,0,0,14.0161,0,9.2843,9.2843,0,0,0,2.6558-6.9121v-.7686q0-.5757.64-.5752l1.28.0635a.5667.5667,0,0,1,.6406.6406v.5118a11.4952,11.4952,0,0,1-3.36,8.64,13.6227,13.6227,0,0,1-17.6963,0Z"/>
|
||||
<path d="M514.4376,46.5028v.96a.5658.5658,0,0,1-.64.6406H503.046a.2263.2263,0,0,0-.2559.2559v41.664a.566.566,0,0,1-.6406.64h-1.2158a.5652.5652,0,0,1-.64-.64V48.3593a.2266.2266,0,0,0-.2558-.2559H489.8619a.5656.5656,0,0,1-.64-.6406v-.96a.5656.5656,0,0,1,.64-.64H513.798A.5658.5658,0,0,1,514.4376,46.5028Z"/>
|
||||
<path d="M522.0665,89.5116a2.8385,2.8385,0,0,1-.8-2.0488,2.9194,2.9194,0,0,1,.8-2.1114,2.7544,2.7544,0,0,1,2.08-.832,2.8465,2.8465,0,0,1,2.9438,2.9434,2.7541,2.7541,0,0,1-.832,2.08,2.9221,2.9221,0,0,1-2.1118.8008A2.754,2.754,0,0,1,522.0665,89.5116Z"/>
|
||||
<path d="M542.4054,88.0077a11.3123,11.3123,0,0,1-3.2-8.416v-5.44a.5656.5656,0,0,1,.64-.64h1.2158a.5661.5661,0,0,1,.64.64v5.5039a9.1424,9.1424,0,0,0,2.5283,6.72,8.9745,8.9745,0,0,0,6.6875,2.5605,8.7908,8.7908,0,0,0,9.28-9.28V46.5028a.5655.5655,0,0,1,.64-.64h1.2163a.566.566,0,0,1,.64.64V79.5917a11.2545,11.2545,0,0,1-3.2325,8.416,13.0618,13.0618,0,0,1-17.0556,0Z"/>
|
||||
<path d="M580.35,88.1034a10.4859,10.4859,0,0,1-3.36-8.1279v-1.792a.5663.5663,0,0,1,.64-.6407h1.0884a.5668.5668,0,0,1,.64.6407v1.6a8.5459,8.5459,0,0,0,2.752,6.6562,10.5353,10.5353,0,0,0,7.36,2.4961,9.8719,9.8719,0,0,0,6.9761-2.3681,8.2161,8.2161,0,0,0,2.56-6.336,8.4,8.4,0,0,0-1.12-4.416,11.3812,11.3812,0,0,0-3.3281-3.3926,71.6714,71.6714,0,0,0-6.1763-3.7119,71.0479,71.0479,0,0,1-6.24-3.84,12.1711,12.1711,0,0,1-3.4238-3.68,10.2614,10.2614,0,0,1-1.28-5.3438,9.8579,9.8579,0,0,1,3.0718-7.7441,12.0122,12.0122,0,0,1,8.32-2.752q5.6954,0,8.96,3.1036a10.8251,10.8251,0,0,1,3.2642,8.2246v1.6a.5658.5658,0,0,1-.64.64h-1.1519a.5652.5652,0,0,1-.64-.64V56.8075a8.8647,8.8647,0,0,0-2.624-6.6885,9.9933,9.9933,0,0,0-7.232-2.5273,9.37,9.37,0,0,0-6.5278,2.1435,7.8224,7.8224,0,0,0-2.3682,6.1123,7.8006,7.8006,0,0,0,1.0244,4.16,10.387,10.387,0,0,0,3.0078,3.0391,62.8714,62.8714,0,0,0,5.9522,3.4882,71.0575,71.0575,0,0,1,6.72,4.2559,13.4674,13.4674,0,0,1,3.648,3.9365,10.049,10.049,0,0,1,1.28,5.1836,10.7177,10.7177,0,0,1-3.2637,8.1924q-3.2637,3.0717-8.832,3.0723Q583.71,91.1757,580.35,88.1034Z"/>
|
||||
</g>
|
||||
<g style="fill:#3c4b64">
|
||||
<g>
|
||||
<path d="M99.835,36.0577l-39-22.5167a12,12,0,0,0-12,0l-39,22.5166a12.0339,12.0339,0,0,0-6,10.3924V91.4833a12.0333,12.0333,0,0,0,6,10.3923l39,22.5167a12,12,0,0,0,12,0l39-22.5167a12.0331,12.0331,0,0,0,6-10.3923V46.45A12.0334,12.0334,0,0,0,99.835,36.0577Zm-2,55.4256a4,4,0,0,1-2,3.4641l-39,22.5167a4.0006,4.0006,0,0,1-4,0l-39-22.5167a4,4,0,0,1-2-3.4641V46.45a4,4,0,0,1,2-3.4642l39-22.5166a4,4,0,0,1,4,0l39,22.5166a4,4,0,0,1,2,3.4642Z"/>
|
||||
<path d="M77.8567,82.0046h-2.866a4,4,0,0,0-1.9247.4934L55.7852,91.9833,35.835,80.4648V57.4872l19.95-11.5185,17.2893,9.4549a3.9993,3.9993,0,0,0,1.9192.4906h2.8632a2,2,0,0,0,2-2V51.2024a2,2,0,0,0-1.04-1.7547L59.628,38.9521a8.0391,8.0391,0,0,0-7.8428.09L31.8346,50.56a8.0246,8.0246,0,0,0-4,6.9287v22.976a8,8,0,0,0,4,6.9283l19.95,11.5186a8.0429,8.0429,0,0,0,7.8433.0879l19.19-10.5312a2,2,0,0,0,1.0378-1.7533v-2.71A2,2,0,0,0,77.8567,82.0046Z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path d="M172.58,45.3618a15.0166,15.0166,0,0,0-15,14.9995V77.6387a15,15,0,0,0,30,0V60.3613A15.0166,15.0166,0,0,0,172.58,45.3618Zm7,32.2769a7,7,0,0,1-14,0V60.3613a7,7,0,0,1,14,0Z"/>
|
||||
<path d="M135.9138,53.4211a7.01,7.01,0,0,1,7.8681,6.0752.9894.9894,0,0,0,.9843.865h6.03a1.0108,1.0108,0,0,0,.9987-1.0971,15.0182,15.0182,0,0,0-15.7162-13.8837,15.2881,15.2881,0,0,0-14.2441,15.4163V77.2037A15.288,15.288,0,0,0,136.0792,92.62a15.0183,15.0183,0,0,0,15.7162-13.8842,1.0107,1.0107,0,0,0-.9987-1.0971h-6.03a.9894.9894,0,0,0-.9843.865,7.01,7.01,0,0,1-7.8679,6.0757,7.1642,7.1642,0,0,1-6.0789-7.1849V60.6057A7.1638,7.1638,0,0,1,135.9138,53.4211Z"/>
|
||||
<path d="M218.7572,72.9277a12.1585,12.1585,0,0,0,7.1843-11.0771V58.1494A12.1494,12.1494,0,0,0,213.7921,46H196.835a1,1,0,0,0-1,1V91a1,1,0,0,0,1,1h6a1,1,0,0,0,1-1V74h6.6216l7.9154,17.4138a1,1,0,0,0,.91.5862h6.5911a1,1,0,0,0,.91-1.4138Zm-.8157-11.0771A4.1538,4.1538,0,0,1,213.7926,66h-9.8511V54h9.8511a4.1538,4.1538,0,0,1,4.1489,4.1494Z"/>
|
||||
<path d="M260.835,46h-26a1,1,0,0,0-1,1V91a1,1,0,0,0,1,1h26a1,1,0,0,0,1-1V85a1,1,0,0,0-1-1h-19V72h13a1,1,0,0,0,1-1V65a1,1,0,0,0-1-1h-13V54h19a1,1,0,0,0,1-1V47A1,1,0,0,0,260.835,46Z"/>
|
||||
<path d="M298.835,46h-6a1,1,0,0,0-1,1V69.6475a7.0066,7.0066,0,1,1-14,0V47a1,1,0,0,0-1-1h-6a1,1,0,0,0-1,1V69.6475a15.0031,15.0031,0,1,0,30,0V47A1,1,0,0,0,298.835,46Z"/>
|
||||
<rect x="307.835" y="46" width="8" height="38" rx="1"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
`,
|
||||
];
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.6 KiB |
@@ -1,12 +0,0 @@
|
||||
export const sygnet = [
|
||||
'160 160',
|
||||
`
|
||||
<title>coreui logo</title>
|
||||
<g>
|
||||
<g style="fill:#fff;">
|
||||
<path d="M125,47.091,86,24.5743a12,12,0,0,0-12,0L35,47.091a12.0336,12.0336,0,0,0-6,10.3923v45.0334a12.0335,12.0335,0,0,0,6,10.3923l39,22.5166a11.9993,11.9993,0,0,0,12,0l39-22.5166a12.0335,12.0335,0,0,0,6-10.3923V57.4833A12.0336,12.0336,0,0,0,125,47.091Zm-2,55.4257a4,4,0,0,1-2,3.464L82,128.4974a4,4,0,0,1-4,0L39,105.9807a4,4,0,0,1-2-3.464V57.4833a4,4,0,0,1,2-3.4641L78,31.5025a4,4,0,0,1,4,0l39,22.5167a4,4,0,0,1,2,3.4641Z"/>
|
||||
<path d="M103.0216,93.0379h-2.866a4,4,0,0,0-1.9246.4935L80.95,103.0167,61,91.4981V68.5206L80.95,57.002l17.2894,9.455a4,4,0,0,0,1.9192.4905h2.8632a2,2,0,0,0,2-2V62.2357a2,2,0,0,0-1.04-1.7547L84.793,49.9854a8.0391,8.0391,0,0,0-7.8428.09L57,61.5929A8.0243,8.0243,0,0,0,53,68.5216v22.976a8,8,0,0,0,4,6.9283l19.95,11.5185a8.0422,8.0422,0,0,0,7.8433.0879l19.19-10.5311a2,2,0,0,0,1.0378-1.7534v-2.71A2,2,0,0,0,103.0216,93.0379Z"/>
|
||||
</g>
|
||||
</g>
|
||||
`,
|
||||
];
|
||||
@@ -5,10 +5,9 @@ import {
|
||||
CModalTitle,
|
||||
CModalBody,
|
||||
CModalFooter,
|
||||
CSpinner,
|
||||
CSwitch,
|
||||
CCol,
|
||||
CRow,
|
||||
CForm,
|
||||
CFormGroup,
|
||||
CInputRadio,
|
||||
CLabel,
|
||||
@@ -17,37 +16,25 @@ import React, { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import DatePicker from 'react-widgets/DatePicker';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { convertDateFromUtc, convertDateToUtc, dateToUnix } from 'utils/helper';
|
||||
import { dateToUnix } from 'utils/helper';
|
||||
import 'react-widgets/styles.css';
|
||||
import { getToken } from 'utils/authHelper';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
import eventBus from 'utils/eventBus';
|
||||
import SuccessfulActionModalBody from 'components/SuccessfulActionModalBody';
|
||||
import LoadingButton from 'components/LoadingButton';
|
||||
|
||||
import styles from './index.module.scss';
|
||||
import { LoadingButton, useAuth, useDevice } from 'ucentral-libs';
|
||||
|
||||
const BlinkModal = ({ show, toggleModal }) => {
|
||||
const { t } = useTranslation();
|
||||
const [hadSuccess, setHadSuccess] = useState(false);
|
||||
const [hadFailure, setHadFailure] = useState(false);
|
||||
const [doingNow, setDoingNow] = useState(false);
|
||||
const { currentToken, endpoints } = useAuth();
|
||||
const { deviceSerialNumber } = useDevice();
|
||||
const [isNow, setIsNow] = useState(false);
|
||||
const [waiting, setWaiting] = useState(false);
|
||||
const [chosenDate, setChosenDate] = useState(new Date().toString());
|
||||
const [chosenPattern, setPattern] = useState('on');
|
||||
const [responseBody, setResponseBody] = useState('');
|
||||
const selectedDeviceId = useSelector((state) => state.selectedDeviceId);
|
||||
const [result, setResult] = useState(null);
|
||||
|
||||
const setDateToLate = () => {
|
||||
const date = convertDateToUtc(new Date());
|
||||
if (date.getHours() >= 3) {
|
||||
date.setDate(date.getDate() + 1);
|
||||
}
|
||||
date.setHours(3);
|
||||
date.setMinutes(0);
|
||||
|
||||
setChosenDate(convertDateFromUtc(date).toString());
|
||||
const toggleNow = () => {
|
||||
setIsNow(!isNow);
|
||||
};
|
||||
|
||||
const setDate = (date) => {
|
||||
@@ -60,47 +47,40 @@ const BlinkModal = ({ show, toggleModal }) => {
|
||||
if (show) {
|
||||
setWaiting(false);
|
||||
setChosenDate(new Date().toString());
|
||||
setResponseBody('');
|
||||
setPattern('on');
|
||||
setDoingNow(false);
|
||||
setHadSuccess(false);
|
||||
setHadFailure(false);
|
||||
setResult(null);
|
||||
}
|
||||
}, [show]);
|
||||
|
||||
const doAction = (isNow) => {
|
||||
if (isNow !== undefined) setDoingNow(isNow);
|
||||
setHadFailure(false);
|
||||
setHadSuccess(false);
|
||||
const doAction = () => {
|
||||
setWaiting(true);
|
||||
|
||||
const token = getToken();
|
||||
const utcDate = new Date(chosenDate);
|
||||
const utcDateString = utcDate.toISOString();
|
||||
|
||||
const parameters = {
|
||||
serialNumber: selectedDeviceId,
|
||||
when: isNow ? 0 : dateToUnix(utcDateString),
|
||||
serialNumber: deviceSerialNumber,
|
||||
when: isNow ? 0 : dateToUnix(utcDate),
|
||||
pattern: chosenPattern,
|
||||
duration: 30,
|
||||
};
|
||||
|
||||
const headers = {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${token}`,
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
};
|
||||
|
||||
axiosInstance
|
||||
.post(`/device/${encodeURIComponent(selectedDeviceId)}/leds`, parameters, { headers })
|
||||
.post(
|
||||
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}/leds`,
|
||||
parameters,
|
||||
{ headers },
|
||||
)
|
||||
.then(() => {
|
||||
setHadSuccess(true);
|
||||
setResult('success');
|
||||
})
|
||||
.catch(() => {
|
||||
setResponseBody('Error while submitting command!');
|
||||
setHadFailure(true);
|
||||
setResult('error');
|
||||
})
|
||||
.finally(() => {
|
||||
setDoingNow(false);
|
||||
setWaiting(false);
|
||||
eventBus.dispatch('actionCompleted', { message: 'An action has been completed' });
|
||||
});
|
||||
@@ -111,33 +91,72 @@ const BlinkModal = ({ show, toggleModal }) => {
|
||||
<CModalHeader closeButton>
|
||||
<CModalTitle>{t('blink.device_leds')}</CModalTitle>
|
||||
</CModalHeader>
|
||||
{hadSuccess ? (
|
||||
{result === 'success' ? (
|
||||
<SuccessfulActionModalBody toggleModal={toggleModal} />
|
||||
) : (
|
||||
<div>
|
||||
<CModalBody>
|
||||
<h6>{t('blink.when_blink_leds')}</h6>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CCol>
|
||||
<CButton onClick={() => doAction(true)} disabled={waiting} block color="primary">
|
||||
{waiting && doingNow ? t('common.loading_ellipsis') : t('common.do_now')}
|
||||
<CSpinner
|
||||
color="light"
|
||||
hidden={!waiting || !doingNow}
|
||||
component="span"
|
||||
size="sm"
|
||||
/>
|
||||
</CButton>
|
||||
<CFormGroup row>
|
||||
<CCol md="3">
|
||||
<CLabel>{t('blink.pattern')}</CLabel>
|
||||
</CCol>
|
||||
<CCol>
|
||||
<CButton disabled={waiting} block color="primary" onClick={setDateToLate}>
|
||||
{t('common.later_tonight')}
|
||||
</CButton>
|
||||
<CFormGroup variant="custom-radio" onClick={() => setPattern('on')} inline>
|
||||
<CInputRadio
|
||||
custom
|
||||
defaultChecked={chosenPattern === 'on'}
|
||||
id="radio1"
|
||||
name="radios"
|
||||
value="option1"
|
||||
/>
|
||||
<CLabel variant="custom-checkbox" htmlFor="radio1">
|
||||
{t('common.on')}
|
||||
</CLabel>
|
||||
</CFormGroup>
|
||||
<CFormGroup variant="custom-radio" onClick={() => setPattern('off')} inline>
|
||||
<CInputRadio
|
||||
custom
|
||||
defaultChecked={chosenPattern === 'off'}
|
||||
id="radio2"
|
||||
name="radios"
|
||||
value="option2"
|
||||
/>
|
||||
<CLabel variant="custom-checkbox" htmlFor="radio2">
|
||||
{t('common.off')}
|
||||
</CLabel>
|
||||
</CFormGroup>
|
||||
<CFormGroup variant="custom-radio" onClick={() => setPattern('blink')} inline>
|
||||
<CInputRadio
|
||||
custom
|
||||
defaultChecked={chosenPattern === 'blink'}
|
||||
id="radio3"
|
||||
name="radios"
|
||||
value="option3"
|
||||
/>
|
||||
<CLabel variant="custom-checkbox" htmlFor="radio3">
|
||||
{t('blink.blink')}
|
||||
</CLabel>
|
||||
</CFormGroup>
|
||||
</CCol>
|
||||
</CFormGroup>
|
||||
<CRow className="pt-1">
|
||||
<CCol md="8">
|
||||
<p>{t('blink.execute_now')}</p>
|
||||
</CCol>
|
||||
<CCol>
|
||||
<CSwitch
|
||||
disabled={waiting}
|
||||
color="primary"
|
||||
defaultChecked={isNow}
|
||||
onClick={toggleNow}
|
||||
labelOn={t('common.yes')}
|
||||
labelOff={t('common.no')}
|
||||
/>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CCol md="4" className={styles.spacedDate}>
|
||||
<p>{t('common.date')}</p>
|
||||
<CRow hidden={isNow} className="pt-3">
|
||||
<CCol md="4" className="pt-2">
|
||||
<p>{t('common.custom_date')}</p>
|
||||
</CCol>
|
||||
<CCol xs="12" md="8">
|
||||
<DatePicker
|
||||
@@ -147,63 +166,17 @@ const BlinkModal = ({ show, toggleModal }) => {
|
||||
placeholder="Select custom date"
|
||||
disabled={waiting}
|
||||
onChange={(date) => setDate(date)}
|
||||
min={convertDateToUtc(new Date())}
|
||||
min={new Date()}
|
||||
/>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CCol md="7">{t('blink.pattern')}</CCol>
|
||||
<CCol>
|
||||
<CForm>
|
||||
<CFormGroup variant="checkbox" onClick={() => setPattern('on')}>
|
||||
<CInputRadio
|
||||
defaultChecked={chosenPattern === 'on'}
|
||||
id="radio1"
|
||||
name="radios"
|
||||
value="option1"
|
||||
/>
|
||||
<CLabel variant="checkbox" htmlFor="radio1">
|
||||
{t('common.on')}
|
||||
</CLabel>
|
||||
</CFormGroup>
|
||||
<CFormGroup variant="checkbox" onClick={() => setPattern('off')}>
|
||||
<CInputRadio
|
||||
defaultChecked={chosenPattern === 'off'}
|
||||
id="radio2"
|
||||
name="radios"
|
||||
value="option2"
|
||||
/>
|
||||
<CLabel variant="checkbox" htmlFor="radio2">
|
||||
{t('common.off')}
|
||||
</CLabel>
|
||||
</CFormGroup>
|
||||
<CFormGroup variant="checkbox" onClick={() => setPattern('blink')}>
|
||||
<CInputRadio
|
||||
defaultChecked={chosenPattern === 'blink'}
|
||||
id="radio3"
|
||||
name="radios"
|
||||
value="option3"
|
||||
/>
|
||||
<CLabel variant="checkbox" htmlFor="radio3">
|
||||
{t('blink.blink')}
|
||||
</CLabel>
|
||||
</CFormGroup>
|
||||
</CForm>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<div hidden={!hadSuccess && !hadFailure}>
|
||||
<div>
|
||||
<pre className="ignore">{responseBody}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</CModalBody>
|
||||
<CModalFooter>
|
||||
<LoadingButton
|
||||
label={t('common.schedule')}
|
||||
label={isNow ? t('blink.set_leds') : t('common.schedule')}
|
||||
isLoadingLabel={t('common.loading_ellipsis')}
|
||||
isLoading={waiting && !doingNow}
|
||||
isLoading={waiting}
|
||||
action={doAction}
|
||||
variant="outline"
|
||||
block={false}
|
||||
disabled={waiting}
|
||||
/>
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
.spacedRow {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.spacedDate {
|
||||
margin-top: 7px;
|
||||
}
|
||||
36
src/components/CommandHistory/DetailsModal.js
Normal file
36
src/components/CommandHistory/DetailsModal.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
CButton,
|
||||
CModal,
|
||||
CModalHeader,
|
||||
CModalBody,
|
||||
CModalTitle,
|
||||
CModalFooter,
|
||||
} from '@coreui/react';
|
||||
|
||||
const DetailsModal = ({ t, show, toggle, details, commandUuid }) => (
|
||||
<CModal size="lg" show={show} onClose={toggle}>
|
||||
<CModalHeader closeButton>
|
||||
<CModalTitle className="text-dark">{commandUuid}</CModalTitle>
|
||||
</CModalHeader>
|
||||
<CModalBody>
|
||||
<pre className="ignore">{JSON.stringify(details, null, 4)}</pre>
|
||||
</CModalBody>
|
||||
<CModalFooter>
|
||||
<CButton color="secondary" onClick={toggle}>
|
||||
{t('common.close')}
|
||||
</CButton>
|
||||
</CModalFooter>
|
||||
</CModal>
|
||||
);
|
||||
|
||||
DetailsModal.propTypes = {
|
||||
t: PropTypes.func.isRequired,
|
||||
show: PropTypes.bool.isRequired,
|
||||
toggle: PropTypes.func.isRequired,
|
||||
details: PropTypes.instanceOf(Object).isRequired,
|
||||
commandUuid: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default DetailsModal;
|
||||
@@ -13,22 +13,20 @@ import {
|
||||
} from '@coreui/react';
|
||||
import CIcon from '@coreui/icons-react';
|
||||
import DatePicker from 'react-widgets/DatePicker';
|
||||
import { cilCloudDownload, cilSync } from '@coreui/icons';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faClipboardCheck } from '@fortawesome/free-solid-svg-icons';
|
||||
import { cilCloudDownload, cilSync, cilCalendarCheck } from '@coreui/icons';
|
||||
import { prettyDate, dateToUnix } from 'utils/helper';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
import { getToken } from 'utils/authHelper';
|
||||
import eventBus from 'utils/eventBus';
|
||||
import ConfirmModal from 'components/ConfirmModal';
|
||||
import LoadingButton from 'components/LoadingButton';
|
||||
import { LoadingButton, useAuth, useDevice } from 'ucentral-libs';
|
||||
import WifiScanResultModalWidget from 'components/WifiScanResultModal';
|
||||
import DeviceCommandsCollapse from './DeviceCommandsCollapse';
|
||||
import DetailsModal from './DetailsModal';
|
||||
import styles from './index.module.scss';
|
||||
|
||||
const DeviceCommands = ({ selectedDeviceId }) => {
|
||||
const DeviceCommands = () => {
|
||||
const { t } = useTranslation();
|
||||
const { currentToken, endpoints } = useAuth();
|
||||
const { deviceSerialNumber } = useDevice();
|
||||
// Wifiscan result related
|
||||
const [chosenWifiScan, setChosenWifiScan] = useState(null);
|
||||
const [showScanModal, setShowScanModal] = useState(false);
|
||||
@@ -36,11 +34,12 @@ const DeviceCommands = ({ selectedDeviceId }) => {
|
||||
// Delete modal related
|
||||
const [showConfirmModal, setShowConfirmModal] = useState(false);
|
||||
const [uuidDelete, setUuidDelete] = useState('');
|
||||
// Details modal related
|
||||
const [showDetailsModal, setShowDetailsModal] = useState(false);
|
||||
const [detailsUuid, setDetailsUuid] = useState('');
|
||||
const [modalDetails, setModalDetails] = useState({});
|
||||
// Main collapsible
|
||||
const [collapse, setCollapse] = useState(false);
|
||||
// Two other open collapsible lists
|
||||
const [details, setDetails] = useState([]);
|
||||
const [responses, setResponses] = useState([]);
|
||||
// General states
|
||||
const [commands, setCommands] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -65,6 +64,10 @@ const DeviceCommands = ({ selectedDeviceId }) => {
|
||||
setShowConfirmModal(!showConfirmModal);
|
||||
};
|
||||
|
||||
const toggleDetailsModal = () => {
|
||||
setShowDetailsModal(!showDetailsModal);
|
||||
};
|
||||
|
||||
const showMoreCommands = () => {
|
||||
setCommandLimit(commandLimit + 50);
|
||||
};
|
||||
@@ -92,7 +95,7 @@ const DeviceCommands = ({ selectedDeviceId }) => {
|
||||
const options = {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${getToken()}`,
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
},
|
||||
params: {
|
||||
limit: commandLimit,
|
||||
@@ -109,7 +112,12 @@ const DeviceCommands = ({ selectedDeviceId }) => {
|
||||
}
|
||||
|
||||
axiosInstance
|
||||
.get(`/commands?serialNumber=${encodeURIComponent(selectedDeviceId)}${extraParams}`, options)
|
||||
.get(
|
||||
`${endpoints.ucentralgw}/api/v1/commands?serialNumber=${encodeURIComponent(
|
||||
deviceSerialNumber,
|
||||
)}${extraParams}`,
|
||||
options,
|
||||
)
|
||||
.then((response) => {
|
||||
setCommands(response.data.commands);
|
||||
})
|
||||
@@ -124,13 +132,16 @@ const DeviceCommands = ({ selectedDeviceId }) => {
|
||||
const options = {
|
||||
headers: {
|
||||
Accept: 'application/octet-stream',
|
||||
Authorization: `Bearer ${getToken()}`,
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
},
|
||||
responseType: 'arraybuffer',
|
||||
};
|
||||
|
||||
axiosInstance
|
||||
.get(`/file/${uuid}?serialNumber=${selectedDeviceId}`, options)
|
||||
.get(
|
||||
`${endpoints.ucentralgw}/api/v1/file/${uuid}?serialNumber=${deviceSerialNumber}`,
|
||||
options,
|
||||
)
|
||||
.then((response) => {
|
||||
const blob = new Blob([response.data], { type: 'application/octet-stream' });
|
||||
const link = document.createElement('a');
|
||||
@@ -147,11 +158,11 @@ const DeviceCommands = ({ selectedDeviceId }) => {
|
||||
const options = {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${getToken()}`,
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
},
|
||||
};
|
||||
return axiosInstance
|
||||
.delete(`/command/${uuidDelete}`, options)
|
||||
.delete(`${endpoints.ucentralgw}/api/v1/command/${uuidDelete}`, options)
|
||||
.then(() => {
|
||||
deleteCommandFromList(uuidDelete);
|
||||
setUuidDelete('');
|
||||
@@ -163,7 +174,7 @@ const DeviceCommands = ({ selectedDeviceId }) => {
|
||||
});
|
||||
};
|
||||
|
||||
const toggleDetails = (item, index) => {
|
||||
const toggleDetails = (item) => {
|
||||
if (item.command === 'wifiscan') {
|
||||
setChosenWifiScan(item.results.status.scan);
|
||||
setChosenWifiScanDate(item.completed);
|
||||
@@ -171,52 +182,22 @@ const DeviceCommands = ({ selectedDeviceId }) => {
|
||||
} else if (item.command === 'trace' && item.waitingForFile === 0) {
|
||||
downloadTrace(item.UUID);
|
||||
} else {
|
||||
const position = details.indexOf(index);
|
||||
let newDetails = details.slice();
|
||||
|
||||
if (position !== -1) {
|
||||
newDetails.splice(position, 1);
|
||||
} else {
|
||||
newDetails = [...details, index];
|
||||
}
|
||||
setDetails(newDetails);
|
||||
setModalDetails(item.results ?? item);
|
||||
setDetailsUuid(item.UUID);
|
||||
toggleDetailsModal();
|
||||
}
|
||||
};
|
||||
|
||||
const toggleResponse = (item, index) => {
|
||||
const position = responses.indexOf(index);
|
||||
let newResponses = responses.slice();
|
||||
|
||||
if (position !== -1) {
|
||||
newResponses.splice(position, 1);
|
||||
} else {
|
||||
newResponses = [...newResponses, index];
|
||||
}
|
||||
setResponses(newResponses);
|
||||
const toggleResponse = (item) => {
|
||||
setModalDetails(item);
|
||||
setDetailsUuid(item.UUID);
|
||||
toggleDetailsModal();
|
||||
};
|
||||
|
||||
const refreshCommands = () => {
|
||||
getCommands();
|
||||
};
|
||||
|
||||
const getDetails = (command, index) => {
|
||||
if (!details.includes(index)) {
|
||||
return <pre className="ignore" />;
|
||||
}
|
||||
if (command.results) {
|
||||
const result = command.results;
|
||||
if (result) return <pre className="ignore">{JSON.stringify(result, null, 4)}</pre>;
|
||||
}
|
||||
return <pre className="ignore">{JSON.stringify(command, null, 4)}</pre>;
|
||||
};
|
||||
|
||||
const getResponse = (commandDetails, index) => {
|
||||
if (!responses.includes(index)) {
|
||||
return <pre className="ignore" />;
|
||||
}
|
||||
return <pre className="ignore">{JSON.stringify(commandDetails, null, 4)}</pre>;
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{ key: 'UUID', label: t('common.id'), _style: { width: '28%' } },
|
||||
{ key: 'command', label: t('common.command'), _style: { width: '10%' } },
|
||||
@@ -233,12 +214,12 @@ const DeviceCommands = ({ selectedDeviceId }) => {
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedDeviceId && start !== '' && end !== '') {
|
||||
if (deviceSerialNumber && start !== '' && end !== '') {
|
||||
getCommands();
|
||||
} else if (selectedDeviceId && start === '' && end === '') {
|
||||
} else if (deviceSerialNumber && start === '' && end === '') {
|
||||
getCommands();
|
||||
}
|
||||
}, [selectedDeviceId, start, end]);
|
||||
}, [deviceSerialNumber, start, end]);
|
||||
|
||||
useEffect(() => {
|
||||
eventBus.on('actionCompleted', () => refreshCommands());
|
||||
@@ -249,7 +230,7 @@ const DeviceCommands = ({ selectedDeviceId }) => {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedDeviceId) {
|
||||
if (deviceSerialNumber) {
|
||||
setCommandLimit(25);
|
||||
setLoadingMore(false);
|
||||
setShowLoadingMore(true);
|
||||
@@ -257,7 +238,7 @@ const DeviceCommands = ({ selectedDeviceId }) => {
|
||||
setEnd('');
|
||||
getCommands();
|
||||
}
|
||||
}, [selectedDeviceId]);
|
||||
}, [deviceSerialNumber]);
|
||||
|
||||
useEffect(() => {
|
||||
if (commandLimit !== 25) {
|
||||
@@ -283,20 +264,15 @@ const DeviceCommands = ({ selectedDeviceId }) => {
|
||||
<CCollapse show={collapse}>
|
||||
<CRow>
|
||||
<CCol />
|
||||
<CCol>
|
||||
<div className={styles.alignRight}>
|
||||
<CCol className="text-right">
|
||||
<div>
|
||||
<CButton onClick={refreshCommands} size="sm">
|
||||
<CIcon
|
||||
name="cil-sync"
|
||||
content={cilSync}
|
||||
className={styles.whiteIcon}
|
||||
size="2xl"
|
||||
/>
|
||||
<CIcon name="cil-sync" content={cilSync} className="text-white" size="2xl" />
|
||||
</CButton>
|
||||
</div>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className={styles.datepickerRow}>
|
||||
<CRow className="mb-2">
|
||||
<CCol>
|
||||
From:
|
||||
<DatePicker includeTime onChange={(date) => modifyStart(date)} />
|
||||
@@ -312,10 +288,8 @@ const DeviceCommands = ({ selectedDeviceId }) => {
|
||||
loading={loading}
|
||||
items={commands ?? []}
|
||||
fields={columns}
|
||||
className={styles.whiteIcon}
|
||||
columnFilter
|
||||
sorter
|
||||
sorterValue={{ column: 'submitted', desc: 'true' }}
|
||||
className="text-white"
|
||||
sorterValue={{ column: 'created', desc: 'true' }}
|
||||
scopedSlots={{
|
||||
completed: (item) => (
|
||||
<td>
|
||||
@@ -349,26 +323,17 @@ const DeviceCommands = ({ selectedDeviceId }) => {
|
||||
>
|
||||
<CButton
|
||||
color="primary"
|
||||
variant={details.includes(index) ? '' : 'outline'}
|
||||
disabled={
|
||||
item.completed === 0 ||
|
||||
(item.command === 'trace' && item.waitingForFile !== 0)
|
||||
}
|
||||
variant="outline"
|
||||
shape="square"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
toggleDetails(item, index);
|
||||
toggleDetails(item);
|
||||
}}
|
||||
>
|
||||
{item.command === 'trace' ? (
|
||||
<CIcon content={cilCloudDownload} size="lg" />
|
||||
) : (
|
||||
<FontAwesomeIcon
|
||||
icon={faClipboardCheck}
|
||||
className={[styles.customIconHeight, 'c-icon c-icon-lg'].join(
|
||||
' ',
|
||||
)}
|
||||
/>
|
||||
<CIcon content={cilCalendarCheck} size="lg" />
|
||||
)}
|
||||
</CButton>
|
||||
</CPopover>
|
||||
@@ -377,11 +342,11 @@ const DeviceCommands = ({ selectedDeviceId }) => {
|
||||
<CPopover content={t('common.details')}>
|
||||
<CButton
|
||||
color="primary"
|
||||
variant={responses.includes(index) ? '' : 'outline'}
|
||||
variant="outline"
|
||||
shape="square"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
toggleResponse(item, index);
|
||||
toggleResponse(item);
|
||||
}}
|
||||
>
|
||||
<CIcon name="cilList" size="lg" />
|
||||
@@ -406,16 +371,6 @@ const DeviceCommands = ({ selectedDeviceId }) => {
|
||||
</CRow>
|
||||
</td>
|
||||
),
|
||||
details: (item, index) => (
|
||||
<DeviceCommandsCollapse
|
||||
details={details}
|
||||
responses={responses}
|
||||
index={index}
|
||||
getDetails={getDetails}
|
||||
getResponse={getResponse}
|
||||
item={item}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<CRow className={styles.loadMoreSpacing}>
|
||||
@@ -435,7 +390,7 @@ const DeviceCommands = ({ selectedDeviceId }) => {
|
||||
<CButton show={collapse ? 'true' : 'false'} color="transparent" onClick={toggle} block>
|
||||
<CIcon
|
||||
name={collapse ? 'cilChevronTop' : 'cilChevronBottom'}
|
||||
className={styles.whiteIcon}
|
||||
className="text-white"
|
||||
size="lg"
|
||||
/>
|
||||
</CButton>
|
||||
@@ -449,13 +404,15 @@ const DeviceCommands = ({ selectedDeviceId }) => {
|
||||
date={chosenWifiScanDate}
|
||||
/>
|
||||
<ConfirmModal show={showConfirmModal} toggle={toggleConfirmModal} action={deleteCommand} />
|
||||
<CIcon name="cilNotes" className={styles.whiteIcon} size="lg" />
|
||||
<DetailsModal
|
||||
t={t}
|
||||
show={showDetailsModal}
|
||||
toggle={toggleDetailsModal}
|
||||
details={modalDetails}
|
||||
commandUuid={detailsUuid}
|
||||
/>
|
||||
</CWidgetDropdown>
|
||||
);
|
||||
};
|
||||
|
||||
DeviceCommands.propTypes = {
|
||||
selectedDeviceId: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default DeviceCommands;
|
||||
@@ -2,20 +2,8 @@
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.datepickerRow {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.scrollableBox {
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
.whiteIcon {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.alignRight {
|
||||
float: right;
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
.customIconHeight {
|
||||
@@ -16,17 +16,17 @@ import {
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useSelector } from 'react-redux';
|
||||
import 'react-widgets/styles.css';
|
||||
import { getToken } from 'utils/authHelper';
|
||||
import { useAuth, useDevice } from 'ucentral-libs';
|
||||
import { checkIfJson } from 'utils/helper';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
import eventBus from 'utils/eventBus';
|
||||
import SuccessfulActionModalBody from 'components/SuccessfulActionModalBody';
|
||||
import styles from './index.module.scss';
|
||||
|
||||
const ConfigureModal = ({ show, toggleModal }) => {
|
||||
const { t } = useTranslation();
|
||||
const { currentToken, endpoints } = useAuth();
|
||||
const { deviceSerialNumber } = useDevice();
|
||||
const [hadSuccess, setHadSuccess] = useState(false);
|
||||
const [hadFailure, setHadFailure] = useState(false);
|
||||
const [doingNow, setDoingNow] = useState(false);
|
||||
@@ -36,7 +36,7 @@ const ConfigureModal = ({ show, toggleModal }) => {
|
||||
const [checkingIfSure, setCheckingIfSure] = useState(false);
|
||||
const [errorJson, setErrorJson] = useState(false);
|
||||
const [inputKey, setInputKey] = useState(0);
|
||||
const selectedDeviceId = useSelector((state) => state.selectedDeviceId);
|
||||
|
||||
let fileReader;
|
||||
|
||||
const confirmingIfSure = () => {
|
||||
@@ -69,10 +69,8 @@ const ConfigureModal = ({ show, toggleModal }) => {
|
||||
setHadSuccess(false);
|
||||
setWaiting(true);
|
||||
|
||||
const token = getToken();
|
||||
|
||||
const parameters = {
|
||||
serialNumber: selectedDeviceId,
|
||||
serialNumber: deviceSerialNumber,
|
||||
when: 0,
|
||||
UUID: 1,
|
||||
configuration: JSON.parse(newConfig),
|
||||
@@ -80,11 +78,15 @@ const ConfigureModal = ({ show, toggleModal }) => {
|
||||
|
||||
const headers = {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${token}`,
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
};
|
||||
|
||||
axiosInstance
|
||||
.post(`/device/${encodeURIComponent(selectedDeviceId)}/configure`, parameters, { headers })
|
||||
.post(
|
||||
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}/configure`,
|
||||
parameters,
|
||||
{ headers },
|
||||
)
|
||||
.then(() => {
|
||||
setHadSuccess(true);
|
||||
})
|
||||
@@ -122,7 +124,7 @@ const ConfigureModal = ({ show, toggleModal }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<CModal show={show} onClose={toggleModal}>
|
||||
<CModal show={show} onClose={toggleModal} size="lg">
|
||||
<CModalHeader closeButton>
|
||||
<CModalTitle>{t('configure.title')}</CModalTitle>
|
||||
</CModalHeader>
|
||||
@@ -132,7 +134,7 @@ const ConfigureModal = ({ show, toggleModal }) => {
|
||||
<div>
|
||||
<CModalBody>
|
||||
<CRow>
|
||||
<CCol md="10" className={styles.spacedColumn}>
|
||||
<CCol md="10" className="mt-1">
|
||||
<h6>{t('configure.enter_new')}</h6>
|
||||
</CCol>
|
||||
<CCol>
|
||||
@@ -147,7 +149,7 @@ const ConfigureModal = ({ show, toggleModal }) => {
|
||||
</CButton>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CRow className="mt-4">
|
||||
<CCol>
|
||||
<CForm>
|
||||
<CTextarea
|
||||
@@ -165,7 +167,7 @@ const ConfigureModal = ({ show, toggleModal }) => {
|
||||
</CForm>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CRow className="mt-4">
|
||||
<CCol>{t('configure.choose_file')}</CCol>
|
||||
<CCol>
|
||||
<CInputFile
|
||||
@@ -191,7 +193,7 @@ const ConfigureModal = ({ show, toggleModal }) => {
|
||||
color="primary"
|
||||
onClick={confirmingIfSure}
|
||||
>
|
||||
{t('common.submit')}
|
||||
{t('common.save')}
|
||||
</CButton>
|
||||
<CButton
|
||||
hidden={!checkingIfSure}
|
||||
@@ -199,7 +201,7 @@ const ConfigureModal = ({ show, toggleModal }) => {
|
||||
color="primary"
|
||||
onClick={() => doAction(false)}
|
||||
>
|
||||
{waiting && !doingNow ? 'Loading...' : 'Yes'} {' '}
|
||||
{waiting && !doingNow ? t('common.saving') : t('common.yes')} {' '}
|
||||
<CSpinner color="light" hidden={!waiting || doingNow} component="span" size="sm" />
|
||||
</CButton>
|
||||
<CButton color="secondary" onClick={toggleModal}>
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
.spacedRow {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.spacedColumn {
|
||||
margin-top: 3px;
|
||||
}
|
||||
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;
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
CBadge,
|
||||
} from '@coreui/react';
|
||||
import PropTypes from 'prop-types';
|
||||
import styles from './index.module.scss';
|
||||
|
||||
const ConfirmModal = ({ show, toggle, action }) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -63,7 +62,7 @@ const ConfirmModal = ({ show, toggle, action }) => {
|
||||
}, [show]);
|
||||
|
||||
return (
|
||||
<CModal className={styles.modal} show={show} onClose={toggle}>
|
||||
<CModal className="text-dark" show={show} onClose={toggle}>
|
||||
<CModalHeader closeButton>
|
||||
<CModalTitle>{t('delete_command.title')}</CModalTitle>
|
||||
</CModalHeader>
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
.modal {
|
||||
color: #3c4b64;
|
||||
}
|
||||
168
src/components/CreateUserModal/index.js
Normal file
168
src/components/CreateUserModal/index.js
Normal file
@@ -0,0 +1,168 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { CModal, CModalHeader, CModalBody } from '@coreui/react';
|
||||
import { CreateUserForm, useFormFields, useAuth, useToast } from 'ucentral-libs';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
import { testRegex, validateEmail } from 'utils/helper';
|
||||
|
||||
const initialState = {
|
||||
name: {
|
||||
value: '',
|
||||
error: false,
|
||||
optional: true,
|
||||
},
|
||||
email: {
|
||||
value: '',
|
||||
error: false,
|
||||
},
|
||||
currentPassword: {
|
||||
value: '',
|
||||
error: false,
|
||||
},
|
||||
changePassword: {
|
||||
value: 'on',
|
||||
error: false,
|
||||
},
|
||||
userRole: {
|
||||
value: 'admin',
|
||||
error: false,
|
||||
},
|
||||
notes: {
|
||||
value: '',
|
||||
error: false,
|
||||
optional: true,
|
||||
},
|
||||
description: {
|
||||
value: '',
|
||||
error: false,
|
||||
optional: true,
|
||||
},
|
||||
};
|
||||
|
||||
const CreateUserModal = ({ show, toggle, getUsers }) => {
|
||||
const { t } = useTranslation();
|
||||
const { currentToken, endpoints } = useAuth();
|
||||
const { addToast } = useToast();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [policies, setPolicies] = useState({
|
||||
passwordPolicy: '',
|
||||
passwordPattern: '',
|
||||
accessPolicy: '',
|
||||
});
|
||||
const [formFields, updateFieldWithId, updateField, setFormFields] = useFormFields(initialState);
|
||||
|
||||
const toggleChange = () => {
|
||||
updateField('changePassword', { value: !formFields.changePassword.value });
|
||||
};
|
||||
|
||||
const createUser = () => {
|
||||
setLoading(true);
|
||||
|
||||
const parameters = {
|
||||
id: 0,
|
||||
};
|
||||
|
||||
let validationSuccess = true;
|
||||
|
||||
for (const [key, value] of Object.entries(formFields)) {
|
||||
if (!value.optional && value.value === '') {
|
||||
validationSuccess = false;
|
||||
updateField(key, { value: value.value, error: true });
|
||||
} else if (key === 'currentPassword' && !testRegex(value.value, policies.passwordPattern)) {
|
||||
validationSuccess = false;
|
||||
updateField(key, { value: value.value, error: true });
|
||||
} else if (key === 'email' && !validateEmail(value.value)) {
|
||||
validationSuccess = false;
|
||||
updateField(key, { value: value.value, error: true });
|
||||
} else if (key === 'notes') {
|
||||
parameters[key] = [{ note: value.value }];
|
||||
} else if (key === 'changePassword') {
|
||||
parameters[key] = value.value === 'on';
|
||||
} else {
|
||||
parameters[key] = value.value;
|
||||
}
|
||||
}
|
||||
|
||||
if (validationSuccess) {
|
||||
const headers = {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
};
|
||||
|
||||
axiosInstance
|
||||
.post(`${endpoints.ucentralsec}/api/v1/user/0`, parameters, {
|
||||
headers,
|
||||
})
|
||||
.then(() => {
|
||||
getUsers();
|
||||
setFormFields(initialState);
|
||||
addToast({
|
||||
title: t('common.success'),
|
||||
body: t('user.create_success'),
|
||||
color: 'success',
|
||||
autohide: true,
|
||||
});
|
||||
toggle();
|
||||
})
|
||||
.catch(() => {
|
||||
addToast({
|
||||
title: t('common.error'),
|
||||
body: t('user.create_failure'),
|
||||
color: 'danger',
|
||||
autohide: true,
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
} else {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const getPasswordPolicy = () => {
|
||||
axiosInstance
|
||||
.post(`${endpoints.ucentralsec}/api/v1/oauth2?requirements=true`, {})
|
||||
.then((response) => {
|
||||
const newPolicies = response.data;
|
||||
newPolicies.accessPolicy = `${endpoints.ucentralsec}${newPolicies.accessPolicy}`;
|
||||
newPolicies.passwordPolicy = `${endpoints.ucentralsec}${newPolicies.passwordPolicy}`;
|
||||
setPolicies(response.data);
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (policies.passwordPattern.length === 0) getPasswordPolicy();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setFormFields(initialState);
|
||||
}, [show]);
|
||||
|
||||
return (
|
||||
<CModal show={show} onClose={toggle} size="xl">
|
||||
<CModalHeader>{t('user.create')}</CModalHeader>
|
||||
<CModalBody>
|
||||
<CreateUserForm
|
||||
t={t}
|
||||
fields={formFields}
|
||||
updateField={updateFieldWithId}
|
||||
createUser={createUser}
|
||||
loading={loading}
|
||||
policies={policies}
|
||||
toggleChange={toggleChange}
|
||||
/>
|
||||
</CModalBody>
|
||||
</CModal>
|
||||
);
|
||||
};
|
||||
|
||||
CreateUserModal.propTypes = {
|
||||
show: PropTypes.bool.isRequired,
|
||||
toggle: PropTypes.func.isRequired,
|
||||
getUsers: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default React.memo(CreateUserModal);
|
||||
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, useAuth, useDevice } from 'ucentral-libs';
|
||||
import { dateToUnix } from 'utils/helper';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
import eventBus from 'utils/eventBus';
|
||||
|
||||
const DeleteLogModal = ({ show, toggle, object }) => {
|
||||
const { t } = useTranslation();
|
||||
const { currentToken, endpoints } = useAuth();
|
||||
const { deviceSerialNumber } = useDevice();
|
||||
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 ${currentToken}`,
|
||||
},
|
||||
params: {
|
||||
endDate: dateToUnix(maxDate),
|
||||
},
|
||||
};
|
||||
return axiosInstance
|
||||
.delete(`${endpoints.ucentralgw}/api/v1/device/${deviceSerialNumber}/${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="text-dark" 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="pt-3">
|
||||
<CCol md="4" className="mt-2">
|
||||
<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
|
||||
t={t}
|
||||
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,
|
||||
};
|
||||
|
||||
export default DeleteLogModal;
|
||||
@@ -1,21 +1,26 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import PropTypes from 'prop-types';
|
||||
import { CButton, CCard, CCardHeader, CCardBody, CRow, CCol } from '@coreui/react';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
import { getToken } from 'utils/authHelper';
|
||||
import LoadingButton from 'components/LoadingButton';
|
||||
import { LoadingButton, useAuth, useDevice, useToast } from 'ucentral-libs';
|
||||
import RebootModal from 'components/RebootModal';
|
||||
import FirmwareUpgradeModal from 'components/FirmwareUpgradeModal';
|
||||
import DeviceFirmwareModal from 'components/DeviceFirmwareModal';
|
||||
import ConfigureModal from 'components/ConfigureModal';
|
||||
import TraceModal from 'components/TraceModal';
|
||||
import WifiScanModal from 'components/WifiScanModal';
|
||||
import BlinkModal from 'components/BlinkModal';
|
||||
import FactoryResetModal from 'components/FactoryResetModal';
|
||||
import styles from './index.module.scss';
|
||||
import EventQueueModal from 'components/EventQueueModal';
|
||||
|
||||
const DeviceActions = ({ selectedDeviceId }) => {
|
||||
const DeviceActions = () => {
|
||||
const { t } = useTranslation();
|
||||
const { currentToken, endpoints } = useAuth();
|
||||
const { addToast } = useToast();
|
||||
const { deviceSerialNumber } = useDevice();
|
||||
const [upgradeStatus, setUpgradeStatus] = useState({
|
||||
loading: false,
|
||||
});
|
||||
const [device, setDevice] = useState({});
|
||||
const [showRebootModal, setShowRebootModal] = useState(false);
|
||||
const [showBlinkModal, setShowBlinkModal] = useState(false);
|
||||
const [showUpgradeModal, setShowUpgradeModal] = useState(false);
|
||||
@@ -24,6 +29,7 @@ const DeviceActions = ({ selectedDeviceId }) => {
|
||||
const [connectLoading, setConnectLoading] = useState(false);
|
||||
const [showConfigModal, setConfigModal] = useState(false);
|
||||
const [showFactoryModal, setShowFactoryModal] = useState(false);
|
||||
const [showQueueModal, setShowQueueModal] = useState(false);
|
||||
|
||||
const toggleRebootModal = () => {
|
||||
setShowRebootModal(!showRebootModal);
|
||||
@@ -53,17 +59,24 @@ const DeviceActions = ({ selectedDeviceId }) => {
|
||||
setShowFactoryModal(!showFactoryModal);
|
||||
};
|
||||
|
||||
const toggleQueueModal = () => {
|
||||
setShowQueueModal(!showQueueModal);
|
||||
};
|
||||
|
||||
const getRttysInfo = () => {
|
||||
setConnectLoading(true);
|
||||
const options = {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${getToken()}`,
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
},
|
||||
};
|
||||
|
||||
axiosInstance
|
||||
.get(`/device/${encodeURIComponent(selectedDeviceId)}/rtty`, options)
|
||||
.get(
|
||||
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}/rtty`,
|
||||
options,
|
||||
)
|
||||
.then((response) => {
|
||||
const url = `https://${response.data.server}:${response.data.viewport}/connect/${response.data.connectionId}`;
|
||||
const newWindow = window.open(url, '_blank', 'noopener,noreferrer');
|
||||
@@ -75,9 +88,48 @@ const DeviceActions = ({ selectedDeviceId }) => {
|
||||
});
|
||||
};
|
||||
|
||||
const getDeviceInformation = () => {
|
||||
const options = {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
},
|
||||
};
|
||||
|
||||
axiosInstance
|
||||
.get(`${endpoints.ucentralgw}/api/v1/device/${deviceSerialNumber}`, options)
|
||||
.then((response) => {
|
||||
setDevice(response.data);
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (upgradeStatus.result !== undefined) {
|
||||
addToast({
|
||||
title: upgradeStatus.result.success ? t('common.success') : t('common.error'),
|
||||
body: upgradeStatus.result.success
|
||||
? t('firmware.upgrade_command_submitted')
|
||||
: upgradeStatus.result.error,
|
||||
color: upgradeStatus.result.success ? 'success' : 'danger',
|
||||
autohide: true,
|
||||
});
|
||||
setUpgradeStatus({
|
||||
loading: false,
|
||||
});
|
||||
setShowUpgradeModal(false);
|
||||
}
|
||||
}, [upgradeStatus]);
|
||||
|
||||
useEffect(() => {
|
||||
getDeviceInformation();
|
||||
}, [deviceSerialNumber]);
|
||||
|
||||
return (
|
||||
<CCard>
|
||||
<CCardHeader>{t('actions.title')}</CCardHeader>
|
||||
<CCardHeader>
|
||||
<div className="text-value-lg">{t('actions.title')}</div>
|
||||
</CCardHeader>
|
||||
<CCardBody>
|
||||
<CRow>
|
||||
<CCol>
|
||||
@@ -91,7 +143,7 @@ const DeviceActions = ({ selectedDeviceId }) => {
|
||||
</CButton>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CRow className="mt-3">
|
||||
<CCol>
|
||||
<CButton block color="primary" onClick={toggleUpgradeModal}>
|
||||
{t('actions.firmware_upgrade')}
|
||||
@@ -103,7 +155,7 @@ const DeviceActions = ({ selectedDeviceId }) => {
|
||||
</CButton>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CRow className="mt-3">
|
||||
<CCol>
|
||||
<CButton block color="primary" onClick={toggleScanModal}>
|
||||
{t('actions.wifi_scan')}
|
||||
@@ -115,7 +167,7 @@ const DeviceActions = ({ selectedDeviceId }) => {
|
||||
</CButton>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CRow className="mt-3">
|
||||
<CCol>
|
||||
<LoadingButton
|
||||
isLoading={connectLoading}
|
||||
@@ -130,20 +182,34 @@ const DeviceActions = ({ selectedDeviceId }) => {
|
||||
</CButton>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className="mt-3">
|
||||
<CCol>
|
||||
<CButton block color="primary" onClick={toggleQueueModal}>
|
||||
{t('commands.event_queue')}
|
||||
</CButton>
|
||||
</CCol>
|
||||
<CCol />
|
||||
</CRow>
|
||||
</CCardBody>
|
||||
<RebootModal show={showRebootModal} toggleModal={toggleRebootModal} />
|
||||
<BlinkModal show={showBlinkModal} toggleModal={toggleBlinkModal} />
|
||||
<FirmwareUpgradeModal show={showUpgradeModal} toggleModal={toggleUpgradeModal} />
|
||||
<DeviceFirmwareModal
|
||||
t={t}
|
||||
endpoints={endpoints}
|
||||
currentToken={currentToken}
|
||||
device={device}
|
||||
show={showUpgradeModal}
|
||||
toggleFirmwareModal={toggleUpgradeModal}
|
||||
setUpgradeStatus={setUpgradeStatus}
|
||||
upgradeStatus={upgradeStatus}
|
||||
/>
|
||||
<TraceModal show={showTraceModal} toggleModal={toggleTraceModal} />
|
||||
<WifiScanModal show={showScanModal} toggleModal={toggleScanModal} />
|
||||
<ConfigureModal show={showConfigModal} toggleModal={toggleConfigModal} />
|
||||
<FactoryResetModal show={showFactoryModal} toggleModal={toggleFactoryResetModal} />
|
||||
<EventQueueModal show={showQueueModal} toggle={toggleQueueModal} />
|
||||
</CCard>
|
||||
);
|
||||
};
|
||||
|
||||
DeviceActions.propTypes = {
|
||||
selectedDeviceId: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default DeviceActions;
|
||||
@@ -1,36 +0,0 @@
|
||||
import React from 'react';
|
||||
import { CCollapse, CCardBody } from '@coreui/react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Translation } from 'react-i18next';
|
||||
|
||||
const DeviceCommandsCollapse = ({ details, responses, index, item, getDetails, getResponse }) => (
|
||||
<Translation>
|
||||
{(t) => (
|
||||
<div>
|
||||
<CCollapse show={details.includes(index)}>
|
||||
<CCardBody>
|
||||
<h5>{t('common.result')}</h5>
|
||||
<div>{getDetails(item, index)}</div>
|
||||
</CCardBody>
|
||||
</CCollapse>
|
||||
<CCollapse show={responses.includes(index)}>
|
||||
<CCardBody>
|
||||
<h5>{t('common.details')}</h5>
|
||||
<div>{getResponse(item, index)}</div>
|
||||
</CCardBody>
|
||||
</CCollapse>
|
||||
</div>
|
||||
)}
|
||||
</Translation>
|
||||
);
|
||||
|
||||
DeviceCommandsCollapse.propTypes = {
|
||||
details: PropTypes.instanceOf(Array).isRequired,
|
||||
responses: PropTypes.instanceOf(Array).isRequired,
|
||||
index: PropTypes.number.isRequired,
|
||||
getDetails: PropTypes.func.isRequired,
|
||||
getResponse: PropTypes.func.isRequired,
|
||||
item: PropTypes.instanceOf(Object).isRequired,
|
||||
};
|
||||
|
||||
export default DeviceCommandsCollapse;
|
||||
@@ -10,14 +10,13 @@ import {
|
||||
} from '@coreui/react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Translation } from 'react-i18next';
|
||||
import styles from './index.module.scss';
|
||||
|
||||
const DeviceConfigurationModal = ({ show, toggle, configuration }) => (
|
||||
<Translation>
|
||||
{(t) => (
|
||||
<CModal size="lg" show={show} onClose={toggle}>
|
||||
<CModalHeader closeButton>
|
||||
<CModalTitle className={styles.modalTitle}>{t('configuration.title')}</CModalTitle>
|
||||
<CModalTitle className="text-dark">{t('configuration.title')}</CModalTitle>
|
||||
</CModalHeader>
|
||||
<CModalBody>
|
||||
<pre className="ignore">{JSON.stringify(configuration, null, 4)}</pre>
|
||||
@@ -1,3 +0,0 @@
|
||||
.modalTitle {
|
||||
color: black;
|
||||
}
|
||||
@@ -4,11 +4,8 @@ import {
|
||||
CCard,
|
||||
CCardHeader,
|
||||
CCardBody,
|
||||
CFormGroup,
|
||||
CCol,
|
||||
CLabel,
|
||||
CForm,
|
||||
CInput,
|
||||
CCollapse,
|
||||
CCardFooter,
|
||||
CButton,
|
||||
@@ -16,20 +13,31 @@ import {
|
||||
CPopover,
|
||||
} from '@coreui/react';
|
||||
import CIcon from '@coreui/icons-react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { cilWindowMaximize, cilClone } from '@coreui/icons';
|
||||
import { cilWindowMaximize } from '@coreui/icons';
|
||||
import { prettyDate } from 'utils/helper';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
import { getToken } from 'utils/authHelper';
|
||||
import DeviceConfigurationModal from './containers/DeviceConfigurationModal/index';
|
||||
import styles from './index.module.scss';
|
||||
import {
|
||||
CopyToClipboardButton,
|
||||
HideTextButton,
|
||||
NotesTable,
|
||||
useAuth,
|
||||
useDevice,
|
||||
} from 'ucentral-libs';
|
||||
import DeviceConfigurationModal from './DeviceConfigurationModal';
|
||||
|
||||
const DeviceConfiguration = ({ selectedDeviceId }) => {
|
||||
const DeviceConfiguration = () => {
|
||||
const { t } = useTranslation();
|
||||
const { currentToken, endpoints } = useAuth();
|
||||
const { deviceSerialNumber } = useDevice();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [collapse, setCollapse] = useState(false);
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [device, setDevice] = useState(null);
|
||||
const [copyPasswordSuccess, setCopyPasswordSuccess] = useState('');
|
||||
|
||||
const toggleShowPassword = () => {
|
||||
setShowPassword(!showPassword);
|
||||
};
|
||||
|
||||
const toggle = (e) => {
|
||||
setCollapse(!collapse);
|
||||
@@ -40,32 +48,61 @@ const DeviceConfiguration = ({ selectedDeviceId }) => {
|
||||
setShowModal(!showModal);
|
||||
};
|
||||
|
||||
const copyPasswordToClipboard = () => {
|
||||
const password = device.devicePassword === '' ? 'openwifi' : device.devicePassword;
|
||||
navigator.clipboard.writeText(password);
|
||||
setCopyPasswordSuccess(t('common.copied'));
|
||||
};
|
||||
|
||||
const getDevice = () => {
|
||||
const options = {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${getToken()}`,
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
},
|
||||
};
|
||||
|
||||
axiosInstance
|
||||
.get(`/device/${encodeURIComponent(selectedDeviceId)}`, options)
|
||||
.get(
|
||||
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}`,
|
||||
options,
|
||||
)
|
||||
.then((response) => {
|
||||
setDevice(response.data);
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
const saveNote = (currentNote) => {
|
||||
setLoading(true);
|
||||
|
||||
const parameters = {
|
||||
serialNumber: deviceSerialNumber,
|
||||
notes: [{ note: currentNote }],
|
||||
};
|
||||
|
||||
const headers = {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
};
|
||||
|
||||
axiosInstance
|
||||
.put(
|
||||
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}`,
|
||||
parameters,
|
||||
{ headers },
|
||||
)
|
||||
.then(() => {
|
||||
getDevice();
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const getPassword = () => {
|
||||
const password = device.devicePassword === '' ? 'openwifi' : device.devicePassword;
|
||||
return showPassword ? password : '******';
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedDeviceId) getDevice();
|
||||
setCopyPasswordSuccess(null);
|
||||
}, [selectedDeviceId]);
|
||||
if (deviceSerialNumber) getDevice();
|
||||
}, [deviceSerialNumber]);
|
||||
|
||||
if (device) {
|
||||
return (
|
||||
@@ -73,139 +110,142 @@ const DeviceConfiguration = ({ selectedDeviceId }) => {
|
||||
<CCard>
|
||||
<CCardHeader>
|
||||
<CRow>
|
||||
<CCol>{t('configuration.details')}</CCol>
|
||||
<CCol>
|
||||
<div className={styles.alignRight}>
|
||||
<CPopover content={t('configuration.view_json')}>
|
||||
<CButton color="secondary" onClick={toggleModal} size="sm">
|
||||
<CIcon content={cilWindowMaximize} />
|
||||
</CButton>
|
||||
</CPopover>
|
||||
</div>
|
||||
<div className="text-value-lg">{t('configuration.title')}</div>
|
||||
</CCol>
|
||||
<CCol className="text-right">
|
||||
<CPopover content={t('configuration.view_json')}>
|
||||
<CButton color="secondary" onClick={toggleModal} size="sm">
|
||||
<CIcon content={cilWindowMaximize} />
|
||||
</CButton>
|
||||
</CPopover>
|
||||
</CCol>
|
||||
</CRow>
|
||||
</CCardHeader>
|
||||
<CCardBody>
|
||||
<CForm
|
||||
action=""
|
||||
method="post"
|
||||
encType="multipart/form-data"
|
||||
className="form-horizontal"
|
||||
>
|
||||
<CFormGroup row>
|
||||
<CCol md="3">
|
||||
<CLabel>{t('common.uuid')} : </CLabel>
|
||||
</CCol>
|
||||
<CCol xs="12" md="9">
|
||||
{device.UUID}
|
||||
</CCol>
|
||||
</CFormGroup>
|
||||
<CFormGroup row>
|
||||
<CCol md="3">
|
||||
<CLabel>{t('common.serial_number')} : </CLabel>
|
||||
</CCol>
|
||||
<CCol xs="12" md="9">
|
||||
{device.serialNumber}
|
||||
</CCol>
|
||||
</CFormGroup>
|
||||
<CFormGroup row>
|
||||
<CCol md="3">
|
||||
<CLabel>{t('configuration.type')} : </CLabel>
|
||||
</CCol>
|
||||
<CCol xs="12" md="9">
|
||||
{device.deviceType}
|
||||
</CCol>
|
||||
</CFormGroup>
|
||||
<CFormGroup row>
|
||||
<CCol md="3">
|
||||
<CLabel>{t('configuration.last_configuration_change')} : </CLabel>
|
||||
</CCol>
|
||||
<CCol xs="12" md="9">
|
||||
{prettyDate(device.lastConfigurationChange)}
|
||||
</CCol>
|
||||
</CFormGroup>
|
||||
<CFormGroup row>
|
||||
<CCol md="3">
|
||||
<CLabel>{t('common.mac')} :</CLabel>
|
||||
</CCol>
|
||||
<CCol xs="12" md="9">
|
||||
{device.macAddress}
|
||||
</CCol>
|
||||
</CFormGroup>
|
||||
<CFormGroup row>
|
||||
<CCol md="3">
|
||||
<CLabel>{t('configuration.created')} : </CLabel>
|
||||
</CCol>
|
||||
<CCol xs="12" md="9">
|
||||
{prettyDate(device.createdTimestamp)}
|
||||
</CCol>
|
||||
</CFormGroup>
|
||||
<CFormGroup row>
|
||||
<CRow className="mt-2">
|
||||
<CCol md="3">
|
||||
<CLabel>{t('configuration.uuid')} : </CLabel>
|
||||
</CCol>
|
||||
<CCol xs="12" md="9">
|
||||
{device.UUID}
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className="mt-2">
|
||||
<CCol md="3">
|
||||
<CLabel>{t('common.serial_number')} : </CLabel>
|
||||
</CCol>
|
||||
<CCol xs="12" md="9">
|
||||
{device.serialNumber}
|
||||
<CopyToClipboardButton t={t} size="sm" content={device.serialNumber} />
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className="mt-2">
|
||||
<CCol md="3">
|
||||
<CLabel>{t('configuration.type')} : </CLabel>
|
||||
</CCol>
|
||||
<CCol xs="12" md="9">
|
||||
{device.deviceType}
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className="mt-2">
|
||||
<CCol md="3">
|
||||
<CLabel>{t('firmware.revision')} : </CLabel>
|
||||
</CCol>
|
||||
<CCol xs="12" md="9">
|
||||
{device.firmware}
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className="mt-2">
|
||||
<CCol md="3">
|
||||
<CLabel>{t('configuration.last_configuration_change')} : </CLabel>
|
||||
</CCol>
|
||||
<CCol xs="12" md="9">
|
||||
{prettyDate(device.lastConfigurationChange)}
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className="mt-2">
|
||||
<CCol md="3">
|
||||
<CLabel>{t('common.mac')} :</CLabel>
|
||||
</CCol>
|
||||
<CCol xs="12" md="9">
|
||||
{device.macAddress}
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className="mt-2 mb-4">
|
||||
<CCol md="3">
|
||||
<CLabel className="align-middle">{t('configuration.device_password')} : </CLabel>
|
||||
</CCol>
|
||||
<CCol xs="12" md="2">
|
||||
{getPassword()}
|
||||
</CCol>
|
||||
<CCol md="7">
|
||||
<HideTextButton t={t} toggle={toggleShowPassword} show={showPassword} />
|
||||
<CopyToClipboardButton
|
||||
t={t}
|
||||
size="sm"
|
||||
content={device?.devicePassword === '' ? 'openwifi' : device.devicePassword}
|
||||
/>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<NotesTable
|
||||
t={t}
|
||||
notes={device.notes}
|
||||
loading={loading}
|
||||
addNote={saveNote}
|
||||
descriptionColumn={false}
|
||||
/>
|
||||
<CCollapse show={collapse}>
|
||||
<CRow className="mt-2">
|
||||
<CCol md="3">
|
||||
<CLabel>{t('configuration.last_configuration_download')} : </CLabel>
|
||||
</CCol>
|
||||
<CCol xs="12" md="9">
|
||||
{prettyDate(device.lastConfigurationDownload)}
|
||||
</CCol>
|
||||
</CFormGroup>
|
||||
<CFormGroup row>
|
||||
</CRow>
|
||||
<CRow className="mt-2">
|
||||
<CCol md="3">
|
||||
<CLabel>{t('configuration.device_password')} : </CLabel>
|
||||
<CLabel>{t('common.manufacturer')} :</CLabel>
|
||||
</CCol>
|
||||
<CCol xs="12" md="9">
|
||||
{device.devicePassword === '' ? 'openwifi' : device.devicePassword}
|
||||
<CPopover content={t('common.copy_to_clipboard')}>
|
||||
<CButton onClick={copyPasswordToClipboard} size="sm">
|
||||
<CIcon content={cilClone} />
|
||||
</CButton>
|
||||
</CPopover>
|
||||
{copyPasswordSuccess}
|
||||
{device.manufacturer}
|
||||
</CCol>
|
||||
</CFormGroup>
|
||||
<CCollapse show={collapse}>
|
||||
<CFormGroup row>
|
||||
<CCol md="3">
|
||||
<CLabel>{t('common.manufacturer')} :</CLabel>
|
||||
</CCol>
|
||||
<CCol xs="12" md="9">
|
||||
{device.manufacturer}
|
||||
</CCol>
|
||||
</CFormGroup>
|
||||
<CFormGroup row>
|
||||
<CCol md="3">
|
||||
<CLabel htmlFor="text-input">{t('configuration.notes')} :</CLabel>
|
||||
</CCol>
|
||||
<CCol xs="12" md="9">
|
||||
<CInput id="text-input" name="text-input" placeholder={device.notes} />
|
||||
</CCol>
|
||||
</CFormGroup>
|
||||
<CFormGroup row>
|
||||
<CCol md="3">
|
||||
<CLabel>{t('configuration.owner')} :</CLabel>
|
||||
</CCol>
|
||||
<CCol xs="12" md="9">
|
||||
{device.owner}
|
||||
</CCol>
|
||||
</CFormGroup>
|
||||
<CFormGroup row>
|
||||
<CCol md="3">
|
||||
<CLabel>{t('configuration.location')} :</CLabel>
|
||||
</CCol>
|
||||
<CCol xs="12" md="9">
|
||||
{device.location}
|
||||
</CCol>
|
||||
</CFormGroup>
|
||||
</CCollapse>
|
||||
<CCardFooter>
|
||||
<CButton show={collapse ? 'true' : 'false'} onClick={toggle} block>
|
||||
<CIcon
|
||||
className={styles.blackIcon}
|
||||
name={collapse ? 'cilChevronTop' : 'cilChevronBottom'}
|
||||
size="lg"
|
||||
/>
|
||||
</CButton>
|
||||
</CCardFooter>
|
||||
</CForm>
|
||||
</CRow>
|
||||
<CRow className="mt-2">
|
||||
<CCol md="3">
|
||||
<CLabel>{t('configuration.created')} : </CLabel>
|
||||
</CCol>
|
||||
<CCol xs="12" md="9">
|
||||
{prettyDate(device.createdTimestamp)}
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className="mt-2">
|
||||
<CCol md="3">
|
||||
<CLabel>{t('configuration.owner')} :</CLabel>
|
||||
</CCol>
|
||||
<CCol xs="12" md="9">
|
||||
{device.owner}
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className="mt-2">
|
||||
<CCol md="3">
|
||||
<CLabel>{t('configuration.location')} :</CLabel>
|
||||
</CCol>
|
||||
<CCol xs="12" md="9">
|
||||
{device.location}
|
||||
</CCol>
|
||||
</CRow>
|
||||
</CCollapse>
|
||||
<CCardFooter>
|
||||
<CButton show={collapse ? 'true' : 'false'} onClick={toggle} block>
|
||||
<CIcon
|
||||
className="text-dark"
|
||||
name={collapse ? 'cilChevronTop' : 'cilChevronBottom'}
|
||||
size="lg"
|
||||
/>
|
||||
</CButton>
|
||||
</CCardFooter>
|
||||
</CCardBody>
|
||||
</CCard>
|
||||
<DeviceConfigurationModal show={showModal} toggle={toggleModal} configuration={device} />
|
||||
@@ -221,8 +261,4 @@ const DeviceConfiguration = ({ selectedDeviceId }) => {
|
||||
);
|
||||
};
|
||||
|
||||
DeviceConfiguration.propTypes = {
|
||||
selectedDeviceId: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default DeviceConfiguration;
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
.alignRight {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.blackIcon {
|
||||
color: black;
|
||||
}
|
||||
112
src/components/DeviceFirmwareModal/index.js
Normal file
112
src/components/DeviceFirmwareModal/index.js
Normal file
@@ -0,0 +1,112 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { DeviceFirmwareModal as Modal, useAuth } from 'ucentral-libs';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const DeviceFirmwareModal = ({
|
||||
device,
|
||||
show,
|
||||
toggleFirmwareModal,
|
||||
setUpgradeStatus,
|
||||
upgradeStatus,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { currentToken, endpoints } = useAuth();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [firmwareVersions, setFirmwareVersions] = useState([]);
|
||||
|
||||
const getFirmwareList = () => {
|
||||
setLoading(true);
|
||||
|
||||
const headers = {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
};
|
||||
|
||||
axiosInstance
|
||||
.get(`${endpoints.ucentralfms}/api/v1/firmwares?deviceType=${device.compatible}`, {
|
||||
headers,
|
||||
})
|
||||
.then((response) => {
|
||||
const sortedFirmware = response.data.firmwares.sort((a, b) => {
|
||||
const firstDate = a.imageDate;
|
||||
const secondDate = b.imageDate;
|
||||
if (firstDate < secondDate) return 1;
|
||||
return firstDate > secondDate ? -1 : 0;
|
||||
});
|
||||
setFirmwareVersions(sortedFirmware);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const upgradeToVersion = (uri) => {
|
||||
setUpgradeStatus({
|
||||
loading: true,
|
||||
});
|
||||
|
||||
const headers = {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
};
|
||||
|
||||
const parameters = {
|
||||
serialNumber: device.serialNumber,
|
||||
when: 0,
|
||||
uri,
|
||||
};
|
||||
|
||||
axiosInstance
|
||||
.post(`${endpoints.ucentralgw}/api/v1/device/${device.serialNumber}/upgrade`, parameters, {
|
||||
headers,
|
||||
})
|
||||
.then((response) => {
|
||||
setUpgradeStatus({
|
||||
loading: false,
|
||||
result: {
|
||||
success: response.data.errorCode === 0,
|
||||
error: response.data.errorCode === 0 ? '' : t('firmware.error_fetching_latest'),
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
setUpgradeStatus({
|
||||
loading: false,
|
||||
result: {
|
||||
success: false,
|
||||
error: t('common.general_error'),
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (show && device.compatible) getFirmwareList();
|
||||
}, [device, show]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
t={t}
|
||||
device={device}
|
||||
show={show}
|
||||
toggle={toggleFirmwareModal}
|
||||
firmwareVersions={firmwareVersions}
|
||||
upgradeToVersion={upgradeToVersion}
|
||||
loading={loading}
|
||||
upgradeStatus={upgradeStatus}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
DeviceFirmwareModal.propTypes = {
|
||||
device: PropTypes.instanceOf(Object).isRequired,
|
||||
show: PropTypes.bool.isRequired,
|
||||
toggleFirmwareModal: PropTypes.func.isRequired,
|
||||
setUpgradeStatus: PropTypes.func.isRequired,
|
||||
upgradeStatus: PropTypes.instanceOf(Object).isRequired,
|
||||
};
|
||||
|
||||
export default React.memo(DeviceFirmwareModal);
|
||||
@@ -10,19 +10,21 @@ import {
|
||||
CRow,
|
||||
CCol,
|
||||
CProgress,
|
||||
CPopover,
|
||||
} from '@coreui/react';
|
||||
import CIcon from '@coreui/icons-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import DatePicker from 'react-widgets/DatePicker';
|
||||
import PropTypes from 'prop-types';
|
||||
import { prettyDate, dateToUnix } from 'utils/helper';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
import { getToken } from 'utils/authHelper';
|
||||
import LoadingButton from 'components/LoadingButton';
|
||||
import styles from './index.module.scss';
|
||||
import eventBus from 'utils/eventBus';
|
||||
import { LoadingButton, useAuth, useDevice } from 'ucentral-libs';
|
||||
import DeleteLogModal from 'components/DeleteLogModal';
|
||||
|
||||
const DeviceHealth = ({ selectedDeviceId }) => {
|
||||
const DeviceHealth = () => {
|
||||
const { t } = useTranslation();
|
||||
const { currentToken, endpoints } = useAuth();
|
||||
const { deviceSerialNumber } = useDevice();
|
||||
const [collapse, setCollapse] = useState(false);
|
||||
const [details, setDetails] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -34,6 +36,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);
|
||||
@@ -60,7 +67,7 @@ const DeviceHealth = ({ selectedDeviceId }) => {
|
||||
const options = {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${getToken()}`,
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
},
|
||||
params: {
|
||||
limit: logLimit,
|
||||
@@ -77,7 +84,12 @@ const DeviceHealth = ({ selectedDeviceId }) => {
|
||||
}
|
||||
|
||||
axiosInstance
|
||||
.get(`/device/${encodeURIComponent(selectedDeviceId)}/healthchecks${extraParams}`, options)
|
||||
.get(
|
||||
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(
|
||||
deviceSerialNumber,
|
||||
)}/healthchecks${extraParams}`,
|
||||
options,
|
||||
)
|
||||
.then((response) => {
|
||||
setHealthChecks(response.data.values);
|
||||
})
|
||||
@@ -120,7 +132,7 @@ const DeviceHealth = ({ selectedDeviceId }) => {
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedDeviceId) {
|
||||
if (deviceSerialNumber) {
|
||||
setLogLimit(25);
|
||||
setLoadingMore(false);
|
||||
setShowLoadingMore(true);
|
||||
@@ -128,7 +140,7 @@ const DeviceHealth = ({ selectedDeviceId }) => {
|
||||
setEnd('');
|
||||
getDeviceHealth();
|
||||
}
|
||||
}, [selectedDeviceId]);
|
||||
}, [deviceSerialNumber]);
|
||||
|
||||
useEffect(() => {
|
||||
if (logLimit !== 25) {
|
||||
@@ -160,65 +172,88 @@ const DeviceHealth = ({ selectedDeviceId }) => {
|
||||
}, [healthChecks]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedDeviceId && start !== '' && end !== '') {
|
||||
if (deviceSerialNumber && start !== '' && end !== '') {
|
||||
getDeviceHealth();
|
||||
} else if (selectedDeviceId && start === '' && end === '') {
|
||||
} else if (deviceSerialNumber && start === '' && end === '') {
|
||||
getDeviceHealth();
|
||||
}
|
||||
}, [start, end, selectedDeviceId]);
|
||||
}, [start, end, deviceSerialNumber]);
|
||||
|
||||
useEffect(() => {
|
||||
eventBus.on('deletedHealth', () => getDeviceHealth());
|
||||
|
||||
return () => {
|
||||
eventBus.remove('deletedHealth');
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<CWidgetDropdown
|
||||
header={sanityLevel ? `${sanityLevel}%` : t('common.unknown')}
|
||||
text={t('health.title')}
|
||||
header={t('health.title')}
|
||||
text={sanityLevel ? `${sanityLevel}%` : t('common.unknown')}
|
||||
value={sanityLevel ?? 100}
|
||||
color={barColor}
|
||||
inverse="true"
|
||||
footerSlot={
|
||||
<div className={styles.footer}>
|
||||
<CProgress className={styles.progressBar} color="white" value={sanityLevel ?? 0} />
|
||||
<div className="p-4">
|
||||
<CProgress className="mb-3" color="white" value={sanityLevel ?? 0} />
|
||||
<CCollapse show={collapse}>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<div className="text-right">
|
||||
<CPopover content={t('common.delete')}>
|
||||
<CButton
|
||||
color="light"
|
||||
shape="square"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
toggleDeleteModal();
|
||||
}}
|
||||
>
|
||||
<CIcon name="cilTrash" size="lg" />
|
||||
</CButton>
|
||||
</CPopover>
|
||||
</div>
|
||||
<CRow className="mb-3">
|
||||
<CCol>
|
||||
{t('common.from')}:
|
||||
{t('common.from')}
|
||||
:
|
||||
<DatePicker includeTime onChange={(date) => modifyStart(date)} />
|
||||
</CCol>
|
||||
<CCol>
|
||||
{t('common.to')}:
|
||||
{t('common.to')}
|
||||
:
|
||||
<DatePicker includeTime onChange={(date) => modifyEnd(date)} />
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CCard>
|
||||
<div className={[styles.scrollable, 'overflow-auto'].join(' ')}>
|
||||
<CCard className="p-0">
|
||||
<div className="overflow-auto" style={{ height: '250px' }}>
|
||||
<CDataTable
|
||||
border
|
||||
items={healthChecks ?? []}
|
||||
fields={columns}
|
||||
className={styles.dataTable}
|
||||
className="text-white"
|
||||
loading={loading}
|
||||
sorterValue={{ column: 'recorded', desc: 'true' }}
|
||||
scopedSlots={{
|
||||
recorded: (item) => <td>{prettyDate(item.recorded)}</td>,
|
||||
sanity: (item) => <td>{`${item.sanity}%`}</td>,
|
||||
show_details: (item, index) => {
|
||||
if (item.sanity === 100) {
|
||||
return <></>;
|
||||
}
|
||||
return (
|
||||
<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>
|
||||
);
|
||||
},
|
||||
UUID: (item) => <td className="align-middle">{item.UUID}</td>,
|
||||
recorded: (item) => (
|
||||
<td className="align-middle">{prettyDate(item.recorded)}</td>
|
||||
),
|
||||
sanity: (item) => <td className="align-middle">{`${item.sanity}%`}</td>,
|
||||
show_details: (item, index) => (
|
||||
<td className="align-middle">
|
||||
<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>
|
||||
@@ -229,8 +264,8 @@ const DeviceHealth = ({ selectedDeviceId }) => {
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<CRow className={styles.loadMoreRow}>
|
||||
{showLoadingMore && (
|
||||
{showLoadingMore && (
|
||||
<div className="mb-3">
|
||||
<LoadingButton
|
||||
label={t('common.view_more')}
|
||||
isLoadingLabel={t('common.loading_more_ellipsis')}
|
||||
@@ -238,26 +273,28 @@ const DeviceHealth = ({ selectedDeviceId }) => {
|
||||
action={showMoreLogs}
|
||||
variant="outline"
|
||||
/>
|
||||
)}
|
||||
</CRow>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</CCard>
|
||||
</CCollapse>
|
||||
<CButton show={collapse ? 'true' : 'false'} color="transparent" onClick={toggle} block>
|
||||
<CIcon
|
||||
name={collapse ? 'cilChevronTop' : 'cilChevronBottom'}
|
||||
className={styles.icon}
|
||||
className="text-white"
|
||||
size="lg"
|
||||
/>
|
||||
</CButton>
|
||||
<DeleteLogModal
|
||||
serialNumber={deviceSerialNumber}
|
||||
object="healthchecks"
|
||||
show={showDeleteModal}
|
||||
toggle={toggleDeleteModal}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
DeviceHealth.propTypes = {
|
||||
selectedDeviceId: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default DeviceHealth;
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
.icon {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.dataTable {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.footer {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.progressBar {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.spacedRow {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.loadMoreRow {
|
||||
margin-bottom: 1%;
|
||||
}
|
||||
|
||||
.scrollable {
|
||||
height: 250px;
|
||||
}
|
||||
@@ -1,87 +1,136 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import {
|
||||
CBadge,
|
||||
CCardBody,
|
||||
CDataTable,
|
||||
CButton,
|
||||
CLink,
|
||||
CCard,
|
||||
CCardHeader,
|
||||
CRow,
|
||||
CCol,
|
||||
CPopover,
|
||||
} from '@coreui/react';
|
||||
import ReactPaginate from 'react-paginate';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Select from 'react-select';
|
||||
import PropTypes from 'prop-types';
|
||||
import { cilSync, cilInfo, cilBadge, cilBan } from '@coreui/icons';
|
||||
import CIcon from '@coreui/icons-react';
|
||||
import { getToken } from 'utils/authHelper';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
import { cleanBytesString, cropStringWithEllipsis } from 'utils/helper';
|
||||
import meshIcon from 'assets/icons/Mesh.png';
|
||||
import apIcon from 'assets/icons/AP.png';
|
||||
import internetSwitch from 'assets/icons/Switch.png';
|
||||
import iotIcon from 'assets/icons/IotIcon.png';
|
||||
import { getItem, setItem } from 'utils/localStorageHelper';
|
||||
import styles from './index.module.scss';
|
||||
import DeviceSearchBar from 'components/DeviceSearchBar';
|
||||
import DeviceFirmwareModal from 'components/DeviceFirmwareModal';
|
||||
import FirmwareHistoryModal from 'components/FirmwareHistoryModal';
|
||||
import { DeviceListTable, useAuth, useToast } from 'ucentral-libs';
|
||||
import meshIcon from '../../assets/icons/Mesh.png';
|
||||
import apIcon from '../../assets/icons/AP.png';
|
||||
import internetSwitch from '../../assets/icons/Switch.png';
|
||||
import iotIcon from '../../assets/icons/IotIcon.png';
|
||||
|
||||
const DeviceList = () => {
|
||||
const { t } = useTranslation();
|
||||
const [loadedSerials, setLoadedSerials] = useState(false);
|
||||
const [serialNumbers, setSerialNumbers] = useState([]);
|
||||
const [page, setPage] = useState(0);
|
||||
const { addToast } = useToast();
|
||||
const history = useHistory();
|
||||
const { search } = useLocation();
|
||||
const page = new URLSearchParams(search).get('page');
|
||||
const { currentToken, endpoints } = useAuth();
|
||||
const [upgradeStatus, setUpgradeStatus] = useState({
|
||||
loading: false,
|
||||
});
|
||||
const [deleteStatus, setDeleteStatus] = useState({
|
||||
loading: false,
|
||||
});
|
||||
const [deviceCount, setDeviceCount] = useState(0);
|
||||
const [pageCount, setPageCount] = useState(0);
|
||||
const [devicesPerPage, setDevicesPerPage] = useState(getItem('devicesPerPage') || 10);
|
||||
const [devicesPerPage, setDevicesPerPage] = useState(getItem('devicesPerPage') || '10');
|
||||
const [devices, setDevices] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [showHistoryModal, setHistoryModal] = useState(false);
|
||||
const [showFirmwareModal, setShowFirmwareModal] = useState(false);
|
||||
const [firmwareDevice, setFirmwareDevice] = useState({
|
||||
deviceType: '',
|
||||
serialNumber: '',
|
||||
});
|
||||
|
||||
const getSerialNumbers = () => {
|
||||
const token = getToken();
|
||||
const deviceIcons = {
|
||||
meshIcon,
|
||||
apIcon,
|
||||
internetSwitch,
|
||||
iotIcon,
|
||||
};
|
||||
|
||||
const toggleFirmwareModal = (device) => {
|
||||
setShowFirmwareModal(!showFirmwareModal);
|
||||
if (device !== undefined) setFirmwareDevice(device);
|
||||
};
|
||||
|
||||
const toggleHistoryModal = (device) => {
|
||||
setHistoryModal(!showHistoryModal);
|
||||
if (device !== undefined) setFirmwareDevice(device);
|
||||
};
|
||||
|
||||
const getDeviceInformation = (selectedPage = page, devicePerPage = devicesPerPage) => {
|
||||
setLoading(true);
|
||||
|
||||
const headers = {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${token}`,
|
||||
const options = {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
},
|
||||
};
|
||||
|
||||
let fullDevices;
|
||||
|
||||
axiosInstance
|
||||
.get('/devices?serialOnly=true', {
|
||||
headers,
|
||||
.get(
|
||||
`${
|
||||
endpoints.ucentralgw
|
||||
}/api/v1/devices?deviceWithStatus=true&limit=${devicePerPage}&offset=${
|
||||
devicePerPage * selectedPage + 1
|
||||
}`,
|
||||
options,
|
||||
)
|
||||
.then((response) => {
|
||||
fullDevices = response.data.devicesWithStatus;
|
||||
const serialsToGet = fullDevices.map((device) => device.serialNumber);
|
||||
|
||||
return axiosInstance.get(
|
||||
`${endpoints.ucentralfms}/api/v1/firmwareAge?select=${serialsToGet}`,
|
||||
options,
|
||||
);
|
||||
})
|
||||
.then((response) => {
|
||||
setSerialNumbers(response.data.serialNumbers);
|
||||
setLoadedSerials(true);
|
||||
fullDevices = fullDevices.map((device, index) => {
|
||||
const foundAgeDate = response.data.ages[index].age !== undefined;
|
||||
if (foundAgeDate) {
|
||||
return {
|
||||
...device,
|
||||
firmwareInfo: {
|
||||
age: response.data.ages[index].age,
|
||||
latest: response.data.ages[index].latest,
|
||||
},
|
||||
};
|
||||
}
|
||||
return device;
|
||||
});
|
||||
setDevices(fullDevices);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const getDeviceInformation = () => {
|
||||
const token = getToken();
|
||||
const getCount = () => {
|
||||
setLoading(true);
|
||||
|
||||
const headers = {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${token}`,
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
};
|
||||
|
||||
const startIndex = page * devicesPerPage;
|
||||
const endIndex = parseInt(startIndex, 10) + parseInt(devicesPerPage, 10);
|
||||
const serialsToGet = serialNumbers
|
||||
.slice(startIndex, endIndex)
|
||||
.map((x) => encodeURIComponent(x))
|
||||
.join(',');
|
||||
|
||||
axiosInstance
|
||||
.get(`/devices?deviceWithStatus=true&select=${serialsToGet}`, {
|
||||
.get(`${endpoints.ucentralgw}/api/v1/devices?countOnly=true`, {
|
||||
headers,
|
||||
})
|
||||
.then((response) => {
|
||||
setDevices(response.data.devicesWithStatus);
|
||||
setLoading(false);
|
||||
const devicesCount = response.data.count;
|
||||
const pagesCount = Math.ceil(devicesCount / devicesPerPage);
|
||||
setPageCount(pagesCount);
|
||||
setDeviceCount(devicesCount);
|
||||
|
||||
let selectedPage = page;
|
||||
|
||||
if (page >= pagesCount) {
|
||||
history.push(`/devices?page=${pagesCount - 1}`);
|
||||
selectedPage = pagesCount - 1;
|
||||
}
|
||||
getDeviceInformation(selectedPage);
|
||||
})
|
||||
.catch(() => {
|
||||
setLoading(false);
|
||||
@@ -89,18 +138,22 @@ const DeviceList = () => {
|
||||
};
|
||||
|
||||
const refreshDevice = (serialNumber) => {
|
||||
const token = getToken();
|
||||
setLoading(true);
|
||||
|
||||
const headers = {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${token}`,
|
||||
const options = {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
},
|
||||
};
|
||||
|
||||
axiosInstance
|
||||
.get(`/devices?deviceWithStatus=true&select=${encodeURIComponent(serialNumber)}`, {
|
||||
headers,
|
||||
})
|
||||
.get(
|
||||
`${endpoints.ucentralgw}/api/v1/devices?deviceWithStatus=true&select=${encodeURIComponent(
|
||||
serialNumber,
|
||||
)}`,
|
||||
options,
|
||||
)
|
||||
.then((response) => {
|
||||
const device = response.data.devicesWithStatus[0];
|
||||
const foundIndex = devices.findIndex((obj) => obj.serialNumber === serialNumber);
|
||||
@@ -117,314 +170,215 @@ const DeviceList = () => {
|
||||
const updateDevicesPerPage = (value) => {
|
||||
setItem('devicesPerPage', value);
|
||||
setDevicesPerPage(value);
|
||||
|
||||
const newPageCount = Math.ceil(deviceCount / value);
|
||||
setPageCount(newPageCount);
|
||||
|
||||
let selectedPage = page;
|
||||
|
||||
if (page >= newPageCount) {
|
||||
history.push(`/devices?page=${newPageCount - 1}`);
|
||||
selectedPage = newPageCount - 1;
|
||||
}
|
||||
|
||||
getDeviceInformation(selectedPage, value);
|
||||
};
|
||||
|
||||
const updatePageCount = ({ selected: selectedPage }) => {
|
||||
setPage(selectedPage);
|
||||
history.push(`/devices?page=${selectedPage}`);
|
||||
getDeviceInformation(selectedPage);
|
||||
};
|
||||
|
||||
const upgradeToLatest = (device) => {
|
||||
setUpgradeStatus({
|
||||
loading: true,
|
||||
});
|
||||
|
||||
const options = {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
},
|
||||
};
|
||||
|
||||
axiosInstance
|
||||
.get(
|
||||
`${endpoints.ucentralfms}/api/v1/firmwares?deviceType=${device.compatible}&latestOnly=true`,
|
||||
options,
|
||||
)
|
||||
.then((response) => {
|
||||
if (response.data.uri) {
|
||||
const parameters = {
|
||||
serialNumber: device.serialNumber,
|
||||
when: 0,
|
||||
uri: response.data.uri,
|
||||
};
|
||||
return axiosInstance.post(
|
||||
`${endpoints.ucentralgw}/api/v1/device/${device.serialNumber}/upgrade`,
|
||||
parameters,
|
||||
options,
|
||||
);
|
||||
}
|
||||
setUpgradeStatus({
|
||||
loading: false,
|
||||
result: {
|
||||
success: false,
|
||||
error: t('firmware.error_fetching_latest'),
|
||||
},
|
||||
});
|
||||
return null;
|
||||
})
|
||||
.then((response) => {
|
||||
if (response) {
|
||||
setUpgradeStatus({
|
||||
loading: false,
|
||||
result: {
|
||||
success: response.data.errorCode === 0,
|
||||
error: response.data.errorCode === 0 ? '' : t('firmware.error_fetching_latest'),
|
||||
},
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
setUpgradeStatus({
|
||||
loading: false,
|
||||
result: {
|
||||
success: false,
|
||||
error: t('common.general_error'),
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const connectRtty = (serialNumber) => {
|
||||
const options = {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
},
|
||||
};
|
||||
|
||||
axiosInstance
|
||||
.get(
|
||||
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(serialNumber)}/rtty`,
|
||||
options,
|
||||
)
|
||||
.then((response) => {
|
||||
const url = `https://${response.data.server}:${response.data.viewport}/connect/${response.data.connectionId}`;
|
||||
const newWindow = window.open(url, '_blank', 'noopener,noreferrer');
|
||||
if (newWindow) newWindow.opener = null;
|
||||
})
|
||||
.catch(() => {
|
||||
addToast({
|
||||
title: t('common.error'),
|
||||
body: t('common.unable_to_connect'),
|
||||
color: 'danger',
|
||||
autohide: true,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const deleteDevice = (serialNumber) => {
|
||||
setDeleteStatus({
|
||||
loading: true,
|
||||
});
|
||||
|
||||
const options = {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
},
|
||||
};
|
||||
|
||||
axiosInstance
|
||||
.delete(`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(serialNumber)}`, options)
|
||||
.then(() => {
|
||||
addToast({
|
||||
title: t('common.success'),
|
||||
body: t('common.device_deleted'),
|
||||
color: 'success',
|
||||
autohide: true,
|
||||
});
|
||||
getCount();
|
||||
})
|
||||
.catch(() => {
|
||||
addToast({
|
||||
title: t('common.error'),
|
||||
body: t('common.unable_to_delete'),
|
||||
color: 'danger',
|
||||
autohide: true,
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
setDeleteStatus({
|
||||
loading: false,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getSerialNumbers();
|
||||
if (page === undefined || page === null || Number.isNaN(page)) {
|
||||
history.push(`/devices?page=0`);
|
||||
}
|
||||
getCount();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (loadedSerials) getDeviceInformation();
|
||||
}, [serialNumbers, page, devicesPerPage, loadedSerials]);
|
||||
|
||||
useEffect(() => {
|
||||
if (loadedSerials) {
|
||||
const count = Math.ceil(serialNumbers.length / devicesPerPage);
|
||||
setPageCount(count);
|
||||
if (upgradeStatus.result !== undefined) {
|
||||
addToast({
|
||||
title: upgradeStatus.result.success ? t('common.success') : t('common.error'),
|
||||
body: upgradeStatus.result.success
|
||||
? t('firmware.upgrade_command_submitted')
|
||||
: upgradeStatus.result.error,
|
||||
color: upgradeStatus.result.success ? 'success' : 'danger',
|
||||
autohide: true,
|
||||
});
|
||||
setUpgradeStatus({
|
||||
loading: false,
|
||||
});
|
||||
setShowFirmwareModal(false);
|
||||
}
|
||||
}, [devicesPerPage, loadedSerials]);
|
||||
}, [upgradeStatus]);
|
||||
|
||||
return (
|
||||
<DeviceListDisplay
|
||||
devices={devices}
|
||||
loading={loading}
|
||||
updateDevicesPerPage={updateDevicesPerPage}
|
||||
devicesPerPage={devicesPerPage}
|
||||
pageCount={pageCount}
|
||||
updatePage={updatePageCount}
|
||||
pageRangeDisplayed={5}
|
||||
refreshDevice={refreshDevice}
|
||||
t={t}
|
||||
/>
|
||||
<div>
|
||||
<DeviceListTable
|
||||
currentPage={page}
|
||||
t={t}
|
||||
searchBar={<DeviceSearchBar />}
|
||||
devices={devices}
|
||||
loading={loading}
|
||||
updateDevicesPerPage={updateDevicesPerPage}
|
||||
devicesPerPage={devicesPerPage}
|
||||
pageCount={pageCount}
|
||||
updatePage={updatePageCount}
|
||||
pageRangeDisplayed={5}
|
||||
refreshDevice={refreshDevice}
|
||||
toggleFirmwareModal={toggleFirmwareModal}
|
||||
toggleHistoryModal={toggleHistoryModal}
|
||||
upgradeToLatest={upgradeToLatest}
|
||||
upgradeStatus={upgradeStatus}
|
||||
deviceIcons={deviceIcons}
|
||||
connectRtty={connectRtty}
|
||||
deleteDevice={deleteDevice}
|
||||
deleteStatus={deleteStatus}
|
||||
/>
|
||||
<DeviceFirmwareModal
|
||||
endpoints={endpoints}
|
||||
currentToken={currentToken}
|
||||
device={firmwareDevice}
|
||||
show={showFirmwareModal}
|
||||
toggleFirmwareModal={toggleFirmwareModal}
|
||||
setUpgradeStatus={setUpgradeStatus}
|
||||
upgradeStatus={upgradeStatus}
|
||||
/>
|
||||
<FirmwareHistoryModal
|
||||
serialNumber={firmwareDevice.serialNumber}
|
||||
show={showHistoryModal}
|
||||
toggle={toggleHistoryModal}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const DeviceListDisplay = ({
|
||||
devices,
|
||||
devicesPerPage,
|
||||
loading,
|
||||
updateDevicesPerPage,
|
||||
pageCount,
|
||||
updatePage,
|
||||
refreshDevice,
|
||||
t,
|
||||
}) => {
|
||||
const columns = [
|
||||
{ key: 'deviceType', label: '', filter: false, sorter: false, _style: { width: '5%' } },
|
||||
{ key: 'verifiedCertificate', label: t('common.certificate'), _style: { width: '1%' } },
|
||||
{ key: 'serialNumber', label: t('common.serial_number'), _style: { width: '5%' } },
|
||||
{ key: 'UUID', label: t('common.config_id'), _style: { width: '5%' } },
|
||||
{ key: 'firmware', label: t('common.firmware'), filter: false, _style: { width: '20%' } },
|
||||
{ key: 'compatible', label: t('common.compatible'), filter: false, _style: { width: '20%' } },
|
||||
{ key: 'txBytes', label: 'Tx', filter: false, _style: { width: '12%' } },
|
||||
{ key: 'rxBytes', label: 'Rx', filter: false, _style: { width: '12%' } },
|
||||
{ key: 'ipAddress', label: t('common.ip_address'), _style: { width: '16%' } },
|
||||
{
|
||||
key: 'show_details',
|
||||
label: '',
|
||||
_style: { width: '3%' },
|
||||
sorter: false,
|
||||
filter: false,
|
||||
},
|
||||
{
|
||||
key: 'refresh',
|
||||
label: '',
|
||||
_style: { width: '2%' },
|
||||
sorter: false,
|
||||
filter: false,
|
||||
},
|
||||
];
|
||||
|
||||
const selectOptions = [
|
||||
{ value: '10', label: '10' },
|
||||
{ value: '25', label: '25' },
|
||||
{ value: '50', label: '50' },
|
||||
];
|
||||
|
||||
const getDeviceIcon = (deviceType) => {
|
||||
if (deviceType === 'AP_Default' || deviceType === 'AP') {
|
||||
return <img src={apIcon} className={styles.icon} alt="AP" />;
|
||||
}
|
||||
if (deviceType === 'MESH') {
|
||||
return <img src={meshIcon} className={styles.icon} alt="MESH" />;
|
||||
}
|
||||
if (deviceType === 'SWITCH') {
|
||||
return <img src={internetSwitch} className={styles.icon} alt="SWITCH" />;
|
||||
}
|
||||
if (deviceType === 'IOT') {
|
||||
return <img src={iotIcon} className={styles.icon} alt="SWITCH" />;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const getCertBadge = (cert) => {
|
||||
if (cert === 'NO_CERTIFICATE') {
|
||||
return (
|
||||
<div className={styles.certificateWrapper}>
|
||||
<CIcon className={styles.badge} name="cil-badge" content={cilBadge} size="2xl" alt="AP" />
|
||||
<CIcon
|
||||
className={styles.badCertificate}
|
||||
name="cil-ban"
|
||||
content={cilBan}
|
||||
size="3xl"
|
||||
alt="AP"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
let color = 'transparent';
|
||||
switch (cert) {
|
||||
case 'VALID_CERTIFICATE':
|
||||
color = 'danger';
|
||||
break;
|
||||
case 'MISMATCH_SERIAL':
|
||||
return (
|
||||
<CBadge color={color} className={styles.mismatchBackground}>
|
||||
<CIcon name="cil-badge" content={cilBadge} size="2xl" alt="AP" />
|
||||
</CBadge>
|
||||
);
|
||||
case 'VERIFIED':
|
||||
color = 'success';
|
||||
break;
|
||||
default:
|
||||
return (
|
||||
<div className={styles.certificateWrapper}>
|
||||
<CIcon
|
||||
className={styles.badge}
|
||||
name="cil-badge"
|
||||
content={cilBadge}
|
||||
size="2xl"
|
||||
alt="AP"
|
||||
/>
|
||||
<CIcon
|
||||
className={styles.badCertificate}
|
||||
name="cil-ban"
|
||||
content={cilBan}
|
||||
size="3xl"
|
||||
alt="AP"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<CBadge color={color}>
|
||||
<CIcon name="cil-badge" content={cilBadge} size="2xl" alt="AP" />
|
||||
</CBadge>
|
||||
);
|
||||
};
|
||||
|
||||
const getStatusBadge = (status) => {
|
||||
if (status) {
|
||||
return 'success';
|
||||
}
|
||||
return 'danger';
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<CCard>
|
||||
<CCardHeader>
|
||||
<CRow>
|
||||
<CCol />
|
||||
<CCol xs={2}>
|
||||
<Select
|
||||
isClearable={false}
|
||||
options={selectOptions}
|
||||
defaultValue={{ value: devicesPerPage, label: devicesPerPage }}
|
||||
onChange={(value) => updateDevicesPerPage(value.value)}
|
||||
/>
|
||||
</CCol>
|
||||
</CRow>
|
||||
</CCardHeader>
|
||||
<CCardBody>
|
||||
<CDataTable
|
||||
items={devices ?? []}
|
||||
fields={columns}
|
||||
hover
|
||||
loading={loading}
|
||||
scopedSlots={{
|
||||
serialNumber: (item) => (
|
||||
<td className={styles.column}>
|
||||
<CLink
|
||||
className="c-subheader-nav-link"
|
||||
aria-current="page"
|
||||
to={() => `/devices/${item.serialNumber}`}
|
||||
>
|
||||
{item.serialNumber}
|
||||
</CLink>
|
||||
</td>
|
||||
),
|
||||
deviceType: (item) => (
|
||||
<td className={styles.column}>
|
||||
<CPopover
|
||||
content={item.connected ? t('common.connected') : t('common.not_connected')}
|
||||
placement="top"
|
||||
>
|
||||
<CBadge color={getStatusBadge(item.connected)}>
|
||||
{getDeviceIcon(item.deviceType) ?? item.deviceType}
|
||||
</CBadge>
|
||||
</CPopover>
|
||||
</td>
|
||||
),
|
||||
verifiedCertificate: (item) => (
|
||||
<td className={styles.column}>
|
||||
<CPopover
|
||||
content={item.verifiedCertificate ?? t('common.unknown')}
|
||||
placement="top"
|
||||
>
|
||||
{getCertBadge(item.verifiedCertificate)}
|
||||
</CPopover>
|
||||
</td>
|
||||
),
|
||||
firmware: (item) => (
|
||||
<td>
|
||||
<CPopover
|
||||
content={item.firmware ? item.firmware : t('common.na')}
|
||||
placement="top"
|
||||
>
|
||||
<p>{cropStringWithEllipsis(item.firmware, 16)}</p>
|
||||
</CPopover>
|
||||
</td>
|
||||
),
|
||||
compatible: (item) => (
|
||||
<td>
|
||||
<CPopover
|
||||
content={item.compatible ? item.compatible : t('common.na')}
|
||||
placement="top"
|
||||
>
|
||||
<p>{cropStringWithEllipsis(item.compatible, 16)}</p>
|
||||
</CPopover>
|
||||
</td>
|
||||
),
|
||||
txBytes: (item) => <td>{cleanBytesString(item.txBytes)}</td>,
|
||||
rxBytes: (item) => <td>{cleanBytesString(item.rxBytes)}</td>,
|
||||
ipAddress: (item) => (
|
||||
<td>
|
||||
<CPopover
|
||||
content={item.ipAddress ? item.ipAddress : t('common.na')}
|
||||
placement="top"
|
||||
>
|
||||
<p>{cropStringWithEllipsis(item.ipAddress, 20)}</p>
|
||||
</CPopover>
|
||||
</td>
|
||||
),
|
||||
refresh: (item) => (
|
||||
<td className="py-2">
|
||||
<CPopover content={t('common.refresh_device')}>
|
||||
<CButton
|
||||
onClick={() => refreshDevice(item.serialNumber)}
|
||||
color="primary"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
>
|
||||
<CIcon name="cil-sync" content={cilSync} size="sm" />
|
||||
</CButton>
|
||||
</CPopover>
|
||||
</td>
|
||||
),
|
||||
show_details: (item) => (
|
||||
<td className="py-2">
|
||||
<CPopover content={t('configuration.details')}>
|
||||
<CLink
|
||||
className="c-subheader-nav-link"
|
||||
aria-current="page"
|
||||
to={() => `/devices/${item.serialNumber}`}
|
||||
>
|
||||
<CButton color="primary" variant="outline" shape="square" size="sm">
|
||||
<CIcon name="cil-info" content={cilInfo} size="sm" />
|
||||
</CButton>
|
||||
</CLink>
|
||||
</CPopover>
|
||||
</td>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<ReactPaginate
|
||||
previousLabel="← Previous"
|
||||
nextLabel="Next →"
|
||||
pageCount={pageCount}
|
||||
onPageChange={updatePage}
|
||||
breakClassName="page-item"
|
||||
breakLinkClassName="page-link"
|
||||
containerClassName="pagination"
|
||||
pageClassName="page-item"
|
||||
pageLinkClassName="page-link"
|
||||
previousClassName="page-item"
|
||||
previousLinkClassName="page-link"
|
||||
nextClassName="page-item"
|
||||
nextLinkClassName="page-link"
|
||||
activeClassName="active"
|
||||
/>
|
||||
</CCardBody>
|
||||
</CCard>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
DeviceListDisplay.propTypes = {
|
||||
devices: PropTypes.instanceOf(Array).isRequired,
|
||||
updateDevicesPerPage: PropTypes.func.isRequired,
|
||||
pageCount: PropTypes.number.isRequired,
|
||||
updatePage: PropTypes.func.isRequired,
|
||||
devicesPerPage: PropTypes.string.isRequired,
|
||||
refreshDevice: PropTypes.func.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
loading: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default DeviceList;
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
.icon {
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
}
|
||||
|
||||
.column {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.certificateWrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.badge {
|
||||
position: absolute;
|
||||
left: 31%;
|
||||
margin-top: 8%;
|
||||
}
|
||||
|
||||
.badCertificate {
|
||||
position: absolute;
|
||||
z-index: 99;
|
||||
left: 22%;
|
||||
color: #e55353;
|
||||
}
|
||||
|
||||
.mismatchBackground {
|
||||
background-color: #ffff5c;
|
||||
}
|
||||
@@ -9,19 +9,21 @@ import {
|
||||
CDataTable,
|
||||
CCard,
|
||||
CCardBody,
|
||||
CPopover,
|
||||
} from '@coreui/react';
|
||||
import CIcon from '@coreui/icons-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import DatePicker from 'react-widgets/DatePicker';
|
||||
import PropTypes from 'prop-types';
|
||||
import { prettyDate, dateToUnix } from 'utils/helper';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
import { getToken } from 'utils/authHelper';
|
||||
import LoadingButton from 'components/LoadingButton';
|
||||
import styles from './index.module.scss';
|
||||
import eventBus from 'utils/eventBus';
|
||||
import { LoadingButton, useAuth, useDevice } from 'ucentral-libs';
|
||||
import DeleteLogModal from 'components/DeleteLogModal';
|
||||
|
||||
const DeviceLogs = ({ selectedDeviceId }) => {
|
||||
const DeviceLogs = () => {
|
||||
const { t } = useTranslation();
|
||||
const { currentToken, endpoints } = useAuth();
|
||||
const { deviceSerialNumber } = useDevice();
|
||||
const [collapse, setCollapse] = useState(false);
|
||||
const [details, setDetails] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -31,6 +33,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);
|
||||
@@ -57,7 +64,7 @@ const DeviceLogs = ({ selectedDeviceId }) => {
|
||||
const options = {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${getToken()}`,
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
},
|
||||
params: {
|
||||
limit: logLimit,
|
||||
@@ -74,7 +81,12 @@ const DeviceLogs = ({ selectedDeviceId }) => {
|
||||
}
|
||||
|
||||
axiosInstance
|
||||
.get(`/device/${encodeURIComponent(selectedDeviceId)}/logs${extraParams}`, options)
|
||||
.get(
|
||||
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(
|
||||
deviceSerialNumber,
|
||||
)}/logs${extraParams}`,
|
||||
options,
|
||||
)
|
||||
.then((response) => {
|
||||
setLogs(response.data.values);
|
||||
})
|
||||
@@ -117,7 +129,7 @@ const DeviceLogs = ({ selectedDeviceId }) => {
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedDeviceId) {
|
||||
if (deviceSerialNumber) {
|
||||
setLogLimit(25);
|
||||
setLoadingMore(false);
|
||||
setShowLoadingMore(true);
|
||||
@@ -125,7 +137,7 @@ const DeviceLogs = ({ selectedDeviceId }) => {
|
||||
setEnd('');
|
||||
getLogs();
|
||||
}
|
||||
}, [selectedDeviceId]);
|
||||
}, [deviceSerialNumber]);
|
||||
|
||||
useEffect(() => {
|
||||
if (logLimit !== 25) {
|
||||
@@ -142,97 +154,121 @@ const DeviceLogs = ({ selectedDeviceId }) => {
|
||||
}, [logs]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedDeviceId && start !== '' && end !== '') {
|
||||
if (deviceSerialNumber && start !== '' && end !== '') {
|
||||
getLogs();
|
||||
} else if (selectedDeviceId && start === '' && end === '') {
|
||||
} else if (deviceSerialNumber && start === '' && end === '') {
|
||||
getLogs();
|
||||
}
|
||||
}, [start, end, selectedDeviceId]);
|
||||
}, [start, end, deviceSerialNumber]);
|
||||
|
||||
useEffect(() => {
|
||||
eventBus.on('deletedLogs', () => getLogs());
|
||||
|
||||
return () => {
|
||||
eventBus.remove('deletedLogs');
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<CWidgetDropdown
|
||||
inverse="true"
|
||||
color="gradient-info"
|
||||
header={t('device_logs.title')}
|
||||
footerSlot={
|
||||
<div className={styles.footer}>
|
||||
<CCollapse show={collapse}>
|
||||
<CRow className={styles.datepickerRow}>
|
||||
<CCol>
|
||||
{t('common.from')}
|
||||
<DatePicker includeTime onChange={(date) => modifyStart(date)} />
|
||||
</CCol>
|
||||
<CCol>
|
||||
{t('common.to')}
|
||||
<DatePicker includeTime onChange={(date) => modifyEnd(date)} />
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CCard>
|
||||
<div className={[styles.scrollableCard, 'overflow-auto'].join(' ')}>
|
||||
<CDataTable
|
||||
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>
|
||||
<CWidgetDropdown
|
||||
inverse="true"
|
||||
color="gradient-info"
|
||||
header={t('device_logs.title')}
|
||||
footerSlot={
|
||||
<div className="p-4">
|
||||
<CCollapse show={collapse}>
|
||||
<div className="text-right">
|
||||
<CPopover content={t('common.delete')}>
|
||||
<CButton
|
||||
color="light"
|
||||
shape="square"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
toggleDeleteModal();
|
||||
}}
|
||||
>
|
||||
<CIcon name="cilTrash" size="lg" />
|
||||
</CButton>
|
||||
</CPopover>
|
||||
</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>
|
||||
<CRow className="mb-3">
|
||||
<CCol>
|
||||
{t('common.from')}
|
||||
<DatePicker includeTime onChange={(date) => modifyStart(date)} />
|
||||
</CCol>
|
||||
<CCol>
|
||||
{t('common.to')}
|
||||
<DatePicker includeTime onChange={(date) => modifyEnd(date)} />
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CCard>
|
||||
<div className="overflow-auto" style={{ height: '250px' }}>
|
||||
<CDataTable
|
||||
items={logs ?? []}
|
||||
fields={columns}
|
||||
loading={loading}
|
||||
className="text-white"
|
||||
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>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
{showLoadingMore && (
|
||||
<div className="mb-3">
|
||||
<LoadingButton
|
||||
label={t('common.view_more')}
|
||||
isLoadingLabel={t('common.loading_more_ellipsis')}
|
||||
isLoading={loadingMore}
|
||||
action={showMoreLogs}
|
||||
variant="outline"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</CCard>
|
||||
</CCollapse>
|
||||
<CButton show={collapse ? 'true' : 'false'} color="transparent" onClick={toggle} block>
|
||||
<CIcon
|
||||
name={collapse ? 'cilChevronTop' : 'cilChevronBottom'}
|
||||
className="text-white"
|
||||
size="lg"
|
||||
/>
|
||||
</CButton>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<DeleteLogModal
|
||||
serialNumber={deviceSerialNumber}
|
||||
object="logs"
|
||||
show={showDeleteModal}
|
||||
toggle={toggleDeleteModal}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
DeviceLogs.propTypes = {
|
||||
selectedDeviceId: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default DeviceLogs;
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
.whiteIcon {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.footer {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.datepickerRow {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.scrollableCard {
|
||||
height: 250px;
|
||||
}
|
||||
|
||||
.loadMoreRow {
|
||||
margin-bottom: 1%;
|
||||
}
|
||||
71
src/components/DeviceSearchBar/index.js
Normal file
71
src/components/DeviceSearchBar/index.js
Normal file
@@ -0,0 +1,71 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useAuth, DeviceSearchBar as SearchBar } from 'ucentral-libs';
|
||||
import { checkIfJson } from 'utils/helper';
|
||||
|
||||
const DeviceSearchBar = () => {
|
||||
const { t } = useTranslation();
|
||||
const history = useHistory();
|
||||
const { currentToken, endpoints } = useAuth();
|
||||
const [socket, setSocket] = useState(null);
|
||||
const [results, setResults] = useState([]);
|
||||
const [waitingSearch, setWaitingSearch] = useState('');
|
||||
|
||||
const search = (value) => {
|
||||
if (socket.readyState === WebSocket.OPEN) {
|
||||
if (value.length > 0 && value.match('^[a-fA-F0-9]+$')) {
|
||||
setWaitingSearch('');
|
||||
socket.send(
|
||||
JSON.stringify({ command: 'serial_number_search', serial_prefix: value.toLowerCase() }),
|
||||
);
|
||||
} else {
|
||||
setResults([]);
|
||||
}
|
||||
} else if (socket.readyState !== WebSocket.CONNECTING) {
|
||||
setWaitingSearch(value);
|
||||
setSocket(new WebSocket(`${endpoints.ucentralgw.replace('https', 'wss')}/api/v1/ws`));
|
||||
} else {
|
||||
setWaitingSearch(value);
|
||||
}
|
||||
};
|
||||
|
||||
const closeSocket = () => {
|
||||
if (socket !== null) {
|
||||
socket.close();
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (socket !== null) {
|
||||
socket.onopen = () => {
|
||||
socket.send(`token:${currentToken}`);
|
||||
};
|
||||
|
||||
socket.onmessage = (event) => {
|
||||
if (checkIfJson(event.data)) {
|
||||
const result = JSON.parse(event.data);
|
||||
if (result.command === 'serial_number_search' && result.serialNumbers) {
|
||||
setResults(result.serialNumbers);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (waitingSearch.length > 0) {
|
||||
search(waitingSearch);
|
||||
}
|
||||
}
|
||||
|
||||
return () => closeSocket();
|
||||
}, [socket]);
|
||||
|
||||
useEffect(() => {
|
||||
if (socket === null) {
|
||||
setSocket(new WebSocket(`${endpoints.ucentralgw.replace('https', 'wss')}/api/v1/ws`));
|
||||
}
|
||||
}, []);
|
||||
|
||||
return <SearchBar t={t} search={search} results={results} history={history} />;
|
||||
};
|
||||
|
||||
export default DeviceSearchBar;
|
||||
66
src/components/DeviceStatusCard/index.js
Normal file
66
src/components/DeviceStatusCard/index.js
Normal file
@@ -0,0 +1,66 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
import { DeviceStatusCard as Card, useDevice, useAuth } from 'ucentral-libs';
|
||||
|
||||
const DeviceStatusCard = () => {
|
||||
const { t } = useTranslation();
|
||||
const { currentToken, endpoints } = useAuth();
|
||||
const { deviceSerialNumber } = useDevice();
|
||||
const [lastStats, setLastStats] = useState(null);
|
||||
const [status, setStatus] = useState(null);
|
||||
const [error, setError] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const getData = () => {
|
||||
setLoading(true);
|
||||
const options = {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
},
|
||||
};
|
||||
|
||||
const lastStatsRequest = axiosInstance.get(
|
||||
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(
|
||||
deviceSerialNumber,
|
||||
)}/statistics?lastOnly=true`,
|
||||
options,
|
||||
);
|
||||
const statusRequest = axiosInstance.get(
|
||||
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}/status`,
|
||||
options,
|
||||
);
|
||||
|
||||
Promise.all([lastStatsRequest, statusRequest])
|
||||
.then(([newStats, newStatus]) => {
|
||||
setLastStats(newStats.data);
|
||||
setStatus(newStatus.data);
|
||||
})
|
||||
.catch(() => {
|
||||
setError(true);
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setError(false);
|
||||
if (deviceSerialNumber) getData();
|
||||
}, [deviceSerialNumber]);
|
||||
|
||||
return (
|
||||
<Card
|
||||
t={t}
|
||||
loading={loading}
|
||||
error={error}
|
||||
deviceSerialNumber={deviceSerialNumber}
|
||||
getData={getData}
|
||||
status={status}
|
||||
lastStats={lastStats}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeviceStatusCard;
|
||||
230
src/components/EditUserModal/index.js
Normal file
230
src/components/EditUserModal/index.js
Normal file
@@ -0,0 +1,230 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
import { useUser, EditUserModal as Modal, useAuth, useToast } from 'ucentral-libs';
|
||||
|
||||
const initialState = {
|
||||
Id: {
|
||||
value: '',
|
||||
error: false,
|
||||
editable: false,
|
||||
},
|
||||
changePassword: {
|
||||
value: false,
|
||||
error: false,
|
||||
editable: false,
|
||||
},
|
||||
currentPassword: {
|
||||
value: '',
|
||||
error: false,
|
||||
editable: true,
|
||||
},
|
||||
email: {
|
||||
value: '',
|
||||
error: false,
|
||||
editable: false,
|
||||
},
|
||||
description: {
|
||||
value: '',
|
||||
error: false,
|
||||
editable: true,
|
||||
},
|
||||
name: {
|
||||
value: '',
|
||||
error: false,
|
||||
editable: true,
|
||||
},
|
||||
userRole: {
|
||||
value: '',
|
||||
error: false,
|
||||
editable: true,
|
||||
},
|
||||
notes: {
|
||||
value: [],
|
||||
editable: false,
|
||||
},
|
||||
};
|
||||
|
||||
const EditUserModal = ({ show, toggle, userId, getUsers }) => {
|
||||
const { t } = useTranslation();
|
||||
const { currentToken, endpoints } = useAuth();
|
||||
const { addToast } = useToast();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [initialUser, setInitialUser] = useState({});
|
||||
const [user, updateWithId, updateWithKey, setUser] = useUser(initialState);
|
||||
const [policies, setPolicies] = useState({
|
||||
passwordPolicy: '',
|
||||
passwordPattern: '',
|
||||
accessPolicy: '',
|
||||
});
|
||||
|
||||
const getPasswordPolicy = () => {
|
||||
axiosInstance
|
||||
.post(`${endpoints.ucentralsec}/api/v1/oauth2?requirements=true`, {})
|
||||
.then((response) => {
|
||||
const newPolicies = response.data;
|
||||
newPolicies.accessPolicy = `${endpoints.ucentralsec}${newPolicies.accessPolicy}`;
|
||||
newPolicies.passwordPolicy = `${endpoints.ucentralsec}${newPolicies.passwordPolicy}`;
|
||||
setPolicies(response.data);
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
const getUser = () => {
|
||||
const options = {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
},
|
||||
};
|
||||
|
||||
axiosInstance
|
||||
.get(`${endpoints.ucentralsec}/api/v1/user/${userId}`, options)
|
||||
.then((response) => {
|
||||
const newUser = {};
|
||||
|
||||
for (const key of Object.keys(response.data)) {
|
||||
if (key in initialState && key !== 'currentPassword') {
|
||||
newUser[key] = {
|
||||
...initialState[key],
|
||||
value: response.data[key],
|
||||
};
|
||||
}
|
||||
}
|
||||
setInitialUser({ ...initialState, ...newUser });
|
||||
setUser({ ...initialState, ...newUser });
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
const updateUser = () => {
|
||||
setLoading(true);
|
||||
|
||||
const parameters = {
|
||||
id: userId,
|
||||
};
|
||||
|
||||
let newData = false;
|
||||
|
||||
for (const key of Object.keys(user)) {
|
||||
if (user[key].editable && user[key].value !== initialUser[key].value) {
|
||||
if (key === 'currentPassword' && user[key].length < 8) {
|
||||
updateWithKey('currentPassword', {
|
||||
error: true,
|
||||
});
|
||||
newData = false;
|
||||
break;
|
||||
} else if (key === 'changePassword') {
|
||||
parameters[key] = user[key].value === 'on';
|
||||
newData = true;
|
||||
} else {
|
||||
parameters[key] = user[key].value;
|
||||
newData = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (newData) {
|
||||
const options = {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
},
|
||||
};
|
||||
|
||||
axiosInstance
|
||||
.put(`${endpoints.ucentralsec}/api/v1/user/${userId}`, parameters, options)
|
||||
.then(() => {
|
||||
addToast({
|
||||
title: t('user.update_success_title'),
|
||||
body: t('user.update_success'),
|
||||
color: 'success',
|
||||
autohide: true,
|
||||
});
|
||||
getUsers();
|
||||
toggle();
|
||||
})
|
||||
.catch(() => {
|
||||
addToast({
|
||||
title: t('user.update_failure_title'),
|
||||
body: t('user.update_failure'),
|
||||
color: 'danger',
|
||||
autohide: true,
|
||||
});
|
||||
getUser();
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
} else {
|
||||
setLoading(false);
|
||||
addToast({
|
||||
title: t('user.update_success_title'),
|
||||
body: t('user.update_success'),
|
||||
color: 'success',
|
||||
autohide: true,
|
||||
});
|
||||
getUsers();
|
||||
toggle();
|
||||
}
|
||||
};
|
||||
|
||||
const addNote = (currentNote) => {
|
||||
setLoading(true);
|
||||
|
||||
const parameters = {
|
||||
id: userId,
|
||||
notes: [{ note: currentNote }],
|
||||
};
|
||||
|
||||
const options = {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
},
|
||||
};
|
||||
|
||||
axiosInstance
|
||||
.put(`${endpoints.ucentralsec}/api/v1/user/${userId}`, parameters, options)
|
||||
.then(() => {
|
||||
getUser();
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (userId) {
|
||||
getUser();
|
||||
}
|
||||
if (policies.passwordPattern.length === 0) {
|
||||
getPasswordPolicy();
|
||||
}
|
||||
}, [userId]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
t={t}
|
||||
user={user}
|
||||
updateUserWithId={updateWithId}
|
||||
saveUser={updateUser}
|
||||
loading={loading}
|
||||
policies={policies}
|
||||
show={show}
|
||||
toggle={toggle}
|
||||
addNote={addNote}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
EditUserModal.propTypes = {
|
||||
userId: PropTypes.string.isRequired,
|
||||
show: PropTypes.bool.isRequired,
|
||||
toggle: PropTypes.func.isRequired,
|
||||
getUsers: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default React.memo(EditUserModal);
|
||||
64
src/components/EventQueueModal/index.js
Normal file
64
src/components/EventQueueModal/index.js
Normal file
@@ -0,0 +1,64 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { EventQueueModal as Modal, useAuth, useDevice, useToast } from 'ucentral-libs';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
|
||||
const EventQueueModal = ({ show, toggle }) => {
|
||||
const { t } = useTranslation();
|
||||
const { currentToken, endpoints } = useAuth();
|
||||
const { deviceSerialNumber } = useDevice();
|
||||
const { addToast } = useToast();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [result, setResult] = useState({});
|
||||
|
||||
const getQueue = () => {
|
||||
setLoading(true);
|
||||
|
||||
const options = {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
},
|
||||
};
|
||||
|
||||
const parameters = {
|
||||
serialNumber: deviceSerialNumber,
|
||||
types: ['dhcp', 'wifi'],
|
||||
};
|
||||
|
||||
axiosInstance
|
||||
.post(
|
||||
`${endpoints.ucentralgw}/api/v1/device/${deviceSerialNumber}/eventqueue`,
|
||||
parameters,
|
||||
options,
|
||||
)
|
||||
.then((response) => {
|
||||
setResult(response.data);
|
||||
})
|
||||
.catch(() => {
|
||||
addToast({
|
||||
title: t('common.error'),
|
||||
body: t('commands.unable_queue'),
|
||||
color: 'danger',
|
||||
autohide: true,
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (show) getQueue();
|
||||
}, [show]);
|
||||
|
||||
return <Modal t={t} show={show} toggle={toggle} loading={loading} result={result} />;
|
||||
};
|
||||
|
||||
EventQueueModal.propTypes = {
|
||||
show: PropTypes.bool.isRequired,
|
||||
toggle: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default EventQueueModal;
|
||||
@@ -14,15 +14,15 @@ import {
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useSelector } from 'react-redux';
|
||||
import 'react-widgets/styles.css';
|
||||
import { getToken } from 'utils/authHelper';
|
||||
import { useAuth, useDevice } from 'ucentral-libs';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
import SuccessfulActionModalBody from 'components/SuccessfulActionModalBody';
|
||||
import styles from './index.module.scss';
|
||||
|
||||
const ConfigureModal = ({ show, toggleModal }) => {
|
||||
const { t } = useTranslation();
|
||||
const { currentToken, endpoints } = useAuth();
|
||||
const { deviceSerialNumber } = useDevice();
|
||||
const [hadSuccess, setHadSuccess] = useState(false);
|
||||
const [hadFailure, setHadFailure] = useState(false);
|
||||
const [doingNow, setDoingNow] = useState(false);
|
||||
@@ -30,7 +30,6 @@ const ConfigureModal = ({ show, toggleModal }) => {
|
||||
const [keepRedirector, setKeepRedirector] = useState(true);
|
||||
const [responseBody, setResponseBody] = useState('');
|
||||
const [checkingIfSure, setCheckingIfSure] = useState(false);
|
||||
const selectedDeviceId = useSelector((state) => state.selectedDeviceId);
|
||||
|
||||
const toggleRedirector = () => {
|
||||
setKeepRedirector(!keepRedirector);
|
||||
@@ -54,17 +53,21 @@ const ConfigureModal = ({ show, toggleModal }) => {
|
||||
setWaiting(true);
|
||||
|
||||
const parameters = {
|
||||
serialNumber: selectedDeviceId,
|
||||
serialNumber: deviceSerialNumber,
|
||||
keepRedirector,
|
||||
};
|
||||
|
||||
const headers = {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${getToken()}`,
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
};
|
||||
|
||||
axiosInstance
|
||||
.post(`/device/${encodeURIComponent(selectedDeviceId)}/factory`, parameters, { headers })
|
||||
.post(
|
||||
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}/factory`,
|
||||
parameters,
|
||||
{ headers },
|
||||
)
|
||||
.then(() => {
|
||||
setHadSuccess(true);
|
||||
})
|
||||
@@ -90,9 +93,9 @@ const ConfigureModal = ({ show, toggleModal }) => {
|
||||
<div>
|
||||
<CModalBody>
|
||||
<CAlert color="danger">{t('factory_reset.warning')}</CAlert>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<p className={styles.spacedForm}>{t('factory_reset.redirector')}</p>
|
||||
<CForm className={styles.spacedForm}>
|
||||
<CRow className="mt-3">
|
||||
<p className="pl-4">{t('factory_reset.redirector')}</p>
|
||||
<CForm className="pl-4">
|
||||
<CSwitch
|
||||
color="primary"
|
||||
defaultChecked={keepRedirector}
|
||||
@@ -116,7 +119,7 @@ const ConfigureModal = ({ show, toggleModal }) => {
|
||||
color="primary"
|
||||
onClick={() => confirmingIfSure()}
|
||||
>
|
||||
{t('common.submit')}
|
||||
{t('factory_reset.reset')}
|
||||
</CButton>
|
||||
<CButton
|
||||
hidden={!checkingIfSure}
|
||||
@@ -124,7 +127,7 @@ const ConfigureModal = ({ show, toggleModal }) => {
|
||||
color="primary"
|
||||
onClick={() => doAction(false)}
|
||||
>
|
||||
{waiting && !doingNow ? 'Loading...' : 'Yes'} {' '}
|
||||
{waiting && !doingNow ? t('factory_reset.resetting') : t('common.yes')} {' '}
|
||||
<CSpinner color="light" hidden={!waiting || doingNow} component="span" size="sm" />
|
||||
</CButton>
|
||||
<CButton color="secondary" onClick={toggleModal}>
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
.spacedRow {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.spacedForm {
|
||||
padding-left: 5%;
|
||||
}
|
||||
71
src/components/FirmwareHistoryModal/index.js
Normal file
71
src/components/FirmwareHistoryModal/index.js
Normal file
@@ -0,0 +1,71 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
import {
|
||||
CButton,
|
||||
CModal,
|
||||
CModalBody,
|
||||
CModalHeader,
|
||||
CModalFooter,
|
||||
CModalTitle,
|
||||
} from '@coreui/react';
|
||||
import { FirmwareHistoryTable, useAuth } from 'ucentral-libs';
|
||||
|
||||
const FirmwareHistoryModal = ({ serialNumber, show, toggle }) => {
|
||||
const { t } = useTranslation();
|
||||
const { currentToken, endpoints } = useAuth();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [data, setData] = useState([]);
|
||||
|
||||
const getHistory = () => {
|
||||
setLoading(true);
|
||||
|
||||
const options = {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
},
|
||||
};
|
||||
|
||||
axiosInstance
|
||||
.get(`${endpoints.ucentralfms}/api/v1/revisionHistory/${serialNumber}`, options)
|
||||
.then((response) => setData(response.data.history ?? []))
|
||||
.catch(() => {})
|
||||
.finally(() => setLoading(false));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (show) {
|
||||
getHistory();
|
||||
} else {
|
||||
setData([]);
|
||||
}
|
||||
}, [show]);
|
||||
|
||||
return (
|
||||
<CModal size="xl" show={show} onClose={toggle} scrollable>
|
||||
<CModalHeader closeButton>
|
||||
<CModalTitle>
|
||||
#{serialNumber} {t('firmware.history_title')}
|
||||
</CModalTitle>
|
||||
</CModalHeader>
|
||||
<CModalBody>
|
||||
<FirmwareHistoryTable t={t} loading={loading} data={data} />
|
||||
</CModalBody>
|
||||
<CModalFooter>
|
||||
<CButton color="secondary" onClick={toggle}>
|
||||
{t('common.close')}
|
||||
</CButton>
|
||||
</CModalFooter>
|
||||
</CModal>
|
||||
);
|
||||
};
|
||||
|
||||
FirmwareHistoryModal.propTypes = {
|
||||
serialNumber: PropTypes.string.isRequired,
|
||||
show: PropTypes.bool.isRequired,
|
||||
toggle: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default FirmwareHistoryModal;
|
||||
74
src/components/FirmwareUpgradeModal/UpgradeFooter.js
Normal file
74
src/components/FirmwareUpgradeModal/UpgradeFooter.js
Normal file
@@ -0,0 +1,74 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import PropTypes from 'prop-types';
|
||||
import { CButton, CSpinner, CModalFooter } from '@coreui/react';
|
||||
|
||||
const UpgradeFooter = ({
|
||||
isNow,
|
||||
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}
|
||||
>
|
||||
{isNow ? t('upgrade.upgrade') : t('common.schedule')}
|
||||
</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>
|
||||
);
|
||||
};
|
||||
|
||||
UpgradeFooter.propTypes = {
|
||||
isNow: PropTypes.bool.isRequired,
|
||||
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,
|
||||
};
|
||||
|
||||
UpgradeFooter.defaultProps = {
|
||||
color: 'primary',
|
||||
variant: '',
|
||||
block: false,
|
||||
};
|
||||
|
||||
export default UpgradeFooter;
|
||||
102
src/components/FirmwareUpgradeModal/UpgradeWaitingBody.js
Normal file
102
src/components/FirmwareUpgradeModal/UpgradeWaitingBody.js
Normal file
@@ -0,0 +1,102 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import PropTypes from 'prop-types';
|
||||
import { CModalBody } from '@coreui/react';
|
||||
import { v4 as createUuid } from 'uuid';
|
||||
import { useAuth } from 'ucentral-libs';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
|
||||
const UpgradeWaitingBody = ({ serialNumber }) => {
|
||||
const { t } = useTranslation();
|
||||
const { currentToken, endpoints } = useAuth();
|
||||
const [currentStep, setCurrentStep] = useState(0);
|
||||
const [secondsElapsed, setSecondsElapsed] = useState(0);
|
||||
const [labelsToShow, setLabelsToShow] = useState(['upgrade.command_submitted']);
|
||||
|
||||
const getDeviceConnection = () => {
|
||||
const options = {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
},
|
||||
};
|
||||
|
||||
axiosInstance
|
||||
.get(
|
||||
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(serialNumber)}/status`,
|
||||
options,
|
||||
)
|
||||
.then((response) => response.data.connected)
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
const getFirmwareVersion = () => {
|
||||
const options = {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
},
|
||||
};
|
||||
|
||||
axiosInstance
|
||||
.get(`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(serialNumber)}`, options)
|
||||
.then((response) => response.data.firmware)
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
const refreshStep = () => {
|
||||
if (currentStep === 0 && !getDeviceConnection) {
|
||||
const labelsToAdd = [
|
||||
t('upgrade.device_disconnected'),
|
||||
t('upgrade.device_upgrading_firmware'),
|
||||
t('upgrade.waiting_for_device'),
|
||||
];
|
||||
setLabelsToShow([...labelsToShow, ...labelsToAdd]);
|
||||
setCurrentStep(1);
|
||||
} else if (currentStep === 1 && getDeviceConnection()) {
|
||||
const newFirmware = `: ${getFirmwareVersion()}`;
|
||||
const labelsToAdd = [
|
||||
t('upgrade.device_reconnected'),
|
||||
`${t('upgrade.new_version')}: ${newFirmware}`,
|
||||
];
|
||||
setLabelsToShow([...labelsToShow, ...labelsToAdd]);
|
||||
setCurrentStep(2);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const refreshIntervalId = setInterval(() => {
|
||||
refreshStep();
|
||||
}, 5000);
|
||||
|
||||
const timerIntervalId = setInterval(() => {
|
||||
setSecondsElapsed(secondsElapsed + 1);
|
||||
}, 1000);
|
||||
|
||||
return () => {
|
||||
clearInterval(refreshIntervalId);
|
||||
clearInterval(timerIntervalId);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<CModalBody>
|
||||
<div className="consoleBox">
|
||||
{labelsToShow.map((label) => (
|
||||
<p key={createUuid()}>
|
||||
{new Date().toString()}:{label}
|
||||
</p>
|
||||
))}
|
||||
<p>
|
||||
{t('common.seconds_elapsed')}:{secondsElapsed}
|
||||
</p>
|
||||
</div>
|
||||
</CModalBody>
|
||||
);
|
||||
};
|
||||
|
||||
UpgradeWaitingBody.propTypes = {
|
||||
serialNumber: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default UpgradeWaitingBody;
|
||||
@@ -4,39 +4,55 @@ import {
|
||||
CModalHeader,
|
||||
CModalTitle,
|
||||
CModalBody,
|
||||
CModalFooter,
|
||||
CSpinner,
|
||||
CSwitch,
|
||||
CCol,
|
||||
CRow,
|
||||
CInput,
|
||||
CInvalidFeedback,
|
||||
CModalFooter,
|
||||
} from '@coreui/react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import DatePicker from 'react-widgets/DatePicker';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { convertDateToUtc, convertDateFromUtc, dateToUnix } from 'utils/helper';
|
||||
import { dateToUnix } from 'utils/helper';
|
||||
import 'react-widgets/styles.css';
|
||||
import { getToken } from 'utils/authHelper';
|
||||
import { useDevice, useAuth } from 'ucentral-libs';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
import eventBus from 'utils/eventBus';
|
||||
import styles from './index.module.scss';
|
||||
import ButtonFooter from './UpgradeFooter';
|
||||
import UpgradeWaitingBody from './UpgradeWaitingBody';
|
||||
|
||||
const FirmwareUpgradeModal = ({ show, toggleModal }) => {
|
||||
const { t } = useTranslation();
|
||||
const [hadSuccess, setHadSuccess] = useState(false);
|
||||
const [hadFailure, setHadFailure] = useState(false);
|
||||
const [waiting, setWaiting] = useState(false);
|
||||
const [chosenDate, setChosenDate] = useState(new Date().toString());
|
||||
const { currentToken, endpoints } = useAuth();
|
||||
const { deviceSerialNumber, getDeviceConnection } = useDevice();
|
||||
const [isNow, setIsNow] = useState(true);
|
||||
const [waitForUpgrade, setWaitForUpgrade] = useState(false);
|
||||
const [date, setDate] = useState(new Date().toString());
|
||||
const [firmware, setFirmware] = useState('');
|
||||
const [doingNow, setDoingNow] = useState(false);
|
||||
const [validFirmware, setValidFirmware] = useState(true);
|
||||
const [validDate, setValidDate] = useState(true);
|
||||
const [responseBody, setResponseBody] = useState('');
|
||||
const [checkingIfSure, setCheckingIfSure] = useState(false);
|
||||
const [checkingIfNow, setCheckingIfNow] = useState(false);
|
||||
const selectedDeviceId = useSelector((state) => state.selectedDeviceId);
|
||||
const [blockFields, setBlockFields] = useState(false);
|
||||
const [disabledWaiting, setDisableWaiting] = useState(false);
|
||||
const [waitingForUpgrade, setWaitingForUpgrade] = useState(false);
|
||||
const [showWaitingConsole, setShowWaitingConsole] = useState(false);
|
||||
const [deviceConnected, setDeviceConnected] = useState(true);
|
||||
|
||||
const toggleNow = () => {
|
||||
if (isNow) {
|
||||
setWaitForUpgrade(false);
|
||||
setDisableWaiting(true);
|
||||
} else {
|
||||
setDisableWaiting(false);
|
||||
}
|
||||
|
||||
setIsNow(!isNow);
|
||||
};
|
||||
|
||||
const toggleWaitForUpgrade = () => {
|
||||
setWaitForUpgrade(waitForUpgrade);
|
||||
};
|
||||
|
||||
const formValidation = () => {
|
||||
let valid = true;
|
||||
@@ -45,95 +61,91 @@ const FirmwareUpgradeModal = ({ show, toggleModal }) => {
|
||||
valid = false;
|
||||
}
|
||||
|
||||
if (chosenDate.trim() === '') {
|
||||
if (!isNow && date.trim() === '') {
|
||||
setValidDate(false);
|
||||
valid = false;
|
||||
}
|
||||
return valid;
|
||||
};
|
||||
const setDateToLate = () => {
|
||||
const date = convertDateToUtc(new Date());
|
||||
if (date.getHours() >= 3) {
|
||||
date.setDate(date.getDate() + 1);
|
||||
}
|
||||
date.setHours(3);
|
||||
date.setMinutes(0);
|
||||
|
||||
setChosenDate(convertDateFromUtc(date).toString());
|
||||
};
|
||||
|
||||
const setDate = (date) => {
|
||||
if (date) {
|
||||
setChosenDate(date.toString());
|
||||
}
|
||||
};
|
||||
|
||||
const confirmingIfSure = () => {
|
||||
setCheckingIfSure(true);
|
||||
};
|
||||
|
||||
const confirmingIfNow = () => {
|
||||
setCheckingIfNow(true);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setHadSuccess(false);
|
||||
setHadFailure(false);
|
||||
setWaiting(false);
|
||||
setChosenDate(new Date().toString());
|
||||
setFirmware('');
|
||||
setValidFirmware(true);
|
||||
setResponseBody('');
|
||||
setCheckingIfSure(false);
|
||||
setDoingNow(false);
|
||||
setCheckingIfNow(false);
|
||||
setBlockFields(false);
|
||||
setShowWaitingConsole(false);
|
||||
}, [show]);
|
||||
|
||||
useEffect(() => {
|
||||
setValidFirmware(true);
|
||||
setValidDate(true);
|
||||
}, [firmware, chosenDate]);
|
||||
}, [firmware, date]);
|
||||
|
||||
const postUpgrade = (isNow) => {
|
||||
setDoingNow(isNow);
|
||||
setHadFailure(false);
|
||||
setHadSuccess(false);
|
||||
setWaiting(true);
|
||||
useEffect(() => {
|
||||
if (deviceSerialNumber !== null && show) {
|
||||
const asyncGet = async () => {
|
||||
const isConnected = await getDeviceConnection(
|
||||
deviceSerialNumber,
|
||||
currentToken,
|
||||
endpoints.ucentralgw,
|
||||
);
|
||||
setDisableWaiting(!isConnected);
|
||||
setDeviceConnected(isConnected);
|
||||
};
|
||||
asyncGet();
|
||||
}
|
||||
}, [show]);
|
||||
|
||||
const token = getToken();
|
||||
const utcDate = new Date(chosenDate);
|
||||
const utcDateString = utcDate.toISOString();
|
||||
const postUpgrade = () => {
|
||||
if (formValidation()) {
|
||||
setWaitingForUpgrade(true);
|
||||
setBlockFields(true);
|
||||
const headers = {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
serialNumber: deviceSerialNumber,
|
||||
};
|
||||
|
||||
const headers = {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${token}`,
|
||||
serialNumber: selectedDeviceId,
|
||||
};
|
||||
|
||||
const parameters = {
|
||||
serialNumber: selectedDeviceId,
|
||||
when: isNow ? 0 : dateToUnix(utcDateString),
|
||||
uri: firmware,
|
||||
};
|
||||
axiosInstance
|
||||
.post(`/device/${encodeURIComponent(selectedDeviceId)}/upgrade`, parameters, { headers })
|
||||
.then(() => {
|
||||
setResponseBody('Command submitted successfully');
|
||||
setHadSuccess(true);
|
||||
})
|
||||
.catch(() => {
|
||||
setResponseBody(t('commands.error'));
|
||||
setHadFailure(true);
|
||||
})
|
||||
.finally(() => {
|
||||
setCheckingIfNow(false);
|
||||
setDoingNow(false);
|
||||
setCheckingIfSure(false);
|
||||
setWaiting(false);
|
||||
eventBus.dispatch('actionCompleted', { message: 'An action has been completed' });
|
||||
});
|
||||
const parameters = {
|
||||
serialNumber: deviceSerialNumber,
|
||||
when: isNow ? 0 : dateToUnix(date),
|
||||
uri: firmware,
|
||||
};
|
||||
axiosInstance
|
||||
.post(
|
||||
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}/upgrade`,
|
||||
parameters,
|
||||
{ headers },
|
||||
)
|
||||
.then(() => {
|
||||
if (waitForUpgrade) {
|
||||
setShowWaitingConsole(true);
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
setBlockFields(false);
|
||||
setWaitingForUpgrade(false);
|
||||
eventBus.dispatch('actionCompleted', { message: 'An action has been completed' });
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if (showWaitingConsole) {
|
||||
return (
|
||||
<CModal show={show} onClose={toggleModal}>
|
||||
<CModalHeader closeButton>
|
||||
<CModalTitle>{t('upgrade.title')}</CModalTitle>
|
||||
</CModalHeader>
|
||||
<CModalBody>
|
||||
<UpgradeWaitingBody serialNumber={deviceSerialNumber} />
|
||||
</CModalBody>
|
||||
<CModalFooter>
|
||||
<CButton color="secondary" onClick={toggleModal}>
|
||||
{t('common.close')}
|
||||
</CButton>
|
||||
</CModalFooter>
|
||||
</CModal>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<CModal show={show} onClose={toggleModal}>
|
||||
<CModalHeader closeButton>
|
||||
@@ -141,94 +153,82 @@ const FirmwareUpgradeModal = ({ show, toggleModal }) => {
|
||||
</CModalHeader>
|
||||
<CModalBody>
|
||||
<h6>{t('upgrade.directions')}</h6>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CCol>
|
||||
<CButton
|
||||
color="primary"
|
||||
onClick={() => (formValidation() ? confirmingIfNow() : null)}
|
||||
disabled={waiting}
|
||||
hidden={checkingIfNow}
|
||||
block
|
||||
>
|
||||
{t('common.do_now')}
|
||||
</CButton>
|
||||
<CButton
|
||||
color="primary"
|
||||
onClick={() => (formValidation() ? postUpgrade(true) : null)}
|
||||
disabled={waiting}
|
||||
hidden={!checkingIfNow}
|
||||
block
|
||||
>
|
||||
{waiting && doingNow ? t('common.loading_ellipsis') : t('common.confirm')}
|
||||
<CSpinner hidden={!waiting || doingNow} component="span" size="sm" />
|
||||
</CButton>
|
||||
<CRow className="mt-3">
|
||||
<CCol md="4" className="mt-2">
|
||||
<p>{t('upgrade.firmware_uri')}</p>
|
||||
</CCol>
|
||||
<CCol>
|
||||
<CButton disabled={waiting} block color="primary" onClick={setDateToLate}>
|
||||
{t('common.later_tonight')}
|
||||
</CButton>
|
||||
<CCol md="8">
|
||||
<CInput
|
||||
disabled={blockFields}
|
||||
className={('form-control', { 'is-invalid': !validFirmware })}
|
||||
type="text"
|
||||
id="uri"
|
||||
name="uri-input"
|
||||
autoComplete="firmware-uri"
|
||||
onChange={(event) => setFirmware(event.target.value)}
|
||||
value={firmware}
|
||||
/>
|
||||
<CInvalidFeedback>{t('upgrade.need_uri')}</CInvalidFeedback>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CCol md="4" className={styles.spacedColumn}>
|
||||
<CRow className="mt-3">
|
||||
<CCol md="8">
|
||||
<p>{t('common.execute_now')}</p>
|
||||
</CCol>
|
||||
<CCol>
|
||||
<CSwitch
|
||||
disabled={blockFields}
|
||||
color="primary"
|
||||
defaultChecked={isNow}
|
||||
onClick={toggleNow}
|
||||
labelOn={t('common.yes')}
|
||||
labelOff={t('common.no')}
|
||||
/>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className="mt-3" hidden={isNow}>
|
||||
<CCol md="4" className="mt-2">
|
||||
<p>{t('upgrade.time')}</p>
|
||||
</CCol>
|
||||
<CCol xs="12" md="8">
|
||||
<DatePicker
|
||||
selected={chosenDate === '' ? new Date() : new Date(chosenDate)}
|
||||
value={chosenDate === '' ? new Date() : new Date(chosenDate)}
|
||||
selected={new Date(date)}
|
||||
value={new Date(date)}
|
||||
className={('form-control', { 'is-invalid': !validDate })}
|
||||
includeTime
|
||||
placeholder="Select custom date in UTC"
|
||||
disabled={waiting}
|
||||
onChange={(date) => setDate(date)}
|
||||
min={new Date()}
|
||||
disabled={blockFields}
|
||||
onChange={(newDate) => setDate(newDate.toString())}
|
||||
/>
|
||||
<CInvalidFeedback>{t('common.need_date')}</CInvalidFeedback>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<div>{t('upgrade.firmware_uri')}</div>
|
||||
<CInput
|
||||
disabled={waiting}
|
||||
className={('form-control', { 'is-invalid': !validFirmware })}
|
||||
type="text"
|
||||
id="uri"
|
||||
name="uri-input"
|
||||
placeholder="https://s3-us-west-2.amazonaws.com/ucentral.arilia.com/20210508-linksys_ea8300-uCentral-trunk-43e1a2d-upgrade.bin"
|
||||
autoComplete="firmware-uri"
|
||||
onChange={(event) => setFirmware(event.target.value)}
|
||||
value={firmware}
|
||||
/>
|
||||
<CInvalidFeedback>{t('upgrade.need_uri')}</CInvalidFeedback>
|
||||
<div hidden={!hadSuccess && !hadFailure}>
|
||||
<div>
|
||||
<pre className="ignore">{responseBody}</pre>
|
||||
</div>
|
||||
</div>
|
||||
<CRow className="mt-3" hidden={true || !isNow || disabledWaiting || !deviceConnected}>
|
||||
<CCol md="8">
|
||||
<p>
|
||||
{t('upgrade.wait_for_upgrade')}
|
||||
<b hidden={!disabledWaiting}> {t('upgrade.offline_device')}</b>
|
||||
</p>
|
||||
</CCol>
|
||||
<CCol>
|
||||
<CSwitch
|
||||
disabled={blockFields || disabledWaiting}
|
||||
color="primary"
|
||||
defaultChecked={waitForUpgrade}
|
||||
onClick={toggleWaitForUpgrade}
|
||||
labelOn={t('common.yes')}
|
||||
labelOff={t('common.no')}
|
||||
/>
|
||||
</CCol>
|
||||
</CRow>
|
||||
</CModalBody>
|
||||
<CModalFooter>
|
||||
<div hidden={!checkingIfSure}>{t('common.are_you_sure')}</div>
|
||||
<CButton
|
||||
hidden={checkingIfSure}
|
||||
disabled={waiting}
|
||||
color="primary"
|
||||
onClick={() => (formValidation() ? confirmingIfSure() : null)}
|
||||
>
|
||||
{t('common.schedule')}
|
||||
</CButton>
|
||||
<CButton
|
||||
hidden={!checkingIfSure}
|
||||
disabled={waiting}
|
||||
color="primary"
|
||||
onClick={() => (formValidation() ? postUpgrade() : null)}
|
||||
>
|
||||
{waiting && !doingNow ? 'Loading...' : 'Yes'} {' '}
|
||||
<CSpinner color="light" hidden={!waiting || doingNow} component="span" size="sm" />
|
||||
</CButton>
|
||||
<CButton color="secondary" onClick={toggleModal}>
|
||||
{t('common.cancel')}
|
||||
</CButton>
|
||||
</CModalFooter>
|
||||
<ButtonFooter
|
||||
isNow={isNow}
|
||||
isShown={show}
|
||||
isLoading={waitingForUpgrade}
|
||||
action={postUpgrade}
|
||||
color="primary"
|
||||
toggleParent={toggleModal}
|
||||
/>
|
||||
</CModal>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
.spacedRow {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.spacedColumn {
|
||||
margin-top: 7px;
|
||||
}
|
||||
15
src/components/InterfaceStatistics/DeviceStatisticsChart.js
Normal file
15
src/components/InterfaceStatistics/DeviceStatisticsChart.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Chart from 'react-apexcharts';
|
||||
|
||||
const DeviceStatisticsChart = ({ chart }) => (
|
||||
<div style={{ height: '360px' }}>
|
||||
<Chart series={chart.data} options={chart.options} type="line" height="100%" />
|
||||
</div>
|
||||
);
|
||||
|
||||
DeviceStatisticsChart.propTypes = {
|
||||
chart: PropTypes.instanceOf(Object).isRequired,
|
||||
};
|
||||
|
||||
export default DeviceStatisticsChart;
|
||||
68
src/components/InterfaceStatistics/LatestStatisticsModal.js
Normal file
68
src/components/InterfaceStatistics/LatestStatisticsModal.js
Normal file
@@ -0,0 +1,68 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
CButton,
|
||||
CModal,
|
||||
CModalHeader,
|
||||
CModalBody,
|
||||
CModalTitle,
|
||||
CModalFooter,
|
||||
} from '@coreui/react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
import { useAuth, useDevice } from 'ucentral-libs';
|
||||
|
||||
const LatestStatisticsModal = ({ show, toggle }) => {
|
||||
const { t } = useTranslation();
|
||||
const { currentToken, endpoints } = useAuth();
|
||||
const { deviceSerialNumber } = useDevice();
|
||||
const [latestStats, setLatestStats] = useState('');
|
||||
|
||||
const getLatestStats = () => {
|
||||
const options = {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
},
|
||||
};
|
||||
|
||||
axiosInstance
|
||||
.get(
|
||||
`${endpoints.ucentralgw}/api/v1/device/${deviceSerialNumber}/statistics?lastOnly=true`,
|
||||
options,
|
||||
)
|
||||
.then((response) => {
|
||||
setLatestStats(response.data);
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (show) {
|
||||
getLatestStats();
|
||||
}
|
||||
}, [show]);
|
||||
|
||||
return (
|
||||
<CModal size="lg" show={show} onClose={toggle}>
|
||||
<CModalHeader closeButton>
|
||||
<CModalTitle className="text-dark">{t('statistics.latest_statistics')}</CModalTitle>
|
||||
</CModalHeader>
|
||||
<CModalBody>
|
||||
<pre className="ignore">{JSON.stringify(latestStats, null, 4)}</pre>
|
||||
</CModalBody>
|
||||
<CModalFooter>
|
||||
<CButton color="secondary" onClick={toggle}>
|
||||
{t('common.close')}
|
||||
</CButton>
|
||||
</CModalFooter>
|
||||
</CModal>
|
||||
);
|
||||
};
|
||||
|
||||
LatestStatisticsModal.propTypes = {
|
||||
toggle: PropTypes.func.isRequired,
|
||||
show: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default LatestStatisticsModal;
|
||||
@@ -1,17 +1,20 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import PropTypes from 'prop-types';
|
||||
import { v4 as createUuid } from 'uuid';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
import { getToken } from 'utils/authHelper';
|
||||
import { useAuth, useDevice } from 'ucentral-libs';
|
||||
import { unixToTime, capitalizeFirstLetter } from 'utils/helper';
|
||||
import DeviceStatisticsChart from '../DeviceStatisticsChart';
|
||||
import eventBus from 'utils/eventBus';
|
||||
import DeviceStatisticsChart from './DeviceStatisticsChart';
|
||||
|
||||
const StatisticsChartList = ({ selectedDeviceId, lastRefresh }) => {
|
||||
const StatisticsChartList = () => {
|
||||
const { t } = useTranslation();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [deviceStats, setStats] = useState([]);
|
||||
const [statOptions, setStatOptions] = useState({});
|
||||
const { currentToken, endpoints } = useAuth();
|
||||
const { deviceSerialNumber } = useDevice();
|
||||
const [statOptions, setStatOptions] = useState({
|
||||
interfaceList: [],
|
||||
settings: {},
|
||||
});
|
||||
|
||||
const transformIntoDataset = (data) => {
|
||||
const sortedData = data.sort((a, b) => {
|
||||
@@ -58,9 +61,11 @@ const StatisticsChartList = ({ selectedDeviceId, lastRefresh }) => {
|
||||
// Looping through the interfaces of the log
|
||||
for (const inter of log.data.interfaces) {
|
||||
interfaceList[interfaceTypes[inter.name]][0].data.push(
|
||||
Math.floor(inter.counters.tx_bytes / 1024),
|
||||
inter.counters?.tx_bytes ? Math.floor(inter.counters.tx_bytes / 1024) : 0,
|
||||
);
|
||||
interfaceList[interfaceTypes[inter.name]][1].data.push(
|
||||
inter.counters?.rx_bytes ? Math.floor(inter.counters.rx_bytes / 1024) : 0,
|
||||
);
|
||||
interfaceList[interfaceTypes[inter.name]][1].data.push(Math.floor(inter.counters.rx_bytes));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,6 +74,9 @@ const StatisticsChartList = ({ selectedDeviceId, lastRefresh }) => {
|
||||
id: 'chart',
|
||||
group: 'txrx',
|
||||
},
|
||||
stroke: {
|
||||
curve: 'smooth',
|
||||
},
|
||||
xaxis: {
|
||||
title: {
|
||||
text: 'Time',
|
||||
@@ -97,79 +105,76 @@ const StatisticsChartList = ({ selectedDeviceId, lastRefresh }) => {
|
||||
},
|
||||
};
|
||||
|
||||
setStatOptions(options);
|
||||
setStats(interfaceList);
|
||||
const newOptions = {
|
||||
interfaceList,
|
||||
settings: options,
|
||||
};
|
||||
|
||||
if (statOptions !== newOptions) {
|
||||
setStatOptions(newOptions);
|
||||
}
|
||||
};
|
||||
|
||||
const getStatistics = () => {
|
||||
if (!loading) {
|
||||
setLoading(true);
|
||||
const options = {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
},
|
||||
params: {
|
||||
serialNumber: '24f5a207a130',
|
||||
},
|
||||
};
|
||||
|
||||
const options = {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${getToken()}`,
|
||||
},
|
||||
params: {
|
||||
serialNumber: '24f5a207a130',
|
||||
},
|
||||
};
|
||||
|
||||
axiosInstance
|
||||
.get(`/device/${selectedDeviceId}/statistics?newest=true&limit=50`, options)
|
||||
.then((response) => {
|
||||
transformIntoDataset(response.data.data);
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
}
|
||||
axiosInstance
|
||||
.get(
|
||||
`${endpoints.ucentralgw}/api/v1/device/${deviceSerialNumber}/statistics?newest=true&limit=50`,
|
||||
options,
|
||||
)
|
||||
.then((response) => {
|
||||
transformIntoDataset(response.data.data);
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedDeviceId) {
|
||||
if (deviceSerialNumber) {
|
||||
getStatistics();
|
||||
}
|
||||
}, [selectedDeviceId]);
|
||||
}, [deviceSerialNumber]);
|
||||
|
||||
useEffect(() => {
|
||||
if (lastRefresh !== '' && selectedDeviceId) {
|
||||
getStatistics();
|
||||
}
|
||||
}, [lastRefresh]);
|
||||
eventBus.on('refreshInterfaceStatistics', () => getStatistics());
|
||||
|
||||
return () => {
|
||||
eventBus.remove('refreshInterfaceStatistics');
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{deviceStats.map((data) => (
|
||||
<div key={createUuid()}>
|
||||
<DeviceStatisticsChart
|
||||
key={createUuid()}
|
||||
data={data}
|
||||
options={{
|
||||
...statOptions,
|
||||
title: {
|
||||
text: capitalizeFirstLetter(data[0].titleName),
|
||||
align: 'left',
|
||||
style: {
|
||||
fontSize: '25px',
|
||||
},
|
||||
{statOptions.interfaceList.map((data) => {
|
||||
const options = {
|
||||
data,
|
||||
options: {
|
||||
...statOptions.settings,
|
||||
title: {
|
||||
text: capitalizeFirstLetter(data[0].titleName),
|
||||
align: 'left',
|
||||
style: {
|
||||
fontSize: '25px',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
},
|
||||
},
|
||||
};
|
||||
return (
|
||||
<div key={createUuid()}>
|
||||
<DeviceStatisticsChart chart={options} />
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
StatisticsChartList.propTypes = {
|
||||
lastRefresh: PropTypes.string,
|
||||
selectedDeviceId: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
StatisticsChartList.defaultProps = {
|
||||
lastRefresh: '',
|
||||
};
|
||||
|
||||
export default StatisticsChartList;
|
||||
export default React.memo(StatisticsChartList);
|
||||
@@ -1,22 +0,0 @@
|
||||
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}>
|
||||
<Chart series={data} options={options} type="line" height="100%" />
|
||||
</div>
|
||||
);
|
||||
|
||||
DeviceStatisticsChart.propTypes = {
|
||||
data: PropTypes.instanceOf(Array),
|
||||
options: PropTypes.instanceOf(Object),
|
||||
};
|
||||
|
||||
DeviceStatisticsChart.defaultProps = {
|
||||
data: [],
|
||||
options: {},
|
||||
};
|
||||
|
||||
export default DeviceStatisticsChart;
|
||||
@@ -1,3 +0,0 @@
|
||||
.chart {
|
||||
height: 360px;
|
||||
}
|
||||
@@ -1,47 +1,82 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import PropTypes from 'prop-types';
|
||||
import { CCard, CCardHeader, CCardBody, CPopover, CRow, CCol } from '@coreui/react';
|
||||
import { useHistory, useParams } from 'react-router-dom';
|
||||
import { CCard, CCardHeader, CCardBody, CRow, CCol, CPopover, CButton } from '@coreui/react';
|
||||
import { cilSync } from '@coreui/icons';
|
||||
import CIcon from '@coreui/icons-react';
|
||||
import StatisticsChartList from './containers/StatisticsChartList';
|
||||
import styles from './index.module.scss';
|
||||
import eventBus from 'utils/eventBus';
|
||||
import LifetimeStatsmodal from 'components/LifetimeStatsModal';
|
||||
import StatisticsChartList from './StatisticsChartList';
|
||||
import LatestStatisticsmodal from './LatestStatisticsModal';
|
||||
|
||||
const DeviceStatisticsCard = ({ selectedDeviceId }) => {
|
||||
const DeviceStatisticsCard = () => {
|
||||
const history = useHistory();
|
||||
const { deviceId } = useParams();
|
||||
const { t } = useTranslation();
|
||||
const [lastRefresh, setLastRefresh] = useState('');
|
||||
const [showLatestModal, setShowLatestModal] = useState(false);
|
||||
const [showLifetimeModal, setShowLifetimeModal] = useState(false);
|
||||
|
||||
const toggleLatestModal = () => {
|
||||
setShowLatestModal(!showLatestModal);
|
||||
};
|
||||
|
||||
const toggleLifetimeModal = () => {
|
||||
setShowLifetimeModal(!showLifetimeModal);
|
||||
};
|
||||
|
||||
const goToAnalysis = () => {
|
||||
history.push(`/devices/${deviceId}/wifianalysis`);
|
||||
};
|
||||
|
||||
const refresh = () => {
|
||||
setLastRefresh(new Date().toString());
|
||||
eventBus.dispatch('refreshInterfaceStatistics', { message: 'Refresh interface statistics' });
|
||||
};
|
||||
|
||||
return (
|
||||
<CCard>
|
||||
<CCardHeader>
|
||||
<CRow>
|
||||
<CCol>{t('statistics.title')}</CCol>
|
||||
<CCol className={styles.alignRight}>
|
||||
<CPopover content={t('common.refresh')}>
|
||||
<CIcon
|
||||
onClick={refresh}
|
||||
name="cil-sync"
|
||||
content={cilSync}
|
||||
size="lg"
|
||||
color="primary"
|
||||
/>
|
||||
</CPopover>
|
||||
</CCol>
|
||||
</CRow>
|
||||
</CCardHeader>
|
||||
<CCardBody className={styles.statsBody}>
|
||||
<StatisticsChartList selectedDeviceId={selectedDeviceId} lastRefresh={lastRefresh} />
|
||||
</CCardBody>
|
||||
</CCard>
|
||||
<div>
|
||||
<CCard>
|
||||
<CCardHeader>
|
||||
<CRow>
|
||||
<CCol>
|
||||
<div className="text-value-xxl pt-2">{t('statistics.title')}</div>
|
||||
</CCol>
|
||||
<CCol sm="6" xxl="6">
|
||||
<CRow>
|
||||
<CCol sm="1" xxl="5" />
|
||||
<CCol sm="4" xxl="2" className="text-right">
|
||||
<CButton color="secondary" onClick={goToAnalysis}>
|
||||
{t('wifi_analysis.title')}
|
||||
</CButton>
|
||||
</CCol>
|
||||
<CCol sm="3" xxl="2" className="text-right">
|
||||
<CButton color="secondary" onClick={toggleLatestModal}>
|
||||
{t('statistics.show_latest')}
|
||||
</CButton>
|
||||
</CCol>
|
||||
<CCol sm="3" xxl="2" className="text-right">
|
||||
<CButton color="secondary" onClick={toggleLifetimeModal}>
|
||||
Lifetime Statistics
|
||||
</CButton>
|
||||
</CCol>
|
||||
<CCol sm="1" xxl="1" className="text-center">
|
||||
<CPopover content={t('common.refresh')}>
|
||||
<CButton color="secondary" onClick={refresh} size="sm">
|
||||
<CIcon content={cilSync} />
|
||||
</CButton>
|
||||
</CPopover>
|
||||
</CCol>
|
||||
</CRow>
|
||||
</CCol>
|
||||
</CRow>
|
||||
</CCardHeader>
|
||||
<CCardBody className="p-5">
|
||||
<StatisticsChartList />
|
||||
</CCardBody>
|
||||
</CCard>
|
||||
<LatestStatisticsmodal show={showLatestModal} toggle={toggleLatestModal} />
|
||||
<LifetimeStatsmodal show={showLifetimeModal} toggle={toggleLifetimeModal} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
DeviceStatisticsCard.propTypes = {
|
||||
selectedDeviceId: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default DeviceStatisticsCard;
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
.alignRight {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.statsBody {
|
||||
padding: 5%;
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { CSelect } from '@coreui/react';
|
||||
|
||||
const LanguageSwitcher = () => {
|
||||
const { i18n } = useTranslation();
|
||||
|
||||
return (
|
||||
<CSelect
|
||||
custom
|
||||
defaultValue={i18n.language.split('-')[0]}
|
||||
onChange={(e) => i18n.changeLanguage(e.target.value)}
|
||||
>
|
||||
<option value="de">Deutsche</option>
|
||||
<option value="es">Español</option>
|
||||
<option value="en">English</option>
|
||||
<option value="fr">Français</option>
|
||||
<option value="pt">Portugues</option>
|
||||
</CSelect>
|
||||
);
|
||||
};
|
||||
|
||||
export default LanguageSwitcher;
|
||||
48
src/components/LifetimeStatsModal/index.js
Normal file
48
src/components/LifetimeStatsModal/index.js
Normal file
@@ -0,0 +1,48 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { LifetimeStatsModal as Modal, useAuth, useDevice } from 'ucentral-libs';
|
||||
|
||||
const LifetimeStatsModal = ({ show, toggle }) => {
|
||||
const { t } = useTranslation();
|
||||
const { currentToken, endpoints } = useAuth();
|
||||
const { deviceSerialNumber } = useDevice();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [data, setData] = useState({});
|
||||
|
||||
const getData = () => {
|
||||
setLoading(true);
|
||||
|
||||
const options = {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
},
|
||||
};
|
||||
|
||||
axiosInstance
|
||||
.get(
|
||||
`${endpoints.ucentralgw}/api/v1/device/${deviceSerialNumber}/statistics?lifetime=true`,
|
||||
options,
|
||||
)
|
||||
.then((response) => {
|
||||
setData(response.data);
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => setLoading(false));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (show) getData();
|
||||
}, [show]);
|
||||
|
||||
return <Modal t={t} loading={loading} show={show} toggle={toggle} data={data} />;
|
||||
};
|
||||
|
||||
LifetimeStatsModal.propTypes = {
|
||||
show: PropTypes.bool.isRequired,
|
||||
toggle: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default LifetimeStatsModal;
|
||||
@@ -1,45 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { CButton, CSpinner } from '@coreui/react';
|
||||
|
||||
const LoadingButton = ({
|
||||
isLoading,
|
||||
label,
|
||||
isLoadingLabel,
|
||||
action,
|
||||
color,
|
||||
variant,
|
||||
block,
|
||||
disabled,
|
||||
}) => (
|
||||
<CButton
|
||||
variant={variant}
|
||||
color={color}
|
||||
onClick={action}
|
||||
block={block}
|
||||
disabled={isLoading || disabled}
|
||||
>
|
||||
{isLoading ? isLoadingLabel : label}
|
||||
<CSpinner hidden={!isLoading} color="light" component="span" size="sm" />
|
||||
</CButton>
|
||||
);
|
||||
|
||||
LoadingButton.propTypes = {
|
||||
isLoading: PropTypes.bool.isRequired,
|
||||
block: PropTypes.bool,
|
||||
disabled: PropTypes.bool,
|
||||
label: PropTypes.string.isRequired,
|
||||
isLoadingLabel: PropTypes.string.isRequired,
|
||||
action: PropTypes.func.isRequired,
|
||||
color: PropTypes.string,
|
||||
variant: PropTypes.string,
|
||||
};
|
||||
|
||||
LoadingButton.defaultProps = {
|
||||
color: 'primary',
|
||||
variant: '',
|
||||
block: true,
|
||||
disabled: false,
|
||||
};
|
||||
|
||||
export default LoadingButton;
|
||||
34
src/components/NetworkDiagram/dagreAdapter.js
Normal file
34
src/components/NetworkDiagram/dagreAdapter.js
Normal file
@@ -0,0 +1,34 @@
|
||||
import dagre from 'dagre';
|
||||
import { isNode } from 'react-flow-renderer';
|
||||
|
||||
const setupDag = (elements, nodeWidth, nodeHeight) => {
|
||||
const dagreGraph = new dagre.graphlib.Graph();
|
||||
dagreGraph.setDefaultEdgeLabel(() => ({}));
|
||||
dagreGraph.setGraph({ rankdir: 'TB' });
|
||||
|
||||
elements.forEach((el) => {
|
||||
if (isNode(el)) {
|
||||
dagreGraph.setNode(el.id, { width: nodeWidth, height: nodeHeight });
|
||||
} else {
|
||||
dagreGraph.setEdge(el.source, el.target);
|
||||
}
|
||||
});
|
||||
|
||||
dagre.layout(dagreGraph);
|
||||
|
||||
return elements.map((el) => {
|
||||
const newElement = el;
|
||||
if (isNode(newElement)) {
|
||||
const nodeWithPosition = dagreGraph.node(newElement.id);
|
||||
newElement.targetPosition = 'top';
|
||||
newElement.sourcePosition = 'bottom';
|
||||
newElement.position = {
|
||||
x: nodeWithPosition.x - nodeWidth / 2 + Math.random() / 1000,
|
||||
y: nodeWithPosition.y - nodeHeight / 2,
|
||||
};
|
||||
}
|
||||
return newElement;
|
||||
});
|
||||
};
|
||||
|
||||
export default setupDag;
|
||||
162
src/components/NetworkDiagram/index.js
Normal file
162
src/components/NetworkDiagram/index.js
Normal file
@@ -0,0 +1,162 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { CRow, CCol } from '@coreui/react';
|
||||
import { NetworkDiagram as Graph } from 'ucentral-libs';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import createLayoutedElements from './dagreAdapter';
|
||||
|
||||
const associationStyle = {
|
||||
background: '#3399ff',
|
||||
color: 'white',
|
||||
border: '1px solid #777',
|
||||
width: 220,
|
||||
padding: 10,
|
||||
};
|
||||
|
||||
const recognizedRadioStyle = {
|
||||
background: '#2eb85c',
|
||||
color: 'white',
|
||||
width: 220,
|
||||
padding: 15,
|
||||
};
|
||||
|
||||
const unrecognizedRadioStyle = {
|
||||
background: '#e55353',
|
||||
color: 'white',
|
||||
width: 220,
|
||||
padding: 15,
|
||||
};
|
||||
|
||||
const recognizedRadioNode = (radio) => (
|
||||
<div className="align-middle">
|
||||
<h6 className="align-middle mb-0">
|
||||
Radio #{radio.radio} ({radio.channel < 16 ? '2G' : '5G'})
|
||||
</h6>
|
||||
</div>
|
||||
);
|
||||
|
||||
const unrecognizedRadioNode = (t, radio) => (
|
||||
<div className="align-middle">
|
||||
<h6 className="align-middle mb-0">
|
||||
Radio #{radio.radioIndex} ({t('common.unrecognized')})
|
||||
</h6>
|
||||
</div>
|
||||
);
|
||||
|
||||
const associationNode = (associationInfo) => (
|
||||
<div>
|
||||
<CRow>
|
||||
<CCol className="text-center">
|
||||
<h6>{associationInfo.bssid}</h6>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow>
|
||||
<CCol className="text-left pl-4">Rx Rate : {associationInfo.rxRate}</CCol>
|
||||
</CRow>
|
||||
<CRow>
|
||||
<CCol className="text-left pl-4">Tx Rate : {associationInfo.txRate}</CCol>
|
||||
</CRow>
|
||||
</div>
|
||||
);
|
||||
|
||||
const NetworkDiagram = ({ show, radios, associations }) => {
|
||||
const { t } = useTranslation();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [elements, setElements] = useState([]);
|
||||
|
||||
const getX = (associationsAdded) => {
|
||||
if (associationsAdded === 0) return 0;
|
||||
if ((associationsAdded + 1) % 2 === 0) return -140 * (associationsAdded + 1);
|
||||
return 140 * associationsAdded;
|
||||
};
|
||||
|
||||
const parseData = () => {
|
||||
setLoading(true);
|
||||
const newElements = [];
|
||||
const radiosAdded = {};
|
||||
|
||||
// Creating the radio nodes
|
||||
for (const radio of radios) {
|
||||
if (radiosAdded[radio.radio] === undefined) {
|
||||
newElements.push({
|
||||
id: `r-${radio.radio}`,
|
||||
data: { label: recognizedRadioNode(radio) },
|
||||
position: { x: 0, y: 200 * radio.radio },
|
||||
type: 'input',
|
||||
style: recognizedRadioStyle,
|
||||
});
|
||||
radiosAdded[radio.radio] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Creating the association nodes and their edges
|
||||
for (let i = 0; i < associations.length; i += 1) {
|
||||
const assoc = associations[i];
|
||||
|
||||
// If the radio has not been added, we create a new unknown radio based on its index
|
||||
if (radiosAdded[assoc.radio.radioIndex] === undefined) {
|
||||
newElements.push({
|
||||
id: `r-${assoc.radio.radioIndex}`,
|
||||
data: { label: unrecognizedRadioNode(t, assoc.radio) },
|
||||
position: { x: 0, y: 200 * assoc.radio.radioIndex },
|
||||
type: 'input',
|
||||
style: unrecognizedRadioStyle,
|
||||
});
|
||||
radiosAdded[assoc.radio.radioIndex] = 0;
|
||||
}
|
||||
|
||||
// Adding the association
|
||||
newElements.push({
|
||||
id: `a-${assoc.bssid}`,
|
||||
data: { label: associationNode(assoc) },
|
||||
position: {
|
||||
x: getX(radiosAdded[assoc.radio.radioIndex]),
|
||||
y: 80 + 240 * assoc.radio.radioIndex,
|
||||
},
|
||||
style: associationStyle,
|
||||
type: 'output',
|
||||
});
|
||||
radiosAdded[assoc.radio.radioIndex] += 1;
|
||||
|
||||
// Creating the edge
|
||||
newElements.push({
|
||||
id: `e-${assoc.radio.radioIndex}-${assoc.bssid}`,
|
||||
source: `r-${assoc.radio.radioIndex}`,
|
||||
target: `a-${assoc.bssid}`,
|
||||
arrowHeadType: 'arrowclosed',
|
||||
});
|
||||
}
|
||||
|
||||
setElements(newElements);
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (radios !== null && associations !== null) {
|
||||
parseData();
|
||||
}
|
||||
}, [radios, associations]);
|
||||
|
||||
return (
|
||||
<Graph
|
||||
show={show}
|
||||
loading={loading}
|
||||
elements={createLayoutedElements(elements, 220, 80)}
|
||||
setElements={setElements}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
NetworkDiagram.propTypes = {
|
||||
show: PropTypes.bool,
|
||||
radios: PropTypes.instanceOf(Array),
|
||||
associations: PropTypes.instanceOf(Array),
|
||||
};
|
||||
|
||||
NetworkDiagram.defaultProps = {
|
||||
show: true,
|
||||
radios: null,
|
||||
associations: null,
|
||||
};
|
||||
|
||||
export default NetworkDiagram;
|
||||
@@ -5,45 +5,32 @@ import {
|
||||
CModalTitle,
|
||||
CModalBody,
|
||||
CModalFooter,
|
||||
CSpinner,
|
||||
CSwitch,
|
||||
CCol,
|
||||
CRow,
|
||||
CInvalidFeedback,
|
||||
} from '@coreui/react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import DatePicker from 'react-widgets/DatePicker';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { convertDateToUtc, convertDateFromUtc, dateToUnix } from 'utils/helper';
|
||||
import { dateToUnix } from 'utils/helper';
|
||||
import 'react-widgets/styles.css';
|
||||
import { getToken } from 'utils/authHelper';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
import eventBus from 'utils/eventBus';
|
||||
import LoadingButton from 'components/LoadingButton';
|
||||
import { LoadingButton, useAuth, useDevice } from 'ucentral-libs';
|
||||
import SuccessfulActionModalBody from 'components/SuccessfulActionModalBody';
|
||||
import styles from './index.module.scss';
|
||||
|
||||
const ActionModal = ({ show, toggleModal }) => {
|
||||
const { t } = useTranslation();
|
||||
const [hadSuccess, setHadSuccess] = useState(false);
|
||||
const [hadFailure, setHadFailure] = useState(false);
|
||||
const { currentToken, endpoints } = useAuth();
|
||||
const { deviceSerialNumber } = useDevice();
|
||||
const [waiting, setWaiting] = useState(false);
|
||||
const [validDate, setValidDate] = useState(true);
|
||||
const [result, setResult] = useState(null);
|
||||
const [chosenDate, setChosenDate] = useState(new Date().toString());
|
||||
const [doingNow, setDoingNow] = useState(false);
|
||||
const [responseBody, setResponseBody] = useState('');
|
||||
const selectedDeviceId = useSelector((state) => state.selectedDeviceId);
|
||||
const [isNow, setIsNow] = useState(false);
|
||||
|
||||
const setDateToLate = () => {
|
||||
const date = convertDateToUtc(new Date());
|
||||
if (date.getHours() >= 3) {
|
||||
date.setDate(date.getDate() + 1);
|
||||
}
|
||||
date.setHours(3);
|
||||
date.setMinutes(0);
|
||||
|
||||
setChosenDate(convertDateFromUtc(date).toString());
|
||||
const toggleNow = () => {
|
||||
setIsNow(!isNow);
|
||||
};
|
||||
|
||||
const setDate = (date) => {
|
||||
@@ -54,47 +41,40 @@ const ActionModal = ({ show, toggleModal }) => {
|
||||
|
||||
useEffect(() => {
|
||||
if (show) {
|
||||
setHadSuccess(false);
|
||||
setHadFailure(false);
|
||||
setResult(null);
|
||||
setWaiting(false);
|
||||
setDoingNow(false);
|
||||
setChosenDate(new Date().toString());
|
||||
setResponseBody('');
|
||||
setValidDate(true);
|
||||
}
|
||||
}, [show]);
|
||||
|
||||
const doAction = (isNow) => {
|
||||
if (isNow !== undefined) setDoingNow(isNow);
|
||||
setHadFailure(false);
|
||||
setHadSuccess(false);
|
||||
const doAction = () => {
|
||||
setWaiting(true);
|
||||
|
||||
const token = getToken();
|
||||
const utcDate = new Date(chosenDate);
|
||||
const utcDateString = utcDate.toISOString();
|
||||
|
||||
const parameters = {
|
||||
serialNumber: selectedDeviceId,
|
||||
when: isNow ? 0 : dateToUnix(utcDateString),
|
||||
serialNumber: deviceSerialNumber,
|
||||
when: isNow ? 0 : dateToUnix(utcDate),
|
||||
};
|
||||
|
||||
const headers = {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${token}`,
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
};
|
||||
|
||||
axiosInstance
|
||||
.post(`/device/${encodeURIComponent(selectedDeviceId)}/reboot`, parameters, { headers })
|
||||
.post(
|
||||
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}/reboot`,
|
||||
parameters,
|
||||
{ headers },
|
||||
)
|
||||
.then(() => {
|
||||
setHadSuccess(true);
|
||||
setResult('success');
|
||||
})
|
||||
.catch(() => {
|
||||
setResponseBody(t('commands.error'));
|
||||
setHadFailure(true);
|
||||
setResult('error');
|
||||
})
|
||||
.finally(() => {
|
||||
setDoingNow(false);
|
||||
setWaiting(false);
|
||||
eventBus.dispatch('actionCompleted', { message: 'An action has been completed' });
|
||||
});
|
||||
@@ -105,62 +85,49 @@ const ActionModal = ({ show, toggleModal }) => {
|
||||
<CModalHeader closeButton>
|
||||
<CModalTitle>{t('reboot.title')}</CModalTitle>
|
||||
</CModalHeader>
|
||||
{hadSuccess ? (
|
||||
{result === 'success' ? (
|
||||
<SuccessfulActionModalBody toggleModal={toggleModal} />
|
||||
) : (
|
||||
<div>
|
||||
<CModalBody>
|
||||
<h6>{t('reboot.directions')}</h6>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CCol>
|
||||
<CButton onClick={() => doAction(true)} disabled={waiting} block color="primary">
|
||||
{waiting && doingNow ? t('common.loading_ellipsis') : t('common.do_now')}
|
||||
<CSpinner
|
||||
color="light"
|
||||
hidden={!waiting || !doingNow}
|
||||
component="span"
|
||||
size="sm"
|
||||
/>
|
||||
</CButton>
|
||||
<CRow>
|
||||
<CCol md="8">
|
||||
<p>{t('reboot.now')}</p>
|
||||
</CCol>
|
||||
<CCol>
|
||||
<CButton disabled={waiting} block color="primary" onClick={() => setDateToLate()}>
|
||||
{t('common.later_tonight')}
|
||||
</CButton>
|
||||
<CSwitch
|
||||
disabled={waiting}
|
||||
color="primary"
|
||||
defaultChecked={isNow}
|
||||
onClick={toggleNow}
|
||||
labelOn={t('common.yes')}
|
||||
labelOff={t('common.no')}
|
||||
/>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CCol md="4" className={styles.spacedColumn}>
|
||||
<p>{t('common.date')}:</p>
|
||||
<CRow hidden={isNow} className="mt-2">
|
||||
<CCol md="4" className="pt-2">
|
||||
<p>{t('common.custom_date')}:</p>
|
||||
</CCol>
|
||||
<CCol xs="12" md="8">
|
||||
<DatePicker
|
||||
selected={new Date(chosenDate)}
|
||||
includeTime
|
||||
className={('form-control', { 'is-invalid': !validDate })}
|
||||
value={new Date(chosenDate)}
|
||||
placeholder="Select custom date"
|
||||
disabled={waiting}
|
||||
onChange={(date) => setDate(date)}
|
||||
min={convertDateToUtc(new Date())}
|
||||
min={new Date()}
|
||||
/>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CInvalidFeedback>{t('common.need_date')}</CInvalidFeedback>
|
||||
|
||||
<div hidden={!hadSuccess && !hadFailure}>
|
||||
<div>
|
||||
<pre className="ignore">{responseBody}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</CModalBody>
|
||||
<CModalFooter>
|
||||
<LoadingButton
|
||||
label={t('common.schedule')}
|
||||
label={isNow ? t('reboot.title') : t('common.schedule')}
|
||||
isLoadingLabel={t('common.loading_ellipsis')}
|
||||
isLoading={waiting}
|
||||
action={doAction}
|
||||
variant="outline"
|
||||
block={false}
|
||||
disabled={waiting}
|
||||
/>
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
.spacedRow {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.spacedColumn {
|
||||
margin-top: 7px;
|
||||
}
|
||||
119
src/components/TraceModal/WaitingForTraceBody.js
Normal file
119
src/components/TraceModal/WaitingForTraceBody.js
Normal file
@@ -0,0 +1,119 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import PropTypes from 'prop-types';
|
||||
import { CAlert, CModalBody, CButton, CSpinner, CModalFooter } from '@coreui/react';
|
||||
import { useAuth } from 'ucentral-libs';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
|
||||
const WaitingForTraceBody = ({ serialNumber, commandUuid, toggle }) => {
|
||||
const { t } = useTranslation();
|
||||
const { currentToken, endpoints } = useAuth();
|
||||
const [secondsElapsed, setSecondsElapsed] = useState(0);
|
||||
const [waitingForFile, setWaitingForFile] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
const getTraceResult = () => {
|
||||
const options = {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
},
|
||||
};
|
||||
|
||||
axiosInstance
|
||||
.get(`${endpoints.ucentralgw}/api/v1/command/${encodeURIComponent(commandUuid)}`, options)
|
||||
.then((response) => {
|
||||
if (response.data.waitingForFile === 0) {
|
||||
setWaitingForFile(false);
|
||||
}
|
||||
if (response.data.errorCode !== 0) {
|
||||
setWaitingForFile(false);
|
||||
setError(response.data.errorText);
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
const downloadTrace = () => {
|
||||
const options = {
|
||||
headers: {
|
||||
Accept: 'application/octet-stream',
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
},
|
||||
responseType: 'arraybuffer',
|
||||
};
|
||||
|
||||
axiosInstance
|
||||
.get(
|
||||
`${endpoints.ucentralgw}/api/v1/file/${commandUuid}?serialNumber=${serialNumber}`,
|
||||
options,
|
||||
)
|
||||
.then((response) => {
|
||||
const blob = new Blob([response.data], { type: 'application/octet-stream' });
|
||||
const link = document.createElement('a');
|
||||
link.href = window.URL.createObjectURL(blob);
|
||||
link.download = `Trace_${commandUuid}.pcap`;
|
||||
link.click();
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setInterval(() => {
|
||||
setSecondsElapsed(secondsElapsed + 1);
|
||||
}, 1000);
|
||||
if (!waitingForFile) {
|
||||
clearInterval(timer);
|
||||
}
|
||||
return () => {
|
||||
clearInterval(timer);
|
||||
};
|
||||
}, [waitingForFile, secondsElapsed]);
|
||||
|
||||
useEffect(() => {
|
||||
const refreshStatus = setInterval(() => {
|
||||
getTraceResult();
|
||||
}, 5000);
|
||||
if (!waitingForFile) {
|
||||
clearInterval(refreshStatus);
|
||||
}
|
||||
return () => {
|
||||
clearInterval(refreshStatus);
|
||||
};
|
||||
}, [waitingForFile]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<CModalBody>
|
||||
<h6>{t('trace.waiting_seconds', { seconds: secondsElapsed })}</h6>
|
||||
<p>{t('trace.waiting_directions')}</p>
|
||||
<div className="d-flex align-middle justify-content-center">
|
||||
<CSpinner hidden={!waitingForFile} />
|
||||
<CButton
|
||||
hidden={waitingForFile || error}
|
||||
onClick={downloadTrace}
|
||||
disabled={waitingForFile || error}
|
||||
color="primary"
|
||||
>
|
||||
{t('trace.download_trace')}
|
||||
</CButton>
|
||||
<CAlert hidden={waitingForFile || !error} className="my-3" color="danger">
|
||||
{t('trace.trace_not_successful', { error })}
|
||||
</CAlert>
|
||||
</div>
|
||||
</CModalBody>
|
||||
<CModalFooter>
|
||||
<CButton color="secondary" block onClick={toggle}>
|
||||
{t('common.exit')}
|
||||
</CButton>
|
||||
</CModalFooter>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
WaitingForTraceBody.propTypes = {
|
||||
serialNumber: PropTypes.string.isRequired,
|
||||
commandUuid: PropTypes.string.isRequired,
|
||||
toggle: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default WaitingForTraceBody;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user