[WIFI-11223] Migrating to prov-ui style
Signed-off-by: Charles <charles.bourque96@gmail.com>
1
.env
Normal file
@@ -0,0 +1 @@
|
|||||||
|
VITE_UCENTRALSEC_URL="https://ucentral.dpaas.arilia.com:16001"
|
||||||
@@ -1,4 +1,10 @@
|
|||||||
/src/assets
|
/src/assets
|
||||||
/build
|
/build
|
||||||
/node_modules
|
/node_modules
|
||||||
|
/dist
|
||||||
|
/icons
|
||||||
|
helm
|
||||||
|
docker-entrypoint.d
|
||||||
|
.dockerignore
|
||||||
|
DockerFile
|
||||||
.github
|
.github
|
||||||
|
|||||||
93
.eslintrc
@@ -1,22 +1,79 @@
|
|||||||
{
|
{
|
||||||
"extends": ["airbnb", "prettier"],
|
|
||||||
"plugins": ["prettier"],
|
|
||||||
"env": {
|
"env": {
|
||||||
"browser": true,
|
"browser": true,
|
||||||
"jest": 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": {
|
"rules": {
|
||||||
"max-len": ["error", {"code": 150}],
|
"import/extensions": [
|
||||||
"prefer-promise-reject-errors": ["off"],
|
"error",
|
||||||
"react/jsx-filename-extension": ["off"],
|
"ignorePackages",
|
||||||
"react/prop-types": ["warn"],
|
{
|
||||||
"no-return-assign": ["off"],
|
"js": "never",
|
||||||
"react/jsx-props-no-spreading": ["off"],
|
"jsx": "never",
|
||||||
"react/destructuring-assignment": ["off"],
|
"ts": "never",
|
||||||
|
"tsx": "never"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"@typescript-eslint/naming-convention": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"selector": "function",
|
||||||
|
"format": ["PascalCase", "camelCase"],
|
||||||
|
"leadingUnderscore": "allowSingleOrDouble"
|
||||||
|
}
|
||||||
|
],
|
||||||
"no-restricted-syntax": ["error", "ForInStatement", "LabeledStatement", "WithStatement"],
|
"no-restricted-syntax": ["error", "ForInStatement", "LabeledStatement", "WithStatement"],
|
||||||
"react/jsx-one-expression-per-line": "off",
|
"react/function-component-definition": [2, { "namedComponents": "arrow-function" }],
|
||||||
"react/jsx-wrap-multilines": "off",
|
"import/order": [
|
||||||
"react/jsx-curly-newline": "off"
|
"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": {
|
"settings": {
|
||||||
"import/resolver": {
|
"import/resolver": {
|
||||||
@@ -24,11 +81,5 @@
|
|||||||
"paths": ["src"]
|
"paths": ["src"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"parser": "babel-eslint",
|
|
||||||
"parserOptions": {
|
|
||||||
"sourceType": "module",
|
|
||||||
"allowImportExportEverywhere": false,
|
|
||||||
"codeFrame": false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
5
.gitignore
vendored
@@ -1,9 +1,8 @@
|
|||||||
|
|
||||||
|
|
||||||
# dependencies
|
# dependencies
|
||||||
/node_modules
|
/node_modules
|
||||||
/.pnp
|
/.pnp
|
||||||
.pnp.js
|
.pnp.js
|
||||||
|
/dev-dist
|
||||||
|
|
||||||
# testing
|
# testing
|
||||||
/coverage
|
/coverage
|
||||||
@@ -19,5 +18,3 @@
|
|||||||
.env.production.local
|
.env.production.local
|
||||||
|
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
/src/assets
|
/src/assets
|
||||||
build
|
build
|
||||||
|
dist
|
||||||
node_modules
|
node_modules
|
||||||
.github
|
.github
|
||||||
|
|||||||
14
.prettierrc
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"printWidth": 100,
|
"printWidth": 120,
|
||||||
"trailingComma": "all",
|
"trailingComma": "all",
|
||||||
"tabWidth": 2,
|
"tabWidth": 2,
|
||||||
"semi": true,
|
"semi": true,
|
||||||
"singleQuote": true
|
"singleQuote": true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
{
|
|
||||||
"presets": [
|
|
||||||
[
|
|
||||||
"@babel/preset-env",
|
|
||||||
{
|
|
||||||
"modules": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"@babel/preset-react"
|
|
||||||
],
|
|
||||||
"env": {
|
|
||||||
"production": {
|
|
||||||
"plugins": [
|
|
||||||
"@babel/plugin-transform-react-inline-elements",
|
|
||||||
"@babel/plugin-transform-react-constant-elements",
|
|
||||||
[
|
|
||||||
"transform-react-remove-prop-types",
|
|
||||||
{
|
|
||||||
"removeImport": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
const path = require('path');
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
// Source files
|
|
||||||
src: path.resolve(__dirname, '../src'),
|
|
||||||
|
|
||||||
// Production build files
|
|
||||||
build: path.resolve(__dirname, '../build'),
|
|
||||||
|
|
||||||
// Static files that get copied to build folder
|
|
||||||
public: path.resolve(__dirname, '../public'),
|
|
||||||
};
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
/* eslint-disable import/no-extraneous-dependencies */
|
|
||||||
/* eslint-disable prefer-template */
|
|
||||||
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
|
|
||||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
|
||||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
|
||||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
|
||||||
const webpack = require('webpack');
|
|
||||||
const path = require('path');
|
|
||||||
const paths = require('./paths');
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
entry: [paths.src + '/index.js'],
|
|
||||||
output: {
|
|
||||||
path: paths.build,
|
|
||||||
filename: '[name].bundle.js',
|
|
||||||
publicPath: '/',
|
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
modules: [path.resolve('./node_modules'), path.resolve('./src')],
|
|
||||||
preferRelative: true,
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
new webpack.DefinePlugin({
|
|
||||||
'process.env.VERSION': JSON.stringify(process.env.npm_package_version),
|
|
||||||
}),
|
|
||||||
new MiniCssExtractPlugin({
|
|
||||||
filename: 'styles/[name].[contenthash].css',
|
|
||||||
chunkFilename: '[id].[contenthash].css',
|
|
||||||
}),
|
|
||||||
new CopyWebpackPlugin({
|
|
||||||
patterns: [
|
|
||||||
{
|
|
||||||
from: paths.src + '/assets',
|
|
||||||
to: 'assets',
|
|
||||||
globOptions: {
|
|
||||||
ignore: ['*.DS_Store'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
from: paths.public + '/locales',
|
|
||||||
to: 'locales',
|
|
||||||
globOptions: {
|
|
||||||
ignore: ['*.DS_Store'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
from: paths.public + '/config.json',
|
|
||||||
to: 'config.json',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
new HtmlWebpackPlugin({
|
|
||||||
title: 'uCentralGW',
|
|
||||||
favicon: paths.public + '/favicon.ico',
|
|
||||||
template: paths.public + '/index.html',
|
|
||||||
filename: 'index.html',
|
|
||||||
}),
|
|
||||||
new CleanWebpackPlugin(),
|
|
||||||
],
|
|
||||||
|
|
||||||
module: {
|
|
||||||
rules: [
|
|
||||||
{
|
|
||||||
test: /\.(js|jsx)$/,
|
|
||||||
exclude: /node_modules/,
|
|
||||||
use: ['babel-loader'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.(css|scss)$/,
|
|
||||||
use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.svg$/,
|
|
||||||
use: ['@svgr/webpack'],
|
|
||||||
},
|
|
||||||
{ test: /\.(?:ico|gif|png|jpg|jpeg)$/i, type: 'asset/resource' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
/* eslint-disable import/no-extraneous-dependencies */
|
|
||||||
/* eslint-disable prefer-template */
|
|
||||||
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
|
|
||||||
const { merge } = require('webpack-merge');
|
|
||||||
const path = require('path');
|
|
||||||
const paths = require('./paths');
|
|
||||||
const common = require('./webpack.common');
|
|
||||||
|
|
||||||
module.exports = merge(common, {
|
|
||||||
mode: 'development',
|
|
||||||
|
|
||||||
target: 'web',
|
|
||||||
|
|
||||||
devtool: 'inline-source-map',
|
|
||||||
|
|
||||||
devServer: {
|
|
||||||
historyApiFallback: true,
|
|
||||||
contentBase: paths.build,
|
|
||||||
open: true,
|
|
||||||
compress: false,
|
|
||||||
hot: true,
|
|
||||||
port: 3000,
|
|
||||||
},
|
|
||||||
module: {
|
|
||||||
rules: [
|
|
||||||
{
|
|
||||||
test: /\.[js]sx?$/,
|
|
||||||
exclude: /node_modules/,
|
|
||||||
use: [
|
|
||||||
{
|
|
||||||
loader: require.resolve('babel-loader'),
|
|
||||||
options: {
|
|
||||||
plugins: [require.resolve('react-refresh/babel')],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
modules: [
|
|
||||||
'node_modules',
|
|
||||||
'src',
|
|
||||||
path.resolve(__dirname, '../', 'node_modules', 'ucentral-libs', 'src'),
|
|
||||||
],
|
|
||||||
alias: {
|
|
||||||
react: path.resolve(__dirname, '../', 'node_modules', 'react'),
|
|
||||||
'react-router-dom': path.resolve('./node_modules/react-router-dom'),
|
|
||||||
'ucentral-libs': path.resolve(__dirname, '../', 'node_modules', 'ucentral-libs', 'src'),
|
|
||||||
graphlib: path.resolve(__dirname, '../', 'node_modules', 'graphlib'),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: [new ReactRefreshWebpackPlugin()],
|
|
||||||
});
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
/* eslint-disable import/no-extraneous-dependencies */
|
|
||||||
/* eslint-disable prefer-template */
|
|
||||||
const { merge } = require('webpack-merge');
|
|
||||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
|
||||||
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
|
|
||||||
const TerserPlugin = require('terser-webpack-plugin');
|
|
||||||
const CompressionPlugin = require('compression-webpack-plugin');
|
|
||||||
const path = require('path');
|
|
||||||
const paths = require('./paths');
|
|
||||||
const common = require('./webpack.common');
|
|
||||||
|
|
||||||
module.exports = merge(common, {
|
|
||||||
mode: 'production',
|
|
||||||
devtool: false,
|
|
||||||
output: {
|
|
||||||
path: paths.build,
|
|
||||||
publicPath: '/',
|
|
||||||
filename: 'js/[name].[contenthash].bundle.js',
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
// new BundleAnalyzerPlugin(),
|
|
||||||
new MiniCssExtractPlugin({
|
|
||||||
filename: 'styles/[name].[contenthash].css',
|
|
||||||
chunkFilename: '[contenthash].css',
|
|
||||||
}),
|
|
||||||
new CompressionPlugin({
|
|
||||||
filename: '[path]/[name].gz[query]',
|
|
||||||
algorithm: 'gzip',
|
|
||||||
test: /\.js$|\.css$|\.html$|\.eot?.+$|\.ttf?.+$|\.woff?.+$|\.svg?.+$/,
|
|
||||||
threshold: 10240,
|
|
||||||
minRatio: 0.8,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
module: {
|
|
||||||
rules: [],
|
|
||||||
},
|
|
||||||
optimization: {
|
|
||||||
minimize: true,
|
|
||||||
minimizer: [
|
|
||||||
'...',
|
|
||||||
new TerserPlugin({
|
|
||||||
terserOptions: {
|
|
||||||
warnings: false,
|
|
||||||
compress: {
|
|
||||||
comparisons: false,
|
|
||||||
},
|
|
||||||
parse: {},
|
|
||||||
mangle: true,
|
|
||||||
output: {
|
|
||||||
ascii_only: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
parallel: true,
|
|
||||||
}),
|
|
||||||
new CssMinimizerPlugin(),
|
|
||||||
],
|
|
||||||
nodeEnv: 'production',
|
|
||||||
sideEffects: true,
|
|
||||||
runtimeChunk: 'single',
|
|
||||||
splitChunks: {
|
|
||||||
chunks: 'all',
|
|
||||||
maxInitialRequests: 10,
|
|
||||||
minSize: 0,
|
|
||||||
cacheGroups: {
|
|
||||||
vendor: {
|
|
||||||
test: /[\\/]node_modules[\\/]/,
|
|
||||||
name(module) {
|
|
||||||
const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];
|
|
||||||
return `npm.${packageName.replace('@', '')}`;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
modules: [],
|
|
||||||
alias: {
|
|
||||||
graphlib: path.resolve(__dirname, '../', 'node_modules', 'graphlib'),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
performance: {
|
|
||||||
hints: false,
|
|
||||||
maxEntrypointSize: 512000,
|
|
||||||
maxAssetSize: 512000,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
20
index.html
Normal 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>
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"baseUrl": "src",
|
|
||||||
"paths": {
|
|
||||||
"*": ["*"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"include": ["src"]
|
|
||||||
}
|
|
||||||
28256
package-lock.json
generated
160
package.json
@@ -1,101 +1,87 @@
|
|||||||
{
|
{
|
||||||
"name": "ucentral-client",
|
"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": {
|
"dependencies": {
|
||||||
"@coreui/coreui": "^3.4.0",
|
"@chakra-ui/icons": "^2.0.11",
|
||||||
"@coreui/icons": "^2.0.1",
|
"@chakra-ui/react": "^2.3.6",
|
||||||
"@coreui/icons-react": "^1.1.0",
|
"@chakra-ui/theme-tools": "^2.0.12",
|
||||||
"@coreui/react": "^3.4.6",
|
"@chakra-ui/utils": "^2.0.11",
|
||||||
"@coreui/react-chartjs": "^1.1.0",
|
"@emotion/react": "^11.10.4",
|
||||||
"apexcharts": "^3.27.1",
|
"@emotion/styled": "^11.10.4",
|
||||||
"axios": "^0.21.1",
|
"@fontsource/inter": "^4.5.14",
|
||||||
"axios-retry": "^3.1.9",
|
"@react-spring/web": "^9.5.5",
|
||||||
|
"axios": "^1.1.3",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
|
"chakra-react-select": "^4.3.0",
|
||||||
"dagre": "^0.8.5",
|
"dagre": "^0.8.5",
|
||||||
"i18next": "^20.3.1",
|
"formik": "^2.2.9",
|
||||||
"i18next-browser-languagedetector": "^6.1.2",
|
"framer-motion": "^7.6.1",
|
||||||
"i18next-http-backend": "^1.2.6",
|
"i18next": "^22.0.0",
|
||||||
"prop-types": "^15.7.2",
|
"i18next-browser-languagedetector": "^6.1.8",
|
||||||
"react": "^17.0.2",
|
"i18next-http-backend": "^1.4.4",
|
||||||
"react-apexcharts": "^1.3.9",
|
"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-country-flag": "^3.0.2",
|
||||||
"react-csv": "^2.2.2",
|
"react-csv": "^2.2.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-datepicker": "^4.8.0",
|
||||||
"react-flow-renderer": "^9.6.6",
|
"react-dom": "^18.2.0",
|
||||||
"react-i18next": "^11.11.0",
|
"@textea/json-viewer": "^2.10.0",
|
||||||
"react-paginate": "^7.1.3",
|
"react-fast-compare": "^3.2.0",
|
||||||
"react-router-dom": "^5.2.0",
|
"react-i18next": "^11.18.6",
|
||||||
"react-select": "^4.3.1",
|
"react-masonry-css": "^1.0.16",
|
||||||
"react-tooltip": "^4.2.21",
|
"react-query": "^3.39.2",
|
||||||
"react-widgets": "^5.1.1",
|
"react-router-dom": "^6.4.2",
|
||||||
"sass": "^1.35.1",
|
"react-table": "^7.8.0",
|
||||||
"ucentral-libs": "^1.0.61",
|
"source-map-explorer": "^2.5.3",
|
||||||
"uuid": "^8.3.2"
|
"vite": "^3.1.8",
|
||||||
},
|
"typescript": "^4.8.4",
|
||||||
"main": "index.js",
|
"uuid": "^9.0.0",
|
||||||
"scripts": {
|
"yup": "^0.32.11",
|
||||||
"start": "webpack serve --config config/webpack.dev.js",
|
"zustand": "^4.1.2"
|
||||||
"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"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.14.6",
|
"@types/node": "^18.11.2",
|
||||||
"@babel/plugin-proposal-class-properties": "^7.14.5",
|
"@types/react": "^18.0.21",
|
||||||
"@babel/plugin-transform-runtime": "^7.14.5",
|
"@types/react-csv": "^1.1.3",
|
||||||
"@babel/polyfill": "^7.12.1",
|
"@types/react-dom": "^18.0.6",
|
||||||
"@babel/preset-env": "^7.14.7",
|
"@types/react-table": "^7.7.12",
|
||||||
"@babel/preset-react": "^7.14.5",
|
"@types/uuid": "^8.3.4",
|
||||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.4.3",
|
"eslint": "8.25.0",
|
||||||
"@svgr/webpack": "^5.5.0",
|
"vite-tsconfig-paths": "^3.5.1",
|
||||||
"autoprefixer": "^10.2.6",
|
"lint-staged": "^13.0.3",
|
||||||
"babel-eslint": "^10.1.0",
|
"@vitejs/plugin-react": "^2.1.0",
|
||||||
"babel-loader": "^8.2.2",
|
"vite-plugin-pwa": "^0.13.1",
|
||||||
"clean-webpack-plugin": "^3.0.0",
|
"prettier": "^2.7.1",
|
||||||
"compression-webpack-plugin": "^8.0.1",
|
"eslint-config-airbnb": "^19.0.4",
|
||||||
"copy-webpack-plugin": "^7.0.0",
|
"eslint-config-airbnb-typescript": "^17.0.0",
|
||||||
"css-loader": "^5.2.6",
|
"eslint-config-airbnb-typescript-prettier": "^5.0.0",
|
||||||
"css-minimizer-webpack-plugin": "^2.0.0",
|
"eslint-config-prettier": "^8.5.0",
|
||||||
"dotenv-webpack": "^6.0.4",
|
|
||||||
"eslint": "^7.29.0",
|
|
||||||
"eslint-config-airbnb": "^18.2.1",
|
|
||||||
"eslint-config-prettier": "^7.2.0",
|
|
||||||
"eslint-import-resolver-alias": "^1.1.2",
|
"eslint-import-resolver-alias": "^1.1.2",
|
||||||
"eslint-loader": "^4.0.2",
|
|
||||||
"eslint-plugin-babel": "^5.3.1",
|
"eslint-plugin-babel": "^5.3.1",
|
||||||
"eslint-plugin-import": "^2.23.4",
|
"eslint-plugin-import": "^2.26.0",
|
||||||
"eslint-plugin-prettier": "^3.4.0",
|
"eslint-plugin-jsx-a11y": "^6.6.1",
|
||||||
"eslint-plugin-react": "^7.24.0",
|
"eslint-plugin-no-inline-styles": "^1.0.5",
|
||||||
"eslint-plugin-react-hooks": "^4.2.0",
|
"eslint-plugin-prettier": "^4.2.1",
|
||||||
"html-webpack-plugin": "^5.3.2",
|
"eslint-plugin-react": "^7.31.10",
|
||||||
"husky": "^4.3.8",
|
"eslint-plugin-react-hooks": "^4.6.0"
|
||||||
"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"
|
|
||||||
},
|
},
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
"production": [
|
"production": [
|
||||||
|
|||||||
BIN
public/android-chrome-192x192.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
public/android-chrome-384x384.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
public/android-chrome-512x512.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
public/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
9
public/browserconfig.xml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<browserconfig>
|
||||||
|
<msapplication>
|
||||||
|
<tile>
|
||||||
|
<square150x150logo src="/mstile-150x150.png"/>
|
||||||
|
<TileColor>#414141</TileColor>
|
||||||
|
</tile>
|
||||||
|
</msapplication>
|
||||||
|
</browserconfig>
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"DEFAULT_UCENTRALSEC_URL": "https://ucentral.dpaas.arilia.com:16001",
|
|
||||||
"ALLOW_UCENTRALSEC_CHANGE": false
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 218 KiB After Width: | Height: | Size: 218 KiB |
|
Before Width: | Height: | Size: 158 KiB After Width: | Height: | Size: 158 KiB |
|
Before Width: | Height: | Size: 140 KiB After Width: | Height: | Size: 140 KiB |
|
Before Width: | Height: | Size: 121 KiB After Width: | Height: | Size: 121 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 192 KiB After Width: | Height: | Size: 192 KiB |
|
Before Width: | Height: | Size: 197 KiB After Width: | Height: | Size: 197 KiB |
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 89 KiB After Width: | Height: | Size: 89 KiB |
|
Before Width: | Height: | Size: 89 KiB After Width: | Height: | Size: 89 KiB |
|
Before Width: | Height: | Size: 204 KiB After Width: | Height: | Size: 204 KiB |
|
Before Width: | Height: | Size: 159 KiB After Width: | Height: | Size: 159 KiB |
|
Before Width: | Height: | Size: 159 KiB After Width: | Height: | Size: 159 KiB |
|
Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 103 KiB |
|
Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 103 KiB |
|
Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 103 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 80 KiB |
BIN
public/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 1021 B |
BIN
public/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 202 KiB After Width: | Height: | Size: 15 KiB |
@@ -1,165 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- Generator: Adobe Illustrator 24.2.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
|
||||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
|
||||||
viewBox="0 0 141.5 185.6" style="enable-background:new 0 0 141.5 185.6;" xml:space="preserve">
|
|
||||||
<style type="text/css">
|
|
||||||
.st0{fill:#414141;}
|
|
||||||
.st1{fill:#FFFFFF;}
|
|
||||||
.st2{fill:#FED206;}
|
|
||||||
.st3{fill:#EB6F53;}
|
|
||||||
.st4{fill:#3BA9B6;}
|
|
||||||
</style>
|
|
||||||
<g>
|
|
||||||
<g>
|
|
||||||
<path class="st0" d="M120.7,183.9H21.5c-10.8,0-19.5-8.7-19.5-19.5V20.5c0-10.8,8.7-19.5,19.5-19.5h99.2
|
|
||||||
c10.8,0,19.5,8.7,19.5,19.5v143.9C140.2,175.2,131.5,183.9,120.7,183.9z"/>
|
|
||||||
<g>
|
|
||||||
<g>
|
|
||||||
<g>
|
|
||||||
<path class="st1" d="M46.3,166.2v-3.4h-1.2v-0.6h3.1v0.6H47v3.4H46.3z"/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path class="st1" d="M49,166.2v-4h2.7v0.6h-2v1h2v0.6h-2v1.1h2v0.6H49z"/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path class="st1" d="M52.6,166.2v-4h0.7v3.4h1.8v0.6H52.6z"/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path class="st1" d="M55.7,166.2v-4h2.7v0.6h-2v1h2v0.6h-2v1.1h2v0.6H55.7z"/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path class="st1" d="M59.1,164.2c0-1.2,0.9-2.1,2.1-2.1c0.8,0,1.3,0.4,1.6,0.9l-0.6,0.3c-0.2-0.3-0.6-0.6-1-0.6
|
|
||||||
c-0.8,0-1.4,0.6-1.4,1.4c0,0.8,0.6,1.4,1.4,1.4c0.4,0,0.8-0.3,1-0.6l0.6,0.3c-0.3,0.5-0.8,0.9-1.6,0.9
|
|
||||||
C60,166.3,59.1,165.5,59.1,164.2z"/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path class="st1" d="M63.2,164.2c0-1.2,0.8-2.1,2-2.1c1.2,0,2,0.9,2,2.1c0,1.2-0.8,2.1-2,2.1C64,166.3,63.2,165.4,63.2,164.2z
|
|
||||||
M66.5,164.2c0-0.8-0.5-1.4-1.3-1.4c-0.8,0-1.3,0.6-1.3,1.4c0,0.8,0.5,1.4,1.3,1.4C66,165.7,66.5,165,66.5,164.2z"/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path class="st1" d="M71.3,166.2v-3.1l-1.2,3.1h-0.3l-1.2-3.1v3.1h-0.7v-4h1l1.1,2.7l1.1-2.7h1v4H71.3z"/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path class="st1" d="M75.7,166.2v-4h0.7v4H75.7z"/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path class="st1" d="M80.4,166.2l-2.1-2.8v2.8h-0.7v-4h0.7l2,2.8v-2.8h0.7v4H80.4z"/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path class="st1" d="M82.3,166.2v-4H85v0.6h-2v1h2v0.6h-2v1.7H82.3z"/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path class="st1" d="M87.9,166.2l-0.9-1.5h-0.7v1.5h-0.7v-4h1.7c0.8,0,1.3,0.5,1.3,1.2c0,0.7-0.5,1.1-0.9,1.2l1,1.6H87.9z
|
|
||||||
M88,163.5c0-0.4-0.3-0.6-0.7-0.6h-1v1.3h1C87.7,164.1,88,163.9,88,163.5z"/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path class="st1" d="M92.4,166.2l-0.3-0.8h-1.8l-0.3,0.8h-0.8l1.6-4h0.9l1.6,4H92.4z M91.2,162.9l-0.7,1.9h1.4L91.2,162.9z"/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path class="st1" d="M95.8,166.2v-4h1.5c0.8,0,1.2,0.5,1.2,1.2c0,0.6-0.4,1.2-1.2,1.2h-1.2v1.7H95.8z M98.2,163.4
|
|
||||||
c0-0.5-0.3-0.9-0.9-0.9h-1.1v1.7h1.1C97.8,164.3,98.2,163.9,98.2,163.4z"/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path class="st1" d="M101.5,166.2l-1.1-1.6h-0.9v1.6h-0.3v-4h1.5c0.7,0,1.2,0.4,1.2,1.2c0,0.7-0.5,1.1-1.1,1.1l1.2,1.7H101.5z
|
|
||||||
M101.6,163.4c0-0.5-0.4-0.9-0.9-0.9h-1.1v1.7h1.1C101.2,164.3,101.6,163.9,101.6,163.4z"/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path class="st1" d="M102.8,164.2c0-1.2,0.8-2.1,1.9-2.1c1.2,0,1.9,0.9,1.9,2.1c0,1.2-0.8,2.1-1.9,2.1
|
|
||||||
C103.6,166.3,102.8,165.4,102.8,164.2z M106.3,164.2c0-1-0.6-1.7-1.6-1.7c-1,0-1.6,0.7-1.6,1.7c0,1,0.6,1.7,1.6,1.7
|
|
||||||
C105.7,166,106.3,165.2,106.3,164.2z"/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path class="st1" d="M106.9,165.8l0.2-0.3c0.2,0.2,0.4,0.4,0.8,0.4c0.5,0,0.9-0.4,0.9-0.9v-2.8h0.3v2.8c0,0.8-0.5,1.2-1.2,1.2
|
|
||||||
C107.5,166.3,107.2,166.1,106.9,165.8z"/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path class="st1" d="M110.4,166.2v-4h2.5v0.3h-2.2v1.5h2.1v0.3h-2.1v1.6h2.2v0.3H110.4z"/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path class="st1" d="M113.5,164.2c0-1.2,0.9-2.1,2-2.1c0.6,0,1.1,0.3,1.5,0.7l-0.3,0.2c-0.3-0.3-0.7-0.6-1.2-0.6
|
|
||||||
c-0.9,0-1.7,0.7-1.7,1.7c0,1,0.7,1.7,1.7,1.7c0.5,0,0.9-0.2,1.2-0.6l0.3,0.2c-0.4,0.4-0.8,0.7-1.5,0.7
|
|
||||||
C114.4,166.3,113.5,165.5,113.5,164.2z"/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path class="st1" d="M118.7,166.2v-3.7h-1.3v-0.3h2.9v0.3H119v3.7H118.7z"/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<polygon class="st1" points="26.3,163.8 31.6,158.5 36.9,163.8 37.7,163.8 31.6,157.6 25.5,163.8 "/>
|
|
||||||
<polygon class="st1" points="36.9,164.7 31.6,170 26.3,164.7 25.5,164.7 31.6,170.8 37.7,164.7 "/>
|
|
||||||
<polygon class="st1" points="31,163.8 36.3,158.5 41.6,163.8 42.5,163.8 36.3,157.6 30.2,163.8 "/>
|
|
||||||
<polygon class="st1" points="41.6,164.7 36.3,170 31,164.7 30.2,164.7 36.3,170.8 42.5,164.7 "/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path class="st1" d="M33.2,100.7c-4.6,0-8.3,3.7-8.3,8.3s3.7,8.3,8.3,8.3s8.3-3.7,8.3-8.3S37.8,100.7,33.2,100.7z"/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<g>
|
|
||||||
<g>
|
|
||||||
<path class="st2" d="M33.2,35.2c40.7,0,73.8,33.1,73.8,73.8c0,0.7,0,1.4,0,2.1c0,1.7,0.6,3.3,1.7,4.6c1.2,1.2,2.8,1.9,4.5,2
|
|
||||||
l0.2,0c3.5,0,6.3-2.7,6.4-6.2c0-0.8,0-1.7,0-2.5c0-47.7-38.8-86.6-86.6-86.6c-0.8,0-1.7,0-2.5,0c-1.7,0-3.3,0.8-4.5,2
|
|
||||||
c-1.2,1.2-1.8,2.9-1.7,4.6c0.1,3.5,3,6.3,6.6,6.2C31.8,35.2,32.5,35.2,33.2,35.2z"/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<g>
|
|
||||||
<g>
|
|
||||||
<path class="st3" d="M33.2,60.5c26.7,0,48.5,21.7,48.5,48.5c0,0.6,0,1.3,0,2c-0.1,1.7,0.5,3.3,1.7,4.6c1.2,1.3,2.7,2,4.4,2.1
|
|
||||||
c1.7,0.1,3.3-0.5,4.6-1.7c1.2-1.2,2-2.7,2-4.4c0-0.9,0.1-1.8,0.1-2.6c0-33.8-27.5-61.2-61.2-61.2c-0.8,0-1.6,0-2.6,0.1
|
|
||||||
c-1.7,0.1-3.3,0.8-4.4,2.1c-1.2,1.3-1.8,2.9-1.7,4.6s0.8,3.3,2.1,4.4c1.3,1.2,2.9,1.8,4.6,1.7C31.9,60.5,32.6,60.5,33.2,60.5z"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<g>
|
|
||||||
<g>
|
|
||||||
<path class="st4" d="M33.2,86.7c12.3,0,22.3,10,22.3,22.3c0,0.5,0,1.1-0.1,1.8c-0.3,3.5,2.3,6.6,5.8,6.9
|
|
||||||
c3.5,0.3,6.6-2.3,6.9-5.8c0.1-1,0.1-1.9,0.1-2.8c0-19.3-15.7-35.1-35.1-35.1c-0.9,0-1.8,0-2.8,0.1c-1.7,0.1-3.2,0.9-4.3,2.2
|
|
||||||
c-1.1,1.3-1.6,2.9-1.5,4.6c0.1,1.7,0.9,3.2,2.2,4.3c1.3,1.1,2.9,1.6,4.6,1.5C32.1,86.7,32.7,86.7,33.2,86.7z"/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path class="st1" d="M35.8,130.4c1.1,0.6,2.1,1.5,2.7,2.6c0.7,1.1,1,2.3,1,3.7s-0.3,2.6-1,3.7c-0.7,1.1-1.6,2-2.7,2.6
|
|
||||||
c-1.1,0.6-2.4,1-3.8,1s-2.7-0.3-3.8-1c-1.1-0.6-2.1-1.5-2.7-2.6c-0.7-1.1-1-2.3-1-3.7c0-1.3,0.3-2.6,1-3.7c0.7-1.1,1.6-2,2.7-2.6
|
|
||||||
c1.1-0.6,2.4-0.9,3.8-0.9C33.4,129.5,34.7,129.8,35.8,130.4z M29.9,132.9c-0.7,0.4-1.2,0.9-1.6,1.6s-0.6,1.4-0.6,2.2
|
|
||||||
c0,0.8,0.2,1.6,0.6,2.3c0.4,0.7,0.9,1.2,1.6,1.6c0.7,0.4,1.4,0.6,2.1,0.6c0.8,0,1.5-0.2,2.1-0.6c0.6-0.4,1.2-0.9,1.5-1.6
|
|
||||||
c0.4-0.7,0.6-1.4,0.6-2.3c0-0.8-0.2-1.6-0.6-2.2s-0.9-1.2-1.5-1.6c-0.6-0.4-1.4-0.6-2.1-0.6C31.3,132.3,30.6,132.5,29.9,132.9z"/>
|
|
||||||
<path class="st1" d="M50.6,133.6c0.8,0.5,1.4,1.1,1.8,2c0.4,0.8,0.6,1.8,0.6,2.9c0,1.1-0.2,2-0.6,2.8c-0.4,0.8-1,1.5-1.8,1.9
|
|
||||||
c-0.8,0.5-1.6,0.7-2.6,0.7c-0.7,0-1.4-0.1-2-0.4s-1.1-0.7-1.5-1.2v5.4h-3.1V133h3.1v1.6c0.4-0.5,0.9-1,1.4-1.2s1.2-0.4,2-0.4
|
|
||||||
C48.9,132.9,49.8,133.1,50.6,133.6z M49.1,140.5c0.5-0.6,0.7-1.3,0.7-2.2c0-0.9-0.2-1.6-0.7-2.1c-0.5-0.6-1.1-0.8-1.9-0.8
|
|
||||||
s-1.4,0.3-1.9,0.8c-0.5,0.6-0.8,1.3-0.8,2.1c0,0.9,0.2,1.6,0.8,2.2s1.1,0.8,1.9,0.8S48.6,141,49.1,140.5z"/>
|
|
||||||
<path class="st1" d="M63.4,134.4c0.9,1,1.4,2.4,1.4,4.2c0,0.3,0,0.6,0,0.7H57c0.2,0.7,0.5,1.2,1,1.6c0.5,0.4,1.1,0.6,1.8,0.6
|
|
||||||
c0.5,0,1-0.1,1.5-0.3s0.9-0.5,1.3-0.9l1.6,1.6c-0.5,0.6-1.2,1.1-2,1.4c-0.8,0.3-1.6,0.5-2.6,0.5c-1.1,0-2.1-0.2-3-0.7
|
|
||||||
s-1.5-1.1-2-1.9c-0.5-0.8-0.7-1.8-0.7-2.9c0-1.1,0.2-2.1,0.7-2.9s1.1-1.5,2-1.9c0.8-0.5,1.8-0.7,2.9-0.7
|
|
||||||
C61.2,132.9,62.5,133.4,63.4,134.4z M61.8,137.5c0-0.7-0.3-1.3-0.7-1.7s-1-0.6-1.7-0.6c-0.7,0-1.2,0.2-1.7,0.6
|
|
||||||
c-0.4,0.4-0.7,1-0.9,1.7H61.8z"/>
|
|
||||||
<path class="st1" d="M76.2,134c0.7,0.7,1.1,1.7,1.1,3v6.8h-3.1v-5.9c0-0.7-0.2-1.2-0.6-1.6s-0.9-0.6-1.5-0.6
|
|
||||||
c-0.8,0-1.4,0.3-1.8,0.8c-0.4,0.5-0.7,1.2-0.7,2v5.3h-3.1V133h3.1v1.9c0.7-1.3,2-2,3.7-2C74.6,132.8,75.5,133.2,76.2,134z"/>
|
|
||||||
<path class="st1" d="M96,129.7h3.3l-4.7,14h-3.3l-2.9-10.1l-3,10.1h-3.2l-4.7-14h3.4l3,10.7l3-10.7H90l3.1,10.7L96,129.7z"/>
|
|
||||||
<path class="st1" d="M103.3,128.7c0.3,0.3,0.5,0.7,0.5,1.2s-0.2,0.9-0.5,1.2c-0.3,0.3-0.7,0.5-1.2,0.5c-0.5,0-0.9-0.2-1.2-0.5
|
|
||||||
c-0.3-0.3-0.5-0.7-0.5-1.2c0-0.5,0.2-0.9,0.5-1.2c0.3-0.3,0.7-0.5,1.2-0.5C102.6,128.2,103,128.3,103.3,128.7z M100.6,133h3.1
|
|
||||||
v10.8h-3.1V133z"/>
|
|
||||||
<path class="st1" d="M106.5,129.7h10.1l0,2.6h-6.9v3.4h6.3v2.6h-6.3v5.3h-3.2V129.7z"/>
|
|
||||||
<path class="st1" d="M120.9,128.7c0.3,0.3,0.5,0.7,0.5,1.2s-0.2,0.9-0.5,1.2c-0.3,0.3-0.7,0.5-1.2,0.5c-0.5,0-0.9-0.2-1.2-0.5
|
|
||||||
c-0.3-0.3-0.5-0.7-0.5-1.2c0-0.5,0.2-0.9,0.5-1.2c0.3-0.3,0.7-0.5,1.2-0.5C120.1,128.2,120.5,128.3,120.9,128.7z M118.1,133h3.1
|
|
||||||
v10.8h-3.1V133z"/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 8.0 KiB |
@@ -1,14 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<link rel="icon" href="favicon.ico" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
<meta name="theme-color" content="#000000" />
|
|
||||||
<title>Gateway</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
|
||||||
<div id="root"></div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
BIN
public/mstile-150x150.png
Normal file
|
After Width: | Height: | Size: 9.9 KiB |
38
public/safari-pinned-tab.svg
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||||
|
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||||
|
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="744.000000pt" height="744.000000pt" viewBox="0 0 744.000000 744.000000"
|
||||||
|
preserveAspectRatio="xMidYMid meet">
|
||||||
|
<metadata>
|
||||||
|
Created by potrace 1.14, written by Peter Selinger 2001-2017
|
||||||
|
</metadata>
|
||||||
|
<g transform="translate(0.000000,744.000000) scale(0.100000,-0.100000)"
|
||||||
|
fill="#000000" stroke="none">
|
||||||
|
<path d="M1827 7404 c-2 -2 -50 -6 -108 -8 -57 -3 -120 -10 -139 -15 -19 -6
|
||||||
|
-56 -16 -82 -22 -27 -7 -48 -16 -48 -20 0 -4 -14 -10 -30 -14 -17 -4 -36 -12
|
||||||
|
-43 -18 -7 -7 -28 -21 -46 -31 -115 -64 -247 -224 -304 -366 -59 -150 -55 65
|
||||||
|
-56 -3180 0 -3270 -5 -3017 60 -3178 92 -232 303 -410 546 -463 32 -7 65 -14
|
||||||
|
73 -16 22 -5 4127 -3 4180 2 77 7 279 77 300 104 3 3 25 19 50 35 56 37 135
|
||||||
|
116 180 181 19 28 38 52 43 53 4 2 5 7 2 12 -3 4 2 13 10 20 8 7 15 21 15 31
|
||||||
|
0 10 4 20 9 23 8 6 39 91 47 131 2 11 7 31 11 45 4 14 8 1364 9 3000 2 3142 3
|
||||||
|
3032 -42 3165 -43 130 -131 255 -245 350 -33 28 -123 90 -127 87 -1 -1 -16 5
|
||||||
|
-34 14 -35 18 -128 50 -173 59 -30 6 -187 18 -215 16 -57 -3 -122 -1 -129 4
|
||||||
|
-5 3 -11 1 -13 -4 -1 -5 -23 -6 -48 -2 -25 4 -58 4 -75 1 -16 -3 -49 -3 -72 1
|
||||||
|
-23 3 -44 2 -45 -2 -2 -4 -23 -4 -48 0 -25 4 -58 4 -75 1 -16 -3 -49 -3 -72 1
|
||||||
|
-23 3 -44 2 -45 -2 -2 -4 -23 -4 -48 0 -25 4 -58 4 -75 1 -16 -3 -49 -3 -72 1
|
||||||
|
-23 3 -44 2 -45 -2 -2 -4 -23 -4 -48 0 -25 4 -58 4 -75 1 -16 -3 -49 -3 -72 1
|
||||||
|
-23 3 -44 2 -45 -2 -2 -4 -23 -4 -48 0 -25 4 -58 4 -75 1 -16 -3 -49 -3 -72 1
|
||||||
|
-23 3 -44 2 -45 -2 -2 -4 -23 -4 -48 0 -25 4 -58 4 -75 1 -16 -3 -49 -3 -72 1
|
||||||
|
-23 3 -44 2 -45 -2 -2 -4 -23 -4 -48 0 -25 4 -58 4 -75 1 -16 -3 -49 -3 -72 1
|
||||||
|
-23 3 -44 2 -45 -2 -2 -4 -23 -4 -48 0 -25 4 -58 4 -75 1 -16 -3 -49 -3 -72 1
|
||||||
|
-23 3 -44 2 -45 -2 -2 -4 -23 -4 -48 0 -25 4 -58 4 -75 1 -16 -3 -49 -3 -72 1
|
||||||
|
-23 3 -44 2 -45 -2 -2 -4 -23 -4 -48 0 -25 4 -58 4 -75 1 -16 -3 -49 -3 -72 1
|
||||||
|
-23 3 -44 2 -45 -2 -2 -4 -23 -4 -48 0 -25 4 -58 4 -75 1 -16 -3 -49 -3 -72 1
|
||||||
|
-23 3 -44 2 -45 -2 -2 -4 -23 -4 -48 0 -25 4 -58 4 -75 1 -16 -3 -49 -3 -72 1
|
||||||
|
-23 3 -44 2 -45 -2 -2 -4 -23 -4 -48 0 -25 4 -58 4 -75 1 -16 -3 -49 -3 -72 1
|
||||||
|
-23 3 -44 2 -45 -2 -2 -4 -23 -4 -48 0 -25 4 -58 4 -75 1 -16 -3 -49 -3 -72 1
|
||||||
|
-23 3 -44 2 -45 -2 -2 -4 -23 -4 -48 0 -25 4 -58 4 -75 1 -16 -3 -49 -3 -72 1
|
||||||
|
-23 3 -44 2 -45 -2 -2 -4 -24 -4 -50 0 -27 5 -49 6 -51 5z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.3 KiB |
39
src/App.js
@@ -1,39 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { HashRouter, Switch } from 'react-router-dom';
|
|
||||||
import 'scss/style.scss';
|
|
||||||
import Router from 'router';
|
|
||||||
import { AuthProvider } from 'ucentral-libs';
|
|
||||||
import { checkIfJson } from 'utils/helper';
|
|
||||||
import axiosInstance from 'utils/axiosInstance';
|
|
||||||
import { getItem } from 'utils/localStorageHelper';
|
|
||||||
|
|
||||||
const loading = (
|
|
||||||
<div className="pt-3 text-center">
|
|
||||||
<div className="sk-spinner sk-spinner-pulse" />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
const App = () => {
|
|
||||||
const storageToken = getItem('access_token');
|
|
||||||
const apiEndpoints = checkIfJson(getItem('gateway_endpoints'))
|
|
||||||
? JSON.parse(getItem('gateway_endpoints'))
|
|
||||||
: {};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AuthProvider
|
|
||||||
axiosInstance={axiosInstance}
|
|
||||||
token={storageToken ?? ''}
|
|
||||||
apiEndpoints={apiEndpoints}
|
|
||||||
>
|
|
||||||
<HashRouter>
|
|
||||||
<React.Suspense fallback={loading}>
|
|
||||||
<Switch>
|
|
||||||
<Router />
|
|
||||||
</Switch>
|
|
||||||
</React.Suspense>
|
|
||||||
</HashRouter>
|
|
||||||
</AuthProvider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default App;
|
|
||||||
39
src/App.tsx
Normal 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;
|
||||||
|
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 24 KiB |
@@ -1,165 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- Generator: Adobe Illustrator 24.2.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
|
||||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
|
||||||
viewBox="0 0 141.5 185.6" style="enable-background:new 0 0 141.5 185.6;" xml:space="preserve">
|
|
||||||
<style type="text/css">
|
|
||||||
.st0{fill:#414141;}
|
|
||||||
.st1{fill:#FFFFFF;}
|
|
||||||
.st2{fill:#FED206;}
|
|
||||||
.st3{fill:#EB6F53;}
|
|
||||||
.st4{fill:#3BA9B6;}
|
|
||||||
</style>
|
|
||||||
<g>
|
|
||||||
<g>
|
|
||||||
<path class="st0" d="M120.7,183.9H21.5c-10.8,0-19.5-8.7-19.5-19.5V20.5c0-10.8,8.7-19.5,19.5-19.5h99.2
|
|
||||||
c10.8,0,19.5,8.7,19.5,19.5v143.9C140.2,175.2,131.5,183.9,120.7,183.9z"/>
|
|
||||||
<g>
|
|
||||||
<g>
|
|
||||||
<g>
|
|
||||||
<path class="st1" d="M46.3,166.2v-3.4h-1.2v-0.6h3.1v0.6H47v3.4H46.3z"/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path class="st1" d="M49,166.2v-4h2.7v0.6h-2v1h2v0.6h-2v1.1h2v0.6H49z"/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path class="st1" d="M52.6,166.2v-4h0.7v3.4h1.8v0.6H52.6z"/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path class="st1" d="M55.7,166.2v-4h2.7v0.6h-2v1h2v0.6h-2v1.1h2v0.6H55.7z"/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path class="st1" d="M59.1,164.2c0-1.2,0.9-2.1,2.1-2.1c0.8,0,1.3,0.4,1.6,0.9l-0.6,0.3c-0.2-0.3-0.6-0.6-1-0.6
|
|
||||||
c-0.8,0-1.4,0.6-1.4,1.4c0,0.8,0.6,1.4,1.4,1.4c0.4,0,0.8-0.3,1-0.6l0.6,0.3c-0.3,0.5-0.8,0.9-1.6,0.9
|
|
||||||
C60,166.3,59.1,165.5,59.1,164.2z"/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path class="st1" d="M63.2,164.2c0-1.2,0.8-2.1,2-2.1c1.2,0,2,0.9,2,2.1c0,1.2-0.8,2.1-2,2.1C64,166.3,63.2,165.4,63.2,164.2z
|
|
||||||
M66.5,164.2c0-0.8-0.5-1.4-1.3-1.4c-0.8,0-1.3,0.6-1.3,1.4c0,0.8,0.5,1.4,1.3,1.4C66,165.7,66.5,165,66.5,164.2z"/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path class="st1" d="M71.3,166.2v-3.1l-1.2,3.1h-0.3l-1.2-3.1v3.1h-0.7v-4h1l1.1,2.7l1.1-2.7h1v4H71.3z"/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path class="st1" d="M75.7,166.2v-4h0.7v4H75.7z"/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path class="st1" d="M80.4,166.2l-2.1-2.8v2.8h-0.7v-4h0.7l2,2.8v-2.8h0.7v4H80.4z"/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path class="st1" d="M82.3,166.2v-4H85v0.6h-2v1h2v0.6h-2v1.7H82.3z"/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path class="st1" d="M87.9,166.2l-0.9-1.5h-0.7v1.5h-0.7v-4h1.7c0.8,0,1.3,0.5,1.3,1.2c0,0.7-0.5,1.1-0.9,1.2l1,1.6H87.9z
|
|
||||||
M88,163.5c0-0.4-0.3-0.6-0.7-0.6h-1v1.3h1C87.7,164.1,88,163.9,88,163.5z"/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path class="st1" d="M92.4,166.2l-0.3-0.8h-1.8l-0.3,0.8h-0.8l1.6-4h0.9l1.6,4H92.4z M91.2,162.9l-0.7,1.9h1.4L91.2,162.9z"/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path class="st1" d="M95.8,166.2v-4h1.5c0.8,0,1.2,0.5,1.2,1.2c0,0.6-0.4,1.2-1.2,1.2h-1.2v1.7H95.8z M98.2,163.4
|
|
||||||
c0-0.5-0.3-0.9-0.9-0.9h-1.1v1.7h1.1C97.8,164.3,98.2,163.9,98.2,163.4z"/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path class="st1" d="M101.5,166.2l-1.1-1.6h-0.9v1.6h-0.3v-4h1.5c0.7,0,1.2,0.4,1.2,1.2c0,0.7-0.5,1.1-1.1,1.1l1.2,1.7H101.5z
|
|
||||||
M101.6,163.4c0-0.5-0.4-0.9-0.9-0.9h-1.1v1.7h1.1C101.2,164.3,101.6,163.9,101.6,163.4z"/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path class="st1" d="M102.8,164.2c0-1.2,0.8-2.1,1.9-2.1c1.2,0,1.9,0.9,1.9,2.1c0,1.2-0.8,2.1-1.9,2.1
|
|
||||||
C103.6,166.3,102.8,165.4,102.8,164.2z M106.3,164.2c0-1-0.6-1.7-1.6-1.7c-1,0-1.6,0.7-1.6,1.7c0,1,0.6,1.7,1.6,1.7
|
|
||||||
C105.7,166,106.3,165.2,106.3,164.2z"/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path class="st1" d="M106.9,165.8l0.2-0.3c0.2,0.2,0.4,0.4,0.8,0.4c0.5,0,0.9-0.4,0.9-0.9v-2.8h0.3v2.8c0,0.8-0.5,1.2-1.2,1.2
|
|
||||||
C107.5,166.3,107.2,166.1,106.9,165.8z"/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path class="st1" d="M110.4,166.2v-4h2.5v0.3h-2.2v1.5h2.1v0.3h-2.1v1.6h2.2v0.3H110.4z"/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path class="st1" d="M113.5,164.2c0-1.2,0.9-2.1,2-2.1c0.6,0,1.1,0.3,1.5,0.7l-0.3,0.2c-0.3-0.3-0.7-0.6-1.2-0.6
|
|
||||||
c-0.9,0-1.7,0.7-1.7,1.7c0,1,0.7,1.7,1.7,1.7c0.5,0,0.9-0.2,1.2-0.6l0.3,0.2c-0.4,0.4-0.8,0.7-1.5,0.7
|
|
||||||
C114.4,166.3,113.5,165.5,113.5,164.2z"/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path class="st1" d="M118.7,166.2v-3.7h-1.3v-0.3h2.9v0.3H119v3.7H118.7z"/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<polygon class="st1" points="26.3,163.8 31.6,158.5 36.9,163.8 37.7,163.8 31.6,157.6 25.5,163.8 "/>
|
|
||||||
<polygon class="st1" points="36.9,164.7 31.6,170 26.3,164.7 25.5,164.7 31.6,170.8 37.7,164.7 "/>
|
|
||||||
<polygon class="st1" points="31,163.8 36.3,158.5 41.6,163.8 42.5,163.8 36.3,157.6 30.2,163.8 "/>
|
|
||||||
<polygon class="st1" points="41.6,164.7 36.3,170 31,164.7 30.2,164.7 36.3,170.8 42.5,164.7 "/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path class="st1" d="M33.2,100.7c-4.6,0-8.3,3.7-8.3,8.3s3.7,8.3,8.3,8.3s8.3-3.7,8.3-8.3S37.8,100.7,33.2,100.7z"/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<g>
|
|
||||||
<g>
|
|
||||||
<path class="st2" d="M33.2,35.2c40.7,0,73.8,33.1,73.8,73.8c0,0.7,0,1.4,0,2.1c0,1.7,0.6,3.3,1.7,4.6c1.2,1.2,2.8,1.9,4.5,2
|
|
||||||
l0.2,0c3.5,0,6.3-2.7,6.4-6.2c0-0.8,0-1.7,0-2.5c0-47.7-38.8-86.6-86.6-86.6c-0.8,0-1.7,0-2.5,0c-1.7,0-3.3,0.8-4.5,2
|
|
||||||
c-1.2,1.2-1.8,2.9-1.7,4.6c0.1,3.5,3,6.3,6.6,6.2C31.8,35.2,32.5,35.2,33.2,35.2z"/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<g>
|
|
||||||
<g>
|
|
||||||
<path class="st3" d="M33.2,60.5c26.7,0,48.5,21.7,48.5,48.5c0,0.6,0,1.3,0,2c-0.1,1.7,0.5,3.3,1.7,4.6c1.2,1.3,2.7,2,4.4,2.1
|
|
||||||
c1.7,0.1,3.3-0.5,4.6-1.7c1.2-1.2,2-2.7,2-4.4c0-0.9,0.1-1.8,0.1-2.6c0-33.8-27.5-61.2-61.2-61.2c-0.8,0-1.6,0-2.6,0.1
|
|
||||||
c-1.7,0.1-3.3,0.8-4.4,2.1c-1.2,1.3-1.8,2.9-1.7,4.6s0.8,3.3,2.1,4.4c1.3,1.2,2.9,1.8,4.6,1.7C31.9,60.5,32.6,60.5,33.2,60.5z"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<g>
|
|
||||||
<g>
|
|
||||||
<path class="st4" d="M33.2,86.7c12.3,0,22.3,10,22.3,22.3c0,0.5,0,1.1-0.1,1.8c-0.3,3.5,2.3,6.6,5.8,6.9
|
|
||||||
c3.5,0.3,6.6-2.3,6.9-5.8c0.1-1,0.1-1.9,0.1-2.8c0-19.3-15.7-35.1-35.1-35.1c-0.9,0-1.8,0-2.8,0.1c-1.7,0.1-3.2,0.9-4.3,2.2
|
|
||||||
c-1.1,1.3-1.6,2.9-1.5,4.6c0.1,1.7,0.9,3.2,2.2,4.3c1.3,1.1,2.9,1.6,4.6,1.5C32.1,86.7,32.7,86.7,33.2,86.7z"/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path class="st1" d="M35.8,130.4c1.1,0.6,2.1,1.5,2.7,2.6c0.7,1.1,1,2.3,1,3.7s-0.3,2.6-1,3.7c-0.7,1.1-1.6,2-2.7,2.6
|
|
||||||
c-1.1,0.6-2.4,1-3.8,1s-2.7-0.3-3.8-1c-1.1-0.6-2.1-1.5-2.7-2.6c-0.7-1.1-1-2.3-1-3.7c0-1.3,0.3-2.6,1-3.7c0.7-1.1,1.6-2,2.7-2.6
|
|
||||||
c1.1-0.6,2.4-0.9,3.8-0.9C33.4,129.5,34.7,129.8,35.8,130.4z M29.9,132.9c-0.7,0.4-1.2,0.9-1.6,1.6s-0.6,1.4-0.6,2.2
|
|
||||||
c0,0.8,0.2,1.6,0.6,2.3c0.4,0.7,0.9,1.2,1.6,1.6c0.7,0.4,1.4,0.6,2.1,0.6c0.8,0,1.5-0.2,2.1-0.6c0.6-0.4,1.2-0.9,1.5-1.6
|
|
||||||
c0.4-0.7,0.6-1.4,0.6-2.3c0-0.8-0.2-1.6-0.6-2.2s-0.9-1.2-1.5-1.6c-0.6-0.4-1.4-0.6-2.1-0.6C31.3,132.3,30.6,132.5,29.9,132.9z"/>
|
|
||||||
<path class="st1" d="M50.6,133.6c0.8,0.5,1.4,1.1,1.8,2c0.4,0.8,0.6,1.8,0.6,2.9c0,1.1-0.2,2-0.6,2.8c-0.4,0.8-1,1.5-1.8,1.9
|
|
||||||
c-0.8,0.5-1.6,0.7-2.6,0.7c-0.7,0-1.4-0.1-2-0.4s-1.1-0.7-1.5-1.2v5.4h-3.1V133h3.1v1.6c0.4-0.5,0.9-1,1.4-1.2s1.2-0.4,2-0.4
|
|
||||||
C48.9,132.9,49.8,133.1,50.6,133.6z M49.1,140.5c0.5-0.6,0.7-1.3,0.7-2.2c0-0.9-0.2-1.6-0.7-2.1c-0.5-0.6-1.1-0.8-1.9-0.8
|
|
||||||
s-1.4,0.3-1.9,0.8c-0.5,0.6-0.8,1.3-0.8,2.1c0,0.9,0.2,1.6,0.8,2.2s1.1,0.8,1.9,0.8S48.6,141,49.1,140.5z"/>
|
|
||||||
<path class="st1" d="M63.4,134.4c0.9,1,1.4,2.4,1.4,4.2c0,0.3,0,0.6,0,0.7H57c0.2,0.7,0.5,1.2,1,1.6c0.5,0.4,1.1,0.6,1.8,0.6
|
|
||||||
c0.5,0,1-0.1,1.5-0.3s0.9-0.5,1.3-0.9l1.6,1.6c-0.5,0.6-1.2,1.1-2,1.4c-0.8,0.3-1.6,0.5-2.6,0.5c-1.1,0-2.1-0.2-3-0.7
|
|
||||||
s-1.5-1.1-2-1.9c-0.5-0.8-0.7-1.8-0.7-2.9c0-1.1,0.2-2.1,0.7-2.9s1.1-1.5,2-1.9c0.8-0.5,1.8-0.7,2.9-0.7
|
|
||||||
C61.2,132.9,62.5,133.4,63.4,134.4z M61.8,137.5c0-0.7-0.3-1.3-0.7-1.7s-1-0.6-1.7-0.6c-0.7,0-1.2,0.2-1.7,0.6
|
|
||||||
c-0.4,0.4-0.7,1-0.9,1.7H61.8z"/>
|
|
||||||
<path class="st1" d="M76.2,134c0.7,0.7,1.1,1.7,1.1,3v6.8h-3.1v-5.9c0-0.7-0.2-1.2-0.6-1.6s-0.9-0.6-1.5-0.6
|
|
||||||
c-0.8,0-1.4,0.3-1.8,0.8c-0.4,0.5-0.7,1.2-0.7,2v5.3h-3.1V133h3.1v1.9c0.7-1.3,2-2,3.7-2C74.6,132.8,75.5,133.2,76.2,134z"/>
|
|
||||||
<path class="st1" d="M96,129.7h3.3l-4.7,14h-3.3l-2.9-10.1l-3,10.1h-3.2l-4.7-14h3.4l3,10.7l3-10.7H90l3.1,10.7L96,129.7z"/>
|
|
||||||
<path class="st1" d="M103.3,128.7c0.3,0.3,0.5,0.7,0.5,1.2s-0.2,0.9-0.5,1.2c-0.3,0.3-0.7,0.5-1.2,0.5c-0.5,0-0.9-0.2-1.2-0.5
|
|
||||||
c-0.3-0.3-0.5-0.7-0.5-1.2c0-0.5,0.2-0.9,0.5-1.2c0.3-0.3,0.7-0.5,1.2-0.5C102.6,128.2,103,128.3,103.3,128.7z M100.6,133h3.1
|
|
||||||
v10.8h-3.1V133z"/>
|
|
||||||
<path class="st1" d="M106.5,129.7h10.1l0,2.6h-6.9v3.4h6.3v2.6h-6.3v5.3h-3.2V129.7z"/>
|
|
||||||
<path class="st1" d="M120.9,128.7c0.3,0.3,0.5,0.7,0.5,1.2s-0.2,0.9-0.5,1.2c-0.3,0.3-0.7,0.5-1.2,0.5c-0.5,0-0.9-0.2-1.2-0.5
|
|
||||||
c-0.3-0.3-0.5-0.7-0.5-1.2c0-0.5,0.2-0.9,0.5-1.2c0.3-0.3,0.7-0.5,1.2-0.5C120.1,128.2,120.5,128.3,120.9,128.7z M118.1,133h3.1
|
|
||||||
v10.8h-3.1V133z"/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 8.0 KiB |
@@ -1,203 +0,0 @@
|
|||||||
import {
|
|
||||||
cifUs,
|
|
||||||
cifBr,
|
|
||||||
cifIn,
|
|
||||||
cifFr,
|
|
||||||
cifEs,
|
|
||||||
cifPl,
|
|
||||||
cilAlignCenter,
|
|
||||||
cilAlignLeft,
|
|
||||||
cilAlignRight,
|
|
||||||
cilApplicationsSettings,
|
|
||||||
cilArrowRight,
|
|
||||||
cilArrowTop,
|
|
||||||
cilAsterisk,
|
|
||||||
cilBan,
|
|
||||||
cilBarcode,
|
|
||||||
cilBasket,
|
|
||||||
cilBell,
|
|
||||||
cilBold,
|
|
||||||
cilBookmark,
|
|
||||||
cilCalculator,
|
|
||||||
cilCalendar,
|
|
||||||
cilCloudDownload,
|
|
||||||
cilChartPie,
|
|
||||||
cilCheck,
|
|
||||||
cilChevronBottom,
|
|
||||||
cilChevronLeft,
|
|
||||||
cilChevronRight,
|
|
||||||
cilChevronTop,
|
|
||||||
cilCircle,
|
|
||||||
cilCheckCircle,
|
|
||||||
cilCode,
|
|
||||||
cilCommentSquare,
|
|
||||||
cilCreditCard,
|
|
||||||
cilCursor,
|
|
||||||
cilCursorMove,
|
|
||||||
cilDrop,
|
|
||||||
cilDollar,
|
|
||||||
cilEnvelopeClosed,
|
|
||||||
cilEnvelopeLetter,
|
|
||||||
cilEnvelopeOpen,
|
|
||||||
cilEuro,
|
|
||||||
cilGlobeAlt,
|
|
||||||
cilGrid,
|
|
||||||
cilFile,
|
|
||||||
cilFullscreen,
|
|
||||||
cilFullscreenExit,
|
|
||||||
cilGraph,
|
|
||||||
cilHome,
|
|
||||||
cilInbox,
|
|
||||||
cilIndentDecrease,
|
|
||||||
cilIndentIncrease,
|
|
||||||
cilInputPower,
|
|
||||||
cilItalic,
|
|
||||||
cilJustifyCenter,
|
|
||||||
cilJustifyLeft,
|
|
||||||
cilLaptop,
|
|
||||||
cilLayers,
|
|
||||||
cilLightbulb,
|
|
||||||
cilList,
|
|
||||||
cilListNumbered,
|
|
||||||
cilListRich,
|
|
||||||
cilLocationPin,
|
|
||||||
cilLockLocked,
|
|
||||||
cilMagnifyingGlass,
|
|
||||||
cilMap,
|
|
||||||
cilMoon,
|
|
||||||
cilNotes,
|
|
||||||
cilOptions,
|
|
||||||
cilPaperclip,
|
|
||||||
cilPaperPlane,
|
|
||||||
cilPencil,
|
|
||||||
cilPeople,
|
|
||||||
cilPhone,
|
|
||||||
cilPrint,
|
|
||||||
cilPuzzle,
|
|
||||||
cilRouter,
|
|
||||||
cilSave,
|
|
||||||
cilScrubber,
|
|
||||||
cilSettings,
|
|
||||||
cilShare,
|
|
||||||
cilShareAll,
|
|
||||||
cilShareBoxed,
|
|
||||||
cilShieldAlt,
|
|
||||||
cilSpeech,
|
|
||||||
cilSpeedometer,
|
|
||||||
cilSpreadsheet,
|
|
||||||
cilStar,
|
|
||||||
cilSun,
|
|
||||||
cilTags,
|
|
||||||
cilTask,
|
|
||||||
cilTrash,
|
|
||||||
cilUnderline,
|
|
||||||
cilUser,
|
|
||||||
cilUserFemale,
|
|
||||||
cilUserFollow,
|
|
||||||
cilUserUnfollow,
|
|
||||||
cilX,
|
|
||||||
cilXCircle,
|
|
||||||
cilWarning,
|
|
||||||
} from '@coreui/icons';
|
|
||||||
|
|
||||||
export const icons = {
|
|
||||||
cilAlignCenter,
|
|
||||||
cilAlignLeft,
|
|
||||||
cilAlignRight,
|
|
||||||
cilApplicationsSettings,
|
|
||||||
cilArrowRight,
|
|
||||||
cilArrowTop,
|
|
||||||
cilAsterisk,
|
|
||||||
cilBan,
|
|
||||||
cilBarcode,
|
|
||||||
cilBasket,
|
|
||||||
cilBell,
|
|
||||||
cilBold,
|
|
||||||
cilBookmark,
|
|
||||||
cilCalculator,
|
|
||||||
cilCalendar,
|
|
||||||
cilCloudDownload,
|
|
||||||
cilChartPie,
|
|
||||||
cilCheck,
|
|
||||||
cilChevronBottom,
|
|
||||||
cilChevronLeft,
|
|
||||||
cilChevronRight,
|
|
||||||
cilChevronTop,
|
|
||||||
cilCircle,
|
|
||||||
cilCheckCircle,
|
|
||||||
cilCode,
|
|
||||||
cilCommentSquare,
|
|
||||||
cilCreditCard,
|
|
||||||
cilCursor,
|
|
||||||
cilCursorMove,
|
|
||||||
cilDrop,
|
|
||||||
cilDollar,
|
|
||||||
cilEnvelopeClosed,
|
|
||||||
cilEnvelopeLetter,
|
|
||||||
cilEnvelopeOpen,
|
|
||||||
cilEuro,
|
|
||||||
cilGlobeAlt,
|
|
||||||
cilGrid,
|
|
||||||
cilFile,
|
|
||||||
cilFullscreen,
|
|
||||||
cilFullscreenExit,
|
|
||||||
cilGraph,
|
|
||||||
cilHome,
|
|
||||||
cilInbox,
|
|
||||||
cilIndentDecrease,
|
|
||||||
cilIndentIncrease,
|
|
||||||
cilInputPower,
|
|
||||||
cilItalic,
|
|
||||||
cilJustifyCenter,
|
|
||||||
cilJustifyLeft,
|
|
||||||
cilLaptop,
|
|
||||||
cilLayers,
|
|
||||||
cilLightbulb,
|
|
||||||
cilList,
|
|
||||||
cilListNumbered,
|
|
||||||
cilListRich,
|
|
||||||
cilLocationPin,
|
|
||||||
cilLockLocked,
|
|
||||||
cilMagnifyingGlass,
|
|
||||||
cilMap,
|
|
||||||
cilMoon,
|
|
||||||
cilNotes,
|
|
||||||
cilOptions,
|
|
||||||
cilPaperclip,
|
|
||||||
cilPaperPlane,
|
|
||||||
cilPencil,
|
|
||||||
cilPeople,
|
|
||||||
cilPhone,
|
|
||||||
cilPrint,
|
|
||||||
cilPuzzle,
|
|
||||||
cilRouter,
|
|
||||||
cilSave,
|
|
||||||
cilScrubber,
|
|
||||||
cilSettings,
|
|
||||||
cilShare,
|
|
||||||
cilShareAll,
|
|
||||||
cilShareBoxed,
|
|
||||||
cilShieldAlt,
|
|
||||||
cilSpeech,
|
|
||||||
cilSpeedometer,
|
|
||||||
cilSpreadsheet,
|
|
||||||
cilStar,
|
|
||||||
cilSun,
|
|
||||||
cilTags,
|
|
||||||
cilTask,
|
|
||||||
cilTrash,
|
|
||||||
cilUnderline,
|
|
||||||
cilUser,
|
|
||||||
cilUserFemale,
|
|
||||||
cilUserFollow,
|
|
||||||
cilUserUnfollow,
|
|
||||||
cilX,
|
|
||||||
cilXCircle,
|
|
||||||
cilWarning,
|
|
||||||
cifUs,
|
|
||||||
cifBr,
|
|
||||||
cifIn,
|
|
||||||
cifFr,
|
|
||||||
cifEs,
|
|
||||||
cifPl,
|
|
||||||
};
|
|
||||||
@@ -1,163 +0,0 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import Select from 'react-select';
|
|
||||||
import {
|
|
||||||
CForm,
|
|
||||||
CInput,
|
|
||||||
CLabel,
|
|
||||||
CCol,
|
|
||||||
CFormGroup,
|
|
||||||
CInvalidFeedback,
|
|
||||||
CFormText,
|
|
||||||
CRow,
|
|
||||||
CTextarea,
|
|
||||||
} from '@coreui/react';
|
|
||||||
import { CopyToClipboardButton } from 'ucentral-libs';
|
|
||||||
|
|
||||||
const AddDefaultConfigurationForm = ({
|
|
||||||
t,
|
|
||||||
disable,
|
|
||||||
fields,
|
|
||||||
updateField,
|
|
||||||
updateFieldWithKey,
|
|
||||||
deviceTypes,
|
|
||||||
}) => {
|
|
||||||
const [typeOptions, setTypeOptions] = useState([]);
|
|
||||||
const [chosenTypes, setChosenTypes] = useState([]);
|
|
||||||
|
|
||||||
const parseOptions = () => {
|
|
||||||
const options = [{ value: '*', label: 'All' }];
|
|
||||||
const newOptions = deviceTypes.map((option) => ({
|
|
||||||
value: option,
|
|
||||||
label: option,
|
|
||||||
}));
|
|
||||||
options.push(...newOptions);
|
|
||||||
setTypeOptions(options);
|
|
||||||
setChosenTypes([]);
|
|
||||||
};
|
|
||||||
|
|
||||||
const typeOnChange = (chosenArray) => {
|
|
||||||
const allIndex = chosenArray.findIndex((el) => el.value === '*');
|
|
||||||
|
|
||||||
// If the All option was chosen before, we take it out of the array
|
|
||||||
if (allIndex === 0 && chosenTypes.length > 0) {
|
|
||||||
const newResults = chosenArray.slice(1);
|
|
||||||
setChosenTypes(newResults);
|
|
||||||
updateFieldWithKey('deviceTypes', {
|
|
||||||
value: newResults.map((el) => el.value),
|
|
||||||
error: false,
|
|
||||||
notEmpty: true,
|
|
||||||
});
|
|
||||||
} else if (allIndex > 0) {
|
|
||||||
setChosenTypes([{ value: '*', label: 'All' }]);
|
|
||||||
updateFieldWithKey('deviceTypes', { value: ['*'], error: false, notEmpty: true });
|
|
||||||
} else if (chosenArray.length > 0) {
|
|
||||||
setChosenTypes(chosenArray);
|
|
||||||
updateFieldWithKey('deviceTypes', {
|
|
||||||
value: chosenArray.map((el) => el.value),
|
|
||||||
error: false,
|
|
||||||
notEmpty: true,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setChosenTypes([]);
|
|
||||||
updateFieldWithKey('deviceTypes', { value: [], error: false, notEmpty: true });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
parseOptions();
|
|
||||||
}, [deviceTypes]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<CForm>
|
|
||||||
<CFormGroup row className="pb-3">
|
|
||||||
<CLabel col htmlFor="name">
|
|
||||||
{t('user.name')}
|
|
||||||
</CLabel>
|
|
||||||
<CCol sm="7">
|
|
||||||
<CInput
|
|
||||||
id="name"
|
|
||||||
type="text"
|
|
||||||
required
|
|
||||||
value={fields.name.value}
|
|
||||||
onChange={updateField}
|
|
||||||
invalid={fields.name.error}
|
|
||||||
disabled={disable}
|
|
||||||
maxLength="50"
|
|
||||||
/>
|
|
||||||
<CInvalidFeedback>{t('common.required')}</CInvalidFeedback>
|
|
||||||
</CCol>
|
|
||||||
</CFormGroup>
|
|
||||||
<CFormGroup row className="pb-3">
|
|
||||||
<CLabel col htmlFor="description">
|
|
||||||
{t('user.description')}
|
|
||||||
</CLabel>
|
|
||||||
<CCol sm="7">
|
|
||||||
<CInput
|
|
||||||
id="description"
|
|
||||||
type="text"
|
|
||||||
required
|
|
||||||
value={fields.description.value}
|
|
||||||
onChange={updateField}
|
|
||||||
invalid={fields.description.error}
|
|
||||||
disabled={disable}
|
|
||||||
maxLength="50"
|
|
||||||
/>
|
|
||||||
<CInvalidFeedback>{t('common.required')}</CInvalidFeedback>
|
|
||||||
</CCol>
|
|
||||||
</CFormGroup>
|
|
||||||
<CRow className="pb-3">
|
|
||||||
<CLabel col htmlFor="deviceTypes">
|
|
||||||
<div>{t('configuration.supported_device_types')}:</div>
|
|
||||||
</CLabel>
|
|
||||||
<CCol sm="7">
|
|
||||||
<Select
|
|
||||||
isMulti
|
|
||||||
closeMenuOnSelect={false}
|
|
||||||
id="deviceTypes"
|
|
||||||
options={typeOptions}
|
|
||||||
onChange={typeOnChange}
|
|
||||||
value={chosenTypes}
|
|
||||||
className={`basic-multi-select ${fields.deviceTypes.error ? 'border-danger' : ''}`}
|
|
||||||
classNamePrefix="select"
|
|
||||||
/>
|
|
||||||
<CFormText hidden={!fields.deviceTypes.error} color="danger">
|
|
||||||
{t('configuration.need_device_type')}
|
|
||||||
</CFormText>
|
|
||||||
</CCol>
|
|
||||||
</CRow>
|
|
||||||
<div className="pb-3">
|
|
||||||
{t('configure.enter_new')}
|
|
||||||
<CopyToClipboardButton t={t} size="sm" content={fields.configuration.value} />
|
|
||||||
</div>
|
|
||||||
<CRow className="pb-3">
|
|
||||||
<CCol>
|
|
||||||
<CTextarea
|
|
||||||
style={{ overflowY: 'scroll', height: '500px' }}
|
|
||||||
id="configuration"
|
|
||||||
type="text"
|
|
||||||
required
|
|
||||||
value={fields.configuration.value}
|
|
||||||
onChange={updateField}
|
|
||||||
invalid={fields.configuration.error}
|
|
||||||
disabled={disable}
|
|
||||||
/>
|
|
||||||
<CFormText hidden={!fields.configuration.error} color="danger">
|
|
||||||
{t('configure.valid_json')}
|
|
||||||
</CFormText>
|
|
||||||
</CCol>
|
|
||||||
</CRow>
|
|
||||||
</CForm>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
AddDefaultConfigurationForm.propTypes = {
|
|
||||||
t: PropTypes.func.isRequired,
|
|
||||||
disable: PropTypes.bool.isRequired,
|
|
||||||
fields: PropTypes.instanceOf(Object).isRequired,
|
|
||||||
updateField: PropTypes.func.isRequired,
|
|
||||||
updateFieldWithKey: PropTypes.func.isRequired,
|
|
||||||
deviceTypes: PropTypes.instanceOf(Array).isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AddDefaultConfigurationForm;
|
|
||||||
@@ -1,183 +0,0 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { CModal, CModalHeader, CModalTitle, CModalBody, CButton, CPopover } from '@coreui/react';
|
|
||||||
import CIcon from '@coreui/icons-react';
|
|
||||||
import { cilX, cilSave } from '@coreui/icons';
|
|
||||||
import { useToast, useFormFields, useAuth } from 'ucentral-libs';
|
|
||||||
import axiosInstance from 'utils/axiosInstance';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { checkIfJson } from 'utils/helper';
|
|
||||||
import Form from './Form';
|
|
||||||
|
|
||||||
const initialForm = {
|
|
||||||
name: {
|
|
||||||
value: '',
|
|
||||||
error: false,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
value: '',
|
|
||||||
error: false,
|
|
||||||
},
|
|
||||||
deviceTypes: {
|
|
||||||
value: [],
|
|
||||||
error: false,
|
|
||||||
notEmpty: true,
|
|
||||||
},
|
|
||||||
configuration: {
|
|
||||||
value: '',
|
|
||||||
error: false,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const AddConfigurationModal = ({ show, toggle, refresh }) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const { addToast } = useToast();
|
|
||||||
const { currentToken, endpoints } = useAuth();
|
|
||||||
const [fields, updateFieldWithId, updateField, setFormFields] = useFormFields(initialForm);
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const [deviceTypes, setDeviceTypes] = useState([]);
|
|
||||||
|
|
||||||
const getDeviceTypes = () => {
|
|
||||||
setLoading(true);
|
|
||||||
|
|
||||||
const headers = {
|
|
||||||
Accept: 'application/json',
|
|
||||||
Authorization: `Bearer ${currentToken}`,
|
|
||||||
};
|
|
||||||
|
|
||||||
axiosInstance
|
|
||||||
.get(`${endpoints.owfms}/api/v1/firmwares?deviceSet=true`, {
|
|
||||||
headers,
|
|
||||||
})
|
|
||||||
.then((response) => {
|
|
||||||
setDeviceTypes([...response.data.deviceTypes]);
|
|
||||||
})
|
|
||||||
.catch(() => {})
|
|
||||||
.finally(() => {
|
|
||||||
setLoading(false);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const validation = () => {
|
|
||||||
let success = true;
|
|
||||||
|
|
||||||
for (const [key, field] of Object.entries(fields)) {
|
|
||||||
if (field.required && field.value === '') {
|
|
||||||
updateField(key, { error: true });
|
|
||||||
success = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (field.notEmpty && field.value.length === 0) {
|
|
||||||
updateField(key, { error: true, notEmpty: true });
|
|
||||||
success = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!checkIfJson(fields.configuration.value)) {
|
|
||||||
updateField('configuration', { error: true });
|
|
||||||
success = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return success;
|
|
||||||
};
|
|
||||||
|
|
||||||
const addConfiguration = () => {
|
|
||||||
if (validation()) {
|
|
||||||
setLoading(true);
|
|
||||||
const options = {
|
|
||||||
headers: {
|
|
||||||
Accept: 'application/json',
|
|
||||||
Authorization: `Bearer ${currentToken}`,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const parameters = {
|
|
||||||
name: fields.name.value,
|
|
||||||
description: fields.description.value,
|
|
||||||
modelIds: fields.deviceTypes.value,
|
|
||||||
configuration: fields.configuration.value,
|
|
||||||
};
|
|
||||||
|
|
||||||
axiosInstance
|
|
||||||
.post(
|
|
||||||
`${endpoints.owgw}/api/v1/default_configuration/${fields.name.value}`,
|
|
||||||
parameters,
|
|
||||||
options,
|
|
||||||
)
|
|
||||||
.then(() => {
|
|
||||||
if (refresh !== null) refresh();
|
|
||||||
toggle();
|
|
||||||
addToast({
|
|
||||||
title: t('common.success'),
|
|
||||||
body: t('configuration.creation_success'),
|
|
||||||
color: 'success',
|
|
||||||
autohide: true,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
addToast({
|
|
||||||
title: t('common.error'),
|
|
||||||
body: t('entity.add_failure', { error: e.response?.data?.ErrorDescription }),
|
|
||||||
color: 'danger',
|
|
||||||
autohide: true,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
setLoading(false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (show) {
|
|
||||||
getDeviceTypes();
|
|
||||||
setFormFields(initialForm);
|
|
||||||
}
|
|
||||||
}, [show]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<CModal className="text-dark" size="lg" show={show} onClose={toggle}>
|
|
||||||
<CModalHeader className="p-1">
|
|
||||||
<CModalTitle className="pl-1 pt-1">{t('configuration.create')}</CModalTitle>
|
|
||||||
<div className="text-right">
|
|
||||||
<CPopover content={t('common.add')}>
|
|
||||||
<CButton color="primary" variant="outline" className="ml-2" onClick={addConfiguration}>
|
|
||||||
<CIcon content={cilSave} />
|
|
||||||
</CButton>
|
|
||||||
</CPopover>
|
|
||||||
<CPopover content={t('common.close')}>
|
|
||||||
<CButton color="primary" variant="outline" className="ml-2" onClick={toggle}>
|
|
||||||
<CIcon content={cilX} />
|
|
||||||
</CButton>
|
|
||||||
</CPopover>
|
|
||||||
</div>
|
|
||||||
</CModalHeader>
|
|
||||||
<CModalBody className="px-5">
|
|
||||||
<Form
|
|
||||||
t={t}
|
|
||||||
disable={loading}
|
|
||||||
fields={fields}
|
|
||||||
updateField={updateFieldWithId}
|
|
||||||
updateFieldWithKey={updateField}
|
|
||||||
deviceTypes={deviceTypes}
|
|
||||||
show={show}
|
|
||||||
/>
|
|
||||||
</CModalBody>
|
|
||||||
</CModal>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
AddConfigurationModal.propTypes = {
|
|
||||||
show: PropTypes.bool.isRequired,
|
|
||||||
toggle: PropTypes.func.isRequired,
|
|
||||||
refresh: PropTypes.func,
|
|
||||||
};
|
|
||||||
|
|
||||||
AddConfigurationModal.defaultProps = {
|
|
||||||
refresh: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AddConfigurationModal;
|
|
||||||
@@ -1,158 +0,0 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import {
|
|
||||||
CButton,
|
|
||||||
CModal,
|
|
||||||
CModalHeader,
|
|
||||||
CModalTitle,
|
|
||||||
CModalBody,
|
|
||||||
CPopover,
|
|
||||||
CRow,
|
|
||||||
CCol,
|
|
||||||
CLabel,
|
|
||||||
CTextarea,
|
|
||||||
CInput,
|
|
||||||
CInvalidFeedback,
|
|
||||||
} from '@coreui/react';
|
|
||||||
import CIcon from '@coreui/icons-react';
|
|
||||||
import { useAuth, useToast } from 'ucentral-libs';
|
|
||||||
import { cilPlus, cilX } from '@coreui/icons';
|
|
||||||
import axiosInstance from 'utils/axiosInstance';
|
|
||||||
|
|
||||||
const AddToBlacklistModal = ({ show, toggle, serialNumber, refresh }) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const { addToast } = useToast();
|
|
||||||
const { endpoints, currentToken } = useAuth();
|
|
||||||
const [chosenSerialNumber, setChosenSerialNumber] = useState('');
|
|
||||||
const [reason, setReason] = useState('');
|
|
||||||
|
|
||||||
const addToBlacklist = () => {
|
|
||||||
setLoading(true);
|
|
||||||
|
|
||||||
const parameters = {
|
|
||||||
serialNumber: chosenSerialNumber,
|
|
||||||
reason,
|
|
||||||
};
|
|
||||||
|
|
||||||
const headers = {
|
|
||||||
Accept: 'application/json',
|
|
||||||
Authorization: `Bearer ${currentToken}`,
|
|
||||||
};
|
|
||||||
|
|
||||||
axiosInstance
|
|
||||||
.post(`${endpoints.owgw}/api/v1/blacklist/${chosenSerialNumber}`, parameters, { headers })
|
|
||||||
.then(() => {
|
|
||||||
addToast({
|
|
||||||
title: t('common.success'),
|
|
||||||
body: t('device.success_added_blacklist'),
|
|
||||||
color: 'success',
|
|
||||||
autohide: true,
|
|
||||||
});
|
|
||||||
toggle();
|
|
||||||
if (refresh) refresh();
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
addToast({
|
|
||||||
title: t('common.error'),
|
|
||||||
body: t('device.error_adding_blacklist', { error: e.response?.data?.ErrorDescription }),
|
|
||||||
color: 'danger',
|
|
||||||
autohide: true,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
setLoading(false);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (show) {
|
|
||||||
if (serialNumber) setChosenSerialNumber(serialNumber);
|
|
||||||
else setChosenSerialNumber('');
|
|
||||||
}
|
|
||||||
}, [show, serialNumber]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<CModal className="text-dark" size="lg" show={show} onClose={toggle}>
|
|
||||||
<CModalHeader className="p-1">
|
|
||||||
<CModalTitle className="pl-1 pt-1">{t('device.add_to_blacklist')}</CModalTitle>
|
|
||||||
<div className="text-right">
|
|
||||||
<CPopover content={t('common.add')}>
|
|
||||||
<CButton
|
|
||||||
color="primary"
|
|
||||||
variant="outline"
|
|
||||||
className="ml-2"
|
|
||||||
onClick={addToBlacklist}
|
|
||||||
disabled={
|
|
||||||
chosenSerialNumber.length !== 12 ||
|
|
||||||
!chosenSerialNumber.match('^[a-fA-F0-9]+$') ||
|
|
||||||
reason === '' ||
|
|
||||||
loading
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<CIcon content={cilPlus} />
|
|
||||||
</CButton>
|
|
||||||
</CPopover>
|
|
||||||
<CPopover content={t('common.close')}>
|
|
||||||
<CButton color="primary" variant="outline" className="ml-2" onClick={toggle}>
|
|
||||||
<CIcon content={cilX} />
|
|
||||||
</CButton>
|
|
||||||
</CPopover>
|
|
||||||
</div>
|
|
||||||
</CModalHeader>
|
|
||||||
<CModalBody>
|
|
||||||
<CRow>
|
|
||||||
<CLabel col sm="3">
|
|
||||||
{t('common.serial_number')}
|
|
||||||
</CLabel>
|
|
||||||
<CCol sm="9" className="pt-1">
|
|
||||||
<CInput
|
|
||||||
id="description"
|
|
||||||
type="text"
|
|
||||||
required
|
|
||||||
value={chosenSerialNumber}
|
|
||||||
onChange={(e) => setChosenSerialNumber(e.target.value)}
|
|
||||||
invalid={
|
|
||||||
chosenSerialNumber.length !== 12 && chosenSerialNumber.match('^[a-fA-F0-9]+$')
|
|
||||||
}
|
|
||||||
disabled={loading}
|
|
||||||
maxLength="50"
|
|
||||||
/>
|
|
||||||
<CInvalidFeedback>{t('entity.valid_serial')}</CInvalidFeedback>
|
|
||||||
</CCol>
|
|
||||||
</CRow>
|
|
||||||
<CRow>
|
|
||||||
<CLabel col sm="3">
|
|
||||||
{t('common.reason')}
|
|
||||||
</CLabel>
|
|
||||||
<CCol sm="9" className="pt-2">
|
|
||||||
<CTextarea
|
|
||||||
name="reason"
|
|
||||||
id="reason"
|
|
||||||
rows="3"
|
|
||||||
type="text"
|
|
||||||
required
|
|
||||||
value={reason}
|
|
||||||
onChange={(e) => setReason(e.target.value)}
|
|
||||||
/>
|
|
||||||
</CCol>
|
|
||||||
</CRow>
|
|
||||||
</CModalBody>
|
|
||||||
</CModal>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
AddToBlacklistModal.propTypes = {
|
|
||||||
show: PropTypes.bool.isRequired,
|
|
||||||
toggle: PropTypes.func.isRequired,
|
|
||||||
serialNumber: PropTypes.string,
|
|
||||||
refresh: PropTypes.func,
|
|
||||||
};
|
|
||||||
|
|
||||||
AddToBlacklistModal.defaultProps = {
|
|
||||||
serialNumber: '',
|
|
||||||
refresh: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AddToBlacklistModal;
|
|
||||||
@@ -1,210 +0,0 @@
|
|||||||
import React, { useEffect } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import ReactPaginate from 'react-paginate';
|
|
||||||
import {
|
|
||||||
CCardBody,
|
|
||||||
CDataTable,
|
|
||||||
CButton,
|
|
||||||
CLink,
|
|
||||||
CCard,
|
|
||||||
CCardHeader,
|
|
||||||
CPopover,
|
|
||||||
CSelect,
|
|
||||||
CButtonToolbar,
|
|
||||||
} from '@coreui/react';
|
|
||||||
import { cilSearch, cilPencil, cilPlus, cilTrash } from '@coreui/icons';
|
|
||||||
import CIcon from '@coreui/icons-react';
|
|
||||||
import ReactTooltip from 'react-tooltip';
|
|
||||||
import { FormattedDate } from 'ucentral-libs';
|
|
||||||
|
|
||||||
const BlacklistTable = ({
|
|
||||||
currentPage,
|
|
||||||
devices,
|
|
||||||
toggleAddBlacklist,
|
|
||||||
toggleEditModal,
|
|
||||||
devicesPerPage,
|
|
||||||
loading,
|
|
||||||
removeFromBlacklist,
|
|
||||||
updateDevicesPerPage,
|
|
||||||
pageCount,
|
|
||||||
updatePage,
|
|
||||||
t,
|
|
||||||
}) => {
|
|
||||||
const columns = [
|
|
||||||
{ key: 'serialNumber', label: t('common.serial_number'), _style: { width: '6%' } },
|
|
||||||
{ key: 'created', label: t('device.blacklisted_on'), _style: { width: '1%' } },
|
|
||||||
{ key: 'author', label: t('common.by'), filter: false, _style: { width: '15%' } },
|
|
||||||
{ key: 'reason', label: t('common.reason'), filter: false },
|
|
||||||
{ key: 'actions', label: t('actions.actions'), _style: { width: '1%' } },
|
|
||||||
];
|
|
||||||
|
|
||||||
const hideTooltips = () => ReactTooltip.hide();
|
|
||||||
|
|
||||||
const escFunction = (event) => {
|
|
||||||
if (event.keyCode === 27) {
|
|
||||||
hideTooltips();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
document.addEventListener('keydown', escFunction, false);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener('keydown', escFunction, false);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<CCard className="m-0 p-0">
|
|
||||||
<CCardHeader className="p-0 text-right">
|
|
||||||
<CPopover content={t('device.add_to_blacklist')}>
|
|
||||||
<CButton size="sm" color="primary" onClick={toggleAddBlacklist}>
|
|
||||||
<CIcon content={cilPlus} />
|
|
||||||
</CButton>
|
|
||||||
</CPopover>
|
|
||||||
</CCardHeader>
|
|
||||||
<CCardBody className="p-0">
|
|
||||||
<CDataTable
|
|
||||||
addTableClasses="ignore-overflow table-sm"
|
|
||||||
items={devices ?? []}
|
|
||||||
fields={columns}
|
|
||||||
hover
|
|
||||||
border
|
|
||||||
loading={loading}
|
|
||||||
scopedSlots={{
|
|
||||||
serialNumber: (item) => (
|
|
||||||
<td className="text-center align-middle">
|
|
||||||
<CLink
|
|
||||||
className="c-subheader-nav-link"
|
|
||||||
aria-current="page"
|
|
||||||
to={() => `/devices/${item.serialNumber}`}
|
|
||||||
>
|
|
||||||
{item.serialNumber}
|
|
||||||
</CLink>
|
|
||||||
</td>
|
|
||||||
),
|
|
||||||
created: (item) => (
|
|
||||||
<td className="text-left align-middle">
|
|
||||||
<div style={{ width: '130px' }}>
|
|
||||||
<FormattedDate date={item.created} />
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
),
|
|
||||||
author: (item) => <td className="align-middle">{item.author}</td>,
|
|
||||||
reason: (item) => <td className="align-middle">{item.reason}</td>,
|
|
||||||
actions: (item) => (
|
|
||||||
<td className="text-center align-middle">
|
|
||||||
<CButtonToolbar
|
|
||||||
role="group"
|
|
||||||
className="justify-content-center"
|
|
||||||
style={{ width: '130px' }}
|
|
||||||
>
|
|
||||||
<CPopover content={t('configuration.details')}>
|
|
||||||
<CLink
|
|
||||||
className="c-subheader-nav-link"
|
|
||||||
aria-current="page"
|
|
||||||
to={() => `/devices/${item.serialNumber}`}
|
|
||||||
>
|
|
||||||
<CButton
|
|
||||||
color="primary"
|
|
||||||
variant="outline"
|
|
||||||
shape="square"
|
|
||||||
size="sm"
|
|
||||||
className="mx-1"
|
|
||||||
style={{ width: '33px', height: '30px' }}
|
|
||||||
>
|
|
||||||
<CIcon name="cil-search" content={cilSearch} size="sm" />
|
|
||||||
</CButton>
|
|
||||||
</CLink>
|
|
||||||
</CPopover>
|
|
||||||
<CPopover content={t('device.remove_from_blacklist')}>
|
|
||||||
<CButton
|
|
||||||
onClick={() => removeFromBlacklist(item.serialNumber)}
|
|
||||||
color="primary"
|
|
||||||
variant="outline"
|
|
||||||
shape="square"
|
|
||||||
size="sm"
|
|
||||||
className="mx-1"
|
|
||||||
style={{ width: '33px', height: '30px' }}
|
|
||||||
>
|
|
||||||
<CIcon content={cilTrash} size="sm" />
|
|
||||||
</CButton>
|
|
||||||
</CPopover>
|
|
||||||
<CPopover content={t('common.edit')}>
|
|
||||||
<CButton
|
|
||||||
onClick={() => toggleEditModal(item.serialNumber)}
|
|
||||||
color="primary"
|
|
||||||
variant="outline"
|
|
||||||
shape="square"
|
|
||||||
size="sm"
|
|
||||||
className="mx-1"
|
|
||||||
style={{ width: '33px', height: '30px' }}
|
|
||||||
>
|
|
||||||
<CIcon content={cilPencil} size="sm" />
|
|
||||||
</CButton>
|
|
||||||
</CPopover>
|
|
||||||
</CButtonToolbar>
|
|
||||||
</td>
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<div className="d-flex flex-row pl-3">
|
|
||||||
<div className="pr-3">
|
|
||||||
<ReactPaginate
|
|
||||||
previousLabel="← Previous"
|
|
||||||
nextLabel="Next →"
|
|
||||||
pageCount={pageCount}
|
|
||||||
onPageChange={updatePage}
|
|
||||||
forcePage={Number(currentPage)}
|
|
||||||
breakClassName="page-item"
|
|
||||||
breakLinkClassName="page-link"
|
|
||||||
containerClassName="pagination"
|
|
||||||
pageClassName="page-item"
|
|
||||||
pageLinkClassName="page-link"
|
|
||||||
previousClassName="page-item"
|
|
||||||
previousLinkClassName="page-link"
|
|
||||||
nextClassName="page-item"
|
|
||||||
nextLinkClassName="page-link"
|
|
||||||
activeClassName="active"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<p className="pr-2 mt-1">{t('common.items_per_page')}</p>
|
|
||||||
<div style={{ width: '100px' }} className="px-2">
|
|
||||||
<CSelect
|
|
||||||
custom
|
|
||||||
defaultValue={devicesPerPage}
|
|
||||||
onChange={(e) => updateDevicesPerPage(e.target.value)}
|
|
||||||
disabled={loading}
|
|
||||||
>
|
|
||||||
<option value="10">10</option>
|
|
||||||
<option value="25">25</option>
|
|
||||||
<option value="50">50</option>
|
|
||||||
</CSelect>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CCardBody>
|
|
||||||
</CCard>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
BlacklistTable.propTypes = {
|
|
||||||
currentPage: PropTypes.string,
|
|
||||||
devices: PropTypes.instanceOf(Array).isRequired,
|
|
||||||
toggleAddBlacklist: PropTypes.func.isRequired,
|
|
||||||
toggleEditModal: PropTypes.func.isRequired,
|
|
||||||
updateDevicesPerPage: PropTypes.func.isRequired,
|
|
||||||
pageCount: PropTypes.number.isRequired,
|
|
||||||
updatePage: PropTypes.func.isRequired,
|
|
||||||
devicesPerPage: PropTypes.string.isRequired,
|
|
||||||
removeFromBlacklist: PropTypes.func.isRequired,
|
|
||||||
t: PropTypes.func.isRequired,
|
|
||||||
loading: PropTypes.bool.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
BlacklistTable.defaultProps = {
|
|
||||||
currentPage: '0',
|
|
||||||
};
|
|
||||||
|
|
||||||
export default React.memo(BlacklistTable);
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
.firmwareTooltip {
|
|
||||||
opacity: 1 !important;
|
|
||||||
padding: 0px 0px 0px 0px !important;
|
|
||||||
border-radius: 1rem !important;
|
|
||||||
background-color: #fff !important;
|
|
||||||
border-color: #321fdb !important;
|
|
||||||
font-size: 0.875rem !important;
|
|
||||||
font-weight: 400 !important;
|
|
||||||
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.2) !important;
|
|
||||||
width: 400px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.deleteTooltip {
|
|
||||||
opacity: 1 !important;
|
|
||||||
padding: 0px 0px 0px 0px !important;
|
|
||||||
border-radius: 1rem !important;
|
|
||||||
background-color: #fff !important;
|
|
||||||
border-color: #321fdb !important;
|
|
||||||
font-size: 0.875rem !important;
|
|
||||||
font-weight: 400 !important;
|
|
||||||
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.2) !important;
|
|
||||||
width: 200px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tooltipHeader {
|
|
||||||
padding-left: 5px;
|
|
||||||
padding-right: 10px;
|
|
||||||
border-top-left-radius: 1rem !important;
|
|
||||||
border-top-right-radius: 1rem !important;
|
|
||||||
}
|
|
||||||
@@ -1,244 +0,0 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { useHistory } from 'react-router-dom';
|
|
||||||
import axiosInstance from 'utils/axiosInstance';
|
|
||||||
import { getItem, setItem } from 'utils/localStorageHelper';
|
|
||||||
import { useAuth, useToast, useToggle } from 'ucentral-libs';
|
|
||||||
import AddToBlacklistModal from 'components/AddToBlacklistModal';
|
|
||||||
import EditBlacklistModal from 'components/EditBlacklistModal';
|
|
||||||
import Table from './Table';
|
|
||||||
|
|
||||||
const BlacklistTable = () => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const { addToast } = useToast();
|
|
||||||
const history = useHistory();
|
|
||||||
const [page, setPage] = useState(parseInt(sessionStorage.getItem('deviceTable') ?? 0, 10));
|
|
||||||
const { currentToken, endpoints } = useAuth();
|
|
||||||
const [deviceCount, setDeviceCount] = useState(0);
|
|
||||||
const [pageCount, setPageCount] = useState(0);
|
|
||||||
const [devicesPerPage, setDevicesPerPage] = useState(getItem('devicesPerPage') || '10');
|
|
||||||
const [devices, setDevices] = useState([]);
|
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
const [editSerial, setEditSerial] = useState('');
|
|
||||||
const [showEditModal, setShowEditModal] = useState(false);
|
|
||||||
const [showAddModal, toggleAddModal] = useToggle(false);
|
|
||||||
|
|
||||||
const toggleEditModal = (serialNumber) => {
|
|
||||||
if (serialNumber) setEditSerial(serialNumber);
|
|
||||||
setShowEditModal(!showEditModal);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getDeviceInformation = (selectedPage = page, devicePerPage = devicesPerPage) => {
|
|
||||||
setLoading(true);
|
|
||||||
|
|
||||||
const options = {
|
|
||||||
headers: {
|
|
||||||
Accept: 'application/json',
|
|
||||||
Authorization: `Bearer ${currentToken}`,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
axiosInstance
|
|
||||||
.get(
|
|
||||||
`${endpoints.owgw}/api/v1/blacklist?limit=${devicePerPage}&offset=${
|
|
||||||
devicePerPage * selectedPage
|
|
||||||
}`,
|
|
||||||
options,
|
|
||||||
)
|
|
||||||
.then((response) => {
|
|
||||||
setDevices(response.data.devices);
|
|
||||||
setLoading(false);
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
addToast({
|
|
||||||
title: t('common.error'),
|
|
||||||
body: t('device.error_fetching_devices', { error: e.response?.data?.ErrorDescription }),
|
|
||||||
color: 'danger',
|
|
||||||
autohide: true,
|
|
||||||
});
|
|
||||||
setLoading(false);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const getCount = () => {
|
|
||||||
setLoading(true);
|
|
||||||
|
|
||||||
const headers = {
|
|
||||||
Accept: 'application/json',
|
|
||||||
Authorization: `Bearer ${currentToken}`,
|
|
||||||
};
|
|
||||||
|
|
||||||
axiosInstance
|
|
||||||
.get(`${endpoints.owgw}/api/v1/blacklist?countOnly=true`, {
|
|
||||||
headers,
|
|
||||||
})
|
|
||||||
.then((response) => {
|
|
||||||
const devicesCount = response.data.count;
|
|
||||||
const pagesCount = Math.ceil(devicesCount / devicesPerPage);
|
|
||||||
setPageCount(pagesCount);
|
|
||||||
setDeviceCount(devicesCount);
|
|
||||||
|
|
||||||
let selectedPage = page;
|
|
||||||
|
|
||||||
if (page >= pagesCount) {
|
|
||||||
history.push(`/devices?page=${pagesCount - 1}`);
|
|
||||||
selectedPage = pagesCount - 1;
|
|
||||||
}
|
|
||||||
getDeviceInformation(selectedPage);
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
addToast({
|
|
||||||
title: t('common.error'),
|
|
||||||
body: t('device.error_fetching_devices', { error: e.response?.data?.ErrorDescription }),
|
|
||||||
color: 'danger',
|
|
||||||
autohide: true,
|
|
||||||
});
|
|
||||||
setLoading(false);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const refreshDevice = (serialNumber) => {
|
|
||||||
setLoading(true);
|
|
||||||
|
|
||||||
const options = {
|
|
||||||
headers: {
|
|
||||||
Accept: 'application/json',
|
|
||||||
Authorization: `Bearer ${currentToken}`,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let newDevice;
|
|
||||||
|
|
||||||
axiosInstance
|
|
||||||
.get(
|
|
||||||
`${endpoints.owgw}/api/v1/blacklist?deviceWithStatus=true&select=${encodeURIComponent(
|
|
||||||
serialNumber,
|
|
||||||
)}`,
|
|
||||||
options,
|
|
||||||
)
|
|
||||||
.then(
|
|
||||||
({
|
|
||||||
data: {
|
|
||||||
devicesWithStatus: [device],
|
|
||||||
},
|
|
||||||
}) => {
|
|
||||||
newDevice = device;
|
|
||||||
|
|
||||||
return axiosInstance.get(
|
|
||||||
`${endpoints.owfms}/api/v1/firmwareAge?select=${serialNumber}`,
|
|
||||||
options,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.then((response) => {
|
|
||||||
newDevice.firmwareInfo = {
|
|
||||||
age: response.data.ages[0].age,
|
|
||||||
latest: response.data.ages[0].latest,
|
|
||||||
};
|
|
||||||
const foundIndex = devices.findIndex((obj) => obj.serialNumber === serialNumber);
|
|
||||||
const newList = devices;
|
|
||||||
newList[foundIndex] = newDevice;
|
|
||||||
setDevices(newList);
|
|
||||||
setLoading(false);
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
addToast({
|
|
||||||
title: t('common.error'),
|
|
||||||
body: t('device.error_fetching_devices', { error: e.response?.data?.ErrorDescription }),
|
|
||||||
color: 'danger',
|
|
||||||
autohide: true,
|
|
||||||
});
|
|
||||||
setLoading(false);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateDevicesPerPage = (value) => {
|
|
||||||
setItem('devicesPerPage', value);
|
|
||||||
setDevicesPerPage(value);
|
|
||||||
|
|
||||||
const newPageCount = Math.ceil(deviceCount / value);
|
|
||||||
setPageCount(newPageCount);
|
|
||||||
|
|
||||||
let selectedPage = page;
|
|
||||||
|
|
||||||
if (page >= newPageCount) {
|
|
||||||
history.push(`/blacklist?page=${newPageCount - 1}`);
|
|
||||||
selectedPage = newPageCount - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
getDeviceInformation(selectedPage, value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const updatePageCount = ({ selected: selectedPage }) => {
|
|
||||||
sessionStorage.setItem('deviceTable', selectedPage);
|
|
||||||
setPage(selectedPage);
|
|
||||||
getDeviceInformation(selectedPage);
|
|
||||||
};
|
|
||||||
|
|
||||||
const removeFromBlacklist = (serialNumber) => {
|
|
||||||
setLoading(true);
|
|
||||||
|
|
||||||
const headers = {
|
|
||||||
Accept: 'application/json',
|
|
||||||
Authorization: `Bearer ${currentToken}`,
|
|
||||||
};
|
|
||||||
|
|
||||||
axiosInstance
|
|
||||||
.delete(`${endpoints.owgw}/api/v1/blacklist/${serialNumber}`, { headers })
|
|
||||||
.then(() => {
|
|
||||||
addToast({
|
|
||||||
title: t('common.success'),
|
|
||||||
body: t('device.success_removed_blacklist'),
|
|
||||||
color: 'success',
|
|
||||||
autohide: true,
|
|
||||||
});
|
|
||||||
getCount();
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
addToast({
|
|
||||||
title: t('common.error'),
|
|
||||||
body: t('device.error_adding_blacklist', { error: e.response?.data?.ErrorDescription }),
|
|
||||||
color: 'danger',
|
|
||||||
autohide: true,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
setLoading(false);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
getCount();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Table
|
|
||||||
currentPage={page}
|
|
||||||
t={t}
|
|
||||||
devices={devices}
|
|
||||||
loading={loading}
|
|
||||||
toggleAddBlacklist={toggleAddModal}
|
|
||||||
toggleEditModal={toggleEditModal}
|
|
||||||
updateDevicesPerPage={updateDevicesPerPage}
|
|
||||||
devicesPerPage={devicesPerPage}
|
|
||||||
pageCount={pageCount}
|
|
||||||
updatePage={updatePageCount}
|
|
||||||
pageRangeDisplayed={5}
|
|
||||||
refreshDevice={refreshDevice}
|
|
||||||
removeFromBlacklist={removeFromBlacklist}
|
|
||||||
/>
|
|
||||||
{showAddModal ? (
|
|
||||||
<AddToBlacklistModal show={showAddModal} toggle={toggleAddModal} refresh={getCount} />
|
|
||||||
) : null}
|
|
||||||
<EditBlacklistModal
|
|
||||||
show={showEditModal}
|
|
||||||
toggle={toggleEditModal}
|
|
||||||
refresh={getCount}
|
|
||||||
serialNumber={editSerial}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default BlacklistTable;
|
|
||||||
@@ -1,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;
|
|
||||||
50
src/components/Buttons/AlertButton/index.tsx
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Button, IconButton, Tooltip, useBreakpoint } from '@chakra-ui/react';
|
||||||
|
import { Warning } from 'phosphor-react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { ThemeProps } from 'models/Theme';
|
||||||
|
|
||||||
|
export interface AlertButtonProps extends ThemeProps {
|
||||||
|
onClick: () => void;
|
||||||
|
isDisabled?: boolean;
|
||||||
|
isLoading?: boolean;
|
||||||
|
isCompact?: boolean;
|
||||||
|
label?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const _AlertButton: React.FC<AlertButtonProps> = ({ onClick, isDisabled, isLoading, isCompact, label, ...props }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const breakpoint = useBreakpoint();
|
||||||
|
|
||||||
|
if (!isCompact && breakpoint !== 'base' && breakpoint !== 'sm') {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
colorScheme="red"
|
||||||
|
type="button"
|
||||||
|
onClick={onClick}
|
||||||
|
rightIcon={<Warning size={20} />}
|
||||||
|
isLoading={isLoading}
|
||||||
|
isDisabled={isDisabled}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{label ?? t('common.alert')}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Tooltip label={label ?? t('common.alert')}>
|
||||||
|
<IconButton
|
||||||
|
aria-label="alert-button"
|
||||||
|
colorScheme="red"
|
||||||
|
type="button"
|
||||||
|
onClick={onClick}
|
||||||
|
icon={<Warning size={20} />}
|
||||||
|
isLoading={isLoading}
|
||||||
|
isDisabled={isDisabled}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AlertButton = React.memo(_AlertButton);
|
||||||
28
src/components/Buttons/CloseButton/index.tsx
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { IconButton, SpaceProps } from '@chakra-ui/react';
|
||||||
|
import { X } from 'phosphor-react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
export interface CloseButtonProps extends SpaceProps {
|
||||||
|
onClick: () => void;
|
||||||
|
isDisabled?: boolean;
|
||||||
|
isLoading?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const _CloseButton: React.FC<CloseButtonProps> = ({ onClick, isDisabled, isLoading, ...props }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IconButton
|
||||||
|
aria-label={t('common.close')}
|
||||||
|
colorScheme="gray"
|
||||||
|
onClick={onClick}
|
||||||
|
icon={<X size={20} />}
|
||||||
|
isLoading={isLoading}
|
||||||
|
isDisabled={isDisabled}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CloseButton = React.memo(_CloseButton);
|
||||||
49
src/components/Buttons/CreateButton/index.tsx
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Button, IconButton, Tooltip, useBreakpoint, SpaceProps } from '@chakra-ui/react';
|
||||||
|
import { Plus } from 'phosphor-react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
export interface CreateButtonProps extends SpaceProps {
|
||||||
|
onClick?: () => void;
|
||||||
|
isDisabled?: boolean;
|
||||||
|
isLoading?: boolean;
|
||||||
|
isCompact?: boolean;
|
||||||
|
label?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const _CreateButton: React.FC<CreateButtonProps> = ({ onClick, isDisabled, isLoading, isCompact, label, ...props }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const breakpoint = useBreakpoint();
|
||||||
|
|
||||||
|
if (!isCompact && breakpoint !== 'base' && breakpoint !== 'sm') {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
colorScheme="blue"
|
||||||
|
type="button"
|
||||||
|
onClick={onClick}
|
||||||
|
rightIcon={<Plus size={20} />}
|
||||||
|
isLoading={isLoading}
|
||||||
|
isDisabled={isDisabled}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{label ?? t('common.create')}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Tooltip label={label ?? t('common.create')}>
|
||||||
|
<IconButton
|
||||||
|
aria-label="Create"
|
||||||
|
colorScheme="blue"
|
||||||
|
type="button"
|
||||||
|
onClick={onClick}
|
||||||
|
icon={<Plus size={20} />}
|
||||||
|
isLoading={isLoading}
|
||||||
|
isDisabled={isDisabled}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CreateButton = React.memo(_CreateButton);
|
||||||
61
src/components/Buttons/DeleteButton/index.tsx
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Button, IconButton, Tooltip, useBreakpoint } from '@chakra-ui/react';
|
||||||
|
import { Trash } from 'phosphor-react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
export interface DeleteButtonProps {
|
||||||
|
onClick: () => void;
|
||||||
|
isDisabled?: boolean;
|
||||||
|
isLoading?: boolean;
|
||||||
|
isCompact?: boolean;
|
||||||
|
label?: string;
|
||||||
|
ml?: string | number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const _DeleteButton: React.FC<DeleteButtonProps> = ({
|
||||||
|
onClick,
|
||||||
|
isDisabled,
|
||||||
|
isLoading,
|
||||||
|
isCompact,
|
||||||
|
label,
|
||||||
|
ml,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const breakpoint = useBreakpoint();
|
||||||
|
|
||||||
|
if (!isCompact && breakpoint !== 'base' && breakpoint !== 'sm') {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
colorScheme="red"
|
||||||
|
onClick={onClick}
|
||||||
|
rightIcon={<Trash size={20} />}
|
||||||
|
isLoading={isLoading}
|
||||||
|
isDisabled={isDisabled}
|
||||||
|
ml={ml}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{label ?? t('crud.delete')}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip label={label ?? t('crud.delete')}>
|
||||||
|
<IconButton
|
||||||
|
colorScheme="red"
|
||||||
|
aria-label="delete"
|
||||||
|
type="button"
|
||||||
|
onClick={onClick}
|
||||||
|
icon={<Trash size={20} />}
|
||||||
|
isLoading={isLoading}
|
||||||
|
isDisabled={isDisabled}
|
||||||
|
ml={ml}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DeleteButton = React.memo(_DeleteButton);
|
||||||
82
src/components/Buttons/DeviceActionDropdown/RebootButton.tsx
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { MenuItem, useToast } from '@chakra-ui/react';
|
||||||
|
import { 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;
|
||||||
176
src/components/Buttons/DeviceActionDropdown/index.tsx
Normal 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);
|
||||||
46
src/components/Buttons/EditButton/index.tsx
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { IconButton, Button, Tooltip, useBreakpoint } from '@chakra-ui/react';
|
||||||
|
import { Pen } from 'phosphor-react';
|
||||||
|
|
||||||
|
export interface EditButtonProps {
|
||||||
|
onClick: () => void;
|
||||||
|
isDisabled?: boolean;
|
||||||
|
isLoading?: boolean;
|
||||||
|
isCompact?: boolean;
|
||||||
|
label?: string;
|
||||||
|
ml?: string | number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const _EditButton: React.FC<EditButtonProps> = ({ onClick, label, isDisabled, isLoading, isCompact, ...props }) => {
|
||||||
|
const breakpoint = useBreakpoint();
|
||||||
|
|
||||||
|
if (!isCompact && breakpoint !== 'base' && breakpoint !== 'sm') {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
colorScheme="gray"
|
||||||
|
onClick={onClick}
|
||||||
|
rightIcon={<Pen size={20} />}
|
||||||
|
isLoading={isLoading}
|
||||||
|
isDisabled={isDisabled}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Tooltip label={label}>
|
||||||
|
<IconButton
|
||||||
|
aria-label="edit"
|
||||||
|
colorScheme="gray"
|
||||||
|
onClick={onClick}
|
||||||
|
icon={<Pen size={20} />}
|
||||||
|
isLoading={isLoading}
|
||||||
|
isDisabled={isDisabled}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EditButton = React.memo(_EditButton);
|
||||||
79
src/components/Buttons/FileInputButton/index.tsx
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { FormControl, Input, InputGroup } from '@chakra-ui/react';
|
||||||
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
|
export interface FileInputButtonProps {
|
||||||
|
value: string;
|
||||||
|
setValue: (v: string, file?: File) => void;
|
||||||
|
setFileName?: (v: string) => void;
|
||||||
|
refreshId: string;
|
||||||
|
accept: string;
|
||||||
|
isHidden?: boolean;
|
||||||
|
isStringFile?: boolean;
|
||||||
|
sizeLimit?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const _FileInputButton: React.FC<FileInputButtonProps> = ({
|
||||||
|
value,
|
||||||
|
setValue,
|
||||||
|
setFileName,
|
||||||
|
refreshId,
|
||||||
|
accept,
|
||||||
|
isHidden,
|
||||||
|
isStringFile,
|
||||||
|
sizeLimit,
|
||||||
|
}) => {
|
||||||
|
const [fileKey, setFileKey] = useState(uuid());
|
||||||
|
let fileReader: FileReader | undefined;
|
||||||
|
|
||||||
|
const handleStringFileRead = () => {
|
||||||
|
if (fileReader) {
|
||||||
|
const content = fileReader.result;
|
||||||
|
if (content) {
|
||||||
|
setValue(content as string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const changeFile = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const file = e.target.files ? e.target.files[0] : undefined;
|
||||||
|
if (file) {
|
||||||
|
if (sizeLimit && file.size > sizeLimit) {
|
||||||
|
setFileKey(uuid());
|
||||||
|
} else {
|
||||||
|
const newVal = URL.createObjectURL(file);
|
||||||
|
if (!isStringFile) {
|
||||||
|
setValue(newVal, file);
|
||||||
|
if (setFileName) setFileName(file.name ?? '');
|
||||||
|
} else {
|
||||||
|
fileReader = new FileReader();
|
||||||
|
if (setFileName) setFileName(file.name);
|
||||||
|
fileReader.onloadend = handleStringFileRead;
|
||||||
|
fileReader.readAsText(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (value === '') setFileKey(uuid());
|
||||||
|
}, [refreshId, value]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormControl hidden={isHidden}>
|
||||||
|
<InputGroup>
|
||||||
|
<Input
|
||||||
|
borderRadius="15px"
|
||||||
|
pt={1}
|
||||||
|
fontSize="sm"
|
||||||
|
type="file"
|
||||||
|
onChange={changeFile}
|
||||||
|
key={fileKey}
|
||||||
|
accept={accept}
|
||||||
|
/>
|
||||||
|
</InputGroup>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FileInputButton = React.memo(_FileInputButton);
|
||||||
61
src/components/Buttons/RefreshButton/index.tsx
Normal 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);
|
||||||
59
src/components/Buttons/ResponsiveButton/index.tsx
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Button, IconButton, SpaceProps, Tooltip, useBreakpoint } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
export interface ResponsiveButtonProps extends SpaceProps {
|
||||||
|
onClick: () => void;
|
||||||
|
isDisabled?: boolean;
|
||||||
|
isLoading?: boolean;
|
||||||
|
isCompact?: boolean;
|
||||||
|
color: string;
|
||||||
|
label: string;
|
||||||
|
icon?: React.ReactElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
const _ResponsiveButton: React.FC<ResponsiveButtonProps> = ({
|
||||||
|
onClick,
|
||||||
|
isDisabled,
|
||||||
|
isLoading,
|
||||||
|
isCompact,
|
||||||
|
color,
|
||||||
|
label,
|
||||||
|
icon,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
const breakpoint = useBreakpoint();
|
||||||
|
|
||||||
|
if (!isCompact && breakpoint !== 'base' && breakpoint !== 'sm') {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
colorScheme={color}
|
||||||
|
type="button"
|
||||||
|
onClick={onClick}
|
||||||
|
// @ts-ignore
|
||||||
|
rightIcon={icon}
|
||||||
|
isLoading={isLoading}
|
||||||
|
isDisabled={isDisabled}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Tooltip label={label}>
|
||||||
|
<IconButton
|
||||||
|
aria-label={label}
|
||||||
|
colorScheme={color}
|
||||||
|
type="button"
|
||||||
|
onClick={onClick}
|
||||||
|
// @ts-ignore
|
||||||
|
icon={icon}
|
||||||
|
isLoading={isLoading}
|
||||||
|
isDisabled={isDisabled}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ResponsiveButton = React.memo(_ResponsiveButton);
|
||||||
60
src/components/Buttons/SaveButton/index.tsx
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Button, IconButton, Tooltip, useBreakpoint } from '@chakra-ui/react';
|
||||||
|
import { FloppyDisk } from 'phosphor-react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
export interface SaveButtonProps
|
||||||
|
extends React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement> {
|
||||||
|
onClick: () => void;
|
||||||
|
isDisabled?: boolean;
|
||||||
|
isLoading?: boolean;
|
||||||
|
isCompact?: boolean;
|
||||||
|
isDirty?: boolean;
|
||||||
|
dirtyCheck?: boolean;
|
||||||
|
ml?: string | number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const _SaveButton: React.FC<SaveButtonProps> = ({
|
||||||
|
onClick,
|
||||||
|
isDisabled,
|
||||||
|
isLoading,
|
||||||
|
isCompact,
|
||||||
|
isDirty,
|
||||||
|
dirtyCheck,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const breakpoint = useBreakpoint();
|
||||||
|
|
||||||
|
if (!isCompact && breakpoint !== 'base' && breakpoint !== 'sm') {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
colorScheme="blue"
|
||||||
|
type="submit"
|
||||||
|
onClick={onClick}
|
||||||
|
rightIcon={<FloppyDisk size={20} />}
|
||||||
|
isLoading={isLoading}
|
||||||
|
isDisabled={isDisabled || (dirtyCheck && !isDirty)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{t('common.save')}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Tooltip label={t('common.save')}>
|
||||||
|
<IconButton
|
||||||
|
aria-label="save"
|
||||||
|
colorScheme="blue"
|
||||||
|
type="submit"
|
||||||
|
onClick={onClick}
|
||||||
|
icon={<FloppyDisk size={20} />}
|
||||||
|
isLoading={isLoading}
|
||||||
|
isDisabled={isDisabled || (dirtyCheck && !isDirty)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SaveButton = React.memo(_SaveButton);
|
||||||
78
src/components/Buttons/StepButton/index.tsx
Normal 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);
|
||||||
83
src/components/Buttons/ToggleEditButton/index.tsx
Normal 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);
|
||||||
57
src/components/Buttons/WarningButton/index.tsx
Normal 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);
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
31
src/components/CompactTaskDisplay/index.tsx
Normal 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;
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||