style: add eslint configuration and fixed linter issues (#35)

* style: add eslint configuration as per bee-js

* chore: add `plugin:react/reocommended` in `.eslintrc`

Co-authored-by: nugaon <50576770+nugaon@users.noreply.github.com>

* chore: add `consistent` to `array-bracket-newline` as per review

* style: after automatic fixes with `npm run lint`

* style: fixed all linter errors

* refactor: fixed all linter warnings

* chore: added missing new line at end of `.prettierrc` file

Co-authored-by: nugaon <50576770+nugaon@users.noreply.github.com>
This commit is contained in:
Vojtech Simetka
2021-04-03 14:04:37 +02:00
committed by GitHub
parent 9838aa70c8
commit bc01d60728
54 changed files with 3454 additions and 2782 deletions
+94
View File
@@ -0,0 +1,94 @@
{
"extends": [
"plugin:@typescript-eslint/recommended",
"prettier",
"plugin:prettier/recommended",
"plugin:react/recommended"
],
"env": {
"browser": true,
"commonjs": true,
"es6": true,
"jest": true,
"node": true
},
"parserOptions": {
"sourceType": "module",
"ecmaVersion": 2018
},
"plugins": ["jest"],
"rules": {
"array-bracket-newline": ["error", "consistent"],
"strict": ["error", "safe"],
"block-scoped-var": "error",
"complexity": "warn",
"default-case": "error",
"dot-notation": "warn",
"eqeqeq": "error",
"guard-for-in": "warn",
"linebreak-style": ["warn", "unix"],
"no-alert": "error",
"no-case-declarations": "error",
"no-console": "error",
"no-constant-condition": "error",
"no-continue": "warn",
"no-div-regex": "error",
"no-empty": "warn",
"no-empty-pattern": "error",
"no-implicit-coercion": "error",
"prefer-arrow-callback": "warn",
"no-labels": "error",
"no-loop-func": "error",
"no-nested-ternary": "warn",
"no-script-url": "error",
"quote-props": ["error", "as-needed"],
"require-yield": "error",
"max-nested-callbacks": ["error", 4],
"max-depth": ["error", 4],
"require-await": "error",
"space-before-function-paren": [
"error",
{
"anonymous": "never",
"named": "never",
"asyncArrow": "always"
}
],
"padding-line-between-statements": [
"error",
{ "blankLine": "always", "prev": "*", "next": "if" },
{ "blankLine": "always", "prev": "*", "next": "function" },
{ "blankLine": "always", "prev": "*", "next": "return" }
],
"no-useless-constructor": "off",
"no-dupe-class-members": "off",
"no-unused-expressions": "off",
"curly": ["error", "multi-line"],
"object-curly-spacing": ["error", "always"],
"comma-dangle": ["error", "always-multiline"],
"@typescript-eslint/no-useless-constructor": "error",
"@typescript-eslint/no-unused-expressions": "error",
"@typescript-eslint/member-delimiter-style": [
"error",
{
"multiline": {
"delimiter": "none",
"requireLast": true
},
"singleline": {
"delimiter": "semi",
"requireLast": false
}
}
],
"react/react-in-jsx-scope": "off" // React v17+ does not need React in scope for JSX elements
},
"overrides": [
{
"files": ["*.spec.ts", "*.spec.tsx", "*.test.ts", "*.test.tsx"],
"rules": {
"max-nested-callbacks": ["error", 10] // allow describe/it/test nesting
}
}
]
}
+13
View File
@@ -0,0 +1,13 @@
{
"printWidth": 120,
"tabWidth": 2,
"useTabs": false,
"bracketSpacing": true,
"semi": false,
"singleQuote": true,
"quoteProps": "as-needed",
"trailingComma": "all",
"endOfLine": "lf",
"arrowParens": "avoid",
"proseWrap": "always"
}
+248 -70
View File
@@ -37,6 +37,12 @@
"@types/react-copy-to-clipboard": "^5.0.0",
"@types/react-dom": "^17.0.3",
"@types/react-syntax-highlighter": "^13.5.0",
"eslint": "^7.23.0",
"eslint-config-prettier": "^8.1.0",
"eslint-plugin-jest": "^24.3.2",
"eslint-plugin-prettier": "^3.3.1",
"eslint-plugin-react": "^7.23.1",
"prettier": "^2.2.1",
"react-scripts": "4.0.3",
"typescript": "^4.2.3",
"web-vitals": "^1.1.1"
@@ -1393,9 +1399,9 @@
"integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow=="
},
"node_modules/@eslint/eslintrc": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.3.0.tgz",
"integrity": "sha512-1JTKgrOKAHVivSvOYw+sJOunkBjUOvjqWk1DPja7ZFhIS2mX/4EgTT8M7eTK9jrKhL/FvXXEbQwIs3pg1xp3dg==",
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.0.tgz",
"integrity": "sha512-2ZPCc+uNbjV5ERJr+aKSPRwZgKd2z11x0EgLvb1PURmUrn9QNRXFqje0Ldq454PfAVyaJYyrDvvIKSFP4NnBog==",
"dev": true,
"dependencies": {
"ajv": "^6.12.4",
@@ -1405,7 +1411,6 @@
"ignore": "^4.0.6",
"import-fresh": "^3.2.1",
"js-yaml": "^3.13.1",
"lodash": "^4.17.20",
"minimatch": "^3.0.4",
"strip-json-comments": "^3.1.1"
},
@@ -1423,6 +1428,9 @@
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@eslint/eslintrc/node_modules/ignore": {
@@ -3174,7 +3182,10 @@
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz",
"integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==",
"dev": true
"dev": true,
"peerDependencies": {
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
"node_modules/acorn-walk": {
"version": "7.2.0",
@@ -6868,13 +6879,13 @@
}
},
"node_modules/eslint": {
"version": "7.20.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-7.20.0.tgz",
"integrity": "sha512-qGi0CTcOGP2OtCQBgWZlQjcTuP0XkIpYFj25XtRTQSHC+umNnp7UMshr2G8SLsRFYDdAPFeHOsiteadmMH02Yw==",
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-7.23.0.tgz",
"integrity": "sha512-kqvNVbdkjzpFy0XOszNwjkKzZ+6TcwCQ/h+ozlcIWwaimBBuhlQ4nN6kbiM2L+OjDcznkTJxzYfRFH92sx4a0Q==",
"dev": true,
"dependencies": {
"@babel/code-frame": "7.12.11",
"@eslint/eslintrc": "^0.3.0",
"@eslint/eslintrc": "^0.4.0",
"ajv": "^6.10.0",
"chalk": "^4.0.0",
"cross-spawn": "^7.0.2",
@@ -6887,10 +6898,10 @@
"espree": "^7.3.1",
"esquery": "^1.4.0",
"esutils": "^2.0.2",
"file-entry-cache": "^6.0.0",
"file-entry-cache": "^6.0.1",
"functional-red-black-tree": "^1.0.1",
"glob-parent": "^5.0.0",
"globals": "^12.1.0",
"globals": "^13.6.0",
"ignore": "^4.0.6",
"import-fresh": "^3.0.0",
"imurmurhash": "^0.1.4",
@@ -6898,7 +6909,7 @@
"js-yaml": "^3.13.1",
"json-stable-stringify-without-jsonify": "^1.0.1",
"levn": "^0.4.1",
"lodash": "^4.17.20",
"lodash": "^4.17.21",
"minimatch": "^3.0.4",
"natural-compare": "^1.4.0",
"optionator": "^0.9.1",
@@ -6916,6 +6927,21 @@
},
"engines": {
"node": "^10.12.0 || >=12.0.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/eslint-config-prettier": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.1.0.tgz",
"integrity": "sha512-oKMhGv3ihGbCIimCAjqkdzx2Q+jthoqnXSP+d86M9tptwugycmTFdVR4IpLgq2c4SHifbwO90z2fQ8/Aio73yw==",
"dev": true,
"bin": {
"eslint-config-prettier": "bin/cli.js"
},
"peerDependencies": {
"eslint": ">=7.0.0"
}
},
"node_modules/eslint-config-react-app": {
@@ -7128,15 +7154,24 @@
"dev": true
},
"node_modules/eslint-plugin-jest": {
"version": "24.1.5",
"resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-24.1.5.tgz",
"integrity": "sha512-FIP3lwC8EzEG+rOs1y96cOJmMVpdFNreoDJv29B5vIupVssRi8zrSY3QadogT0K3h1Y8TMxJ6ZSAzYUmFCp2hg==",
"version": "24.3.2",
"resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-24.3.2.tgz",
"integrity": "sha512-cicWDr+RvTAOKS3Q/k03+Z3odt3VCiWamNUHWd6QWbVQWcYJyYgUTu8x0mx9GfeDEimawU5kQC+nQ3MFxIM6bw==",
"dev": true,
"dependencies": {
"@typescript-eslint/experimental-utils": "^4.0.1"
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"@typescript-eslint/eslint-plugin": ">= 4",
"eslint": ">=5"
},
"peerDependenciesMeta": {
"@typescript-eslint/eslint-plugin": {
"optional": true
}
}
},
"node_modules/eslint-plugin-jsx-a11y": {
@@ -7167,26 +7202,51 @@
"integrity": "sha512-117l1H6U4X3Krn+MrzYrL57d5H7siRHWraBs7s+LjRuFK7Fe7hJqnJ0skWlinqsycVLU5YAo6L8CsEYQ0V5prg==",
"dev": true
},
"node_modules/eslint-plugin-react": {
"version": "7.22.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.22.0.tgz",
"integrity": "sha512-p30tuX3VS+NWv9nQot9xIGAHBXR0+xJVaZriEsHoJrASGCJZDJ8JLNM0YqKqI0AKm6Uxaa1VUHoNEibxRCMQHA==",
"node_modules/eslint-plugin-prettier": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.3.1.tgz",
"integrity": "sha512-Rq3jkcFY8RYeQLgk2cCwuc0P7SEFwDravPhsJZOQ5N4YI4DSg50NyqJ/9gdZHzQlHf8MvafSesbNJCcP/FF6pQ==",
"dev": true,
"dependencies": {
"array-includes": "^3.1.1",
"array.prototype.flatmap": "^1.2.3",
"prettier-linter-helpers": "^1.0.0"
},
"engines": {
"node": ">=6.0.0"
},
"peerDependencies": {
"eslint": ">=5.0.0",
"prettier": ">=1.13.0"
},
"peerDependenciesMeta": {
"eslint-config-prettier": {
"optional": true
}
}
},
"node_modules/eslint-plugin-react": {
"version": "7.23.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.23.1.tgz",
"integrity": "sha512-MvFGhZjI8Z4HusajmSw0ougGrq3Gs4vT/0WgwksZgf5RrLrRa2oYAw56okU4tZJl8+j7IYNuTM+2RnFEuTSdRQ==",
"dev": true,
"dependencies": {
"array-includes": "^3.1.3",
"array.prototype.flatmap": "^1.2.4",
"doctrine": "^2.1.0",
"has": "^1.0.3",
"jsx-ast-utils": "^2.4.1 || ^3.0.0",
"object.entries": "^1.1.2",
"object.fromentries": "^2.0.2",
"object.values": "^1.1.1",
"minimatch": "^3.0.4",
"object.entries": "^1.1.3",
"object.fromentries": "^2.0.4",
"object.values": "^1.1.3",
"prop-types": "^15.7.2",
"resolve": "^1.18.1",
"string.prototype.matchall": "^4.0.2"
"resolve": "^2.0.0-next.3",
"string.prototype.matchall": "^4.0.4"
},
"engines": {
"node": ">=4"
},
"peerDependencies": {
"eslint": "^3 || ^4 || ^5 || ^6 || ^7"
}
},
"node_modules/eslint-plugin-react-hooks": {
@@ -7210,6 +7270,19 @@
"node": ">=0.10.0"
}
},
"node_modules/eslint-plugin-react/node_modules/resolve": {
"version": "2.0.0-next.3",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.3.tgz",
"integrity": "sha512-W8LucSynKUIDu9ylraa7ueVZ7hc0uAgJBxVsQSKOXOyle8a93qXhcz+XAXZ8bIq2d6i4Ehddn6Evt+0/UwKk6Q==",
"dev": true,
"dependencies": {
"is-core-module": "^2.2.0",
"path-parse": "^1.0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/eslint-plugin-testing-library": {
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-testing-library/-/eslint-plugin-testing-library-3.10.1.tgz",
@@ -7398,15 +7471,18 @@
}
},
"node_modules/eslint/node_modules/globals": {
"version": "12.4.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz",
"integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==",
"version": "13.7.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-13.7.0.tgz",
"integrity": "sha512-Aipsz6ZKRxa/xQkZhNg0qIWXT6x6rD46f6x/PCnBomlttdIyAPak4YD9jTmKpZ72uROSMU87qJtcgpgHaVchiA==",
"dev": true,
"dependencies": {
"type-fest": "^0.8.1"
"type-fest": "^0.20.2"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/eslint/node_modules/ignore": {
@@ -7448,6 +7524,18 @@
"node": ">=8"
}
},
"node_modules/eslint/node_modules/type-fest": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
"integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
"dev": true,
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/eslint/node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -7931,6 +8019,12 @@
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"dev": true
},
"node_modules/fast-diff": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz",
"integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==",
"dev": true
},
"node_modules/fast-glob": {
"version": "3.2.5",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz",
@@ -12977,18 +13071,21 @@
}
},
"node_modules/object.values": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.2.tgz",
"integrity": "sha512-MYC0jvJopr8EK6dPBiO8Nb9mvjdypOachO5REGk6MXzujbBrAisKo3HmdEI6kZDL6fC31Mwee/5YbtMebixeag==",
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.3.tgz",
"integrity": "sha512-nkF6PfDB9alkOUxpf1HNm/QlkeW3SReqL5WXeBLpEJJnlPSvRaDQpW3gQTksTN3fgJX4hL42RzKyOin6ff3tyw==",
"dev": true,
"dependencies": {
"call-bind": "^1.0.0",
"call-bind": "^1.0.2",
"define-properties": "^1.1.3",
"es-abstract": "^1.18.0-next.1",
"es-abstract": "^1.18.0-next.2",
"has": "^1.0.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/obuf": {
@@ -15008,6 +15105,30 @@
"node": ">=0.10.0"
}
},
"node_modules/prettier": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.2.1.tgz",
"integrity": "sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==",
"dev": true,
"bin": {
"prettier": "bin-prettier.js"
},
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/prettier-linter-helpers": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
"integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
"dev": true,
"dependencies": {
"fast-diff": "^1.1.2"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/pretty-bytes": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz",
@@ -18037,6 +18158,9 @@
"dev": true,
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/style-loader": {
@@ -22515,9 +22639,9 @@
"integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow=="
},
"@eslint/eslintrc": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.3.0.tgz",
"integrity": "sha512-1JTKgrOKAHVivSvOYw+sJOunkBjUOvjqWk1DPja7ZFhIS2mX/4EgTT8M7eTK9jrKhL/FvXXEbQwIs3pg1xp3dg==",
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.0.tgz",
"integrity": "sha512-2ZPCc+uNbjV5ERJr+aKSPRwZgKd2z11x0EgLvb1PURmUrn9QNRXFqje0Ldq454PfAVyaJYyrDvvIKSFP4NnBog==",
"dev": true,
"requires": {
"ajv": "^6.12.4",
@@ -22527,7 +22651,6 @@
"ignore": "^4.0.6",
"import-fresh": "^3.2.1",
"js-yaml": "^3.13.1",
"lodash": "^4.17.20",
"minimatch": "^3.0.4",
"strip-json-comments": "^3.1.1"
},
@@ -24080,7 +24203,8 @@
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz",
"integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==",
"dev": true
"dev": true,
"requires": {}
},
"acorn-walk": {
"version": "7.2.0",
@@ -27277,13 +27401,13 @@
}
},
"eslint": {
"version": "7.20.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-7.20.0.tgz",
"integrity": "sha512-qGi0CTcOGP2OtCQBgWZlQjcTuP0XkIpYFj25XtRTQSHC+umNnp7UMshr2G8SLsRFYDdAPFeHOsiteadmMH02Yw==",
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-7.23.0.tgz",
"integrity": "sha512-kqvNVbdkjzpFy0XOszNwjkKzZ+6TcwCQ/h+ozlcIWwaimBBuhlQ4nN6kbiM2L+OjDcznkTJxzYfRFH92sx4a0Q==",
"dev": true,
"requires": {
"@babel/code-frame": "7.12.11",
"@eslint/eslintrc": "^0.3.0",
"@eslint/eslintrc": "^0.4.0",
"ajv": "^6.10.0",
"chalk": "^4.0.0",
"cross-spawn": "^7.0.2",
@@ -27296,10 +27420,10 @@
"espree": "^7.3.1",
"esquery": "^1.4.0",
"esutils": "^2.0.2",
"file-entry-cache": "^6.0.0",
"file-entry-cache": "^6.0.1",
"functional-red-black-tree": "^1.0.1",
"glob-parent": "^5.0.0",
"globals": "^12.1.0",
"globals": "^13.6.0",
"ignore": "^4.0.6",
"import-fresh": "^3.0.0",
"imurmurhash": "^0.1.4",
@@ -27307,7 +27431,7 @@
"js-yaml": "^3.13.1",
"json-stable-stringify-without-jsonify": "^1.0.1",
"levn": "^0.4.1",
"lodash": "^4.17.20",
"lodash": "^4.17.21",
"minimatch": "^3.0.4",
"natural-compare": "^1.4.0",
"optionator": "^0.9.1",
@@ -27352,12 +27476,12 @@
}
},
"globals": {
"version": "12.4.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz",
"integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==",
"version": "13.7.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-13.7.0.tgz",
"integrity": "sha512-Aipsz6ZKRxa/xQkZhNg0qIWXT6x6rD46f6x/PCnBomlttdIyAPak4YD9jTmKpZ72uROSMU87qJtcgpgHaVchiA==",
"dev": true,
"requires": {
"type-fest": "^0.8.1"
"type-fest": "^0.20.2"
}
},
"ignore": {
@@ -27387,6 +27511,12 @@
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"dev": true
},
"type-fest": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
"integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
"dev": true
},
"which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -27398,6 +27528,13 @@
}
}
},
"eslint-config-prettier": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.1.0.tgz",
"integrity": "sha512-oKMhGv3ihGbCIimCAjqkdzx2Q+jthoqnXSP+d86M9tptwugycmTFdVR4IpLgq2c4SHifbwO90z2fQ8/Aio73yw==",
"dev": true,
"requires": {}
},
"eslint-config-react-app": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-6.0.0.tgz",
@@ -27578,9 +27715,9 @@
}
},
"eslint-plugin-jest": {
"version": "24.1.5",
"resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-24.1.5.tgz",
"integrity": "sha512-FIP3lwC8EzEG+rOs1y96cOJmMVpdFNreoDJv29B5vIupVssRi8zrSY3QadogT0K3h1Y8TMxJ6ZSAzYUmFCp2hg==",
"version": "24.3.2",
"resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-24.3.2.tgz",
"integrity": "sha512-cicWDr+RvTAOKS3Q/k03+Z3odt3VCiWamNUHWd6QWbVQWcYJyYgUTu8x0mx9GfeDEimawU5kQC+nQ3MFxIM6bw==",
"dev": true,
"requires": {
"@typescript-eslint/experimental-utils": "^4.0.1"
@@ -27613,23 +27750,33 @@
}
}
},
"eslint-plugin-react": {
"version": "7.22.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.22.0.tgz",
"integrity": "sha512-p30tuX3VS+NWv9nQot9xIGAHBXR0+xJVaZriEsHoJrASGCJZDJ8JLNM0YqKqI0AKm6Uxaa1VUHoNEibxRCMQHA==",
"eslint-plugin-prettier": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.3.1.tgz",
"integrity": "sha512-Rq3jkcFY8RYeQLgk2cCwuc0P7SEFwDravPhsJZOQ5N4YI4DSg50NyqJ/9gdZHzQlHf8MvafSesbNJCcP/FF6pQ==",
"dev": true,
"requires": {
"array-includes": "^3.1.1",
"array.prototype.flatmap": "^1.2.3",
"prettier-linter-helpers": "^1.0.0"
}
},
"eslint-plugin-react": {
"version": "7.23.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.23.1.tgz",
"integrity": "sha512-MvFGhZjI8Z4HusajmSw0ougGrq3Gs4vT/0WgwksZgf5RrLrRa2oYAw56okU4tZJl8+j7IYNuTM+2RnFEuTSdRQ==",
"dev": true,
"requires": {
"array-includes": "^3.1.3",
"array.prototype.flatmap": "^1.2.4",
"doctrine": "^2.1.0",
"has": "^1.0.3",
"jsx-ast-utils": "^2.4.1 || ^3.0.0",
"object.entries": "^1.1.2",
"object.fromentries": "^2.0.2",
"object.values": "^1.1.1",
"minimatch": "^3.0.4",
"object.entries": "^1.1.3",
"object.fromentries": "^2.0.4",
"object.values": "^1.1.3",
"prop-types": "^15.7.2",
"resolve": "^1.18.1",
"string.prototype.matchall": "^4.0.2"
"resolve": "^2.0.0-next.3",
"string.prototype.matchall": "^4.0.4"
},
"dependencies": {
"doctrine": {
@@ -27640,6 +27787,16 @@
"requires": {
"esutils": "^2.0.2"
}
},
"resolve": {
"version": "2.0.0-next.3",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.3.tgz",
"integrity": "sha512-W8LucSynKUIDu9ylraa7ueVZ7hc0uAgJBxVsQSKOXOyle8a93qXhcz+XAXZ8bIq2d6i4Ehddn6Evt+0/UwKk6Q==",
"dev": true,
"requires": {
"is-core-module": "^2.2.0",
"path-parse": "^1.0.6"
}
}
}
},
@@ -28162,6 +28319,12 @@
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"dev": true
},
"fast-diff": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz",
"integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==",
"dev": true
},
"fast-glob": {
"version": "3.2.5",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz",
@@ -32413,14 +32576,14 @@
}
},
"object.values": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.2.tgz",
"integrity": "sha512-MYC0jvJopr8EK6dPBiO8Nb9mvjdypOachO5REGk6MXzujbBrAisKo3HmdEI6kZDL6fC31Mwee/5YbtMebixeag==",
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.3.tgz",
"integrity": "sha512-nkF6PfDB9alkOUxpf1HNm/QlkeW3SReqL5WXeBLpEJJnlPSvRaDQpW3gQTksTN3fgJX4hL42RzKyOin6ff3tyw==",
"dev": true,
"requires": {
"call-bind": "^1.0.0",
"call-bind": "^1.0.2",
"define-properties": "^1.1.3",
"es-abstract": "^1.18.0-next.1",
"es-abstract": "^1.18.0-next.2",
"has": "^1.0.3"
}
},
@@ -34125,6 +34288,21 @@
"integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=",
"dev": true
},
"prettier": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.2.1.tgz",
"integrity": "sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==",
"dev": true
},
"prettier-linter-helpers": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
"integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
"dev": true,
"requires": {
"fast-diff": "^1.1.2"
}
},
"pretty-bytes": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz",
+9 -1
View File
@@ -50,6 +50,12 @@
"@types/react-copy-to-clipboard": "^5.0.0",
"@types/react-dom": "^17.0.3",
"@types/react-syntax-highlighter": "^13.5.0",
"eslint": "^7.23.0",
"eslint-config-prettier": "^8.1.0",
"eslint-plugin-jest": "^24.3.2",
"eslint-plugin-prettier": "^3.3.1",
"eslint-plugin-react": "^7.23.1",
"prettier": "^2.2.1",
"react-scripts": "4.0.3",
"typescript": "^4.2.3",
"web-vitals": "^1.1.1"
@@ -58,7 +64,9 @@
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
"eject": "react-scripts eject",
"lint": "eslint --fix \"src/**/*.ts\" \"src/**/*.tsx\" && prettier --write \"src/**/*.ts\" \"src/**/*.tsx\"",
"lint:check": "eslint \"src/**/*.ts\" \"src/**/*.tsx\" && prettier --check \"src/**/*.ts\" \"src/**/*.tsx\""
},
"eslintConfig": {
"extends": [
+15
View File
@@ -0,0 +1,15 @@
declare module 'react-identicons' {
interface Props {
string: string
size?: number
padding?: number
bg?: string
fg?: string
palette?: string[]
count?: number
getColor?: () => string
}
const Identicon = (props: Props): JSXElementConstructor => ReactNode
export default Identicon
}
+6 -6
View File
@@ -1,5 +1,5 @@
import { render } from '@testing-library/react';
import App from './App';
import { render } from '@testing-library/react'
import App from './App'
// Mocks methods that are not implemented in JSDOM
// see https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom
@@ -15,9 +15,9 @@ Object.defineProperty(window, 'matchMedia', {
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
});
})
// TODO: this is not a good test and should be removed. Keeping it in to make sure the whole app renders (to be used in CI)
test('should render the app', async () => {
render(<App />);
});
test('should render the app', () => {
render(<App />)
})
+18 -18
View File
@@ -1,18 +1,18 @@
import React, { useEffect, useState } from 'react';
import { BrowserRouter as Router } from 'react-router-dom';
import './App.css';
import { ReactElement, useEffect, useState } from 'react'
import { BrowserRouter as Router } from 'react-router-dom'
import './App.css'
import { ThemeProvider } from '@material-ui/styles';
import CssBaseline from '@material-ui/core/CssBaseline';
import { ThemeProvider } from '@material-ui/styles'
import CssBaseline from '@material-ui/core/CssBaseline'
import BaseRouter from './routes/routes';
import { lightTheme, darkTheme } from './theme';
import BaseRouter from './routes/routes'
import { lightTheme, darkTheme } from './theme'
function App() {
const [themeMode, toggleThemeMode] = useState('light');
const App = (): ReactElement => {
const [themeMode, toggleThemeMode] = useState('light')
useEffect(() => {
let theme = localStorage.getItem('theme')
const theme = localStorage.getItem('theme')
if (theme) {
toggleThemeMode(String(localStorage.getItem('theme')))
@@ -21,14 +21,14 @@ function App() {
}
window?.matchMedia('(prefers-color-scheme: dark)')?.addEventListener('change', e => {
toggleThemeMode(e?.matches ? "dark" : "light")
});
return () => window?.matchMedia('(prefers-color-scheme: dark)')?.removeEventListener('change', e => {
toggleThemeMode(e?.matches ? "dark" : "light")
toggleThemeMode(e?.matches ? 'dark' : 'light')
})
}, []);
return () =>
window?.matchMedia('(prefers-color-scheme: dark)')?.removeEventListener('change', e => {
toggleThemeMode(e?.matches ? 'dark' : 'light')
})
}, [])
return (
<div className="App">
@@ -39,7 +39,7 @@ function App() {
</Router>
</ThemeProvider>
</div>
);
)
}
export default App;
export default App
+60 -66
View File
@@ -1,98 +1,92 @@
import React from 'react';
import Button from '@material-ui/core/Button';
import Input from '@material-ui/core/Input';
import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
import DialogContentText from '@material-ui/core/DialogContentText';
import DialogTitle from '@material-ui/core/DialogTitle';
import { Snackbar, Container, CircularProgress } from '@material-ui/core';
import { ReactElement, useState } from 'react'
import Button from '@material-ui/core/Button'
import Input from '@material-ui/core/Input'
import Dialog from '@material-ui/core/Dialog'
import DialogActions from '@material-ui/core/DialogActions'
import DialogContent from '@material-ui/core/DialogContent'
import DialogContentText from '@material-ui/core/DialogContentText'
import DialogTitle from '@material-ui/core/DialogTitle'
import { Snackbar, Container, CircularProgress } from '@material-ui/core'
import { beeDebugApi } from '../services/bee';
import { beeDebugApi } from '../services/bee'
import EthereumAddress from './EthereumAddress';
import EthereumAddress from './EthereumAddress'
export default function DepositModal() {
const [open, setOpen] = React.useState<boolean>(false);
const [peerId, setPeerId] = React.useState('');
const [loadingCashout, setLoadingCashout] = React.useState<boolean>(false);
const [showToast, setToastVisibility] = React.useState<boolean>(false);
const [toastContent, setToastContent] = React.useState<JSX.Element | null>(null);
export default function DepositModal(): ReactElement {
const [open, setOpen] = useState<boolean>(false)
const [peerId, setPeerId] = useState('')
const [loadingCashout, setLoadingCashout] = useState<boolean>(false)
const [showToast, setToastVisibility] = useState<boolean>(false)
const [toastContent, setToastContent] = useState<JSX.Element | null>(null)
const handleClickOpen = () => {
setOpen(true);
};
setOpen(true)
}
const handleClose = () => {
setOpen(false);
};
setOpen(false)
}
const handleCashout = () => {
if (peerId) {
setLoadingCashout(true)
beeDebugApi.chequebook.peerCashout(peerId)
setLoadingCashout(true)
beeDebugApi.chequebook
.peerCashout(peerId)
.then(res => {
setOpen(false);
handleToast(<span>Successfully cashed out cheque. Transaction
<EthereumAddress
hideBlockie
transaction
address={res.transactionHash}
network={'goerli'}
/>
</span>)
setOpen(false)
handleToast(
<span>
Successfully cashed out cheque. Transaction
<EthereumAddress hideBlockie transaction address={res.transactionHash} network={'goerli'} />
</span>,
)
})
.catch(error => {
.catch(() => {
// FIXME: handle errors more gracefully
handleToast(<span>Error with cashout</span>)
})
.finally(() => {
setLoadingCashout(false)
})
} else {
handleToast(<span>Peer Id invalid</span>)
handleToast(<span>Peer Id invalid</span>)
}
};
}
const handleToast = (text: JSX.Element) => {
setToastContent(text)
setToastVisibility(true);
setTimeout(
() => setToastVisibility(false),
7000
);
};
setToastVisibility(true)
setTimeout(() => setToastVisibility(false), 7000)
}
return (
<div>
<Button variant="contained" color="primary" onClick={handleClickOpen} style={{marginLeft:'7px'}}>
<Button variant="contained" color="primary" onClick={handleClickOpen} style={{ marginLeft: '7px' }}>
Cashout
</Button>
<Snackbar
anchorOrigin={{ vertical: 'top', horizontal: 'center' }}
open={showToast}
message={toastContent}
/>
<Snackbar anchorOrigin={{ vertical: 'top', horizontal: 'center' }} open={showToast} message={toastContent} />
<Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title">
<DialogTitle id="form-dialog-title">Cashout Cheque</DialogTitle>
{loadingCashout ?
<Container style={{textAlign:'center', padding:'50px'}}>
<CircularProgress />
{loadingCashout ? (
<Container style={{ textAlign: 'center', padding: '50px' }}>
<CircularProgress />
</Container>
:
<DialogContent>
<DialogContentText style={{marginTop: '20px'}}>
Specify the peer Id of the peer you would like to cashout.
</DialogContentText>
<Input
autoFocus
margin="dense"
id="peerId"
type="text"
placeholder='Peer Id'
fullWidth
onChange={(e) => setPeerId(e.target.value)}
/>
</DialogContent>}
) : (
<DialogContent>
<DialogContentText style={{ marginTop: '20px' }}>
Specify the peer Id of the peer you would like to cashout.
</DialogContentText>
<Input
autoFocus
margin="dense"
id="peerId"
type="text"
placeholder="Peer Id"
fullWidth
onChange={e => setPeerId(e.target.value)}
/>
</DialogContent>
)}
<DialogActions>
<Button onClick={handleClose} color="primary">
Cancel
@@ -103,5 +97,5 @@ export default function DepositModal() {
</DialogActions>
</Dialog>
</div>
);
)
}
+24 -33
View File
@@ -1,37 +1,28 @@
import React from 'react';
import { IconButton, Snackbar } from '@material-ui/core';
import {CopyToClipboard} from 'react-copy-to-clipboard';
import { Clipboard } from 'react-feather';
import { ReactElement, useState } from 'react'
import { IconButton, Snackbar } from '@material-ui/core'
import { CopyToClipboard } from 'react-copy-to-clipboard'
import { Clipboard } from 'react-feather'
interface IProps {
value: string,
interface Props {
value: string
}
export default function ClipboardCopy(props: IProps) {
const [copied, setCopied] = React.useState(false);
const handleCopy = () => {
setCopied(true);
setTimeout(
() => setCopied(false),
3000
);
};
return (
<div style={{marginRight:'3px', marginLeft:'3px'}}>
<Snackbar
anchorOrigin={{ vertical: 'top', horizontal: 'center' }}
open={copied}
message="Copied"
/>
<IconButton color="primary" size='small' onClick={handleCopy}>
<CopyToClipboard text={props.value}
>
<Clipboard style={{height:'20px'}}/>
</CopyToClipboard>
</IconButton>
</div>
);
export default function ClipboardCopy(props: Props): ReactElement {
const [copied, setCopied] = useState(false)
const handleCopy = () => {
setCopied(true)
setTimeout(() => setCopied(false), 3000)
}
return (
<div style={{ marginRight: '3px', marginLeft: '3px' }}>
<Snackbar anchorOrigin={{ vertical: 'top', horizontal: 'center' }} open={copied} message="Copied" />
<IconButton color="primary" size="small" onClick={handleCopy}>
<CopyToClipboard text={props.value}>
<Clipboard style={{ height: '20px' }} />
</CopyToClipboard>
</IconButton>
</div>
)
}
+13 -15
View File
@@ -1,22 +1,20 @@
import SyntaxHighlighter from 'react-syntax-highlighter';
import type { ReactElement } from 'react'
import SyntaxHighlighter from 'react-syntax-highlighter'
interface IProps {
code: string,
language: string,
showLineNumbers?: boolean,
interface Props {
code: string
language: string
showLineNumbers?: boolean
}
const CodeBlock = (props: IProps) => {
const CodeBlock = (props: Props): ReactElement => {
return (
<div style={{textAlign:'left'}}>
<SyntaxHighlighter
language={props.language}
showLineNumbers={props.showLineNumbers}
>
<div style={{ textAlign: 'left' }}>
<SyntaxHighlighter language={props.language} showLineNumbers={props.showLineNumbers}>
{props.code}
</SyntaxHighlighter>
</SyntaxHighlighter>
</div>
);
};
)
}
export default CodeBlock;
export default CodeBlock
+129 -140
View File
@@ -1,158 +1,147 @@
import React, { useEffect } from 'react';
import { withStyles, Theme, createStyles } from '@material-ui/core/styles';
import { Tabs, Tab, Box, Typography } from '@material-ui/core';
import CodeBlock from './CodeBlock';
import React, { ReactElement, useEffect } from 'react'
import { withStyles, Theme, createStyles } from '@material-ui/core/styles'
import { Tabs, Tab, Box, Typography } from '@material-ui/core'
import CodeBlock from './CodeBlock'
interface TabPanelProps {
children?: React.ReactNode;
index: any;
value: any;
children?: React.ReactNode
index: number
value: number
}
interface IProps {
linux: string;
mac: string;
showLineNumbers?: boolean
interface Props {
linux: string
mac: string
showLineNumbers?: boolean
}
function a11yProps(index: any) {
return {
id: `simple-tab-${index}`,
'aria-controls': `simple-tabpanel-${index}`,
};
function a11yProps(index: number) {
return {
id: `simple-tab-${index}`,
'aria-controls': `simple-tabpanel-${index}`,
}
}
function getOS() {
var userAgent = window.navigator.userAgent,
platform = window.navigator.platform,
macosPlatforms = ['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K'],
windowsPlatforms = ['Win32', 'Win64', 'Windows', 'WinCE'],
iosPlatforms = ['iPhone', 'iPad', 'iPod'],
os = null;
const userAgent = window.navigator.userAgent
const platform = window.navigator.platform
const macosPlatforms = ['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K']
const windowsPlatforms = ['Win32', 'Win64', 'Windows', 'WinCE']
const iosPlatforms = ['iPhone', 'iPad', 'iPod']
if (macosPlatforms.indexOf(platform) !== -1) {
os = 'macOS';
} else if (iosPlatforms.indexOf(platform) !== -1) {
os = 'iOS';
} else if (windowsPlatforms.indexOf(platform) !== -1) {
os = 'windows';
} else if (/Android/.test(userAgent)) {
os = 'android';
} else if (!os && /Linux/.test(platform)) {
os = 'linux';
if (macosPlatforms.includes(platform)) return 'macOS'
if (iosPlatforms.includes(platform)) return 'iOS'
if (windowsPlatforms.includes(platform)) return 'windows'
if (/Android/.test(userAgent)) return 'android'
if (/Linux/.test(platform)) return 'linux'
return null
}
export default function CodeBlockTabs(props: Props): ReactElement {
const [value, setValue] = React.useState(0)
const handleChange = (event: React.ChangeEvent<unknown>, newValue: number) => {
setValue(newValue)
}
return os;
}
useEffect(() => {
const os = getOS()
export default function CodeBlockTabs(props: IProps) {
const [value, setValue] = React.useState(0);
if (os === 'windows') {
setValue(0)
} else if (os === 'linux') {
setValue(0)
} else if (os === 'macOS') {
setValue(1)
}
}, [])
const handleChange = (event: React.ChangeEvent<{}>, newValue: number) => {
setValue(newValue);
};
useEffect(() => {
let os = getOS()
if(os === 'windows') {
setValue(0)
} else if (os === 'linux') {
setValue(0)
} else if (os === 'macOS') {
setValue(1)
}
}, [])
function TabPanel(props: TabPanelProps) {
const { children, value, index, ...other } = props;
return (
<div
role="tabpanel"
hidden={value !== index}
id={`simple-tabpanel-${index}`}
aria-labelledby={`simple-tab-${index}`}
{...other}
>
{value === index && (
<Box style={{ marginTop: '-12px' }}>
<Typography component="div">{children}</Typography>
</Box>
)}
</div>
);
}
const AntTabs = withStyles({
root: {
borderBottom: '1px solid #e8e8e8',
},
indicator: {
backgroundColor: '#3f51b5',
},
})(Tabs);
interface StyledTabProps {
label: string;
}
const AntTab = withStyles((theme: Theme) =>
createStyles({
root: {
textTransform: 'none',
minWidth: 72,
backgroundColor: 'transparent',
fontWeight: theme.typography.fontWeightRegular,
marginRight: theme.spacing(4),
fontFamily: [
'-apple-system',
'BlinkMacSystemFont',
'"Segoe UI"',
'Roboto',
'"Helvetica Neue"',
'Arial',
'sans-serif',
'"Apple Color Emoji"',
'"Segoe UI Emoji"',
'"Segoe UI Symbol"',
].join(','),
'&:hover': {
color: '#3f51b5',
opacity: 1,
},
'&$selected': {
color: '#3f51b5',
fontWeight: theme.typography.fontWeightMedium,
},
'&:focus': {
color: '#3f51b5',
},
},
selected: {},
}),
)((props: StyledTabProps) => <Tab disableRipple {...props} />);
function TabPanel(props: TabPanelProps) {
const { children, value, index, ...other } = props
return (
<div>
<AntTabs style={{ marginTop: '12px' }} value={value} onChange={handleChange} aria-label="ant example">
<AntTab label="Linux" {...a11yProps(0)} />
<AntTab label="MacOS" {...a11yProps(1)} />
</AntTabs>
<TabPanel value={value} index={0}>
<CodeBlock
showLineNumbers={props.showLineNumbers}
language='bash'
code={props.linux}
/>
</TabPanel>
<TabPanel value={value} index={1}>
<CodeBlock
showLineNumbers={props.showLineNumbers}
language='bash'
code={props.mac}
/>
</TabPanel>
</div>
<div
role="tabpanel"
hidden={value !== index}
id={`simple-tabpanel-${index}`}
aria-labelledby={`simple-tab-${index}`}
{...other}
>
{value === index && (
<Box style={{ marginTop: '-12px' }}>
<Typography component="div">{children}</Typography>
</Box>
)}
</div>
)
}
const AntTabs = withStyles({
root: {
borderBottom: '1px solid #e8e8e8',
},
indicator: {
backgroundColor: '#3f51b5',
},
})(Tabs)
interface StyledTabProps {
label: string
}
const AntTab = withStyles((theme: Theme) =>
createStyles({
root: {
textTransform: 'none',
minWidth: 72,
backgroundColor: 'transparent',
fontWeight: theme.typography.fontWeightRegular,
marginRight: theme.spacing(4),
fontFamily: [
'-apple-system',
'BlinkMacSystemFont',
'"Segoe UI"',
'Roboto',
'"Helvetica Neue"',
'Arial',
'sans-serif',
'"Apple Color Emoji"',
'"Segoe UI Emoji"',
'"Segoe UI Symbol"',
].join(','),
'&:hover': {
color: '#3f51b5',
opacity: 1,
},
'&$selected': {
color: '#3f51b5',
fontWeight: theme.typography.fontWeightMedium,
},
'&:focus': {
color: '#3f51b5',
},
},
selected: {},
}),
)((props: StyledTabProps) => <Tab disableRipple {...props} />)
return (
<div>
<AntTabs style={{ marginTop: '12px' }} value={value} onChange={handleChange} aria-label="ant example">
<AntTab label="Linux" {...a11yProps(0)} />
<AntTab label="MacOS" {...a11yProps(1)} />
</AntTabs>
<TabPanel value={value} index={0}>
<CodeBlock showLineNumbers={props.showLineNumbers} language="bash" code={props.linux} />
</TabPanel>
<TabPanel value={value} index={1}>
<CodeBlock showLineNumbers={props.showLineNumbers} language="bash" code={props.mac} />
</TabPanel>
</div>
)
}
+55 -41
View File
@@ -1,48 +1,62 @@
import React, { useState } from 'react';
import { TextField, Button, CircularProgress, Container } from '@material-ui/core';
import React, { ReactElement, useState } from 'react'
import { TextField, Button, CircularProgress, Container } from '@material-ui/core'
interface IProps {
defaultHost?: string,
hostName: string,
interface Props {
defaultHost?: string
hostName: string
}
export default function ConnectToHost(props: IProps) {
const [hostInputVisible, toggleHostInputVisibility] = useState(false)
const [connectingToHost, setConnectingToHost] = useState(false)
const [host, setHost] = useState('')
export default function ConnectToHost(props: Props): ReactElement {
const [hostInputVisible, toggleHostInputVisibility] = useState(false)
const [connectingToHost, setConnectingToHost] = useState(false)
const [host, setHost] = useState('')
const handleNewHostConnection = () => {
if (host) {
setConnectingToHost(true)
sessionStorage.setItem(props.hostName, host)
toggleHostInputVisibility(!hostInputVisible)
window.location.reload();
}
const handleNewHostConnection = () => {
if (host) {
setConnectingToHost(true)
sessionStorage.setItem(props.hostName, host)
toggleHostInputVisibility(!hostInputVisible)
window.location.reload()
}
}
return (
<div>
{hostInputVisible ?
<div style={{display:'flex'}}>
<TextField
defaultValue={props.defaultHost}
label="Enter host"
variant="outlined"
size='small'
onChange={(e) => setHost(e.target.value)}
style={{marginRight:'15px', minWidth:'300px'}}
/>
<Button onClick={() => handleNewHostConnection()} size='small' variant="outlined">Connect</Button>
<Button style={{marginLeft:'7px'}} onClick={() => toggleHostInputVisibility(!hostInputVisible)} size='small'>Cancel</Button>
</div>
:
connectingToHost ?
<Container style={{textAlign:'center', padding:'0px'}}>
<CircularProgress size={20} />
</Container>
:
<Button onClick={() => toggleHostInputVisibility(!hostInputVisible)} size='small' variant="outlined">Change host</Button>
}
</div>
)
return (
<div>
{
// FIXME: this should be broken up
/* eslint-disable no-nested-ternary */
hostInputVisible ? (
<div style={{ display: 'flex' }}>
<TextField
defaultValue={props.defaultHost}
label="Enter host"
variant="outlined"
size="small"
onChange={e => setHost(e.target.value)}
style={{ marginRight: '15px', minWidth: '300px' }}
/>
<Button onClick={() => handleNewHostConnection()} size="small" variant="outlined">
Connect
</Button>
<Button
style={{ marginLeft: '7px' }}
onClick={() => toggleHostInputVisibility(!hostInputVisible)}
size="small"
>
Cancel
</Button>
</div>
) : connectingToHost ? (
<Container style={{ textAlign: 'center', padding: '0px' }}>
<CircularProgress size={20} />
</Container>
) : (
<Button onClick={() => toggleHostInputVisibility(!hostInputVisible)} size="small" variant="outlined">
Change host
</Button>
)
/* eslint-enable no-nested-ternary */
}
</div>
)
}
+36 -44
View File
@@ -1,77 +1,69 @@
import React from 'react';
import Button from '@material-ui/core/Button';
import Input from '@material-ui/core/Input';
import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
import DialogContentText from '@material-ui/core/DialogContentText';
import DialogTitle from '@material-ui/core/DialogTitle';
import { Snackbar } from '@material-ui/core';
import React, { ReactElement } from 'react'
import Button from '@material-ui/core/Button'
import Input from '@material-ui/core/Input'
import Dialog from '@material-ui/core/Dialog'
import DialogActions from '@material-ui/core/DialogActions'
import DialogContent from '@material-ui/core/DialogContent'
import DialogContentText from '@material-ui/core/DialogContentText'
import DialogTitle from '@material-ui/core/DialogTitle'
import { Snackbar } from '@material-ui/core'
import { beeDebugApi } from '../services/bee';
import { beeDebugApi } from '../services/bee'
export default function DepositModal() {
const [open, setOpen] = React.useState(false);
const [amount, setAmount] = React.useState(BigInt(0));
const [showToast, setToastVisibility] = React.useState(false);
const [toastContent, setToastContent] = React.useState('');
export default function DepositModal(): ReactElement {
const [open, setOpen] = React.useState(false)
const [amount, setAmount] = React.useState(BigInt(0))
const [showToast, setToastVisibility] = React.useState(false)
const [toastContent, setToastContent] = React.useState('')
const handleClickOpen = () => {
setOpen(true);
};
setOpen(true)
}
const handleClose = () => {
setOpen(false);
};
setOpen(false)
}
const handleWithdraw = () => {
if (amount > 0) {
beeDebugApi.chequebook.deposit(amount)
beeDebugApi.chequebook
.deposit(amount)
.then(res => {
setOpen(false);
handleToast(`Successful Deposit. Transaction ${res.transactionHash}`)
setOpen(false)
handleToast(`Successful Deposit. Transaction ${res.transactionHash}`)
})
.catch(error => {
handleToast('Error with Deposit')
.catch(() => {
handleToast('Error with Deposit')
})
} else {
handleToast('Must be amount of greater than 0')
handleToast('Must be amount of greater than 0')
}
};
}
const handleToast = (text: string) => {
setToastContent(text)
setToastVisibility(true);
setTimeout(
() => setToastVisibility(false),
7000
);
};
setToastVisibility(true)
setTimeout(() => setToastVisibility(false), 7000)
}
return (
<div>
<Button variant="outlined" color="primary" onClick={handleClickOpen} style={{marginLeft:'7px'}}>
<Button variant="outlined" color="primary" onClick={handleClickOpen} style={{ marginLeft: '7px' }}>
Deposit
</Button>
<Snackbar
anchorOrigin={{ vertical: 'top', horizontal: 'center' }}
open={showToast}
message={toastContent}
/>
<Snackbar anchorOrigin={{ vertical: 'top', horizontal: 'center' }} open={showToast} message={toastContent} />
<Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title">
<DialogTitle id="form-dialog-title">Deposit Funds</DialogTitle>
<DialogContent>
<DialogContentText>
Specify the amount you would like to deposit to your node.
</DialogContentText>
<DialogContentText>Specify the amount you would like to deposit to your node.</DialogContentText>
<Input
autoFocus
margin="dense"
id="name"
type="number"
placeholder='Amount'
placeholder="Amount"
fullWidth
onChange={(e) => setAmount(BigInt(e.target.value))}
onChange={e => setAmount(BigInt(e.target.value))}
/>
</DialogContent>
<DialogActions>
@@ -84,5 +76,5 @@ export default function DepositModal() {
</DialogActions>
</Dialog>
</div>
);
)
}
+51 -48
View File
@@ -1,54 +1,57 @@
import React from 'react';
import { Typography } from '@material-ui/core/'
import QRCodeModal from './QRCodeModal'
import ClipboardCopy from './ClipboardCopy'
import { Typography } from '@material-ui/core/';
import QRCodeModal from './QRCodeModal';
import ClipboardCopy from './ClipboardCopy';
import Identicon from 'react-identicons'
import { ReactElement } from 'react'
// @ts-ignore
import Identicon from 'react-identicons';
interface IProps {
address: string | undefined,
network?: string,
hideBlockie?: boolean,
transaction?: boolean,
truncate?: boolean,
interface Props {
address: string | undefined
network?: string
hideBlockie?: boolean
transaction?: boolean
truncate?: boolean
}
export default function EthereumAddress(props: IProps) {
return (
<Typography component="div" variant="subtitle1">
{props.address ?
<div style={{display:'flex'}}>
{props.hideBlockie ?
null
:
<div style={{paddingTop:'5px', marginRight: '10px', }}>
<Identicon size='20' string={props.address} />
</div>}
<div>
<a
style={props.truncate ?
{ marginRight:'7px', maxWidth:'200px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', display:'block'}
:
{ marginRight:'7px'}
}
href={`https://${props.network}.${process.env.REACT_APP_ETHERSCAN_HOST}/${props.transaction ? 'tx' : 'address' }/${props.address}`}
target='_blank'
rel="noreferrer"
>
{ props.address }
</a>
</div>
<QRCodeModal
value={ props.address }
label={'Ethereum Address'}
/>
<ClipboardCopy
value={ props.address }
/>
export default function EthereumAddress(props: Props): ReactElement {
return (
<Typography component="div" variant="subtitle1">
{props.address ? (
<div style={{ display: 'flex' }}>
{props.hideBlockie ? null : (
<div style={{ paddingTop: '5px', marginRight: '10px' }}>
<Identicon size={20} string={props.address} />
</div>
: '-' }
</Typography>
)
)}
<div>
<a
style={
props.truncate
? {
marginRight: '7px',
maxWidth: '200px',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
display: 'block',
}
: { marginRight: '7px' }
}
href={`https://${props.network}.${process.env.REACT_APP_ETHERSCAN_HOST}/${
props.transaction ? 'tx' : 'address'
}/${props.address}`}
target="_blank"
rel="noreferrer"
>
{props.address}
</a>
</div>
<QRCodeModal value={props.address} label={'Ethereum Address'} />
<ClipboardCopy value={props.address} />
</div>
) : (
'-'
)}
</Typography>
)
}
+54 -54
View File
@@ -1,14 +1,14 @@
import React from 'react'
import React, { ReactElement } from 'react'
import { Theme, createStyles, makeStyles } from '@material-ui/core/styles';
import { Card, CardContent, Typography } from '@material-ui/core/';
import { createStyles, makeStyles } from '@material-ui/core/styles'
import { Card, CardContent, Typography } from '@material-ui/core/'
import EthereumAddress from '../components/EthereumAddress';
import { Skeleton } from '@material-ui/lab';
import EthereumAddress from '../components/EthereumAddress'
import { Skeleton } from '@material-ui/lab'
import type { ChequebookAddressResponse, NodeAddresses } from '@ethersphere/bee-js';
import type { ChequebookAddressResponse, NodeAddresses } from '@ethersphere/bee-js'
const useStyles = makeStyles((theme: Theme) =>
const useStyles = makeStyles(() =>
createStyles({
root: {
display: 'flex',
@@ -22,58 +22,58 @@ const useStyles = makeStyles((theme: Theme) =>
flex: '1 0 auto',
},
status: {
color: '#fff',
backgroundColor: '#76a9fa',
}
color: '#fff',
backgroundColor: '#76a9fa',
},
}),
);
)
interface IProps{
nodeAddresses: NodeAddresses | null,
isLoadingNodeAddresses: boolean,
chequebookAddress: ChequebookAddressResponse | null,
isLoadingChequebookAddress: boolean,
interface Props {
nodeAddresses: NodeAddresses | null
isLoadingNodeAddresses: boolean
chequebookAddress: ChequebookAddressResponse | null
isLoadingChequebookAddress: boolean
}
function EthereumAddressCard(props: IProps) {
const classes = useStyles();
function EthereumAddressCard(props: Props): ReactElement {
const classes = useStyles()
return (
<div>
<Card className={classes.root}>
{props.isLoadingNodeAddresses ?
<div style={{padding: '16px'}}>
<Skeleton width={300} height={30} animation="wave" />
<Skeleton width={300} height={50} animation="wave" />
</div>
:
<div className={classes.details}>
<CardContent className={classes.content}>
<Typography variant="subtitle1" gutterBottom>Ethereum Address</Typography>
<EthereumAddress
address={props.nodeAddresses?.ethereum}
network={'goerli'}
/>
</CardContent>
</div>}
{props.isLoadingChequebookAddress ?
<div style={{padding: '16px'}}>
<Skeleton width={300} height={30} animation="wave" />
<Skeleton width={300} height={50} animation="wave" />
</div>
:
<div className={classes.details}>
<CardContent className={classes.content}>
<Typography variant="subtitle1" gutterBottom>Chequebook Contract Address</Typography>
<EthereumAddress
address={props.chequebookAddress?.chequebookaddress}
network={'goerli'}
/>
</CardContent>
</div>}
</Card>
</div>
)
return (
<div>
<Card className={classes.root}>
{props.isLoadingNodeAddresses ? (
<div style={{ padding: '16px' }}>
<Skeleton width={300} height={30} animation="wave" />
<Skeleton width={300} height={50} animation="wave" />
</div>
) : (
<div className={classes.details}>
<CardContent className={classes.content}>
<Typography variant="subtitle1" gutterBottom>
Ethereum Address
</Typography>
<EthereumAddress address={props.nodeAddresses?.ethereum} network={'goerli'} />
</CardContent>
</div>
)}
{props.isLoadingChequebookAddress ? (
<div style={{ padding: '16px' }}>
<Skeleton width={300} height={30} animation="wave" />
<Skeleton width={300} height={50} animation="wave" />
</div>
) : (
<div className={classes.details}>
<CardContent className={classes.content}>
<Typography variant="subtitle1" gutterBottom>
Chequebook Contract Address
</Typography>
<EthereumAddress address={props.chequebookAddress?.chequebookaddress} network={'goerli'} />
</CardContent>
</div>
)}
</Card>
</div>
)
}
export default EthereumAddressCard
+20 -28
View File
@@ -1,30 +1,31 @@
import React, { useState } from 'react';
import { useState, ReactElement } from 'react'
import { createStyles, Theme, makeStyles } from '@material-ui/core/styles';
import { Toolbar, Chip, IconButton } from '@material-ui/core/';
import { createStyles, makeStyles } from '@material-ui/core/styles'
import { Toolbar, Chip, IconButton } from '@material-ui/core/'
import { Sun, Moon } from 'react-feather';
import { Sun, Moon } from 'react-feather'
const drawerWidth = 240;
const drawerWidth = 240
const useStyles = makeStyles((theme: Theme) =>
const useStyles = makeStyles(() =>
createStyles({
appBar: {
width: `calc(100% - ${drawerWidth}px)`,
marginLeft: drawerWidth,
},
network: {
}
network: {},
}),
);
)
interface Props {
themeMode: string
}
export default function SideBar(props: any) {
const [darkMode, toggleDarkMode] = useState(false);
export default function SideBar(props: Props): ReactElement {
const [darkMode, toggleDarkMode] = useState(false)
const switchTheme = () => {
let theme = localStorage.getItem('theme')
const theme = localStorage.getItem('theme')
if (theme) {
localStorage.setItem('theme', theme === 'light' ? 'dark' : 'light')
} else {
@@ -35,26 +36,17 @@ export default function SideBar(props: any) {
window.location.reload()
}
const classes = useStyles();
const classes = useStyles()
return (
<div>
<div style={{ display: 'fixed' }} className={classes.appBar}>
<Toolbar style={{ display: 'flex' }}>
<Chip
style={{ marginLeft: '7px' }}
size="small"
label='Goerli'
className={classes.network}
/>
<Chip style={{ marginLeft: '7px' }} size="small" label="Goerli" className={classes.network} />
<div style={{ width: '100%' }}>
<div style={{ float: 'right' }} >
<div style={{ float: 'right' }}>
<IconButton style={{ marginRight: '10px' }} aria-label="dark-mode" onClick={() => switchTheme()}>
{props.themeMode === 'dark' ?
<Moon />
:
<Sun />
}
{props.themeMode === 'dark' ? <Moon /> : <Sun />}
</IconButton>
{/* <Chip
label="Connect Wallet"
@@ -65,5 +57,5 @@ export default function SideBar(props: any) {
</Toolbar>
</div>
</div>
);
)
}
+39 -39
View File
@@ -1,43 +1,43 @@
import React,{ useState } from 'react';
import QRCode from 'qrcode.react';
import { IconButton, Dialog, DialogTitle } from '@material-ui/core';
import { FilterCenterFocusSharp } from '@material-ui/icons';
import { ReactElement, useState } from 'react'
import QRCode from 'qrcode.react'
import { IconButton, Dialog, DialogTitle } from '@material-ui/core'
import { FilterCenterFocusSharp } from '@material-ui/icons'
interface IProps {
value: string,
label: string,
interface Props {
value: string
label: string
}
export default function QRCodeModal(props: IProps) {
const [open, setOpen] = useState(false);
const handleOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
return (
<div>
<IconButton color="primary" size='small' onClick={handleOpen}>
<FilterCenterFocusSharp/>
</IconButton>
<Dialog onClose={handleClose} aria-labelledby="simple-dialog-title" open={open}>
<div style={{padding: '30px', textAlign: 'center'}}>
<DialogTitle id="simple-dialog-title">{ props.label }</DialogTitle>
<QRCode
value={props.value}
size={150}
bgColor={"#ffffff"}
fgColor={"#000000"}
level={"L"}
includeMargin={false}
renderAs={"svg"}
/>
</div>
</Dialog>
</div>
);
export default function QRCodeModal(props: Props): ReactElement {
const [open, setOpen] = useState(false)
const handleOpen = () => {
setOpen(true)
}
const handleClose = () => {
setOpen(false)
}
return (
<div>
<IconButton color="primary" size="small" onClick={handleOpen}>
<FilterCenterFocusSharp />
</IconButton>
<Dialog onClose={handleClose} aria-labelledby="simple-dialog-title" open={open}>
<div style={{ padding: '30px', textAlign: 'center' }}>
<DialogTitle id="simple-dialog-title">{props.label}</DialogTitle>
<QRCode
value={props.value}
size={150}
bgColor={'#ffffff'}
fgColor={'#000000'}
level={'L'}
includeMargin={false}
renderAs={'svg'}
/>
</div>
</Dialog>
</div>
)
}
+21 -25
View File
@@ -1,8 +1,8 @@
import React from 'react';
import React from 'react'
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles';
import { Paper, InputBase, IconButton } from '@material-ui/core';
import { Search } from '@material-ui/icons';
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
import { Paper, InputBase, IconButton } from '@material-ui/core'
import { Search } from '@material-ui/icons'
const useStyles = makeStyles((theme: Theme) =>
createStyles({
@@ -24,27 +24,23 @@ const useStyles = makeStyles((theme: Theme) =>
margin: 4,
},
}),
);
)
interface IProps {
export default function SearchBar() {
const classes = useStyles()
}
export default function SearchBar(props: IProps) {
const classes = useStyles();
return (
<div>
<Paper component="form" className={classes.root}>
<InputBase
className={classes.input}
placeholder="Enter hash e.g. 0773a91efd6547c754fc1d95fb1c62c7d1b47f959c2caa685dfec8736da95c1c"
inputProps={{ 'aria-label': 'search google maps' }}
/>
<IconButton type="submit" className={classes.iconButton} aria-label="search">
<Search />
</IconButton>
</Paper>
</div>
)
return (
<div>
<Paper component="form" className={classes.root}>
<InputBase
className={classes.input}
placeholder="Enter hash e.g. 0773a91efd6547c754fc1d95fb1c62c7d1b47f959c2caa685dfec8736da95c1c"
inputProps={{ 'aria-label': 'search google maps' }}
/>
<IconButton type="submit" className={classes.iconButton} aria-label="search">
<Search />
</IconButton>
</Paper>
</div>
)
}
+75 -53
View File
@@ -1,46 +1,47 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { ReactElement } from 'react'
import { Link, RouteComponentProps } from 'react-router-dom'
import { createStyles, Theme, makeStyles } from '@material-ui/core/styles';
import { ListItemText, ListItemIcon, ListItem, Divider, List, Drawer, Link as MUILink } from '@material-ui/core';
import { OpenInNewSharp } from '@material-ui/icons';
import { Activity, FileText, DollarSign, Share2, Settings } from 'react-feather';
import { createStyles, Theme, makeStyles } from '@material-ui/core/styles'
import { ListItemText, ListItemIcon, ListItem, Divider, List, Drawer, Link as MUILink } from '@material-ui/core'
import { OpenInNewSharp } from '@material-ui/icons'
import { Activity, FileText, DollarSign, Share2, Settings } from 'react-feather'
import SwarmLogoOrange from '../assets/swarm-logo-orange.svg'
import { Health } from '@ethersphere/bee-js'
const drawerWidth = 240;
const drawerWidth = 240
const navBarItems = [
{
'label': 'Status',
'id': 'status',
'path': '/',
'icon': 'activity'
label: 'Status',
id: 'status',
path: '/',
icon: Activity,
},
{
'label': 'Files',
'id': 'files',
'path': '/files/',
'icon': 'file-text'
label: 'Files',
id: 'files',
path: '/files/',
icon: FileText,
},
{
'label': 'Accounting',
'id': 'accounting',
'path': '/accounting/',
'icon': 'dollar-sign'
label: 'Accounting',
id: 'accounting',
path: '/accounting/',
icon: DollarSign,
},
{
'label': 'Peers',
'id': 'peers',
'path': '/peers/',
'icon': 'share-2'
label: 'Peers',
id: 'peers',
path: '/peers/',
icon: Share2,
},
{
'label': 'Settings',
'id': 'settings',
'path': '/settings/',
'icon': 'settings'
}
label: 'Settings',
id: 'settings',
path: '/settings/',
icon: Settings,
},
]
const useStyles = makeStyles((theme: Theme) =>
@@ -68,29 +69,20 @@ const useStyles = makeStyles((theme: Theme) =>
},
activeSideBarItem: {
borderLeft: '4px solid #dd7700',
backgroundColor: 'inherit !important'
backgroundColor: 'inherit !important',
},
toolbar: theme.mixins.toolbar,
}),
);
)
const getIcon = (iconPath: string) => {
switch (iconPath) {
case 'activity':
return <Activity style={{ height: '20px' }} />
case 'file-text':
return <FileText style={{ height: '20px' }} />
case 'dollar-sign':
return <DollarSign style={{ height: '20px' }} />
case 'share-2':
return <Share2 style={{ height: '20px' }} />
case 'settings':
return <Settings style={{ height: '20px' }} />
}
interface Props extends RouteComponentProps {
themeMode: string
health: boolean
nodeHealth: Health | null
}
export default function SideBar(props: any) {
const classes = useStyles();
export default function SideBar(props: Props): ReactElement {
const classes = useStyles()
return (
<div className={classes.root}>
@@ -103,18 +95,30 @@ export default function SideBar(props: any) {
anchor="left"
>
<div className={classes.toolbar} style={{ textAlign: 'left', marginLeft: 20 }}>
<Link to='/'>
<img alt='swarm' className={classes.logo} src={props.themeMode === 'light' ? SwarmLogoOrange : SwarmLogoOrange} style={{ maxHeight: '30px', alignItems: 'center' }} />
<Link to="/">
<img
alt="swarm"
className={classes.logo}
src={props.themeMode === 'light' ? SwarmLogoOrange : SwarmLogoOrange}
style={{ maxHeight: '30px', alignItems: 'center' }}
/>
</Link>
</div>
<List>
{navBarItems.map(item => (
<Link to={item.path} key={item.id} style={{ color: 'inherit', textDecoration: 'none' }}>
<ListItem button selected={props.location.pathname === item.path} className={props.location.pathname === item.path ? classes.activeSideBarItem : ''}>
<ListItem
button
selected={props.location.pathname === item.path}
className={props.location.pathname === item.path ? classes.activeSideBarItem : ''}
>
<ListItemIcon className={props.location.pathname === item.path ? classes.activeSideBar : ''}>
{getIcon(item.icon)}
<item.icon style={{ height: '20px' }} />
</ListItemIcon>
<ListItemText primary={item.label} className={props.location.pathname === item.path ? classes.activeSideBar : ''} />
<ListItemText
primary={item.label}
className={props.location.pathname === item.path ? classes.activeSideBar : ''}
/>
</ListItem>
</Link>
))}
@@ -131,16 +135,34 @@ export default function SideBar(props: any) {
<div style={{ position: 'fixed', bottom: 0, width: 'inherit', padding: '10px' }}>
<ListItem>
<div style={{ marginRight: '30px' }}>
<div style={{ backgroundColor: props.health ? '#32c48d' : '#c9201f', marginRight: '7px', height: '10px', width: '10px', borderRadius: '50%', display: 'inline-block' }} />
<div
style={{
backgroundColor: props.health ? '#32c48d' : '#c9201f',
marginRight: '7px',
height: '10px',
width: '10px',
borderRadius: '50%',
display: 'inline-block',
}}
/>
<span>API</span>
</div>
<div>
<div style={{ backgroundColor: props.nodeHealth?.status === 'ok' ? '#32c48d' : '#c9201f', marginRight: '7px', height: '10px', width: '10px', borderRadius: '50%', display: 'inline-block' }} />
<div
style={{
backgroundColor: props.nodeHealth?.status === 'ok' ? '#32c48d' : '#c9201f',
marginRight: '7px',
height: '10px',
width: '10px',
borderRadius: '50%',
display: 'inline-block',
}}
/>
<span>Debug API</span>
</div>
</ListItem>
</div>
</Drawer>
</div>
);
)
}
+42 -45
View File
@@ -1,53 +1,50 @@
import React from 'react'
import type { ReactElement } from 'react'
import { makeStyles, } from '@material-ui/core/styles';
import { Card, CardContent, Typography } from '@material-ui/core/';
import { Skeleton } from '@material-ui/lab';
import { makeStyles } from '@material-ui/core/styles'
import { Card, CardContent, Typography } from '@material-ui/core/'
import { Skeleton } from '@material-ui/lab'
const useStyles = makeStyles({
root: {
minWidth: 275,
},
title: {
fontSize: 16,
},
pos: {
marginBottom: 12,
},
});
root: {
minWidth: 275,
},
title: {
fontSize: 16,
},
pos: {
marginBottom: 12,
},
})
interface IProps {
label: string
statistic?: string
loading?: boolean
interface Props {
label: string
statistic?: string
loading?: boolean
}
export default function StatCard({loading, label, statistic}: IProps) {
const classes = useStyles();
export default function StatCard({ loading, label, statistic }: Props): ReactElement {
const classes = useStyles()
return (
<Card className={classes.root}>
<CardContent>
{loading && (
<>
<Skeleton width={180} height={25} animation="wave" />
<Skeleton width={180} height={35} animation="wave" />
</>
)
}
{!loading && (
<>
<Typography className={classes.title} color="textSecondary" gutterBottom>
{label}
</Typography>
<Typography variant="h5" component="h2">
{statistic}
</Typography>
</>
)
}
</CardContent>
</Card>
)
return (
<Card className={classes.root}>
<CardContent>
{loading && (
<>
<Skeleton width={180} height={25} animation="wave" />
<Skeleton width={180} height={35} animation="wave" />
</>
)}
{!loading && (
<>
<Typography className={classes.title} color="textSecondary" gutterBottom>
{label}
</Typography>
<Typography variant="h5" component="h2">
{statistic}
</Typography>
</>
)}
</CardContent>
</Card>
)
}
+20 -31
View File
@@ -1,39 +1,28 @@
import type { Topology } from '@ethersphere/bee-js';
import { Grid } from '@material-ui/core/';
import StatCard from './StatCard';
import type { Topology } from '@ethersphere/bee-js'
import { Grid } from '@material-ui/core/'
import type { ReactElement } from 'react'
import StatCard from './StatCard'
interface Props {
isLoading: boolean
topology: Topology | null
error: Error | null // FIXME: should display error
isLoading: boolean
topology: Topology | null
error: Error | null // FIXME: should display error
}
const TopologyStats = ({isLoading, topology, error}: Props) => (
<Grid style={{ marginBottom: '20px', flexGrow: 1 }}>
<Grid container spacing={3}>
<Grid key={1} item xs={12} sm={12} md={6} lg={4} xl={4}>
<StatCard
label='Connected Peers'
statistic={topology?.connected.toString()}
loading={isLoading}
/>
</Grid>
<Grid key={2} item xs={12} sm={12} md={6} lg={4} xl={4}>
<StatCard
label='Population'
statistic={topology?.population.toString()}
loading={isLoading}
/>
</Grid>
<Grid key={3} item xs={12} sm={12} md={6} lg={4} xl={4}>
<StatCard
label='Depth'
statistic={topology?.depth.toString()}
loading={isLoading}
/>
</Grid>
</Grid>
const TopologyStats = ({ isLoading, topology }: Props): ReactElement => (
<Grid style={{ marginBottom: '20px', flexGrow: 1 }}>
<Grid container spacing={3}>
<Grid key={1} item xs={12} sm={12} md={6} lg={4} xl={4}>
<StatCard label="Connected Peers" statistic={topology?.connected.toString()} loading={isLoading} />
</Grid>
<Grid key={2} item xs={12} sm={12} md={6} lg={4} xl={4}>
<StatCard label="Population" statistic={topology?.population.toString()} loading={isLoading} />
</Grid>
<Grid key={3} item xs={12} sm={12} md={6} lg={4} xl={4}>
<StatCard label="Depth" statistic={topology?.depth.toString()} loading={isLoading} />
</Grid>
</Grid>
</Grid>
)
export default TopologyStats
+40 -32
View File
@@ -1,39 +1,47 @@
import React from 'react';
import { Link } from 'react-router-dom';
import type { ReactElement } from 'react'
import { Link } from 'react-router-dom'
import { makeStyles, } from '@material-ui/core/styles';
import { Card, CardContent, Typography } from '@material-ui/core/';
import { makeStyles } from '@material-ui/core/styles'
import { Card, CardContent, Typography } from '@material-ui/core/'
const useStyles = makeStyles({
root: {
flexGrow: 1,
marginTop: '20px'
},
title: {
textAlign:'center',
fontSize: 26,
},
});
root: {
flexGrow: 1,
marginTop: '20px',
},
title: {
textAlign: 'center',
fontSize: 26,
},
})
export default function TroubleshootConnectionCard(): ReactElement {
const classes = useStyles()
export default function TroubleshootConnectionCard() {
const classes = useStyles();
return (
<Card className={classes.root}>
<CardContent>
<Typography className={classes.title} gutterBottom>
Looks like your node is not connected
</Typography>
<div style={{ marginBottom: '20px', textAlign: 'center' }}>
<strong>
<Link to="/">Click to run status checks</Link> on your nodes connection or check out the{' '}
<a href={process.env.REACT_APP_BEE_DOCS_HOST} target="_blank" rel="noreferrer">
Swarm Bee Docs
</a>
</strong>
</div>
return (
<Card className={classes.root}>
<CardContent>
<Typography className={classes.title} gutterBottom>
Looks like your node is not connected
</Typography>
<div style={{marginBottom:'20px', textAlign:'center'}}>
<strong><Link to='/' >Click to run status checks</Link> on your nodes connection or check out the <a href={process.env.REACT_APP_BEE_DOCS_HOST} target='_blank' rel="noreferrer">Swarm Bee Docs</a></strong>
</div>
<div style={{marginBottom:'20px', textAlign:'center'}}>
<p style={{marginTop:'50px'}}>Still not working? Drop us a message on the Ethereum Swarm <a href={process.env.REACT_APP_BEE_DISCORD_HOST} target='_blank' rel="noreferrer">Discord</a></p>
</div>
</CardContent>
</Card>
)
<div style={{ marginBottom: '20px', textAlign: 'center' }}>
<p style={{ marginTop: '50px' }}>
Still not working? Drop us a message on the Ethereum Swarm{' '}
<a href={process.env.REACT_APP_BEE_DISCORD_HOST} target="_blank" rel="noreferrer">
Discord
</a>
</p>
</div>
</CardContent>
</Card>
)
}
+36 -43
View File
@@ -1,77 +1,70 @@
import React from 'react';
import Button from '@material-ui/core/Button';
import Input from '@material-ui/core/Input';
import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
import DialogContentText from '@material-ui/core/DialogContentText';
import DialogTitle from '@material-ui/core/DialogTitle';
import { Snackbar } from '@material-ui/core';
import { ReactElement, useState } from 'react'
import Button from '@material-ui/core/Button'
import Input from '@material-ui/core/Input'
import Dialog from '@material-ui/core/Dialog'
import DialogActions from '@material-ui/core/DialogActions'
import DialogContent from '@material-ui/core/DialogContent'
import DialogContentText from '@material-ui/core/DialogContentText'
import DialogTitle from '@material-ui/core/DialogTitle'
import { Snackbar } from '@material-ui/core'
import { beeDebugApi } from '../services/bee';
import { beeDebugApi } from '../services/bee'
export default function WithdrawlModal() {
const [open, setOpen] = React.useState(false);
const [amount, setAmount] = React.useState(BigInt(0));
const [showToast, setToastVisibility] = React.useState(false);
const [toastContent, setToastContent] = React.useState('');
export default function WithdrawlModal(): ReactElement {
const [open, setOpen] = useState(false)
const [amount, setAmount] = useState(BigInt(0))
const [showToast, setToastVisibility] = useState(false)
const [toastContent, setToastContent] = useState('')
const handleClickOpen = () => {
setOpen(true);
};
setOpen(true)
}
const handleClose = () => {
setOpen(false);
};
setOpen(false)
}
const handleWithdraw = () => {
if (amount > 0) {
beeDebugApi.chequebook.withdraw(amount)
beeDebugApi.chequebook
.withdraw(amount)
.then(res => {
setOpen(false);
handleToast(`Successful withdrawl. Transaction ${res.transactionHash}`)
setOpen(false)
handleToast(`Successful withdrawl. Transaction ${res.transactionHash}`)
})
.catch(error => {
handleToast('Error with withdrawl')
.catch(() => {
// FIXME: should probably detail the error
handleToast('Error with withdrawing')
})
} else {
handleToast('Must be amount of greater than 0')
handleToast('Must be amount of greater than 0')
}
};
}
const handleToast = (text: string) => {
setToastContent(text)
setToastVisibility(true);
setTimeout(
() => setToastVisibility(false),
7000
);
};
setToastVisibility(true)
setTimeout(() => setToastVisibility(false), 7000)
}
return (
<div>
<Button variant="outlined" color="primary" onClick={handleClickOpen}>
Withdraw
</Button>
<Snackbar
anchorOrigin={{ vertical: 'top', horizontal: 'center' }}
open={showToast}
message={toastContent}
/>
<Snackbar anchorOrigin={{ vertical: 'top', horizontal: 'center' }} open={showToast} message={toastContent} />
<Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title">
<DialogTitle id="form-dialog-title">Withdraw Funds</DialogTitle>
<DialogContent>
<DialogContentText>
Specify the amount you would like to withdraw from your node.
</DialogContentText>
<DialogContentText>Specify the amount you would like to withdraw from your node.</DialogContentText>
<Input
autoFocus
margin="dense"
id="name"
type="number"
placeholder='Amount'
placeholder="Amount"
fullWidth
onChange={(e) => setAmount(BigInt(e.target.value))}
onChange={e => setAmount(BigInt(e.target.value))}
/>
</DialogContent>
<DialogActions>
@@ -84,5 +77,5 @@ export default function WithdrawlModal() {
</DialogActions>
</Dialog>
</div>
);
)
}
+351 -251
View File
@@ -1,293 +1,393 @@
import { useState, useEffect } from "react";
import { useState, useEffect } from 'react'
import { NodeAddresses, ChequebookAddressResponse, ChequebookBalanceResponse, BalanceResponse,
LastChequesResponse, AllSettlements, LastCashoutActionResponse, Health, Peer, Topology, PingResponse, LastChequesForPeerResponse } from '@ethersphere/bee-js'
import {
NodeAddresses,
ChequebookAddressResponse,
ChequebookBalanceResponse,
BalanceResponse,
LastChequesResponse,
AllSettlements,
LastCashoutActionResponse,
Health,
Peer,
Topology,
PingResponse,
LastChequesForPeerResponse,
} from '@ethersphere/bee-js'
import { beeDebugApi, beeApi } from '../services/bee';
import { beeDebugApi, beeApi } from '../services/bee'
export const useApiHealth = () => {
const [health, setHealth] = useState<boolean>(false)
const [isLoadingHealth, setLoading] = useState<boolean>(false)
const [error, setError] = useState<Error | null>(null)
export interface HealthHook {
health: boolean
isLoadingHealth: boolean
error: Error | null
}
export const useApiHealth = (): HealthHook => {
const [health, setHealth] = useState<boolean>(false)
const [isLoadingHealth, setLoading] = useState<boolean>(false)
const [error, setError] = useState<Error | null>(null)
useEffect(() => {
setLoading(true)
beeApi.status.health()
.then(res => {
setHealth(res)
})
.catch(error => {
setError(error)
})
.finally(() => {
setLoading(false)
})
}, [])
useEffect(() => {
setLoading(true)
beeApi.status
.health()
.then(res => {
setHealth(res)
})
.catch(error => {
setError(error)
})
.finally(() => {
setLoading(false)
})
}, [])
return { health, isLoadingHealth, error } ;
return { health, isLoadingHealth, error }
}
export const useDebugApiHealth = () => {
const [nodeHealth, setNodeHealth] = useState<Health | null>(null)
const [isLoadingNodeHealth, setLoading] = useState<boolean>(false)
const [error, setError] = useState<Error | null>(null)
useEffect(() => {
setLoading(true)
beeDebugApi.status.nodeHealth()
.then(res => {
setNodeHealth(res)
})
.catch(error => {
setError(error)
})
.finally(() => {
setLoading(false)
})
}, [])
return { nodeHealth, isLoadingNodeHealth, error } ;
export interface DebugHealthHook {
nodeHealth: Health | null
isLoadingNodeHealth: boolean
error: Error | null
}
export const useApiNodeAddresses = () => {
const [nodeAddresses, setNodeAddresses] = useState<NodeAddresses | null>(null)
const [isLoadingNodeAddresses, setLoading] = useState<boolean>(false)
const [error, setError] = useState<Error | null>(null)
export const useDebugApiHealth = (): DebugHealthHook => {
const [nodeHealth, setNodeHealth] = useState<Health | null>(null)
const [isLoadingNodeHealth, setLoading] = useState<boolean>(false)
const [error, setError] = useState<Error | null>(null)
useEffect(() => {
setLoading(true)
beeDebugApi.connectivity.addresses()
.then(res => {
setNodeAddresses(res)
})
.catch(error => {
setError(error)
})
.finally(() => {
setLoading(false)
})
}, [])
useEffect(() => {
setLoading(true)
beeDebugApi.status
.nodeHealth()
.then(res => {
setNodeHealth(res)
})
.catch(error => {
setError(error)
})
.finally(() => {
setLoading(false)
})
}, [])
return { nodeAddresses, isLoadingNodeAddresses, error } ;
return { nodeHealth, isLoadingNodeHealth, error }
}
export const useApiNodeTopology = () => {
const [topology, setNodeTopology] = useState<Topology | null>(null)
const [isLoading, setLoading] = useState<boolean>(false)
const [error, setError] = useState<Error | null>(null)
useEffect(() => {
setLoading(true)
beeDebugApi.connectivity.topology()
.then(res => {
setNodeTopology(res)
})
.catch(error => {
setError(error)
})
.finally(() => {
setLoading(false)
})
}, [])
return { topology, isLoading, error } ;
export interface NodeAddressesHook {
nodeAddresses: NodeAddresses | null
isLoadingNodeAddresses: boolean
error: Error | null
}
export const useApiChequebookAddress = () => {
const [chequebookAddress, setChequebookAddress] = useState<ChequebookAddressResponse | null>(null)
const [isLoadingChequebookAddress, setLoading] = useState<boolean>(false)
const [error, setError] = useState<Error | null>(null)
export const useApiNodeAddresses = (): NodeAddressesHook => {
const [nodeAddresses, setNodeAddresses] = useState<NodeAddresses | null>(null)
const [isLoadingNodeAddresses, setLoading] = useState<boolean>(false)
const [error, setError] = useState<Error | null>(null)
useEffect(() => {
setLoading(true)
beeDebugApi.chequebook.address()
.then(res => {
setChequebookAddress(res)
})
.catch(error => {
setError(error)
})
.finally(() => {
setLoading(false)
})
}, [])
useEffect(() => {
setLoading(true)
beeDebugApi.connectivity
.addresses()
.then(res => {
setNodeAddresses(res)
})
.catch(error => {
setError(error)
})
.finally(() => {
setLoading(false)
})
}, [])
return { chequebookAddress, isLoadingChequebookAddress, error };
return { nodeAddresses, isLoadingNodeAddresses, error }
}
export const useApiNodePeers = () => {
const [peers, setPeers] = useState<Peer[] | null>(null)
const [isLoading, setLoading] = useState<boolean>(false)
const [error, setError] = useState<Error | null>(null)
useEffect(() => {
setLoading(true)
beeDebugApi.connectivity.listPeers()
.then(res => {
setPeers(res)
})
.catch(error => {
setError(error)
})
.finally(() => {
setLoading(false)
})
}, [])
return { peers, isLoading, error };
export interface NodeTopologyHook {
topology: Topology | null
isLoading: boolean
error: Error | null
}
export const useApiChequebookBalance = () => {
const [chequebookBalance, setChequebookBalance] = useState<ChequebookBalanceResponse | null>(null)
const [isLoadingChequebookBalance, setLoading] = useState<boolean>(false)
const [error, setError] = useState<Error | null>(null)
export const useApiNodeTopology = (): NodeTopologyHook => {
const [topology, setNodeTopology] = useState<Topology | null>(null)
const [isLoading, setLoading] = useState<boolean>(false)
const [error, setError] = useState<Error | null>(null)
useEffect(() => {
setLoading(true)
beeDebugApi.chequebook.balance()
.then(res => {
setChequebookBalance(res)
})
.catch(error => {
setError(error)
})
.finally(() => {
setLoading(false)
})
}, [])
useEffect(() => {
setLoading(true)
beeDebugApi.connectivity
.topology()
.then(res => {
setNodeTopology(res)
})
.catch(error => {
setError(error)
})
.finally(() => {
setLoading(false)
})
}, [])
return { chequebookBalance, isLoadingChequebookBalance, error };
return { topology, isLoading, error }
}
export interface ChequebookAddressHook {
chequebookAddress: ChequebookAddressResponse | null
isLoadingChequebookAddress: boolean
error: Error | null
}
export const useApiPeerBalances = () => {
const [peerBalances, setPeerBalances] = useState<BalanceResponse | null>(null)
const [isLoadingPeerBalances, setLoading] = useState<boolean>(false)
const [error, setError] = useState<Error | null>(null)
export const useApiChequebookAddress = (): ChequebookAddressHook => {
const [chequebookAddress, setChequebookAddress] = useState<ChequebookAddressResponse | null>(null)
const [isLoadingChequebookAddress, setLoading] = useState<boolean>(false)
const [error, setError] = useState<Error | null>(null)
useEffect(() => {
setLoading(true)
beeDebugApi.balance.balances()
.then(res => {
setPeerBalances(res)
})
.catch(error => {
setError(error)
})
.finally(() => {
setLoading(false)
})
}, [])
useEffect(() => {
setLoading(true)
beeDebugApi.chequebook
.address()
.then(res => {
setChequebookAddress(res)
})
.catch(error => {
setError(error)
})
.finally(() => {
setLoading(false)
})
}, [])
return { peerBalances, isLoadingPeerBalances, error };
return { chequebookAddress, isLoadingChequebookAddress, error }
}
export const useApiPeerCheques = () => {
const [peerCheques, setPeerCheques] = useState<LastChequesResponse | null>(null)
const [isLoadingPeerCheques, setLoading] = useState<boolean>(false)
const [error, setError] = useState<Error | null>(null)
useEffect(() => {
setLoading(true)
beeDebugApi.chequebook.getLastCheques()
.then(res => {
setPeerCheques(res)
})
.catch(error => {
setError(error)
})
.finally(() => {
setLoading(false)
})
}, [])
return { peerCheques, isLoadingPeerCheques, error };
export interface NodePeersHook {
peers: Peer[] | null
isLoading: boolean
error: Error | null
}
export const useApiPeerLastCheque = (peerId: string) => {
const [peerCheque, setPeerCheque] = useState<LastChequesForPeerResponse | null>(null)
const [isLoadingPeerCheque, setLoading] = useState<boolean>(false)
const [error, setError] = useState<Error | null>(null)
export const useApiNodePeers = (): NodePeersHook => {
const [peers, setPeers] = useState<Peer[] | null>(null)
const [isLoading, setLoading] = useState<boolean>(false)
const [error, setError] = useState<Error | null>(null)
useEffect(() => {
setLoading(true)
beeDebugApi.chequebook.getPeerLastCheques(peerId)
.then(res => {
setPeerCheque(res)
})
.catch(error => {
setError(error)
})
.finally(() => {
setLoading(false)
})
}, [peerId])
useEffect(() => {
setLoading(true)
beeDebugApi.connectivity
.listPeers()
.then(res => {
setPeers(res)
})
.catch(error => {
setError(error)
})
.finally(() => {
setLoading(false)
})
}, [])
return { peerCheque, isLoadingPeerCheque, error };
return { peers, isLoading, error }
}
export const useApiSettlements = () => {
const [settlements, setSettlements] = useState<AllSettlements | null>(null)
const [isLoadingSettlements, setLoading] = useState<boolean>(false)
const [error, setError] = useState<Error | null>(null)
useEffect(() => {
setLoading(true)
beeDebugApi.settlements.getSettlements()
.then(res => {
setSettlements(res)
})
.catch(error => {
setError(error)
})
.finally(() => {
setLoading(false)
})
}, [])
return { settlements, isLoadingSettlements, error };
export interface ChequebookBalanceHook {
chequebookBalance: ChequebookBalanceResponse | null
isLoadingChequebookBalance: boolean
error: Error | null
}
export const useApiChequebookBalance = (): ChequebookBalanceHook => {
const [chequebookBalance, setChequebookBalance] = useState<ChequebookBalanceResponse | null>(null)
const [isLoadingChequebookBalance, setLoading] = useState<boolean>(false)
const [error, setError] = useState<Error | null>(null)
export const useApiPingPeer = (peerId: string) => {
const [peerRTP, setPeerRTP] = useState<PingResponse | null>()
const [isPingingPeer, setPingingPeer] = useState<boolean>(false)
const [error, setError] = useState<Error | null>(null)
useEffect(() => {
setLoading(true)
beeDebugApi.chequebook
.balance()
.then(res => {
setChequebookBalance(res)
})
.catch(error => {
setError(error)
})
.finally(() => {
setLoading(false)
})
}, [])
useEffect(() => {
setPingingPeer(true)
beeDebugApi.connectivity.ping(peerId)
.then(res => {
setPeerRTP(res)
})
.catch(error => {
setError(error)
})
.finally(() => {
setPingingPeer(false)
})
}, [peerId])
return { peerRTP, isPingingPeer, error };
return { chequebookBalance, isLoadingChequebookBalance, error }
}
export const useApiPeerLastCashout = (peerId: string) => {
const [peerCashout, setPeerCashout] = useState<LastCashoutActionResponse | null>(null)
const [isLoadingPeerCashout, setLoading] = useState<boolean>(false)
const [error, setError] = useState<Error | null>(null)
useEffect(() => {
setLoading(true)
beeDebugApi.chequebook.getPeerLastCashout(peerId)
.then(res => {
setPeerCashout(res)
})
.catch(error => {
setError(error)
})
.finally(() => {
setLoading(false)
})
}, [peerId])
return { peerCashout, isLoadingPeerCashout, error };
export interface PeerBalanceHook {
peerBalances: BalanceResponse | null
isLoadingPeerBalances: boolean
error: Error | null
}
export const useApiPeerBalances = (): PeerBalanceHook => {
const [peerBalances, setPeerBalances] = useState<BalanceResponse | null>(null)
const [isLoadingPeerBalances, setLoading] = useState<boolean>(false)
const [error, setError] = useState<Error | null>(null)
useEffect(() => {
setLoading(true)
beeDebugApi.balance
.balances()
.then(res => {
setPeerBalances(res)
})
.catch(error => {
setError(error)
})
.finally(() => {
setLoading(false)
})
}, [])
return { peerBalances, isLoadingPeerBalances, error }
}
export interface PeerChequesHook {
peerCheques: LastChequesResponse | null
isLoadingPeerCheques: boolean
error: Error | null
}
export const useApiPeerCheques = (): PeerChequesHook => {
const [peerCheques, setPeerCheques] = useState<LastChequesResponse | null>(null)
const [isLoadingPeerCheques, setLoading] = useState<boolean>(false)
const [error, setError] = useState<Error | null>(null)
useEffect(() => {
setLoading(true)
beeDebugApi.chequebook
.getLastCheques()
.then(res => {
setPeerCheques(res)
})
.catch(error => {
setError(error)
})
.finally(() => {
setLoading(false)
})
}, [])
return { peerCheques, isLoadingPeerCheques, error }
}
export interface PeerLastChequesHook {
peerCheque: LastChequesForPeerResponse | null
isLoadingPeerCheque: boolean
error: Error | null
}
export const useApiPeerLastCheque = (peerId: string): PeerLastChequesHook => {
const [peerCheque, setPeerCheque] = useState<LastChequesForPeerResponse | null>(null)
const [isLoadingPeerCheque, setLoading] = useState<boolean>(false)
const [error, setError] = useState<Error | null>(null)
useEffect(() => {
setLoading(true)
beeDebugApi.chequebook
.getPeerLastCheques(peerId)
.then(res => {
setPeerCheque(res)
})
.catch(error => {
setError(error)
})
.finally(() => {
setLoading(false)
})
}, [peerId])
return { peerCheque, isLoadingPeerCheque, error }
}
export interface SettlementsHook {
settlements: AllSettlements | null
isLoadingSettlements: boolean
error: Error | null
}
export const useApiSettlements = (): SettlementsHook => {
const [settlements, setSettlements] = useState<AllSettlements | null>(null)
const [isLoadingSettlements, setLoading] = useState<boolean>(false)
const [error, setError] = useState<Error | null>(null)
useEffect(() => {
setLoading(true)
beeDebugApi.settlements
.getSettlements()
.then(res => {
setSettlements(res)
})
.catch(error => {
setError(error)
})
.finally(() => {
setLoading(false)
})
}, [])
return { settlements, isLoadingSettlements, error }
}
export interface PingPeerHook {
peerRTP: PingResponse | null
isPingingPeer: boolean
error: Error | null
}
export const useApiPingPeer = (peerId: string): PingPeerHook => {
const [peerRTP, setPeerRTP] = useState<PingResponse | null>(null)
const [isPingingPeer, setPingingPeer] = useState<boolean>(false)
const [error, setError] = useState<Error | null>(null)
useEffect(() => {
setPingingPeer(true)
beeDebugApi.connectivity
.ping(peerId)
.then(res => {
setPeerRTP(res)
})
.catch(error => {
setError(error)
})
.finally(() => {
setPingingPeer(false)
})
}, [peerId])
return { peerRTP, isPingingPeer, error }
}
export interface PeerLastCashoutHook {
peerCashout: LastCashoutActionResponse | null
isLoadingPeerCashout: boolean
error: Error | null
}
export const useApiPeerLastCashout = (peerId: string): PeerLastCashoutHook => {
const [peerCashout, setPeerCashout] = useState<LastCashoutActionResponse | null>(null)
const [isLoadingPeerCashout, setLoading] = useState<boolean>(false)
const [error, setError] = useState<Error | null>(null)
useEffect(() => {
setLoading(true)
beeDebugApi.chequebook
.getPeerLastCashout(peerId)
.then(res => {
setPeerCashout(res)
})
.catch(error => {
setError(error)
})
.finally(() => {
setLoading(false)
})
}, [peerId])
return { peerCashout, isLoadingPeerCashout, error }
}
+8 -8
View File
@@ -1,17 +1,17 @@
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
import App from './App'
import reportWebVitals from './reportWebVitals'
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
document.getElementById('root'),
)
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
reportWebVitals()
+42 -41
View File
@@ -1,12 +1,12 @@
import React, { useState, useEffect } from 'react'
import React, { useState, useEffect, ReactElement } from 'react'
import { createStyles, Theme, makeStyles } from '@material-ui/core/styles';
import { createStyles, Theme, makeStyles } from '@material-ui/core/styles'
import SideBar from '../components/SideBar';
import NavBar from '../components/NavBar';
import { useApiHealth, useDebugApiHealth } from '../hooks/apiHooks';
import SideBar from '../components/SideBar'
import NavBar from '../components/NavBar'
import { useApiHealth, useDebugApiHealth } from '../hooks/apiHooks'
import { RouteComponentProps } from 'react-router'
const useStyles = makeStyles((theme: Theme) =>
createStyles({
@@ -16,7 +16,7 @@ const useStyles = makeStyles((theme: Theme) =>
flexGrow: 1,
backgroundColor: theme.palette.background.default,
padding: theme.spacing(3),
paddingBottom:'65px',
paddingBottom: '65px',
},
footer: {
marginLeft: '240px',
@@ -24,55 +24,56 @@ const useStyles = makeStyles((theme: Theme) =>
position: 'fixed',
bottom: 0,
flexGrow: 1,
width:'-webkit-fill-available',
width: '-webkit-fill-available',
padding: theme.spacing(2),
textAlign:'center'
textAlign: 'center',
},
logo: {
height: '20px',
marginRight: '7px',
}
},
}),
);
)
interface Props extends RouteComponentProps {
children?: ReactElement
}
const Dashboard = (props: any) => {
const classes = useStyles();
const Dashboard = (props: Props): ReactElement => {
const classes = useStyles()
const [themeMode, toggleThemeMode] = useState('light');
const [themeMode, toggleThemeMode] = useState('light')
const { health, isLoadingHealth } = useApiHealth()
const { nodeHealth, isLoadingNodeHealth } = useDebugApiHealth()
// FIXME: handle errrors and loading
const { health } = useApiHealth()
const { nodeHealth } = useDebugApiHealth()
useEffect(() => {
let theme = localStorage.getItem('theme')
useEffect(() => {
const theme = localStorage.getItem('theme')
if (theme) {
toggleThemeMode(String(localStorage.getItem('theme')))
} else if (window?.matchMedia('(prefers-color-scheme: dark)')?.matches) {
toggleThemeMode('dark')
}
window?.matchMedia('(prefers-color-scheme: dark)')?.addEventListener('change', e => {
toggleThemeMode(e?.matches ? "dark" : "light")
});
if (theme) {
toggleThemeMode(String(localStorage.getItem('theme')))
} else if (window?.matchMedia('(prefers-color-scheme: dark)')?.matches) {
toggleThemeMode('dark')
}
return () => window?.matchMedia('(prefers-color-scheme: dark)')?.removeEventListener('change', e => {
toggleThemeMode(e?.matches ? "dark" : "light")
window?.matchMedia('(prefers-color-scheme: dark)')?.addEventListener('change', e => {
toggleThemeMode(e?.matches ? 'dark' : 'light')
})
return () =>
window?.matchMedia('(prefers-color-scheme: dark)')?.removeEventListener('change', e => {
toggleThemeMode(e?.matches ? 'dark' : 'light')
})
}, [])
}, [])
let childrenInjectedWithProps = React.cloneElement(props.children, { health, nodeHealth, isLoadingHealth, isLoadingNodeHealth })
return (
<div>
<SideBar {...props} themeMode={themeMode} health={health} nodeHealth={nodeHealth} />
<NavBar themeMode={themeMode} />
<main className={classes.content} >
{childrenInjectedWithProps}
</main>
</div>
)
return (
<div>
<SideBar {...props} themeMode={themeMode} health={health} nodeHealth={nodeHealth} />
<NavBar themeMode={themeMode} />
<main className={classes.content}>{props.children}</main>
</div>
)
}
export default Dashboard
+100 -86
View File
@@ -1,17 +1,17 @@
import React from 'react'
import { ReactElement } from 'react'
import { Theme, createStyles, makeStyles } from '@material-ui/core/styles';
import { Card, CardContent, Typography, Grid } from '@material-ui/core/';
import { Skeleton } from '@material-ui/lab';
import WithdrawlModal from '../../components/WithdrawlModal';
import DepositModal from '../../components/DepositModal';
import CashoutModal from '../../components/CashoutModal';
import { createStyles, makeStyles } from '@material-ui/core/styles'
import { Card, CardContent, Typography, Grid } from '@material-ui/core/'
import { Skeleton } from '@material-ui/lab'
import WithdrawlModal from '../../components/WithdrawlModal'
import DepositModal from '../../components/DepositModal'
import CashoutModal from '../../components/CashoutModal'
import { ConvertBalanceToBZZ } from '../../utils/common';
import { ConvertBalanceToBZZ } from '../../utils/common'
import type { ChequebookAddressResponse } from '@ethersphere/bee-js';
import type { AllSettlements, ChequebookAddressResponse } from '@ethersphere/bee-js'
const useStyles = makeStyles((theme: Theme) =>
const useStyles = makeStyles(() =>
createStyles({
root: {
display: 'flex',
@@ -20,93 +20,107 @@ const useStyles = makeStyles((theme: Theme) =>
display: 'flex',
},
address: {
color: 'grey',
fontWeight: 400,
},
color: 'grey',
fontWeight: 400,
},
content: {
flexGrow: 1,
flexGrow: 1,
},
status: {
color: '#fff',
backgroundColor: '#76a9fa',
}
color: '#fff',
backgroundColor: '#76a9fa',
},
}),
);
)
interface ChequebookBalance {
totalBalance: number,
availableBalance: number
totalBalance: number
availableBalance: number
}
interface IProps{
chequebookAddress: ChequebookAddressResponse | null,
isLoadingChequebookAddress: boolean,
chequebookBalance: ChequebookBalance | null,
isLoadingChequebookBalance: boolean,
settlements: any,
isLoadingSettlements: boolean,
interface Props {
chequebookAddress: ChequebookAddressResponse | null
isLoadingChequebookAddress: boolean
chequebookBalance: ChequebookBalance | null
isLoadingChequebookBalance: boolean
settlements: AllSettlements | null
isLoadingSettlements: boolean
}
function AccountCard(props: Props): ReactElement {
const classes = useStyles()
function AccountCard(props: IProps) {
const classes = useStyles();
return (
<div>
<div style={{justifyContent: 'space-between', display: 'flex'}}>
<h2 style={{ marginTop: '0px' }}>Accounting</h2>
<div style={{display:'flex'}}>
<WithdrawlModal />
<DepositModal />
<CashoutModal />
</div>
</div>
<Card className={classes.root}>
{ !props.isLoadingChequebookBalance && !props.isLoadingSettlements && props.chequebookBalance ?
<div className={classes.details}>
<CardContent className={classes.content}>
<Grid container spacing={5}>
<Grid item>
<Typography component="h2" variant="h6" color="primary" gutterBottom>
Total Balance
</Typography>
<Typography component="p" variant="h5">
{ConvertBalanceToBZZ(props.chequebookBalance.totalBalance)}
</Typography>
</Grid>
<Grid item>
<Typography component="h2" variant="h6" color="primary" gutterBottom>
Available Balance
</Typography>
<Typography component="p" variant="h5">
{ConvertBalanceToBZZ(props.chequebookBalance.availableBalance)}
</Typography>
</Grid>
<Grid item>
<Typography component="h2" variant="h6" color="primary" gutterBottom>
Total Sent / Received
</Typography>
<Typography component="div" variant="h5" >
<span style={{marginRight:'7px'}}>{ConvertBalanceToBZZ(props.settlements.totalsent)} / {ConvertBalanceToBZZ(props.settlements.totalreceived)}</span>
<span style={{ color: props.settlements.totalsent > props.settlements.totalreceived ? '#c9201f' : '#32c48d' }}>({ConvertBalanceToBZZ(props.settlements.totalsent - props.settlements.totalreceived)})</span>
</Typography>
</Grid>
</Grid>
</CardContent>
</div>
:
<div className={classes.details}>
<Skeleton width={180} height={110} animation="wave" style={{ marginLeft: '12px', marginRight: '12px'}} />
<Skeleton width={180} height={110} animation="wave" style={{ marginLeft: '12px', marginRight: '12px'}} />
<Skeleton width={180} height={110} animation="wave" style={{ marginLeft: '12px', marginRight: '12px'}} />
<Skeleton width={180} height={110} animation="wave" />
</div>
}
</Card>
return (
<div>
<div style={{ justifyContent: 'space-between', display: 'flex' }}>
<h2 style={{ marginTop: '0px' }}>Accounting</h2>
<div style={{ display: 'flex' }}>
<WithdrawlModal />
<DepositModal />
<CashoutModal />
</div>
)
</div>
<Card className={classes.root}>
{!props.isLoadingChequebookBalance && !props.isLoadingSettlements && props.chequebookBalance ? (
<div className={classes.details}>
<CardContent className={classes.content}>
<Grid container spacing={5}>
<Grid item>
<Typography component="h2" variant="h6" color="primary" gutterBottom>
Total Balance
</Typography>
<Typography component="p" variant="h5">
{ConvertBalanceToBZZ(props.chequebookBalance.totalBalance)}
</Typography>
</Grid>
<Grid item>
<Typography component="h2" variant="h6" color="primary" gutterBottom>
Available Balance
</Typography>
<Typography component="p" variant="h5">
{ConvertBalanceToBZZ(props.chequebookBalance.availableBalance)}
</Typography>
</Grid>
<Grid item>
<Typography component="h2" variant="h6" color="primary" gutterBottom>
Total Sent / Received
</Typography>
<Typography component="div" variant="h5">
<span style={{ marginRight: '7px' }}>
{ConvertBalanceToBZZ(props.settlements?.totalsent || 0)} /{' '}
{ConvertBalanceToBZZ(props.settlements?.totalreceived || 0)}
</span>
<span
style={{
color:
props.settlements && props.settlements?.totalsent > props.settlements?.totalreceived
? '#c9201f'
: '#32c48d',
}}
>
(
{ConvertBalanceToBZZ(
(props.settlements && props.settlements?.totalsent - props.settlements?.totalreceived) || 0,
)}
)
</span>
</Typography>
</Grid>
</Grid>
</CardContent>
</div>
) : (
<div className={classes.details}>
<Skeleton width={180} height={110} animation="wave" style={{ marginLeft: '12px', marginRight: '12px' }} />
<Skeleton width={180} height={110} animation="wave" style={{ marginLeft: '12px', marginRight: '12px' }} />
<Skeleton width={180} height={110} animation="wave" style={{ marginLeft: '12px', marginRight: '12px' }} />
<Skeleton width={180} height={110} animation="wave" />
</div>
)}
</Card>
</div>
)
}
export default AccountCard;
export default AccountCard
+68 -52
View File
@@ -1,64 +1,80 @@
import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
import { Table, TableBody, TableCell, TableContainer, TableRow, TableHead, Paper, Container, CircularProgress } from '@material-ui/core';
import type { ReactElement } from 'react'
import { makeStyles } from '@material-ui/core/styles'
import {
Table,
TableBody,
TableCell,
TableContainer,
TableRow,
TableHead,
Paper,
Container,
CircularProgress,
} from '@material-ui/core'
import { ConvertBalanceToBZZ } from '../../utils/common';
import { ConvertBalanceToBZZ } from '../../utils/common'
const useStyles = makeStyles({
table: {
minWidth: 650,
},
});
table: {
minWidth: 650,
},
})
interface PeerBalance {
balance: number,
peer: string,
balance: number
peer: string
}
interface PeerBalances {
balances: Array<PeerBalance>
balances: Array<PeerBalance>
}
interface IProps {
peerBalances: PeerBalances | null,
loading?: boolean,
}
function BalancesTable(props: IProps) {
const classes = useStyles();
return (
<div>
{props.loading ?
<Container style={{textAlign:'center', padding:'50px'}}>
<CircularProgress />
</Container>
:
<TableContainer component={Paper}>
<Table className={classes.table} size="small" aria-label="simple table">
<TableHead>
<TableRow>
<TableCell>Peer</TableCell>
<TableCell style={{textAlign:'right'}}>Balance (BZZ)</TableCell>
<TableCell align="right"></TableCell>
</TableRow>
</TableHead>
<TableBody>
{props.peerBalances?.balances.map((peerBalance: PeerBalance, idx: number) => (
<TableRow key={peerBalance.peer}>
<TableCell>{peerBalance.peer}</TableCell>
<TableCell style={{ color: ConvertBalanceToBZZ(peerBalance.balance) > 0 ? '#32c48d' : '#c9201f', textAlign:'right', fontFamily: 'monospace, monospace' }}>
{ConvertBalanceToBZZ(peerBalance.balance).toFixed(7).toLocaleString() }
</TableCell>
<TableCell align="right">
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>}
</div>
)
interface Props {
peerBalances: PeerBalances | null
loading?: boolean
}
export default BalancesTable;
function BalancesTable(props: Props): ReactElement {
const classes = useStyles()
return (
<div>
{props.loading ? (
<Container style={{ textAlign: 'center', padding: '50px' }}>
<CircularProgress />
</Container>
) : (
<TableContainer component={Paper}>
<Table className={classes.table} size="small" aria-label="simple table">
<TableHead>
<TableRow>
<TableCell>Peer</TableCell>
<TableCell style={{ textAlign: 'right' }}>Balance (BZZ)</TableCell>
<TableCell align="right"></TableCell>
</TableRow>
</TableHead>
<TableBody>
{props.peerBalances?.balances.map((peerBalance: PeerBalance) => (
<TableRow key={peerBalance.peer}>
<TableCell>{peerBalance.peer}</TableCell>
<TableCell
style={{
color: ConvertBalanceToBZZ(peerBalance.balance) > 0 ? '#32c48d' : '#c9201f',
textAlign: 'right',
fontFamily: 'monospace, monospace',
}}
>
{ConvertBalanceToBZZ(peerBalance.balance).toFixed(7).toLocaleString()}
</TableCell>
<TableCell align="right"></TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
)}
</div>
)
}
export default BalancesTable
+106 -94
View File
@@ -1,113 +1,125 @@
import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
import { Table, TableBody, TableCell, TableContainer, TableRow, TableHead, Paper, Container, CircularProgress } from '@material-ui/core';
import React, { ReactElement } from 'react'
import { makeStyles } from '@material-ui/core/styles'
import {
Table,
TableBody,
TableCell,
TableContainer,
TableRow,
TableHead,
Paper,
Container,
CircularProgress,
} from '@material-ui/core'
import { ConvertBalanceToBZZ } from '../../utils/common';
import EthereumAddress from '../../components/EthereumAddress';
import ClipboardCopy from '../../components/ClipboardCopy';
import PeerDetailDrawer from './PeerDetailDrawer';
import { ConvertBalanceToBZZ } from '../../utils/common'
import EthereumAddress from '../../components/EthereumAddress'
import ClipboardCopy from '../../components/ClipboardCopy'
import PeerDetailDrawer from './PeerDetailDrawer'
const useStyles = makeStyles({
table: {
minWidth: 650,
},
});
table: {
minWidth: 650,
},
})
interface ChequeEvent {
beneficiary: string,
chequebook: string,
payout: number,
beneficiary: string
chequebook: string
payout: number
}
interface PeerCheque {
lastreceived?: ChequeEvent,
lastsent?: ChequeEvent,
peer: string,
lastreceived?: ChequeEvent
lastsent?: ChequeEvent
peer: string
}
interface PeerCheques {
lastcheques: Array<PeerCheque>
lastcheques: Array<PeerCheque>
}
interface IProps {
peerCheques: PeerCheques | null,
loading?: boolean,
interface Props {
peerCheques: PeerCheques | null
loading?: boolean
}
function ChequebookTable(props: IProps) {
const classes = useStyles();
return (
function ChequebookTable(props: Props): ReactElement {
const classes = useStyles()
return (
<div>
{props.loading ? (
<Container style={{ textAlign: 'center', padding: '50px' }}>
<CircularProgress />
</Container>
) : (
<div>
{props.loading ?
<Container style={{textAlign:'center', padding:'50px'}}>
<CircularProgress />
</Container>
:
<div>
<TableContainer component={Paper}>
<Table className={classes.table} size="small" aria-label="simple table">
<TableHead>
<TableRow>
<TableCell>Peer</TableCell>
<TableCell>Last Received</TableCell>
<TableCell>Last Sent</TableCell>
<TableCell align="right"></TableCell>
</TableRow>
</TableHead>
<TableBody>
{props.peerCheques?.lastcheques.map((peerCheque: PeerCheque, idx: number) => (
<TableRow key={peerCheque.peer}>
<TableCell>
<div style={{display:'flex'}}>
<small>
<PeerDetailDrawer
peerId={peerCheque.peer}
/>
</small>
<ClipboardCopy value={peerCheque.peer} />
</div>
</TableCell>
<TableCell style={{maxWidth:'320px'}}>
<p style={{marginBottom: '0px', fontFamily: 'monospace, monospace', display:'flex'}}>
<span style={{whiteSpace: 'nowrap', marginRight:'12px', paddingTop:'3px'}}>
{peerCheque.lastreceived?.payout ?
`${ConvertBalanceToBZZ(peerCheque.lastreceived?.payout).toFixed(7).toLocaleString()} from`
: '-'}
</span>
{peerCheque.lastreceived ?
<EthereumAddress
hideBlockie
truncate
network='goerli'
address={peerCheque.lastreceived.beneficiary}
/> : null}
</p>
</TableCell>
<TableCell style={{maxWidth:'320px'}}>
<p style={{marginBottom: '0px', fontFamily: 'monospace, monospace', display:'flex'}}>
<span style={{whiteSpace: 'nowrap', marginRight:'12px', paddingTop:'3px'}}>
{peerCheque.lastsent?.payout ? `${ConvertBalanceToBZZ(peerCheque.lastsent?.payout).toFixed(7).toLocaleString()} to` : '-'}
</span>
{peerCheque.lastsent ?
<EthereumAddress
hideBlockie
truncate
network='goerli'
address={peerCheque.lastsent.beneficiary}
/> : null}
</p>
</TableCell>
<TableCell align="right">
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</div>}
<TableContainer component={Paper}>
<Table className={classes.table} size="small" aria-label="simple table">
<TableHead>
<TableRow>
<TableCell>Peer</TableCell>
<TableCell>Last Received</TableCell>
<TableCell>Last Sent</TableCell>
<TableCell align="right"></TableCell>
</TableRow>
</TableHead>
<TableBody>
{props.peerCheques?.lastcheques.map((peerCheque: PeerCheque) => (
<TableRow key={peerCheque.peer}>
<TableCell>
<div style={{ display: 'flex' }}>
<small>
<PeerDetailDrawer peerId={peerCheque.peer} />
</small>
<ClipboardCopy value={peerCheque.peer} />
</div>
</TableCell>
<TableCell style={{ maxWidth: '320px' }}>
<p style={{ marginBottom: '0px', fontFamily: 'monospace, monospace', display: 'flex' }}>
<span style={{ whiteSpace: 'nowrap', marginRight: '12px', paddingTop: '3px' }}>
{peerCheque.lastreceived?.payout
? `${ConvertBalanceToBZZ(peerCheque.lastreceived?.payout).toFixed(7).toLocaleString()} from`
: '-'}
</span>
{peerCheque.lastreceived ? (
<EthereumAddress
hideBlockie
truncate
network="goerli"
address={peerCheque.lastreceived.beneficiary}
/>
) : null}
</p>
</TableCell>
<TableCell style={{ maxWidth: '320px' }}>
<p style={{ marginBottom: '0px', fontFamily: 'monospace, monospace', display: 'flex' }}>
<span style={{ whiteSpace: 'nowrap', marginRight: '12px', paddingTop: '3px' }}>
{peerCheque.lastsent?.payout
? `${ConvertBalanceToBZZ(peerCheque.lastsent?.payout).toFixed(7).toLocaleString()} to`
: '-'}
</span>
{peerCheque.lastsent ? (
<EthereumAddress
hideBlockie
truncate
network="goerli"
address={peerCheque.lastsent.beneficiary}
/>
) : null}
</p>
</TableCell>
<TableCell align="right"></TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</div>
)
)}
</div>
)
}
export default ChequebookTable;
export default ChequebookTable
+158 -176
View File
@@ -1,188 +1,170 @@
import React, { useState } from 'react'
import { Paper, Container, Drawer, Button, Typography, CircularProgress, Grid } from '@material-ui/core';
import ClipboardCopy from '../../components/ClipboardCopy';
import { beeDebugApi } from '../../services/bee';
import EthereumAddress from '../../components/EthereumAddress';
import { ConvertBalanceToBZZ } from '../../utils/common';
import React, { ReactElement, useState } from 'react'
import { Paper, Container, Drawer, Button, Typography, CircularProgress, Grid } from '@material-ui/core'
import ClipboardCopy from '../../components/ClipboardCopy'
import { beeDebugApi } from '../../services/bee'
import EthereumAddress from '../../components/EthereumAddress'
import { ConvertBalanceToBZZ } from '../../utils/common'
import { LastCashoutActionResponse, LastChequesForPeerResponse } from '@ethersphere/bee-js'
function truncStringPortion(str: string, firstCharCount=10, endCharCount=10) {
var convertedStr="";
convertedStr+=str.substring(0, firstCharCount);
convertedStr += ".".repeat(3);
convertedStr+=str.substring(str.length-endCharCount, str.length);
return convertedStr;
function truncStringPortion(str: string, firstCharCount = 10, endCharCount = 10) {
let convertedStr = ''
convertedStr += str.substring(0, firstCharCount)
convertedStr += '.'.repeat(3)
convertedStr += str.substring(str.length - endCharCount, str.length)
return convertedStr
}
export default function Index(props: any) {
const [open, setOpen] = useState(false);
const [peerCashout, setPeerCashout] = useState({ "peer": "",
"chequebook": "",
"cumulativePayout": 0,
"beneficiary": "",
"transactionHash": "",
"result": {
"recipient": "",
"lastPayout": 0,
"bounced": false
}});
interface Props {
peerId: string
}
const [peerCheque, setPeerCheque] = useState({
lastreceived: { beneficiary: "", payout: 0, chequebook: "" },
lastsent: { beneficiary: "", payout: 0, chequebook: "" },
peer: ""
})
export default function Index(props: Props): ReactElement {
const [open, setOpen] = useState(false)
const [peerCashout, setPeerCashout] = useState<LastCashoutActionResponse | null>(null)
const [peerCheque, setPeerCheque] = useState<LastChequesForPeerResponse | null>(null)
const [isLoadingPeerCheque, setIsLoadingPeerCheque] = useState<boolean>(false)
const [isLoadingPeerCashout, setIsLoadingPeerCashout] = useState<boolean>(false);
const [isLoadingPeerCheque, setIsLoadingPeerCheque] = useState<boolean>(false)
const [isLoadingPeerCashout, setIsLoadingPeerCashout] = useState<boolean>(false)
const handleClickOpen = (peerId: string) => {
setIsLoadingPeerCashout(true)
beeDebugApi.chequebook
.getPeerLastCashout(peerId)
.then(res => {
setPeerCashout(res)
})
.catch(() => {
// FIXME: handle the error
})
.finally(() => {
setIsLoadingPeerCashout(false)
})
const handleClickOpen = (peerId: string) => {
setIsLoadingPeerCashout(true)
beeDebugApi.chequebook.getPeerLastCashout(peerId)
.then(res => {
setPeerCashout(res)
})
.catch(error => {
})
.finally(() => {
setIsLoadingPeerCashout(false)
})
setIsLoadingPeerCheque(true)
beeDebugApi.chequebook
.getPeerLastCheques(peerId)
.then(res => {
setPeerCheque(res)
})
.catch(() => {
// FIXME: handle the error
})
.finally(() => {
setIsLoadingPeerCheque(false)
})
setIsLoadingPeerCheque(true)
beeDebugApi.chequebook.getPeerLastCheques(peerId)
.then(res => {
setPeerCheque(res)
})
.catch(error => {
})
.finally(() => {
setIsLoadingPeerCheque(false)
})
setOpen(true)
}
setOpen(true);
}
const handleClose = () => {
setOpen(false);
};
const handleClose = () => {
setOpen(false)
}
return (
<div>
<Button color="primary" onClick={() => handleClickOpen(props.peerId)}>{truncStringPortion(props.peerId)}</Button>
<Drawer anchor={'right'} open={open} onClose={handleClose}>
<div style={{padding:'20px'}}>
<Typography variant="h5" gutterBottom style={{display:'flex'}}>
<span>Peer: { truncStringPortion(props.peerId) }</span>
<ClipboardCopy value={props.peerId} />
</Typography>
<Paper>
{ isLoadingPeerCashout || isLoadingPeerCheque ?
<Container style={{textAlign:'center', padding:'50px'}}>
<CircularProgress />
</Container>
:
<div style={{textAlign:'left', padding:'10px'}}>
<h3>Last Cheque</h3>
<Grid container spacing={1}>
<Grid key={1} item xs={12} sm={12} xl={6}>
<h5>Last Sent</h5>
<p>
Payout:
<span style={{marginBottom: '0px', fontFamily: 'monospace, monospace'}}> {
peerCheque.lastsent?.payout ? ConvertBalanceToBZZ(peerCheque.lastsent?.payout) : '-'
}</span>
</p>
<p>Beneficiary:
<EthereumAddress
network={'goerli'}
hideBlockie
address={peerCheque.lastsent?.beneficiary}
/>
</p>
<p>Chequebook:
<EthereumAddress
network={'goerli'}
hideBlockie
address={peerCheque.lastsent?.chequebook}
/>
</p>
</Grid>
<Grid key={1} item xs={12} sm={12} xl={6}>
<h5>Last Received</h5>
<p>
Payout:
<span style={{marginBottom: '0px', fontFamily: 'monospace, monospace'}}> {
peerCheque.lastreceived?.payout ? ConvertBalanceToBZZ(peerCheque.lastreceived?.payout) : '-'}</span>
</p>
<p>Beneficiary:
<EthereumAddress
network={'goerli'}
hideBlockie
address={peerCheque.lastreceived?.beneficiary}
/>
</p>
<p>Chequebook:
<EthereumAddress
network={'goerli'}
hideBlockie
address={peerCheque.lastreceived?.chequebook}
/>
</p>
</Grid>
</Grid>
<h3>Last Cashout</h3>
{peerCashout.cumulativePayout > 0 ?
<div>
<p>
Cumulative Payout:
<span style={{marginBottom: '0px', fontFamily: 'monospace, monospace'}}>
{peerCashout.cumulativePayout ? ConvertBalanceToBZZ(peerCashout.cumulativePayout) : '-'}
</span>
</p>
<p>
Last Payout:
<span style={{marginBottom: '0px', fontFamily: 'monospace, monospace'}}> {
peerCashout.result.lastPayout ? ConvertBalanceToBZZ(peerCashout.result.lastPayout) : '-'
}</span>
<span> {peerCashout.result.bounced ? 'Bounced' : ''}</span>
</p>
<p>Beneficiary:
<EthereumAddress
network={'goerli'}
hideBlockie
address={peerCashout.beneficiary}
/>
</p>
<p>Chequebook:
<EthereumAddress
network={'goerli'}
hideBlockie
address={peerCashout.chequebook}
/>
</p>
<p>Recipient:
<EthereumAddress
network={'goerli'}
hideBlockie
address={peerCashout.result.recipient}
/>
</p>
<p>Transaction:
<EthereumAddress
transaction
network={'goerli'}
hideBlockie
address={peerCashout.transactionHash}
/>
</p>
</div>
: 'None'}
</div>
}
</Paper>
</div>
</Drawer>
return (
<div>
<Button color="primary" onClick={() => handleClickOpen(props.peerId)}>
{truncStringPortion(props.peerId)}
</Button>
<Drawer anchor={'right'} open={open} onClose={handleClose}>
<div style={{ padding: '20px' }}>
<Typography variant="h5" gutterBottom style={{ display: 'flex' }}>
<span>Peer: {truncStringPortion(props.peerId)}</span>
<ClipboardCopy value={props.peerId} />
</Typography>
<Paper>
{isLoadingPeerCashout || isLoadingPeerCheque ? (
<Container style={{ textAlign: 'center', padding: '50px' }}>
<CircularProgress />
</Container>
) : (
<div style={{ textAlign: 'left', padding: '10px' }}>
<h3>Last Cheque</h3>
<Grid container spacing={1}>
<Grid key={1} item xs={12} sm={12} xl={6}>
<h5>Last Sent</h5>
<p>
Payout:
<span style={{ marginBottom: '0px', fontFamily: 'monospace, monospace' }}>
{' '}
{peerCheque?.lastsent?.payout ? ConvertBalanceToBZZ(peerCheque?.lastsent?.payout) : '-'}
</span>
</p>
<p>
Beneficiary:
<EthereumAddress network={'goerli'} hideBlockie address={peerCheque?.lastsent?.beneficiary} />
</p>
<p>
Chequebook:
<EthereumAddress network={'goerli'} hideBlockie address={peerCheque?.lastsent?.chequebook} />
</p>
</Grid>
<Grid key={1} item xs={12} sm={12} xl={6}>
<h5>Last Received</h5>
<p>
Payout:
<span style={{ marginBottom: '0px', fontFamily: 'monospace, monospace' }}>
{' '}
{peerCheque?.lastreceived?.payout ? ConvertBalanceToBZZ(peerCheque?.lastreceived?.payout) : '-'}
</span>
</p>
<p>
Beneficiary:
<EthereumAddress network={'goerli'} hideBlockie address={peerCheque?.lastreceived?.beneficiary} />
</p>
<p>
Chequebook:
<EthereumAddress network={'goerli'} hideBlockie address={peerCheque?.lastreceived?.chequebook} />
</p>
</Grid>
</Grid>
<h3>Last Cashout</h3>
{peerCashout && peerCashout?.cumulativePayout > 0 ? (
<div>
<p>
Cumulative Payout:
<span style={{ marginBottom: '0px', fontFamily: 'monospace, monospace' }}>
{peerCashout?.cumulativePayout ? ConvertBalanceToBZZ(peerCashout?.cumulativePayout) : '-'}
</span>
</p>
<p>
Last Payout:
<span style={{ marginBottom: '0px', fontFamily: 'monospace, monospace' }}>
{' '}
{peerCashout?.result.lastPayout ? ConvertBalanceToBZZ(peerCashout?.result.lastPayout) : '-'}
</span>
<span> {peerCashout?.result.bounced ? 'Bounced' : ''}</span>
</p>
<p>
Beneficiary:
<EthereumAddress network={'goerli'} hideBlockie address={peerCashout?.beneficiary} />
</p>
<p>
Chequebook:
<EthereumAddress network={'goerli'} hideBlockie address={peerCashout?.chequebook} />
</p>
<p>
Recipient:
<EthereumAddress network={'goerli'} hideBlockie address={peerCashout?.result.recipient} />
</p>
<p>
Transaction:
<EthereumAddress
transaction
network={'goerli'}
hideBlockie
address={peerCashout?.transactionHash}
/>
</p>
</div>
) : (
'None'
)}
</div>
)}
</Paper>
</div>
)
</Drawer>
</div>
)
}
+61 -51
View File
@@ -1,59 +1,69 @@
import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
import { Table, TableBody, TableCell, TableContainer, TableRow, TableHead, Paper, Container, CircularProgress } from '@material-ui/core';
import { ReactElement } from 'react'
import { makeStyles } from '@material-ui/core/styles'
import {
Table,
TableBody,
TableCell,
TableContainer,
TableRow,
TableHead,
Paper,
Container,
CircularProgress,
} from '@material-ui/core'
import { ConvertBalanceToBZZ } from '../../utils/common';
import { ConvertBalanceToBZZ } from '../../utils/common'
import type { AllSettlements, Settlements } from '@ethersphere/bee-js'
const useStyles = makeStyles({
table: {
minWidth: 650,
},
});
table: {
minWidth: 650,
},
})
interface IProps {
nodeSettlements: AllSettlements | null,
loading?: boolean,
}
function SettlementsTable(props: IProps) {
const classes = useStyles();
return (
<div>
{props.loading ?
<Container style={{textAlign:'center', padding:'50px'}}>
<CircularProgress />
</Container>
:
<TableContainer component={Paper}>
<Table className={classes.table} size="small" aria-label="simple table">
<TableHead>
<TableRow>
<TableCell>Peer</TableCell>
<TableCell>Received (BZZ)</TableCell>
<TableCell>Sent (BZZ)</TableCell>
</TableRow>
</TableHead>
<TableBody>
{props.nodeSettlements?.settlements.map((item: Settlements, idx: number) => (
<TableRow key={item.peer}>
<TableCell>{item.peer}</TableCell>
<TableCell style={{ fontFamily: 'monospace, monospace'}}>
{item.received > 0 ? ConvertBalanceToBZZ(item.received).toFixed(7).toLocaleString() : item.received}
</TableCell>
<TableCell style={{ fontFamily: 'monospace, monospace'}}>
{item.sent > 0 ? ConvertBalanceToBZZ(item.sent).toFixed(7).toLocaleString() : item.sent}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>}
</div>
)
interface Props {
nodeSettlements: AllSettlements | null
loading?: boolean
}
export default SettlementsTable;
function SettlementsTable(props: Props): ReactElement {
const classes = useStyles()
return (
<div>
{props.loading ? (
<Container style={{ textAlign: 'center', padding: '50px' }}>
<CircularProgress />
</Container>
) : (
<TableContainer component={Paper}>
<Table className={classes.table} size="small" aria-label="simple table">
<TableHead>
<TableRow>
<TableCell>Peer</TableCell>
<TableCell>Received (BZZ)</TableCell>
<TableCell>Sent (BZZ)</TableCell>
</TableRow>
</TableHead>
<TableBody>
{props.nodeSettlements?.settlements.map((item: Settlements) => (
<TableRow key={item.peer}>
<TableCell>{item.peer}</TableCell>
<TableCell style={{ fontFamily: 'monospace, monospace' }}>
{item.received > 0 ? ConvertBalanceToBZZ(item.received).toFixed(7).toLocaleString() : item.received}
</TableCell>
<TableCell style={{ fontFamily: 'monospace, monospace' }}>
{item.sent > 0 ? ConvertBalanceToBZZ(item.sent).toFixed(7).toLocaleString() : item.sent}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
)}
</div>
)
}
export default SettlementsTable
+141 -139
View File
@@ -1,166 +1,168 @@
import React from 'react';
import { withStyles, Theme, createStyles } from '@material-ui/core/styles';
import { Tabs, Tab, Box, Typography, Container, CircularProgress } from '@material-ui/core';
import { ReactElement, useState, ChangeEvent, ReactChild } from 'react'
import { withStyles, Theme, createStyles } from '@material-ui/core/styles'
import { Tabs, Tab, Box, Typography, Container, CircularProgress } from '@material-ui/core'
import AccountCard from '../accounting/AccountCard';
import BalancesTable from './BalancesTable';
import ChequebookTable from './ChequebookTable';
import SettlementsTable from './SettlementsTable';
import EthereumAddressCard from '../../components/EthereumAddressCard';
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard';
import AccountCard from '../accounting/AccountCard'
import BalancesTable from './BalancesTable'
import ChequebookTable from './ChequebookTable'
import SettlementsTable from './SettlementsTable'
import EthereumAddressCard from '../../components/EthereumAddressCard'
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard'
import { useApiNodeAddresses, useApiChequebookAddress, useApiChequebookBalance, useApiPeerBalances, useApiPeerCheques, useApiSettlements } from '../../hooks/apiHooks';
import {
useApiNodeAddresses,
useApiChequebookAddress,
useApiChequebookBalance,
useApiPeerBalances,
useApiPeerCheques,
useApiSettlements,
useApiHealth,
useDebugApiHealth,
} from '../../hooks/apiHooks'
interface TabPanelProps {
children?: React.ReactNode;
index: any;
value: any;
children?: ReactChild
index: number
value: number
}
function a11yProps(index: any) {
return {
id: `simple-tab-${index}`,
'aria-controls': `simple-tabpanel-${index}`,
};
function a11yProps(index: number) {
return {
id: `simple-tab-${index}`,
'aria-controls': `simple-tabpanel-${index}`,
}
}
export default function Accounting(props: any) {
const [value, setValue] = React.useState(0);
export default function Accounting(): ReactElement {
const [value, setValue] = useState(0)
const handleChange = (event: React.ChangeEvent<{}>, newValue: number) => {
setValue(newValue);
};
const handleChange = (event: ChangeEvent<unknown>, newValue: number) => {
setValue(newValue)
}
const { chequebookAddress, isLoadingChequebookAddress } = useApiChequebookAddress()
const { chequebookBalance, isLoadingChequebookBalance } = useApiChequebookBalance()
const { peerBalances, isLoadingPeerBalances } = useApiPeerBalances()
const { nodeAddresses, isLoadingNodeAddresses } = useApiNodeAddresses()
const { chequebookAddress, isLoadingChequebookAddress } = useApiChequebookAddress()
const { chequebookBalance, isLoadingChequebookBalance } = useApiChequebookBalance()
const { peerBalances, isLoadingPeerBalances } = useApiPeerBalances()
const { nodeAddresses, isLoadingNodeAddresses } = useApiNodeAddresses()
const { health, isLoadingHealth } = useApiHealth()
const { nodeHealth, isLoadingNodeHealth } = useDebugApiHealth()
const { peerCheques, isLoadingPeerCheques } = useApiPeerCheques()
const { settlements, isLoadingSettlements } = useApiSettlements()
const { peerCheques, isLoadingPeerCheques } = useApiPeerCheques()
const { settlements, isLoadingSettlements } = useApiSettlements()
function TabPanel(props: TabPanelProps) {
const { children, value, index, ...other } = props
function TabPanel(props: TabPanelProps) {
const { children, value, index, ...other } = props;
return (
<div
role="tabpanel"
hidden={value !== index}
id={`simple-tabpanel-${index}`}
aria-labelledby={`simple-tab-${index}`}
{...other}
>
{value === index && (
<Box style={{ marginTop: '20px' }}>
<Typography component="div">{children}</Typography>
</Box>
)}
</div>
);
}
const AntTabs = withStyles({
root: {
borderBottom: '1px solid #e8e8e8',
},
indicator: {
backgroundColor: '#3f51b5',
},
})(Tabs);
interface StyledTabProps {
label: string;
}
const AntTab = withStyles((theme: Theme) =>
createStyles({
root: {
textTransform: 'none',
minWidth: 72,
backgroundColor: 'transparent',
fontWeight: theme.typography.fontWeightRegular,
marginRight: theme.spacing(4),
fontFamily: [
'-apple-system',
'BlinkMacSystemFont',
'"Segoe UI"',
'Roboto',
'"Helvetica Neue"',
'Arial',
'sans-serif',
'"Apple Color Emoji"',
'"Segoe UI Emoji"',
'"Segoe UI Symbol"',
].join(','),
'&:hover': {
color: '#3f51b5',
opacity: 1,
},
'&$selected': {
color: '#3f51b5',
fontWeight: theme.typography.fontWeightMedium,
},
'&:focus': {
color: '#3f51b5',
},
},
selected: {},
}),
)((props: StyledTabProps) => <Tab disableRipple {...props} />);
return (
<div>
{props.nodeHealth?.status === 'ok' && props.health ?
<div
role="tabpanel"
hidden={value !== index}
id={`simple-tabpanel-${index}`}
aria-labelledby={`simple-tab-${index}`}
{...other}
>
{value === index && (
<Box style={{ marginTop: '20px' }}>
<Typography component="div">{children}</Typography>
</Box>
)}
</div>
)
}
const AntTabs = withStyles({
root: {
borderBottom: '1px solid #e8e8e8',
},
indicator: {
backgroundColor: '#3f51b5',
},
})(Tabs)
interface StyledTabProps {
label: string
}
const AntTab = withStyles((theme: Theme) =>
createStyles({
root: {
textTransform: 'none',
minWidth: 72,
backgroundColor: 'transparent',
fontWeight: theme.typography.fontWeightRegular,
marginRight: theme.spacing(4),
fontFamily: [
'-apple-system',
'BlinkMacSystemFont',
'"Segoe UI"',
'Roboto',
'"Helvetica Neue"',
'Arial',
'sans-serif',
'"Apple Color Emoji"',
'"Segoe UI Emoji"',
'"Segoe UI Symbol"',
].join(','),
'&:hover': {
color: '#3f51b5',
opacity: 1,
},
'&$selected': {
color: '#3f51b5',
fontWeight: theme.typography.fontWeightMedium,
},
'&:focus': {
color: '#3f51b5',
},
},
selected: {},
}),
)((props: StyledTabProps) => <Tab disableRipple {...props} />)
return (
<div>
{
// FIXME: this should be broken up
/* eslint-disable no-nested-ternary */
nodeHealth?.status === 'ok' && health ? (
<div>
<AccountCard
chequebookAddress={chequebookAddress}
isLoadingChequebookAddress={isLoadingChequebookAddress}
chequebookBalance={chequebookBalance}
isLoadingChequebookBalance={isLoadingChequebookBalance}
settlements={settlements}
isLoadingSettlements={isLoadingSettlements}
chequebookAddress={chequebookAddress}
isLoadingChequebookAddress={isLoadingChequebookAddress}
chequebookBalance={chequebookBalance}
isLoadingChequebookBalance={isLoadingChequebookBalance}
settlements={settlements}
isLoadingSettlements={isLoadingSettlements}
/>
<EthereumAddressCard
nodeAddresses={nodeAddresses}
isLoadingNodeAddresses={isLoadingNodeAddresses}
chequebookAddress={chequebookAddress}
isLoadingChequebookAddress={isLoadingChequebookAddress}
<EthereumAddressCard
nodeAddresses={nodeAddresses}
isLoadingNodeAddresses={isLoadingNodeAddresses}
chequebookAddress={chequebookAddress}
isLoadingChequebookAddress={isLoadingChequebookAddress}
/>
<AntTabs style={{ marginTop: '12px' }} value={value} onChange={handleChange} aria-label="ant example">
<AntTab label="Balances" {...a11yProps(0)} />
<AntTab label="Chequebook" {...a11yProps(1)} />
<AntTab label="Settlements" {...a11yProps(2)} />
<AntTab label="Balances" {...a11yProps(0)} />
<AntTab label="Chequebook" {...a11yProps(1)} />
<AntTab label="Settlements" {...a11yProps(2)} />
</AntTabs>
<TabPanel value={value} index={0}>
<BalancesTable
peerBalances={peerBalances}
loading={isLoadingPeerBalances}
/>
<BalancesTable peerBalances={peerBalances} loading={isLoadingPeerBalances} />
</TabPanel>
<TabPanel value={value} index={1}>
<ChequebookTable
peerCheques={peerCheques}
loading={isLoadingPeerCheques}
/>
<ChequebookTable peerCheques={peerCheques} loading={isLoadingPeerCheques} />
</TabPanel>
<TabPanel value={value} index={2}>
<SettlementsTable
nodeSettlements={settlements}
loading={isLoadingSettlements}
/>
<SettlementsTable nodeSettlements={settlements} loading={isLoadingSettlements} />
</TabPanel>
</div>
:
props.isLoadingHealth || props.isLoadingNodeHealth ?
<Container style={{textAlign:'center', padding:'50px'}}>
<CircularProgress />
</Container>
:
<TroubleshootConnectionCard />
}
</div>
)
</div>
) : isLoadingHealth || isLoadingNodeHealth ? (
<Container style={{ textAlign: 'center', padding: '50px' }}>
<CircularProgress />
</Container>
) : (
<TroubleshootConnectionCard />
) /* eslint-enable no-nested-ternary */
}
</div>
)
}
+128 -117
View File
@@ -1,14 +1,15 @@
import React, { useState } from 'react';
import { beeApi } from '../../services/bee';
import { ReactElement, useState } from 'react'
import { beeApi } from '../../services/bee'
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles';
import { Paper, InputBase, IconButton, Button, Container, CircularProgress } from '@material-ui/core';
import { Search } from '@material-ui/icons';
import {DropzoneArea} from 'material-ui-dropzone'
import ClipboardCopy from '../../components/ClipboardCopy';
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
import { Paper, InputBase, IconButton, Button, Container, CircularProgress } from '@material-ui/core'
import { Search } from '@material-ui/icons'
import { DropzoneArea } from 'material-ui-dropzone'
import ClipboardCopy from '../../components/ClipboardCopy'
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard';
import { Data, FileData } from '@ethersphere/bee-js';
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard'
import { Data, FileData } from '@ethersphere/bee-js'
import { useApiHealth, useDebugApiHealth } from '../../hooks/apiHooks'
const useStyles = makeStyles((theme: Theme) =>
createStyles({
@@ -30,128 +31,138 @@ const useStyles = makeStyles((theme: Theme) =>
margin: 4,
},
}),
);
)
export default function Files(props: any) {
const classes = useStyles();
export default function Files(): ReactElement {
const classes = useStyles()
const [inputMode, setInputMode] = useState<'browse' | 'upload'>('browse');
const [searchInput, setSearchInput] = useState('');
const [searchResult, setSearchResult] = useState<FileData<Data> | null>(null);
const [loadingSearch, setLoadingSearch] = useState(false);
const [inputMode, setInputMode] = useState<'browse' | 'upload'>('browse')
const [searchInput, setSearchInput] = useState('')
const [searchResult, setSearchResult] = useState<FileData<Data> | null>(null)
const [loadingSearch, setLoadingSearch] = useState(false)
const { health, isLoadingHealth } = useApiHealth()
const { nodeHealth, isLoadingNodeHealth } = useDebugApiHealth()
const [files, setFiles] = useState<File[]>([]);
const [uploadReference, setUploadReference] = useState('');
const [uploadingFile, setUploadingFile] = useState(false);
const [files, setFiles] = useState<File[]>([])
const [uploadReference, setUploadReference] = useState('')
const [uploadingFile, setUploadingFile] = useState(false)
const getFile = () => {
setLoadingSearch(true)
beeApi.files.downloadFile(searchInput)
.then(res => {
setSearchResult(res)
const downloadUrl = window.URL.createObjectURL(new Blob([res.data]));
const link = document.createElement('a');
link.href = downloadUrl;
link.setAttribute('download', 'file.zip'); //any other extension
document.body.appendChild(link);
link.click();
link.remove();
})
.catch(error => {
})
.finally(() => {
setLoadingSearch(false)
})
const getFile = () => {
setLoadingSearch(true)
beeApi.files
.downloadFile(searchInput)
.then(res => {
setSearchResult(res)
const downloadUrl = window.URL.createObjectURL(new Blob([res.data]))
const link = document.createElement('a')
link.href = downloadUrl
link.setAttribute('download', 'file.zip') //any other extension
document.body.appendChild(link)
link.click()
link.remove()
})
.catch(() => {
// FIXME: handle the error
})
.finally(() => {
setLoadingSearch(false)
})
}
const uploadFile = () => {
setUploadingFile(true)
beeApi.files
.uploadFile(files[0])
.then(hash => {
setUploadReference(hash)
setFiles([])
})
.catch(() => {
// FIXME: handle the error
})
.finally(() => {
setUploadingFile(false)
})
}
const handleChange = (files?: File[]) => {
if (files) {
setFiles(files)
}
}
const uploadFile = () => {
setUploadingFile(true)
beeApi.files.uploadFile(files[0])
.then(hash => {
setUploadReference(hash)
setFiles([])
})
.catch(error => {
})
.finally(() => {
setUploadingFile(false)
})
}
const handleChange = (files: any) => {
if (files) {
setFiles(files)
}
}
return (
<div>
{props.nodeHealth?.status === 'ok' && props.health ?
<Container maxWidth="sm">
<div style={{marginBottom: '7px'}}>
<Button color="primary" style={{marginRight: '7px'}} onClick={() => setInputMode('browse')}>Browse</Button>
<Button color="primary" onClick={() => setInputMode('upload')}>Upload</Button>
</div>
{inputMode === 'browse' ?
<Paper component="form" className={classes.root}>
<InputBase
return (
<div>
{
// FIXME: this should be broken up
/* eslint-disable no-nested-ternary */
nodeHealth?.status === 'ok' && health ? (
<Container maxWidth="sm">
<div style={{ marginBottom: '7px' }}>
<Button color="primary" style={{ marginRight: '7px' }} onClick={() => setInputMode('browse')}>
Browse
</Button>
<Button color="primary" onClick={() => setInputMode('upload')}>
Upload
</Button>
</div>
{inputMode === 'browse' ? (
<Paper component="form" className={classes.root}>
<InputBase
className={classes.input}
placeholder="Enter hash e.g. 0773a91efd6547c754fc1d95fb1c62c7d1b47f959c2caa685dfec8736da95c1c"
inputProps={{ 'aria-label': 'search swarm nodes' }}
onChange={(e) => setSearchInput(e.target.value)}
/>
<IconButton onClick={() => getFile()} className={classes.iconButton} aria-label="search">
<Search />
</IconButton>
onChange={e => setSearchInput(e.target.value)}
/>
<IconButton onClick={() => getFile()} className={classes.iconButton} aria-label="search">
<Search />
</IconButton>
</Paper>
:
) : (
<div>
{uploadingFile ?
<Container style={{textAlign:'center', padding:'50px'}}>
{uploadingFile ? (
<Container style={{ textAlign: 'center', padding: '50px' }}>
<CircularProgress />
</Container>
:
<div>
{uploadReference ?
<Paper component="form" className={classes.root} style={{marginBottom:'15px', display: 'flex'}}>
<span>{uploadReference}</span>
<ClipboardCopy
value={uploadReference}
/>
</Paper>
:
null
}
<DropzoneArea
onChange={handleChange}
/>
<div style={{marginTop:'15px'}}>
<Button onClick={() => uploadFile()} className={classes.iconButton}>
</Container>
) : (
<div>
{uploadReference ? (
<Paper
component="form"
className={classes.root}
style={{ marginBottom: '15px', display: 'flex' }}
>
<span>{uploadReference}</span>
<ClipboardCopy value={uploadReference} />
</Paper>
) : null}
<DropzoneArea onChange={handleChange} />
<div style={{ marginTop: '15px' }}>
<Button onClick={() => uploadFile()} className={classes.iconButton}>
Upload
</Button>
</Button>
</div>
</div>
</div>}
)}
</div>
}
{loadingSearch ?
<Container style={{textAlign:'center', padding:'50px'}}>
<CircularProgress />
</Container>
:
<div style={{padding:'20px'}} >
{searchResult}
</div>
}
</Container>
:
props.isLoadingHealth || props.isLoadingNodeHealth ?
<Container style={{textAlign:'center', padding:'50px'}}>
)}
{loadingSearch ? (
<Container style={{ textAlign: 'center', padding: '50px' }}>
<CircularProgress />
</Container>
:
<TroubleshootConnectionCard
/>}
</div>
)
</Container>
) : (
<div style={{ padding: '20px' }}>{searchResult}</div>
)}
</Container>
) : isLoadingHealth || isLoadingNodeHealth ? (
<Container style={{ textAlign: 'center', padding: '50px' }}>
<CircularProgress />
</Container>
) : (
<TroubleshootConnectionCard />
) /* eslint-enable no-nested-ternary */
}
</div>
)
}
+98 -77
View File
@@ -1,92 +1,113 @@
import React, { useState } from 'react';
import { makeStyles } from '@material-ui/core/styles';
import { Table, TableBody, TableCell, TableContainer, TableRow, TableHead, Button, Paper, Tooltip, Container, CircularProgress } from '@material-ui/core';
import { Autorenew } from '@material-ui/icons';
import React, { ReactElement, useState } from 'react'
import { makeStyles } from '@material-ui/core/styles'
import {
Table,
TableBody,
TableCell,
TableContainer,
TableRow,
TableHead,
Button,
Paper,
Tooltip,
Container,
CircularProgress,
} from '@material-ui/core'
import { Autorenew } from '@material-ui/icons'
import { beeDebugApi } from '../../services/bee';
import type { Peer } from '@ethersphere/bee-js';
import { beeDebugApi } from '../../services/bee'
import type { Peer } from '@ethersphere/bee-js'
const useStyles = makeStyles({
table: {
minWidth: 650,
},
});
table: {
minWidth: 650,
},
})
interface Props {
peers: Peer[] | null
isLoading: boolean
error: Error | null
peers: Peer[] | null
isLoading: boolean
error: Error | null
}
function PeerTable(props: Props) {
const classes = useStyles();
function PeerTable(props: Props): ReactElement {
const classes = useStyles()
const [peerLatency, setPeerLatency] = useState([{ peerId: '', rtt: '', loading: false }]);
const [peerLatency, setPeerLatency] = useState([{ peerId: '', rtt: '', loading: false }])
const PingPeer = async (peerId: string) => {
setPeerLatency([...peerLatency, { peerId: peerId, rtt: '', loading: true }])
beeDebugApi.connectivity.ping(peerId)
.then(res => {
setPeerLatency([...peerLatency, { peerId: peerId, rtt: res.rtt, loading: false }])
})
.catch(error => {
setPeerLatency([...peerLatency, { peerId: peerId, rtt: 'error', loading: false }])
})
}
if (props.isLoading) {
return (
<Container style={{textAlign:'center', padding:'50px'}}>
<CircularProgress />
</Container>
)
}
if (props.error || props.peers === null) {
return (
<Container style={{textAlign:'center', padding:'50px'}}>
<p>Failed to load peers</p>
</Container>
)
}
const PingPeer = (peerId: string) => {
setPeerLatency([...peerLatency, { peerId: peerId, rtt: '', loading: true }])
beeDebugApi.connectivity
.ping(peerId)
.then(res => {
setPeerLatency([...peerLatency, { peerId: peerId, rtt: res.rtt, loading: false }])
})
.catch(() => {
setPeerLatency([...peerLatency, { peerId: peerId, rtt: 'error', loading: false }])
})
}
if (props.isLoading) {
return (
<div>
<TableContainer component={Paper}>
<Table className={classes.table} aria-label="simple table">
<TableHead>
<TableRow>
<TableCell>Index</TableCell>
<TableCell>Peer Id</TableCell>
<TableCell align="right">Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{props.peers.map((peer: any, idx: number) => (
<TableRow key={peer.address}>
<TableCell component="th" scope="row">
{idx + 1}
</TableCell>
<TableCell>{peer.address}</TableCell>
<TableCell align="right">
<Tooltip title="Ping node">
<Button color="primary" onClick={() => PingPeer(peer.address)} >
{peerLatency.find(item => item.peerId === peer.address) ?
peerLatency.filter(item => item.peerId === peer.address)[0].loading ? <CircularProgress size={20} /> :
peerLatency.filter(item => item.peerId === peer.address)[0].rtt :
<Autorenew />}
</Button>
</Tooltip>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</div>
<Container style={{ textAlign: 'center', padding: '50px' }}>
<CircularProgress />
</Container>
)
}
if (props.error || props.peers === null) {
return (
<Container style={{ textAlign: 'center', padding: '50px' }}>
<p>Failed to load peers</p>
</Container>
)
}
return (
<div>
<TableContainer component={Paper}>
<Table className={classes.table} aria-label="simple table">
<TableHead>
<TableRow>
<TableCell>Index</TableCell>
<TableCell>Peer Id</TableCell>
<TableCell align="right">Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{props.peers.map((peer: Peer, idx: number) => (
<TableRow key={peer.address}>
<TableCell component="th" scope="row">
{idx + 1}
</TableCell>
<TableCell>{peer.address}</TableCell>
<TableCell align="right">
<Tooltip title="Ping node">
<Button color="primary" onClick={() => PingPeer(peer.address)}>
{
// FIXME: this should be broken up
/* eslint-disable no-nested-ternary */
peerLatency.find(item => item.peerId === peer.address) ? (
peerLatency.filter(item => item.peerId === peer.address)[0].loading ? (
<CircularProgress size={20} />
) : (
peerLatency.filter(item => item.peerId === peer.address)[0].rtt
)
) : (
<Autorenew />
)
/* eslint-enable no-nested-ternary */
}
</Button>
</Tooltip>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</div>
)
}
export default PeerTable
+26 -25
View File
@@ -1,31 +1,32 @@
import { Container, CircularProgress } from '@material-ui/core/';
import PeerTable from './PeerTable';
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard';
import { Container, CircularProgress } from '@material-ui/core/'
import PeerTable from './PeerTable'
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard'
import { useApiNodeTopology, useApiNodePeers, useDebugApiHealth } from '../../hooks/apiHooks';
import TopologyStats from '../../components/TopologyStats';
import { useApiNodeTopology, useApiNodePeers, useDebugApiHealth } from '../../hooks/apiHooks'
import TopologyStats from '../../components/TopologyStats'
import { ReactElement } from 'react'
export default function Peers() {
const topology = useApiNodeTopology()
const debugHealth = useDebugApiHealth()
const peers = useApiNodePeers()
if (debugHealth.isLoadingNodeHealth) {
return (
<Container style={{textAlign:'center', padding:'50px'}}>
<CircularProgress />
</Container>
)
}
if (debugHealth.error) {
return <TroubleshootConnectionCard />
}
export default function Peers(): ReactElement {
const topology = useApiNodeTopology()
const debugHealth = useDebugApiHealth()
const peers = useApiNodePeers()
if (debugHealth.isLoadingNodeHealth) {
return (
<>
<TopologyStats {...topology} />
<PeerTable {...peers} />
</>
<Container style={{ textAlign: 'center', padding: '50px' }}>
<CircularProgress />
</Container>
)
}
if (debugHealth.error) {
return <TroubleshootConnectionCard />
}
return (
<>
<TopologyStats {...topology} />
<PeerTable {...peers} />
</>
)
}
+81 -72
View File
@@ -1,77 +1,86 @@
import React, { useState } from 'react'
import { Paper, Container, TextField, Typography, Button } from '@material-ui/core';
import React, { ReactElement, useState } from 'react'
import { Paper, Container, TextField, Typography, Button } from '@material-ui/core'
export default function Settings() {
export default function Settings(): ReactElement {
const [refreshVisibility, toggleRefreshVisibility] = useState(false)
const [host, setHost] = useState('')
const [debugHost, setDebugHost] = useState('')
const [refreshVisibility, toggleRefreshVisibility] = useState(false)
const [host, setHost] = useState('')
const [debugHost, setDebugHost] = useState('')
const handleNewHostConnection = () => {
if (host) {
sessionStorage.setItem('api_host', host)
}
if (debugHost) {
sessionStorage.setItem('debug_api_host', debugHost)
}
if (host || debugHost) {
toggleRefreshVisibility(!refreshVisibility)
window.location.reload();
}
const handleNewHostConnection = () => {
if (host) {
sessionStorage.setItem('api_host', host)
}
return (
<div>
<Container>
<Typography variant="h4" gutterBottom>
Settings
</Typography>
<Paper>
<TextField
id="filled-full-width"
label="API Endpoint"
style={{ margin: 0 }}
placeholder="ex: 127.0.0.0.1:1633"
helperText="Enter node host override / port"
fullWidth
defaultValue={sessionStorage.getItem('api_host') ? sessionStorage.getItem('api_host') : process.env.REACT_APP_BEE_HOST}
margin="normal"
InputLabelProps={{
shrink: true,
}}
onChange={(e) => {
setHost(e.target.value)
toggleRefreshVisibility(true)
}}
variant="filled"
/>
</Paper>
<Paper style={{marginTop:'20px'}}>
<TextField
id="filled-full-width"
label="Debug API Endpoint"
style={{ margin: 0 }}
placeholder="ex: 127.0.0.0.1:1635"
helperText="Enter node debug host override / port"
fullWidth
defaultValue={sessionStorage.getItem('debug_api_host') ? sessionStorage.getItem('debug_api_host') : process.env.REACT_APP_BEE_DEBUG_HOST}
onChange={(e) => {
setDebugHost(e.target.value)
toggleRefreshVisibility(true)
}}
margin="normal"
InputLabelProps={{
shrink: true,
}}
variant="filled"
/>
</Paper>
{refreshVisibility ?
<div style={{marginTop:'20px'}}>
<Button variant='outlined' color='primary' onClick={() => handleNewHostConnection()}>Save</Button>
</div>
: null}
</Container>
</div>
)
if (debugHost) {
sessionStorage.setItem('debug_api_host', debugHost)
}
if (host || debugHost) {
toggleRefreshVisibility(!refreshVisibility)
window.location.reload()
}
}
return (
<div>
<Container>
<Typography variant="h4" gutterBottom>
Settings
</Typography>
<Paper>
<TextField
id="filled-full-width"
label="API Endpoint"
style={{ margin: 0 }}
placeholder="ex: 127.0.0.0.1:1633"
helperText="Enter node host override / port"
fullWidth
defaultValue={
sessionStorage.getItem('api_host') ? sessionStorage.getItem('api_host') : process.env.REACT_APP_BEE_HOST
}
margin="normal"
InputLabelProps={{
shrink: true,
}}
onChange={e => {
setHost(e.target.value)
toggleRefreshVisibility(true)
}}
variant="filled"
/>
</Paper>
<Paper style={{ marginTop: '20px' }}>
<TextField
id="filled-full-width"
label="Debug API Endpoint"
style={{ margin: 0 }}
placeholder="ex: 127.0.0.0.1:1635"
helperText="Enter node debug host override / port"
fullWidth
defaultValue={
sessionStorage.getItem('debug_api_host')
? sessionStorage.getItem('debug_api_host')
: process.env.REACT_APP_BEE_DEBUG_HOST
}
onChange={e => {
setDebugHost(e.target.value)
toggleRefreshVisibility(true)
}}
margin="normal"
InputLabelProps={{
shrink: true,
}}
variant="filled"
/>
</Paper>
{refreshVisibility ? (
<div style={{ marginTop: '20px' }}>
<Button variant="outlined" color="primary" onClick={() => handleNewHostConnection()}>
Save
</Button>
</div>
) : null}
</Container>
</div>
)
}
+133 -72
View File
@@ -1,15 +1,21 @@
import React, { useEffect, useState } from 'react';
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles';
import { Typography, Paper, Button, Step, StepLabel, StepContent, Stepper, StepButton } from '@material-ui/core/';
import { CheckCircle, Error, Sync, ExpandLessSharp, ExpandMoreSharp } from '@material-ui/icons/';
import DebugConnectionCheck from './SetupSteps/DebugConnectionCheck';
import NodeConnectionCheck from './SetupSteps/NodeConnectionCheck';
import VersionCheck from './SetupSteps/VersionCheck';
import EthereumConnectionCheck from './SetupSteps/EthereumConnectionCheck';
import ChequebookDeployFund from './SetupSteps/ChequebookDeployFund';
import PeerConnection from './SetupSteps/PeerConnection';
import { ReactElement, useEffect, useState } from 'react'
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
import { Typography, Paper, Button, Step, StepLabel, StepContent, Stepper, StepButton } from '@material-ui/core/'
import { CheckCircle, Error, Sync, ExpandLessSharp, ExpandMoreSharp } from '@material-ui/icons/'
import DebugConnectionCheck from './SetupSteps/DebugConnectionCheck'
import NodeConnectionCheck from './SetupSteps/NodeConnectionCheck'
import VersionCheck from './SetupSteps/VersionCheck'
import EthereumConnectionCheck from './SetupSteps/EthereumConnectionCheck'
import ChequebookDeployFund from './SetupSteps/ChequebookDeployFund'
import PeerConnection from './SetupSteps/PeerConnection'
import type {
ChequebookAddressResponse,
ChequebookBalanceResponse,
Health,
NodeAddresses,
Topology,
} from '@ethersphere/bee-js'
const useStyles = makeStyles((theme: Theme) =>
createStyles({
@@ -27,50 +33,100 @@ const useStyles = makeStyles((theme: Theme) =>
padding: theme.spacing(5),
},
}),
);
)
function getSteps() {
return [
'Debug Connection Check',
'Version Check',
'Version Check',
'Connect to Ethereum Blockchain',
'Deploy and Fund Chequebook',
'Node Connection Check',
'Node Connection Check',
'Connect to Peers',
];
]
}
interface Props {
nodeHealth: Health | null
nodeApiHealth: boolean
nodeAddresses: NodeAddresses | null
chequebookAddress: ChequebookAddressResponse | null
chequebookBalance: ChequebookBalanceResponse | null
beeRelease: LatestBeeRelease | null
nodeTopology: Topology | null
isLoadingBeeRelease: boolean
isLoadingNodeHealth: boolean
isLoadingNodeAddresses: boolean
isLoadingNodeTopology: boolean
isLoadingHealth: boolean
isLoadingChequebookAddress: boolean
isLoadingChequebookBalance: boolean
setStatusChecksVisible: (value: boolean) => void
apiHost: string
debugApiHost: string
}
function getStepContent(step: number, props: any) {
function getStepContent(step: number, props: Props) {
const {
nodeHealth,
debugApiHost,
beeRelease,
isLoadingBeeRelease,
nodeAddresses,
isLoadingNodeAddresses,
isLoadingChequebookBalance,
chequebookAddress,
chequebookBalance,
isLoadingChequebookAddress,
nodeApiHealth,
apiHost,
isLoadingNodeTopology,
nodeTopology,
} = props
switch (step) {
case 0:
return <DebugConnectionCheck {...props} />;
return <DebugConnectionCheck nodeHealth={nodeHealth} debugApiHost={debugApiHost} />
case 1:
return <VersionCheck {...props} />;
return <VersionCheck nodeHealth={nodeHealth} beeRelease={beeRelease} isLoadingBeeRelease={isLoadingBeeRelease} />
case 2:
return <EthereumConnectionCheck {...props} />;
return <EthereumConnectionCheck nodeAddresses={nodeAddresses} isLoadingNodeAddresses={isLoadingNodeAddresses} />
case 3:
return <ChequebookDeployFund {...props} />;
return (
<ChequebookDeployFund
chequebookAddress={chequebookAddress}
chequebookBalance={chequebookBalance}
isLoadingChequebookAddress={isLoadingChequebookAddress}
isLoadingChequebookBalance={isLoadingChequebookBalance}
/>
)
case 4:
return <NodeConnectionCheck {...props} />;
return <NodeConnectionCheck nodeApiHealth={nodeApiHealth} apiHost={apiHost} />
default:
return <PeerConnection {...props} />;
return <PeerConnection nodeTopology={nodeTopology} isLoadingNodeTopology={isLoadingNodeTopology} />
}
}
export default function NodeSetupWorkflow(props: any) {
const {nodeHealth, nodeApiHealth, nodeAddresses, chequebookAddress, chequebookBalance, beeRelease, nodeTopology, setStatusChecksVisible} = props
const classes = useStyles();
const [activeStep, setActiveStep] = useState(0);
const [completed, setCompleted] = useState<{ [k: number]: boolean }>({});
const steps = getSteps();
export default function NodeSetupWorkflow(props: Props): ReactElement {
const {
nodeHealth,
nodeApiHealth,
nodeAddresses,
chequebookAddress,
chequebookBalance,
beeRelease,
nodeTopology,
setStatusChecksVisible,
} = props
const classes = useStyles()
const [activeStep, setActiveStep] = useState(0)
const [completed, setCompleted] = useState<{ [k: number]: boolean }>({})
const steps = getSteps()
useEffect(() => {
const handleComplete = (index: number) => {
const newCompleted = completed;
newCompleted[index] = true;
setCompleted(newCompleted);
};
const newCompleted = completed
newCompleted[index] = true
setCompleted(newCompleted)
}
const evaluateNodeStatus = () => {
if (nodeHealth?.status === 'ok') {
@@ -88,7 +144,7 @@ export default function NodeSetupWorkflow(props: any) {
setActiveStep(3)
}
if (chequebookAddress?.chequebookaddress && chequebookBalance.totalBalance > 0) {
if (chequebookAddress?.chequebookaddress && chequebookBalance && chequebookBalance.totalBalance > 0) {
handleComplete(3)
setActiveStep(4)
}
@@ -104,45 +160,62 @@ export default function NodeSetupWorkflow(props: any) {
}
}
evaluateNodeStatus()
}, [nodeHealth, nodeApiHealth, nodeAddresses, chequebookAddress, beeRelease, chequebookBalance, nodeTopology, completed])
}, [
nodeHealth,
nodeApiHealth,
nodeAddresses,
chequebookAddress,
beeRelease,
chequebookBalance,
nodeTopology,
completed,
])
const handleNext = () => {
setActiveStep((prevActiveStep) => prevActiveStep + 1);
};
setActiveStep(prevActiveStep => prevActiveStep + 1)
}
const handleBack = () => {
setActiveStep((prevActiveStep) => prevActiveStep - 1);
};
setActiveStep(prevActiveStep => prevActiveStep - 1)
}
const handleSetupComplete = () => {
setStatusChecksVisible(false)
};
}
return (
<div className={classes.root}>
<Typography variant="h4" gutterBottom>
Node Setup
<span style={{marginLeft:'25px'}}>
<Button variant='outlined' size='small' onClick={() => window.location.reload()}><Sync/><span style={{marginLeft:'7px'}}>Refresh Checks</span></Button>
<span style={{ marginLeft: '25px' }}>
<Button variant="outlined" size="small" onClick={() => window.location.reload()}>
<Sync />
<span style={{ marginLeft: '7px' }}>Refresh Checks</span>
</Button>
</span>
</Typography>
<Stepper nonLinear activeStep={activeStep} orientation="vertical">
{steps.map((label, index) => (
<Step key={label}>
<StepLabel
onClick={() => setActiveStep(index === activeStep ? 6 : index)}
StepIconComponent={() => {
if(completed[index])
return <CheckCircle style={{color:'#32c48d', height: '25px', cursor:'pointer'}} />
else {
return <Error style={{color:'#c9201f', height: '25px', cursor:'pointer'}} />
}
}}
<StepLabel
onClick={() => setActiveStep(index === activeStep ? 6 : index)}
StepIconComponent={() => {
if (completed[index]) {
return <CheckCircle style={{ color: '#32c48d', height: '25px', cursor: 'pointer' }} />
} else {
return <Error style={{ color: '#c9201f', height: '25px', cursor: 'pointer' }} />
}
}}
>
<StepButton onClick={() => setActiveStep(index === activeStep ? 6 : index)} style={{justifyContent:'space-between'}}>
<div style={{display:'flex'}}>
<div style={{marginTop:'5px'}}>{label}</div>
<div style={{marginLeft:'12px'}}>{index === activeStep ? <ExpandLessSharp /> : <ExpandMoreSharp />}</div>
<StepButton
onClick={() => setActiveStep(index === activeStep ? 6 : index)}
style={{ justifyContent: 'space-between' }}
>
<div style={{ display: 'flex' }}>
<div style={{ marginTop: '5px' }}>{label}</div>
<div style={{ marginLeft: '12px' }}>
{index === activeStep ? <ExpandLessSharp /> : <ExpandMoreSharp />}
</div>
</div>
</StepButton>
</StepLabel>
@@ -150,20 +223,11 @@ export default function NodeSetupWorkflow(props: any) {
<Typography component="div">{getStepContent(index, props)}</Typography>
<div className={classes.actionsContainer}>
<div>
<Button
disabled={activeStep === 0}
onClick={handleBack}
className={classes.button}
>
<Button disabled={activeStep === 0} onClick={handleBack} className={classes.button}>
Back
</Button>
<Button
variant="contained"
color="primary"
onClick={handleNext}
className={classes.button}
>
Next
<Button variant="contained" color="primary" onClick={handleNext} className={classes.button}>
Next
</Button>
</div>
</div>
@@ -174,10 +238,7 @@ export default function NodeSetupWorkflow(props: any) {
{Object.values(completed).filter(value => value).length === 6 ? (
<Paper square elevation={0} className={classes.resetContainer}>
<Typography>Bee setup complete! Welcome to the swarm and the internet of decentralized storage</Typography>
<Button
onClick={handleBack}
className={classes.button}
>
<Button onClick={handleBack} className={classes.button}>
Back
</Button>
<Button onClick={handleSetupComplete} variant="contained" color="primary" className={classes.button}>
@@ -186,5 +247,5 @@ export default function NodeSetupWorkflow(props: any) {
</Paper>
) : null}
</div>
);
)
}
@@ -1,44 +1,57 @@
import { Typography } from '@material-ui/core/';
import { CheckCircle, Warning } from '@material-ui/icons/';
import EthereumAddress from '../../../components/EthereumAddress';
import DepositModal from '../../../components/DepositModal';
import CodeBlockTabs from '../../../components/CodeBlockTabs';
import { Typography } from '@material-ui/core/'
import { CheckCircle, Warning } from '@material-ui/icons/'
import EthereumAddress from '../../../components/EthereumAddress'
import DepositModal from '../../../components/DepositModal'
import CodeBlockTabs from '../../../components/CodeBlockTabs'
import type { ChequebookAddressResponse, ChequebookBalanceResponse } from '@ethersphere/bee-js'
import type { ReactElement } from 'react'
export default function ChequebookDeployFund(props: any) {
return (
<div>
<p style={{marginBottom:'20px', display:'flex'}}>
<span style={{ marginRight:'40px'}} >Deploy chequebook and fund with BZZ</span>
{props.chequebookAddress?.chequebookaddress ?
<DepositModal />
: null }
</p>
<div style={{ marginBottom:'10px' }}>
{props.chequebookAddress?.chequebookaddress && props.chequebookBalance && props.chequebookBalance?.totalBalance > 0 ?
<div>
<CheckCircle style={{color:'#32c48d', marginRight: '7px', height: '18px'}} />
<span>Your chequebook is deployed and funded!</span>
</div>
:
props.loadingChequebookAddress || props.isLoadingChequebookBalance ?
null
:
<div>
<Warning style={{color:'#ff9800', marginRight: '7px', height: '18px'}} />
<span>Your chequebook is either not deployed or funded. Run the below commands to get your address and deposit ETH. Then visit the BZZaar here <Typography variant='button'>https://bzz.ethswarm.org/?transaction=buy&amount=10&slippage=30&receiver=[ENTER_ADDRESS_HERE]</Typography> to get BZZ</span>
<CodeBlockTabs
showLineNumbers
linux={`bee-get-addr`}
mac={`bee-get-addr`}
/>
</div>
}
</div>
<Typography variant="subtitle1" gutterBottom>Chequebook Address</Typography>
<EthereumAddress
address={props.chequebookAddress?.chequebookaddress}
network={'goerli'}
/>
</div>
)
interface Props {
chequebookAddress: ChequebookAddressResponse | null
chequebookBalance: ChequebookBalanceResponse | null
isLoadingChequebookAddress: boolean
isLoadingChequebookBalance: boolean
}
const ChequebookDeployFund = (props: Props): ReactElement => (
<div>
<p style={{ marginBottom: '20px', display: 'flex' }}>
<span style={{ marginRight: '40px' }}>Deploy chequebook and fund with BZZ</span>
{props.chequebookAddress?.chequebookaddress ? <DepositModal /> : null}
</p>
<div style={{ marginBottom: '10px' }}>
{
// FIXME: this should be broken up
/* eslint-disable no-nested-ternary */
props.chequebookAddress?.chequebookaddress &&
props.chequebookBalance &&
props.chequebookBalance?.totalBalance > 0 ? (
<div>
<CheckCircle style={{ color: '#32c48d', marginRight: '7px', height: '18px' }} />
<span>Your chequebook is deployed and funded!</span>
</div>
) : props.isLoadingChequebookAddress || props.isLoadingChequebookBalance ? null : (
<div>
<Warning style={{ color: '#ff9800', marginRight: '7px', height: '18px' }} />
<span>
Your chequebook is either not deployed or funded. Run the below commands to get your address and deposit
ETH. Then visit the BZZaar here{' '}
<Typography variant="button">
https://bzz.ethswarm.org/?transaction=buy&amount=10&slippage=30&receiver=[ENTER_ADDRESS_HERE]
</Typography>{' '}
to get BZZ
</span>
<CodeBlockTabs showLineNumbers linux={`bee-get-addr`} mac={`bee-get-addr`} />
</div>
)
/* eslint-enable no-nested-ternary */
}
</div>
<Typography variant="subtitle1" gutterBottom>
Chequebook Address
</Typography>
<EthereumAddress address={props.chequebookAddress?.chequebookaddress} network={'goerli'} />
</div>
)
export default ChequebookDeployFund
@@ -1,76 +1,101 @@
import React from 'react'
import { Typography, Accordion, AccordionSummary, AccordionDetails } from '@material-ui/core/';
import MuiAlert from '@material-ui/lab/Alert';
import { CheckCircle, Error, ExpandMoreSharp } from '@material-ui/icons/';
import type { ReactElement } from 'react'
import { Typography, Accordion, AccordionSummary, AccordionDetails } from '@material-ui/core/'
import MuiAlert from '@material-ui/lab/Alert'
import { CheckCircle, Error, ExpandMoreSharp } from '@material-ui/icons/'
import ConnectToHost from '../../../components/ConnectToHost';
import ConnectToHost from '../../../components/ConnectToHost'
import CodeBlockTabs from '../../../components/CodeBlockTabs'
import type { Health } from '@ethersphere/bee-js'
export default function NodeConnectionCheck(props: any) {
return (
interface Props {
nodeHealth: Health | null
debugApiHost: string
}
export default function NodeConnectionCheck(props: Props): ReactElement {
return (
<div>
<p>Connect to Bee Node Debug API</p>
<div>
<div style={{ display: 'flex', marginBottom: '25px' }}>
{props.nodeHealth?.status === 'ok' ? (
<CheckCircle style={{ color: '#32c48d', marginRight: '7px', height: '18px' }} />
) : (
<Error style={{ color: '#c9201f', marginRight: '7px', height: '18px' }} />
)}
<span style={{ marginRight: '15px' }}>
Debug API (<Typography variant="button">{props.debugApiHost}</Typography>)
</span>
<ConnectToHost hostName={'debug_api_host'} defaultHost={props.debugApiHost} />
</div>
<div>
<p>Connect to Bee Node Debug API</p>
<div>
<div style={{display:'flex', marginBottom: '25px'}}>
{ props.nodeHealth?.status === 'ok' ?
<CheckCircle style={{color:'#32c48d', marginRight: '7px', height: '18px'}} />
:
<Error style={{color:'#c9201f', marginRight: '7px', height: '18px'}} />
}
<span style={{marginRight:'15px'}}>Debug API (<Typography variant="button">{props.debugApiHost}</Typography>)</span>
<ConnectToHost hostName={'debug_api_host'} defaultHost={props.debugApiHost} />
</div>
<div>
{ props.nodeHealth?.status !== 'ok' ?
<Typography component="div" variant="body2" gutterBottom style={{margin: '15px'}}>
We cannot connect to your nodes debug API at <Typography variant="button">{props.debugApiHost}</Typography>. Please check the following to troubleshoot your issue.
<Accordion style={{marginTop:'20px'}}>
<AccordionSummary
expandIcon={<ExpandMoreSharp />}
aria-controls="panel1a-content"
id="panel1a-header"
>
<Typography>Troubleshoot</Typography>
</AccordionSummary>
<AccordionDetails>
<Typography component="div">
{props.nodeHealth?.status !== 'ok' ? (
<Typography component="div" variant="body2" gutterBottom style={{ margin: '15px' }}>
We cannot connect to your nodes debug API at{' '}
<Typography variant="button">{props.debugApiHost}</Typography>. Please check the following to troubleshoot
your issue.
<Accordion style={{ marginTop: '20px' }}>
<AccordionSummary expandIcon={<ExpandMoreSharp />} aria-controls="panel1a-content" id="panel1a-header">
<Typography>Troubleshoot</Typography>
</AccordionSummary>
<AccordionDetails>
<Typography component="div">
<ol>
<li>Check the status of your node by running the below command to see if your node is running.</li>
<li>
Check the status of your node by running the below command to see if your node is running.
</li>
<CodeBlockTabs
showLineNumbers
linux={`sudo systemctl status bee`}
mac={`brew services status swarm-bee`}
showLineNumbers
linux={`sudo systemctl status bee`}
mac={`brew services status swarm-bee`}
/>
<li>If your node is running, check your firewall settings to make sure that port 1635 (or your custom specified port) is bound to localhost. If your node is not running try executing the below command to start your bee node</li>
<MuiAlert style={{marginTop:'10px', marginBottom:'10px'}} elevation={6} variant="filled" severity="error">
Your debug node API should never be completely open to the internet. If you want to connect remotely, make sure your firewall settings are set to only allow specific trusted IP addresses and block all other ports. A simple google search for "what is my ip" will show you your computers public IP address to allow.
<li>
If your node is running, check your firewall settings to make sure that port 1635 (or your
custom specified port) is bound to localhost. If your node is not running try executing the
below command to start your bee node
</li>
<MuiAlert
style={{ marginTop: '10px', marginBottom: '10px' }}
elevation={6}
variant="filled"
severity="error"
>
Your debug node API should never be completely open to the internet. If you want to connect
remotely, make sure your firewall settings are set to only allow specific trusted IP addresses
and block all other ports. A simple google search for &quot;what is my ip&quot; will show you
your computers public IP address to allow.
</MuiAlert>
<CodeBlockTabs
showLineNumbers
linux={`sudo systemctl start bee`}
mac={`brew services start swarm-bee`}
showLineNumbers
linux={`sudo systemctl start bee`}
mac={`brew services start swarm-bee`}
/>
<li>Run the commands to validate your node is running and see the log output.</li>
<CodeBlockTabs
showLineNumbers
linux={`sudo systemctl status bee \njournalctl --lines=100 --follow --unit bee`}
mac={`brew services status swarm-bee \ntail -f /usr/local/var/log/swarm-bee/bee.log`}
showLineNumbers
linux={`sudo systemctl status bee \njournalctl --lines=100 --follow --unit bee`}
mac={`brew services status swarm-bee \ntail -f /usr/local/var/log/swarm-bee/bee.log`}
/>
<li>Lastly, check your nodes configuration settings to validate the debug API is enabled and the Cross Origin Resource Sharing (CORS) setting is configured to allow your host. Config parameter <strong>debug-api-enable</strong> must be set to <strong>true</strong> and <strong>cors-allowed-origins</strong> must be set to your host domain or IP. If edits are made to the configuration run the restart command below for changes to take effect.</li>
<li>
Lastly, check your nodes configuration settings to validate the debug API is enabled and the
Cross Origin Resource Sharing (CORS) setting is configured to allow your host. Config parameter{' '}
<strong>debug-api-enable</strong> must be set to <strong>true</strong> and{' '}
<strong>cors-allowed-origins</strong> must be set to your host domain or IP. If edits are made
to the configuration run the restart command below for changes to take effect.
</li>
<CodeBlockTabs
showLineNumbers
linux={`sudo vi /etc/bee/bee.yaml\nsudo systemctl restart bee`}
mac={`sudo vi /etc/bee/bee.yaml \nbrew services restart swarm-bee`}
showLineNumbers
linux={`sudo vi /etc/bee/bee.yaml\nsudo systemctl restart bee`}
mac={`sudo vi /etc/bee/bee.yaml \nbrew services restart swarm-bee`}
/>
</ol>
</Typography>
</AccordionDetails>
</Accordion>
</Typography>
:
null}
</div>
</div>
</Typography>
</AccordionDetails>
</Accordion>
</Typography>
) : null}
</div>
</div>
</div>
)
}
)
}
@@ -1,37 +1,56 @@
import React from 'react';
import { Typography } from '@material-ui/core/';
import { CheckCircle, Warning } from '@material-ui/icons/';
import EthereumAddress from '../../../components/EthereumAddress';
import type { ReactElement } from 'react'
import { Typography } from '@material-ui/core/'
import { CheckCircle, Warning } from '@material-ui/icons/'
import EthereumAddress from '../../../components/EthereumAddress'
import type { NodeAddresses } from '@ethersphere/bee-js'
export default function EthereumConnectionCheck(props: any) {
return (
<div>
<p>Connect to the ethereum blockchain.</p>
<div style={{ marginBottom:'10px' }}>
{props.nodeAddresses?.ethereum ?
<div>
<CheckCircle style={{color:'#32c48d', marginRight: '7px', height: '18px'}} />
<span>Your connected to the Ethereum network</span>
</div>
:
props.loadingNodeAddresses ?
null
:
<div>
<Warning style={{color:'#ff9800', marginRight: '7px', height: '18px'}} />
<span>Your not connected to the Ethereum network. </span>
<p>Your Bee node must have access to the Ethereum blockchain, so that it can interact and deploy your chequebook contract. You can run <a href='https://github.com/goerli/testnet' rel='noreferrer' target='_blank'>your own Goerli node</a>, or use a provider such as <a href='https://rpc.slock.it/goerli' rel='noreferrer' target='_blank'>rpc.slock.it/goerli</a> or <a href='https://infura.io/' rel='noreferrer' target='_blank'>Infura</a>.
By default, Bee expects a local Goerli node at http://localhost:8545. To use a provider instead, simply change your <strong>--swap-endpoint</strong> in your configuration file.
</p>
</div>
}
</div>
<Typography variant="subtitle1" gutterBottom>Node Address</Typography>
<EthereumAddress
address={props.nodeAddresses?.ethereum}
network={'goerli'}
/>
</div>
)
interface Props {
nodeAddresses: NodeAddresses | null
isLoadingNodeAddresses: boolean
}
export default function EthereumConnectionCheck(props: Props): ReactElement {
return (
<div>
<p>Connect to the ethereum blockchain.</p>
<div style={{ marginBottom: '10px' }}>
{
// FIXME: this should be broken up
/* eslint-disable no-nested-ternary */
props.nodeAddresses?.ethereum ? (
<div>
<CheckCircle style={{ color: '#32c48d', marginRight: '7px', height: '18px' }} />
<span>Your connected to the Ethereum network</span>
</div>
) : props.isLoadingNodeAddresses ? null : (
<div>
<Warning style={{ color: '#ff9800', marginRight: '7px', height: '18px' }} />
<span>Your not connected to the Ethereum network. </span>
<p>
Your Bee node must have access to the Ethereum blockchain, so that it can interact and deploy your
chequebook contract. You can run{' '}
<a href="https://github.com/goerli/testnet" rel="noreferrer" target="_blank">
your own Goerli node
</a>
, or use a provider such as{' '}
<a href="https://rpc.slock.it/goerli" rel="noreferrer" target="_blank">
rpc.slock.it/goerli
</a>{' '}
or{' '}
<a href="https://infura.io/" rel="noreferrer" target="_blank">
Infura
</a>
. By default, Bee expects a local Goerli node at http://localhost:8545. To use a provider instead,
simply change your <strong>--swap-endpoint</strong> in your configuration file.
</p>
</div>
) /* eslint-enable no-nested-ternary */
}
</div>
<Typography variant="subtitle1" gutterBottom>
Node Address
</Typography>
<EthereumAddress address={props.nodeAddresses?.ethereum} network={'goerli'} />
</div>
)
}
@@ -1,64 +1,71 @@
import React from 'react'
import { Typography, Accordion, AccordionSummary, AccordionDetails } from '@material-ui/core/';
import { CheckCircle, Error, ExpandMoreSharp } from '@material-ui/icons/';
import React, { ReactElement } from 'react'
import { Typography, Accordion, AccordionSummary, AccordionDetails } from '@material-ui/core/'
import { CheckCircle, Error, ExpandMoreSharp } from '@material-ui/icons/'
import ConnectToHost from '../../../components/ConnectToHost';
import ConnectToHost from '../../../components/ConnectToHost'
import CodeBlockTabs from '../../../components/CodeBlockTabs'
export default function NodeConnectionCheck(props: any) {
return (
<div>
<p>Connect to Bee Node API</p>
<div style={{display:'flex', marginBottom: '25px'}}>
{ props.nodeApiHealth ?
<CheckCircle style={{color:'#32c48d', marginRight: '7px', height: '18px'}} />
:
<Error style={{color:'#c9201f', marginRight: '7px', height: '18px'}} />
}
<span style={{marginRight:'15px'}}>Node API (<Typography variant="button">{props.apiHost}</Typography>)</span>
<ConnectToHost hostName='api_host' defaultHost={props.apiHost} />
</div>
<div>
{ !props.nodeApiHealth ?
<Typography component="div" variant="body2" gutterBottom style={{margin: '15px'}}>
We cannot connect to your nodes API at <Typography variant="button">{props.apiHost}</Typography>. Please check the following to troubleshoot your issue.
<Accordion style={{marginTop:'20px'}}>
<AccordionSummary
expandIcon={<ExpandMoreSharp />}
aria-controls="panel1a-content"
id="panel1a-header"
>
<Typography>Troubleshoot</Typography>
</AccordionSummary>
<AccordionDetails>
<Typography component="div">
<ol>
<li>Check the status of your node by running the below command to see if your node is running.</li>
<CodeBlockTabs
showLineNumbers
linux={`sudo systemctl status bee`}
mac={`brew services status swarm-bee`}
/>
<li>If your node is running, check your firewall settings to make sure that port 1633 (or your custom specified port) is exposed to the internet. If your node is not running try executing the below command to start your bee node</li>
<CodeBlockTabs
showLineNumbers
linux={`sudo systemctl start bee`}
mac={`brew services start swarm-bee`}
/>
<li>Run the commands to validate your node is running and see the log output.</li>
<CodeBlockTabs
showLineNumbers
linux={`sudo systemctl status bee \njournalctl --lines=100 --follow --unit bee`}
mac={`brew services status swarm-bee \ntail -f /usr/local/var/log/swarm-bee/bee.log`}
/>
</ol>
</Typography>
</AccordionDetails>
</Accordion>
</Typography>
:
null}
</div>
</div>
)
interface Props {
nodeApiHealth: boolean
apiHost: string
}
export default function NodeConnectionCheck(props: Props): ReactElement {
return (
<div>
<p>Connect to Bee Node API</p>
<div style={{ display: 'flex', marginBottom: '25px' }}>
{props.nodeApiHealth ? (
<CheckCircle style={{ color: '#32c48d', marginRight: '7px', height: '18px' }} />
) : (
<Error style={{ color: '#c9201f', marginRight: '7px', height: '18px' }} />
)}
<span style={{ marginRight: '15px' }}>
Node API (<Typography variant="button">{props.apiHost}</Typography>)
</span>
<ConnectToHost hostName="api_host" defaultHost={props.apiHost} />
</div>
<div>
{!props.nodeApiHealth ? (
<Typography component="div" variant="body2" gutterBottom style={{ margin: '15px' }}>
We cannot connect to your nodes API at <Typography variant="button">{props.apiHost}</Typography>. Please
check the following to troubleshoot your issue.
<Accordion style={{ marginTop: '20px' }}>
<AccordionSummary expandIcon={<ExpandMoreSharp />} aria-controls="panel1a-content" id="panel1a-header">
<Typography>Troubleshoot</Typography>
</AccordionSummary>
<AccordionDetails>
<Typography component="div">
<ol>
<li>Check the status of your node by running the below command to see if your node is running.</li>
<CodeBlockTabs
showLineNumbers
linux={`sudo systemctl status bee`}
mac={`brew services status swarm-bee`}
/>
<li>
If your node is running, check your firewall settings to make sure that port 1633 (or your custom
specified port) is exposed to the internet. If your node is not running try executing the below
command to start your bee node
</li>
<CodeBlockTabs
showLineNumbers
linux={`sudo systemctl start bee`}
mac={`brew services start swarm-bee`}
/>
<li>Run the commands to validate your node is running and see the log output.</li>
<CodeBlockTabs
showLineNumbers
linux={`sudo systemctl status bee \njournalctl --lines=100 --follow --unit bee`}
mac={`brew services status swarm-bee \ntail -f /usr/local/var/log/swarm-bee/bee.log`}
/>
</ol>
</Typography>
</AccordionDetails>
</Accordion>
</Typography>
) : null}
</div>
</div>
)
}
+51 -43
View File
@@ -1,45 +1,53 @@
import React from 'react';
import { Typography } from '@material-ui/core/';
import { CheckCircle, Warning } from '@material-ui/icons/';
import React, { ReactElement } from 'react'
import { Typography } from '@material-ui/core/'
import { CheckCircle, Warning } from '@material-ui/icons/'
import { Topology } from '@ethersphere/bee-js'
export default function PeerConnection(props: any) {
return (
<div>
<p>Connect to Peers</p>
<div style={{ marginBottom:'10px' }}>
{props.nodeTopology.connected && props.nodeTopology.connected > 0 ?
<div>
<CheckCircle style={{color:'#32c48d', marginRight: '7px', height: '18px'}} />
<span>Your connected to {props.nodeTopology.connected} peers!</span>
</div>
:
props.loadingNodeTopology ?
null
:
<div>
<Warning style={{color:'#ff9800', marginRight: '7px', height: '18px'}} />
<span>Your node is not connected to any peers</span>
</div>
}
</div>
<div style={{display:'flex'}}>
<div style={{marginRight:'30px'}}>
<Typography component="div" variant="subtitle1" gutterBottom color="textSecondary">
<span>Connected Peers</span>
</Typography>
<Typography component="h2" variant="h5">
{ props.nodeTopology.connected }
</Typography>
</div>
<div>
<Typography component="div" variant="subtitle1" gutterBottom color="textSecondary">
<span>Discovered Nodes</span>
</Typography>
<Typography component="h2" variant="h5">
{ props.nodeTopology.population }
</Typography>
</div>
</div>
</div>
)
interface Props {
nodeTopology: Topology | null
isLoadingNodeTopology: boolean
}
export default function PeerConnection(props: Props): ReactElement {
return (
<div>
<p>Connect to Peers</p>
<div style={{ marginBottom: '10px' }}>
html_url
{
// FIXME: this should be broken up
/* eslint-disable no-nested-ternary */
props.nodeTopology?.connected && props.nodeTopology?.connected > 0 ? (
<div>
<CheckCircle style={{ color: '#32c48d', marginRight: '7px', height: '18px' }} />
<span>Your connected to {props.nodeTopology.connected} peers!</span>
</div>
) : props.isLoadingNodeTopology ? null : (
<div>
<Warning style={{ color: '#ff9800', marginRight: '7px', height: '18px' }} />
<span>Your node is not connected to any peers</span>
</div>
) /* eslint-enable no-nested-ternary */
}
</div>
<div style={{ display: 'flex' }}>
<div style={{ marginRight: '30px' }}>
<Typography component="div" variant="subtitle1" gutterBottom color="textSecondary">
<span>Connected Peers</span>
</Typography>
<Typography component="h2" variant="h5">
{props.nodeTopology?.connected}
</Typography>
</div>
<div>
<Typography component="div" variant="subtitle1" gutterBottom color="textSecondary">
<span>Discovered Nodes</span>
</Typography>
<Typography component="h2" variant="h5">
{props.nodeTopology?.population}
</Typography>
</div>
</div>
</div>
)
}
+79 -43
View File
@@ -1,45 +1,81 @@
import React from 'react';
import { Typography } from '@material-ui/core/';
import { CheckCircle, Warning } from '@material-ui/icons/';
import CodeBlockTabs from '../../../components/CodeBlockTabs';
import React, { ReactElement } from 'react'
import { Typography } from '@material-ui/core/'
import { CheckCircle, Warning } from '@material-ui/icons/'
import CodeBlockTabs from '../../../components/CodeBlockTabs'
import { Health } from '@ethersphere/bee-js'
export default function VersionCheck(props: any) {
return (
<div>
<p>Check to make sure the latest version of <a href='https://github.com/ethersphere/bee' rel='noreferrer' target='_blank'>Bee</a> is running</p>
{props.beeRelease && props.beeRelease.name === `v${props.nodeReadiness?.version?.split('-')[0]}` ?
<div>
<CheckCircle style={{color:'#32c48d', marginRight: '7px', height: '18px'}} />
<span>Your running the latest version of Bee</span>
</div>
:
props.loadingBeeRelease ?
null
:
<div>
<Warning style={{color:'#ff9800', marginRight: '7px', height: '18px'}} />
<span>Your Bee version is out of date. Please update to the <a href={props.beeRelease.html_url} rel='noreferrer' target='_blank'>latest</a> before continuing. Rerun the installation script below to upgrade. Reference the docs for help with updating. <a href='https://docs.ethswarm.org/docs/installation/manual#upgrading-bee' rel='noreferrer' target='_blank'>Docs</a></span>
<CodeBlockTabs
showLineNumbers
linux={`bee version\nwget https://github.com/ethersphere/bee/releases/download/${props.beeRelease.name}/bee_${props.nodeReadiness?.version?.split('-')[0]}_amd64.deb\nsudo dpkg -i bee_${props.nodeReadiness?.version?.split('-')[0]}_amd64.deb`}
mac={`bee version\nbrew tap ethersphere/tap\nbrew install swarm-bee\nbrew services start swarm-bee`}
/>
</div>
}
<div style={{display:'flex'}}>
<div style={{marginRight:'30px'}}>
<p><span>Current Version</span></p>
<Typography component="h5" variant="h5">
<span>{props.nodeReadiness?.version ? ` v${props.nodeReadiness?.version?.split('-')[0]}` : '-'}</span>
</Typography>
</div>
<div>
<p><span>Latest Version</span></p>
<Typography component="h5" variant="h5">
<span>{props.beeRelease && props.beeRelease.name ? props.beeRelease.name : '-'}</span>
</Typography>
</div>
</div>
</div>
)
interface Props {
beeRelease: LatestBeeRelease | null
isLoadingBeeRelease: boolean
nodeHealth: Health | null
}
export default function VersionCheck(props: Props): ReactElement {
return (
<div>
<p>
Check to make sure the latest version of{' '}
<a href="https://github.com/ethersphere/bee" rel="noreferrer" target="_blank">
Bee
</a>{' '}
is running
</p>
{
// FIXME: this should be broken up
/* eslint-disable no-nested-ternary */
props.beeRelease && props.beeRelease.name === `v${props.nodeHealth?.version?.split('-')[0]}` ? (
<div>
<CheckCircle style={{ color: '#32c48d', marginRight: '7px', height: '18px' }} />
<span>Your running the latest version of Bee</span>
</div>
) : props.isLoadingBeeRelease ? null : (
<div>
<Warning style={{ color: '#ff9800', marginRight: '7px', height: '18px' }} />
<span>
Your Bee version is out of date. Please update to the{' '}
<a href={props.beeRelease?.html_url} rel="noreferrer" target="_blank">
latest
</a>{' '}
before continuing. Rerun the installation script below to upgrade. Reference the docs for help with
updating.{' '}
<a
href="https://docs.ethswarm.org/docs/installation/manual#upgrading-bee"
rel="noreferrer"
target="_blank"
>
Docs
</a>
</span>
<CodeBlockTabs
showLineNumbers
linux={`bee version\nwget https://github.com/ethersphere/bee/releases/download/${
props.beeRelease?.name
}/bee_${props.nodeHealth?.version?.split('-')[0]}_amd64.deb\nsudo dpkg -i bee_${
props.nodeHealth?.version?.split('-')[0]
}_amd64.deb`}
mac={`bee version\nbrew tap ethersphere/tap\nbrew install swarm-bee\nbrew services start swarm-bee`}
/>
</div>
) /* eslint-enable no-nested-ternary */
}
<div style={{ display: 'flex' }}>
<div style={{ marginRight: '30px' }}>
<p>
<span>Current Version</span>
</p>
<Typography component="h5" variant="h5">
<span>{props.nodeHealth?.version ? ` v${props.nodeHealth?.version?.split('-')[0]}` : '-'}</span>
</Typography>
</div>
<div>
<p>
<span>Latest Version</span>
</p>
<Typography component="h5" variant="h5">
<span>{props.beeRelease && props.beeRelease.name ? props.beeRelease.name : '-'}</span>
</Typography>
</div>
</div>
</div>
)
}
+126 -114
View File
@@ -1,13 +1,13 @@
import React, { useState } from 'react'
import { Link } from 'react-router-dom';
import { ReactElement, useState } from 'react'
import { Link } from 'react-router-dom'
import { Theme, createStyles, makeStyles } from '@material-ui/core/styles';
import { Card, CardContent, Typography, Chip, Button } from '@material-ui/core/';
import { CheckCircle, Error, ArrowRight, ArrowDropUp } from '@material-ui/icons/';
import { Skeleton } from '@material-ui/lab';
import type { Health, NodeAddresses, Topology } from '@ethersphere/bee-js';
import { createStyles, makeStyles } from '@material-ui/core/styles'
import { Card, CardContent, Typography, Chip, Button } from '@material-ui/core/'
import { CheckCircle, Error, ArrowRight, ArrowDropUp } from '@material-ui/icons/'
import { Skeleton } from '@material-ui/lab'
import type { Health, NodeAddresses, Topology } from '@ethersphere/bee-js'
const useStyles = makeStyles((theme: Theme) =>
const useStyles = makeStyles(() =>
createStyles({
root: {
display: 'flex',
@@ -22,120 +22,132 @@ const useStyles = makeStyles((theme: Theme) =>
flex: '1 0 auto',
},
status: {
color: '#2145a0',
backgroundColor: '#e1effe',
}
color: '#2145a0',
backgroundColor: '#e1effe',
},
}),
);
)
interface IProps{
nodeHealth: Health,
loadingNodeHealth: boolean,
beeRelease: any,
loadingBeeRelease: boolean,
nodeAddresses: NodeAddresses,
nodeTopology: Topology,
loadingNodeTopology: boolean,
setStatusChecksVisible: any,
interface Props {
nodeHealth: Health | null
loadingNodeHealth: boolean
beeRelease: LatestBeeRelease | null
loadingBeeRelease: boolean
nodeAddresses: NodeAddresses
nodeTopology: Topology
loadingNodeTopology: boolean
setStatusChecksVisible: (value: boolean) => void
}
function StatusCard(props: IProps) {
const classes = useStyles();
function StatusCard(props: Props): ReactElement {
const classes = useStyles()
const [underlayAddressesVisible, setUnderlayAddresessVisible] = useState<Boolean>(false)
const [underlayAddressesVisible, setUnderlayAddresessVisible] = useState<boolean>(false)
return (
<div>
<Card className={classes.root}>
{ !props.loadingNodeHealth && props.nodeHealth ?
<div className={classes.details}>
<CardContent className={classes.content}>
<Typography component="h5" variant="h5" style={{display:'flex', justifyContent:'space-between'}}>
{ props.nodeHealth.status === 'ok' ?
<div>
<CheckCircle style={{color:'#32c48d', marginRight: '7px'}} />
<span>Connected to Bee Node</span>
</div>
:
<div>
<Error style={{color:'#c9201f', marginRight: '7px'}} />
<span>Could not connect to Bee Node</span>
</div>
}
<Button variant='outlined' color='primary' size='small' style={{marginLeft:'12px'}} onClick={() => props.setStatusChecksVisible(true)}>View Status Checks</Button>
</Typography>
<div style={{marginBottom: '20px' }}>
<span style={{marginRight:'20px'}}>Discovered Nodes: { props.nodeTopology.population }</span>
<span style={{marginRight:'20px'}}>
<span>Connected Peers: </span>
<Link to='/peers/'>
{ props.nodeTopology.connected }
</Link>
</span>
</div>
return (
<div>
<Card className={classes.root}>
{!props.loadingNodeHealth && props.nodeHealth ? (
<div className={classes.details}>
<CardContent className={classes.content}>
<Typography component="h5" variant="h5" style={{ display: 'flex', justifyContent: 'space-between' }}>
{props.nodeHealth.status === 'ok' ? (
<div>
<CheckCircle style={{ color: '#32c48d', marginRight: '7px' }} />
<span>Connected to Bee Node</span>
</div>
) : (
<div>
<Error style={{ color: '#c9201f', marginRight: '7px' }} />
<span>Could not connect to Bee Node</span>
</div>
)}
<Button
variant="outlined"
color="primary"
size="small"
style={{ marginLeft: '12px' }}
onClick={() => props.setStatusChecksVisible(true)}
>
View Status Checks
</Button>
</Typography>
<div style={{ marginBottom: '20px' }}>
<span style={{ marginRight: '20px' }}>Discovered Nodes: {props.nodeTopology.population}</span>
<span style={{ marginRight: '20px' }}>
<span>Connected Peers: </span>
<Link to="/peers/">{props.nodeTopology.connected}</Link>
</span>
</div>
<div>
<Typography component="div" variant="subtitle2" gutterBottom>
<span>AGENT: </span>
<a href="https://github.com/ethersphere/bee" rel="noreferrer" target="_blank">
Bee
</a>
<span>{props.nodeHealth?.version ? ` v${props.nodeHealth.version}` : '-'}</span>
{
// FIXME: this should be broken up
/* eslint-disable no-nested-ternary */
props.beeRelease && props.beeRelease.name === `v${props.nodeHealth?.version?.split('-')[0]}` ? (
<Chip
style={{ marginLeft: '7px', color: '#2145a0' }}
size="small"
label="latest"
className={classes.status}
/>
) : props.loadingBeeRelease ? (
''
) : (
<Typography variant="button">update</Typography>
)
/* eslint-enable no-nested-ternary */
}
</Typography>
<Typography component="div" variant="subtitle2" gutterBottom>
<span>PUBLIC KEY: </span>
<span>{props.nodeAddresses.public_key ? props.nodeAddresses.public_key : '-'}</span>
</Typography>
<Typography component="div" variant="subtitle2" gutterBottom>
<span>PSS PUBLIC KEY: </span>
<span>{props.nodeAddresses.pss_public_key ? props.nodeAddresses.pss_public_key : '-'}</span>
</Typography>
<Typography component="div" variant="subtitle2" gutterBottom>
<Typography component="div" style={{ marginTop: '20px' }}>
<span>OVERLAY ADDRESS (PEER ID): </span>
<span>{props.nodeAddresses.overlay ? props.nodeAddresses.overlay : '-'}</span>
</Typography>
<Typography component="div" onClick={() => setUnderlayAddresessVisible(!underlayAddressesVisible)}>
<Button color="primary" style={{ padding: 0, marginTop: '6px' }}>
{underlayAddressesVisible ? (
<ArrowDropUp style={{ fontSize: '12px' }} />
) : (
<ArrowRight style={{ fontSize: '12px' }} />
)}
<span>Underlay Addresses</span>
</Button>
</Typography>
{underlayAddressesVisible ? (
<div>
<Typography component="div" variant="subtitle2" gutterBottom>
<span>AGENT: </span>
<a href='https://github.com/ethersphere/bee' rel='noreferrer' target='_blank'>Bee</a>
<span>{props.nodeHealth?.version ? ` v${props.nodeHealth.version}` : '-'}</span>
{props.beeRelease && props.beeRelease.name === `v${props.nodeHealth?.version?.split('-')[0]}` ?
<Chip
style={{ marginLeft: '7px', color: '#2145a0' }}
size="small"
label='latest'
className={classes.status}
/>
:
props.loadingBeeRelease ?
''
:
<Typography variant="button">update</Typography>
}
</Typography>
<Typography component="div" variant="subtitle2" gutterBottom>
<span>PUBLIC KEY: </span>
<span>{ props.nodeAddresses.public_key ? props.nodeAddresses.public_key : '-' }</span>
</Typography>
<Typography component="div" variant="subtitle2" gutterBottom>
<span>PSS PUBLIC KEY: </span>
<span>{ props.nodeAddresses.pss_public_key ? props.nodeAddresses.pss_public_key : '-' }</span>
</Typography>
<Typography component="div" variant="subtitle2" gutterBottom>
<Typography component="div" style={{marginTop:'20px'}}>
<span>OVERLAY ADDRESS (PEER ID): </span>
<span>{ props.nodeAddresses.overlay ? props.nodeAddresses.overlay : '-' }</span>
</Typography>
<Typography component="div" onClick={() => setUnderlayAddresessVisible(!underlayAddressesVisible)}>
<Button color="primary" style={{padding: 0, marginTop:'6px'}}>
{ underlayAddressesVisible ?
<ArrowDropUp style={{fontSize:'12px'}} /> :
<ArrowRight style={{fontSize:'12px'}} />
}
<span>Underlay Addresses</span>
</Button>
</Typography>
{underlayAddressesVisible ?
<div>
{ props.nodeAddresses.underlay ?
props.nodeAddresses.underlay.map(item => (<li>{item}</li>))
: '-' }
</div>
: null}
</Typography>
{props.nodeAddresses.underlay
? props.nodeAddresses.underlay.map(item => <li key={item}>{item}</li>)
: '-'}
</div>
</CardContent>
</div>
:
<div style={{padding: '16px'}}>
<Skeleton width={650} height={32} animation="wave" />
<Skeleton width={650} height={24} animation="wave" />
<Skeleton width={650} height={24} animation="wave" />
</div>
}
</Card>
</div>
)
) : null}
</Typography>
</div>
</CardContent>
</div>
) : (
<div style={{ padding: '16px' }}>
<Skeleton width={650} height={32} animation="wave" />
<Skeleton width={650} height={24} animation="wave" />
<Skeleton width={650} height={24} animation="wave" />
</div>
)}
</Card>
</div>
)
}
export default StatusCard
+128 -120
View File
@@ -1,135 +1,143 @@
import React, { useState, useEffect } from 'react'
import axios from 'axios';
import { Container, CircularProgress } from '@material-ui/core';
import { useState, useEffect, ReactElement } from 'react'
import axios from 'axios'
import { Container, CircularProgress } from '@material-ui/core'
import NodeSetupWorkflow from './NodeSetupWorkflow';
import StatusCard from './StatusCard';
import EthereumAddressCard from '../../components/EthereumAddressCard';
import { useApiHealth, useDebugApiHealth, useApiNodeAddresses, useApiChequebookAddress, useApiNodeTopology, useApiChequebookBalance } from '../../hooks/apiHooks';
import NodeSetupWorkflow from './NodeSetupWorkflow'
import StatusCard from './StatusCard'
import EthereumAddressCard from '../../components/EthereumAddressCard'
import {
useApiHealth,
useDebugApiHealth,
useApiNodeAddresses,
useApiChequebookAddress,
useApiNodeTopology,
useApiChequebookBalance,
} from '../../hooks/apiHooks'
export default function Status() {
const [beeRelease, setBeeRelease] = useState({ name: ''});
const [isLoadingBeeRelease, setIsLoadingBeeRelease] = useState<boolean>(false);
export default function Status(): ReactElement {
const [beeRelease, setBeeRelease] = useState<LatestBeeRelease | null>(null)
const [isLoadingBeeRelease, setIsLoadingBeeRelease] = useState<boolean>(false)
const [apiHost, setApiHost] = useState('');
const [debugApiHost, setDebugApiHost] = useState('');
const [apiHost, setApiHost] = useState('')
const [debugApiHost, setDebugApiHost] = useState('')
const [statusChecksVisible, setStatusChecksVisible] = useState<boolean>(false);
const [statusChecksVisible, setStatusChecksVisible] = useState<boolean>(false)
const { health, isLoadingHealth } = useApiHealth()
const { nodeHealth, isLoadingNodeHealth } = useDebugApiHealth()
const { nodeAddresses, isLoadingNodeAddresses } = useApiNodeAddresses()
const { chequebookAddress, isLoadingChequebookAddress } = useApiChequebookAddress()
const { topology: nodeTopology, isLoading: isLoadingNodeTopology } = useApiNodeTopology()
const { chequebookBalance, isLoadingChequebookBalance } = useApiChequebookBalance()
const { health, isLoadingHealth } = useApiHealth()
const { nodeHealth, isLoadingNodeHealth } = useDebugApiHealth()
const { nodeAddresses, isLoadingNodeAddresses } = useApiNodeAddresses()
const { chequebookAddress, isLoadingChequebookAddress } = useApiChequebookAddress()
const { topology: nodeTopology, isLoading: isLoadingNodeTopology } = useApiNodeTopology()
const { chequebookBalance, isLoadingChequebookBalance } = useApiChequebookBalance()
const fetchLatestBeeRelease = () => {
setIsLoadingBeeRelease(true)
axios
.get(`${process.env.REACT_APP_BEE_GITHUB_REPO_URL}/releases/latest`)
.then(res => {
setBeeRelease(res.data)
})
.catch(() => {
// FIXME: should do something about the error
})
.finally(() => {
setIsLoadingBeeRelease(false)
})
}
const fetchLatestBeeRelease = async () => {
setIsLoadingBeeRelease(true)
axios.get(`${process.env.REACT_APP_BEE_GITHUB_REPO_URL}/releases/latest`)
.then(res => {
setBeeRelease(res.data)
})
.catch(error => {
console.log(error)
})
.finally(() => {
setIsLoadingBeeRelease(false)
})
const fetchApiHost = () => {
let apiHost
if (sessionStorage.getItem('api_host')) {
apiHost = String(sessionStorage.getItem('api_host') || '')
} else {
apiHost = String(process.env.REACT_APP_BEE_HOST)
}
setApiHost(apiHost)
}
const fetchApiHost = () => {
let apiHost
if (sessionStorage.getItem('api_host')) {
apiHost = String(sessionStorage.getItem('api_host') || '')
} else {
apiHost = String(process.env.REACT_APP_BEE_HOST)
}
setApiHost(apiHost)
const fetchDebugApiHost = () => {
let debugApiHost
if (sessionStorage.getItem('debug_api_host')) {
debugApiHost = String(sessionStorage.getItem('debug_api_host') || '')
} else {
debugApiHost = String(process.env.REACT_APP_BEE_DEBUG_HOST)
}
setDebugApiHost(debugApiHost)
}
const fetchDebugApiHost = () => {
let debugApiHost
if (sessionStorage.getItem('debug_api_host')) {
debugApiHost = String(sessionStorage.getItem('debug_api_host') || '')
} else {
debugApiHost = String(process.env.REACT_APP_BEE_DEBUG_HOST)
}
setDebugApiHost(debugApiHost)
}
useEffect(() => {
fetchApiHost()
fetchDebugApiHost()
fetchLatestBeeRelease()
}, [])
useEffect(() => {
fetchApiHost()
fetchDebugApiHost()
fetchLatestBeeRelease()
}, []);
return (
// FIXME: this should be broken up
/* eslint-disable no-nested-ternary */
return (
<div>
{nodeHealth?.status === 'ok' &&
health &&
beeRelease &&
beeRelease.name === `v${nodeHealth?.version?.split('-')[0]}` &&
nodeAddresses?.ethereum &&
chequebookAddress?.chequebookaddress &&
chequebookBalance &&
chequebookBalance?.totalBalance > 0 &&
nodeTopology?.connected &&
nodeTopology?.connected > 0 &&
!statusChecksVisible ? (
<div>
{nodeHealth?.status === 'ok' &&
health &&
beeRelease &&
beeRelease.name === `v${nodeHealth?.version?.split('-')[0]}` &&
nodeAddresses?.ethereum &&
chequebookAddress?.chequebookaddress && chequebookBalance && chequebookBalance?.totalBalance > 0 &&
nodeTopology?.connected && nodeTopology?.connected > 0 &&
!statusChecksVisible ?
<div>
<StatusCard
nodeHealth={nodeHealth}
loadingNodeHealth={isLoadingNodeHealth}
beeRelease={beeRelease}
loadingBeeRelease={isLoadingBeeRelease}
nodeAddresses={nodeAddresses}
loadingNodeTopology={isLoadingNodeTopology}
nodeTopology={nodeTopology}
setStatusChecksVisible={setStatusChecksVisible}
/>
<EthereumAddressCard
nodeAddresses={nodeAddresses}
isLoadingNodeAddresses={isLoadingNodeAddresses}
chequebookAddress={chequebookAddress}
isLoadingChequebookAddress={isLoadingChequebookAddress}
/>
</div>
:
( isLoadingNodeHealth || isLoadingHealth || isLoadingChequebookAddress ||
isLoadingNodeTopology || isLoadingBeeRelease || isLoadingNodeAddresses || isLoadingBeeRelease || isLoadingChequebookBalance
)
?
<Container style={{textAlign:'center', padding:'50px'}}>
<CircularProgress />
</Container>
:
<NodeSetupWorkflow
beeRelease={beeRelease}
isLoadingBeeRelease={isLoadingBeeRelease}
nodeHealth={nodeHealth}
isLoadingNodeHealth={isLoadingNodeHealth}
nodeAddresses={nodeAddresses}
isLoadingNodeAddresses={isLoadingNodeAddresses}
nodeTopology={nodeTopology}
isLoadingNodeTopology={isLoadingNodeTopology}
nodeApiHealth={health}
isLoadingHealth={isLoadingHealth}
chequebookAddress={chequebookAddress}
isLoadingChequebookAddress={isLoadingChequebookAddress}
chequebookBalance={chequebookBalance}
isLoadingChequebookBalance={isLoadingChequebookBalance}
apiHost={apiHost}
debugApiHost={debugApiHost}
setStatusChecksVisible={setStatusChecksVisible}
/>
}
<StatusCard
nodeHealth={nodeHealth}
loadingNodeHealth={isLoadingNodeHealth}
beeRelease={beeRelease}
loadingBeeRelease={isLoadingBeeRelease}
nodeAddresses={nodeAddresses}
loadingNodeTopology={isLoadingNodeTopology}
nodeTopology={nodeTopology}
setStatusChecksVisible={setStatusChecksVisible}
/>
<EthereumAddressCard
nodeAddresses={nodeAddresses}
isLoadingNodeAddresses={isLoadingNodeAddresses}
chequebookAddress={chequebookAddress}
isLoadingChequebookAddress={isLoadingChequebookAddress}
/>
</div>
)
) : isLoadingNodeHealth ||
isLoadingHealth ||
isLoadingChequebookAddress ||
isLoadingNodeTopology ||
isLoadingBeeRelease ||
isLoadingNodeAddresses ||
isLoadingBeeRelease ||
isLoadingChequebookBalance ? (
<Container style={{ textAlign: 'center', padding: '50px' }}>
<CircularProgress />
</Container>
) : (
<NodeSetupWorkflow
beeRelease={beeRelease}
isLoadingBeeRelease={isLoadingBeeRelease}
nodeHealth={nodeHealth}
isLoadingNodeHealth={isLoadingNodeHealth}
nodeAddresses={nodeAddresses}
isLoadingNodeAddresses={isLoadingNodeAddresses}
nodeTopology={nodeTopology}
isLoadingNodeTopology={isLoadingNodeTopology}
nodeApiHealth={health}
isLoadingHealth={isLoadingHealth}
chequebookAddress={chequebookAddress}
isLoadingChequebookAddress={isLoadingChequebookAddress}
chequebookBalance={chequebookBalance}
isLoadingChequebookBalance={isLoadingChequebookBalance}
apiHost={apiHost}
debugApiHost={debugApiHost}
setStatusChecksVisible={setStatusChecksVisible}
/>
)}
</div>
)
}
+5
View File
@@ -1 +1,6 @@
/// <reference types="react-scripts" />
interface LatestBeeRelease {
name: string
html_url: string
}
+10 -10
View File
@@ -1,15 +1,15 @@
import { ReportHandler } from 'web-vitals';
import { ReportHandler } from 'web-vitals'
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
const reportWebVitals = (onPerfEntry?: ReportHandler): void => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
getCLS(onPerfEntry)
getFID(onPerfEntry)
getFCP(onPerfEntry)
getLCP(onPerfEntry)
getTTFB(onPerfEntry)
})
}
};
}
export default reportWebVitals;
export default reportWebVitals
-13
View File
@@ -1,13 +0,0 @@
import React from 'react';
import { Route } from 'react-router-dom';
const AppRoute = ( {component: Component, layout: Layout, ...rest }) => (
<Route {...rest} render={props => (
<Layout {...props}>
<Component {...props} />
</Layout>
)} />
)
export default AppRoute;
+23
View File
@@ -0,0 +1,23 @@
import type { JSXElementConstructor, ReactElement } from 'react'
import { Route, RouteComponentProps } from 'react-router-dom'
interface Props {
component: JSXElementConstructor<RouteComponentProps>
layout: JSXElementConstructor<RouteComponentProps>
exact?: boolean
path: string
}
const AppRoute = ({ component: Component, layout: Layout, exact, path }: Props): ReactElement => (
<Route
exact={exact}
path={path}
render={props => (
<Layout {...props}>
<Component {...props} />
</Layout>
)}
/>
)
export default AppRoute
+19 -20
View File
@@ -1,27 +1,26 @@
import React from 'react';
import { Switch } from 'react-router-dom';
import type { ReactElement } from 'react'
import { Switch } from 'react-router-dom'
import AppRoute from './AppRoute';
import AppRoute from './AppRoute'
// layouts
import Dashboard from '../layout/Dashboard';
import Dashboard from '../layout/Dashboard'
// pages
import Status from '../pages/status/index';
import Files from '../pages/files/index';
import Peers from '../pages/peers/index';
import Accounting from '../pages/accounting/index';
import Settings from '../pages/settings/index';
import Status from '../pages/status/index'
import Files from '../pages/files/index'
import Peers from '../pages/peers/index'
import Accounting from '../pages/accounting/index'
import Settings from '../pages/settings/index'
const BaseRouter = (): ReactElement => (
<Switch>
<AppRoute exact path="/" layout={Dashboard} component={Status} />
<AppRoute exact path="/files/" layout={Dashboard} component={Files} />
<AppRoute exact path="/peers/" layout={Dashboard} component={Peers} />
<AppRoute exact path="/accounting/" layout={Dashboard} component={Accounting} />
<AppRoute exact path="/settings/" layout={Dashboard} component={Settings} />
</Switch>
)
const BaseRouter = (props: any) => (
<Switch>
<AppRoute exact path='/' layout={ Dashboard } component={Status}/>
<AppRoute exact path='/files/' layout={ Dashboard } component={Files}/>
<AppRoute exact path='/peers/' layout={ Dashboard } component={Peers}/>
<AppRoute exact path='/accounting/' layout={ Dashboard } component={Accounting}/>
<AppRoute exact path='/settings/' layout={ Dashboard } component={Settings}/>
</Switch>
);
export default BaseRouter;
export default BaseRouter
+97 -76
View File
@@ -1,95 +1,116 @@
import { Bee, BeeDebug, Reference } from "@ethersphere/bee-js";
import {
AllSettlements,
BalanceResponse,
Bee,
BeeDebug,
CashoutResponse,
ChequebookAddressResponse,
ChequebookBalanceResponse,
Data,
DepositTokensResponse,
FileData,
Health,
LastCashoutActionResponse,
LastChequesForPeerResponse,
LastChequesResponse,
NodeAddresses,
Peer,
PingResponse,
Reference,
Topology,
WithdrawTokensResponse,
} from '@ethersphere/bee-js'
const beeJSClient = () => {
let apiHost = process.env.REACT_APP_BEE_HOST || 'http://localhost:1633'
if (sessionStorage.getItem('api_host')) {
apiHost = String(sessionStorage.getItem('api_host'))
}
let apiHost = process.env.REACT_APP_BEE_HOST || 'http://localhost:1633'
return new Bee(apiHost)
if (sessionStorage.getItem('api_host')) {
apiHost = String(sessionStorage.getItem('api_host'))
}
return new Bee(apiHost)
}
const beeJSDebugClient = () => {
let debugApiHost = process.env.REACT_APP_BEE_DEBUG_HOST || 'http://localhost:1635'
if (sessionStorage.getItem('debug_api_host')) {
debugApiHost = String(sessionStorage.getItem('debug_api_host'))
}
let debugApiHost = process.env.REACT_APP_BEE_DEBUG_HOST || 'http://localhost:1635'
return new BeeDebug(debugApiHost)
if (sessionStorage.getItem('debug_api_host')) {
debugApiHost = String(sessionStorage.getItem('debug_api_host'))
}
return new BeeDebug(debugApiHost)
}
export const beeApi = {
status: {
health() {
return beeJSClient().isConnected()
}
status: {
health(): Promise<boolean> {
return beeJSClient().isConnected()
},
files: {
uploadFile(file: File) {
return beeJSClient().uploadFile(file)
},
downloadFile(hash: string | Reference) {
return beeJSClient().downloadFile(hash)
},
},
files: {
uploadFile(file: File): Promise<Reference> {
return beeJSClient().uploadFile(file)
},
downloadFile(hash: string | Reference): Promise<FileData<Data>> {
return beeJSClient().downloadFile(hash)
},
},
}
export const beeDebugApi = {
status: {
nodeHealth() {
return beeJSDebugClient().getHealth()
},
status: {
nodeHealth(): Promise<Health> {
return beeJSDebugClient().getHealth()
},
connectivity: {
addresses() {
return beeJSDebugClient().getNodeAddresses()
},
listPeers() {
return beeJSDebugClient().getPeers()
},
topology() {
return beeJSDebugClient().getTopology()
},
ping(peerId: string) {
return beeJSDebugClient().pingPeer(peerId)
}
},
connectivity: {
addresses(): Promise<NodeAddresses> {
return beeJSDebugClient().getNodeAddresses()
},
balance: {
balances() {
return beeJSDebugClient().getAllBalances()
}
listPeers(): Promise<Peer[]> {
return beeJSDebugClient().getPeers()
},
chequebook: {
address() {
return beeJSDebugClient().getChequebookAddress()
},
balance() {
return beeJSDebugClient().getChequebookBalance()
},
getLastCheques() {
return beeJSDebugClient().getLastCheques()
},
peerCashout(peerId: string) {
return beeJSDebugClient().cashoutLastCheque(peerId)
},
getPeerLastCashout(peerId: string) {
return beeJSDebugClient().getLastCashoutAction(peerId)
},
getPeerLastCheques(peerId: string) {
return beeJSDebugClient().getLastChequesForPeer(peerId)
},
withdraw(amount: bigint) {
return beeJSDebugClient().withdrawTokens(amount)
},
deposit(amount: bigint) {
return beeJSDebugClient().depositTokens(amount)
},
topology(): Promise<Topology> {
return beeJSDebugClient().getTopology()
},
settlements: {
getSettlements() {
return beeJSDebugClient().getAllSettlements()
}
}
}
ping(peerId: string): Promise<PingResponse> {
return beeJSDebugClient().pingPeer(peerId)
},
},
balance: {
balances(): Promise<BalanceResponse> {
return beeJSDebugClient().getAllBalances()
},
},
chequebook: {
address(): Promise<ChequebookAddressResponse> {
return beeJSDebugClient().getChequebookAddress()
},
balance(): Promise<ChequebookBalanceResponse> {
return beeJSDebugClient().getChequebookBalance()
},
getLastCheques(): Promise<LastChequesResponse> {
return beeJSDebugClient().getLastCheques()
},
peerCashout(peerId: string): Promise<CashoutResponse> {
return beeJSDebugClient().cashoutLastCheque(peerId)
},
getPeerLastCashout(peerId: string): Promise<LastCashoutActionResponse> {
return beeJSDebugClient().getLastCashoutAction(peerId)
},
getPeerLastCheques(peerId: string): Promise<LastChequesForPeerResponse> {
return beeJSDebugClient().getLastChequesForPeer(peerId)
},
withdraw(amount: bigint): Promise<WithdrawTokensResponse> {
return beeJSDebugClient().withdrawTokens(amount)
},
deposit(amount: bigint): Promise<DepositTokensResponse> {
return beeJSDebugClient().depositTokens(amount)
},
},
settlements: {
getSettlements(): Promise<AllSettlements> {
return beeJSDebugClient().getAllSettlements()
},
},
}
+1 -1
View File
@@ -2,4 +2,4 @@
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';
import '@testing-library/jest-dom'
+33 -49
View File
@@ -1,4 +1,4 @@
import { createMuiTheme } from '@material-ui/core/styles';
import { createMuiTheme } from '@material-ui/core/styles'
declare module '@material-ui/core/styles/createPalette' {
interface TypeBackground {
@@ -7,54 +7,38 @@ declare module '@material-ui/core/styles/createPalette' {
}
export const lightTheme = createMuiTheme({
palette: {
type: "light",
background: {
default: '#fafafa',
},
primary: {
main: '#6a6a6a',
},
secondary: {
main: '#333333',
},
palette: {
type: 'light',
background: {
default: '#fafafa',
},
typography: {
fontFamily: [
'Work Sans',
'Montserrat',
'Nunito',
'Roboto',
'"Helvetica Neue"',
'Arial',
'sans-serif'
].join(','),
}
});
primary: {
main: '#6a6a6a',
},
secondary: {
main: '#333333',
},
},
typography: {
fontFamily: ['Work Sans', 'Montserrat', 'Nunito', 'Roboto', '"Helvetica Neue"', 'Arial', 'sans-serif'].join(','),
},
})
export const darkTheme = createMuiTheme({
palette: {
type: "dark",
background: {
default: '#0d1117',
paper: '#161b22',
},
primary: {
main: '#dd7700',
},
secondary: {
main: '#1f2937',
},
palette: {
type: 'dark',
background: {
default: '#0d1117',
paper: '#161b22',
},
typography: {
fontFamily: [
'Work Sans',
'Montserrat',
'Nunito',
'Roboto',
'"Helvetica Neue"',
'Arial',
'sans-serif'
].join(','),
}
});
primary: {
main: '#dd7700',
},
secondary: {
main: '#1f2937',
},
},
typography: {
fontFamily: ['Work Sans', 'Montserrat', 'Nunito', 'Roboto', '"Helvetica Neue"', 'Arial', 'sans-serif'].join(','),
},
})
+3 -4
View File
@@ -1,4 +1,3 @@
export const ConvertBalanceToBZZ = (amount: number) => {
return amount / (10 ** 16)
}
export const ConvertBalanceToBZZ = (amount: number): number => {
return amount / 10 ** 16
}