[WIFI-11223] Migrating to prov-ui style

Signed-off-by: Charles <charles.bourque96@gmail.com>
This commit is contained in:
Charles
2022-10-19 11:07:52 +01:00
parent 31bdda8bf8
commit 2e7836eec3
458 changed files with 37270 additions and 36864 deletions

1
.env Normal file
View File

@@ -0,0 +1 @@
VITE_UCENTRALSEC_URL="https://ucentral.dpaas.arilia.com:16001"

View File

@@ -1,4 +1,10 @@
/src/assets
/build
/node_modules
/dist
/icons
helm
docker-entrypoint.d
.dockerignore
DockerFile
.github

View File

@@ -1,22 +1,79 @@
{
"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/"],
"plugins": ["import", "react", "@typescript-eslint", "prettier"],
"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"
],
"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"],
"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 +81,5 @@
"paths": ["src"]
}
}
},
"parser": "babel-eslint",
"parserOptions": {
"sourceType": "module",
"allowImportExportEverywhere": false,
"codeFrame": false
}
}
}

5
.gitignore vendored
View File

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

View File

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

View File

@@ -1,7 +1,7 @@
{
"printWidth": 100,
"trailingComma": "all",
"tabWidth": 2,
"semi": true,
"singleQuote": true
}
{
"printWidth": 120,
"trailingComma": "all",
"tabWidth": 2,
"semi": true,
"singleQuote": true
}

View File

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

View File

@@ -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'),
};

View File

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

View File

@@ -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()],
});

View File

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

20
index.html Normal file
View File

@@ -0,0 +1,20 @@
<!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" />
<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>

View File

@@ -1,9 +0,0 @@
{
"compilerOptions": {
"baseUrl": "src",
"paths": {
"*": ["*"]
}
},
"include": ["src"]
}

28256
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,101 +1,87 @@
{
"name": "ucentral-client",
"version": "2.7.0(9)",
"version": "2.8.0(0)",
"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.61",
"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",
"react-query": "^3.39.2",
"react-router-dom": "^6.4.2",
"react-table": "^7.8.0",
"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/uuid": "^8.3.4",
"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": [

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

BIN
public/apple-touch-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

9
public/browserconfig.xml Normal file
View 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>

View File

@@ -1,4 +0,0 @@
{
"DEFAULT_UCENTRALSEC_URL": "https://ucentral.dpaas.arilia.com:16001",
"ALLOW_UCENTRALSEC_CHANGE": false
}

View File

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 104 KiB

View File

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 80 KiB

View File

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 80 KiB

View File

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 75 KiB

View File

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 75 KiB

View File

Before

Width:  |  Height:  |  Size: 218 KiB

After

Width:  |  Height:  |  Size: 218 KiB

View File

Before

Width:  |  Height:  |  Size: 158 KiB

After

Width:  |  Height:  |  Size: 158 KiB

View File

Before

Width:  |  Height:  |  Size: 140 KiB

After

Width:  |  Height:  |  Size: 140 KiB

View File

Before

Width:  |  Height:  |  Size: 121 KiB

After

Width:  |  Height:  |  Size: 121 KiB

View File

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

View File

Before

Width:  |  Height:  |  Size: 192 KiB

After

Width:  |  Height:  |  Size: 192 KiB

View File

Before

Width:  |  Height:  |  Size: 197 KiB

After

Width:  |  Height:  |  Size: 197 KiB

View File

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

View File

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 59 KiB

View File

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 59 KiB

View File

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 51 KiB

View File

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 72 KiB

View File

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 72 KiB

View File

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 98 KiB

View File

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 89 KiB

View File

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 89 KiB

View File

Before

Width:  |  Height:  |  Size: 204 KiB

After

Width:  |  Height:  |  Size: 204 KiB

View File

Before

Width:  |  Height:  |  Size: 159 KiB

After

Width:  |  Height:  |  Size: 159 KiB

View File

Before

Width:  |  Height:  |  Size: 159 KiB

After

Width:  |  Height:  |  Size: 159 KiB

View File

Before

Width:  |  Height:  |  Size: 103 KiB

After

Width:  |  Height:  |  Size: 103 KiB

View File

Before

Width:  |  Height:  |  Size: 103 KiB

After

Width:  |  Height:  |  Size: 103 KiB

View File

Before

Width:  |  Height:  |  Size: 103 KiB

After

Width:  |  Height:  |  Size: 103 KiB

View File

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 59 KiB

View File

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 80 KiB

View File

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 80 KiB

BIN
public/favicon-16x16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1021 B

BIN
public/favicon-32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 202 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,165 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.2.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 141.5 185.6" style="enable-background:new 0 0 141.5 185.6;" xml:space="preserve">
<style type="text/css">
.st0{fill:#414141;}
.st1{fill:#FFFFFF;}
.st2{fill:#FED206;}
.st3{fill:#EB6F53;}
.st4{fill:#3BA9B6;}
</style>
<g>
<g>
<path class="st0" d="M120.7,183.9H21.5c-10.8,0-19.5-8.7-19.5-19.5V20.5c0-10.8,8.7-19.5,19.5-19.5h99.2
c10.8,0,19.5,8.7,19.5,19.5v143.9C140.2,175.2,131.5,183.9,120.7,183.9z"/>
<g>
<g>
<g>
<path class="st1" d="M46.3,166.2v-3.4h-1.2v-0.6h3.1v0.6H47v3.4H46.3z"/>
</g>
<g>
<path class="st1" d="M49,166.2v-4h2.7v0.6h-2v1h2v0.6h-2v1.1h2v0.6H49z"/>
</g>
<g>
<path class="st1" d="M52.6,166.2v-4h0.7v3.4h1.8v0.6H52.6z"/>
</g>
<g>
<path class="st1" d="M55.7,166.2v-4h2.7v0.6h-2v1h2v0.6h-2v1.1h2v0.6H55.7z"/>
</g>
<g>
<path class="st1" d="M59.1,164.2c0-1.2,0.9-2.1,2.1-2.1c0.8,0,1.3,0.4,1.6,0.9l-0.6,0.3c-0.2-0.3-0.6-0.6-1-0.6
c-0.8,0-1.4,0.6-1.4,1.4c0,0.8,0.6,1.4,1.4,1.4c0.4,0,0.8-0.3,1-0.6l0.6,0.3c-0.3,0.5-0.8,0.9-1.6,0.9
C60,166.3,59.1,165.5,59.1,164.2z"/>
</g>
<g>
<path class="st1" d="M63.2,164.2c0-1.2,0.8-2.1,2-2.1c1.2,0,2,0.9,2,2.1c0,1.2-0.8,2.1-2,2.1C64,166.3,63.2,165.4,63.2,164.2z
M66.5,164.2c0-0.8-0.5-1.4-1.3-1.4c-0.8,0-1.3,0.6-1.3,1.4c0,0.8,0.5,1.4,1.3,1.4C66,165.7,66.5,165,66.5,164.2z"/>
</g>
<g>
<path class="st1" d="M71.3,166.2v-3.1l-1.2,3.1h-0.3l-1.2-3.1v3.1h-0.7v-4h1l1.1,2.7l1.1-2.7h1v4H71.3z"/>
</g>
<g>
<path class="st1" d="M75.7,166.2v-4h0.7v4H75.7z"/>
</g>
<g>
<path class="st1" d="M80.4,166.2l-2.1-2.8v2.8h-0.7v-4h0.7l2,2.8v-2.8h0.7v4H80.4z"/>
</g>
<g>
<path class="st1" d="M82.3,166.2v-4H85v0.6h-2v1h2v0.6h-2v1.7H82.3z"/>
</g>
<g>
<path class="st1" d="M87.9,166.2l-0.9-1.5h-0.7v1.5h-0.7v-4h1.7c0.8,0,1.3,0.5,1.3,1.2c0,0.7-0.5,1.1-0.9,1.2l1,1.6H87.9z
M88,163.5c0-0.4-0.3-0.6-0.7-0.6h-1v1.3h1C87.7,164.1,88,163.9,88,163.5z"/>
</g>
<g>
<path class="st1" d="M92.4,166.2l-0.3-0.8h-1.8l-0.3,0.8h-0.8l1.6-4h0.9l1.6,4H92.4z M91.2,162.9l-0.7,1.9h1.4L91.2,162.9z"/>
</g>
<g>
<path class="st1" d="M95.8,166.2v-4h1.5c0.8,0,1.2,0.5,1.2,1.2c0,0.6-0.4,1.2-1.2,1.2h-1.2v1.7H95.8z M98.2,163.4
c0-0.5-0.3-0.9-0.9-0.9h-1.1v1.7h1.1C97.8,164.3,98.2,163.9,98.2,163.4z"/>
</g>
<g>
<path class="st1" d="M101.5,166.2l-1.1-1.6h-0.9v1.6h-0.3v-4h1.5c0.7,0,1.2,0.4,1.2,1.2c0,0.7-0.5,1.1-1.1,1.1l1.2,1.7H101.5z
M101.6,163.4c0-0.5-0.4-0.9-0.9-0.9h-1.1v1.7h1.1C101.2,164.3,101.6,163.9,101.6,163.4z"/>
</g>
<g>
<path class="st1" d="M102.8,164.2c0-1.2,0.8-2.1,1.9-2.1c1.2,0,1.9,0.9,1.9,2.1c0,1.2-0.8,2.1-1.9,2.1
C103.6,166.3,102.8,165.4,102.8,164.2z M106.3,164.2c0-1-0.6-1.7-1.6-1.7c-1,0-1.6,0.7-1.6,1.7c0,1,0.6,1.7,1.6,1.7
C105.7,166,106.3,165.2,106.3,164.2z"/>
</g>
<g>
<path class="st1" d="M106.9,165.8l0.2-0.3c0.2,0.2,0.4,0.4,0.8,0.4c0.5,0,0.9-0.4,0.9-0.9v-2.8h0.3v2.8c0,0.8-0.5,1.2-1.2,1.2
C107.5,166.3,107.2,166.1,106.9,165.8z"/>
</g>
<g>
<path class="st1" d="M110.4,166.2v-4h2.5v0.3h-2.2v1.5h2.1v0.3h-2.1v1.6h2.2v0.3H110.4z"/>
</g>
<g>
<path class="st1" d="M113.5,164.2c0-1.2,0.9-2.1,2-2.1c0.6,0,1.1,0.3,1.5,0.7l-0.3,0.2c-0.3-0.3-0.7-0.6-1.2-0.6
c-0.9,0-1.7,0.7-1.7,1.7c0,1,0.7,1.7,1.7,1.7c0.5,0,0.9-0.2,1.2-0.6l0.3,0.2c-0.4,0.4-0.8,0.7-1.5,0.7
C114.4,166.3,113.5,165.5,113.5,164.2z"/>
</g>
<g>
<path class="st1" d="M118.7,166.2v-3.7h-1.3v-0.3h2.9v0.3H119v3.7H118.7z"/>
</g>
</g>
<g>
<polygon class="st1" points="26.3,163.8 31.6,158.5 36.9,163.8 37.7,163.8 31.6,157.6 25.5,163.8 "/>
<polygon class="st1" points="36.9,164.7 31.6,170 26.3,164.7 25.5,164.7 31.6,170.8 37.7,164.7 "/>
<polygon class="st1" points="31,163.8 36.3,158.5 41.6,163.8 42.5,163.8 36.3,157.6 30.2,163.8 "/>
<polygon class="st1" points="41.6,164.7 36.3,170 31,164.7 30.2,164.7 36.3,170.8 42.5,164.7 "/>
</g>
</g>
<g>
<path class="st1" d="M33.2,100.7c-4.6,0-8.3,3.7-8.3,8.3s3.7,8.3,8.3,8.3s8.3-3.7,8.3-8.3S37.8,100.7,33.2,100.7z"/>
</g>
<g>
<g>
<g>
<path class="st2" d="M33.2,35.2c40.7,0,73.8,33.1,73.8,73.8c0,0.7,0,1.4,0,2.1c0,1.7,0.6,3.3,1.7,4.6c1.2,1.2,2.8,1.9,4.5,2
l0.2,0c3.5,0,6.3-2.7,6.4-6.2c0-0.8,0-1.7,0-2.5c0-47.7-38.8-86.6-86.6-86.6c-0.8,0-1.7,0-2.5,0c-1.7,0-3.3,0.8-4.5,2
c-1.2,1.2-1.8,2.9-1.7,4.6c0.1,3.5,3,6.3,6.6,6.2C31.8,35.2,32.5,35.2,33.2,35.2z"/>
</g>
</g>
</g>
<g>
<g>
<g>
<path class="st3" d="M33.2,60.5c26.7,0,48.5,21.7,48.5,48.5c0,0.6,0,1.3,0,2c-0.1,1.7,0.5,3.3,1.7,4.6c1.2,1.3,2.7,2,4.4,2.1
c1.7,0.1,3.3-0.5,4.6-1.7c1.2-1.2,2-2.7,2-4.4c0-0.9,0.1-1.8,0.1-2.6c0-33.8-27.5-61.2-61.2-61.2c-0.8,0-1.6,0-2.6,0.1
c-1.7,0.1-3.3,0.8-4.4,2.1c-1.2,1.3-1.8,2.9-1.7,4.6s0.8,3.3,2.1,4.4c1.3,1.2,2.9,1.8,4.6,1.7C31.9,60.5,32.6,60.5,33.2,60.5z"
/>
</g>
</g>
</g>
<g>
<g>
<g>
<path class="st4" d="M33.2,86.7c12.3,0,22.3,10,22.3,22.3c0,0.5,0,1.1-0.1,1.8c-0.3,3.5,2.3,6.6,5.8,6.9
c3.5,0.3,6.6-2.3,6.9-5.8c0.1-1,0.1-1.9,0.1-2.8c0-19.3-15.7-35.1-35.1-35.1c-0.9,0-1.8,0-2.8,0.1c-1.7,0.1-3.2,0.9-4.3,2.2
c-1.1,1.3-1.6,2.9-1.5,4.6c0.1,1.7,0.9,3.2,2.2,4.3c1.3,1.1,2.9,1.6,4.6,1.5C32.1,86.7,32.7,86.7,33.2,86.7z"/>
</g>
</g>
</g>
</g>
<g>
<path class="st1" d="M35.8,130.4c1.1,0.6,2.1,1.5,2.7,2.6c0.7,1.1,1,2.3,1,3.7s-0.3,2.6-1,3.7c-0.7,1.1-1.6,2-2.7,2.6
c-1.1,0.6-2.4,1-3.8,1s-2.7-0.3-3.8-1c-1.1-0.6-2.1-1.5-2.7-2.6c-0.7-1.1-1-2.3-1-3.7c0-1.3,0.3-2.6,1-3.7c0.7-1.1,1.6-2,2.7-2.6
c1.1-0.6,2.4-0.9,3.8-0.9C33.4,129.5,34.7,129.8,35.8,130.4z M29.9,132.9c-0.7,0.4-1.2,0.9-1.6,1.6s-0.6,1.4-0.6,2.2
c0,0.8,0.2,1.6,0.6,2.3c0.4,0.7,0.9,1.2,1.6,1.6c0.7,0.4,1.4,0.6,2.1,0.6c0.8,0,1.5-0.2,2.1-0.6c0.6-0.4,1.2-0.9,1.5-1.6
c0.4-0.7,0.6-1.4,0.6-2.3c0-0.8-0.2-1.6-0.6-2.2s-0.9-1.2-1.5-1.6c-0.6-0.4-1.4-0.6-2.1-0.6C31.3,132.3,30.6,132.5,29.9,132.9z"/>
<path class="st1" d="M50.6,133.6c0.8,0.5,1.4,1.1,1.8,2c0.4,0.8,0.6,1.8,0.6,2.9c0,1.1-0.2,2-0.6,2.8c-0.4,0.8-1,1.5-1.8,1.9
c-0.8,0.5-1.6,0.7-2.6,0.7c-0.7,0-1.4-0.1-2-0.4s-1.1-0.7-1.5-1.2v5.4h-3.1V133h3.1v1.6c0.4-0.5,0.9-1,1.4-1.2s1.2-0.4,2-0.4
C48.9,132.9,49.8,133.1,50.6,133.6z M49.1,140.5c0.5-0.6,0.7-1.3,0.7-2.2c0-0.9-0.2-1.6-0.7-2.1c-0.5-0.6-1.1-0.8-1.9-0.8
s-1.4,0.3-1.9,0.8c-0.5,0.6-0.8,1.3-0.8,2.1c0,0.9,0.2,1.6,0.8,2.2s1.1,0.8,1.9,0.8S48.6,141,49.1,140.5z"/>
<path class="st1" d="M63.4,134.4c0.9,1,1.4,2.4,1.4,4.2c0,0.3,0,0.6,0,0.7H57c0.2,0.7,0.5,1.2,1,1.6c0.5,0.4,1.1,0.6,1.8,0.6
c0.5,0,1-0.1,1.5-0.3s0.9-0.5,1.3-0.9l1.6,1.6c-0.5,0.6-1.2,1.1-2,1.4c-0.8,0.3-1.6,0.5-2.6,0.5c-1.1,0-2.1-0.2-3-0.7
s-1.5-1.1-2-1.9c-0.5-0.8-0.7-1.8-0.7-2.9c0-1.1,0.2-2.1,0.7-2.9s1.1-1.5,2-1.9c0.8-0.5,1.8-0.7,2.9-0.7
C61.2,132.9,62.5,133.4,63.4,134.4z M61.8,137.5c0-0.7-0.3-1.3-0.7-1.7s-1-0.6-1.7-0.6c-0.7,0-1.2,0.2-1.7,0.6
c-0.4,0.4-0.7,1-0.9,1.7H61.8z"/>
<path class="st1" d="M76.2,134c0.7,0.7,1.1,1.7,1.1,3v6.8h-3.1v-5.9c0-0.7-0.2-1.2-0.6-1.6s-0.9-0.6-1.5-0.6
c-0.8,0-1.4,0.3-1.8,0.8c-0.4,0.5-0.7,1.2-0.7,2v5.3h-3.1V133h3.1v1.9c0.7-1.3,2-2,3.7-2C74.6,132.8,75.5,133.2,76.2,134z"/>
<path class="st1" d="M96,129.7h3.3l-4.7,14h-3.3l-2.9-10.1l-3,10.1h-3.2l-4.7-14h3.4l3,10.7l3-10.7H90l3.1,10.7L96,129.7z"/>
<path class="st1" d="M103.3,128.7c0.3,0.3,0.5,0.7,0.5,1.2s-0.2,0.9-0.5,1.2c-0.3,0.3-0.7,0.5-1.2,0.5c-0.5,0-0.9-0.2-1.2-0.5
c-0.3-0.3-0.5-0.7-0.5-1.2c0-0.5,0.2-0.9,0.5-1.2c0.3-0.3,0.7-0.5,1.2-0.5C102.6,128.2,103,128.3,103.3,128.7z M100.6,133h3.1
v10.8h-3.1V133z"/>
<path class="st1" d="M106.5,129.7h10.1l0,2.6h-6.9v3.4h6.3v2.6h-6.3v5.3h-3.2V129.7z"/>
<path class="st1" d="M120.9,128.7c0.3,0.3,0.5,0.7,0.5,1.2s-0.2,0.9-0.5,1.2c-0.3,0.3-0.7,0.5-1.2,0.5c-0.5,0-0.9-0.2-1.2-0.5
c-0.3-0.3-0.5-0.7-0.5-1.2c0-0.5,0.2-0.9,0.5-1.2c0.3-0.3,0.7-0.5,1.2-0.5C120.1,128.2,120.5,128.3,120.9,128.7z M118.1,133h3.1
v10.8h-3.1V133z"/>
</g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 8.0 KiB

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

BIN
public/mstile-150x150.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

View 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

View File

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

39
src/App.tsx Normal file
View File

@@ -0,0 +1,39 @@
import React, { Suspense } from 'react';
import { Spinner } from '@chakra-ui/react';
import { QueryClientProvider, QueryClient } from 'react-query';
import { HashRouter } from 'react-router-dom';
import Router from 'router';
import { AuthProvider } from 'contexts/AuthProvider';
import { ProvisioningSocketProvider } from 'contexts/ProvisioningSocketProvider';
import { ControllerSocketProvider } from 'contexts/ControllerSocketProvider';
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}>
<ProvisioningSocketProvider>
<ControllerSocketProvider>
<Router />
</ControllerSocketProvider>
</ProvisioningSocketProvider>
</AuthProvider>
</Suspense>
</HashRouter>
</QueryClientProvider>
);
};
export default App;

View File

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

View File

@@ -1,165 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.2.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 141.5 185.6" style="enable-background:new 0 0 141.5 185.6;" xml:space="preserve">
<style type="text/css">
.st0{fill:#414141;}
.st1{fill:#FFFFFF;}
.st2{fill:#FED206;}
.st3{fill:#EB6F53;}
.st4{fill:#3BA9B6;}
</style>
<g>
<g>
<path class="st0" d="M120.7,183.9H21.5c-10.8,0-19.5-8.7-19.5-19.5V20.5c0-10.8,8.7-19.5,19.5-19.5h99.2
c10.8,0,19.5,8.7,19.5,19.5v143.9C140.2,175.2,131.5,183.9,120.7,183.9z"/>
<g>
<g>
<g>
<path class="st1" d="M46.3,166.2v-3.4h-1.2v-0.6h3.1v0.6H47v3.4H46.3z"/>
</g>
<g>
<path class="st1" d="M49,166.2v-4h2.7v0.6h-2v1h2v0.6h-2v1.1h2v0.6H49z"/>
</g>
<g>
<path class="st1" d="M52.6,166.2v-4h0.7v3.4h1.8v0.6H52.6z"/>
</g>
<g>
<path class="st1" d="M55.7,166.2v-4h2.7v0.6h-2v1h2v0.6h-2v1.1h2v0.6H55.7z"/>
</g>
<g>
<path class="st1" d="M59.1,164.2c0-1.2,0.9-2.1,2.1-2.1c0.8,0,1.3,0.4,1.6,0.9l-0.6,0.3c-0.2-0.3-0.6-0.6-1-0.6
c-0.8,0-1.4,0.6-1.4,1.4c0,0.8,0.6,1.4,1.4,1.4c0.4,0,0.8-0.3,1-0.6l0.6,0.3c-0.3,0.5-0.8,0.9-1.6,0.9
C60,166.3,59.1,165.5,59.1,164.2z"/>
</g>
<g>
<path class="st1" d="M63.2,164.2c0-1.2,0.8-2.1,2-2.1c1.2,0,2,0.9,2,2.1c0,1.2-0.8,2.1-2,2.1C64,166.3,63.2,165.4,63.2,164.2z
M66.5,164.2c0-0.8-0.5-1.4-1.3-1.4c-0.8,0-1.3,0.6-1.3,1.4c0,0.8,0.5,1.4,1.3,1.4C66,165.7,66.5,165,66.5,164.2z"/>
</g>
<g>
<path class="st1" d="M71.3,166.2v-3.1l-1.2,3.1h-0.3l-1.2-3.1v3.1h-0.7v-4h1l1.1,2.7l1.1-2.7h1v4H71.3z"/>
</g>
<g>
<path class="st1" d="M75.7,166.2v-4h0.7v4H75.7z"/>
</g>
<g>
<path class="st1" d="M80.4,166.2l-2.1-2.8v2.8h-0.7v-4h0.7l2,2.8v-2.8h0.7v4H80.4z"/>
</g>
<g>
<path class="st1" d="M82.3,166.2v-4H85v0.6h-2v1h2v0.6h-2v1.7H82.3z"/>
</g>
<g>
<path class="st1" d="M87.9,166.2l-0.9-1.5h-0.7v1.5h-0.7v-4h1.7c0.8,0,1.3,0.5,1.3,1.2c0,0.7-0.5,1.1-0.9,1.2l1,1.6H87.9z
M88,163.5c0-0.4-0.3-0.6-0.7-0.6h-1v1.3h1C87.7,164.1,88,163.9,88,163.5z"/>
</g>
<g>
<path class="st1" d="M92.4,166.2l-0.3-0.8h-1.8l-0.3,0.8h-0.8l1.6-4h0.9l1.6,4H92.4z M91.2,162.9l-0.7,1.9h1.4L91.2,162.9z"/>
</g>
<g>
<path class="st1" d="M95.8,166.2v-4h1.5c0.8,0,1.2,0.5,1.2,1.2c0,0.6-0.4,1.2-1.2,1.2h-1.2v1.7H95.8z M98.2,163.4
c0-0.5-0.3-0.9-0.9-0.9h-1.1v1.7h1.1C97.8,164.3,98.2,163.9,98.2,163.4z"/>
</g>
<g>
<path class="st1" d="M101.5,166.2l-1.1-1.6h-0.9v1.6h-0.3v-4h1.5c0.7,0,1.2,0.4,1.2,1.2c0,0.7-0.5,1.1-1.1,1.1l1.2,1.7H101.5z
M101.6,163.4c0-0.5-0.4-0.9-0.9-0.9h-1.1v1.7h1.1C101.2,164.3,101.6,163.9,101.6,163.4z"/>
</g>
<g>
<path class="st1" d="M102.8,164.2c0-1.2,0.8-2.1,1.9-2.1c1.2,0,1.9,0.9,1.9,2.1c0,1.2-0.8,2.1-1.9,2.1
C103.6,166.3,102.8,165.4,102.8,164.2z M106.3,164.2c0-1-0.6-1.7-1.6-1.7c-1,0-1.6,0.7-1.6,1.7c0,1,0.6,1.7,1.6,1.7
C105.7,166,106.3,165.2,106.3,164.2z"/>
</g>
<g>
<path class="st1" d="M106.9,165.8l0.2-0.3c0.2,0.2,0.4,0.4,0.8,0.4c0.5,0,0.9-0.4,0.9-0.9v-2.8h0.3v2.8c0,0.8-0.5,1.2-1.2,1.2
C107.5,166.3,107.2,166.1,106.9,165.8z"/>
</g>
<g>
<path class="st1" d="M110.4,166.2v-4h2.5v0.3h-2.2v1.5h2.1v0.3h-2.1v1.6h2.2v0.3H110.4z"/>
</g>
<g>
<path class="st1" d="M113.5,164.2c0-1.2,0.9-2.1,2-2.1c0.6,0,1.1,0.3,1.5,0.7l-0.3,0.2c-0.3-0.3-0.7-0.6-1.2-0.6
c-0.9,0-1.7,0.7-1.7,1.7c0,1,0.7,1.7,1.7,1.7c0.5,0,0.9-0.2,1.2-0.6l0.3,0.2c-0.4,0.4-0.8,0.7-1.5,0.7
C114.4,166.3,113.5,165.5,113.5,164.2z"/>
</g>
<g>
<path class="st1" d="M118.7,166.2v-3.7h-1.3v-0.3h2.9v0.3H119v3.7H118.7z"/>
</g>
</g>
<g>
<polygon class="st1" points="26.3,163.8 31.6,158.5 36.9,163.8 37.7,163.8 31.6,157.6 25.5,163.8 "/>
<polygon class="st1" points="36.9,164.7 31.6,170 26.3,164.7 25.5,164.7 31.6,170.8 37.7,164.7 "/>
<polygon class="st1" points="31,163.8 36.3,158.5 41.6,163.8 42.5,163.8 36.3,157.6 30.2,163.8 "/>
<polygon class="st1" points="41.6,164.7 36.3,170 31,164.7 30.2,164.7 36.3,170.8 42.5,164.7 "/>
</g>
</g>
<g>
<path class="st1" d="M33.2,100.7c-4.6,0-8.3,3.7-8.3,8.3s3.7,8.3,8.3,8.3s8.3-3.7,8.3-8.3S37.8,100.7,33.2,100.7z"/>
</g>
<g>
<g>
<g>
<path class="st2" d="M33.2,35.2c40.7,0,73.8,33.1,73.8,73.8c0,0.7,0,1.4,0,2.1c0,1.7,0.6,3.3,1.7,4.6c1.2,1.2,2.8,1.9,4.5,2
l0.2,0c3.5,0,6.3-2.7,6.4-6.2c0-0.8,0-1.7,0-2.5c0-47.7-38.8-86.6-86.6-86.6c-0.8,0-1.7,0-2.5,0c-1.7,0-3.3,0.8-4.5,2
c-1.2,1.2-1.8,2.9-1.7,4.6c0.1,3.5,3,6.3,6.6,6.2C31.8,35.2,32.5,35.2,33.2,35.2z"/>
</g>
</g>
</g>
<g>
<g>
<g>
<path class="st3" d="M33.2,60.5c26.7,0,48.5,21.7,48.5,48.5c0,0.6,0,1.3,0,2c-0.1,1.7,0.5,3.3,1.7,4.6c1.2,1.3,2.7,2,4.4,2.1
c1.7,0.1,3.3-0.5,4.6-1.7c1.2-1.2,2-2.7,2-4.4c0-0.9,0.1-1.8,0.1-2.6c0-33.8-27.5-61.2-61.2-61.2c-0.8,0-1.6,0-2.6,0.1
c-1.7,0.1-3.3,0.8-4.4,2.1c-1.2,1.3-1.8,2.9-1.7,4.6s0.8,3.3,2.1,4.4c1.3,1.2,2.9,1.8,4.6,1.7C31.9,60.5,32.6,60.5,33.2,60.5z"
/>
</g>
</g>
</g>
<g>
<g>
<g>
<path class="st4" d="M33.2,86.7c12.3,0,22.3,10,22.3,22.3c0,0.5,0,1.1-0.1,1.8c-0.3,3.5,2.3,6.6,5.8,6.9
c3.5,0.3,6.6-2.3,6.9-5.8c0.1-1,0.1-1.9,0.1-2.8c0-19.3-15.7-35.1-35.1-35.1c-0.9,0-1.8,0-2.8,0.1c-1.7,0.1-3.2,0.9-4.3,2.2
c-1.1,1.3-1.6,2.9-1.5,4.6c0.1,1.7,0.9,3.2,2.2,4.3c1.3,1.1,2.9,1.6,4.6,1.5C32.1,86.7,32.7,86.7,33.2,86.7z"/>
</g>
</g>
</g>
</g>
<g>
<path class="st1" d="M35.8,130.4c1.1,0.6,2.1,1.5,2.7,2.6c0.7,1.1,1,2.3,1,3.7s-0.3,2.6-1,3.7c-0.7,1.1-1.6,2-2.7,2.6
c-1.1,0.6-2.4,1-3.8,1s-2.7-0.3-3.8-1c-1.1-0.6-2.1-1.5-2.7-2.6c-0.7-1.1-1-2.3-1-3.7c0-1.3,0.3-2.6,1-3.7c0.7-1.1,1.6-2,2.7-2.6
c1.1-0.6,2.4-0.9,3.8-0.9C33.4,129.5,34.7,129.8,35.8,130.4z M29.9,132.9c-0.7,0.4-1.2,0.9-1.6,1.6s-0.6,1.4-0.6,2.2
c0,0.8,0.2,1.6,0.6,2.3c0.4,0.7,0.9,1.2,1.6,1.6c0.7,0.4,1.4,0.6,2.1,0.6c0.8,0,1.5-0.2,2.1-0.6c0.6-0.4,1.2-0.9,1.5-1.6
c0.4-0.7,0.6-1.4,0.6-2.3c0-0.8-0.2-1.6-0.6-2.2s-0.9-1.2-1.5-1.6c-0.6-0.4-1.4-0.6-2.1-0.6C31.3,132.3,30.6,132.5,29.9,132.9z"/>
<path class="st1" d="M50.6,133.6c0.8,0.5,1.4,1.1,1.8,2c0.4,0.8,0.6,1.8,0.6,2.9c0,1.1-0.2,2-0.6,2.8c-0.4,0.8-1,1.5-1.8,1.9
c-0.8,0.5-1.6,0.7-2.6,0.7c-0.7,0-1.4-0.1-2-0.4s-1.1-0.7-1.5-1.2v5.4h-3.1V133h3.1v1.6c0.4-0.5,0.9-1,1.4-1.2s1.2-0.4,2-0.4
C48.9,132.9,49.8,133.1,50.6,133.6z M49.1,140.5c0.5-0.6,0.7-1.3,0.7-2.2c0-0.9-0.2-1.6-0.7-2.1c-0.5-0.6-1.1-0.8-1.9-0.8
s-1.4,0.3-1.9,0.8c-0.5,0.6-0.8,1.3-0.8,2.1c0,0.9,0.2,1.6,0.8,2.2s1.1,0.8,1.9,0.8S48.6,141,49.1,140.5z"/>
<path class="st1" d="M63.4,134.4c0.9,1,1.4,2.4,1.4,4.2c0,0.3,0,0.6,0,0.7H57c0.2,0.7,0.5,1.2,1,1.6c0.5,0.4,1.1,0.6,1.8,0.6
c0.5,0,1-0.1,1.5-0.3s0.9-0.5,1.3-0.9l1.6,1.6c-0.5,0.6-1.2,1.1-2,1.4c-0.8,0.3-1.6,0.5-2.6,0.5c-1.1,0-2.1-0.2-3-0.7
s-1.5-1.1-2-1.9c-0.5-0.8-0.7-1.8-0.7-2.9c0-1.1,0.2-2.1,0.7-2.9s1.1-1.5,2-1.9c0.8-0.5,1.8-0.7,2.9-0.7
C61.2,132.9,62.5,133.4,63.4,134.4z M61.8,137.5c0-0.7-0.3-1.3-0.7-1.7s-1-0.6-1.7-0.6c-0.7,0-1.2,0.2-1.7,0.6
c-0.4,0.4-0.7,1-0.9,1.7H61.8z"/>
<path class="st1" d="M76.2,134c0.7,0.7,1.1,1.7,1.1,3v6.8h-3.1v-5.9c0-0.7-0.2-1.2-0.6-1.6s-0.9-0.6-1.5-0.6
c-0.8,0-1.4,0.3-1.8,0.8c-0.4,0.5-0.7,1.2-0.7,2v5.3h-3.1V133h3.1v1.9c0.7-1.3,2-2,3.7-2C74.6,132.8,75.5,133.2,76.2,134z"/>
<path class="st1" d="M96,129.7h3.3l-4.7,14h-3.3l-2.9-10.1l-3,10.1h-3.2l-4.7-14h3.4l3,10.7l3-10.7H90l3.1,10.7L96,129.7z"/>
<path class="st1" d="M103.3,128.7c0.3,0.3,0.5,0.7,0.5,1.2s-0.2,0.9-0.5,1.2c-0.3,0.3-0.7,0.5-1.2,0.5c-0.5,0-0.9-0.2-1.2-0.5
c-0.3-0.3-0.5-0.7-0.5-1.2c0-0.5,0.2-0.9,0.5-1.2c0.3-0.3,0.7-0.5,1.2-0.5C102.6,128.2,103,128.3,103.3,128.7z M100.6,133h3.1
v10.8h-3.1V133z"/>
<path class="st1" d="M106.5,129.7h10.1l0,2.6h-6.9v3.4h6.3v2.6h-6.3v5.3h-3.2V129.7z"/>
<path class="st1" d="M120.9,128.7c0.3,0.3,0.5,0.7,0.5,1.2s-0.2,0.9-0.5,1.2c-0.3,0.3-0.7,0.5-1.2,0.5c-0.5,0-0.9-0.2-1.2-0.5
c-0.3-0.3-0.5-0.7-0.5-1.2c0-0.5,0.2-0.9,0.5-1.2c0.3-0.3,0.7-0.5,1.2-0.5C120.1,128.2,120.5,128.3,120.9,128.7z M118.1,133h3.1
v10.8h-3.1V133z"/>
</g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 8.0 KiB

View File

@@ -1,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,
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,185 +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((e) => {
if (e.response?.data?.ErrorDescription !== undefined) {
const split = e.response?.data?.ErrorDescription.split(':');
if (split !== undefined && split.length >= 2) {
addToast({
title: t('common.error'),
body: split[1],
color: 'danger',
autohide: true,
});
}
}
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;

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

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

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

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

View File

@@ -0,0 +1,82 @@
import * as React from 'react';
import { MenuItem, useToast } from '@chakra-ui/react';
import { AxiosError } from 'axios';
import { useTranslation } from 'react-i18next';
import { GatewayDevice } from 'models/Device';
import { useControllerStore } from 'contexts/ControllerSocketProvider/useStore';
import { useRebootDevice } from 'hooks/Network/Devices';
import { useMutationResult } from 'hooks/useMutationResult';
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;

View File

@@ -0,0 +1,176 @@
import React from 'react';
import { IconButton, Menu, MenuButton, MenuItem, MenuList, Spinner, Tooltip, useToast } from '@chakra-ui/react';
import axios, { AxiosError } 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;
size?: 'sm' | 'md' | 'lg';
}
const DeviceActionDropdown = ({
device,
refresh,
isDisabled,
onOpenScan,
onOpenFactoryReset,
onOpenTrace,
onOpenUpgradeModal,
onOpenEventQueue,
onOpenTelemetryModal,
onOpenConfigureModal,
size,
}: Props) => {
const { t } = useTranslation();
const toast = useToast();
const addEventListeners = useControllerStore((state) => state.addEventListeners);
const { refetch: getRtty, isLoading: 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) => {
onBlinkError(e as AxiosError);
},
});
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 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')}>
<MenuButton
as={IconButton}
aria-label="Commands"
icon={isRtty ? <Spinner /> : <Wrench size={20} />}
size={size ?? 'sm'}
isDisabled={isDisabled}
ml={2}
/>
</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={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);

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

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

View File

@@ -0,0 +1,61 @@
import React from 'react';
import { Button, IconButton, 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';
}
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);

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

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

View File

@@ -0,0 +1,78 @@
import React, { useCallback, useMemo } from 'react';
import { Button, IconButton, Tooltip, useBreakpoint } from '@chakra-ui/react';
import { ArrowRight, FloppyDisk } from 'phosphor-react';
import { useTranslation } from 'react-i18next';
export interface StepButtonProps {
onNext: () => void;
onSave?: () => void;
currentStep: number;
lastStep: number;
isDisabled?: boolean;
isLoading?: boolean;
isCompact?: boolean;
ml?: string | number;
}
const _StepButton: React.FC<StepButtonProps> = ({
onNext,
onSave,
isDisabled,
isLoading,
isCompact,
currentStep,
lastStep,
ml,
...props
}) => {
const { t } = useTranslation();
const breakpoint = useBreakpoint();
const onClick = useCallback(
() => (currentStep === lastStep && onSave ? onSave() : onNext()),
[currentStep, lastStep, onNext, onSave],
);
const icon = useMemo(
() => (currentStep === lastStep ? <FloppyDisk size={20} /> : <ArrowRight size={20} />),
[currentStep, lastStep],
);
const label = useMemo(
() => (currentStep === lastStep ? t('common.save') : t('common.next')),
[currentStep, lastStep],
);
if (!isCompact && breakpoint !== 'base' && breakpoint !== 'sm') {
return (
<Button
type="button"
colorScheme="blue"
onClick={onClick}
rightIcon={icon}
isLoading={isLoading}
isDisabled={isDisabled}
ml={ml}
{...props}
>
{label}
</Button>
);
}
return (
<Tooltip label={label}>
<IconButton
colorScheme="blue"
aria-label="next"
type="button"
onClick={onClick}
icon={icon}
isLoading={isLoading}
isDisabled={isDisabled}
ml={ml}
{...props}
/>
</Tooltip>
);
};
export const StepButton = React.memo(_StepButton);

View File

@@ -0,0 +1,83 @@
import React from 'react';
import { Button, IconButton, Tooltip, useBreakpoint, useDisclosure } from '@chakra-ui/react';
import { Pencil, X } from 'phosphor-react';
import { useTranslation } from 'react-i18next';
import { ConfirmCloseAlertModal } from '../../Modals/ConfirmCloseAlert';
export interface ToggleEditButtonProps {
toggleEdit: () => void;
isDisabled?: boolean;
isLoading?: boolean;
isCompact?: boolean;
isEditing: boolean;
isDirty?: boolean;
ml?: string | number;
}
const _ToggleEditButton: React.FC<ToggleEditButtonProps> = ({
toggleEdit,
isEditing,
isDirty,
isDisabled,
isLoading,
isCompact,
ml,
...props
}) => {
const { t } = useTranslation();
const breakpoint = useBreakpoint();
const { isOpen: showConfirm, onOpen: openConfirm, onClose: closeConfirm } = useDisclosure();
const toggle = () => {
if (isEditing && isDirty) {
openConfirm();
} else {
toggleEdit();
}
};
const closeCancelAndForm = () => {
closeConfirm();
toggleEdit();
};
if (!isCompact && breakpoint !== 'base' && breakpoint !== 'sm') {
return (
<>
<Button
colorScheme="gray"
type="button"
onClick={toggle}
rightIcon={isEditing ? <X size={20} /> : <Pencil size={20} />}
isLoading={isLoading}
isDisabled={isDisabled}
ml={ml}
{...props}
>
{isEditing ? t('common.stop_editing') : t('common.edit')}
</Button>
<ConfirmCloseAlertModal isOpen={showConfirm} confirm={closeCancelAndForm} cancel={closeConfirm} />
</>
);
}
return (
<>
<Tooltip label={isEditing ? t('common.stop_editing') : t('common.edit')}>
<IconButton
aria-label="toggle-edit"
colorScheme="gray"
type="button"
onClick={toggle}
icon={isEditing ? <X size={20} /> : <Pencil size={20} />}
isLoading={isLoading}
isDisabled={isDisabled}
ml={ml}
{...props}
/>
</Tooltip>
<ConfirmCloseAlertModal isOpen={showConfirm} confirm={closeCancelAndForm} cancel={closeConfirm} />
</>
);
};
export const ToggleEditButton = React.memo(_ToggleEditButton);

View File

@@ -0,0 +1,57 @@
import React from 'react';
import { ThemeProps } from 'models/Theme';
import { Button, IconButton, Tooltip, useBreakpoint } from '@chakra-ui/react';
import { Warning } from 'phosphor-react';
import { useTranslation } from 'react-i18next';
export interface WarningButtonProps extends ThemeProps {
onClick: () => void;
isDisabled?: boolean;
isLoading?: boolean;
isCompact?: boolean;
label?: string;
}
const _WarningButton: React.FC<WarningButtonProps> = ({
onClick,
isDisabled,
isLoading,
isCompact,
label,
...props
}) => {
const { t } = useTranslation();
const breakpoint = useBreakpoint();
if (!isCompact && breakpoint !== 'base' && breakpoint !== 'sm') {
return (
<Button
colorScheme="yellow"
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="yellow"
type="button"
onClick={onClick}
icon={<Warning size={20} />}
isLoading={isLoading}
isDisabled={isDisabled}
{...props}
/>
</Tooltip>
);
};
export const WarningButton = React.memo(_WarningButton);

View File

@@ -1,100 +0,0 @@
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import {
CRow,
CCol,
CCard,
CCardBody,
CCardHeader,
CLabel,
CPopover,
CSpinner,
CButton,
} from '@coreui/react';
import CIcon from '@coreui/icons-react';
import { cilSync } from '@coreui/icons';
import { useTranslation } from 'react-i18next';
import { CopyToClipboardButton, useAuth, useToast, FormattedDate } from 'ucentral-libs';
import axiosInstance from 'utils/axiosInstance';
const CapabilitiesDisplay = ({ serialNumber }) => {
const { t } = useTranslation();
const { currentToken, endpoints } = useAuth();
const [capabilities, setCapabilities] = useState({});
const { addToast } = useToast();
const [loading, setLoading] = useState(false);
const getCapabilities = () => {
const options = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${currentToken}`,
},
};
axiosInstance
.get(
`${endpoints.owgw}/api/v1/device/${encodeURIComponent(serialNumber)}/capabilities`,
options,
)
.then((response) => {
setCapabilities(response.data);
})
.catch((e) => {
addToast({
title: t('common.error'),
body: t('device.error_fetching_device', { error: e.response?.data?.ErrorDescription }),
color: 'danger',
autohide: true,
});
})
.finally(() => {
setLoading(false);
});
};
useEffect(() => {
getCapabilities();
}, []);
return (
<CCard className="m-0">
<CCardHeader className="dark-header">
<div className="d-flex flex-row-reverse align-items-center">
<div className="text-right">
<CPopover content={t('common.refresh')}>
<CButton size="sm" color="info" onClick={getCapabilities}>
<CIcon content={cilSync} />
</CButton>
</CPopover>
</div>
</div>
</CCardHeader>
<CCardBody>
<h5>
{t('device.capabilities')}
<CopyToClipboardButton
t={t}
size="sm"
content={JSON.stringify(capabilities?.capabilities ?? {})}
/>
</h5>
<CRow>
<CCol>
<CLabel>
{t('inventory.last_modification')}: <FormattedDate date={capabilities?.lastUpdate} />
</CLabel>
</CCol>
</CRow>
{loading ? <CSpinner /> : null}
<pre className="ignore">{JSON.stringify(capabilities?.capabilities ?? {}, null, 4)}</pre>
</CCardBody>
</CCard>
);
};
CapabilitiesDisplay.propTypes = {
serialNumber: PropTypes.string.isRequired,
};
export default CapabilitiesDisplay;

View File

@@ -1,33 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { CButton, CModal, CModalHeader, CModalBody, CModalTitle, CPopover } from '@coreui/react';
import CIcon from '@coreui/icons-react';
import { cilX } from '@coreui/icons';
const DetailsModal = ({ t, show, toggle, details, commandUuid }) => (
<CModal size="lg" show={show} onClose={toggle}>
<CModalHeader className="p-1">
<CModalTitle className="text-dark">{commandUuid}</CModalTitle>
<div className="text-right">
<CPopover content={t('common.close')}>
<CButton color="primary" variant="outline" className="ml-2" onClick={toggle}>
<CIcon content={cilX} />
</CButton>
</CPopover>
</div>
</CModalHeader>
<CModalBody>
<pre className="ignore">{JSON.stringify(details, null, 2)}</pre>
</CModalBody>
</CModal>
);
DetailsModal.propTypes = {
t: PropTypes.func.isRequired,
show: PropTypes.bool.isRequired,
toggle: PropTypes.func.isRequired,
details: PropTypes.instanceOf(Object).isRequired,
commandUuid: PropTypes.string.isRequired,
};
export default DetailsModal;

View File

@@ -1,441 +0,0 @@
/* eslint-disable-rule prefer-destructuring */
import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import {
CCardHeader,
CCardBody,
CButton,
CDataTable,
CCard,
CPopover,
CButtonToolbar,
CFormText,
} from '@coreui/react';
import CIcon from '@coreui/icons-react';
import DatePicker from 'react-widgets/DatePicker';
import { cilCloudDownload, cilSync, cilCalendarCheck } from '@coreui/icons';
import { dateToUnix } from 'utils/helper';
import axiosInstance from 'utils/axiosInstance';
import eventBus from 'utils/eventBus';
import ConfirmModal from 'components/ConfirmModal';
import { LoadingButton, useAuth, useDevice, FormattedDate } from 'ucentral-libs';
import WifiScanResultModalWidget from 'components/WifiScanResultModal';
import DetailsModal from './DetailsModal';
const DeviceCommands = () => {
const { t } = useTranslation();
const { currentToken, endpoints } = useAuth();
const { deviceSerialNumber } = useDevice();
// Wifiscan result related
const [chosenWifiScan, setChosenWifiScan] = useState(null);
const [showScanModal, setShowScanModal] = useState(false);
const [chosenWifiScanDate, setChosenWifiScanDate] = useState('');
// Delete modal related
const [showConfirmModal, setShowConfirmModal] = useState(false);
const [uuidDelete, setUuidDelete] = useState('');
// Details modal related
const [showDetailsModal, setShowDetailsModal] = useState(false);
const [detailsUuid, setDetailsUuid] = useState('');
const [modalDetails, setModalDetails] = useState({});
// General states
const [commands, setCommands] = useState([]);
const [loading, setLoading] = useState(false);
const [start, setStart] = useState('');
const [startError, setStartError] = useState(false);
const [end, setEnd] = useState('');
const [endError, setEndError] = useState(false);
const [commandLimit, setCommandLimit] = useState(25);
// Load more button related
const [loadingMore, setLoadingMore] = useState(false);
const [showLoadingMore, setShowLoadingMore] = useState(true);
const toggleScanModal = () => {
setShowScanModal(!showScanModal);
};
const toggleConfirmModal = (uuid) => {
setUuidDelete(uuid);
setShowConfirmModal(!showConfirmModal);
};
const toggleDetailsModal = () => {
setShowDetailsModal(!showDetailsModal);
};
const showMoreCommands = () => {
setCommandLimit(commandLimit + 50);
};
const modifyStart = (value) => {
try {
new Date(value).toISOString();
setStartError(false);
setStart(value);
} catch (e) {
setStart('');
setStartError(true);
}
};
const modifyEnd = (value) => {
try {
new Date(value).toISOString();
setEndError(false);
setEnd(value);
} catch (e) {
setEnd('');
setEndError(true);
}
};
const deleteCommandFromList = (commandUuid) => {
const indexToDelete = commands.map((e) => e.UUID).indexOf(commandUuid);
const newCommands = commands;
newCommands.splice(indexToDelete, 1);
setCommands(newCommands);
};
const getCommands = () => {
if (loading) return;
setLoadingMore(true);
setLoading(true);
const options = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${currentToken}`,
},
params: {
limit: commandLimit,
},
};
let extraParams = '&newest=true';
if (start !== '' && end !== '') {
const utcStart = new Date(start).toISOString();
const utcEnd = new Date(end).toISOString();
options.params.startDate = dateToUnix(utcStart);
options.params.endDate = dateToUnix(utcEnd);
extraParams = '';
}
axiosInstance
.get(
`${endpoints.owgw}/api/v1/commands?serialNumber=${encodeURIComponent(
deviceSerialNumber,
)}${extraParams}`,
options,
)
.then((response) => {
setCommands(response.data.commands);
})
.catch(() => {})
.finally(() => {
setLoading(false);
setLoadingMore(false);
});
};
const downloadTrace = (uuid) => {
const options = {
headers: {
Accept: 'application/octet-stream',
Authorization: `Bearer ${currentToken}`,
},
responseType: 'arraybuffer',
};
axiosInstance
.get(`${endpoints.owgw}/api/v1/file/${uuid}?serialNumber=${deviceSerialNumber}`, options)
.then((response) => {
const blob = new Blob([response.data], { type: 'application/octet-stream' });
const link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = `Trace_${uuid}.pcap`;
link.click();
});
};
const deleteCommand = async () => {
if (uuidDelete === '') {
return false;
}
const options = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${currentToken}`,
},
};
return axiosInstance
.delete(`${endpoints.owgw}/api/v1/command/${uuidDelete}`, options)
.then(() => {
deleteCommandFromList(uuidDelete);
setUuidDelete('');
return true;
})
.catch(() => {
setUuidDelete('');
return false;
});
};
const toggleDetails = (item) => {
if (item.command === 'wifiscan') {
setChosenWifiScan(item.results.status.scan);
setChosenWifiScanDate(item.completed);
setShowScanModal(true);
} else if (item.command === 'trace' && item.waitingForFile === 0) {
downloadTrace(item.UUID);
} else {
setModalDetails(item.results ?? item);
setDetailsUuid(item.UUID);
toggleDetailsModal();
}
};
const toggleResponse = (item) => {
setModalDetails(item);
setDetailsUuid(item.UUID);
toggleDetailsModal();
};
const refreshCommands = () => {
getCommands();
};
const columns = [
{ key: 'submitted', label: t('common.submitted'), filter: false, _style: { width: '20%' } },
{ key: 'command', label: t('common.command'), _style: { width: '0%' } },
{ key: 'status', label: t('common.status'), _style: { width: '0%' } },
{ key: 'executed', label: t('common.executed'), filter: false, _style: { width: '16%' } },
{ key: 'completed', label: t('common.completed'), filter: false, _style: { width: '16%' } },
{ key: 'errorCode', label: t('common.error_code'), filter: false },
{
key: 'show_buttons',
label: '',
sorter: false,
filter: false,
_style: { width: '1%' },
},
];
useEffect(() => {
if (deviceSerialNumber && start !== '' && end !== '') {
getCommands();
} else if (deviceSerialNumber && start === '' && end === '') {
getCommands();
}
}, [deviceSerialNumber, start, end]);
useEffect(() => {
eventBus.on('actionCompleted', () => refreshCommands());
return () => {
eventBus.remove('actionCompleted');
};
}, []);
useEffect(() => {
if (deviceSerialNumber) {
setCommandLimit(25);
setLoadingMore(false);
setShowLoadingMore(true);
setStart('');
setEnd('');
getCommands();
}
}, [deviceSerialNumber]);
useEffect(() => {
if (commandLimit !== 25) {
getCommands();
}
}, [commandLimit]);
useEffect(() => {
if (commands.length === 0 || (commands.length > 0 && commands.length < commandLimit)) {
setShowLoadingMore(false);
} else {
setShowLoadingMore(true);
}
}, [commands]);
return (
<div>
<CCard className="m-0">
<CCardHeader className="dark-header">
<div className="d-flex flex-row-reverse align-items-center">
<div className="pl-2">
<CPopover content={t('common.refresh')}>
<CButton
size="sm"
color="info"
onClick={getCommands}
disabled={startError || endError}
>
<CIcon content={cilSync} />
</CButton>
</CPopover>
</div>
<div className="pl-2">
<DatePicker
includeTime
onChange={(date) => modifyEnd(date)}
value={end ? new Date(end) : undefined}
/>
<CFormText color="danger" hidden={!endError}>
{t('common.invalid_date_explanation')}
</CFormText>
</div>
To:
<div className="pl-2">
<DatePicker
includeTime
onChange={(date) => modifyStart(date)}
value={start ? new Date(start) : undefined}
/>
<CFormText color="danger" hidden={!startError}>
{t('common.invalid_date_explanation')}
</CFormText>
</div>
From:
</div>
</CCardHeader>
<CCardBody className="p-1">
<div className="overflow-auto" style={{ height: 'calc(100vh - 620px)' }}>
<CDataTable
addTableClasses="ignore-overflow table-sm"
border
loading={loading}
items={commands ?? []}
fields={columns}
className="text-white"
sorterValue={{ column: 'created', desc: 'true' }}
scopedSlots={{
command: (item) => <td className="align-middle">{item.command}</td>,
completed: (item) => (
<td className="align-middle">
{item.completed && item.completed !== 0 ? (
<FormattedDate date={item.completed} />
) : (
'-'
)}
</td>
),
status: (item) => <td className="align-middle">{item.status}</td>,
executed: (item) => (
<td className="align-middle">
{item.executed && item.executed !== 0 ? (
<FormattedDate date={item.executed} />
) : (
'-'
)}
</td>
),
submitted: (item) => (
<td className="align-middle">
{item.submitted && item.submitted !== '' ? (
<FormattedDate date={item.submitted} />
) : (
'-'
)}
</td>
),
errorCode: (item) => <td className="align-middle">{item.errorCode}</td>,
show_buttons: (item, index) => (
<td className="align-middle">
<CButtonToolbar
role="group"
className="justify-content-flex-end"
style={{ width: '160px' }}
>
<CPopover
content={
item.command === 'trace' ? t('common.download') : t('common.result')
}
>
<CButton
color="primary"
variant="outline"
shape="square"
size="sm"
className="mx-2"
disabled={item.completed === 0}
onClick={() => {
toggleDetails(item);
}}
>
{item.command === 'trace' ? (
<CIcon name="cil-cloud-download" content={cilCloudDownload} />
) : (
<CIcon name="cil-calendar-check" content={cilCalendarCheck} />
)}
</CButton>
</CPopover>
<CPopover content={t('common.details')}>
<CButton
color="primary"
variant="outline"
shape="square"
size="sm"
className="mx-2"
onClick={() => {
toggleResponse(item);
}}
>
<CIcon name="cilList" />
</CButton>
</CPopover>
<CPopover content={t('common.delete')}>
<CButton
color="primary"
variant="outline"
shape="square"
size="sm"
className="mx-2"
onClick={() => {
toggleConfirmModal(item.UUID, index);
}}
>
<CIcon name="cilTrash" />
</CButton>
</CPopover>
</CButtonToolbar>
</td>
),
}}
/>
{showLoadingMore && (
<div className="mb-3">
<LoadingButton
label={t('common.view_more')}
isLoadingLabel={t('common.loading_more_ellipsis')}
isLoading={loadingMore}
action={showMoreCommands}
variant="outline"
/>
</div>
)}
</div>
</CCardBody>
</CCard>
<WifiScanResultModalWidget
show={showScanModal}
toggle={toggleScanModal}
scanResults={chosenWifiScan}
date={chosenWifiScanDate}
/>
<ConfirmModal show={showConfirmModal} toggle={toggleConfirmModal} action={deleteCommand} />
<DetailsModal
t={t}
show={showDetailsModal}
toggle={toggleDetailsModal}
details={modalDetails}
commandUuid={detailsUuid}
/>
</div>
);
};
export default DeviceCommands;

View File

@@ -0,0 +1,31 @@
import * as React from 'react';
import { Badge, HStack, Stat, StatHelpText, StatLabel, StatNumber } from '@chakra-ui/react';
import { useTranslation } from 'react-i18next';
import FormattedDate from 'components/InformationDisplays/FormattedDate';
const test = new Date().getTime() / 1000;
type Props = {
date?: number;
venue?: string;
type?: string;
hideStatus?: boolean;
};
const CompactTaskDisplay = ({ date, venue, type, hideStatus }: Props) => {
const { t } = useTranslation();
return (
<HStack spacing={2}>
{!hideStatus && <Badge colorScheme="green">{t('common.success')}</Badge>}
<Stat>
<StatLabel>
<FormattedDate date={date ?? test} />
</StatLabel>
<StatNumber fontSize="xl">{type ?? 'Installation'}</StatNumber>
<StatHelpText>{venue ?? 'Ally Detroit Center'}</StatHelpText>
</Stat>
</HStack>
);
};
export default CompactTaskDisplay;

View File

@@ -1,66 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
CRow,
CCol,
CCard,
CCardBody,
CCardHeader,
CLabel,
CPopover,
CButton,
} from '@coreui/react';
import CIcon from '@coreui/icons-react';
import { cilSync } from '@coreui/icons';
import { prettyDate } from 'utils/helper';
import { useTranslation } from 'react-i18next';
import { CopyToClipboardButton } from 'ucentral-libs';
const ConfigurationDisplay = ({ getData, deviceConfig }) => {
const { t } = useTranslation();
return (
<CCard className="m-0">
<CCardHeader className="dark-header">
<div className="d-flex flex-row-reverse align-items-center">
<div className="text-right">
<CPopover content={t('common.refresh')}>
<CButton size="sm" color="info" onClick={getData}>
<CIcon content={cilSync} />
</CButton>
</CPopover>
</div>
</div>
</CCardHeader>
<CCardBody>
<h5>
{t('configuration.title')}
<CopyToClipboardButton
t={t}
size="sm"
content={JSON.stringify(deviceConfig?.configuration ?? {}, null, 4)}
/>
</h5>
<CRow>
<CCol>
<CLabel>
{t('configuration.last_configuration_change')}:{' '}
{prettyDate(deviceConfig?.lastConfigurationChange)}
</CLabel>
</CCol>
</CRow>
<pre className="ignore">{JSON.stringify(deviceConfig?.configuration ?? {}, null, 4)}</pre>
</CCardBody>
</CCard>
);
};
ConfigurationDisplay.propTypes = {
getData: PropTypes.func.isRequired,
deviceConfig: PropTypes.instanceOf(Object),
};
ConfigurationDisplay.defaultProps = {
deviceConfig: null,
};
export default ConfigurationDisplay;

View File

@@ -1,249 +0,0 @@
import {
CAlert,
CButton,
CModal,
CModalHeader,
CModalTitle,
CModalBody,
CModalFooter,
CSpinner,
CCol,
CRow,
CForm,
CTextarea,
CInvalidFeedback,
CInputFile,
CPopover,
} 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 { useAuth, useDevice, useToast } from 'ucentral-libs';
import { checkIfJson } from 'utils/helper';
import axiosInstance from 'utils/axiosInstance';
import eventBus from 'utils/eventBus';
import SuccessfulActionModalBody from 'components/SuccessfulActionModalBody';
const ConfigureModal = ({ show, toggleModal }) => {
const { t } = useTranslation();
const { currentToken, endpoints } = useAuth();
const { addToast } = useToast();
const { deviceSerialNumber } = useDevice();
const [hadSuccess, setHadSuccess] = useState(false);
const [hadFailure, setHadFailure] = useState(false);
const [doingNow, setDoingNow] = useState(false);
const [waiting, setWaiting] = useState(false);
const [newConfig, setNewConfig] = useState('');
const [responseBody, setResponseBody] = useState('');
const [checkingIfSure, setCheckingIfSure] = useState(false);
const [errorJson, setErrorJson] = useState(false);
const [inputKey, setInputKey] = useState(0);
let fileReader;
const confirmingIfSure = () => {
if (checkIfJson(newConfig)) {
setCheckingIfSure(true);
} else {
setErrorJson(true);
}
};
useEffect(() => {
setHadSuccess(false);
setHadFailure(false);
setWaiting(false);
setResponseBody('');
setCheckingIfSure(false);
setDoingNow(false);
setNewConfig('');
setErrorJson(false);
setInputKey(0);
}, [show]);
useEffect(() => {
setErrorJson(false);
}, [newConfig]);
const doAction = (isNow) => {
setDoingNow(isNow);
setHadFailure(false);
setHadSuccess(false);
setWaiting(true);
const parameters = {
serialNumber: deviceSerialNumber,
when: 0,
UUID: 1,
configuration: JSON.parse(newConfig),
};
const headers = {
Accept: 'application/json',
Authorization: `Bearer ${currentToken}`,
};
axiosInstance
.post(
`${endpoints.owgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}/configure`,
parameters,
{ headers },
)
.then(() => {
addToast({
title: t('common.success'),
body: t('commands.command_success'),
color: 'success',
autohide: true,
});
toggleModal();
})
.catch((e) => {
setResponseBody('Error while submitting command!');
if (e.response?.data?.ErrorDescription !== undefined) {
const split = e.response?.data?.ErrorDescription.split(':');
if (split !== undefined && split.length >= 2) {
addToast({
title: t('common.error'),
body: split[1],
color: 'danger',
autohide: true,
});
}
}
setHadFailure(true);
})
.finally(() => {
setDoingNow(false);
setCheckingIfSure(false);
setWaiting(false);
eventBus.dispatch('actionCompleted', { message: 'An action has been completed' });
});
};
const handleJsonRead = () => {
setErrorJson(false);
const content = fileReader.result;
if (checkIfJson(content)) {
setNewConfig(content);
} else {
setErrorJson(true);
}
};
const handleJsonFile = (file) => {
fileReader = new FileReader();
fileReader.onloadend = handleJsonRead;
fileReader.readAsText(file);
};
const resetText = () => {
setInputKey(inputKey + 1);
setNewConfig('');
};
return (
<CModal show={show} onClose={toggleModal} size="lg">
<CModalHeader className="p-1">
<CModalTitle className="pl-1 pt-1">{t('configure.title')}</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>
{hadSuccess ? (
<SuccessfulActionModalBody toggleModal={toggleModal} />
) : (
<div>
<CModalBody>
<CRow>
<CCol md="10" className="mt-1">
<h6>{t('configure.enter_new')}</h6>
</CCol>
<CCol>
<CButton
type="reset"
size="sm"
onClick={resetText}
color="danger"
variant="outline"
>
{t('common.clear')}
</CButton>
</CCol>
</CRow>
<CRow className="mt-4">
<CCol>
<CForm>
<CTextarea
name="textarea-input"
id="textarea-input"
rows="9"
placeholder={t('configure.placeholder')}
value={newConfig}
onChange={(event) => setNewConfig(event.target.value)}
invalid={errorJson}
/>
<CInvalidFeedback className="help-block">
{t('configure.valid_json')}
</CInvalidFeedback>
</CForm>
</CCol>
</CRow>
<CRow className="mt-4">
<CCol>{t('configure.choose_file')}</CCol>
<CCol>
<CInputFile
id="file-input"
name="file-input"
accept=".json"
onChange={(e) => handleJsonFile(e.target.files[0])}
key={inputKey}
/>
</CCol>
</CRow>
<CAlert color="danger" hidden={!hadSuccess && !hadFailure}>
{responseBody}
</CAlert>
</CModalBody>
<CModalFooter>
<div hidden={!checkingIfSure}>Are you sure?</div>
<CButton
disabled={waiting}
hidden={checkingIfSure}
color="primary"
onClick={confirmingIfSure}
>
{t('common.save')}
</CButton>
<CButton
hidden={!checkingIfSure}
disabled={waiting}
color="primary"
onClick={() => doAction(false)}
>
{waiting && !doingNow ? t('common.saving') : t('common.yes')} {' '}
<CSpinner color="light" hidden={!waiting || doingNow} component="span" size="sm" />
</CButton>
<CButton color="secondary" onClick={toggleModal}>
{t('common.cancel')}
</CButton>
</CModalFooter>
</div>
)}
</CModal>
);
};
ConfigureModal.propTypes = {
show: PropTypes.bool.isRequired,
toggleModal: PropTypes.func.isRequired,
};
export default ConfigureModal;

View File

@@ -1,64 +0,0 @@
import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import PropTypes from 'prop-types';
import { CButton, CSpinner, CModalFooter } from '@coreui/react';
const ConfirmFooter = ({ isShown, isLoading, action, color, variant, block, toggleParent }) => {
const { t } = useTranslation();
const [askingIfSure, setAskingIfSure] = useState(false);
const confirmingIfSure = () => {
setAskingIfSure(true);
};
useEffect(() => {
setAskingIfSure(false);
}, [isShown]);
return (
<CModalFooter>
<div hidden={!askingIfSure}>{t('common.are_you_sure')}</div>
<CButton
disabled={isLoading}
hidden={askingIfSure}
color={color}
variant={variant}
onClick={() => confirmingIfSure()}
block={block}
>
{t('common.submit')}
</CButton>
<CButton
disabled={isLoading}
hidden={!askingIfSure}
color={color}
onClick={() => action()}
block={block}
>
{isLoading ? t('common.loading_ellipsis') : t('common.yes')}
<CSpinner color="light" hidden={!isLoading} component="span" size="sm" />
</CButton>
<CButton color="secondary" onClick={toggleParent}>
{t('common.cancel')}
</CButton>
</CModalFooter>
);
};
ConfirmFooter.propTypes = {
isLoading: PropTypes.bool.isRequired,
block: PropTypes.bool,
action: PropTypes.func.isRequired,
color: PropTypes.string,
variant: PropTypes.string,
toggleParent: PropTypes.func.isRequired,
isShown: PropTypes.bool.isRequired,
};
ConfirmFooter.defaultProps = {
color: 'primary',
variant: '',
block: false,
};
export default ConfirmFooter;

View File

@@ -1,97 +0,0 @@
import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import {
CButton,
CModal,
CModalHeader,
CModalTitle,
CModalBody,
CModalFooter,
CSpinner,
CBadge,
CPopover,
} from '@coreui/react';
import CIcon from '@coreui/icons-react';
import { cilX } from '@coreui/icons';
import PropTypes from 'prop-types';
const ConfirmModal = ({ show, toggle, action }) => {
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
const [haveResult, setHaveResult] = useState(false);
const [success, setSuccess] = useState(false);
const getButtonContent = () => {
if (haveResult) {
if (success) {
return (
<CBadge color="success" shape="pill">
{t('common.success')}
</CBadge>
);
}
return (
<CBadge color="danger" shape="pill">
{t('common.failure')}
</CBadge>
);
}
if (loading) {
return (
<div>
{t('common.loading_ellipsis')}
<CSpinner color="light" component="span" size="sm" />
</div>
);
}
return t('common.yes');
};
const doAction = async () => {
setLoading(true);
const result = await action();
setSuccess(result);
setHaveResult(true);
setLoading(false);
if (result) {
toggle();
}
};
useEffect(() => {
setLoading(false);
setHaveResult(false);
setSuccess(false);
}, [show]);
return (
<CModal className="text-dark" show={show} onClose={toggle}>
<CModalHeader className="p-1">
<CModalTitle className="pl-1 pt-1">{t('delete_command.title')}</CModalTitle>
<div className="text-right">
<CPopover content={t('common.close')}>
<CButton color="primary" variant="outline" className="ml-2" onClick={toggle}>
<CIcon content={cilX} />
</CButton>
</CPopover>
</div>
</CModalHeader>
<CModalBody>
<h6>{t('delete_command.explanation')}</h6>
</CModalBody>
<CModalFooter>
<CButton disabled={loading} color="primary" onClick={() => doAction()}>
{getButtonContent()}
</CButton>
</CModalFooter>
</CModal>
);
};
ConfirmModal.propTypes = {
show: PropTypes.bool.isRequired,
toggle: PropTypes.func.isRequired,
action: PropTypes.func.isRequired,
};
export default ConfirmModal;

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