From 9aba70d214d7f3a5b7c66b58fd1482ed19f9d17a Mon Sep 17 00:00:00 2001 From: Ben Phelps Date: Tue, 1 Aug 2023 03:54:19 +0300 Subject: [PATCH] glances widget test 1 --- package.json | 1 + pnpm-lock.yaml | 260 ++++++++++++++++++++++- public/locales/en/common.json | 5 +- src/components/services/item.jsx | 10 +- src/utils/config/service-helpers.js | 6 +- src/utils/proxy/handlers/credentialed.js | 2 + src/widgets/components.js | 1 + src/widgets/glances/chart.jsx | 46 ++++ src/widgets/glances/chart_dual.jsx | 61 ++++++ src/widgets/glances/component.jsx | 34 +++ src/widgets/glances/cpu.jsx | 100 +++++++++ src/widgets/glances/custom_tooltip.jsx | 15 ++ src/widgets/glances/disk.jsx | 113 ++++++++++ src/widgets/glances/memory.jsx | 96 +++++++++ src/widgets/glances/net.jsx | 96 +++++++++ src/widgets/glances/process.jsx | 72 +++++++ src/widgets/glances/sensor.jsx | 99 +++++++++ src/widgets/glances/widget.js | 8 + src/widgets/widgets.js | 2 + 19 files changed, 1018 insertions(+), 9 deletions(-) create mode 100644 src/widgets/glances/chart.jsx create mode 100644 src/widgets/glances/chart_dual.jsx create mode 100644 src/widgets/glances/component.jsx create mode 100644 src/widgets/glances/cpu.jsx create mode 100644 src/widgets/glances/custom_tooltip.jsx create mode 100644 src/widgets/glances/disk.jsx create mode 100644 src/widgets/glances/memory.jsx create mode 100644 src/widgets/glances/net.jsx create mode 100644 src/widgets/glances/process.jsx create mode 100644 src/widgets/glances/sensor.jsx create mode 100644 src/widgets/glances/widget.js diff --git a/package.json b/package.json index 14fedf036..fd9965fd6 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "react-dom": "^18.2.0", "react-i18next": "^11.18.6", "react-icons": "^4.4.0", + "recharts": "^2.7.2", "shvl": "^3.0.0", "swr": "^1.3.0", "systeminformation": "^5.17.12", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a1231d8ca..d5f412949 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,5 +1,9 @@ lockfileVersion: '6.0' +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + dependencies: '@headlessui/react': specifier: ^1.7.2 @@ -61,6 +65,9 @@ dependencies: react-icons: specifier: ^4.4.0 version: 4.8.0(react@18.2.0) + recharts: + specifier: ^2.7.2 + version: 2.7.2(prop-types@15.8.1)(react-dom@18.2.0)(react@18.2.0) shvl: specifier: ^3.0.0 version: 3.0.0 @@ -435,6 +442,48 @@ packages: tailwindcss: 3.3.0(postcss@8.4.21) dev: true + /@types/d3-array@3.0.5: + resolution: {integrity: sha512-Qk7fpJ6qFp+26VeQ47WY0mkwXaiq8+76RJcncDEfMc2ocRzXLO67bLFRNI4OX1aGBoPzsM5Y2T+/m1pldOgD+A==} + dev: false + + /@types/d3-color@3.1.0: + resolution: {integrity: sha512-HKuicPHJuvPgCD+np6Se9MQvS6OCbJmOjGvylzMJRlDwUXjKTTXs6Pwgk79O09Vj/ho3u1ofXnhFOaEWWPrlwA==} + dev: false + + /@types/d3-ease@3.0.0: + resolution: {integrity: sha512-aMo4eaAOijJjA6uU+GIeW018dvy9+oH5Y2VPPzjjfxevvGQ/oRDs+tfYC9b50Q4BygRR8yE2QCLsrT0WtAVseA==} + dev: false + + /@types/d3-interpolate@3.0.1: + resolution: {integrity: sha512-jx5leotSeac3jr0RePOH1KdR9rISG91QIE4Q2PYTu4OymLTZfA3SrnURSLzKH48HmXVUru50b8nje4E79oQSQw==} + dependencies: + '@types/d3-color': 3.1.0 + dev: false + + /@types/d3-path@3.0.0: + resolution: {integrity: sha512-0g/A+mZXgFkQxN3HniRDbXMN79K3CdTpLsevj+PXiTcb2hVyvkZUBg37StmgCQkaD84cUJ4uaDAWq7UJOQy2Tg==} + dev: false + + /@types/d3-scale@4.0.3: + resolution: {integrity: sha512-PATBiMCpvHJSMtZAMEhc2WyL+hnzarKzI6wAHYjhsonjWJYGq5BXTzQjv4l8m2jO183/4wZ90rKvSeT7o72xNQ==} + dependencies: + '@types/d3-time': 3.0.0 + dev: false + + /@types/d3-shape@3.1.1: + resolution: {integrity: sha512-6Uh86YFF7LGg4PQkuO2oG6EMBRLuW9cbavUW46zkIO5kuS2PfTqo2o9SkgtQzguBHbLgNnU90UNsITpsX1My+A==} + dependencies: + '@types/d3-path': 3.0.0 + dev: false + + /@types/d3-time@3.0.0: + resolution: {integrity: sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg==} + dev: false + + /@types/d3-timer@3.0.0: + resolution: {integrity: sha512-HNB/9GHqu7Fo8AQiugyJbv6ZxYz58wef0esl4Mv828w1ZKpAshw/uFWVDUcIB9KKFeFKoxS3cHY07FFgtTRZ1g==} + dev: false + /@types/hoist-non-react-statics@3.3.1: resolution: {integrity: sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==} dependencies: @@ -785,6 +834,7 @@ packages: /buildcheck@0.0.3: resolution: {integrity: sha512-pziaA+p/wdVImfcbsZLNF32EiWyujlQLwolMqUQE8xpKNOH7KmZQaY8sXN7DGOEzPAElo9QTaeNRfGnf3iOJbA==} engines: {node: '>=10.0.0'} + requiresBuild: true dev: false optional: true @@ -1020,6 +1070,10 @@ packages: nth-check: 2.1.1 dev: false + /css-unit-converter@1.1.2: + resolution: {integrity: sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA==} + dev: false + /css-what@6.1.0: resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} engines: {node: '>= 6'} @@ -1034,6 +1088,77 @@ packages: resolution: {integrity: sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==} dev: false + /d3-array@3.2.4: + resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} + engines: {node: '>=12'} + dependencies: + internmap: 2.0.3 + dev: false + + /d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + dev: false + + /d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} + dev: false + + /d3-format@3.1.0: + resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==} + engines: {node: '>=12'} + dev: false + + /d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + dependencies: + d3-color: 3.1.0 + dev: false + + /d3-path@3.1.0: + resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} + engines: {node: '>=12'} + dev: false + + /d3-scale@4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} + engines: {node: '>=12'} + dependencies: + d3-array: 3.2.4 + d3-format: 3.1.0 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + dev: false + + /d3-shape@3.2.0: + resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} + engines: {node: '>=12'} + dependencies: + d3-path: 3.1.0 + dev: false + + /d3-time-format@4.1.0: + resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} + engines: {node: '>=12'} + dependencies: + d3-time: 3.1.0 + dev: false + + /d3-time@3.1.0: + resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} + engines: {node: '>=12'} + dependencies: + d3-array: 3.2.4 + dev: false + + /d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + dev: false + /damerau-levenshtein@1.0.8: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} dev: true @@ -1067,6 +1192,10 @@ packages: dependencies: ms: 2.1.2 + /decimal.js-light@2.5.1: + resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} + dev: false + /decompress-response@6.0.0: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} engines: {node: '>=10'} @@ -1175,6 +1304,12 @@ packages: esutils: 2.0.3 dev: true + /dom-helpers@3.4.0: + resolution: {integrity: sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==} + dependencies: + '@babel/runtime': 7.21.0 + dev: false + /dom-serializer@2.0.0: resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} dependencies: @@ -1654,6 +1789,10 @@ packages: deprecated: Use promise-toolbox/fromEvent instead dev: false + /eventemitter3@4.0.7: + resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + dev: false + /execa@5.0.0: resolution: {integrity: sha512-ov6w/2LCiuyO4RLYGdpFGjkcs0wMTgGE8PrkTHikeUy5iJekXyPIKUjifk5CsE0pt7sMCrMZ3YNqoCj6idQOnQ==} engines: {node: '>=10'} @@ -1685,6 +1824,11 @@ packages: resolution: {integrity: sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==} dev: true + /fast-equals@5.0.1: + resolution: {integrity: sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ==} + engines: {node: '>=6.0.0'} + dev: false + /fast-glob@3.2.12: resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==} engines: {node: '>=8.6.0'} @@ -2158,6 +2302,11 @@ packages: side-channel: 1.0.4 dev: true + /internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: '>=12'} + dev: false + /interpret@1.4.0: resolution: {integrity: sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==} engines: {node: '>= 0.10'} @@ -2361,6 +2510,7 @@ packages: /jose@4.13.1: resolution: {integrity: sha512-MSJQC5vXco5Br38mzaQKiq9mwt7lwj2eXpgpRyQYNHYt2lq1PjkWa7DLXX0WVcQLE9HhMh3jPiufS7fhJf+CLQ==} + requiresBuild: true dev: false optional: true @@ -2480,6 +2630,10 @@ packages: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} dev: true + /lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + dev: false + /logform@2.5.1: resolution: {integrity: sha512-9FyqAm9o9NKKfiAKfZoYo9bGXXuwMkxQiQttkT4YjjVtQVIQtK6LmVtlxmCaFswo6N4AfEkHqZTV0taDtPotNg==} dependencies: @@ -2623,6 +2777,7 @@ packages: /nan@2.17.0: resolution: {integrity: sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==} + requiresBuild: true dev: false optional: true @@ -2755,11 +2910,11 @@ packages: /object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} - dev: true /object-hash@2.2.0: resolution: {integrity: sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==} engines: {node: '>= 6'} + requiresBuild: true dev: false optional: true @@ -2832,6 +2987,7 @@ packages: /oidc-token-hash@5.0.1: resolution: {integrity: sha512-EvoOtz6FIEBzE+9q253HsLCVRiK/0doEJ2HCvvqMQb3dHZrP3WlJKYtJ55CRTw4jmYomzH4wkPuCj/I3ZvpKxQ==} engines: {node: ^10.13.0 || >=12.0.0} + requiresBuild: true dev: false optional: true @@ -3023,6 +3179,10 @@ packages: util-deprecate: 1.0.2 dev: true + /postcss-value-parser@3.3.1: + resolution: {integrity: sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==} + dev: false + /postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} dev: true @@ -3077,7 +3237,6 @@ packages: loose-envify: 1.4.0 object-assign: 4.1.1 react-is: 16.13.1 - dev: true /psl@1.9.0: resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} @@ -3162,6 +3321,49 @@ packages: /react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + /react-lifecycles-compat@3.0.4: + resolution: {integrity: sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==} + dev: false + + /react-resize-detector@8.1.0(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-S7szxlaIuiy5UqLhLL1KY3aoyGHbZzsTpYal9eYMwCyKqoqoVLCmIgAgNyIM1FhnP2KyBygASJxdhejrzjMb+w==} + peerDependencies: + react: ^16.0.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 + dependencies: + lodash: 4.17.21 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /react-smooth@2.0.3(prop-types@15.8.1)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-yl4y3XiMorss7ayF5QnBiSprig0+qFHui8uh7Hgg46QX5O+aRMRKlfGGNGLHno35JkQSvSYY8eCWkBfHfrSHfg==} + 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 + dependencies: + fast-equals: 5.0.1 + prop-types: 15.8.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-transition-group: 2.9.0(react-dom@18.2.0)(react@18.2.0) + dev: false + + /react-transition-group@2.9.0(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==} + peerDependencies: + react: '>=15.0.0' + react-dom: '>=15.0.0' + dependencies: + dom-helpers: 3.4.0 + loose-envify: 1.4.0 + prop-types: 15.8.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-lifecycles-compat: 3.0.4 + dev: false + /react@18.2.0: resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} engines: {node: '>=0.10.0'} @@ -3212,6 +3414,34 @@ packages: picomatch: 2.3.1 dev: true + /recharts-scale@0.4.5: + resolution: {integrity: sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==} + dependencies: + decimal.js-light: 2.5.1 + dev: false + + /recharts@2.7.2(prop-types@15.8.1)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-HMKRBkGoOXHW+7JcRa6+MukPSifNtJlqbc+JreGVNA407VLE/vOP+8n3YYjprDVVIF9E2ZgwWnL3D7K/LUFzBg==} + 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 + dependencies: + classnames: 2.3.2 + eventemitter3: 4.0.7 + lodash: 4.17.21 + prop-types: 15.8.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-is: 16.13.1 + react-resize-detector: 8.1.0(react-dom@18.2.0)(react@18.2.0) + react-smooth: 2.0.3(prop-types@15.8.1)(react-dom@18.2.0)(react@18.2.0) + recharts-scale: 0.4.5 + reduce-css-calc: 2.1.8 + victory-vendor: 36.6.11 + dev: false + /rechoir@0.6.2: resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==} engines: {node: '>= 0.10'} @@ -3219,6 +3449,13 @@ packages: resolve: 1.22.1 dev: false + /reduce-css-calc@2.1.8: + resolution: {integrity: sha512-8liAVezDmUcH+tdzoEGrhfbGcP7nOV4NkGE3a74+qqvE7nt9i4sKLGBuZNOnpI4WiGksiNPklZxva80061QiPg==} + dependencies: + css-unit-converter: 1.1.2 + postcss-value-parser: 3.3.1 + dev: false + /regenerator-runtime@0.13.11: resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} @@ -3896,6 +4133,25 @@ packages: extsprintf: 1.3.0 dev: false + /victory-vendor@36.6.11: + resolution: {integrity: sha512-nT8kCiJp8dQh8g991J/R5w5eE2KnO8EAIP0xocWlh9l2okngMWglOPoMZzJvek8Q1KUc4XE/mJxTZnvOB1sTYg==} + dependencies: + '@types/d3-array': 3.0.5 + '@types/d3-ease': 3.0.0 + '@types/d3-interpolate': 3.0.1 + '@types/d3-scale': 4.0.3 + '@types/d3-shape': 3.1.1 + '@types/d3-time': 3.0.0 + '@types/d3-timer': 3.0.0 + d3-array: 3.2.4 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-scale: 4.0.2 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-timer: 3.0.1 + dev: false + /void-elements@3.1.0: resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} engines: {node: '>=0.10.0'} diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 46d8e991f..7a8fe0237 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -371,7 +371,10 @@ "free": "Free", "used": "Used", "days": "d", - "hours": "h" + "hours": "h", + "crit": "Crit", + "read": "Read", + "write": "Write" }, "quicklaunch": { "bookmark": "Bookmark", diff --git a/src/components/services/item.jsx b/src/components/services/item.jsx index 36e454cef..a5743fb1b 100644 --- a/src/components/services/item.jsx +++ b/src/components/services/item.jsx @@ -34,9 +34,9 @@ export default function Item({ service, group }) {
-
+
{service.icon && (hasLink ? ( -
+
{service.name}

{service.description}

) : (
-
+
{service.name}

{service.description}

)} -
+
{service.ping && (
diff --git a/src/utils/config/service-helpers.js b/src/utils/config/service-helpers.js index 92fa7b227..1e241e463 100644 --- a/src/utils/config/service-helpers.js +++ b/src/utils/config/service-helpers.js @@ -294,7 +294,8 @@ export function cleanServiceGroups(groups) { snapshotHost, // kopia snapshotPath, userEmail, // azuredevops - repositoryId + repositoryId, + metric, // glances } = cleanedService.widget; let fieldsList = fields; @@ -358,6 +359,9 @@ export function cleanServiceGroups(groups) { if (snapshotHost) cleanedService.widget.snapshotHost = snapshotHost; if (snapshotPath) cleanedService.widget.snapshotPath = snapshotPath; } + if (type === "glances") { + if (metric) cleanedService.widget.metric = metric; + } } return cleanedService; diff --git a/src/utils/proxy/handlers/credentialed.js b/src/utils/proxy/handlers/credentialed.js index 498c0d6df..d0321cb85 100644 --- a/src/utils/proxy/handlers/credentialed.js +++ b/src/utils/proxy/handlers/credentialed.js @@ -64,6 +64,8 @@ export default async function credentialedProxyHandler(req, res, map) { } else if (widget.type === "azuredevops") { headers.Authorization = `Basic ${Buffer.from(`$:${widget.key}`).toString("base64")}`; + } else if (widget.type === "glances") { + headers.Authorization = `Basic ${Buffer.from(`${widget.username}:${widget.password}`).toString("base64")}`; } else { headers["X-API-Key"] = `${widget.key}`; } diff --git a/src/widgets/components.js b/src/widgets/components.js index ddaca4cf7..851322e7c 100644 --- a/src/widgets/components.js +++ b/src/widgets/components.js @@ -24,6 +24,7 @@ const components = { freshrss: dynamic(() => import("./freshrss/component")), gamedig: dynamic(() => import("./gamedig/component")), ghostfolio: dynamic(() => import("./ghostfolio/component")), + glances: dynamic(() => import("./glances/component")), gluetun: dynamic(() => import("./gluetun/component")), gotify: dynamic(() => import("./gotify/component")), grafana: dynamic(() => import("./grafana/component")), diff --git a/src/widgets/glances/chart.jsx b/src/widgets/glances/chart.jsx new file mode 100644 index 000000000..448bb99b1 --- /dev/null +++ b/src/widgets/glances/chart.jsx @@ -0,0 +1,46 @@ +import { PureComponent } from "react"; +import { AreaChart, Area, ResponsiveContainer, Tooltip } from "recharts"; + +import CustomTooltip from "./custom_tooltip"; + +class Chart extends PureComponent { + render() { + const { dataPoints, formatter, label } = this.props; + + return ( +
+ + + + + + + + + + } + classNames="rounded-md text-xs p-0.5" + contentStyle={{ + backgroundColor: "rgb(var(--color-800))", + color: "rgb(var(--color-100))" + }} + /> + + +
+ ); + } +} + +export default Chart; diff --git a/src/widgets/glances/chart_dual.jsx b/src/widgets/glances/chart_dual.jsx new file mode 100644 index 000000000..ceecacba6 --- /dev/null +++ b/src/widgets/glances/chart_dual.jsx @@ -0,0 +1,61 @@ +import { PureComponent } from "react"; +import { AreaChart, Area, ResponsiveContainer, Tooltip } from "recharts"; + +import CustomTooltip from "./custom_tooltip"; + +class ChartDual extends PureComponent { + render() { + const { dataPoints, formatter, stack, label } = this.props; + + return ( +
+ + + + + + + + + + + + + + + + } + classNames="rounded-md text-xs p-0.5" + contentStyle={{ + backgroundColor: "rgb(var(--color-800))", + color: "rgb(var(--color-100))" + }} + + /> + + +
+ ); + } +} + +export default ChartDual; diff --git a/src/widgets/glances/component.jsx b/src/widgets/glances/component.jsx new file mode 100644 index 000000000..5eeff280b --- /dev/null +++ b/src/widgets/glances/component.jsx @@ -0,0 +1,34 @@ +import Memory from "./memory"; +import Cpu from "./cpu"; +import Sensor from "./sensor"; +import Net from "./net"; +import Process from "./process"; +import Disk from "./disk"; + +export default function Component({ service }) { + const { widget } = service; + + if (widget.metric === "memory") { + return ; + } + + if (widget.metric === "process") { + return ; + } + + if (widget.metric.match(/^network:/)) { + return ; + } + + if (widget.metric.match(/^sensor:/)) { + return ; + } + + if (widget.metric.match(/^disk:/)) { + return ; + } + + if (widget.metric === "cpu") { + return ; + } +} diff --git a/src/widgets/glances/cpu.jsx b/src/widgets/glances/cpu.jsx new file mode 100644 index 000000000..daec91e07 --- /dev/null +++ b/src/widgets/glances/cpu.jsx @@ -0,0 +1,100 @@ +import dynamic from "next/dynamic"; +import { useState, useEffect } from "react"; +import { useTranslation } from "next-i18next"; + +import useWidgetAPI from "utils/proxy/use-widget-api"; + +const Chart = dynamic(() => import("./chart"), { ssr: false }); + +const pointsLimit = 15; + +export default function Component({ service }) { + const { t } = useTranslation(); + + const [dataPoints, setDataPoints] = useState(new Array(pointsLimit).fill({ value: 0 }, 0, pointsLimit)); + + const { data, error } = useWidgetAPI(service.widget, 'cpu', { + refreshInterval: 1000, + }); + + const { data: systemData, error: systemError } = useWidgetAPI(service.widget, 'system'); + + useEffect(() => { + if (data) { + setDataPoints((prevDataPoints) => { + const newDataPoints = [...prevDataPoints, { value: data.total }]; + if (newDataPoints.length > pointsLimit) { + newDataPoints.shift(); + } + return newDataPoints; + }); + } + }, [data]); + + if (error) { + return
+
+
+ {t("widget.api_error")} +
+
+
; + } + + if (!data) { + return
+
+
+ - +
+
+
; + } + + return ( + <> +
+ t("common.number", { + value, + style: "unit", + unit: "percent", + maximumFractionDigits: 0, + })} + /> +
+
+ {systemData && !systemError && ( + <> + {systemData.linux_distro && ( +
+ {systemData.linux_distro} +
+ )} + {systemData.os_version && ( +
+ {systemData.os_version} +
+ )} + {systemData.hostname && ( +
+ {systemData.hostname} +
+ )} + + )} +
+
+ {t("common.number", { + value: data.total, + style: "unit", + unit: "percent", + maximumFractionDigits: 0, + })} {t("resources.used")} +
+
+ + ); +} diff --git a/src/widgets/glances/custom_tooltip.jsx b/src/widgets/glances/custom_tooltip.jsx new file mode 100644 index 000000000..ef3881ef6 --- /dev/null +++ b/src/widgets/glances/custom_tooltip.jsx @@ -0,0 +1,15 @@ +export default function Tooltip({ active, payload, formatter }) { + if (active && payload && payload.length) { + return ( +
+ {payload.map((pld, id) => ( +
+
{formatter(pld.value)} {payload[id].name}
+
+ ))} +
+ ); + } + + return null; +}; diff --git a/src/widgets/glances/disk.jsx b/src/widgets/glances/disk.jsx new file mode 100644 index 000000000..7ad783acb --- /dev/null +++ b/src/widgets/glances/disk.jsx @@ -0,0 +1,113 @@ +import dynamic from "next/dynamic"; +import { useState, useEffect } from "react"; +import { useTranslation } from "next-i18next"; + +import useWidgetAPI from "utils/proxy/use-widget-api"; + +const ChartDual = dynamic(() => import("./chart_dual"), { ssr: false }); + +const pointsLimit = 15; + +export default function Component({ service }) { + const { t } = useTranslation(); + const { widget } = service; + const [, diskName] = widget.metric.split(':'); + + const [dataPoints, setDataPoints] = useState(new Array(pointsLimit).fill({ read_bytes: 0, write_bytes: 0, time_since_update: 0 }, 0, pointsLimit)); + const [ratePoints, setRatePoints] = useState(new Array(pointsLimit).fill({ a: 0, b: 0 }, 0, pointsLimit)); + + const { data, error } = useWidgetAPI(service.widget, 'diskio', { + refreshInterval: 1000, + }); + + const calculateRates = (d) => d.map(item => ({ + a: item.read_bytes / item.time_since_update, + b: item.write_bytes / item.time_since_update + })); + + useEffect(() => { + if (data) { + const diskData = data.find((item) => item.disk_name === diskName); + + setDataPoints((prevDataPoints) => { + const newDataPoints = [...prevDataPoints, diskData]; + if (newDataPoints.length > pointsLimit) { + newDataPoints.shift(); + } + return newDataPoints; + }); + } + }, [data, diskName]); + + useEffect(() => { + setRatePoints(calculateRates(dataPoints)); + }, [dataPoints]); + + if (error) { + return
+
+
+ {t("widget.api_error")} +
+
+
; + } + + if (!data) { + return
+
+
+ - +
+
+
; + } + + const diskData = data.find((item) => item.disk_name === diskName); + + if (!diskData) { + return
+
+
; + } + + const diskRates = calculateRates(dataPoints); + const currentRate = diskRates[diskRates.length - 1]; + + return ( + <> +
+ t("common.bitrate", { + value, + })} + /> +
+
+ {currentRate && !error && ( + <> +
+ {t("common.bitrate", { + value: currentRate.a, + })} {t("glances.read")} +
+
+ {t("common.bitrate", { + value: currentRate.b, + })} {t("glances.write")} +
+ + )} +
+
+ {t("common.bitrate", { + value: currentRate.a + currentRate.b, + })} +
+
+ + ); +} diff --git a/src/widgets/glances/memory.jsx b/src/widgets/glances/memory.jsx new file mode 100644 index 000000000..0e6002eef --- /dev/null +++ b/src/widgets/glances/memory.jsx @@ -0,0 +1,96 @@ +import dynamic from "next/dynamic"; +import { useState, useEffect } from "react"; +import { useTranslation } from "next-i18next"; + +import useWidgetAPI from "utils/proxy/use-widget-api"; + +const ChartDual = dynamic(() => import("./chart_dual"), { ssr: false }); + +const pointsLimit = 15; + +export default function Component({ service }) { + const { t } = useTranslation(); + + const [dataPoints, setDataPoints] = useState(new Array(pointsLimit).fill({ value: 0 }, 0, pointsLimit)); + + const { data, error } = useWidgetAPI(service.widget, 'mem', { + refreshInterval: 1000, + }); + + useEffect(() => { + if (data) { + setDataPoints((prevDataPoints) => { + const newDataPoints = [...prevDataPoints, { a: data.used, b: data.free }]; + if (newDataPoints.length > pointsLimit) { + newDataPoints.shift(); + } + return newDataPoints; + }); + } + }, [data]); + + if (error) { + return
+
+
+ {t("widget.api_error")} +
+
+
; + } + if (!data) { + return
+
+
+ - +
+
+
; + } + + return ( + <> +
+ t("common.bytes", { + value, + maximumFractionDigits: 0, + })} + /> +
+
+ {data && !error && ( + <> + {data.free && ( +
+ {t("common.bytes", { + value: data.free, + maximumFractionDigits: 0, + })} {t("resources.free")} +
+ )} + + {data.total && ( +
+ {t("common.bytes", { + value: data.total, + maximumFractionDigits: 0, + })} {t("resources.total")} +
+ )} + + )} +
+
+ {t("common.bytes", { + value: data.used, + maximumFractionDigits: 0, + })} {t("resources.used")} +
+
+ + ); +} diff --git a/src/widgets/glances/net.jsx b/src/widgets/glances/net.jsx new file mode 100644 index 000000000..a630ae744 --- /dev/null +++ b/src/widgets/glances/net.jsx @@ -0,0 +1,96 @@ +import dynamic from "next/dynamic"; +import { useState, useEffect } from "react"; +import { useTranslation } from "next-i18next"; + +import useWidgetAPI from "utils/proxy/use-widget-api"; + +const ChartDual = dynamic(() => import("./chart_dual"), { ssr: false }); + +const pointsLimit = 15; + +export default function Component({ service }) { + const { t } = useTranslation(); + const { widget } = service; + const [, interfaceName] = widget.metric.split(':'); + + const [dataPoints, setDataPoints] = useState(new Array(pointsLimit).fill({ value: 0 }, 0, pointsLimit)); + + const { data, error } = useWidgetAPI(widget, 'network', { + refreshInterval: 1000, + }); + + useEffect(() => { + if (data) { + const interfaceData = data.find((item) => item[item.key] === interfaceName); + + if (interfaceData) { + setDataPoints((prevDataPoints) => { + const newDataPoints = [...prevDataPoints, { a: interfaceData.tx, b: interfaceData.rx }]; + if (newDataPoints.length > pointsLimit) { + newDataPoints.shift(); + } + return newDataPoints; + }); + } + } + }, [data, interfaceName]); + + if (error) { + return
+
+
+ {t("widget.api_error")} +
+
+
; + } + if (!data) { + return
+
+
+ - +
+
+
; + } + const interfaceData = data.find((item) => item[item.key] === interfaceName); + + if (!interfaceData) { + return
+
+
; + } + + return ( + <> +
+ t("common.byterate", { + value, + maximumFractionDigits: 0, + })} + /> +
+
+ {interfaceData && interfaceData.interface_name && ( +
+ {interfaceData.interface_name} +
+ )} + {t("common.bitrate", { + value: interfaceData.tx, + maximumFractionDigits: 0, + })} {t("docker.tx")} +
+
+ {t("common.bitrate", { + value: interfaceData.rx, + maximumFractionDigits: 0, + })} {t("docker.rx")} +
+
+ + ); +} diff --git a/src/widgets/glances/process.jsx b/src/widgets/glances/process.jsx new file mode 100644 index 000000000..7d9785e67 --- /dev/null +++ b/src/widgets/glances/process.jsx @@ -0,0 +1,72 @@ +import { useTranslation } from "next-i18next"; + +import useWidgetAPI from "utils/proxy/use-widget-api"; +import ResolvedIcon from "components/resolvedicon"; + +const statusMap = { + "R": , // running + "S": , // sleeping + "D": , // disk sleep + "Z": , // zombie + "T": , // traced + "t": , // traced + "X": , // dead +}; + +export default function Component({ service }) { + const { t } = useTranslation(); + + const { data, error } = useWidgetAPI(service.widget, 'processlist', { + refreshInterval: 1000, + }); + + if (error) { + return
+
+
+ {t("widget.api_error")} +
+
+
; + } + + if (!data) { + return
+
+
+ - +
+
+
; + } + + data.splice(5); + + return ( + <> +
+
+
+
{t("resources.cpu")}
+
{t("resources.mem")}
+
+
+
+ { data.map((item) =>
+
+
+ {statusMap[item.status]} +
+
{item.name}
+
{item.cpu_percent.toFixed(1)}%
+
{t("common.bytes", { + value: item.memory_info[0], + maximumFractionDigits: 0, + })}
+
+
) } +
+
+ + ); +} diff --git a/src/widgets/glances/sensor.jsx b/src/widgets/glances/sensor.jsx new file mode 100644 index 000000000..32d01eb20 --- /dev/null +++ b/src/widgets/glances/sensor.jsx @@ -0,0 +1,99 @@ +import dynamic from "next/dynamic"; +import { useState, useEffect } from "react"; +import { useTranslation } from "next-i18next"; + +import useWidgetAPI from "utils/proxy/use-widget-api"; + +const Chart = dynamic(() => import("./chart"), { ssr: false }); + +const pointsLimit = 15; + +export default function Component({ service }) { + const { t } = useTranslation(); + const { widget } = service; + const [, sensorName] = widget.metric.split(':'); + + const [dataPoints, setDataPoints] = useState(new Array(pointsLimit).fill({ value: 0 }, 0, pointsLimit)); + + const { data, error } = useWidgetAPI(service.widget, 'sensors', { + refreshInterval: 1000, + }); + + useEffect(() => { + if (data) { + const sensorData = data.find((item) => item.label === sensorName); + setDataPoints((prevDataPoints) => { + const newDataPoints = [...prevDataPoints, { value: sensorData.value }]; + if (newDataPoints.length > pointsLimit) { + newDataPoints.shift(); + } + return newDataPoints; + }); + } + }, [data, sensorName]); + + if (error) { + return
+
+
+ {t("widget.api_error")} +
+
+
; + } + + if (!data) { + return
+
+
+ - +
+
+
; + } + + const sensorData = data.find((item) => item.label === sensorName); + + if (!sensorData) { + return
+
+
; + } + + return ( + <> +
+ t("common.number", { + value, + })} + /> +
+
+ {sensorData && !error && ( + <> + {sensorData.warning && ( +
+ {sensorData.warning}{sensorData.unit} {t("glances.warn")} +
+ )} + {sensorData.critical && ( +
+ {sensorData.critical} {sensorData.unit} {t("glances.crit")} +
+ )} + + )} +
+
+ {t("common.number", { + value: sensorData.value, + })} {sensorData.unit} +
+
+ + ); +} diff --git a/src/widgets/glances/widget.js b/src/widgets/glances/widget.js new file mode 100644 index 000000000..3da1c6d12 --- /dev/null +++ b/src/widgets/glances/widget.js @@ -0,0 +1,8 @@ +import credentialedProxyHandler from "utils/proxy/handlers/credentialed"; + +const widget = { + api: "{url}/api/3/{endpoint}", + proxyHandler: credentialedProxyHandler, +}; + +export default widget; diff --git a/src/widgets/widgets.js b/src/widgets/widgets.js index 46143447e..41a31253a 100644 --- a/src/widgets/widgets.js +++ b/src/widgets/widgets.js @@ -19,6 +19,7 @@ import flood from "./flood/widget"; import freshrss from "./freshrss/widget"; import gamedig from "./gamedig/widget"; import ghostfolio from "./ghostfolio/widget"; +import glances from "./glances/widget"; import gluetun from "./gluetun/widget"; import gotify from "./gotify/widget"; import grafana from "./grafana/widget"; @@ -111,6 +112,7 @@ const widgets = { freshrss, gamedig, ghostfolio, + glances, gluetun, gotify, grafana,