Compare commits

...

112 Commits

Author SHA1 Message Date
Dmitry Dunaev
85c7a34af5 Chg: release candidate version fix for helm 2021-07-28 19:48:27 +03:00
Dmitry Dunaev
df6ec36515 Add: KUBERNETES_DEPLOYED env variable temporary added for redeployment forcing 2021-07-22 18:47:57 +03:00
Dmitry Dunaev
db7394d86c Merge pull request #36 from Telecominfraproject/feature/helm-docs
[WIFI-2592] Add: Helm README
2021-07-21 16:21:27 +03:00
Dmitry Dunaev
772e7cd07d [WIFI-2592] Add: Helm README 2021-07-21 14:59:14 +03:00
Dmitry Dunaev
54bc4ad403 Merge pull request #35 from Telecominfraproject/feature/git-branching
[WIFI-2622] Add: branching release model in CI
2021-07-19 17:22:31 +03:00
Dmitry Dunaev
8fe26a08b3 [WIFI-2622] Add: branching release model in CI 2021-07-19 17:19:16 +03:00
Charles
6f78768459 Update README.md 2021-07-13 12:09:14 -04:00
Dmitry Dunaev
c3c31ec4ac Fix: downgrading node to 14 to resolve node-sass incompatibility 2021-07-13 18:42:12 +03:00
Charles
3dce199b79 Update README.md 2021-07-13 11:04:02 -04:00
Charles
403e8bc185 Update README.md 2021-07-13 11:02:12 -04:00
Charles
0f15ae7bef Merge pull request #34 from Telecominfraproject/dev-microservice
Dev microservice
2021-07-13 10:12:20 -04:00
Charles
02385c5544 Merge pull request #33 from stephb9959/main
Webpack use
2021-07-12 15:30:20 -04:00
bourquecharles
a422d5d446 Webpack and dependency cleanup 2021-07-12 15:20:55 -04:00
bourquecharles
5fe05e2890 Library update 2021-07-12 12:29:00 -04:00
bourquecharles
d9f3406ae7 Dev webpack working 2021-07-12 12:05:30 -04:00
bourquecharles
28ec0482c8 Eslint fix 2021-07-09 15:28:47 -04:00
bourquecharles
273a91f357 gitignore fixed 2021-07-09 15:08:04 -04:00
bourquecharles
2c7df5abd1 Now building in the /build folder again 2021-07-09 15:07:45 -04:00
bourquecharles
09232c5640 Eslint change 2021-07-09 14:58:31 -04:00
bourquecharles
fe84f6bdb1 Webpack use 2021-07-09 14:56:21 -04:00
Dmitry Dunaev
e1727d2f91 [WIFI-2920] Chg: env variables to match microservice architecture 2021-07-09 16:20:16 +03:00
Charles
74cac90696 Merge pull request #32 from stephb9959/main
Device Notes in config
2021-07-07 13:32:47 -04:00
Charles
4b2bde3c3a Merge pull request #9 from stephb9959/31-device-notes-display
31 device notes display
2021-07-07 09:51:18 -04:00
bourquecharles
c4b19a3b24 New device notes table 2021-07-07 09:49:55 -04:00
bourquecharles
7384558ad0 Users can now add new notes 2021-07-06 14:11:08 -04:00
bourquecharles
cc8be05594 Deleted past file references 2021-07-06 11:46:01 -04:00
bourquecharles
bff117e45a Deleted unused files 2021-07-06 11:44:40 -04:00
Charles
62b1c23b3c Merge pull request #30 from stephb9959/main
Version 0.9.12
2021-07-05 15:28:43 -04:00
bourquecharles
340d21edac Fixed husky running pre-commit 2021-07-05 11:16:34 -04:00
bourquecharles
73a824330a Reinstalled sass dependency 2021-07-05 10:04:27 -04:00
bourquecharles
96cc7dc78b Version 0.9.12 2021-07-05 09:55:58 -04:00
bourquecharles
b71b4cd1a5 Uninstalled sass dependency 2021-07-05 09:40:54 -04:00
bourquecharles
a54acfd347 Simplified Trace select 2021-07-05 09:36:28 -04:00
bourquecharles
4d4af822d9 Uninstalled Select dependency 2021-07-05 09:31:21 -04:00
bourquecharles
3bc028fc3b Uninstalled redux 2021-07-05 09:14:17 -04:00
bourquecharles
e1e74cb29a Uninstalled http/https library 2021-07-05 09:13:31 -04:00
bourquecharles
d9a8a02cd7 Prettier run 2021-07-05 08:53:48 -04:00
bourquecharles
4c96158f11 Merge branch 'main' of https://github.com/stephb9959/wlan-cloud-ucentralgw-ui into main 2021-07-02 18:16:48 -04:00
bourquecharles
7162303e62 Label changes for UCENTRALSEC change 2021-07-02 18:16:19 -04:00
Charles
26ef27f251 Merge branch 'Telecominfraproject:main' into main 2021-07-02 17:50:11 -04:00
bourquecharles
f2f370602f Merge branch 'main' of https://github.com/stephb9959/wlan-cloud-ucentralgw-ui into main 2021-07-02 17:42:18 -04:00
bourquecharles
2056482179 Changed variables for UCENTRALSEC 2021-07-02 17:42:09 -04:00
Charles
e8960368a5 Update README.md 2021-07-02 17:38:40 -04:00
bourquecharles
c017fe0774 Now using endpoints returned from the gateway sec 2021-07-02 17:34:13 -04:00
Dmitry Dunaev
3ea22477ae Merge pull request #29 from Telecominfraproject/feature/cicd
[WIFI-2882] Add: image build workflow
2021-07-02 15:21:56 +03:00
Dmitry Dunaev
258ba197bb [WIFI-2882] Add: image build and cleanup workflow 2021-07-02 14:32:12 +03:00
bourquecharles
908a034433 Version 0.9.9 2021-07-01 16:45:32 -04:00
Charles
101509edd6 Merge pull request #8 from stephb9959/28-migrating-to-context
Migrated from redux to context API
2021-07-01 16:44:02 -04:00
bourquecharles
ae7200815d Migrated from redux to context API 2021-07-01 16:42:51 -04:00
bourquecharles
f42b3d48d3 Version 0.9.8 2021-07-01 10:46:54 -04:00
bourquecharles
e60a703f19 Reboot and blink modal with one less line for user 2021-07-01 10:45:54 -04:00
bourquecharles
281a84f19c Simplified RebootModal component 2021-07-01 09:50:02 -04:00
bourquecharles
563b94765e Alignment adjustment for MemoryBar placement 2021-07-01 09:14:50 -04:00
bourquecharles
9695a3ba71 Adjustment to column width 2021-07-01 09:12:27 -04:00
Charles
5b6f0e8f78 Merge branch 'Telecominfraproject:main' into main 2021-07-01 09:08:17 -04:00
Dmitry Dunaev
405e8eb944 Merge pull request #27 from Telecominfraproject/feature/add-helm
[WIFI-2695] Add: helm chart
2021-07-01 14:47:33 +03:00
Dmitry Dunaev
1d771a2bea [WIFI-2695] Add: helm chart 2021-07-01 14:46:59 +03:00
bourquecharles
895301edd3 Dependency cleanup 2021-06-30 15:59:13 -04:00
Charles
568961c4d9 Merge pull request #7 from stephb9959/feature/20-at-a-glance-device-tile
#20 Device Status Card
2021-06-30 15:23:22 -04:00
Charles
ce5c25f169 Merge branch 'Telecominfraproject:main' into main 2021-06-30 15:21:59 -04:00
bourquecharles
0b142b0658 Version 0.9.7 2021-06-30 15:19:47 -04:00
bourquecharles
87b3450221 First version of device status card 2021-06-30 15:17:36 -04:00
bourquecharles
97b3716dc9 Prettier run 2021-06-30 15:17:09 -04:00
bourquecharles
c83b8eb8aa Device configuration CSS cleanup 2021-06-30 15:16:06 -04:00
Stephane Bourque
d5244f8626 Update README.md 2021-06-30 08:32:15 -07:00
Charles
d5156bf000 Update README.md 2021-06-30 11:27:27 -04:00
Dmitry Dunaev
ae36c31dea Merge pull request #26 from Telecominfraproject/feature/configuration-via-envs
[WIFI-2853] Add: configration generation in runtime via env variables
2021-06-30 17:07:03 +03:00
Dmitry Dunaev
77224477b3 [WIFI-2853] Add: configration generation in runtime via env variables 2021-06-30 16:41:15 +03:00
bourquecharles
80fa5e7d43 Version 0.9.6 2021-06-30 08:25:19 -04:00
bourquecharles
84de2595b9 Changed button label in blink modal 2021-06-30 08:24:36 -04:00
bourquecharles
c0023436e8 Trace download now a button instead of a link 2021-06-30 08:24:19 -04:00
bourquecharles
6593be8bdf Health title made biggest on tile 2021-06-30 08:24:01 -04:00
Charles
fb344db120 Merge pull request #25 from stephb9959/main
More accurate translations in German
2021-06-29 16:28:39 -04:00
bourquecharles
6cf081527f More accurate translations in German 2021-06-29 16:27:42 -04:00
Charles
dc4dca417b Merge pull request #24 from stephb9959/main
Using smooth lines for stats line graph
2021-06-29 16:08:53 -04:00
bourquecharles
be7a55fbc0 Using smooth lines for stats line graph 2021-06-29 16:07:17 -04:00
Charles
7b476cf38a Merge pull request #23 from stephb9959/main
General UI fixes
2021-06-29 16:02:31 -04:00
Charles
236e2ced62 Merge pull request #6 from stephb9959/bugfix/21-general-ui-fixes
Bugfix/21 general UI fixes
2021-06-29 15:48:49 -04:00
bourquecharles
e99fd9f6b2 New copy to clipboard component. Alignment fixes 2021-06-29 15:47:55 -04:00
bourquecharles
78eb883fef Aligned Statistics title and options 2021-06-29 15:20:37 -04:00
bourquecharles
16608c5fcf Standardized modal footer buttons 2021-06-29 15:05:20 -04:00
bourquecharles
657f61b43e Wifi scan modal has 'Cancel' instead of Close 2021-06-29 14:41:31 -04:00
bourquecharles
3e279aff58 Cleaned labels of mentioning 'Device' all the time 2021-06-29 14:40:32 -04:00
Charles
6628c69d19 Merge pull request #19 from stephb9959/main
Label changes and Wifi Scan loading
2021-06-29 10:55:56 -04:00
bourquecharles
cdcd29a47c Version 0.9.5 2021-06-29 10:45:20 -04:00
bourquecharles
d8ab92531f Added loading phase, label changes for wifi scan. 2021-06-29 10:44:32 -04:00
bourquecharles
f6876d13fe Label changes for WaitingForTraceBody 2021-06-29 09:23:46 -04:00
bourquecharles
5253009bad Undid adding .babel file to project 2021-06-29 08:53:02 -04:00
bourquecharles
d74e1f4172 Merge branch 'main' of https://github.com/stephb9959/wlan-cloud-ucentralgw-ui into main 2021-06-29 08:48:37 -04:00
bourquecharles
0ad81c85f2 Added babel translation file 2021-06-29 08:48:28 -04:00
Stephane Bourque
ec8ea71dcc Merge pull request #11 from Telecominfraproject/feature/dockerfile
Add: docker related files
2021-06-28 12:36:30 -07:00
Stephane Bourque
cd4be5c7b1 Merge pull request #14 from stephb9959/main
Version 0.9.4 : wait for Trace, CSS fixes, latest stats
2021-06-28 12:35:38 -07:00
bourquecharles
e2433024d7 Standardized component titles on device page 2021-06-28 15:03:05 -04:00
bourquecharles
ddc877ee3d Using css for cropping strings with ellipsis 2021-06-28 12:19:05 -04:00
bourquecharles
60b03d49c6 Version 0.9.4 2021-06-28 10:24:47 -04:00
Charles
853590376b Merge pull request #5 from stephb9959/bugfix/10-statistics-chart-rendering
Fixed re-renders of statistics chart list
2021-06-28 09:53:47 -04:00
bourquecharles
cccd7b7cec Fixed re-renders of statistics chart list 2021-06-28 09:48:51 -04:00
bourquecharles
68f7570720 Added label for waiting for trace file 2021-06-28 08:26:36 -04:00
Charles
59e1709cff Merge pull request #4 from stephb9959/feature/9-updating-reboot-blink-modal
Feature/9 updating reboot blink modal
2021-06-25 15:26:33 -04:00
bourquecharles
39b05192fe Small refactor of reboot modal 2021-06-25 15:23:33 -04:00
bourquecharles
fa377132a8 Blink refactored 2021-06-25 15:14:25 -04:00
bourquecharles
b3e5cd79ed hotfix: clearer wait for trace behaviour 2021-06-25 14:41:41 -04:00
Charles
6f7b05649b Merge pull request #3 from stephb9959/feature/4-firmware-upgrade-wait
Feature/4 firmware upgrade wait
2021-06-25 14:21:18 -04:00
bourquecharles
4861a746b4 Temporarily deactivating wait for upgrade 2021-06-25 13:57:25 -04:00
bourquecharles
e75c163248 Waiting for trace 2021-06-25 09:35:47 -04:00
bourquecharles
d8b501fff0 Merge branch 'main' into feature/4-firmware-upgrad 2021-06-24 11:53:34 -04:00
bourquecharles
9de6c27c69 Reworked the firmware upgrade modal and added wait option 2021-06-24 11:33:00 -04:00
Charles
80811a6071 Merge branch 'Telecominfraproject:main' into main 2021-06-24 11:26:57 -04:00
Charles
814cb09e44 Merge pull request #2 from stephb9959/feature/7-icon-to-retrieve-last-stats
Feature/7 icon to retrieve last stats
2021-06-24 11:25:20 -04:00
bourquecharles
050a4a3b78 Created LatestStatisticsModal component 2021-06-24 10:48:36 -04:00
bourquecharles
ced06d1391 Cleaned unnecessary nesting in the pages directory 2021-06-24 08:58:08 -04:00
bourquecharles
9876c98628 Cleaned useless nesting in components folder 2021-06-24 08:56:01 -04:00
110 changed files with 12527 additions and 23313 deletions

View File

@@ -25,3 +25,4 @@ yarn-error.log*
.git
.github
Dockerfile
helm

View File

@@ -1 +1,4 @@
/src/assets
/build
/node_modules
.github

View File

@@ -1,16 +1,11 @@
{
"parser": "babel-eslint",
"parserOptions": {
"sourceType": "module",
"allowImportExportEverywhere": false,
"codeFrame": false
},
"extends": ["airbnb", "prettier"],
"env": {
"extends": ["airbnb", "prettier"],
"plugins": ["prettier"],
"env": {
"browser": true,
"jest": true
},
"rules": {
},
"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
View 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
View 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
View File

@@ -1,4 +1,4 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules

4
.prettierignore Normal file
View File

@@ -0,0 +1,4 @@
/src/assets
build
node_modules
.github

View File

@@ -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

View File

@@ -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.
### 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
View File

@@ -0,0 +1,7 @@
{
"presets": [
"@babel/preset-env",
"@babel/preset-react"
],
"plugins": ["@babel/plugin-proposal-class-properties"]
}

12
config/paths.js Normal file
View 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
View 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
View 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
View 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,
},
});

View 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
View File

@@ -0,0 +1 @@
*.swp

22
helm/.helmignore Normal file
View 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
View 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
View 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.

View 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 -}}

View 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 }}

View 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 }}

View 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 }}

View 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
View 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

31211
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -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"
}
}

View File

@@ -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
}

View File

@@ -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>

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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

View File

@@ -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"
}

View File

@@ -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

View File

@@ -1,2 +0,0 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *

View File

@@ -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 (
<AuthProvider token={storageToken ?? ''} apiEndpoints={apiEndpoints}>
<HashRouter>
<React.Suspense fallback={loading}>
<Switch>
<Route
path="/"
name="Devices"
render={(props) => (isLoggedIn ? <TheLayout {...props} /> : <Login {...props} />)}
/>
<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

View File

@@ -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,

View File

@@ -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

View File

@@ -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>
`,
];

View File

@@ -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}
/>

View File

@@ -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;

View File

@@ -7,7 +7,7 @@
}
.scrollableBox {
height: 400px;
height: 200px;
}
.whiteIcon {

View File

@@ -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}>

View 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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -1,3 +0,0 @@
.modalTitle {
color: black;
}

View File

@@ -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,115 +84,105 @@ const DeviceConfiguration = ({ selectedDeviceId }) => {
</CRow>
</CCardHeader>
<CCardBody>
<CForm
action=""
method="post"
encType="multipart/form-data"
className="form-horizontal"
>
<CFormGroup row>
<CRow className={styles.spacedRow}>
<CCol md="3">
<CLabel>{t('common.uuid')} : </CLabel>
</CCol>
<CCol xs="12" md="9">
{device.UUID}
</CCol>
</CFormGroup>
<CFormGroup row>
</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>
</CFormGroup>
<CFormGroup row>
</CRow>
<CRow className={styles.spacedRow}>
<CCol md="3">
<CLabel>{t('configuration.type')} : </CLabel>
</CCol>
<CCol xs="12" md="9">
{device.deviceType}
</CCol>
</CFormGroup>
<CFormGroup row>
</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>
</CFormGroup>
<CFormGroup row>
</CRow>
<CRow className={styles.spacedRow}>
<CCol md="3">
<CLabel>{t('common.mac')} :</CLabel>
</CCol>
<CCol xs="12" md="9">
{device.macAddress}
</CCol>
</CFormGroup>
<CFormGroup row>
</CRow>
<CRow className={styles.spacedRow}>
<CCol md="3">
<CLabel>{t('configuration.created')} : </CLabel>
</CCol>
<CCol xs="12" md="9">
{prettyDate(device.createdTimestamp)}
</CCol>
</CFormGroup>
<CFormGroup row>
</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>
<CCol md="3">
<CLabel>{t('configuration.device_password')} : </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}
</CCol>
</CFormGroup>
<CCollapse show={collapse}>
<CFormGroup row>
</CRow>
<CRow className={styles.spacedRow}>
<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>
</CRow>
<CRow className={styles.spacedRow}>
<CCol md="3">
<CLabel>{t('configuration.owner')} :</CLabel>
</CCol>
<CCol xs="12" md="9">
{device.owner}
</CCol>
</CFormGroup>
<CFormGroup row>
</CRow>
<CRow className={styles.spacedRow}>
<CCol md="3">
<CLabel>{t('configuration.location')} :</CLabel>
</CCol>
<CCol xs="12" md="9">
{device.location}
</CCol>
</CFormGroup>
</CRow>
</CCollapse>
<CCardFooter>
<CButton show={collapse ? 'true' : 'false'} onClick={toggle} block>
@@ -205,7 +193,6 @@ const DeviceConfiguration = ({ selectedDeviceId }) => {
/>
</CButton>
</CCardFooter>
</CForm>
</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;

View File

@@ -5,3 +5,16 @@
.blackIcon {
color: black;
}
.modalTitle {
color: black;
}
.topPadding {
padding-top: 5px;
}
.spacedRow {
margin-top: 5px;
margin-bottom: 5px;
}

View File

@@ -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;

View File

@@ -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)}`, {
.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>
),

View File

@@ -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;

View 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;

View 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;
}

View 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);

View 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);

View 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;
}

View File

@@ -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}>

View 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;

View 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;

View File

@@ -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);
const token = getToken();
const utcDate = new Date(chosenDate);
const utcDateString = utcDate.toISOString();
useEffect(() => {
if (deviceSerialNumber !== null && show) {
const asyncGet = async () => {
const isConnected = await getDeviceConnection(
deviceSerialNumber,
currentToken,
endpoints.ucentralgw,
);
setDisableWaiting(!isConnected);
setDeviceConnected(isConnected);
};
asyncGet();
}
}, [show]);
const postUpgrade = () => {
if (formValidation()) {
setWaitingForUpgrade(true);
setBlockFields(true);
const headers = {
Accept: 'application/json',
Authorization: `Bearer ${token}`,
serialNumber: selectedDeviceId,
Authorization: `Bearer ${currentToken}`,
serialNumber: deviceSerialNumber,
};
const parameters = {
serialNumber: selectedDeviceId,
when: isNow ? 0 : dateToUnix(utcDateString),
serialNumber: deviceSerialNumber,
when: isNow ? 0 : dateToUnix(date),
uri: firmware,
};
axiosInstance
.post(`/device/${encodeURIComponent(selectedDeviceId)}/upgrade`, parameters, { headers })
.post(
`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}/upgrade`,
parameters,
{ headers },
)
.then(() => {
setResponseBody('Command submitted successfully');
setHadSuccess(true);
})
.catch(() => {
setResponseBody(t('commands.error'));
setHadFailure(true);
if (waitForUpgrade) {
setShowWaitingConsole(true);
}
})
.catch(() => {})
.finally(() => {
setCheckingIfNow(false);
setDoingNow(false);
setCheckingIfSure(false);
setWaiting(false);
setBlockFields(false);
setWaitingForUpgrade(false);
eventBus.dispatch('actionCompleted', { message: 'An action has been completed' });
});
}
};
if (showWaitingConsole) {
return (
<CModal show={show} onClose={toggleModal}>
<CModalHeader closeButton>
<CModalTitle>{t('upgrade.title')}</CModalTitle>
</CModalHeader>
<CModalBody>
<UpgradeWaitingBody serialNumber={deviceSerialNumber} />
</CModalBody>
<CModalFooter>
<CButton color="secondary" onClick={toggleModal}>
{t('common.close')}
</CButton>
</CModalFooter>
</CModal>
);
}
return (
<CModal show={show} onClose={toggleModal}>
<CModalHeader closeButton>
@@ -141,94 +156,85 @@ const FirmwareUpgradeModal = ({ show, toggleModal }) => {
</CModalHeader>
<CModalBody>
<h6>{t('upgrade.directions')}</h6>
<CRow className={styles.spacedRow}>
<CCol>
<CButton
color="primary"
onClick={() => (formValidation() ? confirmingIfNow() : null)}
disabled={waiting}
hidden={checkingIfNow}
block
>
{t('common.do_now')}
</CButton>
<CButton
color="primary"
onClick={() => (formValidation() ? postUpgrade(true) : null)}
disabled={waiting}
hidden={!checkingIfNow}
block
>
{waiting && doingNow ? t('common.loading_ellipsis') : t('common.confirm')}
<CSpinner hidden={!waiting || doingNow} component="span" size="sm" />
</CButton>
</CCol>
<CCol>
<CButton disabled={waiting} block color="primary" onClick={setDateToLate}>
{t('common.later_tonight')}
</CButton>
</CCol>
</CRow>
<CRow className={styles.spacedRow}>
<CCol md="4" className={styles.spacedColumn}>
<p>{t('upgrade.time')}</p>
<p>{t('upgrade.firmware_uri')}</p>
</CCol>
<CCol xs="12" md="8">
<DatePicker
selected={chosenDate === '' ? new Date() : new Date(chosenDate)}
value={chosenDate === '' ? new Date() : new Date(chosenDate)}
className={('form-control', { 'is-invalid': !validDate })}
includeTime
placeholder="Select custom date in UTC"
disabled={waiting}
onChange={(date) => setDate(date)}
min={new Date()}
/>
<CInvalidFeedback>{t('common.need_date')}</CInvalidFeedback>
</CCol>
</CRow>
<div>{t('upgrade.firmware_uri')}</div>
<CCol md="8">
<CInput
disabled={waiting}
disabled={blockFields}
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>
</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={new Date(date)}
value={new Date(date)}
className={('form-control', { 'is-invalid': !validDate })}
includeTime
disabled={blockFields}
onChange={(newDate) => setDate(newDate.toString())}
/>
<CInvalidFeedback>{t('common.need_date')}</CInvalidFeedback>
</CCol>
</CRow>
<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}
<ButtonFooter
isNow={isNow}
isShown={show}
isLoading={waitingForUpgrade}
action={postUpgrade}
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>
toggleParent={toggleModal}
/>
</CModal>
);
};

View 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;

View 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;

View File

@@ -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,13 +117,10 @@ const StatisticsChartList = ({ selectedDeviceId, lastRefresh }) => {
};
const getStatistics = () => {
if (!loading) {
setLoading(true);
const options = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${getToken()}`,
Authorization: `Bearer ${currentToken}`,
},
params: {
serialNumber: '24f5a207a130',
@@ -126,37 +128,36 @@ const StatisticsChartList = ({ selectedDeviceId, lastRefresh }) => {
};
axiosInstance
.get(`/device/${selectedDeviceId}/statistics?newest=true&limit=50`, options)
.get(
`${endpoints.ucentralgw}/api/v1/device/${deviceSerialNumber}/statistics?newest=true&limit=50`,
options,
)
.then((response) => {
transformIntoDataset(response.data.data);
})
.catch(() => {})
.finally(() => {
setLoading(false);
});
}
.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.interfaceList.map((data) => {
const options = {
data,
options: {
...statOptions.settings,
title: {
text: capitalizeFirstLetter(data[0].titleName),
@@ -165,21 +166,16 @@ const StatisticsChartList = ({ selectedDeviceId, lastRefresh }) => {
fontSize: '25px',
},
},
}}
/>
},
};
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);

View File

@@ -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;

View File

@@ -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 (
<div>
<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>
<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 selectedDeviceId={selectedDeviceId} lastRefresh={lastRefresh} />
<StatisticsChartList />
</CCardBody>
</CCard>
<LatestStatisticsModal show={showLatestModal} toggle={toggleLatestModal} />
</div>
);
};
DeviceStatisticsCard.propTypes = {
selectedDeviceId: PropTypes.string.isRequired,
};
export default DeviceStatisticsCard;

View File

@@ -1,7 +1,15 @@
.alignRight {
.cardOptions {
text-align: right;
}
.cardTitle {
padding-top: 10px;
}
.statsBody {
padding: 5%;
}
.modalTitle {
color: black;
}

View File

@@ -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}
/>

View File

@@ -5,3 +5,7 @@
.spacedColumn {
margin-top: 7px;
}
.spacedDate {
padding-top: 5px;
}

View 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;

View File

@@ -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,39 +79,68 @@ 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 (
<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}
disabled={blockFields}
block
color="primary"
onClick={() => setUsingDuration(true)}
@@ -130,7 +150,7 @@ const TraceModal = ({ show, toggleModal }) => {
</CCol>
<CCol>
<CButton
disabled={waiting}
disabled={blockFields}
block
color="primary"
onClick={() => setUsingDuration(false)}
@@ -145,55 +165,32 @@ const TraceModal = ({ show, toggleModal }) => {
</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
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 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
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="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>
@@ -223,6 +220,21 @@ const TraceModal = ({ show, toggleModal }) => {
</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>
@@ -231,20 +243,27 @@ const TraceModal = ({ show, toggleModal }) => {
</CModalBody>
<CModalFooter>
<LoadingButton
label="Schedule"
isLoadingLabel="Loading..."
isLoading={waiting}
label={t('trace.trace')}
isLoadingLabel={t('common.loading_ellipsis')}
isLoading={blockFields}
action={doAction}
variant="outline"
block={false}
disabled={waiting}
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>
{getBody()}
</CModal>
);
};

View File

@@ -5,3 +5,10 @@
.spacedColumn {
margin-top: 7px;
}
.centerDiv {
display: flex;
justify-content: center;
align-items: center;
height: 20px;
}

View File

@@ -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,6 +131,7 @@ const WifiScanModal = ({ show, toggleModal }) => {
<CModalTitle>{t('actions.wifi_scan')}</CModalTitle>
</CModalHeader>
<CModalBody>
<div hidden={hideOptions || waiting}>
<h6>{t('scan.directions')}</h6>
<CRow className={styles.spacedRow}>
<CCol md="3">
@@ -157,14 +165,32 @@ const WifiScanModal = ({ show, toggleModal }) => {
</CForm>
</CCol>
</CRow>
<div className={styles.spacedRow} hidden={!hadSuccess && !hadFailure}>
</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>

View File

@@ -9,3 +9,14 @@
.spacedSwitch {
padding-left: 5%;
}
.bottomSpace {
margin-bottom: 20px;
}
.centerDiv {
display: flex;
justify-content: center;
align-items: center;
height: 20px;
}

View File

@@ -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 = () => {

View File

@@ -1,11 +0,0 @@
.cardTitle {
color: black;
}
.scrollable {
height: 250px;
}
.datatable {
color: white;
}

View File

@@ -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 }) => {

View File

@@ -1,3 +1,15 @@
.modalTitle {
color: black;
}
.cardTitle {
color: black;
}
.scrollable {
height: 250px;
}
.datatable {
color: white;
}

View 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);

View 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);

View File

@@ -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>
</React.StrictMode>,
document.getElementById('root'),
);

View File

@@ -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">

View File

@@ -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;

View File

@@ -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);

View File

@@ -1,6 +1,6 @@
.sidebarImgFull {
height: 75px;
width: 75px;
height: 100px;
width: 230px;
}
.sidebarImgMinimized {

View File

@@ -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;

View File

@@ -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">
<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>
</DeviceProvider>
</div>
</>
);
};

View File

@@ -0,0 +1,3 @@
.spacedRow {
margin-top: 10px;
}

Some files were not shown because too many files have changed in this diff Show More