Compare commits
	
		
			131 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					177d24e508 | ||
| 
						 | 
					69b0d1ee9d | ||
| 
						 | 
					ef52497b04 | ||
| 
						 | 
					039e641046 | ||
| 
						 | 
					f1f62efe6f | ||
| 
						 | 
					b3053f32b2 | ||
| 
						 | 
					09184b0402 | ||
| 
						 | 
					98562fd967 | ||
| 
						 | 
					65e9e64cb4 | ||
| 
						 | 
					573ecbd58d | ||
| 
						 | 
					a801fcca49 | ||
| 
						 | 
					e9d16ee172 | ||
| 
						 | 
					db4dfc93e8 | ||
| 
						 | 
					975b715a7c | ||
| 
						 | 
					cf17f03ae0 | ||
| 
						 | 
					64f3ee797e | ||
| 
						 | 
					e287705e88 | ||
| 
						 | 
					9583b2bae0 | ||
| 
						 | 
					2698993a6d | ||
| 
						 | 
					a14b595e8c | ||
| 
						 | 
					d7957b85ae | ||
| 
						 | 
					227a51423d | ||
| 
						 | 
					ea0e7340cc | ||
| 
						 | 
					999680e94b | ||
| 
						 | 
					566dbbb157 | ||
| 
						 | 
					75d995d54e | ||
| 
						 | 
					908faa491b | ||
| 
						 | 
					7a254e343e | ||
| 
						 | 
					016ac336b9 | ||
| 
						 | 
					1cfd3a10ad | ||
| 
						 | 
					1838029d22 | ||
| 
						 | 
					7767043a5a | ||
| 
						 | 
					b1cfa6db19 | ||
| 
						 | 
					623d5a5546 | ||
| 
						 | 
					8c676eb965 | ||
| 
						 | 
					1e4ccce36c | ||
| 
						 | 
					1808206e74 | ||
| 
						 | 
					0fbc2b92aa | ||
| 
						 | 
					42d274e988 | ||
| 
						 | 
					ec64680a66 | ||
| 
						 | 
					d006b89efd | ||
| 
						 | 
					48654c382d | ||
| 
						 | 
					8d23168a87 | ||
| 
						 | 
					e27baaec9d | ||
| 
						 | 
					31a37ae506 | ||
| 
						 | 
					6360f90102 | ||
| 
						 | 
					b829003711 | ||
| 
						 | 
					fae773b25a | ||
| 
						 | 
					0e8df4441d | ||
| 
						 | 
					35e7e26d67 | ||
| 
						 | 
					14c88280f5 | ||
| 
						 | 
					aaab9d3bc5 | ||
| 
						 | 
					02095595c6 | ||
| 
						 | 
					9a80664ce2 | ||
| 
						 | 
					b69e7e4ddf | ||
| 
						 | 
					db642782b0 | ||
| 
						 | 
					33dedbbfa3 | ||
| 
						 | 
					a8f53de511 | ||
| 
						 | 
					3b7dad989f | ||
| 
						 | 
					4d62c5298a | ||
| 
						 | 
					538c6b5233 | ||
| 
						 | 
					969a14802b | ||
| 
						 | 
					5c7f683d16 | ||
| 
						 | 
					c7874e5e40 | ||
| 
						 | 
					628e4fa873 | ||
| 
						 | 
					8e01abbeb6 | ||
| 
						 | 
					89ee99f98d | ||
| 
						 | 
					21452d091f | ||
| 
						 | 
					d21f55b476 | ||
| 
						 | 
					d01453ea1d | ||
| 
						 | 
					09e3327e94 | ||
| 
						 | 
					00deeb9fdd | ||
| 
						 | 
					0aed1ba04f | ||
| 
						 | 
					526947e3a1 | ||
| 
						 | 
					b52308df80 | ||
| 
						 | 
					bdb9e02df0 | ||
| 
						 | 
					6273020127 | ||
| 
						 | 
					4d1a464954 | ||
| 
						 | 
					9711ab6c5f | ||
| 
						 | 
					95963eb0be | ||
| 
						 | 
					0596edb0a1 | ||
| 
						 | 
					7ac82d4ad9 | ||
| 
						 | 
					74a9ad955b | ||
| 
						 | 
					c23cce672c | ||
| 
						 | 
					d39b4b3624 | ||
| 
						 | 
					2c3ebeec09 | ||
| 
						 | 
					86f2ffa61f | ||
| 
						 | 
					017781ff35 | ||
| 
						 | 
					7d72ad0f37 | ||
| 
						 | 
					152033f98a | ||
| 
						 | 
					c21bf5b87d | ||
| 
						 | 
					fa60d40171 | ||
| 
						 | 
					d80d5557c8 | ||
| 
						 | 
					6c7ac8d7f4 | ||
| 
						 | 
					d72867da35 | ||
| 
						 | 
					14347b73f2 | ||
| 
						 | 
					11e2bf4cbb | ||
| 
						 | 
					b2553d35fd | ||
| 
						 | 
					58f8a02557 | ||
| 
						 | 
					34450144ba | ||
| 
						 | 
					f9e08d53af | ||
| 
						 | 
					006e402d9f | ||
| 
						 | 
					8132012534 | ||
| 
						 | 
					3e8f02eea4 | ||
| 
						 | 
					7312980453 | ||
| 
						 | 
					8c20d41d89 | ||
| 
						 | 
					d50d53ac1f | ||
| 
						 | 
					b0d7ab2e81 | ||
| 
						 | 
					91223b7518 | ||
| 
						 | 
					616e4b6e0c | ||
| 
						 | 
					5170ea81e7 | ||
| 
						 | 
					53a8cd8ee4 | ||
| 
						 | 
					2229e8cb7d | ||
| 
						 | 
					9ce1041b0e | ||
| 
						 | 
					187065098b | ||
| 
						 | 
					467bdf0045 | ||
| 
						 | 
					9462b5b461 | ||
| 
						 | 
					da7f29a9e0 | ||
| 
						 | 
					d06bfd91ff | ||
| 
						 | 
					b9bba10697 | ||
| 
						 | 
					4581de92ad | ||
| 
						 | 
					72c520cca7 | ||
| 
						 | 
					835ee2a046 | ||
| 
						 | 
					d03dee8fda | ||
| 
						 | 
					6e89598a51 | ||
| 
						 | 
					0a2aa6f734 | ||
| 
						 | 
					2e7836eec3 | ||
| 
						 | 
					31bdda8bf8 | ||
| 
						 | 
					2fc93fa819 | ||
| 
						 | 
					3c5a939b16 | ||
| 
						 | 
					0f40c4cd49 | 
							
								
								
									
										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
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										90
									
								
								.eslintrc
									
									
									
									
									
								
							
							
						
						@@ -1,22 +1,80 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "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/"],
 | 
				
			||||||
 | 
					  "extends": [
 | 
				
			||||||
 | 
					    "plugin:react/recommended",
 | 
				
			||||||
 | 
					    "plugin:@typescript-eslint/eslint-recommended",
 | 
				
			||||||
 | 
					    "plugin:@typescript-eslint/recommended",
 | 
				
			||||||
 | 
					    "airbnb",
 | 
				
			||||||
 | 
					    "airbnb-typescript",
 | 
				
			||||||
 | 
					    "prettier",
 | 
				
			||||||
 | 
					    "plugin:import/errors",
 | 
				
			||||||
 | 
					    "plugin:import/warnings",
 | 
				
			||||||
 | 
					    "plugin:import/typescript"
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  "plugins": ["import", "react", "@typescript-eslint", "prettier"],
 | 
				
			||||||
  "rules": {
 | 
					  "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"],
 | 
				
			||||||
 | 
					    "import/prefer-default-export": ["off"],
 | 
				
			||||||
 | 
					    "react/prop-types": ["warn"],
 | 
				
			||||||
 | 
					    "react/require-default-props": "off",
 | 
				
			||||||
 | 
					    "react/jsx-props-no-spreading": ["off"],
 | 
				
			||||||
 | 
					    "react/jsx-curly-newline": "off",
 | 
				
			||||||
 | 
					    "no-underscore-dangle": "off"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "settings": {
 | 
					  "settings": {
 | 
				
			||||||
    "import/resolver": {
 | 
					    "import/resolver": {
 | 
				
			||||||
@@ -24,11 +82,5 @@
 | 
				
			|||||||
        "paths": ["src"]
 | 
					        "paths": ["src"]
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
    "parser": "babel-eslint",
 | 
					 | 
				
			||||||
    "parserOptions": {
 | 
					 | 
				
			||||||
      "sourceType": "module",
 | 
					 | 
				
			||||||
      "allowImportExportEverywhere": false,
 | 
					 | 
				
			||||||
      "codeFrame": false
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										4
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -26,7 +26,7 @@ jobs:
 | 
				
			|||||||
      DOCKER_REGISTRY_USERNAME: ucentral
 | 
					      DOCKER_REGISTRY_USERNAME: ucentral
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
    - name: Checkout actions repo
 | 
					    - name: Checkout actions repo
 | 
				
			||||||
      uses: actions/checkout@v2
 | 
					      uses: actions/checkout@v3
 | 
				
			||||||
      with:
 | 
					      with:
 | 
				
			||||||
        repository: Telecominfraproject/.github
 | 
					        repository: Telecominfraproject/.github
 | 
				
			||||||
        path: github
 | 
					        path: github
 | 
				
			||||||
@@ -56,7 +56,7 @@ jobs:
 | 
				
			|||||||
      - docker
 | 
					      - docker
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
    - name: Checkout actions repo
 | 
					    - name: Checkout actions repo
 | 
				
			||||||
      uses: actions/checkout@v2
 | 
					      uses: actions/checkout@v3
 | 
				
			||||||
      with:
 | 
					      with:
 | 
				
			||||||
        repository: Telecominfraproject/.github
 | 
					        repository: Telecominfraproject/.github
 | 
				
			||||||
        path: github
 | 
					        path: github
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										2
									
								
								.github/workflows/enforce-jira-issue-key.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -11,7 +11,7 @@ jobs:
 | 
				
			|||||||
    runs-on: ubuntu-latest
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - name: Checkout actions repo
 | 
					      - name: Checkout actions repo
 | 
				
			||||||
        uses: actions/checkout@v2
 | 
					        uses: actions/checkout@v3
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          repository: Telecominfraproject/.github
 | 
					          repository: Telecominfraproject/.github
 | 
				
			||||||
          path: github
 | 
					          path: github
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										2
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -17,7 +17,7 @@ jobs:
 | 
				
			|||||||
      HELM_REPO_USERNAME: ucentral
 | 
					      HELM_REPO_USERNAME: ucentral
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - name: Checkout uCentral assembly chart repo
 | 
					      - name: Checkout uCentral assembly chart repo
 | 
				
			||||||
        uses: actions/checkout@v2
 | 
					        uses: actions/checkout@v3
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          path: wlan-cloud-ucentralgw-ui
 | 
					          path: wlan-cloud-ucentralgw-ui
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										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
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
    "printWidth": 100,
 | 
					  "printWidth": 120,
 | 
				
			||||||
  "trailingComma": "all",
 | 
					  "trailingComma": "all",
 | 
				
			||||||
  "tabWidth": 2,
 | 
					  "tabWidth": 2,
 | 
				
			||||||
  "semi": true,
 | 
					  "semi": true,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,8 @@
 | 
				
			|||||||
FROM node:18.7.0-alpine3.15 AS build
 | 
					FROM node:18.7.0-alpine3.15 AS build
 | 
				
			||||||
 | 
					
 | 
				
			||||||
COPY package.json package-lock.json /
 | 
					WORKDIR /app
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					COPY package.json package-lock.json /app/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
RUN npm install
 | 
					RUN npm install
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -10,6 +12,6 @@ RUN npm run build
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
FROM nginx:1.22.0-alpine AS runtime
 | 
					FROM nginx:1.22.0-alpine AS runtime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
COPY --from=build /build/ /usr/share/nginx/html/
 | 
					COPY --from=build /app/build/ /usr/share/nginx/html/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
COPY --from=build docker-entrypoint.d/40-generate-config.sh /docker-entrypoint.d/40-generate-config.sh
 | 
					COPY --from=build /app/docker-entrypoint.d/40-generate-config.sh /docker-entrypoint.d/40-generate-config.sh
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										25
									
								
								README.md
									
									
									
									
									
								
							
							
						
						@@ -1,6 +1,7 @@
 | 
				
			|||||||
# uCentralGW UI
 | 
					# uCentralGW UI
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## What is this?
 | 
					## What is this?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
The uCentralGW Client is a user interface that lets you monitor and manage devices connected to the [uCentral gateway](https://github.com/Telecominfraproject/wlan-cloud-ucentralgw). To use the interface,
 | 
					The uCentralGW Client is a user interface that lets you monitor and manage devices connected to the [uCentral gateway](https://github.com/Telecominfraproject/wlan-cloud-ucentralgw). To use the interface,
 | 
				
			||||||
you either need to run it on your machine for [development](#development) or build it for [production](#production).
 | 
					you either need to run it on your machine for [development](#development) or build it for [production](#production).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -9,40 +10,34 @@ NOTE: This UI will be evolving as micro services are added to the uCentral progr
 | 
				
			|||||||
## Running the solution
 | 
					## Running the solution
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Development
 | 
					### Development
 | 
				
			||||||
 | 
					
 | 
				
			||||||
You need to run these commands in the root folder of the project and also have npm installed on your machine.
 | 
					You need to run these commands in the root folder of the project and also have npm installed on your machine.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
git clone https://github.com/Telecominfraproject/wlan-cloud-ucentralgw-ui
 | 
					git clone https://github.com/Telecominfraproject/wlan-cloud-ucentralgw-ui
 | 
				
			||||||
cd wlan-cloud-ucentralgw-ui
 | 
					cd wlan-cloud-ucentralgw-ui
 | 
				
			||||||
npm install
 | 
					npm install
 | 
				
			||||||
npm start
 | 
					npm run dev
 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Run these commands if you want to run the solution on your machine while also doing development on the [uCentral UI Library](https://github.com/Telecominfraproject/wlan-cloud-ucentral-ui-libs).
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
git clone https://github.com/Telecominfraproject/wlan-cloud-ucentralgw-ui
 | 
					 | 
				
			||||||
git clone https://github.com/Telecominfraproject/wlan-cloud-ucentral-ui-libs
 | 
					 | 
				
			||||||
cd wlan-cloud-ucentralgw-ui
 | 
					 | 
				
			||||||
npm link ../wlan-cloud-ucentral-ui-libs // Add sudo at the start of this command if it fails because of permissions
 | 
					 | 
				
			||||||
npm start
 | 
					 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Production
 | 
					### Production
 | 
				
			||||||
 | 
					
 | 
				
			||||||
You need to run this in the root folder of the project and also have npm installed on your machine.
 | 
					You need to run this in the root folder of the project and also have npm installed on your machine.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
git clone https://github.com/Telecominfraproject/wlan-cloud-ucentralgw-ui
 | 
					git clone https://github.com/Telecominfraproject/wlan-cloud-ucentralgw-ui
 | 
				
			||||||
cd wlan-cloud-ucentralgw-ui
 | 
					cd wlan-cloud-ucentralgw-ui
 | 
				
			||||||
npm install
 | 
					npm install
 | 
				
			||||||
npm run build
 | 
					npm run build
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Once the build is done, you can move the `build` folder on your server.
 | 
					Once the build is done, you can move the `build` folder on your server.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Configuration
 | 
					### Configuration
 | 
				
			||||||
You must change the `config.json` file in `public` directory to point to your uCentral Security Service URL (uCentralSec). You may also limit the ability for users to change the default uCentralSec. If you do not allow a uCentralSec change, the uCentralSec URL will not appear on the login screen. 
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					You can control the uCentral Security Service URL (uCentralSec) by modifying the ENV variable "VITE_UCENTRALSEC_URL". There is an example .env file located at the root of this repository.
 | 
				
			||||||
Here are the current default values:
 | 
					Here are the current default values:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
{
 | 
					VITE_UCENTRALSEC_URL="https://ucentral.dpaas.arilia.com:16001"
 | 
				
			||||||
  "DEFAULT_UCENTRALSEC_URL": "https://ucentral.dpaas.arilia.com:16001",
 | 
					 | 
				
			||||||
  "ALLOW_UCENTRALSEC_CHANGE": false
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,25 +0,0 @@
 | 
				
			|||||||
{
 | 
					 | 
				
			||||||
  "presets": [
 | 
					 | 
				
			||||||
    [
 | 
					 | 
				
			||||||
      "@babel/preset-env",
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        "modules": false
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    ],
 | 
					 | 
				
			||||||
    "@babel/preset-react"
 | 
					 | 
				
			||||||
  ],
 | 
					 | 
				
			||||||
  "env": {
 | 
					 | 
				
			||||||
    "production": {
 | 
					 | 
				
			||||||
      "plugins": [
 | 
					 | 
				
			||||||
        "@babel/plugin-transform-react-inline-elements",
 | 
					 | 
				
			||||||
        "@babel/plugin-transform-react-constant-elements",
 | 
					 | 
				
			||||||
        [
 | 
					 | 
				
			||||||
          "transform-react-remove-prop-types",
 | 
					 | 
				
			||||||
          {
 | 
					 | 
				
			||||||
            "removeImport": true
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        ]
 | 
					 | 
				
			||||||
      ]
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,12 +0,0 @@
 | 
				
			|||||||
const path = require('path');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
module.exports = {
 | 
					 | 
				
			||||||
  // Source files
 | 
					 | 
				
			||||||
  src: path.resolve(__dirname, '../src'),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Production build files
 | 
					 | 
				
			||||||
  build: path.resolve(__dirname, '../build'),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Static files that get copied to build folder
 | 
					 | 
				
			||||||
  public: path.resolve(__dirname, '../public'),
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
@@ -1,79 +0,0 @@
 | 
				
			|||||||
/* eslint-disable import/no-extraneous-dependencies */
 | 
					 | 
				
			||||||
/* eslint-disable prefer-template */
 | 
					 | 
				
			||||||
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
 | 
					 | 
				
			||||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
 | 
					 | 
				
			||||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
 | 
					 | 
				
			||||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
 | 
					 | 
				
			||||||
const webpack = require('webpack');
 | 
					 | 
				
			||||||
const path = require('path');
 | 
					 | 
				
			||||||
const paths = require('./paths');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
module.exports = {
 | 
					 | 
				
			||||||
  entry: [paths.src + '/index.js'],
 | 
					 | 
				
			||||||
  output: {
 | 
					 | 
				
			||||||
    path: paths.build,
 | 
					 | 
				
			||||||
    filename: '[name].bundle.js',
 | 
					 | 
				
			||||||
    publicPath: '/',
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  resolve: {
 | 
					 | 
				
			||||||
    modules: [path.resolve('./node_modules'), path.resolve('./src')],
 | 
					 | 
				
			||||||
    preferRelative: true,
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  plugins: [
 | 
					 | 
				
			||||||
    new webpack.DefinePlugin({
 | 
					 | 
				
			||||||
      'process.env.VERSION': JSON.stringify(process.env.npm_package_version),
 | 
					 | 
				
			||||||
    }),
 | 
					 | 
				
			||||||
    new MiniCssExtractPlugin({
 | 
					 | 
				
			||||||
      filename: 'styles/[name].[contenthash].css',
 | 
					 | 
				
			||||||
      chunkFilename: '[id].[contenthash].css',
 | 
					 | 
				
			||||||
    }),
 | 
					 | 
				
			||||||
    new CopyWebpackPlugin({
 | 
					 | 
				
			||||||
      patterns: [
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          from: paths.src + '/assets',
 | 
					 | 
				
			||||||
          to: 'assets',
 | 
					 | 
				
			||||||
          globOptions: {
 | 
					 | 
				
			||||||
            ignore: ['*.DS_Store'],
 | 
					 | 
				
			||||||
          },
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          from: paths.public + '/locales',
 | 
					 | 
				
			||||||
          to: 'locales',
 | 
					 | 
				
			||||||
          globOptions: {
 | 
					 | 
				
			||||||
            ignore: ['*.DS_Store'],
 | 
					 | 
				
			||||||
          },
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          from: paths.public + '/config.json',
 | 
					 | 
				
			||||||
          to: 'config.json',
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
      ],
 | 
					 | 
				
			||||||
    }),
 | 
					 | 
				
			||||||
    new HtmlWebpackPlugin({
 | 
					 | 
				
			||||||
      title: 'uCentralGW',
 | 
					 | 
				
			||||||
      favicon: paths.public + '/favicon.ico',
 | 
					 | 
				
			||||||
      template: paths.public + '/index.html',
 | 
					 | 
				
			||||||
      filename: 'index.html',
 | 
					 | 
				
			||||||
    }),
 | 
					 | 
				
			||||||
    new CleanWebpackPlugin(),
 | 
					 | 
				
			||||||
  ],
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  module: {
 | 
					 | 
				
			||||||
    rules: [
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        test: /\.(js|jsx)$/,
 | 
					 | 
				
			||||||
        exclude: /node_modules/,
 | 
					 | 
				
			||||||
        use: ['babel-loader'],
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        test: /\.(css|scss)$/,
 | 
					 | 
				
			||||||
        use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'],
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        test: /\.svg$/,
 | 
					 | 
				
			||||||
        use: ['@svgr/webpack'],
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      { test: /\.(?:ico|gif|png|jpg|jpeg)$/i, type: 'asset/resource' },
 | 
					 | 
				
			||||||
    ],
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
@@ -1,54 +0,0 @@
 | 
				
			|||||||
/* eslint-disable import/no-extraneous-dependencies */
 | 
					 | 
				
			||||||
/* eslint-disable prefer-template */
 | 
					 | 
				
			||||||
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
 | 
					 | 
				
			||||||
const { merge } = require('webpack-merge');
 | 
					 | 
				
			||||||
const path = require('path');
 | 
					 | 
				
			||||||
const paths = require('./paths');
 | 
					 | 
				
			||||||
const common = require('./webpack.common');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
module.exports = merge(common, {
 | 
					 | 
				
			||||||
  mode: 'development',
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  target: 'web',
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  devtool: 'inline-source-map',
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  devServer: {
 | 
					 | 
				
			||||||
    historyApiFallback: true,
 | 
					 | 
				
			||||||
    contentBase: paths.build,
 | 
					 | 
				
			||||||
    open: true,
 | 
					 | 
				
			||||||
    compress: false,
 | 
					 | 
				
			||||||
    hot: true,
 | 
					 | 
				
			||||||
    port: 3000,
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  module: {
 | 
					 | 
				
			||||||
    rules: [
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        test: /\.[js]sx?$/,
 | 
					 | 
				
			||||||
        exclude: /node_modules/,
 | 
					 | 
				
			||||||
        use: [
 | 
					 | 
				
			||||||
          {
 | 
					 | 
				
			||||||
            loader: require.resolve('babel-loader'),
 | 
					 | 
				
			||||||
            options: {
 | 
					 | 
				
			||||||
              plugins: [require.resolve('react-refresh/babel')],
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
          },
 | 
					 | 
				
			||||||
        ],
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
    ],
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  resolve: {
 | 
					 | 
				
			||||||
    modules: [
 | 
					 | 
				
			||||||
      'node_modules',
 | 
					 | 
				
			||||||
      'src',
 | 
					 | 
				
			||||||
      path.resolve(__dirname, '../', 'node_modules', 'ucentral-libs', 'src'),
 | 
					 | 
				
			||||||
    ],
 | 
					 | 
				
			||||||
    alias: {
 | 
					 | 
				
			||||||
      react: path.resolve(__dirname, '../', 'node_modules', 'react'),
 | 
					 | 
				
			||||||
      'react-router-dom': path.resolve('./node_modules/react-router-dom'),
 | 
					 | 
				
			||||||
      'ucentral-libs': path.resolve(__dirname, '../', 'node_modules', 'ucentral-libs', 'src'),
 | 
					 | 
				
			||||||
      graphlib: path.resolve(__dirname, '../', 'node_modules', 'graphlib'),
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  plugins: [new ReactRefreshWebpackPlugin()],
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
@@ -1,86 +0,0 @@
 | 
				
			|||||||
/* eslint-disable import/no-extraneous-dependencies */
 | 
					 | 
				
			||||||
/* eslint-disable prefer-template */
 | 
					 | 
				
			||||||
const { merge } = require('webpack-merge');
 | 
					 | 
				
			||||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
 | 
					 | 
				
			||||||
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
 | 
					 | 
				
			||||||
const TerserPlugin = require('terser-webpack-plugin');
 | 
					 | 
				
			||||||
const CompressionPlugin = require('compression-webpack-plugin');
 | 
					 | 
				
			||||||
const path = require('path');
 | 
					 | 
				
			||||||
const paths = require('./paths');
 | 
					 | 
				
			||||||
const common = require('./webpack.common');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
module.exports = merge(common, {
 | 
					 | 
				
			||||||
  mode: 'production',
 | 
					 | 
				
			||||||
  devtool: false,
 | 
					 | 
				
			||||||
  output: {
 | 
					 | 
				
			||||||
    path: paths.build,
 | 
					 | 
				
			||||||
    publicPath: '/',
 | 
					 | 
				
			||||||
    filename: 'js/[name].[contenthash].bundle.js',
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  plugins: [
 | 
					 | 
				
			||||||
    // new BundleAnalyzerPlugin(),
 | 
					 | 
				
			||||||
    new MiniCssExtractPlugin({
 | 
					 | 
				
			||||||
      filename: 'styles/[name].[contenthash].css',
 | 
					 | 
				
			||||||
      chunkFilename: '[contenthash].css',
 | 
					 | 
				
			||||||
    }),
 | 
					 | 
				
			||||||
    new CompressionPlugin({
 | 
					 | 
				
			||||||
      filename: '[path]/[name].gz[query]',
 | 
					 | 
				
			||||||
      algorithm: 'gzip',
 | 
					 | 
				
			||||||
      test: /\.js$|\.css$|\.html$|\.eot?.+$|\.ttf?.+$|\.woff?.+$|\.svg?.+$/,
 | 
					 | 
				
			||||||
      threshold: 10240,
 | 
					 | 
				
			||||||
      minRatio: 0.8,
 | 
					 | 
				
			||||||
    }),
 | 
					 | 
				
			||||||
  ],
 | 
					 | 
				
			||||||
  module: {
 | 
					 | 
				
			||||||
    rules: [],
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  optimization: {
 | 
					 | 
				
			||||||
    minimize: true,
 | 
					 | 
				
			||||||
    minimizer: [
 | 
					 | 
				
			||||||
      '...',
 | 
					 | 
				
			||||||
      new TerserPlugin({
 | 
					 | 
				
			||||||
        terserOptions: {
 | 
					 | 
				
			||||||
          warnings: false,
 | 
					 | 
				
			||||||
          compress: {
 | 
					 | 
				
			||||||
            comparisons: false,
 | 
					 | 
				
			||||||
          },
 | 
					 | 
				
			||||||
          parse: {},
 | 
					 | 
				
			||||||
          mangle: true,
 | 
					 | 
				
			||||||
          output: {
 | 
					 | 
				
			||||||
            ascii_only: true,
 | 
					 | 
				
			||||||
          },
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        parallel: true,
 | 
					 | 
				
			||||||
      }),
 | 
					 | 
				
			||||||
      new CssMinimizerPlugin(),
 | 
					 | 
				
			||||||
    ],
 | 
					 | 
				
			||||||
    nodeEnv: 'production',
 | 
					 | 
				
			||||||
    sideEffects: true,
 | 
					 | 
				
			||||||
    runtimeChunk: 'single',
 | 
					 | 
				
			||||||
    splitChunks: {
 | 
					 | 
				
			||||||
      chunks: 'all',
 | 
					 | 
				
			||||||
      maxInitialRequests: 10,
 | 
					 | 
				
			||||||
      minSize: 0,
 | 
					 | 
				
			||||||
      cacheGroups: {
 | 
					 | 
				
			||||||
        vendor: {
 | 
					 | 
				
			||||||
          test: /[\\/]node_modules[\\/]/,
 | 
					 | 
				
			||||||
          name(module) {
 | 
					 | 
				
			||||||
            const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];
 | 
					 | 
				
			||||||
            return `npm.${packageName.replace('@', '')}`;
 | 
					 | 
				
			||||||
          },
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  resolve: {
 | 
					 | 
				
			||||||
    modules: [],
 | 
					 | 
				
			||||||
    alias: {
 | 
					 | 
				
			||||||
      graphlib: path.resolve(__dirname, '../', 'node_modules', 'graphlib'),
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  performance: {
 | 
					 | 
				
			||||||
    hints: false,
 | 
					 | 
				
			||||||
    maxEntrypointSize: 512000,
 | 
					 | 
				
			||||||
    maxAssetSize: 512000,
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
@@ -1,6 +1,32 @@
 | 
				
			|||||||
#!/bin/ash
 | 
					#!/bin/ash
 | 
				
			||||||
# Check if variables are set
 | 
					 | 
				
			||||||
export DEFAULT_OWSEC_URL="${DEFAULT_OWSEC_URL:-https://ucentral.dpaas.arilia.com:16001}"
 | 
					 | 
				
			||||||
export ALLOW_OWSEC_CHANGE="${ALLOW_OWSEC_CHANGE:-false}"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
echo '{"DEFAULT_UCENTRALSEC_URL": "'$DEFAULT_UCENTRALSEC_URL'","ALLOW_UCENTRALSEC_CHANGE": '$ALLOW_UCENTRALSEC_CHANGE'}' > /usr/share/nginx/html/config.json
 | 
					ENV_CONFIG_PATH=/usr/share/nginx/html/env-config.js
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Recreate config file
 | 
				
			||||||
 | 
					rm -rf $ENV_CONFIG_PATH
 | 
				
			||||||
 | 
					touch $ENV_CONFIG_PATH
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Add assignment
 | 
				
			||||||
 | 
					echo "window._env_ = {" >> $ENV_CONFIG_PATH
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Read each line in .env file
 | 
				
			||||||
 | 
					# Each line represents key=value pairs
 | 
				
			||||||
 | 
					env | grep REACT_ | while read -r line || [[ -n "$line" ]];
 | 
				
			||||||
 | 
					do
 | 
				
			||||||
 | 
					  echo $line
 | 
				
			||||||
 | 
					  # Split env variables by character `=`
 | 
				
			||||||
 | 
					  if printf '%s\n' "$line" | grep -q -e '='; then
 | 
				
			||||||
 | 
					    varname=$(printf '%s\n' "$line" | sed -e 's/=.*//')
 | 
				
			||||||
 | 
					    varvalue=$(printf '%s\n' "$line" | sed -e 's/^[^=]*=//')
 | 
				
			||||||
 | 
					  fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Read value of current variable if exists as Environment variable
 | 
				
			||||||
 | 
					  value=$(printf '%s\n' "${!varname}")
 | 
				
			||||||
 | 
					  # Otherwise use value from .env file
 | 
				
			||||||
 | 
					  [[ -z $value ]] && value=${varvalue}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Append configuration property to JS file
 | 
				
			||||||
 | 
					  echo "  $varname: \"$value\"," >> $ENV_CONFIG_PATH
 | 
				
			||||||
 | 
					done
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					echo "}" >> $ENV_CONFIG_PATH
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,7 +8,7 @@ fullnameOverride: ""
 | 
				
			|||||||
images:
 | 
					images:
 | 
				
			||||||
  owgwui:
 | 
					  owgwui:
 | 
				
			||||||
    repository: tip-tip-wlan-cloud-ucentral.jfrog.io/owgw-ui
 | 
					    repository: tip-tip-wlan-cloud-ucentral.jfrog.io/owgw-ui
 | 
				
			||||||
    tag: v2.7.0-RC1
 | 
					    tag: v2.9.0
 | 
				
			||||||
    pullPolicy: Always
 | 
					    pullPolicy: Always
 | 
				
			||||||
 | 
					
 | 
				
			||||||
services:
 | 
					services:
 | 
				
			||||||
@@ -75,5 +75,4 @@ podAnnotations: {}
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
# Application
 | 
					# Application
 | 
				
			||||||
public_env_variables:
 | 
					public_env_variables:
 | 
				
			||||||
  DEFAULT_UCENTRALSEC_URL: https://ucentral.dpaas.arilia.com:16001
 | 
					  REACT_APP_UCENTRALSEC_URL: https://ucentral.dpaas.arilia.com:16001
 | 
				
			||||||
  ALLOW_UCENTRALSEC_CHANGE: false
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										21
									
								
								index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					<!DOCTYPE html>
 | 
				
			||||||
 | 
					<html lang="en">
 | 
				
			||||||
 | 
					  <head>
 | 
				
			||||||
 | 
					    <meta charset="utf-8" />
 | 
				
			||||||
 | 
					    <meta name="viewport" content="width=device-width, initial-scale=1" />
 | 
				
			||||||
 | 
					    <title>Controller</title>
 | 
				
			||||||
 | 
					    <meta name="description" content="OpenWiFi Controller App" />
 | 
				
			||||||
 | 
					    <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
 | 
				
			||||||
 | 
					    <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
 | 
				
			||||||
 | 
					    <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
 | 
				
			||||||
 | 
					    <link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5" />
 | 
				
			||||||
 | 
					    <meta name="msapplication-TileColor" content="#da532c" />
 | 
				
			||||||
 | 
					    <script src="/env-config.js"></script>
 | 
				
			||||||
 | 
					    <meta name="theme-color" content="#000000" />
 | 
				
			||||||
 | 
					  </head>
 | 
				
			||||||
 | 
					  <body>
 | 
				
			||||||
 | 
					    <noscript>You need to enable JavaScript to run this app.</noscript>
 | 
				
			||||||
 | 
					    <div id="root"></div>
 | 
				
			||||||
 | 
					    <script type="module" src="./src/index.tsx"></script>
 | 
				
			||||||
 | 
					  </body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
@@ -1,9 +0,0 @@
 | 
				
			|||||||
{
 | 
					 | 
				
			||||||
  "compilerOptions": {
 | 
					 | 
				
			||||||
    "baseUrl": "src",
 | 
					 | 
				
			||||||
    "paths": {
 | 
					 | 
				
			||||||
      "*": ["*"]
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "include": ["src"]
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										27848
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
							
								
								
									
										169
									
								
								package.json
									
									
									
									
									
								
							
							
						
						@@ -1,101 +1,96 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "name": "ucentral-client",
 | 
					  "name": "ucentral-client",
 | 
				
			||||||
  "version": "2.7.0(8)",
 | 
					  "version": "2.9.0(23)",
 | 
				
			||||||
 | 
					  "description": "",
 | 
				
			||||||
 | 
					  "private": true,
 | 
				
			||||||
 | 
					  "main": "index.tsx",
 | 
				
			||||||
 | 
					  "scripts": {
 | 
				
			||||||
 | 
					    "dev": "vite",
 | 
				
			||||||
 | 
					    "build": "vite build",
 | 
				
			||||||
 | 
					    "format": "prettier --write \"src/**/*x.{ts,tsx,js,jsx}\"",
 | 
				
			||||||
 | 
					    "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",
 | 
					    "@googlemaps/react-wrapper": "^1.1.35",
 | 
				
			||||||
 | 
					    "@googlemaps/typescript-guards": "^2.0.3",
 | 
				
			||||||
 | 
					    "@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",
 | 
					    "fast-equals": "^4.0.3",
 | 
				
			||||||
    "i18next-http-backend": "^1.2.6",
 | 
					    "framer-motion": "^7.6.1",
 | 
				
			||||||
    "prop-types": "^15.7.2",
 | 
					    "i18next": "^22.0.0",
 | 
				
			||||||
    "react": "^17.0.2",
 | 
					    "i18next-browser-languagedetector": "^6.1.8",
 | 
				
			||||||
    "react-apexcharts": "^1.3.9",
 | 
					    "i18next-http-backend": "^1.4.4",
 | 
				
			||||||
 | 
					    "libphonenumber-js": "^1.10.14",
 | 
				
			||||||
 | 
					    "phosphor-react": "^1.4.1",
 | 
				
			||||||
 | 
					    "prop-types": "^15.8.1",
 | 
				
			||||||
 | 
					    "react": "^18.2.0",
 | 
				
			||||||
 | 
					    "react-app-polyfill": "^3.0.0",
 | 
				
			||||||
 | 
					    "react-chartjs-2": "^4.3.1",
 | 
				
			||||||
 | 
					    "chart.js": "^3.9.1",
 | 
				
			||||||
    "react-country-flag": "^3.0.2",
 | 
					    "react-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",
 | 
					    "@tanstack/react-query": "^4.12.0",
 | 
				
			||||||
    "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",
 | 
					    "react-virtualized-auto-sizer": "^1.0.7",
 | 
				
			||||||
    "uuid": "^8.3.2"
 | 
					    "react-window": "^1.8.8",
 | 
				
			||||||
  },
 | 
					    "source-map-explorer": "^2.5.3",
 | 
				
			||||||
  "main": "index.js",
 | 
					    "vite": "^3.1.8",
 | 
				
			||||||
  "scripts": {
 | 
					    "typescript": "^4.8.4",
 | 
				
			||||||
    "start": "webpack serve --config config/webpack.dev.js",
 | 
					    "uuid": "^9.0.0",
 | 
				
			||||||
    "build": "webpack --config config/webpack.prod.js",
 | 
					    "yup": "^0.32.11",
 | 
				
			||||||
    "format": "prettier --write 'src/**/*.js'",
 | 
					    "zustand": "^4.1.2"
 | 
				
			||||||
    "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/google.maps": "^3.51.0",
 | 
				
			||||||
    "@babel/plugin-proposal-class-properties": "^7.14.5",
 | 
					    "@types/node": "^18.11.2",
 | 
				
			||||||
    "@babel/plugin-transform-runtime": "^7.14.5",
 | 
					    "@types/react": "^18.0.21",
 | 
				
			||||||
    "@babel/polyfill": "^7.12.1",
 | 
					    "@types/react-csv": "^1.1.3",
 | 
				
			||||||
    "@babel/preset-env": "^7.14.7",
 | 
					    "@types/react-dom": "^18.0.6",
 | 
				
			||||||
    "@babel/preset-react": "^7.14.5",
 | 
					    "@types/react-table": "^7.7.12",
 | 
				
			||||||
    "@pmmmwh/react-refresh-webpack-plugin": "^0.4.3",
 | 
					    "@types/react-datepicker": "4.8.0",
 | 
				
			||||||
    "@svgr/webpack": "^5.5.0",
 | 
					    "@types/uuid": "^8.3.4",
 | 
				
			||||||
    "autoprefixer": "^10.2.6",
 | 
					    "@types/react-virtualized-auto-sizer": "^1.0.1",
 | 
				
			||||||
    "babel-eslint": "^10.1.0",
 | 
					    "@types/react-window": "^1.8.5",
 | 
				
			||||||
    "babel-loader": "^8.2.2",
 | 
					    "eslint": "8.25.0",
 | 
				
			||||||
    "clean-webpack-plugin": "^3.0.0",
 | 
					    "vite-tsconfig-paths": "^3.5.1",
 | 
				
			||||||
    "compression-webpack-plugin": "^8.0.1",
 | 
					    "lint-staged": "^13.0.3",
 | 
				
			||||||
    "copy-webpack-plugin": "^7.0.0",
 | 
					    "@vitejs/plugin-react": "^2.1.0",
 | 
				
			||||||
    "css-loader": "^5.2.6",
 | 
					    "vite-plugin-pwa": "^0.13.1",
 | 
				
			||||||
    "css-minimizer-webpack-plugin": "^2.0.0",
 | 
					    "prettier": "^2.7.1",
 | 
				
			||||||
    "dotenv-webpack": "^6.0.4",
 | 
					    "eslint-config-airbnb": "^19.0.4",
 | 
				
			||||||
    "eslint": "^7.29.0",
 | 
					    "eslint-config-airbnb-typescript": "^17.0.0",
 | 
				
			||||||
    "eslint-config-airbnb": "^18.2.1",
 | 
					    "eslint-config-airbnb-typescript-prettier": "^5.0.0",
 | 
				
			||||||
    "eslint-config-prettier": "^7.2.0",
 | 
					    "eslint-config-prettier": "^8.5.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/devices/yuncore_ax840.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 32 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/devices/yuncore_fap640.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 38 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/devices/yuncore_fap650.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 47 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/favicon-16x16.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1021 B  | 
							
								
								
									
										
											BIN
										
									
								
								public/favicon-32x32.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1.9 KiB  | 
| 
		 Before Width: | Height: | Size: 202 KiB After Width: | Height: | Size: 15 KiB  | 
@@ -1,165 +0,0 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="utf-8"?>
 | 
					 | 
				
			||||||
<!-- Generator: Adobe Illustrator 24.2.3, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
 | 
					 | 
				
			||||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
 | 
					 | 
				
			||||||
	 viewBox="0 0 141.5 185.6" style="enable-background:new 0 0 141.5 185.6;" xml:space="preserve">
 | 
					 | 
				
			||||||
<style type="text/css">
 | 
					 | 
				
			||||||
	.st0{fill:#414141;}
 | 
					 | 
				
			||||||
	.st1{fill:#FFFFFF;}
 | 
					 | 
				
			||||||
	.st2{fill:#FED206;}
 | 
					 | 
				
			||||||
	.st3{fill:#EB6F53;}
 | 
					 | 
				
			||||||
	.st4{fill:#3BA9B6;}
 | 
					 | 
				
			||||||
</style>
 | 
					 | 
				
			||||||
<g>
 | 
					 | 
				
			||||||
	<g>
 | 
					 | 
				
			||||||
		<path class="st0" d="M120.7,183.9H21.5c-10.8,0-19.5-8.7-19.5-19.5V20.5c0-10.8,8.7-19.5,19.5-19.5h99.2
 | 
					 | 
				
			||||||
			c10.8,0,19.5,8.7,19.5,19.5v143.9C140.2,175.2,131.5,183.9,120.7,183.9z"/>
 | 
					 | 
				
			||||||
		<g>
 | 
					 | 
				
			||||||
			<g>
 | 
					 | 
				
			||||||
				<g>
 | 
					 | 
				
			||||||
					<path class="st1" d="M46.3,166.2v-3.4h-1.2v-0.6h3.1v0.6H47v3.4H46.3z"/>
 | 
					 | 
				
			||||||
				</g>
 | 
					 | 
				
			||||||
				<g>
 | 
					 | 
				
			||||||
					<path class="st1" d="M49,166.2v-4h2.7v0.6h-2v1h2v0.6h-2v1.1h2v0.6H49z"/>
 | 
					 | 
				
			||||||
				</g>
 | 
					 | 
				
			||||||
				<g>
 | 
					 | 
				
			||||||
					<path class="st1" d="M52.6,166.2v-4h0.7v3.4h1.8v0.6H52.6z"/>
 | 
					 | 
				
			||||||
				</g>
 | 
					 | 
				
			||||||
				<g>
 | 
					 | 
				
			||||||
					<path class="st1" d="M55.7,166.2v-4h2.7v0.6h-2v1h2v0.6h-2v1.1h2v0.6H55.7z"/>
 | 
					 | 
				
			||||||
				</g>
 | 
					 | 
				
			||||||
				<g>
 | 
					 | 
				
			||||||
					<path class="st1" d="M59.1,164.2c0-1.2,0.9-2.1,2.1-2.1c0.8,0,1.3,0.4,1.6,0.9l-0.6,0.3c-0.2-0.3-0.6-0.6-1-0.6
 | 
					 | 
				
			||||||
						c-0.8,0-1.4,0.6-1.4,1.4c0,0.8,0.6,1.4,1.4,1.4c0.4,0,0.8-0.3,1-0.6l0.6,0.3c-0.3,0.5-0.8,0.9-1.6,0.9
 | 
					 | 
				
			||||||
						C60,166.3,59.1,165.5,59.1,164.2z"/>
 | 
					 | 
				
			||||||
				</g>
 | 
					 | 
				
			||||||
				<g>
 | 
					 | 
				
			||||||
					<path class="st1" d="M63.2,164.2c0-1.2,0.8-2.1,2-2.1c1.2,0,2,0.9,2,2.1c0,1.2-0.8,2.1-2,2.1C64,166.3,63.2,165.4,63.2,164.2z
 | 
					 | 
				
			||||||
						 M66.5,164.2c0-0.8-0.5-1.4-1.3-1.4c-0.8,0-1.3,0.6-1.3,1.4c0,0.8,0.5,1.4,1.3,1.4C66,165.7,66.5,165,66.5,164.2z"/>
 | 
					 | 
				
			||||||
				</g>
 | 
					 | 
				
			||||||
				<g>
 | 
					 | 
				
			||||||
					<path class="st1" d="M71.3,166.2v-3.1l-1.2,3.1h-0.3l-1.2-3.1v3.1h-0.7v-4h1l1.1,2.7l1.1-2.7h1v4H71.3z"/>
 | 
					 | 
				
			||||||
				</g>
 | 
					 | 
				
			||||||
				<g>
 | 
					 | 
				
			||||||
					<path class="st1" d="M75.7,166.2v-4h0.7v4H75.7z"/>
 | 
					 | 
				
			||||||
				</g>
 | 
					 | 
				
			||||||
				<g>
 | 
					 | 
				
			||||||
					<path class="st1" d="M80.4,166.2l-2.1-2.8v2.8h-0.7v-4h0.7l2,2.8v-2.8h0.7v4H80.4z"/>
 | 
					 | 
				
			||||||
				</g>
 | 
					 | 
				
			||||||
				<g>
 | 
					 | 
				
			||||||
					<path class="st1" d="M82.3,166.2v-4H85v0.6h-2v1h2v0.6h-2v1.7H82.3z"/>
 | 
					 | 
				
			||||||
				</g>
 | 
					 | 
				
			||||||
				<g>
 | 
					 | 
				
			||||||
					<path class="st1" d="M87.9,166.2l-0.9-1.5h-0.7v1.5h-0.7v-4h1.7c0.8,0,1.3,0.5,1.3,1.2c0,0.7-0.5,1.1-0.9,1.2l1,1.6H87.9z
 | 
					 | 
				
			||||||
						 M88,163.5c0-0.4-0.3-0.6-0.7-0.6h-1v1.3h1C87.7,164.1,88,163.9,88,163.5z"/>
 | 
					 | 
				
			||||||
				</g>
 | 
					 | 
				
			||||||
				<g>
 | 
					 | 
				
			||||||
					<path class="st1" d="M92.4,166.2l-0.3-0.8h-1.8l-0.3,0.8h-0.8l1.6-4h0.9l1.6,4H92.4z M91.2,162.9l-0.7,1.9h1.4L91.2,162.9z"/>
 | 
					 | 
				
			||||||
				</g>
 | 
					 | 
				
			||||||
				<g>
 | 
					 | 
				
			||||||
					<path class="st1" d="M95.8,166.2v-4h1.5c0.8,0,1.2,0.5,1.2,1.2c0,0.6-0.4,1.2-1.2,1.2h-1.2v1.7H95.8z M98.2,163.4
 | 
					 | 
				
			||||||
						c0-0.5-0.3-0.9-0.9-0.9h-1.1v1.7h1.1C97.8,164.3,98.2,163.9,98.2,163.4z"/>
 | 
					 | 
				
			||||||
				</g>
 | 
					 | 
				
			||||||
				<g>
 | 
					 | 
				
			||||||
					<path class="st1" d="M101.5,166.2l-1.1-1.6h-0.9v1.6h-0.3v-4h1.5c0.7,0,1.2,0.4,1.2,1.2c0,0.7-0.5,1.1-1.1,1.1l1.2,1.7H101.5z
 | 
					 | 
				
			||||||
						 M101.6,163.4c0-0.5-0.4-0.9-0.9-0.9h-1.1v1.7h1.1C101.2,164.3,101.6,163.9,101.6,163.4z"/>
 | 
					 | 
				
			||||||
				</g>
 | 
					 | 
				
			||||||
				<g>
 | 
					 | 
				
			||||||
					<path class="st1" d="M102.8,164.2c0-1.2,0.8-2.1,1.9-2.1c1.2,0,1.9,0.9,1.9,2.1c0,1.2-0.8,2.1-1.9,2.1
 | 
					 | 
				
			||||||
						C103.6,166.3,102.8,165.4,102.8,164.2z M106.3,164.2c0-1-0.6-1.7-1.6-1.7c-1,0-1.6,0.7-1.6,1.7c0,1,0.6,1.7,1.6,1.7
 | 
					 | 
				
			||||||
						C105.7,166,106.3,165.2,106.3,164.2z"/>
 | 
					 | 
				
			||||||
				</g>
 | 
					 | 
				
			||||||
				<g>
 | 
					 | 
				
			||||||
					<path class="st1" d="M106.9,165.8l0.2-0.3c0.2,0.2,0.4,0.4,0.8,0.4c0.5,0,0.9-0.4,0.9-0.9v-2.8h0.3v2.8c0,0.8-0.5,1.2-1.2,1.2
 | 
					 | 
				
			||||||
						C107.5,166.3,107.2,166.1,106.9,165.8z"/>
 | 
					 | 
				
			||||||
				</g>
 | 
					 | 
				
			||||||
				<g>
 | 
					 | 
				
			||||||
					<path class="st1" d="M110.4,166.2v-4h2.5v0.3h-2.2v1.5h2.1v0.3h-2.1v1.6h2.2v0.3H110.4z"/>
 | 
					 | 
				
			||||||
				</g>
 | 
					 | 
				
			||||||
				<g>
 | 
					 | 
				
			||||||
					<path class="st1" d="M113.5,164.2c0-1.2,0.9-2.1,2-2.1c0.6,0,1.1,0.3,1.5,0.7l-0.3,0.2c-0.3-0.3-0.7-0.6-1.2-0.6
 | 
					 | 
				
			||||||
						c-0.9,0-1.7,0.7-1.7,1.7c0,1,0.7,1.7,1.7,1.7c0.5,0,0.9-0.2,1.2-0.6l0.3,0.2c-0.4,0.4-0.8,0.7-1.5,0.7
 | 
					 | 
				
			||||||
						C114.4,166.3,113.5,165.5,113.5,164.2z"/>
 | 
					 | 
				
			||||||
				</g>
 | 
					 | 
				
			||||||
				<g>
 | 
					 | 
				
			||||||
					<path class="st1" d="M118.7,166.2v-3.7h-1.3v-0.3h2.9v0.3H119v3.7H118.7z"/>
 | 
					 | 
				
			||||||
				</g>
 | 
					 | 
				
			||||||
			</g>
 | 
					 | 
				
			||||||
			<g>
 | 
					 | 
				
			||||||
				<polygon class="st1" points="26.3,163.8 31.6,158.5 36.9,163.8 37.7,163.8 31.6,157.6 25.5,163.8 				"/>
 | 
					 | 
				
			||||||
				<polygon class="st1" points="36.9,164.7 31.6,170 26.3,164.7 25.5,164.7 31.6,170.8 37.7,164.7 				"/>
 | 
					 | 
				
			||||||
				<polygon class="st1" points="31,163.8 36.3,158.5 41.6,163.8 42.5,163.8 36.3,157.6 30.2,163.8 				"/>
 | 
					 | 
				
			||||||
				<polygon class="st1" points="41.6,164.7 36.3,170 31,164.7 30.2,164.7 36.3,170.8 42.5,164.7 				"/>
 | 
					 | 
				
			||||||
			</g>
 | 
					 | 
				
			||||||
		</g>
 | 
					 | 
				
			||||||
		<g>
 | 
					 | 
				
			||||||
			<path class="st1" d="M33.2,100.7c-4.6,0-8.3,3.7-8.3,8.3s3.7,8.3,8.3,8.3s8.3-3.7,8.3-8.3S37.8,100.7,33.2,100.7z"/>
 | 
					 | 
				
			||||||
		</g>
 | 
					 | 
				
			||||||
		<g>
 | 
					 | 
				
			||||||
			<g>
 | 
					 | 
				
			||||||
				<g>
 | 
					 | 
				
			||||||
					<path class="st2" d="M33.2,35.2c40.7,0,73.8,33.1,73.8,73.8c0,0.7,0,1.4,0,2.1c0,1.7,0.6,3.3,1.7,4.6c1.2,1.2,2.8,1.9,4.5,2
 | 
					 | 
				
			||||||
						l0.2,0c3.5,0,6.3-2.7,6.4-6.2c0-0.8,0-1.7,0-2.5c0-47.7-38.8-86.6-86.6-86.6c-0.8,0-1.7,0-2.5,0c-1.7,0-3.3,0.8-4.5,2
 | 
					 | 
				
			||||||
						c-1.2,1.2-1.8,2.9-1.7,4.6c0.1,3.5,3,6.3,6.6,6.2C31.8,35.2,32.5,35.2,33.2,35.2z"/>
 | 
					 | 
				
			||||||
				</g>
 | 
					 | 
				
			||||||
			</g>
 | 
					 | 
				
			||||||
		</g>
 | 
					 | 
				
			||||||
		<g>
 | 
					 | 
				
			||||||
			<g>
 | 
					 | 
				
			||||||
				<g>
 | 
					 | 
				
			||||||
					<path class="st3" d="M33.2,60.5c26.7,0,48.5,21.7,48.5,48.5c0,0.6,0,1.3,0,2c-0.1,1.7,0.5,3.3,1.7,4.6c1.2,1.3,2.7,2,4.4,2.1
 | 
					 | 
				
			||||||
						c1.7,0.1,3.3-0.5,4.6-1.7c1.2-1.2,2-2.7,2-4.4c0-0.9,0.1-1.8,0.1-2.6c0-33.8-27.5-61.2-61.2-61.2c-0.8,0-1.6,0-2.6,0.1
 | 
					 | 
				
			||||||
						c-1.7,0.1-3.3,0.8-4.4,2.1c-1.2,1.3-1.8,2.9-1.7,4.6s0.8,3.3,2.1,4.4c1.3,1.2,2.9,1.8,4.6,1.7C31.9,60.5,32.6,60.5,33.2,60.5z"
 | 
					 | 
				
			||||||
						/>
 | 
					 | 
				
			||||||
				</g>
 | 
					 | 
				
			||||||
			</g>
 | 
					 | 
				
			||||||
		</g>
 | 
					 | 
				
			||||||
		<g>
 | 
					 | 
				
			||||||
			<g>
 | 
					 | 
				
			||||||
				<g>
 | 
					 | 
				
			||||||
					<path class="st4" d="M33.2,86.7c12.3,0,22.3,10,22.3,22.3c0,0.5,0,1.1-0.1,1.8c-0.3,3.5,2.3,6.6,5.8,6.9
 | 
					 | 
				
			||||||
						c3.5,0.3,6.6-2.3,6.9-5.8c0.1-1,0.1-1.9,0.1-2.8c0-19.3-15.7-35.1-35.1-35.1c-0.9,0-1.8,0-2.8,0.1c-1.7,0.1-3.2,0.9-4.3,2.2
 | 
					 | 
				
			||||||
						c-1.1,1.3-1.6,2.9-1.5,4.6c0.1,1.7,0.9,3.2,2.2,4.3c1.3,1.1,2.9,1.6,4.6,1.5C32.1,86.7,32.7,86.7,33.2,86.7z"/>
 | 
					 | 
				
			||||||
				</g>
 | 
					 | 
				
			||||||
			</g>
 | 
					 | 
				
			||||||
		</g>
 | 
					 | 
				
			||||||
	</g>
 | 
					 | 
				
			||||||
	<g>
 | 
					 | 
				
			||||||
		<path class="st1" d="M35.8,130.4c1.1,0.6,2.1,1.5,2.7,2.6c0.7,1.1,1,2.3,1,3.7s-0.3,2.6-1,3.7c-0.7,1.1-1.6,2-2.7,2.6
 | 
					 | 
				
			||||||
			c-1.1,0.6-2.4,1-3.8,1s-2.7-0.3-3.8-1c-1.1-0.6-2.1-1.5-2.7-2.6c-0.7-1.1-1-2.3-1-3.7c0-1.3,0.3-2.6,1-3.7c0.7-1.1,1.6-2,2.7-2.6
 | 
					 | 
				
			||||||
			c1.1-0.6,2.4-0.9,3.8-0.9C33.4,129.5,34.7,129.8,35.8,130.4z M29.9,132.9c-0.7,0.4-1.2,0.9-1.6,1.6s-0.6,1.4-0.6,2.2
 | 
					 | 
				
			||||||
			c0,0.8,0.2,1.6,0.6,2.3c0.4,0.7,0.9,1.2,1.6,1.6c0.7,0.4,1.4,0.6,2.1,0.6c0.8,0,1.5-0.2,2.1-0.6c0.6-0.4,1.2-0.9,1.5-1.6
 | 
					 | 
				
			||||||
			c0.4-0.7,0.6-1.4,0.6-2.3c0-0.8-0.2-1.6-0.6-2.2s-0.9-1.2-1.5-1.6c-0.6-0.4-1.4-0.6-2.1-0.6C31.3,132.3,30.6,132.5,29.9,132.9z"/>
 | 
					 | 
				
			||||||
		<path class="st1" d="M50.6,133.6c0.8,0.5,1.4,1.1,1.8,2c0.4,0.8,0.6,1.8,0.6,2.9c0,1.1-0.2,2-0.6,2.8c-0.4,0.8-1,1.5-1.8,1.9
 | 
					 | 
				
			||||||
			c-0.8,0.5-1.6,0.7-2.6,0.7c-0.7,0-1.4-0.1-2-0.4s-1.1-0.7-1.5-1.2v5.4h-3.1V133h3.1v1.6c0.4-0.5,0.9-1,1.4-1.2s1.2-0.4,2-0.4
 | 
					 | 
				
			||||||
			C48.9,132.9,49.8,133.1,50.6,133.6z M49.1,140.5c0.5-0.6,0.7-1.3,0.7-2.2c0-0.9-0.2-1.6-0.7-2.1c-0.5-0.6-1.1-0.8-1.9-0.8
 | 
					 | 
				
			||||||
			s-1.4,0.3-1.9,0.8c-0.5,0.6-0.8,1.3-0.8,2.1c0,0.9,0.2,1.6,0.8,2.2s1.1,0.8,1.9,0.8S48.6,141,49.1,140.5z"/>
 | 
					 | 
				
			||||||
		<path class="st1" d="M63.4,134.4c0.9,1,1.4,2.4,1.4,4.2c0,0.3,0,0.6,0,0.7H57c0.2,0.7,0.5,1.2,1,1.6c0.5,0.4,1.1,0.6,1.8,0.6
 | 
					 | 
				
			||||||
			c0.5,0,1-0.1,1.5-0.3s0.9-0.5,1.3-0.9l1.6,1.6c-0.5,0.6-1.2,1.1-2,1.4c-0.8,0.3-1.6,0.5-2.6,0.5c-1.1,0-2.1-0.2-3-0.7
 | 
					 | 
				
			||||||
			s-1.5-1.1-2-1.9c-0.5-0.8-0.7-1.8-0.7-2.9c0-1.1,0.2-2.1,0.7-2.9s1.1-1.5,2-1.9c0.8-0.5,1.8-0.7,2.9-0.7
 | 
					 | 
				
			||||||
			C61.2,132.9,62.5,133.4,63.4,134.4z M61.8,137.5c0-0.7-0.3-1.3-0.7-1.7s-1-0.6-1.7-0.6c-0.7,0-1.2,0.2-1.7,0.6
 | 
					 | 
				
			||||||
			c-0.4,0.4-0.7,1-0.9,1.7H61.8z"/>
 | 
					 | 
				
			||||||
		<path class="st1" d="M76.2,134c0.7,0.7,1.1,1.7,1.1,3v6.8h-3.1v-5.9c0-0.7-0.2-1.2-0.6-1.6s-0.9-0.6-1.5-0.6
 | 
					 | 
				
			||||||
			c-0.8,0-1.4,0.3-1.8,0.8c-0.4,0.5-0.7,1.2-0.7,2v5.3h-3.1V133h3.1v1.9c0.7-1.3,2-2,3.7-2C74.6,132.8,75.5,133.2,76.2,134z"/>
 | 
					 | 
				
			||||||
		<path class="st1" d="M96,129.7h3.3l-4.7,14h-3.3l-2.9-10.1l-3,10.1h-3.2l-4.7-14h3.4l3,10.7l3-10.7H90l3.1,10.7L96,129.7z"/>
 | 
					 | 
				
			||||||
		<path class="st1" d="M103.3,128.7c0.3,0.3,0.5,0.7,0.5,1.2s-0.2,0.9-0.5,1.2c-0.3,0.3-0.7,0.5-1.2,0.5c-0.5,0-0.9-0.2-1.2-0.5
 | 
					 | 
				
			||||||
			c-0.3-0.3-0.5-0.7-0.5-1.2c0-0.5,0.2-0.9,0.5-1.2c0.3-0.3,0.7-0.5,1.2-0.5C102.6,128.2,103,128.3,103.3,128.7z M100.6,133h3.1
 | 
					 | 
				
			||||||
			v10.8h-3.1V133z"/>
 | 
					 | 
				
			||||||
		<path class="st1" d="M106.5,129.7h10.1l0,2.6h-6.9v3.4h6.3v2.6h-6.3v5.3h-3.2V129.7z"/>
 | 
					 | 
				
			||||||
		<path class="st1" d="M120.9,128.7c0.3,0.3,0.5,0.7,0.5,1.2s-0.2,0.9-0.5,1.2c-0.3,0.3-0.7,0.5-1.2,0.5c-0.5,0-0.9-0.2-1.2-0.5
 | 
					 | 
				
			||||||
			c-0.3-0.3-0.5-0.7-0.5-1.2c0-0.5,0.2-0.9,0.5-1.2c0.3-0.3,0.7-0.5,1.2-0.5C120.1,128.2,120.5,128.3,120.9,128.7z M118.1,133h3.1
 | 
					 | 
				
			||||||
			v10.8h-3.1V133z"/>
 | 
					 | 
				
			||||||
	</g>
 | 
					 | 
				
			||||||
</g>
 | 
					 | 
				
			||||||
<g>
 | 
					 | 
				
			||||||
</g>
 | 
					 | 
				
			||||||
<g>
 | 
					 | 
				
			||||||
</g>
 | 
					 | 
				
			||||||
<g>
 | 
					 | 
				
			||||||
</g>
 | 
					 | 
				
			||||||
<g>
 | 
					 | 
				
			||||||
</g>
 | 
					 | 
				
			||||||
<g>
 | 
					 | 
				
			||||||
</g>
 | 
					 | 
				
			||||||
<g>
 | 
					 | 
				
			||||||
</g>
 | 
					 | 
				
			||||||
</svg>
 | 
					 | 
				
			||||||
| 
		 Before Width: | Height: | Size: 8.0 KiB  | 
@@ -1,14 +0,0 @@
 | 
				
			|||||||
<!DOCTYPE html>
 | 
					 | 
				
			||||||
<html lang="en">
 | 
					 | 
				
			||||||
  <head>
 | 
					 | 
				
			||||||
    <meta charset="utf-8" />
 | 
					 | 
				
			||||||
    <link rel="icon" href="favicon.ico" />
 | 
					 | 
				
			||||||
    <meta name="viewport" content="width=device-width, initial-scale=1" />
 | 
					 | 
				
			||||||
    <meta name="theme-color" content="#000000" />
 | 
					 | 
				
			||||||
    <title>Gateway</title>
 | 
					 | 
				
			||||||
  </head>
 | 
					 | 
				
			||||||
  <body>
 | 
					 | 
				
			||||||
    <noscript>You need to enable JavaScript to run this app.</noscript>
 | 
					 | 
				
			||||||
    <div id="root"></div>
 | 
					 | 
				
			||||||
  </body>
 | 
					 | 
				
			||||||
</html>
 | 
					 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								public/mstile-150x150.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 9.9 KiB  | 
							
								
								
									
										38
									
								
								public/safari-pinned-tab.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,38 @@
 | 
				
			|||||||
 | 
					<?xml version="1.0" standalone="no"?>
 | 
				
			||||||
 | 
					<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
 | 
				
			||||||
 | 
					 "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
 | 
				
			||||||
 | 
					<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
 | 
				
			||||||
 | 
					 width="744.000000pt" height="744.000000pt" viewBox="0 0 744.000000 744.000000"
 | 
				
			||||||
 | 
					 preserveAspectRatio="xMidYMid meet">
 | 
				
			||||||
 | 
					<metadata>
 | 
				
			||||||
 | 
					Created by potrace 1.14, written by Peter Selinger 2001-2017
 | 
				
			||||||
 | 
					</metadata>
 | 
				
			||||||
 | 
					<g transform="translate(0.000000,744.000000) scale(0.100000,-0.100000)"
 | 
				
			||||||
 | 
					fill="#000000" stroke="none">
 | 
				
			||||||
 | 
					<path d="M1827 7404 c-2 -2 -50 -6 -108 -8 -57 -3 -120 -10 -139 -15 -19 -6
 | 
				
			||||||
 | 
					-56 -16 -82 -22 -27 -7 -48 -16 -48 -20 0 -4 -14 -10 -30 -14 -17 -4 -36 -12
 | 
				
			||||||
 | 
					-43 -18 -7 -7 -28 -21 -46 -31 -115 -64 -247 -224 -304 -366 -59 -150 -55 65
 | 
				
			||||||
 | 
					-56 -3180 0 -3270 -5 -3017 60 -3178 92 -232 303 -410 546 -463 32 -7 65 -14
 | 
				
			||||||
 | 
					73 -16 22 -5 4127 -3 4180 2 77 7 279 77 300 104 3 3 25 19 50 35 56 37 135
 | 
				
			||||||
 | 
					116 180 181 19 28 38 52 43 53 4 2 5 7 2 12 -3 4 2 13 10 20 8 7 15 21 15 31
 | 
				
			||||||
 | 
					0 10 4 20 9 23 8 6 39 91 47 131 2 11 7 31 11 45 4 14 8 1364 9 3000 2 3142 3
 | 
				
			||||||
 | 
					3032 -42 3165 -43 130 -131 255 -245 350 -33 28 -123 90 -127 87 -1 -1 -16 5
 | 
				
			||||||
 | 
					-34 14 -35 18 -128 50 -173 59 -30 6 -187 18 -215 16 -57 -3 -122 -1 -129 4
 | 
				
			||||||
 | 
					-5 3 -11 1 -13 -4 -1 -5 -23 -6 -48 -2 -25 4 -58 4 -75 1 -16 -3 -49 -3 -72 1
 | 
				
			||||||
 | 
					-23 3 -44 2 -45 -2 -2 -4 -23 -4 -48 0 -25 4 -58 4 -75 1 -16 -3 -49 -3 -72 1
 | 
				
			||||||
 | 
					-23 3 -44 2 -45 -2 -2 -4 -23 -4 -48 0 -25 4 -58 4 -75 1 -16 -3 -49 -3 -72 1
 | 
				
			||||||
 | 
					-23 3 -44 2 -45 -2 -2 -4 -23 -4 -48 0 -25 4 -58 4 -75 1 -16 -3 -49 -3 -72 1
 | 
				
			||||||
 | 
					-23 3 -44 2 -45 -2 -2 -4 -23 -4 -48 0 -25 4 -58 4 -75 1 -16 -3 -49 -3 -72 1
 | 
				
			||||||
 | 
					-23 3 -44 2 -45 -2 -2 -4 -23 -4 -48 0 -25 4 -58 4 -75 1 -16 -3 -49 -3 -72 1
 | 
				
			||||||
 | 
					-23 3 -44 2 -45 -2 -2 -4 -23 -4 -48 0 -25 4 -58 4 -75 1 -16 -3 -49 -3 -72 1
 | 
				
			||||||
 | 
					-23 3 -44 2 -45 -2 -2 -4 -23 -4 -48 0 -25 4 -58 4 -75 1 -16 -3 -49 -3 -72 1
 | 
				
			||||||
 | 
					-23 3 -44 2 -45 -2 -2 -4 -23 -4 -48 0 -25 4 -58 4 -75 1 -16 -3 -49 -3 -72 1
 | 
				
			||||||
 | 
					-23 3 -44 2 -45 -2 -2 -4 -23 -4 -48 0 -25 4 -58 4 -75 1 -16 -3 -49 -3 -72 1
 | 
				
			||||||
 | 
					-23 3 -44 2 -45 -2 -2 -4 -23 -4 -48 0 -25 4 -58 4 -75 1 -16 -3 -49 -3 -72 1
 | 
				
			||||||
 | 
					-23 3 -44 2 -45 -2 -2 -4 -23 -4 -48 0 -25 4 -58 4 -75 1 -16 -3 -49 -3 -72 1
 | 
				
			||||||
 | 
					-23 3 -44 2 -45 -2 -2 -4 -23 -4 -48 0 -25 4 -58 4 -75 1 -16 -3 -49 -3 -72 1
 | 
				
			||||||
 | 
					-23 3 -44 2 -45 -2 -2 -4 -23 -4 -48 0 -25 4 -58 4 -75 1 -16 -3 -49 -3 -72 1
 | 
				
			||||||
 | 
					-23 3 -44 2 -45 -2 -2 -4 -23 -4 -48 0 -25 4 -58 4 -75 1 -16 -3 -49 -3 -72 1
 | 
				
			||||||
 | 
					-23 3 -44 2 -45 -2 -2 -4 -24 -4 -50 0 -27 5 -49 6 -51 5z"/>
 | 
				
			||||||
 | 
					</g>
 | 
				
			||||||
 | 
					</svg>
 | 
				
			||||||
| 
		 After Width: | Height: | Size: 2.3 KiB  | 
							
								
								
									
										39
									
								
								src/App.js
									
									
									
									
									
								
							
							
						
						@@ -1,39 +0,0 @@
 | 
				
			|||||||
import React from 'react';
 | 
					 | 
				
			||||||
import { HashRouter, Switch } from 'react-router-dom';
 | 
					 | 
				
			||||||
import 'scss/style.scss';
 | 
					 | 
				
			||||||
import Router from 'router';
 | 
					 | 
				
			||||||
import { AuthProvider } from 'ucentral-libs';
 | 
					 | 
				
			||||||
import { checkIfJson } from 'utils/helper';
 | 
					 | 
				
			||||||
import axiosInstance from 'utils/axiosInstance';
 | 
					 | 
				
			||||||
import { getItem } from 'utils/localStorageHelper';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const loading = (
 | 
					 | 
				
			||||||
  <div className="pt-3 text-center">
 | 
					 | 
				
			||||||
    <div className="sk-spinner sk-spinner-pulse" />
 | 
					 | 
				
			||||||
  </div>
 | 
					 | 
				
			||||||
);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const App = () => {
 | 
					 | 
				
			||||||
  const storageToken = getItem('access_token');
 | 
					 | 
				
			||||||
  const apiEndpoints = checkIfJson(getItem('gateway_endpoints'))
 | 
					 | 
				
			||||||
    ? JSON.parse(getItem('gateway_endpoints'))
 | 
					 | 
				
			||||||
    : {};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return (
 | 
					 | 
				
			||||||
    <AuthProvider
 | 
					 | 
				
			||||||
      axiosInstance={axiosInstance}
 | 
					 | 
				
			||||||
      token={storageToken ?? ''}
 | 
					 | 
				
			||||||
      apiEndpoints={apiEndpoints}
 | 
					 | 
				
			||||||
    >
 | 
					 | 
				
			||||||
      <HashRouter>
 | 
					 | 
				
			||||||
        <React.Suspense fallback={loading}>
 | 
					 | 
				
			||||||
          <Switch>
 | 
					 | 
				
			||||||
            <Router />
 | 
					 | 
				
			||||||
          </Switch>
 | 
					 | 
				
			||||||
        </React.Suspense>
 | 
					 | 
				
			||||||
      </HashRouter>
 | 
					 | 
				
			||||||
    </AuthProvider>
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default App;
 | 
					 | 
				
			||||||
							
								
								
									
										45
									
								
								src/App.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,45 @@
 | 
				
			|||||||
 | 
					import React, { Suspense } from 'react';
 | 
				
			||||||
 | 
					import { Spinner } from '@chakra-ui/react';
 | 
				
			||||||
 | 
					import { QueryClientProvider, QueryClient } from '@tanstack/react-query';
 | 
				
			||||||
 | 
					import { HashRouter } from 'react-router-dom';
 | 
				
			||||||
 | 
					import { AuthProvider } from 'contexts/AuthProvider';
 | 
				
			||||||
 | 
					import { ControllerSocketProvider } from 'contexts/ControllerSocketProvider';
 | 
				
			||||||
 | 
					import { FirmwareSocketProvider } from 'contexts/FirmwareSocketProvider';
 | 
				
			||||||
 | 
					import { ProvisioningSocketProvider } from 'contexts/ProvisioningSocketProvider';
 | 
				
			||||||
 | 
					import { SecuritySocketProvider } from 'contexts/SecuritySocketProvider';
 | 
				
			||||||
 | 
					import Router from 'router';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const queryClient = new QueryClient({
 | 
				
			||||||
 | 
					  defaultOptions: {
 | 
				
			||||||
 | 
					    queries: {
 | 
				
			||||||
 | 
					      retry: 0,
 | 
				
			||||||
 | 
					      refetchOnWindowFocus: false,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const App = () => {
 | 
				
			||||||
 | 
					  const storageToken = localStorage.getItem('access_token') ?? sessionStorage.getItem('access_token');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <QueryClientProvider client={queryClient}>
 | 
				
			||||||
 | 
					      <HashRouter>
 | 
				
			||||||
 | 
					        <Suspense fallback={<Spinner />}>
 | 
				
			||||||
 | 
					          <AuthProvider token={storageToken !== null ? storageToken : undefined}>
 | 
				
			||||||
 | 
					            <SecuritySocketProvider>
 | 
				
			||||||
 | 
					              <FirmwareSocketProvider>
 | 
				
			||||||
 | 
					                <ProvisioningSocketProvider>
 | 
				
			||||||
 | 
					                  <ControllerSocketProvider>
 | 
				
			||||||
 | 
					                    <Router />
 | 
				
			||||||
 | 
					                  </ControllerSocketProvider>
 | 
				
			||||||
 | 
					                </ProvisioningSocketProvider>
 | 
				
			||||||
 | 
					              </FirmwareSocketProvider>
 | 
				
			||||||
 | 
					            </SecuritySocketProvider>
 | 
				
			||||||
 | 
					          </AuthProvider>
 | 
				
			||||||
 | 
					        </Suspense>
 | 
				
			||||||
 | 
					      </HashRouter>
 | 
				
			||||||
 | 
					    </QueryClientProvider>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default App;
 | 
				
			||||||
| 
		 Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 7.3 KiB  | 
| 
		 Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 7.3 KiB  | 
| 
		 Before Width: | Height: | Size: 24 KiB  | 
@@ -1,165 +0,0 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="utf-8"?>
 | 
					 | 
				
			||||||
<!-- Generator: Adobe Illustrator 24.2.3, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
 | 
					 | 
				
			||||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
 | 
					 | 
				
			||||||
	 viewBox="0 0 141.5 185.6" style="enable-background:new 0 0 141.5 185.6;" xml:space="preserve">
 | 
					 | 
				
			||||||
<style type="text/css">
 | 
					 | 
				
			||||||
	.st0{fill:#414141;}
 | 
					 | 
				
			||||||
	.st1{fill:#FFFFFF;}
 | 
					 | 
				
			||||||
	.st2{fill:#FED206;}
 | 
					 | 
				
			||||||
	.st3{fill:#EB6F53;}
 | 
					 | 
				
			||||||
	.st4{fill:#3BA9B6;}
 | 
					 | 
				
			||||||
</style>
 | 
					 | 
				
			||||||
<g>
 | 
					 | 
				
			||||||
	<g>
 | 
					 | 
				
			||||||
		<path class="st0" d="M120.7,183.9H21.5c-10.8,0-19.5-8.7-19.5-19.5V20.5c0-10.8,8.7-19.5,19.5-19.5h99.2
 | 
					 | 
				
			||||||
			c10.8,0,19.5,8.7,19.5,19.5v143.9C140.2,175.2,131.5,183.9,120.7,183.9z"/>
 | 
					 | 
				
			||||||
		<g>
 | 
					 | 
				
			||||||
			<g>
 | 
					 | 
				
			||||||
				<g>
 | 
					 | 
				
			||||||
					<path class="st1" d="M46.3,166.2v-3.4h-1.2v-0.6h3.1v0.6H47v3.4H46.3z"/>
 | 
					 | 
				
			||||||
				</g>
 | 
					 | 
				
			||||||
				<g>
 | 
					 | 
				
			||||||
					<path class="st1" d="M49,166.2v-4h2.7v0.6h-2v1h2v0.6h-2v1.1h2v0.6H49z"/>
 | 
					 | 
				
			||||||
				</g>
 | 
					 | 
				
			||||||
				<g>
 | 
					 | 
				
			||||||
					<path class="st1" d="M52.6,166.2v-4h0.7v3.4h1.8v0.6H52.6z"/>
 | 
					 | 
				
			||||||
				</g>
 | 
					 | 
				
			||||||
				<g>
 | 
					 | 
				
			||||||
					<path class="st1" d="M55.7,166.2v-4h2.7v0.6h-2v1h2v0.6h-2v1.1h2v0.6H55.7z"/>
 | 
					 | 
				
			||||||
				</g>
 | 
					 | 
				
			||||||
				<g>
 | 
					 | 
				
			||||||
					<path class="st1" d="M59.1,164.2c0-1.2,0.9-2.1,2.1-2.1c0.8,0,1.3,0.4,1.6,0.9l-0.6,0.3c-0.2-0.3-0.6-0.6-1-0.6
 | 
					 | 
				
			||||||
						c-0.8,0-1.4,0.6-1.4,1.4c0,0.8,0.6,1.4,1.4,1.4c0.4,0,0.8-0.3,1-0.6l0.6,0.3c-0.3,0.5-0.8,0.9-1.6,0.9
 | 
					 | 
				
			||||||
						C60,166.3,59.1,165.5,59.1,164.2z"/>
 | 
					 | 
				
			||||||
				</g>
 | 
					 | 
				
			||||||
				<g>
 | 
					 | 
				
			||||||
					<path class="st1" d="M63.2,164.2c0-1.2,0.8-2.1,2-2.1c1.2,0,2,0.9,2,2.1c0,1.2-0.8,2.1-2,2.1C64,166.3,63.2,165.4,63.2,164.2z
 | 
					 | 
				
			||||||
						 M66.5,164.2c0-0.8-0.5-1.4-1.3-1.4c-0.8,0-1.3,0.6-1.3,1.4c0,0.8,0.5,1.4,1.3,1.4C66,165.7,66.5,165,66.5,164.2z"/>
 | 
					 | 
				
			||||||
				</g>
 | 
					 | 
				
			||||||
				<g>
 | 
					 | 
				
			||||||
					<path class="st1" d="M71.3,166.2v-3.1l-1.2,3.1h-0.3l-1.2-3.1v3.1h-0.7v-4h1l1.1,2.7l1.1-2.7h1v4H71.3z"/>
 | 
					 | 
				
			||||||
				</g>
 | 
					 | 
				
			||||||
				<g>
 | 
					 | 
				
			||||||
					<path class="st1" d="M75.7,166.2v-4h0.7v4H75.7z"/>
 | 
					 | 
				
			||||||
				</g>
 | 
					 | 
				
			||||||
				<g>
 | 
					 | 
				
			||||||
					<path class="st1" d="M80.4,166.2l-2.1-2.8v2.8h-0.7v-4h0.7l2,2.8v-2.8h0.7v4H80.4z"/>
 | 
					 | 
				
			||||||
				</g>
 | 
					 | 
				
			||||||
				<g>
 | 
					 | 
				
			||||||
					<path class="st1" d="M82.3,166.2v-4H85v0.6h-2v1h2v0.6h-2v1.7H82.3z"/>
 | 
					 | 
				
			||||||
				</g>
 | 
					 | 
				
			||||||
				<g>
 | 
					 | 
				
			||||||
					<path class="st1" d="M87.9,166.2l-0.9-1.5h-0.7v1.5h-0.7v-4h1.7c0.8,0,1.3,0.5,1.3,1.2c0,0.7-0.5,1.1-0.9,1.2l1,1.6H87.9z
 | 
					 | 
				
			||||||
						 M88,163.5c0-0.4-0.3-0.6-0.7-0.6h-1v1.3h1C87.7,164.1,88,163.9,88,163.5z"/>
 | 
					 | 
				
			||||||
				</g>
 | 
					 | 
				
			||||||
				<g>
 | 
					 | 
				
			||||||
					<path class="st1" d="M92.4,166.2l-0.3-0.8h-1.8l-0.3,0.8h-0.8l1.6-4h0.9l1.6,4H92.4z M91.2,162.9l-0.7,1.9h1.4L91.2,162.9z"/>
 | 
					 | 
				
			||||||
				</g>
 | 
					 | 
				
			||||||
				<g>
 | 
					 | 
				
			||||||
					<path class="st1" d="M95.8,166.2v-4h1.5c0.8,0,1.2,0.5,1.2,1.2c0,0.6-0.4,1.2-1.2,1.2h-1.2v1.7H95.8z M98.2,163.4
 | 
					 | 
				
			||||||
						c0-0.5-0.3-0.9-0.9-0.9h-1.1v1.7h1.1C97.8,164.3,98.2,163.9,98.2,163.4z"/>
 | 
					 | 
				
			||||||
				</g>
 | 
					 | 
				
			||||||
				<g>
 | 
					 | 
				
			||||||
					<path class="st1" d="M101.5,166.2l-1.1-1.6h-0.9v1.6h-0.3v-4h1.5c0.7,0,1.2,0.4,1.2,1.2c0,0.7-0.5,1.1-1.1,1.1l1.2,1.7H101.5z
 | 
					 | 
				
			||||||
						 M101.6,163.4c0-0.5-0.4-0.9-0.9-0.9h-1.1v1.7h1.1C101.2,164.3,101.6,163.9,101.6,163.4z"/>
 | 
					 | 
				
			||||||
				</g>
 | 
					 | 
				
			||||||
				<g>
 | 
					 | 
				
			||||||
					<path class="st1" d="M102.8,164.2c0-1.2,0.8-2.1,1.9-2.1c1.2,0,1.9,0.9,1.9,2.1c0,1.2-0.8,2.1-1.9,2.1
 | 
					 | 
				
			||||||
						C103.6,166.3,102.8,165.4,102.8,164.2z M106.3,164.2c0-1-0.6-1.7-1.6-1.7c-1,0-1.6,0.7-1.6,1.7c0,1,0.6,1.7,1.6,1.7
 | 
					 | 
				
			||||||
						C105.7,166,106.3,165.2,106.3,164.2z"/>
 | 
					 | 
				
			||||||
				</g>
 | 
					 | 
				
			||||||
				<g>
 | 
					 | 
				
			||||||
					<path class="st1" d="M106.9,165.8l0.2-0.3c0.2,0.2,0.4,0.4,0.8,0.4c0.5,0,0.9-0.4,0.9-0.9v-2.8h0.3v2.8c0,0.8-0.5,1.2-1.2,1.2
 | 
					 | 
				
			||||||
						C107.5,166.3,107.2,166.1,106.9,165.8z"/>
 | 
					 | 
				
			||||||
				</g>
 | 
					 | 
				
			||||||
				<g>
 | 
					 | 
				
			||||||
					<path class="st1" d="M110.4,166.2v-4h2.5v0.3h-2.2v1.5h2.1v0.3h-2.1v1.6h2.2v0.3H110.4z"/>
 | 
					 | 
				
			||||||
				</g>
 | 
					 | 
				
			||||||
				<g>
 | 
					 | 
				
			||||||
					<path class="st1" d="M113.5,164.2c0-1.2,0.9-2.1,2-2.1c0.6,0,1.1,0.3,1.5,0.7l-0.3,0.2c-0.3-0.3-0.7-0.6-1.2-0.6
 | 
					 | 
				
			||||||
						c-0.9,0-1.7,0.7-1.7,1.7c0,1,0.7,1.7,1.7,1.7c0.5,0,0.9-0.2,1.2-0.6l0.3,0.2c-0.4,0.4-0.8,0.7-1.5,0.7
 | 
					 | 
				
			||||||
						C114.4,166.3,113.5,165.5,113.5,164.2z"/>
 | 
					 | 
				
			||||||
				</g>
 | 
					 | 
				
			||||||
				<g>
 | 
					 | 
				
			||||||
					<path class="st1" d="M118.7,166.2v-3.7h-1.3v-0.3h2.9v0.3H119v3.7H118.7z"/>
 | 
					 | 
				
			||||||
				</g>
 | 
					 | 
				
			||||||
			</g>
 | 
					 | 
				
			||||||
			<g>
 | 
					 | 
				
			||||||
				<polygon class="st1" points="26.3,163.8 31.6,158.5 36.9,163.8 37.7,163.8 31.6,157.6 25.5,163.8 				"/>
 | 
					 | 
				
			||||||
				<polygon class="st1" points="36.9,164.7 31.6,170 26.3,164.7 25.5,164.7 31.6,170.8 37.7,164.7 				"/>
 | 
					 | 
				
			||||||
				<polygon class="st1" points="31,163.8 36.3,158.5 41.6,163.8 42.5,163.8 36.3,157.6 30.2,163.8 				"/>
 | 
					 | 
				
			||||||
				<polygon class="st1" points="41.6,164.7 36.3,170 31,164.7 30.2,164.7 36.3,170.8 42.5,164.7 				"/>
 | 
					 | 
				
			||||||
			</g>
 | 
					 | 
				
			||||||
		</g>
 | 
					 | 
				
			||||||
		<g>
 | 
					 | 
				
			||||||
			<path class="st1" d="M33.2,100.7c-4.6,0-8.3,3.7-8.3,8.3s3.7,8.3,8.3,8.3s8.3-3.7,8.3-8.3S37.8,100.7,33.2,100.7z"/>
 | 
					 | 
				
			||||||
		</g>
 | 
					 | 
				
			||||||
		<g>
 | 
					 | 
				
			||||||
			<g>
 | 
					 | 
				
			||||||
				<g>
 | 
					 | 
				
			||||||
					<path class="st2" d="M33.2,35.2c40.7,0,73.8,33.1,73.8,73.8c0,0.7,0,1.4,0,2.1c0,1.7,0.6,3.3,1.7,4.6c1.2,1.2,2.8,1.9,4.5,2
 | 
					 | 
				
			||||||
						l0.2,0c3.5,0,6.3-2.7,6.4-6.2c0-0.8,0-1.7,0-2.5c0-47.7-38.8-86.6-86.6-86.6c-0.8,0-1.7,0-2.5,0c-1.7,0-3.3,0.8-4.5,2
 | 
					 | 
				
			||||||
						c-1.2,1.2-1.8,2.9-1.7,4.6c0.1,3.5,3,6.3,6.6,6.2C31.8,35.2,32.5,35.2,33.2,35.2z"/>
 | 
					 | 
				
			||||||
				</g>
 | 
					 | 
				
			||||||
			</g>
 | 
					 | 
				
			||||||
		</g>
 | 
					 | 
				
			||||||
		<g>
 | 
					 | 
				
			||||||
			<g>
 | 
					 | 
				
			||||||
				<g>
 | 
					 | 
				
			||||||
					<path class="st3" d="M33.2,60.5c26.7,0,48.5,21.7,48.5,48.5c0,0.6,0,1.3,0,2c-0.1,1.7,0.5,3.3,1.7,4.6c1.2,1.3,2.7,2,4.4,2.1
 | 
					 | 
				
			||||||
						c1.7,0.1,3.3-0.5,4.6-1.7c1.2-1.2,2-2.7,2-4.4c0-0.9,0.1-1.8,0.1-2.6c0-33.8-27.5-61.2-61.2-61.2c-0.8,0-1.6,0-2.6,0.1
 | 
					 | 
				
			||||||
						c-1.7,0.1-3.3,0.8-4.4,2.1c-1.2,1.3-1.8,2.9-1.7,4.6s0.8,3.3,2.1,4.4c1.3,1.2,2.9,1.8,4.6,1.7C31.9,60.5,32.6,60.5,33.2,60.5z"
 | 
					 | 
				
			||||||
						/>
 | 
					 | 
				
			||||||
				</g>
 | 
					 | 
				
			||||||
			</g>
 | 
					 | 
				
			||||||
		</g>
 | 
					 | 
				
			||||||
		<g>
 | 
					 | 
				
			||||||
			<g>
 | 
					 | 
				
			||||||
				<g>
 | 
					 | 
				
			||||||
					<path class="st4" d="M33.2,86.7c12.3,0,22.3,10,22.3,22.3c0,0.5,0,1.1-0.1,1.8c-0.3,3.5,2.3,6.6,5.8,6.9
 | 
					 | 
				
			||||||
						c3.5,0.3,6.6-2.3,6.9-5.8c0.1-1,0.1-1.9,0.1-2.8c0-19.3-15.7-35.1-35.1-35.1c-0.9,0-1.8,0-2.8,0.1c-1.7,0.1-3.2,0.9-4.3,2.2
 | 
					 | 
				
			||||||
						c-1.1,1.3-1.6,2.9-1.5,4.6c0.1,1.7,0.9,3.2,2.2,4.3c1.3,1.1,2.9,1.6,4.6,1.5C32.1,86.7,32.7,86.7,33.2,86.7z"/>
 | 
					 | 
				
			||||||
				</g>
 | 
					 | 
				
			||||||
			</g>
 | 
					 | 
				
			||||||
		</g>
 | 
					 | 
				
			||||||
	</g>
 | 
					 | 
				
			||||||
	<g>
 | 
					 | 
				
			||||||
		<path class="st1" d="M35.8,130.4c1.1,0.6,2.1,1.5,2.7,2.6c0.7,1.1,1,2.3,1,3.7s-0.3,2.6-1,3.7c-0.7,1.1-1.6,2-2.7,2.6
 | 
					 | 
				
			||||||
			c-1.1,0.6-2.4,1-3.8,1s-2.7-0.3-3.8-1c-1.1-0.6-2.1-1.5-2.7-2.6c-0.7-1.1-1-2.3-1-3.7c0-1.3,0.3-2.6,1-3.7c0.7-1.1,1.6-2,2.7-2.6
 | 
					 | 
				
			||||||
			c1.1-0.6,2.4-0.9,3.8-0.9C33.4,129.5,34.7,129.8,35.8,130.4z M29.9,132.9c-0.7,0.4-1.2,0.9-1.6,1.6s-0.6,1.4-0.6,2.2
 | 
					 | 
				
			||||||
			c0,0.8,0.2,1.6,0.6,2.3c0.4,0.7,0.9,1.2,1.6,1.6c0.7,0.4,1.4,0.6,2.1,0.6c0.8,0,1.5-0.2,2.1-0.6c0.6-0.4,1.2-0.9,1.5-1.6
 | 
					 | 
				
			||||||
			c0.4-0.7,0.6-1.4,0.6-2.3c0-0.8-0.2-1.6-0.6-2.2s-0.9-1.2-1.5-1.6c-0.6-0.4-1.4-0.6-2.1-0.6C31.3,132.3,30.6,132.5,29.9,132.9z"/>
 | 
					 | 
				
			||||||
		<path class="st1" d="M50.6,133.6c0.8,0.5,1.4,1.1,1.8,2c0.4,0.8,0.6,1.8,0.6,2.9c0,1.1-0.2,2-0.6,2.8c-0.4,0.8-1,1.5-1.8,1.9
 | 
					 | 
				
			||||||
			c-0.8,0.5-1.6,0.7-2.6,0.7c-0.7,0-1.4-0.1-2-0.4s-1.1-0.7-1.5-1.2v5.4h-3.1V133h3.1v1.6c0.4-0.5,0.9-1,1.4-1.2s1.2-0.4,2-0.4
 | 
					 | 
				
			||||||
			C48.9,132.9,49.8,133.1,50.6,133.6z M49.1,140.5c0.5-0.6,0.7-1.3,0.7-2.2c0-0.9-0.2-1.6-0.7-2.1c-0.5-0.6-1.1-0.8-1.9-0.8
 | 
					 | 
				
			||||||
			s-1.4,0.3-1.9,0.8c-0.5,0.6-0.8,1.3-0.8,2.1c0,0.9,0.2,1.6,0.8,2.2s1.1,0.8,1.9,0.8S48.6,141,49.1,140.5z"/>
 | 
					 | 
				
			||||||
		<path class="st1" d="M63.4,134.4c0.9,1,1.4,2.4,1.4,4.2c0,0.3,0,0.6,0,0.7H57c0.2,0.7,0.5,1.2,1,1.6c0.5,0.4,1.1,0.6,1.8,0.6
 | 
					 | 
				
			||||||
			c0.5,0,1-0.1,1.5-0.3s0.9-0.5,1.3-0.9l1.6,1.6c-0.5,0.6-1.2,1.1-2,1.4c-0.8,0.3-1.6,0.5-2.6,0.5c-1.1,0-2.1-0.2-3-0.7
 | 
					 | 
				
			||||||
			s-1.5-1.1-2-1.9c-0.5-0.8-0.7-1.8-0.7-2.9c0-1.1,0.2-2.1,0.7-2.9s1.1-1.5,2-1.9c0.8-0.5,1.8-0.7,2.9-0.7
 | 
					 | 
				
			||||||
			C61.2,132.9,62.5,133.4,63.4,134.4z M61.8,137.5c0-0.7-0.3-1.3-0.7-1.7s-1-0.6-1.7-0.6c-0.7,0-1.2,0.2-1.7,0.6
 | 
					 | 
				
			||||||
			c-0.4,0.4-0.7,1-0.9,1.7H61.8z"/>
 | 
					 | 
				
			||||||
		<path class="st1" d="M76.2,134c0.7,0.7,1.1,1.7,1.1,3v6.8h-3.1v-5.9c0-0.7-0.2-1.2-0.6-1.6s-0.9-0.6-1.5-0.6
 | 
					 | 
				
			||||||
			c-0.8,0-1.4,0.3-1.8,0.8c-0.4,0.5-0.7,1.2-0.7,2v5.3h-3.1V133h3.1v1.9c0.7-1.3,2-2,3.7-2C74.6,132.8,75.5,133.2,76.2,134z"/>
 | 
					 | 
				
			||||||
		<path class="st1" d="M96,129.7h3.3l-4.7,14h-3.3l-2.9-10.1l-3,10.1h-3.2l-4.7-14h3.4l3,10.7l3-10.7H90l3.1,10.7L96,129.7z"/>
 | 
					 | 
				
			||||||
		<path class="st1" d="M103.3,128.7c0.3,0.3,0.5,0.7,0.5,1.2s-0.2,0.9-0.5,1.2c-0.3,0.3-0.7,0.5-1.2,0.5c-0.5,0-0.9-0.2-1.2-0.5
 | 
					 | 
				
			||||||
			c-0.3-0.3-0.5-0.7-0.5-1.2c0-0.5,0.2-0.9,0.5-1.2c0.3-0.3,0.7-0.5,1.2-0.5C102.6,128.2,103,128.3,103.3,128.7z M100.6,133h3.1
 | 
					 | 
				
			||||||
			v10.8h-3.1V133z"/>
 | 
					 | 
				
			||||||
		<path class="st1" d="M106.5,129.7h10.1l0,2.6h-6.9v3.4h6.3v2.6h-6.3v5.3h-3.2V129.7z"/>
 | 
					 | 
				
			||||||
		<path class="st1" d="M120.9,128.7c0.3,0.3,0.5,0.7,0.5,1.2s-0.2,0.9-0.5,1.2c-0.3,0.3-0.7,0.5-1.2,0.5c-0.5,0-0.9-0.2-1.2-0.5
 | 
					 | 
				
			||||||
			c-0.3-0.3-0.5-0.7-0.5-1.2c0-0.5,0.2-0.9,0.5-1.2c0.3-0.3,0.7-0.5,1.2-0.5C120.1,128.2,120.5,128.3,120.9,128.7z M118.1,133h3.1
 | 
					 | 
				
			||||||
			v10.8h-3.1V133z"/>
 | 
					 | 
				
			||||||
	</g>
 | 
					 | 
				
			||||||
</g>
 | 
					 | 
				
			||||||
<g>
 | 
					 | 
				
			||||||
</g>
 | 
					 | 
				
			||||||
<g>
 | 
					 | 
				
			||||||
</g>
 | 
					 | 
				
			||||||
<g>
 | 
					 | 
				
			||||||
</g>
 | 
					 | 
				
			||||||
<g>
 | 
					 | 
				
			||||||
</g>
 | 
					 | 
				
			||||||
<g>
 | 
					 | 
				
			||||||
</g>
 | 
					 | 
				
			||||||
<g>
 | 
					 | 
				
			||||||
</g>
 | 
					 | 
				
			||||||
</svg>
 | 
					 | 
				
			||||||
| 
		 Before Width: | Height: | Size: 8.0 KiB  | 
@@ -1,203 +0,0 @@
 | 
				
			|||||||
import {
 | 
					 | 
				
			||||||
  cifUs,
 | 
					 | 
				
			||||||
  cifBr,
 | 
					 | 
				
			||||||
  cifIn,
 | 
					 | 
				
			||||||
  cifFr,
 | 
					 | 
				
			||||||
  cifEs,
 | 
					 | 
				
			||||||
  cifPl,
 | 
					 | 
				
			||||||
  cilAlignCenter,
 | 
					 | 
				
			||||||
  cilAlignLeft,
 | 
					 | 
				
			||||||
  cilAlignRight,
 | 
					 | 
				
			||||||
  cilApplicationsSettings,
 | 
					 | 
				
			||||||
  cilArrowRight,
 | 
					 | 
				
			||||||
  cilArrowTop,
 | 
					 | 
				
			||||||
  cilAsterisk,
 | 
					 | 
				
			||||||
  cilBan,
 | 
					 | 
				
			||||||
  cilBarcode,
 | 
					 | 
				
			||||||
  cilBasket,
 | 
					 | 
				
			||||||
  cilBell,
 | 
					 | 
				
			||||||
  cilBold,
 | 
					 | 
				
			||||||
  cilBookmark,
 | 
					 | 
				
			||||||
  cilCalculator,
 | 
					 | 
				
			||||||
  cilCalendar,
 | 
					 | 
				
			||||||
  cilCloudDownload,
 | 
					 | 
				
			||||||
  cilChartPie,
 | 
					 | 
				
			||||||
  cilCheck,
 | 
					 | 
				
			||||||
  cilChevronBottom,
 | 
					 | 
				
			||||||
  cilChevronLeft,
 | 
					 | 
				
			||||||
  cilChevronRight,
 | 
					 | 
				
			||||||
  cilChevronTop,
 | 
					 | 
				
			||||||
  cilCircle,
 | 
					 | 
				
			||||||
  cilCheckCircle,
 | 
					 | 
				
			||||||
  cilCode,
 | 
					 | 
				
			||||||
  cilCommentSquare,
 | 
					 | 
				
			||||||
  cilCreditCard,
 | 
					 | 
				
			||||||
  cilCursor,
 | 
					 | 
				
			||||||
  cilCursorMove,
 | 
					 | 
				
			||||||
  cilDrop,
 | 
					 | 
				
			||||||
  cilDollar,
 | 
					 | 
				
			||||||
  cilEnvelopeClosed,
 | 
					 | 
				
			||||||
  cilEnvelopeLetter,
 | 
					 | 
				
			||||||
  cilEnvelopeOpen,
 | 
					 | 
				
			||||||
  cilEuro,
 | 
					 | 
				
			||||||
  cilGlobeAlt,
 | 
					 | 
				
			||||||
  cilGrid,
 | 
					 | 
				
			||||||
  cilFile,
 | 
					 | 
				
			||||||
  cilFullscreen,
 | 
					 | 
				
			||||||
  cilFullscreenExit,
 | 
					 | 
				
			||||||
  cilGraph,
 | 
					 | 
				
			||||||
  cilHome,
 | 
					 | 
				
			||||||
  cilInbox,
 | 
					 | 
				
			||||||
  cilIndentDecrease,
 | 
					 | 
				
			||||||
  cilIndentIncrease,
 | 
					 | 
				
			||||||
  cilInputPower,
 | 
					 | 
				
			||||||
  cilItalic,
 | 
					 | 
				
			||||||
  cilJustifyCenter,
 | 
					 | 
				
			||||||
  cilJustifyLeft,
 | 
					 | 
				
			||||||
  cilLaptop,
 | 
					 | 
				
			||||||
  cilLayers,
 | 
					 | 
				
			||||||
  cilLightbulb,
 | 
					 | 
				
			||||||
  cilList,
 | 
					 | 
				
			||||||
  cilListNumbered,
 | 
					 | 
				
			||||||
  cilListRich,
 | 
					 | 
				
			||||||
  cilLocationPin,
 | 
					 | 
				
			||||||
  cilLockLocked,
 | 
					 | 
				
			||||||
  cilMagnifyingGlass,
 | 
					 | 
				
			||||||
  cilMap,
 | 
					 | 
				
			||||||
  cilMoon,
 | 
					 | 
				
			||||||
  cilNotes,
 | 
					 | 
				
			||||||
  cilOptions,
 | 
					 | 
				
			||||||
  cilPaperclip,
 | 
					 | 
				
			||||||
  cilPaperPlane,
 | 
					 | 
				
			||||||
  cilPencil,
 | 
					 | 
				
			||||||
  cilPeople,
 | 
					 | 
				
			||||||
  cilPhone,
 | 
					 | 
				
			||||||
  cilPrint,
 | 
					 | 
				
			||||||
  cilPuzzle,
 | 
					 | 
				
			||||||
  cilRouter,
 | 
					 | 
				
			||||||
  cilSave,
 | 
					 | 
				
			||||||
  cilScrubber,
 | 
					 | 
				
			||||||
  cilSettings,
 | 
					 | 
				
			||||||
  cilShare,
 | 
					 | 
				
			||||||
  cilShareAll,
 | 
					 | 
				
			||||||
  cilShareBoxed,
 | 
					 | 
				
			||||||
  cilShieldAlt,
 | 
					 | 
				
			||||||
  cilSpeech,
 | 
					 | 
				
			||||||
  cilSpeedometer,
 | 
					 | 
				
			||||||
  cilSpreadsheet,
 | 
					 | 
				
			||||||
  cilStar,
 | 
					 | 
				
			||||||
  cilSun,
 | 
					 | 
				
			||||||
  cilTags,
 | 
					 | 
				
			||||||
  cilTask,
 | 
					 | 
				
			||||||
  cilTrash,
 | 
					 | 
				
			||||||
  cilUnderline,
 | 
					 | 
				
			||||||
  cilUser,
 | 
					 | 
				
			||||||
  cilUserFemale,
 | 
					 | 
				
			||||||
  cilUserFollow,
 | 
					 | 
				
			||||||
  cilUserUnfollow,
 | 
					 | 
				
			||||||
  cilX,
 | 
					 | 
				
			||||||
  cilXCircle,
 | 
					 | 
				
			||||||
  cilWarning,
 | 
					 | 
				
			||||||
} from '@coreui/icons';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const icons = {
 | 
					 | 
				
			||||||
  cilAlignCenter,
 | 
					 | 
				
			||||||
  cilAlignLeft,
 | 
					 | 
				
			||||||
  cilAlignRight,
 | 
					 | 
				
			||||||
  cilApplicationsSettings,
 | 
					 | 
				
			||||||
  cilArrowRight,
 | 
					 | 
				
			||||||
  cilArrowTop,
 | 
					 | 
				
			||||||
  cilAsterisk,
 | 
					 | 
				
			||||||
  cilBan,
 | 
					 | 
				
			||||||
  cilBarcode,
 | 
					 | 
				
			||||||
  cilBasket,
 | 
					 | 
				
			||||||
  cilBell,
 | 
					 | 
				
			||||||
  cilBold,
 | 
					 | 
				
			||||||
  cilBookmark,
 | 
					 | 
				
			||||||
  cilCalculator,
 | 
					 | 
				
			||||||
  cilCalendar,
 | 
					 | 
				
			||||||
  cilCloudDownload,
 | 
					 | 
				
			||||||
  cilChartPie,
 | 
					 | 
				
			||||||
  cilCheck,
 | 
					 | 
				
			||||||
  cilChevronBottom,
 | 
					 | 
				
			||||||
  cilChevronLeft,
 | 
					 | 
				
			||||||
  cilChevronRight,
 | 
					 | 
				
			||||||
  cilChevronTop,
 | 
					 | 
				
			||||||
  cilCircle,
 | 
					 | 
				
			||||||
  cilCheckCircle,
 | 
					 | 
				
			||||||
  cilCode,
 | 
					 | 
				
			||||||
  cilCommentSquare,
 | 
					 | 
				
			||||||
  cilCreditCard,
 | 
					 | 
				
			||||||
  cilCursor,
 | 
					 | 
				
			||||||
  cilCursorMove,
 | 
					 | 
				
			||||||
  cilDrop,
 | 
					 | 
				
			||||||
  cilDollar,
 | 
					 | 
				
			||||||
  cilEnvelopeClosed,
 | 
					 | 
				
			||||||
  cilEnvelopeLetter,
 | 
					 | 
				
			||||||
  cilEnvelopeOpen,
 | 
					 | 
				
			||||||
  cilEuro,
 | 
					 | 
				
			||||||
  cilGlobeAlt,
 | 
					 | 
				
			||||||
  cilGrid,
 | 
					 | 
				
			||||||
  cilFile,
 | 
					 | 
				
			||||||
  cilFullscreen,
 | 
					 | 
				
			||||||
  cilFullscreenExit,
 | 
					 | 
				
			||||||
  cilGraph,
 | 
					 | 
				
			||||||
  cilHome,
 | 
					 | 
				
			||||||
  cilInbox,
 | 
					 | 
				
			||||||
  cilIndentDecrease,
 | 
					 | 
				
			||||||
  cilIndentIncrease,
 | 
					 | 
				
			||||||
  cilInputPower,
 | 
					 | 
				
			||||||
  cilItalic,
 | 
					 | 
				
			||||||
  cilJustifyCenter,
 | 
					 | 
				
			||||||
  cilJustifyLeft,
 | 
					 | 
				
			||||||
  cilLaptop,
 | 
					 | 
				
			||||||
  cilLayers,
 | 
					 | 
				
			||||||
  cilLightbulb,
 | 
					 | 
				
			||||||
  cilList,
 | 
					 | 
				
			||||||
  cilListNumbered,
 | 
					 | 
				
			||||||
  cilListRich,
 | 
					 | 
				
			||||||
  cilLocationPin,
 | 
					 | 
				
			||||||
  cilLockLocked,
 | 
					 | 
				
			||||||
  cilMagnifyingGlass,
 | 
					 | 
				
			||||||
  cilMap,
 | 
					 | 
				
			||||||
  cilMoon,
 | 
					 | 
				
			||||||
  cilNotes,
 | 
					 | 
				
			||||||
  cilOptions,
 | 
					 | 
				
			||||||
  cilPaperclip,
 | 
					 | 
				
			||||||
  cilPaperPlane,
 | 
					 | 
				
			||||||
  cilPencil,
 | 
					 | 
				
			||||||
  cilPeople,
 | 
					 | 
				
			||||||
  cilPhone,
 | 
					 | 
				
			||||||
  cilPrint,
 | 
					 | 
				
			||||||
  cilPuzzle,
 | 
					 | 
				
			||||||
  cilRouter,
 | 
					 | 
				
			||||||
  cilSave,
 | 
					 | 
				
			||||||
  cilScrubber,
 | 
					 | 
				
			||||||
  cilSettings,
 | 
					 | 
				
			||||||
  cilShare,
 | 
					 | 
				
			||||||
  cilShareAll,
 | 
					 | 
				
			||||||
  cilShareBoxed,
 | 
					 | 
				
			||||||
  cilShieldAlt,
 | 
					 | 
				
			||||||
  cilSpeech,
 | 
					 | 
				
			||||||
  cilSpeedometer,
 | 
					 | 
				
			||||||
  cilSpreadsheet,
 | 
					 | 
				
			||||||
  cilStar,
 | 
					 | 
				
			||||||
  cilSun,
 | 
					 | 
				
			||||||
  cilTags,
 | 
					 | 
				
			||||||
  cilTask,
 | 
					 | 
				
			||||||
  cilTrash,
 | 
					 | 
				
			||||||
  cilUnderline,
 | 
					 | 
				
			||||||
  cilUser,
 | 
					 | 
				
			||||||
  cilUserFemale,
 | 
					 | 
				
			||||||
  cilUserFollow,
 | 
					 | 
				
			||||||
  cilUserUnfollow,
 | 
					 | 
				
			||||||
  cilX,
 | 
					 | 
				
			||||||
  cilXCircle,
 | 
					 | 
				
			||||||
  cilWarning,
 | 
					 | 
				
			||||||
  cifUs,
 | 
					 | 
				
			||||||
  cifBr,
 | 
					 | 
				
			||||||
  cifIn,
 | 
					 | 
				
			||||||
  cifFr,
 | 
					 | 
				
			||||||
  cifEs,
 | 
					 | 
				
			||||||
  cifPl,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
@@ -1,163 +0,0 @@
 | 
				
			|||||||
import React, { useState, useEffect } from 'react';
 | 
					 | 
				
			||||||
import PropTypes from 'prop-types';
 | 
					 | 
				
			||||||
import Select from 'react-select';
 | 
					 | 
				
			||||||
import {
 | 
					 | 
				
			||||||
  CForm,
 | 
					 | 
				
			||||||
  CInput,
 | 
					 | 
				
			||||||
  CLabel,
 | 
					 | 
				
			||||||
  CCol,
 | 
					 | 
				
			||||||
  CFormGroup,
 | 
					 | 
				
			||||||
  CInvalidFeedback,
 | 
					 | 
				
			||||||
  CFormText,
 | 
					 | 
				
			||||||
  CRow,
 | 
					 | 
				
			||||||
  CTextarea,
 | 
					 | 
				
			||||||
} from '@coreui/react';
 | 
					 | 
				
			||||||
import { CopyToClipboardButton } from 'ucentral-libs';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const AddDefaultConfigurationForm = ({
 | 
					 | 
				
			||||||
  t,
 | 
					 | 
				
			||||||
  disable,
 | 
					 | 
				
			||||||
  fields,
 | 
					 | 
				
			||||||
  updateField,
 | 
					 | 
				
			||||||
  updateFieldWithKey,
 | 
					 | 
				
			||||||
  deviceTypes,
 | 
					 | 
				
			||||||
}) => {
 | 
					 | 
				
			||||||
  const [typeOptions, setTypeOptions] = useState([]);
 | 
					 | 
				
			||||||
  const [chosenTypes, setChosenTypes] = useState([]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const parseOptions = () => {
 | 
					 | 
				
			||||||
    const options = [{ value: '*', label: 'All' }];
 | 
					 | 
				
			||||||
    const newOptions = deviceTypes.map((option) => ({
 | 
					 | 
				
			||||||
      value: option,
 | 
					 | 
				
			||||||
      label: option,
 | 
					 | 
				
			||||||
    }));
 | 
					 | 
				
			||||||
    options.push(...newOptions);
 | 
					 | 
				
			||||||
    setTypeOptions(options);
 | 
					 | 
				
			||||||
    setChosenTypes([]);
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const typeOnChange = (chosenArray) => {
 | 
					 | 
				
			||||||
    const allIndex = chosenArray.findIndex((el) => el.value === '*');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // If the All option was chosen before, we take it out of the array
 | 
					 | 
				
			||||||
    if (allIndex === 0 && chosenTypes.length > 0) {
 | 
					 | 
				
			||||||
      const newResults = chosenArray.slice(1);
 | 
					 | 
				
			||||||
      setChosenTypes(newResults);
 | 
					 | 
				
			||||||
      updateFieldWithKey('deviceTypes', {
 | 
					 | 
				
			||||||
        value: newResults.map((el) => el.value),
 | 
					 | 
				
			||||||
        error: false,
 | 
					 | 
				
			||||||
        notEmpty: true,
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    } else if (allIndex > 0) {
 | 
					 | 
				
			||||||
      setChosenTypes([{ value: '*', label: 'All' }]);
 | 
					 | 
				
			||||||
      updateFieldWithKey('deviceTypes', { value: ['*'], error: false, notEmpty: true });
 | 
					 | 
				
			||||||
    } else if (chosenArray.length > 0) {
 | 
					 | 
				
			||||||
      setChosenTypes(chosenArray);
 | 
					 | 
				
			||||||
      updateFieldWithKey('deviceTypes', {
 | 
					 | 
				
			||||||
        value: chosenArray.map((el) => el.value),
 | 
					 | 
				
			||||||
        error: false,
 | 
					 | 
				
			||||||
        notEmpty: true,
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      setChosenTypes([]);
 | 
					 | 
				
			||||||
      updateFieldWithKey('deviceTypes', { value: [], error: false, notEmpty: true });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  useEffect(() => {
 | 
					 | 
				
			||||||
    parseOptions();
 | 
					 | 
				
			||||||
  }, [deviceTypes]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return (
 | 
					 | 
				
			||||||
    <CForm>
 | 
					 | 
				
			||||||
      <CFormGroup row className="pb-3">
 | 
					 | 
				
			||||||
        <CLabel col htmlFor="name">
 | 
					 | 
				
			||||||
          {t('user.name')}
 | 
					 | 
				
			||||||
        </CLabel>
 | 
					 | 
				
			||||||
        <CCol sm="7">
 | 
					 | 
				
			||||||
          <CInput
 | 
					 | 
				
			||||||
            id="name"
 | 
					 | 
				
			||||||
            type="text"
 | 
					 | 
				
			||||||
            required
 | 
					 | 
				
			||||||
            value={fields.name.value}
 | 
					 | 
				
			||||||
            onChange={updateField}
 | 
					 | 
				
			||||||
            invalid={fields.name.error}
 | 
					 | 
				
			||||||
            disabled={disable}
 | 
					 | 
				
			||||||
            maxLength="50"
 | 
					 | 
				
			||||||
          />
 | 
					 | 
				
			||||||
          <CInvalidFeedback>{t('common.required')}</CInvalidFeedback>
 | 
					 | 
				
			||||||
        </CCol>
 | 
					 | 
				
			||||||
      </CFormGroup>
 | 
					 | 
				
			||||||
      <CFormGroup row className="pb-3">
 | 
					 | 
				
			||||||
        <CLabel col htmlFor="description">
 | 
					 | 
				
			||||||
          {t('user.description')}
 | 
					 | 
				
			||||||
        </CLabel>
 | 
					 | 
				
			||||||
        <CCol sm="7">
 | 
					 | 
				
			||||||
          <CInput
 | 
					 | 
				
			||||||
            id="description"
 | 
					 | 
				
			||||||
            type="text"
 | 
					 | 
				
			||||||
            required
 | 
					 | 
				
			||||||
            value={fields.description.value}
 | 
					 | 
				
			||||||
            onChange={updateField}
 | 
					 | 
				
			||||||
            invalid={fields.description.error}
 | 
					 | 
				
			||||||
            disabled={disable}
 | 
					 | 
				
			||||||
            maxLength="50"
 | 
					 | 
				
			||||||
          />
 | 
					 | 
				
			||||||
          <CInvalidFeedback>{t('common.required')}</CInvalidFeedback>
 | 
					 | 
				
			||||||
        </CCol>
 | 
					 | 
				
			||||||
      </CFormGroup>
 | 
					 | 
				
			||||||
      <CRow className="pb-3">
 | 
					 | 
				
			||||||
        <CLabel col htmlFor="deviceTypes">
 | 
					 | 
				
			||||||
          <div>{t('configuration.supported_device_types')}:</div>
 | 
					 | 
				
			||||||
        </CLabel>
 | 
					 | 
				
			||||||
        <CCol sm="7">
 | 
					 | 
				
			||||||
          <Select
 | 
					 | 
				
			||||||
            isMulti
 | 
					 | 
				
			||||||
            closeMenuOnSelect={false}
 | 
					 | 
				
			||||||
            id="deviceTypes"
 | 
					 | 
				
			||||||
            options={typeOptions}
 | 
					 | 
				
			||||||
            onChange={typeOnChange}
 | 
					 | 
				
			||||||
            value={chosenTypes}
 | 
					 | 
				
			||||||
            className={`basic-multi-select ${fields.deviceTypes.error ? 'border-danger' : ''}`}
 | 
					 | 
				
			||||||
            classNamePrefix="select"
 | 
					 | 
				
			||||||
          />
 | 
					 | 
				
			||||||
          <CFormText hidden={!fields.deviceTypes.error} color="danger">
 | 
					 | 
				
			||||||
            {t('configuration.need_device_type')}
 | 
					 | 
				
			||||||
          </CFormText>
 | 
					 | 
				
			||||||
        </CCol>
 | 
					 | 
				
			||||||
      </CRow>
 | 
					 | 
				
			||||||
      <div className="pb-3">
 | 
					 | 
				
			||||||
        {t('configure.enter_new')}
 | 
					 | 
				
			||||||
        <CopyToClipboardButton t={t} size="sm" content={fields.configuration.value} />
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
      <CRow className="pb-3">
 | 
					 | 
				
			||||||
        <CCol>
 | 
					 | 
				
			||||||
          <CTextarea
 | 
					 | 
				
			||||||
            style={{ overflowY: 'scroll', height: '500px' }}
 | 
					 | 
				
			||||||
            id="configuration"
 | 
					 | 
				
			||||||
            type="text"
 | 
					 | 
				
			||||||
            required
 | 
					 | 
				
			||||||
            value={fields.configuration.value}
 | 
					 | 
				
			||||||
            onChange={updateField}
 | 
					 | 
				
			||||||
            invalid={fields.configuration.error}
 | 
					 | 
				
			||||||
            disabled={disable}
 | 
					 | 
				
			||||||
          />
 | 
					 | 
				
			||||||
          <CFormText hidden={!fields.configuration.error} color="danger">
 | 
					 | 
				
			||||||
            {t('configure.valid_json')}
 | 
					 | 
				
			||||||
          </CFormText>
 | 
					 | 
				
			||||||
        </CCol>
 | 
					 | 
				
			||||||
      </CRow>
 | 
					 | 
				
			||||||
    </CForm>
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
AddDefaultConfigurationForm.propTypes = {
 | 
					 | 
				
			||||||
  t: PropTypes.func.isRequired,
 | 
					 | 
				
			||||||
  disable: PropTypes.bool.isRequired,
 | 
					 | 
				
			||||||
  fields: PropTypes.instanceOf(Object).isRequired,
 | 
					 | 
				
			||||||
  updateField: PropTypes.func.isRequired,
 | 
					 | 
				
			||||||
  updateFieldWithKey: PropTypes.func.isRequired,
 | 
					 | 
				
			||||||
  deviceTypes: PropTypes.instanceOf(Array).isRequired,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default AddDefaultConfigurationForm;
 | 
					 | 
				
			||||||
@@ -1,183 +0,0 @@
 | 
				
			|||||||
import React, { useState, useEffect } from 'react';
 | 
					 | 
				
			||||||
import PropTypes from 'prop-types';
 | 
					 | 
				
			||||||
import { CModal, CModalHeader, CModalTitle, CModalBody, CButton, CPopover } from '@coreui/react';
 | 
					 | 
				
			||||||
import CIcon from '@coreui/icons-react';
 | 
					 | 
				
			||||||
import { cilX, cilSave } from '@coreui/icons';
 | 
					 | 
				
			||||||
import { useToast, useFormFields, useAuth } from 'ucentral-libs';
 | 
					 | 
				
			||||||
import axiosInstance from 'utils/axiosInstance';
 | 
					 | 
				
			||||||
import { useTranslation } from 'react-i18next';
 | 
					 | 
				
			||||||
import { checkIfJson } from 'utils/helper';
 | 
					 | 
				
			||||||
import Form from './Form';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const initialForm = {
 | 
					 | 
				
			||||||
  name: {
 | 
					 | 
				
			||||||
    value: '',
 | 
					 | 
				
			||||||
    error: false,
 | 
					 | 
				
			||||||
    required: true,
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  description: {
 | 
					 | 
				
			||||||
    value: '',
 | 
					 | 
				
			||||||
    error: false,
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  deviceTypes: {
 | 
					 | 
				
			||||||
    value: [],
 | 
					 | 
				
			||||||
    error: false,
 | 
					 | 
				
			||||||
    notEmpty: true,
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  configuration: {
 | 
					 | 
				
			||||||
    value: '',
 | 
					 | 
				
			||||||
    error: false,
 | 
					 | 
				
			||||||
    required: true,
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const AddConfigurationModal = ({ show, toggle, refresh }) => {
 | 
					 | 
				
			||||||
  const { t } = useTranslation();
 | 
					 | 
				
			||||||
  const { addToast } = useToast();
 | 
					 | 
				
			||||||
  const { currentToken, endpoints } = useAuth();
 | 
					 | 
				
			||||||
  const [fields, updateFieldWithId, updateField, setFormFields] = useFormFields(initialForm);
 | 
					 | 
				
			||||||
  const [loading, setLoading] = useState(false);
 | 
					 | 
				
			||||||
  const [deviceTypes, setDeviceTypes] = useState([]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const getDeviceTypes = () => {
 | 
					 | 
				
			||||||
    setLoading(true);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const headers = {
 | 
					 | 
				
			||||||
      Accept: 'application/json',
 | 
					 | 
				
			||||||
      Authorization: `Bearer ${currentToken}`,
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    axiosInstance
 | 
					 | 
				
			||||||
      .get(`${endpoints.owfms}/api/v1/firmwares?deviceSet=true`, {
 | 
					 | 
				
			||||||
        headers,
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
      .then((response) => {
 | 
					 | 
				
			||||||
        setDeviceTypes([...response.data.deviceTypes]);
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
      .catch(() => {})
 | 
					 | 
				
			||||||
      .finally(() => {
 | 
					 | 
				
			||||||
        setLoading(false);
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const validation = () => {
 | 
					 | 
				
			||||||
    let success = true;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    for (const [key, field] of Object.entries(fields)) {
 | 
					 | 
				
			||||||
      if (field.required && field.value === '') {
 | 
					 | 
				
			||||||
        updateField(key, { error: true });
 | 
					 | 
				
			||||||
        success = false;
 | 
					 | 
				
			||||||
        break;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      if (field.notEmpty && field.value.length === 0) {
 | 
					 | 
				
			||||||
        updateField(key, { error: true, notEmpty: true });
 | 
					 | 
				
			||||||
        success = false;
 | 
					 | 
				
			||||||
        break;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (!checkIfJson(fields.configuration.value)) {
 | 
					 | 
				
			||||||
      updateField('configuration', { error: true });
 | 
					 | 
				
			||||||
      success = false;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return success;
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const addConfiguration = () => {
 | 
					 | 
				
			||||||
    if (validation()) {
 | 
					 | 
				
			||||||
      setLoading(true);
 | 
					 | 
				
			||||||
      const options = {
 | 
					 | 
				
			||||||
        headers: {
 | 
					 | 
				
			||||||
          Accept: 'application/json',
 | 
					 | 
				
			||||||
          Authorization: `Bearer ${currentToken}`,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const parameters = {
 | 
					 | 
				
			||||||
        name: fields.name.value,
 | 
					 | 
				
			||||||
        description: fields.description.value,
 | 
					 | 
				
			||||||
        modelIds: fields.deviceTypes.value,
 | 
					 | 
				
			||||||
        configuration: fields.configuration.value,
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      axiosInstance
 | 
					 | 
				
			||||||
        .post(
 | 
					 | 
				
			||||||
          `${endpoints.owgw}/api/v1/default_configuration/${fields.name.value}`,
 | 
					 | 
				
			||||||
          parameters,
 | 
					 | 
				
			||||||
          options,
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        .then(() => {
 | 
					 | 
				
			||||||
          if (refresh !== null) refresh();
 | 
					 | 
				
			||||||
          toggle();
 | 
					 | 
				
			||||||
          addToast({
 | 
					 | 
				
			||||||
            title: t('common.success'),
 | 
					 | 
				
			||||||
            body: t('configuration.creation_success'),
 | 
					 | 
				
			||||||
            color: 'success',
 | 
					 | 
				
			||||||
            autohide: true,
 | 
					 | 
				
			||||||
          });
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
        .catch((e) => {
 | 
					 | 
				
			||||||
          addToast({
 | 
					 | 
				
			||||||
            title: t('common.error'),
 | 
					 | 
				
			||||||
            body: t('entity.add_failure', { error: e.response?.data?.ErrorDescription }),
 | 
					 | 
				
			||||||
            color: 'danger',
 | 
					 | 
				
			||||||
            autohide: true,
 | 
					 | 
				
			||||||
          });
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
        .finally(() => {
 | 
					 | 
				
			||||||
          setLoading(false);
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  useEffect(() => {
 | 
					 | 
				
			||||||
    if (show) {
 | 
					 | 
				
			||||||
      getDeviceTypes();
 | 
					 | 
				
			||||||
      setFormFields(initialForm);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }, [show]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return (
 | 
					 | 
				
			||||||
    <CModal className="text-dark" size="lg" show={show} onClose={toggle}>
 | 
					 | 
				
			||||||
      <CModalHeader className="p-1">
 | 
					 | 
				
			||||||
        <CModalTitle className="pl-1 pt-1">{t('configuration.create')}</CModalTitle>
 | 
					 | 
				
			||||||
        <div className="text-right">
 | 
					 | 
				
			||||||
          <CPopover content={t('common.add')}>
 | 
					 | 
				
			||||||
            <CButton color="primary" variant="outline" className="ml-2" onClick={addConfiguration}>
 | 
					 | 
				
			||||||
              <CIcon content={cilSave} />
 | 
					 | 
				
			||||||
            </CButton>
 | 
					 | 
				
			||||||
          </CPopover>
 | 
					 | 
				
			||||||
          <CPopover content={t('common.close')}>
 | 
					 | 
				
			||||||
            <CButton color="primary" variant="outline" className="ml-2" onClick={toggle}>
 | 
					 | 
				
			||||||
              <CIcon content={cilX} />
 | 
					 | 
				
			||||||
            </CButton>
 | 
					 | 
				
			||||||
          </CPopover>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
      </CModalHeader>
 | 
					 | 
				
			||||||
      <CModalBody className="px-5">
 | 
					 | 
				
			||||||
        <Form
 | 
					 | 
				
			||||||
          t={t}
 | 
					 | 
				
			||||||
          disable={loading}
 | 
					 | 
				
			||||||
          fields={fields}
 | 
					 | 
				
			||||||
          updateField={updateFieldWithId}
 | 
					 | 
				
			||||||
          updateFieldWithKey={updateField}
 | 
					 | 
				
			||||||
          deviceTypes={deviceTypes}
 | 
					 | 
				
			||||||
          show={show}
 | 
					 | 
				
			||||||
        />
 | 
					 | 
				
			||||||
      </CModalBody>
 | 
					 | 
				
			||||||
    </CModal>
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
AddConfigurationModal.propTypes = {
 | 
					 | 
				
			||||||
  show: PropTypes.bool.isRequired,
 | 
					 | 
				
			||||||
  toggle: PropTypes.func.isRequired,
 | 
					 | 
				
			||||||
  refresh: PropTypes.func,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
AddConfigurationModal.defaultProps = {
 | 
					 | 
				
			||||||
  refresh: null,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default AddConfigurationModal;
 | 
					 | 
				
			||||||
@@ -1,158 +0,0 @@
 | 
				
			|||||||
import React, { useEffect, useState } from 'react';
 | 
					 | 
				
			||||||
import PropTypes from 'prop-types';
 | 
					 | 
				
			||||||
import { useTranslation } from 'react-i18next';
 | 
					 | 
				
			||||||
import {
 | 
					 | 
				
			||||||
  CButton,
 | 
					 | 
				
			||||||
  CModal,
 | 
					 | 
				
			||||||
  CModalHeader,
 | 
					 | 
				
			||||||
  CModalTitle,
 | 
					 | 
				
			||||||
  CModalBody,
 | 
					 | 
				
			||||||
  CPopover,
 | 
					 | 
				
			||||||
  CRow,
 | 
					 | 
				
			||||||
  CCol,
 | 
					 | 
				
			||||||
  CLabel,
 | 
					 | 
				
			||||||
  CTextarea,
 | 
					 | 
				
			||||||
  CInput,
 | 
					 | 
				
			||||||
  CInvalidFeedback,
 | 
					 | 
				
			||||||
} from '@coreui/react';
 | 
					 | 
				
			||||||
import CIcon from '@coreui/icons-react';
 | 
					 | 
				
			||||||
import { useAuth, useToast } from 'ucentral-libs';
 | 
					 | 
				
			||||||
import { cilPlus, cilX } from '@coreui/icons';
 | 
					 | 
				
			||||||
import axiosInstance from 'utils/axiosInstance';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const AddToBlacklistModal = ({ show, toggle, serialNumber, refresh }) => {
 | 
					 | 
				
			||||||
  const { t } = useTranslation();
 | 
					 | 
				
			||||||
  const [loading, setLoading] = useState(false);
 | 
					 | 
				
			||||||
  const { addToast } = useToast();
 | 
					 | 
				
			||||||
  const { endpoints, currentToken } = useAuth();
 | 
					 | 
				
			||||||
  const [chosenSerialNumber, setChosenSerialNumber] = useState('');
 | 
					 | 
				
			||||||
  const [reason, setReason] = useState('');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const addToBlacklist = () => {
 | 
					 | 
				
			||||||
    setLoading(true);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const parameters = {
 | 
					 | 
				
			||||||
      serialNumber: chosenSerialNumber,
 | 
					 | 
				
			||||||
      reason,
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const headers = {
 | 
					 | 
				
			||||||
      Accept: 'application/json',
 | 
					 | 
				
			||||||
      Authorization: `Bearer ${currentToken}`,
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    axiosInstance
 | 
					 | 
				
			||||||
      .post(`${endpoints.owgw}/api/v1/blacklist/${chosenSerialNumber}`, parameters, { headers })
 | 
					 | 
				
			||||||
      .then(() => {
 | 
					 | 
				
			||||||
        addToast({
 | 
					 | 
				
			||||||
          title: t('common.success'),
 | 
					 | 
				
			||||||
          body: t('device.success_added_blacklist'),
 | 
					 | 
				
			||||||
          color: 'success',
 | 
					 | 
				
			||||||
          autohide: true,
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
        toggle();
 | 
					 | 
				
			||||||
        if (refresh) refresh();
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
      .catch((e) => {
 | 
					 | 
				
			||||||
        addToast({
 | 
					 | 
				
			||||||
          title: t('common.error'),
 | 
					 | 
				
			||||||
          body: t('device.error_adding_blacklist', { error: e.response?.data?.ErrorDescription }),
 | 
					 | 
				
			||||||
          color: 'danger',
 | 
					 | 
				
			||||||
          autohide: true,
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
      .finally(() => {
 | 
					 | 
				
			||||||
        setLoading(false);
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  useEffect(() => {
 | 
					 | 
				
			||||||
    if (show) {
 | 
					 | 
				
			||||||
      if (serialNumber) setChosenSerialNumber(serialNumber);
 | 
					 | 
				
			||||||
      else setChosenSerialNumber('');
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }, [show, serialNumber]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return (
 | 
					 | 
				
			||||||
    <CModal className="text-dark" size="lg" show={show} onClose={toggle}>
 | 
					 | 
				
			||||||
      <CModalHeader className="p-1">
 | 
					 | 
				
			||||||
        <CModalTitle className="pl-1 pt-1">{t('device.add_to_blacklist')}</CModalTitle>
 | 
					 | 
				
			||||||
        <div className="text-right">
 | 
					 | 
				
			||||||
          <CPopover content={t('common.add')}>
 | 
					 | 
				
			||||||
            <CButton
 | 
					 | 
				
			||||||
              color="primary"
 | 
					 | 
				
			||||||
              variant="outline"
 | 
					 | 
				
			||||||
              className="ml-2"
 | 
					 | 
				
			||||||
              onClick={addToBlacklist}
 | 
					 | 
				
			||||||
              disabled={
 | 
					 | 
				
			||||||
                chosenSerialNumber.length !== 12 ||
 | 
					 | 
				
			||||||
                !chosenSerialNumber.match('^[a-fA-F0-9]+$') ||
 | 
					 | 
				
			||||||
                reason === '' ||
 | 
					 | 
				
			||||||
                loading
 | 
					 | 
				
			||||||
              }
 | 
					 | 
				
			||||||
            >
 | 
					 | 
				
			||||||
              <CIcon content={cilPlus} />
 | 
					 | 
				
			||||||
            </CButton>
 | 
					 | 
				
			||||||
          </CPopover>
 | 
					 | 
				
			||||||
          <CPopover content={t('common.close')}>
 | 
					 | 
				
			||||||
            <CButton color="primary" variant="outline" className="ml-2" onClick={toggle}>
 | 
					 | 
				
			||||||
              <CIcon content={cilX} />
 | 
					 | 
				
			||||||
            </CButton>
 | 
					 | 
				
			||||||
          </CPopover>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
      </CModalHeader>
 | 
					 | 
				
			||||||
      <CModalBody>
 | 
					 | 
				
			||||||
        <CRow>
 | 
					 | 
				
			||||||
          <CLabel col sm="3">
 | 
					 | 
				
			||||||
            {t('common.serial_number')}
 | 
					 | 
				
			||||||
          </CLabel>
 | 
					 | 
				
			||||||
          <CCol sm="9" className="pt-1">
 | 
					 | 
				
			||||||
            <CInput
 | 
					 | 
				
			||||||
              id="description"
 | 
					 | 
				
			||||||
              type="text"
 | 
					 | 
				
			||||||
              required
 | 
					 | 
				
			||||||
              value={chosenSerialNumber}
 | 
					 | 
				
			||||||
              onChange={(e) => setChosenSerialNumber(e.target.value)}
 | 
					 | 
				
			||||||
              invalid={
 | 
					 | 
				
			||||||
                chosenSerialNumber.length !== 12 && chosenSerialNumber.match('^[a-fA-F0-9]+$')
 | 
					 | 
				
			||||||
              }
 | 
					 | 
				
			||||||
              disabled={loading}
 | 
					 | 
				
			||||||
              maxLength="50"
 | 
					 | 
				
			||||||
            />
 | 
					 | 
				
			||||||
            <CInvalidFeedback>{t('entity.valid_serial')}</CInvalidFeedback>
 | 
					 | 
				
			||||||
          </CCol>
 | 
					 | 
				
			||||||
        </CRow>
 | 
					 | 
				
			||||||
        <CRow>
 | 
					 | 
				
			||||||
          <CLabel col sm="3">
 | 
					 | 
				
			||||||
            {t('common.reason')}
 | 
					 | 
				
			||||||
          </CLabel>
 | 
					 | 
				
			||||||
          <CCol sm="9" className="pt-2">
 | 
					 | 
				
			||||||
            <CTextarea
 | 
					 | 
				
			||||||
              name="reason"
 | 
					 | 
				
			||||||
              id="reason"
 | 
					 | 
				
			||||||
              rows="3"
 | 
					 | 
				
			||||||
              type="text"
 | 
					 | 
				
			||||||
              required
 | 
					 | 
				
			||||||
              value={reason}
 | 
					 | 
				
			||||||
              onChange={(e) => setReason(e.target.value)}
 | 
					 | 
				
			||||||
            />
 | 
					 | 
				
			||||||
          </CCol>
 | 
					 | 
				
			||||||
        </CRow>
 | 
					 | 
				
			||||||
      </CModalBody>
 | 
					 | 
				
			||||||
    </CModal>
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
AddToBlacklistModal.propTypes = {
 | 
					 | 
				
			||||||
  show: PropTypes.bool.isRequired,
 | 
					 | 
				
			||||||
  toggle: PropTypes.func.isRequired,
 | 
					 | 
				
			||||||
  serialNumber: PropTypes.string,
 | 
					 | 
				
			||||||
  refresh: PropTypes.func,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
AddToBlacklistModal.defaultProps = {
 | 
					 | 
				
			||||||
  serialNumber: '',
 | 
					 | 
				
			||||||
  refresh: null,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default AddToBlacklistModal;
 | 
					 | 
				
			||||||
@@ -1,210 +0,0 @@
 | 
				
			|||||||
import React, { useEffect } from 'react';
 | 
					 | 
				
			||||||
import PropTypes from 'prop-types';
 | 
					 | 
				
			||||||
import ReactPaginate from 'react-paginate';
 | 
					 | 
				
			||||||
import {
 | 
					 | 
				
			||||||
  CCardBody,
 | 
					 | 
				
			||||||
  CDataTable,
 | 
					 | 
				
			||||||
  CButton,
 | 
					 | 
				
			||||||
  CLink,
 | 
					 | 
				
			||||||
  CCard,
 | 
					 | 
				
			||||||
  CCardHeader,
 | 
					 | 
				
			||||||
  CPopover,
 | 
					 | 
				
			||||||
  CSelect,
 | 
					 | 
				
			||||||
  CButtonToolbar,
 | 
					 | 
				
			||||||
} from '@coreui/react';
 | 
					 | 
				
			||||||
import { cilSearch, cilPencil, cilPlus, cilTrash } from '@coreui/icons';
 | 
					 | 
				
			||||||
import CIcon from '@coreui/icons-react';
 | 
					 | 
				
			||||||
import ReactTooltip from 'react-tooltip';
 | 
					 | 
				
			||||||
import { FormattedDate } from 'ucentral-libs';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const BlacklistTable = ({
 | 
					 | 
				
			||||||
  currentPage,
 | 
					 | 
				
			||||||
  devices,
 | 
					 | 
				
			||||||
  toggleAddBlacklist,
 | 
					 | 
				
			||||||
  toggleEditModal,
 | 
					 | 
				
			||||||
  devicesPerPage,
 | 
					 | 
				
			||||||
  loading,
 | 
					 | 
				
			||||||
  removeFromBlacklist,
 | 
					 | 
				
			||||||
  updateDevicesPerPage,
 | 
					 | 
				
			||||||
  pageCount,
 | 
					 | 
				
			||||||
  updatePage,
 | 
					 | 
				
			||||||
  t,
 | 
					 | 
				
			||||||
}) => {
 | 
					 | 
				
			||||||
  const columns = [
 | 
					 | 
				
			||||||
    { key: 'serialNumber', label: t('common.serial_number'), _style: { width: '6%' } },
 | 
					 | 
				
			||||||
    { key: 'created', label: t('device.blacklisted_on'), _style: { width: '1%' } },
 | 
					 | 
				
			||||||
    { key: 'author', label: t('common.by'), filter: false, _style: { width: '15%' } },
 | 
					 | 
				
			||||||
    { key: 'reason', label: t('common.reason'), filter: false },
 | 
					 | 
				
			||||||
    { key: 'actions', label: t('actions.actions'), _style: { width: '1%' } },
 | 
					 | 
				
			||||||
  ];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const hideTooltips = () => ReactTooltip.hide();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const escFunction = (event) => {
 | 
					 | 
				
			||||||
    if (event.keyCode === 27) {
 | 
					 | 
				
			||||||
      hideTooltips();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  useEffect(() => {
 | 
					 | 
				
			||||||
    document.addEventListener('keydown', escFunction, false);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return () => {
 | 
					 | 
				
			||||||
      document.removeEventListener('keydown', escFunction, false);
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
  }, []);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return (
 | 
					 | 
				
			||||||
    <>
 | 
					 | 
				
			||||||
      <CCard className="m-0 p-0">
 | 
					 | 
				
			||||||
        <CCardHeader className="p-0 text-right">
 | 
					 | 
				
			||||||
          <CPopover content={t('device.add_to_blacklist')}>
 | 
					 | 
				
			||||||
            <CButton size="sm" color="primary" onClick={toggleAddBlacklist}>
 | 
					 | 
				
			||||||
              <CIcon content={cilPlus} />
 | 
					 | 
				
			||||||
            </CButton>
 | 
					 | 
				
			||||||
          </CPopover>
 | 
					 | 
				
			||||||
        </CCardHeader>
 | 
					 | 
				
			||||||
        <CCardBody className="p-0">
 | 
					 | 
				
			||||||
          <CDataTable
 | 
					 | 
				
			||||||
            addTableClasses="ignore-overflow table-sm"
 | 
					 | 
				
			||||||
            items={devices ?? []}
 | 
					 | 
				
			||||||
            fields={columns}
 | 
					 | 
				
			||||||
            hover
 | 
					 | 
				
			||||||
            border
 | 
					 | 
				
			||||||
            loading={loading}
 | 
					 | 
				
			||||||
            scopedSlots={{
 | 
					 | 
				
			||||||
              serialNumber: (item) => (
 | 
					 | 
				
			||||||
                <td className="text-center align-middle">
 | 
					 | 
				
			||||||
                  <CLink
 | 
					 | 
				
			||||||
                    className="c-subheader-nav-link"
 | 
					 | 
				
			||||||
                    aria-current="page"
 | 
					 | 
				
			||||||
                    to={() => `/devices/${item.serialNumber}`}
 | 
					 | 
				
			||||||
                  >
 | 
					 | 
				
			||||||
                    {item.serialNumber}
 | 
					 | 
				
			||||||
                  </CLink>
 | 
					 | 
				
			||||||
                </td>
 | 
					 | 
				
			||||||
              ),
 | 
					 | 
				
			||||||
              created: (item) => (
 | 
					 | 
				
			||||||
                <td className="text-left align-middle">
 | 
					 | 
				
			||||||
                  <div style={{ width: '130px' }}>
 | 
					 | 
				
			||||||
                    <FormattedDate date={item.created} />
 | 
					 | 
				
			||||||
                  </div>
 | 
					 | 
				
			||||||
                </td>
 | 
					 | 
				
			||||||
              ),
 | 
					 | 
				
			||||||
              author: (item) => <td className="align-middle">{item.author}</td>,
 | 
					 | 
				
			||||||
              reason: (item) => <td className="align-middle">{item.reason}</td>,
 | 
					 | 
				
			||||||
              actions: (item) => (
 | 
					 | 
				
			||||||
                <td className="text-center align-middle">
 | 
					 | 
				
			||||||
                  <CButtonToolbar
 | 
					 | 
				
			||||||
                    role="group"
 | 
					 | 
				
			||||||
                    className="justify-content-center"
 | 
					 | 
				
			||||||
                    style={{ width: '130px' }}
 | 
					 | 
				
			||||||
                  >
 | 
					 | 
				
			||||||
                    <CPopover content={t('configuration.details')}>
 | 
					 | 
				
			||||||
                      <CLink
 | 
					 | 
				
			||||||
                        className="c-subheader-nav-link"
 | 
					 | 
				
			||||||
                        aria-current="page"
 | 
					 | 
				
			||||||
                        to={() => `/devices/${item.serialNumber}`}
 | 
					 | 
				
			||||||
                      >
 | 
					 | 
				
			||||||
                        <CButton
 | 
					 | 
				
			||||||
                          color="primary"
 | 
					 | 
				
			||||||
                          variant="outline"
 | 
					 | 
				
			||||||
                          shape="square"
 | 
					 | 
				
			||||||
                          size="sm"
 | 
					 | 
				
			||||||
                          className="mx-1"
 | 
					 | 
				
			||||||
                          style={{ width: '33px', height: '30px' }}
 | 
					 | 
				
			||||||
                        >
 | 
					 | 
				
			||||||
                          <CIcon name="cil-search" content={cilSearch} size="sm" />
 | 
					 | 
				
			||||||
                        </CButton>
 | 
					 | 
				
			||||||
                      </CLink>
 | 
					 | 
				
			||||||
                    </CPopover>
 | 
					 | 
				
			||||||
                    <CPopover content={t('device.remove_from_blacklist')}>
 | 
					 | 
				
			||||||
                      <CButton
 | 
					 | 
				
			||||||
                        onClick={() => removeFromBlacklist(item.serialNumber)}
 | 
					 | 
				
			||||||
                        color="primary"
 | 
					 | 
				
			||||||
                        variant="outline"
 | 
					 | 
				
			||||||
                        shape="square"
 | 
					 | 
				
			||||||
                        size="sm"
 | 
					 | 
				
			||||||
                        className="mx-1"
 | 
					 | 
				
			||||||
                        style={{ width: '33px', height: '30px' }}
 | 
					 | 
				
			||||||
                      >
 | 
					 | 
				
			||||||
                        <CIcon content={cilTrash} size="sm" />
 | 
					 | 
				
			||||||
                      </CButton>
 | 
					 | 
				
			||||||
                    </CPopover>
 | 
					 | 
				
			||||||
                    <CPopover content={t('common.edit')}>
 | 
					 | 
				
			||||||
                      <CButton
 | 
					 | 
				
			||||||
                        onClick={() => toggleEditModal(item.serialNumber)}
 | 
					 | 
				
			||||||
                        color="primary"
 | 
					 | 
				
			||||||
                        variant="outline"
 | 
					 | 
				
			||||||
                        shape="square"
 | 
					 | 
				
			||||||
                        size="sm"
 | 
					 | 
				
			||||||
                        className="mx-1"
 | 
					 | 
				
			||||||
                        style={{ width: '33px', height: '30px' }}
 | 
					 | 
				
			||||||
                      >
 | 
					 | 
				
			||||||
                        <CIcon content={cilPencil} size="sm" />
 | 
					 | 
				
			||||||
                      </CButton>
 | 
					 | 
				
			||||||
                    </CPopover>
 | 
					 | 
				
			||||||
                  </CButtonToolbar>
 | 
					 | 
				
			||||||
                </td>
 | 
					 | 
				
			||||||
              ),
 | 
					 | 
				
			||||||
            }}
 | 
					 | 
				
			||||||
          />
 | 
					 | 
				
			||||||
          <div className="d-flex flex-row pl-3">
 | 
					 | 
				
			||||||
            <div className="pr-3">
 | 
					 | 
				
			||||||
              <ReactPaginate
 | 
					 | 
				
			||||||
                previousLabel="← Previous"
 | 
					 | 
				
			||||||
                nextLabel="Next →"
 | 
					 | 
				
			||||||
                pageCount={pageCount}
 | 
					 | 
				
			||||||
                onPageChange={updatePage}
 | 
					 | 
				
			||||||
                forcePage={Number(currentPage)}
 | 
					 | 
				
			||||||
                breakClassName="page-item"
 | 
					 | 
				
			||||||
                breakLinkClassName="page-link"
 | 
					 | 
				
			||||||
                containerClassName="pagination"
 | 
					 | 
				
			||||||
                pageClassName="page-item"
 | 
					 | 
				
			||||||
                pageLinkClassName="page-link"
 | 
					 | 
				
			||||||
                previousClassName="page-item"
 | 
					 | 
				
			||||||
                previousLinkClassName="page-link"
 | 
					 | 
				
			||||||
                nextClassName="page-item"
 | 
					 | 
				
			||||||
                nextLinkClassName="page-link"
 | 
					 | 
				
			||||||
                activeClassName="active"
 | 
					 | 
				
			||||||
              />
 | 
					 | 
				
			||||||
            </div>
 | 
					 | 
				
			||||||
            <p className="pr-2 mt-1">{t('common.items_per_page')}</p>
 | 
					 | 
				
			||||||
            <div style={{ width: '100px' }} className="px-2">
 | 
					 | 
				
			||||||
              <CSelect
 | 
					 | 
				
			||||||
                custom
 | 
					 | 
				
			||||||
                defaultValue={devicesPerPage}
 | 
					 | 
				
			||||||
                onChange={(e) => updateDevicesPerPage(e.target.value)}
 | 
					 | 
				
			||||||
                disabled={loading}
 | 
					 | 
				
			||||||
              >
 | 
					 | 
				
			||||||
                <option value="10">10</option>
 | 
					 | 
				
			||||||
                <option value="25">25</option>
 | 
					 | 
				
			||||||
                <option value="50">50</option>
 | 
					 | 
				
			||||||
              </CSelect>
 | 
					 | 
				
			||||||
            </div>
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
        </CCardBody>
 | 
					 | 
				
			||||||
      </CCard>
 | 
					 | 
				
			||||||
    </>
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
BlacklistTable.propTypes = {
 | 
					 | 
				
			||||||
  currentPage: PropTypes.string,
 | 
					 | 
				
			||||||
  devices: PropTypes.instanceOf(Array).isRequired,
 | 
					 | 
				
			||||||
  toggleAddBlacklist: PropTypes.func.isRequired,
 | 
					 | 
				
			||||||
  toggleEditModal: PropTypes.func.isRequired,
 | 
					 | 
				
			||||||
  updateDevicesPerPage: PropTypes.func.isRequired,
 | 
					 | 
				
			||||||
  pageCount: PropTypes.number.isRequired,
 | 
					 | 
				
			||||||
  updatePage: PropTypes.func.isRequired,
 | 
					 | 
				
			||||||
  devicesPerPage: PropTypes.string.isRequired,
 | 
					 | 
				
			||||||
  removeFromBlacklist: PropTypes.func.isRequired,
 | 
					 | 
				
			||||||
  t: PropTypes.func.isRequired,
 | 
					 | 
				
			||||||
  loading: PropTypes.bool.isRequired,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
BlacklistTable.defaultProps = {
 | 
					 | 
				
			||||||
  currentPage: '0',
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default React.memo(BlacklistTable);
 | 
					 | 
				
			||||||
@@ -1,30 +0,0 @@
 | 
				
			|||||||
.firmwareTooltip {
 | 
					 | 
				
			||||||
  opacity: 1 !important;
 | 
					 | 
				
			||||||
  padding: 0px 0px 0px 0px !important;
 | 
					 | 
				
			||||||
  border-radius: 1rem !important;
 | 
					 | 
				
			||||||
  background-color: #fff !important;
 | 
					 | 
				
			||||||
  border-color: #321fdb !important;
 | 
					 | 
				
			||||||
  font-size: 0.875rem !important;
 | 
					 | 
				
			||||||
  font-weight: 400 !important;
 | 
					 | 
				
			||||||
  box-shadow: 0 3px 10px rgba(0, 0, 0, 0.2) !important;
 | 
					 | 
				
			||||||
  width: 400px;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.deleteTooltip {
 | 
					 | 
				
			||||||
  opacity: 1 !important;
 | 
					 | 
				
			||||||
  padding: 0px 0px 0px 0px !important;
 | 
					 | 
				
			||||||
  border-radius: 1rem !important;
 | 
					 | 
				
			||||||
  background-color: #fff !important;
 | 
					 | 
				
			||||||
  border-color: #321fdb !important;
 | 
					 | 
				
			||||||
  font-size: 0.875rem !important;
 | 
					 | 
				
			||||||
  font-weight: 400 !important;
 | 
					 | 
				
			||||||
  box-shadow: 0 3px 10px rgba(0, 0, 0, 0.2) !important;
 | 
					 | 
				
			||||||
  width: 200px;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.tooltipHeader {
 | 
					 | 
				
			||||||
  padding-left: 5px;
 | 
					 | 
				
			||||||
  padding-right: 10px;
 | 
					 | 
				
			||||||
  border-top-left-radius: 1rem !important;
 | 
					 | 
				
			||||||
  border-top-right-radius: 1rem !important;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,244 +0,0 @@
 | 
				
			|||||||
import React, { useEffect, useState } from 'react';
 | 
					 | 
				
			||||||
import { useTranslation } from 'react-i18next';
 | 
					 | 
				
			||||||
import { useHistory } from 'react-router-dom';
 | 
					 | 
				
			||||||
import axiosInstance from 'utils/axiosInstance';
 | 
					 | 
				
			||||||
import { getItem, setItem } from 'utils/localStorageHelper';
 | 
					 | 
				
			||||||
import { useAuth, useToast, useToggle } from 'ucentral-libs';
 | 
					 | 
				
			||||||
import AddToBlacklistModal from 'components/AddToBlacklistModal';
 | 
					 | 
				
			||||||
import EditBlacklistModal from 'components/EditBlacklistModal';
 | 
					 | 
				
			||||||
import Table from './Table';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const BlacklistTable = () => {
 | 
					 | 
				
			||||||
  const { t } = useTranslation();
 | 
					 | 
				
			||||||
  const { addToast } = useToast();
 | 
					 | 
				
			||||||
  const history = useHistory();
 | 
					 | 
				
			||||||
  const [page, setPage] = useState(parseInt(sessionStorage.getItem('deviceTable') ?? 0, 10));
 | 
					 | 
				
			||||||
  const { currentToken, endpoints } = useAuth();
 | 
					 | 
				
			||||||
  const [deviceCount, setDeviceCount] = useState(0);
 | 
					 | 
				
			||||||
  const [pageCount, setPageCount] = useState(0);
 | 
					 | 
				
			||||||
  const [devicesPerPage, setDevicesPerPage] = useState(getItem('devicesPerPage') || '10');
 | 
					 | 
				
			||||||
  const [devices, setDevices] = useState([]);
 | 
					 | 
				
			||||||
  const [loading, setLoading] = useState(true);
 | 
					 | 
				
			||||||
  const [editSerial, setEditSerial] = useState('');
 | 
					 | 
				
			||||||
  const [showEditModal, setShowEditModal] = useState(false);
 | 
					 | 
				
			||||||
  const [showAddModal, toggleAddModal] = useToggle(false);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const toggleEditModal = (serialNumber) => {
 | 
					 | 
				
			||||||
    if (serialNumber) setEditSerial(serialNumber);
 | 
					 | 
				
			||||||
    setShowEditModal(!showEditModal);
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const getDeviceInformation = (selectedPage = page, devicePerPage = devicesPerPage) => {
 | 
					 | 
				
			||||||
    setLoading(true);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const options = {
 | 
					 | 
				
			||||||
      headers: {
 | 
					 | 
				
			||||||
        Accept: 'application/json',
 | 
					 | 
				
			||||||
        Authorization: `Bearer ${currentToken}`,
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    axiosInstance
 | 
					 | 
				
			||||||
      .get(
 | 
					 | 
				
			||||||
        `${endpoints.owgw}/api/v1/blacklist?limit=${devicePerPage}&offset=${
 | 
					 | 
				
			||||||
          devicePerPage * selectedPage
 | 
					 | 
				
			||||||
        }`,
 | 
					 | 
				
			||||||
        options,
 | 
					 | 
				
			||||||
      )
 | 
					 | 
				
			||||||
      .then((response) => {
 | 
					 | 
				
			||||||
        setDevices(response.data.devices);
 | 
					 | 
				
			||||||
        setLoading(false);
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
      .catch((e) => {
 | 
					 | 
				
			||||||
        addToast({
 | 
					 | 
				
			||||||
          title: t('common.error'),
 | 
					 | 
				
			||||||
          body: t('device.error_fetching_devices', { error: e.response?.data?.ErrorDescription }),
 | 
					 | 
				
			||||||
          color: 'danger',
 | 
					 | 
				
			||||||
          autohide: true,
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
        setLoading(false);
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const getCount = () => {
 | 
					 | 
				
			||||||
    setLoading(true);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const headers = {
 | 
					 | 
				
			||||||
      Accept: 'application/json',
 | 
					 | 
				
			||||||
      Authorization: `Bearer ${currentToken}`,
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    axiosInstance
 | 
					 | 
				
			||||||
      .get(`${endpoints.owgw}/api/v1/blacklist?countOnly=true`, {
 | 
					 | 
				
			||||||
        headers,
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
      .then((response) => {
 | 
					 | 
				
			||||||
        const devicesCount = response.data.count;
 | 
					 | 
				
			||||||
        const pagesCount = Math.ceil(devicesCount / devicesPerPage);
 | 
					 | 
				
			||||||
        setPageCount(pagesCount);
 | 
					 | 
				
			||||||
        setDeviceCount(devicesCount);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let selectedPage = page;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (page >= pagesCount) {
 | 
					 | 
				
			||||||
          history.push(`/devices?page=${pagesCount - 1}`);
 | 
					 | 
				
			||||||
          selectedPage = pagesCount - 1;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        getDeviceInformation(selectedPage);
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
      .catch((e) => {
 | 
					 | 
				
			||||||
        addToast({
 | 
					 | 
				
			||||||
          title: t('common.error'),
 | 
					 | 
				
			||||||
          body: t('device.error_fetching_devices', { error: e.response?.data?.ErrorDescription }),
 | 
					 | 
				
			||||||
          color: 'danger',
 | 
					 | 
				
			||||||
          autohide: true,
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
        setLoading(false);
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const refreshDevice = (serialNumber) => {
 | 
					 | 
				
			||||||
    setLoading(true);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const options = {
 | 
					 | 
				
			||||||
      headers: {
 | 
					 | 
				
			||||||
        Accept: 'application/json',
 | 
					 | 
				
			||||||
        Authorization: `Bearer ${currentToken}`,
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let newDevice;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    axiosInstance
 | 
					 | 
				
			||||||
      .get(
 | 
					 | 
				
			||||||
        `${endpoints.owgw}/api/v1/blacklist?deviceWithStatus=true&select=${encodeURIComponent(
 | 
					 | 
				
			||||||
          serialNumber,
 | 
					 | 
				
			||||||
        )}`,
 | 
					 | 
				
			||||||
        options,
 | 
					 | 
				
			||||||
      )
 | 
					 | 
				
			||||||
      .then(
 | 
					 | 
				
			||||||
        ({
 | 
					 | 
				
			||||||
          data: {
 | 
					 | 
				
			||||||
            devicesWithStatus: [device],
 | 
					 | 
				
			||||||
          },
 | 
					 | 
				
			||||||
        }) => {
 | 
					 | 
				
			||||||
          newDevice = device;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          return axiosInstance.get(
 | 
					 | 
				
			||||||
            `${endpoints.owfms}/api/v1/firmwareAge?select=${serialNumber}`,
 | 
					 | 
				
			||||||
            options,
 | 
					 | 
				
			||||||
          );
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
      )
 | 
					 | 
				
			||||||
      .then((response) => {
 | 
					 | 
				
			||||||
        newDevice.firmwareInfo = {
 | 
					 | 
				
			||||||
          age: response.data.ages[0].age,
 | 
					 | 
				
			||||||
          latest: response.data.ages[0].latest,
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
        const foundIndex = devices.findIndex((obj) => obj.serialNumber === serialNumber);
 | 
					 | 
				
			||||||
        const newList = devices;
 | 
					 | 
				
			||||||
        newList[foundIndex] = newDevice;
 | 
					 | 
				
			||||||
        setDevices(newList);
 | 
					 | 
				
			||||||
        setLoading(false);
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
      .catch((e) => {
 | 
					 | 
				
			||||||
        addToast({
 | 
					 | 
				
			||||||
          title: t('common.error'),
 | 
					 | 
				
			||||||
          body: t('device.error_fetching_devices', { error: e.response?.data?.ErrorDescription }),
 | 
					 | 
				
			||||||
          color: 'danger',
 | 
					 | 
				
			||||||
          autohide: true,
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
        setLoading(false);
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const updateDevicesPerPage = (value) => {
 | 
					 | 
				
			||||||
    setItem('devicesPerPage', value);
 | 
					 | 
				
			||||||
    setDevicesPerPage(value);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const newPageCount = Math.ceil(deviceCount / value);
 | 
					 | 
				
			||||||
    setPageCount(newPageCount);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let selectedPage = page;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (page >= newPageCount) {
 | 
					 | 
				
			||||||
      history.push(`/blacklist?page=${newPageCount - 1}`);
 | 
					 | 
				
			||||||
      selectedPage = newPageCount - 1;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    getDeviceInformation(selectedPage, value);
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const updatePageCount = ({ selected: selectedPage }) => {
 | 
					 | 
				
			||||||
    sessionStorage.setItem('deviceTable', selectedPage);
 | 
					 | 
				
			||||||
    setPage(selectedPage);
 | 
					 | 
				
			||||||
    getDeviceInformation(selectedPage);
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const removeFromBlacklist = (serialNumber) => {
 | 
					 | 
				
			||||||
    setLoading(true);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const headers = {
 | 
					 | 
				
			||||||
      Accept: 'application/json',
 | 
					 | 
				
			||||||
      Authorization: `Bearer ${currentToken}`,
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    axiosInstance
 | 
					 | 
				
			||||||
      .delete(`${endpoints.owgw}/api/v1/blacklist/${serialNumber}`, { headers })
 | 
					 | 
				
			||||||
      .then(() => {
 | 
					 | 
				
			||||||
        addToast({
 | 
					 | 
				
			||||||
          title: t('common.success'),
 | 
					 | 
				
			||||||
          body: t('device.success_removed_blacklist'),
 | 
					 | 
				
			||||||
          color: 'success',
 | 
					 | 
				
			||||||
          autohide: true,
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
        getCount();
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
      .catch((e) => {
 | 
					 | 
				
			||||||
        addToast({
 | 
					 | 
				
			||||||
          title: t('common.error'),
 | 
					 | 
				
			||||||
          body: t('device.error_adding_blacklist', { error: e.response?.data?.ErrorDescription }),
 | 
					 | 
				
			||||||
          color: 'danger',
 | 
					 | 
				
			||||||
          autohide: true,
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
      .finally(() => {
 | 
					 | 
				
			||||||
        setLoading(false);
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  useEffect(() => {
 | 
					 | 
				
			||||||
    getCount();
 | 
					 | 
				
			||||||
  }, []);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return (
 | 
					 | 
				
			||||||
    <div>
 | 
					 | 
				
			||||||
      <Table
 | 
					 | 
				
			||||||
        currentPage={page}
 | 
					 | 
				
			||||||
        t={t}
 | 
					 | 
				
			||||||
        devices={devices}
 | 
					 | 
				
			||||||
        loading={loading}
 | 
					 | 
				
			||||||
        toggleAddBlacklist={toggleAddModal}
 | 
					 | 
				
			||||||
        toggleEditModal={toggleEditModal}
 | 
					 | 
				
			||||||
        updateDevicesPerPage={updateDevicesPerPage}
 | 
					 | 
				
			||||||
        devicesPerPage={devicesPerPage}
 | 
					 | 
				
			||||||
        pageCount={pageCount}
 | 
					 | 
				
			||||||
        updatePage={updatePageCount}
 | 
					 | 
				
			||||||
        pageRangeDisplayed={5}
 | 
					 | 
				
			||||||
        refreshDevice={refreshDevice}
 | 
					 | 
				
			||||||
        removeFromBlacklist={removeFromBlacklist}
 | 
					 | 
				
			||||||
      />
 | 
					 | 
				
			||||||
      {showAddModal ? (
 | 
					 | 
				
			||||||
        <AddToBlacklistModal show={showAddModal} toggle={toggleAddModal} refresh={getCount} />
 | 
					 | 
				
			||||||
      ) : null}
 | 
					 | 
				
			||||||
      <EditBlacklistModal
 | 
					 | 
				
			||||||
        show={showEditModal}
 | 
					 | 
				
			||||||
        toggle={toggleEditModal}
 | 
					 | 
				
			||||||
        refresh={getCount}
 | 
					 | 
				
			||||||
        serialNumber={editSerial}
 | 
					 | 
				
			||||||
      />
 | 
					 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default BlacklistTable;
 | 
					 | 
				
			||||||
@@ -1,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;
 | 
					 | 
				
			||||||
							
								
								
									
										57
									
								
								src/components/Buttons/AlertButton/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,57 @@
 | 
				
			|||||||
 | 
					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 = true,
 | 
				
			||||||
 | 
					  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);
 | 
				
			||||||
							
								
								
									
										56
									
								
								src/components/Buttons/CreateButton/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,56 @@
 | 
				
			|||||||
 | 
					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 = true,
 | 
				
			||||||
 | 
					  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 = true,
 | 
				
			||||||
 | 
					  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);
 | 
				
			||||||
							
								
								
									
										211
									
								
								src/components/Buttons/DeviceActionDropdown/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,211 @@
 | 
				
			|||||||
 | 
					import React from 'react';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  Button,
 | 
				
			||||||
 | 
					  IconButton,
 | 
				
			||||||
 | 
					  Menu,
 | 
				
			||||||
 | 
					  MenuButton,
 | 
				
			||||||
 | 
					  MenuItem,
 | 
				
			||||||
 | 
					  MenuList,
 | 
				
			||||||
 | 
					  Portal,
 | 
				
			||||||
 | 
					  Spinner,
 | 
				
			||||||
 | 
					  Tooltip,
 | 
				
			||||||
 | 
					  useToast,
 | 
				
			||||||
 | 
					} from '@chakra-ui/react';
 | 
				
			||||||
 | 
					import axios from 'axios';
 | 
				
			||||||
 | 
					import { Wrench } from 'phosphor-react';
 | 
				
			||||||
 | 
					import { useTranslation } from 'react-i18next';
 | 
				
			||||||
 | 
					import { useControllerStore } from 'contexts/ControllerSocketProvider/useStore';
 | 
				
			||||||
 | 
					import { useBlinkDevice, useGetDeviceRtty } from 'hooks/Network/Devices';
 | 
				
			||||||
 | 
					import { useUpdateDeviceToLatest } from 'hooks/Network/Firmware';
 | 
				
			||||||
 | 
					import { useMutationResult } from 'hooks/useMutationResult';
 | 
				
			||||||
 | 
					import { GatewayDevice } from 'models/Device';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface Props {
 | 
				
			||||||
 | 
					  device: GatewayDevice;
 | 
				
			||||||
 | 
					  refresh: () => void;
 | 
				
			||||||
 | 
					  isDisabled?: boolean;
 | 
				
			||||||
 | 
					  onOpenScan: (serialNumber: string) => void;
 | 
				
			||||||
 | 
					  onOpenFactoryReset: (serialNumber: string) => void;
 | 
				
			||||||
 | 
					  onOpenUpgradeModal: (serialNumber: string) => void;
 | 
				
			||||||
 | 
					  onOpenTrace: (serialNumber: string) => void;
 | 
				
			||||||
 | 
					  onOpenEventQueue: (serialNumber: string) => void;
 | 
				
			||||||
 | 
					  onOpenConfigureModal: (serialNumber: string) => void;
 | 
				
			||||||
 | 
					  onOpenTelemetryModal: (serialNumber: string) => void;
 | 
				
			||||||
 | 
					  onOpenScriptModal: (device: GatewayDevice) => void;
 | 
				
			||||||
 | 
					  onOpenRebootModal: (serialNumber: string) => void;
 | 
				
			||||||
 | 
					  size?: 'sm' | 'md' | 'lg';
 | 
				
			||||||
 | 
					  isCompact?: boolean;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DeviceActionDropdown = ({
 | 
				
			||||||
 | 
					  device,
 | 
				
			||||||
 | 
					  refresh,
 | 
				
			||||||
 | 
					  isDisabled,
 | 
				
			||||||
 | 
					  onOpenScan,
 | 
				
			||||||
 | 
					  onOpenFactoryReset,
 | 
				
			||||||
 | 
					  onOpenTrace,
 | 
				
			||||||
 | 
					  onOpenUpgradeModal,
 | 
				
			||||||
 | 
					  onOpenEventQueue,
 | 
				
			||||||
 | 
					  onOpenTelemetryModal,
 | 
				
			||||||
 | 
					  onOpenConfigureModal,
 | 
				
			||||||
 | 
					  onOpenScriptModal,
 | 
				
			||||||
 | 
					  onOpenRebootModal,
 | 
				
			||||||
 | 
					  size,
 | 
				
			||||||
 | 
					  isCompact = true,
 | 
				
			||||||
 | 
					}: Props) => {
 | 
				
			||||||
 | 
					  const { t } = useTranslation();
 | 
				
			||||||
 | 
					  const toast = useToast();
 | 
				
			||||||
 | 
					  const addEventListeners = useControllerStore((state) => state.addEventListeners);
 | 
				
			||||||
 | 
					  const { refetch: getRtty, isInitialLoading: isRtty } = useGetDeviceRtty({
 | 
				
			||||||
 | 
					    serialNumber: device.serialNumber,
 | 
				
			||||||
 | 
					    extraId: 'inventory-modal',
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  const { mutateAsync: blink } = useBlinkDevice({ serialNumber: device.serialNumber });
 | 
				
			||||||
 | 
					  const { onSuccess: onBlinkSuccess, onError: onBlinkError } = useMutationResult({
 | 
				
			||||||
 | 
					    objName: t('devices.one'),
 | 
				
			||||||
 | 
					    operationType: 'blink',
 | 
				
			||||||
 | 
					    refresh,
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  const updateToLatest = useUpdateDeviceToLatest({ serialNumber: device.serialNumber, compatible: device.compatible });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const handleBlinkClick = () => {
 | 
				
			||||||
 | 
					    blink(undefined, {
 | 
				
			||||||
 | 
					      onError: (e) => {
 | 
				
			||||||
 | 
					        if (axios.isAxiosError(e)) onBlinkError(e);
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    onBlinkSuccess();
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					  const handleOpenScan = () => onOpenScan(device.serialNumber);
 | 
				
			||||||
 | 
					  const handleOpenFactoryReset = () => onOpenFactoryReset(device.serialNumber);
 | 
				
			||||||
 | 
					  const handleOpenUpgrade = () => onOpenUpgradeModal(device.serialNumber);
 | 
				
			||||||
 | 
					  const handleOpenTrace = () => onOpenTrace(device.serialNumber);
 | 
				
			||||||
 | 
					  const handleOpenQueue = () => onOpenEventQueue(device.serialNumber);
 | 
				
			||||||
 | 
					  const handleOpenConfigure = () => onOpenConfigureModal(device.serialNumber);
 | 
				
			||||||
 | 
					  const handleOpenTelemetry = () => onOpenTelemetryModal(device.serialNumber);
 | 
				
			||||||
 | 
					  const handleOpenScript = () => onOpenScriptModal(device);
 | 
				
			||||||
 | 
					  const handleUpdateToLatest = () => {
 | 
				
			||||||
 | 
					    updateToLatest.mutate(
 | 
				
			||||||
 | 
					      { keepRedirector: true },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        onSuccess: () => {
 | 
				
			||||||
 | 
					          toast({
 | 
				
			||||||
 | 
					            id: `upgrade-to-latest-start-${device.serialNumber}`,
 | 
				
			||||||
 | 
					            title: t('common.success'),
 | 
				
			||||||
 | 
					            description: t('controller.devices.sent_upgrade_to_latest'),
 | 
				
			||||||
 | 
					            status: 'success',
 | 
				
			||||||
 | 
					            duration: 5000,
 | 
				
			||||||
 | 
					            isClosable: true,
 | 
				
			||||||
 | 
					            position: 'top-right',
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					          addEventListeners([
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					              id: `device-connection-upgrade-${device.serialNumber}`,
 | 
				
			||||||
 | 
					              type: 'DEVICE_CONNECTION',
 | 
				
			||||||
 | 
					              serialNumber: device.serialNumber,
 | 
				
			||||||
 | 
					              callback: () => {
 | 
				
			||||||
 | 
					                const id = `device-connection-upgrade-notification-${device.serialNumber}`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (!toast.isActive(id)) {
 | 
				
			||||||
 | 
					                  toast({
 | 
				
			||||||
 | 
					                    id,
 | 
				
			||||||
 | 
					                    title: t('common.success'),
 | 
				
			||||||
 | 
					                    description: t('controller.devices.finished_upgrade', { serialNumber: device.serialNumber }),
 | 
				
			||||||
 | 
					                    status: 'success',
 | 
				
			||||||
 | 
					                    duration: 5000,
 | 
				
			||||||
 | 
					                    isClosable: true,
 | 
				
			||||||
 | 
					                    position: 'top-right',
 | 
				
			||||||
 | 
					                  });
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					              },
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					              id: `device-disconnected-upgrade-${device.serialNumber}`,
 | 
				
			||||||
 | 
					              type: 'DEVICE_DISCONNECTION',
 | 
				
			||||||
 | 
					              serialNumber: device.serialNumber,
 | 
				
			||||||
 | 
					              callback: () => {
 | 
				
			||||||
 | 
					                const id = `device-disconnection-upgrade-notification-${device.serialNumber}`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (!toast.isActive(id)) {
 | 
				
			||||||
 | 
					                  toast({
 | 
				
			||||||
 | 
					                    id,
 | 
				
			||||||
 | 
					                    title: t('common.success'),
 | 
				
			||||||
 | 
					                    description: t('controller.devices.started_upgrade', { serialNumber: device.serialNumber }),
 | 
				
			||||||
 | 
					                    status: 'success',
 | 
				
			||||||
 | 
					                    duration: 5000,
 | 
				
			||||||
 | 
					                    isClosable: true,
 | 
				
			||||||
 | 
					                    position: 'top-right',
 | 
				
			||||||
 | 
					                  });
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					              },
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					          ]);
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        onError: (e) => {
 | 
				
			||||||
 | 
					          if (axios.isAxiosError(e)) {
 | 
				
			||||||
 | 
					            toast({
 | 
				
			||||||
 | 
					              id: `upgrade-to-latest-error-${device.serialNumber}`,
 | 
				
			||||||
 | 
					              title: t('common.error'),
 | 
				
			||||||
 | 
					              description: e?.response?.data?.ErrorDescription,
 | 
				
			||||||
 | 
					              status: 'error',
 | 
				
			||||||
 | 
					              duration: 5000,
 | 
				
			||||||
 | 
					              isClosable: true,
 | 
				
			||||||
 | 
					              position: 'top-right',
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const handleConnectClick = () => getRtty();
 | 
				
			||||||
 | 
					  const handleRebootClick = () => onOpenRebootModal(device.serialNumber);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <Menu>
 | 
				
			||||||
 | 
					      <Tooltip label={t('common.actions')}>
 | 
				
			||||||
 | 
					        {size === undefined || isCompact ? (
 | 
				
			||||||
 | 
					          <MenuButton
 | 
				
			||||||
 | 
					            as={IconButton}
 | 
				
			||||||
 | 
					            aria-label="Commands"
 | 
				
			||||||
 | 
					            icon={isRtty ? <Spinner /> : <Wrench size={20} />}
 | 
				
			||||||
 | 
					            size={size ?? 'sm'}
 | 
				
			||||||
 | 
					            isDisabled={isDisabled}
 | 
				
			||||||
 | 
					            ml={2}
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
 | 
					        ) : (
 | 
				
			||||||
 | 
					          <MenuButton
 | 
				
			||||||
 | 
					            as={Button}
 | 
				
			||||||
 | 
					            aria-label="Commands"
 | 
				
			||||||
 | 
					            rightIcon={isRtty ? <Spinner /> : <Wrench size={20} />}
 | 
				
			||||||
 | 
					            size={size ?? 'sm'}
 | 
				
			||||||
 | 
					            isDisabled={isDisabled}
 | 
				
			||||||
 | 
					            ml={2}
 | 
				
			||||||
 | 
					          >
 | 
				
			||||||
 | 
					            {t('common.actions')}
 | 
				
			||||||
 | 
					          </MenuButton>
 | 
				
			||||||
 | 
					        )}
 | 
				
			||||||
 | 
					      </Tooltip>
 | 
				
			||||||
 | 
					      <Portal>
 | 
				
			||||||
 | 
					        <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>
 | 
				
			||||||
 | 
					          <MenuItem onClick={handleRebootClick}>{t('commands.reboot')}</MenuItem>
 | 
				
			||||||
 | 
					          <MenuItem onClick={handleOpenTelemetry}>{t('controller.telemetry.title')}</MenuItem>
 | 
				
			||||||
 | 
					          <MenuItem onClick={handleOpenScript}>{t('script.one')}</MenuItem>
 | 
				
			||||||
 | 
					          <MenuItem onClick={handleOpenTrace}>{t('controller.devices.trace')}</MenuItem>
 | 
				
			||||||
 | 
					          <MenuItem onClick={handleUpdateToLatest} hidden>
 | 
				
			||||||
 | 
					            {t('premium.toolbox.upgrade_to_latest')}
 | 
				
			||||||
 | 
					          </MenuItem>
 | 
				
			||||||
 | 
					          <MenuItem onClick={handleOpenScan}>{t('commands.wifiscan')}</MenuItem>
 | 
				
			||||||
 | 
					        </MenuList>
 | 
				
			||||||
 | 
					      </Portal>
 | 
				
			||||||
 | 
					    </Menu>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default React.memo(DeviceActionDropdown);
 | 
				
			||||||
							
								
								
									
										55
									
								
								src/components/Buttons/EditButton/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,55 @@
 | 
				
			|||||||
 | 
					import React from 'react';
 | 
				
			||||||
 | 
					import { IconButton, Button, Tooltip, useBreakpoint } from '@chakra-ui/react';
 | 
				
			||||||
 | 
					import { Pen } from 'phosphor-react';
 | 
				
			||||||
 | 
					import { useTranslation } from 'react-i18next';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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 = true,
 | 
				
			||||||
 | 
					  ...props
 | 
				
			||||||
 | 
					}) => {
 | 
				
			||||||
 | 
					  const { t } = useTranslation();
 | 
				
			||||||
 | 
					  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 ?? t('common.edit')}
 | 
				
			||||||
 | 
					      </Button>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <Tooltip label={label ?? t('common.edit')} hasArrow>
 | 
				
			||||||
 | 
					      <IconButton
 | 
				
			||||||
 | 
					        aria-label="edit"
 | 
				
			||||||
 | 
					        colorScheme="gray"
 | 
				
			||||||
 | 
					        onClick={onClick}
 | 
				
			||||||
 | 
					        icon={<Pen size={20} />}
 | 
				
			||||||
 | 
					        isLoading={isLoading}
 | 
				
			||||||
 | 
					        isDisabled={isDisabled}
 | 
				
			||||||
 | 
					        {...props}
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					    </Tooltip>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const EditButton = React.memo(_EditButton);
 | 
				
			||||||
							
								
								
									
										79
									
								
								src/components/Buttons/FileInputButton/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,79 @@
 | 
				
			|||||||
 | 
					import React, { useEffect, useState } from 'react';
 | 
				
			||||||
 | 
					import { FormControl, Input, InputGroup } from '@chakra-ui/react';
 | 
				
			||||||
 | 
					import { v4 as uuid } from 'uuid';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface FileInputButtonProps {
 | 
				
			||||||
 | 
					  value: string;
 | 
				
			||||||
 | 
					  setValue: (v: string, file?: File) => void;
 | 
				
			||||||
 | 
					  setFileName?: (v: string) => void;
 | 
				
			||||||
 | 
					  refreshId: string;
 | 
				
			||||||
 | 
					  accept: string;
 | 
				
			||||||
 | 
					  isHidden?: boolean;
 | 
				
			||||||
 | 
					  isStringFile?: boolean;
 | 
				
			||||||
 | 
					  sizeLimit?: number;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const _FileInputButton: React.FC<FileInputButtonProps> = ({
 | 
				
			||||||
 | 
					  value,
 | 
				
			||||||
 | 
					  setValue,
 | 
				
			||||||
 | 
					  setFileName,
 | 
				
			||||||
 | 
					  refreshId,
 | 
				
			||||||
 | 
					  accept,
 | 
				
			||||||
 | 
					  isHidden,
 | 
				
			||||||
 | 
					  isStringFile,
 | 
				
			||||||
 | 
					  sizeLimit,
 | 
				
			||||||
 | 
					}) => {
 | 
				
			||||||
 | 
					  const [fileKey, setFileKey] = useState(uuid());
 | 
				
			||||||
 | 
					  let fileReader: FileReader | undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const handleStringFileRead = () => {
 | 
				
			||||||
 | 
					    if (fileReader) {
 | 
				
			||||||
 | 
					      const content = fileReader.result;
 | 
				
			||||||
 | 
					      if (content) {
 | 
				
			||||||
 | 
					        setValue(content as string);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const changeFile = (e: React.ChangeEvent<HTMLInputElement>) => {
 | 
				
			||||||
 | 
					    const file = e.target.files ? e.target.files[0] : undefined;
 | 
				
			||||||
 | 
					    if (file) {
 | 
				
			||||||
 | 
					      if (sizeLimit && file.size > sizeLimit) {
 | 
				
			||||||
 | 
					        setFileKey(uuid());
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        const newVal = URL.createObjectURL(file);
 | 
				
			||||||
 | 
					        if (!isStringFile) {
 | 
				
			||||||
 | 
					          setValue(newVal, file);
 | 
				
			||||||
 | 
					          if (setFileName) setFileName(file.name ?? '');
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          fileReader = new FileReader();
 | 
				
			||||||
 | 
					          if (setFileName) setFileName(file.name);
 | 
				
			||||||
 | 
					          fileReader.onloadend = handleStringFileRead;
 | 
				
			||||||
 | 
					          fileReader.readAsText(file);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    if (value === '') setFileKey(uuid());
 | 
				
			||||||
 | 
					  }, [refreshId, value]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <FormControl hidden={isHidden}>
 | 
				
			||||||
 | 
					      <InputGroup>
 | 
				
			||||||
 | 
					        <Input
 | 
				
			||||||
 | 
					          borderRadius="15px"
 | 
				
			||||||
 | 
					          pt={1}
 | 
				
			||||||
 | 
					          fontSize="sm"
 | 
				
			||||||
 | 
					          type="file"
 | 
				
			||||||
 | 
					          onChange={changeFile}
 | 
				
			||||||
 | 
					          key={fileKey}
 | 
				
			||||||
 | 
					          accept={accept}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      </InputGroup>
 | 
				
			||||||
 | 
					    </FormControl>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const FileInputButton = React.memo(_FileInputButton);
 | 
				
			||||||
							
								
								
									
										62
									
								
								src/components/Buttons/RefreshButton/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,62 @@
 | 
				
			|||||||
 | 
					import React from 'react';
 | 
				
			||||||
 | 
					import { Button, IconButton, ThemeTypings, Tooltip, useBreakpoint } from '@chakra-ui/react';
 | 
				
			||||||
 | 
					import { ArrowsClockwise } from 'phosphor-react';
 | 
				
			||||||
 | 
					import { useTranslation } from 'react-i18next';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface RefreshButtonProps {
 | 
				
			||||||
 | 
					  onClick: () => void;
 | 
				
			||||||
 | 
					  isDisabled?: boolean;
 | 
				
			||||||
 | 
					  isFetching?: boolean;
 | 
				
			||||||
 | 
					  isCompact?: boolean;
 | 
				
			||||||
 | 
					  ml?: string | number;
 | 
				
			||||||
 | 
					  size?: 'sm' | 'md' | 'lg';
 | 
				
			||||||
 | 
					  colorScheme?: ThemeTypings['colorSchemes'];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const _RefreshButton: React.FC<RefreshButtonProps> = ({
 | 
				
			||||||
 | 
					  onClick,
 | 
				
			||||||
 | 
					  isDisabled,
 | 
				
			||||||
 | 
					  isFetching,
 | 
				
			||||||
 | 
					  isCompact = true,
 | 
				
			||||||
 | 
					  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 = true,
 | 
				
			||||||
 | 
					  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 = true,
 | 
				
			||||||
 | 
					  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 = true,
 | 
				
			||||||
 | 
					  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);
 | 
				
			||||||