diff --git a/.github/workflows/bump-meshery-version.yml b/.github/workflows/bump-meshery-version.yml index d38b55402..45722c87a 100644 --- a/.github/workflows/bump-meshery-version.yml +++ b/.github/workflows/bump-meshery-version.yml @@ -100,6 +100,44 @@ jobs: _This pull request has been auto-generated by [l5io](http://github.com/l5io)_ assignees: l5io draft: false + bump-layer5: + runs-on: ubuntu-latest + needs: versions-check + steps: + - name: Checkout Layer5 code + uses: actions/checkout@v4 + with: + repository: layer5io/layer5 + fetch-depth: 1 + token: ${{ secrets.RELEASEDRAFTER_PAT }} + - uses: actions/setup-node@v4 + with: + node-version: 18 + cache: "npm" + cache-dependency-path: '**/package-lock.json' + - name: Make changes to pull request + run: npm install @layer5/sistent@${{needs.versions-check.outputs.current}} + - name: Create Pull Request + id: cpr + uses: peter-evans/create-pull-request@v7 + with: + token: ${{ secrets.RELEASEDRAFTER_PAT }} + commit-message: Bump sistent v${{ needs.versions-check.outputs.current }} dependencies + committer: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com> + author: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com> + signoff: true + branch: bump-sistent-bot + delete-branch: true + title: '[Chore]: Bump Sistent v${{ needs.versions-check.outputs.current }}' + add-paths: | + package.json + package-lock.json + body: | + Update to Sistent v${{ needs.versions-check.outputs.current }} + + _This pull request has been auto-generated by [l5io](http://github.com/l5io)_ + assignees: l5io + draft: false bump-meshery-cloud: runs-on: ubuntu-latest needs: versions-check diff --git a/package-lock.json b/package-lock.json index ed4e5204b..887072551 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,10 +8,12 @@ "name": "@layer5/sistent", "version": "0.14.11", "dependencies": { + "billboard.js": "^3.14.3", "js-yaml": "^4.1.0", "lodash": "^4.17.21", "re-resizable": "^6.10.3", "react-draggable": "^4.4.6", + "moment": "^2.30.1", "react-share": "^5.1.0" }, "devDependencies": { @@ -3042,6 +3044,21 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", + "license": "MIT" + }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, "node_modules/@types/debug": { "version": "4.1.12", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", @@ -4082,6 +4099,29 @@ ], "license": "MIT" }, + "node_modules/billboard.js": { + "version": "3.14.3", + "resolved": "https://registry.npmjs.org/billboard.js/-/billboard.js-3.14.3.tgz", + "integrity": "sha512-DhldgsPcAv6Y9iw7VTnY6NaRKUlZ2UvW9e60tCi2C3cxq9HzQWrnx4f0xDCk51O/SDshhE+/2PcaIdhZ9DD7+A==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "^3.0.11", + "@types/d3-transition": "^3.0.9", + "d3-axis": "^3.0.0", + "d3-brush": "^3.0.0", + "d3-drag": "^3.0.0", + "d3-dsv": "^3.0.1", + "d3-ease": "^3.0.1", + "d3-hierarchy": "^3.1.2", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-selection": "^3.0.0", + "d3-shape": "^3.2.0", + "d3-time-format": "^4.1.0", + "d3-transition": "^3.0.1", + "d3-zoom": "^3.0.0" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -5020,6 +5060,273 @@ "node": ">=4" } }, + "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==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "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==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "license": "ISC", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/d3-dsv/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "license": "BSD-3-Clause", + "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==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "license": "ISC", + "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==", + "license": "ISC", + "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==", + "license": "ISC", + "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==", + "license": "ISC", + "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-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "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==", + "license": "ISC", + "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==", + "license": "ISC", + "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==", + "license": "ISC", + "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==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/dargs": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz", @@ -7288,6 +7595,15 @@ "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==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/is-arguments": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", @@ -10717,6 +11033,15 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/mri": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", @@ -12289,6 +12614,12 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" + }, "node_modules/rxjs": { "version": "7.8.1", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", @@ -12345,7 +12676,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, "license": "MIT" }, "node_modules/saxes": { @@ -16358,6 +16688,19 @@ "@babel/types": "^7.20.7" } }, + "@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==" + }, + "@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "requires": { + "@types/d3-selection": "*" + } + }, "@types/debug": { "version": "4.1.12", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", @@ -17123,6 +17466,28 @@ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", "dev": true }, + "billboard.js": { + "version": "3.14.3", + "resolved": "https://registry.npmjs.org/billboard.js/-/billboard.js-3.14.3.tgz", + "integrity": "sha512-DhldgsPcAv6Y9iw7VTnY6NaRKUlZ2UvW9e60tCi2C3cxq9HzQWrnx4f0xDCk51O/SDshhE+/2PcaIdhZ9DD7+A==", + "requires": { + "@types/d3-selection": "^3.0.11", + "@types/d3-transition": "^3.0.9", + "d3-axis": "^3.0.0", + "d3-brush": "^3.0.0", + "d3-drag": "^3.0.0", + "d3-dsv": "^3.0.1", + "d3-ease": "^3.0.1", + "d3-hierarchy": "^3.1.2", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-selection": "^3.0.0", + "d3-shape": "^3.2.0", + "d3-time-format": "^4.1.0", + "d3-transition": "^3.0.1", + "d3-zoom": "^3.0.0" + } + }, "binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -17751,6 +18116,173 @@ } } }, + "d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "requires": { + "internmap": "1 - 2" + } + }, + "d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==" + }, + "d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "requires": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + } + }, + "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==" + }, + "d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==" + }, + "d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "requires": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + } + }, + "d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "requires": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "dependencies": { + "commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==" + }, + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + } + } + }, + "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==" + }, + "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==" + }, + "d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==" + }, + "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==", + "requires": { + "d3-color": "1 - 3" + } + }, + "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==" + }, + "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==", + "requires": { + "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" + } + }, + "d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==" + }, + "d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "requires": { + "d3-path": "^3.1.0" + } + }, + "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==", + "requires": { + "d3-array": "2 - 3" + } + }, + "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==", + "requires": { + "d3-time": "1 - 3" + } + }, + "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==" + }, + "d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "requires": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + } + }, + "d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "requires": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + } + }, "dargs": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz", @@ -19332,6 +19864,11 @@ "side-channel": "^1.0.4" } }, + "internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==" + }, "is-arguments": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", @@ -21651,6 +22188,11 @@ "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true }, + "moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==" + }, "mri": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", @@ -22736,6 +23278,11 @@ "queue-microtask": "^1.2.2" } }, + "rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" + }, "rxjs": { "version": "7.8.1", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", @@ -22776,8 +23323,7 @@ "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "saxes": { "version": "6.0.0", diff --git a/package.json b/package.json index 8436340c7..162bb5a20 100644 --- a/package.json +++ b/package.json @@ -116,10 +116,12 @@ "access": "public" }, "dependencies": { + "billboard.js": "^3.14.3", "js-yaml": "^4.1.0", "lodash": "^4.17.21", "re-resizable": "^6.10.3", "react-draggable": "^4.4.6", + "moment": "^2.30.1", "react-share": "^5.1.0" } } diff --git a/src/custom/BBChart/BBChart.tsx b/src/custom/BBChart/BBChart.tsx new file mode 100644 index 000000000..503c6554f --- /dev/null +++ b/src/custom/BBChart/BBChart.tsx @@ -0,0 +1,27 @@ +import { ChartOptions, bb } from 'billboard.js'; +import { memo, useEffect, useRef } from 'react'; + +interface BBChartProps { + options: ChartOptions; +} + +const BBChart = ({ options }: BBChartProps) => { + const _chartRef = useRef(null); + + useEffect(() => { + if (!_chartRef.current) return; + + const chart = bb.generate({ + bindto: _chartRef.current, + ...options + }); + + return () => { + chart.destroy(); + }; + }, [options]); + + return
e.stopPropagation()} />; +}; + +export default memo(BBChart); diff --git a/src/custom/BBChart/index.ts b/src/custom/BBChart/index.ts new file mode 100644 index 000000000..2980714e6 --- /dev/null +++ b/src/custom/BBChart/index.ts @@ -0,0 +1,3 @@ +import BBChart from './BBChart'; + +export { BBChart }; diff --git a/src/custom/CatalogDesignTable/DesignTableColumnConfig.tsx b/src/custom/CatalogDesignTable/DesignTableColumnConfig.tsx index ad884cee7..17c26b203 100644 --- a/src/custom/CatalogDesignTable/DesignTableColumnConfig.tsx +++ b/src/custom/CatalogDesignTable/DesignTableColumnConfig.tsx @@ -3,7 +3,7 @@ import { PLAYGROUND_MODES } from '../../constants/constants'; import { ChainIcon, CopyIcon, KanvasIcon, PublishIcon } from '../../icons'; import Download from '../../icons/Download/Download'; import { CHARCOAL } from '../../theme'; -import { downloadYaml, slugify } from '../CatalogDetail/helper'; +import { downloadPattern, slugify } from '../CatalogDetail/helper'; import { RESOURCE_TYPES } from '../CatalogDetail/types'; import { Pattern } from '../CustomCatalog/CustomCard'; import { ConditionalTooltip } from '../Helpers/CondtionalTooltip'; @@ -25,6 +25,7 @@ interface ColumnConfigProps { handleCopyUrl: (type: string, name: string, id: string) => void; handleClone: (name: string, id: string) => void; handleShowDetails: (designId: string, designName: string) => void; + getDownloadUrl: (id: string) => string; isDownloadAllowed: boolean; isCopyLinkAllowed: boolean; isDeleteAllowed: boolean; @@ -53,6 +54,7 @@ export const createDesignsColumnsConfig = ({ handleCopyUrl, handleClone, handleShowDetails, + getDownloadUrl, isUnpublishAllowed, isCopyLinkAllowed, isDeleteAllowed, @@ -167,7 +169,7 @@ export const createDesignsColumnsConfig = ({ const actionsList = [ { title: 'Download', - onClick: () => downloadYaml(rowData?.pattern_file, rowData?.name), + onClick: () => downloadPattern(rowData.id, rowData.name, getDownloadUrl), disabled: !isDownloadAllowed, icon: }, @@ -175,7 +177,7 @@ export const createDesignsColumnsConfig = ({ title: 'Copy Link', disabled: rowData.visibility === 'private' || !isCopyLinkAllowed, onClick: () => { - handleCopyUrl(RESOURCE_TYPES.DESIGNS, rowData?.name, rowData?.id); + handleCopyUrl(RESOURCE_TYPES.DESIGN, rowData?.name, rowData?.id); }, icon: }, @@ -185,9 +187,7 @@ export const createDesignsColumnsConfig = ({ window.open( `https://playground.meshery.io/extension/meshmap?mode=${ PLAYGROUND_MODES.DESIGNER - }&type=${RESOURCE_TYPES.DESIGNS}&id=${rowData?.id}&name=${slugify( - rowData?.name - )}`, + }&type=${RESOURCE_TYPES.DESIGN}&id=${rowData?.id}&name=${slugify(rowData?.name)}`, '_blank' ); }, diff --git a/src/custom/CatalogDesignTable/columnConfig.tsx b/src/custom/CatalogDesignTable/columnConfig.tsx index bcdf1c452..06cca4e06 100644 --- a/src/custom/CatalogDesignTable/columnConfig.tsx +++ b/src/custom/CatalogDesignTable/columnConfig.tsx @@ -13,8 +13,7 @@ import { PublishIcon, TwitterIcon } from '../../icons'; -import { downloadFilter, downloadYaml } from '../CatalogDetail/helper'; -import { RESOURCE_TYPES } from '../CatalogDetail/types'; +import { downloadPattern } from '../CatalogDetail/helper'; import { Pattern } from '../CustomCatalog/CustomCard'; import { ConditionalTooltip } from '../Helpers/CondtionalTooltip'; import { ColView } from '../Helpers/ResponsiveColumns/responsive-coulmns.tsx/responsive-column'; @@ -48,13 +47,13 @@ interface ColumnConfigProps { handleUnpublish?: (design: Pattern) => void; maxWidth?: boolean; getCatalogUrl: (type: string, name: string) => string; - type?: string; theme?: any; showUnpublish?: boolean; showOpenPlayground?: boolean; currentUserId?: string; isCloneDisabled?: boolean; isUnpublishDisabled?: boolean; + getDownloadUrl: (id: string) => string; } interface ActionItem { @@ -74,7 +73,7 @@ export const createDesignColumns = ({ handleUnpublish = () => {}, maxWidth = true, getCatalogUrl, - type, + getDownloadUrl, theme, showUnpublish, currentUserId, @@ -82,7 +81,6 @@ export const createDesignColumns = ({ isUnpublishDisabled, showOpenPlayground }: ColumnConfigProps): MUIDataTableColumn[] => { - const cleanedType = type?.replace('my-', '').replace(/s$/, ''); return [ { name: 'id', @@ -260,11 +258,7 @@ export const createDesignColumns = ({ }, { title: 'Download', - onClick: () => { - cleanedType === RESOURCE_TYPES.FILTERS - ? downloadFilter(rowData.id, rowData.name) - : downloadYaml(rowData.pattern_file, rowData.name); - }, + onClick: () => downloadPattern(rowData.id, rowData.name, getDownloadUrl), icon: }, { diff --git a/src/custom/CatalogDetail/ActionButton.tsx b/src/custom/CatalogDetail/ActionButton.tsx index 0141e7368..bf601a09a 100644 --- a/src/custom/CatalogDetail/ActionButton.tsx +++ b/src/custom/CatalogDetail/ActionButton.tsx @@ -4,15 +4,16 @@ import { CopyIcon, DeleteIcon, EditIcon, KanvasIcon, PublishIcon } from '../../i import Download from '../../icons/Download/Download'; import { charcoal, useTheme } from '../../theme'; import { Pattern } from '../CustomCatalog/CustomCard'; -import { downloadFilter, downloadYaml } from './helper'; +import { downloadPattern, downloadYaml } from './helper'; import { ActionButton, StyledActionWrapper, UnpublishAction } from './style'; -import { RESOURCE_TYPES } from './types'; +import { FILTERS, VIEWS } from './types'; interface ActionButtonsProps { actionItems: boolean; details: Pattern; type: string; isCloneLoading: boolean; + getDownloadUrl: (id: string) => string; handleClone: (name: string, id: string) => void; handleUnpublish: () => void; isCloneDisabled: boolean; @@ -34,6 +35,7 @@ const ActionButtons: React.FC = ({ isCloneDisabled, showUnpublishAction, handleUnpublish, + getDownloadUrl, showOpenPlaygroundAction, onOpenPlaygroundClick, showInfoAction, @@ -83,16 +85,16 @@ const ActionButtons: React.FC = ({ color: theme.palette.text.default }} onClick={() => - cleanedType === RESOURCE_TYPES.FILTERS - ? downloadFilter(details.id, details.name) - : downloadYaml(details.pattern_file, details.name) + cleanedType === VIEWS + ? downloadYaml(details.pattern_file, details.name) + : downloadPattern(details.id, details.name, getDownloadUrl) } > Download - {cleanedType !== RESOURCE_TYPES.FILTERS && ( + {cleanedType !== FILTERS && ( void; showDeleteAction?: boolean; handleDelete: () => void; + getDownloadUrl: (id: string) => string; } const LeftPanel: React.FC = ({ @@ -48,7 +49,8 @@ const LeftPanel: React.FC = ({ showInfoAction = false, handleInfoClick, showDeleteAction = false, - handleDelete + handleDelete, + getDownloadUrl }) => { const theme = useTheme(); @@ -95,6 +97,7 @@ const LeftPanel: React.FC = ({ handleInfoClick={handleInfoClick} showDeleteAction={showDeleteAction} handleDelete={handleDelete} + getDownloadUrl={getDownloadUrl} /> {showTechnologies && ( = ({ size="small" onClick={() => handleCopyUrl(cleanedType, details?.name, details?.id)} > - + diff --git a/src/custom/CatalogDetail/helper.ts b/src/custom/CatalogDetail/helper.ts index 2d6601f77..b43477bf9 100644 --- a/src/custom/CatalogDetail/helper.ts +++ b/src/custom/CatalogDetail/helper.ts @@ -34,14 +34,16 @@ export function slugify(str: string): string { return str; } -export const downloadFilter = (id: string, name: string): void => { - const dataUri = `${process.env.API_ENDPOINT_PREFIX}/api/content/filters/download/${id}`; - - // Add the .wasm extension to the filename - const fileNameWithExtension = name + '.wasm'; - +export const downloadPattern = ( + id: string, + name: string, + getDownloadUrl: (id: string) => string +): void => { + const downloadUrl = getDownloadUrl(id); + + const fileNameWithExtension = `${name}.yaml`; const linkElement = document.createElement('a'); - linkElement.setAttribute('href', dataUri); + linkElement.setAttribute('href', downloadUrl); linkElement.setAttribute('download', fileNameWithExtension); linkElement.click(); linkElement.remove(); diff --git a/src/custom/CatalogDetail/types.ts b/src/custom/CatalogDetail/types.ts index 1cd2adc78..2255a6f53 100644 --- a/src/custom/CatalogDetail/types.ts +++ b/src/custom/CatalogDetail/types.ts @@ -32,11 +32,15 @@ export interface Theme { } export const RESOURCE_TYPES = { - DESIGNS: 'design', - FILTERS: 'filter', - VIEWS: 'view' + DESIGN: 'design', + FILTER: 'filter', + VIEW: 'view' }; +export const PATTERNS = 'patterns'; +export const FILTERS = 'filters'; +export const VIEWS = 'views'; + export type ContentClassType = { community: { icon: React.ComponentType; diff --git a/src/custom/ResourceDetailFormatters/Component.tsx b/src/custom/ResourceDetailFormatters/Component.tsx new file mode 100644 index 000000000..5e2e65f57 --- /dev/null +++ b/src/custom/ResourceDetailFormatters/Component.tsx @@ -0,0 +1,168 @@ +import React from 'react'; +import { Grid, IconButton, Typography } from '../../base'; +import { iconSmall } from '../../constants/iconsSizes'; +import { CopyIcon } from '../../icons'; +import { useTheme } from '../../theme'; +import { CustomTooltip } from './../CustomTooltip'; +import { NumberState } from './Formatter'; +import { + Details, + ElementDataWrap, + Heading, + KeyValueGrid, + KeyValueGridCell, + KeyValueGridTitle, + LongWrap, + StyledNumberBox, + Title, + VariableSubfield, + Wrap +} from './styles'; +import { + ActionIconButtonProps, + CategoryProps, + CopyToClipboardProps, + EnvironmentVariablesProps, + KeyValueProps, + LongDetailsProps, + NumberStateFormatterProps, + PrimaryDetailsProps, + SectionHeadingProps +} from './types'; +import { splitCamelCaseString } from './utils.js'; + +export const PrimaryDetails: React.FC = ({ title, value, hide = false }) => { + const titleFormatted = splitCamelCaseString(title); + const show = hide === false ? hide : true; + + if (!value || value === ` `) { + return null; + } + + if (show) { + return ( +
+ + {titleFormatted}: + {value} + +
+ ); + } + return null; +}; + +export const CopyToClipboard: React.FC = ({ data }) => { + const theme = useTheme(); + const copyToClipboard = () => { + navigator.clipboard.writeText(data); + }; + + return ( + + + + + + ); +}; + +export const SectionHeading: React.FC = ({ children }) => { + return {children + ':'}; +}; + +export const LongDetails: React.FC = ({ title, value }) => { + const titleFormatted = splitCamelCaseString(title); + + if (!value || value === ` `) { + return null; + } + + return ( +
+ + {titleFormatted} + {/* */} + +
+ ); +}; + +export const EnvironmentVariables: React.FC = ({ title, value }) => { + return ( +
+ + + {title}:{value} + + +
+ ); +}; + +export const Category: React.FC = ({ title, hide = false }) => { + const show = hide === false ? hide : true; + + if (show) { + return ( + + {title} + + ); + } + return null; +}; + +export const NumberStateFormatter: React.FC = ({ data }) => { + if (!data) { + return null; + } + + return ( + + {data.map((item) => ( + + ))} + + ); +}; + +export const ActionIconButton: React.FC = ({ title, Icon, onClick }) => { + const theme = useTheme(); + return ( + +
+ + + +
+
+ ); +}; + +export const KeyValueInRow: React.FC = ({ Key, Value }) => { + if (!Value || !Key) return null; + return ( + + + + {Key} + + +
{React.isValidElement(Value) ? Value : String(Value)}
+
+
+
+ ); +}; diff --git a/src/custom/ResourceDetailFormatters/Details.tsx b/src/custom/ResourceDetailFormatters/Details.tsx new file mode 100644 index 000000000..ab83ef831 --- /dev/null +++ b/src/custom/ResourceDetailFormatters/Details.tsx @@ -0,0 +1,25 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { OperatorDataContainer } from './styles'; +import { isEmptyAtAllDepths } from './utils'; + +interface OperatorDataFormatterProps { + data: any; + FormatStructuredData: any; + propertyFormatter: any; +} + +export const OperatorDataFormatter = ({ + data, + FormatStructuredData, + propertyFormatter +}: OperatorDataFormatterProps) => { + if (!data || isEmptyAtAllDepths(data)) { + return null; + } + + return ( + + + + ); +}; diff --git a/src/custom/ResourceDetailFormatters/ExpandArrow.tsx b/src/custom/ResourceDetailFormatters/ExpandArrow.tsx new file mode 100644 index 000000000..6512f1252 --- /dev/null +++ b/src/custom/ResourceDetailFormatters/ExpandArrow.tsx @@ -0,0 +1,19 @@ +import ExpandLessIcon from '@mui/icons-material/ExpandLess'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import { iconMedium } from '../../constants/iconsSizes'; +import { useTheme } from '../../theme'; + +interface ExpandArrowProps { + expanded: boolean; +} + +const ExpandArrow: React.FC = ({ expanded }) => { + const theme = useTheme(); + return expanded ? ( + + ) : ( + + ); +}; + +export default ExpandArrow; diff --git a/src/custom/ResourceDetailFormatters/Formatter.tsx b/src/custom/ResourceDetailFormatters/Formatter.tsx new file mode 100644 index 000000000..11cde80b5 --- /dev/null +++ b/src/custom/ResourceDetailFormatters/Formatter.tsx @@ -0,0 +1,802 @@ +import VisibilityIcon from '@mui/icons-material/Visibility'; +import VisibilityOffIcon from '@mui/icons-material/VisibilityOff'; +import { ChartOptions } from 'billboard.js'; +import _ from 'lodash'; +import React, { useCallback, useContext, useMemo } from 'react'; +import { Box, Chip, Collapse, Grid, IconButton, Typography } from '../../base'; +import { CARIBBEAN_GREEN, KEPPEL, SAFFRON, blue, red } from '../../theme'; +import { BBChart } from '../BBChart'; +import { CustomTooltip } from '../CustomTooltip'; +import ResponsiveDataTable from '../ResponsiveDataTable'; +import { CopyToClipboard, EnvironmentVariables, KeyValueInRow, SectionHeading } from './Component'; +import ExpandArrow from './ExpandArrow'; +import { Level, LevelContext } from './context'; +import { + CodeFormatterCode, + CodeFormatterPre, + CollapsibleSectionContainer, + CollapsibleSectionContent, + CollapsibleSectionTitle, + Details, + ElementData, + ElementDataWrap, + EnvironmentVariablesContainer, + FlexResourceContainer, + KeyValField, + LongWrap, + NumberStateContainer, + NumberStateQuantity, + NumberStateTitle, + NumberStateValue, + NumberStateValueContainer, + ResourceProgressContainer, + StyledArrayUl, + StyledChip, + StyledEnvironmentVariablesCode, + StyledEnvironmentVariablesPre, + StyledTitle, + Wrap +} from './styles'; +import { + ArrayFormatterProps, + CodeFormatterProps, + CollapsibleSectionProps, + ContainerFormatterProps, + DetailSectionProps, + EnvironmentFormatterProps, + LabelFormatterProps, + ListFormatterProps, + MemoryUsageProps, + NumberStateProps, + OperatorDynamicFormatterProps, + PortsFormatterProps, + SecretFormatterProps, + StatusFormatterProps, + TableDataFormatterProps, + TextWithLinkFormatterProps +} from './types'; +import { splitCamelCaseString } from './utils'; + +interface ResourceProgressProps { + title: string; + percentage: number; + type: string; +} + +interface StatusColorType { + background: string; + text: string; +} + +interface StatusColorsType { + [key: string]: StatusColorType; +} + +const STATUS_COLORS: StatusColorsType = { + Active: { background: KEPPEL, text: 'white' }, + Pending: { background: SAFFRON, text: 'black' }, + Terminating: { background: red[30], text: 'white' }, + Succeeded: { background: blue[30], text: 'white' }, + Failed: { background: red[30], text: 'white' }, + Initializing: { background: blue[30], text: 'white' }, + Deleting: { background: red[30], text: 'white' }, + NotReady: { background: red[30], text: 'white' }, + Ready: { background: KEPPEL, text: 'white' }, + CrashLoopBackOff: { background: red[30], text: 'white' }, + Completed: { background: KEPPEL, text: 'black' }, + ImagePullBackOff: { background: red[30], text: 'white' }, + ErrImagePull: { background: red[30], text: 'white' }, + Running: { background: KEPPEL, text: 'white' }, + Waiting: { background: SAFFRON, text: 'black' }, + ContainerCreating: { background: blue[30], text: 'white' }, + Evicted: { background: red[30], text: 'white' }, + OOMKilled: { background: red[30], text: 'white' }, + RunningDegraded: { background: SAFFRON, text: 'black' }, + Restarting: { background: blue[30], text: 'white' }, + Preempted: { background: SAFFRON, text: 'black' }, + Provisioning: { background: blue[30], text: 'white' }, + Available: { background: KEPPEL, text: 'white' }, + Progressing: { background: blue[30], text: 'white' }, + ReplicaFailure: { background: red[30], text: 'white' }, + Bound: { background: KEPPEL, text: 'white' }, + Released: { background: SAFFRON, text: 'black' }, + Terminated: { background: red[30], text: 'white' } +}; + +export const EnvironmentFormatter: React.FC = ({ data }) => { + if (!data) { + return null; + } + const convertEnvironmentValue = (obj: { + value?: string; + valueFrom?: { + [key: string]: { + apiVersion: string; + fieldPath: string; + }; + }; + }) => { + const { value, valueFrom } = obj; + if (valueFrom) { + const key = Object.keys(valueFrom)[0]; + const { apiVersion, fieldPath } = valueFrom[key]; + return `${key}(${apiVersion}: ${fieldPath})`; + } else { + return value; + } + }; + + return ( + + + + {data?.map((item) => { + const value = convertEnvironmentValue(item); + return ; + })} + + + + ); +}; + +export const CodeFormatter: React.FC = ({ data }) => { + return ( + + + + + + ); +}; + +export const PortsFormatter: React.FC = ({ data }) => { + return ( + + {data?.map((item, index) => ( +
+ + {`${item.name}: `} + {`(${item.containerPort || item.port}/${item.protocol})`} + +
+ ))} +
+ ); +}; + +export const ArrayFormatter: React.FC = ({ data }) => { + return ( + + {data.map((item, index) => ( + + ))} + + ); +}; + +export const ListFormatter: React.FC = ({ data }) => { + return ( +
    + {data.map((item, index) => ( +
  1. {item}
  2. + ))} +
+ ); +}; + +export const OperatorDynamicFormatter: React.FC = ({ data }) => { + const level = useContext(LevelContext); + const regex = /(.*--)|(^\/)|([$/:*=()<>{}]{2,})/; + + if (_.isNil(data)) { + return null; + } + + if (_.isNumber(data)) { + return {data}; + } + + if (_.isString(data)) { + return ( + <> + + {data} + {regex.test(data) && } + + + ); + } + + if (_.isArray(data)) { + return ; + } + + if (_.isBoolean(data)) { + return ( + + {data ? 'TRUE' : 'FALSE'} + + ); + } + + if (_.isObject(data)) { + if ( + Object.keys(data).length === 2 && + Object.keys(data).includes('key') && + Object.keys(data).includes('value') + ) { + const typedData = data as { key: string; value: string }; + return ( +
+ + {typedData.key}: + {typedData.value} + +
+ ); + } + + return Object.entries(data).map(([key, value]) => { + if (key === 'args' || key === 'query') { + return ( +
+ + {splitCamelCaseString(key)} + + +
+ ); + } + + return ( +
+ + {splitCamelCaseString(key)} + + + + +
+ ); + }); + } + + return null; +}; + +export const StatusFormatter: React.FC = ({ status }) => { + if (!status) { + return null; + } + if (_.isObject(status)) { + return ( + + {Object.entries(status).map(([key, value]) => ( + + + + ))} + + ); + } + const statusColor = STATUS_COLORS[status]; + + return ( + + ); +}; + +export const LabelFormatter: React.FC = ({ + data, + onClick, + selectedLabels +}) => { + if (!data) { + return null; + } + const handleClick = (item: string) => { + const newArr = selectedLabels.includes(item) + ? selectedLabels.filter((i) => i !== item) + : [...selectedLabels, item]; + onClick(newArr); + }; + + return ( + + {data.map((item, index) => { + return ( + + handleClick(item)} + clickable={onClick !== undefined && true} + style={{ + backgroundColor: selectedLabels.includes(item) ? KEPPEL : undefined + }} + /> + + ); + })} + + ); +}; + +export const StatusChip = ({ status }: { status: string }) => { + if (!status) { + return null; + } + const statusColor = STATUS_COLORS[status]; + + return ( + + ); +}; + +export const MemoryUsage: React.FC = ({ + allocatable, + capacity, + height, + width +}) => { + const convertKiToBytes = useCallback((kiValue: string): number => { + return parseInt(kiValue.replace('Ki', '')) * 1024; + }, []); + + const cpuUsage = useMemo(() => { + if (!allocatable || !capacity) return 0; + const usedCPU = parseInt(capacity.cpu) - parseInt(allocatable.cpu); + return (usedCPU / parseInt(capacity.cpu)) * 100; + }, [allocatable, capacity]); + + const memoryUsage = useMemo(() => { + if (!allocatable || !capacity) return 0; + const totalMemory = parseInt(capacity.memory.replace('Ki', '')); + const availableMemory = parseInt(allocatable.memory.replace('Ki', '')); + const usedMemory = totalMemory - availableMemory; + return (usedMemory / totalMemory) * 100; + }, [allocatable, capacity]); + + const diskUsage = useMemo(() => { + if (!allocatable || !capacity) return 0; + const totalStorageInBytes = convertKiToBytes(capacity['ephemeral-storage']); + const availableStorageInBytes = parseInt(allocatable['ephemeral-storage']); + const usedStorageInBytes = totalStorageInBytes - availableStorageInBytes; + return (usedStorageInBytes / totalStorageInBytes) * 100; + }, [allocatable, capacity, convertKiToBytes]); + + const chartOptions = useCallback( + (percentage: number, type: string): ChartOptions => { + const roundedPercentage = parseFloat(percentage.toFixed(2)); + return { + data: { + columns: [[type, roundedPercentage]], + type: 'gauge' + }, + gauge: { + min: 0, + max: 100, + label: { + format: (value: number) => `${value}%` + } + }, + color: { + pattern: [KEPPEL, SAFFRON, '#F97600', '#FF0000'], + threshold: { + values: [30, 60, 90, 100] + } + }, + size: { + height: height ?? 150, + width: width ?? 200 + }, + legend: { + show: false + } + }; + }, + [height, width] + ); + + const ResourceProgress = useCallback>( + ({ title, percentage, type }) => ( + + {title} + + + ), + [chartOptions] + ); + + if (!allocatable || !capacity) { + return null; + } + + return ( + + + + + + ); +}; + +export const TableDataFormatter: React.FC = ({ + title, + data, + showAll = true, + mainTableData, + mainTableCols +}) => { + type ColumnType = { + name: string; + label: string; + options: { + sort: boolean; + }; + }; + if (!showAll) { + return null; + } + let columns: ColumnType[] = []; + let tableData: string[][] = []; + + if (!mainTableCols && !mainTableData) { + if (Array.isArray(data)) { + if (data.length > 0) { + columns = Object.keys(data[0]).map((key) => ({ + name: key, + label: splitCamelCaseString(key), + options: { + sort: false + } + })); + tableData = data.map((item) => Object.values(item)); + } + } else { + columns = Object.keys(data).map((key) => ({ + name: key, + label: splitCamelCaseString(key), + options: { + sort: false + } + })); + tableData = [Object.values(data)]; + } + } + const options = { + filter: false, + download: false, + print: false, + viewColumns: false, + selectableRows: 'none', + search: false, + responsive: 'standard', + pagination: false, + elevation: 1 + }; + + return ( + + {title && {title}} + + + + ); +}; + +export const TextWithLinkFormatter: React.FC = ({ + title, + value, + variant = 'row', + onClick +}) => { + const handleClick = useCallback(() => { + onClick(); + }, [onClick]); + + const LinkComponent = ( + + {value} + + ); + return variant === 'row' ? ( + + ) : ( + + {title} + {LinkComponent} + + ); +}; + +const DetailSection: React.FC = ({ + title = '', + data, + formatter: Formatter +}) => { + if (!data) { + return null; + } + return ( + + + + + } + /> + + ); +}; + +export const ContainerFormatter: React.FC = ({ + containerSpec, + containerStatus +}) => { + const status = _.capitalize(Object.keys(containerStatus.state)[0]); + const stateValues = Object.values(containerStatus.state)[0]; + const startedAt = stateValues ? stateValues?.startedAt : null; + return ( + + } + /> + + ( + + {data ? new Date(data).toLocaleString() : 'Not Available'} + + )} + /> + {data}} + /> + + } + /> + + } + /> + } + /> + + + + + {containerSpec.volumeMounts?.map((item, index) => { + const roStatus = item.readOnly ? ' (RO)' : ' (RW)'; + return ( + + + + + + {`from ${item.name}${roStatus}`} + + + ); + })} + + } + /> + {containerSpec.command && ( + + )} + {containerSpec.livenessProbe && ( + + )} + {containerSpec.readinessProbe && ( + + )} + {containerSpec.startupProbe && ( + + )} + + {containerSpec.resources?.requests && ( + + )} + {containerSpec.resources?.limits && ( + + )} + + ); +}; + +export const SecretFormatter: React.FC = ({ data }) => { + const [showSecret, setShowSecret] = React.useState<{ [key: string]: boolean }>({}); + + const handleToggleVisibility = useCallback((key: string): void => { + setShowSecret((prev) => ({ + ...prev, + [key]: !prev[key] + })); + }, []); + + const parsedData = useMemo(() => { + try { + return JSON.parse(data); + } catch { + return null; + } + }, [data]); + + if (!parsedData || typeof parsedData !== 'object') { + return null; + } + + const keys = Object.keys(parsedData); + + return ( + + {keys.map((key) => ( + + {key} + + {showSecret[key] ? parsedData[key] : '••••••••'} + handleToggleVisibility(key)} + style={{ padding: '4px' }} + > + {showSecret[key] ? ( + +
+ +
+
+ ) : ( + +
+ +
+
+ )} +
+
+
+ ))} +
+ ); +}; + +export const CollapsibleSectionFormatter: React.FC = ({ + title, + children, + showAll = true, + numberText, + level = 0 +}) => { + const margin = level * 16; + const [openSection, setOpenSection] = React.useState(false); + const toggleOpen = () => setOpenSection((prev) => !prev); + if (!showAll) { + return null; + } + return ( + + + {title} + + + {numberText && `(${numberText})`} + + + + + + + + {children} + + + ); +}; + +export const NumberState: React.FC = ({ title, value, quantity }) => { + return ( + + {title && {title}} + + + {value}{' '} + + + {quantity} + + + + ); +}; diff --git a/src/custom/ResourceDetailFormatters/context.tsx b/src/custom/ResourceDetailFormatters/context.tsx new file mode 100644 index 000000000..8a1b6ba7e --- /dev/null +++ b/src/custom/ResourceDetailFormatters/context.tsx @@ -0,0 +1,12 @@ +import React from 'react'; + +export const LevelContext = React.createContext(0); + +interface LevelProps { + children: React.ReactNode; +} + +export const Level: React.FC = ({ children }) => { + const level = React.useContext(LevelContext); + return {children}; +}; diff --git a/src/custom/ResourceDetailFormatters/index.ts b/src/custom/ResourceDetailFormatters/index.ts new file mode 100644 index 000000000..f03eb462c --- /dev/null +++ b/src/custom/ResourceDetailFormatters/index.ts @@ -0,0 +1,39 @@ +import { KeyValueInRow, NumberStateFormatter } from './Component'; +import { OperatorDataFormatter } from './Details'; +import { + CodeFormatter, + CollapsibleSectionFormatter, + ContainerFormatter, + LabelFormatter, + ListFormatter, + MemoryUsage, + NumberState, + OperatorDynamicFormatter, + SecretFormatter, + StatusFormatter, + TableDataFormatter, + TextWithLinkFormatter +} from './Formatter'; +import { useResourceCleanData } from './useResourceCleanData'; +import { extractPodVolumnTables, splitCamelCaseString } from './utils'; + +export { + CodeFormatter, + CollapsibleSectionFormatter, + ContainerFormatter, + KeyValueInRow, + LabelFormatter, + ListFormatter, + MemoryUsage, + NumberState, + NumberStateFormatter, + OperatorDataFormatter, + OperatorDynamicFormatter, + SecretFormatter, + StatusFormatter, + TableDataFormatter, + TextWithLinkFormatter, + extractPodVolumnTables, + splitCamelCaseString, + useResourceCleanData +}; diff --git a/src/custom/ResourceDetailFormatters/styles.ts b/src/custom/ResourceDetailFormatters/styles.ts new file mode 100644 index 000000000..523eafb48 --- /dev/null +++ b/src/custom/ResourceDetailFormatters/styles.ts @@ -0,0 +1,303 @@ +import { alpha, Theme } from '@mui/material'; +import { Box, Chip, Grid, IconButton, Typography } from '../../base'; +import { charcoal, KEPPEL, styled } from '../../theme'; + +interface StyledProps { + noPadding?: boolean; + openSection?: boolean; + display?: string; + theme?: Theme; +} + +export const FlexContainer = styled(Box)({ + display: 'flex', + flexWrap: 'wrap', + gap: 24 +}); + +export const FlexItem = styled(Box)({ + flex: '1 1 calc(50% - 12px)', + minWidth: '300px' +}); + +export const FullWidthItem = styled(Box)({ + flex: '1 1 100%' +}); + +export const StyledPaper = styled(Box)(({ theme }) => ({ + padding: theme.spacing(4), + backgroundColor: '#202020', + color: theme.palette.text.primary +})); + +export const StyledArrayUl = styled('ol')(() => ({ + listStyleType: 'none', + paddingInline: '0rem', + display: 'flex', + flexDirection: 'column', + margin: '0.5rem' +})); + +export const StyledPortsUl = styled('ul')({ + listStyleType: 'none', + marginBlock: '0.25rem', + paddingInline: '0.5rem', + display: 'flex', + flexDirection: 'column' +}); + +export const Title = styled('span')({ + fontSize: '0.5rem', + fontWeight: 'bold', + fontFamily: 'Qanelas Soft, sans-serif' +}); + +export const ElementData = styled('span')({ + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + paddingLeft: '0' +}); + +export const Wrap = styled('div')(() => ({ + width: '100%', + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis' +})); + +export const VariableSubfield = styled('div')(({ theme }) => ({ + color: 'rgb(134, 183, 235)', + letterSpacing: '1px', + fontSize: '.85rem', + fontFamily: theme.typography.fontFamily +})); + +export const LongWrap = styled('div')(({ display }) => ({ + width: '100%', + textOverflow: 'ellipsis', + wordWrap: 'break-word', + overflowWrap: 'break-word', + wordBreak: 'break-all', + display: display || 'block', + gap: display === 'flex' ? '0.5rem' : '0' +})); + +export const KeyValField = styled('span')(({ theme }) => ({ + color: theme.palette.mode === 'dark' ? charcoal[60] : charcoal[20], + fontWeight: 'bold' +})); + +export const State = styled('span')(() => ({ + verticalAlign: 'middle', + paddingRight: '8px', + display: 'flex' +})); + +export const Heading = styled('div')({ + display: 'flex', + alignItems: 'center', + paddingInline: '1rem', + margin: '1.5rem auto' +}); + +export const ElementDataWrap = styled('span')({ + paddingLeft: '0', + width: '100%', + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis' +}); + +export const Details = styled('div', { + shouldForwardProp: (prop): prop is keyof StyledProps => prop !== 'noPadding' +})(({ noPadding }) => ({ + fontSize: '1rem', + paddingLeft: noPadding ? '' : '1rem', + width: 'fit-content' +})); + +export const StyledTitle = styled(Typography)({ + cursor: 'pointer', + padding: '0.25rem', + width: '100%', + paddingLeft: '0' +}); + +export const CollapsibleSectionContainer = styled(Box)({ + borderRadius: '0.25rem', + marginBottom: '0.5rem', + overflow: 'hidden' +}); + +export const CollapsibleSectionTitle = styled('div', { + shouldForwardProp: (prop): prop is keyof StyledProps => prop !== 'openSection' +})(({ theme, openSection }) => ({ + display: 'flex', + cursor: 'pointer', + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + padding: '0rem', + fontWeight: 'regular', + borderBottom: openSection ? `1px solid ${KEPPEL}` : `1px solid ${theme?.palette.divider}`, + backgroundColor: openSection ? alpha(KEPPEL, 0.1) : 'transparent', + marginBlock: '0.25rem' +})); + +export const CollapsibleSectionContent = styled(Box)({ + display: 'flex', + flexDirection: 'column', + gap: '0.5rem', + padding: '0.2rem', + backgroundColor: 'transparent' +}); + +export const StyledEnvironmentVariablesCode = styled('code')(({ theme }) => ({ + backgroundColor: theme.palette.mode === 'light' ? '#e9eff1' : '#253137', + color: theme.palette.text.primary, + width: '100%', + display: 'flex', + flexDirection: 'column', + gap: '0.5rem' +})); + +export const StyledEnvironmentVariablesPre = styled('pre')(({ theme }) => ({ + backgroundColor: theme.palette.mode === 'light' ? '#e9eff1' : '#253137', + color: theme.palette.text.primary, + padding: '0.5rem', + margin: '0', + width: '100%' +})); + +export const EnvironmentVariablesContainer = styled('div')({ + display: 'flex', + flexDirection: 'column', + gap: '1rem' +}); + +export const EnvironmentVariableValue = styled('span')({ + maxWidth: '50px', + whiteSpace: 'pre-wrap' +}); + +export const CodeFormatterPre = styled('pre')(({ theme }) => ({ + backgroundColor: theme.palette.mode === 'light' ? '#e9eff1' : '#212121', + color: theme.palette.text.primary, + width: '100%', + wordWrap: 'break-word', + overflowWrap: 'break-word', + wordBreak: 'break-all', + margin: 0, + padding: '0.5rem' +})); + +export const CodeFormatterCode = styled('code')(({ theme }) => ({ + backgroundColor: theme.palette.mode === 'light' ? '#e9eff1' : '#212121', + color: theme.palette.text.primary, + fontFamily: theme.typography.fontFamily +})); + +export const NumberStateContainer = styled('div')({ + textAlign: 'center' +}); + +export const NumberStateTitle = styled(Typography)({ + paddingRight: '1vh', + marginTop: '0.35rem', + marginBottom: '0' +}); + +export const NumberStateValueContainer = styled('div')({ + display: 'inline-flex', + alignItems: 'center', + paddingRight: '1vh' +}); + +export const NumberStateValue = styled(Typography)(({ theme }) => ({ + marginRight: '0.25rem', + marginBottom: '0', + whiteSpace: 'nowrap', + color: theme.palette.mode === 'dark' ? charcoal[60] : charcoal[20] +})); + +export const NumberStateQuantity = styled(Typography)({ + fontSize: '1rem', + marginTop: '1.5rem' +}); + +export const StyledNumberBox = styled(Box)({ + display: 'flex', + flexDirection: 'row', + flexWrap: 'wrap', + gap: '1.5rem' +}); + +export const StyledChip = styled(Chip)({ + borderRadius: '0.25rem', + minHeight: '1.5rem', + height: 'auto', + '& .MuiChip-label': { + display: 'block', + whiteSpace: 'normal' + } +}); + +export const ResourceProgressContainer = styled(Box)({ + marginTop: 1, + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + flexWrap: 'wrap' +}); + +export const FlexResourceContainer = styled(Box)({ + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-between', + width: '100%', + flexWrap: 'wrap' +}); + +export const SecretContainer = styled(Box)({ + display: 'flex', + flexDirection: 'column', + gap: 1 +}); + +export const SecretItemContainer = styled(Box)({ + display: 'flex', + gap: 4, + alignItems: 'center' +}); + +export const SecretValueContainer = styled(Box)({ + display: 'flex', + alignItems: 'center', + gap: 1 +}); + +export const SecretIconButton = styled(IconButton)({ + padding: '4px' +}); + +export const OperatorDataContainer = styled('div')({ + border: '1px solid gray', + padding: '1rem', + borderRadius: '0.5rem', + marginTop: '1rem' +}); + +export const KeyValueGrid = styled(Grid)(({ theme }) => ({ + borderBottom: `1px solid ${theme.palette.divider}`, + paddingBlock: '0.5rem' +})); + +export const KeyValueGridTitle = styled(Typography)({ + fontWeight: 'bold', + textTransform: 'capitalize' +}); + +export const KeyValueGridCell = styled(Grid)({ + placeSelf: 'center' +}); diff --git a/src/custom/ResourceDetailFormatters/types.ts b/src/custom/ResourceDetailFormatters/types.ts new file mode 100644 index 000000000..9305cbaaf --- /dev/null +++ b/src/custom/ResourceDetailFormatters/types.ts @@ -0,0 +1,267 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { ComponentType, ReactNode } from 'react'; + +export interface PrimaryDetailsProps { + title: string; + value: string; + hide?: boolean; +} + +export interface CopyToClipboardProps { + data: string; +} + +export interface CollapsibleSectionProps { + title: string; + children: ReactNode; + showAll?: boolean; + numberText?: string | number; + level?: number; +} + +export interface SectionHeadingProps { + children: string; +} + +export interface LongDetailsProps { + title: string; + value: string; +} + +export interface NumberStateProps { + title?: string; + value: string | number; + quantity: string; +} + +export interface EnvironmentVariablesProps { + title: string; + value?: string; + hide?: boolean; +} + +export interface CategoryProps { + title: string; + hide?: boolean; +} + +export interface NumberStateData { + title: string; + value: string | number; + quantity: string; +} + +export interface NumberStateFormatterProps { + data: NumberStateData[]; +} + +export interface ActionIconButtonProps { + title: string; + Icon: ComponentType<{ fill: string; height?: number; width?: number }>; + onClick: () => void; +} + +export interface KeyValueProps { + Key: string; + Value: string | number | ReactNode; +} + +export interface EnvironmentFormatterProps { + data?: { + name: string; + value?: string; + valueFrom?: { + [key: string]: { + apiVersion: string; + fieldPath: string; + }; + }; + }[]; +} + +export interface CodeFormatterProps { + data: any; +} + +export interface PortsFormatterProps { + data?: { + name?: string; + containerPort?: number; + port?: number; + protocol: string; + }[]; +} + +export interface ArrayFormatterProps { + data: any[]; +} + +export interface ListFormatterProps { + data: string[]; +} + +export interface OperatorDynamicFormatterProps { + data: any; + level?: number; +} + +export interface StatusFormatterProps { + status: string; + rightPosition?: string; +} + +export interface LabelFormatterProps { + data: string[]; + onClick: (labels: string[]) => void; + selectedLabels: string[]; +} + +export interface MemoryUsageProps { + allocatable?: { + cpu: string; + memory: string; + 'ephemeral-storage': string; + }; + capacity?: { + cpu: string; + memory: string; + 'ephemeral-storage': string; + }; + height?: number; + width?: number; +} + +export interface TableDataFormatterProps { + title?: string; + data?: any; + showAll?: boolean; + mainTableData?: any[][]; + mainTableCols?: any[]; +} + +export interface TextWithLinkFormatterProps { + title: string; + value: string; + variant: 'row' | 'column'; + onClick: () => void; +} + +export interface JSONViewFormatterProps { + data: any; +} + +export interface DetailSectionProps { + title?: string; + data: any; + formatter: React.ComponentType; +} + +export interface ContainerFormatterProps { + containerSpec: { + ports?: any[]; + imagePullPolicy?: string; + image?: string; + env?: any[]; + volumeMounts?: { + name: string; + mountPath: string; + readOnly?: boolean; + }[]; + command?: any[]; + livenessProbe?: any; + readinessProbe?: any; + startupProbe?: any; + args?: any[]; + resources?: { + requests?: any; + limits?: any; + }; + }; + containerStatus: { + state: { + [key: string]: { + startedAt?: string; + }; + }; + restartCount: number; + containerID: string; + }; +} + +export interface SecretFormatterProps { + data: string; +} + +export interface NumberState { + title: string; + value: number | string; + quantity: string; +} + +export interface Resource { + status?: { + attribute?: string; + containerStatuses?: Array<{ restartCount?: number }>; + nodeInfo?: { kubeletVersion?: string }; + podIP?: string; + hostIP?: string; + qosClass?: string; + replicas?: number; + availableReplicas?: number; + readyReplicas?: number; + loadBalancer?: { ingress?: Array<{ ip?: string }> }; + allocatable?: Record; + capacity?: Record; + conditions?: Array<{ type?: string }>; + }; + spec?: { + attribute?: string; + containers?: Array<{ image?: string }>; + initContainers?: Array<{ name?: string }>; + nodeSelector?: Record; + template?: { + spec?: { + containers?: Array<{ image?: string }>; + nodeSelector?: Record; + }; + }; + resources?: { requests?: { storage?: string } }; + claimRef?: { name?: string; namespace?: string }; + storageClassName?: string; + type?: string; + clusterIP?: string; + updateStrategy?: { type?: string }; + externalIPs?: string[]; + finalizers?: string[]; + accessModes?: string[]; + selector?: { matchLabels?: Record }; + serviceAccountName?: string; + tolerations?: unknown; + volumes?: unknown; + rules?: Array<{ host?: string }>; + }; + metadata?: { + creationTimestamp?: string; + namespace?: string; + labels?: Array<{ key?: string; value?: string }>; + annotations?: Array<{ key?: string; value?: string }>; + }; + kind?: string; + component?: { + kind?: string; + }; + apiVersion?: string; + configuration?: { + spec?: { strategy?: { type?: string } }; + data?: unknown; + }; + type?: string; + data?: string; +} + +export interface GetResourceCleanDataProps { + resource: Resource; + dispatchMsgToEditor?: (msg: any) => void; + activeLabels?: string[]; + showStatus?: boolean; +} diff --git a/src/custom/ResourceDetailFormatters/useResourceCleanData.ts b/src/custom/ResourceDetailFormatters/useResourceCleanData.ts new file mode 100644 index 000000000..4d4a0f1e3 --- /dev/null +++ b/src/custom/ResourceDetailFormatters/useResourceCleanData.ts @@ -0,0 +1,176 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import _ from 'lodash'; +import moment from 'moment'; +import { GetResourceCleanDataProps, NumberState } from './types'; + +export const useResourceCleanData = () => { + const structureNumberStates = (parsedStatus: any, parsedSpec: any): NumberState[] => { + const numberStates: NumberState[] = []; + + if (parsedSpec?.priority !== undefined) { + numberStates.push({ + title: 'Priority', + value: parsedSpec.priority, + quantity: '' + }); + } + + if (parsedSpec?.containers) { + numberStates.push({ + title: 'Containers', + value: parsedSpec.containers.length, + quantity: 'total' + }); + } + + if (parsedStatus?.containerStatuses) { + const totalRestarts = parsedStatus.containerStatuses.reduce( + (sum: number, container: { restartCount?: number }) => sum + (container.restartCount || 0), + 0 + ); + numberStates.push({ + title: 'Total Restarts', + value: totalRestarts, + quantity: 'times' + }); + } + + return numberStates; + }; + + const getAge = (creationTimestamp?: string): string | undefined => { + if (!creationTimestamp) return undefined; + const creationTime = moment(creationTimestamp); + const currentTime = moment(); + const ageInHours = currentTime.diff(creationTime, 'hours'); + return ageInHours >= 24 ? `${Math.floor(ageInHours / 24)} days` : `${ageInHours} hours`; + }; + + const getStatus = (attribute: any): string | false => { + if (attribute?.phase) { + return attribute.phase; + } + const readyCondition = attribute?.conditions?.find( + (cond: { type: string }) => cond.type === 'Ready' + ); + return readyCondition ? 'Ready' : false; + }; + + const joinwithEqual = (object: Record | undefined): string[] => { + if (!object) return []; + return Object.entries(object).map(([key, value]) => { + return `${key}=${value}`; + }); + }; + + const getResourceCleanData = ({ + resource, + activeLabels, + dispatchMsgToEditor, + showStatus = true + }: GetResourceCleanDataProps) => { + const parsedStatus = resource?.status?.attribute && JSON.parse(resource?.status?.attribute); + const parsedSpec = resource?.spec?.attribute && JSON.parse(resource?.spec.attribute); + const numberStates = structureNumberStates(parsedStatus, parsedSpec); + const kind = resource?.kind ?? resource?.component?.kind; + const cleanData = { + age: getAge(resource?.metadata?.creationTimestamp), + kind: kind, + status: showStatus && getStatus(parsedStatus), + kubeletVersion: parsedStatus?.nodeInfo?.kubeletVersion, + podIP: parsedStatus?.podIP, + hostIP: parsedStatus?.hostIP, + QoSClass: parsedStatus?.qosClass, + size: parsedSpec?.resources?.requests?.storage, + claim: parsedSpec?.claimRef?.name, + claimNamespace: parsedSpec?.claimRef?.namespace, + apiVersion: resource?.apiVersion, + pods: + parsedStatus?.replicas === undefined + ? parsedStatus?.availableReplicas?.toString() + : `${ + parsedStatus?.availableReplicas?.toString() ?? '0' + } / ${parsedStatus?.replicas?.toString()}`, + replicas: + parsedStatus?.readyReplicas !== undefined && + parsedStatus?.replicas !== undefined && + `${parsedStatus?.readyReplicas} / ${parsedStatus?.replicas}`, + strategyType: resource?.configuration?.spec?.strategy?.type, + storageClass: parsedSpec?.storageClassName, + secretType: resource?.type, + serviceType: parsedSpec?.type, + clusterIp: parsedSpec?.clusterIP, + updateStrategy: parsedSpec?.updateStrategy?.type, + externalIp: parsedSpec?.externalIPs, + finalizers: parsedSpec?.finalizers, + accessModes: parsedSpec?.accessModes, + deeplinks: { + links: [ + { nodeName: parsedSpec?.nodeName, label: 'Node' }, + { namespace: resource?.metadata?.namespace, label: 'Namespace' }, + { serviceAccount: parsedSpec?.serviceAccountName, label: 'ServiceAccount' } + ], + dispatchMsgToEditor: dispatchMsgToEditor + }, + selector: parsedSpec?.selector?.matchLabels + ? joinwithEqual(parsedSpec?.selector?.matchLabels) + : joinwithEqual(parsedSpec?.selector), + images: parsedSpec?.template?.spec?.containers?.map((container: { image?: string }) => { + return container?.image; + }), + numberStates: numberStates, + nodeSelector: + joinwithEqual(parsedSpec?.nodeSelector) || + joinwithEqual(parsedSpec?.template?.spec?.nodeSelector), + loadBalancer: parsedStatus?.loadBalancer?.ingress?.map((ingress: { ip?: string }) => { + return ingress?.ip; + }), + rules: parsedSpec?.rules?.map((rule: { host?: string }) => { + return rule?.host; + }), + usage: { + allocatable: parsedStatus?.allocatable, + capacity: parsedStatus?.capacity + }, + configData: resource?.configuration?.data, + capacity: parsedSpec?.capacity?.storage, + totalCapacity: parsedStatus?.capacity, + totalAllocatable: parsedStatus?.allocatable, + conditions: { + ...parsedStatus?.conditions?.map((condition: { type?: string }) => { + return condition?.type; + }) + }, + tolerations: parsedSpec?.tolerations, + podVolumes: parsedSpec?.volumes, + ingressRules: parsedSpec?.rules, + connections: kind === 'Service' && _.omit(parsedSpec, ['selector', 'type']), + labels: { + data: resource?.metadata?.labels?.map((label) => { + const value = label?.value !== undefined ? label?.value : ''; + return `${label?.key}=${value}`; + }), + dispatchMsgToEditor: dispatchMsgToEditor, + activeViewFilters: activeLabels + }, + annotations: resource?.metadata?.annotations?.map((annotation) => { + const value = annotation?.value !== undefined ? annotation?.value : ''; + return `${annotation?.key}=${value}`; + }), + // secret: resource?.data, //TODO: show it when we have the role based access control for secrets + initContainers: parsedSpec?.initContainers && + parsedStatus?.initContainerStatuses && { + spec: parsedSpec?.initContainers, + status: parsedStatus?.initContainerStatuses + }, + containers: parsedSpec?.containers && + parsedStatus?.containerStatuses && { + spec: parsedSpec?.containers, + status: parsedStatus?.containerStatuses + } + }; + return cleanData; + }; + + return { getResourceCleanData, structureNumberStates, getAge, getStatus, joinwithEqual }; +}; diff --git a/src/custom/ResourceDetailFormatters/utils.ts b/src/custom/ResourceDetailFormatters/utils.ts new file mode 100644 index 000000000..932e84a71 --- /dev/null +++ b/src/custom/ResourceDetailFormatters/utils.ts @@ -0,0 +1,91 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import _ from 'lodash'; + +interface TableData { + name: string; + [key: string]: string | number | undefined; +} + +interface TableColumn { + name: string; + label: string; + options: { + sort: boolean; + }; +} + +interface TableStructure { + key: string; + columns: TableColumn[]; + rows: TableData[]; +} + +export const splitCamelCaseString = (str: string): string => { + const pluralPatternRegex = /(?<=\w)[A-Z]+s$/; + const pluralMatches = str.match(pluralPatternRegex); + + if (!pluralMatches) { + return _.startCase(str); + } + const reconstructedInput = str.replace(pluralPatternRegex, ''); + + return _.startCase(reconstructedInput) + ' ' + pluralMatches[0]; +}; + +export const extractPodVolumnTables = (data: TableData[] | null): TableStructure[] => { + if (!data) { + return []; + } + const uniqueKeys = _.uniq( + _.flatMap(data, (item) => Object.keys(item).filter((key) => key !== 'name')) + ); + + return uniqueKeys.map((key) => { + const rows = data + .filter((item) => _.has(item, key)) + .map((item) => { + const baseData: TableData = { name: item.name }; + const nestedData = _.get(item, key); + + if (_.isObject(nestedData)) { + Object.entries(nestedData).forEach(([nestedKey, value]) => { + baseData[nestedKey] = + nestedKey === 'defaultMode' + ? value?.toString() + : nestedKey === 'sources' && _.isArray(value) + ? value?.length + : JSON.stringify(value); + }); + } else { + baseData[key] = JSON.stringify(nestedData); + } + + return baseData; + }); + + const columns: TableColumn[] = rows.length + ? Object.keys(rows[0]).map((columnKey) => ({ + name: columnKey, + label: _.startCase(columnKey), + options: { + sort: false + } + })) + : []; + + return { key, columns, rows }; + }); +}; + +export function isEmptyAtAllDepths(input: any): boolean { + if (_.isArray(input)) { + // If the input is an array, check if all items are empty at all depths + return input.every(isEmptyAtAllDepths); + } else if (_.isObject(input)) { + // If the input is an object, check if all properties are empty at all depths + return _.every(input, isEmptyAtAllDepths); + } else { + // If the input is not an array or object, check if it's empty + return _.isEmpty(input); + } +} diff --git a/src/custom/SearchBar.tsx b/src/custom/SearchBar.tsx index 91ffd9458..ed3349630 100644 --- a/src/custom/SearchBar.tsx +++ b/src/custom/SearchBar.tsx @@ -135,7 +135,7 @@ function SearchBar({ } }} > - <> +
)} - +
); } diff --git a/src/custom/ShareModal/ShareModal.tsx b/src/custom/ShareModal/ShareModal.tsx index 6150249cd..cd1c7c518 100644 --- a/src/custom/ShareModal/ShareModal.tsx +++ b/src/custom/ShareModal/ShareModal.tsx @@ -257,12 +257,14 @@ const ShareModal: React.FC = ({ ) : ( )} @@ -289,7 +291,13 @@ const ShareModal: React.FC = ({ ))} - + {selectedOption === SHARE_MODE.PRIVATE ? options.PRIVATE : options.PUBLIC}
diff --git a/src/custom/Workspaces/DesignTable.tsx b/src/custom/Workspaces/DesignTable.tsx index 594f3e0d8..e099cc572 100644 --- a/src/custom/Workspaces/DesignTable.tsx +++ b/src/custom/Workspaces/DesignTable.tsx @@ -39,6 +39,7 @@ export interface DesignTableProps { workspaceName: string, workspaceId: string ) => void; + getDownloadUrl: (id: string) => string; handlePublish: (publishModal: PublishModalState, data: any) => void; publishModalHandler: any; handleUnpublishModal: (design: Pattern, modalRef: React.RefObject) => void; @@ -83,6 +84,7 @@ const DesignTable: React.FC = ({ handleShowDetails, handleUnpublishModal, handleWorkspaceDesignDeleteModal, + getDownloadUrl, publishModalHandler, isCopyLinkAllowed, isDeleteAllowed, @@ -122,6 +124,7 @@ const DesignTable: React.FC = ({ handleCopyUrl, handleClone, handleShowDetails, + getDownloadUrl, isCopyLinkAllowed, isDeleteAllowed, isDownloadAllowed, diff --git a/src/custom/index.tsx b/src/custom/index.tsx index 0da7c1984..10ede3d60 100644 --- a/src/custom/index.tsx +++ b/src/custom/index.tsx @@ -1,4 +1,5 @@ import { ActionButton } from './ActionButton'; +import { BBChart } from './BBChart'; import { BookmarkNotification } from './BookmarkNotification'; import CatalogFilter, { CatalogFilterProps } from './CatalogFilter/CatalogFilter'; import { ChapterCard } from './ChapterCard'; @@ -68,6 +69,7 @@ export { UserSearchField } from './UserSearchField'; export { ActionButton, + BBChart, BookmarkNotification, CatalogCardDesignLogo, CatalogFilter, @@ -149,6 +151,7 @@ export type { export * from './CatalogDesignTable'; export * from './CatalogDetail'; export * from './Dialog'; +export * from './ResourceDetailFormatters'; export * from './ShareModal'; export * from './UserSearchField'; export * from './Workspaces'; diff --git a/src/theme/colors/colors.ts b/src/theme/colors/colors.ts index 8823dc584..45c94f786 100644 --- a/src/theme/colors/colors.ts +++ b/src/theme/colors/colors.ts @@ -89,6 +89,7 @@ export const saffron = { * Grayscale Colors */ export const charcoal = { + 110: '#525252', 100: '#FDFDFD', 90: '#EAEDEE', 80: '#D2D8DA', diff --git a/src/theme/palette.ts b/src/theme/palette.ts index cba6b52b4..d1165091c 100644 --- a/src/theme/palette.ts +++ b/src/theme/palette.ts @@ -18,6 +18,7 @@ declare module '@mui/material/styles' { graphics?: { default: string; }; + tabs?: string; tertiary?: string; hover?: string; blur?: { @@ -194,6 +195,7 @@ export const lightModePalette: PaletteOptions = { default: Colors.charcoal[100], secondary: Colors.accentGrey[90], tertiary: Colors.accentGrey[80], + tabs: Colors.charcoal[70], hover: Colors.charcoal[90], supplementary: Colors.accentGrey[40], blur: { @@ -309,6 +311,7 @@ export const darkModePalette: PaletteOptions = { default: Colors.charcoal[10], secondary: Colors.accentGrey[20], tertiary: Colors.accentGrey[30], + tabs: Colors.charcoal[110], hover: Colors.charcoal[20], supplementary: Colors.accentGrey[40], blur: { diff --git a/src/theme/typography.ts b/src/theme/typography.ts index aab617e5f..37a2ee0d4 100644 --- a/src/theme/typography.ts +++ b/src/theme/typography.ts @@ -97,7 +97,7 @@ export const typography = (mode: PaletteMode): TypographyOptions => { } }, textB2SemiBold: { - fontFamily: ['"Open Sans"', 'sans-serif'].join(','), + fontFamily: ['Qanelas Soft Regular'].join(','), color: mode === 'light' ? common.black : common.white, fontSize: '1rem', fontWeight: 600,