UI: NodeList added Pie Chart and extracted Table Data
All checks were successful
build / test (pull_request) Successful in 39s
All checks were successful
build / test (pull_request) Successful in 39s
This commit is contained in:
parent
3d3dcc800b
commit
de9cac534b
File diff suppressed because it is too large
Load Diff
334
pkgs/ui/package-lock.json
generated
334
pkgs/ui/package-lock.json
generated
@ -19,10 +19,12 @@
|
|||||||
"classnames": "^2.3.2",
|
"classnames": "^2.3.2",
|
||||||
"eslint": "8.46.0",
|
"eslint": "8.46.0",
|
||||||
"eslint-config-next": "13.4.12",
|
"eslint-config-next": "13.4.12",
|
||||||
|
"hex-rgb": "^5.0.0",
|
||||||
"next": "13.4.12",
|
"next": "13.4.12",
|
||||||
"postcss": "8.4.27",
|
"postcss": "8.4.27",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
|
"recharts": "^2.7.3",
|
||||||
"tailwindcss": "3.3.3",
|
"tailwindcss": "3.3.3",
|
||||||
"typescript": "5.1.6"
|
"typescript": "5.1.6"
|
||||||
},
|
},
|
||||||
@ -929,6 +931,60 @@
|
|||||||
"tslib": "^2.4.0"
|
"tslib": "^2.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/d3-array": {
|
||||||
|
"version": "3.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.0.5.tgz",
|
||||||
|
"integrity": "sha512-Qk7fpJ6qFp+26VeQ47WY0mkwXaiq8+76RJcncDEfMc2ocRzXLO67bLFRNI4OX1aGBoPzsM5Y2T+/m1pldOgD+A=="
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-color": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-HKuicPHJuvPgCD+np6Se9MQvS6OCbJmOjGvylzMJRlDwUXjKTTXs6Pwgk79O09Vj/ho3u1ofXnhFOaEWWPrlwA=="
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-ease": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-aMo4eaAOijJjA6uU+GIeW018dvy9+oH5Y2VPPzjjfxevvGQ/oRDs+tfYC9b50Q4BygRR8yE2QCLsrT0WtAVseA=="
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-interpolate": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-jx5leotSeac3jr0RePOH1KdR9rISG91QIE4Q2PYTu4OymLTZfA3SrnURSLzKH48HmXVUru50b8nje4E79oQSQw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/d3-color": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-path": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-0g/A+mZXgFkQxN3HniRDbXMN79K3CdTpLsevj+PXiTcb2hVyvkZUBg37StmgCQkaD84cUJ4uaDAWq7UJOQy2Tg=="
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-scale": {
|
||||||
|
"version": "4.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.3.tgz",
|
||||||
|
"integrity": "sha512-PATBiMCpvHJSMtZAMEhc2WyL+hnzarKzI6wAHYjhsonjWJYGq5BXTzQjv4l8m2jO183/4wZ90rKvSeT7o72xNQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/d3-time": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-shape": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-6Uh86YFF7LGg4PQkuO2oG6EMBRLuW9cbavUW46zkIO5kuS2PfTqo2o9SkgtQzguBHbLgNnU90UNsITpsX1My+A==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/d3-path": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-time": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg=="
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-timer": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-HNB/9GHqu7Fo8AQiugyJbv6ZxYz58wef0esl4Mv828w1ZKpAshw/uFWVDUcIB9KKFeFKoxS3cHY07FFgtTRZ1g=="
|
||||||
|
},
|
||||||
"node_modules/@types/json5": {
|
"node_modules/@types/json5": {
|
||||||
"version": "0.0.29",
|
"version": "0.0.29",
|
||||||
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
|
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
|
||||||
@ -1669,6 +1725,11 @@
|
|||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/css-unit-converter": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-unit-converter/-/css-unit-converter-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA=="
|
||||||
|
},
|
||||||
"node_modules/cssesc": {
|
"node_modules/cssesc": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
||||||
@ -1685,6 +1746,116 @@
|
|||||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
|
||||||
"integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ=="
|
"integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ=="
|
||||||
},
|
},
|
||||||
|
"node_modules/d3-array": {
|
||||||
|
"version": "3.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
|
||||||
|
"integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
|
||||||
|
"dependencies": {
|
||||||
|
"internmap": "1 - 2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-color": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-ease": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-format": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-interpolate": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-color": "1 - 3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-path": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-scale": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-array": "2.10.0 - 3",
|
||||||
|
"d3-format": "1 - 3",
|
||||||
|
"d3-interpolate": "1.2.0 - 3",
|
||||||
|
"d3-time": "2.1.1 - 3",
|
||||||
|
"d3-time-format": "2 - 4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-shape": {
|
||||||
|
"version": "3.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
|
||||||
|
"integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-path": "^3.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-time": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-array": "2 - 3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-time-format": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-time": "1 - 3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-timer": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/damerau-levenshtein": {
|
"node_modules/damerau-levenshtein": {
|
||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
|
||||||
@ -1706,6 +1877,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/decimal.js-light": {
|
||||||
|
"version": "2.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
|
||||||
|
"integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="
|
||||||
|
},
|
||||||
"node_modules/deep-is": {
|
"node_modules/deep-is": {
|
||||||
"version": "0.1.4",
|
"version": "0.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
|
||||||
@ -2379,6 +2555,11 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/eventemitter3": {
|
||||||
|
"version": "4.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
|
||||||
|
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="
|
||||||
|
},
|
||||||
"node_modules/execa": {
|
"node_modules/execa": {
|
||||||
"version": "7.2.0",
|
"version": "7.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz",
|
||||||
@ -2406,6 +2587,14 @@
|
|||||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
|
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
|
||||||
},
|
},
|
||||||
|
"node_modules/fast-equals": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fast-glob": {
|
"node_modules/fast-glob": {
|
||||||
"version": "3.3.1",
|
"version": "3.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz",
|
||||||
@ -2805,6 +2994,17 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/hex-rgb": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/hex-rgb/-/hex-rgb-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-NQO+lgVUCtHxZ792FodgW0zflK+ozS9X9dwGp9XvvmPlH7pyxd588cn24TD3rmPm/N0AIRXF10Otah8yKqGw4w==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/hoist-non-react-statics": {
|
"node_modules/hoist-non-react-statics": {
|
||||||
"version": "3.3.2",
|
"version": "3.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
||||||
@ -2879,6 +3079,14 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/internmap": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/is-array-buffer": {
|
"node_modules/is-array-buffer": {
|
||||||
"version": "3.0.2",
|
"version": "3.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz",
|
||||||
@ -3302,6 +3510,11 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash": {
|
||||||
|
"version": "4.17.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||||
|
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||||
|
},
|
||||||
"node_modules/lodash.merge": {
|
"node_modules/lodash.merge": {
|
||||||
"version": "4.6.2",
|
"version": "4.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||||
@ -4123,6 +4336,60 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||||
},
|
},
|
||||||
|
"node_modules/react-lifecycles-compat": {
|
||||||
|
"version": "3.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
|
||||||
|
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
|
||||||
|
},
|
||||||
|
"node_modules/react-resize-detector": {
|
||||||
|
"version": "8.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-8.1.0.tgz",
|
||||||
|
"integrity": "sha512-S7szxlaIuiy5UqLhLL1KY3aoyGHbZzsTpYal9eYMwCyKqoqoVLCmIgAgNyIM1FhnP2KyBygASJxdhejrzjMb+w==",
|
||||||
|
"dependencies": {
|
||||||
|
"lodash": "^4.17.21"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.0.0 || ^17.0.0 || ^18.0.0",
|
||||||
|
"react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-smooth": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-yl4y3XiMorss7ayF5QnBiSprig0+qFHui8uh7Hgg46QX5O+aRMRKlfGGNGLHno35JkQSvSYY8eCWkBfHfrSHfg==",
|
||||||
|
"dependencies": {
|
||||||
|
"fast-equals": "^5.0.0",
|
||||||
|
"react-transition-group": "2.9.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"prop-types": "^15.6.0",
|
||||||
|
"react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0",
|
||||||
|
"react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-smooth/node_modules/dom-helpers": {
|
||||||
|
"version": "3.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz",
|
||||||
|
"integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.1.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-smooth/node_modules/react-transition-group": {
|
||||||
|
"version": "2.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz",
|
||||||
|
"integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==",
|
||||||
|
"dependencies": {
|
||||||
|
"dom-helpers": "^3.4.0",
|
||||||
|
"loose-envify": "^1.4.0",
|
||||||
|
"prop-types": "^15.6.2",
|
||||||
|
"react-lifecycles-compat": "^3.0.4"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=15.0.0",
|
||||||
|
"react-dom": ">=15.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-transition-group": {
|
"node_modules/react-transition-group": {
|
||||||
"version": "4.4.5",
|
"version": "4.4.5",
|
||||||
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
|
||||||
@ -4157,6 +4424,52 @@
|
|||||||
"node": ">=8.10.0"
|
"node": ">=8.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/recharts": {
|
||||||
|
"version": "2.7.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/recharts/-/recharts-2.7.3.tgz",
|
||||||
|
"integrity": "sha512-cKoO9jUZRQavn06H6Ih2EcG82zUNdQH3OEGWVCmluSDyp3d7fIpDAsbMTd8hE8+T+MD8P76iicv/J4pJspDP7A==",
|
||||||
|
"dependencies": {
|
||||||
|
"classnames": "^2.2.5",
|
||||||
|
"eventemitter3": "^4.0.1",
|
||||||
|
"lodash": "^4.17.19",
|
||||||
|
"react-is": "^16.10.2",
|
||||||
|
"react-resize-detector": "^8.0.4",
|
||||||
|
"react-smooth": "^2.0.2",
|
||||||
|
"recharts-scale": "^0.4.4",
|
||||||
|
"reduce-css-calc": "^2.1.8",
|
||||||
|
"victory-vendor": "^36.6.8"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"prop-types": "^15.6.0",
|
||||||
|
"react": "^16.0.0 || ^17.0.0 || ^18.0.0",
|
||||||
|
"react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/recharts-scale": {
|
||||||
|
"version": "0.4.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz",
|
||||||
|
"integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==",
|
||||||
|
"dependencies": {
|
||||||
|
"decimal.js-light": "^2.4.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/reduce-css-calc": {
|
||||||
|
"version": "2.1.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-2.1.8.tgz",
|
||||||
|
"integrity": "sha512-8liAVezDmUcH+tdzoEGrhfbGcP7nOV4NkGE3a74+qqvE7nt9i4sKLGBuZNOnpI4WiGksiNPklZxva80061QiPg==",
|
||||||
|
"dependencies": {
|
||||||
|
"css-unit-converter": "^1.1.1",
|
||||||
|
"postcss-value-parser": "^3.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/reduce-css-calc/node_modules/postcss-value-parser": {
|
||||||
|
"version": "3.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz",
|
||||||
|
"integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ=="
|
||||||
|
},
|
||||||
"node_modules/regenerator-runtime": {
|
"node_modules/regenerator-runtime": {
|
||||||
"version": "0.13.11",
|
"version": "0.13.11",
|
||||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
|
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
|
||||||
@ -4974,6 +5287,27 @@
|
|||||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
|
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
|
||||||
},
|
},
|
||||||
|
"node_modules/victory-vendor": {
|
||||||
|
"version": "36.6.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.6.11.tgz",
|
||||||
|
"integrity": "sha512-nT8kCiJp8dQh8g991J/R5w5eE2KnO8EAIP0xocWlh9l2okngMWglOPoMZzJvek8Q1KUc4XE/mJxTZnvOB1sTYg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/d3-array": "^3.0.3",
|
||||||
|
"@types/d3-ease": "^3.0.0",
|
||||||
|
"@types/d3-interpolate": "^3.0.1",
|
||||||
|
"@types/d3-scale": "^4.0.2",
|
||||||
|
"@types/d3-shape": "^3.1.0",
|
||||||
|
"@types/d3-time": "^3.0.0",
|
||||||
|
"@types/d3-timer": "^3.0.0",
|
||||||
|
"d3-array": "^3.1.6",
|
||||||
|
"d3-ease": "^3.0.1",
|
||||||
|
"d3-interpolate": "^3.0.1",
|
||||||
|
"d3-scale": "^4.0.2",
|
||||||
|
"d3-shape": "^3.1.0",
|
||||||
|
"d3-time": "^3.0.0",
|
||||||
|
"d3-timer": "^3.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/watchpack": {
|
"node_modules/watchpack": {
|
||||||
"version": "2.4.0",
|
"version": "2.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz",
|
||||||
|
@ -23,10 +23,12 @@
|
|||||||
"classnames": "^2.3.2",
|
"classnames": "^2.3.2",
|
||||||
"eslint": "8.46.0",
|
"eslint": "8.46.0",
|
||||||
"eslint-config-next": "13.4.12",
|
"eslint-config-next": "13.4.12",
|
||||||
|
"hex-rgb": "^5.0.0",
|
||||||
"next": "13.4.12",
|
"next": "13.4.12",
|
||||||
"postcss": "8.4.27",
|
"postcss": "8.4.27",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
|
"recharts": "^2.7.3",
|
||||||
"tailwindcss": "3.3.3",
|
"tailwindcss": "3.3.3",
|
||||||
"typescript": "5.1.6"
|
"typescript": "5.1.6"
|
||||||
},
|
},
|
||||||
|
@ -27,47 +27,53 @@ import Stack from '@mui/material/Stack/Stack';
|
|||||||
import ModeIcon from '@mui/icons-material/Mode';
|
import ModeIcon from '@mui/icons-material/Mode';
|
||||||
import ClearIcon from '@mui/icons-material/Clear';
|
import ClearIcon from '@mui/icons-material/Clear';
|
||||||
import Fade from '@mui/material/Fade/Fade';
|
import Fade from '@mui/material/Fade/Fade';
|
||||||
|
import NodePieChart, { PieData } from './NodePieChart';
|
||||||
|
import Grid2 from '@mui/material/Unstable_Grid2'; // Grid version 2
|
||||||
|
import { Card, CardContent, Container, FormGroup, useTheme } from '@mui/material';
|
||||||
|
import hexRgb from 'hex-rgb';
|
||||||
|
import useMediaQuery from '@mui/material/useMediaQuery';
|
||||||
|
|
||||||
interface Data {
|
|
||||||
|
export interface TableData {
|
||||||
name: string;
|
name: string;
|
||||||
id: string;
|
id: string;
|
||||||
status: boolean;
|
status: NodeStatus;
|
||||||
last_seen: number;
|
last_seen: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createData(
|
export enum NodeStatus {
|
||||||
name: string,
|
Online,
|
||||||
id: string,
|
Offline,
|
||||||
status: boolean,
|
Pending,
|
||||||
last_seen: number,
|
|
||||||
|
|
||||||
): Data {
|
|
||||||
if (status && last_seen > 0) {
|
|
||||||
console.error("Last seen should be 0 if status is true");
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
name,
|
|
||||||
id,
|
|
||||||
status,
|
|
||||||
last_seen: last_seen,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const rows = [
|
|
||||||
createData('Matchbox', "42:0:f21:6916:e333:c47e:4b5c:e74c", true, 0),
|
interface HeadCell {
|
||||||
createData('Ahorn', "42:0:3c46:b51c:b34d:b7e1:3b02:8d24", true, 0),
|
disablePadding: boolean;
|
||||||
createData('Yellow', "42:0:3c46:98ac:9c80:4f25:50e3:1d8f", false, 16.0),
|
id: keyof TableData;
|
||||||
createData('Rauter', "42:0:61ea:b777:61ea:803:f885:3523", false, 6.0),
|
label: string;
|
||||||
createData('Porree', "42:0:e644:4499:d034:895e:34c8:6f9a", false, 13),
|
alignRight: boolean;
|
||||||
createData('Helsinki', "42:0:3c46:fd4a:acf9:e971:6036:8047", true, 0),
|
}
|
||||||
createData('Kelle', "42:0:3c46:362d:a9aa:4996:c78e:839a", true, 0),
|
|
||||||
createData('Shodan', "42:0:3c46:6745:adf4:a844:26c4:bf91", true, 0.0),
|
const headCells: readonly HeadCell[] = [
|
||||||
createData('Qubasa', "42:0:3c46:123e:bbea:3529:db39:6764", false, 7.0),
|
{
|
||||||
createData('Green', "42:0:a46e:5af:632c:d2fe:a71d:cde0", false, 2),
|
id: 'name',
|
||||||
createData('Gum', "42:0:e644:238d:3e46:c884:6ec5:16c", false, 0),
|
alignRight: false,
|
||||||
createData('Xu', "42:0:ca48:c2c2:19fb:a0e9:95b9:794f", true, 0),
|
disablePadding: false,
|
||||||
createData('Zaatar', "42:0:3c46:156e:10b6:3bd6:6e82:b2cd", true, 0),
|
label: 'DISPLAY NAME & ID',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'status',
|
||||||
|
alignRight: false,
|
||||||
|
disablePadding: false,
|
||||||
|
label: 'STATUS',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'last_seen',
|
||||||
|
alignRight: false,
|
||||||
|
disablePadding: false,
|
||||||
|
label: 'LAST SEEN',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
function descendingComparator<T>(a: T, b: T, orderBy: keyof T) {
|
function descendingComparator<T>(a: T, b: T, orderBy: keyof T) {
|
||||||
@ -110,36 +116,10 @@ function stableSort<T>(array: readonly T[], comparator: (a: T, b: T) => number)
|
|||||||
return stabilizedThis.map((el) => el[0]);
|
return stabilizedThis.map((el) => el[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface HeadCell {
|
|
||||||
disablePadding: boolean;
|
|
||||||
id: keyof Data;
|
|
||||||
label: string;
|
|
||||||
alignRight: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const headCells: readonly HeadCell[] = [
|
|
||||||
{
|
|
||||||
id: 'name',
|
|
||||||
alignRight: false,
|
|
||||||
disablePadding: false,
|
|
||||||
label: 'Display Name & ID',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'status',
|
|
||||||
alignRight: false,
|
|
||||||
disablePadding: false,
|
|
||||||
label: 'Status',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'last_seen',
|
|
||||||
alignRight: false,
|
|
||||||
disablePadding: false,
|
|
||||||
label: 'Last Seen',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
interface EnhancedTableProps {
|
interface EnhancedTableProps {
|
||||||
onRequestSort: (event: React.MouseEvent<unknown>, property: keyof Data) => void;
|
onRequestSort: (event: React.MouseEvent<unknown>, property: keyof TableData) => void;
|
||||||
order: Order;
|
order: Order;
|
||||||
orderBy: string;
|
orderBy: string;
|
||||||
rowCount: number;
|
rowCount: number;
|
||||||
@ -149,7 +129,7 @@ function EnhancedTableHead(props: EnhancedTableProps) {
|
|||||||
const { order, orderBy, onRequestSort } =
|
const { order, orderBy, onRequestSort } =
|
||||||
props;
|
props;
|
||||||
const createSortHandler =
|
const createSortHandler =
|
||||||
(property: keyof Data) => (event: React.MouseEvent<unknown>) => {
|
(property: keyof TableData) => (event: React.MouseEvent<unknown>) => {
|
||||||
onRequestSort(event, property);
|
onRequestSort(event, property);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -182,10 +162,161 @@ function EnhancedTableHead(props: EnhancedTableProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
interface EnhancedTableToolbarProps {
|
interface EnhancedTableToolbarProps {
|
||||||
selected: string | undefined;
|
selected: string | undefined;
|
||||||
|
tableData: TableData[];
|
||||||
onClear: () => void;
|
onClear: () => void;
|
||||||
}
|
}
|
||||||
|
function EnhancedTableToolbar(props: EnhancedTableToolbarProps) {
|
||||||
|
const { selected, onClear, tableData } = props;
|
||||||
|
const theme = useTheme();
|
||||||
|
const matches = useMediaQuery(theme.breakpoints.down('lg'));
|
||||||
|
const isSelected = selected != undefined;
|
||||||
|
const [debug, setDebug] = React.useState<boolean>(false);
|
||||||
|
const debugSx = debug ? {
|
||||||
|
'--Grid-borderWidth': '1px',
|
||||||
|
borderTop: 'var(--Grid-borderWidth) solid',
|
||||||
|
borderLeft: 'var(--Grid-borderWidth) solid',
|
||||||
|
borderColor: 'divider',
|
||||||
|
'& > div': {
|
||||||
|
borderRight: 'var(--Grid-borderWidth) solid',
|
||||||
|
borderBottom: 'var(--Grid-borderWidth) solid',
|
||||||
|
borderColor: 'divider',
|
||||||
|
}
|
||||||
|
} : {};
|
||||||
|
|
||||||
|
const pieData = React.useMemo(() => {
|
||||||
|
const online = tableData.filter((row) => row.status === NodeStatus.Online).length;
|
||||||
|
const offline = tableData.filter((row) => row.status === NodeStatus.Offline).length;
|
||||||
|
const pending = tableData.filter((row) => row.status === NodeStatus.Pending).length;
|
||||||
|
|
||||||
|
return [
|
||||||
|
{ name: 'Online', value: online, color: '#2E7D32' },
|
||||||
|
{ name: 'Offline', value: offline, color: '#db3927' },
|
||||||
|
{ name: 'Pending', value: pending, color: '#FFBB28' },
|
||||||
|
];
|
||||||
|
}, [tableData]);
|
||||||
|
|
||||||
|
const cardData = React.useMemo(() => {
|
||||||
|
let copy = pieData.filter((pieItem) => pieItem.value > 0);
|
||||||
|
const elem = { name: 'Total', value: copy.reduce((a, b) => a + b.value, 0), color: '#000000' };
|
||||||
|
copy.push(elem);
|
||||||
|
return copy;
|
||||||
|
}, [pieData]);
|
||||||
|
|
||||||
|
const cardStack = (
|
||||||
|
<Stack
|
||||||
|
sx={{ ...debugSx, paddingTop: 6 }}
|
||||||
|
height={350}
|
||||||
|
id="cardBox"
|
||||||
|
display="flex"
|
||||||
|
flexDirection="column"
|
||||||
|
justifyContent="flex-start"
|
||||||
|
flexWrap="wrap">
|
||||||
|
{cardData.map((pieItem) => (
|
||||||
|
<Card key={pieItem.name} sx={{ marginBottom: 2, marginRight: 2, width: 110, height: 110, backgroundColor: hexRgb(pieItem.color, { format: 'css', alpha: 0.18 }) }}>
|
||||||
|
<CardContent >
|
||||||
|
<Typography variant="h4" component="div" gutterBottom={true} textAlign="center">
|
||||||
|
{pieItem.value}
|
||||||
|
</Typography>
|
||||||
|
<Typography sx={{ mb: 1.5 }} color="text.secondary" textAlign="center">
|
||||||
|
{pieItem.name}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
|
||||||
|
const selectedToolbar = (
|
||||||
|
<Toolbar
|
||||||
|
sx={{
|
||||||
|
pl: { sm: 2 },
|
||||||
|
pr: { xs: 1, sm: 1 },
|
||||||
|
bgcolor: (theme) =>
|
||||||
|
alpha(theme.palette.primary.main, theme.palette.action.activatedOpacity),
|
||||||
|
}}>
|
||||||
|
<Tooltip title="Clear">
|
||||||
|
<IconButton onClick={onClear}>
|
||||||
|
<ClearIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
<Typography
|
||||||
|
sx={{ flex: '1 1 100%' }}
|
||||||
|
color="inherit"
|
||||||
|
style={{ fontSize: 18, marginBottom: 3, marginLeft: 3 }}
|
||||||
|
component="div"
|
||||||
|
>
|
||||||
|
{selected} selected
|
||||||
|
</Typography>
|
||||||
|
<Tooltip title="Edit">
|
||||||
|
<IconButton>
|
||||||
|
<ModeIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</Toolbar >
|
||||||
|
);
|
||||||
|
|
||||||
|
const unselectedToolbar = (
|
||||||
|
<Toolbar
|
||||||
|
sx={{
|
||||||
|
pl: { sm: 2 },
|
||||||
|
pr: { xs: 1, sm: 1 },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box sx={{ flex: '1 1 100%' }} ></Box>
|
||||||
|
<Tooltip title="Filter list">
|
||||||
|
<IconButton>
|
||||||
|
<FilterListIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</Toolbar >
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid2 container spacing={1} sx={debugSx}>
|
||||||
|
<Grid2 xs={6}>
|
||||||
|
<Typography
|
||||||
|
sx={{ marginLeft: 3, marginTop: 1 }}
|
||||||
|
variant="h6"
|
||||||
|
id="tableTitle"
|
||||||
|
component="div"
|
||||||
|
>
|
||||||
|
NODES
|
||||||
|
</Typography>
|
||||||
|
</Grid2>
|
||||||
|
{/* Debug Controls */}
|
||||||
|
<Grid2 xs={6} justifyContent="right" display="flex">
|
||||||
|
<FormGroup>
|
||||||
|
<FormControlLabel control={<Switch onChange={() => { setDebug(!debug) }} checked={debug} />} label="Debug" />
|
||||||
|
</FormGroup>
|
||||||
|
</Grid2>
|
||||||
|
|
||||||
|
{/* Pie Chart Grid */}
|
||||||
|
<Grid2 lg={6} sm={12} display="flex" justifyContent="center" alignItems="center">
|
||||||
|
<Box height={350} width={400}>
|
||||||
|
<NodePieChart data={pieData} showLabels={matches} />
|
||||||
|
</Box>
|
||||||
|
</Grid2>
|
||||||
|
|
||||||
|
{/* Card Stack Grid */}
|
||||||
|
<Grid2 lg={6} display="flex" sx={{ display: { lg: 'flex', sm: 'none' } }} >
|
||||||
|
{cardStack}
|
||||||
|
</Grid2>
|
||||||
|
|
||||||
|
{/*Toolbar Grid */}
|
||||||
|
<Grid2 xs={12}>
|
||||||
|
{isSelected ? selectedToolbar : unselectedToolbar}
|
||||||
|
</Grid2>
|
||||||
|
|
||||||
|
</Grid2>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function renderLastSeen(last_seen: number) {
|
function renderLastSeen(last_seen: number) {
|
||||||
return (
|
return (
|
||||||
@ -208,98 +339,47 @@ function renderName(name: string, id: string) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderStatus(status: boolean) {
|
function renderStatus(status: NodeStatus) {
|
||||||
if (status) {
|
switch (status) {
|
||||||
return (
|
case NodeStatus.Online:
|
||||||
<Stack direction="row" alignItems="center" gap={1}>
|
return (
|
||||||
<CircleIcon color="success" style={{ fontSize: 15 }} />
|
<Stack direction="row" alignItems="center" gap={1}>
|
||||||
<Typography component="div" align="left" variant="body1">
|
<CircleIcon color="success" style={{ fontSize: 15 }} />
|
||||||
Online
|
<Typography component="div" align="left" variant="body1">
|
||||||
</Typography>
|
Online
|
||||||
</Stack>
|
</Typography>
|
||||||
);
|
</Stack>
|
||||||
|
);
|
||||||
|
|
||||||
|
case NodeStatus.Offline:
|
||||||
|
return (
|
||||||
|
<Stack direction="row" alignItems="center" gap={1}>
|
||||||
|
<CircleIcon color="error" style={{ fontSize: 15 }} />
|
||||||
|
<Typography component="div" align="left" variant="body1">
|
||||||
|
Offline
|
||||||
|
</Typography>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
case NodeStatus.Pending:
|
||||||
|
return (
|
||||||
|
<Stack direction="row" alignItems="center" gap={1}>
|
||||||
|
<CircleIcon color="warning" style={{ fontSize: 15 }} />
|
||||||
|
<Typography component="div" align="left" variant="body1">
|
||||||
|
Pending
|
||||||
|
</Typography>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return (
|
|
||||||
<Stack direction="row" alignItems="center" gap={1}>
|
|
||||||
<CircleIcon color="error" style={{ fontSize: 15 }} />
|
|
||||||
<Typography component="div" align="left" variant="body1">
|
|
||||||
Offline
|
|
||||||
</Typography>
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function EnhancedTableToolbar(props: EnhancedTableToolbarProps) {
|
export interface NodeTableProps {
|
||||||
const { selected, onClear } = props;
|
tableData: TableData[]
|
||||||
const somethingSelected = selected !== undefined;
|
|
||||||
|
|
||||||
const handleSomethingSelected = () => {
|
|
||||||
|
|
||||||
if (somethingSelected) {
|
|
||||||
return (
|
|
||||||
|
|
||||||
<Toolbar
|
|
||||||
sx={{
|
|
||||||
pl: { sm: 2 },
|
|
||||||
pr: { xs: 1, sm: 1 },
|
|
||||||
bgcolor: (theme) =>
|
|
||||||
alpha(theme.palette.primary.main, theme.palette.action.activatedOpacity),
|
|
||||||
}}>
|
|
||||||
<Tooltip title="Clear">
|
|
||||||
<IconButton onClick={onClear}>
|
|
||||||
<ClearIcon />
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
<Typography
|
|
||||||
sx={{ flex: '1 1 100%' }}
|
|
||||||
color="inherit"
|
|
||||||
style={{ fontSize: 18, marginBottom: 2.5, marginLeft: 3 }}
|
|
||||||
component="div"
|
|
||||||
>
|
|
||||||
{selected} selected
|
|
||||||
</Typography>
|
|
||||||
<Tooltip title="Edit">
|
|
||||||
<IconButton>
|
|
||||||
<ModeIcon />
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
</Toolbar >
|
|
||||||
|
|
||||||
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
|
|
||||||
<Toolbar
|
|
||||||
sx={{
|
|
||||||
pl: { sm: 2 },
|
|
||||||
pr: { xs: 1, sm: 1 }
|
|
||||||
}}>
|
|
||||||
<Typography
|
|
||||||
sx={{ flex: '1 1 100%' }}
|
|
||||||
variant="h6"
|
|
||||||
id="tableTitle"
|
|
||||||
component="div"
|
|
||||||
>
|
|
||||||
Nodes
|
|
||||||
</Typography>
|
|
||||||
<Tooltip title="Filter list">
|
|
||||||
<IconButton>
|
|
||||||
<FilterListIcon />
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
</Toolbar >
|
|
||||||
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return handleSomethingSelected();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function EnhancedTable() {
|
export default function NodeTable(props: NodeTableProps) {
|
||||||
|
let {tableData} = props;
|
||||||
const [order, setOrder] = React.useState<Order>('asc');
|
const [order, setOrder] = React.useState<Order>('asc');
|
||||||
const [orderBy, setOrderBy] = React.useState<keyof Data>('status');
|
const [orderBy, setOrderBy] = React.useState<keyof TableData>('status');
|
||||||
const [selected, setSelected] = React.useState<string | undefined>(undefined);
|
const [selected, setSelected] = React.useState<string | undefined>(undefined);
|
||||||
const [page, setPage] = React.useState(0);
|
const [page, setPage] = React.useState(0);
|
||||||
const [dense, setDense] = React.useState(false);
|
const [dense, setDense] = React.useState(false);
|
||||||
@ -307,7 +387,7 @@ export default function EnhancedTable() {
|
|||||||
|
|
||||||
const handleRequestSort = (
|
const handleRequestSort = (
|
||||||
event: React.MouseEvent<unknown>,
|
event: React.MouseEvent<unknown>,
|
||||||
property: keyof Data,
|
property: keyof TableData,
|
||||||
) => {
|
) => {
|
||||||
const isAsc = orderBy === property && order === 'asc';
|
const isAsc = orderBy === property && order === 'asc';
|
||||||
setOrder(isAsc ? 'desc' : 'asc');
|
setOrder(isAsc ? 'desc' : 'asc');
|
||||||
@ -315,7 +395,8 @@ export default function EnhancedTable() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleClick = (event: React.MouseEvent<unknown>, name: string) => {
|
const handleClick = (event: React.MouseEvent<unknown>, name: string) => {
|
||||||
if (selected === name) {
|
// Speed optimization. We compare string pointers here instead of the string content.
|
||||||
|
if (selected == name) {
|
||||||
setSelected(undefined);
|
setSelected(undefined);
|
||||||
} else {
|
} else {
|
||||||
setSelected(name);
|
setSelected(name);
|
||||||
@ -331,106 +412,93 @@ export default function EnhancedTable() {
|
|||||||
setPage(0);
|
setPage(0);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleChangeDense = (event: React.ChangeEvent<HTMLInputElement>) => {
|
// Speed optimization. We compare string pointers here instead of the string content.
|
||||||
setDense(event.target.checked);
|
const isSelected = (name: string) => name == selected;
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: Make a number to increase comparison speed and ui performance
|
|
||||||
const isSelected = (name: string) => name === selected;
|
|
||||||
|
|
||||||
// Avoid a layout jump when reaching the last page with empty rows.
|
// Avoid a layout jump when reaching the last page with empty rows.
|
||||||
const emptyRows =
|
const emptyRows =
|
||||||
page > 0 ? Math.max(0, (1 + page) * rowsPerPage - rows.length) : 0;
|
page > 0 ? Math.max(0, (1 + page) * rowsPerPage - tableData.length) : 0;
|
||||||
|
|
||||||
const visibleRows = React.useMemo(
|
const visibleRows = React.useMemo(
|
||||||
() =>
|
() =>
|
||||||
stableSort(rows, getComparator(order, orderBy)).slice(
|
stableSort(tableData, getComparator(order, orderBy)).slice(
|
||||||
page * rowsPerPage,
|
page * rowsPerPage,
|
||||||
page * rowsPerPage + rowsPerPage,
|
page * rowsPerPage + rowsPerPage,
|
||||||
),
|
),
|
||||||
[order, orderBy, page, rowsPerPage],
|
[order, orderBy, page, rowsPerPage, tableData],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ width: '100%' }}>
|
<Paper elevation={1} sx={{ margin: 5 }}>
|
||||||
<Paper sx={{ width: '100%', mb: 2 }} id="test">
|
<Box sx={{ width: '100%' }}>
|
||||||
<EnhancedTableToolbar selected={selected} onClear={() => setSelected(undefined)} />
|
<Paper sx={{ width: '100%', mb: 2 }}>
|
||||||
<TableContainer>
|
<EnhancedTableToolbar tableData={tableData} selected={selected} onClear={() => setSelected(undefined)} />
|
||||||
<Table
|
<TableContainer>
|
||||||
sx={{ minWidth: 750 }}
|
<Table
|
||||||
aria-labelledby="tableTitle"
|
sx={{ minWidth: 750 }}
|
||||||
size={dense ? 'small' : 'medium'}
|
aria-labelledby="tableTitle"
|
||||||
>
|
size={dense ? 'small' : 'medium'}
|
||||||
<EnhancedTableHead
|
>
|
||||||
order={order}
|
<EnhancedTableHead
|
||||||
orderBy={orderBy}
|
order={order}
|
||||||
onRequestSort={handleRequestSort}
|
orderBy={orderBy}
|
||||||
rowCount={rows.length}
|
onRequestSort={handleRequestSort}
|
||||||
/>
|
rowCount={tableData.length}
|
||||||
<TableBody>
|
/>
|
||||||
{visibleRows.map((row, index) => {
|
<TableBody>
|
||||||
const isItemSelected = isSelected(row.name);
|
{visibleRows.map((row, index) => {
|
||||||
const labelId = `enhanced-table-checkbox-${index}`;
|
const isItemSelected = isSelected(row.name);
|
||||||
|
const labelId = `enhanced-table-checkbox-${index}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableRow
|
<TableRow
|
||||||
hover
|
hover
|
||||||
onClick={(event) => handleClick(event, row.name)}
|
onClick={(event) => handleClick(event, row.name)}
|
||||||
role="checkbox"
|
role="checkbox"
|
||||||
aria-checked={isItemSelected}
|
aria-checked={isItemSelected}
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
key={row.name}
|
key={row.name}
|
||||||
selected={isItemSelected}
|
selected={isItemSelected}
|
||||||
sx={{ cursor: 'pointer' }}
|
sx={{ cursor: 'pointer' }}
|
||||||
>
|
|
||||||
{/* <TableCell padding="checkbox">
|
|
||||||
<Checkbox
|
|
||||||
color="primary"
|
|
||||||
checked={isItemSelected}
|
|
||||||
inputProps={{
|
|
||||||
'aria-labelledby': labelId,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</TableCell> */}
|
|
||||||
<TableCell
|
|
||||||
component="th"
|
|
||||||
id={labelId}
|
|
||||||
scope="row"
|
|
||||||
>
|
>
|
||||||
{renderName(row.name, row.id)}
|
<TableCell
|
||||||
</TableCell>
|
component="th"
|
||||||
<TableCell align="right">{renderStatus(row.status)}</TableCell>
|
id={labelId}
|
||||||
<TableCell align="right">{renderLastSeen(row.last_seen)}</TableCell>
|
scope="row"
|
||||||
|
>
|
||||||
|
{renderName(row.name, row.id)}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="right">{renderStatus(row.status)}</TableCell>
|
||||||
|
<TableCell align="right">{renderLastSeen(row.last_seen)}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{emptyRows > 0 && (
|
||||||
|
<TableRow
|
||||||
|
style={{
|
||||||
|
height: (dense ? 33 : 53) * emptyRows,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TableCell colSpan={6} />
|
||||||
</TableRow>
|
</TableRow>
|
||||||
);
|
)}
|
||||||
})}
|
</TableBody>
|
||||||
{emptyRows > 0 && (
|
</Table>
|
||||||
<TableRow
|
</TableContainer>
|
||||||
style={{
|
{/* TODO: This creates the error Warning: Prop `id` did not match. Server: ":RspmmcqH1:" Client: ":R3j6qpj9H1:" */}
|
||||||
height: (dense ? 33 : 53) * emptyRows,
|
<TablePagination
|
||||||
}}
|
rowsPerPageOptions={[5, 10, 25]}
|
||||||
>
|
component="div"
|
||||||
<TableCell colSpan={6} />
|
count={tableData.length}
|
||||||
</TableRow>
|
rowsPerPage={rowsPerPage}
|
||||||
)}
|
page={page}
|
||||||
</TableBody>
|
onPageChange={handleChangePage}
|
||||||
</Table>
|
onRowsPerPageChange={handleChangeRowsPerPage}
|
||||||
</TableContainer>
|
/>
|
||||||
{/* TODO: This creates the error Warning: Prop `id` did not match. Server: ":RspmmcqH1:" Client: ":R3j6qpj9H1:" */}
|
</Paper>
|
||||||
<TablePagination
|
</Box>
|
||||||
rowsPerPageOptions={[5, 10, 25]}
|
</Paper>
|
||||||
component="div"
|
|
||||||
count={rows.length}
|
|
||||||
rowsPerPage={rowsPerPage}
|
|
||||||
page={page}
|
|
||||||
onPageChange={handleChangePage}
|
|
||||||
onRowsPerPageChange={handleChangeRowsPerPage}
|
|
||||||
/>
|
|
||||||
</Paper>
|
|
||||||
<FormControlLabel
|
|
||||||
control={<Switch checked={dense} onChange={handleChangeDense} />}
|
|
||||||
label="Dense padding"
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
45
pkgs/ui/src/app/nodes/NodePieChart.tsx
Normal file
45
pkgs/ui/src/app/nodes/NodePieChart.tsx
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import React, { PureComponent } from 'react';
|
||||||
|
import { PieChart, Pie, Sector, Cell, ResponsiveContainer, Legend } from 'recharts';
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
import { Box, Color } from '@mui/material';
|
||||||
|
|
||||||
|
|
||||||
|
export interface PieData {
|
||||||
|
name: string;
|
||||||
|
value: number;
|
||||||
|
color: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
data: PieData[];
|
||||||
|
showLabels?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function NodePieChart(props: Props ) {
|
||||||
|
const theme = useTheme();
|
||||||
|
const {data, showLabels} = props;
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box height={350}>
|
||||||
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
|
<PieChart>
|
||||||
|
<Pie
|
||||||
|
data={data}
|
||||||
|
innerRadius={85}
|
||||||
|
outerRadius={120}
|
||||||
|
fill={theme.palette.primary.main}
|
||||||
|
dataKey="value"
|
||||||
|
nameKey="name"
|
||||||
|
label={showLabels}
|
||||||
|
>
|
||||||
|
{data.map((entry, index) => (
|
||||||
|
<Cell key={`cell-${index}`} fill={entry.color} />
|
||||||
|
))}
|
||||||
|
</Pie>
|
||||||
|
<Legend verticalAlign="bottom" />
|
||||||
|
</PieChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
@ -1,36 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
export default function PieData() {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
"id": "scala",
|
|
||||||
"label": "scala",
|
|
||||||
"value": 317,
|
|
||||||
"color": "hsl(3, 70%, 50%)"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "rust",
|
|
||||||
"label": "rust",
|
|
||||||
"value": 489,
|
|
||||||
"color": "hsl(113, 70%, 50%)"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "css",
|
|
||||||
"label": "css",
|
|
||||||
"value": 456,
|
|
||||||
"color": "hsl(17, 70%, 50%)"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "elixir",
|
|
||||||
"label": "elixir",
|
|
||||||
"value": 343,
|
|
||||||
"color": "hsl(232, 70%, 50%)"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "haskell",
|
|
||||||
"label": "haskell",
|
|
||||||
"value": 167,
|
|
||||||
"color": "hsl(292, 70%, 50%)"
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
@ -1,17 +1,47 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { StrictMode } from "react";
|
import { StrictMode } from "react";
|
||||||
import NodeList from "./NodeList";
|
import NodeList, { NodeStatus, TableData } from "./NodeList";
|
||||||
|
|
||||||
import Box from "@mui/material/Box";
|
import Box from "@mui/material/Box";
|
||||||
|
|
||||||
|
function createData(
|
||||||
|
name: string,
|
||||||
|
id: string,
|
||||||
|
status: NodeStatus,
|
||||||
|
last_seen: number,
|
||||||
|
|
||||||
|
): TableData {
|
||||||
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
id,
|
||||||
|
status,
|
||||||
|
last_seen: last_seen,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const tableData = [
|
||||||
|
createData('Matchbox', "42:0:f21:6916:e333:c47e:4b5c:e74c", NodeStatus.Pending, 0),
|
||||||
|
createData('Ahorn', "42:0:3c46:b51c:b34d:b7e1:3b02:8d24", NodeStatus.Online, 0),
|
||||||
|
createData('Yellow', "42:0:3c46:98ac:9c80:4f25:50e3:1d8f", NodeStatus.Offline, 16.0),
|
||||||
|
createData('Rauter', "42:0:61ea:b777:61ea:803:f885:3523", NodeStatus.Offline, 6.0),
|
||||||
|
createData('Porree', "42:0:e644:4499:d034:895e:34c8:6f9a", NodeStatus.Offline, 13),
|
||||||
|
createData('Helsinki', "42:0:3c46:fd4a:acf9:e971:6036:8047", NodeStatus.Online, 0),
|
||||||
|
createData('Kelle', "42:0:3c46:362d:a9aa:4996:c78e:839a", NodeStatus.Online, 0),
|
||||||
|
createData('Shodan', "42:0:3c46:6745:adf4:a844:26c4:bf91", NodeStatus.Online, 0.0),
|
||||||
|
createData('Qubasa', "42:0:3c46:123e:bbea:3529:db39:6764", NodeStatus.Offline, 7.0),
|
||||||
|
createData('Green', "42:0:a46e:5af:632c:d2fe:a71d:cde0", NodeStatus.Offline, 2),
|
||||||
|
createData('Gum', "42:0:e644:238d:3e46:c884:6ec5:16c", NodeStatus.Offline, 0),
|
||||||
|
createData('Xu', "42:0:ca48:c2c2:19fb:a0e9:95b9:794f", NodeStatus.Online, 0),
|
||||||
|
createData('Zaatar', "42:0:3c46:156e:10b6:3bd6:6e82:b2cd", NodeStatus.Online, 0),
|
||||||
|
];
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box sx={{ backgroundColor: "#e9ecf5", height: "100%", width: "100%" }} display="inline-block" id="rootBox">
|
||||||
|
<NodeList tableData={tableData} />
|
||||||
|
|
||||||
<NodeList />
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
@ -1,13 +1,16 @@
|
|||||||
import { createTheme } from "@mui/material/styles";
|
import { createTheme } from "@mui/material/styles";
|
||||||
|
|
||||||
|
|
||||||
export const darkTheme = createTheme({
|
export const darkTheme = createTheme({
|
||||||
palette: {
|
palette: {
|
||||||
mode: "dark",
|
mode: "dark",
|
||||||
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const lightTheme = createTheme({
|
export const lightTheme = createTheme({
|
||||||
palette: {
|
palette: {
|
||||||
mode: "light",
|
mode: "light",
|
||||||
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user