Compare commits
	
		
			140 Commits
		
	
	
		
			v2.6.0-RC1
			...
			v2.8.0-RC1
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 7f0897d189 | ||
|   | 42d274e988 | ||
|   | ec64680a66 | ||
|   | d006b89efd | ||
|   | 48654c382d | ||
|   | 8d23168a87 | ||
|   | e27baaec9d | ||
|   | 31a37ae506 | ||
|   | 6360f90102 | ||
|   | b829003711 | ||
|   | fae773b25a | ||
|   | 0e8df4441d | ||
|   | 35e7e26d67 | ||
|   | 14c88280f5 | ||
|   | aaab9d3bc5 | ||
|   | 02095595c6 | ||
|   | 9a80664ce2 | ||
|   | b69e7e4ddf | ||
|   | db642782b0 | ||
|   | 33dedbbfa3 | ||
|   | a8f53de511 | ||
|   | 3b7dad989f | ||
|   | 4d62c5298a | ||
|   | 538c6b5233 | ||
|   | 969a14802b | ||
|   | 5c7f683d16 | ||
|   | c7874e5e40 | ||
|   | 628e4fa873 | ||
|   | 8e01abbeb6 | ||
|   | 89ee99f98d | ||
|   | 21452d091f | ||
|   | d21f55b476 | ||
|   | d01453ea1d | ||
|   | 09e3327e94 | ||
|   | 00deeb9fdd | ||
|   | 0aed1ba04f | ||
|   | 526947e3a1 | ||
|   | b52308df80 | ||
|   | bdb9e02df0 | ||
|   | 6273020127 | ||
|   | 4d1a464954 | ||
|   | 9711ab6c5f | ||
|   | 95963eb0be | ||
|   | 0596edb0a1 | ||
|   | 7ac82d4ad9 | ||
|   | 74a9ad955b | ||
|   | c23cce672c | ||
|   | d39b4b3624 | ||
|   | 2c3ebeec09 | ||
|   | 86f2ffa61f | ||
|   | 017781ff35 | ||
|   | 7d72ad0f37 | ||
|   | 152033f98a | ||
|   | c21bf5b87d | ||
|   | fa60d40171 | ||
|   | d80d5557c8 | ||
|   | 6c7ac8d7f4 | ||
|   | d72867da35 | ||
|   | 14347b73f2 | ||
|   | 11e2bf4cbb | ||
|   | b2553d35fd | ||
|   | 58f8a02557 | ||
|   | 34450144ba | ||
|   | f9e08d53af | ||
|   | 006e402d9f | ||
|   | 8132012534 | ||
|   | 3e8f02eea4 | ||
|   | 7312980453 | ||
|   | 8c20d41d89 | ||
|   | d50d53ac1f | ||
|   | b0d7ab2e81 | ||
|   | 91223b7518 | ||
|   | 616e4b6e0c | ||
|   | 5170ea81e7 | ||
|   | 53a8cd8ee4 | ||
|   | 2229e8cb7d | ||
|   | 9ce1041b0e | ||
|   | 187065098b | ||
|   | 467bdf0045 | ||
|   | 9462b5b461 | ||
|   | da7f29a9e0 | ||
|   | d06bfd91ff | ||
|   | b9bba10697 | ||
|   | 4581de92ad | ||
|   | 72c520cca7 | ||
|   | 835ee2a046 | ||
|   | d03dee8fda | ||
|   | 6e89598a51 | ||
|   | 0a2aa6f734 | ||
|   | 2e7836eec3 | ||
|   | 31bdda8bf8 | ||
|   | 2fc93fa819 | ||
|   | 3c5a939b16 | ||
|   | 0f40c4cd49 | ||
|   | 7ad184cb48 | ||
|   | 41a7d5d0a8 | ||
|   | 78c48e004c | ||
|   | 7106d61881 | ||
|   | 8ead4c4708 | ||
|   | 52ca7d3503 | ||
|   | 7d504da0a8 | ||
|   | c6dee2252b | ||
|   | 680c4a9ec4 | ||
|   | 3887d57fa4 | ||
|   | d733daed9d | ||
|   | de8651ab52 | ||
|   | 0ce641d10b | ||
|   | 316224b424 | ||
|   | cf9bbce284 | ||
|   | 6eae6c046e | ||
|   | 837a430228 | ||
|   | 71431f8fb5 | ||
|   | 0c7cd1f299 | ||
|   | 674682e919 | ||
|   | a5ca8115af | ||
|   | d4338fce42 | ||
|   | 14e8135f81 | ||
|   | e925f07505 | ||
|   | b792b51bd0 | ||
|   | fb64813b2a | ||
|   | b16e0e33ab | ||
|   | 818921e4a2 | ||
|   | 6c437459ca | ||
|   | b276901874 | ||
|   | 85b92f46f5 | ||
|   | 237b8b5ede | ||
|   | 438d008c34 | ||
|   | 53a3de1ebc | ||
|   | 2d35747e75 | ||
|   | 71feebea6d | ||
|   | c8c75e7a70 | ||
|   | 7b2263e9a5 | ||
|   | 9cd216bbba | ||
|   | e032ff4485 | ||
|   | fbe9ca5dd9 | ||
|   | 4533bb6dd7 | ||
|   | 3320c03603 | ||
|   | c3574d96d7 | ||
|   | bc12b598ce | ||
|   | a34f679c43 | 
							
								
								
									
										1
									
								
								.env
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1 @@ | ||||
| VITE_UCENTRALSEC_URL="https://ucentral.dpaas.arilia.com:16001" | ||||
| @@ -1,4 +1,10 @@ | ||||
| /src/assets | ||||
| /build | ||||
| /node_modules | ||||
| /dist | ||||
| /icons | ||||
| helm | ||||
| docker-entrypoint.d | ||||
| .dockerignore | ||||
| DockerFile | ||||
| .github | ||||
|   | ||||
							
								
								
									
										94
									
								
								.eslintrc
									
									
									
									
									
								
							
							
						
						| @@ -1,22 +1,80 @@ | ||||
| { | ||||
|   "extends": ["airbnb", "prettier"], | ||||
|   "plugins": ["prettier"], | ||||
|   "env": { | ||||
|       "browser": true, | ||||
|       "jest": true | ||||
|     "browser": true, | ||||
|     "es2021": true | ||||
|   }, | ||||
|   "parser": "@typescript-eslint/parser", | ||||
|   "parserOptions": { | ||||
|     "ecmaFeatures": { | ||||
|       "jsx": true | ||||
|     }, | ||||
|     "ecmaVersion": 12, | ||||
|     "sourceType": "module", | ||||
|     "allowImportExportEverywhere": false, | ||||
|     "codeFrame": false, | ||||
|     "project": "./tsconfig.json" | ||||
|   }, | ||||
|   "ignorePatterns": ["build/", "dist/"], | ||||
|   "extends": [ | ||||
|     "plugin:react/recommended", | ||||
|     "plugin:@typescript-eslint/eslint-recommended", | ||||
|     "plugin:@typescript-eslint/recommended", | ||||
|     "airbnb", | ||||
|     "airbnb-typescript", | ||||
|     "prettier", | ||||
|     "plugin:import/errors", | ||||
|     "plugin:import/warnings", | ||||
|     "plugin:import/typescript" | ||||
|   ], | ||||
|   "plugins": ["import", "react", "@typescript-eslint", "prettier"], | ||||
|   "rules": { | ||||
|     "max-len": ["error", {"code": 150}], | ||||
|     "prefer-promise-reject-errors": ["off"], | ||||
|     "react/jsx-filename-extension": ["off"], | ||||
|     "react/prop-types": ["warn"], | ||||
|     "no-return-assign": ["off"], | ||||
|     "react/jsx-props-no-spreading": ["off"], | ||||
|     "react/destructuring-assignment": ["off"], | ||||
|     "import/extensions": [ | ||||
|       "error", | ||||
|       "ignorePackages", | ||||
|       { | ||||
|         "js": "never", | ||||
|         "jsx": "never", | ||||
|         "ts": "never", | ||||
|         "tsx": "never" | ||||
|       } | ||||
|     ], | ||||
|     "@typescript-eslint/naming-convention": [ | ||||
|       "error", | ||||
|       { | ||||
|         "selector": "function", | ||||
|         "format": ["PascalCase", "camelCase"], | ||||
|         "leadingUnderscore": "allowSingleOrDouble" | ||||
|       } | ||||
|     ], | ||||
|     "no-restricted-syntax": ["error", "ForInStatement", "LabeledStatement", "WithStatement"], | ||||
|     "react/jsx-one-expression-per-line": "off", | ||||
|     "react/jsx-wrap-multilines": "off", | ||||
|     "react/jsx-curly-newline": "off" | ||||
|     "react/function-component-definition": [2, { "namedComponents": "arrow-function" }], | ||||
|     "import/order": [ | ||||
|       "error", | ||||
|       { | ||||
|         "alphabetize": { | ||||
|           "order": "asc", | ||||
|           "caseInsensitive": true | ||||
|         }, | ||||
|         "newlines-between": "never", | ||||
|         "groups": ["builtin", "external", "parent", "sibling", "index"], | ||||
|         "pathGroups": [ | ||||
|           { | ||||
|             "pattern": "react", | ||||
|             "group": "external", | ||||
|             "position": "before" | ||||
|           } | ||||
|         ], | ||||
|         "pathGroupsExcludedImportTypes": ["builtin"] | ||||
|       } | ||||
|     ], | ||||
|     "max-len": ["error", { "code": 150 }], | ||||
|     "@typescript-eslint/ban-ts-comment": ["off"], | ||||
|     "import/prefer-default-export": ["off"], | ||||
|     "react/prop-types": ["warn"], | ||||
|     "react/require-default-props": "off", | ||||
|     "react/jsx-props-no-spreading": ["off"], | ||||
|     "react/jsx-curly-newline": "off", | ||||
|     "no-underscore-dangle": "off" | ||||
|   }, | ||||
|   "settings": { | ||||
|     "import/resolver": { | ||||
| @@ -24,11 +82,5 @@ | ||||
|         "paths": ["src"] | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|     "parser": "babel-eslint", | ||||
|     "parserOptions": { | ||||
|       "sourceType": "module", | ||||
|       "allowImportExportEverywhere": false, | ||||
|       "codeFrame": false | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
							
								
								
									
										4
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -26,7 +26,7 @@ jobs: | ||||
|       DOCKER_REGISTRY_USERNAME: ucentral | ||||
|     steps: | ||||
|     - name: Checkout actions repo | ||||
|       uses: actions/checkout@v2 | ||||
|       uses: actions/checkout@v3 | ||||
|       with: | ||||
|         repository: Telecominfraproject/.github | ||||
|         path: github | ||||
| @@ -56,7 +56,7 @@ jobs: | ||||
|       - docker | ||||
|     steps: | ||||
|     - name: Checkout actions repo | ||||
|       uses: actions/checkout@v2 | ||||
|       uses: actions/checkout@v3 | ||||
|       with: | ||||
|         repository: Telecominfraproject/.github | ||||
|         path: github | ||||
|   | ||||
							
								
								
									
										8
									
								
								.github/workflows/cleanup.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -17,4 +17,10 @@ jobs: | ||||
|     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/owgw-ui/$PR_BRANCH_TAG" | ||||
|  | ||||
|           if [[ ! $PR_BRANCH_TAG =~ (main|master|release-*) ]]; then | ||||
|             echo "PR branch is $PR_BRANCH_TAG, deleting Docker image" | ||||
|             curl -s -uucentral:${{ secrets.DOCKER_REGISTRY_PASSWORD }} -X DELETE "https://tip.jfrog.io/artifactory/tip-wlan-cloud-ucentral/owgw-ui/$PR_BRANCH_TAG" | ||||
|           else | ||||
|             echo "PR branch is $PR_BRANCH_TAG, not deleting Docker image" | ||||
|           fi | ||||
|   | ||||
							
								
								
									
										2
									
								
								.github/workflows/enforce-jira-issue-key.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -11,7 +11,7 @@ jobs: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Checkout actions repo | ||||
|         uses: actions/checkout@v2 | ||||
|         uses: actions/checkout@v3 | ||||
|         with: | ||||
|           repository: Telecominfraproject/.github | ||||
|           path: github | ||||
|   | ||||
							
								
								
									
										2
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -17,7 +17,7 @@ jobs: | ||||
|       HELM_REPO_USERNAME: ucentral | ||||
|     steps: | ||||
|       - name: Checkout uCentral assembly chart repo | ||||
|         uses: actions/checkout@v2 | ||||
|         uses: actions/checkout@v3 | ||||
|         with: | ||||
|           path: wlan-cloud-ucentralgw-ui | ||||
|  | ||||
|   | ||||
							
								
								
									
										5
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,9 +1,8 @@ | ||||
|  | ||||
|  | ||||
| # dependencies | ||||
| /node_modules | ||||
| /.pnp | ||||
| .pnp.js | ||||
| /dev-dist | ||||
|  | ||||
| # testing | ||||
| /coverage | ||||
| @@ -19,5 +18,3 @@ | ||||
| .env.production.local | ||||
|  | ||||
| npm-debug.log* | ||||
| yarn-debug.log* | ||||
| yarn-error.log* | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| /src/assets | ||||
| build | ||||
| dist | ||||
| node_modules | ||||
| .github | ||||
|   | ||||
							
								
								
									
										14
									
								
								.prettierrc
									
									
									
									
									
								
							
							
						
						| @@ -1,7 +1,7 @@ | ||||
| {  | ||||
|     "printWidth": 100, | ||||
|     "trailingComma": "all", | ||||
|     "tabWidth": 2,  | ||||
|     "semi": true,  | ||||
|     "singleQuote": true | ||||
| } | ||||
| { | ||||
|   "printWidth": 120, | ||||
|   "trailingComma": "all", | ||||
|   "tabWidth": 2, | ||||
|   "semi": true, | ||||
|   "singleQuote": true | ||||
| } | ||||
|   | ||||
							
								
								
									
										12
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						| @@ -1,6 +1,8 @@ | ||||
| FROM node:14-alpine3.11 AS build | ||||
| FROM node:18.7.0-alpine3.15 AS build | ||||
|  | ||||
| COPY package.json package-lock.json / | ||||
| WORKDIR /app | ||||
|  | ||||
| COPY package.json package-lock.json /app/ | ||||
|  | ||||
| RUN npm install | ||||
|  | ||||
| @@ -8,8 +10,8 @@ COPY . . | ||||
|  | ||||
| RUN npm run build | ||||
|  | ||||
| FROM nginx:1.20.1-alpine AS runtime | ||||
| FROM nginx:1.22.0-alpine AS runtime | ||||
|  | ||||
| COPY --from=build /build/ /usr/share/nginx/html/ | ||||
| COPY --from=build /app/build/ /usr/share/nginx/html/ | ||||
|  | ||||
| COPY --from=build docker-entrypoint.d/40-generate-config.sh /docker-entrypoint.d/40-generate-config.sh | ||||
| COPY --from=build /app/docker-entrypoint.d/40-generate-config.sh /docker-entrypoint.d/40-generate-config.sh | ||||
|   | ||||
							
								
								
									
										27
									
								
								README.md
									
									
									
									
									
								
							
							
						
						| @@ -1,6 +1,7 @@ | ||||
| # uCentralGW UI | ||||
|  | ||||
| ## What is this? | ||||
|  | ||||
| The uCentralGW Client is a user interface that lets you monitor and manage devices connected to the [uCentral gateway](https://github.com/Telecominfraproject/wlan-cloud-ucentralgw). To use the interface, | ||||
| you either need to run it on your machine for [development](#development) or build it for [production](#production). | ||||
|  | ||||
| @@ -9,40 +10,34 @@ NOTE: This UI will be evolving as micro services are added to the uCentral progr | ||||
| ## Running the solution | ||||
|  | ||||
| ### Development | ||||
|  | ||||
| You need to run these commands in the root folder of the project and also have npm installed on your machine. | ||||
|  | ||||
| ``` | ||||
| git clone https://github.com/Telecominfraproject/wlan-cloud-ucentralgw-ui | ||||
| cd wlan-cloud-ucentralgw-ui | ||||
| npm install | ||||
| npm start | ||||
| ``` | ||||
|  | ||||
| Run these commands if you want to run the solution on your machine while also doing development on the [uCentral UI Library](https://github.com/Telecominfraproject/wlan-cloud-ucentral-ui-libs). | ||||
| ``` | ||||
| git clone https://github.com/Telecominfraproject/wlan-cloud-ucentralgw-ui | ||||
| git clone https://github.com/Telecominfraproject/wlan-cloud-ucentral-ui-libs | ||||
| cd wlan-cloud-ucentralgw-ui | ||||
| npm link ../wlan-cloud-ucentral-ui-libs // Add sudo at the start of this command if it fails because of permissions | ||||
| npm start | ||||
| npm run dev | ||||
| ``` | ||||
|  | ||||
| ### Production | ||||
|  | ||||
| You need to run this in the root folder of the project and also have npm installed on your machine. | ||||
|  | ||||
| ``` | ||||
| git clone https://github.com/Telecominfraproject/wlan-cloud-ucentralgw-ui | ||||
| cd wlan-cloud-ucentralgw-ui | ||||
| npm install | ||||
| npm run build | ||||
| ``` | ||||
|  | ||||
| Once the build is done, you can move the `build` folder on your server. | ||||
|  | ||||
| ### Configuration | ||||
| You must change the `config.json` file in `public` directory to point to your uCentral Security Service URL (uCentralSec). You may also limit the ability for users to change the default uCentralSec. If you do not allow a uCentralSec change, the uCentralSec URL will not appear on the login screen.  | ||||
|  | ||||
| Here are the current default values:  | ||||
| You can control the uCentral Security Service URL (uCentralSec) by modifying the ENV variable "VITE_UCENTRALSEC_URL". There is an example .env file located at the root of this repository. | ||||
| Here are the current default values: | ||||
|  | ||||
| ``` | ||||
| { | ||||
|   "DEFAULT_UCENTRALSEC_URL": "https://ucentral.dpaas.arilia.com:16001", | ||||
|   "ALLOW_UCENTRALSEC_CHANGE": false | ||||
| } | ||||
| VITE_UCENTRALSEC_URL="https://ucentral.dpaas.arilia.com:16001" | ||||
| ``` | ||||
|   | ||||
| @@ -1,25 +0,0 @@ | ||||
| { | ||||
|   "presets": [ | ||||
|     [ | ||||
|       "@babel/preset-env", | ||||
|       { | ||||
|         "modules": false | ||||
|       } | ||||
|     ], | ||||
|     "@babel/preset-react" | ||||
|   ], | ||||
|   "env": { | ||||
|     "production": { | ||||
|       "plugins": [ | ||||
|         "@babel/plugin-transform-react-inline-elements", | ||||
|         "@babel/plugin-transform-react-constant-elements", | ||||
|         [ | ||||
|           "transform-react-remove-prop-types", | ||||
|           { | ||||
|             "removeImport": true | ||||
|           } | ||||
|         ] | ||||
|       ] | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @@ -1,12 +0,0 @@ | ||||
| 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'), | ||||
| }; | ||||
| @@ -1,79 +0,0 @@ | ||||
| /* 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 webpack = require('webpack'); | ||||
| 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 webpack.DefinePlugin({ | ||||
|       'process.env.VERSION': JSON.stringify(process.env.npm_package_version), | ||||
|     }), | ||||
|     new MiniCssExtractPlugin({ | ||||
|       filename: 'styles/[name].[contenthash].css', | ||||
|       chunkFilename: '[id].[contenthash].css', | ||||
|     }), | ||||
|     new CopyWebpackPlugin({ | ||||
|       patterns: [ | ||||
|         { | ||||
|           from: paths.src + '/assets', | ||||
|           to: 'assets', | ||||
|           globOptions: { | ||||
|             ignore: ['*.DS_Store'], | ||||
|           }, | ||||
|         }, | ||||
|         { | ||||
|           from: paths.public + '/locales', | ||||
|           to: 'locales', | ||||
|           globOptions: { | ||||
|             ignore: ['*.DS_Store'], | ||||
|           }, | ||||
|         }, | ||||
|         { | ||||
|           from: paths.public + '/config.json', | ||||
|           to: 'config.json', | ||||
|         }, | ||||
|       ], | ||||
|     }), | ||||
|     new HtmlWebpackPlugin({ | ||||
|       title: 'uCentralGW', | ||||
|       favicon: paths.public + '/favicon.ico', | ||||
|       template: paths.public + '/index.html', | ||||
|       filename: 'index.html', | ||||
|     }), | ||||
|     new CleanWebpackPlugin(), | ||||
|   ], | ||||
|  | ||||
|   module: { | ||||
|     rules: [ | ||||
|       { | ||||
|         test: /\.(js|jsx)$/, | ||||
|         exclude: /node_modules/, | ||||
|         use: ['babel-loader'], | ||||
|       }, | ||||
|       { | ||||
|         test: /\.(css|scss)$/, | ||||
|         use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'], | ||||
|       }, | ||||
|       { | ||||
|         test: /\.svg$/, | ||||
|         use: ['@svgr/webpack'], | ||||
|       }, | ||||
|       { test: /\.(?:ico|gif|png|jpg|jpeg)$/i, type: 'asset/resource' }, | ||||
|     ], | ||||
|   }, | ||||
| }; | ||||
| @@ -1,54 +0,0 @@ | ||||
| /* eslint-disable import/no-extraneous-dependencies */ | ||||
| /* eslint-disable prefer-template */ | ||||
| const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin'); | ||||
| const { merge } = require('webpack-merge'); | ||||
| const path = require('path'); | ||||
| const paths = require('./paths'); | ||||
| const common = require('./webpack.common'); | ||||
|  | ||||
| module.exports = merge(common, { | ||||
|   mode: 'development', | ||||
|  | ||||
|   target: 'web', | ||||
|  | ||||
|   devtool: 'inline-source-map', | ||||
|  | ||||
|   devServer: { | ||||
|     historyApiFallback: true, | ||||
|     contentBase: paths.build, | ||||
|     open: true, | ||||
|     compress: false, | ||||
|     hot: true, | ||||
|     port: 3000, | ||||
|   }, | ||||
|   module: { | ||||
|     rules: [ | ||||
|       { | ||||
|         test: /\.[js]sx?$/, | ||||
|         exclude: /node_modules/, | ||||
|         use: [ | ||||
|           { | ||||
|             loader: require.resolve('babel-loader'), | ||||
|             options: { | ||||
|               plugins: [require.resolve('react-refresh/babel')], | ||||
|             }, | ||||
|           }, | ||||
|         ], | ||||
|       }, | ||||
|     ], | ||||
|   }, | ||||
|   resolve: { | ||||
|     modules: [ | ||||
|       'node_modules', | ||||
|       'src', | ||||
|       path.resolve(__dirname, '../', 'node_modules', 'ucentral-libs', 'src'), | ||||
|     ], | ||||
|     alias: { | ||||
|       react: path.resolve(__dirname, '../', 'node_modules', 'react'), | ||||
|       'react-router-dom': path.resolve('./node_modules/react-router-dom'), | ||||
|       'ucentral-libs': path.resolve(__dirname, '../', 'node_modules', 'ucentral-libs', 'src'), | ||||
|       graphlib: path.resolve(__dirname, '../', 'node_modules', 'graphlib'), | ||||
|     }, | ||||
|   }, | ||||
|   plugins: [new ReactRefreshWebpackPlugin()], | ||||
| }); | ||||
| @@ -1,86 +0,0 @@ | ||||
| /* eslint-disable import/no-extraneous-dependencies */ | ||||
| /* eslint-disable prefer-template */ | ||||
| const { merge } = require('webpack-merge'); | ||||
| const MiniCssExtractPlugin = require('mini-css-extract-plugin'); | ||||
| const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); | ||||
| const TerserPlugin = require('terser-webpack-plugin'); | ||||
| const CompressionPlugin = require('compression-webpack-plugin'); | ||||
| const path = require('path'); | ||||
| const paths = require('./paths'); | ||||
| const common = require('./webpack.common'); | ||||
|  | ||||
| module.exports = merge(common, { | ||||
|   mode: 'production', | ||||
|   devtool: false, | ||||
|   output: { | ||||
|     path: paths.build, | ||||
|     publicPath: '/', | ||||
|     filename: 'js/[name].[contenthash].bundle.js', | ||||
|   }, | ||||
|   plugins: [ | ||||
|     // new BundleAnalyzerPlugin(), | ||||
|     new MiniCssExtractPlugin({ | ||||
|       filename: 'styles/[name].[contenthash].css', | ||||
|       chunkFilename: '[contenthash].css', | ||||
|     }), | ||||
|     new CompressionPlugin({ | ||||
|       filename: '[path]/[name].gz[query]', | ||||
|       algorithm: 'gzip', | ||||
|       test: /\.js$|\.css$|\.html$|\.eot?.+$|\.ttf?.+$|\.woff?.+$|\.svg?.+$/, | ||||
|       threshold: 10240, | ||||
|       minRatio: 0.8, | ||||
|     }), | ||||
|   ], | ||||
|   module: { | ||||
|     rules: [], | ||||
|   }, | ||||
|   optimization: { | ||||
|     minimize: true, | ||||
|     minimizer: [ | ||||
|       '...', | ||||
|       new TerserPlugin({ | ||||
|         terserOptions: { | ||||
|           warnings: false, | ||||
|           compress: { | ||||
|             comparisons: false, | ||||
|           }, | ||||
|           parse: {}, | ||||
|           mangle: true, | ||||
|           output: { | ||||
|             ascii_only: true, | ||||
|           }, | ||||
|         }, | ||||
|         parallel: true, | ||||
|       }), | ||||
|       new CssMinimizerPlugin(), | ||||
|     ], | ||||
|     nodeEnv: 'production', | ||||
|     sideEffects: true, | ||||
|     runtimeChunk: 'single', | ||||
|     splitChunks: { | ||||
|       chunks: 'all', | ||||
|       maxInitialRequests: 10, | ||||
|       minSize: 0, | ||||
|       cacheGroups: { | ||||
|         vendor: { | ||||
|           test: /[\\/]node_modules[\\/]/, | ||||
|           name(module) { | ||||
|             const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1]; | ||||
|             return `npm.${packageName.replace('@', '')}`; | ||||
|           }, | ||||
|         }, | ||||
|       }, | ||||
|     }, | ||||
|   }, | ||||
|   resolve: { | ||||
|     modules: [], | ||||
|     alias: { | ||||
|       graphlib: path.resolve(__dirname, '../', 'node_modules', 'graphlib'), | ||||
|     }, | ||||
|   }, | ||||
|   performance: { | ||||
|     hints: false, | ||||
|     maxEntrypointSize: 512000, | ||||
|     maxAssetSize: 512000, | ||||
|   }, | ||||
| }); | ||||
| @@ -1,6 +1,32 @@ | ||||
| #!/bin/ash | ||||
| # Check if variables are set | ||||
| export DEFAULT_OWSEC_URL="${DEFAULT_OWSEC_URL:-https://ucentral.dpaas.arilia.com:16001}" | ||||
| export ALLOW_OWSEC_CHANGE="${ALLOW_OWSEC_CHANGE:-false}" | ||||
|  | ||||
| echo '{"DEFAULT_UCENTRALSEC_URL": "'$DEFAULT_UCENTRALSEC_URL'","ALLOW_UCENTRALSEC_CHANGE": '$ALLOW_UCENTRALSEC_CHANGE'}' > /usr/share/nginx/html/config.json | ||||
| ENV_CONFIG_PATH=/usr/share/nginx/html/env-config.js | ||||
|  | ||||
| # Recreate config file | ||||
| rm -rf $ENV_CONFIG_PATH | ||||
| touch $ENV_CONFIG_PATH | ||||
|  | ||||
| # Add assignment | ||||
| echo "window._env_ = {" >> $ENV_CONFIG_PATH | ||||
|  | ||||
| # Read each line in .env file | ||||
| # Each line represents key=value pairs | ||||
| env | grep REACT_ | while read -r line || [[ -n "$line" ]]; | ||||
| do | ||||
|   echo $line | ||||
|   # Split env variables by character `=` | ||||
|   if printf '%s\n' "$line" | grep -q -e '='; then | ||||
|     varname=$(printf '%s\n' "$line" | sed -e 's/=.*//') | ||||
|     varvalue=$(printf '%s\n' "$line" | sed -e 's/^[^=]*=//') | ||||
|   fi | ||||
|  | ||||
|   # Read value of current variable if exists as Environment variable | ||||
|   value=$(printf '%s\n' "${!varname}") | ||||
|   # Otherwise use value from .env file | ||||
|   [[ -z $value ]] && value=${varvalue} | ||||
|  | ||||
|   # Append configuration property to JS file | ||||
|   echo "  $varname: \"$value\"," >> $ENV_CONFIG_PATH | ||||
| done | ||||
|  | ||||
| echo "}" >> $ENV_CONFIG_PATH | ||||
|   | ||||
| @@ -8,7 +8,7 @@ fullnameOverride: "" | ||||
| images: | ||||
|   owgwui: | ||||
|     repository: tip-tip-wlan-cloud-ucentral.jfrog.io/owgw-ui | ||||
|     tag: v2.6.0-RC1 | ||||
|     tag: v2.8.0-RC1 | ||||
|     pullPolicy: Always | ||||
|  | ||||
| services: | ||||
| @@ -75,5 +75,4 @@ podAnnotations: {} | ||||
|  | ||||
| # Application | ||||
| public_env_variables: | ||||
|   DEFAULT_UCENTRALSEC_URL: https://ucentral.dpaas.arilia.com:16001 | ||||
|   ALLOW_UCENTRALSEC_CHANGE: false | ||||
|   REACT_APP_UCENTRALSEC_URL: https://ucentral.dpaas.arilia.com:16001 | ||||
|   | ||||
							
								
								
									
										21
									
								
								index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,21 @@ | ||||
| <!DOCTYPE html> | ||||
| <html lang="en"> | ||||
|   <head> | ||||
|     <meta charset="utf-8" /> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1" /> | ||||
|     <title>Controller</title> | ||||
|     <meta name="description" content="OpenWiFi Controller App" /> | ||||
|     <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" /> | ||||
|     <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" /> | ||||
|     <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" /> | ||||
|     <link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5" /> | ||||
|     <meta name="msapplication-TileColor" content="#da532c" /> | ||||
|     <script src="/env-config.js"></script> | ||||
|     <meta name="theme-color" content="#000000" /> | ||||
|   </head> | ||||
|   <body> | ||||
|     <noscript>You need to enable JavaScript to run this app.</noscript> | ||||
|     <div id="root"></div> | ||||
|     <script type="module" src="./src/index.tsx"></script> | ||||
|   </body> | ||||
| </html> | ||||
| @@ -1,9 +0,0 @@ | ||||
| { | ||||
|   "compilerOptions": { | ||||
|     "baseUrl": "src", | ||||
|     "paths": { | ||||
|       "*": ["*"] | ||||
|     } | ||||
|   }, | ||||
|   "include": ["src"] | ||||
| } | ||||
							
								
								
									
										28365
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
							
								
								
									
										166
									
								
								package.json
									
									
									
									
									
								
							
							
						
						| @@ -1,100 +1,92 @@ | ||||
| { | ||||
|   "name": "ucentral-client", | ||||
|   "version": "2.6.25", | ||||
|   "version": "2.8.0(44)", | ||||
|   "description": "", | ||||
|   "private": true, | ||||
|   "main": "index.tsx", | ||||
|   "scripts": { | ||||
|     "dev": "vite", | ||||
|     "build": "vite build", | ||||
|     "format": "prettier --write \"src/**/*.js\"", | ||||
|     "analyze": "source-map-explorer 'build/static/js/*.js'", | ||||
|     "lint": "TIMING=1 eslint \"src/**/*.{ts,tsx,js,jsx}\" --fix", | ||||
|     "clean": "rm -rf node_modules && rm -rf build" | ||||
|   }, | ||||
|   "author": "", | ||||
|   "license": "ISC", | ||||
|   "dependencies": { | ||||
|     "@coreui/coreui": "^3.4.0", | ||||
|     "@coreui/icons": "^2.0.1", | ||||
|     "@coreui/icons-react": "^1.1.0", | ||||
|     "@coreui/react": "^3.4.6", | ||||
|     "@coreui/react-chartjs": "^1.1.0", | ||||
|     "apexcharts": "^3.27.1", | ||||
|     "axios": "^0.21.1", | ||||
|     "axios-retry": "^3.1.9", | ||||
|     "@chakra-ui/icons": "^2.0.11", | ||||
|     "@chakra-ui/react": "^2.3.6", | ||||
|     "@chakra-ui/theme-tools": "^2.0.12", | ||||
|     "@chakra-ui/utils": "^2.0.11", | ||||
|     "@emotion/react": "^11.10.4", | ||||
|     "@emotion/styled": "^11.10.4", | ||||
|     "@fontsource/inter": "^4.5.14", | ||||
|     "@react-spring/web": "^9.5.5", | ||||
|     "axios": "^1.1.3", | ||||
|     "buffer": "^6.0.3", | ||||
|     "chakra-react-select": "^4.3.0", | ||||
|     "dagre": "^0.8.5", | ||||
|     "i18next": "^20.3.1", | ||||
|     "i18next-browser-languagedetector": "^6.1.2", | ||||
|     "i18next-http-backend": "^1.2.6", | ||||
|     "prop-types": "^15.7.2", | ||||
|     "react": "^17.0.2", | ||||
|     "react-apexcharts": "^1.3.9", | ||||
|     "formik": "^2.2.9", | ||||
|     "framer-motion": "^7.6.1", | ||||
|     "i18next": "^22.0.0", | ||||
|     "i18next-browser-languagedetector": "^6.1.8", | ||||
|     "i18next-http-backend": "^1.4.4", | ||||
|     "libphonenumber-js": "^1.10.14", | ||||
|     "phosphor-react": "^1.4.1", | ||||
|     "prop-types": "^15.8.1", | ||||
|     "react": "^18.2.0", | ||||
|     "react-app-polyfill": "^3.0.0", | ||||
|     "react-chartjs-2": "^4.3.1", | ||||
|     "chart.js": "^3.9.1", | ||||
|     "react-country-flag": "^3.0.2", | ||||
|     "react-csv": "^2.2.2", | ||||
|     "react-dom": "^17.0.2", | ||||
|     "react-flow-renderer": "^9.6.6", | ||||
|     "react-i18next": "^11.11.0", | ||||
|     "react-paginate": "^7.1.3", | ||||
|     "react-router-dom": "^5.2.0", | ||||
|     "react-select": "^4.3.1", | ||||
|     "react-tooltip": "^4.2.21", | ||||
|     "react-widgets": "^5.1.1", | ||||
|     "sass": "^1.35.1", | ||||
|     "ucentral-libs": "^1.0.60", | ||||
|     "uuid": "^8.3.2" | ||||
|   }, | ||||
|   "main": "index.js", | ||||
|   "scripts": { | ||||
|     "start": "webpack serve --config config/webpack.dev.js", | ||||
|     "build": "webpack --config config/webpack.prod.js", | ||||
|     "format": "prettier --write 'src/**/*.js'", | ||||
|     "eslint-fix": "eslint --fix 'src/**/*.js'" | ||||
|   }, | ||||
|   "eslintConfig": { | ||||
|     "extends": "react-app" | ||||
|   }, | ||||
|   "husky": { | ||||
|     "hooks": { | ||||
|       "pre-commit": "lint-staged" | ||||
|     } | ||||
|   }, | ||||
|   "lint-staged": { | ||||
|     "*.{js,jsx}": [ | ||||
|       "eslint", | ||||
|       "prettier --write" | ||||
|     ] | ||||
|     "react-datepicker": "^4.8.0", | ||||
|     "react-dom": "^18.2.0", | ||||
|     "@textea/json-viewer": "^2.10.0", | ||||
|     "react-fast-compare": "^3.2.0", | ||||
|     "react-i18next": "^11.18.6", | ||||
|     "react-masonry-css": "^1.0.16", | ||||
|     "@tanstack/react-query": "^4.12.0", | ||||
|     "react-router-dom": "^6.4.2", | ||||
|     "react-table": "^7.8.0", | ||||
|     "react-virtualized-auto-sizer": "^1.0.7", | ||||
|     "react-window": "^1.8.8", | ||||
|     "source-map-explorer": "^2.5.3", | ||||
|     "vite": "^3.1.8", | ||||
|     "typescript": "^4.8.4", | ||||
|     "uuid": "^9.0.0", | ||||
|     "yup": "^0.32.11", | ||||
|     "zustand": "^4.1.2" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@babel/core": "^7.14.6", | ||||
|     "@babel/plugin-proposal-class-properties": "^7.14.5", | ||||
|     "@babel/plugin-transform-runtime": "^7.14.5", | ||||
|     "@babel/polyfill": "^7.12.1", | ||||
|     "@babel/preset-env": "^7.14.7", | ||||
|     "@babel/preset-react": "^7.14.5", | ||||
|     "@pmmmwh/react-refresh-webpack-plugin": "^0.4.3", | ||||
|     "@svgr/webpack": "^5.5.0", | ||||
|     "autoprefixer": "^10.2.6", | ||||
|     "babel-eslint": "^10.1.0", | ||||
|     "babel-loader": "^8.2.2", | ||||
|     "clean-webpack-plugin": "^3.0.0", | ||||
|     "compression-webpack-plugin": "^8.0.1", | ||||
|     "copy-webpack-plugin": "^7.0.0", | ||||
|     "css-loader": "^5.2.6", | ||||
|     "css-minimizer-webpack-plugin": "^2.0.0", | ||||
|     "dotenv-webpack": "^6.0.4", | ||||
|     "eslint": "^7.29.0", | ||||
|     "eslint-config-airbnb": "^18.2.1", | ||||
|     "eslint-config-prettier": "^7.2.0", | ||||
|     "@types/node": "^18.11.2", | ||||
|     "@types/react": "^18.0.21", | ||||
|     "@types/react-csv": "^1.1.3", | ||||
|     "@types/react-dom": "^18.0.6", | ||||
|     "@types/react-table": "^7.7.12", | ||||
|     "@types/react-datepicker": "4.8.0", | ||||
|     "@types/uuid": "^8.3.4", | ||||
|     "@types/react-virtualized-auto-sizer": "^1.0.1", | ||||
|     "@types/react-window": "^1.8.5", | ||||
|     "eslint": "8.25.0", | ||||
|     "vite-tsconfig-paths": "^3.5.1", | ||||
|     "lint-staged": "^13.0.3", | ||||
|     "@vitejs/plugin-react": "^2.1.0", | ||||
|     "vite-plugin-pwa": "^0.13.1", | ||||
|     "prettier": "^2.7.1", | ||||
|     "eslint-config-airbnb": "^19.0.4", | ||||
|     "eslint-config-airbnb-typescript": "^17.0.0", | ||||
|     "eslint-config-airbnb-typescript-prettier": "^5.0.0", | ||||
|     "eslint-config-prettier": "^8.5.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", | ||||
|     "path": "^0.12.7", | ||||
|     "prettier": "^2.3.2", | ||||
|     "react-refresh": "^0.9.0", | ||||
|     "sass-loader": "^11.1.1", | ||||
|     "style-loader": "^2.0.0", | ||||
|     "terser-webpack-plugin": "^5.1.4", | ||||
|     "webpack": "^5.40.0", | ||||
|     "webpack-bundle-analyzer": "^4.4.2", | ||||
|     "webpack-cli": "^4.9.1", | ||||
|     "webpack-dev-server": "^3.11.2", | ||||
|     "webpack-merge": "^5.8.0" | ||||
|     "eslint-plugin-import": "^2.26.0", | ||||
|     "eslint-plugin-jsx-a11y": "^6.6.1", | ||||
|     "eslint-plugin-no-inline-styles": "^1.0.5", | ||||
|     "eslint-plugin-prettier": "^4.2.1", | ||||
|     "eslint-plugin-react": "^7.31.10", | ||||
|     "eslint-plugin-react-hooks": "^4.6.0" | ||||
|   }, | ||||
|   "browserslist": { | ||||
|     "production": [ | ||||
|   | ||||
							
								
								
									
										
											BIN
										
									
								
								public/android-chrome-192x192.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 12 KiB | 
							
								
								
									
										
											BIN
										
									
								
								public/android-chrome-384x384.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 29 KiB | 
							
								
								
									
										
											BIN
										
									
								
								public/android-chrome-512x512.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 36 KiB | 
							
								
								
									
										
											BIN
										
									
								
								public/apple-touch-icon.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 11 KiB | 
							
								
								
									
										9
									
								
								public/browserconfig.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,9 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <browserconfig> | ||||
|     <msapplication> | ||||
|         <tile> | ||||
|             <square150x150logo src="/mstile-150x150.png"/> | ||||
|             <TileColor>#414141</TileColor> | ||||
|         </tile> | ||||
|     </msapplication> | ||||
| </browserconfig> | ||||
| @@ -1,4 +0,0 @@ | ||||
| { | ||||
|   "DEFAULT_UCENTRALSEC_URL": "https://ucentral.dpaas.arilia.com:16001", | ||||
|   "ALLOW_UCENTRALSEC_CHANGE": false | ||||
| } | ||||
| Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 104 KiB | 
| Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 80 KiB | 
| Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 80 KiB | 
| Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 75 KiB | 
| Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 75 KiB | 
| Before Width: | Height: | Size: 218 KiB After Width: | Height: | Size: 218 KiB | 
| Before Width: | Height: | Size: 158 KiB After Width: | Height: | Size: 158 KiB | 
| Before Width: | Height: | Size: 140 KiB After Width: | Height: | Size: 140 KiB | 
| Before Width: | Height: | Size: 121 KiB After Width: | Height: | Size: 121 KiB | 
| Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB | 
| Before Width: | Height: | Size: 192 KiB After Width: | Height: | Size: 192 KiB | 
| Before Width: | Height: | Size: 197 KiB After Width: | Height: | Size: 197 KiB | 
| Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB | 
| Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 59 KiB | 
| Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 59 KiB | 
| Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 51 KiB | 
| Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 72 KiB | 
| Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 72 KiB | 
| Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB | 
| Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 98 KiB | 
| Before Width: | Height: | Size: 89 KiB After Width: | Height: | Size: 89 KiB | 
| Before Width: | Height: | Size: 89 KiB After Width: | Height: | Size: 89 KiB | 
| Before Width: | Height: | Size: 204 KiB After Width: | Height: | Size: 204 KiB | 
| Before Width: | Height: | Size: 159 KiB After Width: | Height: | Size: 159 KiB | 
| Before Width: | Height: | Size: 159 KiB After Width: | Height: | Size: 159 KiB | 
| Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 103 KiB | 
| Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 103 KiB | 
| Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 103 KiB | 
| Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 59 KiB | 
| Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 80 KiB | 
| Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 80 KiB | 
							
								
								
									
										
											BIN
										
									
								
								public/devices/yuncore_ax840.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 32 KiB | 
							
								
								
									
										
											BIN
										
									
								
								public/devices/yuncore_fap640.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 38 KiB | 
							
								
								
									
										
											BIN
										
									
								
								public/devices/yuncore_fap650.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 47 KiB | 
							
								
								
									
										
											BIN
										
									
								
								public/favicon-16x16.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1021 B | 
							
								
								
									
										
											BIN
										
									
								
								public/favicon-32x32.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.9 KiB | 
| Before Width: | Height: | Size: 202 KiB After Width: | Height: | Size: 15 KiB | 
| @@ -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,14 +0,0 @@ | ||||
| <!DOCTYPE html> | ||||
| <html lang="en"> | ||||
|   <head> | ||||
|     <meta charset="utf-8" /> | ||||
|     <link rel="icon" href="favicon.ico" /> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1" /> | ||||
|     <meta name="theme-color" content="#000000" /> | ||||
|     <title>Gateway</title> | ||||
|   </head> | ||||
|   <body> | ||||
|     <noscript>You need to enable JavaScript to run this app.</noscript> | ||||
|     <div id="root"></div> | ||||
|   </body> | ||||
| </html> | ||||
							
								
								
									
										
											BIN
										
									
								
								public/mstile-150x150.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 9.9 KiB | 
							
								
								
									
										38
									
								
								public/safari-pinned-tab.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,38 @@ | ||||
| <?xml version="1.0" standalone="no"?> | ||||
| <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" | ||||
|  "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> | ||||
| <svg version="1.0" xmlns="http://www.w3.org/2000/svg" | ||||
|  width="744.000000pt" height="744.000000pt" viewBox="0 0 744.000000 744.000000" | ||||
|  preserveAspectRatio="xMidYMid meet"> | ||||
| <metadata> | ||||
| Created by potrace 1.14, written by Peter Selinger 2001-2017 | ||||
| </metadata> | ||||
| <g transform="translate(0.000000,744.000000) scale(0.100000,-0.100000)" | ||||
| fill="#000000" stroke="none"> | ||||
| <path d="M1827 7404 c-2 -2 -50 -6 -108 -8 -57 -3 -120 -10 -139 -15 -19 -6 | ||||
| -56 -16 -82 -22 -27 -7 -48 -16 -48 -20 0 -4 -14 -10 -30 -14 -17 -4 -36 -12 | ||||
| -43 -18 -7 -7 -28 -21 -46 -31 -115 -64 -247 -224 -304 -366 -59 -150 -55 65 | ||||
| -56 -3180 0 -3270 -5 -3017 60 -3178 92 -232 303 -410 546 -463 32 -7 65 -14 | ||||
| 73 -16 22 -5 4127 -3 4180 2 77 7 279 77 300 104 3 3 25 19 50 35 56 37 135 | ||||
| 116 180 181 19 28 38 52 43 53 4 2 5 7 2 12 -3 4 2 13 10 20 8 7 15 21 15 31 | ||||
| 0 10 4 20 9 23 8 6 39 91 47 131 2 11 7 31 11 45 4 14 8 1364 9 3000 2 3142 3 | ||||
| 3032 -42 3165 -43 130 -131 255 -245 350 -33 28 -123 90 -127 87 -1 -1 -16 5 | ||||
| -34 14 -35 18 -128 50 -173 59 -30 6 -187 18 -215 16 -57 -3 -122 -1 -129 4 | ||||
| -5 3 -11 1 -13 -4 -1 -5 -23 -6 -48 -2 -25 4 -58 4 -75 1 -16 -3 -49 -3 -72 1 | ||||
| -23 3 -44 2 -45 -2 -2 -4 -23 -4 -48 0 -25 4 -58 4 -75 1 -16 -3 -49 -3 -72 1 | ||||
| -23 3 -44 2 -45 -2 -2 -4 -23 -4 -48 0 -25 4 -58 4 -75 1 -16 -3 -49 -3 -72 1 | ||||
| -23 3 -44 2 -45 -2 -2 -4 -23 -4 -48 0 -25 4 -58 4 -75 1 -16 -3 -49 -3 -72 1 | ||||
| -23 3 -44 2 -45 -2 -2 -4 -23 -4 -48 0 -25 4 -58 4 -75 1 -16 -3 -49 -3 -72 1 | ||||
| -23 3 -44 2 -45 -2 -2 -4 -23 -4 -48 0 -25 4 -58 4 -75 1 -16 -3 -49 -3 -72 1 | ||||
| -23 3 -44 2 -45 -2 -2 -4 -23 -4 -48 0 -25 4 -58 4 -75 1 -16 -3 -49 -3 -72 1 | ||||
| -23 3 -44 2 -45 -2 -2 -4 -23 -4 -48 0 -25 4 -58 4 -75 1 -16 -3 -49 -3 -72 1 | ||||
| -23 3 -44 2 -45 -2 -2 -4 -23 -4 -48 0 -25 4 -58 4 -75 1 -16 -3 -49 -3 -72 1 | ||||
| -23 3 -44 2 -45 -2 -2 -4 -23 -4 -48 0 -25 4 -58 4 -75 1 -16 -3 -49 -3 -72 1 | ||||
| -23 3 -44 2 -45 -2 -2 -4 -23 -4 -48 0 -25 4 -58 4 -75 1 -16 -3 -49 -3 -72 1 | ||||
| -23 3 -44 2 -45 -2 -2 -4 -23 -4 -48 0 -25 4 -58 4 -75 1 -16 -3 -49 -3 -72 1 | ||||
| -23 3 -44 2 -45 -2 -2 -4 -23 -4 -48 0 -25 4 -58 4 -75 1 -16 -3 -49 -3 -72 1 | ||||
| -23 3 -44 2 -45 -2 -2 -4 -23 -4 -48 0 -25 4 -58 4 -75 1 -16 -3 -49 -3 -72 1 | ||||
| -23 3 -44 2 -45 -2 -2 -4 -23 -4 -48 0 -25 4 -58 4 -75 1 -16 -3 -49 -3 -72 1 | ||||
| -23 3 -44 2 -45 -2 -2 -4 -24 -4 -50 0 -27 5 -49 6 -51 5z"/> | ||||
| </g> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 2.3 KiB | 
							
								
								
									
										39
									
								
								src/App.js
									
									
									
									
									
								
							
							
						
						| @@ -1,39 +0,0 @@ | ||||
| import React from 'react'; | ||||
| import { HashRouter, Switch } from 'react-router-dom'; | ||||
| import 'scss/style.scss'; | ||||
| import Router from 'router'; | ||||
| import { AuthProvider } from 'ucentral-libs'; | ||||
| import { checkIfJson } from 'utils/helper'; | ||||
| import axiosInstance from 'utils/axiosInstance'; | ||||
| import { getItem } from 'utils/localStorageHelper'; | ||||
|  | ||||
| const loading = ( | ||||
|   <div className="pt-3 text-center"> | ||||
|     <div className="sk-spinner sk-spinner-pulse" /> | ||||
|   </div> | ||||
| ); | ||||
|  | ||||
| const App = () => { | ||||
|   const storageToken = getItem('access_token'); | ||||
|   const apiEndpoints = checkIfJson(getItem('gateway_endpoints')) | ||||
|     ? JSON.parse(getItem('gateway_endpoints')) | ||||
|     : {}; | ||||
|  | ||||
|   return ( | ||||
|     <AuthProvider | ||||
|       axiosInstance={axiosInstance} | ||||
|       token={storageToken ?? ''} | ||||
|       apiEndpoints={apiEndpoints} | ||||
|     > | ||||
|       <HashRouter> | ||||
|         <React.Suspense fallback={loading}> | ||||
|           <Switch> | ||||
|             <Router /> | ||||
|           </Switch> | ||||
|         </React.Suspense> | ||||
|       </HashRouter> | ||||
|     </AuthProvider> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
| export default App; | ||||
							
								
								
									
										45
									
								
								src/App.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,45 @@ | ||||
| import React, { Suspense } from 'react'; | ||||
| import { Spinner } from '@chakra-ui/react'; | ||||
| import { QueryClientProvider, QueryClient } from '@tanstack/react-query'; | ||||
| import { HashRouter } from 'react-router-dom'; | ||||
| import { AuthProvider } from 'contexts/AuthProvider'; | ||||
| import { ControllerSocketProvider } from 'contexts/ControllerSocketProvider'; | ||||
| import { FirmwareSocketProvider } from 'contexts/FirmwareSocketProvider'; | ||||
| import { ProvisioningSocketProvider } from 'contexts/ProvisioningSocketProvider'; | ||||
| import { SecuritySocketProvider } from 'contexts/SecuritySocketProvider'; | ||||
| import Router from 'router'; | ||||
|  | ||||
| const queryClient = new QueryClient({ | ||||
|   defaultOptions: { | ||||
|     queries: { | ||||
|       retry: 0, | ||||
|       refetchOnWindowFocus: false, | ||||
|     }, | ||||
|   }, | ||||
| }); | ||||
|  | ||||
| const App = () => { | ||||
|   const storageToken = localStorage.getItem('access_token') ?? sessionStorage.getItem('access_token'); | ||||
|  | ||||
|   return ( | ||||
|     <QueryClientProvider client={queryClient}> | ||||
|       <HashRouter> | ||||
|         <Suspense fallback={<Spinner />}> | ||||
|           <AuthProvider token={storageToken !== null ? storageToken : undefined}> | ||||
|             <SecuritySocketProvider> | ||||
|               <FirmwareSocketProvider> | ||||
|                 <ProvisioningSocketProvider> | ||||
|                   <ControllerSocketProvider> | ||||
|                     <Router /> | ||||
|                   </ControllerSocketProvider> | ||||
|                 </ProvisioningSocketProvider> | ||||
|               </FirmwareSocketProvider> | ||||
|             </SecuritySocketProvider> | ||||
|           </AuthProvider> | ||||
|         </Suspense> | ||||
|       </HashRouter> | ||||
|     </QueryClientProvider> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
| export default App; | ||||
| Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 7.3 KiB | 
| Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 7.3 KiB | 
| Before Width: | Height: | Size: 24 KiB | 
| @@ -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,203 +0,0 @@ | ||||
| import { | ||||
|   cifUs, | ||||
|   cifBr, | ||||
|   cifIn, | ||||
|   cifFr, | ||||
|   cifEs, | ||||
|   cifPl, | ||||
|   cilAlignCenter, | ||||
|   cilAlignLeft, | ||||
|   cilAlignRight, | ||||
|   cilApplicationsSettings, | ||||
|   cilArrowRight, | ||||
|   cilArrowTop, | ||||
|   cilAsterisk, | ||||
|   cilBan, | ||||
|   cilBarcode, | ||||
|   cilBasket, | ||||
|   cilBell, | ||||
|   cilBold, | ||||
|   cilBookmark, | ||||
|   cilCalculator, | ||||
|   cilCalendar, | ||||
|   cilCloudDownload, | ||||
|   cilChartPie, | ||||
|   cilCheck, | ||||
|   cilChevronBottom, | ||||
|   cilChevronLeft, | ||||
|   cilChevronRight, | ||||
|   cilChevronTop, | ||||
|   cilCircle, | ||||
|   cilCheckCircle, | ||||
|   cilCode, | ||||
|   cilCommentSquare, | ||||
|   cilCreditCard, | ||||
|   cilCursor, | ||||
|   cilCursorMove, | ||||
|   cilDrop, | ||||
|   cilDollar, | ||||
|   cilEnvelopeClosed, | ||||
|   cilEnvelopeLetter, | ||||
|   cilEnvelopeOpen, | ||||
|   cilEuro, | ||||
|   cilGlobeAlt, | ||||
|   cilGrid, | ||||
|   cilFile, | ||||
|   cilFullscreen, | ||||
|   cilFullscreenExit, | ||||
|   cilGraph, | ||||
|   cilHome, | ||||
|   cilInbox, | ||||
|   cilIndentDecrease, | ||||
|   cilIndentIncrease, | ||||
|   cilInputPower, | ||||
|   cilItalic, | ||||
|   cilJustifyCenter, | ||||
|   cilJustifyLeft, | ||||
|   cilLaptop, | ||||
|   cilLayers, | ||||
|   cilLightbulb, | ||||
|   cilList, | ||||
|   cilListNumbered, | ||||
|   cilListRich, | ||||
|   cilLocationPin, | ||||
|   cilLockLocked, | ||||
|   cilMagnifyingGlass, | ||||
|   cilMap, | ||||
|   cilMoon, | ||||
|   cilNotes, | ||||
|   cilOptions, | ||||
|   cilPaperclip, | ||||
|   cilPaperPlane, | ||||
|   cilPencil, | ||||
|   cilPeople, | ||||
|   cilPhone, | ||||
|   cilPrint, | ||||
|   cilPuzzle, | ||||
|   cilRouter, | ||||
|   cilSave, | ||||
|   cilScrubber, | ||||
|   cilSettings, | ||||
|   cilShare, | ||||
|   cilShareAll, | ||||
|   cilShareBoxed, | ||||
|   cilShieldAlt, | ||||
|   cilSpeech, | ||||
|   cilSpeedometer, | ||||
|   cilSpreadsheet, | ||||
|   cilStar, | ||||
|   cilSun, | ||||
|   cilTags, | ||||
|   cilTask, | ||||
|   cilTrash, | ||||
|   cilUnderline, | ||||
|   cilUser, | ||||
|   cilUserFemale, | ||||
|   cilUserFollow, | ||||
|   cilUserUnfollow, | ||||
|   cilX, | ||||
|   cilXCircle, | ||||
|   cilWarning, | ||||
| } from '@coreui/icons'; | ||||
|  | ||||
| export const icons = { | ||||
|   cilAlignCenter, | ||||
|   cilAlignLeft, | ||||
|   cilAlignRight, | ||||
|   cilApplicationsSettings, | ||||
|   cilArrowRight, | ||||
|   cilArrowTop, | ||||
|   cilAsterisk, | ||||
|   cilBan, | ||||
|   cilBarcode, | ||||
|   cilBasket, | ||||
|   cilBell, | ||||
|   cilBold, | ||||
|   cilBookmark, | ||||
|   cilCalculator, | ||||
|   cilCalendar, | ||||
|   cilCloudDownload, | ||||
|   cilChartPie, | ||||
|   cilCheck, | ||||
|   cilChevronBottom, | ||||
|   cilChevronLeft, | ||||
|   cilChevronRight, | ||||
|   cilChevronTop, | ||||
|   cilCircle, | ||||
|   cilCheckCircle, | ||||
|   cilCode, | ||||
|   cilCommentSquare, | ||||
|   cilCreditCard, | ||||
|   cilCursor, | ||||
|   cilCursorMove, | ||||
|   cilDrop, | ||||
|   cilDollar, | ||||
|   cilEnvelopeClosed, | ||||
|   cilEnvelopeLetter, | ||||
|   cilEnvelopeOpen, | ||||
|   cilEuro, | ||||
|   cilGlobeAlt, | ||||
|   cilGrid, | ||||
|   cilFile, | ||||
|   cilFullscreen, | ||||
|   cilFullscreenExit, | ||||
|   cilGraph, | ||||
|   cilHome, | ||||
|   cilInbox, | ||||
|   cilIndentDecrease, | ||||
|   cilIndentIncrease, | ||||
|   cilInputPower, | ||||
|   cilItalic, | ||||
|   cilJustifyCenter, | ||||
|   cilJustifyLeft, | ||||
|   cilLaptop, | ||||
|   cilLayers, | ||||
|   cilLightbulb, | ||||
|   cilList, | ||||
|   cilListNumbered, | ||||
|   cilListRich, | ||||
|   cilLocationPin, | ||||
|   cilLockLocked, | ||||
|   cilMagnifyingGlass, | ||||
|   cilMap, | ||||
|   cilMoon, | ||||
|   cilNotes, | ||||
|   cilOptions, | ||||
|   cilPaperclip, | ||||
|   cilPaperPlane, | ||||
|   cilPencil, | ||||
|   cilPeople, | ||||
|   cilPhone, | ||||
|   cilPrint, | ||||
|   cilPuzzle, | ||||
|   cilRouter, | ||||
|   cilSave, | ||||
|   cilScrubber, | ||||
|   cilSettings, | ||||
|   cilShare, | ||||
|   cilShareAll, | ||||
|   cilShareBoxed, | ||||
|   cilShieldAlt, | ||||
|   cilSpeech, | ||||
|   cilSpeedometer, | ||||
|   cilSpreadsheet, | ||||
|   cilStar, | ||||
|   cilSun, | ||||
|   cilTags, | ||||
|   cilTask, | ||||
|   cilTrash, | ||||
|   cilUnderline, | ||||
|   cilUser, | ||||
|   cilUserFemale, | ||||
|   cilUserFollow, | ||||
|   cilUserUnfollow, | ||||
|   cilX, | ||||
|   cilXCircle, | ||||
|   cilWarning, | ||||
|   cifUs, | ||||
|   cifBr, | ||||
|   cifIn, | ||||
|   cifFr, | ||||
|   cifEs, | ||||
|   cifPl, | ||||
| }; | ||||
| @@ -1,163 +0,0 @@ | ||||
| import React, { useState, useEffect } from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import Select from 'react-select'; | ||||
| import { | ||||
|   CForm, | ||||
|   CInput, | ||||
|   CLabel, | ||||
|   CCol, | ||||
|   CFormGroup, | ||||
|   CInvalidFeedback, | ||||
|   CFormText, | ||||
|   CRow, | ||||
|   CTextarea, | ||||
| } from '@coreui/react'; | ||||
| import { CopyToClipboardButton } from 'ucentral-libs'; | ||||
|  | ||||
| const AddDefaultConfigurationForm = ({ | ||||
|   t, | ||||
|   disable, | ||||
|   fields, | ||||
|   updateField, | ||||
|   updateFieldWithKey, | ||||
|   deviceTypes, | ||||
| }) => { | ||||
|   const [typeOptions, setTypeOptions] = useState([]); | ||||
|   const [chosenTypes, setChosenTypes] = useState([]); | ||||
|  | ||||
|   const parseOptions = () => { | ||||
|     const options = [{ value: '*', label: 'All' }]; | ||||
|     const newOptions = deviceTypes.map((option) => ({ | ||||
|       value: option, | ||||
|       label: option, | ||||
|     })); | ||||
|     options.push(...newOptions); | ||||
|     setTypeOptions(options); | ||||
|     setChosenTypes([]); | ||||
|   }; | ||||
|  | ||||
|   const typeOnChange = (chosenArray) => { | ||||
|     const allIndex = chosenArray.findIndex((el) => el.value === '*'); | ||||
|  | ||||
|     // If the All option was chosen before, we take it out of the array | ||||
|     if (allIndex === 0 && chosenTypes.length > 0) { | ||||
|       const newResults = chosenArray.slice(1); | ||||
|       setChosenTypes(newResults); | ||||
|       updateFieldWithKey('deviceTypes', { | ||||
|         value: newResults.map((el) => el.value), | ||||
|         error: false, | ||||
|         notEmpty: true, | ||||
|       }); | ||||
|     } else if (allIndex > 0) { | ||||
|       setChosenTypes([{ value: '*', label: 'All' }]); | ||||
|       updateFieldWithKey('deviceTypes', { value: ['*'], error: false, notEmpty: true }); | ||||
|     } else if (chosenArray.length > 0) { | ||||
|       setChosenTypes(chosenArray); | ||||
|       updateFieldWithKey('deviceTypes', { | ||||
|         value: chosenArray.map((el) => el.value), | ||||
|         error: false, | ||||
|         notEmpty: true, | ||||
|       }); | ||||
|     } else { | ||||
|       setChosenTypes([]); | ||||
|       updateFieldWithKey('deviceTypes', { value: [], error: false, notEmpty: true }); | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   useEffect(() => { | ||||
|     parseOptions(); | ||||
|   }, [deviceTypes]); | ||||
|  | ||||
|   return ( | ||||
|     <CForm> | ||||
|       <CFormGroup row className="pb-3"> | ||||
|         <CLabel col htmlFor="name"> | ||||
|           {t('user.name')} | ||||
|         </CLabel> | ||||
|         <CCol sm="7"> | ||||
|           <CInput | ||||
|             id="name" | ||||
|             type="text" | ||||
|             required | ||||
|             value={fields.name.value} | ||||
|             onChange={updateField} | ||||
|             invalid={fields.name.error} | ||||
|             disabled={disable} | ||||
|             maxLength="50" | ||||
|           /> | ||||
|           <CInvalidFeedback>{t('common.required')}</CInvalidFeedback> | ||||
|         </CCol> | ||||
|       </CFormGroup> | ||||
|       <CFormGroup row className="pb-3"> | ||||
|         <CLabel col htmlFor="description"> | ||||
|           {t('user.description')} | ||||
|         </CLabel> | ||||
|         <CCol sm="7"> | ||||
|           <CInput | ||||
|             id="description" | ||||
|             type="text" | ||||
|             required | ||||
|             value={fields.description.value} | ||||
|             onChange={updateField} | ||||
|             invalid={fields.description.error} | ||||
|             disabled={disable} | ||||
|             maxLength="50" | ||||
|           /> | ||||
|           <CInvalidFeedback>{t('common.required')}</CInvalidFeedback> | ||||
|         </CCol> | ||||
|       </CFormGroup> | ||||
|       <CRow className="pb-3"> | ||||
|         <CLabel col htmlFor="deviceTypes"> | ||||
|           <div>{t('configuration.supported_device_types')}:</div> | ||||
|         </CLabel> | ||||
|         <CCol sm="7"> | ||||
|           <Select | ||||
|             isMulti | ||||
|             closeMenuOnSelect={false} | ||||
|             id="deviceTypes" | ||||
|             options={typeOptions} | ||||
|             onChange={typeOnChange} | ||||
|             value={chosenTypes} | ||||
|             className={`basic-multi-select ${fields.deviceTypes.error ? 'border-danger' : ''}`} | ||||
|             classNamePrefix="select" | ||||
|           /> | ||||
|           <CFormText hidden={!fields.deviceTypes.error} color="danger"> | ||||
|             {t('configuration.need_device_type')} | ||||
|           </CFormText> | ||||
|         </CCol> | ||||
|       </CRow> | ||||
|       <div className="pb-3"> | ||||
|         {t('configure.enter_new')} | ||||
|         <CopyToClipboardButton t={t} size="sm" content={fields.configuration.value} /> | ||||
|       </div> | ||||
|       <CRow className="pb-3"> | ||||
|         <CCol> | ||||
|           <CTextarea | ||||
|             style={{ overflowY: 'scroll', height: '500px' }} | ||||
|             id="configuration" | ||||
|             type="text" | ||||
|             required | ||||
|             value={fields.configuration.value} | ||||
|             onChange={updateField} | ||||
|             invalid={fields.configuration.error} | ||||
|             disabled={disable} | ||||
|           /> | ||||
|           <CFormText hidden={!fields.configuration.error} color="danger"> | ||||
|             {t('configure.valid_json')} | ||||
|           </CFormText> | ||||
|         </CCol> | ||||
|       </CRow> | ||||
|     </CForm> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
| AddDefaultConfigurationForm.propTypes = { | ||||
|   t: PropTypes.func.isRequired, | ||||
|   disable: PropTypes.bool.isRequired, | ||||
|   fields: PropTypes.instanceOf(Object).isRequired, | ||||
|   updateField: PropTypes.func.isRequired, | ||||
|   updateFieldWithKey: PropTypes.func.isRequired, | ||||
|   deviceTypes: PropTypes.instanceOf(Array).isRequired, | ||||
| }; | ||||
|  | ||||
| export default AddDefaultConfigurationForm; | ||||
| @@ -1,183 +0,0 @@ | ||||
| import React, { useState, useEffect } from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import { CModal, CModalHeader, CModalTitle, CModalBody, CButton, CPopover } from '@coreui/react'; | ||||
| import CIcon from '@coreui/icons-react'; | ||||
| import { cilX, cilSave } from '@coreui/icons'; | ||||
| import { useToast, useFormFields, useAuth } from 'ucentral-libs'; | ||||
| import axiosInstance from 'utils/axiosInstance'; | ||||
| import { useTranslation } from 'react-i18next'; | ||||
| import { checkIfJson } from 'utils/helper'; | ||||
| import Form from './Form'; | ||||
|  | ||||
| const initialForm = { | ||||
|   name: { | ||||
|     value: '', | ||||
|     error: false, | ||||
|     required: true, | ||||
|   }, | ||||
|   description: { | ||||
|     value: '', | ||||
|     error: false, | ||||
|   }, | ||||
|   deviceTypes: { | ||||
|     value: [], | ||||
|     error: false, | ||||
|     notEmpty: true, | ||||
|   }, | ||||
|   configuration: { | ||||
|     value: '', | ||||
|     error: false, | ||||
|     required: true, | ||||
|   }, | ||||
| }; | ||||
|  | ||||
| const AddConfigurationModal = ({ show, toggle, refresh }) => { | ||||
|   const { t } = useTranslation(); | ||||
|   const { addToast } = useToast(); | ||||
|   const { currentToken, endpoints } = useAuth(); | ||||
|   const [fields, updateFieldWithId, updateField, setFormFields] = useFormFields(initialForm); | ||||
|   const [loading, setLoading] = useState(false); | ||||
|   const [deviceTypes, setDeviceTypes] = useState([]); | ||||
|  | ||||
|   const getDeviceTypes = () => { | ||||
|     setLoading(true); | ||||
|  | ||||
|     const headers = { | ||||
|       Accept: 'application/json', | ||||
|       Authorization: `Bearer ${currentToken}`, | ||||
|     }; | ||||
|  | ||||
|     axiosInstance | ||||
|       .get(`${endpoints.owfms}/api/v1/firmwares?deviceSet=true`, { | ||||
|         headers, | ||||
|       }) | ||||
|       .then((response) => { | ||||
|         setDeviceTypes([...response.data.deviceTypes]); | ||||
|       }) | ||||
|       .catch(() => {}) | ||||
|       .finally(() => { | ||||
|         setLoading(false); | ||||
|       }); | ||||
|   }; | ||||
|  | ||||
|   const validation = () => { | ||||
|     let success = true; | ||||
|  | ||||
|     for (const [key, field] of Object.entries(fields)) { | ||||
|       if (field.required && field.value === '') { | ||||
|         updateField(key, { error: true }); | ||||
|         success = false; | ||||
|         break; | ||||
|       } | ||||
|       if (field.notEmpty && field.value.length === 0) { | ||||
|         updateField(key, { error: true, notEmpty: true }); | ||||
|         success = false; | ||||
|         break; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     if (!checkIfJson(fields.configuration.value)) { | ||||
|       updateField('configuration', { error: true }); | ||||
|       success = false; | ||||
|     } | ||||
|  | ||||
|     return success; | ||||
|   }; | ||||
|  | ||||
|   const addConfiguration = () => { | ||||
|     if (validation()) { | ||||
|       setLoading(true); | ||||
|       const options = { | ||||
|         headers: { | ||||
|           Accept: 'application/json', | ||||
|           Authorization: `Bearer ${currentToken}`, | ||||
|         }, | ||||
|       }; | ||||
|  | ||||
|       const parameters = { | ||||
|         name: fields.name.value, | ||||
|         description: fields.description.value, | ||||
|         modelIds: fields.deviceTypes.value, | ||||
|         configuration: fields.configuration.value, | ||||
|       }; | ||||
|  | ||||
|       axiosInstance | ||||
|         .post( | ||||
|           `${endpoints.owgw}/api/v1/default_configuration/${fields.name.value}`, | ||||
|           parameters, | ||||
|           options, | ||||
|         ) | ||||
|         .then(() => { | ||||
|           if (refresh !== null) refresh(); | ||||
|           toggle(); | ||||
|           addToast({ | ||||
|             title: t('common.success'), | ||||
|             body: t('configuration.creation_success'), | ||||
|             color: 'success', | ||||
|             autohide: true, | ||||
|           }); | ||||
|         }) | ||||
|         .catch((e) => { | ||||
|           addToast({ | ||||
|             title: t('common.error'), | ||||
|             body: t('entity.add_failure', { error: e.response?.data?.ErrorDescription }), | ||||
|             color: 'danger', | ||||
|             autohide: true, | ||||
|           }); | ||||
|         }) | ||||
|         .finally(() => { | ||||
|           setLoading(false); | ||||
|         }); | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   useEffect(() => { | ||||
|     if (show) { | ||||
|       getDeviceTypes(); | ||||
|       setFormFields(initialForm); | ||||
|     } | ||||
|   }, [show]); | ||||
|  | ||||
|   return ( | ||||
|     <CModal className="text-dark" size="lg" show={show} onClose={toggle}> | ||||
|       <CModalHeader className="p-1"> | ||||
|         <CModalTitle className="pl-1 pt-1">{t('configuration.create')}</CModalTitle> | ||||
|         <div className="text-right"> | ||||
|           <CPopover content={t('common.add')}> | ||||
|             <CButton color="primary" variant="outline" className="ml-2" onClick={addConfiguration}> | ||||
|               <CIcon content={cilSave} /> | ||||
|             </CButton> | ||||
|           </CPopover> | ||||
|           <CPopover content={t('common.close')}> | ||||
|             <CButton color="primary" variant="outline" className="ml-2" onClick={toggle}> | ||||
|               <CIcon content={cilX} /> | ||||
|             </CButton> | ||||
|           </CPopover> | ||||
|         </div> | ||||
|       </CModalHeader> | ||||
|       <CModalBody className="px-5"> | ||||
|         <Form | ||||
|           t={t} | ||||
|           disable={loading} | ||||
|           fields={fields} | ||||
|           updateField={updateFieldWithId} | ||||
|           updateFieldWithKey={updateField} | ||||
|           deviceTypes={deviceTypes} | ||||
|           show={show} | ||||
|         /> | ||||
|       </CModalBody> | ||||
|     </CModal> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
| AddConfigurationModal.propTypes = { | ||||
|   show: PropTypes.bool.isRequired, | ||||
|   toggle: PropTypes.func.isRequired, | ||||
|   refresh: PropTypes.func, | ||||
| }; | ||||
|  | ||||
| AddConfigurationModal.defaultProps = { | ||||
|   refresh: null, | ||||
| }; | ||||
|  | ||||
| export default AddConfigurationModal; | ||||
| @@ -1,158 +0,0 @@ | ||||
| import React, { useEffect, useState } from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import { useTranslation } from 'react-i18next'; | ||||
| import { | ||||
|   CButton, | ||||
|   CModal, | ||||
|   CModalHeader, | ||||
|   CModalTitle, | ||||
|   CModalBody, | ||||
|   CPopover, | ||||
|   CRow, | ||||
|   CCol, | ||||
|   CLabel, | ||||
|   CTextarea, | ||||
|   CInput, | ||||
|   CInvalidFeedback, | ||||
| } from '@coreui/react'; | ||||
| import CIcon from '@coreui/icons-react'; | ||||
| import { useAuth, useToast } from 'ucentral-libs'; | ||||
| import { cilPlus, cilX } from '@coreui/icons'; | ||||
| import axiosInstance from 'utils/axiosInstance'; | ||||
|  | ||||
| const AddToBlacklistModal = ({ show, toggle, serialNumber, refresh }) => { | ||||
|   const { t } = useTranslation(); | ||||
|   const [loading, setLoading] = useState(false); | ||||
|   const { addToast } = useToast(); | ||||
|   const { endpoints, currentToken } = useAuth(); | ||||
|   const [chosenSerialNumber, setChosenSerialNumber] = useState(''); | ||||
|   const [reason, setReason] = useState(''); | ||||
|  | ||||
|   const addToBlacklist = () => { | ||||
|     setLoading(true); | ||||
|  | ||||
|     const parameters = { | ||||
|       serialNumber: chosenSerialNumber, | ||||
|       reason, | ||||
|     }; | ||||
|  | ||||
|     const headers = { | ||||
|       Accept: 'application/json', | ||||
|       Authorization: `Bearer ${currentToken}`, | ||||
|     }; | ||||
|  | ||||
|     axiosInstance | ||||
|       .post(`${endpoints.owgw}/api/v1/blacklist/${chosenSerialNumber}`, parameters, { headers }) | ||||
|       .then(() => { | ||||
|         addToast({ | ||||
|           title: t('common.success'), | ||||
|           body: t('device.success_added_blacklist'), | ||||
|           color: 'success', | ||||
|           autohide: true, | ||||
|         }); | ||||
|         toggle(); | ||||
|         if (refresh) refresh(); | ||||
|       }) | ||||
|       .catch((e) => { | ||||
|         addToast({ | ||||
|           title: t('common.error'), | ||||
|           body: t('device.error_adding_blacklist', { error: e.response?.data?.ErrorDescription }), | ||||
|           color: 'danger', | ||||
|           autohide: true, | ||||
|         }); | ||||
|       }) | ||||
|       .finally(() => { | ||||
|         setLoading(false); | ||||
|       }); | ||||
|   }; | ||||
|  | ||||
|   useEffect(() => { | ||||
|     if (show) { | ||||
|       if (serialNumber) setChosenSerialNumber(serialNumber); | ||||
|       else setChosenSerialNumber(''); | ||||
|     } | ||||
|   }, [show, serialNumber]); | ||||
|  | ||||
|   return ( | ||||
|     <CModal className="text-dark" size="lg" show={show} onClose={toggle}> | ||||
|       <CModalHeader className="p-1"> | ||||
|         <CModalTitle className="pl-1 pt-1">{t('device.add_to_blacklist')}</CModalTitle> | ||||
|         <div className="text-right"> | ||||
|           <CPopover content={t('common.add')}> | ||||
|             <CButton | ||||
|               color="primary" | ||||
|               variant="outline" | ||||
|               className="ml-2" | ||||
|               onClick={addToBlacklist} | ||||
|               disabled={ | ||||
|                 chosenSerialNumber.length !== 12 || | ||||
|                 !chosenSerialNumber.match('^[a-fA-F0-9]+$') || | ||||
|                 reason === '' || | ||||
|                 loading | ||||
|               } | ||||
|             > | ||||
|               <CIcon content={cilPlus} /> | ||||
|             </CButton> | ||||
|           </CPopover> | ||||
|           <CPopover content={t('common.close')}> | ||||
|             <CButton color="primary" variant="outline" className="ml-2" onClick={toggle}> | ||||
|               <CIcon content={cilX} /> | ||||
|             </CButton> | ||||
|           </CPopover> | ||||
|         </div> | ||||
|       </CModalHeader> | ||||
|       <CModalBody> | ||||
|         <CRow> | ||||
|           <CLabel col sm="3"> | ||||
|             {t('common.serial_number')} | ||||
|           </CLabel> | ||||
|           <CCol sm="9" className="pt-1"> | ||||
|             <CInput | ||||
|               id="description" | ||||
|               type="text" | ||||
|               required | ||||
|               value={chosenSerialNumber} | ||||
|               onChange={(e) => setChosenSerialNumber(e.target.value)} | ||||
|               invalid={ | ||||
|                 chosenSerialNumber.length !== 12 && chosenSerialNumber.match('^[a-fA-F0-9]+$') | ||||
|               } | ||||
|               disabled={loading} | ||||
|               maxLength="50" | ||||
|             /> | ||||
|             <CInvalidFeedback>{t('entity.valid_serial')}</CInvalidFeedback> | ||||
|           </CCol> | ||||
|         </CRow> | ||||
|         <CRow> | ||||
|           <CLabel col sm="3"> | ||||
|             {t('common.reason')} | ||||
|           </CLabel> | ||||
|           <CCol sm="9" className="pt-2"> | ||||
|             <CTextarea | ||||
|               name="reason" | ||||
|               id="reason" | ||||
|               rows="3" | ||||
|               type="text" | ||||
|               required | ||||
|               value={reason} | ||||
|               onChange={(e) => setReason(e.target.value)} | ||||
|             /> | ||||
|           </CCol> | ||||
|         </CRow> | ||||
|       </CModalBody> | ||||
|     </CModal> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
| AddToBlacklistModal.propTypes = { | ||||
|   show: PropTypes.bool.isRequired, | ||||
|   toggle: PropTypes.func.isRequired, | ||||
|   serialNumber: PropTypes.string, | ||||
|   refresh: PropTypes.func, | ||||
| }; | ||||
|  | ||||
| AddToBlacklistModal.defaultProps = { | ||||
|   serialNumber: '', | ||||
|   refresh: null, | ||||
| }; | ||||
|  | ||||
| export default AddToBlacklistModal; | ||||
| @@ -1,210 +0,0 @@ | ||||
| import React, { useEffect } from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import ReactPaginate from 'react-paginate'; | ||||
| import { | ||||
|   CCardBody, | ||||
|   CDataTable, | ||||
|   CButton, | ||||
|   CLink, | ||||
|   CCard, | ||||
|   CCardHeader, | ||||
|   CPopover, | ||||
|   CSelect, | ||||
|   CButtonToolbar, | ||||
| } from '@coreui/react'; | ||||
| import { cilSearch, cilPencil, cilPlus, cilTrash } from '@coreui/icons'; | ||||
| import CIcon from '@coreui/icons-react'; | ||||
| import ReactTooltip from 'react-tooltip'; | ||||
| import { FormattedDate } from 'ucentral-libs'; | ||||
|  | ||||
| const BlacklistTable = ({ | ||||
|   currentPage, | ||||
|   devices, | ||||
|   toggleAddBlacklist, | ||||
|   toggleEditModal, | ||||
|   devicesPerPage, | ||||
|   loading, | ||||
|   removeFromBlacklist, | ||||
|   updateDevicesPerPage, | ||||
|   pageCount, | ||||
|   updatePage, | ||||
|   t, | ||||
| }) => { | ||||
|   const columns = [ | ||||
|     { key: 'serialNumber', label: t('common.serial_number'), _style: { width: '6%' } }, | ||||
|     { key: 'created', label: t('device.blacklisted_on'), _style: { width: '1%' } }, | ||||
|     { key: 'author', label: t('common.by'), filter: false, _style: { width: '15%' } }, | ||||
|     { key: 'reason', label: t('common.reason'), filter: false }, | ||||
|     { key: 'actions', label: t('actions.actions'), _style: { width: '1%' } }, | ||||
|   ]; | ||||
|  | ||||
|   const hideTooltips = () => ReactTooltip.hide(); | ||||
|  | ||||
|   const escFunction = (event) => { | ||||
|     if (event.keyCode === 27) { | ||||
|       hideTooltips(); | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   useEffect(() => { | ||||
|     document.addEventListener('keydown', escFunction, false); | ||||
|  | ||||
|     return () => { | ||||
|       document.removeEventListener('keydown', escFunction, false); | ||||
|     }; | ||||
|   }, []); | ||||
|  | ||||
|   return ( | ||||
|     <> | ||||
|       <CCard className="m-0 p-0"> | ||||
|         <CCardHeader className="p-0 text-right"> | ||||
|           <CPopover content={t('device.add_to_blacklist')}> | ||||
|             <CButton size="sm" color="primary" onClick={toggleAddBlacklist}> | ||||
|               <CIcon content={cilPlus} /> | ||||
|             </CButton> | ||||
|           </CPopover> | ||||
|         </CCardHeader> | ||||
|         <CCardBody className="p-0"> | ||||
|           <CDataTable | ||||
|             addTableClasses="ignore-overflow table-sm" | ||||
|             items={devices ?? []} | ||||
|             fields={columns} | ||||
|             hover | ||||
|             border | ||||
|             loading={loading} | ||||
|             scopedSlots={{ | ||||
|               serialNumber: (item) => ( | ||||
|                 <td className="text-center align-middle"> | ||||
|                   <CLink | ||||
|                     className="c-subheader-nav-link" | ||||
|                     aria-current="page" | ||||
|                     to={() => `/devices/${item.serialNumber}`} | ||||
|                   > | ||||
|                     {item.serialNumber} | ||||
|                   </CLink> | ||||
|                 </td> | ||||
|               ), | ||||
|               created: (item) => ( | ||||
|                 <td className="text-left align-middle"> | ||||
|                   <div style={{ width: '130px' }}> | ||||
|                     <FormattedDate date={item.created} /> | ||||
|                   </div> | ||||
|                 </td> | ||||
|               ), | ||||
|               author: (item) => <td className="align-middle">{item.author}</td>, | ||||
|               reason: (item) => <td className="align-middle">{item.reason}</td>, | ||||
|               actions: (item) => ( | ||||
|                 <td className="text-center align-middle"> | ||||
|                   <CButtonToolbar | ||||
|                     role="group" | ||||
|                     className="justify-content-center" | ||||
|                     style={{ width: '130px' }} | ||||
|                   > | ||||
|                     <CPopover content={t('configuration.details')}> | ||||
|                       <CLink | ||||
|                         className="c-subheader-nav-link" | ||||
|                         aria-current="page" | ||||
|                         to={() => `/devices/${item.serialNumber}`} | ||||
|                       > | ||||
|                         <CButton | ||||
|                           color="primary" | ||||
|                           variant="outline" | ||||
|                           shape="square" | ||||
|                           size="sm" | ||||
|                           className="mx-1" | ||||
|                           style={{ width: '33px', height: '30px' }} | ||||
|                         > | ||||
|                           <CIcon name="cil-search" content={cilSearch} size="sm" /> | ||||
|                         </CButton> | ||||
|                       </CLink> | ||||
|                     </CPopover> | ||||
|                     <CPopover content={t('device.remove_from_blacklist')}> | ||||
|                       <CButton | ||||
|                         onClick={() => removeFromBlacklist(item.serialNumber)} | ||||
|                         color="primary" | ||||
|                         variant="outline" | ||||
|                         shape="square" | ||||
|                         size="sm" | ||||
|                         className="mx-1" | ||||
|                         style={{ width: '33px', height: '30px' }} | ||||
|                       > | ||||
|                         <CIcon content={cilTrash} size="sm" /> | ||||
|                       </CButton> | ||||
|                     </CPopover> | ||||
|                     <CPopover content={t('common.edit')}> | ||||
|                       <CButton | ||||
|                         onClick={() => toggleEditModal(item.serialNumber)} | ||||
|                         color="primary" | ||||
|                         variant="outline" | ||||
|                         shape="square" | ||||
|                         size="sm" | ||||
|                         className="mx-1" | ||||
|                         style={{ width: '33px', height: '30px' }} | ||||
|                       > | ||||
|                         <CIcon content={cilPencil} size="sm" /> | ||||
|                       </CButton> | ||||
|                     </CPopover> | ||||
|                   </CButtonToolbar> | ||||
|                 </td> | ||||
|               ), | ||||
|             }} | ||||
|           /> | ||||
|           <div className="d-flex flex-row pl-3"> | ||||
|             <div className="pr-3"> | ||||
|               <ReactPaginate | ||||
|                 previousLabel="← Previous" | ||||
|                 nextLabel="Next →" | ||||
|                 pageCount={pageCount} | ||||
|                 onPageChange={updatePage} | ||||
|                 forcePage={Number(currentPage)} | ||||
|                 breakClassName="page-item" | ||||
|                 breakLinkClassName="page-link" | ||||
|                 containerClassName="pagination" | ||||
|                 pageClassName="page-item" | ||||
|                 pageLinkClassName="page-link" | ||||
|                 previousClassName="page-item" | ||||
|                 previousLinkClassName="page-link" | ||||
|                 nextClassName="page-item" | ||||
|                 nextLinkClassName="page-link" | ||||
|                 activeClassName="active" | ||||
|               /> | ||||
|             </div> | ||||
|             <p className="pr-2 mt-1">{t('common.items_per_page')}</p> | ||||
|             <div style={{ width: '100px' }} className="px-2"> | ||||
|               <CSelect | ||||
|                 custom | ||||
|                 defaultValue={devicesPerPage} | ||||
|                 onChange={(e) => updateDevicesPerPage(e.target.value)} | ||||
|                 disabled={loading} | ||||
|               > | ||||
|                 <option value="10">10</option> | ||||
|                 <option value="25">25</option> | ||||
|                 <option value="50">50</option> | ||||
|               </CSelect> | ||||
|             </div> | ||||
|           </div> | ||||
|         </CCardBody> | ||||
|       </CCard> | ||||
|     </> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
| BlacklistTable.propTypes = { | ||||
|   currentPage: PropTypes.string, | ||||
|   devices: PropTypes.instanceOf(Array).isRequired, | ||||
|   toggleAddBlacklist: PropTypes.func.isRequired, | ||||
|   toggleEditModal: PropTypes.func.isRequired, | ||||
|   updateDevicesPerPage: PropTypes.func.isRequired, | ||||
|   pageCount: PropTypes.number.isRequired, | ||||
|   updatePage: PropTypes.func.isRequired, | ||||
|   devicesPerPage: PropTypes.string.isRequired, | ||||
|   removeFromBlacklist: PropTypes.func.isRequired, | ||||
|   t: PropTypes.func.isRequired, | ||||
|   loading: PropTypes.bool.isRequired, | ||||
| }; | ||||
|  | ||||
| BlacklistTable.defaultProps = { | ||||
|   currentPage: '0', | ||||
| }; | ||||
|  | ||||
| export default React.memo(BlacklistTable); | ||||
| @@ -1,30 +0,0 @@ | ||||
| .firmwareTooltip { | ||||
|   opacity: 1 !important; | ||||
|   padding: 0px 0px 0px 0px !important; | ||||
|   border-radius: 1rem !important; | ||||
|   background-color: #fff !important; | ||||
|   border-color: #321fdb !important; | ||||
|   font-size: 0.875rem !important; | ||||
|   font-weight: 400 !important; | ||||
|   box-shadow: 0 3px 10px rgba(0, 0, 0, 0.2) !important; | ||||
|   width: 400px; | ||||
| } | ||||
|  | ||||
| .deleteTooltip { | ||||
|   opacity: 1 !important; | ||||
|   padding: 0px 0px 0px 0px !important; | ||||
|   border-radius: 1rem !important; | ||||
|   background-color: #fff !important; | ||||
|   border-color: #321fdb !important; | ||||
|   font-size: 0.875rem !important; | ||||
|   font-weight: 400 !important; | ||||
|   box-shadow: 0 3px 10px rgba(0, 0, 0, 0.2) !important; | ||||
|   width: 200px; | ||||
| } | ||||
|  | ||||
| .tooltipHeader { | ||||
|   padding-left: 5px; | ||||
|   padding-right: 10px; | ||||
|   border-top-left-radius: 1rem !important; | ||||
|   border-top-right-radius: 1rem !important; | ||||
| } | ||||
| @@ -1,244 +0,0 @@ | ||||
| import React, { useEffect, useState } from 'react'; | ||||
| import { useTranslation } from 'react-i18next'; | ||||
| import { useHistory } from 'react-router-dom'; | ||||
| import axiosInstance from 'utils/axiosInstance'; | ||||
| import { getItem, setItem } from 'utils/localStorageHelper'; | ||||
| import { useAuth, useToast, useToggle } from 'ucentral-libs'; | ||||
| import AddToBlacklistModal from 'components/AddToBlacklistModal'; | ||||
| import EditBlacklistModal from 'components/EditBlacklistModal'; | ||||
| import Table from './Table'; | ||||
|  | ||||
| const BlacklistTable = () => { | ||||
|   const { t } = useTranslation(); | ||||
|   const { addToast } = useToast(); | ||||
|   const history = useHistory(); | ||||
|   const [page, setPage] = useState(parseInt(sessionStorage.getItem('deviceTable') ?? 0, 10)); | ||||
|   const { currentToken, endpoints } = useAuth(); | ||||
|   const [deviceCount, setDeviceCount] = useState(0); | ||||
|   const [pageCount, setPageCount] = useState(0); | ||||
|   const [devicesPerPage, setDevicesPerPage] = useState(getItem('devicesPerPage') || '10'); | ||||
|   const [devices, setDevices] = useState([]); | ||||
|   const [loading, setLoading] = useState(true); | ||||
|   const [editSerial, setEditSerial] = useState(''); | ||||
|   const [showEditModal, setShowEditModal] = useState(false); | ||||
|   const [showAddModal, toggleAddModal] = useToggle(false); | ||||
|  | ||||
|   const toggleEditModal = (serialNumber) => { | ||||
|     if (serialNumber) setEditSerial(serialNumber); | ||||
|     setShowEditModal(!showEditModal); | ||||
|   }; | ||||
|  | ||||
|   const getDeviceInformation = (selectedPage = page, devicePerPage = devicesPerPage) => { | ||||
|     setLoading(true); | ||||
|  | ||||
|     const options = { | ||||
|       headers: { | ||||
|         Accept: 'application/json', | ||||
|         Authorization: `Bearer ${currentToken}`, | ||||
|       }, | ||||
|     }; | ||||
|  | ||||
|     axiosInstance | ||||
|       .get( | ||||
|         `${endpoints.owgw}/api/v1/blacklist?limit=${devicePerPage}&offset=${ | ||||
|           devicePerPage * selectedPage | ||||
|         }`, | ||||
|         options, | ||||
|       ) | ||||
|       .then((response) => { | ||||
|         setDevices(response.data.devices); | ||||
|         setLoading(false); | ||||
|       }) | ||||
|       .catch((e) => { | ||||
|         addToast({ | ||||
|           title: t('common.error'), | ||||
|           body: t('device.error_fetching_devices', { error: e.response?.data?.ErrorDescription }), | ||||
|           color: 'danger', | ||||
|           autohide: true, | ||||
|         }); | ||||
|         setLoading(false); | ||||
|       }); | ||||
|   }; | ||||
|  | ||||
|   const getCount = () => { | ||||
|     setLoading(true); | ||||
|  | ||||
|     const headers = { | ||||
|       Accept: 'application/json', | ||||
|       Authorization: `Bearer ${currentToken}`, | ||||
|     }; | ||||
|  | ||||
|     axiosInstance | ||||
|       .get(`${endpoints.owgw}/api/v1/blacklist?countOnly=true`, { | ||||
|         headers, | ||||
|       }) | ||||
|       .then((response) => { | ||||
|         const devicesCount = response.data.count; | ||||
|         const pagesCount = Math.ceil(devicesCount / devicesPerPage); | ||||
|         setPageCount(pagesCount); | ||||
|         setDeviceCount(devicesCount); | ||||
|  | ||||
|         let selectedPage = page; | ||||
|  | ||||
|         if (page >= pagesCount) { | ||||
|           history.push(`/devices?page=${pagesCount - 1}`); | ||||
|           selectedPage = pagesCount - 1; | ||||
|         } | ||||
|         getDeviceInformation(selectedPage); | ||||
|       }) | ||||
|       .catch((e) => { | ||||
|         addToast({ | ||||
|           title: t('common.error'), | ||||
|           body: t('device.error_fetching_devices', { error: e.response?.data?.ErrorDescription }), | ||||
|           color: 'danger', | ||||
|           autohide: true, | ||||
|         }); | ||||
|         setLoading(false); | ||||
|       }); | ||||
|   }; | ||||
|  | ||||
|   const refreshDevice = (serialNumber) => { | ||||
|     setLoading(true); | ||||
|  | ||||
|     const options = { | ||||
|       headers: { | ||||
|         Accept: 'application/json', | ||||
|         Authorization: `Bearer ${currentToken}`, | ||||
|       }, | ||||
|     }; | ||||
|  | ||||
|     let newDevice; | ||||
|  | ||||
|     axiosInstance | ||||
|       .get( | ||||
|         `${endpoints.owgw}/api/v1/blacklist?deviceWithStatus=true&select=${encodeURIComponent( | ||||
|           serialNumber, | ||||
|         )}`, | ||||
|         options, | ||||
|       ) | ||||
|       .then( | ||||
|         ({ | ||||
|           data: { | ||||
|             devicesWithStatus: [device], | ||||
|           }, | ||||
|         }) => { | ||||
|           newDevice = device; | ||||
|  | ||||
|           return axiosInstance.get( | ||||
|             `${endpoints.owfms}/api/v1/firmwareAge?select=${serialNumber}`, | ||||
|             options, | ||||
|           ); | ||||
|         }, | ||||
|       ) | ||||
|       .then((response) => { | ||||
|         newDevice.firmwareInfo = { | ||||
|           age: response.data.ages[0].age, | ||||
|           latest: response.data.ages[0].latest, | ||||
|         }; | ||||
|         const foundIndex = devices.findIndex((obj) => obj.serialNumber === serialNumber); | ||||
|         const newList = devices; | ||||
|         newList[foundIndex] = newDevice; | ||||
|         setDevices(newList); | ||||
|         setLoading(false); | ||||
|       }) | ||||
|       .catch((e) => { | ||||
|         addToast({ | ||||
|           title: t('common.error'), | ||||
|           body: t('device.error_fetching_devices', { error: e.response?.data?.ErrorDescription }), | ||||
|           color: 'danger', | ||||
|           autohide: true, | ||||
|         }); | ||||
|         setLoading(false); | ||||
|       }); | ||||
|   }; | ||||
|  | ||||
|   const updateDevicesPerPage = (value) => { | ||||
|     setItem('devicesPerPage', value); | ||||
|     setDevicesPerPage(value); | ||||
|  | ||||
|     const newPageCount = Math.ceil(deviceCount / value); | ||||
|     setPageCount(newPageCount); | ||||
|  | ||||
|     let selectedPage = page; | ||||
|  | ||||
|     if (page >= newPageCount) { | ||||
|       history.push(`/blacklist?page=${newPageCount - 1}`); | ||||
|       selectedPage = newPageCount - 1; | ||||
|     } | ||||
|  | ||||
|     getDeviceInformation(selectedPage, value); | ||||
|   }; | ||||
|  | ||||
|   const updatePageCount = ({ selected: selectedPage }) => { | ||||
|     sessionStorage.setItem('deviceTable', selectedPage); | ||||
|     setPage(selectedPage); | ||||
|     getDeviceInformation(selectedPage); | ||||
|   }; | ||||
|  | ||||
|   const removeFromBlacklist = (serialNumber) => { | ||||
|     setLoading(true); | ||||
|  | ||||
|     const headers = { | ||||
|       Accept: 'application/json', | ||||
|       Authorization: `Bearer ${currentToken}`, | ||||
|     }; | ||||
|  | ||||
|     axiosInstance | ||||
|       .delete(`${endpoints.owgw}/api/v1/blacklist/${serialNumber}`, { headers }) | ||||
|       .then(() => { | ||||
|         addToast({ | ||||
|           title: t('common.success'), | ||||
|           body: t('device.success_removed_blacklist'), | ||||
|           color: 'success', | ||||
|           autohide: true, | ||||
|         }); | ||||
|         getCount(); | ||||
|       }) | ||||
|       .catch((e) => { | ||||
|         addToast({ | ||||
|           title: t('common.error'), | ||||
|           body: t('device.error_adding_blacklist', { error: e.response?.data?.ErrorDescription }), | ||||
|           color: 'danger', | ||||
|           autohide: true, | ||||
|         }); | ||||
|       }) | ||||
|       .finally(() => { | ||||
|         setLoading(false); | ||||
|       }); | ||||
|   }; | ||||
|  | ||||
|   useEffect(() => { | ||||
|     getCount(); | ||||
|   }, []); | ||||
|  | ||||
|   return ( | ||||
|     <div> | ||||
|       <Table | ||||
|         currentPage={page} | ||||
|         t={t} | ||||
|         devices={devices} | ||||
|         loading={loading} | ||||
|         toggleAddBlacklist={toggleAddModal} | ||||
|         toggleEditModal={toggleEditModal} | ||||
|         updateDevicesPerPage={updateDevicesPerPage} | ||||
|         devicesPerPage={devicesPerPage} | ||||
|         pageCount={pageCount} | ||||
|         updatePage={updatePageCount} | ||||
|         pageRangeDisplayed={5} | ||||
|         refreshDevice={refreshDevice} | ||||
|         removeFromBlacklist={removeFromBlacklist} | ||||
|       /> | ||||
|       {showAddModal ? ( | ||||
|         <AddToBlacklistModal show={showAddModal} toggle={toggleAddModal} refresh={getCount} /> | ||||
|       ) : null} | ||||
|       <EditBlacklistModal | ||||
|         show={showEditModal} | ||||
|         toggle={toggleEditModal} | ||||
|         refresh={getCount} | ||||
|         serialNumber={editSerial} | ||||
|       /> | ||||
|     </div> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
| export default BlacklistTable; | ||||
| @@ -1,174 +0,0 @@ | ||||
| import { | ||||
|   CButton, | ||||
|   CModal, | ||||
|   CModalHeader, | ||||
|   CModalTitle, | ||||
|   CModalBody, | ||||
|   CModalFooter, | ||||
|   CCol, | ||||
|   CFormGroup, | ||||
|   CInputRadio, | ||||
|   CLabel, | ||||
|   CPopover, | ||||
|   CRow, | ||||
| } from '@coreui/react'; | ||||
| import CIcon from '@coreui/icons-react'; | ||||
| import { cilX } from '@coreui/icons'; | ||||
| import React, { useState, useEffect } from 'react'; | ||||
| import { useTranslation } from 'react-i18next'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import 'react-widgets/styles.css'; | ||||
| import axiosInstance from 'utils/axiosInstance'; | ||||
| import eventBus from 'utils/eventBus'; | ||||
| import SuccessfulActionModalBody from 'components/SuccessfulActionModalBody'; | ||||
| import { LoadingButton, useAuth, useDevice, useToast } from 'ucentral-libs'; | ||||
|  | ||||
| const BlinkModal = ({ show, toggleModal }) => { | ||||
|   const { t } = useTranslation(); | ||||
|   const { currentToken, endpoints } = useAuth(); | ||||
|   const { deviceSerialNumber } = useDevice(); | ||||
|   const { addToast } = useToast(); | ||||
|   const [waiting, setWaiting] = useState(false); | ||||
|   const [chosenPattern, setPattern] = useState('blink'); | ||||
|   const [result, setResult] = useState(null); | ||||
|  | ||||
|   useEffect(() => { | ||||
|     if (show) { | ||||
|       setWaiting(false); | ||||
|       setPattern('blink'); | ||||
|       setResult(null); | ||||
|     } | ||||
|   }, [show]); | ||||
|  | ||||
|   const doAction = () => { | ||||
|     setWaiting(true); | ||||
|  | ||||
|     const parameters = { | ||||
|       serialNumber: deviceSerialNumber, | ||||
|       when: 0, | ||||
|       pattern: chosenPattern, | ||||
|       duration: 30, | ||||
|     }; | ||||
|  | ||||
|     const headers = { | ||||
|       Accept: 'application/json', | ||||
|       Authorization: `Bearer ${currentToken}`, | ||||
|     }; | ||||
|  | ||||
|     axiosInstance | ||||
|       .post( | ||||
|         `${endpoints.owgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}/leds`, | ||||
|         parameters, | ||||
|         { headers }, | ||||
|       ) | ||||
|       .then(() => { | ||||
|         if (chosenPattern !== 'blink') { | ||||
|           addToast({ | ||||
|             title: t('common.success'), | ||||
|             body: t('commands.command_success'), | ||||
|             color: 'success', | ||||
|             autohide: true, | ||||
|           }); | ||||
|         } | ||||
|         toggleModal(); | ||||
|       }) | ||||
|       .catch(() => { | ||||
|         setResult('error'); | ||||
|       }) | ||||
|       .finally(() => { | ||||
|         setWaiting(false); | ||||
|         eventBus.dispatch('actionCompleted', { message: 'An action has been completed' }); | ||||
|       }); | ||||
|   }; | ||||
|  | ||||
|   return ( | ||||
|     <CModal show={show} onClose={toggleModal}> | ||||
|       <CModalHeader className="p-1"> | ||||
|         <CModalTitle className="pl-1 pt-1">{t('blink.device_leds')}</CModalTitle> | ||||
|         <div className="text-right"> | ||||
|           <CPopover content={t('common.close')}> | ||||
|             <CButton color="primary" variant="outline" className="ml-2" onClick={toggleModal}> | ||||
|               <CIcon content={cilX} /> | ||||
|             </CButton> | ||||
|           </CPopover> | ||||
|         </div> | ||||
|       </CModalHeader> | ||||
|       {result === 'success' ? ( | ||||
|         <SuccessfulActionModalBody toggleModal={toggleModal} /> | ||||
|       ) : ( | ||||
|         <div> | ||||
|           <CModalBody> | ||||
|             <CRow className="mb-3"> | ||||
|               <CCol>{t('blink.explanation')}</CCol> | ||||
|             </CRow> | ||||
|             <CFormGroup row className="mb-0"> | ||||
|               <CCol md="3"> | ||||
|                 <CLabel>{t('blink.pattern')}</CLabel> | ||||
|               </CCol> | ||||
|               <CCol> | ||||
|                 <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> | ||||
|                 <CFormGroup variant="custom-radio" onClick={() => setPattern('on')} inline> | ||||
|                   <CInputRadio | ||||
|                     custom | ||||
|                     defaultChecked={chosenPattern === 'on'} | ||||
|                     id="radio1" | ||||
|                     name="radios" | ||||
|                     value="option1" | ||||
|                   /> | ||||
|                   <CLabel variant="custom-checkbox" htmlFor="radio1"> | ||||
|                     {t('common.on')} | ||||
|                   </CLabel> | ||||
|                 </CFormGroup> | ||||
|                 <CFormGroup variant="custom-radio" onClick={() => setPattern('off')} inline> | ||||
|                   <CInputRadio | ||||
|                     custom | ||||
|                     defaultChecked={chosenPattern === 'off'} | ||||
|                     id="radio2" | ||||
|                     name="radios" | ||||
|                     value="option2" | ||||
|                   /> | ||||
|                   <CLabel variant="custom-checkbox" htmlFor="radio2"> | ||||
|                     {t('common.off')} | ||||
|                   </CLabel> | ||||
|                 </CFormGroup> | ||||
|               </CCol> | ||||
|             </CFormGroup> | ||||
|           </CModalBody> | ||||
|           <CModalFooter> | ||||
|             <LoadingButton | ||||
|               label={t('common.submit')} | ||||
|               isLoadingLabel={ | ||||
|                 chosenPattern === 'blink' ? 'LEDs are blinking...  ' : t('common.loading_ellipsis') | ||||
|               } | ||||
|               isLoading={waiting} | ||||
|               action={doAction} | ||||
|               block={false} | ||||
|               disabled={waiting} | ||||
|             /> | ||||
|             <CButton color="secondary" onClick={toggleModal}> | ||||
|               {t('common.cancel')} | ||||
|             </CButton> | ||||
|           </CModalFooter> | ||||
|         </div> | ||||
|       )} | ||||
|     </CModal> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
| BlinkModal.propTypes = { | ||||
|   show: PropTypes.bool.isRequired, | ||||
|   toggleModal: PropTypes.func.isRequired, | ||||
| }; | ||||
|  | ||||
| export default BlinkModal; | ||||
							
								
								
									
										50
									
								
								src/components/Buttons/AlertButton/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,50 @@ | ||||
| import React from 'react'; | ||||
| import { Button, IconButton, Tooltip, useBreakpoint } from '@chakra-ui/react'; | ||||
| import { Warning } from 'phosphor-react'; | ||||
| import { useTranslation } from 'react-i18next'; | ||||
| import { ThemeProps } from 'models/Theme'; | ||||
|  | ||||
| export interface AlertButtonProps extends ThemeProps { | ||||
|   onClick: () => void; | ||||
|   isDisabled?: boolean; | ||||
|   isLoading?: boolean; | ||||
|   isCompact?: boolean; | ||||
|   label?: string; | ||||
| } | ||||
|  | ||||
| const _AlertButton: React.FC<AlertButtonProps> = ({ onClick, isDisabled, isLoading, isCompact, label, ...props }) => { | ||||
|   const { t } = useTranslation(); | ||||
|   const breakpoint = useBreakpoint(); | ||||
|  | ||||
|   if (!isCompact && breakpoint !== 'base' && breakpoint !== 'sm') { | ||||
|     return ( | ||||
|       <Button | ||||
|         colorScheme="red" | ||||
|         type="button" | ||||
|         onClick={onClick} | ||||
|         rightIcon={<Warning size={20} />} | ||||
|         isLoading={isLoading} | ||||
|         isDisabled={isDisabled} | ||||
|         {...props} | ||||
|       > | ||||
|         {label ?? t('common.alert')} | ||||
|       </Button> | ||||
|     ); | ||||
|   } | ||||
|   return ( | ||||
|     <Tooltip label={label ?? t('common.alert')}> | ||||
|       <IconButton | ||||
|         aria-label="alert-button" | ||||
|         colorScheme="red" | ||||
|         type="button" | ||||
|         onClick={onClick} | ||||
|         icon={<Warning size={20} />} | ||||
|         isLoading={isLoading} | ||||
|         isDisabled={isDisabled} | ||||
|         {...props} | ||||
|       /> | ||||
|     </Tooltip> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
| export const AlertButton = React.memo(_AlertButton); | ||||
							
								
								
									
										28
									
								
								src/components/Buttons/CloseButton/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,28 @@ | ||||
| import React from 'react'; | ||||
| import { IconButton, SpaceProps } from '@chakra-ui/react'; | ||||
| import { X } from 'phosphor-react'; | ||||
| import { useTranslation } from 'react-i18next'; | ||||
|  | ||||
| export interface CloseButtonProps extends SpaceProps { | ||||
|   onClick: () => void; | ||||
|   isDisabled?: boolean; | ||||
|   isLoading?: boolean; | ||||
| } | ||||
|  | ||||
| const _CloseButton: React.FC<CloseButtonProps> = ({ onClick, isDisabled, isLoading, ...props }) => { | ||||
|   const { t } = useTranslation(); | ||||
|  | ||||
|   return ( | ||||
|     <IconButton | ||||
|       aria-label={t('common.close')} | ||||
|       colorScheme="gray" | ||||
|       onClick={onClick} | ||||
|       icon={<X size={20} />} | ||||
|       isLoading={isLoading} | ||||
|       isDisabled={isDisabled} | ||||
|       {...props} | ||||
|     /> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
| export const CloseButton = React.memo(_CloseButton); | ||||
							
								
								
									
										49
									
								
								src/components/Buttons/CreateButton/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,49 @@ | ||||
| import React from 'react'; | ||||
| import { Button, IconButton, Tooltip, useBreakpoint, SpaceProps } from '@chakra-ui/react'; | ||||
| import { Plus } from 'phosphor-react'; | ||||
| import { useTranslation } from 'react-i18next'; | ||||
|  | ||||
| export interface CreateButtonProps extends SpaceProps { | ||||
|   onClick?: () => void; | ||||
|   isDisabled?: boolean; | ||||
|   isLoading?: boolean; | ||||
|   isCompact?: boolean; | ||||
|   label?: string; | ||||
| } | ||||
|  | ||||
| const _CreateButton: React.FC<CreateButtonProps> = ({ onClick, isDisabled, isLoading, isCompact, label, ...props }) => { | ||||
|   const { t } = useTranslation(); | ||||
|   const breakpoint = useBreakpoint(); | ||||
|  | ||||
|   if (!isCompact && breakpoint !== 'base' && breakpoint !== 'sm') { | ||||
|     return ( | ||||
|       <Button | ||||
|         colorScheme="blue" | ||||
|         type="button" | ||||
|         onClick={onClick} | ||||
|         rightIcon={<Plus size={20} />} | ||||
|         isLoading={isLoading} | ||||
|         isDisabled={isDisabled} | ||||
|         {...props} | ||||
|       > | ||||
|         {label ?? t('common.create')} | ||||
|       </Button> | ||||
|     ); | ||||
|   } | ||||
|   return ( | ||||
|     <Tooltip label={label ?? t('common.create')}> | ||||
|       <IconButton | ||||
|         aria-label="Create" | ||||
|         colorScheme="blue" | ||||
|         type="button" | ||||
|         onClick={onClick} | ||||
|         icon={<Plus size={20} />} | ||||
|         isLoading={isLoading} | ||||
|         isDisabled={isDisabled} | ||||
|         {...props} | ||||
|       /> | ||||
|     </Tooltip> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
| export const CreateButton = React.memo(_CreateButton); | ||||
							
								
								
									
										61
									
								
								src/components/Buttons/DeleteButton/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,61 @@ | ||||
| import React from 'react'; | ||||
| import { Button, IconButton, Tooltip, useBreakpoint } from '@chakra-ui/react'; | ||||
| import { Trash } from 'phosphor-react'; | ||||
| import { useTranslation } from 'react-i18next'; | ||||
|  | ||||
| export interface DeleteButtonProps { | ||||
|   onClick: () => void; | ||||
|   isDisabled?: boolean; | ||||
|   isLoading?: boolean; | ||||
|   isCompact?: boolean; | ||||
|   label?: string; | ||||
|   ml?: string | number; | ||||
| } | ||||
|  | ||||
| const _DeleteButton: React.FC<DeleteButtonProps> = ({ | ||||
|   onClick, | ||||
|   isDisabled, | ||||
|   isLoading, | ||||
|   isCompact, | ||||
|   label, | ||||
|   ml, | ||||
|   ...props | ||||
| }) => { | ||||
|   const { t } = useTranslation(); | ||||
|   const breakpoint = useBreakpoint(); | ||||
|  | ||||
|   if (!isCompact && breakpoint !== 'base' && breakpoint !== 'sm') { | ||||
|     return ( | ||||
|       <Button | ||||
|         type="button" | ||||
|         colorScheme="red" | ||||
|         onClick={onClick} | ||||
|         rightIcon={<Trash size={20} />} | ||||
|         isLoading={isLoading} | ||||
|         isDisabled={isDisabled} | ||||
|         ml={ml} | ||||
|         {...props} | ||||
|       > | ||||
|         {label ?? t('crud.delete')} | ||||
|       </Button> | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   return ( | ||||
|     <Tooltip label={label ?? t('crud.delete')}> | ||||
|       <IconButton | ||||
|         colorScheme="red" | ||||
|         aria-label="delete" | ||||
|         type="button" | ||||
|         onClick={onClick} | ||||
|         icon={<Trash size={20} />} | ||||
|         isLoading={isLoading} | ||||
|         isDisabled={isDisabled} | ||||
|         ml={ml} | ||||
|         {...props} | ||||
|       /> | ||||
|     </Tooltip> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
| export const DeleteButton = React.memo(_DeleteButton); | ||||
							
								
								
									
										82
									
								
								src/components/Buttons/DeviceActionDropdown/RebootButton.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,82 @@ | ||||
| import * as React from 'react'; | ||||
| import { MenuItem, useToast } from '@chakra-ui/react'; | ||||
| import { useTranslation } from 'react-i18next'; | ||||
| import { useControllerStore } from 'contexts/ControllerSocketProvider/useStore'; | ||||
| import { useRebootDevice } from 'hooks/Network/Devices'; | ||||
| import { useMutationResult } from 'hooks/useMutationResult'; | ||||
| import { AxiosError } from 'models/Axios'; | ||||
| import { GatewayDevice } from 'models/Device'; | ||||
|  | ||||
| type Props = { | ||||
|   device: GatewayDevice; | ||||
|   refresh: () => void; | ||||
| }; | ||||
|  | ||||
| const RebootMenuItem = ({ device, refresh }: Props) => { | ||||
|   const { t } = useTranslation(); | ||||
|   const toast = useToast(); | ||||
|   const addEventListeners = useControllerStore((state) => state.addEventListeners); | ||||
|   const { mutateAsync: reboot } = useRebootDevice({ serialNumber: device.serialNumber }); | ||||
|   const { onSuccess: onRebootSuccess, onError: onRebootError } = useMutationResult({ | ||||
|     objName: t('devices.one'), | ||||
|     operationType: 'reboot', | ||||
|     refresh: () => { | ||||
|       refresh(); | ||||
|       addEventListeners([ | ||||
|         { | ||||
|           id: `device-connection-${device.serialNumber}`, | ||||
|           type: 'DEVICE_CONNECTION', | ||||
|           serialNumber: device.serialNumber, | ||||
|           callback: () => { | ||||
|             const id = `device-connection-notification-${device.serialNumber}`; | ||||
|  | ||||
|             if (!toast.isActive(id)) { | ||||
|               toast({ | ||||
|                 id, | ||||
|                 title: t('common.success'), | ||||
|                 description: t('controller.devices.finished_reboot', { serialNumber: device.serialNumber }), | ||||
|                 status: 'success', | ||||
|                 duration: 5000, | ||||
|                 isClosable: true, | ||||
|                 position: 'top-right', | ||||
|               }); | ||||
|             } | ||||
|           }, | ||||
|         }, | ||||
|         { | ||||
|           id: `device-disconnected-${device.serialNumber}`, | ||||
|           type: 'DEVICE_DISCONNECTION', | ||||
|           serialNumber: device.serialNumber, | ||||
|           callback: () => { | ||||
|             const id = `device-disconnection-notification-${device.serialNumber}`; | ||||
|  | ||||
|             if (!toast.isActive(id)) { | ||||
|               toast({ | ||||
|                 id, | ||||
|                 title: t('common.success'), | ||||
|                 description: t('controller.devices.started_reboot', { serialNumber: device.serialNumber }), | ||||
|                 status: 'success', | ||||
|                 duration: 5000, | ||||
|                 isClosable: true, | ||||
|                 position: 'top-right', | ||||
|               }); | ||||
|             } | ||||
|           }, | ||||
|         }, | ||||
|       ]); | ||||
|     }, | ||||
|   }); | ||||
|   const handleRebootClick = () => | ||||
|     reboot(undefined, { | ||||
|       onSuccess: () => { | ||||
|         onRebootSuccess(); | ||||
|       }, | ||||
|       onError: (e) => { | ||||
|         onRebootError(e as AxiosError); | ||||
|       }, | ||||
|     }); | ||||
|  | ||||
|   return <MenuItem onClick={handleRebootClick}>{t('commands.reboot')}</MenuItem>; | ||||
| }; | ||||
|  | ||||
| export default RebootMenuItem; | ||||
							
								
								
									
										195
									
								
								src/components/Buttons/DeviceActionDropdown/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,195 @@ | ||||
| import React from 'react'; | ||||
| import { Button, IconButton, Menu, MenuButton, MenuItem, MenuList, Spinner, Tooltip, useToast } from '@chakra-ui/react'; | ||||
| import axios from 'axios'; | ||||
| import { Wrench } from 'phosphor-react'; | ||||
| import { useTranslation } from 'react-i18next'; | ||||
| import RebootMenuItem from './RebootButton'; | ||||
| import { useControllerStore } from 'contexts/ControllerSocketProvider/useStore'; | ||||
| import { useBlinkDevice, useGetDeviceRtty } from 'hooks/Network/Devices'; | ||||
| import { useUpdateDeviceToLatest } from 'hooks/Network/Firmware'; | ||||
| import { useMutationResult } from 'hooks/useMutationResult'; | ||||
| import { GatewayDevice } from 'models/Device'; | ||||
|  | ||||
| interface Props { | ||||
|   device: GatewayDevice; | ||||
|   refresh: () => void; | ||||
|   isDisabled?: boolean; | ||||
|   onOpenScan: (serialNumber: string) => void; | ||||
|   onOpenFactoryReset: (serialNumber: string) => void; | ||||
|   onOpenUpgradeModal: (serialNumber: string) => void; | ||||
|   onOpenTrace: (serialNumber: string) => void; | ||||
|   onOpenEventQueue: (serialNumber: string) => void; | ||||
|   onOpenConfigureModal: (serialNumber: string) => void; | ||||
|   onOpenTelemetryModal: (serialNumber: string) => void; | ||||
|   onOpenScriptModal: (device: GatewayDevice) => void; | ||||
|   size?: 'sm' | 'md' | 'lg'; | ||||
|   isCompact?: boolean; | ||||
| } | ||||
|  | ||||
| const DeviceActionDropdown = ({ | ||||
|   device, | ||||
|   refresh, | ||||
|   isDisabled, | ||||
|   onOpenScan, | ||||
|   onOpenFactoryReset, | ||||
|   onOpenTrace, | ||||
|   onOpenUpgradeModal, | ||||
|   onOpenEventQueue, | ||||
|   onOpenTelemetryModal, | ||||
|   onOpenConfigureModal, | ||||
|   onOpenScriptModal, | ||||
|   size, | ||||
|   isCompact, | ||||
| }: Props) => { | ||||
|   const { t } = useTranslation(); | ||||
|   const toast = useToast(); | ||||
|   const addEventListeners = useControllerStore((state) => state.addEventListeners); | ||||
|   const { refetch: getRtty, isInitialLoading: isRtty } = useGetDeviceRtty({ | ||||
|     serialNumber: device.serialNumber, | ||||
|     extraId: 'inventory-modal', | ||||
|   }); | ||||
|   const { mutateAsync: blink } = useBlinkDevice({ serialNumber: device.serialNumber }); | ||||
|   const { onSuccess: onBlinkSuccess, onError: onBlinkError } = useMutationResult({ | ||||
|     objName: t('devices.one'), | ||||
|     operationType: 'blink', | ||||
|     refresh, | ||||
|   }); | ||||
|   const updateToLatest = useUpdateDeviceToLatest({ serialNumber: device.serialNumber, compatible: device.compatible }); | ||||
|  | ||||
|   const handleBlinkClick = () => { | ||||
|     blink(undefined, { | ||||
|       onError: (e) => { | ||||
|         if (axios.isAxiosError(e)) onBlinkError(e); | ||||
|       }, | ||||
|     }); | ||||
|     onBlinkSuccess(); | ||||
|   }; | ||||
|   const handleOpenScan = () => onOpenScan(device.serialNumber); | ||||
|   const handleOpenFactoryReset = () => onOpenFactoryReset(device.serialNumber); | ||||
|   const handleOpenUpgrade = () => onOpenUpgradeModal(device.serialNumber); | ||||
|   const handleOpenTrace = () => onOpenTrace(device.serialNumber); | ||||
|   const handleOpenQueue = () => onOpenEventQueue(device.serialNumber); | ||||
|   const handleOpenConfigure = () => onOpenConfigureModal(device.serialNumber); | ||||
|   const handleOpenTelemetry = () => onOpenTelemetryModal(device.serialNumber); | ||||
|   const handleOpenScript = () => onOpenScriptModal(device); | ||||
|   const handleUpdateToLatest = () => { | ||||
|     updateToLatest.mutate( | ||||
|       { keepRedirector: true }, | ||||
|       { | ||||
|         onSuccess: () => { | ||||
|           toast({ | ||||
|             id: `upgrade-to-latest-start-${device.serialNumber}`, | ||||
|             title: t('common.success'), | ||||
|             description: t('controller.devices.sent_upgrade_to_latest'), | ||||
|             status: 'success', | ||||
|             duration: 5000, | ||||
|             isClosable: true, | ||||
|             position: 'top-right', | ||||
|           }); | ||||
|           addEventListeners([ | ||||
|             { | ||||
|               id: `device-connection-upgrade-${device.serialNumber}`, | ||||
|               type: 'DEVICE_CONNECTION', | ||||
|               serialNumber: device.serialNumber, | ||||
|               callback: () => { | ||||
|                 const id = `device-connection-upgrade-notification-${device.serialNumber}`; | ||||
|  | ||||
|                 if (!toast.isActive(id)) { | ||||
|                   toast({ | ||||
|                     id, | ||||
|                     title: t('common.success'), | ||||
|                     description: t('controller.devices.finished_upgrade', { serialNumber: device.serialNumber }), | ||||
|                     status: 'success', | ||||
|                     duration: 5000, | ||||
|                     isClosable: true, | ||||
|                     position: 'top-right', | ||||
|                   }); | ||||
|                 } | ||||
|               }, | ||||
|             }, | ||||
|             { | ||||
|               id: `device-disconnected-upgrade-${device.serialNumber}`, | ||||
|               type: 'DEVICE_DISCONNECTION', | ||||
|               serialNumber: device.serialNumber, | ||||
|               callback: () => { | ||||
|                 const id = `device-disconnection-upgrade-notification-${device.serialNumber}`; | ||||
|  | ||||
|                 if (!toast.isActive(id)) { | ||||
|                   toast({ | ||||
|                     id, | ||||
|                     title: t('common.success'), | ||||
|                     description: t('controller.devices.started_upgrade', { serialNumber: device.serialNumber }), | ||||
|                     status: 'success', | ||||
|                     duration: 5000, | ||||
|                     isClosable: true, | ||||
|                     position: 'top-right', | ||||
|                   }); | ||||
|                 } | ||||
|               }, | ||||
|             }, | ||||
|           ]); | ||||
|         }, | ||||
|         onError: (e) => { | ||||
|           if (axios.isAxiosError(e)) { | ||||
|             toast({ | ||||
|               id: `upgrade-to-latest-error-${device.serialNumber}`, | ||||
|               title: t('common.error'), | ||||
|               description: e?.response?.data?.ErrorDescription, | ||||
|               status: 'error', | ||||
|               duration: 5000, | ||||
|               isClosable: true, | ||||
|               position: 'top-right', | ||||
|             }); | ||||
|           } | ||||
|         }, | ||||
|       }, | ||||
|     ); | ||||
|   }; | ||||
|   const handleConnectClick = () => getRtty(); | ||||
|  | ||||
|   return ( | ||||
|     <Menu> | ||||
|       <Tooltip label={t('commands.other')}> | ||||
|         {size === undefined || isCompact ? ( | ||||
|           <MenuButton | ||||
|             as={IconButton} | ||||
|             aria-label="Commands" | ||||
|             icon={isRtty ? <Spinner /> : <Wrench size={20} />} | ||||
|             size={size ?? 'sm'} | ||||
|             isDisabled={isDisabled} | ||||
|             ml={2} | ||||
|           /> | ||||
|         ) : ( | ||||
|           <MenuButton | ||||
|             as={Button} | ||||
|             aria-label="Commands" | ||||
|             rightIcon={isRtty ? <Spinner /> : <Wrench size={20} />} | ||||
|             size={size ?? 'sm'} | ||||
|             isDisabled={isDisabled} | ||||
|             ml={2} | ||||
|           > | ||||
|             {t('commands.other')} | ||||
|           </MenuButton> | ||||
|         )} | ||||
|       </Tooltip> | ||||
|       <MenuList> | ||||
|         <MenuItem onClick={handleBlinkClick}>{t('commands.blink')}</MenuItem> | ||||
|         <MenuItem onClick={handleOpenConfigure}>{t('controller.configure.title')}</MenuItem> | ||||
|         <MenuItem onClick={handleConnectClick}>{t('commands.connect')}</MenuItem> | ||||
|         <MenuItem onClick={handleOpenQueue}>{t('controller.queue.title')}</MenuItem> | ||||
|         <MenuItem onClick={handleOpenFactoryReset}>{t('commands.factory_reset')}</MenuItem> | ||||
|         <MenuItem onClick={handleOpenUpgrade}>{t('commands.firmware_upgrade')}</MenuItem> | ||||
|         <RebootMenuItem device={device} refresh={refresh} /> | ||||
|         <MenuItem onClick={handleOpenTelemetry}>{t('controller.telemetry.title')}</MenuItem> | ||||
|         <MenuItem onClick={handleOpenScript}>{t('script.one')}</MenuItem> | ||||
|         <MenuItem onClick={handleOpenTrace}>{t('controller.devices.trace')}</MenuItem> | ||||
|         <MenuItem onClick={handleUpdateToLatest} hidden> | ||||
|           {t('premium.toolbox.upgrade_to_latest')} | ||||
|         </MenuItem> | ||||
|         <MenuItem onClick={handleOpenScan}>{t('commands.wifiscan')}</MenuItem> | ||||
|       </MenuList> | ||||
|     </Menu> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
| export default React.memo(DeviceActionDropdown); | ||||
							
								
								
									
										46
									
								
								src/components/Buttons/EditButton/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,46 @@ | ||||
| import React from 'react'; | ||||
| import { IconButton, Button, Tooltip, useBreakpoint } from '@chakra-ui/react'; | ||||
| import { Pen } from 'phosphor-react'; | ||||
|  | ||||
| export interface EditButtonProps { | ||||
|   onClick: () => void; | ||||
|   isDisabled?: boolean; | ||||
|   isLoading?: boolean; | ||||
|   isCompact?: boolean; | ||||
|   label?: string; | ||||
|   ml?: string | number; | ||||
| } | ||||
|  | ||||
| const _EditButton: React.FC<EditButtonProps> = ({ onClick, label, isDisabled, isLoading, isCompact, ...props }) => { | ||||
|   const breakpoint = useBreakpoint(); | ||||
|  | ||||
|   if (!isCompact && breakpoint !== 'base' && breakpoint !== 'sm') { | ||||
|     return ( | ||||
|       <Button | ||||
|         colorScheme="gray" | ||||
|         onClick={onClick} | ||||
|         rightIcon={<Pen size={20} />} | ||||
|         isLoading={isLoading} | ||||
|         isDisabled={isDisabled} | ||||
|         {...props} | ||||
|       > | ||||
|         {label} | ||||
|       </Button> | ||||
|     ); | ||||
|   } | ||||
|   return ( | ||||
|     <Tooltip label={label}> | ||||
|       <IconButton | ||||
|         aria-label="edit" | ||||
|         colorScheme="gray" | ||||
|         onClick={onClick} | ||||
|         icon={<Pen size={20} />} | ||||
|         isLoading={isLoading} | ||||
|         isDisabled={isDisabled} | ||||
|         {...props} | ||||
|       /> | ||||
|     </Tooltip> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
| export const EditButton = React.memo(_EditButton); | ||||
							
								
								
									
										79
									
								
								src/components/Buttons/FileInputButton/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,79 @@ | ||||
| import React, { useEffect, useState } from 'react'; | ||||
| import { FormControl, Input, InputGroup } from '@chakra-ui/react'; | ||||
| import { v4 as uuid } from 'uuid'; | ||||
|  | ||||
| export interface FileInputButtonProps { | ||||
|   value: string; | ||||
|   setValue: (v: string, file?: File) => void; | ||||
|   setFileName?: (v: string) => void; | ||||
|   refreshId: string; | ||||
|   accept: string; | ||||
|   isHidden?: boolean; | ||||
|   isStringFile?: boolean; | ||||
|   sizeLimit?: number; | ||||
| } | ||||
|  | ||||
| const _FileInputButton: React.FC<FileInputButtonProps> = ({ | ||||
|   value, | ||||
|   setValue, | ||||
|   setFileName, | ||||
|   refreshId, | ||||
|   accept, | ||||
|   isHidden, | ||||
|   isStringFile, | ||||
|   sizeLimit, | ||||
| }) => { | ||||
|   const [fileKey, setFileKey] = useState(uuid()); | ||||
|   let fileReader: FileReader | undefined; | ||||
|  | ||||
|   const handleStringFileRead = () => { | ||||
|     if (fileReader) { | ||||
|       const content = fileReader.result; | ||||
|       if (content) { | ||||
|         setValue(content as string); | ||||
|       } | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   const changeFile = (e: React.ChangeEvent<HTMLInputElement>) => { | ||||
|     const file = e.target.files ? e.target.files[0] : undefined; | ||||
|     if (file) { | ||||
|       if (sizeLimit && file.size > sizeLimit) { | ||||
|         setFileKey(uuid()); | ||||
|       } else { | ||||
|         const newVal = URL.createObjectURL(file); | ||||
|         if (!isStringFile) { | ||||
|           setValue(newVal, file); | ||||
|           if (setFileName) setFileName(file.name ?? ''); | ||||
|         } else { | ||||
|           fileReader = new FileReader(); | ||||
|           if (setFileName) setFileName(file.name); | ||||
|           fileReader.onloadend = handleStringFileRead; | ||||
|           fileReader.readAsText(file); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   useEffect(() => { | ||||
|     if (value === '') setFileKey(uuid()); | ||||
|   }, [refreshId, value]); | ||||
|  | ||||
|   return ( | ||||
|     <FormControl hidden={isHidden}> | ||||
|       <InputGroup> | ||||
|         <Input | ||||
|           borderRadius="15px" | ||||
|           pt={1} | ||||
|           fontSize="sm" | ||||
|           type="file" | ||||
|           onChange={changeFile} | ||||
|           key={fileKey} | ||||
|           accept={accept} | ||||
|         /> | ||||
|       </InputGroup> | ||||
|     </FormControl> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
| export const FileInputButton = React.memo(_FileInputButton); | ||||
							
								
								
									
										62
									
								
								src/components/Buttons/RefreshButton/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,62 @@ | ||||
| import React from 'react'; | ||||
| import { Button, IconButton, ThemeTypings, Tooltip, useBreakpoint } from '@chakra-ui/react'; | ||||
| import { ArrowsClockwise } from 'phosphor-react'; | ||||
| import { useTranslation } from 'react-i18next'; | ||||
|  | ||||
| export interface RefreshButtonProps { | ||||
|   onClick: () => void; | ||||
|   isDisabled?: boolean; | ||||
|   isFetching?: boolean; | ||||
|   isCompact?: boolean; | ||||
|   ml?: string | number; | ||||
|   size?: 'sm' | 'md' | 'lg'; | ||||
|   colorScheme?: ThemeTypings['colorSchemes']; | ||||
| } | ||||
|  | ||||
| const _RefreshButton: React.FC<RefreshButtonProps> = ({ | ||||
|   onClick, | ||||
|   isDisabled, | ||||
|   isFetching, | ||||
|   isCompact, | ||||
|   ml, | ||||
|   size, | ||||
|   ...props | ||||
| }) => { | ||||
|   const { t } = useTranslation(); | ||||
|   const breakpoint = useBreakpoint(); | ||||
|  | ||||
|   if (!isCompact && breakpoint !== 'base' && breakpoint !== 'sm') { | ||||
|     return ( | ||||
|       <Button | ||||
|         minWidth="112px" | ||||
|         colorScheme="gray" | ||||
|         onClick={onClick} | ||||
|         rightIcon={<ArrowsClockwise size={20} />} | ||||
|         isDisabled={isDisabled} | ||||
|         isLoading={isFetching} | ||||
|         ml={ml} | ||||
|         size={size} | ||||
|         {...props} | ||||
|       > | ||||
|         {t('common.refresh')} | ||||
|       </Button> | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   return ( | ||||
|     <Tooltip label={t('common.refresh')}> | ||||
|       <IconButton | ||||
|         aria-label="refresh" | ||||
|         colorScheme="gray" | ||||
|         onClick={onClick} | ||||
|         icon={<ArrowsClockwise size={20} />} | ||||
|         isDisabled={isDisabled} | ||||
|         isLoading={isFetching} | ||||
|         ml={ml} | ||||
|         {...props} | ||||
|       /> | ||||
|     </Tooltip> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
| export const RefreshButton = React.memo(_RefreshButton); | ||||
							
								
								
									
										59
									
								
								src/components/Buttons/ResponsiveButton/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,59 @@ | ||||
| import React from 'react'; | ||||
| import { Button, IconButton, SpaceProps, Tooltip, useBreakpoint } from '@chakra-ui/react'; | ||||
|  | ||||
| export interface ResponsiveButtonProps extends SpaceProps { | ||||
|   onClick: () => void; | ||||
|   isDisabled?: boolean; | ||||
|   isLoading?: boolean; | ||||
|   isCompact?: boolean; | ||||
|   color: string; | ||||
|   label: string; | ||||
|   icon?: React.ReactElement; | ||||
| } | ||||
|  | ||||
| const _ResponsiveButton: React.FC<ResponsiveButtonProps> = ({ | ||||
|   onClick, | ||||
|   isDisabled, | ||||
|   isLoading, | ||||
|   isCompact, | ||||
|   color, | ||||
|   label, | ||||
|   icon, | ||||
|   ...props | ||||
| }) => { | ||||
|   const breakpoint = useBreakpoint(); | ||||
|  | ||||
|   if (!isCompact && breakpoint !== 'base' && breakpoint !== 'sm') { | ||||
|     return ( | ||||
|       <Button | ||||
|         colorScheme={color} | ||||
|         type="button" | ||||
|         onClick={onClick} | ||||
|         // @ts-ignore | ||||
|         rightIcon={icon} | ||||
|         isLoading={isLoading} | ||||
|         isDisabled={isDisabled} | ||||
|         {...props} | ||||
|       > | ||||
|         {label} | ||||
|       </Button> | ||||
|     ); | ||||
|   } | ||||
|   return ( | ||||
|     <Tooltip label={label}> | ||||
|       <IconButton | ||||
|         aria-label={label} | ||||
|         colorScheme={color} | ||||
|         type="button" | ||||
|         onClick={onClick} | ||||
|         // @ts-ignore | ||||
|         icon={icon} | ||||
|         isLoading={isLoading} | ||||
|         isDisabled={isDisabled} | ||||
|         {...props} | ||||
|       /> | ||||
|     </Tooltip> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
| export const ResponsiveButton = React.memo(_ResponsiveButton); | ||||
							
								
								
									
										60
									
								
								src/components/Buttons/SaveButton/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,60 @@ | ||||
| import React from 'react'; | ||||
| import { Button, IconButton, Tooltip, useBreakpoint } from '@chakra-ui/react'; | ||||
| import { FloppyDisk } from 'phosphor-react'; | ||||
| import { useTranslation } from 'react-i18next'; | ||||
|  | ||||
| export interface SaveButtonProps | ||||
|   extends React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement> { | ||||
|   onClick: () => void; | ||||
|   isDisabled?: boolean; | ||||
|   isLoading?: boolean; | ||||
|   isCompact?: boolean; | ||||
|   isDirty?: boolean; | ||||
|   dirtyCheck?: boolean; | ||||
|   ml?: string | number; | ||||
| } | ||||
|  | ||||
| const _SaveButton: React.FC<SaveButtonProps> = ({ | ||||
|   onClick, | ||||
|   isDisabled, | ||||
|   isLoading, | ||||
|   isCompact, | ||||
|   isDirty, | ||||
|   dirtyCheck, | ||||
|   ...props | ||||
| }) => { | ||||
|   const { t } = useTranslation(); | ||||
|   const breakpoint = useBreakpoint(); | ||||
|  | ||||
|   if (!isCompact && breakpoint !== 'base' && breakpoint !== 'sm') { | ||||
|     return ( | ||||
|       <Button | ||||
|         colorScheme="blue" | ||||
|         type="submit" | ||||
|         onClick={onClick} | ||||
|         rightIcon={<FloppyDisk size={20} />} | ||||
|         isLoading={isLoading} | ||||
|         isDisabled={isDisabled || (dirtyCheck && !isDirty)} | ||||
|         {...props} | ||||
|       > | ||||
|         {t('common.save')} | ||||
|       </Button> | ||||
|     ); | ||||
|   } | ||||
|   return ( | ||||
|     <Tooltip label={t('common.save')}> | ||||
|       <IconButton | ||||
|         aria-label="save" | ||||
|         colorScheme="blue" | ||||
|         type="submit" | ||||
|         onClick={onClick} | ||||
|         icon={<FloppyDisk size={20} />} | ||||
|         isLoading={isLoading} | ||||
|         isDisabled={isDisabled || (dirtyCheck && !isDirty)} | ||||
|         {...props} | ||||
|       /> | ||||
|     </Tooltip> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
| export const SaveButton = React.memo(_SaveButton); | ||||