From 7166eb6e2ff9be2cbf50a12a756bea88800a2270 Mon Sep 17 00:00:00 2001 From: Maycon Santos Date: Mon, 5 Sep 2022 09:08:51 +0200 Subject: [PATCH] Network Routes page (#71) Add Network routes page to interact with the routes API endpoint of our management --- package-lock.json | 553 ++++++++++++++++----------------- package.json | 1 + src/App.tsx | 2 + src/components/Navbar.tsx | 1 + src/components/RouteUpdate.tsx | 322 +++++++++++++++++++ src/store/index.ts | 2 + src/store/root-action.ts | 4 +- src/store/root-reducer.ts | 4 +- src/store/route/actions.ts | 34 ++ src/store/route/index.ts | 7 + src/store/route/reducer.ts | 89 ++++++ src/store/route/sagas.ts | 139 +++++++++ src/store/route/service.ts | 32 ++ src/store/route/types.ts | 11 + src/views/Peers.tsx | 244 ++++++++++----- src/views/Routes.tsx | 412 ++++++++++++++++++++++++ 16 files changed, 1493 insertions(+), 364 deletions(-) create mode 100644 src/components/RouteUpdate.tsx create mode 100644 src/store/route/actions.ts create mode 100644 src/store/route/index.ts create mode 100644 src/store/route/reducer.ts create mode 100644 src/store/route/sagas.ts create mode 100644 src/store/route/service.ts create mode 100644 src/store/route/types.ts create mode 100644 src/views/Routes.tsx diff --git a/package-lock.json b/package-lock.json index 33af1f3..0a0f914 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ "antd": "^4.20.6", "autoprefixer": "^10.4.4", "axios": "^0.27.2", + "cidr-regex": "^3.1.1", "copyfiles": "^2.4.1", "heroicons": "^1.0.6", "highlight.js": "^11.2.0", @@ -2339,6 +2340,21 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/@eslint/eslintrc/node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -2369,6 +2385,11 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, "node_modules/@eslint/eslintrc/node_modules/type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -4648,13 +4669,13 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", "dependencies": { "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", "uri-js": "^4.2.2" }, "funding": { @@ -4678,34 +4699,6 @@ } } }, - "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, - "node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "peerDependencies": { - "ajv": "^6.9.1" - } - }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -5108,6 +5101,34 @@ "webpack": ">=2" } }, + "node_modules/babel-loader/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/babel-loader/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/babel-loader/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, "node_modules/babel-loader/node_modules/schema-utils": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", @@ -5721,6 +5742,17 @@ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.2.tgz", "integrity": "sha512-xmDt/QIAdeZ9+nfdPsaBCpMvHNLFiLdjj59qjqn+6iPe6YmHGQ35sBnQ8uslRBXFmXkiZQOJRjvQeoGppoTjjg==" }, + "node_modules/cidr-regex": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/cidr-regex/-/cidr-regex-3.1.1.tgz", + "integrity": "sha512-RBqYd32aDwbCMFJRL6wHOlDNYJsPNTt8vC82ErHF5vKt8QQzxm1FrkW8s/R5pVrXMf17sba09Uoy91PKiddAsw==", + "dependencies": { + "ip-regex": "^4.1.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/cjs-module-lexer": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", @@ -6271,21 +6303,6 @@ } } }, - "node_modules/css-minimizer-webpack-plugin/node_modules/ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, "node_modules/css-minimizer-webpack-plugin/node_modules/ajv-keywords": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", @@ -6297,11 +6314,6 @@ "ajv": "^8.8.2" } }, - "node_modules/css-minimizer-webpack-plugin/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, "node_modules/css-minimizer-webpack-plugin/node_modules/schema-utils": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", @@ -7640,21 +7652,6 @@ "webpack": "^5.0.0" } }, - "node_modules/eslint-webpack-plugin/node_modules/ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, "node_modules/eslint-webpack-plugin/node_modules/ajv-keywords": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", @@ -7679,11 +7676,6 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/eslint-webpack-plugin/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, "node_modules/eslint-webpack-plugin/node_modules/schema-utils": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", @@ -7716,6 +7708,21 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/eslint/node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -7761,6 +7768,11 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, "node_modules/eslint/node_modules/type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -8320,6 +8332,29 @@ } } }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, "node_modules/fork-ts-checker-webpack-plugin/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -8364,6 +8399,11 @@ "node": ">=10" } }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, "node_modules/fork-ts-checker-webpack-plugin/node_modules/schema-utils": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", @@ -9198,6 +9238,14 @@ "node": ">= 0.4" } }, + "node_modules/ip-regex": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", + "integrity": "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==", + "engines": { + "node": ">=8" + } + }, "node_modules/ipaddr.js": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz", @@ -11653,9 +11701,9 @@ "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" }, "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -12088,21 +12136,6 @@ "webpack": "^5.0.0" } }, - "node_modules/mini-css-extract-plugin/node_modules/ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, "node_modules/mini-css-extract-plugin/node_modules/ajv-keywords": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", @@ -12114,11 +12147,6 @@ "ajv": "^8.8.2" } }, - "node_modules/mini-css-extract-plugin/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, "node_modules/mini-css-extract-plugin/node_modules/schema-utils": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", @@ -15713,6 +15741,34 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/schema-utils/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/schema-utils/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/schema-utils/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, "node_modules/scroll-into-view-if-needed": { "version": "2.2.29", "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.29.tgz", @@ -17374,21 +17430,6 @@ "webpack": "^4.0.0 || ^5.0.0" } }, - "node_modules/webpack-dev-middleware/node_modules/ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, "node_modules/webpack-dev-middleware/node_modules/ajv-keywords": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", @@ -17400,11 +17441,6 @@ "ajv": "^8.8.2" } }, - "node_modules/webpack-dev-middleware/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, "node_modules/webpack-dev-middleware/node_modules/schema-utils": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", @@ -17477,21 +17513,6 @@ } } }, - "node_modules/webpack-dev-server/node_modules/ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, "node_modules/webpack-dev-server/node_modules/ajv-keywords": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", @@ -17503,11 +17524,6 @@ "ajv": "^8.8.2" } }, - "node_modules/webpack-dev-server/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, "node_modules/webpack-dev-server/node_modules/schema-utils": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", @@ -17770,21 +17786,6 @@ "node": ">=10.0.0" } }, - "node_modules/workbox-build/node_modules/ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, "node_modules/workbox-build/node_modules/fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", @@ -17799,11 +17800,6 @@ "node": ">=10" } }, - "node_modules/workbox-build/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, "node_modules/workbox-build/node_modules/source-map": { "version": "0.8.0-beta.0", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", @@ -19615,6 +19611,17 @@ "strip-json-comments": "^3.1.1" }, "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -19636,6 +19643,11 @@ "argparse": "^2.0.1" } }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, "type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -21403,13 +21415,13 @@ } }, "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", "requires": { "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", "uri-js": "^4.2.2" } }, @@ -21419,31 +21431,8 @@ "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", "requires": { "ajv": "^8.0.0" - }, - "dependencies": { - "ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - } } }, - "ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==" - }, "ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -21733,6 +21722,27 @@ "schema-utils": "^2.6.5" }, "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, "schema-utils": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", @@ -22186,6 +22196,14 @@ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.2.tgz", "integrity": "sha512-xmDt/QIAdeZ9+nfdPsaBCpMvHNLFiLdjj59qjqn+6iPe6YmHGQ35sBnQ8uslRBXFmXkiZQOJRjvQeoGppoTjjg==" }, + "cidr-regex": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/cidr-regex/-/cidr-regex-3.1.1.tgz", + "integrity": "sha512-RBqYd32aDwbCMFJRL6wHOlDNYJsPNTt8vC82ErHF5vKt8QQzxm1FrkW8s/R5pVrXMf17sba09Uoy91PKiddAsw==", + "requires": { + "ip-regex": "^4.1.0" + } + }, "cjs-module-lexer": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", @@ -22580,17 +22598,6 @@ "source-map": "^0.6.1" }, "dependencies": { - "ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, "ajv-keywords": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", @@ -22599,11 +22606,6 @@ "fast-deep-equal": "^3.1.3" } }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, "schema-utils": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", @@ -23292,6 +23294,17 @@ "v8-compile-cache": "^2.0.3" }, "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -23322,6 +23335,11 @@ "argparse": "^2.0.1" } }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, "type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -23627,17 +23645,6 @@ "schema-utils": "^4.0.0" }, "dependencies": { - "ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, "ajv-keywords": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", @@ -23656,11 +23663,6 @@ "supports-color": "^8.0.0" } }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, "schema-utils": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", @@ -24086,6 +24088,22 @@ "tapable": "^1.0.0" }, "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==" + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -24118,6 +24136,11 @@ "universalify": "^2.0.0" } }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, "schema-utils": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", @@ -24716,6 +24739,11 @@ "side-channel": "^1.0.4" } }, + "ip-regex": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", + "integrity": "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==" + }, "ipaddr.js": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz", @@ -26579,9 +26607,9 @@ "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" }, "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -26902,17 +26930,6 @@ "schema-utils": "^4.0.0" }, "dependencies": { - "ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, "ajv-keywords": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", @@ -26921,11 +26938,6 @@ "fast-deep-equal": "^3.1.3" } }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, "schema-utils": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", @@ -29304,6 +29316,29 @@ "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", "ajv-keywords": "^3.5.2" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + } } }, "scroll-into-view-if-needed": { @@ -30592,17 +30627,6 @@ "schema-utils": "^4.0.0" }, "dependencies": { - "ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, "ajv-keywords": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", @@ -30611,11 +30635,6 @@ "fast-deep-equal": "^3.1.3" } }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, "schema-utils": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", @@ -30665,17 +30684,6 @@ "ws": "^8.4.2" }, "dependencies": { - "ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, "ajv-keywords": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", @@ -30684,11 +30692,6 @@ "fast-deep-equal": "^3.1.3" } }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, "schema-utils": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", @@ -30871,17 +30874,6 @@ "workbox-window": "6.5.4" }, "dependencies": { - "ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, "fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", @@ -30893,11 +30885,6 @@ "universalify": "^2.0.0" } }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, "source-map": { "version": "0.8.0-beta.0", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", diff --git a/package.json b/package.json index 8f720a4..7250a36 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "antd": "^4.20.6", "autoprefixer": "^10.4.4", "axios": "^0.27.2", + "cidr-regex": "^3.1.1", "copyfiles": "^2.4.1", "heroicons": "^1.0.6", "highlight.js": "^11.2.0", diff --git a/src/App.tsx b/src/App.tsx index a42a3e3..c47381e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,6 +8,7 @@ import SetupKeys from "./views/SetupKeys"; import AddPeer from "./views/AddPeer"; import Users from './views/Users'; import AccessControl from './views/AccessControl'; +import Routes from './views/Routes'; import Banner from "./components/Banner"; import {store} from "./store"; import { Col, Layout, Row} from 'antd'; @@ -68,6 +69,7 @@ function App() { + diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index 567372e..9b260e3 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -34,6 +34,7 @@ const Navbar = () => { { label: (Add Peer), key: '/add-peer' }, { label: (Setup Keys), key: '/setup-keys' }, { label: (Access Control), key: '/acls' }, + { label: (Network Routes), key: '/routes' }, { label: (Users), key: '/users' } ] as ItemType[]) const logoutWithRedirect = () => diff --git a/src/components/RouteUpdate.tsx b/src/components/RouteUpdate.tsx new file mode 100644 index 0000000..2135ce6 --- /dev/null +++ b/src/components/RouteUpdate.tsx @@ -0,0 +1,322 @@ +import React, {useEffect, useRef, useState} from 'react'; +import {useDispatch, useSelector} from "react-redux"; +import {RootState} from "typesafe-actions"; +import { actions as routeActions } from '../store/route'; +import { + Col, + Row, + Input, + InputNumber, + Space, + Switch, + SelectProps, + Button, Drawer, Form, Divider, Select, Tag, Radio, RadioChangeEvent, Typography +} from "antd"; +import {CloseOutlined, FlagFilled, QuestionCircleFilled} from "@ant-design/icons"; +import {Route} from "../store/route/types"; +import {Header} from "antd/es/layout/layout"; +import {RuleObject} from "antd/lib/form"; +import {useOidcAccessToken} from "@axa-fr/react-oidc"; +import cidrRegex from 'cidr-regex'; + +const { Paragraph } = Typography; + +interface FormRoute extends Route { +} + +const RouteUpdate = () => { + const {accessToken} = useOidcAccessToken() + const dispatch = useDispatch() + const setupNewRouteVisible = useSelector((state: RootState) => state.route.setupNewRouteVisible) + const peers = useSelector((state: RootState) => state.peer.data) + const route = useSelector((state: RootState) => state.route.route) + const savedRoute = useSelector((state: RootState) => state.route.savedRoute) + + const [editName, setEditName] = useState(false) + const [editDescription, setEditDescription] = useState(false) + const options: SelectProps['options'] = []; + const [formRoute, setFormRoute] = useState({} as FormRoute) + const [form] = Form.useForm() + const inputNameRef = useRef(null) + const inputDescriptionRef = useRef(null) + const peerSeparator = " - " + + const optionsDisabledEnabled = [{label: 'Enabled', value: true}, {label: 'Disabled', value: false}] + + useEffect(() => { + if (editName) inputNameRef.current!.focus({ + cursor: 'end', + }); + }, [editName]); + + useEffect(() => { + if (editDescription) inputDescriptionRef.current!.focus({ + cursor: 'end', + }); + }, [editDescription]); + + useEffect(() => { + if (!route) return + let peerName = '' + let p = peers.find(_p => _p.ip === route.peer) + peerName = p ? p.name + peerSeparator + p.ip : route.peer + + const fRoute = { + ...route, + peer: peerName + } as FormRoute + setFormRoute(fRoute) + form.setFieldsValue(fRoute) + }, [route]) + + peers.forEach((p) => { + let os:string + os = p.os + if (!os.toLowerCase().startsWith("darwin") && !os.toLowerCase().startsWith("windows")) { + options?.push({ + label: p.name + peerSeparator + p.ip, + value: p.name + peerSeparator + p.ip, + disabled: false + }) + } + }) + + + + const createRouteToSave = ():Route => { + let peerID = '' + if (formRoute.peer != '') { + peerID = formRoute.peer.split(peerSeparator)[1] + } + console.log(formRoute) + // let p = peers.find(_p => _p.name === formRoute.peer) + // peerID = p ? p.ip : '' + + return { + id: formRoute.id, + network: formRoute.network, + network_id: formRoute.network_id, + description: formRoute.description, + peer: peerID, + enabled: formRoute.enabled, + masquerade: formRoute.masquerade, + metric: formRoute.metric + } as Route + } + + const handleFormSubmit = () => { + form.validateFields() + .then((values) => { + const routeToSave = createRouteToSave() + dispatch(routeActions.saveRoute.request({getAccessTokenSilently:accessToken, payload: routeToSave})) + }) + .catch((errorInfo) => { + console.log('errorInfo', errorInfo) + }); + }; + + const setVisibleNewRoute = (status:boolean) => { + dispatch(routeActions.setSetupNewRouteVisible(status)); + } + + const onCancel = () => { + if (savedRoute.loading) return + setEditName(false) + dispatch(routeActions.setRoute({ + network: '', + network_id: '', + description: '', + peer: "", + metric: 9999, + masquerade: false, + enabled: true + } as Route)) + setVisibleNewRoute(false) + } + + const onChange = (data:any) => { + setFormRoute({...formRoute, ...data}) + } + + const dropDownRender = (menu: React.ReactElement) => ( + <> + {menu} + + ) + + const toggleEditName = (status:boolean) => { + setEditName(status); + } + + const toggleEditDescription = (status:boolean) => { + setEditDescription(status); + } + + const networkRangeValidator = (_: RuleObject, value: string) => { + if (!cidrRegex().test(value)) { + return Promise.reject(new Error("Please enter a valid CIDR, e.g. 192.168.1.0/24")) + } + + if (Number(value.split("/")[1]) < 7) { + return Promise.reject(new Error("Please enter a network mask larger than /7")) + } + + return Promise.resolve() + } + + return ( + <> + {route && + + + + + } + > +
+ + +
+ + + {!editName && !editDescription && formRoute.id && + + } + + + { !editName && formRoute.id ? ( +
toggleEditName(true)}>{formRoute.id ? formRoute.network_id : 'New Route'}
+ ) : ( + + toggleEditName(false)} onBlur={() => toggleEditName(false)} autoComplete="off" maxLength={40}/> + + )} + { !editDescription ? ( +
toggleEditDescription(true)}>{formRoute.description && formRoute.description.trim() !== "" ? formRoute.description : 'Add description...'}
+ ) : ( + + toggleEditDescription(false)} onBlur={() => toggleEditDescription(false)} autoComplete="off" maxLength={200}/> + + )} + +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + @@ -296,7 +369,8 @@ export const Peers = () => { optionType="button" buttonStyle="solid" /> - { xxl={5} span={5}> - Add Peer + Add + Peer
{failed && - + } {/*{loading && }*/} `Showing ${range[0]} to ${range[1]} of ${total} peers`)}} + pagination={{ + pageSize, + showSizeChanger: false, + showTotal: ((total, range) => `Showing ${range[0]} to ${range[1]} of ${total} peers`) + }} className="card-table" showSorterTooltip={false} scroll={{x: true}} @@ -328,56 +408,61 @@ export const Peers = () => { onFilter={(value: string | number | boolean, record) => (record as any).name.includes(value)} defaultSortOrder='ascend' sorter={(a, b) => ((a as any).name.localeCompare((b as any).name))} - render={(text:string, record:PeerDataTable,) => { - return + render={(text: string, record: PeerDataTable,) => { + return }} /> { const _a = (a as any).ip.split('.') const _b = (b as any).ip.split('.') - const a_s = _a.map((i:any) => i.padStart(3, '0')).join() - const b_s = _b.map((i:any) => i.padStart(3, '0')).join() + const a_s = _a.map((i: any) => i.padStart(3, '0')).join() + const b_s = _b.map((i: any) => i.padStart(3, '0')).join() return a_s.localeCompare(b_s) }} render={(text, record, index) => { - return + return }} /> { - return text ? online : offline + return text ? online : + offline }} /> { + render={(text, record: PeerDataTable, index) => { return renderPopoverGroups(text, record.groups, record) }} /> { - let isWindows = record.os.toLocaleLowerCase().startsWith("windows") - let toggle = { - if (checked) { - showConfirmEnableSSH(record) - } else { - handleSwitchSSH(record, checked) - } - }} - /> + render={(e, record: PeerDataTable, index) => { + let isWindows = record.os.toLocaleLowerCase().startsWith("windows") + let toggle = { + if (checked) { + showConfirmEnableSSH(record) + } else { + handleSwitchSSH(record, checked) + } + }} + /> - if (isWindows) { - return - {toggle} - - } else { - return toggle - } + if (isWindows) { + return + {toggle} + + } else { + return toggle } - } + } + } /> { return formatOS(text) }} /> - + { - return { if (visible) setPeerToAction(record as PeerDataTable) }}> diff --git a/src/views/Routes.tsx b/src/views/Routes.tsx new file mode 100644 index 0000000..fefed24 --- /dev/null +++ b/src/views/Routes.tsx @@ -0,0 +1,412 @@ +import React, {useEffect, useState} from 'react'; +import { + Alert, + Button, Card, + Col, Dropdown, Input, Menu, message, Modal, Popover, Radio, RadioChangeEvent, + Row, Select, Space, Switch, Table, Tag, Tooltip, + Typography +} from "antd"; +import {Container} from "../components/Container"; +import {useDispatch, useSelector} from "react-redux"; +import {RootState} from "typesafe-actions"; +import {Route} from "../store/route/types"; +import {actions as routeActions} from "../store/route"; +import {actions as peerActions} from "../store/peer"; +import {filter, sortBy} from "lodash"; +import { ExclamationCircleOutlined,QuestionCircleOutlined} from "@ant-design/icons"; +import RouteUpdate from "../components/RouteUpdate"; +import tableSpin from "../components/Spin"; +import {useOidcAccessToken} from '@axa-fr/react-oidc'; +const { Title, Paragraph } = Typography; +const { Column } = Table; +const { confirm } = Modal; + +interface RouteDataTable extends Route { + key: string; + sourceCount: number; + sourceLabel: ''; + destinationCount: number; + destinationLabel: ''; +} + +export const Routes = () => { + const {accessToken} = useOidcAccessToken() + const dispatch = useDispatch() + + const routes = useSelector((state: RootState) => state.route.data); + const failed = useSelector((state: RootState) => state.route.failed); + const loading = useSelector((state: RootState) => state.route.loading); + const deletedRoute = useSelector((state: RootState) => state.route.deletedRoute); + const savedRoute = useSelector((state: RootState) => state.route.savedRoute); + const peers = useSelector((state: RootState) => state.peer.data) + const [showTutorial, setShowTutorial] = useState(true) + const [textToSearch, setTextToSearch] = useState(''); + const [optionAllEnable, setOptionAllEnable] = useState('enabled'); + const [pageSize, setPageSize] = useState(5); + const [currentPage, setCurrentPage] = useState(1); + const [dataTable, setDataTable] = useState([] as RouteDataTable[]); + const [routeToAction, setRouteToAction] = useState(null as RouteDataTable | null); + + const pageSizeOptions = [ + {label: "5", value: "5"}, + {label: "10", value: "10"}, + {label: "15", value: "15"} + ] + + const optionsAllEnabled = [{label: 'Enabled', value: 'enabled'},{label: 'All', value: 'all'}] + + const itemsMenuAction = [ + { + key: "view", + label: () + }, + // { + // key: "delete", + // label: () + // }, + { + key: "delete", + label: () + } + ] + const actionsMenu = () + + + const isShowTutorial = (routes:Route[]):boolean => { + return (!routes.length || (routes.length === 1 && routes[0].network === "Default")) + } + + const peerNameToID = (name:string):string => { + let id = name + let p = peers.find(_p => _p?.name === name) + if (p) { + id = p.ip + } + return id + } + + const peerIDToName = (id:string):string => { + let name = id + let p = peers.find(_p => _p?.ip === id) + if (p) { + name = p.name + } + return name + } + + const transformDataTable = (d:Route[]):RouteDataTable[] => { + return d.map(p => { + return { + key: p.id, + ...p, + peer: peerIDToName(p.peer), + } as RouteDataTable + }) + } + + useEffect(() => { + dispatch(routeActions.getRoutes.request({getAccessTokenSilently:accessToken, payload: null})); + dispatch(peerActions.getPeers.request({getAccessTokenSilently:accessToken, payload: null})); + }, []) + + useEffect(() => { + setShowTutorial(isShowTutorial(routes)) + setDataTable(sortBy(transformDataTable(filterDataTable()), "name")) + }, [routes]) + + useEffect(() => { + setDataTable(transformDataTable(filterDataTable())) + }, [textToSearch, optionAllEnable]) + + const styleNotification = { marginTop: 85 } + + const saveKey = 'saving'; + useEffect(() => { + if (savedRoute.loading) { + message.loading({ content: 'Saving...', key: saveKey, duration: 0, style: styleNotification }) + } else if (savedRoute.success) { + message.success({ content: 'Route has been successfully updated.', key: saveKey, duration: 2, style: styleNotification }); + dispatch(routeActions.setSetupNewRouteVisible(false)) + dispatch(routeActions.setSavedRoute({ ...savedRoute, success: false })) + dispatch(routeActions.resetSavedRoute(null)) + } else if (savedRoute.error) { + message.error({ content: savedRoute.error.data? savedRoute.error.data : savedRoute.error.message, key: saveKey, duration: 2, style: styleNotification }); + dispatch(routeActions.setSavedRoute({ ...savedRoute, error: null })) + dispatch(routeActions.resetSavedRoute(null)) + } + }, [savedRoute]) + + const deleteKey = 'deleting'; + useEffect(() => { + const style = { marginTop: 85 } + if (deletedRoute.loading) { + message.loading({ content: 'Deleting...', key: deleteKey, style }) + } else if (deletedRoute.success) { + message.success({ content: 'Route has been successfully disabled.', key: deleteKey, duration: 2, style }) + dispatch(routeActions.resetDeletedRoute(null)) + } else if (deletedRoute.error) { + message.error({ content: 'Failed to remove route. You might not have enough permissions.', key: deleteKey, duration: 2, style }) + dispatch(routeActions.resetDeletedRoute(null)) + } + }, [deletedRoute]) + + const onChangeTextToSearch = (e: React.ChangeEvent) => { + setTextToSearch(e.target.value) + }; + + const searchDataTable = () => { + const data = filterDataTable() + setDataTable(transformDataTable(data)) + } + + const onChangeAllEnabled = ({ target: { value } }: RadioChangeEvent) => { + setOptionAllEnable(value) + } + + const onChangePageSize = (value: string) => { + setPageSize(parseInt(value.toString())) + } + + const showConfirmDelete = () => { + confirm({ + icon: , + width: 600, + content: + {routeToAction && + <> + Delete netowork route "{routeToAction ? routeToAction.network_id : ''}" + Are you sure you want to delete this route from your account? + + } + , + okType: 'danger', + onOk() { + dispatch(routeActions.deleteRoute.request({getAccessTokenSilently:accessToken, payload: routeToAction?.id || ''})); + }, + onCancel() { + setRouteToAction(null); + }, + }); + } + + const filterDataTable = ():Route[] => { + const t = textToSearch.toLowerCase().trim() + let f:Route[] = filter(routes, (f:Route) => + (f.network_id.toLowerCase().includes(t) ||f.network.toLowerCase().includes(t) || f.description.toLowerCase().includes(t) || peerIDToName(f.peer).toLowerCase().includes(t) || t === "") + ) as Route[] + if (optionAllEnable !== "all") { + f = filter(f, (f:Route) => f.enabled) + } + return f + } + + const onClickAddNewRoute = () => { + dispatch(routeActions.setSetupNewRouteVisible(true)); + dispatch(routeActions.setRoute({ + network: '', + network_id: '', + description: '', + peer: '', + masquerade: false, + metric: 9999, + enabled: true + } as Route)) + } + + const onClickViewRoute = () => { + dispatch(routeActions.setSetupNewRouteVisible(true)); + dispatch(routeActions.setRoute({ + id: routeToAction?.id || null, + network: routeToAction?.network, + network_id: routeToAction?.network_id, + description: routeToAction?.description, + peer: routeToAction?.peer, + metric: routeToAction?.metric, + masquerade: routeToAction?.masquerade, + enabled: routeToAction?.enabled + } as Route)) + } + + const setRouteAndView = (route: RouteDataTable) => { + dispatch(routeActions.setSetupNewRouteVisible(true)); + dispatch(routeActions.setRoute({ + id: route.id || null, + network: route.network, + network_id: route.network_id, + description: route.description, + peer: route.peer, + metric: route.metric, + masquerade: route.masquerade, + enabled: route.enabled + } as Route)) + } + + const showConfirmEnableMasquerade = (record: RouteDataTable, checked: boolean) => { + let label = record.network_id ? record.network_id : record.network + let tittle = "Enable Masquerade for \"" + label + "\"?" + let content = "Enabling this option hides other NetBird network IPs behind the routing peer local address when accessing the target Network CIDR. This option allows access to your private networks without configuring routes on your local routers or other devices." + + if (!checked) { + tittle = "Disable Masquerade for \"" + label + "\"?" + content = "Disabling this option stops hiding all traffic coming from other NetBird peers behind the routing peer local address when accessing the target Network CIDR. You will need to configure routes for your NetBird network pointing to your routing peer on your local routers or other devices." + } + + confirm({ + icon: , + title: tittle, + width: 600, + content: content, + okType: 'danger', + onOk() { + handleSwitchMasquerade(record, checked) + }, + onCancel() { + }, + }); + } + + function handleSwitchMasquerade(record: RouteDataTable, checked: boolean) { + const route = { + ...record, + peer: peerNameToID(record.peer), + masquerade: checked, + } as Route + dispatch(routeActions.saveRoute.request({getAccessTokenSilently:accessToken, payload: route})); + } + + return( + <> + + + + Network Routes + Network routes allow you to create routes to access other networks without installing NetBird on every resource. + + + + + + + + +
`Showing ${range[0]} to ${range[1]} of ${total} routes`), + onChange: (page, pageSize) => { + setCurrentPage(page) + } + }} + className={`access-control-table ${showTutorial ? "card-table card-table-no-placeholder" : "card-table"}`} + showSorterTooltip={false} + scroll={{x: true}} + loading={tableSpin(loading)} + dataSource={dataTable}> + + + Network Identifier + + + + + } + dataIndex="network_id" + onFilter={(value: string | number | boolean, record) => (record as any).name.includes(value)} + defaultSortOrder='ascend' + sorter={(a, b) => ((a as any).network_id.localeCompare((b as any).network_id))} + render={(text, record, index) => { + const desc = (record as RouteDataTable).description.trim() + return + setRouteAndView(record as RouteDataTable)} className="tooltip-label">{text} + + }} + /> + (record as any).network.includes(value)} + sorter={(a, b) => ((a as any).network.localeCompare((b as any).network))} + defaultSortOrder='ascend' + /> + { + return text ? enabled : disabled + }} + /> + (record as any).peer.includes(value)} + sorter={(a, b) => ((a as any).peer.localeCompare((b as any).peer))} + // render={(peerIP:string, RouteDataTable,) => { + // let p = peers.find(_p => _p?.ip === peerIP) + // return
{p?.name}
+ // }} + /> + (record as any).metric.includes(value)} + sorter={(a, b) => ((a as any).metric - ((b as any).metric))} + /> + { + let toggle = { + showConfirmEnableMasquerade(record, checked) + }} + /> + return + {toggle} + + }} + /> + { + if (deletedRoute.loading || savedRoute.loading) return <> + return { + if (visible) setRouteToAction(record as RouteDataTable) + }}> + }} + /> +
+ {showTutorial && + + + + } +
+ + + + + + + ) +} + +export default Routes; \ No newline at end of file