Compare commits
	
		
			6 Commits
		
	
	
		
			v2.8.0
			...
			release/v2
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | a41f66f002 | ||
|   | 866036f323 | ||
|   | 6c4cbff1a6 | ||
|   | 6298d4f29a | ||
|   | 8f0828a394 | ||
|   | c4cfc76b2d | 
| @@ -1,10 +1,4 @@ | |||||||
| /src/assets | /src/assets | ||||||
| /build | /build | ||||||
| /node_modules | /node_modules | ||||||
| /dist |  | ||||||
| /icons |  | ||||||
| helm |  | ||||||
| docker-entrypoint.d |  | ||||||
| .dockerignore |  | ||||||
| DockerFile |  | ||||||
| .github | .github | ||||||
|   | |||||||
							
								
								
									
										88
									
								
								.eslintrc
									
									
									
									
									
								
							
							
						
						| @@ -1,80 +1,22 @@ | |||||||
| { | { | ||||||
|  |   "extends": ["airbnb", "prettier"], | ||||||
|  |   "plugins": ["prettier"], | ||||||
|   "env": { |   "env": { | ||||||
|       "browser": true, |       "browser": true, | ||||||
|     "es2021": true |       "jest": 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": { | ||||||
|     "import/extensions": [ |     "max-len": ["error", {"code": 150}], | ||||||
|       "error", |     "prefer-promise-reject-errors": ["off"], | ||||||
|       "ignorePackages", |     "react/jsx-filename-extension": ["off"], | ||||||
|       { |  | ||||||
|         "js": "never", |  | ||||||
|         "jsx": "never", |  | ||||||
|         "ts": "never", |  | ||||||
|         "tsx": "never" |  | ||||||
|       } |  | ||||||
|     ], |  | ||||||
|     "@typescript-eslint/naming-convention": [ |  | ||||||
|       "error", |  | ||||||
|       { |  | ||||||
|         "selector": "function", |  | ||||||
|         "format": ["PascalCase", "camelCase"], |  | ||||||
|         "leadingUnderscore": "allowSingleOrDouble" |  | ||||||
|       } |  | ||||||
|     ], |  | ||||||
|     "no-restricted-syntax": ["error", "ForInStatement", "LabeledStatement", "WithStatement"], |  | ||||||
|     "react/function-component-definition": [2, { "namedComponents": "arrow-function" }], |  | ||||||
|     "import/order": [ |  | ||||||
|       "error", |  | ||||||
|       { |  | ||||||
|         "alphabetize": { |  | ||||||
|           "order": "asc", |  | ||||||
|           "caseInsensitive": true |  | ||||||
|         }, |  | ||||||
|         "newlines-between": "never", |  | ||||||
|         "groups": ["builtin", "external", "parent", "sibling", "index"], |  | ||||||
|         "pathGroups": [ |  | ||||||
|           { |  | ||||||
|             "pattern": "react", |  | ||||||
|             "group": "external", |  | ||||||
|             "position": "before" |  | ||||||
|           } |  | ||||||
|         ], |  | ||||||
|         "pathGroupsExcludedImportTypes": ["builtin"] |  | ||||||
|       } |  | ||||||
|     ], |  | ||||||
|     "max-len": ["error", { "code": 150 }], |  | ||||||
|     "@typescript-eslint/ban-ts-comment": ["off"], |  | ||||||
|     "import/prefer-default-export": ["off"], |  | ||||||
|     "react/prop-types": ["warn"], |     "react/prop-types": ["warn"], | ||||||
|     "react/require-default-props": "off", |     "no-return-assign": ["off"], | ||||||
|     "react/jsx-props-no-spreading": ["off"], |     "react/jsx-props-no-spreading": ["off"], | ||||||
|     "react/jsx-curly-newline": "off", |     "react/destructuring-assignment": ["off"], | ||||||
|     "no-underscore-dangle": "off" |     "no-restricted-syntax": ["error", "ForInStatement", "LabeledStatement", "WithStatement"], | ||||||
|  |     "react/jsx-one-expression-per-line": "off", | ||||||
|  |     "react/jsx-wrap-multilines": "off", | ||||||
|  |     "react/jsx-curly-newline": "off" | ||||||
|   }, |   }, | ||||||
|   "settings": { |   "settings": { | ||||||
|     "import/resolver": { |     "import/resolver": { | ||||||
| @@ -82,5 +24,11 @@ | |||||||
|         "paths": ["src"] |         "paths": ["src"] | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |   }, | ||||||
|  |     "parser": "babel-eslint", | ||||||
|  |     "parserOptions": { | ||||||
|  |       "sourceType": "module", | ||||||
|  |       "allowImportExportEverywhere": false, | ||||||
|  |       "codeFrame": false | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										78
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -12,7 +12,6 @@ on: | |||||||
|   pull_request: |   pull_request: | ||||||
|     branches: |     branches: | ||||||
|       - main |       - main | ||||||
|       - 'release/*' |  | ||||||
|  |  | ||||||
| defaults: | defaults: | ||||||
|   run: |   run: | ||||||
| @@ -25,48 +24,45 @@ jobs: | |||||||
|       DOCKER_REGISTRY_URL: tip-tip-wlan-cloud-ucentral.jfrog.io |       DOCKER_REGISTRY_URL: tip-tip-wlan-cloud-ucentral.jfrog.io | ||||||
|       DOCKER_REGISTRY_USERNAME: ucentral |       DOCKER_REGISTRY_USERNAME: ucentral | ||||||
|     steps: |     steps: | ||||||
|     - name: Checkout actions repo |     - uses: actions/checkout@v2 | ||||||
|       uses: actions/checkout@v3 |  | ||||||
|       with: |  | ||||||
|         repository: Telecominfraproject/.github |  | ||||||
|         path: github |  | ||||||
|  |  | ||||||
|     - name: Build and push Docker image |     - name: Build Docker image | ||||||
|       uses: ./github/composite-actions/docker-image-build |       run: docker build -t owgw-ui:${{ github.sha }} . | ||||||
|       with: |  | ||||||
|         image_name: owgw-ui |  | ||||||
|         registry: tip-tip-wlan-cloud-ucentral.jfrog.io |  | ||||||
|         registry_user: ucentral |  | ||||||
|         registry_password: ${{ secrets.DOCKER_REGISTRY_PASSWORD }} |  | ||||||
|  |  | ||||||
|     - name: Notify on failure via Slack |     - name: Tag Docker image | ||||||
|       if: failure() && github.ref == 'refs/heads/main' |       run: | | ||||||
|       uses: rtCamp/action-slack-notify@v2 |         TAGS="${{ github.sha }}" | ||||||
|       env: |  | ||||||
|         SLACK_USERNAME: GitHub Actions failure notifier |  | ||||||
|         SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} |  | ||||||
|         SLACK_COLOR: "${{ job.status }}" |  | ||||||
|         SLACK_ICON: https://raw.githubusercontent.com/quintessence/slack-icons/master/images/github-logo-slack-icon.png |  | ||||||
|         SLACK_TITLE: Docker build failed for OWGW-UI service |  | ||||||
|  |  | ||||||
|   trigger-deploy-to-dev: |         if [[ ${GITHUB_REF} == "refs/heads/"* ]] | ||||||
|     runs-on: ubuntu-latest |         then | ||||||
|     if: github.ref == 'refs/heads/main' |           CURRENT_TAG=$(echo ${GITHUB_REF#refs/heads/} | tr '/' '-') | ||||||
|     needs: |           TAGS="$TAGS $CURRENT_TAG" | ||||||
|       - docker |         else | ||||||
|     steps: |           if [[ ${GITHUB_REF} == "refs/tags/"* ]] | ||||||
|     - name: Checkout actions repo |           then | ||||||
|       uses: actions/checkout@v3 |             CURRENT_TAG=$(echo ${GITHUB_REF#refs/tags/} | tr '/' '-') | ||||||
|       with: |             TAGS="$TAGS $CURRENT_TAG" | ||||||
|         repository: Telecominfraproject/.github |           else # PR build | ||||||
|         path: github |             CURRENT_TAG=$(echo ${GITHUB_HEAD_REF#refs/heads/} | tr '/' '-') | ||||||
|  |             TAGS="$TAGS $CURRENT_TAG" | ||||||
|  |           fi | ||||||
|  |         fi | ||||||
|  |  | ||||||
|     - name: Trigger deployment of the latest version to dev instance and wait for result |         echo "Result tags: $TAGS" | ||||||
|       uses: ./github/composite-actions/trigger-workflow-and-wait |  | ||||||
|  |         for tag in $TAGS; do | ||||||
|  |           docker tag owgw-ui:${{ github.sha }} ${{ env.DOCKER_REGISTRY_URL }}/owgw-ui:$tag | ||||||
|  |         done | ||||||
|  |  | ||||||
|  |     - name: Log into Docker registry | ||||||
|  |       if: startsWith(github.ref, 'refs/tags/') || startsWith(github.ref, 'refs/pull/') || github.ref == 'refs/heads/main' | ||||||
|  |       uses: docker/login-action@v1 | ||||||
|       with: |       with: | ||||||
|         owner: Telecominfraproject |         registry: ${{ env.DOCKER_REGISTRY_URL }} | ||||||
|         repo: wlan-testing |         username: ${{ env.DOCKER_REGISTRY_USERNAME }} | ||||||
|         workflow: ucentralgw-dev-deployment.yaml |         password: ${{ secrets.DOCKER_REGISTRY_PASSWORD }} | ||||||
|         token: ${{ secrets.WLAN_TESTING_PAT }} |  | ||||||
|         ref: master |     - name: Push Docker images | ||||||
|         inputs: '{"force_latest": "true"}' |       if: startsWith(github.ref, 'refs/tags/') || startsWith(github.ref, 'refs/pull/') || github.ref == 'refs/heads/main' | ||||||
|  |       run: | | ||||||
|  |         docker images | grep ${{ env.DOCKER_REGISTRY_URL }}/owgw-ui | awk -F ' ' '{print $1":"$2}' | xargs -I {} docker push {} | ||||||
|   | |||||||
							
								
								
									
										9
									
								
								.github/workflows/cleanup.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -4,7 +4,6 @@ on: | |||||||
|   pull_request: |   pull_request: | ||||||
|     branches: |     branches: | ||||||
|       - main |       - main | ||||||
|       - 'release/*' |  | ||||||
|     types: [ closed ] |     types: [ closed ] | ||||||
|  |  | ||||||
| defaults: | defaults: | ||||||
| @@ -17,10 +16,4 @@ jobs: | |||||||
|     steps: |     steps: | ||||||
|       - run: | |       - run: | | ||||||
|           export PR_BRANCH_TAG=$(echo ${GITHUB_HEAD_REF#refs/heads/} | tr '/' '-') |           export PR_BRANCH_TAG=$(echo ${GITHUB_HEAD_REF#refs/heads/} | tr '/' '-') | ||||||
|  |           curl -uucentral:${{ secrets.DOCKER_REGISTRY_PASSWORD }} -X DELETE "https://tip.jfrog.io/artifactory/tip-wlan-cloud-ucentral/owgw-ui/$PR_BRANCH_TAG" | ||||||
|           if [[ ! $PR_BRANCH_TAG =~ (main|master|release-*) ]]; then |  | ||||||
|             echo "PR branch is $PR_BRANCH_TAG, deleting Docker image" |  | ||||||
|             curl -s -uucentral:${{ secrets.DOCKER_REGISTRY_PASSWORD }} -X DELETE "https://tip.jfrog.io/artifactory/tip-wlan-cloud-ucentral/owgw-ui/$PR_BRANCH_TAG" |  | ||||||
|           else |  | ||||||
|             echo "PR branch is $PR_BRANCH_TAG, not deleting Docker image" |  | ||||||
|           fi |  | ||||||
|   | |||||||
							
								
								
									
										24
									
								
								.github/workflows/enforce-jira-issue-key.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,24 +0,0 @@ | |||||||
| name: Ensure Jira issue is linked |  | ||||||
|  |  | ||||||
| on: |  | ||||||
|   pull_request: |  | ||||||
|     types: [opened, edited, reopened, synchronize] |  | ||||||
|     branches: |  | ||||||
|       - 'release/*' |  | ||||||
|  |  | ||||||
| jobs: |  | ||||||
|   check_for_issue_key: |  | ||||||
|     runs-on: ubuntu-latest |  | ||||||
|     steps: |  | ||||||
|       - name: Checkout actions repo |  | ||||||
|         uses: actions/checkout@v3 |  | ||||||
|         with: |  | ||||||
|           repository: Telecominfraproject/.github |  | ||||||
|           path: github |  | ||||||
|  |  | ||||||
|       - name: Run JIRA check |  | ||||||
|         uses: ./github/composite-actions/enforce-jira-issue-key |  | ||||||
|         with: |  | ||||||
|           jira_base_url: ${{ secrets.TIP_JIRA_URL }} |  | ||||||
|           jira_user_email: ${{ secrets.TIP_JIRA_USER_EMAIL }} |  | ||||||
|           jira_api_token: ${{ secrets.TIP_JIRA_API_TOKEN }} |  | ||||||
							
								
								
									
										46
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,46 +0,0 @@ | |||||||
| name: Release chart package |  | ||||||
|  |  | ||||||
| on: |  | ||||||
|   push: |  | ||||||
|     tags: |  | ||||||
|       - 'v*' |  | ||||||
|  |  | ||||||
| defaults: |  | ||||||
|   run: |  | ||||||
|     shell: bash |  | ||||||
|  |  | ||||||
| jobs: |  | ||||||
|   helm-package: |  | ||||||
|     runs-on: ubuntu-20.04 |  | ||||||
|     env: |  | ||||||
|       HELM_REPO_URL: https://tip.jfrog.io/artifactory/tip-wlan-cloud-ucentral-helm/ |  | ||||||
|       HELM_REPO_USERNAME: ucentral |  | ||||||
|     steps: |  | ||||||
|       - name: Checkout uCentral assembly chart repo |  | ||||||
|         uses: actions/checkout@v3 |  | ||||||
|         with: |  | ||||||
|           path: wlan-cloud-ucentralgw-ui |  | ||||||
|  |  | ||||||
|       - name: Build package |  | ||||||
|         working-directory: wlan-cloud-ucentralgw-ui/helm |  | ||||||
|         run: | |  | ||||||
|           helm plugin install https://github.com/aslafy-z/helm-git --version 0.10.0 |  | ||||||
|           helm repo add bitnami https://charts.bitnami.com/bitnami |  | ||||||
|           helm repo update |  | ||||||
|           helm dependency update |  | ||||||
|           mkdir dist |  | ||||||
|           helm package . -d dist |  | ||||||
|  |  | ||||||
|       - name: Generate GitHub release body |  | ||||||
|         working-directory: wlan-cloud-ucentralgw-ui/helm |  | ||||||
|         run: | |  | ||||||
|           pip3 install yq -q |  | ||||||
|           echo "Docker image - tip-tip-wlan-cloud-ucentral.jfrog.io/owgw-ui:$GITHUB_REF_NAME" > release.txt |  | ||||||
|           echo "Helm charted may be attached to this release" >> release.txt |  | ||||||
|           echo "Deployment artifacts may be found in https://github.com/Telecominfraproject/wlan-cloud-ucentral-deploy/tree/$GITHUB_REF_NAME" >> release.txt |  | ||||||
|  |  | ||||||
|       - name: Create GitHub release |  | ||||||
|         uses: softprops/action-gh-release@v1 |  | ||||||
|         with: |  | ||||||
|           body_path: wlan-cloud-ucentralgw-ui/helm/release.txt |  | ||||||
|           files: wlan-cloud-ucentralgw-ui/helm/dist/* |  | ||||||
							
								
								
									
										5
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,8 +1,9 @@ | |||||||
|  |  | ||||||
|  |  | ||||||
| # dependencies | # dependencies | ||||||
| /node_modules | /node_modules | ||||||
| /.pnp | /.pnp | ||||||
| .pnp.js | .pnp.js | ||||||
| /dev-dist |  | ||||||
|  |  | ||||||
| # testing | # testing | ||||||
| /coverage | /coverage | ||||||
| @@ -18,3 +19,5 @@ | |||||||
| .env.production.local | .env.production.local | ||||||
|  |  | ||||||
| npm-debug.log* | npm-debug.log* | ||||||
|  | yarn-debug.log* | ||||||
|  | yarn-error.log* | ||||||
|   | |||||||
| @@ -1,5 +1,4 @@ | |||||||
| /src/assets | /src/assets | ||||||
| build | build | ||||||
| dist |  | ||||||
| node_modules | node_modules | ||||||
| .github | .github | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| {  | {  | ||||||
|   "printWidth": 120, |     "printWidth": 100, | ||||||
|     "trailingComma": "all", |     "trailingComma": "all", | ||||||
|     "tabWidth": 2,  |     "tabWidth": 2,  | ||||||
|     "semi": true,  |     "semi": true,  | ||||||
|   | |||||||
							
								
								
									
										12
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						| @@ -1,8 +1,6 @@ | |||||||
| FROM node:18.7.0-alpine3.15 AS build | FROM node:14-alpine3.11 AS build | ||||||
|  |  | ||||||
| WORKDIR /app | COPY package.json package-lock.json / | ||||||
|  |  | ||||||
| COPY package.json package-lock.json /app/ |  | ||||||
|  |  | ||||||
| RUN npm install | RUN npm install | ||||||
|  |  | ||||||
| @@ -10,8 +8,8 @@ COPY . . | |||||||
|  |  | ||||||
| RUN npm run build | RUN npm run build | ||||||
|  |  | ||||||
| FROM nginx:1.22.0-alpine AS runtime | FROM nginx:1.20.1-alpine AS runtime | ||||||
|  |  | ||||||
| COPY --from=build /app/build/ /usr/share/nginx/html/ | COPY --from=build /build/ /usr/share/nginx/html/ | ||||||
|  |  | ||||||
| COPY --from=build /app/docker-entrypoint.d/40-generate-config.sh /docker-entrypoint.d/40-generate-config.sh | COPY --from=build docker-entrypoint.d/40-generate-config.sh /docker-entrypoint.d/40-generate-config.sh | ||||||
|   | |||||||
							
								
								
									
										25
									
								
								README.md
									
									
									
									
									
								
							
							
						
						| @@ -1,7 +1,6 @@ | |||||||
| # 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). | ||||||
|  |  | ||||||
| @@ -10,34 +9,40 @@ NOTE: This UI will be evolving as micro services are added to the uCentral progr | |||||||
| ## Running the solution | ## Running the solution | ||||||
|  |  | ||||||
| ### Development | ### Development | ||||||
|  |  | ||||||
| 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 run dev | npm start | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | Run these commands if you want to run the solution on your machine while also doing development on the [uCentral UI Library](https://github.com/Telecominfraproject/wlan-cloud-ucentral-ui-libs). | ||||||
|  | ``` | ||||||
|  | git clone https://github.com/Telecominfraproject/wlan-cloud-ucentralgw-ui | ||||||
|  | git clone https://github.com/Telecominfraproject/wlan-cloud-ucentral-ui-libs | ||||||
|  | cd wlan-cloud-ucentralgw-ui | ||||||
|  | npm link ../wlan-cloud-ucentral-ui-libs // Add sudo at the start of this command if it fails because of permissions | ||||||
|  | npm start | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| ### Production | ### Production | ||||||
|  |  | ||||||
| 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 | ||||||
|  | } | ||||||
| ``` | ``` | ||||||
|   | |||||||
							
								
								
									
										25
									
								
								babel.config.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,25 @@ | |||||||
|  | { | ||||||
|  |   "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 | ||||||
|  |           } | ||||||
|  |         ] | ||||||
|  |       ] | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										12
									
								
								config/paths.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,12 @@ | |||||||
|  | const path = require('path'); | ||||||
|  |  | ||||||
|  | module.exports = { | ||||||
|  |   // Source files | ||||||
|  |   src: path.resolve(__dirname, '../src'), | ||||||
|  |  | ||||||
|  |   // Production build files | ||||||
|  |   build: path.resolve(__dirname, '../build'), | ||||||
|  |  | ||||||
|  |   // Static files that get copied to build folder | ||||||
|  |   public: path.resolve(__dirname, '../public'), | ||||||
|  | }; | ||||||
							
								
								
									
										79
									
								
								config/webpack.common.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,79 @@ | |||||||
|  | /* 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' }, | ||||||
|  |     ], | ||||||
|  |   }, | ||||||
|  | }; | ||||||
							
								
								
									
										54
									
								
								config/webpack.dev.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,54 @@ | |||||||
|  | /* 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()], | ||||||
|  | }); | ||||||
							
								
								
									
										86
									
								
								config/webpack.prod.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,86 @@ | |||||||
|  | /* 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,32 +1,6 @@ | |||||||
| #!/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}" | ||||||
|  |  | ||||||
| ENV_CONFIG_PATH=/usr/share/nginx/html/env-config.js | echo '{"DEFAULT_UCENTRALSEC_URL": "'$DEFAULT_UCENTRALSEC_URL'","ALLOW_UCENTRALSEC_CHANGE": '$ALLOW_UCENTRALSEC_CHANGE'}' > /usr/share/nginx/html/config.json | ||||||
|  |  | ||||||
| # 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 |  | ||||||
|   | |||||||
| @@ -11,7 +11,6 @@ metadata: | |||||||
|     app.kubernetes.io/managed-by: {{ .Release.Service }} |     app.kubernetes.io/managed-by: {{ .Release.Service }} | ||||||
| spec: | spec: | ||||||
|   replicas: {{ .Values.replicaCount }} |   replicas: {{ .Values.replicaCount }} | ||||||
|   revisionHistoryLimit: {{ .Values.revisionHistoryLimit }} |  | ||||||
|   selector: |   selector: | ||||||
|     matchLabels: |     matchLabels: | ||||||
|       app.kubernetes.io/name: {{ include "owgwui.name" . }} |       app.kubernetes.io/name: {{ include "owgwui.name" . }} | ||||||
|   | |||||||
| @@ -1,6 +1,5 @@ | |||||||
| # System | # System | ||||||
| replicaCount: 1 | replicaCount: 1 | ||||||
| revisionHistoryLimit: 2 |  | ||||||
|  |  | ||||||
| nameOverride: "" | nameOverride: "" | ||||||
| fullnameOverride: "" | fullnameOverride: "" | ||||||
| @@ -8,7 +7,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.8.0 |     tag: v2.4.2 | ||||||
|     pullPolicy: Always |     pullPolicy: Always | ||||||
|  |  | ||||||
| services: | services: | ||||||
| @@ -75,4 +74,5 @@ podAnnotations: {} | |||||||
|  |  | ||||||
| # Application | # Application | ||||||
| public_env_variables: | public_env_variables: | ||||||
|   REACT_APP_UCENTRALSEC_URL: https://ucentral.dpaas.arilia.com:16001 |   DEFAULT_UCENTRALSEC_URL: https://ucentral.dpaas.arilia.com:16001 | ||||||
|  |   ALLOW_UCENTRALSEC_CHANGE: false | ||||||
|   | |||||||
							
								
								
									
										21
									
								
								index.html
									
									
									
									
									
								
							
							
						
						| @@ -1,21 +0,0 @@ | |||||||
| <!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> |  | ||||||
							
								
								
									
										9
									
								
								jsconfig.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,9 @@ | |||||||
|  | { | ||||||
|  |   "compilerOptions": { | ||||||
|  |     "baseUrl": "src", | ||||||
|  |     "paths": { | ||||||
|  |       "*": ["*"] | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   "include": ["src"] | ||||||
|  | } | ||||||
							
								
								
									
										31681
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
							
								
								
									
										169
									
								
								package.json
									
									
									
									
									
								
							
							
						
						| @@ -1,92 +1,99 @@ | |||||||
| { | { | ||||||
|   "name": "ucentral-client", |   "name": "ucentral-client", | ||||||
|   "version": "2.8.0(44)", |   "version": "2.4.3", | ||||||
|   "description": "", |  | ||||||
|   "private": true, |  | ||||||
|   "main": "index.tsx", |  | ||||||
|   "scripts": { |  | ||||||
|     "dev": "vite", |  | ||||||
|     "build": "vite build", |  | ||||||
|     "format": "prettier --write \"src/**/*.js\"", |  | ||||||
|     "analyze": "source-map-explorer 'build/static/js/*.js'", |  | ||||||
|     "lint": "TIMING=1 eslint \"src/**/*.{ts,tsx,js,jsx}\" --fix", |  | ||||||
|     "clean": "rm -rf node_modules && rm -rf build" |  | ||||||
|   }, |  | ||||||
|   "author": "", |  | ||||||
|   "license": "ISC", |  | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "@chakra-ui/icons": "^2.0.11", |     "@coreui/coreui": "^3.4.0", | ||||||
|     "@chakra-ui/react": "^2.3.6", |     "@coreui/icons": "^2.0.1", | ||||||
|     "@chakra-ui/theme-tools": "^2.0.12", |     "@coreui/icons-react": "^1.1.0", | ||||||
|     "@chakra-ui/utils": "^2.0.11", |     "@coreui/react": "^3.4.6", | ||||||
|     "@emotion/react": "^11.10.4", |     "@coreui/react-chartjs": "^1.1.0", | ||||||
|     "@emotion/styled": "^11.10.4", |     "apexcharts": "^3.27.1", | ||||||
|     "@fontsource/inter": "^4.5.14", |     "axios": "^0.21.1", | ||||||
|     "@react-spring/web": "^9.5.5", |     "axios-retry": "^3.1.9", | ||||||
|     "axios": "^1.1.3", |  | ||||||
|     "buffer": "^6.0.3", |  | ||||||
|     "chakra-react-select": "^4.3.0", |  | ||||||
|     "dagre": "^0.8.5", |     "dagre": "^0.8.5", | ||||||
|     "formik": "^2.2.9", |     "i18next": "^20.3.1", | ||||||
|     "framer-motion": "^7.6.1", |     "i18next-browser-languagedetector": "^6.1.2", | ||||||
|     "i18next": "^22.0.0", |     "i18next-http-backend": "^1.2.6", | ||||||
|     "i18next-browser-languagedetector": "^6.1.8", |     "prop-types": "^15.7.2", | ||||||
|     "i18next-http-backend": "^1.4.4", |     "react": "^17.0.2", | ||||||
|     "libphonenumber-js": "^1.10.14", |     "react-apexcharts": "^1.3.9", | ||||||
|     "phosphor-react": "^1.4.1", |     "react-dom": "^17.0.2", | ||||||
|     "prop-types": "^15.8.1", |     "react-flow-renderer": "^9.6.6", | ||||||
|     "react": "^18.2.0", |     "react-i18next": "^11.11.0", | ||||||
|     "react-app-polyfill": "^3.0.0", |     "react-paginate": "^7.1.3", | ||||||
|     "react-chartjs-2": "^4.3.1", |     "react-router-dom": "^5.2.0", | ||||||
|     "chart.js": "^3.9.1", |     "react-select": "^4.3.1", | ||||||
|     "react-country-flag": "^3.0.2", |     "react-tooltip": "^4.2.21", | ||||||
|     "react-csv": "^2.2.2", |     "react-widgets": "^5.1.1", | ||||||
|     "react-datepicker": "^4.8.0", |     "sass": "^1.35.1", | ||||||
|     "react-dom": "^18.2.0", |     "ucentral-libs": "^1.0.37", | ||||||
|     "@textea/json-viewer": "^2.10.0", |     "uuid": "^8.3.2" | ||||||
|     "react-fast-compare": "^3.2.0", |   }, | ||||||
|     "react-i18next": "^11.18.6", |   "main": "index.js", | ||||||
|     "react-masonry-css": "^1.0.16", |   "scripts": { | ||||||
|     "@tanstack/react-query": "^4.12.0", |     "start": "webpack serve --config config/webpack.dev.js", | ||||||
|     "react-router-dom": "^6.4.2", |     "build": "webpack --config config/webpack.prod.js", | ||||||
|     "react-table": "^7.8.0", |     "format": "prettier --write 'src/**/*.js'", | ||||||
|     "react-virtualized-auto-sizer": "^1.0.7", |     "eslint-fix": "eslint --fix 'src/**/*.js'" | ||||||
|     "react-window": "^1.8.8", |   }, | ||||||
|     "source-map-explorer": "^2.5.3", |   "eslintConfig": { | ||||||
|     "vite": "^3.1.8", |     "extends": "react-app" | ||||||
|     "typescript": "^4.8.4", |   }, | ||||||
|     "uuid": "^9.0.0", |   "husky": { | ||||||
|     "yup": "^0.32.11", |     "hooks": { | ||||||
|     "zustand": "^4.1.2" |       "pre-commit": "lint-staged" | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   "lint-staged": { | ||||||
|  |     "*.{js,jsx}": [ | ||||||
|  |       "eslint", | ||||||
|  |       "prettier --write" | ||||||
|  |     ] | ||||||
|   }, |   }, | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|     "@types/node": "^18.11.2", |     "@babel/core": "^7.14.6", | ||||||
|     "@types/react": "^18.0.21", |     "@babel/plugin-proposal-class-properties": "^7.14.5", | ||||||
|     "@types/react-csv": "^1.1.3", |     "@babel/plugin-transform-runtime": "^7.14.5", | ||||||
|     "@types/react-dom": "^18.0.6", |     "@babel/polyfill": "^7.12.1", | ||||||
|     "@types/react-table": "^7.7.12", |     "@babel/preset-env": "^7.14.7", | ||||||
|     "@types/react-datepicker": "4.8.0", |     "@babel/preset-react": "^7.14.5", | ||||||
|     "@types/uuid": "^8.3.4", |     "@pmmmwh/react-refresh-webpack-plugin": "^0.4.3", | ||||||
|     "@types/react-virtualized-auto-sizer": "^1.0.1", |     "@svgr/webpack": "^5.5.0", | ||||||
|     "@types/react-window": "^1.8.5", |     "autoprefixer": "^10.2.6", | ||||||
|     "eslint": "8.25.0", |     "babel-eslint": "^10.1.0", | ||||||
|     "vite-tsconfig-paths": "^3.5.1", |     "babel-loader": "^8.2.2", | ||||||
|     "lint-staged": "^13.0.3", |     "clean-webpack-plugin": "^3.0.0", | ||||||
|     "@vitejs/plugin-react": "^2.1.0", |     "compression-webpack-plugin": "^8.0.1", | ||||||
|     "vite-plugin-pwa": "^0.13.1", |     "copy-webpack-plugin": "^7.0.0", | ||||||
|     "prettier": "^2.7.1", |     "css-loader": "^5.2.6", | ||||||
|     "eslint-config-airbnb": "^19.0.4", |     "css-minimizer-webpack-plugin": "^2.0.0", | ||||||
|     "eslint-config-airbnb-typescript": "^17.0.0", |     "dotenv-webpack": "^6.0.4", | ||||||
|     "eslint-config-airbnb-typescript-prettier": "^5.0.0", |     "eslint": "^7.29.0", | ||||||
|     "eslint-config-prettier": "^8.5.0", |     "eslint-config-airbnb": "^18.2.1", | ||||||
|  |     "eslint-config-prettier": "^7.2.0", | ||||||
|     "eslint-import-resolver-alias": "^1.1.2", |     "eslint-import-resolver-alias": "^1.1.2", | ||||||
|  |     "eslint-loader": "^4.0.2", | ||||||
|     "eslint-plugin-babel": "^5.3.1", |     "eslint-plugin-babel": "^5.3.1", | ||||||
|     "eslint-plugin-import": "^2.26.0", |     "eslint-plugin-import": "^2.23.4", | ||||||
|     "eslint-plugin-jsx-a11y": "^6.6.1", |     "eslint-plugin-prettier": "^3.4.0", | ||||||
|     "eslint-plugin-no-inline-styles": "^1.0.5", |     "eslint-plugin-react": "^7.24.0", | ||||||
|     "eslint-plugin-prettier": "^4.2.1", |     "eslint-plugin-react-hooks": "^4.2.0", | ||||||
|     "eslint-plugin-react": "^7.31.10", |     "html-webpack-plugin": "^5.3.2", | ||||||
|     "eslint-plugin-react-hooks": "^4.6.0" |     "husky": "^4.3.8", | ||||||
|  |     "lint-staged": "^11.0.0", | ||||||
|  |     "mini-css-extract-plugin": "^1.6.1", | ||||||
|  |     "node-sass": "^5.0.0", | ||||||
|  |     "path": "^0.12.7", | ||||||
|  |     "prettier": "^2.3.2", | ||||||
|  |     "react-refresh": "^0.9.0", | ||||||
|  |     "sass-loader": "^11.1.1", | ||||||
|  |     "style-loader": "^2.0.0", | ||||||
|  |     "terser-webpack-plugin": "^5.1.4", | ||||||
|  |     "webpack": "^5.40.0", | ||||||
|  |     "webpack-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": [ | ||||||
|   | |||||||
| Before Width: | Height: | Size: 12 KiB | 
| Before Width: | Height: | Size: 29 KiB | 
| Before Width: | Height: | Size: 36 KiB | 
| Before Width: | Height: | Size: 11 KiB | 
| @@ -1,9 +0,0 @@ | |||||||
| <?xml version="1.0" encoding="utf-8"?> |  | ||||||
| <browserconfig> |  | ||||||
|     <msapplication> |  | ||||||
|         <tile> |  | ||||||
|             <square150x150logo src="/mstile-150x150.png"/> |  | ||||||
|             <TileColor>#414141</TileColor> |  | ||||||
|         </tile> |  | ||||||
|     </msapplication> |  | ||||||
| </browserconfig> |  | ||||||
							
								
								
									
										4
									
								
								public/config.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,4 @@ | |||||||
|  | { | ||||||
|  |   "DEFAULT_UCENTRALSEC_URL": "https://ucentral.dpaas.arilia.com:16001", | ||||||
|  |   "ALLOW_UCENTRALSEC_CHANGE": false | ||||||
|  | } | ||||||
| Before Width: | Height: | Size: 104 KiB | 
| Before Width: | Height: | Size: 80 KiB | 
| Before Width: | Height: | Size: 80 KiB | 
| Before Width: | Height: | Size: 75 KiB | 
| Before Width: | Height: | Size: 75 KiB | 
| Before Width: | Height: | Size: 218 KiB | 
| Before Width: | Height: | Size: 158 KiB | 
| Before Width: | Height: | Size: 140 KiB | 
| Before Width: | Height: | Size: 121 KiB | 
| Before Width: | Height: | Size: 44 KiB | 
| Before Width: | Height: | Size: 192 KiB | 
| Before Width: | Height: | Size: 197 KiB | 
| Before Width: | Height: | Size: 50 KiB | 
| Before Width: | Height: | Size: 59 KiB | 
| Before Width: | Height: | Size: 59 KiB | 
| Before Width: | Height: | Size: 51 KiB | 
| Before Width: | Height: | Size: 72 KiB | 
| Before Width: | Height: | Size: 72 KiB | 
| Before Width: | Height: | Size: 34 KiB | 
| Before Width: | Height: | Size: 98 KiB | 
| Before Width: | Height: | Size: 89 KiB | 
| Before Width: | Height: | Size: 89 KiB | 
| Before Width: | Height: | Size: 204 KiB | 
| Before Width: | Height: | Size: 159 KiB | 
| Before Width: | Height: | Size: 159 KiB | 
| Before Width: | Height: | Size: 103 KiB | 
| Before Width: | Height: | Size: 103 KiB | 
| Before Width: | Height: | Size: 103 KiB | 
| Before Width: | Height: | Size: 59 KiB | 
| Before Width: | Height: | Size: 80 KiB | 
| Before Width: | Height: | Size: 80 KiB | 
| Before Width: | Height: | Size: 32 KiB | 
| Before Width: | Height: | Size: 38 KiB | 
| Before Width: | Height: | Size: 47 KiB | 
| Before Width: | Height: | Size: 1021 B | 
| Before Width: | Height: | Size: 1.9 KiB | 
| Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 202 KiB | 
							
								
								
									
										165
									
								
								public/favicon.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,165 @@ | |||||||
|  | <?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> | ||||||
| After Width: | Height: | Size: 8.0 KiB | 
							
								
								
									
										14
									
								
								public/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,14 @@ | |||||||
|  | <!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> | ||||||
| Before Width: | Height: | Size: 9.9 KiB | 
| @@ -1,38 +0,0 @@ | |||||||
| <?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> |  | ||||||
| Before Width: | Height: | Size: 2.3 KiB | 
							
								
								
									
										39
									
								
								src/App.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,39 @@ | |||||||
|  | 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
									
									
									
									
									
								
							
							
						
						| @@ -1,45 +0,0 @@ | |||||||
| 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; |  | ||||||
							
								
								
									
										165
									
								
								src/assets/OpenWiFi_BadgeLogo_DarkGrey.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,165 @@ | |||||||
|  | <?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> | ||||||
| After Width: | Height: | Size: 8.0 KiB | 
| 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: 2.0 KiB After Width: | Height: | Size: 2.0 KiB | 
| Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB | 
| Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB | 
| Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB | 
							
								
								
									
										201
									
								
								src/assets/icons/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,201 @@ | |||||||
|  | import { | ||||||
|  |   cifUs, | ||||||
|  |   cifBr, | ||||||
|  |   cifIn, | ||||||
|  |   cifFr, | ||||||
|  |   cifEs, | ||||||
|  |   cifPl, | ||||||
|  |   cilAlignCenter, | ||||||
|  |   cilAlignLeft, | ||||||
|  |   cilAlignRight, | ||||||
|  |   cilApplicationsSettings, | ||||||
|  |   cilArrowRight, | ||||||
|  |   cilArrowTop, | ||||||
|  |   cilAsterisk, | ||||||
|  |   cilBan, | ||||||
|  |   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, | ||||||
|  |   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, | ||||||
|  | }; | ||||||
							
								
								
									
										215
									
								
								src/components/BlinkModal/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,215 @@ | |||||||
|  | import { | ||||||
|  |   CButton, | ||||||
|  |   CModal, | ||||||
|  |   CModalHeader, | ||||||
|  |   CModalTitle, | ||||||
|  |   CModalBody, | ||||||
|  |   CModalFooter, | ||||||
|  |   CSwitch, | ||||||
|  |   CCol, | ||||||
|  |   CRow, | ||||||
|  |   CFormGroup, | ||||||
|  |   CInputRadio, | ||||||
|  |   CLabel, | ||||||
|  |   CPopover, | ||||||
|  | } from '@coreui/react'; | ||||||
|  | import CIcon from '@coreui/icons-react'; | ||||||
|  | import { cilX } from '@coreui/icons'; | ||||||
|  | import React, { useState, useEffect } from 'react'; | ||||||
|  | import { useTranslation } from 'react-i18next'; | ||||||
|  | import DatePicker from 'react-widgets/DatePicker'; | ||||||
|  | import PropTypes from 'prop-types'; | ||||||
|  | import { dateToUnix } from 'utils/helper'; | ||||||
|  | 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 [isNow, setIsNow] = useState(false); | ||||||
|  |   const [waiting, setWaiting] = useState(false); | ||||||
|  |   const [chosenDate, setChosenDate] = useState(new Date().toString()); | ||||||
|  |   const [chosenPattern, setPattern] = useState('on'); | ||||||
|  |   const [result, setResult] = useState(null); | ||||||
|  |  | ||||||
|  |   const toggleNow = () => { | ||||||
|  |     setIsNow(!isNow); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   const setDate = (date) => { | ||||||
|  |     if (date) { | ||||||
|  |       setChosenDate(date.toString()); | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   useEffect(() => { | ||||||
|  |     if (show) { | ||||||
|  |       setWaiting(false); | ||||||
|  |       setChosenDate(new Date().toString()); | ||||||
|  |       setPattern('on'); | ||||||
|  |       setResult(null); | ||||||
|  |     } | ||||||
|  |   }, [show]); | ||||||
|  |  | ||||||
|  |   const doAction = () => { | ||||||
|  |     setWaiting(true); | ||||||
|  |     const utcDate = new Date(chosenDate); | ||||||
|  |  | ||||||
|  |     const parameters = { | ||||||
|  |       serialNumber: deviceSerialNumber, | ||||||
|  |       when: isNow ? 0 : dateToUnix(utcDate), | ||||||
|  |       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(() => { | ||||||
|  |         addToast({ | ||||||
|  |           title: t('common.success'), | ||||||
|  |           body: t('commands.command_success'), | ||||||
|  |           color: 'success', | ||||||
|  |           autohide: true, | ||||||
|  |         }); | ||||||
|  |         toggleModal(); | ||||||
|  |       }) | ||||||
|  |       .catch(() => { | ||||||
|  |         setResult('error'); | ||||||
|  |       }) | ||||||
|  |       .finally(() => { | ||||||
|  |         setWaiting(false); | ||||||
|  |         eventBus.dispatch('actionCompleted', { message: 'An action has been completed' }); | ||||||
|  |       }); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   return ( | ||||||
|  |     <CModal show={show} onClose={toggleModal}> | ||||||
|  |       <CModalHeader className="p-1"> | ||||||
|  |         <CModalTitle className="pl-1 pt-1">{t('blink.device_leds')}</CModalTitle> | ||||||
|  |         <div className="text-right"> | ||||||
|  |           <CPopover content={t('common.close')}> | ||||||
|  |             <CButton color="primary" variant="outline" className="ml-2" onClick={toggleModal}> | ||||||
|  |               <CIcon content={cilX} /> | ||||||
|  |             </CButton> | ||||||
|  |           </CPopover> | ||||||
|  |         </div> | ||||||
|  |       </CModalHeader> | ||||||
|  |       {result === 'success' ? ( | ||||||
|  |         <SuccessfulActionModalBody toggleModal={toggleModal} /> | ||||||
|  |       ) : ( | ||||||
|  |         <div> | ||||||
|  |           <CModalBody> | ||||||
|  |             <CFormGroup row> | ||||||
|  |               <CCol md="3"> | ||||||
|  |                 <CLabel>{t('blink.pattern')}</CLabel> | ||||||
|  |               </CCol> | ||||||
|  |               <CCol> | ||||||
|  |                 <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> | ||||||
|  |                 <CFormGroup variant="custom-radio" onClick={() => setPattern('blink')} inline> | ||||||
|  |                   <CInputRadio | ||||||
|  |                     custom | ||||||
|  |                     defaultChecked={chosenPattern === 'blink'} | ||||||
|  |                     id="radio3" | ||||||
|  |                     name="radios" | ||||||
|  |                     value="option3" | ||||||
|  |                   /> | ||||||
|  |                   <CLabel variant="custom-checkbox" htmlFor="radio3"> | ||||||
|  |                     {t('blink.blink')} | ||||||
|  |                   </CLabel> | ||||||
|  |                 </CFormGroup> | ||||||
|  |               </CCol> | ||||||
|  |             </CFormGroup> | ||||||
|  |             <CRow className="pt-1"> | ||||||
|  |               <CCol md="8"> | ||||||
|  |                 <p>{t('blink.execute_now')}</p> | ||||||
|  |               </CCol> | ||||||
|  |               <CCol> | ||||||
|  |                 <CSwitch | ||||||
|  |                   disabled={waiting} | ||||||
|  |                   color="primary" | ||||||
|  |                   defaultChecked={isNow} | ||||||
|  |                   onClick={toggleNow} | ||||||
|  |                   labelOn={t('common.yes')} | ||||||
|  |                   labelOff={t('common.no')} | ||||||
|  |                 /> | ||||||
|  |               </CCol> | ||||||
|  |             </CRow> | ||||||
|  |             <CRow hidden={isNow} className="pt-3"> | ||||||
|  |               <CCol md="4" className="pt-2"> | ||||||
|  |                 <p>{t('common.custom_date')}</p> | ||||||
|  |               </CCol> | ||||||
|  |               <CCol xs="12" md="8"> | ||||||
|  |                 <DatePicker | ||||||
|  |                   selected={new Date(chosenDate)} | ||||||
|  |                   includeTime | ||||||
|  |                   value={new Date(chosenDate)} | ||||||
|  |                   placeholder="Select custom date" | ||||||
|  |                   disabled={waiting} | ||||||
|  |                   onChange={(date) => setDate(date)} | ||||||
|  |                   min={new Date()} | ||||||
|  |                 /> | ||||||
|  |               </CCol> | ||||||
|  |             </CRow> | ||||||
|  |           </CModalBody> | ||||||
|  |           <CModalFooter> | ||||||
|  |             <LoadingButton | ||||||
|  |               label={isNow ? t('blink.set_leds') : t('common.schedule')} | ||||||
|  |               isLoadingLabel={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; | ||||||
| @@ -1,50 +0,0 @@ | |||||||
| import React from 'react'; |  | ||||||
| import { Button, IconButton, Tooltip, useBreakpoint } from '@chakra-ui/react'; |  | ||||||
| import { Warning } from 'phosphor-react'; |  | ||||||
| import { useTranslation } from 'react-i18next'; |  | ||||||
| import { ThemeProps } from 'models/Theme'; |  | ||||||
|  |  | ||||||
| export interface AlertButtonProps extends ThemeProps { |  | ||||||
|   onClick: () => void; |  | ||||||
|   isDisabled?: boolean; |  | ||||||
|   isLoading?: boolean; |  | ||||||
|   isCompact?: boolean; |  | ||||||
|   label?: string; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| const _AlertButton: React.FC<AlertButtonProps> = ({ onClick, isDisabled, isLoading, isCompact, label, ...props }) => { |  | ||||||
|   const { t } = useTranslation(); |  | ||||||
|   const breakpoint = useBreakpoint(); |  | ||||||
|  |  | ||||||
|   if (!isCompact && breakpoint !== 'base' && breakpoint !== 'sm') { |  | ||||||
|     return ( |  | ||||||
|       <Button |  | ||||||
|         colorScheme="red" |  | ||||||
|         type="button" |  | ||||||
|         onClick={onClick} |  | ||||||
|         rightIcon={<Warning size={20} />} |  | ||||||
|         isLoading={isLoading} |  | ||||||
|         isDisabled={isDisabled} |  | ||||||
|         {...props} |  | ||||||
|       > |  | ||||||
|         {label ?? t('common.alert')} |  | ||||||
|       </Button> |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
|   return ( |  | ||||||
|     <Tooltip label={label ?? t('common.alert')}> |  | ||||||
|       <IconButton |  | ||||||
|         aria-label="alert-button" |  | ||||||
|         colorScheme="red" |  | ||||||
|         type="button" |  | ||||||
|         onClick={onClick} |  | ||||||
|         icon={<Warning size={20} />} |  | ||||||
|         isLoading={isLoading} |  | ||||||
|         isDisabled={isDisabled} |  | ||||||
|         {...props} |  | ||||||
|       /> |  | ||||||
|     </Tooltip> |  | ||||||
|   ); |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| export const AlertButton = React.memo(_AlertButton); |  | ||||||
| @@ -1,28 +0,0 @@ | |||||||
| 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); |  | ||||||
| @@ -1,49 +0,0 @@ | |||||||
| import React from 'react'; |  | ||||||
| import { Button, IconButton, Tooltip, useBreakpoint, SpaceProps } from '@chakra-ui/react'; |  | ||||||
| import { Plus } from 'phosphor-react'; |  | ||||||
| import { useTranslation } from 'react-i18next'; |  | ||||||
|  |  | ||||||
| export interface CreateButtonProps extends SpaceProps { |  | ||||||
|   onClick?: () => void; |  | ||||||
|   isDisabled?: boolean; |  | ||||||
|   isLoading?: boolean; |  | ||||||
|   isCompact?: boolean; |  | ||||||
|   label?: string; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| const _CreateButton: React.FC<CreateButtonProps> = ({ onClick, isDisabled, isLoading, isCompact, label, ...props }) => { |  | ||||||
|   const { t } = useTranslation(); |  | ||||||
|   const breakpoint = useBreakpoint(); |  | ||||||
|  |  | ||||||
|   if (!isCompact && breakpoint !== 'base' && breakpoint !== 'sm') { |  | ||||||
|     return ( |  | ||||||
|       <Button |  | ||||||
|         colorScheme="blue" |  | ||||||
|         type="button" |  | ||||||
|         onClick={onClick} |  | ||||||
|         rightIcon={<Plus size={20} />} |  | ||||||
|         isLoading={isLoading} |  | ||||||
|         isDisabled={isDisabled} |  | ||||||
|         {...props} |  | ||||||
|       > |  | ||||||
|         {label ?? t('common.create')} |  | ||||||
|       </Button> |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
|   return ( |  | ||||||
|     <Tooltip label={label ?? t('common.create')}> |  | ||||||
|       <IconButton |  | ||||||
|         aria-label="Create" |  | ||||||
|         colorScheme="blue" |  | ||||||
|         type="button" |  | ||||||
|         onClick={onClick} |  | ||||||
|         icon={<Plus size={20} />} |  | ||||||
|         isLoading={isLoading} |  | ||||||
|         isDisabled={isDisabled} |  | ||||||
|         {...props} |  | ||||||
|       /> |  | ||||||
|     </Tooltip> |  | ||||||
|   ); |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| export const CreateButton = React.memo(_CreateButton); |  | ||||||
| @@ -1,61 +0,0 @@ | |||||||
| import React from 'react'; |  | ||||||
| import { Button, IconButton, Tooltip, useBreakpoint } from '@chakra-ui/react'; |  | ||||||
| import { Trash } from 'phosphor-react'; |  | ||||||
| import { useTranslation } from 'react-i18next'; |  | ||||||
|  |  | ||||||
| export interface DeleteButtonProps { |  | ||||||
|   onClick: () => void; |  | ||||||
|   isDisabled?: boolean; |  | ||||||
|   isLoading?: boolean; |  | ||||||
|   isCompact?: boolean; |  | ||||||
|   label?: string; |  | ||||||
|   ml?: string | number; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| const _DeleteButton: React.FC<DeleteButtonProps> = ({ |  | ||||||
|   onClick, |  | ||||||
|   isDisabled, |  | ||||||
|   isLoading, |  | ||||||
|   isCompact, |  | ||||||
|   label, |  | ||||||
|   ml, |  | ||||||
|   ...props |  | ||||||
| }) => { |  | ||||||
|   const { t } = useTranslation(); |  | ||||||
|   const breakpoint = useBreakpoint(); |  | ||||||
|  |  | ||||||
|   if (!isCompact && breakpoint !== 'base' && breakpoint !== 'sm') { |  | ||||||
|     return ( |  | ||||||
|       <Button |  | ||||||
|         type="button" |  | ||||||
|         colorScheme="red" |  | ||||||
|         onClick={onClick} |  | ||||||
|         rightIcon={<Trash size={20} />} |  | ||||||
|         isLoading={isLoading} |  | ||||||
|         isDisabled={isDisabled} |  | ||||||
|         ml={ml} |  | ||||||
|         {...props} |  | ||||||
|       > |  | ||||||
|         {label ?? t('crud.delete')} |  | ||||||
|       </Button> |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   return ( |  | ||||||
|     <Tooltip label={label ?? t('crud.delete')}> |  | ||||||
|       <IconButton |  | ||||||
|         colorScheme="red" |  | ||||||
|         aria-label="delete" |  | ||||||
|         type="button" |  | ||||||
|         onClick={onClick} |  | ||||||
|         icon={<Trash size={20} />} |  | ||||||
|         isLoading={isLoading} |  | ||||||
|         isDisabled={isDisabled} |  | ||||||
|         ml={ml} |  | ||||||
|         {...props} |  | ||||||
|       /> |  | ||||||
|     </Tooltip> |  | ||||||
|   ); |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| export const DeleteButton = React.memo(_DeleteButton); |  | ||||||
| @@ -1,82 +0,0 @@ | |||||||
| import * as React from 'react'; |  | ||||||
| import { MenuItem, useToast } from '@chakra-ui/react'; |  | ||||||
| import { useTranslation } from 'react-i18next'; |  | ||||||
| import { useControllerStore } from 'contexts/ControllerSocketProvider/useStore'; |  | ||||||
| import { useRebootDevice } from 'hooks/Network/Devices'; |  | ||||||
| import { useMutationResult } from 'hooks/useMutationResult'; |  | ||||||
| import { AxiosError } from 'models/Axios'; |  | ||||||
| import { GatewayDevice } from 'models/Device'; |  | ||||||
|  |  | ||||||
| type Props = { |  | ||||||
|   device: GatewayDevice; |  | ||||||
|   refresh: () => void; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| const RebootMenuItem = ({ device, refresh }: Props) => { |  | ||||||
|   const { t } = useTranslation(); |  | ||||||
|   const toast = useToast(); |  | ||||||
|   const addEventListeners = useControllerStore((state) => state.addEventListeners); |  | ||||||
|   const { mutateAsync: reboot } = useRebootDevice({ serialNumber: device.serialNumber }); |  | ||||||
|   const { onSuccess: onRebootSuccess, onError: onRebootError } = useMutationResult({ |  | ||||||
|     objName: t('devices.one'), |  | ||||||
|     operationType: 'reboot', |  | ||||||
|     refresh: () => { |  | ||||||
|       refresh(); |  | ||||||
|       addEventListeners([ |  | ||||||
|         { |  | ||||||
|           id: `device-connection-${device.serialNumber}`, |  | ||||||
|           type: 'DEVICE_CONNECTION', |  | ||||||
|           serialNumber: device.serialNumber, |  | ||||||
|           callback: () => { |  | ||||||
|             const id = `device-connection-notification-${device.serialNumber}`; |  | ||||||
|  |  | ||||||
|             if (!toast.isActive(id)) { |  | ||||||
|               toast({ |  | ||||||
|                 id, |  | ||||||
|                 title: t('common.success'), |  | ||||||
|                 description: t('controller.devices.finished_reboot', { serialNumber: device.serialNumber }), |  | ||||||
|                 status: 'success', |  | ||||||
|                 duration: 5000, |  | ||||||
|                 isClosable: true, |  | ||||||
|                 position: 'top-right', |  | ||||||
|               }); |  | ||||||
|             } |  | ||||||
|           }, |  | ||||||
|         }, |  | ||||||
|         { |  | ||||||
|           id: `device-disconnected-${device.serialNumber}`, |  | ||||||
|           type: 'DEVICE_DISCONNECTION', |  | ||||||
|           serialNumber: device.serialNumber, |  | ||||||
|           callback: () => { |  | ||||||
|             const id = `device-disconnection-notification-${device.serialNumber}`; |  | ||||||
|  |  | ||||||
|             if (!toast.isActive(id)) { |  | ||||||
|               toast({ |  | ||||||
|                 id, |  | ||||||
|                 title: t('common.success'), |  | ||||||
|                 description: t('controller.devices.started_reboot', { serialNumber: device.serialNumber }), |  | ||||||
|                 status: 'success', |  | ||||||
|                 duration: 5000, |  | ||||||
|                 isClosable: true, |  | ||||||
|                 position: 'top-right', |  | ||||||
|               }); |  | ||||||
|             } |  | ||||||
|           }, |  | ||||||
|         }, |  | ||||||
|       ]); |  | ||||||
|     }, |  | ||||||
|   }); |  | ||||||
|   const handleRebootClick = () => |  | ||||||
|     reboot(undefined, { |  | ||||||
|       onSuccess: () => { |  | ||||||
|         onRebootSuccess(); |  | ||||||
|       }, |  | ||||||
|       onError: (e) => { |  | ||||||
|         onRebootError(e as AxiosError); |  | ||||||
|       }, |  | ||||||
|     }); |  | ||||||
|  |  | ||||||
|   return <MenuItem onClick={handleRebootClick}>{t('commands.reboot')}</MenuItem>; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| export default RebootMenuItem; |  | ||||||
| @@ -1,195 +0,0 @@ | |||||||
| import React from 'react'; |  | ||||||
| import { Button, IconButton, Menu, MenuButton, MenuItem, MenuList, Spinner, Tooltip, useToast } from '@chakra-ui/react'; |  | ||||||
| import axios from 'axios'; |  | ||||||
| import { Wrench } from 'phosphor-react'; |  | ||||||
| import { useTranslation } from 'react-i18next'; |  | ||||||
| import RebootMenuItem from './RebootButton'; |  | ||||||
| import { useControllerStore } from 'contexts/ControllerSocketProvider/useStore'; |  | ||||||
| import { useBlinkDevice, useGetDeviceRtty } from 'hooks/Network/Devices'; |  | ||||||
| import { useUpdateDeviceToLatest } from 'hooks/Network/Firmware'; |  | ||||||
| import { useMutationResult } from 'hooks/useMutationResult'; |  | ||||||
| import { GatewayDevice } from 'models/Device'; |  | ||||||
|  |  | ||||||
| interface Props { |  | ||||||
|   device: GatewayDevice; |  | ||||||
|   refresh: () => void; |  | ||||||
|   isDisabled?: boolean; |  | ||||||
|   onOpenScan: (serialNumber: string) => void; |  | ||||||
|   onOpenFactoryReset: (serialNumber: string) => void; |  | ||||||
|   onOpenUpgradeModal: (serialNumber: string) => void; |  | ||||||
|   onOpenTrace: (serialNumber: string) => void; |  | ||||||
|   onOpenEventQueue: (serialNumber: string) => void; |  | ||||||
|   onOpenConfigureModal: (serialNumber: string) => void; |  | ||||||
|   onOpenTelemetryModal: (serialNumber: string) => void; |  | ||||||
|   onOpenScriptModal: (device: GatewayDevice) => void; |  | ||||||
|   size?: 'sm' | 'md' | 'lg'; |  | ||||||
|   isCompact?: boolean; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| const DeviceActionDropdown = ({ |  | ||||||
|   device, |  | ||||||
|   refresh, |  | ||||||
|   isDisabled, |  | ||||||
|   onOpenScan, |  | ||||||
|   onOpenFactoryReset, |  | ||||||
|   onOpenTrace, |  | ||||||
|   onOpenUpgradeModal, |  | ||||||
|   onOpenEventQueue, |  | ||||||
|   onOpenTelemetryModal, |  | ||||||
|   onOpenConfigureModal, |  | ||||||
|   onOpenScriptModal, |  | ||||||
|   size, |  | ||||||
|   isCompact, |  | ||||||
| }: Props) => { |  | ||||||
|   const { t } = useTranslation(); |  | ||||||
|   const toast = useToast(); |  | ||||||
|   const addEventListeners = useControllerStore((state) => state.addEventListeners); |  | ||||||
|   const { refetch: getRtty, isInitialLoading: isRtty } = useGetDeviceRtty({ |  | ||||||
|     serialNumber: device.serialNumber, |  | ||||||
|     extraId: 'inventory-modal', |  | ||||||
|   }); |  | ||||||
|   const { mutateAsync: blink } = useBlinkDevice({ serialNumber: device.serialNumber }); |  | ||||||
|   const { onSuccess: onBlinkSuccess, onError: onBlinkError } = useMutationResult({ |  | ||||||
|     objName: t('devices.one'), |  | ||||||
|     operationType: 'blink', |  | ||||||
|     refresh, |  | ||||||
|   }); |  | ||||||
|   const updateToLatest = useUpdateDeviceToLatest({ serialNumber: device.serialNumber, compatible: device.compatible }); |  | ||||||
|  |  | ||||||
|   const handleBlinkClick = () => { |  | ||||||
|     blink(undefined, { |  | ||||||
|       onError: (e) => { |  | ||||||
|         if (axios.isAxiosError(e)) onBlinkError(e); |  | ||||||
|       }, |  | ||||||
|     }); |  | ||||||
|     onBlinkSuccess(); |  | ||||||
|   }; |  | ||||||
|   const handleOpenScan = () => onOpenScan(device.serialNumber); |  | ||||||
|   const handleOpenFactoryReset = () => onOpenFactoryReset(device.serialNumber); |  | ||||||
|   const handleOpenUpgrade = () => onOpenUpgradeModal(device.serialNumber); |  | ||||||
|   const handleOpenTrace = () => onOpenTrace(device.serialNumber); |  | ||||||
|   const handleOpenQueue = () => onOpenEventQueue(device.serialNumber); |  | ||||||
|   const handleOpenConfigure = () => onOpenConfigureModal(device.serialNumber); |  | ||||||
|   const handleOpenTelemetry = () => onOpenTelemetryModal(device.serialNumber); |  | ||||||
|   const handleOpenScript = () => onOpenScriptModal(device); |  | ||||||
|   const handleUpdateToLatest = () => { |  | ||||||
|     updateToLatest.mutate( |  | ||||||
|       { keepRedirector: true }, |  | ||||||
|       { |  | ||||||
|         onSuccess: () => { |  | ||||||
|           toast({ |  | ||||||
|             id: `upgrade-to-latest-start-${device.serialNumber}`, |  | ||||||
|             title: t('common.success'), |  | ||||||
|             description: t('controller.devices.sent_upgrade_to_latest'), |  | ||||||
|             status: 'success', |  | ||||||
|             duration: 5000, |  | ||||||
|             isClosable: true, |  | ||||||
|             position: 'top-right', |  | ||||||
|           }); |  | ||||||
|           addEventListeners([ |  | ||||||
|             { |  | ||||||
|               id: `device-connection-upgrade-${device.serialNumber}`, |  | ||||||
|               type: 'DEVICE_CONNECTION', |  | ||||||
|               serialNumber: device.serialNumber, |  | ||||||
|               callback: () => { |  | ||||||
|                 const id = `device-connection-upgrade-notification-${device.serialNumber}`; |  | ||||||
|  |  | ||||||
|                 if (!toast.isActive(id)) { |  | ||||||
|                   toast({ |  | ||||||
|                     id, |  | ||||||
|                     title: t('common.success'), |  | ||||||
|                     description: t('controller.devices.finished_upgrade', { serialNumber: device.serialNumber }), |  | ||||||
|                     status: 'success', |  | ||||||
|                     duration: 5000, |  | ||||||
|                     isClosable: true, |  | ||||||
|                     position: 'top-right', |  | ||||||
|                   }); |  | ||||||
|                 } |  | ||||||
|               }, |  | ||||||
|             }, |  | ||||||
|             { |  | ||||||
|               id: `device-disconnected-upgrade-${device.serialNumber}`, |  | ||||||
|               type: 'DEVICE_DISCONNECTION', |  | ||||||
|               serialNumber: device.serialNumber, |  | ||||||
|               callback: () => { |  | ||||||
|                 const id = `device-disconnection-upgrade-notification-${device.serialNumber}`; |  | ||||||
|  |  | ||||||
|                 if (!toast.isActive(id)) { |  | ||||||
|                   toast({ |  | ||||||
|                     id, |  | ||||||
|                     title: t('common.success'), |  | ||||||
|                     description: t('controller.devices.started_upgrade', { serialNumber: device.serialNumber }), |  | ||||||
|                     status: 'success', |  | ||||||
|                     duration: 5000, |  | ||||||
|                     isClosable: true, |  | ||||||
|                     position: 'top-right', |  | ||||||
|                   }); |  | ||||||
|                 } |  | ||||||
|               }, |  | ||||||
|             }, |  | ||||||
|           ]); |  | ||||||
|         }, |  | ||||||
|         onError: (e) => { |  | ||||||
|           if (axios.isAxiosError(e)) { |  | ||||||
|             toast({ |  | ||||||
|               id: `upgrade-to-latest-error-${device.serialNumber}`, |  | ||||||
|               title: t('common.error'), |  | ||||||
|               description: e?.response?.data?.ErrorDescription, |  | ||||||
|               status: 'error', |  | ||||||
|               duration: 5000, |  | ||||||
|               isClosable: true, |  | ||||||
|               position: 'top-right', |  | ||||||
|             }); |  | ||||||
|           } |  | ||||||
|         }, |  | ||||||
|       }, |  | ||||||
|     ); |  | ||||||
|   }; |  | ||||||
|   const handleConnectClick = () => getRtty(); |  | ||||||
|  |  | ||||||
|   return ( |  | ||||||
|     <Menu> |  | ||||||
|       <Tooltip label={t('commands.other')}> |  | ||||||
|         {size === undefined || isCompact ? ( |  | ||||||
|           <MenuButton |  | ||||||
|             as={IconButton} |  | ||||||
|             aria-label="Commands" |  | ||||||
|             icon={isRtty ? <Spinner /> : <Wrench size={20} />} |  | ||||||
|             size={size ?? 'sm'} |  | ||||||
|             isDisabled={isDisabled} |  | ||||||
|             ml={2} |  | ||||||
|           /> |  | ||||||
|         ) : ( |  | ||||||
|           <MenuButton |  | ||||||
|             as={Button} |  | ||||||
|             aria-label="Commands" |  | ||||||
|             rightIcon={isRtty ? <Spinner /> : <Wrench size={20} />} |  | ||||||
|             size={size ?? 'sm'} |  | ||||||
|             isDisabled={isDisabled} |  | ||||||
|             ml={2} |  | ||||||
|           > |  | ||||||
|             {t('commands.other')} |  | ||||||
|           </MenuButton> |  | ||||||
|         )} |  | ||||||
|       </Tooltip> |  | ||||||
|       <MenuList> |  | ||||||
|         <MenuItem onClick={handleBlinkClick}>{t('commands.blink')}</MenuItem> |  | ||||||
|         <MenuItem onClick={handleOpenConfigure}>{t('controller.configure.title')}</MenuItem> |  | ||||||
|         <MenuItem onClick={handleConnectClick}>{t('commands.connect')}</MenuItem> |  | ||||||
|         <MenuItem onClick={handleOpenQueue}>{t('controller.queue.title')}</MenuItem> |  | ||||||
|         <MenuItem onClick={handleOpenFactoryReset}>{t('commands.factory_reset')}</MenuItem> |  | ||||||
|         <MenuItem onClick={handleOpenUpgrade}>{t('commands.firmware_upgrade')}</MenuItem> |  | ||||||
|         <RebootMenuItem device={device} refresh={refresh} /> |  | ||||||
|         <MenuItem onClick={handleOpenTelemetry}>{t('controller.telemetry.title')}</MenuItem> |  | ||||||
|         <MenuItem onClick={handleOpenScript}>{t('script.one')}</MenuItem> |  | ||||||
|         <MenuItem onClick={handleOpenTrace}>{t('controller.devices.trace')}</MenuItem> |  | ||||||
|         <MenuItem onClick={handleUpdateToLatest} hidden> |  | ||||||
|           {t('premium.toolbox.upgrade_to_latest')} |  | ||||||
|         </MenuItem> |  | ||||||
|         <MenuItem onClick={handleOpenScan}>{t('commands.wifiscan')}</MenuItem> |  | ||||||
|       </MenuList> |  | ||||||
|     </Menu> |  | ||||||
|   ); |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| export default React.memo(DeviceActionDropdown); |  | ||||||
| @@ -1,46 +0,0 @@ | |||||||
| import React from 'react'; |  | ||||||
| import { IconButton, Button, Tooltip, useBreakpoint } from '@chakra-ui/react'; |  | ||||||
| import { Pen } from 'phosphor-react'; |  | ||||||
|  |  | ||||||
| export interface EditButtonProps { |  | ||||||
|   onClick: () => void; |  | ||||||
|   isDisabled?: boolean; |  | ||||||
|   isLoading?: boolean; |  | ||||||
|   isCompact?: boolean; |  | ||||||
|   label?: string; |  | ||||||
|   ml?: string | number; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| const _EditButton: React.FC<EditButtonProps> = ({ onClick, label, isDisabled, isLoading, isCompact, ...props }) => { |  | ||||||
|   const breakpoint = useBreakpoint(); |  | ||||||
|  |  | ||||||
|   if (!isCompact && breakpoint !== 'base' && breakpoint !== 'sm') { |  | ||||||
|     return ( |  | ||||||
|       <Button |  | ||||||
|         colorScheme="gray" |  | ||||||
|         onClick={onClick} |  | ||||||
|         rightIcon={<Pen size={20} />} |  | ||||||
|         isLoading={isLoading} |  | ||||||
|         isDisabled={isDisabled} |  | ||||||
|         {...props} |  | ||||||
|       > |  | ||||||
|         {label} |  | ||||||
|       </Button> |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
|   return ( |  | ||||||
|     <Tooltip label={label}> |  | ||||||
|       <IconButton |  | ||||||
|         aria-label="edit" |  | ||||||
|         colorScheme="gray" |  | ||||||
|         onClick={onClick} |  | ||||||
|         icon={<Pen size={20} />} |  | ||||||
|         isLoading={isLoading} |  | ||||||
|         isDisabled={isDisabled} |  | ||||||
|         {...props} |  | ||||||
|       /> |  | ||||||
|     </Tooltip> |  | ||||||
|   ); |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| export const EditButton = React.memo(_EditButton); |  | ||||||
| @@ -1,79 +0,0 @@ | |||||||
| 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); |  | ||||||
| @@ -1,62 +0,0 @@ | |||||||
| import React from 'react'; |  | ||||||
| import { Button, IconButton, ThemeTypings, Tooltip, useBreakpoint } from '@chakra-ui/react'; |  | ||||||
| import { ArrowsClockwise } from 'phosphor-react'; |  | ||||||
| import { useTranslation } from 'react-i18next'; |  | ||||||
|  |  | ||||||
| export interface RefreshButtonProps { |  | ||||||
|   onClick: () => void; |  | ||||||
|   isDisabled?: boolean; |  | ||||||
|   isFetching?: boolean; |  | ||||||
|   isCompact?: boolean; |  | ||||||
|   ml?: string | number; |  | ||||||
|   size?: 'sm' | 'md' | 'lg'; |  | ||||||
|   colorScheme?: ThemeTypings['colorSchemes']; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| const _RefreshButton: React.FC<RefreshButtonProps> = ({ |  | ||||||
|   onClick, |  | ||||||
|   isDisabled, |  | ||||||
|   isFetching, |  | ||||||
|   isCompact, |  | ||||||
|   ml, |  | ||||||
|   size, |  | ||||||
|   ...props |  | ||||||
| }) => { |  | ||||||
|   const { t } = useTranslation(); |  | ||||||
|   const breakpoint = useBreakpoint(); |  | ||||||
|  |  | ||||||
|   if (!isCompact && breakpoint !== 'base' && breakpoint !== 'sm') { |  | ||||||
|     return ( |  | ||||||
|       <Button |  | ||||||
|         minWidth="112px" |  | ||||||
|         colorScheme="gray" |  | ||||||
|         onClick={onClick} |  | ||||||
|         rightIcon={<ArrowsClockwise size={20} />} |  | ||||||
|         isDisabled={isDisabled} |  | ||||||
|         isLoading={isFetching} |  | ||||||
|         ml={ml} |  | ||||||
|         size={size} |  | ||||||
|         {...props} |  | ||||||
|       > |  | ||||||
|         {t('common.refresh')} |  | ||||||
|       </Button> |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   return ( |  | ||||||
|     <Tooltip label={t('common.refresh')}> |  | ||||||
|       <IconButton |  | ||||||
|         aria-label="refresh" |  | ||||||
|         colorScheme="gray" |  | ||||||
|         onClick={onClick} |  | ||||||
|         icon={<ArrowsClockwise size={20} />} |  | ||||||
|         isDisabled={isDisabled} |  | ||||||
|         isLoading={isFetching} |  | ||||||
|         ml={ml} |  | ||||||
|         {...props} |  | ||||||
|       /> |  | ||||||
|     </Tooltip> |  | ||||||
|   ); |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| export const RefreshButton = React.memo(_RefreshButton); |  | ||||||
| @@ -1,59 +0,0 @@ | |||||||
| import React from 'react'; |  | ||||||
| import { Button, IconButton, SpaceProps, Tooltip, useBreakpoint } from '@chakra-ui/react'; |  | ||||||
|  |  | ||||||
| export interface ResponsiveButtonProps extends SpaceProps { |  | ||||||
|   onClick: () => void; |  | ||||||
|   isDisabled?: boolean; |  | ||||||
|   isLoading?: boolean; |  | ||||||
|   isCompact?: boolean; |  | ||||||
|   color: string; |  | ||||||
|   label: string; |  | ||||||
|   icon?: React.ReactElement; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| const _ResponsiveButton: React.FC<ResponsiveButtonProps> = ({ |  | ||||||
|   onClick, |  | ||||||
|   isDisabled, |  | ||||||
|   isLoading, |  | ||||||
|   isCompact, |  | ||||||
|   color, |  | ||||||
|   label, |  | ||||||
|   icon, |  | ||||||
|   ...props |  | ||||||
| }) => { |  | ||||||
|   const breakpoint = useBreakpoint(); |  | ||||||
|  |  | ||||||
|   if (!isCompact && breakpoint !== 'base' && breakpoint !== 'sm') { |  | ||||||
|     return ( |  | ||||||
|       <Button |  | ||||||
|         colorScheme={color} |  | ||||||
|         type="button" |  | ||||||
|         onClick={onClick} |  | ||||||
|         // @ts-ignore |  | ||||||
|         rightIcon={icon} |  | ||||||
|         isLoading={isLoading} |  | ||||||
|         isDisabled={isDisabled} |  | ||||||
|         {...props} |  | ||||||
|       > |  | ||||||
|         {label} |  | ||||||
|       </Button> |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
|   return ( |  | ||||||
|     <Tooltip label={label}> |  | ||||||
|       <IconButton |  | ||||||
|         aria-label={label} |  | ||||||
|         colorScheme={color} |  | ||||||
|         type="button" |  | ||||||
|         onClick={onClick} |  | ||||||
|         // @ts-ignore |  | ||||||
|         icon={icon} |  | ||||||
|         isLoading={isLoading} |  | ||||||
|         isDisabled={isDisabled} |  | ||||||
|         {...props} |  | ||||||
|       /> |  | ||||||
|     </Tooltip> |  | ||||||
|   ); |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| export const ResponsiveButton = React.memo(_ResponsiveButton); |  | ||||||
| @@ -1,60 +0,0 @@ | |||||||
| import React from 'react'; |  | ||||||
| import { Button, IconButton, Tooltip, useBreakpoint } from '@chakra-ui/react'; |  | ||||||
| import { FloppyDisk } from 'phosphor-react'; |  | ||||||
| import { useTranslation } from 'react-i18next'; |  | ||||||
|  |  | ||||||
| export interface SaveButtonProps |  | ||||||
|   extends React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement> { |  | ||||||
|   onClick: () => void; |  | ||||||
|   isDisabled?: boolean; |  | ||||||
|   isLoading?: boolean; |  | ||||||
|   isCompact?: boolean; |  | ||||||
|   isDirty?: boolean; |  | ||||||
|   dirtyCheck?: boolean; |  | ||||||
|   ml?: string | number; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| const _SaveButton: React.FC<SaveButtonProps> = ({ |  | ||||||
|   onClick, |  | ||||||
|   isDisabled, |  | ||||||
|   isLoading, |  | ||||||
|   isCompact, |  | ||||||
|   isDirty, |  | ||||||
|   dirtyCheck, |  | ||||||
|   ...props |  | ||||||
| }) => { |  | ||||||
|   const { t } = useTranslation(); |  | ||||||
|   const breakpoint = useBreakpoint(); |  | ||||||
|  |  | ||||||
|   if (!isCompact && breakpoint !== 'base' && breakpoint !== 'sm') { |  | ||||||
|     return ( |  | ||||||
|       <Button |  | ||||||
|         colorScheme="blue" |  | ||||||
|         type="submit" |  | ||||||
|         onClick={onClick} |  | ||||||
|         rightIcon={<FloppyDisk size={20} />} |  | ||||||
|         isLoading={isLoading} |  | ||||||
|         isDisabled={isDisabled || (dirtyCheck && !isDirty)} |  | ||||||
|         {...props} |  | ||||||
|       > |  | ||||||
|         {t('common.save')} |  | ||||||
|       </Button> |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
|   return ( |  | ||||||
|     <Tooltip label={t('common.save')}> |  | ||||||
|       <IconButton |  | ||||||
|         aria-label="save" |  | ||||||
|         colorScheme="blue" |  | ||||||
|         type="submit" |  | ||||||
|         onClick={onClick} |  | ||||||
|         icon={<FloppyDisk size={20} />} |  | ||||||
|         isLoading={isLoading} |  | ||||||
|         isDisabled={isDisabled || (dirtyCheck && !isDirty)} |  | ||||||
|         {...props} |  | ||||||
|       /> |  | ||||||
|     </Tooltip> |  | ||||||
|   ); |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| export const SaveButton = React.memo(_SaveButton); |  | ||||||
| @@ -1,78 +0,0 @@ | |||||||
| 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); |  | ||||||
| @@ -1,83 +0,0 @@ | |||||||
| import React from 'react'; |  | ||||||
| import { Button, IconButton, Tooltip, useBreakpoint, useDisclosure } from '@chakra-ui/react'; |  | ||||||
| import { Pencil, X } from 'phosphor-react'; |  | ||||||
| import { useTranslation } from 'react-i18next'; |  | ||||||
| import { ConfirmCloseAlertModal } from '../../Modals/ConfirmCloseAlert'; |  | ||||||
|  |  | ||||||
| export interface ToggleEditButtonProps { |  | ||||||
|   toggleEdit: () => void; |  | ||||||
|   isDisabled?: boolean; |  | ||||||
|   isLoading?: boolean; |  | ||||||
|   isCompact?: boolean; |  | ||||||
|   isEditing: boolean; |  | ||||||
|   isDirty?: boolean; |  | ||||||
|   ml?: string | number; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| const _ToggleEditButton: React.FC<ToggleEditButtonProps> = ({ |  | ||||||
|   toggleEdit, |  | ||||||
|   isEditing, |  | ||||||
|   isDirty, |  | ||||||
|   isDisabled, |  | ||||||
|   isLoading, |  | ||||||
|   isCompact, |  | ||||||
|   ml, |  | ||||||
|   ...props |  | ||||||
| }) => { |  | ||||||
|   const { t } = useTranslation(); |  | ||||||
|   const breakpoint = useBreakpoint(); |  | ||||||
|   const { isOpen: showConfirm, onOpen: openConfirm, onClose: closeConfirm } = useDisclosure(); |  | ||||||
|  |  | ||||||
|   const toggle = () => { |  | ||||||
|     if (isEditing && isDirty) { |  | ||||||
|       openConfirm(); |  | ||||||
|     } else { |  | ||||||
|       toggleEdit(); |  | ||||||
|     } |  | ||||||
|   }; |  | ||||||
|  |  | ||||||
|   const closeCancelAndForm = () => { |  | ||||||
|     closeConfirm(); |  | ||||||
|     toggleEdit(); |  | ||||||
|   }; |  | ||||||
|  |  | ||||||
|   if (!isCompact && breakpoint !== 'base' && breakpoint !== 'sm') { |  | ||||||
|     return ( |  | ||||||
|       <> |  | ||||||
|         <Button |  | ||||||
|           colorScheme="gray" |  | ||||||
|           type="button" |  | ||||||
|           onClick={toggle} |  | ||||||
|           rightIcon={isEditing ? <X size={20} /> : <Pencil size={20} />} |  | ||||||
|           isLoading={isLoading} |  | ||||||
|           isDisabled={isDisabled} |  | ||||||
|           ml={ml} |  | ||||||
|           {...props} |  | ||||||
|         > |  | ||||||
|           {isEditing ? t('common.stop_editing') : t('common.edit')} |  | ||||||
|         </Button> |  | ||||||
|         <ConfirmCloseAlertModal isOpen={showConfirm} confirm={closeCancelAndForm} cancel={closeConfirm} /> |  | ||||||
|       </> |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
|   return ( |  | ||||||
|     <> |  | ||||||
|       <Tooltip label={isEditing ? t('common.stop_editing') : t('common.edit')}> |  | ||||||
|         <IconButton |  | ||||||
|           aria-label="toggle-edit" |  | ||||||
|           colorScheme="gray" |  | ||||||
|           type="button" |  | ||||||
|           onClick={toggle} |  | ||||||
|           icon={isEditing ? <X size={20} /> : <Pencil size={20} />} |  | ||||||
|           isLoading={isLoading} |  | ||||||
|           isDisabled={isDisabled} |  | ||||||
|           ml={ml} |  | ||||||
|           {...props} |  | ||||||
|         /> |  | ||||||
|       </Tooltip> |  | ||||||
|       <ConfirmCloseAlertModal isOpen={showConfirm} confirm={closeCancelAndForm} cancel={closeConfirm} /> |  | ||||||
|     </> |  | ||||||
|   ); |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| export const ToggleEditButton = React.memo(_ToggleEditButton); |  | ||||||