mirror of
https://github.com/Telecominfraproject/wlan-cloud-ucentralgw-ui.git
synced 2025-10-30 02:12:33 +00:00
Compare commits
112 Commits
feature/do
...
v2.0.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
85c7a34af5 | ||
|
|
df6ec36515 | ||
|
|
db7394d86c | ||
|
|
772e7cd07d | ||
|
|
54bc4ad403 | ||
|
|
8fe26a08b3 | ||
|
|
6f78768459 | ||
|
|
c3c31ec4ac | ||
|
|
3dce199b79 | ||
|
|
403e8bc185 | ||
|
|
0f15ae7bef | ||
|
|
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 | ||
|
|
68f7570720 | ||
|
|
59e1709cff | ||
|
|
39b05192fe | ||
|
|
fa377132a8 | ||
|
|
b3e5cd79ed | ||
|
|
6f7b05649b | ||
|
|
4861a746b4 | ||
|
|
e75c163248 | ||
|
|
d8b501fff0 | ||
|
|
9de6c27c69 | ||
|
|
80811a6071 | ||
|
|
814cb09e44 | ||
|
|
050a4a3b78 | ||
|
|
ced06d1391 | ||
|
|
9876c98628 |
@@ -25,3 +25,4 @@ yarn-error.log*
|
||||
.git
|
||||
.github
|
||||
Dockerfile
|
||||
helm
|
||||
|
||||
@@ -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
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM node:16-alpine3.11 AS build
|
||||
FROM node:14-alpine3.11 AS build
|
||||
|
||||
COPY package.json package-lock.json /
|
||||
|
||||
@@ -6,9 +6,10 @@ RUN npm install
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN echo '{"DEFAULT_GATEWAY_URL": "https://ucentral.dpaas.arilia.com:16001","ALLOW_GATEWAY_CHANGE": true}' > public/config.json \
|
||||
&& npm run build
|
||||
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
|
||||
|
||||
28
README.md
28
README.md
@@ -9,18 +9,40 @@ NOTE: This UI will be evolving as micro services are added to the uCentral progr
|
||||
## 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.
|
||||
Once the build is done, you can move the `build` folder on your server.
|
||||
|
||||
### 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.
|
||||
|
||||
Here are the current default values:
|
||||
```
|
||||
{
|
||||
"DEFAULT_UCENTRALSEC_URL": "https://ucentral.dpaas.arilia.com:16001",
|
||||
"ALLOW_UCENTRALSEC_CHANGE": false
|
||||
}
|
||||
```
|
||||
|
||||
7
babel.config.json
Normal file
7
babel.config.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"presets": [
|
||||
"@babel/preset-env",
|
||||
"@babel/preset-react"
|
||||
],
|
||||
"plugins": ["@babel/plugin-proposal-class-properties"]
|
||||
}
|
||||
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'),
|
||||
};
|
||||
76
config/webpack.common.js
Normal file
76
config/webpack.common.js
Normal file
@@ -0,0 +1,76 @@
|
||||
/* 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 CleanWebpackPlugin(),
|
||||
|
||||
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',
|
||||
}),
|
||||
],
|
||||
|
||||
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' },
|
||||
],
|
||||
},
|
||||
};
|
||||
53
config/webpack.dev.js
Normal file
53
config/webpack.dev.js
Normal file
@@ -0,0 +1,53 @@
|
||||
/* 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'),
|
||||
},
|
||||
},
|
||||
plugins: [new ReactRefreshWebpackPlugin()],
|
||||
});
|
||||
36
config/webpack.prod.js
Normal file
36
config/webpack.prod.js
Normal file
@@ -0,0 +1,36 @@
|
||||
/* 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 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 MiniCssExtractPlugin({
|
||||
filename: 'styles/[name].[contenthash].css',
|
||||
chunkFilename: '[contenthash].css',
|
||||
}),
|
||||
],
|
||||
module: {
|
||||
rules: [],
|
||||
},
|
||||
optimization: {
|
||||
minimize: true,
|
||||
minimizer: [`...`, new TerserPlugin(), new CssMinimizerPlugin()],
|
||||
},
|
||||
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.0.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"]
|
||||
}
|
||||
|
||||
30991
package-lock.json
generated
30991
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
85
package.json
85
package.json
@@ -1,23 +1,14 @@
|
||||
{
|
||||
"name": "ucentral-client",
|
||||
"version": "0.9.3",
|
||||
"private": true,
|
||||
"version": "0.9.14",
|
||||
"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",
|
||||
"apexcharts": "^3.27.1",
|
||||
"axios": "^0.21.1",
|
||||
"axios-retry": "^3.1.9",
|
||||
"http": "^0.0.1-security",
|
||||
"https": "^1.0.0",
|
||||
"i18next": "^20.3.1",
|
||||
"i18next-browser-languagedetector": "^6.1.2",
|
||||
"i18next-http-backend": "^1.2.6",
|
||||
@@ -27,20 +18,18 @@
|
||||
"react-dom": "^17.0.2",
|
||||
"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-widgets": "^5.1.1",
|
||||
"redux": "^4.1.0",
|
||||
"sass": "^1.35.1",
|
||||
"ucentral-libs": "^0.8.7",
|
||||
"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"
|
||||
@@ -51,12 +40,54 @@
|
||||
}
|
||||
},
|
||||
"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",
|
||||
"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-cli": "^4.7.2",
|
||||
"webpack-dev-server": "^3.11.2",
|
||||
"webpack-merge": "^5.8.0"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
@@ -68,17 +99,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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"DEFAULT_GATEWAY_URL": "https://ucentral.dpaas.arilia.com:16001",
|
||||
"ALLOW_GATEWAY_CHANGE": false
|
||||
}
|
||||
"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,103 @@
|
||||
{
|
||||
"actions": {
|
||||
"blink": "Blinken",
|
||||
"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": "WiFi 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",
|
||||
"success": "Befehl wurde erfolgreich übermittelt",
|
||||
"title": "Gerätebefehle"
|
||||
},
|
||||
"common": {
|
||||
"add": "Hinzufügen",
|
||||
"adding_ellipsis": "Hinzufügen ...",
|
||||
"are_you_sure": "Bist du sicher?",
|
||||
"cancel": "Stornieren",
|
||||
"cancel": "Abbrechen",
|
||||
"certificate": "Zertifikat",
|
||||
"clear": "klar",
|
||||
"clear": "Löschen",
|
||||
"close": "Schließen",
|
||||
"command": "Befehl",
|
||||
"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",
|
||||
"created_by": "Erstellt von",
|
||||
"custom_date": "Benutzerdefiniertes Datum",
|
||||
"date": "Datum",
|
||||
"day": "tag",
|
||||
"days": "tage",
|
||||
"delete": "Löschen",
|
||||
"details": "Einzelheiten",
|
||||
"device_list": "Liste der Geräte",
|
||||
"device_page": "Geräteseite",
|
||||
"device_page": "Geräte",
|
||||
"devices": "Geräte",
|
||||
"dismiss": "entlassen",
|
||||
"do_now": "Jetzt tun!",
|
||||
"do_now": "Sofort",
|
||||
"download": "Herunterladen",
|
||||
"duration": "Dauer",
|
||||
"error": "Error",
|
||||
"executed": "Hingerichtet",
|
||||
"error": "Fehler",
|
||||
"execute_now": "Möchten Sie diesen Befehl jetzt ausführen?",
|
||||
"executed": "Ausgeführt",
|
||||
"exit": "Ausgang",
|
||||
"firmware": "Firmware",
|
||||
"from": "Von",
|
||||
"id": "Ich würde",
|
||||
"hour": "stunde",
|
||||
"hours": "std",
|
||||
"id": "ID",
|
||||
"ip_address": "IP Adresse",
|
||||
"later_tonight": "Heute Abend später",
|
||||
"later_tonight": "Später am Abend",
|
||||
"loading_ellipsis": "Wird geladen...",
|
||||
"loading_more_ellipsis": "Mehr laden ...",
|
||||
"logout": "Ausloggen",
|
||||
"mac": "MAC-Adresse",
|
||||
"manufacturer": "Hersteller",
|
||||
"na": "N / A",
|
||||
"minute": "Minute",
|
||||
"minutes": "protokoll",
|
||||
"na": "(unbekannt)",
|
||||
"need_date": "Du brauchst ein Datum...",
|
||||
"no": "Nein",
|
||||
"no_items": "Keine Gegenstände",
|
||||
"not_connected": "Nicht verbunden",
|
||||
"off": "aus",
|
||||
"on": "auf",
|
||||
"off": "Aus",
|
||||
"on": "An",
|
||||
"recorded": "Verzeichnet",
|
||||
"refresh": "Aktualisierung",
|
||||
"refresh_device": "Gerät aktualisieren",
|
||||
"result": "Ergebnis",
|
||||
"save": "Sparen",
|
||||
"saving": "Speichern ...",
|
||||
"schedule": "Zeitplan",
|
||||
"serial_number": "Ordnungsnummer",
|
||||
"second": "zweite",
|
||||
"seconds": "sekunden",
|
||||
"seconds_elapsed": "Sekunden verstrichen",
|
||||
"serial_number": "Seriennummer",
|
||||
"start": "Start",
|
||||
"submit": "einreichen",
|
||||
"submit": "Absenden",
|
||||
"submitted": "Eingereicht",
|
||||
"success": "Erfolg",
|
||||
"to": "zu",
|
||||
"unknown": "unbekannte",
|
||||
"uuid": "UUID",
|
||||
"view_more": "Mehr sehen",
|
||||
"view_more": "Mehr anzeigen",
|
||||
"yes": "Ja"
|
||||
},
|
||||
"configuration": {
|
||||
@@ -86,18 +107,19 @@
|
||||
"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"
|
||||
"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.",
|
||||
@@ -110,59 +132,97 @@
|
||||
"healthchecks_title": "Healthchecks löschen"
|
||||
},
|
||||
"device_logs": {
|
||||
"log": "Log",
|
||||
"severity": "Schwere",
|
||||
"log": "Protokoll",
|
||||
"severity": "Wichtigkeit",
|
||||
"title": "Geräteprotokolle"
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"login": {
|
||||
"login": "Anmeldung",
|
||||
"login_error": "Anmeldefehler, bestätigen Sie, dass Ihr Benutzername, Ihr Passwort und Ihre Gateway-URL gültig sind",
|
||||
"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",
|
||||
"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."
|
||||
},
|
||||
"statistics": {
|
||||
"data": "Daten (KB)",
|
||||
"latest_statistics": "Neueste Statistiken",
|
||||
"show_latest": "Neueste Statistiken anzeigen JSON",
|
||||
"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"
|
||||
},
|
||||
"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",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,22 +7,26 @@
|
||||
"factory_reset": "Factory Reset",
|
||||
"firmware_upgrade": "Firmware Upgrade",
|
||||
"reboot": "Reboot",
|
||||
"title": "Device Actions",
|
||||
"title": "Commands",
|
||||
"trace": "Trace",
|
||||
"wifi_scan": "Wifi 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"
|
||||
"success": "Command submitted successfully, you can look at the Commands log for the result",
|
||||
"title": "Command History"
|
||||
},
|
||||
"common": {
|
||||
"add": "Add",
|
||||
"adding_ellipsis": "Adding...",
|
||||
"are_you_sure": "Are you sure?",
|
||||
"cancel": "Cancel",
|
||||
"certificate": "Certificate",
|
||||
@@ -36,7 +40,11 @@
|
||||
"connected": "Connected",
|
||||
"copied": "Copied!",
|
||||
"copy_to_clipboard": "Copy to clipboard",
|
||||
"created_by": "Created By",
|
||||
"custom_date": "Custom Date",
|
||||
"date": "Date",
|
||||
"day": "day",
|
||||
"days": "days",
|
||||
"delete": "Delete",
|
||||
"details": "Details",
|
||||
"device_list": "List of Devices",
|
||||
@@ -47,9 +55,13 @@
|
||||
"download": "Download",
|
||||
"duration": "Duration",
|
||||
"error": "Error",
|
||||
"execute_now": "Would you like to execute this command now?",
|
||||
"executed": "Executed",
|
||||
"exit": "Exit",
|
||||
"firmware": "Firmware",
|
||||
"from": "From",
|
||||
"hour": "hour",
|
||||
"hours": "hours",
|
||||
"id": "Id",
|
||||
"ip_address": "Ip Address",
|
||||
"later_tonight": "Later tonight",
|
||||
@@ -58,8 +70,12 @@
|
||||
"logout": "Logout",
|
||||
"mac": "MAC Address",
|
||||
"manufacturer": "Manufacturer",
|
||||
"minute": "minute",
|
||||
"minutes": "minutes",
|
||||
"na": "N/A",
|
||||
"need_date": "You need a date...",
|
||||
"no": "No",
|
||||
"no_items": "No Items",
|
||||
"not_connected": "Not Connected",
|
||||
"off": "Off",
|
||||
"on": "On",
|
||||
@@ -67,7 +83,12 @@
|
||||
"refresh": "Refresh",
|
||||
"refresh_device": "Refresh Device",
|
||||
"result": "Result",
|
||||
"save": "Save",
|
||||
"saving": "Saving... ",
|
||||
"schedule": "Schedule",
|
||||
"second": "second",
|
||||
"seconds": "seconds",
|
||||
"seconds_elapsed": "Seconds elapsed",
|
||||
"serial_number": "Serial Number",
|
||||
"start": "Start",
|
||||
"submit": "Submit",
|
||||
@@ -81,14 +102,15 @@
|
||||
},
|
||||
"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",
|
||||
"view_json": "View raw JSON"
|
||||
},
|
||||
@@ -96,7 +118,7 @@
|
||||
"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": {
|
||||
@@ -112,11 +134,13 @@
|
||||
"device_logs": {
|
||||
"log": "Log",
|
||||
"severity": "Severity",
|
||||
"title": "Device Logs"
|
||||
"title": "Logs"
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"footer": {
|
||||
@@ -126,43 +150,79 @@
|
||||
},
|
||||
"health": {
|
||||
"sanity": "Sanity",
|
||||
"title": "Device Health"
|
||||
"title": "Health"
|
||||
},
|
||||
"login": {
|
||||
"login": "Login",
|
||||
"login_error": "Login error, confirm that your username, password and gateway url are valid",
|
||||
"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",
|
||||
"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": "Wifi 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."
|
||||
},
|
||||
"statistics": {
|
||||
"data": "Data (KB)",
|
||||
"latest_statistics": "Latest Statistics",
|
||||
"show_latest": "Show latest statistics JSON",
|
||||
"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 "
|
||||
},
|
||||
"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",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,22 +7,26 @@
|
||||
"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"
|
||||
},
|
||||
"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"
|
||||
"success": "Comando enviado con éxito, puede consultar el registro de Comandos para ver el resultado",
|
||||
"title": "Historial de Comandos"
|
||||
},
|
||||
"common": {
|
||||
"add": "Añadir",
|
||||
"adding_ellipsis": "Añadiendo ...",
|
||||
"are_you_sure": "¿Estás seguro?",
|
||||
"cancel": "Cancelar",
|
||||
"certificate": "Certificado",
|
||||
@@ -36,7 +40,11 @@
|
||||
"connected": "Conectado",
|
||||
"copied": "Copiado!",
|
||||
"copy_to_clipboard": "Copiar al portapapeles",
|
||||
"created_by": "Creado por",
|
||||
"custom_date": "Fecha personalizada",
|
||||
"date": "Fecha",
|
||||
"day": "día",
|
||||
"days": "días",
|
||||
"delete": "Borrar",
|
||||
"details": "Detalles",
|
||||
"device_list": "Listado de dispositivos",
|
||||
@@ -47,9 +55,13 @@
|
||||
"download": "Descargar",
|
||||
"duration": "Duración",
|
||||
"error": "Error",
|
||||
"execute_now": "¿Le gustaría ejecutar este comando ahora?",
|
||||
"executed": "ejecutado",
|
||||
"exit": "salida",
|
||||
"firmware": "Firmware",
|
||||
"from": "Desde",
|
||||
"hour": "hora",
|
||||
"hours": "horas",
|
||||
"id": "Carné de identidad",
|
||||
"ip_address": "Dirección IP",
|
||||
"later_tonight": "Más tarde esta noche",
|
||||
@@ -58,8 +70,12 @@
|
||||
"logout": "Cerrar sesión",
|
||||
"mac": "Dirección MAC",
|
||||
"manufacturer": "Fabricante",
|
||||
"minute": "minuto",
|
||||
"minutes": "minutos",
|
||||
"na": "N / A",
|
||||
"need_date": "Necesitas una cita ...",
|
||||
"no": "No",
|
||||
"no_items": "No hay articulos",
|
||||
"not_connected": "No conectado",
|
||||
"off": "Apagado",
|
||||
"on": "en",
|
||||
@@ -67,7 +83,12 @@
|
||||
"refresh": "Refrescar",
|
||||
"refresh_device": "Actualizar dispositivo",
|
||||
"result": "Resultado",
|
||||
"save": "Salvar",
|
||||
"saving": "Ahorro...",
|
||||
"schedule": "Programar",
|
||||
"second": "segundo",
|
||||
"seconds": "segundos",
|
||||
"seconds_elapsed": "Segundos transcurridos",
|
||||
"serial_number": "Número de serie",
|
||||
"start": "comienzo",
|
||||
"submit": "Enviar",
|
||||
@@ -81,14 +102,15 @@
|
||||
},
|
||||
"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",
|
||||
"view_json": "Ver JSON sin procesar"
|
||||
},
|
||||
@@ -96,7 +118,7 @@
|
||||
"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": {
|
||||
@@ -112,11 +134,13 @@
|
||||
"device_logs": {
|
||||
"log": "Iniciar sesión",
|
||||
"severity": "Gravedad",
|
||||
"title": "Registros de dispositivos"
|
||||
"title": "Registros"
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"footer": {
|
||||
@@ -126,43 +150,79 @@
|
||||
},
|
||||
"health": {
|
||||
"sanity": "Cordura",
|
||||
"title": "Salud del dispositivo"
|
||||
"title": "Salud"
|
||||
},
|
||||
"login": {
|
||||
"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",
|
||||
"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",
|
||||
"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 Wifi",
|
||||
"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."
|
||||
},
|
||||
"statistics": {
|
||||
"data": "Datos (KB)",
|
||||
"latest_statistics": "Últimas estadísticas",
|
||||
"show_latest": "Mostrar las últimas estadísticas JSON",
|
||||
"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"
|
||||
},
|
||||
"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",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,22 +7,26 @@
|
||||
"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"
|
||||
"success": "Commande soumise avec succès, vous pouvez consulter le journal des commandes pour le résultat",
|
||||
"title": "Historique des commandes"
|
||||
},
|
||||
"common": {
|
||||
"add": "Ajouter",
|
||||
"adding_ellipsis": "Ajouter...",
|
||||
"are_you_sure": "Êtes-vous sûr?",
|
||||
"cancel": "annuler",
|
||||
"certificate": "Certificat",
|
||||
@@ -36,7 +40,11 @@
|
||||
"connected": "Connecté",
|
||||
"copied": "Copié!",
|
||||
"copy_to_clipboard": "Copier dans le presse-papier",
|
||||
"created_by": "Créé par",
|
||||
"custom_date": "Date personnalisée",
|
||||
"date": "Rendez-vous amoureux",
|
||||
"day": "journée",
|
||||
"days": "journées",
|
||||
"delete": "Effacer",
|
||||
"details": "Détails",
|
||||
"device_list": "Liste des appareils",
|
||||
@@ -47,9 +55,13 @@
|
||||
"download": "Télécharger",
|
||||
"duration": "Durée",
|
||||
"error": "erreur",
|
||||
"execute_now": "Souhaitez-vous exécuter cette commande maintenant ?",
|
||||
"executed": "réalisé",
|
||||
"exit": "Sortie",
|
||||
"firmware": "Micrologiciel",
|
||||
"from": "De",
|
||||
"hour": "heure",
|
||||
"hours": "heures",
|
||||
"id": "Id",
|
||||
"ip_address": "Adresse IP",
|
||||
"later_tonight": "Plus tard ce soir",
|
||||
@@ -58,8 +70,12 @@
|
||||
"logout": "Connectez - Out",
|
||||
"mac": "ADRESSE MAC",
|
||||
"manufacturer": "fabricant",
|
||||
"minute": "minute",
|
||||
"minutes": "minutes",
|
||||
"na": "N / A",
|
||||
"need_date": "Vous avez besoin d'un rendez-vous...",
|
||||
"no": "Non",
|
||||
"no_items": "Pas d'objet",
|
||||
"not_connected": "Pas connecté",
|
||||
"off": "De",
|
||||
"on": "sur",
|
||||
@@ -67,7 +83,12 @@
|
||||
"refresh": "Rafraîchir",
|
||||
"refresh_device": "Actualiser l'appareil",
|
||||
"result": "Résultat",
|
||||
"save": "Sauvegarder",
|
||||
"saving": "Économie...",
|
||||
"schedule": "Programme",
|
||||
"second": "seconde",
|
||||
"seconds": "secondes",
|
||||
"seconds_elapsed": "Secondes écoulées",
|
||||
"serial_number": "Numéro de série",
|
||||
"start": "Début",
|
||||
"submit": "Soumettre",
|
||||
@@ -81,14 +102,15 @@
|
||||
},
|
||||
"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",
|
||||
"view_json": "Afficher le JSON brut"
|
||||
},
|
||||
@@ -96,7 +118,7 @@
|
||||
"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": {
|
||||
@@ -112,11 +134,13 @@
|
||||
"device_logs": {
|
||||
"log": "Bûche",
|
||||
"severity": "Gravité",
|
||||
"title": "Journaux de l'appareil"
|
||||
"title": "Journaux"
|
||||
},
|
||||
"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é"
|
||||
},
|
||||
"footer": {
|
||||
@@ -126,43 +150,79 @@
|
||||
},
|
||||
"health": {
|
||||
"sanity": "Santé mentale",
|
||||
"title": "Santé de l'appareil"
|
||||
"title": "Santé"
|
||||
},
|
||||
"login": {
|
||||
"login": "S'identifier",
|
||||
"login_error": "Erreur de connexion, confirmez que votre nom d'utilisateur, mot de passe et URL de passerelle sont valides",
|
||||
"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",
|
||||
"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."
|
||||
},
|
||||
"statistics": {
|
||||
"data": "Données (Ko)",
|
||||
"latest_statistics": "Dernières statistiques",
|
||||
"show_latest": "Afficher les dernières statistiques JSON",
|
||||
"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"
|
||||
},
|
||||
"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",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,22 +7,26 @@
|
||||
"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"
|
||||
},
|
||||
"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"
|
||||
"success": "Comando enviado com sucesso, você pode consultar o log de Comandos para ver o resultado",
|
||||
"title": "Histórico de Comandos"
|
||||
},
|
||||
"common": {
|
||||
"add": "Adicionar",
|
||||
"adding_ellipsis": "Adicionando ...",
|
||||
"are_you_sure": "Você tem certeza?",
|
||||
"cancel": "Cancelar",
|
||||
"certificate": "Certificado",
|
||||
@@ -36,7 +40,11 @@
|
||||
"connected": "Conectado",
|
||||
"copied": "Copiado!",
|
||||
"copy_to_clipboard": "Copiar para área de transferência",
|
||||
"created_by": "Criado Por",
|
||||
"custom_date": "Data personalizada",
|
||||
"date": "Encontro",
|
||||
"day": "dia",
|
||||
"days": "dias",
|
||||
"delete": "Excluir",
|
||||
"details": "Detalhes",
|
||||
"device_list": "Lista de Dispositivos",
|
||||
@@ -47,9 +55,13 @@
|
||||
"download": "Baixar",
|
||||
"duration": "Duração",
|
||||
"error": "Erro",
|
||||
"execute_now": "Você gostaria de executar este comando agora?",
|
||||
"executed": "Executado",
|
||||
"exit": "Saída",
|
||||
"firmware": "Firmware",
|
||||
"from": "De",
|
||||
"hour": "hora",
|
||||
"hours": "horas",
|
||||
"id": "identidade",
|
||||
"ip_address": "Endereço de IP",
|
||||
"later_tonight": "Logo à noite",
|
||||
@@ -58,8 +70,12 @@
|
||||
"logout": "Sair",
|
||||
"mac": "Endereço MAC",
|
||||
"manufacturer": "Fabricante",
|
||||
"minute": "minuto",
|
||||
"minutes": "minutos",
|
||||
"na": "N / D",
|
||||
"need_date": "Você precisa de um encontro ...",
|
||||
"no": "Não",
|
||||
"no_items": "Nenhum item",
|
||||
"not_connected": "Não conectado",
|
||||
"off": "Fora",
|
||||
"on": "em",
|
||||
@@ -67,7 +83,12 @@
|
||||
"refresh": "REFRESH",
|
||||
"refresh_device": "Atualizar dispositivo",
|
||||
"result": "Resultado",
|
||||
"save": "Salve",
|
||||
"saving": "Salvando ...",
|
||||
"schedule": "Cronograma",
|
||||
"second": "segundo",
|
||||
"seconds": "segundos",
|
||||
"seconds_elapsed": "Segundos decorridos",
|
||||
"serial_number": "Número de série",
|
||||
"start": "Começar",
|
||||
"submit": "Enviar",
|
||||
@@ -81,14 +102,15 @@
|
||||
},
|
||||
"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",
|
||||
"view_json": "Exibir JSON bruto"
|
||||
},
|
||||
@@ -96,7 +118,7 @@
|
||||
"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": {
|
||||
@@ -112,11 +134,13 @@
|
||||
"device_logs": {
|
||||
"log": "Registro",
|
||||
"severity": "Gravidade",
|
||||
"title": "Registros de dispositivos"
|
||||
"title": "Toras"
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"footer": {
|
||||
@@ -126,43 +150,79 @@
|
||||
},
|
||||
"health": {
|
||||
"sanity": "Sanidade",
|
||||
"title": "Saúde do Dispositivo"
|
||||
"title": "Saúde"
|
||||
},
|
||||
"login": {
|
||||
"login": "Entrar",
|
||||
"login_error": "Erro de login, confirme se seu nome de usuário, senha e url de gateway são válidos",
|
||||
"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",
|
||||
"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."
|
||||
},
|
||||
"statistics": {
|
||||
"data": "Dados (KB)",
|
||||
"latest_statistics": "Estatísticas mais recentes",
|
||||
"show_latest": "Mostrar estatísticas mais recentes JSON",
|
||||
"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"
|
||||
},
|
||||
"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",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
||||
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: *
|
||||
44
src/App.js
44
src/App.js
@@ -1,7 +1,9 @@
|
||||
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 'contexts/AuthProvider';
|
||||
import { checkIfJson } from 'utils/helper';
|
||||
|
||||
const loading = (
|
||||
<div className="pt-3 text-center">
|
||||
@@ -9,32 +11,22 @@ 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 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 |
@@ -122,14 +122,11 @@ import {
|
||||
cilXCircle,
|
||||
cilWarning,
|
||||
} from '@coreui/icons';
|
||||
import { sygnet } from './sygnet';
|
||||
import { logo } from './logo';
|
||||
import { logoNegative } from './logo-negative';
|
||||
|
||||
import { logo } from './CoreuiLogo';
|
||||
|
||||
export const icons = {
|
||||
sygnet,
|
||||
logo,
|
||||
logoNegative,
|
||||
cilAlignCenter,
|
||||
cilAlignLeft,
|
||||
cilAlignRight,
|
||||
|
||||
@@ -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>
|
||||
`,
|
||||
];
|
||||
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,10 +16,10 @@ 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 { useAuth } from 'contexts/AuthProvider';
|
||||
import { useDevice } from 'contexts/DeviceProvider';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
import eventBus from 'utils/eventBus';
|
||||
import SuccessfulActionModalBody from 'components/SuccessfulActionModalBody';
|
||||
@@ -30,24 +29,16 @@ import styles from './index.module.scss';
|
||||
|
||||
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 +51,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 +95,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={styles.spacedRow}>
|
||||
<CCol md="8">
|
||||
<p className={styles.spacedText}>{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}>
|
||||
<CRow hidden={isNow} className={styles.spacedRow}>
|
||||
<CCol md="4" className={styles.spacedDate}>
|
||||
<p>{t('common.date')}</p>
|
||||
<p>{t('common.custom_date')}</p>
|
||||
</CCol>
|
||||
<CCol xs="12" md="8">
|
||||
<DatePicker
|
||||
@@ -147,63 +170,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}
|
||||
/>
|
||||
|
||||
@@ -13,13 +13,11 @@ 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 { useAuth } from 'contexts/AuthProvider';
|
||||
import { useDevice } from 'contexts/DeviceProvider';
|
||||
import eventBus from 'utils/eventBus';
|
||||
import ConfirmModal from 'components/ConfirmModal';
|
||||
import LoadingButton from 'components/LoadingButton';
|
||||
@@ -27,8 +25,10 @@ import WifiScanResultModalWidget from 'components/WifiScanResultModal';
|
||||
import DeviceCommandsCollapse from './DeviceCommandsCollapse';
|
||||
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);
|
||||
@@ -92,7 +92,7 @@ const DeviceCommands = ({ selectedDeviceId }) => {
|
||||
const options = {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${getToken()}`,
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
},
|
||||
params: {
|
||||
limit: commandLimit,
|
||||
@@ -109,7 +109,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 +129,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 +155,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('');
|
||||
@@ -233,12 +241,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 +257,7 @@ const DeviceCommands = ({ selectedDeviceId }) => {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedDeviceId) {
|
||||
if (deviceSerialNumber) {
|
||||
setCommandLimit(25);
|
||||
setLoadingMore(false);
|
||||
setShowLoadingMore(true);
|
||||
@@ -257,7 +265,7 @@ const DeviceCommands = ({ selectedDeviceId }) => {
|
||||
setEnd('');
|
||||
getCommands();
|
||||
}
|
||||
}, [selectedDeviceId]);
|
||||
}, [deviceSerialNumber]);
|
||||
|
||||
useEffect(() => {
|
||||
if (commandLimit !== 25) {
|
||||
@@ -313,9 +321,7 @@ const DeviceCommands = ({ selectedDeviceId }) => {
|
||||
items={commands ?? []}
|
||||
fields={columns}
|
||||
className={styles.whiteIcon}
|
||||
columnFilter
|
||||
sorter
|
||||
sorterValue={{ column: 'submitted', desc: 'true' }}
|
||||
sorterValue={{ column: 'created', desc: 'true' }}
|
||||
scopedSlots={{
|
||||
completed: (item) => (
|
||||
<td>
|
||||
@@ -363,12 +369,7 @@ const DeviceCommands = ({ selectedDeviceId }) => {
|
||||
{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>
|
||||
@@ -449,13 +450,8 @@ const DeviceCommands = ({ selectedDeviceId }) => {
|
||||
date={chosenWifiScanDate}
|
||||
/>
|
||||
<ConfirmModal show={showConfirmModal} toggle={toggleConfirmModal} action={deleteCommand} />
|
||||
<CIcon name="cilNotes" className={styles.whiteIcon} size="lg" />
|
||||
</CWidgetDropdown>
|
||||
);
|
||||
};
|
||||
|
||||
DeviceCommands.propTypes = {
|
||||
selectedDeviceId: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default DeviceCommands;
|
||||
@@ -7,7 +7,7 @@
|
||||
}
|
||||
|
||||
.scrollableBox {
|
||||
height: 400px;
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
.whiteIcon {
|
||||
@@ -16,9 +16,9 @@ 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 } from 'contexts/AuthProvider';
|
||||
import { useDevice } from 'contexts/DeviceProvider';
|
||||
import { checkIfJson } from 'utils/helper';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
import eventBus from 'utils/eventBus';
|
||||
@@ -27,6 +27,8 @@ 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 +38,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 +71,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 +80,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);
|
||||
})
|
||||
@@ -191,7 +195,7 @@ const ConfigureModal = ({ show, toggleModal }) => {
|
||||
color="primary"
|
||||
onClick={confirmingIfSure}
|
||||
>
|
||||
{t('common.submit')}
|
||||
{t('common.save')}
|
||||
</CButton>
|
||||
<CButton
|
||||
hidden={!checkingIfSure}
|
||||
@@ -199,7 +203,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}>
|
||||
|
||||
37
src/components/CopyToClipboardButton/index.js
Normal file
37
src/components/CopyToClipboardButton/index.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import CIcon from '@coreui/icons-react';
|
||||
import { cilClone } from '@coreui/icons';
|
||||
import PropTypes from 'prop-types';
|
||||
import { CButton, CPopover } from '@coreui/react';
|
||||
|
||||
const CopyToClipboardButton = ({ content, size }) => {
|
||||
const { t } = useTranslation();
|
||||
const [result, setResult] = useState('');
|
||||
|
||||
const copyToClipboard = () => {
|
||||
navigator.clipboard.writeText(content);
|
||||
setResult(t('common.copied'));
|
||||
};
|
||||
|
||||
return (
|
||||
<CPopover content={t('common.copy_to_clipboard')}>
|
||||
<CButton onClick={copyToClipboard} size={size}>
|
||||
<CIcon content={cilClone} />
|
||||
{' '}
|
||||
{result || ''}
|
||||
</CButton>
|
||||
</CPopover>
|
||||
);
|
||||
};
|
||||
|
||||
CopyToClipboardButton.propTypes = {
|
||||
content: PropTypes.string.isRequired,
|
||||
size: PropTypes.string,
|
||||
};
|
||||
|
||||
CopyToClipboardButton.defaultProps = {
|
||||
size: 'sm',
|
||||
};
|
||||
|
||||
export default CopyToClipboardButton;
|
||||
@@ -6,12 +6,15 @@ import PropTypes from 'prop-types';
|
||||
import ConfirmFooter from 'components/ConfirmFooter';
|
||||
import { dateToUnix } from 'utils/helper';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
import { getToken } from 'utils/authHelper';
|
||||
import { useDevice } from 'contexts/DeviceProvider';
|
||||
import { useAuth } from 'contexts/AuthProvider';
|
||||
import eventBus from 'utils/eventBus';
|
||||
import styles from './index.module.scss';
|
||||
|
||||
const DeleteLogModal = ({ serialNumber, show, toggle, object }) => {
|
||||
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());
|
||||
|
||||
@@ -27,14 +30,14 @@ const DeleteLogModal = ({ serialNumber, show, toggle, object }) => {
|
||||
const options = {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${getToken()}`,
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
},
|
||||
params: {
|
||||
endDate: dateToUnix(maxDate),
|
||||
},
|
||||
};
|
||||
return axiosInstance
|
||||
.delete(`/device/${serialNumber}/${object}`, options)
|
||||
.delete(`${endpoints.ucentralgw}/api/v1/device/${deviceSerialNumber}/${object}`, options)
|
||||
.then(() => {})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
@@ -94,7 +97,6 @@ DeleteLogModal.propTypes = {
|
||||
show: PropTypes.bool.isRequired,
|
||||
toggle: PropTypes.func.isRequired,
|
||||
object: PropTypes.string.isRequired,
|
||||
serialNumber: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default DeleteLogModal;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React, { useState } 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 { useAuth } from 'contexts/AuthProvider';
|
||||
import { useDevice } from 'contexts/DeviceProvider';
|
||||
import LoadingButton from 'components/LoadingButton';
|
||||
import RebootModal from 'components/RebootModal';
|
||||
import FirmwareUpgradeModal from 'components/FirmwareUpgradeModal';
|
||||
@@ -12,10 +12,13 @@ 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';
|
||||
|
||||
const DeviceActions = ({ selectedDeviceId }) => {
|
||||
const DeviceActions = () => {
|
||||
const { t } = useTranslation();
|
||||
const { currentToken, endpoints } = useAuth();
|
||||
const { deviceSerialNumber } = useDevice();
|
||||
const [showRebootModal, setShowRebootModal] = useState(false);
|
||||
const [showBlinkModal, setShowBlinkModal] = useState(false);
|
||||
const [showUpgradeModal, setShowUpgradeModal] = useState(false);
|
||||
@@ -58,12 +61,15 @@ const DeviceActions = ({ selectedDeviceId }) => {
|
||||
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');
|
||||
@@ -77,7 +83,9 @@ const DeviceActions = ({ selectedDeviceId }) => {
|
||||
|
||||
return (
|
||||
<CCard>
|
||||
<CCardHeader>{t('actions.title')}</CCardHeader>
|
||||
<CCardHeader>
|
||||
<div className="text-value-lg">{t('actions.title')}</div>
|
||||
</CCardHeader>
|
||||
<CCardBody>
|
||||
<CRow>
|
||||
<CCol>
|
||||
@@ -142,8 +150,4 @@ const DeviceActions = ({ selectedDeviceId }) => {
|
||||
);
|
||||
};
|
||||
|
||||
DeviceActions.propTypes = {
|
||||
selectedDeviceId: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default DeviceActions;
|
||||
@@ -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,23 @@ 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 { useAuth } from 'contexts/AuthProvider';
|
||||
import { useDevice } from 'contexts/DeviceProvider';
|
||||
import CopyToClipboardButton from 'components/CopyToClipboardButton';
|
||||
import DeviceNotes from 'components/DeviceNotes';
|
||||
import DeviceConfigurationModal from './DeviceConfigurationModal';
|
||||
import styles from './index.module.scss';
|
||||
|
||||
const DeviceConfiguration = ({ selectedDeviceId }) => {
|
||||
const DeviceConfiguration = () => {
|
||||
const { t } = useTranslation();
|
||||
const { currentToken, endpoints } = useAuth();
|
||||
const { deviceSerialNumber } = useDevice();
|
||||
const [collapse, setCollapse] = useState(false);
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [device, setDevice] = useState(null);
|
||||
const [copyPasswordSuccess, setCopyPasswordSuccess] = useState('');
|
||||
|
||||
const toggle = (e) => {
|
||||
setCollapse(!collapse);
|
||||
@@ -40,22 +40,19 @@ 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);
|
||||
})
|
||||
@@ -63,9 +60,8 @@ const DeviceConfiguration = ({ selectedDeviceId }) => {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedDeviceId) getDevice();
|
||||
setCopyPasswordSuccess(null);
|
||||
}, [selectedDeviceId]);
|
||||
if (deviceSerialNumber) getDevice();
|
||||
}, [deviceSerialNumber]);
|
||||
|
||||
if (device) {
|
||||
return (
|
||||
@@ -73,7 +69,9 @@ const DeviceConfiguration = ({ selectedDeviceId }) => {
|
||||
<CCard>
|
||||
<CCardHeader>
|
||||
<CRow>
|
||||
<CCol>{t('configuration.details')}</CCol>
|
||||
<CCol>
|
||||
<div className="text-value-lg">{t('configuration.title')}</div>
|
||||
</CCol>
|
||||
<CCol>
|
||||
<div className={styles.alignRight}>
|
||||
<CPopover content={t('configuration.view_json')}>
|
||||
@@ -86,126 +84,115 @@ const DeviceConfiguration = ({ selectedDeviceId }) => {
|
||||
</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={styles.spacedRow}>
|
||||
<CCol md="3">
|
||||
<CLabel>{t('common.uuid')} : </CLabel>
|
||||
</CCol>
|
||||
<CCol xs="12" md="9">
|
||||
{device.UUID}
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CCol md="3">
|
||||
<CLabel>{t('common.serial_number')} : </CLabel>
|
||||
</CCol>
|
||||
<CCol xs="12" md="9">
|
||||
{device.serialNumber}
|
||||
<CopyToClipboardButton size="sm" content={device.serialNumber} />
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CCol md="3">
|
||||
<CLabel>{t('configuration.type')} : </CLabel>
|
||||
</CCol>
|
||||
<CCol xs="12" md="9">
|
||||
{device.deviceType}
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CCol md="3">
|
||||
<CLabel>{t('configuration.last_configuration_change')} : </CLabel>
|
||||
</CCol>
|
||||
<CCol xs="12" md="9">
|
||||
{prettyDate(device.lastConfigurationChange)}
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CCol md="3">
|
||||
<CLabel>{t('common.mac')} :</CLabel>
|
||||
</CCol>
|
||||
<CCol xs="12" md="9">
|
||||
{device.macAddress}
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CCol md="3">
|
||||
<CLabel>{t('configuration.created')} : </CLabel>
|
||||
</CCol>
|
||||
<CCol xs="12" md="9">
|
||||
{prettyDate(device.createdTimestamp)}
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CCol md="3" className={styles.topPadding}>
|
||||
<CLabel>{t('configuration.device_password')} : </CLabel>
|
||||
</CCol>
|
||||
<CCol xs="12" md="9">
|
||||
{device.devicePassword === '' ? 'openwifi' : device.devicePassword}
|
||||
<CopyToClipboardButton
|
||||
size="sm"
|
||||
content={device?.devicePassword === '' ? 'openwifi' : device.devicePassword}
|
||||
/>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<DeviceNotes
|
||||
notes={device.notes}
|
||||
refreshNotes={getDevice}
|
||||
serialNumber={deviceSerialNumber}
|
||||
/>
|
||||
<CCollapse show={collapse}>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<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={styles.spacedRow}>
|
||||
<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={styles.spacedRow}>
|
||||
<CCol md="3">
|
||||
<CLabel>{t('configuration.owner')} :</CLabel>
|
||||
</CCol>
|
||||
<CCol xs="12" md="9">
|
||||
{device.owner}
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<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={styles.blackIcon}
|
||||
name={collapse ? 'cilChevronTop' : 'cilChevronBottom'}
|
||||
size="lg"
|
||||
/>
|
||||
</CButton>
|
||||
</CCardFooter>
|
||||
</CCardBody>
|
||||
</CCard>
|
||||
<DeviceConfigurationModal show={showModal} toggle={toggleModal} configuration={device} />
|
||||
@@ -221,8 +208,4 @@ const DeviceConfiguration = ({ selectedDeviceId }) => {
|
||||
);
|
||||
};
|
||||
|
||||
DeviceConfiguration.propTypes = {
|
||||
selectedDeviceId: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default DeviceConfiguration;
|
||||
|
||||
@@ -5,3 +5,16 @@
|
||||
.blackIcon {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.modalTitle {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.topPadding {
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
.spacedRow {
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
@@ -15,17 +15,19 @@ import {
|
||||
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 { useAuth } from 'contexts/AuthProvider';
|
||||
import { useDevice } from 'contexts/DeviceProvider';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
import { getToken } from 'utils/authHelper';
|
||||
import eventBus from 'utils/eventBus';
|
||||
import LoadingButton from 'components/LoadingButton';
|
||||
import DeleteLogModal from 'components/DeleteLogModal';
|
||||
import styles from './index.module.scss';
|
||||
|
||||
const DeviceHealth = ({ selectedDeviceId }) => {
|
||||
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);
|
||||
@@ -68,7 +70,7 @@ const DeviceHealth = ({ selectedDeviceId }) => {
|
||||
const options = {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${getToken()}`,
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
},
|
||||
params: {
|
||||
limit: logLimit,
|
||||
@@ -85,7 +87,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);
|
||||
})
|
||||
@@ -128,7 +135,7 @@ const DeviceHealth = ({ selectedDeviceId }) => {
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedDeviceId) {
|
||||
if (deviceSerialNumber) {
|
||||
setLogLimit(25);
|
||||
setLoadingMore(false);
|
||||
setShowLoadingMore(true);
|
||||
@@ -136,7 +143,7 @@ const DeviceHealth = ({ selectedDeviceId }) => {
|
||||
setEnd('');
|
||||
getDeviceHealth();
|
||||
}
|
||||
}, [selectedDeviceId]);
|
||||
}, [deviceSerialNumber]);
|
||||
|
||||
useEffect(() => {
|
||||
if (logLimit !== 25) {
|
||||
@@ -168,12 +175,12 @@ 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());
|
||||
@@ -185,8 +192,8 @@ const DeviceHealth = ({ selectedDeviceId }) => {
|
||||
|
||||
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"
|
||||
@@ -210,11 +217,13 @@ const DeviceHealth = ({ selectedDeviceId }) => {
|
||||
</div>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<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>
|
||||
@@ -281,7 +290,7 @@ const DeviceHealth = ({ selectedDeviceId }) => {
|
||||
/>
|
||||
</CButton>
|
||||
<DeleteLogModal
|
||||
serialNumber={selectedDeviceId}
|
||||
serialNumber={deviceSerialNumber}
|
||||
object="healthchecks"
|
||||
show={showDeleteModal}
|
||||
toggle={toggleDeleteModal}
|
||||
@@ -292,8 +301,4 @@ const DeviceHealth = ({ selectedDeviceId }) => {
|
||||
);
|
||||
};
|
||||
|
||||
DeviceHealth.propTypes = {
|
||||
selectedDeviceId: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default DeviceHealth;
|
||||
|
||||
@@ -10,16 +10,16 @@ import {
|
||||
CRow,
|
||||
CCol,
|
||||
CPopover,
|
||||
CSelect,
|
||||
} 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 { useAuth } from 'contexts/AuthProvider';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
import { cleanBytesString, cropStringWithEllipsis } from 'utils/helper';
|
||||
import { cleanBytesString } from 'utils/helper';
|
||||
import meshIcon from 'assets/icons/Mesh.png';
|
||||
import apIcon from 'assets/icons/AP.png';
|
||||
import internetSwitch from 'assets/icons/Switch.png';
|
||||
@@ -29,6 +29,7 @@ import styles from './index.module.scss';
|
||||
|
||||
const DeviceList = () => {
|
||||
const { t } = useTranslation();
|
||||
const { currentToken, endpoints } = useAuth();
|
||||
const [loadedSerials, setLoadedSerials] = useState(false);
|
||||
const [serialNumbers, setSerialNumbers] = useState([]);
|
||||
const [page, setPage] = useState(0);
|
||||
@@ -38,16 +39,15 @@ const DeviceList = () => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const getSerialNumbers = () => {
|
||||
const token = getToken();
|
||||
setLoading(true);
|
||||
|
||||
const headers = {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${token}`,
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
};
|
||||
|
||||
axiosInstance
|
||||
.get('/devices?serialOnly=true', {
|
||||
.get(`${endpoints.ucentralgw}/api/v1/devices?serialOnly=true`, {
|
||||
headers,
|
||||
})
|
||||
.then((response) => {
|
||||
@@ -60,12 +60,11 @@ const DeviceList = () => {
|
||||
};
|
||||
|
||||
const getDeviceInformation = () => {
|
||||
const token = getToken();
|
||||
setLoading(true);
|
||||
|
||||
const headers = {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${token}`,
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
};
|
||||
|
||||
const startIndex = page * devicesPerPage;
|
||||
@@ -76,7 +75,7 @@ const DeviceList = () => {
|
||||
.join(',');
|
||||
|
||||
axiosInstance
|
||||
.get(`/devices?deviceWithStatus=true&select=${serialsToGet}`, {
|
||||
.get(`${endpoints.ucentralgw}/api/v1/devices?deviceWithStatus=true&select=${serialsToGet}`, {
|
||||
headers,
|
||||
})
|
||||
.then((response) => {
|
||||
@@ -89,18 +88,22 @@ const DeviceList = () => {
|
||||
};
|
||||
|
||||
const refreshDevice = (serialNumber) => {
|
||||
const token = getToken();
|
||||
setLoading(true);
|
||||
|
||||
const headers = {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${token}`,
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
};
|
||||
|
||||
axiosInstance
|
||||
.get(`/devices?deviceWithStatus=true&select=${encodeURIComponent(serialNumber)}`, {
|
||||
headers,
|
||||
})
|
||||
.get(
|
||||
`${endpoints.ucentralgw}/api/v1/devices?deviceWithStatus=true&select=${encodeURIComponent(
|
||||
serialNumber,
|
||||
)}`,
|
||||
{
|
||||
headers,
|
||||
},
|
||||
)
|
||||
.then((response) => {
|
||||
const device = response.data.devicesWithStatus[0];
|
||||
const foundIndex = devices.findIndex((obj) => obj.serialNumber === serialNumber);
|
||||
@@ -168,7 +171,7 @@ const DeviceListDisplay = ({
|
||||
{ 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: 'firmware', label: t('common.firmware'), filter: false },
|
||||
{ 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%' } },
|
||||
@@ -189,12 +192,6 @@ const DeviceListDisplay = ({
|
||||
},
|
||||
];
|
||||
|
||||
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" />;
|
||||
@@ -281,13 +278,17 @@ const DeviceListDisplay = ({
|
||||
<CCardHeader>
|
||||
<CRow>
|
||||
<CCol />
|
||||
<CCol xs={2}>
|
||||
<Select
|
||||
isClearable={false}
|
||||
options={selectOptions}
|
||||
defaultValue={{ value: devicesPerPage, label: devicesPerPage }}
|
||||
onChange={(value) => updateDevicesPerPage(value.value)}
|
||||
/>
|
||||
<CCol xs={1}>
|
||||
<CSelect
|
||||
custom
|
||||
defaultValue={devicesPerPage}
|
||||
onChange={(e) => updateDevicesPerPage(e.target.value)}
|
||||
disabled={loading}
|
||||
>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
</CSelect>
|
||||
</CCol>
|
||||
</CRow>
|
||||
</CCardHeader>
|
||||
@@ -296,6 +297,7 @@ const DeviceListDisplay = ({
|
||||
items={devices ?? []}
|
||||
fields={columns}
|
||||
hover
|
||||
border
|
||||
loading={loading}
|
||||
scopedSlots={{
|
||||
serialNumber: (item) => (
|
||||
@@ -337,7 +339,9 @@ const DeviceListDisplay = ({
|
||||
content={item.firmware ? item.firmware : t('common.na')}
|
||||
placement="top"
|
||||
>
|
||||
<p>{cropStringWithEllipsis(item.firmware, 16)}</p>
|
||||
<p style={{ width: '225px' }} className="text-truncate">
|
||||
{item.firmware}
|
||||
</p>
|
||||
</CPopover>
|
||||
</td>
|
||||
),
|
||||
@@ -347,7 +351,9 @@ const DeviceListDisplay = ({
|
||||
content={item.compatible ? item.compatible : t('common.na')}
|
||||
placement="top"
|
||||
>
|
||||
<p>{cropStringWithEllipsis(item.compatible, 16)}</p>
|
||||
<p style={{ width: '150px' }} className="text-truncate">
|
||||
{item.compatible}
|
||||
</p>
|
||||
</CPopover>
|
||||
</td>
|
||||
),
|
||||
@@ -359,7 +365,9 @@ const DeviceListDisplay = ({
|
||||
content={item.ipAddress ? item.ipAddress : t('common.na')}
|
||||
placement="top"
|
||||
>
|
||||
<p>{cropStringWithEllipsis(item.ipAddress, 20)}</p>
|
||||
<p style={{ width: '150px' }} className="text-truncate">
|
||||
{item.ipAddress}
|
||||
</p>
|
||||
</CPopover>
|
||||
</td>
|
||||
),
|
||||
|
||||
@@ -14,17 +14,19 @@ import {
|
||||
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 { useAuth } from 'contexts/AuthProvider';
|
||||
import { useDevice } from 'contexts/DeviceProvider';
|
||||
import eventBus from 'utils/eventBus';
|
||||
import LoadingButton from 'components/LoadingButton';
|
||||
import DeleteLogModal from 'components/DeleteLogModal';
|
||||
import styles from './index.module.scss';
|
||||
|
||||
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);
|
||||
@@ -65,7 +67,7 @@ const DeviceLogs = ({ selectedDeviceId }) => {
|
||||
const options = {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${getToken()}`,
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
},
|
||||
params: {
|
||||
limit: logLimit,
|
||||
@@ -82,7 +84,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);
|
||||
})
|
||||
@@ -125,7 +132,7 @@ const DeviceLogs = ({ selectedDeviceId }) => {
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedDeviceId) {
|
||||
if (deviceSerialNumber) {
|
||||
setLogLimit(25);
|
||||
setLoadingMore(false);
|
||||
setShowLoadingMore(true);
|
||||
@@ -133,7 +140,7 @@ const DeviceLogs = ({ selectedDeviceId }) => {
|
||||
setEnd('');
|
||||
getLogs();
|
||||
}
|
||||
}, [selectedDeviceId]);
|
||||
}, [deviceSerialNumber]);
|
||||
|
||||
useEffect(() => {
|
||||
if (logLimit !== 25) {
|
||||
@@ -150,12 +157,12 @@ 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());
|
||||
@@ -256,11 +263,9 @@ const DeviceLogs = ({ selectedDeviceId }) => {
|
||||
</CButton>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<CIcon name="cilList" className={styles.whiteIcon} size="lg" />
|
||||
</CWidgetDropdown>
|
||||
/>
|
||||
<DeleteLogModal
|
||||
serialNumber={selectedDeviceId}
|
||||
serialNumber={deviceSerialNumber}
|
||||
object="logs"
|
||||
show={showDeleteModal}
|
||||
toggle={toggleDeleteModal}
|
||||
@@ -269,8 +274,4 @@ const DeviceLogs = ({ selectedDeviceId }) => {
|
||||
);
|
||||
};
|
||||
|
||||
DeviceLogs.propTypes = {
|
||||
selectedDeviceId: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default DeviceLogs;
|
||||
|
||||
111
src/components/DeviceNotes/index.js
Normal file
111
src/components/DeviceNotes/index.js
Normal file
@@ -0,0 +1,111 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { CDataTable, CRow, CCol, CLabel, CInput } from '@coreui/react';
|
||||
import PropTypes from 'prop-types';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
import { useAuth } from 'contexts/AuthProvider';
|
||||
import { prettyDate } from 'utils/helper';
|
||||
import LoadingButton from 'components/LoadingButton';
|
||||
|
||||
import styles from './index.module.scss';
|
||||
|
||||
const DeviceNotes = ({ serialNumber, notes, refreshNotes }) => {
|
||||
const { t } = useTranslation();
|
||||
const { currentToken, endpoints } = useAuth();
|
||||
const [currentNote, setCurrentNote] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const saveNote = () => {
|
||||
setLoading(true);
|
||||
|
||||
const parameters = {
|
||||
serialNumber,
|
||||
notes: [{ note: currentNote }],
|
||||
};
|
||||
|
||||
const headers = {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
};
|
||||
|
||||
axiosInstance
|
||||
.put(
|
||||
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(serialNumber)}`,
|
||||
parameters,
|
||||
{ headers },
|
||||
)
|
||||
.then(() => {
|
||||
setCurrentNote('');
|
||||
refreshNotes();
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
const columns = [
|
||||
{ key: 'created', label: t('common.date'), _style: { width: '30%' } },
|
||||
{ key: 'createdBy', label: t('common.created_by'), _style: { width: '20%' } },
|
||||
{ key: 'note', label: t('configuration.note'), _style: { width: '50%' } },
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CCol md="3">
|
||||
<CLabel>{t('configuration.notes')} :</CLabel>
|
||||
</CCol>
|
||||
<CCol xs="9" md="7">
|
||||
<CInput
|
||||
id="notes-input"
|
||||
name="text-input"
|
||||
value={currentNote}
|
||||
onChange={(e) => setCurrentNote(e.target.value)}
|
||||
/>
|
||||
</CCol>
|
||||
<CCol>
|
||||
<LoadingButton
|
||||
label={t('common.add')}
|
||||
isLoadingLabel={t('common.adding_ellipsis')}
|
||||
isLoading={loading}
|
||||
action={saveNote}
|
||||
disabled={loading || currentNote === ''}
|
||||
/>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow>
|
||||
<CCol md="3" />
|
||||
<CCol xs="12" md="9">
|
||||
<div className={['overflow-auto', styles.scrollableBox].join(' ')}>
|
||||
<CDataTable
|
||||
striped
|
||||
responsive
|
||||
border
|
||||
loading={loading}
|
||||
fields={columns}
|
||||
className={styles.table}
|
||||
items={notes || []}
|
||||
noItemsView={{ noItems: t('common.no_items') }}
|
||||
sorterValue={{ column: 'created', desc: 'true' }}
|
||||
scopedSlots={{
|
||||
created: (item) => (
|
||||
<td>
|
||||
{item.created && item.created !== 0 ? prettyDate(item.created) : t('common.na')}
|
||||
</td>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</CCol>
|
||||
</CRow>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
DeviceNotes.propTypes = {
|
||||
serialNumber: PropTypes.string.isRequired,
|
||||
notes: PropTypes.arrayOf(PropTypes.instanceOf(Object)).isRequired,
|
||||
refreshNotes: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default DeviceNotes;
|
||||
15
src/components/DeviceNotes/index.module.scss
Normal file
15
src/components/DeviceNotes/index.module.scss
Normal file
@@ -0,0 +1,15 @@
|
||||
.scrollableBox {
|
||||
height: 200px;
|
||||
border-style: solid;
|
||||
border-color: #ced2d8;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.table {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.spacedRow {
|
||||
margin-top: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
36
src/components/DeviceStatusCard/MemoryBar.js
Normal file
36
src/components/DeviceStatusCard/MemoryBar.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import React from 'react';
|
||||
import { CPopover, CProgress, CProgressBar } from '@coreui/react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { cleanBytesString } from 'utils/helper';
|
||||
|
||||
const MemoryBar = ({ usedBytes, totalBytes }) => {
|
||||
const { t } = useTranslation();
|
||||
const used = cleanBytesString(usedBytes);
|
||||
const total = cleanBytesString(totalBytes);
|
||||
const percentage = Math.floor((usedBytes / totalBytes) * 100);
|
||||
|
||||
return (
|
||||
<CPopover content={t('status.used_total_memory', { used, total })}>
|
||||
<CProgress>
|
||||
<CProgressBar value={percentage}>
|
||||
{percentage >= 25 ? t('status.percentage_used', { percentage, total }) : ''}
|
||||
</CProgressBar>
|
||||
<CProgressBar value={100 - percentage} color="transparent">
|
||||
<div style={{ color: 'black' }}>
|
||||
{percentage < 25
|
||||
? t('status.percentage_free', { percentage: 100 - percentage, total })
|
||||
: ''}
|
||||
</div>
|
||||
</CProgressBar>
|
||||
</CProgress>
|
||||
</CPopover>
|
||||
);
|
||||
};
|
||||
|
||||
MemoryBar.propTypes = {
|
||||
usedBytes: PropTypes.number.isRequired,
|
||||
totalBytes: PropTypes.number.isRequired,
|
||||
};
|
||||
|
||||
export default React.memo(MemoryBar);
|
||||
197
src/components/DeviceStatusCard/index.js
Normal file
197
src/components/DeviceStatusCard/index.js
Normal file
@@ -0,0 +1,197 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
CCard,
|
||||
CCardHeader,
|
||||
CRow,
|
||||
CCol,
|
||||
CCardBody,
|
||||
CBadge,
|
||||
CModalBody,
|
||||
CAlert,
|
||||
CPopover,
|
||||
CButton,
|
||||
CSpinner,
|
||||
} from '@coreui/react';
|
||||
import CIcon from '@coreui/icons-react';
|
||||
import { useAuth } from 'contexts/AuthProvider';
|
||||
import { useDevice } from 'contexts/DeviceProvider';
|
||||
import { cilSync } from '@coreui/icons';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
import { prettyDate, secondsToDetailed } from 'utils/helper';
|
||||
import MemoryBar from './MemoryBar';
|
||||
|
||||
import styles from './index.module.scss';
|
||||
|
||||
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 transformLoad = (load) => {
|
||||
if (load === undefined) return t('common.na');
|
||||
return `${((load / 65536) * 100).toFixed(2)}%`;
|
||||
};
|
||||
|
||||
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]);
|
||||
|
||||
if (!error) {
|
||||
return (
|
||||
<CCard>
|
||||
<CCardHeader>
|
||||
<CRow>
|
||||
<CCol>
|
||||
<div className="text-value-lg">
|
||||
{t('status.title', { serialNumber: deviceSerialNumber })}
|
||||
</div>
|
||||
</CCol>
|
||||
<CCol>
|
||||
<div className={styles.alignRight}>
|
||||
<CPopover content={t('common.refresh')}>
|
||||
<CButton color="secondary" onClick={getData} size="sm">
|
||||
<CIcon content={cilSync} />
|
||||
</CButton>
|
||||
</CPopover>
|
||||
</div>
|
||||
</CCol>
|
||||
</CRow>
|
||||
</CCardHeader>
|
||||
<CCardBody>
|
||||
{(!lastStats || !status) && loading ? (
|
||||
<div className={styles.centerContainer}>
|
||||
<CSpinner className={styles.spinner} />
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ position: 'relative' }}>
|
||||
<div className={styles.overlayContainer} hidden={!loading}>
|
||||
<CSpinner className={styles.spinner} />
|
||||
</div>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CCol md="5">{t('status.connection_status')} :</CCol>
|
||||
<CCol xs="10" md="7">
|
||||
{status?.connected ? (
|
||||
<CBadge color="success">{t('common.connected')}</CBadge>
|
||||
) : (
|
||||
<CBadge color="danger">{t('common.not_connected')}</CBadge>
|
||||
)}
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CCol md="5">{t('status.uptime')} :</CCol>
|
||||
<CCol xs="10" md="7">
|
||||
{secondsToDetailed(
|
||||
lastStats?.unit?.uptime,
|
||||
t('common.day'),
|
||||
t('common.days'),
|
||||
t('common.hour'),
|
||||
t('common.hours'),
|
||||
t('common.minute'),
|
||||
t('common.minutes'),
|
||||
t('common.second'),
|
||||
t('common.seconds'),
|
||||
)}
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CCol md="5">{t('status.last_contact')} :</CCol>
|
||||
<CCol xs="10" md="7">
|
||||
{prettyDate(status?.lastContact)}
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CCol md="5">{t('status.localtime')} :</CCol>
|
||||
<CCol xs="10" md="7">
|
||||
{prettyDate(lastStats?.unit?.localtime)}
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CCol md="5">{t('status.load_averages')} :</CCol>
|
||||
<CCol xs="10" md="7">
|
||||
{transformLoad(lastStats?.unit?.load[0])}
|
||||
{' / '}
|
||||
{transformLoad(lastStats?.unit?.load[1])}
|
||||
{' / '}
|
||||
{transformLoad(lastStats?.unit?.load[2])}
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CCol md="5">{t('status.memory')} :</CCol>
|
||||
<CCol xs="9" md="6" style={{ paddingTop: '5px' }}>
|
||||
<MemoryBar
|
||||
usedBytes={
|
||||
lastStats?.unit?.memory?.total && lastStats?.unit?.memory?.free
|
||||
? lastStats?.unit?.memory?.total - lastStats?.unit?.memory?.free
|
||||
: 0
|
||||
}
|
||||
totalBytes={lastStats?.unit?.memory?.total ?? 0}
|
||||
/>
|
||||
</CCol>
|
||||
</CRow>
|
||||
</div>
|
||||
)}
|
||||
</CCardBody>
|
||||
</CCard>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<CCard>
|
||||
<CCardHeader>
|
||||
<CRow>
|
||||
<CCol>
|
||||
<div className="text-value-lg">
|
||||
{t('status.title', { serialNumber: deviceSerialNumber })}
|
||||
</div>
|
||||
</CCol>
|
||||
</CRow>
|
||||
</CCardHeader>
|
||||
<CModalBody>
|
||||
<CAlert hidden={!error} color="danger" className={styles.centerContainer}>
|
||||
{t('status.error')}
|
||||
</CAlert>
|
||||
</CModalBody>
|
||||
</CCard>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(DeviceStatusCard);
|
||||
29
src/components/DeviceStatusCard/index.module.scss
Normal file
29
src/components/DeviceStatusCard/index.module.scss
Normal file
@@ -0,0 +1,29 @@
|
||||
.centerContainer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.overlayContainer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.spacedRow {
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.alignRight {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
height: 50px;
|
||||
width: 50px;
|
||||
}
|
||||
@@ -14,15 +14,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 } from 'contexts/AuthProvider';
|
||||
import { useDevice } from 'contexts/DeviceProvider';
|
||||
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 +32,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 +55,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);
|
||||
})
|
||||
@@ -116,7 +121,7 @@ const ConfigureModal = ({ show, toggleModal }) => {
|
||||
color="primary"
|
||||
onClick={() => confirmingIfSure()}
|
||||
>
|
||||
{t('common.submit')}
|
||||
{t('factory_reset.reset')}
|
||||
</CButton>
|
||||
<CButton
|
||||
hidden={!checkingIfSure}
|
||||
@@ -124,7 +129,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}>
|
||||
|
||||
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 'contexts/AuthProvider';
|
||||
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,58 @@ 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 { useAuth } from 'contexts/AuthProvider';
|
||||
import { useDevice } from 'contexts/DeviceProvider';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
import eventBus from 'utils/eventBus';
|
||||
import getDeviceConnection from 'utils/deviceHelper';
|
||||
import ButtonFooter from './UpgradeFooter';
|
||||
import styles from './index.module.scss';
|
||||
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 } = 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 +64,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>
|
||||
@@ -142,93 +157,84 @@ const FirmwareUpgradeModal = ({ show, toggleModal }) => {
|
||||
<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>
|
||||
<CCol md="4" className={styles.spacedColumn}>
|
||||
<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="8">
|
||||
<p className={styles.spacedText}>{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={styles.spacedRow} hidden={isNow}>
|
||||
<CCol md="4" className={styles.spacedColumn}>
|
||||
<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={styles.spacedRow}
|
||||
hidden={true || !isNow || disabledWaiting || !deviceConnected}
|
||||
>
|
||||
<CCol md="8">
|
||||
<p className={styles.spacedText}>
|
||||
{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>
|
||||
);
|
||||
};
|
||||
|
||||
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;
|
||||
70
src/components/InterfaceStatistics/LatestStatisticsModal.js
Normal file
70
src/components/InterfaceStatistics/LatestStatisticsModal.js
Normal file
@@ -0,0 +1,70 @@
|
||||
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 } from 'contexts/AuthProvider';
|
||||
import { useDevice } from 'contexts/DeviceProvider';
|
||||
import styles from './index.module.scss';
|
||||
|
||||
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={styles.modalTitle}>{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,15 +1,17 @@
|
||||
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 } from 'contexts/AuthProvider';
|
||||
import { useDevice } from 'contexts/DeviceProvider';
|
||||
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 { currentToken, endpoints } = useAuth();
|
||||
const { deviceSerialNumber } = useDevice();
|
||||
const [statOptions, setStatOptions] = useState({
|
||||
interfaceList: [],
|
||||
settings: {},
|
||||
@@ -60,10 +62,10 @@ 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(
|
||||
Math.floor(inter.counters.rx_bytes / 1024),
|
||||
inter.counters?.rx_bytes ? Math.floor(inter.counters.rx_bytes / 1024) : 0,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -73,6 +75,9 @@ const StatisticsChartList = ({ selectedDeviceId, lastRefresh }) => {
|
||||
id: 'chart',
|
||||
group: 'txrx',
|
||||
},
|
||||
stroke: {
|
||||
curve: 'smooth',
|
||||
},
|
||||
xaxis: {
|
||||
title: {
|
||||
text: 'Time',
|
||||
@@ -112,74 +117,65 @@ const StatisticsChartList = ({ selectedDeviceId, lastRefresh }) => {
|
||||
};
|
||||
|
||||
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 (!loading && lastRefresh !== '' && selectedDeviceId) {
|
||||
getStatistics();
|
||||
}
|
||||
}, [lastRefresh]);
|
||||
eventBus.on('refreshInterfaceStatistics', () => getStatistics());
|
||||
|
||||
return () => {
|
||||
eventBus.remove('refreshInterfaceStatistics');
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{statOptions.interfaceList.map((data) => (
|
||||
<div key={createUuid()}>
|
||||
<DeviceStatisticsChart
|
||||
key={createUuid()}
|
||||
data={data}
|
||||
options={{
|
||||
...statOptions.settings,
|
||||
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,21 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Chart from 'react-apexcharts';
|
||||
|
||||
const DeviceStatisticsChart = ({ data, options }) => (
|
||||
<div style={{ height: '360px' }}>
|
||||
<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,47 +1,67 @@
|
||||
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 { cilSync } from '@coreui/icons';
|
||||
import {
|
||||
CDropdown,
|
||||
CDropdownToggle,
|
||||
CDropdownMenu,
|
||||
CDropdownItem,
|
||||
CCard,
|
||||
CCardHeader,
|
||||
CCardBody,
|
||||
CRow,
|
||||
CCol,
|
||||
} from '@coreui/react';
|
||||
import { cilOptions } from '@coreui/icons';
|
||||
import CIcon from '@coreui/icons-react';
|
||||
import StatisticsChartList from './containers/StatisticsChartList';
|
||||
import eventBus from 'utils/eventBus';
|
||||
import StatisticsChartList from './StatisticsChartList';
|
||||
import LatestStatisticsModal from './LatestStatisticsModal';
|
||||
import styles from './index.module.scss';
|
||||
|
||||
const DeviceStatisticsCard = ({ selectedDeviceId }) => {
|
||||
const DeviceStatisticsCard = () => {
|
||||
const { t } = useTranslation();
|
||||
const [lastRefresh, setLastRefresh] = useState(new Date().toString());
|
||||
const [showLatestModal, setShowLatestModal] = useState(false);
|
||||
|
||||
const toggleLatestModal = () => {
|
||||
setShowLatestModal(!showLatestModal);
|
||||
};
|
||||
|
||||
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-lg', styles.cardTitle].join(' ')}>
|
||||
{t('statistics.title')}
|
||||
</div>
|
||||
</CCol>
|
||||
<CCol className={styles.cardOptions}>
|
||||
<CDropdown className="m-1 btn-group">
|
||||
<CDropdownToggle>
|
||||
<CIcon name="cil-options" content={cilOptions} size="lg" color="primary" />
|
||||
</CDropdownToggle>
|
||||
<CDropdownMenu>
|
||||
<CDropdownItem onClick={refresh}>{t('common.refresh')}</CDropdownItem>
|
||||
<CDropdownItem onClick={toggleLatestModal}>
|
||||
{t('statistics.show_latest')}
|
||||
</CDropdownItem>
|
||||
</CDropdownMenu>
|
||||
</CDropdown>
|
||||
</CCol>
|
||||
</CRow>
|
||||
</CCardHeader>
|
||||
<CCardBody className={styles.statsBody}>
|
||||
<StatisticsChartList />
|
||||
</CCardBody>
|
||||
</CCard>
|
||||
<LatestStatisticsModal show={showLatestModal} toggle={toggleLatestModal} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
DeviceStatisticsCard.propTypes = {
|
||||
selectedDeviceId: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default DeviceStatisticsCard;
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
.alignRight {
|
||||
.cardOptions {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.cardTitle {
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.statsBody {
|
||||
padding: 5%;
|
||||
}
|
||||
|
||||
.modalTitle {
|
||||
color: black;
|
||||
}
|
||||
|
||||
@@ -5,19 +5,18 @@ 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 { useAuth } from 'contexts/AuthProvider';
|
||||
import { useDevice } from 'contexts/DeviceProvider';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
import eventBus from 'utils/eventBus';
|
||||
import LoadingButton from 'components/LoadingButton';
|
||||
@@ -26,24 +25,15 @@ 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 +44,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 +88,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={styles.spacedRow}>
|
||||
<CCol md="4" className={styles.spacedDate}>
|
||||
<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}
|
||||
/>
|
||||
|
||||
@@ -5,3 +5,7 @@
|
||||
.spacedColumn {
|
||||
margin-top: 7px;
|
||||
}
|
||||
|
||||
.spacedDate {
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
113
src/components/TraceModal/WaitingForTraceBody.js
Normal file
113
src/components/TraceModal/WaitingForTraceBody.js
Normal file
@@ -0,0 +1,113 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import PropTypes from 'prop-types';
|
||||
import { CModalBody, CButton, CSpinner, CModalFooter } from '@coreui/react';
|
||||
import { useAuth } from 'contexts/AuthProvider';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
|
||||
import styles from './index.module.scss';
|
||||
|
||||
const WaitingForTraceBody = ({ serialNumber, commandUuid, toggle }) => {
|
||||
const { t } = useTranslation();
|
||||
const { currentToken, endpoints } = useAuth();
|
||||
const [secondsElapsed, setSecondsElapsed] = useState(0);
|
||||
const [waitingForFile, setWaitingForFile] = useState(true);
|
||||
|
||||
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);
|
||||
}
|
||||
})
|
||||
.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={styles.centerDiv}>
|
||||
<CSpinner hidden={!waitingForFile} />
|
||||
<CButton
|
||||
hidden={waitingForFile}
|
||||
onClick={downloadTrace}
|
||||
disabled={waitingForFile}
|
||||
color="primary"
|
||||
>
|
||||
{t('trace.download_trace')}
|
||||
</CButton>
|
||||
</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;
|
||||
@@ -7,8 +7,8 @@ import {
|
||||
CModalFooter,
|
||||
CCol,
|
||||
CRow,
|
||||
CInvalidFeedback,
|
||||
CSelect,
|
||||
CSwitch,
|
||||
CForm,
|
||||
CInputRadio,
|
||||
CFormGroup,
|
||||
@@ -16,67 +16,58 @@ import {
|
||||
} 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, dateToUnix } from 'utils/helper';
|
||||
import 'react-widgets/styles.css';
|
||||
import { getToken } from 'utils/authHelper';
|
||||
import { useAuth } from 'contexts/AuthProvider';
|
||||
import { useDevice } from 'contexts/DeviceProvider';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
import eventBus from 'utils/eventBus';
|
||||
import getDeviceConnection from 'utils/deviceHelper';
|
||||
import LoadingButton from 'components/LoadingButton';
|
||||
import SuccessfulActionModalBody from 'components/SuccessfulActionModalBody';
|
||||
import WaitingForTraceBody from './WaitingForTraceBody';
|
||||
import styles from './index.module.scss';
|
||||
|
||||
const TraceModal = ({ show, toggleModal }) => {
|
||||
const { t } = useTranslation();
|
||||
const { currentToken, endpoints } = useAuth();
|
||||
const { deviceSerialNumber } = useDevice();
|
||||
const [hadSuccess, setHadSuccess] = useState(false);
|
||||
const [hadFailure, setHadFailure] = useState(false);
|
||||
const [waiting, setWaiting] = useState(false);
|
||||
const [blockFields, setBlockFields] = useState(false);
|
||||
const [usingDuration, setUsingDuration] = useState(true);
|
||||
const [duration, setDuration] = useState(20);
|
||||
const [packets, setPackets] = useState(100);
|
||||
const [chosenDate, setChosenDate] = useState(new Date().toString());
|
||||
const [responseBody, setResponseBody] = useState('');
|
||||
const [chosenInterface, setChosenInterface] = useState('up');
|
||||
const selectedDeviceId = useSelector((state) => state.selectedDeviceId);
|
||||
const [isDeviceConnected, setIsDeviceConnected] = useState(false);
|
||||
const [waitForTrace, setWaitForTrace] = useState(false);
|
||||
const [waitingForTrace, setWaitingForTrace] = useState(false);
|
||||
const [commandUuid, setCommandUuid] = useState(null);
|
||||
|
||||
const setDate = (date) => {
|
||||
if (date) {
|
||||
setChosenDate(date.toString());
|
||||
}
|
||||
const toggleWaitForTrace = () => {
|
||||
setWaitForTrace(!waitForTrace);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setWaitForTrace(false);
|
||||
setHadSuccess(false);
|
||||
setHadFailure(false);
|
||||
setWaiting(false);
|
||||
setChosenDate(new Date().toString());
|
||||
setResponseBody('');
|
||||
setDuration(20);
|
||||
setPackets(100);
|
||||
setChosenInterface('up');
|
||||
setWaitingForTrace(false);
|
||||
}, [show]);
|
||||
|
||||
const doAction = () => {
|
||||
setBlockFields(true);
|
||||
setHadFailure(false);
|
||||
setHadSuccess(false);
|
||||
setWaiting(true);
|
||||
|
||||
const token = getToken();
|
||||
const dateChosen = new Date(chosenDate);
|
||||
const now = new Date();
|
||||
let utcDateString = dateChosen.toISOString();
|
||||
|
||||
if (dateChosen <= now) {
|
||||
const newDate = new Date();
|
||||
newDate.setSeconds(newDate.getSeconds() + 60);
|
||||
utcDateString = newDate.toISOString();
|
||||
}
|
||||
|
||||
const parameters = {
|
||||
serialNumber: selectedDeviceId,
|
||||
when: dateChosen <= now ? 0 : dateToUnix(utcDateString),
|
||||
serialNumber: deviceSerialNumber,
|
||||
when: 0,
|
||||
network: chosenInterface,
|
||||
};
|
||||
|
||||
@@ -88,163 +79,191 @@ const TraceModal = ({ show, toggleModal }) => {
|
||||
|
||||
const headers = {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${token}`,
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
};
|
||||
|
||||
axiosInstance
|
||||
.post(`/device/${encodeURIComponent(selectedDeviceId)}/trace`, parameters, { headers })
|
||||
.then(() => {
|
||||
.post(
|
||||
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}/trace`,
|
||||
parameters,
|
||||
{ headers },
|
||||
)
|
||||
.then((response) => {
|
||||
setHadSuccess(true);
|
||||
if (waitForTrace) {
|
||||
setCommandUuid(response.data.UUID);
|
||||
setWaitingForTrace(true);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
setResponseBody(t('commands.error'));
|
||||
setHadFailure(true);
|
||||
})
|
||||
.finally(() => {
|
||||
setWaiting(false);
|
||||
setBlockFields(false);
|
||||
setBlockFields(false);
|
||||
eventBus.dispatch('actionCompleted', { message: 'An action has been completed' });
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (deviceSerialNumber !== null && show) {
|
||||
const asyncGet = async () => {
|
||||
const isConnected = await getDeviceConnection(
|
||||
deviceSerialNumber,
|
||||
currentToken,
|
||||
endpoints.ucentralgw,
|
||||
);
|
||||
setIsDeviceConnected(isConnected);
|
||||
};
|
||||
asyncGet();
|
||||
}
|
||||
}, [show]);
|
||||
|
||||
const getBody = () => {
|
||||
if (waitingForTrace) {
|
||||
return (
|
||||
<WaitingForTraceBody
|
||||
toggle={toggleModal}
|
||||
serialNumber={deviceSerialNumber}
|
||||
commandUuid={commandUuid}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (hadSuccess) {
|
||||
return <SuccessfulActionModalBody toggleModal={toggleModal} />;
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<CModalBody>
|
||||
<h6>{t('trace.directions')}</h6>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CCol>
|
||||
<CButton
|
||||
disabled={blockFields}
|
||||
block
|
||||
color="primary"
|
||||
onClick={() => setUsingDuration(true)}
|
||||
>
|
||||
{t('common.duration')}
|
||||
</CButton>
|
||||
</CCol>
|
||||
<CCol>
|
||||
<CButton
|
||||
disabled={blockFields}
|
||||
block
|
||||
color="primary"
|
||||
onClick={() => setUsingDuration(false)}
|
||||
>
|
||||
{t('trace.packets')}
|
||||
</CButton>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CCol md="4" className={styles.spacedColumn}>
|
||||
{usingDuration ? 'Duration: ' : 'Packets: '}
|
||||
</CCol>
|
||||
<CCol xs="12" md="8">
|
||||
{usingDuration ? (
|
||||
<CSelect
|
||||
custom
|
||||
defaultValue={duration}
|
||||
disabled={blockFields}
|
||||
onChange={(e) => setDuration(e.target.value)}
|
||||
>
|
||||
<option value="20">20s</option>
|
||||
<option value="40">40s</option>
|
||||
<option value="60">60s</option>
|
||||
<option value="120">120s</option>
|
||||
</CSelect>
|
||||
) : (
|
||||
<CSelect
|
||||
custom
|
||||
defaultValue={packets}
|
||||
disabled={blockFields}
|
||||
onChange={(e) => setPackets(e.target.value)}
|
||||
>
|
||||
<option value="100">100</option>
|
||||
<option value="250">250</option>
|
||||
<option value="500">500</option>
|
||||
<option value="1000">1000</option>
|
||||
</CSelect>
|
||||
)}
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CCol md="7">{t('trace.choose_network')}:</CCol>
|
||||
<CCol>
|
||||
<CForm>
|
||||
<CFormGroup variant="checkbox" onClick={() => setChosenInterface('up')}>
|
||||
<CInputRadio
|
||||
defaultChecked={chosenInterface === 'up'}
|
||||
id="traceRadio1"
|
||||
name="radios"
|
||||
value="traceOption1"
|
||||
/>
|
||||
<CLabel variant="checkbox" htmlFor="traceRadio1">
|
||||
Up
|
||||
</CLabel>
|
||||
</CFormGroup>
|
||||
<CFormGroup variant="checkbox" onClick={() => setChosenInterface('down')}>
|
||||
<CInputRadio
|
||||
defaultChecked={chosenInterface === 'down'}
|
||||
id="traceRadio2"
|
||||
name="radios"
|
||||
value="traceOption2"
|
||||
/>
|
||||
<CLabel variant="checkbox" htmlFor="traceRadio2">
|
||||
Down
|
||||
</CLabel>
|
||||
</CFormGroup>
|
||||
</CForm>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className={styles.spacedRow} hidden={!isDeviceConnected}>
|
||||
<CCol md="8">
|
||||
<p className={styles.spacedText}>{t('trace.wait_for_file')}</p>
|
||||
</CCol>
|
||||
<CCol>
|
||||
<CSwitch
|
||||
disabled={blockFields}
|
||||
color="primary"
|
||||
defaultChecked={waitForTrace}
|
||||
onClick={toggleWaitForTrace}
|
||||
labelOn={t('common.yes')}
|
||||
labelOff={t('common.no')}
|
||||
/>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<div hidden={!hadSuccess && !hadFailure}>
|
||||
<div>
|
||||
<pre className="ignore">{responseBody} </pre>
|
||||
</div>
|
||||
</div>
|
||||
</CModalBody>
|
||||
<CModalFooter>
|
||||
<LoadingButton
|
||||
label={t('trace.trace')}
|
||||
isLoadingLabel={t('common.loading_ellipsis')}
|
||||
isLoading={blockFields}
|
||||
action={doAction}
|
||||
block={false}
|
||||
disabled={blockFields}
|
||||
/>
|
||||
<CButton color="secondary" onClick={toggleModal}>
|
||||
{t('common.cancel')}
|
||||
</CButton>
|
||||
</CModalFooter>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<CModal show={show} onClose={toggleModal}>
|
||||
<CModalHeader closeButton>
|
||||
<CModalTitle>{t('trace.title')}</CModalTitle>
|
||||
</CModalHeader>
|
||||
{hadSuccess ? (
|
||||
<SuccessfulActionModalBody toggleModal={toggleModal} />
|
||||
) : (
|
||||
<div>
|
||||
<CModalBody>
|
||||
<h6>{t('trace.directions')}</h6>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CCol>
|
||||
<CButton
|
||||
disabled={waiting}
|
||||
block
|
||||
color="primary"
|
||||
onClick={() => setUsingDuration(true)}
|
||||
>
|
||||
{t('common.duration')}
|
||||
</CButton>
|
||||
</CCol>
|
||||
<CCol>
|
||||
<CButton
|
||||
disabled={waiting}
|
||||
block
|
||||
color="primary"
|
||||
onClick={() => setUsingDuration(false)}
|
||||
>
|
||||
{t('trace.packets')}
|
||||
</CButton>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CCol md="4" className={styles.spacedColumn}>
|
||||
{usingDuration ? 'Duration: ' : 'Packets: '}
|
||||
</CCol>
|
||||
<CCol xs="12" md="8">
|
||||
{usingDuration ? (
|
||||
<CSelect defaultValue="duration" disabled={waiting}>
|
||||
<option value="20" onClick={() => setDuration(20)}>
|
||||
20s
|
||||
</option>
|
||||
<option value="40" onClick={() => setDuration(40)}>
|
||||
40s
|
||||
</option>
|
||||
<option value="60" onClick={() => setDuration(60)}>
|
||||
60s
|
||||
</option>
|
||||
<option value="120" onClick={() => setDuration(120)}>
|
||||
120s
|
||||
</option>
|
||||
</CSelect>
|
||||
) : (
|
||||
<CSelect defaultValue={packets} disabled={waiting}>
|
||||
<option value="100" onClick={() => setPackets(100)}>
|
||||
100
|
||||
</option>
|
||||
<option value="250" onClick={() => setPackets(250)}>
|
||||
250
|
||||
</option>
|
||||
<option value="500" onClick={() => setPackets(500)}>
|
||||
500
|
||||
</option>
|
||||
<option value="1000" onClick={() => setPackets(1000)}>
|
||||
1000
|
||||
</option>
|
||||
</CSelect>
|
||||
)}
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CCol md="4" className={styles.spacedColumn}>
|
||||
<p>{t('common.date')}:</p>
|
||||
</CCol>
|
||||
<CCol xs="12" md="8">
|
||||
<DatePicker
|
||||
selected={new Date(chosenDate)}
|
||||
includeTime
|
||||
value={new Date(chosenDate)}
|
||||
placeholder="Select custom date"
|
||||
disabled={waiting}
|
||||
onChange={(date) => setDate(date)}
|
||||
min={convertDateToUtc(new Date())}
|
||||
/>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CInvalidFeedback>{t('common.need_date')}</CInvalidFeedback>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CCol md="7">{t('trace.choose_network')}:</CCol>
|
||||
<CCol>
|
||||
<CForm>
|
||||
<CFormGroup variant="checkbox" onClick={() => setChosenInterface('up')}>
|
||||
<CInputRadio
|
||||
defaultChecked={chosenInterface === 'up'}
|
||||
id="traceRadio1"
|
||||
name="radios"
|
||||
value="traceOption1"
|
||||
/>
|
||||
<CLabel variant="checkbox" htmlFor="traceRadio1">
|
||||
Up
|
||||
</CLabel>
|
||||
</CFormGroup>
|
||||
<CFormGroup variant="checkbox" onClick={() => setChosenInterface('down')}>
|
||||
<CInputRadio
|
||||
defaultChecked={chosenInterface === 'down'}
|
||||
id="traceRadio2"
|
||||
name="radios"
|
||||
value="traceOption2"
|
||||
/>
|
||||
<CLabel variant="checkbox" htmlFor="traceRadio2">
|
||||
Down
|
||||
</CLabel>
|
||||
</CFormGroup>
|
||||
</CForm>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<div hidden={!hadSuccess && !hadFailure}>
|
||||
<div>
|
||||
<pre className="ignore">{responseBody} </pre>
|
||||
</div>
|
||||
</div>
|
||||
</CModalBody>
|
||||
<CModalFooter>
|
||||
<LoadingButton
|
||||
label="Schedule"
|
||||
isLoadingLabel="Loading..."
|
||||
isLoading={waiting}
|
||||
action={doAction}
|
||||
variant="outline"
|
||||
block={false}
|
||||
disabled={waiting}
|
||||
/>
|
||||
<CButton color="secondary" onClick={toggleModal}>
|
||||
{t('common.cancel')}
|
||||
</CButton>
|
||||
</CModalFooter>
|
||||
</div>
|
||||
)}
|
||||
{getBody()}
|
||||
</CModal>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -5,3 +5,10 @@
|
||||
.spacedColumn {
|
||||
margin-top: 7px;
|
||||
}
|
||||
|
||||
.centerDiv {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
@@ -9,28 +9,31 @@ import {
|
||||
CForm,
|
||||
CSwitch,
|
||||
CCol,
|
||||
CSpinner,
|
||||
} from '@coreui/react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { getToken } from 'utils/authHelper';
|
||||
import { useAuth } from 'contexts/AuthProvider';
|
||||
import { useDevice } from 'contexts/DeviceProvider';
|
||||
import axiosInstance from 'utils/axiosInstance';
|
||||
import eventBus from 'utils/eventBus';
|
||||
import LoadingButton from 'components/LoadingButton';
|
||||
import WifiChannelTable from 'components/WifiScanResultModal/containers/WifiChannelTable';
|
||||
import WifiChannelTable from 'components/WifiScanResultModal/WifiChannelTable';
|
||||
import 'react-widgets/styles.css';
|
||||
import styles from './index.module.scss';
|
||||
|
||||
const WifiScanModal = ({ show, toggleModal }) => {
|
||||
const { t } = useTranslation();
|
||||
const { currentToken, endpoints } = useAuth();
|
||||
const { deviceSerialNumber } = useDevice();
|
||||
const [hadSuccess, setHadSuccess] = useState(false);
|
||||
const [hadFailure, setHadFailure] = useState(false);
|
||||
const [waiting, setWaiting] = useState(false);
|
||||
const [choseVerbose, setVerbose] = useState(true);
|
||||
const [activeScan, setActiveScan] = useState(false);
|
||||
const [hideOptions, setHideOptions] = useState(false);
|
||||
const [channelList, setChannelList] = useState([]);
|
||||
const selectedDeviceId = useSelector((state) => state.selectedDeviceId);
|
||||
|
||||
const toggleVerbose = () => {
|
||||
setVerbose(!choseVerbose);
|
||||
@@ -47,6 +50,7 @@ const WifiScanModal = ({ show, toggleModal }) => {
|
||||
setChannelList([]);
|
||||
setVerbose(true);
|
||||
setActiveScan(false);
|
||||
setHideOptions(false);
|
||||
}, [show]);
|
||||
|
||||
const parseThroughList = (scanList) => {
|
||||
@@ -85,25 +89,28 @@ const WifiScanModal = ({ show, toggleModal }) => {
|
||||
setHadSuccess(false);
|
||||
setWaiting(true);
|
||||
|
||||
const token = getToken();
|
||||
|
||||
const parameters = {
|
||||
serialNumber: selectedDeviceId,
|
||||
serialNumber: deviceSerialNumber,
|
||||
verbose: choseVerbose,
|
||||
activeScan,
|
||||
};
|
||||
const headers = {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${token}`,
|
||||
Authorization: `Bearer ${currentToken}`,
|
||||
};
|
||||
|
||||
axiosInstance
|
||||
.post(`/device/${encodeURIComponent(selectedDeviceId)}/wifiscan`, parameters, { headers })
|
||||
.post(
|
||||
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}/wifiscan`,
|
||||
parameters,
|
||||
{ headers },
|
||||
)
|
||||
.then((response) => {
|
||||
const scanList = response?.data?.results?.status?.scan;
|
||||
|
||||
if (scanList) {
|
||||
setChannelList(parseThroughList(scanList));
|
||||
setHideOptions(true);
|
||||
setHadSuccess(true);
|
||||
} else {
|
||||
setHadFailure(true);
|
||||
@@ -124,47 +131,66 @@ const WifiScanModal = ({ show, toggleModal }) => {
|
||||
<CModalTitle>{t('actions.wifi_scan')}</CModalTitle>
|
||||
</CModalHeader>
|
||||
<CModalBody>
|
||||
<h6>{t('scan.directions')}</h6>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CCol md="3">
|
||||
<p className={styles.spacedText}>Verbose:</p>
|
||||
</CCol>
|
||||
<CCol>
|
||||
<CForm className={styles.spacedSwitch}>
|
||||
<CSwitch
|
||||
color="primary"
|
||||
defaultChecked={choseVerbose}
|
||||
onClick={toggleVerbose}
|
||||
labelOn={t('common.on')}
|
||||
labelOff={t('common.off')}
|
||||
/>
|
||||
</CForm>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CCol md="3">
|
||||
<p className={styles.spacedText}>{t('scan.active')}:</p>
|
||||
</CCol>
|
||||
<CCol>
|
||||
<CForm className={styles.spacedSwitch}>
|
||||
<CSwitch
|
||||
color="primary"
|
||||
defaultChecked={activeScan}
|
||||
onClick={toggleActiveScan}
|
||||
labelOn={t('common.on')}
|
||||
labelOff={t('common.off')}
|
||||
/>
|
||||
</CForm>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<div className={styles.spacedRow} hidden={!hadSuccess && !hadFailure}>
|
||||
<div hidden={hideOptions || waiting}>
|
||||
<h6>{t('scan.directions')}</h6>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CCol md="3">
|
||||
<p className={styles.spacedText}>Verbose:</p>
|
||||
</CCol>
|
||||
<CCol>
|
||||
<CForm className={styles.spacedSwitch}>
|
||||
<CSwitch
|
||||
color="primary"
|
||||
defaultChecked={choseVerbose}
|
||||
onClick={toggleVerbose}
|
||||
labelOn={t('common.on')}
|
||||
labelOff={t('common.off')}
|
||||
/>
|
||||
</CForm>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className={styles.spacedRow}>
|
||||
<CCol md="3">
|
||||
<p className={styles.spacedText}>{t('scan.active')}:</p>
|
||||
</CCol>
|
||||
<CCol>
|
||||
<CForm className={styles.spacedSwitch}>
|
||||
<CSwitch
|
||||
color="primary"
|
||||
defaultChecked={activeScan}
|
||||
onClick={toggleActiveScan}
|
||||
labelOn={t('common.on')}
|
||||
labelOff={t('common.off')}
|
||||
/>
|
||||
</CForm>
|
||||
</CCol>
|
||||
</CRow>
|
||||
</div>
|
||||
<div hidden={!waiting}>
|
||||
<CRow>
|
||||
<CCol>
|
||||
<h6>{t('scan.waiting_directions')}</h6>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow>
|
||||
<CCol className={styles.centerDiv}>
|
||||
<CSpinner />
|
||||
</CCol>
|
||||
</CRow>
|
||||
</div>
|
||||
<div hidden={!hadSuccess && !hadFailure}>
|
||||
<CRow className={styles.bottomSpace}>
|
||||
<CCol>
|
||||
<h6>{t('scan.result_directions')}</h6>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<WifiChannelTable channels={channelList} />
|
||||
</div>
|
||||
</CModalBody>
|
||||
<CModalFooter>
|
||||
<LoadingButton
|
||||
label={t('common.start')}
|
||||
isLoadingLabel={t('common.loading_ellipsis')}
|
||||
label={!hadSuccess && !hadFailure ? t('scan.scan') : t('scan.re_scan')}
|
||||
isLoadingLabel={t('scan.scanning')}
|
||||
isLoading={waiting}
|
||||
action={doAction}
|
||||
variant="outline"
|
||||
@@ -172,7 +198,7 @@ const WifiScanModal = ({ show, toggleModal }) => {
|
||||
disabled={waiting}
|
||||
/>
|
||||
<CButton color="secondary" onClick={toggleModal}>
|
||||
{t('common.cancel')}
|
||||
{!hadSuccess && !hadFailure ? t('common.cancel') : t('common.exit')}
|
||||
</CButton>
|
||||
</CModalFooter>
|
||||
</CModal>
|
||||
|
||||
@@ -9,3 +9,14 @@
|
||||
.spacedSwitch {
|
||||
padding-left: 5%;
|
||||
}
|
||||
|
||||
.bottomSpace {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.centerDiv {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { CCol, CRow } from '@coreui/react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { v4 as createUuid } from 'uuid';
|
||||
import PropTypes from 'prop-types';
|
||||
import WifiChannelCard from '../WifiChannelCard';
|
||||
import WifiChannelCard from './WifiChannelCard';
|
||||
|
||||
const WifiChannelTable = ({ channels }) => {
|
||||
const sortChannels = () => {
|
||||
@@ -1,11 +0,0 @@
|
||||
.cardTitle {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.scrollable {
|
||||
height: 250px;
|
||||
}
|
||||
|
||||
.datatable {
|
||||
color: white;
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
} from '@coreui/react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { prettyDate } from 'utils/helper';
|
||||
import WifiChannelTable from './containers/WifiChannelTable';
|
||||
import WifiChannelTable from './WifiChannelTable';
|
||||
import styles from './index.module.scss';
|
||||
|
||||
const WifiScanResultModal = ({ show, toggle, scanResults, date }) => {
|
||||
|
||||
@@ -1,3 +1,15 @@
|
||||
.modalTitle {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.cardTitle {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.scrollable {
|
||||
height: 250px;
|
||||
}
|
||||
|
||||
.datatable {
|
||||
color: white;
|
||||
}
|
||||
|
||||
27
src/contexts/AuthProvider/index.js
Normal file
27
src/contexts/AuthProvider/index.js
Normal file
@@ -0,0 +1,27 @@
|
||||
import React, { useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const AuthContext = React.createContext();
|
||||
|
||||
export const AuthProvider = ({ token, apiEndpoints, children }) => {
|
||||
const [currentToken, setCurrentToken] = useState(token);
|
||||
const [endpoints, setEndpoints] = useState(apiEndpoints);
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={{ currentToken, setCurrentToken, endpoints, setEndpoints }}>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
AuthProvider.propTypes = {
|
||||
token: PropTypes.string.isRequired,
|
||||
children: PropTypes.node.isRequired,
|
||||
apiEndpoints: PropTypes.instanceOf(Object),
|
||||
};
|
||||
|
||||
AuthProvider.defaultProps = {
|
||||
apiEndpoints: {},
|
||||
};
|
||||
|
||||
export const useAuth = () => React.useContext(AuthContext);
|
||||
21
src/contexts/DeviceProvider/index.js
Normal file
21
src/contexts/DeviceProvider/index.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import React, { useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const DeviceContext = React.createContext();
|
||||
|
||||
export const DeviceProvider = ({ serialNumber, children }) => {
|
||||
const [deviceSerialNumber, setDeviceSerialNumber] = useState(serialNumber);
|
||||
|
||||
return (
|
||||
<DeviceContext.Provider value={{ deviceSerialNumber, setDeviceSerialNumber }}>
|
||||
{children}
|
||||
</DeviceContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
DeviceProvider.propTypes = {
|
||||
serialNumber: PropTypes.string.isRequired,
|
||||
children: PropTypes.node.isRequired,
|
||||
};
|
||||
|
||||
export const useDevice = () => React.useContext(DeviceContext);
|
||||
@@ -1,19 +1,17 @@
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import 'index.css';
|
||||
import { Provider } from 'react-redux';
|
||||
import App from 'App';
|
||||
import store from 'store';
|
||||
import { icons } from 'assets/icons';
|
||||
import '@babel/polyfill';
|
||||
import 'i18n';
|
||||
|
||||
React.icons = icons;
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<Provider store={store}>
|
||||
<App />
|
||||
</Provider>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root'),
|
||||
);
|
||||
|
||||
@@ -6,7 +6,7 @@ const TheFooter = () => (
|
||||
<Translation>
|
||||
{(t) => (
|
||||
<CFooter fixed={false}>
|
||||
<div>{t('footer.version')} 0.9.3</div>
|
||||
<div>{t('footer.version')} 0.9.13</div>
|
||||
<div className="mfs-auto">
|
||||
<span className="mr-1">{t('footer.powered_by')}</span>
|
||||
<a href="https://coreui.io/react" target="_blank" rel="noopener noreferrer">
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
CHeader,
|
||||
@@ -11,26 +10,27 @@ import {
|
||||
CLink,
|
||||
CPopover,
|
||||
} from '@coreui/react';
|
||||
import PropTypes from 'prop-types';
|
||||
import CIcon from '@coreui/icons-react';
|
||||
import { cilAccountLogout } from '@coreui/icons';
|
||||
import { logout } from 'utils/authHelper';
|
||||
import routes from 'routes';
|
||||
import LanguageSwitcher from 'components/LanguageSwitcher';
|
||||
import { LanguageSwitcher } from 'ucentral-libs';
|
||||
import { useAuth } from 'contexts/AuthProvider';
|
||||
|
||||
const TheHeader = () => {
|
||||
const TheHeader = ({ showSidebar, setShowSidebar }) => {
|
||||
const { t, i18n } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
const { currentToken, endpoints } = useAuth();
|
||||
const [translatedRoutes, setTranslatedRoutes] = useState(routes);
|
||||
const sidebarShow = useSelector((state) => state.sidebarShow);
|
||||
|
||||
const toggleSidebar = () => {
|
||||
const val = [true, 'responsive'].includes(sidebarShow) ? false : 'responsive';
|
||||
dispatch({ type: 'set', sidebarShow: val });
|
||||
const val = [true, 'responsive'].includes(showSidebar) ? false : 'responsive';
|
||||
setShowSidebar(val);
|
||||
};
|
||||
|
||||
const toggleSidebarMobile = () => {
|
||||
const val = [false, 'responsive'].includes(sidebarShow) ? true : 'responsive';
|
||||
dispatch({ type: 'set', sidebarShow: val });
|
||||
const val = [false, 'responsive'].includes(showSidebar) ? true : 'responsive';
|
||||
setShowSidebar(val);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -48,13 +48,18 @@ const TheHeader = () => {
|
||||
<CHeaderNav className="d-md-down-none mr-auto" />
|
||||
|
||||
<CHeaderNav className="px-3">
|
||||
<LanguageSwitcher />
|
||||
<LanguageSwitcher i18n={i18n} />
|
||||
</CHeaderNav>
|
||||
|
||||
<CHeaderNav className="px-3">
|
||||
<CPopover content={t('common.logout')}>
|
||||
<CLink className="c-subheader-nav-link">
|
||||
<CIcon name="cilAccountLogout" content={cilAccountLogout} size="2xl" onClick={logout} />
|
||||
<CIcon
|
||||
name="cilAccountLogout"
|
||||
content={cilAccountLogout}
|
||||
size="2xl"
|
||||
onClick={() => logout(currentToken, endpoints.ucentralsec)}
|
||||
/>
|
||||
</CLink>
|
||||
</CPopover>
|
||||
</CHeaderNav>
|
||||
@@ -69,4 +74,9 @@ const TheHeader = () => {
|
||||
);
|
||||
};
|
||||
|
||||
TheHeader.propTypes = {
|
||||
showSidebar: PropTypes.string.isRequired,
|
||||
setShowSidebar: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default TheHeader;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React from 'react';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import {
|
||||
CCreateElement,
|
||||
CSidebar,
|
||||
@@ -11,14 +10,12 @@ import {
|
||||
CSidebarNavDropdown,
|
||||
CSidebarNavItem,
|
||||
} from '@coreui/react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import logoBar from 'assets/OpenWiFi_LogoLockup_WhiteColour.svg';
|
||||
import styles from './index.module.scss';
|
||||
|
||||
const TheSidebar = () => {
|
||||
const TheSidebar = ({ showSidebar, setShowSidebar }) => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
const show = useSelector((state) => state.sidebarShow);
|
||||
|
||||
const navigation = [
|
||||
{
|
||||
@@ -30,16 +27,16 @@ const TheSidebar = () => {
|
||||
];
|
||||
|
||||
return (
|
||||
<CSidebar show={show} onShowChange={(val) => dispatch({ type: 'set', sidebarShow: val })}>
|
||||
<CSidebar show={showSidebar} onShowChange={(val) => setShowSidebar(val)}>
|
||||
<CSidebarBrand className="d-md-down-none" to="/devices">
|
||||
<img
|
||||
className={[styles.sidebarImgFull, 'c-sidebar-brand-full'].join(' ')}
|
||||
src={logoBar}
|
||||
src="assets/OpenWiFi_LogoLockup_WhiteColour.svg"
|
||||
alt="OpenWifi"
|
||||
/>
|
||||
<img
|
||||
className={[styles.sidebarImgMinimized, 'c-sidebar-brand-minimized'].join(' ')}
|
||||
src={logoBar}
|
||||
src="assets/OpenWiFi_LogoLockup_WhiteColour.svg"
|
||||
alt="OpenWifi"
|
||||
/>
|
||||
</CSidebarBrand>
|
||||
@@ -59,4 +56,9 @@ const TheSidebar = () => {
|
||||
);
|
||||
};
|
||||
|
||||
TheSidebar.propTypes = {
|
||||
showSidebar: PropTypes.string.isRequired,
|
||||
setShowSidebar: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default React.memo(TheSidebar);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.sidebarImgFull {
|
||||
height: 75px;
|
||||
width: 75px;
|
||||
height: 100px;
|
||||
width: 230px;
|
||||
}
|
||||
|
||||
.sidebarImgMinimized {
|
||||
|
||||
@@ -1,21 +1,37 @@
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { logout } from 'utils/authHelper';
|
||||
import routes from 'routes';
|
||||
import { useAuth } from 'contexts/AuthProvider';
|
||||
import { Header } from 'ucentral-libs';
|
||||
import Sidebar from './Sidebar';
|
||||
import TheContent from './Content';
|
||||
import TheSidebar from './Sidebar';
|
||||
import TheFooter from './Footer';
|
||||
import TheHeader from './Header';
|
||||
|
||||
const TheLayout = (props) => {
|
||||
const { isLoggedIn } = useSelector((state) => state.connected);
|
||||
if (isLoggedIn) {
|
||||
return <div>{props.children}</div>;
|
||||
}
|
||||
const TheLayout = () => {
|
||||
const [showSidebar, setShowSidebar] = useState('responsive');
|
||||
const { endpoints, currentToken } = useAuth();
|
||||
const { t, i18n } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="c-app c-default-layout">
|
||||
<TheSidebar />
|
||||
<Sidebar
|
||||
showSidebar={showSidebar}
|
||||
setShowSidebar={setShowSidebar}
|
||||
t={t}
|
||||
logo="assets/OpenWiFi_LogoLockup_DarkGreyColour.svg"
|
||||
/>
|
||||
<div className="c-wrapper">
|
||||
<TheHeader />
|
||||
<Header
|
||||
showSidebar={showSidebar}
|
||||
setShowSidebar={setShowSidebar}
|
||||
routes={routes}
|
||||
t={t}
|
||||
i18n={i18n}
|
||||
logout={logout}
|
||||
authToken={currentToken}
|
||||
endpoints={endpoints}
|
||||
/>
|
||||
<div className="c-body">
|
||||
<TheContent />
|
||||
</div>
|
||||
@@ -25,12 +41,4 @@ const TheLayout = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
TheLayout.propTypes = {
|
||||
children: PropTypes.instanceOf(Object),
|
||||
};
|
||||
|
||||
TheLayout.defaultProps = {
|
||||
children: {},
|
||||
};
|
||||
|
||||
export default TheLayout;
|
||||
|
||||
@@ -1,46 +1,40 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import React from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { CRow, CCol } from '@coreui/react';
|
||||
import DeviceHealth from 'components/DeviceHealth';
|
||||
import DeviceConfiguration from 'components/DeviceConfiguration';
|
||||
import DeviceCommandsLog from 'components/DeviceCommandsLog';
|
||||
import CommandHistory from 'components/CommandHistory';
|
||||
import DeviceLogs from 'components/DeviceLogs';
|
||||
import DeviceStatisticsCard from 'components/InterfaceStatistics';
|
||||
import DeviceActionCard from './containers/DeviceActionCard';
|
||||
import DeviceActionCard from 'components/DeviceActionCard';
|
||||
import DeviceStatusCard from 'components/DeviceStatusCard';
|
||||
import { DeviceProvider } from 'contexts/DeviceProvider';
|
||||
|
||||
const DevicePage = () => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const { deviceId } = useParams();
|
||||
const previouslySelectedDeviceId = useSelector((state) => state.selectedDeviceId);
|
||||
|
||||
useEffect(() => {
|
||||
if (deviceId && deviceId !== previouslySelectedDeviceId)
|
||||
dispatch({ type: 'set', selectedDeviceId: deviceId });
|
||||
}, [deviceId]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="App">
|
||||
<div className="App">
|
||||
<DeviceProvider serialNumber={deviceId}>
|
||||
<CRow>
|
||||
<CCol xs="12" sm="6">
|
||||
<DeviceConfiguration selectedDeviceId={deviceId} />
|
||||
<DeviceStatusCard />
|
||||
<DeviceConfiguration />
|
||||
</CCol>
|
||||
<CCol xs="12" sm="6">
|
||||
<DeviceLogs selectedDeviceId={deviceId} />
|
||||
<DeviceHealth selectedDeviceId={deviceId} />
|
||||
<DeviceActionCard selectedDeviceId={deviceId} />
|
||||
<DeviceLogs />
|
||||
<DeviceHealth />
|
||||
<DeviceActionCard />
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow>
|
||||
<CCol>
|
||||
<DeviceStatisticsCard selectedDeviceId={deviceId} />
|
||||
<DeviceCommandsLog selectedDeviceId={deviceId} />
|
||||
<DeviceStatisticsCard />
|
||||
<CommandHistory />
|
||||
</CCol>
|
||||
</CRow>
|
||||
</div>
|
||||
</>
|
||||
</DeviceProvider>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user