mirror of
				https://github.com/Telecominfraproject/wlan-cloud-ucentralgw-ui.git
				synced 2025-10-30 10:22:24 +00:00 
			
		
		
		
	Compare commits
	
		
			112 Commits
		
	
	
		
			feature/do
			...
			release/v2
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 85c7a34af5 | ||
|   | df6ec36515 | ||
|   | db7394d86c | ||
|   | 772e7cd07d | ||
|   | 54bc4ad403 | ||
|   | 8fe26a08b3 | ||
|   | 6f78768459 | ||
|   | c3c31ec4ac | ||
|   | 3dce199b79 | ||
|   | 403e8bc185 | ||
|   | 0f15ae7bef | ||
|   | 02385c5544 | ||
|   | a422d5d446 | ||
|   | 5fe05e2890 | ||
|   | d9f3406ae7 | ||
|   | 28ec0482c8 | ||
|   | 273a91f357 | ||
|   | 2c7df5abd1 | ||
|   | 09232c5640 | ||
|   | fe84f6bdb1 | ||
|   | e1727d2f91 | ||
|   | 74cac90696 | ||
|   | 4b2bde3c3a | ||
|   | c4b19a3b24 | ||
|   | 7384558ad0 | ||
|   | cc8be05594 | ||
|   | bff117e45a | ||
|   | 62b1c23b3c | ||
|   | 340d21edac | ||
|   | 73a824330a | ||
|   | 96cc7dc78b | ||
|   | b71b4cd1a5 | ||
|   | a54acfd347 | ||
|   | 4d4af822d9 | ||
|   | 3bc028fc3b | ||
|   | e1e74cb29a | ||
|   | d9a8a02cd7 | ||
|   | 4c96158f11 | ||
|   | 7162303e62 | ||
|   | 26ef27f251 | ||
|   | f2f370602f | ||
|   | 2056482179 | ||
|   | e8960368a5 | ||
|   | c017fe0774 | ||
|   | 3ea22477ae | ||
|   | 258ba197bb | ||
|   | 908a034433 | ||
|   | 101509edd6 | ||
|   | ae7200815d | ||
|   | f42b3d48d3 | ||
|   | e60a703f19 | ||
|   | 281a84f19c | ||
|   | 563b94765e | ||
|   | 9695a3ba71 | ||
|   | 5b6f0e8f78 | ||
|   | 405e8eb944 | ||
|   | 1d771a2bea | ||
|   | 895301edd3 | ||
|   | 568961c4d9 | ||
|   | ce5c25f169 | ||
|   | 0b142b0658 | ||
|   | 87b3450221 | ||
|   | 97b3716dc9 | ||
|   | c83b8eb8aa | ||
|   | d5244f8626 | ||
|   | d5156bf000 | ||
|   | ae36c31dea | ||
|   | 77224477b3 | ||
|   | 80fa5e7d43 | ||
|   | 84de2595b9 | ||
|   | c0023436e8 | ||
|   | 6593be8bdf | ||
|   | fb344db120 | ||
|   | 6cf081527f | ||
|   | dc4dca417b | ||
|   | be7a55fbc0 | ||
|   | 7b476cf38a | ||
|   | 236e2ced62 | ||
|   | e99fd9f6b2 | ||
|   | 78eb883fef | ||
|   | 16608c5fcf | ||
|   | 657f61b43e | ||
|   | 3e279aff58 | ||
|   | 6628c69d19 | ||
|   | cdcd29a47c | ||
|   | d8ab92531f | ||
|   | f6876d13fe | ||
|   | 5253009bad | ||
|   | d74e1f4172 | ||
|   | 0ad81c85f2 | ||
|   | ec8ea71dcc | ||
|   | cd4be5c7b1 | ||
|   | e2433024d7 | ||
|   | ddc877ee3d | ||
|   | 60b03d49c6 | ||
|   | 853590376b | ||
|   | cccd7b7cec | ||
|   | 68f7570720 | ||
|   | 59e1709cff | ||
|   | 39b05192fe | ||
|   | fa377132a8 | ||
|   | b3e5cd79ed | ||
|   | 6f7b05649b | ||
|   | 4861a746b4 | ||
|   | e75c163248 | ||
|   | d8b501fff0 | ||
|   | 9de6c27c69 | ||
|   | 80811a6071 | ||
|   | 814cb09e44 | ||
|   | 050a4a3b78 | ||
|   | ced06d1391 | ||
|   | 9876c98628 | 
| @@ -25,3 +25,4 @@ yarn-error.log* | |||||||
| .git | .git | ||||||
| .github | .github | ||||||
| Dockerfile | Dockerfile | ||||||
|  | helm | ||||||
|   | |||||||
| @@ -1 +1,4 @@ | |||||||
| /src/assets | /src/assets | ||||||
|  | /build | ||||||
|  | /node_modules | ||||||
|  | .github | ||||||
|   | |||||||
							
								
								
									
										34
									
								
								.eslintrc
									
									
									
									
									
								
							
							
						
						
									
										34
									
								
								.eslintrc
									
									
									
									
									
								
							| @@ -1,16 +1,11 @@ | |||||||
| { | { | ||||||
|     "parser": "babel-eslint", |   "extends": ["airbnb", "prettier"], | ||||||
|     "parserOptions": { |   "plugins": ["prettier"], | ||||||
|     "sourceType": "module", |   "env": { | ||||||
|     "allowImportExportEverywhere": false, |       "browser": true, | ||||||
|     "codeFrame": false |       "jest": true | ||||||
| }, |   }, | ||||||
| "extends": ["airbnb", "prettier"], |   "rules": { | ||||||
| "env": { |  | ||||||
|     "browser": true, |  | ||||||
|     "jest": true |  | ||||||
| }, |  | ||||||
| "rules": { |  | ||||||
|     "max-len": ["error", {"code": 150}], |     "max-len": ["error", {"code": 150}], | ||||||
|     "prefer-promise-reject-errors": ["off"], |     "prefer-promise-reject-errors": ["off"], | ||||||
|     "react/jsx-filename-extension": ["off"], |     "react/jsx-filename-extension": ["off"], | ||||||
| @@ -18,13 +13,22 @@ | |||||||
|     "no-return-assign": ["off"], |     "no-return-assign": ["off"], | ||||||
|     "react/jsx-props-no-spreading": ["off"], |     "react/jsx-props-no-spreading": ["off"], | ||||||
|     "react/destructuring-assignment": ["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": { |   "settings": { | ||||||
|     "import/resolver": { |     "import/resolver": { | ||||||
|       "node": { |       "node": { | ||||||
|         "paths": ["src"] |         "paths": ["src"] | ||||||
|       } |       } | ||||||
|     }, |     } | ||||||
|   }, |   }, | ||||||
| } |     "parser": "babel-eslint", | ||||||
|  |     "parserOptions": { | ||||||
|  |       "sourceType": "module", | ||||||
|  |       "allowImportExportEverywhere": false, | ||||||
|  |       "codeFrame": false | ||||||
|  |     } | ||||||
|  | } | ||||||
|   | |||||||
							
								
								
									
										68
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,68 @@ | |||||||
|  | name: Build Docker image | ||||||
|  |  | ||||||
|  | on: | ||||||
|  |   push: | ||||||
|  |     paths-ignore: | ||||||
|  |       - '**.md' | ||||||
|  |     branches: | ||||||
|  |       - main | ||||||
|  |       - 'release/*' | ||||||
|  |     tags: | ||||||
|  |       - 'v*' | ||||||
|  |   pull_request: | ||||||
|  |     branches: | ||||||
|  |       - main | ||||||
|  |  | ||||||
|  | defaults: | ||||||
|  |   run: | ||||||
|  |     shell: bash | ||||||
|  |  | ||||||
|  | jobs: | ||||||
|  |   docker: | ||||||
|  |     runs-on: ubuntu-20.04 | ||||||
|  |     env: | ||||||
|  |       DOCKER_REGISTRY_URL: tip-tip-wlan-cloud-ucentral.jfrog.io | ||||||
|  |       DOCKER_REGISTRY_USERNAME: ucentral | ||||||
|  |     steps: | ||||||
|  |     - uses: actions/checkout@v2 | ||||||
|  |  | ||||||
|  |     - name: Build Docker image | ||||||
|  |       run: docker build -t wlan-cloud-ucentralgw-ui:${{ github.sha }} . | ||||||
|  |  | ||||||
|  |     - name: Tag Docker image | ||||||
|  |       run: | | ||||||
|  |         TAGS="${{ github.sha }}" | ||||||
|  |  | ||||||
|  |         if [[ ${GITHUB_REF} == "refs/heads/"* ]] | ||||||
|  |         then | ||||||
|  |           CURRENT_TAG=$(echo ${GITHUB_REF#refs/heads/} | tr '/' '-') | ||||||
|  |           TAGS="$TAGS $CURRENT_TAG" | ||||||
|  |         else | ||||||
|  |           if [[ ${GITHUB_REF} == "refs/tags/"* ]] | ||||||
|  |           then | ||||||
|  |             CURRENT_TAG=$(echo ${GITHUB_REF#refs/tags/} | tr '/' '-') | ||||||
|  |             TAGS="$TAGS $CURRENT_TAG" | ||||||
|  |           else # PR build | ||||||
|  |             CURRENT_TAG=$(echo ${GITHUB_HEAD_REF#refs/heads/} | tr '/' '-') | ||||||
|  |             TAGS="$TAGS $CURRENT_TAG" | ||||||
|  |           fi | ||||||
|  |         fi | ||||||
|  |  | ||||||
|  |         echo "Result tags: $TAGS" | ||||||
|  |  | ||||||
|  |         for tag in $TAGS; do | ||||||
|  |           docker tag wlan-cloud-ucentralgw-ui:${{ github.sha }} ${{ env.DOCKER_REGISTRY_URL }}/ucentralgw-ui:$tag | ||||||
|  |         done | ||||||
|  |  | ||||||
|  |     - name: Log into Docker registry | ||||||
|  |       if: startsWith(github.ref, 'refs/tags/') || startsWith(github.ref, 'refs/pull/') || github.ref == 'refs/heads/main' | ||||||
|  |       uses: docker/login-action@v1 | ||||||
|  |       with: | ||||||
|  |         registry: ${{ env.DOCKER_REGISTRY_URL }} | ||||||
|  |         username: ${{ env.DOCKER_REGISTRY_USERNAME }} | ||||||
|  |         password: ${{ secrets.DOCKER_REGISTRY_PASSWORD }} | ||||||
|  |  | ||||||
|  |     - name: Push Docker images | ||||||
|  |       if: startsWith(github.ref, 'refs/tags/') || startsWith(github.ref, 'refs/pull/') || github.ref == 'refs/heads/main' | ||||||
|  |       run: | | ||||||
|  |         docker images | grep ${{ env.DOCKER_REGISTRY_URL }}/ucentralgw-ui | awk -F ' ' '{print $1":"$2}' | xargs -I {} docker push {} | ||||||
							
								
								
									
										19
									
								
								.github/workflows/cleanup.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								.github/workflows/cleanup.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | |||||||
|  | name: Clean up PR Docker images | ||||||
|  |  | ||||||
|  | on: | ||||||
|  |   pull_request: | ||||||
|  |     branches: | ||||||
|  |       - main | ||||||
|  |     types: [ closed ] | ||||||
|  |  | ||||||
|  | defaults: | ||||||
|  |   run: | ||||||
|  |     shell: bash | ||||||
|  |  | ||||||
|  | jobs: | ||||||
|  |   cleanup: | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     steps: | ||||||
|  |       - run: | | ||||||
|  |           export PR_BRANCH_TAG=$(echo ${GITHUB_HEAD_REF#refs/heads/} | tr '/' '-') | ||||||
|  |           curl -uucentral:${{ secrets.DOCKER_REGISTRY_PASSWORD }} -X DELETE "https://tip.jfrog.io/artifactory/tip-wlan-cloud-ucentral/ucentralgw-ui/$PR_BRANCH_TAG" | ||||||
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,4 +1,4 @@ | |||||||
| # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. |  | ||||||
|  |  | ||||||
| # dependencies | # dependencies | ||||||
| /node_modules | /node_modules | ||||||
|   | |||||||
							
								
								
									
										4
									
								
								.prettierignore
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								.prettierignore
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | |||||||
|  | /src/assets | ||||||
|  | build | ||||||
|  | node_modules | ||||||
|  | .github | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| FROM node:16-alpine3.11 AS build | FROM node:14-alpine3.11 AS build | ||||||
|  |  | ||||||
| COPY package.json package-lock.json / | COPY package.json package-lock.json / | ||||||
|  |  | ||||||
| @@ -6,9 +6,10 @@ RUN npm install | |||||||
|  |  | ||||||
| COPY . . | COPY . . | ||||||
|  |  | ||||||
| RUN echo '{"DEFAULT_GATEWAY_URL": "https://ucentral.dpaas.arilia.com:16001","ALLOW_GATEWAY_CHANGE": true}' > public/config.json \ | RUN npm run build | ||||||
|     && npm run build |  | ||||||
|  |  | ||||||
| FROM nginx:1.20.1-alpine AS runtime | FROM nginx:1.20.1-alpine AS runtime | ||||||
|  |  | ||||||
| COPY --from=build /build/ /usr/share/nginx/html/ | COPY --from=build /build/ /usr/share/nginx/html/ | ||||||
|  |  | ||||||
|  | COPY --from=build docker-entrypoint.d/40-generate-config.sh /docker-entrypoint.d/40-generate-config.sh | ||||||
|   | |||||||
							
								
								
									
										28
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								README.md
									
									
									
									
									
								
							| @@ -9,18 +9,40 @@ NOTE: This UI will be evolving as micro services are added to the uCentral progr | |||||||
| ## Running the solution | ## Running the solution | ||||||
|  |  | ||||||
| ### Development | ### 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 | git clone https://github.com/Telecominfraproject/wlan-cloud-ucentralgw-ui | ||||||
| cd wlan-cloud-ucentralgw-ui | cd wlan-cloud-ucentralgw-ui | ||||||
| npm install | npm install | ||||||
| npm start | 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 | ### 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 | git clone https://github.com/Telecominfraproject/wlan-cloud-ucentralgw-ui | ||||||
| cd wlan-cloud-ucentralgw-ui | cd wlan-cloud-ucentralgw-ui | ||||||
|  | npm install | ||||||
| npm run build | npm run build | ||||||
| ``` | ``` | ||||||
| Once the build is done, you can move the `build` folder on your server. | Once the build is done, you can move the `build` folder on your server. | ||||||
|  |  | ||||||
|  | ### Configuration | ||||||
|  | You must change the `config.json` file in `public` directory to point to your uCentral Security Service URL (uCentralSec). You may also limit the ability for users to change the default uCentralSec. If you do not allow a uCentralSec change, the uCentralSec URL will not appear on the login screen.  | ||||||
|  |  | ||||||
|  | Here are the current default values:  | ||||||
|  | ``` | ||||||
|  | { | ||||||
|  |   "DEFAULT_UCENTRALSEC_URL": "https://ucentral.dpaas.arilia.com:16001", | ||||||
|  |   "ALLOW_UCENTRALSEC_CHANGE": false | ||||||
|  | } | ||||||
|  | ``` | ||||||
|   | |||||||
							
								
								
									
										7
									
								
								babel.config.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								babel.config.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | |||||||
|  | { | ||||||
|  |   "presets": [ | ||||||
|  |     "@babel/preset-env", | ||||||
|  |     "@babel/preset-react" | ||||||
|  |   ], | ||||||
|  |   "plugins": ["@babel/plugin-proposal-class-properties"] | ||||||
|  | } | ||||||
							
								
								
									
										12
									
								
								config/paths.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								config/paths.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | const path = require('path'); | ||||||
|  |  | ||||||
|  | module.exports = { | ||||||
|  |   // Source files | ||||||
|  |   src: path.resolve(__dirname, '../src'), | ||||||
|  |  | ||||||
|  |   // Production build files | ||||||
|  |   build: path.resolve(__dirname, '../build'), | ||||||
|  |  | ||||||
|  |   // Static files that get copied to build folder | ||||||
|  |   public: path.resolve(__dirname, '../public'), | ||||||
|  | }; | ||||||
							
								
								
									
										76
									
								
								config/webpack.common.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								config/webpack.common.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,76 @@ | |||||||
|  | /* eslint-disable import/no-extraneous-dependencies */ | ||||||
|  | /* eslint-disable prefer-template */ | ||||||
|  | const { CleanWebpackPlugin } = require('clean-webpack-plugin'); | ||||||
|  | const CopyWebpackPlugin = require('copy-webpack-plugin'); | ||||||
|  | const HtmlWebpackPlugin = require('html-webpack-plugin'); | ||||||
|  | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); | ||||||
|  | const path = require('path'); | ||||||
|  | const paths = require('./paths'); | ||||||
|  |  | ||||||
|  | module.exports = { | ||||||
|  |   entry: [paths.src + '/index.js'], | ||||||
|  |   output: { | ||||||
|  |     path: paths.build, | ||||||
|  |     filename: '[name].bundle.js', | ||||||
|  |     publicPath: '/', | ||||||
|  |   }, | ||||||
|  |   resolve: { | ||||||
|  |     modules: [path.resolve('./node_modules'), path.resolve('./src')], | ||||||
|  |     preferRelative: true, | ||||||
|  |   }, | ||||||
|  |   plugins: [ | ||||||
|  |     new CleanWebpackPlugin(), | ||||||
|  |  | ||||||
|  |     new MiniCssExtractPlugin({ | ||||||
|  |       filename: 'styles/[name].[contenthash].css', | ||||||
|  |       chunkFilename: '[id].[contenthash].css', | ||||||
|  |     }), | ||||||
|  |     new CopyWebpackPlugin({ | ||||||
|  |       patterns: [ | ||||||
|  |         { | ||||||
|  |           from: paths.src + '/assets', | ||||||
|  |           to: 'assets', | ||||||
|  |           globOptions: { | ||||||
|  |             ignore: ['*.DS_Store'], | ||||||
|  |           }, | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           from: paths.public + '/locales', | ||||||
|  |           to: 'locales', | ||||||
|  |           globOptions: { | ||||||
|  |             ignore: ['*.DS_Store'], | ||||||
|  |           }, | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           from: paths.public + '/config.json', | ||||||
|  |           to: 'config.json', | ||||||
|  |         }, | ||||||
|  |       ], | ||||||
|  |     }), | ||||||
|  |     new HtmlWebpackPlugin({ | ||||||
|  |       title: 'uCentralGW', | ||||||
|  |       favicon: paths.public + '/favicon.ico', | ||||||
|  |       template: paths.public + '/index.html', | ||||||
|  |       filename: 'index.html', | ||||||
|  |     }), | ||||||
|  |   ], | ||||||
|  |  | ||||||
|  |   module: { | ||||||
|  |     rules: [ | ||||||
|  |       { | ||||||
|  |         test: /\.(js|jsx)$/, | ||||||
|  |         exclude: /node_modules/, | ||||||
|  |         use: ['babel-loader'], | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         test: /\.(css|scss)$/, | ||||||
|  |         use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'], | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         test: /\.svg$/, | ||||||
|  |         use: ['@svgr/webpack'], | ||||||
|  |       }, | ||||||
|  |       { test: /\.(?:ico|gif|png|jpg|jpeg)$/i, type: 'asset/resource' }, | ||||||
|  |     ], | ||||||
|  |   }, | ||||||
|  | }; | ||||||
							
								
								
									
										53
									
								
								config/webpack.dev.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								config/webpack.dev.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | |||||||
|  | /* eslint-disable import/no-extraneous-dependencies */ | ||||||
|  | /* eslint-disable prefer-template */ | ||||||
|  | const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin'); | ||||||
|  | const { merge } = require('webpack-merge'); | ||||||
|  | const path = require('path'); | ||||||
|  | const paths = require('./paths'); | ||||||
|  | const common = require('./webpack.common'); | ||||||
|  |  | ||||||
|  | module.exports = merge(common, { | ||||||
|  |   mode: 'development', | ||||||
|  |  | ||||||
|  |   target: 'web', | ||||||
|  |  | ||||||
|  |   devtool: 'inline-source-map', | ||||||
|  |  | ||||||
|  |   devServer: { | ||||||
|  |     historyApiFallback: true, | ||||||
|  |     contentBase: paths.build, | ||||||
|  |     open: true, | ||||||
|  |     compress: false, | ||||||
|  |     hot: true, | ||||||
|  |     port: 3000, | ||||||
|  |   }, | ||||||
|  |   module: { | ||||||
|  |     rules: [ | ||||||
|  |       { | ||||||
|  |         test: /\.[js]sx?$/, | ||||||
|  |         exclude: /node_modules/, | ||||||
|  |         use: [ | ||||||
|  |           { | ||||||
|  |             loader: require.resolve('babel-loader'), | ||||||
|  |             options: { | ||||||
|  |               plugins: [require.resolve('react-refresh/babel')], | ||||||
|  |             }, | ||||||
|  |           }, | ||||||
|  |         ], | ||||||
|  |       }, | ||||||
|  |     ], | ||||||
|  |   }, | ||||||
|  |   resolve: { | ||||||
|  |     modules: [ | ||||||
|  |       'node_modules', | ||||||
|  |       'src', | ||||||
|  |       path.resolve(__dirname, '../', 'node_modules', 'ucentral-libs', 'src'), | ||||||
|  |     ], | ||||||
|  |     alias: { | ||||||
|  |       react: path.resolve(__dirname, '../', 'node_modules', 'react'), | ||||||
|  |       'react-router-dom': path.resolve('./node_modules/react-router-dom'), | ||||||
|  |       'ucentral-libs': path.resolve(__dirname, '../', 'node_modules', 'ucentral-libs', 'src'), | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  |   plugins: [new ReactRefreshWebpackPlugin()], | ||||||
|  | }); | ||||||
							
								
								
									
										36
									
								
								config/webpack.prod.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								config/webpack.prod.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | |||||||
|  | /* eslint-disable import/no-extraneous-dependencies */ | ||||||
|  | /* eslint-disable prefer-template */ | ||||||
|  | const { merge } = require('webpack-merge'); | ||||||
|  | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); | ||||||
|  | const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); | ||||||
|  | const TerserPlugin = require('terser-webpack-plugin'); | ||||||
|  | const paths = require('./paths'); | ||||||
|  | const common = require('./webpack.common'); | ||||||
|  |  | ||||||
|  | module.exports = merge(common, { | ||||||
|  |   mode: 'production', | ||||||
|  |   devtool: false, | ||||||
|  |   output: { | ||||||
|  |     path: paths.build, | ||||||
|  |     publicPath: '/', | ||||||
|  |     filename: 'js/[name].[contenthash].bundle.js', | ||||||
|  |   }, | ||||||
|  |   plugins: [ | ||||||
|  |     new MiniCssExtractPlugin({ | ||||||
|  |       filename: 'styles/[name].[contenthash].css', | ||||||
|  |       chunkFilename: '[contenthash].css', | ||||||
|  |     }), | ||||||
|  |   ], | ||||||
|  |   module: { | ||||||
|  |     rules: [], | ||||||
|  |   }, | ||||||
|  |   optimization: { | ||||||
|  |     minimize: true, | ||||||
|  |     minimizer: [`...`, new TerserPlugin(), new CssMinimizerPlugin()], | ||||||
|  |   }, | ||||||
|  |   performance: { | ||||||
|  |     hints: false, | ||||||
|  |     maxEntrypointSize: 512000, | ||||||
|  |     maxAssetSize: 512000, | ||||||
|  |   }, | ||||||
|  | }); | ||||||
							
								
								
									
										6
									
								
								docker-entrypoint.d/40-generate-config.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										6
									
								
								docker-entrypoint.d/40-generate-config.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | #!/bin/ash | ||||||
|  | # Check if variables are set | ||||||
|  | export DEFAULT_UCENTRALSEC_URL="${DEFAULT_UCENTRALSEC_URL:-https://ucentral.dpaas.arilia.com:16001}" | ||||||
|  | export ALLOW_UCENTRALSEC_CHANGE="${ALLOW_UCENTRALSEC_CHANGE:-false}" | ||||||
|  |  | ||||||
|  | echo '{"DEFAULT_UCENTRALSEC_URL": "'$DEFAULT_UCENTRALSEC_URL'","ALLOW_UCENTRALSEC_CHANGE": '$ALLOW_UCENTRALSEC_CHANGE'}' > /usr/share/nginx/html/config.json | ||||||
							
								
								
									
										1
									
								
								helm/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								helm/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | *.swp | ||||||
							
								
								
									
										22
									
								
								helm/.helmignore
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								helm/.helmignore
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | |||||||
|  | # Patterns to ignore when building packages. | ||||||
|  | # This supports shell glob matching, relative path matching, and | ||||||
|  | # negation (prefixed with !). Only one pattern per line. | ||||||
|  | .DS_Store | ||||||
|  | # Common VCS dirs | ||||||
|  | .git/ | ||||||
|  | .gitignore | ||||||
|  | .bzr/ | ||||||
|  | .bzrignore | ||||||
|  | .hg/ | ||||||
|  | .hgignore | ||||||
|  | .svn/ | ||||||
|  | # Common backup files | ||||||
|  | *.swp | ||||||
|  | *.bak | ||||||
|  | *.tmp | ||||||
|  | *~ | ||||||
|  | # Various IDEs | ||||||
|  | .project | ||||||
|  | .idea/ | ||||||
|  | *.tmproj | ||||||
|  | .vscode/ | ||||||
							
								
								
									
										5
									
								
								helm/Chart.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								helm/Chart.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | apiVersion: v1 | ||||||
|  | appVersion: "1.0" | ||||||
|  | description: A Helm chart for Kubernetes | ||||||
|  | name: ucentralgwui | ||||||
|  | version: 0.1.0 | ||||||
							
								
								
									
										82
									
								
								helm/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								helm/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,82 @@ | |||||||
|  | # ucentralgwui | ||||||
|  |  | ||||||
|  | This Helm chart helps to deploy uCentralGW-UI to the Kubernetes clusters. It is mainly used in [assembly chart](https://github.com/Telecominfraproject/wlan-cloud-ucentral-deploy/tree/main/chart) as uCentralGW-UI requires other services as dependencies that are considered in that Helm chart. This chart is purposed to define deployment logic close to the application code itself and define default values that could be overriden during deployment. | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ## TL;DR; | ||||||
|  |  | ||||||
|  | ```bash | ||||||
|  | $ helm install . | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ## Introduction | ||||||
|  |  | ||||||
|  | This chart bootstraps an ucentralgwui on a [Kubernetes](http://kubernetes.io) cluster using the [Helm](https://helm.sh) package manager. | ||||||
|  |  | ||||||
|  | ## Installing the Chart | ||||||
|  |  | ||||||
|  | Currently this chart is not assembled in charts archives, so [helm-git](https://github.com/aslafy-z/helm-git) is required for remote the installation | ||||||
|  |  | ||||||
|  | To install the chart with the release name `my-release`: | ||||||
|  |  | ||||||
|  | ```bash | ||||||
|  | $ helm install --name my-release git+https://github.com/Telecominfraproject/wlan-cloud-ucentralgw-ui@helm?ref=main | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | The command deploys ucentralgwui on the Kubernetes cluster in the default configuration. The [configuration](#configuration) section lists the parameters that can be configured during installation. | ||||||
|  |  | ||||||
|  | > **Tip**: List all releases using `helm list` | ||||||
|  |  | ||||||
|  | ## Uninstalling the Chart | ||||||
|  |  | ||||||
|  | To uninstall/delete the `my-release` deployment: | ||||||
|  |  | ||||||
|  | ```bash | ||||||
|  | $ helm delete my-release | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | The command removes all the Kubernetes components associated with the chart and deletes the release. | ||||||
|  |  | ||||||
|  | ## Configuration | ||||||
|  |  | ||||||
|  | The following table lists the configurable parameters of the chart and their default values. If Default value is not listed in the table, please refer to the [Values](values.yaml) files for details. | ||||||
|  |  | ||||||
|  | | Parameter | Type | Description | Default | | ||||||
|  | |-----------|------|-------------|---------| | ||||||
|  | | replicaCount | number | Amount of replicas to be deployed | `1` | | ||||||
|  | | nameOverride | string | Override to be used for application deployment |  | | ||||||
|  | | fullnameOverride | string | Override to be used for application deployment (has priority over nameOverride) |  | | ||||||
|  | | images.ucentralgwui.repository | string | Docker image repository |  | | ||||||
|  | | images.ucentralgwui.tag | string | Docker image tag | `'master'` | | ||||||
|  | | images.ucentralgwui.pullPolicy | string | Docker image pull policy | `'Always'` | | ||||||
|  | | services.ucentralgwui.type | string | uCentralGW-UI service type | `'ClusterIP'` | | ||||||
|  | | services.ucentralgwui.ports.http.servicePort | number | Websocket endpoint port to be exposed on service | `80` | | ||||||
|  | | services.ucentralgwui.ports.http.targetPort | number | Websocket endpoint port to be targeted by service | `80` | | ||||||
|  | | services.ucentralgwui.ports.http.protocol | string | Websocket endpoint protocol | `'TCP'` | | ||||||
|  | | checks.ucentralgwui.liveness.httpGet.path | string | Liveness check path to be used | `'/'` | | ||||||
|  | | checks.ucentralgwui.liveness.httpGet.port | number | Liveness check port to be used (should be pointint to ALB endpoint) | `http` | | ||||||
|  | | checks.ucentralgwui.readiness.httpGet.path | string | Readiness check path to be used | `'/'` | | ||||||
|  | | checks.ucentralgwui.readiness.httpGet.port | number | Readiness check port to be used | `http` | | ||||||
|  | | ingresses.default.enabled | boolean | Defines if uCentralGW-UI should be exposed via Ingress controller | `False` | | ||||||
|  | | ingresses.default.hosts | array | List of hosts for exposed uCentralGW-UI |  | | ||||||
|  | | ingresses.default.paths | array | List of paths to be exposed for uCentralGW-UI |  | | ||||||
|  | | public_env_variables | hash | Defines list of environment variables to be passed to uCentralGW-UI (required for application configuration) | | | ||||||
|  |  | ||||||
|  |  | ||||||
|  | Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example, | ||||||
|  |  | ||||||
|  | ```bash | ||||||
|  | $ helm install --name my-release \ | ||||||
|  |   --set replicaCount=1 \ | ||||||
|  |     . | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | The above command sets that only 1 instance of your app should be running | ||||||
|  |  | ||||||
|  | Alternatively, a YAML file that specifies the values for the parameters can be provided while installing the chart. For example, | ||||||
|  |  | ||||||
|  | ```bash | ||||||
|  | $ helm install --name my-release -f values.yaml . | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | > **Tip**: You can use the default [values.yaml](values.yaml) as a base for customization. | ||||||
							
								
								
									
										32
									
								
								helm/templates/_helpers.tpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								helm/templates/_helpers.tpl
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | |||||||
|  | {{/* vim: set filetype=mustache: */}} | ||||||
|  | {{/* | ||||||
|  | Expand the name of the chart. | ||||||
|  | */}} | ||||||
|  | {{- define "ucentralgwui.name" -}} | ||||||
|  | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} | ||||||
|  | {{- end -}} | ||||||
|  |  | ||||||
|  | {{/* | ||||||
|  | Create a default fully qualified app name. | ||||||
|  | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). | ||||||
|  | If release name contains chart name it will be used as a full name. | ||||||
|  | */}} | ||||||
|  | {{- define "ucentralgwui.fullname" -}} | ||||||
|  | {{- if .Values.fullnameOverride -}} | ||||||
|  | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} | ||||||
|  | {{- else -}} | ||||||
|  | {{- $name := default .Chart.Name .Values.nameOverride -}} | ||||||
|  | {{- if contains $name .Release.Name -}} | ||||||
|  | {{- .Release.Name | trunc 63 | trimSuffix "-" -}} | ||||||
|  | {{- else -}} | ||||||
|  | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} | ||||||
|  | {{- end -}} | ||||||
|  | {{- end -}} | ||||||
|  | {{- end -}} | ||||||
|  |  | ||||||
|  | {{/* | ||||||
|  | Create chart name and version as used by the chart label. | ||||||
|  | */}} | ||||||
|  | {{- define "ucentralgwui.chart" -}} | ||||||
|  | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} | ||||||
|  | {{- end -}} | ||||||
							
								
								
									
										86
									
								
								helm/templates/deployment.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								helm/templates/deployment.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,86 @@ | |||||||
|  | {{- $root := . -}} | ||||||
|  | --- | ||||||
|  | apiVersion: apps/v1 | ||||||
|  | kind: Deployment | ||||||
|  | metadata: | ||||||
|  |   name: {{ include "ucentralgwui.fullname" . }} | ||||||
|  |   labels: | ||||||
|  |     app.kubernetes.io/name: {{ include "ucentralgwui.name" . }} | ||||||
|  |     helm.sh/chart: {{ include "ucentralgwui.chart" . }} | ||||||
|  |     app.kubernetes.io/instance: {{ .Release.Name }} | ||||||
|  |     app.kubernetes.io/managed-by: {{ .Release.Service }} | ||||||
|  | spec: | ||||||
|  |   replicas: {{ .Values.replicaCount }} | ||||||
|  |   selector: | ||||||
|  |     matchLabels: | ||||||
|  |       app.kubernetes.io/name: {{ include "ucentralgwui.name" . }} | ||||||
|  |       app.kubernetes.io/instance: {{ .Release.Name }} | ||||||
|  |       {{- with .Values.services.ucentralgwui.labels }} | ||||||
|  |       {{- toYaml . | nindent 6 }} | ||||||
|  |       {{- end }} | ||||||
|  |   template: | ||||||
|  |     metadata: | ||||||
|  |       labels: | ||||||
|  |         app.kubernetes.io/name: {{ include "ucentralgwui.name" . }} | ||||||
|  |         app.kubernetes.io/instance: {{ .Release.Name }} | ||||||
|  |         {{- with .Values.services.ucentralgwui.labels }} | ||||||
|  |         {{- toYaml . | nindent 8 }} | ||||||
|  |         {{- end }} | ||||||
|  |     spec: | ||||||
|  |  | ||||||
|  |       containers: | ||||||
|  |  | ||||||
|  |         - name: ucentralgwui | ||||||
|  |           image: "{{ .Values.images.ucentralgwui.repository }}:{{ .Values.images.ucentralgwui.tag }}" | ||||||
|  |           imagePullPolicy: {{ .Values.images.ucentralgwui.pullPolicy }} | ||||||
|  |  | ||||||
|  |           env: | ||||||
|  |             - name: KUBERNETES_DEPLOYED | ||||||
|  |               value: "{{ now }}" | ||||||
|  |           {{- range $key, $value := .Values.public_env_variables }} | ||||||
|  |             - name: {{ $key }} | ||||||
|  |               value: {{ $value | quote }} | ||||||
|  |           {{- end }} | ||||||
|  |  | ||||||
|  |           ports: | ||||||
|  |           {{- range $key, $value := .Values.services.ucentralgwui.ports }} | ||||||
|  |             - name: {{ $key }} | ||||||
|  |               containerPort: {{ $value.targetPort }} | ||||||
|  |               protocol: {{ $value.protocol }} | ||||||
|  |           {{- end }} | ||||||
|  |  | ||||||
|  |           {{- if .Values.checks.ucentralgwui.liveness }} | ||||||
|  |           livenessProbe: | ||||||
|  |             {{- toYaml .Values.checks.ucentralgwui.liveness | nindent 12 }} | ||||||
|  |           {{- end }} | ||||||
|  |           {{- if .Values.checks.ucentralgwui.readiness }} | ||||||
|  |           readinessProbe: | ||||||
|  |             {{- toYaml .Values.checks.ucentralgwui.readiness | nindent 12 }} | ||||||
|  |           {{- end }} | ||||||
|  |  | ||||||
|  |           {{- with .Values.resources }} | ||||||
|  |           resources: | ||||||
|  |             {{- toYaml . | nindent 12 }} | ||||||
|  |           {{- end }} | ||||||
|  |  | ||||||
|  |       imagePullSecrets: | ||||||
|  |       {{- range $image, $imageValue := .Values.images }} | ||||||
|  |         {{- if $imageValue.regcred }} | ||||||
|  |       - name: {{ include "ucentralgwui.fullname" $root }}-{{ $image }}-regcred | ||||||
|  |         {{- end }} | ||||||
|  |       {{- end }} | ||||||
|  |  | ||||||
|  |       {{- with .Values.nodeSelector }} | ||||||
|  |       nodeSelector: | ||||||
|  |         {{- toYaml . | nindent 8 }} | ||||||
|  |       {{- end }} | ||||||
|  |  | ||||||
|  |       {{- with .Values.affinity }} | ||||||
|  |       affinity: | ||||||
|  |         {{- toYaml . | nindent 8 }} | ||||||
|  |       {{- end }} | ||||||
|  |  | ||||||
|  |       {{- with .Values.tolerations }} | ||||||
|  |       tolerations: | ||||||
|  |         {{- toYaml . | nindent 8 }} | ||||||
|  |       {{- end }} | ||||||
							
								
								
									
										47
									
								
								helm/templates/ingress.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								helm/templates/ingress.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | |||||||
|  | {{- $root := . -}} | ||||||
|  | {{- range $ingress, $ingressValue := .Values.ingresses }} | ||||||
|  | {{- if $ingressValue.enabled }} | ||||||
|  | --- | ||||||
|  | apiVersion: extensions/v1beta1 | ||||||
|  | kind: Ingress | ||||||
|  | metadata: | ||||||
|  |   name: {{ include "ucentralgwui.fullname" $root }}-{{ $ingress }} | ||||||
|  |   labels: | ||||||
|  |     app.kubernetes.io/name: {{ include "ucentralgwui.name" $root }} | ||||||
|  |     helm.sh/chart: {{ include "ucentralgwui.chart" $root }} | ||||||
|  |     app.kubernetes.io/instance: {{ $root.Release.Name }} | ||||||
|  |     app.kubernetes.io/managed-by: {{ $root.Release.Service }} | ||||||
|  |   {{- with $ingressValue.annotations }} | ||||||
|  |   annotations: | ||||||
|  |     {{- toYaml . | nindent 4 }} | ||||||
|  |   {{- end }} | ||||||
|  |  | ||||||
|  | spec: | ||||||
|  |  | ||||||
|  | {{- if $ingressValue.tls }} | ||||||
|  |   tls: | ||||||
|  |   {{- range $ingressValue.tls }} | ||||||
|  |     - hosts: | ||||||
|  |       {{- range .hosts }} | ||||||
|  |         - {{ . | quote }} | ||||||
|  |       {{- end }} | ||||||
|  |       secretName: {{ tpl .secretName $root }} | ||||||
|  |   {{- end }} | ||||||
|  | {{- end }} | ||||||
|  |  | ||||||
|  |   rules: | ||||||
|  |   {{- range $ingressValue.hosts }} | ||||||
|  |   - host: {{ . | quote }} | ||||||
|  |     http: | ||||||
|  |       paths: | ||||||
|  |       {{- range $ingressValue.paths }} | ||||||
|  |         - path: {{ .path }} | ||||||
|  |           backend: | ||||||
|  |             serviceName: {{ include "ucentralgwui.fullname" $root }}-{{ .serviceName }} | ||||||
|  |             servicePort: {{ .servicePort }} | ||||||
|  |       {{- end }} | ||||||
|  |   {{- end }} | ||||||
|  |  | ||||||
|  | {{- end }} | ||||||
|  |  | ||||||
|  | {{- end }} | ||||||
							
								
								
									
										21
									
								
								helm/templates/secret-regcred.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								helm/templates/secret-regcred.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | |||||||
|  | {{- define "imagePullSecret" }} | ||||||
|  | {{- printf "{\"auths\": {\"%s\": {\"auth\": \"%s\"}}}" .registry (printf "%s:%s" .username .password | b64enc) | b64enc }} | ||||||
|  | {{- end }} | ||||||
|  | {{- $root := . -}} | ||||||
|  | {{- range $image, $imageValue := .Values.images }} | ||||||
|  | {{- if $imageValue.regcred }} | ||||||
|  | --- | ||||||
|  | apiVersion: v1 | ||||||
|  | kind: Secret | ||||||
|  | type: kubernetes.io/dockerconfigjson | ||||||
|  | metadata: | ||||||
|  |   labels: | ||||||
|  |     app.kuberentes.io/name: {{ include "ucentralgwui.name" $root }} | ||||||
|  |     helm.sh/chart: {{ include "ucentralgwui.chart" $root }} | ||||||
|  |     app.kubernetes.io/instance: {{ $root.Release.Name }} | ||||||
|  |     app.kubernetes.io/managed-by: {{ $root.Release.Service }} | ||||||
|  |   name: {{ include "ucentralgwui.fullname" $root }}-{{ $image }}-regcred | ||||||
|  | data: | ||||||
|  |   .dockerconfigjson: {{ template "imagePullSecret" $imageValue.regcred }} | ||||||
|  | {{- end }} | ||||||
|  | {{- end }} | ||||||
							
								
								
									
										48
									
								
								helm/templates/service.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								helm/templates/service.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | |||||||
|  | {{- $root := . -}} | ||||||
|  | {{- range $service, $serviceValue := .Values.services }} | ||||||
|  | --- | ||||||
|  | apiVersion: v1 | ||||||
|  | kind: Service | ||||||
|  | metadata: | ||||||
|  |   name: {{ include "ucentralgwui.fullname" $root }}-{{ $service }} | ||||||
|  |   {{- with $serviceValue.annotations }} | ||||||
|  |   annotations: | ||||||
|  |     {{- toYaml . | nindent 4 }} | ||||||
|  |   {{- end }} | ||||||
|  |   labels: | ||||||
|  |     app.kubernetes.io/name: {{ include "ucentralgwui.name" $root }} | ||||||
|  |     helm.sh/chart: {{ include "ucentralgwui.chart" $root }} | ||||||
|  |     app.kubernetes.io/instance: {{ $root.Release.Name }} | ||||||
|  |     app.kubernetes.io/managed-by: {{ $root.Release.Service }} | ||||||
|  |  | ||||||
|  |     {{- with $serviceValue.labels }} | ||||||
|  |     {{- toYaml . | nindent 4 }} | ||||||
|  |     {{- end }} | ||||||
|  |  | ||||||
|  |     {{- if $serviceValue.serviceMonitor }} | ||||||
|  |  | ||||||
|  |     {{- range $selector, $selectorValue := $serviceValue.serviceMonitor.serviceSelector }} | ||||||
|  |     {{ $selector }}: {{ tpl $selectorValue $root }} | ||||||
|  |     {{- end }} | ||||||
|  |     {{- end }} | ||||||
|  | spec: | ||||||
|  |   type: {{ $serviceValue.type }} | ||||||
|  |   ports: | ||||||
|  |  | ||||||
|  |   {{- range $service_service, $service_value := $serviceValue.ports }} | ||||||
|  |     - name: {{ $service_service }} | ||||||
|  |       targetPort: {{ $service_value.targetPort }} | ||||||
|  |       protocol: {{ $service_value.protocol }} | ||||||
|  |       port: {{ $service_value.servicePort }} | ||||||
|  |       {{- if and (eq "NodePort" $serviceValue.type) $service_value.nodePort }} | ||||||
|  |       nodePort: {{ $service_value.nodePort }} | ||||||
|  |       {{- end }} | ||||||
|  |   {{- end }} | ||||||
|  |   selector: | ||||||
|  |     app.kubernetes.io/name: {{ include "ucentralgwui.name" $root }} | ||||||
|  |     app.kubernetes.io/instance: {{ $root.Release.Name }} | ||||||
|  |     {{- with $serviceValue.labels }} | ||||||
|  |     {{- toYaml . | nindent 4 }} | ||||||
|  |     {{- end }} | ||||||
|  |  | ||||||
|  | {{- end }} | ||||||
							
								
								
									
										75
									
								
								helm/values.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								helm/values.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,75 @@ | |||||||
|  | # System | ||||||
|  | replicaCount: 1 | ||||||
|  |  | ||||||
|  | nameOverride: "" | ||||||
|  | fullnameOverride: "" | ||||||
|  |  | ||||||
|  | images: | ||||||
|  |   ucentralgwui: | ||||||
|  |     repository: tip-tip-wlan-cloud-ucentral.jfrog.io/ucentralgw-ui | ||||||
|  |     tag: v2.0.0-RC1 | ||||||
|  |     pullPolicy: Always | ||||||
|  |  | ||||||
|  | services: | ||||||
|  |   ucentralgwui: | ||||||
|  |     type: ClusterIP | ||||||
|  |     ports: | ||||||
|  |       http: | ||||||
|  |         servicePort: 80 | ||||||
|  |         targetPort: 80 | ||||||
|  |         protocol: TCP | ||||||
|  |  | ||||||
|  | checks: | ||||||
|  |   ucentralgwui: | ||||||
|  |     liveness: | ||||||
|  |       httpGet: | ||||||
|  |         path: / | ||||||
|  |         port: http | ||||||
|  |     readiness: | ||||||
|  |       httpGet: | ||||||
|  |         path: / | ||||||
|  |         port: http | ||||||
|  |  | ||||||
|  | ingresses: | ||||||
|  |   default: | ||||||
|  |     enabled: false | ||||||
|  |     annotations: {} | ||||||
|  |       # kubernetes.io/ingress.class: nginx | ||||||
|  |       # kubernetes.io/tls-acme: "true" | ||||||
|  |     # tls: | ||||||
|  |     # - secretName: '{{ include "ucentralgwui.fullname" . }}-default-tls' # template may be used | ||||||
|  |     #   cert: | | ||||||
|  |     #     CERT_HERE_IN_PEM | ||||||
|  |     #   key: | | ||||||
|  |     #     KEY_HERE_IN_PEM | ||||||
|  |     #   hosts: | ||||||
|  |     #     - chart-example.local | ||||||
|  |     hosts: | ||||||
|  |     - chart-example.local | ||||||
|  |     paths: | ||||||
|  |     - path: / | ||||||
|  |       serviceName: ucentralgwui | ||||||
|  |       servicePort: http | ||||||
|  |  | ||||||
|  | resources: {} | ||||||
|  |   # We usually recommend not to specify default resources and to leave this as a conscious | ||||||
|  |   # choice for the user. This also increases chances charts run on environments with little | ||||||
|  |   # resources, such as Minikube. If you do want to specify resources, uncomment the following | ||||||
|  |   # lines, adjust them as necessary, and remove the curly braces after 'resources:'. | ||||||
|  |   # requests: | ||||||
|  |   #  cpu: 100m | ||||||
|  |   #  memory: 128Mi | ||||||
|  |   # limits: | ||||||
|  |   #  cpu: 100m | ||||||
|  |   #  memory: 128Mi | ||||||
|  |  | ||||||
|  | nodeSelector: {} | ||||||
|  |  | ||||||
|  | tolerations: [] | ||||||
|  |  | ||||||
|  | affinity: {} | ||||||
|  |  | ||||||
|  | # Application | ||||||
|  | public_env_variables: | ||||||
|  |   DEFAULT_UCENTRALSEC_URL: https://ucentral.dpaas.arilia.com:16001 | ||||||
|  |   ALLOW_UCENTRALSEC_CHANGE: false | ||||||
| @@ -4,6 +4,6 @@ | |||||||
|     "paths": { |     "paths": { | ||||||
|       "*": ["*"] |       "*": ["*"] | ||||||
|     } |     } | ||||||
|   },  |   }, | ||||||
|   "include": ["src"] |   "include": ["src"] | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										30991
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										30991
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										85
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										85
									
								
								package.json
									
									
									
									
									
								
							| @@ -1,23 +1,14 @@ | |||||||
| { | { | ||||||
|   "name": "ucentral-client", |   "name": "ucentral-client", | ||||||
|   "version": "0.9.3", |   "version": "0.9.14", | ||||||
|   "private": true, |  | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "@coreui/coreui": "^3.4.0", |     "@coreui/coreui": "^3.4.0", | ||||||
|     "@coreui/icons": "^2.0.1", |     "@coreui/icons": "^2.0.1", | ||||||
|     "@coreui/icons-react": "^1.1.0", |     "@coreui/icons-react": "^1.1.0", | ||||||
|     "@coreui/react": "^3.4.6", |     "@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", |     "apexcharts": "^3.27.1", | ||||||
|     "axios": "^0.21.1", |     "axios": "^0.21.1", | ||||||
|     "axios-retry": "^3.1.9", |     "axios-retry": "^3.1.9", | ||||||
|     "http": "^0.0.1-security", |  | ||||||
|     "https": "^1.0.0", |  | ||||||
|     "i18next": "^20.3.1", |     "i18next": "^20.3.1", | ||||||
|     "i18next-browser-languagedetector": "^6.1.2", |     "i18next-browser-languagedetector": "^6.1.2", | ||||||
|     "i18next-http-backend": "^1.2.6", |     "i18next-http-backend": "^1.2.6", | ||||||
| @@ -27,20 +18,18 @@ | |||||||
|     "react-dom": "^17.0.2", |     "react-dom": "^17.0.2", | ||||||
|     "react-i18next": "^11.11.0", |     "react-i18next": "^11.11.0", | ||||||
|     "react-paginate": "^7.1.3", |     "react-paginate": "^7.1.3", | ||||||
|     "react-redux": "^7.2.4", |  | ||||||
|     "react-router-dom": "^5.2.0", |     "react-router-dom": "^5.2.0", | ||||||
|     "react-scripts": "^4.0.3", |  | ||||||
|     "react-select": "^4.3.1", |  | ||||||
|     "react-widgets": "^5.1.1", |     "react-widgets": "^5.1.1", | ||||||
|     "redux": "^4.1.0", |  | ||||||
|     "sass": "^1.35.1", |     "sass": "^1.35.1", | ||||||
|  |     "ucentral-libs": "^0.8.7", | ||||||
|     "uuid": "^8.3.2" |     "uuid": "^8.3.2" | ||||||
|   }, |   }, | ||||||
|  |   "main": "index.js", | ||||||
|   "scripts": { |   "scripts": { | ||||||
|     "start": "react-scripts start", |     "start": "webpack serve --config config/webpack.dev.js", | ||||||
|     "build": "react-scripts build", |     "build": "webpack --config config/webpack.prod.js", | ||||||
|     "test": "react-scripts test", |     "format": "prettier --write 'src/**/*.js'", | ||||||
|     "eject": "react-scripts eject" |     "eslint-fix": "eslint --fix 'src/**/*.js'" | ||||||
|   }, |   }, | ||||||
|   "eslintConfig": { |   "eslintConfig": { | ||||||
|     "extends": "react-app" |     "extends": "react-app" | ||||||
| @@ -51,12 +40,54 @@ | |||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   "lint-staged": { |   "lint-staged": { | ||||||
|     "src/**/*.{js,jsx}": [ |     "*.{js,jsx}": [ | ||||||
|       "eslint", |       "eslint", | ||||||
|       "pretty-quick — staged", |       "prettier --write" | ||||||
|       "git add" |  | ||||||
|     ] |     ] | ||||||
|   }, |   }, | ||||||
|  |   "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": { |   "browserslist": { | ||||||
|     "production": [ |     "production": [ | ||||||
|       ">0.2%", |       ">0.2%", | ||||||
| @@ -68,17 +99,5 @@ | |||||||
|       "last 1 firefox version", |       "last 1 firefox version", | ||||||
|       "last 1 safari version" |       "last 1 safari version" | ||||||
|     ] |     ] | ||||||
|   }, |  | ||||||
|   "devDependencies": { |  | ||||||
|     "babel-eslint": "^10.1.0", |  | ||||||
|     "eslint": "^7.28.0", |  | ||||||
|     "eslint-config-airbnb": "^18.2.1", |  | ||||||
|     "eslint-config-prettier": "^8.3.0", |  | ||||||
|     "eslint-plugin-import": "^2.23.4", |  | ||||||
|     "eslint-plugin-react": "^7.24.0", |  | ||||||
|     "husky": "^6.0.0", |  | ||||||
|     "lint-staged": "^11.0.0", |  | ||||||
|     "prettier": "^2.3.1", |  | ||||||
|     "pretty-quick": "^3.1.0" |  | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| { | { | ||||||
|   "DEFAULT_GATEWAY_URL": "https://ucentral.dpaas.arilia.com:16001", |   "DEFAULT_UCENTRALSEC_URL": "https://ucentral.dpaas.arilia.com:16001", | ||||||
|   "ALLOW_GATEWAY_CHANGE": false |   "ALLOW_UCENTRALSEC_CHANGE": false | ||||||
| } | } | ||||||
|   | |||||||
| @@ -2,42 +2,13 @@ | |||||||
| <html lang="en"> | <html lang="en"> | ||||||
|   <head> |   <head> | ||||||
|     <meta charset="utf-8" /> |     <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="viewport" content="width=device-width, initial-scale=1" /> | ||||||
|     <meta name="theme-color" content="#000000" /> |     <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> |     <title>uCentralGW</title> | ||||||
|   </head> |   </head> | ||||||
|   <body> |   <body> | ||||||
|     <noscript>You need to enable JavaScript to run this app.</noscript> |     <noscript>You need to enable JavaScript to run this app.</noscript> | ||||||
|     <div id="root"></div> |     <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> |   </body> | ||||||
| </html> | </html> | ||||||
|   | |||||||
| @@ -1,82 +1,103 @@ | |||||||
| { | { | ||||||
| 	"actions": { | 	"actions": { | ||||||
| 		"blink": "Blinken", | 		"blink": "LEDs Blinken", | ||||||
| 		"configure": "Konfigurieren", | 		"configure": "Konfigurieren", | ||||||
| 		"connect": "Verbinden", | 		"connect": "Konsole öffnen", | ||||||
| 		"connecting": "Verbindung wird hergestellt ...", | 		"connecting": "Die Verbindung wird hergestellt ...", | ||||||
| 		"factory_reset": "Werkseinstellungen zurückgesetzt", | 		"factory_reset": "Auf Werkseinstellungen zurückgesetzt", | ||||||
| 		"firmware_upgrade": "Firmware-Aktualisierung", | 		"firmware_upgrade": "Firmware Aktualisierung", | ||||||
| 		"reboot": "Starten Sie neu", | 		"reboot": "Gerät neustarten", | ||||||
| 		"title": "Geräteaktionen", | 		"title": "Geräte Administrations", | ||||||
| 		"trace": "Spur", | 		"trace": "Tcpdump starten", | ||||||
| 		"wifi_scan": "WLAN-Scan" | 		"wifi_scan": "WiFi Scan" | ||||||
| 	}, | 	}, | ||||||
| 	"blink": { | 	"blink": { | ||||||
| 		"blink": "Blinken", | 		"blink": "LEDs Blinken", | ||||||
| 		"device_leds": "Geräte-LEDs", | 		"device_leds": "LEDs", | ||||||
|  | 		"execute_now": "Möchten Sie dieses Muster jetzt einstellen?", | ||||||
| 		"pattern": "Wählen Sie das Muster, das Sie verwenden möchten:", | 		"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": { | 	"commands": { | ||||||
| 		"error": "Fehler beim Senden des Befehls!", | 		"error": "Fehler beim Senden des Befehls!", | ||||||
| 		"success": "Befehl erfolgreich übermittelt", | 		"success": "Befehl wurde erfolgreich übermittelt", | ||||||
| 		"title": "Gerätebefehle" | 		"title": "Gerätebefehle" | ||||||
| 	}, | 	}, | ||||||
| 	"common": { | 	"common": { | ||||||
|  | 		"add": "Hinzufügen", | ||||||
|  | 		"adding_ellipsis": "Hinzufügen ...", | ||||||
| 		"are_you_sure": "Bist du sicher?", | 		"are_you_sure": "Bist du sicher?", | ||||||
| 		"cancel": "Stornieren", | 		"cancel": "Abbrechen", | ||||||
| 		"certificate": "Zertifikat", | 		"certificate": "Zertifikat", | ||||||
| 		"clear": "klar", | 		"clear": "Löschen", | ||||||
| 		"close": "Schließen", | 		"close": "Schließen", | ||||||
| 		"command": "Befehl", | 		"command": "Befehl", | ||||||
| 		"compatible": "kompatibel", | 		"compatible": "kompatibel", | ||||||
| 		"completed": "Abgeschlossen", | 		"completed": "Abgeschlossen", | ||||||
| 		"config_id": "Konfig. Ich würde", | 		"config_id": "Konfigurations ID", | ||||||
| 		"confirm": "Bestätigen", | 		"confirm": "Bestätigen", | ||||||
| 		"connected": "In Verbindung gebracht", | 		"connected": "Verbindung wurde hergestellt", | ||||||
| 		"copied": "kopiert!", | 		"copied": "kopiert!", | ||||||
| 		"copy_to_clipboard": "In die Zwischenablage kopieren", | 		"copy_to_clipboard": "In die Zwischenablage kopieren", | ||||||
|  | 		"created_by": "Erstellt von", | ||||||
|  | 		"custom_date": "Benutzerdefiniertes Datum", | ||||||
| 		"date": "Datum", | 		"date": "Datum", | ||||||
|  | 		"day": "tag", | ||||||
|  | 		"days": "tage", | ||||||
| 		"delete": "Löschen", | 		"delete": "Löschen", | ||||||
| 		"details": "Einzelheiten", | 		"details": "Einzelheiten", | ||||||
| 		"device_list": "Liste der Geräte", | 		"device_list": "Liste der Geräte", | ||||||
| 		"device_page": "Geräteseite", | 		"device_page": "Geräte", | ||||||
| 		"devices": "Geräte", | 		"devices": "Geräte", | ||||||
| 		"dismiss": "entlassen", | 		"dismiss": "entlassen", | ||||||
| 		"do_now": "Jetzt tun!", | 		"do_now": "Sofort", | ||||||
| 		"download": "Herunterladen", | 		"download": "Herunterladen", | ||||||
| 		"duration": "Dauer", | 		"duration": "Dauer", | ||||||
| 		"error": "Error", | 		"error": "Fehler", | ||||||
| 		"executed": "Hingerichtet", | 		"execute_now": "Möchten Sie diesen Befehl jetzt ausführen?", | ||||||
|  | 		"executed": "Ausgeführt", | ||||||
|  | 		"exit": "Ausgang", | ||||||
| 		"firmware": "Firmware", | 		"firmware": "Firmware", | ||||||
| 		"from": "Von", | 		"from": "Von", | ||||||
| 		"id": "Ich würde", | 		"hour": "stunde", | ||||||
|  | 		"hours": "std", | ||||||
|  | 		"id": "ID", | ||||||
| 		"ip_address": "IP Adresse", | 		"ip_address": "IP Adresse", | ||||||
| 		"later_tonight": "Heute Abend später", | 		"later_tonight": "Später am Abend", | ||||||
| 		"loading_ellipsis": "Wird geladen...", | 		"loading_ellipsis": "Wird geladen...", | ||||||
| 		"loading_more_ellipsis": "Mehr laden ...", | 		"loading_more_ellipsis": "Mehr laden ...", | ||||||
| 		"logout": "Ausloggen", | 		"logout": "Ausloggen", | ||||||
| 		"mac": "MAC-Adresse", | 		"mac": "MAC-Adresse", | ||||||
| 		"manufacturer": "Hersteller", | 		"manufacturer": "Hersteller", | ||||||
| 		"na": "N / A", | 		"minute": "Minute", | ||||||
|  | 		"minutes": "protokoll", | ||||||
|  | 		"na": "(unbekannt)", | ||||||
| 		"need_date": "Du brauchst ein Datum...", | 		"need_date": "Du brauchst ein Datum...", | ||||||
|  | 		"no": "Nein", | ||||||
|  | 		"no_items": "Keine Gegenstände", | ||||||
| 		"not_connected": "Nicht verbunden", | 		"not_connected": "Nicht verbunden", | ||||||
| 		"off": "aus", | 		"off": "Aus", | ||||||
| 		"on": "auf", | 		"on": "An", | ||||||
| 		"recorded": "Verzeichnet", | 		"recorded": "Verzeichnet", | ||||||
| 		"refresh": "Aktualisierung", | 		"refresh": "Aktualisierung", | ||||||
| 		"refresh_device": "Gerät aktualisieren", | 		"refresh_device": "Gerät aktualisieren", | ||||||
| 		"result": "Ergebnis", | 		"result": "Ergebnis", | ||||||
|  | 		"save": "Sparen", | ||||||
|  | 		"saving": "Speichern ...", | ||||||
| 		"schedule": "Zeitplan", | 		"schedule": "Zeitplan", | ||||||
| 		"serial_number": "Ordnungsnummer", | 		"second": "zweite", | ||||||
|  | 		"seconds": "sekunden", | ||||||
|  | 		"seconds_elapsed": "Sekunden verstrichen", | ||||||
|  | 		"serial_number": "Seriennummer", | ||||||
| 		"start": "Start", | 		"start": "Start", | ||||||
| 		"submit": "einreichen", | 		"submit": "Absenden", | ||||||
| 		"submitted": "Eingereicht", | 		"submitted": "Eingereicht", | ||||||
| 		"success": "Erfolg", | 		"success": "Erfolg", | ||||||
| 		"to": "zu", | 		"to": "zu", | ||||||
| 		"unknown": "unbekannte", | 		"unknown": "unbekannte", | ||||||
| 		"uuid": "UUID", | 		"uuid": "UUID", | ||||||
| 		"view_more": "Mehr sehen", | 		"view_more": "Mehr anzeigen", | ||||||
| 		"yes": "Ja" | 		"yes": "Ja" | ||||||
| 	}, | 	}, | ||||||
| 	"configuration": { | 	"configuration": { | ||||||
| @@ -86,18 +107,19 @@ | |||||||
| 		"last_configuration_change": "Letzte Konfigurationsänderung", | 		"last_configuration_change": "Letzte Konfigurationsänderung", | ||||||
| 		"last_configuration_download": "Letzter Konfigurations-Download", | 		"last_configuration_download": "Letzter Konfigurations-Download", | ||||||
| 		"location": "Ort", | 		"location": "Ort", | ||||||
|  | 		"note": "Hinweis", | ||||||
| 		"notes": "Anmerkungen", | 		"notes": "Anmerkungen", | ||||||
| 		"owner": "Inhaber", | 		"owner": "Inhaber", | ||||||
| 		"title": "Gerätekonfiguration", | 		"title": "Gerätekonfiguration", | ||||||
| 		"type": "Gerätetyp", | 		"type": "Gerätetyp", | ||||||
| 		"view_json": "Rohes JSON anzeigen" | 		"view_json": "Rohe Konfiguration anzeigen" | ||||||
| 	}, | 	}, | ||||||
| 	"configure": { | 	"configure": { | ||||||
| 		"choose_file": "Sie müssen eine gültige .json-Datei auswählen:", | 		"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:", | 		"enter_new": "Geben Sie die JSON für die neue Gerätekonfiguration ein:", | ||||||
| 		"placeholder": "JSON konfigurieren", | 		"placeholder": "Konfiguration", | ||||||
| 		"title": "Gerät konfigurieren", | 		"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": { | 	"delete_command": { | ||||||
| 		"explanation": "Möchten Sie diesen Befehl wirklich löschen? Diese Aktion ist nicht umkehrbar.", | 		"explanation": "Möchten Sie diesen Befehl wirklich löschen? Diese Aktion ist nicht umkehrbar.", | ||||||
| @@ -110,59 +132,97 @@ | |||||||
| 		"healthchecks_title": "Healthchecks löschen" | 		"healthchecks_title": "Healthchecks löschen" | ||||||
| 	}, | 	}, | ||||||
| 	"device_logs": { | 	"device_logs": { | ||||||
| 		"log": "Log", | 		"log": "Protokoll", | ||||||
| 		"severity": "Schwere", | 		"severity": "Wichtigkeit", | ||||||
| 		"title": "Geräteprotokolle" | 		"title": "Geräteprotokolle" | ||||||
| 	}, | 	}, | ||||||
| 	"factory_reset": { | 	"factory_reset": { | ||||||
| 		"redirector": "Weiterleitung beibehalten:", | 		"redirector": "Gatewaykonfiguration beibehalten:", | ||||||
|  | 		"reset": "Zurücksetzen", | ||||||
|  | 		"resetting": "Zurücksetzen...", | ||||||
| 		"title": "Gerät auf Werkseinstellungen zurücksetzen", | 		"title": "Gerät auf Werkseinstellungen zurücksetzen", | ||||||
| 		"warning": "Achtung: Nach dem Absenden kann dies nicht rückgängig gemacht werden" | 		"warning": "Achtung: Nach dem Absenden kann dies nicht rückgängig gemacht werden" | ||||||
| 	}, | 	}, | ||||||
| 	"footer": { | 	"footer": { | ||||||
| 		"coreui_for_react": "CoreUI für React", | 		"coreui_for_react": "CoreUI für React", | ||||||
| 		"powered_by": "Unterstützt von", | 		"powered_by": "Unterstützt von", | ||||||
| 		"version": "Ausführung" | 		"version": "Version" | ||||||
| 	}, | 	}, | ||||||
| 	"health": { | 	"health": { | ||||||
| 		"sanity": "Gesundheit", | 		"sanity": "Gesundheitzustand", | ||||||
| 		"title": "Gerätezustand" | 		"title": "Gesundheitzustand" | ||||||
| 	}, | 	}, | ||||||
| 	"login": { | 	"login": { | ||||||
| 		"login": "Anmeldung", | 		"login": "Anmeldung", | ||||||
| 		"login_error": "Anmeldefehler, bestätigen Sie, dass Ihr Benutzername, Ihr Passwort und Ihre Gateway-URL gültig sind", | 		"login_error": "Anmeldefehler, bestätigen Sie, dass Ihr Benutzername, Ihr Passwort und Ihre Gateway-URL gültig sind", | ||||||
| 		"password": "Passwort", | 		"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_password": "Bitte geben Sie Ihr Passwort ein", | ||||||
| 		"please_enter_username": "Bitte geben Sie Ihren Benutzernamen ein", | 		"please_enter_username": "Bitte geben Sie Ihren Benutzernamen ein", | ||||||
| 		"sign_in_to_account": "Melden Sie sich bei Ihrem Konto an", | 		"sign_in_to_account": "Melden Sie sich bei Ihrem Konto an", | ||||||
| 		"username": "Nutzername" | 		"url": "uCentralSec-URL", | ||||||
|  | 		"username": "Benutzername" | ||||||
| 	}, | 	}, | ||||||
| 	"reboot": { | 	"reboot": { | ||||||
| 		"directions": "Wann möchten Sie dieses Gerät neu starten?", | 		"directions": "Wann möchten Sie dieses Gerät neu starten?", | ||||||
|  | 		"now": "Möchten Sie dieses Gerät jetzt neu starten?", | ||||||
| 		"title": "Gerät neustarten" | 		"title": "Gerät neustarten" | ||||||
| 	}, | 	}, | ||||||
| 	"scan": { | 	"scan": { | ||||||
| 		"active": "Aktiven Scan aktivieren", | 		"active": "Aktiven Scan aktivieren", | ||||||
| 		"channel": "Kanal", | 		"channel": "Kanal", | ||||||
| 		"directions": "Starten Sie einen WLAN-Scan dieses Geräts, der ungefähr 25 Sekunden dauern sollte.", | 		"directions": "Starten Sie einen WiFi-Scan dieses Geräts, der ungefähr 25 Sekunden dauern sollte.", | ||||||
| 		"results": "Ergebnisse des WLAN-Scans" | 		"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": { | 	"statistics": { | ||||||
| 		"data": "Daten (KB)", | 		"data": "Daten (KB)", | ||||||
|  | 		"latest_statistics": "Neueste Statistiken", | ||||||
|  | 		"show_latest": "Neueste Statistiken anzeigen JSON", | ||||||
| 		"title": "Statistiken" | 		"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": { | 	"trace": { | ||||||
| 		"choose_network": "Netzwerk auswählen", | 		"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", | 		"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": { | 	"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", | 		"directions": "Wählen Sie eine Uhrzeit und eine Firmware-Version für dieses Gerät", | ||||||
| 		"firmware_uri": "Firmware-URI:", | 		"firmware_uri": "Firmware-URL:", | ||||||
| 		"need_uri": "Du brauchst eine URI...", | 		"need_uri": "Sie brauchen eine URL...", | ||||||
| 		"time": "Zeitpunkt der Aktualisierung:", | 		"new_version": "Neue Version ist", | ||||||
| 		"title": "Firmware-Aktualisierung" | 		"offline_device": "Diese Option ist gesperrt, da dieses Gerät nicht verbunden ist", | ||||||
|  | 		"time": "Zeitpunkt für Aktualisierung:", | ||||||
|  | 		"title": "Firmware Aktualisieren", | ||||||
|  | 		"upgrade": "Aktualisierung", | ||||||
|  | 		"wait_for_upgrade": "Möchten Sie warten, bis das Upgrade abgeschlossen ist?", | ||||||
|  | 		"waiting_for_device": "Warten, bis das Gerät wieder verbunden ist" | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -7,22 +7,26 @@ | |||||||
| 		"factory_reset": "Factory Reset", | 		"factory_reset": "Factory Reset", | ||||||
| 		"firmware_upgrade": "Firmware Upgrade", | 		"firmware_upgrade": "Firmware Upgrade", | ||||||
| 		"reboot": "Reboot", | 		"reboot": "Reboot", | ||||||
| 		"title": "Device Actions", | 		"title": "Commands", | ||||||
| 		"trace": "Trace", | 		"trace": "Trace", | ||||||
| 		"wifi_scan": "Wifi Scan" | 		"wifi_scan": "Wifi Scan" | ||||||
| 	}, | 	}, | ||||||
| 	"blink": { | 	"blink": { | ||||||
| 		"blink": "Blink", | 		"blink": "Blink", | ||||||
| 		"device_leds": "Device LEDs", | 		"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?" | 		"when_blink_leds": "When would you like to make the device LEDs blink?" | ||||||
| 	}, | 	}, | ||||||
| 	"commands": { | 	"commands": { | ||||||
| 		"error": "Error while submitting command!", | 		"error": "Error while submitting command!", | ||||||
| 		"success": "Command submitted successfully", | 		"success": "Command submitted successfully, you can look at the Commands log for the result", | ||||||
| 		"title": "Device Commands" | 		"title": "Command History" | ||||||
| 	}, | 	}, | ||||||
| 	"common": { | 	"common": { | ||||||
|  | 		"add": "Add", | ||||||
|  | 		"adding_ellipsis": "Adding...", | ||||||
| 		"are_you_sure": "Are you sure?", | 		"are_you_sure": "Are you sure?", | ||||||
| 		"cancel": "Cancel", | 		"cancel": "Cancel", | ||||||
| 		"certificate": "Certificate", | 		"certificate": "Certificate", | ||||||
| @@ -36,7 +40,11 @@ | |||||||
| 		"connected": "Connected", | 		"connected": "Connected", | ||||||
| 		"copied": "Copied!", | 		"copied": "Copied!", | ||||||
| 		"copy_to_clipboard": "Copy to clipboard", | 		"copy_to_clipboard": "Copy to clipboard", | ||||||
|  | 		"created_by": "Created By", | ||||||
|  | 		"custom_date": "Custom Date", | ||||||
| 		"date": "Date", | 		"date": "Date", | ||||||
|  | 		"day": "day", | ||||||
|  | 		"days": "days", | ||||||
| 		"delete": "Delete", | 		"delete": "Delete", | ||||||
| 		"details": "Details", | 		"details": "Details", | ||||||
| 		"device_list": "List of Devices", | 		"device_list": "List of Devices", | ||||||
| @@ -47,9 +55,13 @@ | |||||||
| 		"download": "Download", | 		"download": "Download", | ||||||
| 		"duration": "Duration", | 		"duration": "Duration", | ||||||
| 		"error": "Error", | 		"error": "Error", | ||||||
|  | 		"execute_now": "Would you like to execute this command now?", | ||||||
| 		"executed": "Executed", | 		"executed": "Executed", | ||||||
|  | 		"exit": "Exit", | ||||||
| 		"firmware": "Firmware", | 		"firmware": "Firmware", | ||||||
| 		"from": "From", | 		"from": "From", | ||||||
|  | 		"hour": "hour", | ||||||
|  | 		"hours": "hours", | ||||||
| 		"id": "Id", | 		"id": "Id", | ||||||
| 		"ip_address": "Ip Address", | 		"ip_address": "Ip Address", | ||||||
| 		"later_tonight": "Later tonight", | 		"later_tonight": "Later tonight", | ||||||
| @@ -58,8 +70,12 @@ | |||||||
| 		"logout": "Logout", | 		"logout": "Logout", | ||||||
| 		"mac": "MAC Address", | 		"mac": "MAC Address", | ||||||
| 		"manufacturer": "Manufacturer", | 		"manufacturer": "Manufacturer", | ||||||
|  | 		"minute": "minute", | ||||||
|  | 		"minutes": "minutes", | ||||||
| 		"na": "N/A", | 		"na": "N/A", | ||||||
| 		"need_date": "You need a date...", | 		"need_date": "You need a date...", | ||||||
|  | 		"no": "No", | ||||||
|  | 		"no_items": "No Items", | ||||||
| 		"not_connected": "Not Connected", | 		"not_connected": "Not Connected", | ||||||
| 		"off": "Off", | 		"off": "Off", | ||||||
| 		"on": "On", | 		"on": "On", | ||||||
| @@ -67,7 +83,12 @@ | |||||||
| 		"refresh": "Refresh", | 		"refresh": "Refresh", | ||||||
| 		"refresh_device": "Refresh Device", | 		"refresh_device": "Refresh Device", | ||||||
| 		"result": "Result", | 		"result": "Result", | ||||||
|  | 		"save": "Save", | ||||||
|  | 		"saving": "Saving... ", | ||||||
| 		"schedule": "Schedule", | 		"schedule": "Schedule", | ||||||
|  | 		"second": "second", | ||||||
|  | 		"seconds": "seconds", | ||||||
|  | 		"seconds_elapsed": "Seconds elapsed", | ||||||
| 		"serial_number": "Serial Number", | 		"serial_number": "Serial Number", | ||||||
| 		"start": "Start", | 		"start": "Start", | ||||||
| 		"submit": "Submit", | 		"submit": "Submit", | ||||||
| @@ -81,14 +102,15 @@ | |||||||
| 	}, | 	}, | ||||||
| 	"configuration": { | 	"configuration": { | ||||||
| 		"created": "Created", | 		"created": "Created", | ||||||
| 		"details": "Device Details", | 		"details": "Details", | ||||||
| 		"device_password": "Password", | 		"device_password": "Password", | ||||||
| 		"last_configuration_change": "Last Configuration Change", | 		"last_configuration_change": "Last Configuration Change", | ||||||
| 		"last_configuration_download": "Last Configuration Download", | 		"last_configuration_download": "Last Configuration Download", | ||||||
| 		"location": "Location", | 		"location": "Location", | ||||||
|  | 		"note": "Note", | ||||||
| 		"notes": "Notes", | 		"notes": "Notes", | ||||||
| 		"owner": "Owner", | 		"owner": "Owner", | ||||||
| 		"title": "Device Configuration", | 		"title": "Configuration", | ||||||
| 		"type": "Device Type", | 		"type": "Device Type", | ||||||
| 		"view_json": "View raw JSON" | 		"view_json": "View raw JSON" | ||||||
| 	}, | 	}, | ||||||
| @@ -96,7 +118,7 @@ | |||||||
| 		"choose_file": "You need to choose a valid .json file: ", | 		"choose_file": "You need to choose a valid .json file: ", | ||||||
| 		"enter_new": "Enter new device configuration JSON: ", | 		"enter_new": "Enter new device configuration JSON: ", | ||||||
| 		"placeholder": "Config JSON", | 		"placeholder": "Config JSON", | ||||||
| 		"title": "Configure Device", | 		"title": "Configure", | ||||||
| 		"valid_json": "You need to enter valid JSON" | 		"valid_json": "You need to enter valid JSON" | ||||||
| 	}, | 	}, | ||||||
| 	"delete_command": { | 	"delete_command": { | ||||||
| @@ -112,11 +134,13 @@ | |||||||
| 	"device_logs": { | 	"device_logs": { | ||||||
| 		"log": "Log", | 		"log": "Log", | ||||||
| 		"severity": "Severity", | 		"severity": "Severity", | ||||||
| 		"title": "Device Logs" | 		"title": "Logs" | ||||||
| 	}, | 	}, | ||||||
| 	"factory_reset": { | 	"factory_reset": { | ||||||
| 		"redirector": "Keep redirector: ", | 		"redirector": "Keep redirector: ", | ||||||
| 		"title": "Factory Reset Device", | 		"reset": "Reset", | ||||||
|  | 		"resetting": "Resetting... ", | ||||||
|  | 		"title": "Factory Reset", | ||||||
| 		"warning": "Warning: Once you submit this cannot be reverted" | 		"warning": "Warning: Once you submit this cannot be reverted" | ||||||
| 	}, | 	}, | ||||||
| 	"footer": { | 	"footer": { | ||||||
| @@ -126,43 +150,79 @@ | |||||||
| 	}, | 	}, | ||||||
| 	"health": { | 	"health": { | ||||||
| 		"sanity": "Sanity", | 		"sanity": "Sanity", | ||||||
| 		"title": "Device Health" | 		"title": "Health" | ||||||
| 	}, | 	}, | ||||||
| 	"login": { | 	"login": { | ||||||
| 		"login": "Login", | 		"login": "Login", | ||||||
| 		"login_error": "Login error, confirm that your username, password and gateway url are valid", | 		"login_error": "Login error, confirm that your username, password and gateway url are valid", | ||||||
| 		"password": "Password", | 		"password": "Password", | ||||||
| 		"please_enter_gateway": "Please enter a gateway URL", | 		"please_enter_gateway": "Please enter a uCentralSec URL", | ||||||
| 		"please_enter_password": "Please enter your password", | 		"please_enter_password": "Please enter your password", | ||||||
| 		"please_enter_username": "Please enter your username", | 		"please_enter_username": "Please enter your username", | ||||||
| 		"sign_in_to_account": "Sign in to your account", | 		"sign_in_to_account": "Sign in to your account", | ||||||
|  | 		"url": "uCentralSec URL", | ||||||
| 		"username": "Username" | 		"username": "Username" | ||||||
| 	}, | 	}, | ||||||
| 	"reboot": { | 	"reboot": { | ||||||
| 		"directions": "When would you like to reboot this device?", | 		"directions": "When would you like to reboot this device?", | ||||||
| 		"title": "Reboot Device" | 		"now": "Would you like to reboot this device now?", | ||||||
|  | 		"title": "Reboot" | ||||||
| 	}, | 	}, | ||||||
| 	"scan": { | 	"scan": { | ||||||
| 		"active": "Enable active scan", | 		"active": "Enable active scan", | ||||||
| 		"channel": "Channel", | 		"channel": "Channel", | ||||||
| 		"directions": "Launch a wifi scan of this device, which should take approximately 25 seconds.", | 		"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": { | 	"statistics": { | ||||||
| 		"data": "Data (KB)", | 		"data": "Data (KB)", | ||||||
|  | 		"latest_statistics": "Latest Statistics", | ||||||
|  | 		"show_latest": "Show latest statistics JSON", | ||||||
| 		"title": "Statistics" | 		"title": "Statistics" | ||||||
| 	}, | 	}, | ||||||
|  | 	"status": { | ||||||
|  | 		"connection_status": "Connection Status", | ||||||
|  | 		"error": "Status data is unavailable", | ||||||
|  | 		"last_contact": "Last Contact", | ||||||
|  | 		"load_averages": "Load ( 1 / 5 / 15 minute average)", | ||||||
|  | 		"localtime": "Localtime", | ||||||
|  | 		"memory": "Memory Used", | ||||||
|  | 		"percentage_free": "{{percentage}}% of {{total}} free", | ||||||
|  | 		"percentage_used": "{{percentage}}% of {{total}} used", | ||||||
|  | 		"title": "#{{serialNumber}} Status", | ||||||
|  | 		"uptime": "Uptime", | ||||||
|  | 		"used_total_memory": "{{used}} used / {{total}} total " | ||||||
|  | 	}, | ||||||
| 	"trace": { | 	"trace": { | ||||||
| 		"choose_network": "Choose network", | 		"choose_network": "Choose network", | ||||||
| 		"directions": "Launch a remote trace of this device for either a specific duration or a number of packets", | 		"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", | 		"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": { | 	"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", | 		"directions": "Choose a time and a firmware version for this device", | ||||||
| 		"firmware_uri": "Firmware URI:", | 		"firmware_uri": "Firmware URI:", | ||||||
| 		"need_uri": "You need a 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:", | 		"time": "Time of upgrade:", | ||||||
| 		"title": "Firmware Upgrade" | 		"title": "Firmware Upgrade", | ||||||
|  | 		"upgrade": "Upgrade", | ||||||
|  | 		"wait_for_upgrade": "Would you like to wait for the upgrade to finish?", | ||||||
|  | 		"waiting_for_device": "Waiting for device to reconnect" | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -7,22 +7,26 @@ | |||||||
| 		"factory_reset": "Restablecimiento De Fábrica", | 		"factory_reset": "Restablecimiento De Fábrica", | ||||||
| 		"firmware_upgrade": "Actualización de firmware", | 		"firmware_upgrade": "Actualización de firmware", | ||||||
| 		"reboot": "Reiniciar", | 		"reboot": "Reiniciar", | ||||||
| 		"title": "Acciones del dispositivo", | 		"title": "Comandos", | ||||||
| 		"trace": "Rastro", | 		"trace": "Rastro", | ||||||
| 		"wifi_scan": "Escaneo Wifi" | 		"wifi_scan": "Escaneo Wifi" | ||||||
| 	}, | 	}, | ||||||
| 	"blink": { | 	"blink": { | ||||||
| 		"blink": "Parpadeo", | 		"blink": "Parpadeo", | ||||||
| 		"device_leds": "LED de dispositivo", | 		"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:", | 		"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?" | 		"when_blink_leds": "¿Cuándo desea que los LED del dispositivo parpadeen?" | ||||||
| 	}, | 	}, | ||||||
| 	"commands": { | 	"commands": { | ||||||
| 		"error": "¡Error al enviar el comando!", | 		"error": "¡Error al enviar el comando!", | ||||||
| 		"success": "Comando enviado con éxito", | 		"success": "Comando enviado con éxito, puede consultar el registro de Comandos para ver el resultado", | ||||||
| 		"title": "Comandos del dispositivo" | 		"title": "Historial de Comandos" | ||||||
| 	}, | 	}, | ||||||
| 	"common": { | 	"common": { | ||||||
|  | 		"add": "Añadir", | ||||||
|  | 		"adding_ellipsis": "Añadiendo ...", | ||||||
| 		"are_you_sure": "¿Estás seguro?", | 		"are_you_sure": "¿Estás seguro?", | ||||||
| 		"cancel": "Cancelar", | 		"cancel": "Cancelar", | ||||||
| 		"certificate": "Certificado", | 		"certificate": "Certificado", | ||||||
| @@ -36,7 +40,11 @@ | |||||||
| 		"connected": "Conectado", | 		"connected": "Conectado", | ||||||
| 		"copied": "Copiado!", | 		"copied": "Copiado!", | ||||||
| 		"copy_to_clipboard": "Copiar al portapapeles", | 		"copy_to_clipboard": "Copiar al portapapeles", | ||||||
|  | 		"created_by": "Creado por", | ||||||
|  | 		"custom_date": "Fecha personalizada", | ||||||
| 		"date": "Fecha", | 		"date": "Fecha", | ||||||
|  | 		"day": "día", | ||||||
|  | 		"days": "días", | ||||||
| 		"delete": "Borrar", | 		"delete": "Borrar", | ||||||
| 		"details": "Detalles", | 		"details": "Detalles", | ||||||
| 		"device_list": "Listado de dispositivos", | 		"device_list": "Listado de dispositivos", | ||||||
| @@ -47,9 +55,13 @@ | |||||||
| 		"download": "Descargar", | 		"download": "Descargar", | ||||||
| 		"duration": "Duración", | 		"duration": "Duración", | ||||||
| 		"error": "Error", | 		"error": "Error", | ||||||
|  | 		"execute_now": "¿Le gustaría ejecutar este comando ahora?", | ||||||
| 		"executed": "ejecutado", | 		"executed": "ejecutado", | ||||||
|  | 		"exit": "salida", | ||||||
| 		"firmware": "Firmware", | 		"firmware": "Firmware", | ||||||
| 		"from": "Desde", | 		"from": "Desde", | ||||||
|  | 		"hour": "hora", | ||||||
|  | 		"hours": "horas", | ||||||
| 		"id": "Carné de identidad", | 		"id": "Carné de identidad", | ||||||
| 		"ip_address": "Dirección IP", | 		"ip_address": "Dirección IP", | ||||||
| 		"later_tonight": "Más tarde esta noche", | 		"later_tonight": "Más tarde esta noche", | ||||||
| @@ -58,8 +70,12 @@ | |||||||
| 		"logout": "Cerrar sesión", | 		"logout": "Cerrar sesión", | ||||||
| 		"mac": "Dirección MAC", | 		"mac": "Dirección MAC", | ||||||
| 		"manufacturer": "Fabricante", | 		"manufacturer": "Fabricante", | ||||||
|  | 		"minute": "minuto", | ||||||
|  | 		"minutes": "minutos", | ||||||
| 		"na": "N / A", | 		"na": "N / A", | ||||||
| 		"need_date": "Necesitas una cita ...", | 		"need_date": "Necesitas una cita ...", | ||||||
|  | 		"no": "No", | ||||||
|  | 		"no_items": "No hay articulos", | ||||||
| 		"not_connected": "No conectado", | 		"not_connected": "No conectado", | ||||||
| 		"off": "Apagado", | 		"off": "Apagado", | ||||||
| 		"on": "en", | 		"on": "en", | ||||||
| @@ -67,7 +83,12 @@ | |||||||
| 		"refresh": "Refrescar", | 		"refresh": "Refrescar", | ||||||
| 		"refresh_device": "Actualizar dispositivo", | 		"refresh_device": "Actualizar dispositivo", | ||||||
| 		"result": "Resultado", | 		"result": "Resultado", | ||||||
|  | 		"save": "Salvar", | ||||||
|  | 		"saving": "Ahorro...", | ||||||
| 		"schedule": "Programar", | 		"schedule": "Programar", | ||||||
|  | 		"second": "segundo", | ||||||
|  | 		"seconds": "segundos", | ||||||
|  | 		"seconds_elapsed": "Segundos transcurridos", | ||||||
| 		"serial_number": "Número de serie", | 		"serial_number": "Número de serie", | ||||||
| 		"start": "comienzo", | 		"start": "comienzo", | ||||||
| 		"submit": "Enviar", | 		"submit": "Enviar", | ||||||
| @@ -81,14 +102,15 @@ | |||||||
| 	}, | 	}, | ||||||
| 	"configuration": { | 	"configuration": { | ||||||
| 		"created": "creado", | 		"created": "creado", | ||||||
| 		"details": "Detalles del dispositivo", | 		"details": "Detalles", | ||||||
| 		"device_password": "Contraseña", | 		"device_password": "Contraseña", | ||||||
| 		"last_configuration_change": "Último cambio de configuración", | 		"last_configuration_change": "Último cambio de configuración", | ||||||
| 		"last_configuration_download": "Descarga de la última configuración", | 		"last_configuration_download": "Descarga de la última configuración", | ||||||
| 		"location": "Ubicación", | 		"location": "Ubicación", | ||||||
|  | 		"note": "Nota", | ||||||
| 		"notes": "Notas", | 		"notes": "Notas", | ||||||
| 		"owner": "Propietario", | 		"owner": "Propietario", | ||||||
| 		"title": "Configuración del dispositivo", | 		"title": "Configuración", | ||||||
| 		"type": "Tipo de dispositivo", | 		"type": "Tipo de dispositivo", | ||||||
| 		"view_json": "Ver JSON sin procesar" | 		"view_json": "Ver JSON sin procesar" | ||||||
| 	}, | 	}, | ||||||
| @@ -96,7 +118,7 @@ | |||||||
| 		"choose_file": "Debe elegir un archivo .json válido:", | 		"choose_file": "Debe elegir un archivo .json válido:", | ||||||
| 		"enter_new": "Ingrese la nueva configuración del dispositivo JSON:", | 		"enter_new": "Ingrese la nueva configuración del dispositivo JSON:", | ||||||
| 		"placeholder": "Configurar JSON", | 		"placeholder": "Configurar JSON", | ||||||
| 		"title": "Configurar dispositivo", | 		"title": "Configurar", | ||||||
| 		"valid_json": "Debes ingresar un JSON válido" | 		"valid_json": "Debes ingresar un JSON válido" | ||||||
| 	}, | 	}, | ||||||
| 	"delete_command": { | 	"delete_command": { | ||||||
| @@ -112,11 +134,13 @@ | |||||||
| 	"device_logs": { | 	"device_logs": { | ||||||
| 		"log": "Iniciar sesión", | 		"log": "Iniciar sesión", | ||||||
| 		"severity": "Gravedad", | 		"severity": "Gravedad", | ||||||
| 		"title": "Registros de dispositivos" | 		"title": "Registros" | ||||||
| 	}, | 	}, | ||||||
| 	"factory_reset": { | 	"factory_reset": { | ||||||
| 		"redirector": "Mantener el redirector:", | 		"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" | 		"warning": "Advertencia: una vez que envíe, esto no se podrá revertir" | ||||||
| 	}, | 	}, | ||||||
| 	"footer": { | 	"footer": { | ||||||
| @@ -126,43 +150,79 @@ | |||||||
| 	}, | 	}, | ||||||
| 	"health": { | 	"health": { | ||||||
| 		"sanity": "Cordura", | 		"sanity": "Cordura", | ||||||
| 		"title": "Salud del dispositivo" | 		"title": "Salud" | ||||||
| 	}, | 	}, | ||||||
| 	"login": { | 	"login": { | ||||||
| 		"login": "Iniciar sesión", | 		"login": "Iniciar sesión", | ||||||
| 		"login_error": "Error de inicio de sesión, confirme que su nombre de usuario, contraseña y URL de puerta de enlace son válidos", | 		"login_error": "Error de inicio de sesión, confirme que su nombre de usuario, contraseña y URL de puerta de enlace son válidos", | ||||||
| 		"password": "Contraseña", | 		"password": "Contraseña", | ||||||
| 		"please_enter_gateway": "Ingrese una URL de puerta de enlace", | 		"please_enter_gateway": "Ingrese una URL de uCentralSec", | ||||||
| 		"please_enter_password": "Por favor, introduzca su contraseña", | 		"please_enter_password": "Por favor, introduzca su contraseña", | ||||||
| 		"please_enter_username": "Por favor, ingrese su nombre de usuario", | 		"please_enter_username": "Por favor, ingrese su nombre de usuario", | ||||||
| 		"sign_in_to_account": "Iniciar sesión en su cuenta", | 		"sign_in_to_account": "Iniciar sesión en su cuenta", | ||||||
|  | 		"url": "URL de uCentralSec", | ||||||
| 		"username": "Nombre de usuario" | 		"username": "Nombre de usuario" | ||||||
| 	}, | 	}, | ||||||
| 	"reboot": { | 	"reboot": { | ||||||
| 		"directions": "¿Cuándo le gustaría reiniciar este dispositivo?", | 		"directions": "¿Cuándo le gustaría reiniciar este dispositivo?", | ||||||
| 		"title": "Reiniciar dispositivo" | 		"now": "¿Le gustaría reiniciar este dispositivo ahora?", | ||||||
|  | 		"title": "Reiniciar" | ||||||
| 	}, | 	}, | ||||||
| 	"scan": { | 	"scan": { | ||||||
| 		"active": "Habilitar escaneo activo", | 		"active": "Habilitar escaneo activo", | ||||||
| 		"channel": "Canal", | 		"channel": "Canal", | ||||||
| 		"directions": "Ejecute un escaneo wifi de este dispositivo, que debería tomar aproximadamente 25 segundos.", | 		"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": { | 	"statistics": { | ||||||
| 		"data": "Datos (KB)", | 		"data": "Datos (KB)", | ||||||
|  | 		"latest_statistics": "Últimas estadísticas", | ||||||
|  | 		"show_latest": "Mostrar las últimas estadísticas JSON", | ||||||
| 		"title": "estadística" | 		"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": { | 	"trace": { | ||||||
| 		"choose_network": "Elija la red", | 		"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", | 		"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", | 		"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": { | 	"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", | 		"directions": "Elija una hora y una versión de firmware para este dispositivo", | ||||||
| 		"firmware_uri": "URI de firmware:", | 		"firmware_uri": "URI de firmware:", | ||||||
| 		"need_uri": "Necesitas un URI ...", | 		"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:", | 		"time": "Hora de actualización:", | ||||||
| 		"title": "Actualización de firmware" | 		"title": "Actualización de firmware", | ||||||
|  | 		"upgrade": "Mejorar", | ||||||
|  | 		"wait_for_upgrade": "¿Le gustaría esperar a que finalice la actualización?", | ||||||
|  | 		"waiting_for_device": "Esperando que el dispositivo se vuelva a conectar" | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -7,22 +7,26 @@ | |||||||
| 		"factory_reset": "Retour aux paramètres d'usine", | 		"factory_reset": "Retour aux paramètres d'usine", | ||||||
| 		"firmware_upgrade": "Mise à jour du firmware", | 		"firmware_upgrade": "Mise à jour du firmware", | ||||||
| 		"reboot": "Redémarrer", | 		"reboot": "Redémarrer", | ||||||
| 		"title": "Actions de l'appareil", | 		"title": "Les commandes", | ||||||
| 		"trace": "Trace", | 		"trace": "Trace", | ||||||
| 		"wifi_scan": "Balayage Wi-Fi" | 		"wifi_scan": "Balayage Wi-Fi" | ||||||
| 	}, | 	}, | ||||||
| 	"blink": { | 	"blink": { | ||||||
| 		"blink": "Cligner", | 		"blink": "Cligner", | ||||||
| 		"device_leds": "LED de l'appareil", | 		"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 :", | 		"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 ?" | 		"when_blink_leds": "Quand souhaitez-vous faire clignoter les LED de l'appareil ?" | ||||||
| 	}, | 	}, | ||||||
| 	"commands": { | 	"commands": { | ||||||
| 		"error": "Erreur lors de la soumission de la commande !", | 		"error": "Erreur lors de la soumission de la commande !", | ||||||
| 		"success": "Commande soumise avec succès", | 		"success": "Commande soumise avec succès, vous pouvez consulter le journal des commandes pour le résultat", | ||||||
| 		"title": "Commandes de l'appareil" | 		"title": "Historique des commandes" | ||||||
| 	}, | 	}, | ||||||
| 	"common": { | 	"common": { | ||||||
|  | 		"add": "Ajouter", | ||||||
|  | 		"adding_ellipsis": "Ajouter...", | ||||||
| 		"are_you_sure": "Êtes-vous sûr?", | 		"are_you_sure": "Êtes-vous sûr?", | ||||||
| 		"cancel": "annuler", | 		"cancel": "annuler", | ||||||
| 		"certificate": "Certificat", | 		"certificate": "Certificat", | ||||||
| @@ -36,7 +40,11 @@ | |||||||
| 		"connected": "Connecté", | 		"connected": "Connecté", | ||||||
| 		"copied": "Copié!", | 		"copied": "Copié!", | ||||||
| 		"copy_to_clipboard": "Copier dans le presse-papier", | 		"copy_to_clipboard": "Copier dans le presse-papier", | ||||||
|  | 		"created_by": "Créé par", | ||||||
|  | 		"custom_date": "Date personnalisée", | ||||||
| 		"date": "Rendez-vous amoureux", | 		"date": "Rendez-vous amoureux", | ||||||
|  | 		"day": "journée", | ||||||
|  | 		"days": "journées", | ||||||
| 		"delete": "Effacer", | 		"delete": "Effacer", | ||||||
| 		"details": "Détails", | 		"details": "Détails", | ||||||
| 		"device_list": "Liste des appareils", | 		"device_list": "Liste des appareils", | ||||||
| @@ -47,9 +55,13 @@ | |||||||
| 		"download": "Télécharger", | 		"download": "Télécharger", | ||||||
| 		"duration": "Durée", | 		"duration": "Durée", | ||||||
| 		"error": "erreur", | 		"error": "erreur", | ||||||
|  | 		"execute_now": "Souhaitez-vous exécuter cette commande maintenant ?", | ||||||
| 		"executed": "réalisé", | 		"executed": "réalisé", | ||||||
|  | 		"exit": "Sortie", | ||||||
| 		"firmware": "Micrologiciel", | 		"firmware": "Micrologiciel", | ||||||
| 		"from": "De", | 		"from": "De", | ||||||
|  | 		"hour": "heure", | ||||||
|  | 		"hours": "heures", | ||||||
| 		"id": "Id", | 		"id": "Id", | ||||||
| 		"ip_address": "Adresse IP", | 		"ip_address": "Adresse IP", | ||||||
| 		"later_tonight": "Plus tard ce soir", | 		"later_tonight": "Plus tard ce soir", | ||||||
| @@ -58,8 +70,12 @@ | |||||||
| 		"logout": "Connectez - Out", | 		"logout": "Connectez - Out", | ||||||
| 		"mac": "ADRESSE MAC", | 		"mac": "ADRESSE MAC", | ||||||
| 		"manufacturer": "fabricant", | 		"manufacturer": "fabricant", | ||||||
|  | 		"minute": "minute", | ||||||
|  | 		"minutes": "minutes", | ||||||
| 		"na": "N / A", | 		"na": "N / A", | ||||||
| 		"need_date": "Vous avez besoin d'un rendez-vous...", | 		"need_date": "Vous avez besoin d'un rendez-vous...", | ||||||
|  | 		"no": "Non", | ||||||
|  | 		"no_items": "Pas d'objet", | ||||||
| 		"not_connected": "Pas connecté", | 		"not_connected": "Pas connecté", | ||||||
| 		"off": "De", | 		"off": "De", | ||||||
| 		"on": "sur", | 		"on": "sur", | ||||||
| @@ -67,7 +83,12 @@ | |||||||
| 		"refresh": "Rafraîchir", | 		"refresh": "Rafraîchir", | ||||||
| 		"refresh_device": "Actualiser l'appareil", | 		"refresh_device": "Actualiser l'appareil", | ||||||
| 		"result": "Résultat", | 		"result": "Résultat", | ||||||
|  | 		"save": "Sauvegarder", | ||||||
|  | 		"saving": "Économie...", | ||||||
| 		"schedule": "Programme", | 		"schedule": "Programme", | ||||||
|  | 		"second": "seconde", | ||||||
|  | 		"seconds": "secondes", | ||||||
|  | 		"seconds_elapsed": "Secondes écoulées", | ||||||
| 		"serial_number": "Numéro de série", | 		"serial_number": "Numéro de série", | ||||||
| 		"start": "Début", | 		"start": "Début", | ||||||
| 		"submit": "Soumettre", | 		"submit": "Soumettre", | ||||||
| @@ -81,14 +102,15 @@ | |||||||
| 	}, | 	}, | ||||||
| 	"configuration": { | 	"configuration": { | ||||||
| 		"created": "Créé", | 		"created": "Créé", | ||||||
| 		"details": "Détails de l'appareil", | 		"details": "Détails", | ||||||
| 		"device_password": "Mot de passe", | 		"device_password": "Mot de passe", | ||||||
| 		"last_configuration_change": "Dernière modification de configuration", | 		"last_configuration_change": "Dernière modification de configuration", | ||||||
| 		"last_configuration_download": "Téléchargement de la dernière configuration", | 		"last_configuration_download": "Téléchargement de la dernière configuration", | ||||||
| 		"location": "Emplacement", | 		"location": "Emplacement", | ||||||
|  | 		"note": "Remarque", | ||||||
| 		"notes": "Remarques", | 		"notes": "Remarques", | ||||||
| 		"owner": "Propriétaire", | 		"owner": "Propriétaire", | ||||||
| 		"title": "Configuration de l'appareil", | 		"title": "Configuration", | ||||||
| 		"type": "Type d'appareil", | 		"type": "Type d'appareil", | ||||||
| 		"view_json": "Afficher le JSON brut" | 		"view_json": "Afficher le JSON brut" | ||||||
| 	}, | 	}, | ||||||
| @@ -96,7 +118,7 @@ | |||||||
| 		"choose_file": "Vous devez choisir un fichier .json valide :", | 		"choose_file": "Vous devez choisir un fichier .json valide :", | ||||||
| 		"enter_new": "Entrez la nouvelle configuration de l'appareil JSON :", | 		"enter_new": "Entrez la nouvelle configuration de l'appareil JSON :", | ||||||
| 		"placeholder": "Configurer JSON", | 		"placeholder": "Configurer JSON", | ||||||
| 		"title": "Configurer l'appareil", | 		"title": "Configurer", | ||||||
| 		"valid_json": "Vous devez entrer un JSON valide" | 		"valid_json": "Vous devez entrer un JSON valide" | ||||||
| 	}, | 	}, | ||||||
| 	"delete_command": { | 	"delete_command": { | ||||||
| @@ -112,11 +134,13 @@ | |||||||
| 	"device_logs": { | 	"device_logs": { | ||||||
| 		"log": "Bûche", | 		"log": "Bûche", | ||||||
| 		"severity": "Gravité", | 		"severity": "Gravité", | ||||||
| 		"title": "Journaux de l'appareil" | 		"title": "Journaux" | ||||||
| 	}, | 	}, | ||||||
| 	"factory_reset": { | 	"factory_reset": { | ||||||
| 		"redirector": "Conserver le redirecteur :", | 		"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é" | 		"warning": "Avertissement : Une fois que vous avez soumis, cela ne peut pas être annulé" | ||||||
| 	}, | 	}, | ||||||
| 	"footer": { | 	"footer": { | ||||||
| @@ -126,43 +150,79 @@ | |||||||
| 	}, | 	}, | ||||||
| 	"health": { | 	"health": { | ||||||
| 		"sanity": "Santé mentale", | 		"sanity": "Santé mentale", | ||||||
| 		"title": "Santé de l'appareil" | 		"title": "Santé" | ||||||
| 	}, | 	}, | ||||||
| 	"login": { | 	"login": { | ||||||
| 		"login": "S'identifier", | 		"login": "S'identifier", | ||||||
| 		"login_error": "Erreur de connexion, confirmez que votre nom d'utilisateur, mot de passe et URL de passerelle sont valides", | 		"login_error": "Erreur de connexion, confirmez que votre nom d'utilisateur, mot de passe et URL de passerelle sont valides", | ||||||
| 		"password": "Mot de passe", | 		"password": "Mot de passe", | ||||||
| 		"please_enter_gateway": "Veuillez saisir une URL de passerelle", | 		"please_enter_gateway": "Veuillez saisir une URL uCentralSec", | ||||||
| 		"please_enter_password": "s'il vous plait entrez votre mot de passe", | 		"please_enter_password": "s'il vous plait entrez votre mot de passe", | ||||||
| 		"please_enter_username": "s'il vous plaît entrez votre nom d'utilisateur", | 		"please_enter_username": "s'il vous plaît entrez votre nom d'utilisateur", | ||||||
| 		"sign_in_to_account": "Connectez-vous à votre compte", | 		"sign_in_to_account": "Connectez-vous à votre compte", | ||||||
|  | 		"url": "URL uCentralSec", | ||||||
| 		"username": "Nom d'utilisateur" | 		"username": "Nom d'utilisateur" | ||||||
| 	}, | 	}, | ||||||
| 	"reboot": { | 	"reboot": { | ||||||
| 		"directions": "Quand souhaitez-vous redémarrer cet appareil ?", | 		"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": { | 	"scan": { | ||||||
| 		"active": "Activer l'analyse active", | 		"active": "Activer l'analyse active", | ||||||
| 		"channel": "Canal", | 		"channel": "Canal", | ||||||
| 		"directions": "Lancez une analyse wifi de cet appareil, ce qui devrait prendre environ 25 secondes.", | 		"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": { | 	"statistics": { | ||||||
| 		"data": "Données (Ko)", | 		"data": "Données (Ko)", | ||||||
|  | 		"latest_statistics": "Dernières statistiques", | ||||||
|  | 		"show_latest": "Afficher les dernières statistiques JSON", | ||||||
| 		"title": "statistiques" | 		"title": "statistiques" | ||||||
| 	}, | 	}, | ||||||
|  | 	"status": { | ||||||
|  | 		"connection_status": "Statut de connexion", | ||||||
|  | 		"error": "Les données d'état ne sont pas disponibles", | ||||||
|  | 		"last_contact": "Dernier contact", | ||||||
|  | 		"load_averages": "Charge (moyenne 1 / 5 / 15 minutes)", | ||||||
|  | 		"localtime": "heure locale", | ||||||
|  | 		"memory": "Mémoire utilisée", | ||||||
|  | 		"percentage_free": "{{percentage}}% de {{total}} gratuit", | ||||||
|  | 		"percentage_used": "{{percentage}}% de {{total}} utilisé", | ||||||
|  | 		"title": "#{{serialNumber}} état", | ||||||
|  | 		"uptime": "La disponibilité", | ||||||
|  | 		"used_total_memory": "{{used}} utilisé / {{total}} total" | ||||||
|  | 	}, | ||||||
| 	"trace": { | 	"trace": { | ||||||
| 		"choose_network": "Choisir le réseau", | 		"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", | 		"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", | 		"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": { | 	"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", | 		"directions": "Choisissez une heure et une version de firmware pour cet appareil", | ||||||
| 		"firmware_uri": "URI du micrologiciel :", | 		"firmware_uri": "URI du micrologiciel :", | ||||||
| 		"need_uri": "Vous avez besoin d'un URI...", | 		"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 :", | 		"time": "Heure de la mise à niveau :", | ||||||
| 		"title": "Mise à jour du firmware" | 		"title": "Mise à jour du firmware", | ||||||
|  | 		"upgrade": "Améliorer", | ||||||
|  | 		"wait_for_upgrade": "Souhaitez-vous attendre la fin de la mise à niveau ?", | ||||||
|  | 		"waiting_for_device": "En attente de la reconnexion de l'appareil" | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -7,22 +7,26 @@ | |||||||
| 		"factory_reset": "Restauração de fábrica", | 		"factory_reset": "Restauração de fábrica", | ||||||
| 		"firmware_upgrade": "Atualização de firmware", | 		"firmware_upgrade": "Atualização de firmware", | ||||||
| 		"reboot": "Reiniciar", | 		"reboot": "Reiniciar", | ||||||
| 		"title": "Ações do dispositivo", | 		"title": "Comandos", | ||||||
| 		"trace": "Vestígio", | 		"trace": "Vestígio", | ||||||
| 		"wifi_scan": "Wifi Scan" | 		"wifi_scan": "Wifi Scan" | ||||||
| 	}, | 	}, | ||||||
| 	"blink": { | 	"blink": { | ||||||
| 		"blink": "Piscar", | 		"blink": "Piscar", | ||||||
| 		"device_leds": "LEDs do dispositivo", | 		"device_leds": "LEDs do dispositivo", | ||||||
|  | 		"execute_now": "Você gostaria de definir este padrão agora?", | ||||||
| 		"pattern": "Escolha o padrão que deseja usar:", | 		"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?" | 		"when_blink_leds": "Quando você gostaria de fazer os LEDs do dispositivo piscarem?" | ||||||
| 	}, | 	}, | ||||||
| 	"commands": { | 	"commands": { | ||||||
| 		"error": "Erro ao enviar comando!", | 		"error": "Erro ao enviar comando!", | ||||||
| 		"success": "Comando enviado com sucesso", | 		"success": "Comando enviado com sucesso, você pode consultar o log de Comandos para ver o resultado", | ||||||
| 		"title": "Comandos de dispositivo" | 		"title": "Histórico de Comandos" | ||||||
| 	}, | 	}, | ||||||
| 	"common": { | 	"common": { | ||||||
|  | 		"add": "Adicionar", | ||||||
|  | 		"adding_ellipsis": "Adicionando ...", | ||||||
| 		"are_you_sure": "Você tem certeza?", | 		"are_you_sure": "Você tem certeza?", | ||||||
| 		"cancel": "Cancelar", | 		"cancel": "Cancelar", | ||||||
| 		"certificate": "Certificado", | 		"certificate": "Certificado", | ||||||
| @@ -36,7 +40,11 @@ | |||||||
| 		"connected": "Conectado", | 		"connected": "Conectado", | ||||||
| 		"copied": "Copiado!", | 		"copied": "Copiado!", | ||||||
| 		"copy_to_clipboard": "Copiar para área de transferência", | 		"copy_to_clipboard": "Copiar para área de transferência", | ||||||
|  | 		"created_by": "Criado Por", | ||||||
|  | 		"custom_date": "Data personalizada", | ||||||
| 		"date": "Encontro", | 		"date": "Encontro", | ||||||
|  | 		"day": "dia", | ||||||
|  | 		"days": "dias", | ||||||
| 		"delete": "Excluir", | 		"delete": "Excluir", | ||||||
| 		"details": "Detalhes", | 		"details": "Detalhes", | ||||||
| 		"device_list": "Lista de Dispositivos", | 		"device_list": "Lista de Dispositivos", | ||||||
| @@ -47,9 +55,13 @@ | |||||||
| 		"download": "Baixar", | 		"download": "Baixar", | ||||||
| 		"duration": "Duração", | 		"duration": "Duração", | ||||||
| 		"error": "Erro", | 		"error": "Erro", | ||||||
|  | 		"execute_now": "Você gostaria de executar este comando agora?", | ||||||
| 		"executed": "Executado", | 		"executed": "Executado", | ||||||
|  | 		"exit": "Saída", | ||||||
| 		"firmware": "Firmware", | 		"firmware": "Firmware", | ||||||
| 		"from": "De", | 		"from": "De", | ||||||
|  | 		"hour": "hora", | ||||||
|  | 		"hours": "horas", | ||||||
| 		"id": "identidade", | 		"id": "identidade", | ||||||
| 		"ip_address": "Endereço de IP", | 		"ip_address": "Endereço de IP", | ||||||
| 		"later_tonight": "Logo à noite", | 		"later_tonight": "Logo à noite", | ||||||
| @@ -58,8 +70,12 @@ | |||||||
| 		"logout": "Sair", | 		"logout": "Sair", | ||||||
| 		"mac": "Endereço MAC", | 		"mac": "Endereço MAC", | ||||||
| 		"manufacturer": "Fabricante", | 		"manufacturer": "Fabricante", | ||||||
|  | 		"minute": "minuto", | ||||||
|  | 		"minutes": "minutos", | ||||||
| 		"na": "N / D", | 		"na": "N / D", | ||||||
| 		"need_date": "Você precisa de um encontro ...", | 		"need_date": "Você precisa de um encontro ...", | ||||||
|  | 		"no": "Não", | ||||||
|  | 		"no_items": "Nenhum item", | ||||||
| 		"not_connected": "Não conectado", | 		"not_connected": "Não conectado", | ||||||
| 		"off": "Fora", | 		"off": "Fora", | ||||||
| 		"on": "em", | 		"on": "em", | ||||||
| @@ -67,7 +83,12 @@ | |||||||
| 		"refresh": "REFRESH", | 		"refresh": "REFRESH", | ||||||
| 		"refresh_device": "Atualizar dispositivo", | 		"refresh_device": "Atualizar dispositivo", | ||||||
| 		"result": "Resultado", | 		"result": "Resultado", | ||||||
|  | 		"save": "Salve", | ||||||
|  | 		"saving": "Salvando ...", | ||||||
| 		"schedule": "Cronograma", | 		"schedule": "Cronograma", | ||||||
|  | 		"second": "segundo", | ||||||
|  | 		"seconds": "segundos", | ||||||
|  | 		"seconds_elapsed": "Segundos decorridos", | ||||||
| 		"serial_number": "Número de série", | 		"serial_number": "Número de série", | ||||||
| 		"start": "Começar", | 		"start": "Começar", | ||||||
| 		"submit": "Enviar", | 		"submit": "Enviar", | ||||||
| @@ -81,14 +102,15 @@ | |||||||
| 	}, | 	}, | ||||||
| 	"configuration": { | 	"configuration": { | ||||||
| 		"created": "Criado", | 		"created": "Criado", | ||||||
| 		"details": "Detalhes do dispositivo", | 		"details": "Detalhes", | ||||||
| 		"device_password": "Senha", | 		"device_password": "Senha", | ||||||
| 		"last_configuration_change": "Última Mudança de Configuração", | 		"last_configuration_change": "Última Mudança de Configuração", | ||||||
| 		"last_configuration_download": "Último download da configuração", | 		"last_configuration_download": "Último download da configuração", | ||||||
| 		"location": "Localização", | 		"location": "Localização", | ||||||
|  | 		"note": "Nota", | ||||||
| 		"notes": "notas", | 		"notes": "notas", | ||||||
| 		"owner": "Proprietário", | 		"owner": "Proprietário", | ||||||
| 		"title": "Configuração do dispositivo", | 		"title": "Configuração", | ||||||
| 		"type": "Tipo de dispositivo", | 		"type": "Tipo de dispositivo", | ||||||
| 		"view_json": "Exibir JSON bruto" | 		"view_json": "Exibir JSON bruto" | ||||||
| 	}, | 	}, | ||||||
| @@ -96,7 +118,7 @@ | |||||||
| 		"choose_file": "Você precisa escolher um arquivo .json válido:", | 		"choose_file": "Você precisa escolher um arquivo .json válido:", | ||||||
| 		"enter_new": "Insira a nova configuração do dispositivo JSON:", | 		"enter_new": "Insira a nova configuração do dispositivo JSON:", | ||||||
| 		"placeholder": "Config JSON", | 		"placeholder": "Config JSON", | ||||||
| 		"title": "Configurar dispositivo", | 		"title": "Configurar", | ||||||
| 		"valid_json": "Você precisa inserir um JSON válido" | 		"valid_json": "Você precisa inserir um JSON válido" | ||||||
| 	}, | 	}, | ||||||
| 	"delete_command": { | 	"delete_command": { | ||||||
| @@ -112,11 +134,13 @@ | |||||||
| 	"device_logs": { | 	"device_logs": { | ||||||
| 		"log": "Registro", | 		"log": "Registro", | ||||||
| 		"severity": "Gravidade", | 		"severity": "Gravidade", | ||||||
| 		"title": "Registros de dispositivos" | 		"title": "Toras" | ||||||
| 	}, | 	}, | ||||||
| 	"factory_reset": { | 	"factory_reset": { | ||||||
| 		"redirector": "Manter redirecionador:", | 		"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" | 		"warning": "Aviso: depois de enviar, isso não pode ser revertido" | ||||||
| 	}, | 	}, | ||||||
| 	"footer": { | 	"footer": { | ||||||
| @@ -126,43 +150,79 @@ | |||||||
| 	}, | 	}, | ||||||
| 	"health": { | 	"health": { | ||||||
| 		"sanity": "Sanidade", | 		"sanity": "Sanidade", | ||||||
| 		"title": "Saúde do Dispositivo" | 		"title": "Saúde" | ||||||
| 	}, | 	}, | ||||||
| 	"login": { | 	"login": { | ||||||
| 		"login": "Entrar", | 		"login": "Entrar", | ||||||
| 		"login_error": "Erro de login, confirme se seu nome de usuário, senha e url de gateway são válidos", | 		"login_error": "Erro de login, confirme se seu nome de usuário, senha e url de gateway são válidos", | ||||||
| 		"password": "Senha", | 		"password": "Senha", | ||||||
| 		"please_enter_gateway": "Insira um URL de gateway", | 		"please_enter_gateway": "Insira um URL uCentralSec", | ||||||
| 		"please_enter_password": "Por favor, insira sua senha", | 		"please_enter_password": "Por favor, insira sua senha", | ||||||
| 		"please_enter_username": "Por favor insira seu nome de usuário", | 		"please_enter_username": "Por favor insira seu nome de usuário", | ||||||
| 		"sign_in_to_account": "Faça login em sua conta", | 		"sign_in_to_account": "Faça login em sua conta", | ||||||
|  | 		"url": "URL uCentralSec", | ||||||
| 		"username": "Nome de usuário" | 		"username": "Nome de usuário" | ||||||
| 	}, | 	}, | ||||||
| 	"reboot": { | 	"reboot": { | ||||||
| 		"directions": "Quando você gostaria de reinicializar este dispositivo?", | 		"directions": "Quando você gostaria de reinicializar este dispositivo?", | ||||||
| 		"title": "Reiniciar dispositivo" | 		"now": "Você gostaria de reiniciar este dispositivo agora?", | ||||||
|  | 		"title": "Reiniciar" | ||||||
| 	}, | 	}, | ||||||
| 	"scan": { | 	"scan": { | ||||||
| 		"active": "Habilitar varredura ativa", | 		"active": "Habilitar varredura ativa", | ||||||
| 		"channel": "Canal", | 		"channel": "Canal", | ||||||
| 		"directions": "Inicie uma verificação de wi-fi deste dispositivo, o que deve levar aproximadamente 25 segundos.", | 		"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": { | 	"statistics": { | ||||||
| 		"data": "Dados (KB)", | 		"data": "Dados (KB)", | ||||||
|  | 		"latest_statistics": "Estatísticas mais recentes", | ||||||
|  | 		"show_latest": "Mostrar estatísticas mais recentes JSON", | ||||||
| 		"title": "Estatisticas" | 		"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": { | 	"trace": { | ||||||
| 		"choose_network": "Escolha a rede", | 		"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", | 		"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", | 		"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": { | 	"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", | 		"directions": "Escolha um horário e uma versão de firmware para este dispositivo", | ||||||
| 		"firmware_uri": "URI de firmware:", | 		"firmware_uri": "URI de firmware:", | ||||||
| 		"need_uri": "Você precisa de um URI ...", | 		"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:", | 		"time": "Tempo de atualização:", | ||||||
| 		"title": "Atualização de firmware" | 		"title": "Atualização de firmware", | ||||||
|  | 		"upgrade": "Melhorar", | ||||||
|  | 		"wait_for_upgrade": "Você gostaria de esperar a conclusão da atualização?", | ||||||
|  | 		"waiting_for_device": "Esperando que o dispositivo se reconecte" | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 4.1 KiB | 
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 12 KiB | 
| @@ -1,25 +0,0 @@ | |||||||
| { |  | ||||||
|   "short_name": "React App", |  | ||||||
|   "name": "Create React App Sample", |  | ||||||
|   "icons": [ |  | ||||||
|     { |  | ||||||
|       "src": "favicon.ico", |  | ||||||
|       "sizes": "64x64 32x32 24x24 16x16", |  | ||||||
|       "type": "image/x-icon" |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|       "src": "favicon.svg", |  | ||||||
|       "type": "image/svg", |  | ||||||
|       "sizes": "192x192" |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|       "src": "favicon.svg", |  | ||||||
|       "type": "image/svg", |  | ||||||
|       "sizes": "512x512" |  | ||||||
|     } |  | ||||||
|   ], |  | ||||||
|   "start_url": ".", |  | ||||||
|   "display": "standalone", |  | ||||||
|   "theme_color": "#000000", |  | ||||||
|   "background_color": "#ffffff" |  | ||||||
| } |  | ||||||
| @@ -1,165 +0,0 @@ | |||||||
| <?xml version="1.0" encoding="utf-8"?> |  | ||||||
| <!-- Generator: Adobe Illustrator 24.2.3, SVG Export Plug-In . SVG Version: 6.00 Build 0)  --> |  | ||||||
| <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" |  | ||||||
| 	 viewBox="0 0 141.5 185.6" style="enable-background:new 0 0 141.5 185.6;" xml:space="preserve"> |  | ||||||
| <style type="text/css"> |  | ||||||
| 	.st0{fill:#414141;} |  | ||||||
| 	.st1{fill:#FFFFFF;} |  | ||||||
| 	.st2{fill:#FED206;} |  | ||||||
| 	.st3{fill:#EB6F53;} |  | ||||||
| 	.st4{fill:#3BA9B6;} |  | ||||||
| </style> |  | ||||||
| <g> |  | ||||||
| 	<g> |  | ||||||
| 		<path class="st0" d="M120.7,183.9H21.5c-10.8,0-19.5-8.7-19.5-19.5V20.5c0-10.8,8.7-19.5,19.5-19.5h99.2 |  | ||||||
| 			c10.8,0,19.5,8.7,19.5,19.5v143.9C140.2,175.2,131.5,183.9,120.7,183.9z"/> |  | ||||||
| 		<g> |  | ||||||
| 			<g> |  | ||||||
| 				<g> |  | ||||||
| 					<path class="st1" d="M46.3,166.2v-3.4h-1.2v-0.6h3.1v0.6H47v3.4H46.3z"/> |  | ||||||
| 				</g> |  | ||||||
| 				<g> |  | ||||||
| 					<path class="st1" d="M49,166.2v-4h2.7v0.6h-2v1h2v0.6h-2v1.1h2v0.6H49z"/> |  | ||||||
| 				</g> |  | ||||||
| 				<g> |  | ||||||
| 					<path class="st1" d="M52.6,166.2v-4h0.7v3.4h1.8v0.6H52.6z"/> |  | ||||||
| 				</g> |  | ||||||
| 				<g> |  | ||||||
| 					<path class="st1" d="M55.7,166.2v-4h2.7v0.6h-2v1h2v0.6h-2v1.1h2v0.6H55.7z"/> |  | ||||||
| 				</g> |  | ||||||
| 				<g> |  | ||||||
| 					<path class="st1" d="M59.1,164.2c0-1.2,0.9-2.1,2.1-2.1c0.8,0,1.3,0.4,1.6,0.9l-0.6,0.3c-0.2-0.3-0.6-0.6-1-0.6 |  | ||||||
| 						c-0.8,0-1.4,0.6-1.4,1.4c0,0.8,0.6,1.4,1.4,1.4c0.4,0,0.8-0.3,1-0.6l0.6,0.3c-0.3,0.5-0.8,0.9-1.6,0.9 |  | ||||||
| 						C60,166.3,59.1,165.5,59.1,164.2z"/> |  | ||||||
| 				</g> |  | ||||||
| 				<g> |  | ||||||
| 					<path class="st1" d="M63.2,164.2c0-1.2,0.8-2.1,2-2.1c1.2,0,2,0.9,2,2.1c0,1.2-0.8,2.1-2,2.1C64,166.3,63.2,165.4,63.2,164.2z |  | ||||||
| 						 M66.5,164.2c0-0.8-0.5-1.4-1.3-1.4c-0.8,0-1.3,0.6-1.3,1.4c0,0.8,0.5,1.4,1.3,1.4C66,165.7,66.5,165,66.5,164.2z"/> |  | ||||||
| 				</g> |  | ||||||
| 				<g> |  | ||||||
| 					<path class="st1" d="M71.3,166.2v-3.1l-1.2,3.1h-0.3l-1.2-3.1v3.1h-0.7v-4h1l1.1,2.7l1.1-2.7h1v4H71.3z"/> |  | ||||||
| 				</g> |  | ||||||
| 				<g> |  | ||||||
| 					<path class="st1" d="M75.7,166.2v-4h0.7v4H75.7z"/> |  | ||||||
| 				</g> |  | ||||||
| 				<g> |  | ||||||
| 					<path class="st1" d="M80.4,166.2l-2.1-2.8v2.8h-0.7v-4h0.7l2,2.8v-2.8h0.7v4H80.4z"/> |  | ||||||
| 				</g> |  | ||||||
| 				<g> |  | ||||||
| 					<path class="st1" d="M82.3,166.2v-4H85v0.6h-2v1h2v0.6h-2v1.7H82.3z"/> |  | ||||||
| 				</g> |  | ||||||
| 				<g> |  | ||||||
| 					<path class="st1" d="M87.9,166.2l-0.9-1.5h-0.7v1.5h-0.7v-4h1.7c0.8,0,1.3,0.5,1.3,1.2c0,0.7-0.5,1.1-0.9,1.2l1,1.6H87.9z |  | ||||||
| 						 M88,163.5c0-0.4-0.3-0.6-0.7-0.6h-1v1.3h1C87.7,164.1,88,163.9,88,163.5z"/> |  | ||||||
| 				</g> |  | ||||||
| 				<g> |  | ||||||
| 					<path class="st1" d="M92.4,166.2l-0.3-0.8h-1.8l-0.3,0.8h-0.8l1.6-4h0.9l1.6,4H92.4z M91.2,162.9l-0.7,1.9h1.4L91.2,162.9z"/> |  | ||||||
| 				</g> |  | ||||||
| 				<g> |  | ||||||
| 					<path class="st1" d="M95.8,166.2v-4h1.5c0.8,0,1.2,0.5,1.2,1.2c0,0.6-0.4,1.2-1.2,1.2h-1.2v1.7H95.8z M98.2,163.4 |  | ||||||
| 						c0-0.5-0.3-0.9-0.9-0.9h-1.1v1.7h1.1C97.8,164.3,98.2,163.9,98.2,163.4z"/> |  | ||||||
| 				</g> |  | ||||||
| 				<g> |  | ||||||
| 					<path class="st1" d="M101.5,166.2l-1.1-1.6h-0.9v1.6h-0.3v-4h1.5c0.7,0,1.2,0.4,1.2,1.2c0,0.7-0.5,1.1-1.1,1.1l1.2,1.7H101.5z |  | ||||||
| 						 M101.6,163.4c0-0.5-0.4-0.9-0.9-0.9h-1.1v1.7h1.1C101.2,164.3,101.6,163.9,101.6,163.4z"/> |  | ||||||
| 				</g> |  | ||||||
| 				<g> |  | ||||||
| 					<path class="st1" d="M102.8,164.2c0-1.2,0.8-2.1,1.9-2.1c1.2,0,1.9,0.9,1.9,2.1c0,1.2-0.8,2.1-1.9,2.1 |  | ||||||
| 						C103.6,166.3,102.8,165.4,102.8,164.2z M106.3,164.2c0-1-0.6-1.7-1.6-1.7c-1,0-1.6,0.7-1.6,1.7c0,1,0.6,1.7,1.6,1.7 |  | ||||||
| 						C105.7,166,106.3,165.2,106.3,164.2z"/> |  | ||||||
| 				</g> |  | ||||||
| 				<g> |  | ||||||
| 					<path class="st1" d="M106.9,165.8l0.2-0.3c0.2,0.2,0.4,0.4,0.8,0.4c0.5,0,0.9-0.4,0.9-0.9v-2.8h0.3v2.8c0,0.8-0.5,1.2-1.2,1.2 |  | ||||||
| 						C107.5,166.3,107.2,166.1,106.9,165.8z"/> |  | ||||||
| 				</g> |  | ||||||
| 				<g> |  | ||||||
| 					<path class="st1" d="M110.4,166.2v-4h2.5v0.3h-2.2v1.5h2.1v0.3h-2.1v1.6h2.2v0.3H110.4z"/> |  | ||||||
| 				</g> |  | ||||||
| 				<g> |  | ||||||
| 					<path class="st1" d="M113.5,164.2c0-1.2,0.9-2.1,2-2.1c0.6,0,1.1,0.3,1.5,0.7l-0.3,0.2c-0.3-0.3-0.7-0.6-1.2-0.6 |  | ||||||
| 						c-0.9,0-1.7,0.7-1.7,1.7c0,1,0.7,1.7,1.7,1.7c0.5,0,0.9-0.2,1.2-0.6l0.3,0.2c-0.4,0.4-0.8,0.7-1.5,0.7 |  | ||||||
| 						C114.4,166.3,113.5,165.5,113.5,164.2z"/> |  | ||||||
| 				</g> |  | ||||||
| 				<g> |  | ||||||
| 					<path class="st1" d="M118.7,166.2v-3.7h-1.3v-0.3h2.9v0.3H119v3.7H118.7z"/> |  | ||||||
| 				</g> |  | ||||||
| 			</g> |  | ||||||
| 			<g> |  | ||||||
| 				<polygon class="st1" points="26.3,163.8 31.6,158.5 36.9,163.8 37.7,163.8 31.6,157.6 25.5,163.8 				"/> |  | ||||||
| 				<polygon class="st1" points="36.9,164.7 31.6,170 26.3,164.7 25.5,164.7 31.6,170.8 37.7,164.7 				"/> |  | ||||||
| 				<polygon class="st1" points="31,163.8 36.3,158.5 41.6,163.8 42.5,163.8 36.3,157.6 30.2,163.8 				"/> |  | ||||||
| 				<polygon class="st1" points="41.6,164.7 36.3,170 31,164.7 30.2,164.7 36.3,170.8 42.5,164.7 				"/> |  | ||||||
| 			</g> |  | ||||||
| 		</g> |  | ||||||
| 		<g> |  | ||||||
| 			<path class="st1" d="M33.2,100.7c-4.6,0-8.3,3.7-8.3,8.3s3.7,8.3,8.3,8.3s8.3-3.7,8.3-8.3S37.8,100.7,33.2,100.7z"/> |  | ||||||
| 		</g> |  | ||||||
| 		<g> |  | ||||||
| 			<g> |  | ||||||
| 				<g> |  | ||||||
| 					<path class="st2" d="M33.2,35.2c40.7,0,73.8,33.1,73.8,73.8c0,0.7,0,1.4,0,2.1c0,1.7,0.6,3.3,1.7,4.6c1.2,1.2,2.8,1.9,4.5,2 |  | ||||||
| 						l0.2,0c3.5,0,6.3-2.7,6.4-6.2c0-0.8,0-1.7,0-2.5c0-47.7-38.8-86.6-86.6-86.6c-0.8,0-1.7,0-2.5,0c-1.7,0-3.3,0.8-4.5,2 |  | ||||||
| 						c-1.2,1.2-1.8,2.9-1.7,4.6c0.1,3.5,3,6.3,6.6,6.2C31.8,35.2,32.5,35.2,33.2,35.2z"/> |  | ||||||
| 				</g> |  | ||||||
| 			</g> |  | ||||||
| 		</g> |  | ||||||
| 		<g> |  | ||||||
| 			<g> |  | ||||||
| 				<g> |  | ||||||
| 					<path class="st3" d="M33.2,60.5c26.7,0,48.5,21.7,48.5,48.5c0,0.6,0,1.3,0,2c-0.1,1.7,0.5,3.3,1.7,4.6c1.2,1.3,2.7,2,4.4,2.1 |  | ||||||
| 						c1.7,0.1,3.3-0.5,4.6-1.7c1.2-1.2,2-2.7,2-4.4c0-0.9,0.1-1.8,0.1-2.6c0-33.8-27.5-61.2-61.2-61.2c-0.8,0-1.6,0-2.6,0.1 |  | ||||||
| 						c-1.7,0.1-3.3,0.8-4.4,2.1c-1.2,1.3-1.8,2.9-1.7,4.6s0.8,3.3,2.1,4.4c1.3,1.2,2.9,1.8,4.6,1.7C31.9,60.5,32.6,60.5,33.2,60.5z" |  | ||||||
| 						/> |  | ||||||
| 				</g> |  | ||||||
| 			</g> |  | ||||||
| 		</g> |  | ||||||
| 		<g> |  | ||||||
| 			<g> |  | ||||||
| 				<g> |  | ||||||
| 					<path class="st4" d="M33.2,86.7c12.3,0,22.3,10,22.3,22.3c0,0.5,0,1.1-0.1,1.8c-0.3,3.5,2.3,6.6,5.8,6.9 |  | ||||||
| 						c3.5,0.3,6.6-2.3,6.9-5.8c0.1-1,0.1-1.9,0.1-2.8c0-19.3-15.7-35.1-35.1-35.1c-0.9,0-1.8,0-2.8,0.1c-1.7,0.1-3.2,0.9-4.3,2.2 |  | ||||||
| 						c-1.1,1.3-1.6,2.9-1.5,4.6c0.1,1.7,0.9,3.2,2.2,4.3c1.3,1.1,2.9,1.6,4.6,1.5C32.1,86.7,32.7,86.7,33.2,86.7z"/> |  | ||||||
| 				</g> |  | ||||||
| 			</g> |  | ||||||
| 		</g> |  | ||||||
| 	</g> |  | ||||||
| 	<g> |  | ||||||
| 		<path class="st1" d="M35.8,130.4c1.1,0.6,2.1,1.5,2.7,2.6c0.7,1.1,1,2.3,1,3.7s-0.3,2.6-1,3.7c-0.7,1.1-1.6,2-2.7,2.6 |  | ||||||
| 			c-1.1,0.6-2.4,1-3.8,1s-2.7-0.3-3.8-1c-1.1-0.6-2.1-1.5-2.7-2.6c-0.7-1.1-1-2.3-1-3.7c0-1.3,0.3-2.6,1-3.7c0.7-1.1,1.6-2,2.7-2.6 |  | ||||||
| 			c1.1-0.6,2.4-0.9,3.8-0.9C33.4,129.5,34.7,129.8,35.8,130.4z M29.9,132.9c-0.7,0.4-1.2,0.9-1.6,1.6s-0.6,1.4-0.6,2.2 |  | ||||||
| 			c0,0.8,0.2,1.6,0.6,2.3c0.4,0.7,0.9,1.2,1.6,1.6c0.7,0.4,1.4,0.6,2.1,0.6c0.8,0,1.5-0.2,2.1-0.6c0.6-0.4,1.2-0.9,1.5-1.6 |  | ||||||
| 			c0.4-0.7,0.6-1.4,0.6-2.3c0-0.8-0.2-1.6-0.6-2.2s-0.9-1.2-1.5-1.6c-0.6-0.4-1.4-0.6-2.1-0.6C31.3,132.3,30.6,132.5,29.9,132.9z"/> |  | ||||||
| 		<path class="st1" d="M50.6,133.6c0.8,0.5,1.4,1.1,1.8,2c0.4,0.8,0.6,1.8,0.6,2.9c0,1.1-0.2,2-0.6,2.8c-0.4,0.8-1,1.5-1.8,1.9 |  | ||||||
| 			c-0.8,0.5-1.6,0.7-2.6,0.7c-0.7,0-1.4-0.1-2-0.4s-1.1-0.7-1.5-1.2v5.4h-3.1V133h3.1v1.6c0.4-0.5,0.9-1,1.4-1.2s1.2-0.4,2-0.4 |  | ||||||
| 			C48.9,132.9,49.8,133.1,50.6,133.6z M49.1,140.5c0.5-0.6,0.7-1.3,0.7-2.2c0-0.9-0.2-1.6-0.7-2.1c-0.5-0.6-1.1-0.8-1.9-0.8 |  | ||||||
| 			s-1.4,0.3-1.9,0.8c-0.5,0.6-0.8,1.3-0.8,2.1c0,0.9,0.2,1.6,0.8,2.2s1.1,0.8,1.9,0.8S48.6,141,49.1,140.5z"/> |  | ||||||
| 		<path class="st1" d="M63.4,134.4c0.9,1,1.4,2.4,1.4,4.2c0,0.3,0,0.6,0,0.7H57c0.2,0.7,0.5,1.2,1,1.6c0.5,0.4,1.1,0.6,1.8,0.6 |  | ||||||
| 			c0.5,0,1-0.1,1.5-0.3s0.9-0.5,1.3-0.9l1.6,1.6c-0.5,0.6-1.2,1.1-2,1.4c-0.8,0.3-1.6,0.5-2.6,0.5c-1.1,0-2.1-0.2-3-0.7 |  | ||||||
| 			s-1.5-1.1-2-1.9c-0.5-0.8-0.7-1.8-0.7-2.9c0-1.1,0.2-2.1,0.7-2.9s1.1-1.5,2-1.9c0.8-0.5,1.8-0.7,2.9-0.7 |  | ||||||
| 			C61.2,132.9,62.5,133.4,63.4,134.4z M61.8,137.5c0-0.7-0.3-1.3-0.7-1.7s-1-0.6-1.7-0.6c-0.7,0-1.2,0.2-1.7,0.6 |  | ||||||
| 			c-0.4,0.4-0.7,1-0.9,1.7H61.8z"/> |  | ||||||
| 		<path class="st1" d="M76.2,134c0.7,0.7,1.1,1.7,1.1,3v6.8h-3.1v-5.9c0-0.7-0.2-1.2-0.6-1.6s-0.9-0.6-1.5-0.6 |  | ||||||
| 			c-0.8,0-1.4,0.3-1.8,0.8c-0.4,0.5-0.7,1.2-0.7,2v5.3h-3.1V133h3.1v1.9c0.7-1.3,2-2,3.7-2C74.6,132.8,75.5,133.2,76.2,134z"/> |  | ||||||
| 		<path class="st1" d="M96,129.7h3.3l-4.7,14h-3.3l-2.9-10.1l-3,10.1h-3.2l-4.7-14h3.4l3,10.7l3-10.7H90l3.1,10.7L96,129.7z"/> |  | ||||||
| 		<path class="st1" d="M103.3,128.7c0.3,0.3,0.5,0.7,0.5,1.2s-0.2,0.9-0.5,1.2c-0.3,0.3-0.7,0.5-1.2,0.5c-0.5,0-0.9-0.2-1.2-0.5 |  | ||||||
| 			c-0.3-0.3-0.5-0.7-0.5-1.2c0-0.5,0.2-0.9,0.5-1.2c0.3-0.3,0.7-0.5,1.2-0.5C102.6,128.2,103,128.3,103.3,128.7z M100.6,133h3.1 |  | ||||||
| 			v10.8h-3.1V133z"/> |  | ||||||
| 		<path class="st1" d="M106.5,129.7h10.1l0,2.6h-6.9v3.4h6.3v2.6h-6.3v5.3h-3.2V129.7z"/> |  | ||||||
| 		<path class="st1" d="M120.9,128.7c0.3,0.3,0.5,0.7,0.5,1.2s-0.2,0.9-0.5,1.2c-0.3,0.3-0.7,0.5-1.2,0.5c-0.5,0-0.9-0.2-1.2-0.5 |  | ||||||
| 			c-0.3-0.3-0.5-0.7-0.5-1.2c0-0.5,0.2-0.9,0.5-1.2c0.3-0.3,0.7-0.5,1.2-0.5C120.1,128.2,120.5,128.3,120.9,128.7z M118.1,133h3.1 |  | ||||||
| 			v10.8h-3.1V133z"/> |  | ||||||
| 	</g> |  | ||||||
| </g> |  | ||||||
| <g> |  | ||||||
| </g> |  | ||||||
| <g> |  | ||||||
| </g> |  | ||||||
| <g> |  | ||||||
| </g> |  | ||||||
| <g> |  | ||||||
| </g> |  | ||||||
| <g> |  | ||||||
| </g> |  | ||||||
| <g> |  | ||||||
| </g> |  | ||||||
| </svg> |  | ||||||
| Before Width: | Height: | Size: 8.0 KiB | 
| @@ -1,2 +0,0 @@ | |||||||
| # https://www.robotstxt.org/robotstxt.html |  | ||||||
| User-agent: * |  | ||||||
							
								
								
									
										44
									
								
								src/App.js
									
									
									
									
									
								
							
							
						
						
									
										44
									
								
								src/App.js
									
									
									
									
									
								
							| @@ -1,7 +1,9 @@ | |||||||
| import React, { useEffect } from 'react'; | import React from 'react'; | ||||||
| import { HashRouter, Route, Switch } from 'react-router-dom'; | import { HashRouter, Switch } from 'react-router-dom'; | ||||||
| import 'scss/style.scss'; | 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 = ( | const loading = ( | ||||||
|   <div className="pt-3 text-center"> |   <div className="pt-3 text-center"> | ||||||
| @@ -9,32 +11,22 @@ const loading = ( | |||||||
|   </div> |   </div> | ||||||
| ); | ); | ||||||
|  |  | ||||||
| const TheLayout = React.lazy(() => import('layout')); |  | ||||||
| const Login = React.lazy(() => import('pages/LoginPage')); |  | ||||||
|  |  | ||||||
| const App = () => { | const App = () => { | ||||||
|   const isLoggedIn = useSelector((state) => state.connected); |   const storageToken = sessionStorage.getItem('access_token'); | ||||||
|   const dispatch = useDispatch(); |   const apiEndpoints = checkIfJson(sessionStorage.getItem('gateway_endpoints')) | ||||||
|  |     ? JSON.parse(sessionStorage.getItem('gateway_endpoints')) | ||||||
|   useEffect(() => { |     : {}; | ||||||
|     const token = sessionStorage.getItem('access_token'); |  | ||||||
|     if (token !== undefined && token !== null) { |  | ||||||
|       dispatch({ type: 'set', connected: true }); |  | ||||||
|     } |  | ||||||
|   }, [dispatch]); |  | ||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
|     <HashRouter> |     <AuthProvider token={storageToken ?? ''} apiEndpoints={apiEndpoints}> | ||||||
|       <React.Suspense fallback={loading}> |       <HashRouter> | ||||||
|         <Switch> |         <React.Suspense fallback={loading}> | ||||||
|           <Route |           <Switch> | ||||||
|             path="/" |             <Router /> | ||||||
|             name="Devices" |           </Switch> | ||||||
|             render={(props) => (isLoggedIn ? <TheLayout {...props} /> : <Login {...props} />)} |         </React.Suspense> | ||||||
|           /> |       </HashRouter> | ||||||
|         </Switch> |     </AuthProvider> | ||||||
|       </React.Suspense> |  | ||||||
|     </HashRouter> |  | ||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 24 KiB | 
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 24 KiB | 
| @@ -122,14 +122,11 @@ import { | |||||||
|   cilXCircle, |   cilXCircle, | ||||||
|   cilWarning, |   cilWarning, | ||||||
| } from '@coreui/icons'; | } from '@coreui/icons'; | ||||||
| import { sygnet } from './sygnet'; |  | ||||||
| import { logo } from './logo'; | import { logo } from './CoreuiLogo'; | ||||||
| import { logoNegative } from './logo-negative'; |  | ||||||
|  |  | ||||||
| export const icons = { | export const icons = { | ||||||
|   sygnet, |  | ||||||
|   logo, |   logo, | ||||||
|   logoNegative, |  | ||||||
|   cilAlignCenter, |   cilAlignCenter, | ||||||
|   cilAlignLeft, |   cilAlignLeft, | ||||||
|   cilAlignRight, |   cilAlignRight, | ||||||
|   | |||||||
| @@ -1,33 +0,0 @@ | |||||||
| export const logoNegative = [ |  | ||||||
|   '608 134', |  | ||||||
|   ` |  | ||||||
|   <title>coreui react pro logo</title> |  | ||||||
|   <g> |  | ||||||
|     <g style="fill:#80d0ff;"> |  | ||||||
|       <path d="M362.0177,90.1512,353.25,69.4149a.2507.2507,0,0,0-.2559-.1914H343.01a.2263.2263,0,0,0-.2559.2559V90.0233a.5657.5657,0,0,1-.64.64h-1.2163a.5652.5652,0,0,1-.64-.64V46.5028a.5655.5655,0,0,1,.64-.64H353.442a9.9792,9.9792,0,0,1,7.7437,3.2324A12.2,12.2,0,0,1,364.13,57.64a12.4389,12.4389,0,0,1-2.24,7.584,9.37,9.37,0,0,1-6.08,3.7441c-.1709.086-.2139.1915-.128.3194l8.7041,20.6084.064.2558q0,.5127-.5757.5118h-1.1523A.703.703,0,0,1,362.0177,90.1512ZM342.754,48.3593v18.496a.2259.2259,0,0,0,.2559.2559h10.3037a7.6713,7.6713,0,0,0,6.0166-2.5918,9.8807,9.8807,0,0,0,2.3037-6.8164,10.2875,10.2875,0,0,0-2.272-6.9756,7.6033,7.6033,0,0,0-6.0483-2.624H343.01A.2263.2263,0,0,0,342.754,48.3593Z"/> |  | ||||||
|       <path d="M401.3263,48.1034H381.2945a.2262.2262,0,0,0-.2558.2559v18.496a.2259.2259,0,0,0,.2558.2559h13.8238a.5664.5664,0,0,1,.6406.64v.96a.5663.5663,0,0,1-.6406.6406H381.2945a.2263.2263,0,0,0-.2558.2559v18.56a.2258.2258,0,0,0,.2558.2558h20.0318a.5671.5671,0,0,1,.6406.6407v.96a.566.566,0,0,1-.6406.64H379.1827a.5653.5653,0,0,1-.64-.64V46.5028a.5656.5656,0,0,1,.64-.64h22.1436a.5664.5664,0,0,1,.6406.64v.96A.5663.5663,0,0,1,401.3263,48.1034Z"/> |  | ||||||
|       <path d="M439.047,90.1512l-2.4317-8.832a.2971.2971,0,0,0-.32-.1924H419.5274a.2957.2957,0,0,0-.32.1924l-2.3681,8.7676a.6577.6577,0,0,1-.7036.5762H414.919a.5385.5385,0,0,1-.5756-.7041l12.0317-43.584a.6436.6436,0,0,1,.7041-.5117h1.6a.6442.6442,0,0,1,.7041.5117l12.16,43.584.0644.1923q0,.5127-.64.5118h-1.2163A.6428.6428,0,0,1,439.047,90.1512ZM419.9435,78.9188a.3031.3031,0,0,0,.2236.0967h15.4883a.3048.3048,0,0,0,.2236-.0967c.0645-.0635.0742-.1162.0322-.1592l-7.872-28.9287c-.043-.0849-.086-.1279-.128-.1279s-.0859.043-.1279.1279L419.9112,78.76C419.8683,78.8026,419.879,78.8553,419.9435,78.9188Z"/> |  | ||||||
|       <path d="M456.6017,87.911a11.6372,11.6372,0,0,1-3.3277-8.7041V57.1913a11.4158,11.4158,0,0,1,3.36-8.5762,12.0941,12.0941,0,0,1,8.8-3.2637,12.2566,12.2566,0,0,1,8.8643,3.2315,11.3927,11.3927,0,0,1,3.36,8.6084v.64a.5663.5663,0,0,1-.6406.6407l-1.28.0634q-.6408,0-.64-.5761v-.8321a9.289,9.289,0,0,0-2.6558-6.9121,10.6734,10.6734,0,0,0-14.0161,0,9.2854,9.2854,0,0,0-2.6563,6.9121V79.3993a9.2808,9.2808,0,0,0,2.6563,6.9121,10.67,10.67,0,0,0,14.0161,0,9.2843,9.2843,0,0,0,2.6558-6.9121v-.7686q0-.5757.64-.5752l1.28.0635a.5667.5667,0,0,1,.6406.6406v.5118a11.4952,11.4952,0,0,1-3.36,8.64,13.6227,13.6227,0,0,1-17.6963,0Z"/> |  | ||||||
|       <path d="M514.4376,46.5028v.96a.5658.5658,0,0,1-.64.6406H503.046a.2263.2263,0,0,0-.2559.2559v41.664a.566.566,0,0,1-.6406.64h-1.2158a.5652.5652,0,0,1-.64-.64V48.3593a.2266.2266,0,0,0-.2558-.2559H489.8619a.5656.5656,0,0,1-.64-.6406v-.96a.5656.5656,0,0,1,.64-.64H513.798A.5658.5658,0,0,1,514.4376,46.5028Z"/> |  | ||||||
|       <path d="M522.0665,89.5116a2.8385,2.8385,0,0,1-.8-2.0488,2.9194,2.9194,0,0,1,.8-2.1114,2.7544,2.7544,0,0,1,2.08-.832,2.8465,2.8465,0,0,1,2.9438,2.9434,2.7541,2.7541,0,0,1-.832,2.08,2.9221,2.9221,0,0,1-2.1118.8008A2.754,2.754,0,0,1,522.0665,89.5116Z"/> |  | ||||||
|       <path d="M542.4054,88.0077a11.3123,11.3123,0,0,1-3.2-8.416v-5.44a.5656.5656,0,0,1,.64-.64h1.2158a.5661.5661,0,0,1,.64.64v5.5039a9.1424,9.1424,0,0,0,2.5283,6.72,8.9745,8.9745,0,0,0,6.6875,2.5605,8.7908,8.7908,0,0,0,9.28-9.28V46.5028a.5655.5655,0,0,1,.64-.64h1.2163a.566.566,0,0,1,.64.64V79.5917a11.2545,11.2545,0,0,1-3.2325,8.416,13.0618,13.0618,0,0,1-17.0556,0Z"/> |  | ||||||
|       <path d="M580.35,88.1034a10.4859,10.4859,0,0,1-3.36-8.1279v-1.792a.5663.5663,0,0,1,.64-.6407h1.0884a.5668.5668,0,0,1,.64.6407v1.6a8.5459,8.5459,0,0,0,2.752,6.6562,10.5353,10.5353,0,0,0,7.36,2.4961,9.8719,9.8719,0,0,0,6.9761-2.3681,8.2161,8.2161,0,0,0,2.56-6.336,8.4,8.4,0,0,0-1.12-4.416,11.3812,11.3812,0,0,0-3.3281-3.3926,71.6714,71.6714,0,0,0-6.1763-3.7119,71.0479,71.0479,0,0,1-6.24-3.84,12.1711,12.1711,0,0,1-3.4238-3.68,10.2614,10.2614,0,0,1-1.28-5.3438,9.8579,9.8579,0,0,1,3.0718-7.7441,12.0122,12.0122,0,0,1,8.32-2.752q5.6954,0,8.96,3.1036a10.8251,10.8251,0,0,1,3.2642,8.2246v1.6a.5658.5658,0,0,1-.64.64h-1.1519a.5652.5652,0,0,1-.64-.64V56.8075a8.8647,8.8647,0,0,0-2.624-6.6885,9.9933,9.9933,0,0,0-7.232-2.5273,9.37,9.37,0,0,0-6.5278,2.1435,7.8224,7.8224,0,0,0-2.3682,6.1123,7.8006,7.8006,0,0,0,1.0244,4.16,10.387,10.387,0,0,0,3.0078,3.0391,62.8714,62.8714,0,0,0,5.9522,3.4882,71.0575,71.0575,0,0,1,6.72,4.2559,13.4674,13.4674,0,0,1,3.648,3.9365,10.049,10.049,0,0,1,1.28,5.1836,10.7177,10.7177,0,0,1-3.2637,8.1924q-3.2637,3.0717-8.832,3.0723Q583.71,91.1757,580.35,88.1034Z"/> |  | ||||||
|     </g> |  | ||||||
|  |  | ||||||
|     <g style="fill:#fff;"> |  | ||||||
|       <g> |  | ||||||
|         <path d="M99.835,36.0577l-39-22.5167a12,12,0,0,0-12,0l-39,22.5166a12.0339,12.0339,0,0,0-6,10.3924V91.4833a12.0333,12.0333,0,0,0,6,10.3923l39,22.5167a12,12,0,0,0,12,0l39-22.5167a12.0331,12.0331,0,0,0,6-10.3923V46.45A12.0334,12.0334,0,0,0,99.835,36.0577Zm-2,55.4256a4,4,0,0,1-2,3.4641l-39,22.5167a4.0006,4.0006,0,0,1-4,0l-39-22.5167a4,4,0,0,1-2-3.4641V46.45a4,4,0,0,1,2-3.4642l39-22.5166a4,4,0,0,1,4,0l39,22.5166a4,4,0,0,1,2,3.4642Z"/> |  | ||||||
|         <path d="M77.8567,82.0046h-2.866a4,4,0,0,0-1.9247.4934L55.7852,91.9833,35.835,80.4648V57.4872l19.95-11.5185,17.2893,9.4549a3.9993,3.9993,0,0,0,1.9192.4906h2.8632a2,2,0,0,0,2-2V51.2024a2,2,0,0,0-1.04-1.7547L59.628,38.9521a8.0391,8.0391,0,0,0-7.8428.09L31.8346,50.56a8.0246,8.0246,0,0,0-4,6.9287v22.976a8,8,0,0,0,4,6.9283l19.95,11.5186a8.0429,8.0429,0,0,0,7.8433.0879l19.19-10.5312a2,2,0,0,0,1.0378-1.7533v-2.71A2,2,0,0,0,77.8567,82.0046Z"/> |  | ||||||
|       </g> |  | ||||||
|       <g> |  | ||||||
|         <path d="M172.58,45.3618a15.0166,15.0166,0,0,0-15,14.9995V77.6387a15,15,0,0,0,30,0V60.3613A15.0166,15.0166,0,0,0,172.58,45.3618Zm7,32.2769a7,7,0,0,1-14,0V60.3613a7,7,0,0,1,14,0Z"/> |  | ||||||
|         <path d="M135.9138,53.4211a7.01,7.01,0,0,1,7.8681,6.0752.9894.9894,0,0,0,.9843.865h6.03a1.0108,1.0108,0,0,0,.9987-1.0971,15.0182,15.0182,0,0,0-15.7162-13.8837,15.2881,15.2881,0,0,0-14.2441,15.4163V77.2037A15.288,15.288,0,0,0,136.0792,92.62a15.0183,15.0183,0,0,0,15.7162-13.8842,1.0107,1.0107,0,0,0-.9987-1.0971h-6.03a.9894.9894,0,0,0-.9843.865,7.01,7.01,0,0,1-7.8679,6.0757,7.1642,7.1642,0,0,1-6.0789-7.1849V60.6057A7.1638,7.1638,0,0,1,135.9138,53.4211Z"/> |  | ||||||
|         <path d="M218.7572,72.9277a12.1585,12.1585,0,0,0,7.1843-11.0771V58.1494A12.1494,12.1494,0,0,0,213.7921,46H196.835a1,1,0,0,0-1,1V91a1,1,0,0,0,1,1h6a1,1,0,0,0,1-1V74h6.6216l7.9154,17.4138a1,1,0,0,0,.91.5862h6.5911a1,1,0,0,0,.91-1.4138Zm-.8157-11.0771A4.1538,4.1538,0,0,1,213.7926,66h-9.8511V54h9.8511a4.1538,4.1538,0,0,1,4.1489,4.1494Z"/> |  | ||||||
|         <path d="M260.835,46h-26a1,1,0,0,0-1,1V91a1,1,0,0,0,1,1h26a1,1,0,0,0,1-1V85a1,1,0,0,0-1-1h-19V72h13a1,1,0,0,0,1-1V65a1,1,0,0,0-1-1h-13V54h19a1,1,0,0,0,1-1V47A1,1,0,0,0,260.835,46Z"/> |  | ||||||
|         <path d="M298.835,46h-6a1,1,0,0,0-1,1V69.6475a7.0066,7.0066,0,1,1-14,0V47a1,1,0,0,0-1-1h-6a1,1,0,0,0-1,1V69.6475a15.0031,15.0031,0,1,0,30,0V47A1,1,0,0,0,298.835,46Z"/> |  | ||||||
|         <rect x="307.835" y="46" width="8" height="38" rx="1"/> |  | ||||||
|       </g> |  | ||||||
|     </g> |  | ||||||
|   </g> |  | ||||||
| `, |  | ||||||
| ]; |  | ||||||
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 3.6 KiB | 
| @@ -1,12 +0,0 @@ | |||||||
| export const sygnet = [ |  | ||||||
|   '160 160', |  | ||||||
|   ` |  | ||||||
|   <title>coreui logo</title> |  | ||||||
|   <g> |  | ||||||
|     <g style="fill:#fff;"> |  | ||||||
|       <path d="M125,47.091,86,24.5743a12,12,0,0,0-12,0L35,47.091a12.0336,12.0336,0,0,0-6,10.3923v45.0334a12.0335,12.0335,0,0,0,6,10.3923l39,22.5166a11.9993,11.9993,0,0,0,12,0l39-22.5166a12.0335,12.0335,0,0,0,6-10.3923V57.4833A12.0336,12.0336,0,0,0,125,47.091Zm-2,55.4257a4,4,0,0,1-2,3.464L82,128.4974a4,4,0,0,1-4,0L39,105.9807a4,4,0,0,1-2-3.464V57.4833a4,4,0,0,1,2-3.4641L78,31.5025a4,4,0,0,1,4,0l39,22.5167a4,4,0,0,1,2,3.4641Z"/> |  | ||||||
|       <path d="M103.0216,93.0379h-2.866a4,4,0,0,0-1.9246.4935L80.95,103.0167,61,91.4981V68.5206L80.95,57.002l17.2894,9.455a4,4,0,0,0,1.9192.4905h2.8632a2,2,0,0,0,2-2V62.2357a2,2,0,0,0-1.04-1.7547L84.793,49.9854a8.0391,8.0391,0,0,0-7.8428.09L57,61.5929A8.0243,8.0243,0,0,0,53,68.5216v22.976a8,8,0,0,0,4,6.9283l19.95,11.5185a8.0422,8.0422,0,0,0,7.8433.0879l19.19-10.5311a2,2,0,0,0,1.0378-1.7534v-2.71A2,2,0,0,0,103.0216,93.0379Z"/> |  | ||||||
|     </g> |  | ||||||
|   </g> |  | ||||||
| `, |  | ||||||
| ]; |  | ||||||
| @@ -5,10 +5,9 @@ import { | |||||||
|   CModalTitle, |   CModalTitle, | ||||||
|   CModalBody, |   CModalBody, | ||||||
|   CModalFooter, |   CModalFooter, | ||||||
|   CSpinner, |   CSwitch, | ||||||
|   CCol, |   CCol, | ||||||
|   CRow, |   CRow, | ||||||
|   CForm, |  | ||||||
|   CFormGroup, |   CFormGroup, | ||||||
|   CInputRadio, |   CInputRadio, | ||||||
|   CLabel, |   CLabel, | ||||||
| @@ -17,10 +16,10 @@ import React, { useState, useEffect } from 'react'; | |||||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||||
| import DatePicker from 'react-widgets/DatePicker'; | import DatePicker from 'react-widgets/DatePicker'; | ||||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||||
| import { useSelector } from 'react-redux'; | import { dateToUnix } from 'utils/helper'; | ||||||
| import { convertDateFromUtc, convertDateToUtc, dateToUnix } from 'utils/helper'; |  | ||||||
| import 'react-widgets/styles.css'; | 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 axiosInstance from 'utils/axiosInstance'; | ||||||
| import eventBus from 'utils/eventBus'; | import eventBus from 'utils/eventBus'; | ||||||
| import SuccessfulActionModalBody from 'components/SuccessfulActionModalBody'; | import SuccessfulActionModalBody from 'components/SuccessfulActionModalBody'; | ||||||
| @@ -30,24 +29,16 @@ import styles from './index.module.scss'; | |||||||
|  |  | ||||||
| const BlinkModal = ({ show, toggleModal }) => { | const BlinkModal = ({ show, toggleModal }) => { | ||||||
|   const { t } = useTranslation(); |   const { t } = useTranslation(); | ||||||
|   const [hadSuccess, setHadSuccess] = useState(false); |   const { currentToken, endpoints } = useAuth(); | ||||||
|   const [hadFailure, setHadFailure] = useState(false); |   const { deviceSerialNumber } = useDevice(); | ||||||
|   const [doingNow, setDoingNow] = useState(false); |   const [isNow, setIsNow] = useState(false); | ||||||
|   const [waiting, setWaiting] = useState(false); |   const [waiting, setWaiting] = useState(false); | ||||||
|   const [chosenDate, setChosenDate] = useState(new Date().toString()); |   const [chosenDate, setChosenDate] = useState(new Date().toString()); | ||||||
|   const [chosenPattern, setPattern] = useState('on'); |   const [chosenPattern, setPattern] = useState('on'); | ||||||
|   const [responseBody, setResponseBody] = useState(''); |   const [result, setResult] = useState(null); | ||||||
|   const selectedDeviceId = useSelector((state) => state.selectedDeviceId); |  | ||||||
|  |  | ||||||
|   const setDateToLate = () => { |   const toggleNow = () => { | ||||||
|     const date = convertDateToUtc(new Date()); |     setIsNow(!isNow); | ||||||
|     if (date.getHours() >= 3) { |  | ||||||
|       date.setDate(date.getDate() + 1); |  | ||||||
|     } |  | ||||||
|     date.setHours(3); |  | ||||||
|     date.setMinutes(0); |  | ||||||
|  |  | ||||||
|     setChosenDate(convertDateFromUtc(date).toString()); |  | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   const setDate = (date) => { |   const setDate = (date) => { | ||||||
| @@ -60,47 +51,40 @@ const BlinkModal = ({ show, toggleModal }) => { | |||||||
|     if (show) { |     if (show) { | ||||||
|       setWaiting(false); |       setWaiting(false); | ||||||
|       setChosenDate(new Date().toString()); |       setChosenDate(new Date().toString()); | ||||||
|       setResponseBody(''); |  | ||||||
|       setPattern('on'); |       setPattern('on'); | ||||||
|       setDoingNow(false); |       setResult(null); | ||||||
|       setHadSuccess(false); |  | ||||||
|       setHadFailure(false); |  | ||||||
|     } |     } | ||||||
|   }, [show]); |   }, [show]); | ||||||
|  |  | ||||||
|   const doAction = (isNow) => { |   const doAction = () => { | ||||||
|     if (isNow !== undefined) setDoingNow(isNow); |  | ||||||
|     setHadFailure(false); |  | ||||||
|     setHadSuccess(false); |  | ||||||
|     setWaiting(true); |     setWaiting(true); | ||||||
|  |  | ||||||
|     const token = getToken(); |  | ||||||
|     const utcDate = new Date(chosenDate); |     const utcDate = new Date(chosenDate); | ||||||
|     const utcDateString = utcDate.toISOString(); |  | ||||||
|  |  | ||||||
|     const parameters = { |     const parameters = { | ||||||
|       serialNumber: selectedDeviceId, |       serialNumber: deviceSerialNumber, | ||||||
|       when: isNow ? 0 : dateToUnix(utcDateString), |       when: isNow ? 0 : dateToUnix(utcDate), | ||||||
|       pattern: chosenPattern, |       pattern: chosenPattern, | ||||||
|       duration: 30, |       duration: 30, | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     const headers = { |     const headers = { | ||||||
|       Accept: 'application/json', |       Accept: 'application/json', | ||||||
|       Authorization: `Bearer ${token}`, |       Authorization: `Bearer ${currentToken}`, | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     axiosInstance |     axiosInstance | ||||||
|       .post(`/device/${encodeURIComponent(selectedDeviceId)}/leds`, parameters, { headers }) |       .post( | ||||||
|  |         `${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}/leds`, | ||||||
|  |         parameters, | ||||||
|  |         { headers }, | ||||||
|  |       ) | ||||||
|       .then(() => { |       .then(() => { | ||||||
|         setHadSuccess(true); |         setResult('success'); | ||||||
|       }) |       }) | ||||||
|       .catch(() => { |       .catch(() => { | ||||||
|         setResponseBody('Error while submitting command!'); |         setResult('error'); | ||||||
|         setHadFailure(true); |  | ||||||
|       }) |       }) | ||||||
|       .finally(() => { |       .finally(() => { | ||||||
|         setDoingNow(false); |  | ||||||
|         setWaiting(false); |         setWaiting(false); | ||||||
|         eventBus.dispatch('actionCompleted', { message: 'An action has been completed' }); |         eventBus.dispatch('actionCompleted', { message: 'An action has been completed' }); | ||||||
|       }); |       }); | ||||||
| @@ -111,33 +95,72 @@ const BlinkModal = ({ show, toggleModal }) => { | |||||||
|       <CModalHeader closeButton> |       <CModalHeader closeButton> | ||||||
|         <CModalTitle>{t('blink.device_leds')}</CModalTitle> |         <CModalTitle>{t('blink.device_leds')}</CModalTitle> | ||||||
|       </CModalHeader> |       </CModalHeader> | ||||||
|       {hadSuccess ? ( |       {result === 'success' ? ( | ||||||
|         <SuccessfulActionModalBody toggleModal={toggleModal} /> |         <SuccessfulActionModalBody toggleModal={toggleModal} /> | ||||||
|       ) : ( |       ) : ( | ||||||
|         <div> |         <div> | ||||||
|           <CModalBody> |           <CModalBody> | ||||||
|             <h6>{t('blink.when_blink_leds')}</h6> |             <CFormGroup row> | ||||||
|             <CRow className={styles.spacedRow}> |               <CCol md="3"> | ||||||
|               <CCol> |                 <CLabel>{t('blink.pattern')}</CLabel> | ||||||
|                 <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> |  | ||||||
|               </CCol> |               </CCol> | ||||||
|               <CCol> |               <CCol> | ||||||
|                 <CButton disabled={waiting} block color="primary" onClick={setDateToLate}> |                 <CFormGroup variant="custom-radio" onClick={() => setPattern('on')} inline> | ||||||
|                   {t('common.later_tonight')} |                   <CInputRadio | ||||||
|                 </CButton> |                     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> |               </CCol> | ||||||
|             </CRow> |             </CRow> | ||||||
|             <CRow className={styles.spacedRow}> |             <CRow hidden={isNow} className={styles.spacedRow}> | ||||||
|               <CCol md="4" className={styles.spacedDate}> |               <CCol md="4" className={styles.spacedDate}> | ||||||
|                 <p>{t('common.date')}</p> |                 <p>{t('common.custom_date')}</p> | ||||||
|               </CCol> |               </CCol> | ||||||
|               <CCol xs="12" md="8"> |               <CCol xs="12" md="8"> | ||||||
|                 <DatePicker |                 <DatePicker | ||||||
| @@ -147,63 +170,17 @@ const BlinkModal = ({ show, toggleModal }) => { | |||||||
|                   placeholder="Select custom date" |                   placeholder="Select custom date" | ||||||
|                   disabled={waiting} |                   disabled={waiting} | ||||||
|                   onChange={(date) => setDate(date)} |                   onChange={(date) => setDate(date)} | ||||||
|                   min={convertDateToUtc(new Date())} |                   min={new Date()} | ||||||
|                 /> |                 /> | ||||||
|               </CCol> |               </CCol> | ||||||
|             </CRow> |             </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> |           </CModalBody> | ||||||
|           <CModalFooter> |           <CModalFooter> | ||||||
|             <LoadingButton |             <LoadingButton | ||||||
|               label={t('common.schedule')} |               label={isNow ? t('blink.set_leds') : t('common.schedule')} | ||||||
|               isLoadingLabel={t('common.loading_ellipsis')} |               isLoadingLabel={t('common.loading_ellipsis')} | ||||||
|               isLoading={waiting && !doingNow} |               isLoading={waiting} | ||||||
|               action={doAction} |               action={doAction} | ||||||
|               variant="outline" |  | ||||||
|               block={false} |               block={false} | ||||||
|               disabled={waiting} |               disabled={waiting} | ||||||
|             /> |             /> | ||||||
|   | |||||||
| @@ -13,13 +13,11 @@ import { | |||||||
| } from '@coreui/react'; | } from '@coreui/react'; | ||||||
| import CIcon from '@coreui/icons-react'; | import CIcon from '@coreui/icons-react'; | ||||||
| import DatePicker from 'react-widgets/DatePicker'; | import DatePicker from 'react-widgets/DatePicker'; | ||||||
| import { cilCloudDownload, cilSync } from '@coreui/icons'; | import { cilCloudDownload, cilSync, cilCalendarCheck } from '@coreui/icons'; | ||||||
| import PropTypes from 'prop-types'; |  | ||||||
| import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; |  | ||||||
| import { faClipboardCheck } from '@fortawesome/free-solid-svg-icons'; |  | ||||||
| import { prettyDate, dateToUnix } from 'utils/helper'; | import { prettyDate, dateToUnix } from 'utils/helper'; | ||||||
| import axiosInstance from 'utils/axiosInstance'; | import axiosInstance from 'utils/axiosInstance'; | ||||||
| import { getToken } from 'utils/authHelper'; | import { useAuth } from 'contexts/AuthProvider'; | ||||||
|  | import { useDevice } from 'contexts/DeviceProvider'; | ||||||
| import eventBus from 'utils/eventBus'; | import eventBus from 'utils/eventBus'; | ||||||
| import ConfirmModal from 'components/ConfirmModal'; | import ConfirmModal from 'components/ConfirmModal'; | ||||||
| import LoadingButton from 'components/LoadingButton'; | import LoadingButton from 'components/LoadingButton'; | ||||||
| @@ -27,8 +25,10 @@ import WifiScanResultModalWidget from 'components/WifiScanResultModal'; | |||||||
| import DeviceCommandsCollapse from './DeviceCommandsCollapse'; | import DeviceCommandsCollapse from './DeviceCommandsCollapse'; | ||||||
| import styles from './index.module.scss'; | import styles from './index.module.scss'; | ||||||
| 
 | 
 | ||||||
| const DeviceCommands = ({ selectedDeviceId }) => { | const DeviceCommands = () => { | ||||||
|   const { t } = useTranslation(); |   const { t } = useTranslation(); | ||||||
|  |   const { currentToken, endpoints } = useAuth(); | ||||||
|  |   const { deviceSerialNumber } = useDevice(); | ||||||
|   // Wifiscan result related
 |   // Wifiscan result related
 | ||||||
|   const [chosenWifiScan, setChosenWifiScan] = useState(null); |   const [chosenWifiScan, setChosenWifiScan] = useState(null); | ||||||
|   const [showScanModal, setShowScanModal] = useState(false); |   const [showScanModal, setShowScanModal] = useState(false); | ||||||
| @@ -92,7 +92,7 @@ const DeviceCommands = ({ selectedDeviceId }) => { | |||||||
|     const options = { |     const options = { | ||||||
|       headers: { |       headers: { | ||||||
|         Accept: 'application/json', |         Accept: 'application/json', | ||||||
|         Authorization: `Bearer ${getToken()}`, |         Authorization: `Bearer ${currentToken}`, | ||||||
|       }, |       }, | ||||||
|       params: { |       params: { | ||||||
|         limit: commandLimit, |         limit: commandLimit, | ||||||
| @@ -109,7 +109,12 @@ const DeviceCommands = ({ selectedDeviceId }) => { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     axiosInstance |     axiosInstance | ||||||
|       .get(`/commands?serialNumber=${encodeURIComponent(selectedDeviceId)}${extraParams}`, options) |       .get( | ||||||
|  |         `${endpoints.ucentralgw}/api/v1/commands?serialNumber=${encodeURIComponent( | ||||||
|  |           deviceSerialNumber, | ||||||
|  |         )}${extraParams}`,
 | ||||||
|  |         options, | ||||||
|  |       ) | ||||||
|       .then((response) => { |       .then((response) => { | ||||||
|         setCommands(response.data.commands); |         setCommands(response.data.commands); | ||||||
|       }) |       }) | ||||||
| @@ -124,13 +129,16 @@ const DeviceCommands = ({ selectedDeviceId }) => { | |||||||
|     const options = { |     const options = { | ||||||
|       headers: { |       headers: { | ||||||
|         Accept: 'application/octet-stream', |         Accept: 'application/octet-stream', | ||||||
|         Authorization: `Bearer ${getToken()}`, |         Authorization: `Bearer ${currentToken}`, | ||||||
|       }, |       }, | ||||||
|       responseType: 'arraybuffer', |       responseType: 'arraybuffer', | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     axiosInstance |     axiosInstance | ||||||
|       .get(`/file/${uuid}?serialNumber=${selectedDeviceId}`, options) |       .get( | ||||||
|  |         `${endpoints.ucentralgw}/api/v1/file/${uuid}?serialNumber=${deviceSerialNumber}`, | ||||||
|  |         options, | ||||||
|  |       ) | ||||||
|       .then((response) => { |       .then((response) => { | ||||||
|         const blob = new Blob([response.data], { type: 'application/octet-stream' }); |         const blob = new Blob([response.data], { type: 'application/octet-stream' }); | ||||||
|         const link = document.createElement('a'); |         const link = document.createElement('a'); | ||||||
| @@ -147,11 +155,11 @@ const DeviceCommands = ({ selectedDeviceId }) => { | |||||||
|     const options = { |     const options = { | ||||||
|       headers: { |       headers: { | ||||||
|         Accept: 'application/json', |         Accept: 'application/json', | ||||||
|         Authorization: `Bearer ${getToken()}`, |         Authorization: `Bearer ${currentToken}`, | ||||||
|       }, |       }, | ||||||
|     }; |     }; | ||||||
|     return axiosInstance |     return axiosInstance | ||||||
|       .delete(`/command/${uuidDelete}`, options) |       .delete(`${endpoints.ucentralgw}/api/v1/command/${uuidDelete}`, options) | ||||||
|       .then(() => { |       .then(() => { | ||||||
|         deleteCommandFromList(uuidDelete); |         deleteCommandFromList(uuidDelete); | ||||||
|         setUuidDelete(''); |         setUuidDelete(''); | ||||||
| @@ -233,12 +241,12 @@ const DeviceCommands = ({ selectedDeviceId }) => { | |||||||
|   ]; |   ]; | ||||||
| 
 | 
 | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     if (selectedDeviceId && start !== '' && end !== '') { |     if (deviceSerialNumber && start !== '' && end !== '') { | ||||||
|       getCommands(); |       getCommands(); | ||||||
|     } else if (selectedDeviceId && start === '' && end === '') { |     } else if (deviceSerialNumber && start === '' && end === '') { | ||||||
|       getCommands(); |       getCommands(); | ||||||
|     } |     } | ||||||
|   }, [selectedDeviceId, start, end]); |   }, [deviceSerialNumber, start, end]); | ||||||
| 
 | 
 | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     eventBus.on('actionCompleted', () => refreshCommands()); |     eventBus.on('actionCompleted', () => refreshCommands()); | ||||||
| @@ -249,7 +257,7 @@ const DeviceCommands = ({ selectedDeviceId }) => { | |||||||
|   }, []); |   }, []); | ||||||
| 
 | 
 | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     if (selectedDeviceId) { |     if (deviceSerialNumber) { | ||||||
|       setCommandLimit(25); |       setCommandLimit(25); | ||||||
|       setLoadingMore(false); |       setLoadingMore(false); | ||||||
|       setShowLoadingMore(true); |       setShowLoadingMore(true); | ||||||
| @@ -257,7 +265,7 @@ const DeviceCommands = ({ selectedDeviceId }) => { | |||||||
|       setEnd(''); |       setEnd(''); | ||||||
|       getCommands(); |       getCommands(); | ||||||
|     } |     } | ||||||
|   }, [selectedDeviceId]); |   }, [deviceSerialNumber]); | ||||||
| 
 | 
 | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     if (commandLimit !== 25) { |     if (commandLimit !== 25) { | ||||||
| @@ -313,9 +321,7 @@ const DeviceCommands = ({ selectedDeviceId }) => { | |||||||
|                   items={commands ?? []} |                   items={commands ?? []} | ||||||
|                   fields={columns} |                   fields={columns} | ||||||
|                   className={styles.whiteIcon} |                   className={styles.whiteIcon} | ||||||
|                   columnFilter |                   sorterValue={{ column: 'created', desc: 'true' }} | ||||||
|                   sorter |  | ||||||
|                   sorterValue={{ column: 'submitted', desc: 'true' }} |  | ||||||
|                   scopedSlots={{ |                   scopedSlots={{ | ||||||
|                     completed: (item) => ( |                     completed: (item) => ( | ||||||
|                       <td> |                       <td> | ||||||
| @@ -363,12 +369,7 @@ const DeviceCommands = ({ selectedDeviceId }) => { | |||||||
|                                 {item.command === 'trace' ? ( |                                 {item.command === 'trace' ? ( | ||||||
|                                   <CIcon content={cilCloudDownload} size="lg" /> |                                   <CIcon content={cilCloudDownload} size="lg" /> | ||||||
|                                 ) : ( |                                 ) : ( | ||||||
|                                   <FontAwesomeIcon |                                   <CIcon content={cilCalendarCheck} size="lg" /> | ||||||
|                                     icon={faClipboardCheck} |  | ||||||
|                                     className={[styles.customIconHeight, 'c-icon c-icon-lg'].join( |  | ||||||
|                                       ' ', |  | ||||||
|                                     )} |  | ||||||
|                                   /> |  | ||||||
|                                 )} |                                 )} | ||||||
|                               </CButton> |                               </CButton> | ||||||
|                             </CPopover> |                             </CPopover> | ||||||
| @@ -449,13 +450,8 @@ const DeviceCommands = ({ selectedDeviceId }) => { | |||||||
|         date={chosenWifiScanDate} |         date={chosenWifiScanDate} | ||||||
|       /> |       /> | ||||||
|       <ConfirmModal show={showConfirmModal} toggle={toggleConfirmModal} action={deleteCommand} /> |       <ConfirmModal show={showConfirmModal} toggle={toggleConfirmModal} action={deleteCommand} /> | ||||||
|       <CIcon name="cilNotes" className={styles.whiteIcon} size="lg" /> |  | ||||||
|     </CWidgetDropdown> |     </CWidgetDropdown> | ||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| DeviceCommands.propTypes = { |  | ||||||
|   selectedDeviceId: PropTypes.string.isRequired, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export default DeviceCommands; | export default DeviceCommands; | ||||||
| @@ -7,7 +7,7 @@ | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .scrollableBox { | .scrollableBox { | ||||||
|   height: 400px; |   height: 200px; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .whiteIcon { | .whiteIcon { | ||||||
| @@ -16,9 +16,9 @@ import { | |||||||
| import React, { useState, useEffect } from 'react'; | import React, { useState, useEffect } from 'react'; | ||||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||||
| import { useSelector } from 'react-redux'; |  | ||||||
| import 'react-widgets/styles.css'; | 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 { checkIfJson } from 'utils/helper'; | ||||||
| import axiosInstance from 'utils/axiosInstance'; | import axiosInstance from 'utils/axiosInstance'; | ||||||
| import eventBus from 'utils/eventBus'; | import eventBus from 'utils/eventBus'; | ||||||
| @@ -27,6 +27,8 @@ import styles from './index.module.scss'; | |||||||
|  |  | ||||||
| const ConfigureModal = ({ show, toggleModal }) => { | const ConfigureModal = ({ show, toggleModal }) => { | ||||||
|   const { t } = useTranslation(); |   const { t } = useTranslation(); | ||||||
|  |   const { currentToken, endpoints } = useAuth(); | ||||||
|  |   const { deviceSerialNumber } = useDevice(); | ||||||
|   const [hadSuccess, setHadSuccess] = useState(false); |   const [hadSuccess, setHadSuccess] = useState(false); | ||||||
|   const [hadFailure, setHadFailure] = useState(false); |   const [hadFailure, setHadFailure] = useState(false); | ||||||
|   const [doingNow, setDoingNow] = useState(false); |   const [doingNow, setDoingNow] = useState(false); | ||||||
| @@ -36,7 +38,7 @@ const ConfigureModal = ({ show, toggleModal }) => { | |||||||
|   const [checkingIfSure, setCheckingIfSure] = useState(false); |   const [checkingIfSure, setCheckingIfSure] = useState(false); | ||||||
|   const [errorJson, setErrorJson] = useState(false); |   const [errorJson, setErrorJson] = useState(false); | ||||||
|   const [inputKey, setInputKey] = useState(0); |   const [inputKey, setInputKey] = useState(0); | ||||||
|   const selectedDeviceId = useSelector((state) => state.selectedDeviceId); |  | ||||||
|   let fileReader; |   let fileReader; | ||||||
|  |  | ||||||
|   const confirmingIfSure = () => { |   const confirmingIfSure = () => { | ||||||
| @@ -69,10 +71,8 @@ const ConfigureModal = ({ show, toggleModal }) => { | |||||||
|     setHadSuccess(false); |     setHadSuccess(false); | ||||||
|     setWaiting(true); |     setWaiting(true); | ||||||
|  |  | ||||||
|     const token = getToken(); |  | ||||||
|  |  | ||||||
|     const parameters = { |     const parameters = { | ||||||
|       serialNumber: selectedDeviceId, |       serialNumber: deviceSerialNumber, | ||||||
|       when: 0, |       when: 0, | ||||||
|       UUID: 1, |       UUID: 1, | ||||||
|       configuration: JSON.parse(newConfig), |       configuration: JSON.parse(newConfig), | ||||||
| @@ -80,11 +80,15 @@ const ConfigureModal = ({ show, toggleModal }) => { | |||||||
|  |  | ||||||
|     const headers = { |     const headers = { | ||||||
|       Accept: 'application/json', |       Accept: 'application/json', | ||||||
|       Authorization: `Bearer ${token}`, |       Authorization: `Bearer ${currentToken}`, | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     axiosInstance |     axiosInstance | ||||||
|       .post(`/device/${encodeURIComponent(selectedDeviceId)}/configure`, parameters, { headers }) |       .post( | ||||||
|  |         `${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}/configure`, | ||||||
|  |         parameters, | ||||||
|  |         { headers }, | ||||||
|  |       ) | ||||||
|       .then(() => { |       .then(() => { | ||||||
|         setHadSuccess(true); |         setHadSuccess(true); | ||||||
|       }) |       }) | ||||||
| @@ -191,7 +195,7 @@ const ConfigureModal = ({ show, toggleModal }) => { | |||||||
|               color="primary" |               color="primary" | ||||||
|               onClick={confirmingIfSure} |               onClick={confirmingIfSure} | ||||||
|             > |             > | ||||||
|               {t('common.submit')} |               {t('common.save')} | ||||||
|             </CButton> |             </CButton> | ||||||
|             <CButton |             <CButton | ||||||
|               hidden={!checkingIfSure} |               hidden={!checkingIfSure} | ||||||
| @@ -199,7 +203,7 @@ const ConfigureModal = ({ show, toggleModal }) => { | |||||||
|               color="primary" |               color="primary" | ||||||
|               onClick={() => doAction(false)} |               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" /> |               <CSpinner color="light" hidden={!waiting || doingNow} component="span" size="sm" /> | ||||||
|             </CButton> |             </CButton> | ||||||
|             <CButton color="secondary" onClick={toggleModal}> |             <CButton color="secondary" onClick={toggleModal}> | ||||||
|   | |||||||
							
								
								
									
										37
									
								
								src/components/CopyToClipboardButton/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/components/CopyToClipboardButton/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | |||||||
|  | import React, { useState } from 'react'; | ||||||
|  | import { useTranslation } from 'react-i18next'; | ||||||
|  | import CIcon from '@coreui/icons-react'; | ||||||
|  | import { cilClone } from '@coreui/icons'; | ||||||
|  | import PropTypes from 'prop-types'; | ||||||
|  | import { CButton, CPopover } from '@coreui/react'; | ||||||
|  |  | ||||||
|  | const CopyToClipboardButton = ({ content, size }) => { | ||||||
|  |   const { t } = useTranslation(); | ||||||
|  |   const [result, setResult] = useState(''); | ||||||
|  |  | ||||||
|  |   const copyToClipboard = () => { | ||||||
|  |     navigator.clipboard.writeText(content); | ||||||
|  |     setResult(t('common.copied')); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   return ( | ||||||
|  |     <CPopover content={t('common.copy_to_clipboard')}> | ||||||
|  |       <CButton onClick={copyToClipboard} size={size}> | ||||||
|  |         <CIcon content={cilClone} /> | ||||||
|  |         {'   '} | ||||||
|  |         {result || ''} | ||||||
|  |       </CButton> | ||||||
|  |     </CPopover> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | CopyToClipboardButton.propTypes = { | ||||||
|  |   content: PropTypes.string.isRequired, | ||||||
|  |   size: PropTypes.string, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | CopyToClipboardButton.defaultProps = { | ||||||
|  |   size: 'sm', | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default CopyToClipboardButton; | ||||||
| @@ -6,12 +6,15 @@ import PropTypes from 'prop-types'; | |||||||
| import ConfirmFooter from 'components/ConfirmFooter'; | import ConfirmFooter from 'components/ConfirmFooter'; | ||||||
| import { dateToUnix } from 'utils/helper'; | import { dateToUnix } from 'utils/helper'; | ||||||
| import axiosInstance from 'utils/axiosInstance'; | 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 eventBus from 'utils/eventBus'; | ||||||
| import styles from './index.module.scss'; | import styles from './index.module.scss'; | ||||||
|  |  | ||||||
| const DeleteLogModal = ({ serialNumber, show, toggle, object }) => { | const DeleteLogModal = ({ show, toggle, object }) => { | ||||||
|   const { t } = useTranslation(); |   const { t } = useTranslation(); | ||||||
|  |   const { currentToken, endpoints } = useAuth(); | ||||||
|  |   const { deviceSerialNumber } = useDevice(); | ||||||
|   const [loading, setLoading] = useState(false); |   const [loading, setLoading] = useState(false); | ||||||
|   const [maxDate, setMaxDate] = useState(new Date().toString()); |   const [maxDate, setMaxDate] = useState(new Date().toString()); | ||||||
|  |  | ||||||
| @@ -27,14 +30,14 @@ const DeleteLogModal = ({ serialNumber, show, toggle, object }) => { | |||||||
|     const options = { |     const options = { | ||||||
|       headers: { |       headers: { | ||||||
|         Accept: 'application/json', |         Accept: 'application/json', | ||||||
|         Authorization: `Bearer ${getToken()}`, |         Authorization: `Bearer ${currentToken}`, | ||||||
|       }, |       }, | ||||||
|       params: { |       params: { | ||||||
|         endDate: dateToUnix(maxDate), |         endDate: dateToUnix(maxDate), | ||||||
|       }, |       }, | ||||||
|     }; |     }; | ||||||
|     return axiosInstance |     return axiosInstance | ||||||
|       .delete(`/device/${serialNumber}/${object}`, options) |       .delete(`${endpoints.ucentralgw}/api/v1/device/${deviceSerialNumber}/${object}`, options) | ||||||
|       .then(() => {}) |       .then(() => {}) | ||||||
|       .catch(() => {}) |       .catch(() => {}) | ||||||
|       .finally(() => { |       .finally(() => { | ||||||
| @@ -94,7 +97,6 @@ DeleteLogModal.propTypes = { | |||||||
|   show: PropTypes.bool.isRequired, |   show: PropTypes.bool.isRequired, | ||||||
|   toggle: PropTypes.func.isRequired, |   toggle: PropTypes.func.isRequired, | ||||||
|   object: PropTypes.string.isRequired, |   object: PropTypes.string.isRequired, | ||||||
|   serialNumber: PropTypes.string.isRequired, |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| export default DeleteLogModal; | export default DeleteLogModal; | ||||||
|   | |||||||
| @@ -1,9 +1,9 @@ | |||||||
| import React, { useState } from 'react'; | import React, { useState } from 'react'; | ||||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||||
| import PropTypes from 'prop-types'; |  | ||||||
| import { CButton, CCard, CCardHeader, CCardBody, CRow, CCol } from '@coreui/react'; | import { CButton, CCard, CCardHeader, CCardBody, CRow, CCol } from '@coreui/react'; | ||||||
| import axiosInstance from 'utils/axiosInstance'; | 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 LoadingButton from 'components/LoadingButton'; | ||||||
| import RebootModal from 'components/RebootModal'; | import RebootModal from 'components/RebootModal'; | ||||||
| import FirmwareUpgradeModal from 'components/FirmwareUpgradeModal'; | import FirmwareUpgradeModal from 'components/FirmwareUpgradeModal'; | ||||||
| @@ -12,10 +12,13 @@ import TraceModal from 'components/TraceModal'; | |||||||
| import WifiScanModal from 'components/WifiScanModal'; | import WifiScanModal from 'components/WifiScanModal'; | ||||||
| import BlinkModal from 'components/BlinkModal'; | import BlinkModal from 'components/BlinkModal'; | ||||||
| import FactoryResetModal from 'components/FactoryResetModal'; | import FactoryResetModal from 'components/FactoryResetModal'; | ||||||
|  | 
 | ||||||
| import styles from './index.module.scss'; | import styles from './index.module.scss'; | ||||||
| 
 | 
 | ||||||
| const DeviceActions = ({ selectedDeviceId }) => { | const DeviceActions = () => { | ||||||
|   const { t } = useTranslation(); |   const { t } = useTranslation(); | ||||||
|  |   const { currentToken, endpoints } = useAuth(); | ||||||
|  |   const { deviceSerialNumber } = useDevice(); | ||||||
|   const [showRebootModal, setShowRebootModal] = useState(false); |   const [showRebootModal, setShowRebootModal] = useState(false); | ||||||
|   const [showBlinkModal, setShowBlinkModal] = useState(false); |   const [showBlinkModal, setShowBlinkModal] = useState(false); | ||||||
|   const [showUpgradeModal, setShowUpgradeModal] = useState(false); |   const [showUpgradeModal, setShowUpgradeModal] = useState(false); | ||||||
| @@ -58,12 +61,15 @@ const DeviceActions = ({ selectedDeviceId }) => { | |||||||
|     const options = { |     const options = { | ||||||
|       headers: { |       headers: { | ||||||
|         Accept: 'application/json', |         Accept: 'application/json', | ||||||
|         Authorization: `Bearer ${getToken()}`, |         Authorization: `Bearer ${currentToken}`, | ||||||
|       }, |       }, | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     axiosInstance |     axiosInstance | ||||||
|       .get(`/device/${encodeURIComponent(selectedDeviceId)}/rtty`, options) |       .get( | ||||||
|  |         `${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}/rtty`, | ||||||
|  |         options, | ||||||
|  |       ) | ||||||
|       .then((response) => { |       .then((response) => { | ||||||
|         const url = `https://${response.data.server}:${response.data.viewport}/connect/${response.data.connectionId}`; |         const url = `https://${response.data.server}:${response.data.viewport}/connect/${response.data.connectionId}`; | ||||||
|         const newWindow = window.open(url, '_blank', 'noopener,noreferrer'); |         const newWindow = window.open(url, '_blank', 'noopener,noreferrer'); | ||||||
| @@ -77,7 +83,9 @@ const DeviceActions = ({ selectedDeviceId }) => { | |||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|     <CCard> |     <CCard> | ||||||
|       <CCardHeader>{t('actions.title')}</CCardHeader> |       <CCardHeader> | ||||||
|  |         <div className="text-value-lg">{t('actions.title')}</div> | ||||||
|  |       </CCardHeader> | ||||||
|       <CCardBody> |       <CCardBody> | ||||||
|         <CRow> |         <CRow> | ||||||
|           <CCol> |           <CCol> | ||||||
| @@ -142,8 +150,4 @@ const DeviceActions = ({ selectedDeviceId }) => { | |||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| DeviceActions.propTypes = { |  | ||||||
|   selectedDeviceId: PropTypes.string.isRequired, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export default DeviceActions; | export default DeviceActions; | ||||||
| @@ -1,3 +0,0 @@ | |||||||
| .modalTitle { |  | ||||||
|   color: black; |  | ||||||
| } |  | ||||||
| @@ -4,11 +4,8 @@ import { | |||||||
|   CCard, |   CCard, | ||||||
|   CCardHeader, |   CCardHeader, | ||||||
|   CCardBody, |   CCardBody, | ||||||
|   CFormGroup, |  | ||||||
|   CCol, |   CCol, | ||||||
|   CLabel, |   CLabel, | ||||||
|   CForm, |  | ||||||
|   CInput, |  | ||||||
|   CCollapse, |   CCollapse, | ||||||
|   CCardFooter, |   CCardFooter, | ||||||
|   CButton, |   CButton, | ||||||
| @@ -16,20 +13,23 @@ import { | |||||||
|   CPopover, |   CPopover, | ||||||
| } from '@coreui/react'; | } from '@coreui/react'; | ||||||
| import CIcon from '@coreui/icons-react'; | import CIcon from '@coreui/icons-react'; | ||||||
| import PropTypes from 'prop-types'; | import { cilWindowMaximize } from '@coreui/icons'; | ||||||
| import { cilWindowMaximize, cilClone } from '@coreui/icons'; |  | ||||||
| import { prettyDate } from 'utils/helper'; | import { prettyDate } from 'utils/helper'; | ||||||
| import axiosInstance from 'utils/axiosInstance'; | import axiosInstance from 'utils/axiosInstance'; | ||||||
| import { getToken } from 'utils/authHelper'; | import { useAuth } from 'contexts/AuthProvider'; | ||||||
| import DeviceConfigurationModal from './containers/DeviceConfigurationModal/index'; | 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'; | import styles from './index.module.scss'; | ||||||
|  |  | ||||||
| const DeviceConfiguration = ({ selectedDeviceId }) => { | const DeviceConfiguration = () => { | ||||||
|   const { t } = useTranslation(); |   const { t } = useTranslation(); | ||||||
|  |   const { currentToken, endpoints } = useAuth(); | ||||||
|  |   const { deviceSerialNumber } = useDevice(); | ||||||
|   const [collapse, setCollapse] = useState(false); |   const [collapse, setCollapse] = useState(false); | ||||||
|   const [showModal, setShowModal] = useState(false); |   const [showModal, setShowModal] = useState(false); | ||||||
|   const [device, setDevice] = useState(null); |   const [device, setDevice] = useState(null); | ||||||
|   const [copyPasswordSuccess, setCopyPasswordSuccess] = useState(''); |  | ||||||
|  |  | ||||||
|   const toggle = (e) => { |   const toggle = (e) => { | ||||||
|     setCollapse(!collapse); |     setCollapse(!collapse); | ||||||
| @@ -40,22 +40,19 @@ const DeviceConfiguration = ({ selectedDeviceId }) => { | |||||||
|     setShowModal(!showModal); |     setShowModal(!showModal); | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   const copyPasswordToClipboard = () => { |  | ||||||
|     const password = device.devicePassword === '' ? 'openwifi' : device.devicePassword; |  | ||||||
|     navigator.clipboard.writeText(password); |  | ||||||
|     setCopyPasswordSuccess(t('common.copied')); |  | ||||||
|   }; |  | ||||||
|  |  | ||||||
|   const getDevice = () => { |   const getDevice = () => { | ||||||
|     const options = { |     const options = { | ||||||
|       headers: { |       headers: { | ||||||
|         Accept: 'application/json', |         Accept: 'application/json', | ||||||
|         Authorization: `Bearer ${getToken()}`, |         Authorization: `Bearer ${currentToken}`, | ||||||
|       }, |       }, | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     axiosInstance |     axiosInstance | ||||||
|       .get(`/device/${encodeURIComponent(selectedDeviceId)}`, options) |       .get( | ||||||
|  |         `${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}`, | ||||||
|  |         options, | ||||||
|  |       ) | ||||||
|       .then((response) => { |       .then((response) => { | ||||||
|         setDevice(response.data); |         setDevice(response.data); | ||||||
|       }) |       }) | ||||||
| @@ -63,9 +60,8 @@ const DeviceConfiguration = ({ selectedDeviceId }) => { | |||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     if (selectedDeviceId) getDevice(); |     if (deviceSerialNumber) getDevice(); | ||||||
|     setCopyPasswordSuccess(null); |   }, [deviceSerialNumber]); | ||||||
|   }, [selectedDeviceId]); |  | ||||||
|  |  | ||||||
|   if (device) { |   if (device) { | ||||||
|     return ( |     return ( | ||||||
| @@ -73,7 +69,9 @@ const DeviceConfiguration = ({ selectedDeviceId }) => { | |||||||
|         <CCard> |         <CCard> | ||||||
|           <CCardHeader> |           <CCardHeader> | ||||||
|             <CRow> |             <CRow> | ||||||
|               <CCol>{t('configuration.details')}</CCol> |               <CCol> | ||||||
|  |                 <div className="text-value-lg">{t('configuration.title')}</div> | ||||||
|  |               </CCol> | ||||||
|               <CCol> |               <CCol> | ||||||
|                 <div className={styles.alignRight}> |                 <div className={styles.alignRight}> | ||||||
|                   <CPopover content={t('configuration.view_json')}> |                   <CPopover content={t('configuration.view_json')}> | ||||||
| @@ -86,126 +84,115 @@ const DeviceConfiguration = ({ selectedDeviceId }) => { | |||||||
|             </CRow> |             </CRow> | ||||||
|           </CCardHeader> |           </CCardHeader> | ||||||
|           <CCardBody> |           <CCardBody> | ||||||
|             <CForm |             <CRow className={styles.spacedRow}> | ||||||
|               action="" |               <CCol md="3"> | ||||||
|               method="post" |                 <CLabel>{t('common.uuid')} : </CLabel> | ||||||
|               encType="multipart/form-data" |               </CCol> | ||||||
|               className="form-horizontal" |               <CCol xs="12" md="9"> | ||||||
|             > |                 {device.UUID} | ||||||
|               <CFormGroup row> |               </CCol> | ||||||
|                 <CCol md="3"> |             </CRow> | ||||||
|                   <CLabel>{t('common.uuid')} : </CLabel> |             <CRow className={styles.spacedRow}> | ||||||
|                 </CCol> |               <CCol md="3"> | ||||||
|                 <CCol xs="12" md="9"> |                 <CLabel>{t('common.serial_number')} : </CLabel> | ||||||
|                   {device.UUID} |               </CCol> | ||||||
|                 </CCol> |               <CCol xs="12" md="9"> | ||||||
|               </CFormGroup> |                 {device.serialNumber} | ||||||
|               <CFormGroup row> |                 <CopyToClipboardButton size="sm" content={device.serialNumber} /> | ||||||
|                 <CCol md="3"> |               </CCol> | ||||||
|                   <CLabel>{t('common.serial_number')} : </CLabel> |             </CRow> | ||||||
|                 </CCol> |             <CRow className={styles.spacedRow}> | ||||||
|                 <CCol xs="12" md="9"> |               <CCol md="3"> | ||||||
|                   {device.serialNumber} |                 <CLabel>{t('configuration.type')} : </CLabel> | ||||||
|                 </CCol> |               </CCol> | ||||||
|               </CFormGroup> |               <CCol xs="12" md="9"> | ||||||
|               <CFormGroup row> |                 {device.deviceType} | ||||||
|                 <CCol md="3"> |               </CCol> | ||||||
|                   <CLabel>{t('configuration.type')} : </CLabel> |             </CRow> | ||||||
|                 </CCol> |             <CRow className={styles.spacedRow}> | ||||||
|                 <CCol xs="12" md="9"> |               <CCol md="3"> | ||||||
|                   {device.deviceType} |                 <CLabel>{t('configuration.last_configuration_change')} : </CLabel> | ||||||
|                 </CCol> |               </CCol> | ||||||
|               </CFormGroup> |               <CCol xs="12" md="9"> | ||||||
|               <CFormGroup row> |                 {prettyDate(device.lastConfigurationChange)} | ||||||
|                 <CCol md="3"> |               </CCol> | ||||||
|                   <CLabel>{t('configuration.last_configuration_change')} : </CLabel> |             </CRow> | ||||||
|                 </CCol> |             <CRow className={styles.spacedRow}> | ||||||
|                 <CCol xs="12" md="9"> |               <CCol md="3"> | ||||||
|                   {prettyDate(device.lastConfigurationChange)} |                 <CLabel>{t('common.mac')} :</CLabel> | ||||||
|                 </CCol> |               </CCol> | ||||||
|               </CFormGroup> |               <CCol xs="12" md="9"> | ||||||
|               <CFormGroup row> |                 {device.macAddress} | ||||||
|                 <CCol md="3"> |               </CCol> | ||||||
|                   <CLabel>{t('common.mac')} :</CLabel> |             </CRow> | ||||||
|                 </CCol> |             <CRow className={styles.spacedRow}> | ||||||
|                 <CCol xs="12" md="9"> |               <CCol md="3"> | ||||||
|                   {device.macAddress} |                 <CLabel>{t('configuration.created')} : </CLabel> | ||||||
|                 </CCol> |               </CCol> | ||||||
|               </CFormGroup> |               <CCol xs="12" md="9"> | ||||||
|               <CFormGroup row> |                 {prettyDate(device.createdTimestamp)} | ||||||
|                 <CCol md="3"> |               </CCol> | ||||||
|                   <CLabel>{t('configuration.created')} : </CLabel> |             </CRow> | ||||||
|                 </CCol> |             <CRow className={styles.spacedRow}> | ||||||
|                 <CCol xs="12" md="9"> |               <CCol md="3" className={styles.topPadding}> | ||||||
|                   {prettyDate(device.createdTimestamp)} |                 <CLabel>{t('configuration.device_password')} : </CLabel> | ||||||
|                 </CCol> |               </CCol> | ||||||
|               </CFormGroup> |               <CCol xs="12" md="9"> | ||||||
|               <CFormGroup row> |                 {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"> |                 <CCol md="3"> | ||||||
|                   <CLabel>{t('configuration.last_configuration_download')} : </CLabel> |                   <CLabel>{t('configuration.last_configuration_download')} : </CLabel> | ||||||
|                 </CCol> |                 </CCol> | ||||||
|                 <CCol xs="12" md="9"> |                 <CCol xs="12" md="9"> | ||||||
|                   {prettyDate(device.lastConfigurationDownload)} |                   {prettyDate(device.lastConfigurationDownload)} | ||||||
|                 </CCol> |                 </CCol> | ||||||
|               </CFormGroup> |               </CRow> | ||||||
|               <CFormGroup row> |               <CRow className={styles.spacedRow}> | ||||||
|                 <CCol md="3"> |                 <CCol md="3"> | ||||||
|                   <CLabel>{t('configuration.device_password')} : </CLabel> |                   <CLabel>{t('common.manufacturer')} :</CLabel> | ||||||
|                 </CCol> |                 </CCol> | ||||||
|                 <CCol xs="12" md="9"> |                 <CCol xs="12" md="9"> | ||||||
|                   {device.devicePassword === '' ? 'openwifi' : device.devicePassword} |                   {device.manufacturer} | ||||||
|                   <CPopover content={t('common.copy_to_clipboard')}> |  | ||||||
|                     <CButton onClick={copyPasswordToClipboard} size="sm"> |  | ||||||
|                       <CIcon content={cilClone} /> |  | ||||||
|                     </CButton> |  | ||||||
|                   </CPopover> |  | ||||||
|                   {copyPasswordSuccess} |  | ||||||
|                 </CCol> |                 </CCol> | ||||||
|               </CFormGroup> |               </CRow> | ||||||
|               <CCollapse show={collapse}> |               <CRow className={styles.spacedRow}> | ||||||
|                 <CFormGroup row> |                 <CCol md="3"> | ||||||
|                   <CCol md="3"> |                   <CLabel>{t('configuration.owner')} :</CLabel> | ||||||
|                     <CLabel>{t('common.manufacturer')} :</CLabel> |                 </CCol> | ||||||
|                   </CCol> |                 <CCol xs="12" md="9"> | ||||||
|                   <CCol xs="12" md="9"> |                   {device.owner} | ||||||
|                     {device.manufacturer} |                 </CCol> | ||||||
|                   </CCol> |               </CRow> | ||||||
|                 </CFormGroup> |               <CRow className={styles.spacedRow}> | ||||||
|                 <CFormGroup row> |                 <CCol md="3"> | ||||||
|                   <CCol md="3"> |                   <CLabel>{t('configuration.location')} :</CLabel> | ||||||
|                     <CLabel htmlFor="text-input">{t('configuration.notes')} :</CLabel> |                 </CCol> | ||||||
|                   </CCol> |                 <CCol xs="12" md="9"> | ||||||
|                   <CCol xs="12" md="9"> |                   {device.location} | ||||||
|                     <CInput id="text-input" name="text-input" placeholder={device.notes} /> |                 </CCol> | ||||||
|                   </CCol> |               </CRow> | ||||||
|                 </CFormGroup> |             </CCollapse> | ||||||
|                 <CFormGroup row> |             <CCardFooter> | ||||||
|                   <CCol md="3"> |               <CButton show={collapse ? 'true' : 'false'} onClick={toggle} block> | ||||||
|                     <CLabel>{t('configuration.owner')} :</CLabel> |                 <CIcon | ||||||
|                   </CCol> |                   className={styles.blackIcon} | ||||||
|                   <CCol xs="12" md="9"> |                   name={collapse ? 'cilChevronTop' : 'cilChevronBottom'} | ||||||
|                     {device.owner} |                   size="lg" | ||||||
|                   </CCol> |                 /> | ||||||
|                 </CFormGroup> |               </CButton> | ||||||
|                 <CFormGroup row> |             </CCardFooter> | ||||||
|                   <CCol md="3"> |  | ||||||
|                     <CLabel>{t('configuration.location')} :</CLabel> |  | ||||||
|                   </CCol> |  | ||||||
|                   <CCol xs="12" md="9"> |  | ||||||
|                     {device.location} |  | ||||||
|                   </CCol> |  | ||||||
|                 </CFormGroup> |  | ||||||
|               </CCollapse> |  | ||||||
|               <CCardFooter> |  | ||||||
|                 <CButton show={collapse ? 'true' : 'false'} onClick={toggle} block> |  | ||||||
|                   <CIcon |  | ||||||
|                     className={styles.blackIcon} |  | ||||||
|                     name={collapse ? 'cilChevronTop' : 'cilChevronBottom'} |  | ||||||
|                     size="lg" |  | ||||||
|                   /> |  | ||||||
|                 </CButton> |  | ||||||
|               </CCardFooter> |  | ||||||
|             </CForm> |  | ||||||
|           </CCardBody> |           </CCardBody> | ||||||
|         </CCard> |         </CCard> | ||||||
|         <DeviceConfigurationModal show={showModal} toggle={toggleModal} configuration={device} /> |         <DeviceConfigurationModal show={showModal} toggle={toggleModal} configuration={device} /> | ||||||
| @@ -221,8 +208,4 @@ const DeviceConfiguration = ({ selectedDeviceId }) => { | |||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| DeviceConfiguration.propTypes = { |  | ||||||
|   selectedDeviceId: PropTypes.string.isRequired, |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| export default DeviceConfiguration; | export default DeviceConfiguration; | ||||||
|   | |||||||
| @@ -5,3 +5,16 @@ | |||||||
| .blackIcon { | .blackIcon { | ||||||
|   color: black; |   color: black; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | .modalTitle { | ||||||
|  |   color: black; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .topPadding { | ||||||
|  |   padding-top: 5px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .spacedRow { | ||||||
|  |   margin-top: 5px; | ||||||
|  |   margin-bottom: 5px; | ||||||
|  | } | ||||||
|   | |||||||
| @@ -15,17 +15,19 @@ import { | |||||||
| import CIcon from '@coreui/icons-react'; | import CIcon from '@coreui/icons-react'; | ||||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||||
| import DatePicker from 'react-widgets/DatePicker'; | import DatePicker from 'react-widgets/DatePicker'; | ||||||
| import PropTypes from 'prop-types'; |  | ||||||
| import { prettyDate, dateToUnix } from 'utils/helper'; | import { prettyDate, dateToUnix } from 'utils/helper'; | ||||||
|  | import { useAuth } from 'contexts/AuthProvider'; | ||||||
|  | import { useDevice } from 'contexts/DeviceProvider'; | ||||||
| import axiosInstance from 'utils/axiosInstance'; | import axiosInstance from 'utils/axiosInstance'; | ||||||
| import { getToken } from 'utils/authHelper'; |  | ||||||
| import eventBus from 'utils/eventBus'; | import eventBus from 'utils/eventBus'; | ||||||
| import LoadingButton from 'components/LoadingButton'; | import LoadingButton from 'components/LoadingButton'; | ||||||
| import DeleteLogModal from 'components/DeleteLogModal'; | import DeleteLogModal from 'components/DeleteLogModal'; | ||||||
| import styles from './index.module.scss'; | import styles from './index.module.scss'; | ||||||
|  |  | ||||||
| const DeviceHealth = ({ selectedDeviceId }) => { | const DeviceHealth = () => { | ||||||
|   const { t } = useTranslation(); |   const { t } = useTranslation(); | ||||||
|  |   const { currentToken, endpoints } = useAuth(); | ||||||
|  |   const { deviceSerialNumber } = useDevice(); | ||||||
|   const [collapse, setCollapse] = useState(false); |   const [collapse, setCollapse] = useState(false); | ||||||
|   const [details, setDetails] = useState([]); |   const [details, setDetails] = useState([]); | ||||||
|   const [loading, setLoading] = useState(false); |   const [loading, setLoading] = useState(false); | ||||||
| @@ -68,7 +70,7 @@ const DeviceHealth = ({ selectedDeviceId }) => { | |||||||
|     const options = { |     const options = { | ||||||
|       headers: { |       headers: { | ||||||
|         Accept: 'application/json', |         Accept: 'application/json', | ||||||
|         Authorization: `Bearer ${getToken()}`, |         Authorization: `Bearer ${currentToken}`, | ||||||
|       }, |       }, | ||||||
|       params: { |       params: { | ||||||
|         limit: logLimit, |         limit: logLimit, | ||||||
| @@ -85,7 +87,12 @@ const DeviceHealth = ({ selectedDeviceId }) => { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     axiosInstance |     axiosInstance | ||||||
|       .get(`/device/${encodeURIComponent(selectedDeviceId)}/healthchecks${extraParams}`, options) |       .get( | ||||||
|  |         `${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent( | ||||||
|  |           deviceSerialNumber, | ||||||
|  |         )}/healthchecks${extraParams}`, | ||||||
|  |         options, | ||||||
|  |       ) | ||||||
|       .then((response) => { |       .then((response) => { | ||||||
|         setHealthChecks(response.data.values); |         setHealthChecks(response.data.values); | ||||||
|       }) |       }) | ||||||
| @@ -128,7 +135,7 @@ const DeviceHealth = ({ selectedDeviceId }) => { | |||||||
|   ]; |   ]; | ||||||
|  |  | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     if (selectedDeviceId) { |     if (deviceSerialNumber) { | ||||||
|       setLogLimit(25); |       setLogLimit(25); | ||||||
|       setLoadingMore(false); |       setLoadingMore(false); | ||||||
|       setShowLoadingMore(true); |       setShowLoadingMore(true); | ||||||
| @@ -136,7 +143,7 @@ const DeviceHealth = ({ selectedDeviceId }) => { | |||||||
|       setEnd(''); |       setEnd(''); | ||||||
|       getDeviceHealth(); |       getDeviceHealth(); | ||||||
|     } |     } | ||||||
|   }, [selectedDeviceId]); |   }, [deviceSerialNumber]); | ||||||
|  |  | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     if (logLimit !== 25) { |     if (logLimit !== 25) { | ||||||
| @@ -168,12 +175,12 @@ const DeviceHealth = ({ selectedDeviceId }) => { | |||||||
|   }, [healthChecks]); |   }, [healthChecks]); | ||||||
|  |  | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     if (selectedDeviceId && start !== '' && end !== '') { |     if (deviceSerialNumber && start !== '' && end !== '') { | ||||||
|       getDeviceHealth(); |       getDeviceHealth(); | ||||||
|     } else if (selectedDeviceId && start === '' && end === '') { |     } else if (deviceSerialNumber && start === '' && end === '') { | ||||||
|       getDeviceHealth(); |       getDeviceHealth(); | ||||||
|     } |     } | ||||||
|   }, [start, end, selectedDeviceId]); |   }, [start, end, deviceSerialNumber]); | ||||||
|  |  | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     eventBus.on('deletedHealth', () => getDeviceHealth()); |     eventBus.on('deletedHealth', () => getDeviceHealth()); | ||||||
| @@ -185,8 +192,8 @@ const DeviceHealth = ({ selectedDeviceId }) => { | |||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
|     <CWidgetDropdown |     <CWidgetDropdown | ||||||
|       header={sanityLevel ? `${sanityLevel}%` : t('common.unknown')} |       header={t('health.title')} | ||||||
|       text={t('health.title')} |       text={sanityLevel ? `${sanityLevel}%` : t('common.unknown')} | ||||||
|       value={sanityLevel ?? 100} |       value={sanityLevel ?? 100} | ||||||
|       color={barColor} |       color={barColor} | ||||||
|       inverse="true" |       inverse="true" | ||||||
| @@ -210,11 +217,13 @@ const DeviceHealth = ({ selectedDeviceId }) => { | |||||||
|             </div> |             </div> | ||||||
|             <CRow className={styles.spacedRow}> |             <CRow className={styles.spacedRow}> | ||||||
|               <CCol> |               <CCol> | ||||||
|                 {t('common.from')}: |                 {t('common.from')} | ||||||
|  |                 : | ||||||
|                 <DatePicker includeTime onChange={(date) => modifyStart(date)} /> |                 <DatePicker includeTime onChange={(date) => modifyStart(date)} /> | ||||||
|               </CCol> |               </CCol> | ||||||
|               <CCol> |               <CCol> | ||||||
|                 {t('common.to')}: |                 {t('common.to')} | ||||||
|  |                 : | ||||||
|                 <DatePicker includeTime onChange={(date) => modifyEnd(date)} /> |                 <DatePicker includeTime onChange={(date) => modifyEnd(date)} /> | ||||||
|               </CCol> |               </CCol> | ||||||
|             </CRow> |             </CRow> | ||||||
| @@ -281,7 +290,7 @@ const DeviceHealth = ({ selectedDeviceId }) => { | |||||||
|             /> |             /> | ||||||
|           </CButton> |           </CButton> | ||||||
|           <DeleteLogModal |           <DeleteLogModal | ||||||
|             serialNumber={selectedDeviceId} |             serialNumber={deviceSerialNumber} | ||||||
|             object="healthchecks" |             object="healthchecks" | ||||||
|             show={showDeleteModal} |             show={showDeleteModal} | ||||||
|             toggle={toggleDeleteModal} |             toggle={toggleDeleteModal} | ||||||
| @@ -292,8 +301,4 @@ const DeviceHealth = ({ selectedDeviceId }) => { | |||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| DeviceHealth.propTypes = { |  | ||||||
|   selectedDeviceId: PropTypes.string.isRequired, |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| export default DeviceHealth; | export default DeviceHealth; | ||||||
|   | |||||||
| @@ -10,16 +10,16 @@ import { | |||||||
|   CRow, |   CRow, | ||||||
|   CCol, |   CCol, | ||||||
|   CPopover, |   CPopover, | ||||||
|  |   CSelect, | ||||||
| } from '@coreui/react'; | } from '@coreui/react'; | ||||||
| import ReactPaginate from 'react-paginate'; | import ReactPaginate from 'react-paginate'; | ||||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||||
| import Select from 'react-select'; |  | ||||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||||
| import { cilSync, cilInfo, cilBadge, cilBan } from '@coreui/icons'; | import { cilSync, cilInfo, cilBadge, cilBan } from '@coreui/icons'; | ||||||
| import CIcon from '@coreui/icons-react'; | import CIcon from '@coreui/icons-react'; | ||||||
| import { getToken } from 'utils/authHelper'; | import { useAuth } from 'contexts/AuthProvider'; | ||||||
| import axiosInstance from 'utils/axiosInstance'; | import axiosInstance from 'utils/axiosInstance'; | ||||||
| import { cleanBytesString, cropStringWithEllipsis } from 'utils/helper'; | import { cleanBytesString } from 'utils/helper'; | ||||||
| import meshIcon from 'assets/icons/Mesh.png'; | import meshIcon from 'assets/icons/Mesh.png'; | ||||||
| import apIcon from 'assets/icons/AP.png'; | import apIcon from 'assets/icons/AP.png'; | ||||||
| import internetSwitch from 'assets/icons/Switch.png'; | import internetSwitch from 'assets/icons/Switch.png'; | ||||||
| @@ -29,6 +29,7 @@ import styles from './index.module.scss'; | |||||||
|  |  | ||||||
| const DeviceList = () => { | const DeviceList = () => { | ||||||
|   const { t } = useTranslation(); |   const { t } = useTranslation(); | ||||||
|  |   const { currentToken, endpoints } = useAuth(); | ||||||
|   const [loadedSerials, setLoadedSerials] = useState(false); |   const [loadedSerials, setLoadedSerials] = useState(false); | ||||||
|   const [serialNumbers, setSerialNumbers] = useState([]); |   const [serialNumbers, setSerialNumbers] = useState([]); | ||||||
|   const [page, setPage] = useState(0); |   const [page, setPage] = useState(0); | ||||||
| @@ -38,16 +39,15 @@ const DeviceList = () => { | |||||||
|   const [loading, setLoading] = useState(true); |   const [loading, setLoading] = useState(true); | ||||||
|  |  | ||||||
|   const getSerialNumbers = () => { |   const getSerialNumbers = () => { | ||||||
|     const token = getToken(); |  | ||||||
|     setLoading(true); |     setLoading(true); | ||||||
|  |  | ||||||
|     const headers = { |     const headers = { | ||||||
|       Accept: 'application/json', |       Accept: 'application/json', | ||||||
|       Authorization: `Bearer ${token}`, |       Authorization: `Bearer ${currentToken}`, | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     axiosInstance |     axiosInstance | ||||||
|       .get('/devices?serialOnly=true', { |       .get(`${endpoints.ucentralgw}/api/v1/devices?serialOnly=true`, { | ||||||
|         headers, |         headers, | ||||||
|       }) |       }) | ||||||
|       .then((response) => { |       .then((response) => { | ||||||
| @@ -60,12 +60,11 @@ const DeviceList = () => { | |||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   const getDeviceInformation = () => { |   const getDeviceInformation = () => { | ||||||
|     const token = getToken(); |  | ||||||
|     setLoading(true); |     setLoading(true); | ||||||
|  |  | ||||||
|     const headers = { |     const headers = { | ||||||
|       Accept: 'application/json', |       Accept: 'application/json', | ||||||
|       Authorization: `Bearer ${token}`, |       Authorization: `Bearer ${currentToken}`, | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     const startIndex = page * devicesPerPage; |     const startIndex = page * devicesPerPage; | ||||||
| @@ -76,7 +75,7 @@ const DeviceList = () => { | |||||||
|       .join(','); |       .join(','); | ||||||
|  |  | ||||||
|     axiosInstance |     axiosInstance | ||||||
|       .get(`/devices?deviceWithStatus=true&select=${serialsToGet}`, { |       .get(`${endpoints.ucentralgw}/api/v1/devices?deviceWithStatus=true&select=${serialsToGet}`, { | ||||||
|         headers, |         headers, | ||||||
|       }) |       }) | ||||||
|       .then((response) => { |       .then((response) => { | ||||||
| @@ -89,18 +88,22 @@ const DeviceList = () => { | |||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   const refreshDevice = (serialNumber) => { |   const refreshDevice = (serialNumber) => { | ||||||
|     const token = getToken(); |  | ||||||
|     setLoading(true); |     setLoading(true); | ||||||
|  |  | ||||||
|     const headers = { |     const headers = { | ||||||
|       Accept: 'application/json', |       Accept: 'application/json', | ||||||
|       Authorization: `Bearer ${token}`, |       Authorization: `Bearer ${currentToken}`, | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     axiosInstance |     axiosInstance | ||||||
|       .get(`/devices?deviceWithStatus=true&select=${encodeURIComponent(serialNumber)}`, { |       .get( | ||||||
|         headers, |         `${endpoints.ucentralgw}/api/v1/devices?deviceWithStatus=true&select=${encodeURIComponent( | ||||||
|       }) |           serialNumber, | ||||||
|  |         )}`, | ||||||
|  |         { | ||||||
|  |           headers, | ||||||
|  |         }, | ||||||
|  |       ) | ||||||
|       .then((response) => { |       .then((response) => { | ||||||
|         const device = response.data.devicesWithStatus[0]; |         const device = response.data.devicesWithStatus[0]; | ||||||
|         const foundIndex = devices.findIndex((obj) => obj.serialNumber === serialNumber); |         const foundIndex = devices.findIndex((obj) => obj.serialNumber === serialNumber); | ||||||
| @@ -168,7 +171,7 @@ const DeviceListDisplay = ({ | |||||||
|     { key: 'verifiedCertificate', label: t('common.certificate'), _style: { width: '1%' } }, |     { key: 'verifiedCertificate', label: t('common.certificate'), _style: { width: '1%' } }, | ||||||
|     { key: 'serialNumber', label: t('common.serial_number'), _style: { width: '5%' } }, |     { key: 'serialNumber', label: t('common.serial_number'), _style: { width: '5%' } }, | ||||||
|     { key: 'UUID', label: t('common.config_id'), _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: 'compatible', label: t('common.compatible'), filter: false, _style: { width: '20%' } }, | ||||||
|     { key: 'txBytes', label: 'Tx', filter: false, _style: { width: '12%' } }, |     { key: 'txBytes', label: 'Tx', filter: false, _style: { width: '12%' } }, | ||||||
|     { key: 'rxBytes', label: 'Rx', 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) => { |   const getDeviceIcon = (deviceType) => { | ||||||
|     if (deviceType === 'AP_Default' || deviceType === 'AP') { |     if (deviceType === 'AP_Default' || deviceType === 'AP') { | ||||||
|       return <img src={apIcon} className={styles.icon} alt="AP" />; |       return <img src={apIcon} className={styles.icon} alt="AP" />; | ||||||
| @@ -281,13 +278,17 @@ const DeviceListDisplay = ({ | |||||||
|         <CCardHeader> |         <CCardHeader> | ||||||
|           <CRow> |           <CRow> | ||||||
|             <CCol /> |             <CCol /> | ||||||
|             <CCol xs={2}> |             <CCol xs={1}> | ||||||
|               <Select |               <CSelect | ||||||
|                 isClearable={false} |                 custom | ||||||
|                 options={selectOptions} |                 defaultValue={devicesPerPage} | ||||||
|                 defaultValue={{ value: devicesPerPage, label: devicesPerPage }} |                 onChange={(e) => updateDevicesPerPage(e.target.value)} | ||||||
|                 onChange={(value) => updateDevicesPerPage(value.value)} |                 disabled={loading} | ||||||
|               /> |               > | ||||||
|  |                 <option value="10">10</option> | ||||||
|  |                 <option value="25">25</option> | ||||||
|  |                 <option value="50">50</option> | ||||||
|  |               </CSelect> | ||||||
|             </CCol> |             </CCol> | ||||||
|           </CRow> |           </CRow> | ||||||
|         </CCardHeader> |         </CCardHeader> | ||||||
| @@ -296,6 +297,7 @@ const DeviceListDisplay = ({ | |||||||
|             items={devices ?? []} |             items={devices ?? []} | ||||||
|             fields={columns} |             fields={columns} | ||||||
|             hover |             hover | ||||||
|  |             border | ||||||
|             loading={loading} |             loading={loading} | ||||||
|             scopedSlots={{ |             scopedSlots={{ | ||||||
|               serialNumber: (item) => ( |               serialNumber: (item) => ( | ||||||
| @@ -337,7 +339,9 @@ const DeviceListDisplay = ({ | |||||||
|                     content={item.firmware ? item.firmware : t('common.na')} |                     content={item.firmware ? item.firmware : t('common.na')} | ||||||
|                     placement="top" |                     placement="top" | ||||||
|                   > |                   > | ||||||
|                     <p>{cropStringWithEllipsis(item.firmware, 16)}</p> |                     <p style={{ width: '225px' }} className="text-truncate"> | ||||||
|  |                       {item.firmware} | ||||||
|  |                     </p> | ||||||
|                   </CPopover> |                   </CPopover> | ||||||
|                 </td> |                 </td> | ||||||
|               ), |               ), | ||||||
| @@ -347,7 +351,9 @@ const DeviceListDisplay = ({ | |||||||
|                     content={item.compatible ? item.compatible : t('common.na')} |                     content={item.compatible ? item.compatible : t('common.na')} | ||||||
|                     placement="top" |                     placement="top" | ||||||
|                   > |                   > | ||||||
|                     <p>{cropStringWithEllipsis(item.compatible, 16)}</p> |                     <p style={{ width: '150px' }} className="text-truncate"> | ||||||
|  |                       {item.compatible} | ||||||
|  |                     </p> | ||||||
|                   </CPopover> |                   </CPopover> | ||||||
|                 </td> |                 </td> | ||||||
|               ), |               ), | ||||||
| @@ -359,7 +365,9 @@ const DeviceListDisplay = ({ | |||||||
|                     content={item.ipAddress ? item.ipAddress : t('common.na')} |                     content={item.ipAddress ? item.ipAddress : t('common.na')} | ||||||
|                     placement="top" |                     placement="top" | ||||||
|                   > |                   > | ||||||
|                     <p>{cropStringWithEllipsis(item.ipAddress, 20)}</p> |                     <p style={{ width: '150px' }} className="text-truncate"> | ||||||
|  |                       {item.ipAddress} | ||||||
|  |                     </p> | ||||||
|                   </CPopover> |                   </CPopover> | ||||||
|                 </td> |                 </td> | ||||||
|               ), |               ), | ||||||
|   | |||||||
| @@ -14,17 +14,19 @@ import { | |||||||
| import CIcon from '@coreui/icons-react'; | import CIcon from '@coreui/icons-react'; | ||||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||||
| import DatePicker from 'react-widgets/DatePicker'; | import DatePicker from 'react-widgets/DatePicker'; | ||||||
| import PropTypes from 'prop-types'; |  | ||||||
| import { prettyDate, dateToUnix } from 'utils/helper'; | import { prettyDate, dateToUnix } from 'utils/helper'; | ||||||
| import axiosInstance from 'utils/axiosInstance'; | import axiosInstance from 'utils/axiosInstance'; | ||||||
| import { getToken } from 'utils/authHelper'; | import { useAuth } from 'contexts/AuthProvider'; | ||||||
|  | import { useDevice } from 'contexts/DeviceProvider'; | ||||||
| import eventBus from 'utils/eventBus'; | import eventBus from 'utils/eventBus'; | ||||||
| import LoadingButton from 'components/LoadingButton'; | import LoadingButton from 'components/LoadingButton'; | ||||||
| import DeleteLogModal from 'components/DeleteLogModal'; | import DeleteLogModal from 'components/DeleteLogModal'; | ||||||
| import styles from './index.module.scss'; | import styles from './index.module.scss'; | ||||||
|  |  | ||||||
| const DeviceLogs = ({ selectedDeviceId }) => { | const DeviceLogs = () => { | ||||||
|   const { t } = useTranslation(); |   const { t } = useTranslation(); | ||||||
|  |   const { currentToken, endpoints } = useAuth(); | ||||||
|  |   const { deviceSerialNumber } = useDevice(); | ||||||
|   const [collapse, setCollapse] = useState(false); |   const [collapse, setCollapse] = useState(false); | ||||||
|   const [details, setDetails] = useState([]); |   const [details, setDetails] = useState([]); | ||||||
|   const [loading, setLoading] = useState(false); |   const [loading, setLoading] = useState(false); | ||||||
| @@ -65,7 +67,7 @@ const DeviceLogs = ({ selectedDeviceId }) => { | |||||||
|     const options = { |     const options = { | ||||||
|       headers: { |       headers: { | ||||||
|         Accept: 'application/json', |         Accept: 'application/json', | ||||||
|         Authorization: `Bearer ${getToken()}`, |         Authorization: `Bearer ${currentToken}`, | ||||||
|       }, |       }, | ||||||
|       params: { |       params: { | ||||||
|         limit: logLimit, |         limit: logLimit, | ||||||
| @@ -82,7 +84,12 @@ const DeviceLogs = ({ selectedDeviceId }) => { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     axiosInstance |     axiosInstance | ||||||
|       .get(`/device/${encodeURIComponent(selectedDeviceId)}/logs${extraParams}`, options) |       .get( | ||||||
|  |         `${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent( | ||||||
|  |           deviceSerialNumber, | ||||||
|  |         )}/logs${extraParams}`, | ||||||
|  |         options, | ||||||
|  |       ) | ||||||
|       .then((response) => { |       .then((response) => { | ||||||
|         setLogs(response.data.values); |         setLogs(response.data.values); | ||||||
|       }) |       }) | ||||||
| @@ -125,7 +132,7 @@ const DeviceLogs = ({ selectedDeviceId }) => { | |||||||
|   ]; |   ]; | ||||||
|  |  | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     if (selectedDeviceId) { |     if (deviceSerialNumber) { | ||||||
|       setLogLimit(25); |       setLogLimit(25); | ||||||
|       setLoadingMore(false); |       setLoadingMore(false); | ||||||
|       setShowLoadingMore(true); |       setShowLoadingMore(true); | ||||||
| @@ -133,7 +140,7 @@ const DeviceLogs = ({ selectedDeviceId }) => { | |||||||
|       setEnd(''); |       setEnd(''); | ||||||
|       getLogs(); |       getLogs(); | ||||||
|     } |     } | ||||||
|   }, [selectedDeviceId]); |   }, [deviceSerialNumber]); | ||||||
|  |  | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     if (logLimit !== 25) { |     if (logLimit !== 25) { | ||||||
| @@ -150,12 +157,12 @@ const DeviceLogs = ({ selectedDeviceId }) => { | |||||||
|   }, [logs]); |   }, [logs]); | ||||||
|  |  | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     if (selectedDeviceId && start !== '' && end !== '') { |     if (deviceSerialNumber && start !== '' && end !== '') { | ||||||
|       getLogs(); |       getLogs(); | ||||||
|     } else if (selectedDeviceId && start === '' && end === '') { |     } else if (deviceSerialNumber && start === '' && end === '') { | ||||||
|       getLogs(); |       getLogs(); | ||||||
|     } |     } | ||||||
|   }, [start, end, selectedDeviceId]); |   }, [start, end, deviceSerialNumber]); | ||||||
|  |  | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     eventBus.on('deletedLogs', () => getLogs()); |     eventBus.on('deletedLogs', () => getLogs()); | ||||||
| @@ -256,11 +263,9 @@ const DeviceLogs = ({ selectedDeviceId }) => { | |||||||
|             </CButton> |             </CButton> | ||||||
|           </div> |           </div> | ||||||
|         } |         } | ||||||
|       > |       /> | ||||||
|         <CIcon name="cilList" className={styles.whiteIcon} size="lg" /> |  | ||||||
|       </CWidgetDropdown> |  | ||||||
|       <DeleteLogModal |       <DeleteLogModal | ||||||
|         serialNumber={selectedDeviceId} |         serialNumber={deviceSerialNumber} | ||||||
|         object="logs" |         object="logs" | ||||||
|         show={showDeleteModal} |         show={showDeleteModal} | ||||||
|         toggle={toggleDeleteModal} |         toggle={toggleDeleteModal} | ||||||
| @@ -269,8 +274,4 @@ const DeviceLogs = ({ selectedDeviceId }) => { | |||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| DeviceLogs.propTypes = { |  | ||||||
|   selectedDeviceId: PropTypes.string.isRequired, |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| export default DeviceLogs; | export default DeviceLogs; | ||||||
|   | |||||||
							
								
								
									
										111
									
								
								src/components/DeviceNotes/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								src/components/DeviceNotes/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,111 @@ | |||||||
|  | import React, { useState } from 'react'; | ||||||
|  | import { useTranslation } from 'react-i18next'; | ||||||
|  | import { CDataTable, CRow, CCol, CLabel, CInput } from '@coreui/react'; | ||||||
|  | import PropTypes from 'prop-types'; | ||||||
|  | import axiosInstance from 'utils/axiosInstance'; | ||||||
|  | import { useAuth } from 'contexts/AuthProvider'; | ||||||
|  | import { prettyDate } from 'utils/helper'; | ||||||
|  | import LoadingButton from 'components/LoadingButton'; | ||||||
|  |  | ||||||
|  | import styles from './index.module.scss'; | ||||||
|  |  | ||||||
|  | const DeviceNotes = ({ serialNumber, notes, refreshNotes }) => { | ||||||
|  |   const { t } = useTranslation(); | ||||||
|  |   const { currentToken, endpoints } = useAuth(); | ||||||
|  |   const [currentNote, setCurrentNote] = useState(''); | ||||||
|  |   const [loading, setLoading] = useState(false); | ||||||
|  |  | ||||||
|  |   const saveNote = () => { | ||||||
|  |     setLoading(true); | ||||||
|  |  | ||||||
|  |     const parameters = { | ||||||
|  |       serialNumber, | ||||||
|  |       notes: [{ note: currentNote }], | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     const headers = { | ||||||
|  |       Accept: 'application/json', | ||||||
|  |       Authorization: `Bearer ${currentToken}`, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     axiosInstance | ||||||
|  |       .put( | ||||||
|  |         `${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(serialNumber)}`, | ||||||
|  |         parameters, | ||||||
|  |         { headers }, | ||||||
|  |       ) | ||||||
|  |       .then(() => { | ||||||
|  |         setCurrentNote(''); | ||||||
|  |         refreshNotes(); | ||||||
|  |       }) | ||||||
|  |       .catch(() => {}) | ||||||
|  |       .finally(() => { | ||||||
|  |         setLoading(false); | ||||||
|  |       }); | ||||||
|  |   }; | ||||||
|  |   const columns = [ | ||||||
|  |     { key: 'created', label: t('common.date'), _style: { width: '30%' } }, | ||||||
|  |     { key: 'createdBy', label: t('common.created_by'), _style: { width: '20%' } }, | ||||||
|  |     { key: 'note', label: t('configuration.note'), _style: { width: '50%' } }, | ||||||
|  |   ]; | ||||||
|  |  | ||||||
|  |   return ( | ||||||
|  |     <div> | ||||||
|  |       <CRow className={styles.spacedRow}> | ||||||
|  |         <CCol md="3"> | ||||||
|  |           <CLabel>{t('configuration.notes')} :</CLabel> | ||||||
|  |         </CCol> | ||||||
|  |         <CCol xs="9" md="7"> | ||||||
|  |           <CInput | ||||||
|  |             id="notes-input" | ||||||
|  |             name="text-input" | ||||||
|  |             value={currentNote} | ||||||
|  |             onChange={(e) => setCurrentNote(e.target.value)} | ||||||
|  |           /> | ||||||
|  |         </CCol> | ||||||
|  |         <CCol> | ||||||
|  |           <LoadingButton | ||||||
|  |             label={t('common.add')} | ||||||
|  |             isLoadingLabel={t('common.adding_ellipsis')} | ||||||
|  |             isLoading={loading} | ||||||
|  |             action={saveNote} | ||||||
|  |             disabled={loading || currentNote === ''} | ||||||
|  |           /> | ||||||
|  |         </CCol> | ||||||
|  |       </CRow> | ||||||
|  |       <CRow> | ||||||
|  |         <CCol md="3" /> | ||||||
|  |         <CCol xs="12" md="9"> | ||||||
|  |           <div className={['overflow-auto', styles.scrollableBox].join(' ')}> | ||||||
|  |             <CDataTable | ||||||
|  |               striped | ||||||
|  |               responsive | ||||||
|  |               border | ||||||
|  |               loading={loading} | ||||||
|  |               fields={columns} | ||||||
|  |               className={styles.table} | ||||||
|  |               items={notes || []} | ||||||
|  |               noItemsView={{ noItems: t('common.no_items') }} | ||||||
|  |               sorterValue={{ column: 'created', desc: 'true' }} | ||||||
|  |               scopedSlots={{ | ||||||
|  |                 created: (item) => ( | ||||||
|  |                   <td> | ||||||
|  |                     {item.created && item.created !== 0 ? prettyDate(item.created) : t('common.na')} | ||||||
|  |                   </td> | ||||||
|  |                 ), | ||||||
|  |               }} | ||||||
|  |             /> | ||||||
|  |           </div> | ||||||
|  |         </CCol> | ||||||
|  |       </CRow> | ||||||
|  |     </div> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | DeviceNotes.propTypes = { | ||||||
|  |   serialNumber: PropTypes.string.isRequired, | ||||||
|  |   notes: PropTypes.arrayOf(PropTypes.instanceOf(Object)).isRequired, | ||||||
|  |   refreshNotes: PropTypes.func.isRequired, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default DeviceNotes; | ||||||
							
								
								
									
										15
									
								
								src/components/DeviceNotes/index.module.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/components/DeviceNotes/index.module.scss
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | |||||||
|  | .scrollableBox { | ||||||
|  |   height: 200px; | ||||||
|  |   border-style: solid; | ||||||
|  |   border-color: #ced2d8; | ||||||
|  |   margin-bottom: 25px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .table { | ||||||
|  |   color: white; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .spacedRow { | ||||||
|  |   margin-top: 5px; | ||||||
|  |   margin-bottom: 20px; | ||||||
|  | } | ||||||
							
								
								
									
										36
									
								
								src/components/DeviceStatusCard/MemoryBar.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/components/DeviceStatusCard/MemoryBar.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | |||||||
|  | import React from 'react'; | ||||||
|  | import { CPopover, CProgress, CProgressBar } from '@coreui/react'; | ||||||
|  | import PropTypes from 'prop-types'; | ||||||
|  | import { useTranslation } from 'react-i18next'; | ||||||
|  | import { cleanBytesString } from 'utils/helper'; | ||||||
|  |  | ||||||
|  | const MemoryBar = ({ usedBytes, totalBytes }) => { | ||||||
|  |   const { t } = useTranslation(); | ||||||
|  |   const used = cleanBytesString(usedBytes); | ||||||
|  |   const total = cleanBytesString(totalBytes); | ||||||
|  |   const percentage = Math.floor((usedBytes / totalBytes) * 100); | ||||||
|  |  | ||||||
|  |   return ( | ||||||
|  |     <CPopover content={t('status.used_total_memory', { used, total })}> | ||||||
|  |       <CProgress> | ||||||
|  |         <CProgressBar value={percentage}> | ||||||
|  |           {percentage >= 25 ? t('status.percentage_used', { percentage, total }) : ''} | ||||||
|  |         </CProgressBar> | ||||||
|  |         <CProgressBar value={100 - percentage} color="transparent"> | ||||||
|  |           <div style={{ color: 'black' }}> | ||||||
|  |             {percentage < 25 | ||||||
|  |               ? t('status.percentage_free', { percentage: 100 - percentage, total }) | ||||||
|  |               : ''} | ||||||
|  |           </div> | ||||||
|  |         </CProgressBar> | ||||||
|  |       </CProgress> | ||||||
|  |     </CPopover> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | MemoryBar.propTypes = { | ||||||
|  |   usedBytes: PropTypes.number.isRequired, | ||||||
|  |   totalBytes: PropTypes.number.isRequired, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default React.memo(MemoryBar); | ||||||
							
								
								
									
										197
									
								
								src/components/DeviceStatusCard/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										197
									
								
								src/components/DeviceStatusCard/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,197 @@ | |||||||
|  | import React, { useState, useEffect } from 'react'; | ||||||
|  | import { | ||||||
|  |   CCard, | ||||||
|  |   CCardHeader, | ||||||
|  |   CRow, | ||||||
|  |   CCol, | ||||||
|  |   CCardBody, | ||||||
|  |   CBadge, | ||||||
|  |   CModalBody, | ||||||
|  |   CAlert, | ||||||
|  |   CPopover, | ||||||
|  |   CButton, | ||||||
|  |   CSpinner, | ||||||
|  | } from '@coreui/react'; | ||||||
|  | import CIcon from '@coreui/icons-react'; | ||||||
|  | import { useAuth } from 'contexts/AuthProvider'; | ||||||
|  | import { useDevice } from 'contexts/DeviceProvider'; | ||||||
|  | import { cilSync } from '@coreui/icons'; | ||||||
|  | import { useTranslation } from 'react-i18next'; | ||||||
|  | import axiosInstance from 'utils/axiosInstance'; | ||||||
|  | import { prettyDate, secondsToDetailed } from 'utils/helper'; | ||||||
|  | import MemoryBar from './MemoryBar'; | ||||||
|  |  | ||||||
|  | import styles from './index.module.scss'; | ||||||
|  |  | ||||||
|  | const DeviceStatusCard = () => { | ||||||
|  |   const { t } = useTranslation(); | ||||||
|  |   const { currentToken, endpoints } = useAuth(); | ||||||
|  |   const { deviceSerialNumber } = useDevice(); | ||||||
|  |   const [lastStats, setLastStats] = useState(null); | ||||||
|  |   const [status, setStatus] = useState(null); | ||||||
|  |   const [error, setError] = useState(false); | ||||||
|  |   const [loading, setLoading] = useState(false); | ||||||
|  |  | ||||||
|  |   const transformLoad = (load) => { | ||||||
|  |     if (load === undefined) return t('common.na'); | ||||||
|  |     return `${((load / 65536) * 100).toFixed(2)}%`; | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   const getData = () => { | ||||||
|  |     setLoading(true); | ||||||
|  |     const options = { | ||||||
|  |       headers: { | ||||||
|  |         Accept: 'application/json', | ||||||
|  |         Authorization: `Bearer ${currentToken}`, | ||||||
|  |       }, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     const lastStatsRequest = axiosInstance.get( | ||||||
|  |       `${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent( | ||||||
|  |         deviceSerialNumber, | ||||||
|  |       )}/statistics?lastOnly=true`, | ||||||
|  |       options, | ||||||
|  |     ); | ||||||
|  |     const statusRequest = axiosInstance.get( | ||||||
|  |       `${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}/status`, | ||||||
|  |       options, | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     Promise.all([lastStatsRequest, statusRequest]) | ||||||
|  |       .then(([newStats, newStatus]) => { | ||||||
|  |         setLastStats(newStats.data); | ||||||
|  |         setStatus(newStatus.data); | ||||||
|  |       }) | ||||||
|  |       .catch(() => { | ||||||
|  |         setError(true); | ||||||
|  |       }) | ||||||
|  |       .finally(() => { | ||||||
|  |         setLoading(false); | ||||||
|  |       }); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   useEffect(() => { | ||||||
|  |     setError(false); | ||||||
|  |     if (deviceSerialNumber) getData(); | ||||||
|  |   }, [deviceSerialNumber]); | ||||||
|  |  | ||||||
|  |   if (!error) { | ||||||
|  |     return ( | ||||||
|  |       <CCard> | ||||||
|  |         <CCardHeader> | ||||||
|  |           <CRow> | ||||||
|  |             <CCol> | ||||||
|  |               <div className="text-value-lg"> | ||||||
|  |                 {t('status.title', { serialNumber: deviceSerialNumber })} | ||||||
|  |               </div> | ||||||
|  |             </CCol> | ||||||
|  |             <CCol> | ||||||
|  |               <div className={styles.alignRight}> | ||||||
|  |                 <CPopover content={t('common.refresh')}> | ||||||
|  |                   <CButton color="secondary" onClick={getData} size="sm"> | ||||||
|  |                     <CIcon content={cilSync} /> | ||||||
|  |                   </CButton> | ||||||
|  |                 </CPopover> | ||||||
|  |               </div> | ||||||
|  |             </CCol> | ||||||
|  |           </CRow> | ||||||
|  |         </CCardHeader> | ||||||
|  |         <CCardBody> | ||||||
|  |           {(!lastStats || !status) && loading ? ( | ||||||
|  |             <div className={styles.centerContainer}> | ||||||
|  |               <CSpinner className={styles.spinner} /> | ||||||
|  |             </div> | ||||||
|  |           ) : ( | ||||||
|  |             <div style={{ position: 'relative' }}> | ||||||
|  |               <div className={styles.overlayContainer} hidden={!loading}> | ||||||
|  |                 <CSpinner className={styles.spinner} /> | ||||||
|  |               </div> | ||||||
|  |               <CRow className={styles.spacedRow}> | ||||||
|  |                 <CCol md="5">{t('status.connection_status')} :</CCol> | ||||||
|  |                 <CCol xs="10" md="7"> | ||||||
|  |                   {status?.connected ? ( | ||||||
|  |                     <CBadge color="success">{t('common.connected')}</CBadge> | ||||||
|  |                   ) : ( | ||||||
|  |                     <CBadge color="danger">{t('common.not_connected')}</CBadge> | ||||||
|  |                   )} | ||||||
|  |                 </CCol> | ||||||
|  |               </CRow> | ||||||
|  |               <CRow className={styles.spacedRow}> | ||||||
|  |                 <CCol md="5">{t('status.uptime')} :</CCol> | ||||||
|  |                 <CCol xs="10" md="7"> | ||||||
|  |                   {secondsToDetailed( | ||||||
|  |                     lastStats?.unit?.uptime, | ||||||
|  |                     t('common.day'), | ||||||
|  |                     t('common.days'), | ||||||
|  |                     t('common.hour'), | ||||||
|  |                     t('common.hours'), | ||||||
|  |                     t('common.minute'), | ||||||
|  |                     t('common.minutes'), | ||||||
|  |                     t('common.second'), | ||||||
|  |                     t('common.seconds'), | ||||||
|  |                   )} | ||||||
|  |                 </CCol> | ||||||
|  |               </CRow> | ||||||
|  |               <CRow className={styles.spacedRow}> | ||||||
|  |                 <CCol md="5">{t('status.last_contact')} :</CCol> | ||||||
|  |                 <CCol xs="10" md="7"> | ||||||
|  |                   {prettyDate(status?.lastContact)} | ||||||
|  |                 </CCol> | ||||||
|  |               </CRow> | ||||||
|  |               <CRow className={styles.spacedRow}> | ||||||
|  |                 <CCol md="5">{t('status.localtime')} :</CCol> | ||||||
|  |                 <CCol xs="10" md="7"> | ||||||
|  |                   {prettyDate(lastStats?.unit?.localtime)} | ||||||
|  |                 </CCol> | ||||||
|  |               </CRow> | ||||||
|  |               <CRow className={styles.spacedRow}> | ||||||
|  |                 <CCol md="5">{t('status.load_averages')} :</CCol> | ||||||
|  |                 <CCol xs="10" md="7"> | ||||||
|  |                   {transformLoad(lastStats?.unit?.load[0])} | ||||||
|  |                   {' / '} | ||||||
|  |                   {transformLoad(lastStats?.unit?.load[1])} | ||||||
|  |                   {' / '} | ||||||
|  |                   {transformLoad(lastStats?.unit?.load[2])} | ||||||
|  |                 </CCol> | ||||||
|  |               </CRow> | ||||||
|  |               <CRow className={styles.spacedRow}> | ||||||
|  |                 <CCol md="5">{t('status.memory')} :</CCol> | ||||||
|  |                 <CCol xs="9" md="6" style={{ paddingTop: '5px' }}> | ||||||
|  |                   <MemoryBar | ||||||
|  |                     usedBytes={ | ||||||
|  |                       lastStats?.unit?.memory?.total && lastStats?.unit?.memory?.free | ||||||
|  |                         ? lastStats?.unit?.memory?.total - lastStats?.unit?.memory?.free | ||||||
|  |                         : 0 | ||||||
|  |                     } | ||||||
|  |                     totalBytes={lastStats?.unit?.memory?.total ?? 0} | ||||||
|  |                   /> | ||||||
|  |                 </CCol> | ||||||
|  |               </CRow> | ||||||
|  |             </div> | ||||||
|  |           )} | ||||||
|  |         </CCardBody> | ||||||
|  |       </CCard> | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return ( | ||||||
|  |     <CCard> | ||||||
|  |       <CCardHeader> | ||||||
|  |         <CRow> | ||||||
|  |           <CCol> | ||||||
|  |             <div className="text-value-lg"> | ||||||
|  |               {t('status.title', { serialNumber: deviceSerialNumber })} | ||||||
|  |             </div> | ||||||
|  |           </CCol> | ||||||
|  |         </CRow> | ||||||
|  |       </CCardHeader> | ||||||
|  |       <CModalBody> | ||||||
|  |         <CAlert hidden={!error} color="danger" className={styles.centerContainer}> | ||||||
|  |           {t('status.error')} | ||||||
|  |         </CAlert> | ||||||
|  |       </CModalBody> | ||||||
|  |     </CCard> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default React.memo(DeviceStatusCard); | ||||||
							
								
								
									
										29
									
								
								src/components/DeviceStatusCard/index.module.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/components/DeviceStatusCard/index.module.scss
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | |||||||
|  | .centerContainer { | ||||||
|  |   display: flex; | ||||||
|  |   justify-content: center; | ||||||
|  |   align-items: center; | ||||||
|  |   position: relative; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .overlayContainer { | ||||||
|  |   display: flex; | ||||||
|  |   justify-content: center; | ||||||
|  |   align-items: center; | ||||||
|  |   position: absolute; | ||||||
|  |   width: 100%; | ||||||
|  |   height: 100%; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .spacedRow { | ||||||
|  |   margin-top: 5px; | ||||||
|  |   margin-bottom: 5px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .alignRight { | ||||||
|  |   float: right; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .spinner { | ||||||
|  |   height: 50px; | ||||||
|  |   width: 50px; | ||||||
|  | } | ||||||
| @@ -14,15 +14,17 @@ import { | |||||||
| import React, { useState, useEffect } from 'react'; | import React, { useState, useEffect } from 'react'; | ||||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||||
| import { useSelector } from 'react-redux'; |  | ||||||
| import 'react-widgets/styles.css'; | 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 axiosInstance from 'utils/axiosInstance'; | ||||||
| import SuccessfulActionModalBody from 'components/SuccessfulActionModalBody'; | import SuccessfulActionModalBody from 'components/SuccessfulActionModalBody'; | ||||||
| import styles from './index.module.scss'; | import styles from './index.module.scss'; | ||||||
|  |  | ||||||
| const ConfigureModal = ({ show, toggleModal }) => { | const ConfigureModal = ({ show, toggleModal }) => { | ||||||
|   const { t } = useTranslation(); |   const { t } = useTranslation(); | ||||||
|  |   const { currentToken, endpoints } = useAuth(); | ||||||
|  |   const { deviceSerialNumber } = useDevice(); | ||||||
|   const [hadSuccess, setHadSuccess] = useState(false); |   const [hadSuccess, setHadSuccess] = useState(false); | ||||||
|   const [hadFailure, setHadFailure] = useState(false); |   const [hadFailure, setHadFailure] = useState(false); | ||||||
|   const [doingNow, setDoingNow] = useState(false); |   const [doingNow, setDoingNow] = useState(false); | ||||||
| @@ -30,7 +32,6 @@ const ConfigureModal = ({ show, toggleModal }) => { | |||||||
|   const [keepRedirector, setKeepRedirector] = useState(true); |   const [keepRedirector, setKeepRedirector] = useState(true); | ||||||
|   const [responseBody, setResponseBody] = useState(''); |   const [responseBody, setResponseBody] = useState(''); | ||||||
|   const [checkingIfSure, setCheckingIfSure] = useState(false); |   const [checkingIfSure, setCheckingIfSure] = useState(false); | ||||||
|   const selectedDeviceId = useSelector((state) => state.selectedDeviceId); |  | ||||||
|  |  | ||||||
|   const toggleRedirector = () => { |   const toggleRedirector = () => { | ||||||
|     setKeepRedirector(!keepRedirector); |     setKeepRedirector(!keepRedirector); | ||||||
| @@ -54,17 +55,21 @@ const ConfigureModal = ({ show, toggleModal }) => { | |||||||
|     setWaiting(true); |     setWaiting(true); | ||||||
|  |  | ||||||
|     const parameters = { |     const parameters = { | ||||||
|       serialNumber: selectedDeviceId, |       serialNumber: deviceSerialNumber, | ||||||
|       keepRedirector, |       keepRedirector, | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     const headers = { |     const headers = { | ||||||
|       Accept: 'application/json', |       Accept: 'application/json', | ||||||
|       Authorization: `Bearer ${getToken()}`, |       Authorization: `Bearer ${currentToken}`, | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     axiosInstance |     axiosInstance | ||||||
|       .post(`/device/${encodeURIComponent(selectedDeviceId)}/factory`, parameters, { headers }) |       .post( | ||||||
|  |         `${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}/factory`, | ||||||
|  |         parameters, | ||||||
|  |         { headers }, | ||||||
|  |       ) | ||||||
|       .then(() => { |       .then(() => { | ||||||
|         setHadSuccess(true); |         setHadSuccess(true); | ||||||
|       }) |       }) | ||||||
| @@ -116,7 +121,7 @@ const ConfigureModal = ({ show, toggleModal }) => { | |||||||
|               color="primary" |               color="primary" | ||||||
|               onClick={() => confirmingIfSure()} |               onClick={() => confirmingIfSure()} | ||||||
|             > |             > | ||||||
|               {t('common.submit')} |               {t('factory_reset.reset')} | ||||||
|             </CButton> |             </CButton> | ||||||
|             <CButton |             <CButton | ||||||
|               hidden={!checkingIfSure} |               hidden={!checkingIfSure} | ||||||
| @@ -124,7 +129,7 @@ const ConfigureModal = ({ show, toggleModal }) => { | |||||||
|               color="primary" |               color="primary" | ||||||
|               onClick={() => doAction(false)} |               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" /> |               <CSpinner color="light" hidden={!waiting || doingNow} component="span" size="sm" /> | ||||||
|             </CButton> |             </CButton> | ||||||
|             <CButton color="secondary" onClick={toggleModal}> |             <CButton color="secondary" onClick={toggleModal}> | ||||||
|   | |||||||
							
								
								
									
										74
									
								
								src/components/FirmwareUpgradeModal/UpgradeFooter.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								src/components/FirmwareUpgradeModal/UpgradeFooter.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,74 @@ | |||||||
|  | import React, { useState, useEffect } from 'react'; | ||||||
|  | import { useTranslation } from 'react-i18next'; | ||||||
|  | import PropTypes from 'prop-types'; | ||||||
|  | import { CButton, CSpinner, CModalFooter } from '@coreui/react'; | ||||||
|  |  | ||||||
|  | const UpgradeFooter = ({ | ||||||
|  |   isNow, | ||||||
|  |   isShown, | ||||||
|  |   isLoading, | ||||||
|  |   action, | ||||||
|  |   color, | ||||||
|  |   variant, | ||||||
|  |   block, | ||||||
|  |   toggleParent, | ||||||
|  | }) => { | ||||||
|  |   const { t } = useTranslation(); | ||||||
|  |   const [askingIfSure, setAskingIfSure] = useState(false); | ||||||
|  |  | ||||||
|  |   const confirmingIfSure = () => { | ||||||
|  |     setAskingIfSure(true); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   useEffect(() => { | ||||||
|  |     setAskingIfSure(false); | ||||||
|  |   }, [isShown]); | ||||||
|  |  | ||||||
|  |   return ( | ||||||
|  |     <CModalFooter> | ||||||
|  |       <div hidden={!askingIfSure}>{t('common.are_you_sure')}</div> | ||||||
|  |       <CButton | ||||||
|  |         disabled={isLoading} | ||||||
|  |         hidden={askingIfSure} | ||||||
|  |         color={color} | ||||||
|  |         variant={variant} | ||||||
|  |         onClick={() => confirmingIfSure()} | ||||||
|  |         block={block} | ||||||
|  |       > | ||||||
|  |         {isNow ? t('upgrade.upgrade') : t('common.schedule')} | ||||||
|  |       </CButton> | ||||||
|  |       <CButton | ||||||
|  |         disabled={isLoading} | ||||||
|  |         hidden={!askingIfSure} | ||||||
|  |         color={color} | ||||||
|  |         onClick={() => action()} | ||||||
|  |         block={block} | ||||||
|  |       > | ||||||
|  |         {isLoading ? t('common.loading_ellipsis') : t('common.yes')} | ||||||
|  |         <CSpinner color="light" hidden={!isLoading} component="span" size="sm" /> | ||||||
|  |       </CButton> | ||||||
|  |       <CButton color="secondary" onClick={toggleParent}> | ||||||
|  |         {t('common.cancel')} | ||||||
|  |       </CButton> | ||||||
|  |     </CModalFooter> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | UpgradeFooter.propTypes = { | ||||||
|  |   isNow: PropTypes.bool.isRequired, | ||||||
|  |   isLoading: PropTypes.bool.isRequired, | ||||||
|  |   block: PropTypes.bool, | ||||||
|  |   action: PropTypes.func.isRequired, | ||||||
|  |   color: PropTypes.string, | ||||||
|  |   variant: PropTypes.string, | ||||||
|  |   toggleParent: PropTypes.func.isRequired, | ||||||
|  |   isShown: PropTypes.bool.isRequired, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | UpgradeFooter.defaultProps = { | ||||||
|  |   color: 'primary', | ||||||
|  |   variant: '', | ||||||
|  |   block: false, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default UpgradeFooter; | ||||||
							
								
								
									
										102
									
								
								src/components/FirmwareUpgradeModal/UpgradeWaitingBody.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								src/components/FirmwareUpgradeModal/UpgradeWaitingBody.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,102 @@ | |||||||
|  | import React, { useState, useEffect } from 'react'; | ||||||
|  | import { useTranslation } from 'react-i18next'; | ||||||
|  | import PropTypes from 'prop-types'; | ||||||
|  | import { CModalBody } from '@coreui/react'; | ||||||
|  | import { v4 as createUuid } from 'uuid'; | ||||||
|  | import { useAuth } from 'contexts/AuthProvider'; | ||||||
|  | import axiosInstance from 'utils/axiosInstance'; | ||||||
|  |  | ||||||
|  | const UpgradeWaitingBody = ({ serialNumber }) => { | ||||||
|  |   const { t } = useTranslation(); | ||||||
|  |   const { currentToken, endpoints } = useAuth(); | ||||||
|  |   const [currentStep, setCurrentStep] = useState(0); | ||||||
|  |   const [secondsElapsed, setSecondsElapsed] = useState(0); | ||||||
|  |   const [labelsToShow, setLabelsToShow] = useState(['upgrade.command_submitted']); | ||||||
|  |  | ||||||
|  |   const getDeviceConnection = () => { | ||||||
|  |     const options = { | ||||||
|  |       headers: { | ||||||
|  |         Accept: 'application/json', | ||||||
|  |         Authorization: `Bearer ${currentToken}`, | ||||||
|  |       }, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     axiosInstance | ||||||
|  |       .get( | ||||||
|  |         `${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(serialNumber)}/status`, | ||||||
|  |         options, | ||||||
|  |       ) | ||||||
|  |       .then((response) => response.data.connected) | ||||||
|  |       .catch(() => {}); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   const getFirmwareVersion = () => { | ||||||
|  |     const options = { | ||||||
|  |       headers: { | ||||||
|  |         Accept: 'application/json', | ||||||
|  |         Authorization: `Bearer ${currentToken}`, | ||||||
|  |       }, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     axiosInstance | ||||||
|  |       .get(`${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(serialNumber)}`, options) | ||||||
|  |       .then((response) => response.data.firmware) | ||||||
|  |       .catch(() => {}); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   const refreshStep = () => { | ||||||
|  |     if (currentStep === 0 && !getDeviceConnection) { | ||||||
|  |       const labelsToAdd = [ | ||||||
|  |         t('upgrade.device_disconnected'), | ||||||
|  |         t('upgrade.device_upgrading_firmware'), | ||||||
|  |         t('upgrade.waiting_for_device'), | ||||||
|  |       ]; | ||||||
|  |       setLabelsToShow([...labelsToShow, ...labelsToAdd]); | ||||||
|  |       setCurrentStep(1); | ||||||
|  |     } else if (currentStep === 1 && getDeviceConnection()) { | ||||||
|  |       const newFirmware = `: ${getFirmwareVersion()}`; | ||||||
|  |       const labelsToAdd = [ | ||||||
|  |         t('upgrade.device_reconnected'), | ||||||
|  |         `${t('upgrade.new_version')}: ${newFirmware}`, | ||||||
|  |       ]; | ||||||
|  |       setLabelsToShow([...labelsToShow, ...labelsToAdd]); | ||||||
|  |       setCurrentStep(2); | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   useEffect(() => { | ||||||
|  |     const refreshIntervalId = setInterval(() => { | ||||||
|  |       refreshStep(); | ||||||
|  |     }, 5000); | ||||||
|  |  | ||||||
|  |     const timerIntervalId = setInterval(() => { | ||||||
|  |       setSecondsElapsed(secondsElapsed + 1); | ||||||
|  |     }, 1000); | ||||||
|  |  | ||||||
|  |     return () => { | ||||||
|  |       clearInterval(refreshIntervalId); | ||||||
|  |       clearInterval(timerIntervalId); | ||||||
|  |     }; | ||||||
|  |   }, []); | ||||||
|  |  | ||||||
|  |   return ( | ||||||
|  |     <CModalBody> | ||||||
|  |       <div className="consoleBox"> | ||||||
|  |         {labelsToShow.map((label) => ( | ||||||
|  |           <p key={createUuid()}> | ||||||
|  |             {new Date().toString()}:{label} | ||||||
|  |           </p> | ||||||
|  |         ))} | ||||||
|  |         <p> | ||||||
|  |           {t('common.seconds_elapsed')}:{secondsElapsed} | ||||||
|  |         </p> | ||||||
|  |       </div> | ||||||
|  |     </CModalBody> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | UpgradeWaitingBody.propTypes = { | ||||||
|  |   serialNumber: PropTypes.string.isRequired, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default UpgradeWaitingBody; | ||||||
| @@ -4,39 +4,58 @@ import { | |||||||
|   CModalHeader, |   CModalHeader, | ||||||
|   CModalTitle, |   CModalTitle, | ||||||
|   CModalBody, |   CModalBody, | ||||||
|   CModalFooter, |   CSwitch, | ||||||
|   CSpinner, |  | ||||||
|   CCol, |   CCol, | ||||||
|   CRow, |   CRow, | ||||||
|   CInput, |   CInput, | ||||||
|   CInvalidFeedback, |   CInvalidFeedback, | ||||||
|  |   CModalFooter, | ||||||
| } from '@coreui/react'; | } from '@coreui/react'; | ||||||
| import React, { useState, useEffect } from 'react'; | import React, { useState, useEffect } from 'react'; | ||||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||||
| import DatePicker from 'react-widgets/DatePicker'; | import DatePicker from 'react-widgets/DatePicker'; | ||||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||||
| import { useSelector } from 'react-redux'; | import { dateToUnix } from 'utils/helper'; | ||||||
| import { convertDateToUtc, convertDateFromUtc, dateToUnix } from 'utils/helper'; |  | ||||||
| import 'react-widgets/styles.css'; | 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 axiosInstance from 'utils/axiosInstance'; | ||||||
| import eventBus from 'utils/eventBus'; | import eventBus from 'utils/eventBus'; | ||||||
|  | import getDeviceConnection from 'utils/deviceHelper'; | ||||||
|  | import ButtonFooter from './UpgradeFooter'; | ||||||
| import styles from './index.module.scss'; | import styles from './index.module.scss'; | ||||||
|  | import UpgradeWaitingBody from './UpgradeWaitingBody'; | ||||||
|  |  | ||||||
| const FirmwareUpgradeModal = ({ show, toggleModal }) => { | const FirmwareUpgradeModal = ({ show, toggleModal }) => { | ||||||
|   const { t } = useTranslation(); |   const { t } = useTranslation(); | ||||||
|   const [hadSuccess, setHadSuccess] = useState(false); |   const { currentToken, endpoints } = useAuth(); | ||||||
|   const [hadFailure, setHadFailure] = useState(false); |   const { deviceSerialNumber } = useDevice(); | ||||||
|   const [waiting, setWaiting] = useState(false); |   const [isNow, setIsNow] = useState(true); | ||||||
|   const [chosenDate, setChosenDate] = useState(new Date().toString()); |   const [waitForUpgrade, setWaitForUpgrade] = useState(false); | ||||||
|  |   const [date, setDate] = useState(new Date().toString()); | ||||||
|   const [firmware, setFirmware] = useState(''); |   const [firmware, setFirmware] = useState(''); | ||||||
|   const [doingNow, setDoingNow] = useState(false); |  | ||||||
|   const [validFirmware, setValidFirmware] = useState(true); |   const [validFirmware, setValidFirmware] = useState(true); | ||||||
|   const [validDate, setValidDate] = useState(true); |   const [validDate, setValidDate] = useState(true); | ||||||
|   const [responseBody, setResponseBody] = useState(''); |   const [blockFields, setBlockFields] = useState(false); | ||||||
|   const [checkingIfSure, setCheckingIfSure] = useState(false); |   const [disabledWaiting, setDisableWaiting] = useState(false); | ||||||
|   const [checkingIfNow, setCheckingIfNow] = useState(false); |   const [waitingForUpgrade, setWaitingForUpgrade] = useState(false); | ||||||
|   const selectedDeviceId = useSelector((state) => state.selectedDeviceId); |   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 = () => { |   const formValidation = () => { | ||||||
|     let valid = true; |     let valid = true; | ||||||
| @@ -45,95 +64,91 @@ const FirmwareUpgradeModal = ({ show, toggleModal }) => { | |||||||
|       valid = false; |       valid = false; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (chosenDate.trim() === '') { |     if (!isNow && date.trim() === '') { | ||||||
|       setValidDate(false); |       setValidDate(false); | ||||||
|       valid = false; |       valid = false; | ||||||
|     } |     } | ||||||
|     return valid; |     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(() => { |   useEffect(() => { | ||||||
|     setHadSuccess(false); |     setBlockFields(false); | ||||||
|     setHadFailure(false); |     setShowWaitingConsole(false); | ||||||
|     setWaiting(false); |  | ||||||
|     setChosenDate(new Date().toString()); |  | ||||||
|     setFirmware(''); |  | ||||||
|     setValidFirmware(true); |  | ||||||
|     setResponseBody(''); |  | ||||||
|     setCheckingIfSure(false); |  | ||||||
|     setDoingNow(false); |  | ||||||
|     setCheckingIfNow(false); |  | ||||||
|   }, [show]); |   }, [show]); | ||||||
|  |  | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     setValidFirmware(true); |     setValidFirmware(true); | ||||||
|     setValidDate(true); |     setValidDate(true); | ||||||
|   }, [firmware, chosenDate]); |   }, [firmware, date]); | ||||||
|  |  | ||||||
|   const postUpgrade = (isNow) => { |   useEffect(() => { | ||||||
|     setDoingNow(isNow); |     if (deviceSerialNumber !== null && show) { | ||||||
|     setHadFailure(false); |       const asyncGet = async () => { | ||||||
|     setHadSuccess(false); |         const isConnected = await getDeviceConnection( | ||||||
|     setWaiting(true); |           deviceSerialNumber, | ||||||
|  |           currentToken, | ||||||
|  |           endpoints.ucentralgw, | ||||||
|  |         ); | ||||||
|  |         setDisableWaiting(!isConnected); | ||||||
|  |         setDeviceConnected(isConnected); | ||||||
|  |       }; | ||||||
|  |       asyncGet(); | ||||||
|  |     } | ||||||
|  |   }, [show]); | ||||||
|  |  | ||||||
|     const token = getToken(); |   const postUpgrade = () => { | ||||||
|     const utcDate = new Date(chosenDate); |     if (formValidation()) { | ||||||
|     const utcDateString = utcDate.toISOString(); |       setWaitingForUpgrade(true); | ||||||
|  |       setBlockFields(true); | ||||||
|  |       const headers = { | ||||||
|  |         Accept: 'application/json', | ||||||
|  |         Authorization: `Bearer ${currentToken}`, | ||||||
|  |         serialNumber: deviceSerialNumber, | ||||||
|  |       }; | ||||||
|  |  | ||||||
|     const headers = { |       const parameters = { | ||||||
|       Accept: 'application/json', |         serialNumber: deviceSerialNumber, | ||||||
|       Authorization: `Bearer ${token}`, |         when: isNow ? 0 : dateToUnix(date), | ||||||
|       serialNumber: selectedDeviceId, |         uri: firmware, | ||||||
|     }; |       }; | ||||||
|  |       axiosInstance | ||||||
|     const parameters = { |         .post( | ||||||
|       serialNumber: selectedDeviceId, |           `${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}/upgrade`, | ||||||
|       when: isNow ? 0 : dateToUnix(utcDateString), |           parameters, | ||||||
|       uri: firmware, |           { headers }, | ||||||
|     }; |         ) | ||||||
|     axiosInstance |         .then(() => { | ||||||
|       .post(`/device/${encodeURIComponent(selectedDeviceId)}/upgrade`, parameters, { headers }) |           if (waitForUpgrade) { | ||||||
|       .then(() => { |             setShowWaitingConsole(true); | ||||||
|         setResponseBody('Command submitted successfully'); |           } | ||||||
|         setHadSuccess(true); |         }) | ||||||
|       }) |         .catch(() => {}) | ||||||
|       .catch(() => { |         .finally(() => { | ||||||
|         setResponseBody(t('commands.error')); |           setBlockFields(false); | ||||||
|         setHadFailure(true); |           setWaitingForUpgrade(false); | ||||||
|       }) |           eventBus.dispatch('actionCompleted', { message: 'An action has been completed' }); | ||||||
|       .finally(() => { |         }); | ||||||
|         setCheckingIfNow(false); |     } | ||||||
|         setDoingNow(false); |  | ||||||
|         setCheckingIfSure(false); |  | ||||||
|         setWaiting(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 ( |   return ( | ||||||
|     <CModal show={show} onClose={toggleModal}> |     <CModal show={show} onClose={toggleModal}> | ||||||
|       <CModalHeader closeButton> |       <CModalHeader closeButton> | ||||||
| @@ -142,93 +157,84 @@ const FirmwareUpgradeModal = ({ show, toggleModal }) => { | |||||||
|       <CModalBody> |       <CModalBody> | ||||||
|         <h6>{t('upgrade.directions')}</h6> |         <h6>{t('upgrade.directions')}</h6> | ||||||
|         <CRow className={styles.spacedRow}> |         <CRow className={styles.spacedRow}> | ||||||
|           <CCol> |           <CCol md="4" className={styles.spacedColumn}> | ||||||
|             <CButton |             <p>{t('upgrade.firmware_uri')}</p> | ||||||
|               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> | ||||||
|           <CCol> |           <CCol md="8"> | ||||||
|             <CButton disabled={waiting} block color="primary" onClick={setDateToLate}> |             <CInput | ||||||
|               {t('common.later_tonight')} |               disabled={blockFields} | ||||||
|             </CButton> |               className={('form-control', { 'is-invalid': !validFirmware })} | ||||||
|  |               type="text" | ||||||
|  |               id="uri" | ||||||
|  |               name="uri-input" | ||||||
|  |               autoComplete="firmware-uri" | ||||||
|  |               onChange={(event) => setFirmware(event.target.value)} | ||||||
|  |               value={firmware} | ||||||
|  |             /> | ||||||
|  |             <CInvalidFeedback>{t('upgrade.need_uri')}</CInvalidFeedback> | ||||||
|           </CCol> |           </CCol> | ||||||
|         </CRow> |         </CRow> | ||||||
|         <CRow className={styles.spacedRow}> |         <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}> |           <CCol md="4" className={styles.spacedColumn}> | ||||||
|             <p>{t('upgrade.time')}</p> |             <p>{t('upgrade.time')}</p> | ||||||
|           </CCol> |           </CCol> | ||||||
|           <CCol xs="12" md="8"> |           <CCol xs="12" md="8"> | ||||||
|             <DatePicker |             <DatePicker | ||||||
|               selected={chosenDate === '' ? new Date() : new Date(chosenDate)} |               selected={new Date(date)} | ||||||
|               value={chosenDate === '' ? new Date() : new Date(chosenDate)} |               value={new Date(date)} | ||||||
|               className={('form-control', { 'is-invalid': !validDate })} |               className={('form-control', { 'is-invalid': !validDate })} | ||||||
|               includeTime |               includeTime | ||||||
|               placeholder="Select custom date in UTC" |               disabled={blockFields} | ||||||
|               disabled={waiting} |               onChange={(newDate) => setDate(newDate.toString())} | ||||||
|               onChange={(date) => setDate(date)} |  | ||||||
|               min={new Date()} |  | ||||||
|             /> |             /> | ||||||
|             <CInvalidFeedback>{t('common.need_date')}</CInvalidFeedback> |             <CInvalidFeedback>{t('common.need_date')}</CInvalidFeedback> | ||||||
|           </CCol> |           </CCol> | ||||||
|         </CRow> |         </CRow> | ||||||
|         <div>{t('upgrade.firmware_uri')}</div> |         <CRow | ||||||
|         <CInput |           className={styles.spacedRow} | ||||||
|           disabled={waiting} |           hidden={true || !isNow || disabledWaiting || !deviceConnected} | ||||||
|           className={('form-control', { 'is-invalid': !validFirmware })} |         > | ||||||
|           type="text" |           <CCol md="8"> | ||||||
|           id="uri" |             <p className={styles.spacedText}> | ||||||
|           name="uri-input" |               {t('upgrade.wait_for_upgrade')} | ||||||
|           placeholder="https://s3-us-west-2.amazonaws.com/ucentral.arilia.com/20210508-linksys_ea8300-uCentral-trunk-43e1a2d-upgrade.bin" |               <b hidden={!disabledWaiting}> {t('upgrade.offline_device')}</b> | ||||||
|           autoComplete="firmware-uri" |             </p> | ||||||
|           onChange={(event) => setFirmware(event.target.value)} |           </CCol> | ||||||
|           value={firmware} |           <CCol> | ||||||
|         /> |             <CSwitch | ||||||
|         <CInvalidFeedback>{t('upgrade.need_uri')}</CInvalidFeedback> |               disabled={blockFields || disabledWaiting} | ||||||
|         <div hidden={!hadSuccess && !hadFailure}> |               color="primary" | ||||||
|           <div> |               defaultChecked={waitForUpgrade} | ||||||
|             <pre className="ignore">{responseBody}</pre> |               onClick={toggleWaitForUpgrade} | ||||||
|           </div> |               labelOn={t('common.yes')} | ||||||
|         </div> |               labelOff={t('common.no')} | ||||||
|  |             /> | ||||||
|  |           </CCol> | ||||||
|  |         </CRow> | ||||||
|       </CModalBody> |       </CModalBody> | ||||||
|       <CModalFooter> |       <ButtonFooter | ||||||
|         <div hidden={!checkingIfSure}>{t('common.are_you_sure')}</div> |         isNow={isNow} | ||||||
|         <CButton |         isShown={show} | ||||||
|           hidden={checkingIfSure} |         isLoading={waitingForUpgrade} | ||||||
|           disabled={waiting} |         action={postUpgrade} | ||||||
|           color="primary" |         color="primary" | ||||||
|           onClick={() => (formValidation() ? confirmingIfSure() : null)} |         toggleParent={toggleModal} | ||||||
|         > |       /> | ||||||
|           {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> |  | ||||||
|     </CModal> |     </CModal> | ||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
|   | |||||||
							
								
								
									
										15
									
								
								src/components/InterfaceStatistics/DeviceStatisticsChart.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/components/InterfaceStatistics/DeviceStatisticsChart.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | |||||||
|  | import React from 'react'; | ||||||
|  | import PropTypes from 'prop-types'; | ||||||
|  | import Chart from 'react-apexcharts'; | ||||||
|  |  | ||||||
|  | const DeviceStatisticsChart = ({ chart }) => ( | ||||||
|  |   <div style={{ height: '360px' }}> | ||||||
|  |     <Chart series={chart.data} options={chart.options} type="line" height="100%" /> | ||||||
|  |   </div> | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | DeviceStatisticsChart.propTypes = { | ||||||
|  |   chart: PropTypes.instanceOf(Object).isRequired, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default DeviceStatisticsChart; | ||||||
							
								
								
									
										70
									
								
								src/components/InterfaceStatistics/LatestStatisticsModal.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								src/components/InterfaceStatistics/LatestStatisticsModal.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,70 @@ | |||||||
|  | import React, { useState, useEffect } from 'react'; | ||||||
|  | import { | ||||||
|  |   CButton, | ||||||
|  |   CModal, | ||||||
|  |   CModalHeader, | ||||||
|  |   CModalBody, | ||||||
|  |   CModalTitle, | ||||||
|  |   CModalFooter, | ||||||
|  | } from '@coreui/react'; | ||||||
|  | import PropTypes from 'prop-types'; | ||||||
|  | import { useTranslation } from 'react-i18next'; | ||||||
|  | import axiosInstance from 'utils/axiosInstance'; | ||||||
|  | import { useAuth } from 'contexts/AuthProvider'; | ||||||
|  | import { useDevice } from 'contexts/DeviceProvider'; | ||||||
|  | import styles from './index.module.scss'; | ||||||
|  |  | ||||||
|  | const LatestStatisticsModal = ({ show, toggle }) => { | ||||||
|  |   const { t } = useTranslation(); | ||||||
|  |   const { currentToken, endpoints } = useAuth(); | ||||||
|  |   const { deviceSerialNumber } = useDevice(); | ||||||
|  |   const [latestStats, setLatestStats] = useState(''); | ||||||
|  |  | ||||||
|  |   const getLatestStats = () => { | ||||||
|  |     const options = { | ||||||
|  |       headers: { | ||||||
|  |         Accept: 'application/json', | ||||||
|  |         Authorization: `Bearer ${currentToken}`, | ||||||
|  |       }, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     axiosInstance | ||||||
|  |       .get( | ||||||
|  |         `${endpoints.ucentralgw}/api/v1/device/${deviceSerialNumber}/statistics?lastOnly=true`, | ||||||
|  |         options, | ||||||
|  |       ) | ||||||
|  |       .then((response) => { | ||||||
|  |         setLatestStats(response.data); | ||||||
|  |       }) | ||||||
|  |       .catch(() => {}); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   useEffect(() => { | ||||||
|  |     if (show) { | ||||||
|  |       getLatestStats(); | ||||||
|  |     } | ||||||
|  |   }, [show]); | ||||||
|  |  | ||||||
|  |   return ( | ||||||
|  |     <CModal size="lg" show={show} onClose={toggle}> | ||||||
|  |       <CModalHeader closeButton> | ||||||
|  |         <CModalTitle className={styles.modalTitle}>{t('statistics.latest_statistics')}</CModalTitle> | ||||||
|  |       </CModalHeader> | ||||||
|  |       <CModalBody> | ||||||
|  |         <pre className="ignore">{JSON.stringify(latestStats, null, 4)}</pre> | ||||||
|  |       </CModalBody> | ||||||
|  |       <CModalFooter> | ||||||
|  |         <CButton color="secondary" onClick={toggle}> | ||||||
|  |           {t('common.close')} | ||||||
|  |         </CButton> | ||||||
|  |       </CModalFooter> | ||||||
|  |     </CModal> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | LatestStatisticsModal.propTypes = { | ||||||
|  |   toggle: PropTypes.func.isRequired, | ||||||
|  |   show: PropTypes.bool.isRequired, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default LatestStatisticsModal; | ||||||
| @@ -1,15 +1,17 @@ | |||||||
| import React, { useState, useEffect } from 'react'; | import React, { useState, useEffect } from 'react'; | ||||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||||
| import PropTypes from 'prop-types'; |  | ||||||
| import { v4 as createUuid } from 'uuid'; | import { v4 as createUuid } from 'uuid'; | ||||||
| import axiosInstance from 'utils/axiosInstance'; | 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 { 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 { t } = useTranslation(); | ||||||
|   const [loading, setLoading] = useState(false); |   const { currentToken, endpoints } = useAuth(); | ||||||
|  |   const { deviceSerialNumber } = useDevice(); | ||||||
|   const [statOptions, setStatOptions] = useState({ |   const [statOptions, setStatOptions] = useState({ | ||||||
|     interfaceList: [], |     interfaceList: [], | ||||||
|     settings: {}, |     settings: {}, | ||||||
| @@ -60,10 +62,10 @@ const StatisticsChartList = ({ selectedDeviceId, lastRefresh }) => { | |||||||
|       // Looping through the interfaces of the log
 |       // Looping through the interfaces of the log
 | ||||||
|       for (const inter of log.data.interfaces) { |       for (const inter of log.data.interfaces) { | ||||||
|         interfaceList[interfaceTypes[inter.name]][0].data.push( |         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( |         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', |         id: 'chart', | ||||||
|         group: 'txrx', |         group: 'txrx', | ||||||
|       }, |       }, | ||||||
|  |       stroke: { | ||||||
|  |         curve: 'smooth', | ||||||
|  |       }, | ||||||
|       xaxis: { |       xaxis: { | ||||||
|         title: { |         title: { | ||||||
|           text: 'Time', |           text: 'Time', | ||||||
| @@ -112,74 +117,65 @@ const StatisticsChartList = ({ selectedDeviceId, lastRefresh }) => { | |||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   const getStatistics = () => { |   const getStatistics = () => { | ||||||
|     if (!loading) { |     const options = { | ||||||
|       setLoading(true); |       headers: { | ||||||
|  |         Accept: 'application/json', | ||||||
|  |         Authorization: `Bearer ${currentToken}`, | ||||||
|  |       }, | ||||||
|  |       params: { | ||||||
|  |         serialNumber: '24f5a207a130', | ||||||
|  |       }, | ||||||
|  |     }; | ||||||
| 
 | 
 | ||||||
|       const options = { |     axiosInstance | ||||||
|         headers: { |       .get( | ||||||
|           Accept: 'application/json', |         `${endpoints.ucentralgw}/api/v1/device/${deviceSerialNumber}/statistics?newest=true&limit=50`, | ||||||
|           Authorization: `Bearer ${getToken()}`, |         options, | ||||||
|         }, |       ) | ||||||
|         params: { |       .then((response) => { | ||||||
|           serialNumber: '24f5a207a130', |         transformIntoDataset(response.data.data); | ||||||
|         }, |       }) | ||||||
|       }; |       .catch(() => {}); | ||||||
| 
 |  | ||||||
|       axiosInstance |  | ||||||
|         .get(`/device/${selectedDeviceId}/statistics?newest=true&limit=50`, options) |  | ||||||
|         .then((response) => { |  | ||||||
|           transformIntoDataset(response.data.data); |  | ||||||
|         }) |  | ||||||
|         .catch(() => {}) |  | ||||||
|         .finally(() => { |  | ||||||
|           setLoading(false); |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     if (selectedDeviceId) { |     if (deviceSerialNumber) { | ||||||
|       getStatistics(); |       getStatistics(); | ||||||
|     } |     } | ||||||
|   }, [selectedDeviceId]); |   }, [deviceSerialNumber]); | ||||||
| 
 | 
 | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     if (!loading && lastRefresh !== '' && selectedDeviceId) { |     eventBus.on('refreshInterfaceStatistics', () => getStatistics()); | ||||||
|       getStatistics(); | 
 | ||||||
|     } |     return () => { | ||||||
|   }, [lastRefresh]); |       eventBus.remove('refreshInterfaceStatistics'); | ||||||
|  |     }; | ||||||
|  |   }, []); | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|     <div> |     <div> | ||||||
|       {statOptions.interfaceList.map((data) => ( |       {statOptions.interfaceList.map((data) => { | ||||||
|         <div key={createUuid()}> |         const options = { | ||||||
|           <DeviceStatisticsChart |           data, | ||||||
|             key={createUuid()} |           options: { | ||||||
|             data={data} |             ...statOptions.settings, | ||||||
|             options={{ |             title: { | ||||||
|               ...statOptions.settings, |               text: capitalizeFirstLetter(data[0].titleName), | ||||||
|               title: { |               align: 'left', | ||||||
|                 text: capitalizeFirstLetter(data[0].titleName), |               style: { | ||||||
|                 align: 'left', |                 fontSize: '25px', | ||||||
|                 style: { |  | ||||||
|                   fontSize: '25px', |  | ||||||
|                 }, |  | ||||||
|               }, |               }, | ||||||
|             }} |             }, | ||||||
|           /> |           }, | ||||||
|         </div> |         }; | ||||||
|       ))} |         return ( | ||||||
|  |           <div key={createUuid()}> | ||||||
|  |             <DeviceStatisticsChart chart={options} /> | ||||||
|  |           </div> | ||||||
|  |         ); | ||||||
|  |       })} | ||||||
|     </div> |     </div> | ||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| StatisticsChartList.propTypes = { | export default React.memo(StatisticsChartList); | ||||||
|   lastRefresh: PropTypes.string, |  | ||||||
|   selectedDeviceId: PropTypes.string.isRequired, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| StatisticsChartList.defaultProps = { |  | ||||||
|   lastRefresh: '', |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export default StatisticsChartList; |  | ||||||
| @@ -1,21 +0,0 @@ | |||||||
| import React from 'react'; |  | ||||||
| import PropTypes from 'prop-types'; |  | ||||||
| import Chart from 'react-apexcharts'; |  | ||||||
|  |  | ||||||
| const DeviceStatisticsChart = ({ data, options }) => ( |  | ||||||
|   <div style={{ height: '360px' }}> |  | ||||||
|     <Chart series={data} options={options} type="line" height="100%" /> |  | ||||||
|   </div> |  | ||||||
| ); |  | ||||||
|  |  | ||||||
| DeviceStatisticsChart.propTypes = { |  | ||||||
|   data: PropTypes.instanceOf(Array), |  | ||||||
|   options: PropTypes.instanceOf(Object), |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| DeviceStatisticsChart.defaultProps = { |  | ||||||
|   data: [], |  | ||||||
|   options: {}, |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| export default DeviceStatisticsChart; |  | ||||||
| @@ -1,47 +1,67 @@ | |||||||
| import React, { useState } from 'react'; | import React, { useState } from 'react'; | ||||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||||
| import PropTypes from 'prop-types'; | import { | ||||||
| import { CCard, CCardHeader, CCardBody, CPopover, CRow, CCol } from '@coreui/react'; |   CDropdown, | ||||||
| import { cilSync } from '@coreui/icons'; |   CDropdownToggle, | ||||||
|  |   CDropdownMenu, | ||||||
|  |   CDropdownItem, | ||||||
|  |   CCard, | ||||||
|  |   CCardHeader, | ||||||
|  |   CCardBody, | ||||||
|  |   CRow, | ||||||
|  |   CCol, | ||||||
|  | } from '@coreui/react'; | ||||||
|  | import { cilOptions } from '@coreui/icons'; | ||||||
| import CIcon from '@coreui/icons-react'; | 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'; | import styles from './index.module.scss'; | ||||||
|  |  | ||||||
| const DeviceStatisticsCard = ({ selectedDeviceId }) => { | const DeviceStatisticsCard = () => { | ||||||
|   const { t } = useTranslation(); |   const { t } = useTranslation(); | ||||||
|   const [lastRefresh, setLastRefresh] = useState(new Date().toString()); |   const [showLatestModal, setShowLatestModal] = useState(false); | ||||||
|  |  | ||||||
|  |   const toggleLatestModal = () => { | ||||||
|  |     setShowLatestModal(!showLatestModal); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|   const refresh = () => { |   const refresh = () => { | ||||||
|     setLastRefresh(new Date().toString()); |     eventBus.dispatch('refreshInterfaceStatistics', { message: 'Refresh interface statistics' }); | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
|     <CCard> |     <div> | ||||||
|       <CCardHeader> |       <CCard> | ||||||
|         <CRow> |         <CCardHeader> | ||||||
|           <CCol>{t('statistics.title')}</CCol> |           <CRow> | ||||||
|           <CCol className={styles.alignRight}> |             <CCol> | ||||||
|             <CPopover content={t('common.refresh')}> |               <div className={['text-value-lg', styles.cardTitle].join(' ')}> | ||||||
|               <CIcon |                 {t('statistics.title')} | ||||||
|                 onClick={refresh} |               </div> | ||||||
|                 name="cil-sync" |             </CCol> | ||||||
|                 content={cilSync} |             <CCol className={styles.cardOptions}> | ||||||
|                 size="lg" |               <CDropdown className="m-1 btn-group"> | ||||||
|                 color="primary" |                 <CDropdownToggle> | ||||||
|               /> |                   <CIcon name="cil-options" content={cilOptions} size="lg" color="primary" /> | ||||||
|             </CPopover> |                 </CDropdownToggle> | ||||||
|           </CCol> |                 <CDropdownMenu> | ||||||
|         </CRow> |                   <CDropdownItem onClick={refresh}>{t('common.refresh')}</CDropdownItem> | ||||||
|       </CCardHeader> |                   <CDropdownItem onClick={toggleLatestModal}> | ||||||
|       <CCardBody className={styles.statsBody}> |                     {t('statistics.show_latest')} | ||||||
|         <StatisticsChartList selectedDeviceId={selectedDeviceId} lastRefresh={lastRefresh} /> |                   </CDropdownItem> | ||||||
|       </CCardBody> |                 </CDropdownMenu> | ||||||
|     </CCard> |               </CDropdown> | ||||||
|  |             </CCol> | ||||||
|  |           </CRow> | ||||||
|  |         </CCardHeader> | ||||||
|  |         <CCardBody className={styles.statsBody}> | ||||||
|  |           <StatisticsChartList /> | ||||||
|  |         </CCardBody> | ||||||
|  |       </CCard> | ||||||
|  |       <LatestStatisticsModal show={showLatestModal} toggle={toggleLatestModal} /> | ||||||
|  |     </div> | ||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| DeviceStatisticsCard.propTypes = { |  | ||||||
|   selectedDeviceId: PropTypes.string.isRequired, |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| export default DeviceStatisticsCard; | export default DeviceStatisticsCard; | ||||||
|   | |||||||
| @@ -1,7 +1,15 @@ | |||||||
| .alignRight { | .cardOptions { | ||||||
|   text-align: right; |   text-align: right; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | .cardTitle { | ||||||
|  |   padding-top: 10px; | ||||||
|  | } | ||||||
|  |  | ||||||
| .statsBody { | .statsBody { | ||||||
|   padding: 5%; |   padding: 5%; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | .modalTitle { | ||||||
|  |   color: black; | ||||||
|  | } | ||||||
|   | |||||||
| @@ -5,19 +5,18 @@ import { | |||||||
|   CModalTitle, |   CModalTitle, | ||||||
|   CModalBody, |   CModalBody, | ||||||
|   CModalFooter, |   CModalFooter, | ||||||
|   CSpinner, |   CSwitch, | ||||||
|   CCol, |   CCol, | ||||||
|   CRow, |   CRow, | ||||||
|   CInvalidFeedback, |  | ||||||
| } from '@coreui/react'; | } from '@coreui/react'; | ||||||
| import React, { useState, useEffect } from 'react'; | import React, { useState, useEffect } from 'react'; | ||||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||||
| import DatePicker from 'react-widgets/DatePicker'; | import DatePicker from 'react-widgets/DatePicker'; | ||||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||||
| import { useSelector } from 'react-redux'; | import { dateToUnix } from 'utils/helper'; | ||||||
| import { convertDateToUtc, convertDateFromUtc, dateToUnix } from 'utils/helper'; |  | ||||||
| import 'react-widgets/styles.css'; | 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 axiosInstance from 'utils/axiosInstance'; | ||||||
| import eventBus from 'utils/eventBus'; | import eventBus from 'utils/eventBus'; | ||||||
| import LoadingButton from 'components/LoadingButton'; | import LoadingButton from 'components/LoadingButton'; | ||||||
| @@ -26,24 +25,15 @@ import styles from './index.module.scss'; | |||||||
|  |  | ||||||
| const ActionModal = ({ show, toggleModal }) => { | const ActionModal = ({ show, toggleModal }) => { | ||||||
|   const { t } = useTranslation(); |   const { t } = useTranslation(); | ||||||
|   const [hadSuccess, setHadSuccess] = useState(false); |   const { currentToken, endpoints } = useAuth(); | ||||||
|   const [hadFailure, setHadFailure] = useState(false); |   const { deviceSerialNumber } = useDevice(); | ||||||
|   const [waiting, setWaiting] = useState(false); |   const [waiting, setWaiting] = useState(false); | ||||||
|   const [validDate, setValidDate] = useState(true); |   const [result, setResult] = useState(null); | ||||||
|   const [chosenDate, setChosenDate] = useState(new Date().toString()); |   const [chosenDate, setChosenDate] = useState(new Date().toString()); | ||||||
|   const [doingNow, setDoingNow] = useState(false); |   const [isNow, setIsNow] = useState(false); | ||||||
|   const [responseBody, setResponseBody] = useState(''); |  | ||||||
|   const selectedDeviceId = useSelector((state) => state.selectedDeviceId); |  | ||||||
|  |  | ||||||
|   const setDateToLate = () => { |   const toggleNow = () => { | ||||||
|     const date = convertDateToUtc(new Date()); |     setIsNow(!isNow); | ||||||
|     if (date.getHours() >= 3) { |  | ||||||
|       date.setDate(date.getDate() + 1); |  | ||||||
|     } |  | ||||||
|     date.setHours(3); |  | ||||||
|     date.setMinutes(0); |  | ||||||
|  |  | ||||||
|     setChosenDate(convertDateFromUtc(date).toString()); |  | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   const setDate = (date) => { |   const setDate = (date) => { | ||||||
| @@ -54,47 +44,40 @@ const ActionModal = ({ show, toggleModal }) => { | |||||||
|  |  | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     if (show) { |     if (show) { | ||||||
|       setHadSuccess(false); |       setResult(null); | ||||||
|       setHadFailure(false); |  | ||||||
|       setWaiting(false); |       setWaiting(false); | ||||||
|       setDoingNow(false); |  | ||||||
|       setChosenDate(new Date().toString()); |       setChosenDate(new Date().toString()); | ||||||
|       setResponseBody(''); |  | ||||||
|       setValidDate(true); |  | ||||||
|     } |     } | ||||||
|   }, [show]); |   }, [show]); | ||||||
|  |  | ||||||
|   const doAction = (isNow) => { |   const doAction = () => { | ||||||
|     if (isNow !== undefined) setDoingNow(isNow); |  | ||||||
|     setHadFailure(false); |  | ||||||
|     setHadSuccess(false); |  | ||||||
|     setWaiting(true); |     setWaiting(true); | ||||||
|  |  | ||||||
|     const token = getToken(); |  | ||||||
|     const utcDate = new Date(chosenDate); |     const utcDate = new Date(chosenDate); | ||||||
|     const utcDateString = utcDate.toISOString(); |  | ||||||
|  |  | ||||||
|     const parameters = { |     const parameters = { | ||||||
|       serialNumber: selectedDeviceId, |       serialNumber: deviceSerialNumber, | ||||||
|       when: isNow ? 0 : dateToUnix(utcDateString), |       when: isNow ? 0 : dateToUnix(utcDate), | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     const headers = { |     const headers = { | ||||||
|       Accept: 'application/json', |       Accept: 'application/json', | ||||||
|       Authorization: `Bearer ${token}`, |       Authorization: `Bearer ${currentToken}`, | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     axiosInstance |     axiosInstance | ||||||
|       .post(`/device/${encodeURIComponent(selectedDeviceId)}/reboot`, parameters, { headers }) |       .post( | ||||||
|  |         `${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}/reboot`, | ||||||
|  |         parameters, | ||||||
|  |         { headers }, | ||||||
|  |       ) | ||||||
|       .then(() => { |       .then(() => { | ||||||
|         setHadSuccess(true); |         setResult('success'); | ||||||
|       }) |       }) | ||||||
|       .catch(() => { |       .catch(() => { | ||||||
|         setResponseBody(t('commands.error')); |         setResult('error'); | ||||||
|         setHadFailure(true); |  | ||||||
|       }) |       }) | ||||||
|       .finally(() => { |       .finally(() => { | ||||||
|         setDoingNow(false); |  | ||||||
|         setWaiting(false); |         setWaiting(false); | ||||||
|         eventBus.dispatch('actionCompleted', { message: 'An action has been completed' }); |         eventBus.dispatch('actionCompleted', { message: 'An action has been completed' }); | ||||||
|       }); |       }); | ||||||
| @@ -105,62 +88,49 @@ const ActionModal = ({ show, toggleModal }) => { | |||||||
|       <CModalHeader closeButton> |       <CModalHeader closeButton> | ||||||
|         <CModalTitle>{t('reboot.title')}</CModalTitle> |         <CModalTitle>{t('reboot.title')}</CModalTitle> | ||||||
|       </CModalHeader> |       </CModalHeader> | ||||||
|       {hadSuccess ? ( |       {result === 'success' ? ( | ||||||
|         <SuccessfulActionModalBody toggleModal={toggleModal} /> |         <SuccessfulActionModalBody toggleModal={toggleModal} /> | ||||||
|       ) : ( |       ) : ( | ||||||
|         <div> |         <div> | ||||||
|           <CModalBody> |           <CModalBody> | ||||||
|             <h6>{t('reboot.directions')}</h6> |             <CRow> | ||||||
|             <CRow className={styles.spacedRow}> |               <CCol md="8"> | ||||||
|               <CCol> |                 <p>{t('reboot.now')}</p> | ||||||
|                 <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> |  | ||||||
|               </CCol> |               </CCol> | ||||||
|               <CCol> |               <CCol> | ||||||
|                 <CButton disabled={waiting} block color="primary" onClick={() => setDateToLate()}> |                 <CSwitch | ||||||
|                   {t('common.later_tonight')} |                   disabled={waiting} | ||||||
|                 </CButton> |                   color="primary" | ||||||
|  |                   defaultChecked={isNow} | ||||||
|  |                   onClick={toggleNow} | ||||||
|  |                   labelOn={t('common.yes')} | ||||||
|  |                   labelOff={t('common.no')} | ||||||
|  |                 /> | ||||||
|               </CCol> |               </CCol> | ||||||
|             </CRow> |             </CRow> | ||||||
|             <CRow className={styles.spacedRow}> |             <CRow hidden={isNow} className={styles.spacedRow}> | ||||||
|               <CCol md="4" className={styles.spacedColumn}> |               <CCol md="4" className={styles.spacedDate}> | ||||||
|                 <p>{t('common.date')}:</p> |                 <p>{t('common.custom_date')}:</p> | ||||||
|               </CCol> |               </CCol> | ||||||
|               <CCol xs="12" md="8"> |               <CCol xs="12" md="8"> | ||||||
|                 <DatePicker |                 <DatePicker | ||||||
|                   selected={new Date(chosenDate)} |                   selected={new Date(chosenDate)} | ||||||
|                   includeTime |                   includeTime | ||||||
|                   className={('form-control', { 'is-invalid': !validDate })} |  | ||||||
|                   value={new Date(chosenDate)} |                   value={new Date(chosenDate)} | ||||||
|                   placeholder="Select custom date" |                   placeholder="Select custom date" | ||||||
|                   disabled={waiting} |                   disabled={waiting} | ||||||
|                   onChange={(date) => setDate(date)} |                   onChange={(date) => setDate(date)} | ||||||
|                   min={convertDateToUtc(new Date())} |                   min={new Date()} | ||||||
|                 /> |                 /> | ||||||
|               </CCol> |               </CCol> | ||||||
|             </CRow> |             </CRow> | ||||||
|             <CInvalidFeedback>{t('common.need_date')}</CInvalidFeedback> |  | ||||||
|  |  | ||||||
|             <div hidden={!hadSuccess && !hadFailure}> |  | ||||||
|               <div> |  | ||||||
|                 <pre className="ignore">{responseBody}</pre> |  | ||||||
|               </div> |  | ||||||
|             </div> |  | ||||||
|           </CModalBody> |           </CModalBody> | ||||||
|           <CModalFooter> |           <CModalFooter> | ||||||
|             <LoadingButton |             <LoadingButton | ||||||
|               label={t('common.schedule')} |               label={isNow ? t('reboot.title') : t('common.schedule')} | ||||||
|               isLoadingLabel={t('common.loading_ellipsis')} |               isLoadingLabel={t('common.loading_ellipsis')} | ||||||
|               isLoading={waiting} |               isLoading={waiting} | ||||||
|               action={doAction} |               action={doAction} | ||||||
|               variant="outline" |  | ||||||
|               block={false} |               block={false} | ||||||
|               disabled={waiting} |               disabled={waiting} | ||||||
|             /> |             /> | ||||||
|   | |||||||
| @@ -5,3 +5,7 @@ | |||||||
| .spacedColumn { | .spacedColumn { | ||||||
|   margin-top: 7px; |   margin-top: 7px; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | .spacedDate { | ||||||
|  |   padding-top: 5px; | ||||||
|  | } | ||||||
|   | |||||||
							
								
								
									
										113
									
								
								src/components/TraceModal/WaitingForTraceBody.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								src/components/TraceModal/WaitingForTraceBody.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,113 @@ | |||||||
|  | import React, { useState, useEffect } from 'react'; | ||||||
|  | import { useTranslation } from 'react-i18next'; | ||||||
|  | import PropTypes from 'prop-types'; | ||||||
|  | import { CModalBody, CButton, CSpinner, CModalFooter } from '@coreui/react'; | ||||||
|  | import { useAuth } from 'contexts/AuthProvider'; | ||||||
|  | import axiosInstance from 'utils/axiosInstance'; | ||||||
|  |  | ||||||
|  | import styles from './index.module.scss'; | ||||||
|  |  | ||||||
|  | const WaitingForTraceBody = ({ serialNumber, commandUuid, toggle }) => { | ||||||
|  |   const { t } = useTranslation(); | ||||||
|  |   const { currentToken, endpoints } = useAuth(); | ||||||
|  |   const [secondsElapsed, setSecondsElapsed] = useState(0); | ||||||
|  |   const [waitingForFile, setWaitingForFile] = useState(true); | ||||||
|  |  | ||||||
|  |   const getTraceResult = () => { | ||||||
|  |     const options = { | ||||||
|  |       headers: { | ||||||
|  |         Accept: 'application/json', | ||||||
|  |         Authorization: `Bearer ${currentToken}`, | ||||||
|  |       }, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     axiosInstance | ||||||
|  |       .get(`${endpoints.ucentralgw}/api/v1/command/${encodeURIComponent(commandUuid)}`, options) | ||||||
|  |       .then((response) => { | ||||||
|  |         if (response.data.waitingForFile === 0) { | ||||||
|  |           setWaitingForFile(false); | ||||||
|  |         } | ||||||
|  |       }) | ||||||
|  |       .catch(() => {}); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   const downloadTrace = () => { | ||||||
|  |     const options = { | ||||||
|  |       headers: { | ||||||
|  |         Accept: 'application/octet-stream', | ||||||
|  |         Authorization: `Bearer ${currentToken}`, | ||||||
|  |       }, | ||||||
|  |       responseType: 'arraybuffer', | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     axiosInstance | ||||||
|  |       .get( | ||||||
|  |         `${endpoints.ucentralgw}/api/v1/file/${commandUuid}?serialNumber=${serialNumber}`, | ||||||
|  |         options, | ||||||
|  |       ) | ||||||
|  |       .then((response) => { | ||||||
|  |         const blob = new Blob([response.data], { type: 'application/octet-stream' }); | ||||||
|  |         const link = document.createElement('a'); | ||||||
|  |         link.href = window.URL.createObjectURL(blob); | ||||||
|  |         link.download = `Trace_${commandUuid}.pcap`; | ||||||
|  |         link.click(); | ||||||
|  |       }); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   useEffect(() => { | ||||||
|  |     const timer = setInterval(() => { | ||||||
|  |       setSecondsElapsed(secondsElapsed + 1); | ||||||
|  |     }, 1000); | ||||||
|  |     if (!waitingForFile) { | ||||||
|  |       clearInterval(timer); | ||||||
|  |     } | ||||||
|  |     return () => { | ||||||
|  |       clearInterval(timer); | ||||||
|  |     }; | ||||||
|  |   }, [waitingForFile, secondsElapsed]); | ||||||
|  |  | ||||||
|  |   useEffect(() => { | ||||||
|  |     const refreshStatus = setInterval(() => { | ||||||
|  |       getTraceResult(); | ||||||
|  |     }, 5000); | ||||||
|  |     if (!waitingForFile) { | ||||||
|  |       clearInterval(refreshStatus); | ||||||
|  |     } | ||||||
|  |     return () => { | ||||||
|  |       clearInterval(refreshStatus); | ||||||
|  |     }; | ||||||
|  |   }, [waitingForFile]); | ||||||
|  |  | ||||||
|  |   return ( | ||||||
|  |     <div> | ||||||
|  |       <CModalBody> | ||||||
|  |         <h6>{t('trace.waiting_seconds', { seconds: secondsElapsed })}</h6> | ||||||
|  |         <p>{t('trace.waiting_directions')}</p> | ||||||
|  |         <div className={styles.centerDiv}> | ||||||
|  |           <CSpinner hidden={!waitingForFile} /> | ||||||
|  |           <CButton | ||||||
|  |             hidden={waitingForFile} | ||||||
|  |             onClick={downloadTrace} | ||||||
|  |             disabled={waitingForFile} | ||||||
|  |             color="primary" | ||||||
|  |           > | ||||||
|  |             {t('trace.download_trace')} | ||||||
|  |           </CButton> | ||||||
|  |         </div> | ||||||
|  |       </CModalBody> | ||||||
|  |       <CModalFooter> | ||||||
|  |         <CButton color="secondary" block onClick={toggle}> | ||||||
|  |           {t('common.exit')} | ||||||
|  |         </CButton> | ||||||
|  |       </CModalFooter> | ||||||
|  |     </div> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | WaitingForTraceBody.propTypes = { | ||||||
|  |   serialNumber: PropTypes.string.isRequired, | ||||||
|  |   commandUuid: PropTypes.string.isRequired, | ||||||
|  |   toggle: PropTypes.func.isRequired, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default WaitingForTraceBody; | ||||||
| @@ -7,8 +7,8 @@ import { | |||||||
|   CModalFooter, |   CModalFooter, | ||||||
|   CCol, |   CCol, | ||||||
|   CRow, |   CRow, | ||||||
|   CInvalidFeedback, |  | ||||||
|   CSelect, |   CSelect, | ||||||
|  |   CSwitch, | ||||||
|   CForm, |   CForm, | ||||||
|   CInputRadio, |   CInputRadio, | ||||||
|   CFormGroup, |   CFormGroup, | ||||||
| @@ -16,67 +16,58 @@ import { | |||||||
| } from '@coreui/react'; | } from '@coreui/react'; | ||||||
| import React, { useState, useEffect } from 'react'; | import React, { useState, useEffect } from 'react'; | ||||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||||
| import DatePicker from 'react-widgets/DatePicker'; |  | ||||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||||
| import { useSelector } from 'react-redux'; |  | ||||||
| import { convertDateToUtc, dateToUnix } from 'utils/helper'; |  | ||||||
| import 'react-widgets/styles.css'; | 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 axiosInstance from 'utils/axiosInstance'; | ||||||
| import eventBus from 'utils/eventBus'; | import eventBus from 'utils/eventBus'; | ||||||
|  | import getDeviceConnection from 'utils/deviceHelper'; | ||||||
| import LoadingButton from 'components/LoadingButton'; | import LoadingButton from 'components/LoadingButton'; | ||||||
| import SuccessfulActionModalBody from 'components/SuccessfulActionModalBody'; | import SuccessfulActionModalBody from 'components/SuccessfulActionModalBody'; | ||||||
|  | import WaitingForTraceBody from './WaitingForTraceBody'; | ||||||
| import styles from './index.module.scss'; | import styles from './index.module.scss'; | ||||||
|  |  | ||||||
| const TraceModal = ({ show, toggleModal }) => { | const TraceModal = ({ show, toggleModal }) => { | ||||||
|   const { t } = useTranslation(); |   const { t } = useTranslation(); | ||||||
|  |   const { currentToken, endpoints } = useAuth(); | ||||||
|  |   const { deviceSerialNumber } = useDevice(); | ||||||
|   const [hadSuccess, setHadSuccess] = useState(false); |   const [hadSuccess, setHadSuccess] = useState(false); | ||||||
|   const [hadFailure, setHadFailure] = useState(false); |   const [hadFailure, setHadFailure] = useState(false); | ||||||
|   const [waiting, setWaiting] = useState(false); |   const [blockFields, setBlockFields] = useState(false); | ||||||
|   const [usingDuration, setUsingDuration] = useState(true); |   const [usingDuration, setUsingDuration] = useState(true); | ||||||
|   const [duration, setDuration] = useState(20); |   const [duration, setDuration] = useState(20); | ||||||
|   const [packets, setPackets] = useState(100); |   const [packets, setPackets] = useState(100); | ||||||
|   const [chosenDate, setChosenDate] = useState(new Date().toString()); |  | ||||||
|   const [responseBody, setResponseBody] = useState(''); |   const [responseBody, setResponseBody] = useState(''); | ||||||
|   const [chosenInterface, setChosenInterface] = useState('up'); |   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) => { |   const toggleWaitForTrace = () => { | ||||||
|     if (date) { |     setWaitForTrace(!waitForTrace); | ||||||
|       setChosenDate(date.toString()); |  | ||||||
|     } |  | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|  |     setWaitForTrace(false); | ||||||
|     setHadSuccess(false); |     setHadSuccess(false); | ||||||
|     setHadFailure(false); |     setHadFailure(false); | ||||||
|     setWaiting(false); |  | ||||||
|     setChosenDate(new Date().toString()); |  | ||||||
|     setResponseBody(''); |     setResponseBody(''); | ||||||
|     setDuration(20); |     setDuration(20); | ||||||
|     setPackets(100); |     setPackets(100); | ||||||
|     setChosenInterface('up'); |     setChosenInterface('up'); | ||||||
|  |     setWaitingForTrace(false); | ||||||
|   }, [show]); |   }, [show]); | ||||||
|  |  | ||||||
|   const doAction = () => { |   const doAction = () => { | ||||||
|  |     setBlockFields(true); | ||||||
|     setHadFailure(false); |     setHadFailure(false); | ||||||
|     setHadSuccess(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 = { |     const parameters = { | ||||||
|       serialNumber: selectedDeviceId, |       serialNumber: deviceSerialNumber, | ||||||
|       when: dateChosen <= now ? 0 : dateToUnix(utcDateString), |       when: 0, | ||||||
|       network: chosenInterface, |       network: chosenInterface, | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
| @@ -88,163 +79,191 @@ const TraceModal = ({ show, toggleModal }) => { | |||||||
|  |  | ||||||
|     const headers = { |     const headers = { | ||||||
|       Accept: 'application/json', |       Accept: 'application/json', | ||||||
|       Authorization: `Bearer ${token}`, |       Authorization: `Bearer ${currentToken}`, | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     axiosInstance |     axiosInstance | ||||||
|       .post(`/device/${encodeURIComponent(selectedDeviceId)}/trace`, parameters, { headers }) |       .post( | ||||||
|       .then(() => { |         `${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}/trace`, | ||||||
|  |         parameters, | ||||||
|  |         { headers }, | ||||||
|  |       ) | ||||||
|  |       .then((response) => { | ||||||
|         setHadSuccess(true); |         setHadSuccess(true); | ||||||
|  |         if (waitForTrace) { | ||||||
|  |           setCommandUuid(response.data.UUID); | ||||||
|  |           setWaitingForTrace(true); | ||||||
|  |         } | ||||||
|       }) |       }) | ||||||
|       .catch(() => { |       .catch(() => { | ||||||
|         setResponseBody(t('commands.error')); |         setResponseBody(t('commands.error')); | ||||||
|         setHadFailure(true); |         setHadFailure(true); | ||||||
|       }) |       }) | ||||||
|       .finally(() => { |       .finally(() => { | ||||||
|         setWaiting(false); |         setBlockFields(false); | ||||||
|  |         setBlockFields(false); | ||||||
|         eventBus.dispatch('actionCompleted', { message: 'An action has been completed' }); |         eventBus.dispatch('actionCompleted', { message: 'An action has been completed' }); | ||||||
|       }); |       }); | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|  |   useEffect(() => { | ||||||
|  |     if (deviceSerialNumber !== null && show) { | ||||||
|  |       const asyncGet = async () => { | ||||||
|  |         const isConnected = await getDeviceConnection( | ||||||
|  |           deviceSerialNumber, | ||||||
|  |           currentToken, | ||||||
|  |           endpoints.ucentralgw, | ||||||
|  |         ); | ||||||
|  |         setIsDeviceConnected(isConnected); | ||||||
|  |       }; | ||||||
|  |       asyncGet(); | ||||||
|  |     } | ||||||
|  |   }, [show]); | ||||||
|  |  | ||||||
|  |   const getBody = () => { | ||||||
|  |     if (waitingForTrace) { | ||||||
|  |       return ( | ||||||
|  |         <WaitingForTraceBody | ||||||
|  |           toggle={toggleModal} | ||||||
|  |           serialNumber={deviceSerialNumber} | ||||||
|  |           commandUuid={commandUuid} | ||||||
|  |         /> | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |     if (hadSuccess) { | ||||||
|  |       return <SuccessfulActionModalBody toggleModal={toggleModal} />; | ||||||
|  |     } | ||||||
|  |     return ( | ||||||
|  |       <div> | ||||||
|  |         <CModalBody> | ||||||
|  |           <h6>{t('trace.directions')}</h6> | ||||||
|  |           <CRow className={styles.spacedRow}> | ||||||
|  |             <CCol> | ||||||
|  |               <CButton | ||||||
|  |                 disabled={blockFields} | ||||||
|  |                 block | ||||||
|  |                 color="primary" | ||||||
|  |                 onClick={() => setUsingDuration(true)} | ||||||
|  |               > | ||||||
|  |                 {t('common.duration')} | ||||||
|  |               </CButton> | ||||||
|  |             </CCol> | ||||||
|  |             <CCol> | ||||||
|  |               <CButton | ||||||
|  |                 disabled={blockFields} | ||||||
|  |                 block | ||||||
|  |                 color="primary" | ||||||
|  |                 onClick={() => setUsingDuration(false)} | ||||||
|  |               > | ||||||
|  |                 {t('trace.packets')} | ||||||
|  |               </CButton> | ||||||
|  |             </CCol> | ||||||
|  |           </CRow> | ||||||
|  |           <CRow className={styles.spacedRow}> | ||||||
|  |             <CCol md="4" className={styles.spacedColumn}> | ||||||
|  |               {usingDuration ? 'Duration: ' : 'Packets: '} | ||||||
|  |             </CCol> | ||||||
|  |             <CCol xs="12" md="8"> | ||||||
|  |               {usingDuration ? ( | ||||||
|  |                 <CSelect | ||||||
|  |                   custom | ||||||
|  |                   defaultValue={duration} | ||||||
|  |                   disabled={blockFields} | ||||||
|  |                   onChange={(e) => setDuration(e.target.value)} | ||||||
|  |                 > | ||||||
|  |                   <option value="20">20s</option> | ||||||
|  |                   <option value="40">40s</option> | ||||||
|  |                   <option value="60">60s</option> | ||||||
|  |                   <option value="120">120s</option> | ||||||
|  |                 </CSelect> | ||||||
|  |               ) : ( | ||||||
|  |                 <CSelect | ||||||
|  |                   custom | ||||||
|  |                   defaultValue={packets} | ||||||
|  |                   disabled={blockFields} | ||||||
|  |                   onChange={(e) => setPackets(e.target.value)} | ||||||
|  |                 > | ||||||
|  |                   <option value="100">100</option> | ||||||
|  |                   <option value="250">250</option> | ||||||
|  |                   <option value="500">500</option> | ||||||
|  |                   <option value="1000">1000</option> | ||||||
|  |                 </CSelect> | ||||||
|  |               )} | ||||||
|  |             </CCol> | ||||||
|  |           </CRow> | ||||||
|  |           <CRow className={styles.spacedRow}> | ||||||
|  |             <CCol md="7">{t('trace.choose_network')}:</CCol> | ||||||
|  |             <CCol> | ||||||
|  |               <CForm> | ||||||
|  |                 <CFormGroup variant="checkbox" onClick={() => setChosenInterface('up')}> | ||||||
|  |                   <CInputRadio | ||||||
|  |                     defaultChecked={chosenInterface === 'up'} | ||||||
|  |                     id="traceRadio1" | ||||||
|  |                     name="radios" | ||||||
|  |                     value="traceOption1" | ||||||
|  |                   /> | ||||||
|  |                   <CLabel variant="checkbox" htmlFor="traceRadio1"> | ||||||
|  |                     Up | ||||||
|  |                   </CLabel> | ||||||
|  |                 </CFormGroup> | ||||||
|  |                 <CFormGroup variant="checkbox" onClick={() => setChosenInterface('down')}> | ||||||
|  |                   <CInputRadio | ||||||
|  |                     defaultChecked={chosenInterface === 'down'} | ||||||
|  |                     id="traceRadio2" | ||||||
|  |                     name="radios" | ||||||
|  |                     value="traceOption2" | ||||||
|  |                   /> | ||||||
|  |                   <CLabel variant="checkbox" htmlFor="traceRadio2"> | ||||||
|  |                     Down | ||||||
|  |                   </CLabel> | ||||||
|  |                 </CFormGroup> | ||||||
|  |               </CForm> | ||||||
|  |             </CCol> | ||||||
|  |           </CRow> | ||||||
|  |           <CRow className={styles.spacedRow} hidden={!isDeviceConnected}> | ||||||
|  |             <CCol md="8"> | ||||||
|  |               <p className={styles.spacedText}>{t('trace.wait_for_file')}</p> | ||||||
|  |             </CCol> | ||||||
|  |             <CCol> | ||||||
|  |               <CSwitch | ||||||
|  |                 disabled={blockFields} | ||||||
|  |                 color="primary" | ||||||
|  |                 defaultChecked={waitForTrace} | ||||||
|  |                 onClick={toggleWaitForTrace} | ||||||
|  |                 labelOn={t('common.yes')} | ||||||
|  |                 labelOff={t('common.no')} | ||||||
|  |               /> | ||||||
|  |             </CCol> | ||||||
|  |           </CRow> | ||||||
|  |           <div hidden={!hadSuccess && !hadFailure}> | ||||||
|  |             <div> | ||||||
|  |               <pre className="ignore">{responseBody} </pre> | ||||||
|  |             </div> | ||||||
|  |           </div> | ||||||
|  |         </CModalBody> | ||||||
|  |         <CModalFooter> | ||||||
|  |           <LoadingButton | ||||||
|  |             label={t('trace.trace')} | ||||||
|  |             isLoadingLabel={t('common.loading_ellipsis')} | ||||||
|  |             isLoading={blockFields} | ||||||
|  |             action={doAction} | ||||||
|  |             block={false} | ||||||
|  |             disabled={blockFields} | ||||||
|  |           /> | ||||||
|  |           <CButton color="secondary" onClick={toggleModal}> | ||||||
|  |             {t('common.cancel')} | ||||||
|  |           </CButton> | ||||||
|  |         </CModalFooter> | ||||||
|  |       </div> | ||||||
|  |     ); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
|     <CModal show={show} onClose={toggleModal}> |     <CModal show={show} onClose={toggleModal}> | ||||||
|       <CModalHeader closeButton> |       <CModalHeader closeButton> | ||||||
|         <CModalTitle>{t('trace.title')}</CModalTitle> |         <CModalTitle>{t('trace.title')}</CModalTitle> | ||||||
|       </CModalHeader> |       </CModalHeader> | ||||||
|       {hadSuccess ? ( |       {getBody()} | ||||||
|         <SuccessfulActionModalBody toggleModal={toggleModal} /> |  | ||||||
|       ) : ( |  | ||||||
|         <div> |  | ||||||
|           <CModalBody> |  | ||||||
|             <h6>{t('trace.directions')}</h6> |  | ||||||
|             <CRow className={styles.spacedRow}> |  | ||||||
|               <CCol> |  | ||||||
|                 <CButton |  | ||||||
|                   disabled={waiting} |  | ||||||
|                   block |  | ||||||
|                   color="primary" |  | ||||||
|                   onClick={() => setUsingDuration(true)} |  | ||||||
|                 > |  | ||||||
|                   {t('common.duration')} |  | ||||||
|                 </CButton> |  | ||||||
|               </CCol> |  | ||||||
|               <CCol> |  | ||||||
|                 <CButton |  | ||||||
|                   disabled={waiting} |  | ||||||
|                   block |  | ||||||
|                   color="primary" |  | ||||||
|                   onClick={() => setUsingDuration(false)} |  | ||||||
|                 > |  | ||||||
|                   {t('trace.packets')} |  | ||||||
|                 </CButton> |  | ||||||
|               </CCol> |  | ||||||
|             </CRow> |  | ||||||
|             <CRow className={styles.spacedRow}> |  | ||||||
|               <CCol md="4" className={styles.spacedColumn}> |  | ||||||
|                 {usingDuration ? 'Duration: ' : 'Packets: '} |  | ||||||
|               </CCol> |  | ||||||
|               <CCol xs="12" md="8"> |  | ||||||
|                 {usingDuration ? ( |  | ||||||
|                   <CSelect defaultValue="duration" disabled={waiting}> |  | ||||||
|                     <option value="20" onClick={() => setDuration(20)}> |  | ||||||
|                       20s |  | ||||||
|                     </option> |  | ||||||
|                     <option value="40" onClick={() => setDuration(40)}> |  | ||||||
|                       40s |  | ||||||
|                     </option> |  | ||||||
|                     <option value="60" onClick={() => setDuration(60)}> |  | ||||||
|                       60s |  | ||||||
|                     </option> |  | ||||||
|                     <option value="120" onClick={() => setDuration(120)}> |  | ||||||
|                       120s |  | ||||||
|                     </option> |  | ||||||
|                   </CSelect> |  | ||||||
|                 ) : ( |  | ||||||
|                   <CSelect defaultValue={packets} disabled={waiting}> |  | ||||||
|                     <option value="100" onClick={() => setPackets(100)}> |  | ||||||
|                       100 |  | ||||||
|                     </option> |  | ||||||
|                     <option value="250" onClick={() => setPackets(250)}> |  | ||||||
|                       250 |  | ||||||
|                     </option> |  | ||||||
|                     <option value="500" onClick={() => setPackets(500)}> |  | ||||||
|                       500 |  | ||||||
|                     </option> |  | ||||||
|                     <option value="1000" onClick={() => setPackets(1000)}> |  | ||||||
|                       1000 |  | ||||||
|                     </option> |  | ||||||
|                   </CSelect> |  | ||||||
|                 )} |  | ||||||
|               </CCol> |  | ||||||
|             </CRow> |  | ||||||
|             <CRow className={styles.spacedRow}> |  | ||||||
|               <CCol md="4" className={styles.spacedColumn}> |  | ||||||
|                 <p>{t('common.date')}:</p> |  | ||||||
|               </CCol> |  | ||||||
|               <CCol xs="12" md="8"> |  | ||||||
|                 <DatePicker |  | ||||||
|                   selected={new Date(chosenDate)} |  | ||||||
|                   includeTime |  | ||||||
|                   value={new Date(chosenDate)} |  | ||||||
|                   placeholder="Select custom date" |  | ||||||
|                   disabled={waiting} |  | ||||||
|                   onChange={(date) => setDate(date)} |  | ||||||
|                   min={convertDateToUtc(new Date())} |  | ||||||
|                 /> |  | ||||||
|               </CCol> |  | ||||||
|             </CRow> |  | ||||||
|             <CInvalidFeedback>{t('common.need_date')}</CInvalidFeedback> |  | ||||||
|             <CRow className={styles.spacedRow}> |  | ||||||
|               <CCol md="7">{t('trace.choose_network')}:</CCol> |  | ||||||
|               <CCol> |  | ||||||
|                 <CForm> |  | ||||||
|                   <CFormGroup variant="checkbox" onClick={() => setChosenInterface('up')}> |  | ||||||
|                     <CInputRadio |  | ||||||
|                       defaultChecked={chosenInterface === 'up'} |  | ||||||
|                       id="traceRadio1" |  | ||||||
|                       name="radios" |  | ||||||
|                       value="traceOption1" |  | ||||||
|                     /> |  | ||||||
|                     <CLabel variant="checkbox" htmlFor="traceRadio1"> |  | ||||||
|                       Up |  | ||||||
|                     </CLabel> |  | ||||||
|                   </CFormGroup> |  | ||||||
|                   <CFormGroup variant="checkbox" onClick={() => setChosenInterface('down')}> |  | ||||||
|                     <CInputRadio |  | ||||||
|                       defaultChecked={chosenInterface === 'down'} |  | ||||||
|                       id="traceRadio2" |  | ||||||
|                       name="radios" |  | ||||||
|                       value="traceOption2" |  | ||||||
|                     /> |  | ||||||
|                     <CLabel variant="checkbox" htmlFor="traceRadio2"> |  | ||||||
|                       Down |  | ||||||
|                     </CLabel> |  | ||||||
|                   </CFormGroup> |  | ||||||
|                 </CForm> |  | ||||||
|               </CCol> |  | ||||||
|             </CRow> |  | ||||||
|             <div hidden={!hadSuccess && !hadFailure}> |  | ||||||
|               <div> |  | ||||||
|                 <pre className="ignore">{responseBody} </pre> |  | ||||||
|               </div> |  | ||||||
|             </div> |  | ||||||
|           </CModalBody> |  | ||||||
|           <CModalFooter> |  | ||||||
|             <LoadingButton |  | ||||||
|               label="Schedule" |  | ||||||
|               isLoadingLabel="Loading..." |  | ||||||
|               isLoading={waiting} |  | ||||||
|               action={doAction} |  | ||||||
|               variant="outline" |  | ||||||
|               block={false} |  | ||||||
|               disabled={waiting} |  | ||||||
|             /> |  | ||||||
|             <CButton color="secondary" onClick={toggleModal}> |  | ||||||
|               {t('common.cancel')} |  | ||||||
|             </CButton> |  | ||||||
|           </CModalFooter> |  | ||||||
|         </div> |  | ||||||
|       )} |  | ||||||
|     </CModal> |     </CModal> | ||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -5,3 +5,10 @@ | |||||||
| .spacedColumn { | .spacedColumn { | ||||||
|   margin-top: 7px; |   margin-top: 7px; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | .centerDiv { | ||||||
|  |   display: flex; | ||||||
|  |   justify-content: center; | ||||||
|  |   align-items: center; | ||||||
|  |   height: 20px; | ||||||
|  | } | ||||||
|   | |||||||
| @@ -9,28 +9,31 @@ import { | |||||||
|   CForm, |   CForm, | ||||||
|   CSwitch, |   CSwitch, | ||||||
|   CCol, |   CCol, | ||||||
|  |   CSpinner, | ||||||
| } from '@coreui/react'; | } from '@coreui/react'; | ||||||
| import React, { useState, useEffect } from 'react'; | import React, { useState, useEffect } from 'react'; | ||||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||||
| import { useSelector } from 'react-redux'; |  | ||||||
| import PropTypes from 'prop-types'; | 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 axiosInstance from 'utils/axiosInstance'; | ||||||
| import eventBus from 'utils/eventBus'; | import eventBus from 'utils/eventBus'; | ||||||
| import LoadingButton from 'components/LoadingButton'; | import LoadingButton from 'components/LoadingButton'; | ||||||
| import WifiChannelTable from 'components/WifiScanResultModal/containers/WifiChannelTable'; | import WifiChannelTable from 'components/WifiScanResultModal/WifiChannelTable'; | ||||||
| import 'react-widgets/styles.css'; | import 'react-widgets/styles.css'; | ||||||
| import styles from './index.module.scss'; | import styles from './index.module.scss'; | ||||||
|  |  | ||||||
| const WifiScanModal = ({ show, toggleModal }) => { | const WifiScanModal = ({ show, toggleModal }) => { | ||||||
|   const { t } = useTranslation(); |   const { t } = useTranslation(); | ||||||
|  |   const { currentToken, endpoints } = useAuth(); | ||||||
|  |   const { deviceSerialNumber } = useDevice(); | ||||||
|   const [hadSuccess, setHadSuccess] = useState(false); |   const [hadSuccess, setHadSuccess] = useState(false); | ||||||
|   const [hadFailure, setHadFailure] = useState(false); |   const [hadFailure, setHadFailure] = useState(false); | ||||||
|   const [waiting, setWaiting] = useState(false); |   const [waiting, setWaiting] = useState(false); | ||||||
|   const [choseVerbose, setVerbose] = useState(true); |   const [choseVerbose, setVerbose] = useState(true); | ||||||
|   const [activeScan, setActiveScan] = useState(false); |   const [activeScan, setActiveScan] = useState(false); | ||||||
|  |   const [hideOptions, setHideOptions] = useState(false); | ||||||
|   const [channelList, setChannelList] = useState([]); |   const [channelList, setChannelList] = useState([]); | ||||||
|   const selectedDeviceId = useSelector((state) => state.selectedDeviceId); |  | ||||||
|  |  | ||||||
|   const toggleVerbose = () => { |   const toggleVerbose = () => { | ||||||
|     setVerbose(!choseVerbose); |     setVerbose(!choseVerbose); | ||||||
| @@ -47,6 +50,7 @@ const WifiScanModal = ({ show, toggleModal }) => { | |||||||
|     setChannelList([]); |     setChannelList([]); | ||||||
|     setVerbose(true); |     setVerbose(true); | ||||||
|     setActiveScan(false); |     setActiveScan(false); | ||||||
|  |     setHideOptions(false); | ||||||
|   }, [show]); |   }, [show]); | ||||||
|  |  | ||||||
|   const parseThroughList = (scanList) => { |   const parseThroughList = (scanList) => { | ||||||
| @@ -85,25 +89,28 @@ const WifiScanModal = ({ show, toggleModal }) => { | |||||||
|     setHadSuccess(false); |     setHadSuccess(false); | ||||||
|     setWaiting(true); |     setWaiting(true); | ||||||
|  |  | ||||||
|     const token = getToken(); |  | ||||||
|  |  | ||||||
|     const parameters = { |     const parameters = { | ||||||
|       serialNumber: selectedDeviceId, |       serialNumber: deviceSerialNumber, | ||||||
|       verbose: choseVerbose, |       verbose: choseVerbose, | ||||||
|       activeScan, |       activeScan, | ||||||
|     }; |     }; | ||||||
|     const headers = { |     const headers = { | ||||||
|       Accept: 'application/json', |       Accept: 'application/json', | ||||||
|       Authorization: `Bearer ${token}`, |       Authorization: `Bearer ${currentToken}`, | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     axiosInstance |     axiosInstance | ||||||
|       .post(`/device/${encodeURIComponent(selectedDeviceId)}/wifiscan`, parameters, { headers }) |       .post( | ||||||
|  |         `${endpoints.ucentralgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}/wifiscan`, | ||||||
|  |         parameters, | ||||||
|  |         { headers }, | ||||||
|  |       ) | ||||||
|       .then((response) => { |       .then((response) => { | ||||||
|         const scanList = response?.data?.results?.status?.scan; |         const scanList = response?.data?.results?.status?.scan; | ||||||
|  |  | ||||||
|         if (scanList) { |         if (scanList) { | ||||||
|           setChannelList(parseThroughList(scanList)); |           setChannelList(parseThroughList(scanList)); | ||||||
|  |           setHideOptions(true); | ||||||
|           setHadSuccess(true); |           setHadSuccess(true); | ||||||
|         } else { |         } else { | ||||||
|           setHadFailure(true); |           setHadFailure(true); | ||||||
| @@ -124,47 +131,66 @@ const WifiScanModal = ({ show, toggleModal }) => { | |||||||
|         <CModalTitle>{t('actions.wifi_scan')}</CModalTitle> |         <CModalTitle>{t('actions.wifi_scan')}</CModalTitle> | ||||||
|       </CModalHeader> |       </CModalHeader> | ||||||
|       <CModalBody> |       <CModalBody> | ||||||
|         <h6>{t('scan.directions')}</h6> |         <div hidden={hideOptions || waiting}> | ||||||
|         <CRow className={styles.spacedRow}> |           <h6>{t('scan.directions')}</h6> | ||||||
|           <CCol md="3"> |           <CRow className={styles.spacedRow}> | ||||||
|             <p className={styles.spacedText}>Verbose:</p> |             <CCol md="3"> | ||||||
|           </CCol> |               <p className={styles.spacedText}>Verbose:</p> | ||||||
|           <CCol> |             </CCol> | ||||||
|             <CForm className={styles.spacedSwitch}> |             <CCol> | ||||||
|               <CSwitch |               <CForm className={styles.spacedSwitch}> | ||||||
|                 color="primary" |                 <CSwitch | ||||||
|                 defaultChecked={choseVerbose} |                   color="primary" | ||||||
|                 onClick={toggleVerbose} |                   defaultChecked={choseVerbose} | ||||||
|                 labelOn={t('common.on')} |                   onClick={toggleVerbose} | ||||||
|                 labelOff={t('common.off')} |                   labelOn={t('common.on')} | ||||||
|               /> |                   labelOff={t('common.off')} | ||||||
|             </CForm> |                 /> | ||||||
|           </CCol> |               </CForm> | ||||||
|         </CRow> |             </CCol> | ||||||
|         <CRow className={styles.spacedRow}> |           </CRow> | ||||||
|           <CCol md="3"> |           <CRow className={styles.spacedRow}> | ||||||
|             <p className={styles.spacedText}>{t('scan.active')}:</p> |             <CCol md="3"> | ||||||
|           </CCol> |               <p className={styles.spacedText}>{t('scan.active')}:</p> | ||||||
|           <CCol> |             </CCol> | ||||||
|             <CForm className={styles.spacedSwitch}> |             <CCol> | ||||||
|               <CSwitch |               <CForm className={styles.spacedSwitch}> | ||||||
|                 color="primary" |                 <CSwitch | ||||||
|                 defaultChecked={activeScan} |                   color="primary" | ||||||
|                 onClick={toggleActiveScan} |                   defaultChecked={activeScan} | ||||||
|                 labelOn={t('common.on')} |                   onClick={toggleActiveScan} | ||||||
|                 labelOff={t('common.off')} |                   labelOn={t('common.on')} | ||||||
|               /> |                   labelOff={t('common.off')} | ||||||
|             </CForm> |                 /> | ||||||
|           </CCol> |               </CForm> | ||||||
|         </CRow> |             </CCol> | ||||||
|         <div className={styles.spacedRow} hidden={!hadSuccess && !hadFailure}> |           </CRow> | ||||||
|  |         </div> | ||||||
|  |         <div hidden={!waiting}> | ||||||
|  |           <CRow> | ||||||
|  |             <CCol> | ||||||
|  |               <h6>{t('scan.waiting_directions')}</h6> | ||||||
|  |             </CCol> | ||||||
|  |           </CRow> | ||||||
|  |           <CRow> | ||||||
|  |             <CCol className={styles.centerDiv}> | ||||||
|  |               <CSpinner /> | ||||||
|  |             </CCol> | ||||||
|  |           </CRow> | ||||||
|  |         </div> | ||||||
|  |         <div hidden={!hadSuccess && !hadFailure}> | ||||||
|  |           <CRow className={styles.bottomSpace}> | ||||||
|  |             <CCol> | ||||||
|  |               <h6>{t('scan.result_directions')}</h6> | ||||||
|  |             </CCol> | ||||||
|  |           </CRow> | ||||||
|           <WifiChannelTable channels={channelList} /> |           <WifiChannelTable channels={channelList} /> | ||||||
|         </div> |         </div> | ||||||
|       </CModalBody> |       </CModalBody> | ||||||
|       <CModalFooter> |       <CModalFooter> | ||||||
|         <LoadingButton |         <LoadingButton | ||||||
|           label={t('common.start')} |           label={!hadSuccess && !hadFailure ? t('scan.scan') : t('scan.re_scan')} | ||||||
|           isLoadingLabel={t('common.loading_ellipsis')} |           isLoadingLabel={t('scan.scanning')} | ||||||
|           isLoading={waiting} |           isLoading={waiting} | ||||||
|           action={doAction} |           action={doAction} | ||||||
|           variant="outline" |           variant="outline" | ||||||
| @@ -172,7 +198,7 @@ const WifiScanModal = ({ show, toggleModal }) => { | |||||||
|           disabled={waiting} |           disabled={waiting} | ||||||
|         /> |         /> | ||||||
|         <CButton color="secondary" onClick={toggleModal}> |         <CButton color="secondary" onClick={toggleModal}> | ||||||
|           {t('common.cancel')} |           {!hadSuccess && !hadFailure ? t('common.cancel') : t('common.exit')} | ||||||
|         </CButton> |         </CButton> | ||||||
|       </CModalFooter> |       </CModalFooter> | ||||||
|     </CModal> |     </CModal> | ||||||
|   | |||||||
| @@ -9,3 +9,14 @@ | |||||||
| .spacedSwitch { | .spacedSwitch { | ||||||
|   padding-left: 5%; |   padding-left: 5%; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | .bottomSpace { | ||||||
|  |   margin-bottom: 20px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .centerDiv { | ||||||
|  |   display: flex; | ||||||
|  |   justify-content: center; | ||||||
|  |   align-items: center; | ||||||
|  |   height: 20px; | ||||||
|  | } | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ import { CCol, CRow } from '@coreui/react'; | |||||||
| import React, { useEffect } from 'react'; | import React, { useEffect } from 'react'; | ||||||
| import { v4 as createUuid } from 'uuid'; | import { v4 as createUuid } from 'uuid'; | ||||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||||
| import WifiChannelCard from '../WifiChannelCard'; | import WifiChannelCard from './WifiChannelCard'; | ||||||
| 
 | 
 | ||||||
| const WifiChannelTable = ({ channels }) => { | const WifiChannelTable = ({ channels }) => { | ||||||
|   const sortChannels = () => { |   const sortChannels = () => { | ||||||
| @@ -1,11 +0,0 @@ | |||||||
| .cardTitle { |  | ||||||
|   color: black; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| .scrollable { |  | ||||||
|   height: 250px; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| .datatable { |  | ||||||
|   color: white; |  | ||||||
| } |  | ||||||
| @@ -11,7 +11,7 @@ import { | |||||||
| } from '@coreui/react'; | } from '@coreui/react'; | ||||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||||
| import { prettyDate } from 'utils/helper'; | import { prettyDate } from 'utils/helper'; | ||||||
| import WifiChannelTable from './containers/WifiChannelTable'; | import WifiChannelTable from './WifiChannelTable'; | ||||||
| import styles from './index.module.scss'; | import styles from './index.module.scss'; | ||||||
|  |  | ||||||
| const WifiScanResultModal = ({ show, toggle, scanResults, date }) => { | const WifiScanResultModal = ({ show, toggle, scanResults, date }) => { | ||||||
|   | |||||||
| @@ -1,3 +1,15 @@ | |||||||
| .modalTitle { | .modalTitle { | ||||||
|   color: black; |   color: black; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | .cardTitle { | ||||||
|  |   color: black; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .scrollable { | ||||||
|  |   height: 250px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .datatable { | ||||||
|  |   color: white; | ||||||
|  | } | ||||||
|   | |||||||
							
								
								
									
										27
									
								
								src/contexts/AuthProvider/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/contexts/AuthProvider/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | import React, { useState } from 'react'; | ||||||
|  | import PropTypes from 'prop-types'; | ||||||
|  |  | ||||||
|  | const AuthContext = React.createContext(); | ||||||
|  |  | ||||||
|  | export const AuthProvider = ({ token, apiEndpoints, children }) => { | ||||||
|  |   const [currentToken, setCurrentToken] = useState(token); | ||||||
|  |   const [endpoints, setEndpoints] = useState(apiEndpoints); | ||||||
|  |  | ||||||
|  |   return ( | ||||||
|  |     <AuthContext.Provider value={{ currentToken, setCurrentToken, endpoints, setEndpoints }}> | ||||||
|  |       {children} | ||||||
|  |     </AuthContext.Provider> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | AuthProvider.propTypes = { | ||||||
|  |   token: PropTypes.string.isRequired, | ||||||
|  |   children: PropTypes.node.isRequired, | ||||||
|  |   apiEndpoints: PropTypes.instanceOf(Object), | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | AuthProvider.defaultProps = { | ||||||
|  |   apiEndpoints: {}, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export const useAuth = () => React.useContext(AuthContext); | ||||||
							
								
								
									
										21
									
								
								src/contexts/DeviceProvider/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/contexts/DeviceProvider/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | |||||||
|  | import React, { useState } from 'react'; | ||||||
|  | import PropTypes from 'prop-types'; | ||||||
|  |  | ||||||
|  | const DeviceContext = React.createContext(); | ||||||
|  |  | ||||||
|  | export const DeviceProvider = ({ serialNumber, children }) => { | ||||||
|  |   const [deviceSerialNumber, setDeviceSerialNumber] = useState(serialNumber); | ||||||
|  |  | ||||||
|  |   return ( | ||||||
|  |     <DeviceContext.Provider value={{ deviceSerialNumber, setDeviceSerialNumber }}> | ||||||
|  |       {children} | ||||||
|  |     </DeviceContext.Provider> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | DeviceProvider.propTypes = { | ||||||
|  |   serialNumber: PropTypes.string.isRequired, | ||||||
|  |   children: PropTypes.node.isRequired, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export const useDevice = () => React.useContext(DeviceContext); | ||||||
| @@ -1,19 +1,17 @@ | |||||||
|  | /* eslint-disable import/no-extraneous-dependencies */ | ||||||
| import React from 'react'; | import React from 'react'; | ||||||
| import ReactDOM from 'react-dom'; | import ReactDOM from 'react-dom'; | ||||||
| import 'index.css'; | import 'index.css'; | ||||||
| import { Provider } from 'react-redux'; |  | ||||||
| import App from 'App'; | import App from 'App'; | ||||||
| import store from 'store'; |  | ||||||
| import { icons } from 'assets/icons'; | import { icons } from 'assets/icons'; | ||||||
|  | import '@babel/polyfill'; | ||||||
| import 'i18n'; | import 'i18n'; | ||||||
|  |  | ||||||
| React.icons = icons; | React.icons = icons; | ||||||
|  |  | ||||||
| ReactDOM.render( | ReactDOM.render( | ||||||
|   <React.StrictMode> |   <React.StrictMode> | ||||||
|     <Provider store={store}> |     <App /> | ||||||
|       <App /> |  | ||||||
|     </Provider> |  | ||||||
|   </React.StrictMode>, |   </React.StrictMode>, | ||||||
|   document.getElementById('root'), |   document.getElementById('root'), | ||||||
| ); | ); | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ const TheFooter = () => ( | |||||||
|   <Translation> |   <Translation> | ||||||
|     {(t) => ( |     {(t) => ( | ||||||
|       <CFooter fixed={false}> |       <CFooter fixed={false}> | ||||||
|         <div>{t('footer.version')} 0.9.3</div> |         <div>{t('footer.version')} 0.9.13</div> | ||||||
|         <div className="mfs-auto"> |         <div className="mfs-auto"> | ||||||
|           <span className="mr-1">{t('footer.powered_by')}</span> |           <span className="mr-1">{t('footer.powered_by')}</span> | ||||||
|           <a href="https://coreui.io/react" target="_blank" rel="noopener noreferrer"> |           <a href="https://coreui.io/react" target="_blank" rel="noopener noreferrer"> | ||||||
|   | |||||||
| @@ -1,5 +1,4 @@ | |||||||
| import React, { useState, useEffect } from 'react'; | import React, { useState, useEffect } from 'react'; | ||||||
| import { useSelector, useDispatch } from 'react-redux'; |  | ||||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||||
| import { | import { | ||||||
|   CHeader, |   CHeader, | ||||||
| @@ -11,26 +10,27 @@ import { | |||||||
|   CLink, |   CLink, | ||||||
|   CPopover, |   CPopover, | ||||||
| } from '@coreui/react'; | } from '@coreui/react'; | ||||||
|  | import PropTypes from 'prop-types'; | ||||||
| import CIcon from '@coreui/icons-react'; | import CIcon from '@coreui/icons-react'; | ||||||
| import { cilAccountLogout } from '@coreui/icons'; | import { cilAccountLogout } from '@coreui/icons'; | ||||||
| import { logout } from 'utils/authHelper'; | import { logout } from 'utils/authHelper'; | ||||||
| import routes from 'routes'; | 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 { t, i18n } = useTranslation(); | ||||||
|   const dispatch = useDispatch(); |   const { currentToken, endpoints } = useAuth(); | ||||||
|   const [translatedRoutes, setTranslatedRoutes] = useState(routes); |   const [translatedRoutes, setTranslatedRoutes] = useState(routes); | ||||||
|   const sidebarShow = useSelector((state) => state.sidebarShow); |  | ||||||
|  |  | ||||||
|   const toggleSidebar = () => { |   const toggleSidebar = () => { | ||||||
|     const val = [true, 'responsive'].includes(sidebarShow) ? false : 'responsive'; |     const val = [true, 'responsive'].includes(showSidebar) ? false : 'responsive'; | ||||||
|     dispatch({ type: 'set', sidebarShow: val }); |     setShowSidebar(val); | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   const toggleSidebarMobile = () => { |   const toggleSidebarMobile = () => { | ||||||
|     const val = [false, 'responsive'].includes(sidebarShow) ? true : 'responsive'; |     const val = [false, 'responsive'].includes(showSidebar) ? true : 'responsive'; | ||||||
|     dispatch({ type: 'set', sidebarShow: val }); |     setShowSidebar(val); | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
| @@ -48,13 +48,18 @@ const TheHeader = () => { | |||||||
|       <CHeaderNav className="d-md-down-none mr-auto" /> |       <CHeaderNav className="d-md-down-none mr-auto" /> | ||||||
|  |  | ||||||
|       <CHeaderNav className="px-3"> |       <CHeaderNav className="px-3"> | ||||||
|         <LanguageSwitcher /> |         <LanguageSwitcher i18n={i18n} /> | ||||||
|       </CHeaderNav> |       </CHeaderNav> | ||||||
|  |  | ||||||
|       <CHeaderNav className="px-3"> |       <CHeaderNav className="px-3"> | ||||||
|         <CPopover content={t('common.logout')}> |         <CPopover content={t('common.logout')}> | ||||||
|           <CLink className="c-subheader-nav-link"> |           <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> |           </CLink> | ||||||
|         </CPopover> |         </CPopover> | ||||||
|       </CHeaderNav> |       </CHeaderNav> | ||||||
| @@ -69,4 +74,9 @@ const TheHeader = () => { | |||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | TheHeader.propTypes = { | ||||||
|  |   showSidebar: PropTypes.string.isRequired, | ||||||
|  |   setShowSidebar: PropTypes.func.isRequired, | ||||||
|  | }; | ||||||
|  |  | ||||||
| export default TheHeader; | export default TheHeader; | ||||||
|   | |||||||
| @@ -1,5 +1,4 @@ | |||||||
| import React from 'react'; | import React from 'react'; | ||||||
| import { useSelector, useDispatch } from 'react-redux'; |  | ||||||
| import { | import { | ||||||
|   CCreateElement, |   CCreateElement, | ||||||
|   CSidebar, |   CSidebar, | ||||||
| @@ -11,14 +10,12 @@ import { | |||||||
|   CSidebarNavDropdown, |   CSidebarNavDropdown, | ||||||
|   CSidebarNavItem, |   CSidebarNavItem, | ||||||
| } from '@coreui/react'; | } from '@coreui/react'; | ||||||
|  | import PropTypes from 'prop-types'; | ||||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||||
| import logoBar from 'assets/OpenWiFi_LogoLockup_WhiteColour.svg'; |  | ||||||
| import styles from './index.module.scss'; | import styles from './index.module.scss'; | ||||||
|  |  | ||||||
| const TheSidebar = () => { | const TheSidebar = ({ showSidebar, setShowSidebar }) => { | ||||||
|   const { t } = useTranslation(); |   const { t } = useTranslation(); | ||||||
|   const dispatch = useDispatch(); |  | ||||||
|   const show = useSelector((state) => state.sidebarShow); |  | ||||||
|  |  | ||||||
|   const navigation = [ |   const navigation = [ | ||||||
|     { |     { | ||||||
| @@ -30,16 +27,16 @@ const TheSidebar = () => { | |||||||
|   ]; |   ]; | ||||||
|  |  | ||||||
|   return ( |   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"> |       <CSidebarBrand className="d-md-down-none" to="/devices"> | ||||||
|         <img |         <img | ||||||
|           className={[styles.sidebarImgFull, 'c-sidebar-brand-full'].join(' ')} |           className={[styles.sidebarImgFull, 'c-sidebar-brand-full'].join(' ')} | ||||||
|           src={logoBar} |           src="assets/OpenWiFi_LogoLockup_WhiteColour.svg" | ||||||
|           alt="OpenWifi" |           alt="OpenWifi" | ||||||
|         /> |         /> | ||||||
|         <img |         <img | ||||||
|           className={[styles.sidebarImgMinimized, 'c-sidebar-brand-minimized'].join(' ')} |           className={[styles.sidebarImgMinimized, 'c-sidebar-brand-minimized'].join(' ')} | ||||||
|           src={logoBar} |           src="assets/OpenWiFi_LogoLockup_WhiteColour.svg" | ||||||
|           alt="OpenWifi" |           alt="OpenWifi" | ||||||
|         /> |         /> | ||||||
|       </CSidebarBrand> |       </CSidebarBrand> | ||||||
| @@ -59,4 +56,9 @@ const TheSidebar = () => { | |||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | TheSidebar.propTypes = { | ||||||
|  |   showSidebar: PropTypes.string.isRequired, | ||||||
|  |   setShowSidebar: PropTypes.func.isRequired, | ||||||
|  | }; | ||||||
|  |  | ||||||
| export default React.memo(TheSidebar); | export default React.memo(TheSidebar); | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| .sidebarImgFull { | .sidebarImgFull { | ||||||
|   height: 75px; |   height: 100px; | ||||||
|   width: 75px; |   width: 230px; | ||||||
| } | } | ||||||
|  |  | ||||||
| .sidebarImgMinimized { | .sidebarImgMinimized { | ||||||
|   | |||||||
| @@ -1,21 +1,37 @@ | |||||||
| import React from 'react'; | import React, { useState } from 'react'; | ||||||
| import { useSelector } from 'react-redux'; | import { useTranslation } from 'react-i18next'; | ||||||
| import PropTypes from 'prop-types'; | 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 TheContent from './Content'; | ||||||
| import TheSidebar from './Sidebar'; |  | ||||||
| import TheFooter from './Footer'; | import TheFooter from './Footer'; | ||||||
| import TheHeader from './Header'; |  | ||||||
|  |  | ||||||
| const TheLayout = (props) => { | const TheLayout = () => { | ||||||
|   const { isLoggedIn } = useSelector((state) => state.connected); |   const [showSidebar, setShowSidebar] = useState('responsive'); | ||||||
|   if (isLoggedIn) { |   const { endpoints, currentToken } = useAuth(); | ||||||
|     return <div>{props.children}</div>; |   const { t, i18n } = useTranslation(); | ||||||
|   } |  | ||||||
|   return ( |   return ( | ||||||
|     <div className="c-app c-default-layout"> |     <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"> |       <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"> |         <div className="c-body"> | ||||||
|           <TheContent /> |           <TheContent /> | ||||||
|         </div> |         </div> | ||||||
| @@ -25,12 +41,4 @@ const TheLayout = (props) => { | |||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| TheLayout.propTypes = { |  | ||||||
|   children: PropTypes.instanceOf(Object), |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| TheLayout.defaultProps = { |  | ||||||
|   children: {}, |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| export default TheLayout; | export default TheLayout; | ||||||
|   | |||||||
| @@ -1,46 +1,40 @@ | |||||||
| import React, { useEffect } from 'react'; | import React from 'react'; | ||||||
| import { useDispatch, useSelector } from 'react-redux'; |  | ||||||
| import { useParams } from 'react-router-dom'; | import { useParams } from 'react-router-dom'; | ||||||
| import { CRow, CCol } from '@coreui/react'; | import { CRow, CCol } from '@coreui/react'; | ||||||
| import DeviceHealth from 'components/DeviceHealth'; | import DeviceHealth from 'components/DeviceHealth'; | ||||||
| import DeviceConfiguration from 'components/DeviceConfiguration'; | import DeviceConfiguration from 'components/DeviceConfiguration'; | ||||||
| import DeviceCommandsLog from 'components/DeviceCommandsLog'; | import CommandHistory from 'components/CommandHistory'; | ||||||
| import DeviceLogs from 'components/DeviceLogs'; | import DeviceLogs from 'components/DeviceLogs'; | ||||||
| import DeviceStatisticsCard from 'components/InterfaceStatistics'; | 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 DevicePage = () => { | ||||||
|   const dispatch = useDispatch(); |  | ||||||
|  |  | ||||||
|   const { deviceId } = useParams(); |   const { deviceId } = useParams(); | ||||||
|   const previouslySelectedDeviceId = useSelector((state) => state.selectedDeviceId); |  | ||||||
|  |  | ||||||
|   useEffect(() => { |  | ||||||
|     if (deviceId && deviceId !== previouslySelectedDeviceId) |  | ||||||
|       dispatch({ type: 'set', selectedDeviceId: deviceId }); |  | ||||||
|   }, [deviceId]); |  | ||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
|     <> |     <div className="App"> | ||||||
|       <div className="App"> |       <DeviceProvider serialNumber={deviceId}> | ||||||
|         <CRow> |         <CRow> | ||||||
|           <CCol xs="12" sm="6"> |           <CCol xs="12" sm="6"> | ||||||
|             <DeviceConfiguration selectedDeviceId={deviceId} /> |             <DeviceStatusCard /> | ||||||
|  |             <DeviceConfiguration /> | ||||||
|           </CCol> |           </CCol> | ||||||
|           <CCol xs="12" sm="6"> |           <CCol xs="12" sm="6"> | ||||||
|             <DeviceLogs selectedDeviceId={deviceId} /> |             <DeviceLogs /> | ||||||
|             <DeviceHealth selectedDeviceId={deviceId} /> |             <DeviceHealth /> | ||||||
|             <DeviceActionCard selectedDeviceId={deviceId} /> |             <DeviceActionCard /> | ||||||
|           </CCol> |           </CCol> | ||||||
|         </CRow> |         </CRow> | ||||||
|         <CRow> |         <CRow> | ||||||
|           <CCol> |           <CCol> | ||||||
|             <DeviceStatisticsCard selectedDeviceId={deviceId} /> |             <DeviceStatisticsCard /> | ||||||
|             <DeviceCommandsLog selectedDeviceId={deviceId} /> |             <CommandHistory /> | ||||||
|           </CCol> |           </CCol> | ||||||
|         </CRow> |         </CRow> | ||||||
|       </div> |       </DeviceProvider> | ||||||
|     </> |     </div> | ||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user