From c1ae4251b16b96d759f623504496713cf8776318 Mon Sep 17 00:00:00 2001 From: Amit Amrutiya Date: Wed, 8 Jan 2025 19:37:37 +0530 Subject: [PATCH 01/10] feat: add library of billboard.js and moment Signed-off-by: Amit Amrutiya --- package-lock.json | 552 +++++++++++++++++++++++++++++++++++++++++++++- package.json | 2 + 2 files changed, 551 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0034a390..89a4c7f9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,8 +8,10 @@ "name": "@layer5/sistent", "version": "0.14.11", "dependencies": { + "billboard.js": "^3.14.3", "js-yaml": "^4.1.0", "lodash": "^4.17.21", + "moment": "^2.30.1", "react-share": "^5.1.0" }, "devDependencies": { @@ -3040,6 +3042,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", @@ -4080,6 +4097,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", @@ -5019,6 +5059,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", @@ -7287,6 +7594,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", @@ -10716,6 +11032,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", @@ -12264,6 +12589,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", @@ -12320,7 +12651,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": { @@ -16333,6 +16663,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", @@ -17098,6 +17441,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", @@ -17727,6 +18092,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", @@ -19308,6 +19840,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", @@ -21627,6 +22164,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", @@ -22697,6 +23239,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", @@ -22737,8 +23284,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 04cee394..a094977f 100644 --- a/package.json +++ b/package.json @@ -116,8 +116,10 @@ "access": "public" }, "dependencies": { + "billboard.js": "^3.14.3", "js-yaml": "^4.1.0", "lodash": "^4.17.21", + "moment": "^2.30.1", "react-share": "^5.1.0" } } From 4fb013684c94253a392fd350d90de3919e030f28 Mon Sep 17 00:00:00 2001 From: Amit Amrutiya Date: Wed, 8 Jan 2025 19:37:54 +0530 Subject: [PATCH 02/10] feat(chart): add BBChart component for billboard.js integration Signed-off-by: Amit Amrutiya --- src/custom/BBChart/BBChart.tsx | 27 +++++++++++++++++++++++++++ src/custom/BBChart/index.ts | 3 +++ src/custom/index.tsx | 3 +++ 3 files changed, 33 insertions(+) create mode 100644 src/custom/BBChart/BBChart.tsx create mode 100644 src/custom/BBChart/index.ts diff --git a/src/custom/BBChart/BBChart.tsx b/src/custom/BBChart/BBChart.tsx new file mode 100644 index 00000000..503c6554 --- /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 00000000..2980714e --- /dev/null +++ b/src/custom/BBChart/index.ts @@ -0,0 +1,3 @@ +import BBChart from './BBChart'; + +export { BBChart }; diff --git a/src/custom/index.tsx b/src/custom/index.tsx index ac4022ff..2a4e5e93 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'; @@ -67,6 +68,7 @@ export { UserSearchField } from './UserSearchField'; export { ActionButton, + BBChart, BookmarkNotification, CatalogCardDesignLogo, CatalogFilter, @@ -148,6 +150,7 @@ export type { export * from './CatalogDesignTable'; export * from './CatalogDetail'; export * from './Dialog'; +export * from './ResourceDetailFormatters'; export * from './ShareModal'; export * from './UserSearchField'; export * from './Workspaces'; From 709124c1f7d91bc14cd4b5d92fc64a66289431ff Mon Sep 17 00:00:00 2001 From: Amit Amrutiya Date: Wed, 8 Jan 2025 19:38:20 +0530 Subject: [PATCH 03/10] feat(ResourceDetailFormatters): add index and styles files for resource detail formatting Signed-off-by: Amit Amrutiya --- src/custom/ResourceDetailFormatters/index.ts | 39 +++ src/custom/ResourceDetailFormatters/styles.ts | 303 ++++++++++++++++++ 2 files changed, 342 insertions(+) create mode 100644 src/custom/ResourceDetailFormatters/index.ts create mode 100644 src/custom/ResourceDetailFormatters/styles.ts diff --git a/src/custom/ResourceDetailFormatters/index.ts b/src/custom/ResourceDetailFormatters/index.ts new file mode 100644 index 00000000..f03eb462 --- /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 00000000..2eb8722f --- /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, TRANSPARENT_WHITE } 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' ? TRANSPARENT_WHITE : '#253137', + color: 'white', + width: '100%', + display: 'flex', + flexDirection: 'column', + gap: '0.5rem' +})); + +export const StyledEnvironmentVariablesPre = styled('pre')(({ theme }) => ({ + backgroundColor: theme.palette.mode === 'light' ? TRANSPARENT_WHITE : '#253137', + color: 'white', + 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' ? TRANSPARENT_WHITE : '#212121', + color: 'white', + 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' ? TRANSPARENT_WHITE : '#212121', + color: 'white', + 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.3rem' +})); + +export const KeyValueGridTitle = styled(Typography)({ + fontWeight: 'bold', + textTransform: 'capitalize' +}); + +export const KeyValueGridCell = styled(Grid)({ + placeSelf: 'center' +}); From 081d6958bcda0c8134aa70ab45e137aae73df6e9 Mon Sep 17 00:00:00 2001 From: Amit Amrutiya Date: Wed, 8 Jan 2025 19:38:38 +0530 Subject: [PATCH 04/10] feat(ResourceDetailFormatters): implement useResourceCleanData hook for resource data processing Signed-off-by: Amit Amrutiya --- .../useResourceCleanData.ts | 176 ++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 src/custom/ResourceDetailFormatters/useResourceCleanData.ts diff --git a/src/custom/ResourceDetailFormatters/useResourceCleanData.ts b/src/custom/ResourceDetailFormatters/useResourceCleanData.ts new file mode 100644 index 00000000..0499b330 --- /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 => { + 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 + }: 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; + 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, + 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 }; +}; From 755d8293b553b4aaa20ade4c0d25887711ac3f83 Mon Sep 17 00:00:00 2001 From: Amit Amrutiya Date: Tue, 7 Jan 2025 19:39:17 +0530 Subject: [PATCH 05/10] feat: add TypeScript interfaces and utility functions for resource detail formatting Signed-off-by: Amit Amrutiya --- src/custom/ResourceDetailFormatters/types.ts | 264 +++++++++++++++++++ src/custom/ResourceDetailFormatters/utils.ts | 91 +++++++ 2 files changed, 355 insertions(+) create mode 100644 src/custom/ResourceDetailFormatters/types.ts create mode 100644 src/custom/ResourceDetailFormatters/utils.ts diff --git a/src/custom/ResourceDetailFormatters/types.ts b/src/custom/ResourceDetailFormatters/types.ts new file mode 100644 index 00000000..f4e28683 --- /dev/null +++ b/src/custom/ResourceDetailFormatters/types.ts @@ -0,0 +1,264 @@ +/* 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; + 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/utils.ts b/src/custom/ResourceDetailFormatters/utils.ts new file mode 100644 index 00000000..932e84a7 --- /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); + } +} From 7e71d90f141b97cd81ba9e2ab5ae80f72d5b63cf Mon Sep 17 00:00:00 2001 From: Amit Amrutiya Date: Wed, 8 Jan 2025 19:40:22 +0530 Subject: [PATCH 06/10] feat: add new formatters Signed-off-by: Amit Amrutiya --- .../ResourceDetailFormatters/Formatter.tsx | 799 ++++++++++++++++++ 1 file changed, 799 insertions(+) create mode 100644 src/custom/ResourceDetailFormatters/Formatter.tsx diff --git a/src/custom/ResourceDetailFormatters/Formatter.tsx b/src/custom/ResourceDetailFormatters/Formatter.tsx new file mode 100644 index 00000000..360d3f9f --- /dev/null +++ b/src/custom/ResourceDetailFormatters/Formatter.tsx @@ -0,0 +1,799 @@ +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} + /> + + ); + })} + + ); +}; + +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} + + + + ); +}; From 664766556f27c60ea939c653e94e81d4ad9e2f4b Mon Sep 17 00:00:00 2001 From: Amit Amrutiya Date: Wed, 8 Jan 2025 19:40:49 +0530 Subject: [PATCH 07/10] feat: add PrimaryDetails, CopyToClipboard, and other components for resource detail display Signed-off-by: Amit Amrutiya --- .../ResourceDetailFormatters/Component.tsx | 168 ++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 src/custom/ResourceDetailFormatters/Component.tsx diff --git a/src/custom/ResourceDetailFormatters/Component.tsx b/src/custom/ResourceDetailFormatters/Component.tsx new file mode 100644 index 00000000..5e2e65f5 --- /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)}
+
+
+
+ ); +}; From f5daa768c0f854e18903a05f457b333655ac6442 Mon Sep 17 00:00:00 2001 From: Amit Amrutiya Date: Wed, 8 Jan 2025 19:41:22 +0530 Subject: [PATCH 08/10] feat: add abstract detail component Signed-off-by: Amit Amrutiya --- .../ResourceDetailFormatters/Details.tsx | 25 +++++++++++++++++++ .../ResourceDetailFormatters/ExpandArrow.tsx | 19 ++++++++++++++ .../ResourceDetailFormatters/context.tsx | 12 +++++++++ 3 files changed, 56 insertions(+) create mode 100644 src/custom/ResourceDetailFormatters/Details.tsx create mode 100644 src/custom/ResourceDetailFormatters/ExpandArrow.tsx create mode 100644 src/custom/ResourceDetailFormatters/context.tsx diff --git a/src/custom/ResourceDetailFormatters/Details.tsx b/src/custom/ResourceDetailFormatters/Details.tsx new file mode 100644 index 00000000..ab83ef83 --- /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 00000000..6512f125 --- /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/context.tsx b/src/custom/ResourceDetailFormatters/context.tsx new file mode 100644 index 00000000..8a1b6ba7 --- /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}; +}; From 192f004eb94163a01f56c10e02b00b2bceb265f9 Mon Sep 17 00:00:00 2001 From: Amit Amrutiya Date: Wed, 8 Jan 2025 21:01:52 +0530 Subject: [PATCH 09/10] fix: styling color Signed-off-by: Amit Amrutiya --- .../ResourceDetailFormatters/Formatter.tsx | 5 ++++- src/custom/ResourceDetailFormatters/styles.ts | 20 +++++++++---------- .../useResourceCleanData.ts | 2 +- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/custom/ResourceDetailFormatters/Formatter.tsx b/src/custom/ResourceDetailFormatters/Formatter.tsx index 360d3f9f..11cde80b 100644 --- a/src/custom/ResourceDetailFormatters/Formatter.tsx +++ b/src/custom/ResourceDetailFormatters/Formatter.tsx @@ -324,6 +324,9 @@ export const LabelFormatter: React.FC = ({ size="small" onClickCapture={() => handleClick(item)} clickable={onClick !== undefined && true} + style={{ + backgroundColor: selectedLabels.includes(item) ? KEPPEL : undefined + }} /> ); @@ -580,7 +583,7 @@ export const ContainerFormatter: React.FC = ({ const stateValues = Object.values(containerStatus.state)[0]; const startedAt = stateValues ? stateValues?.startedAt : null; return ( - + } diff --git a/src/custom/ResourceDetailFormatters/styles.ts b/src/custom/ResourceDetailFormatters/styles.ts index 2eb8722f..523eafb4 100644 --- a/src/custom/ResourceDetailFormatters/styles.ts +++ b/src/custom/ResourceDetailFormatters/styles.ts @@ -1,6 +1,6 @@ import { alpha, Theme } from '@mui/material'; import { Box, Chip, Grid, IconButton, Typography } from '../../base'; -import { charcoal, KEPPEL, styled, TRANSPARENT_WHITE } from '../../theme'; +import { charcoal, KEPPEL, styled } from '../../theme'; interface StyledProps { noPadding?: boolean; @@ -154,8 +154,8 @@ export const CollapsibleSectionContent = styled(Box)({ }); export const StyledEnvironmentVariablesCode = styled('code')(({ theme }) => ({ - backgroundColor: theme.palette.mode === 'light' ? TRANSPARENT_WHITE : '#253137', - color: 'white', + backgroundColor: theme.palette.mode === 'light' ? '#e9eff1' : '#253137', + color: theme.palette.text.primary, width: '100%', display: 'flex', flexDirection: 'column', @@ -163,8 +163,8 @@ export const StyledEnvironmentVariablesCode = styled('code')(({ theme }) => ({ })); export const StyledEnvironmentVariablesPre = styled('pre')(({ theme }) => ({ - backgroundColor: theme.palette.mode === 'light' ? TRANSPARENT_WHITE : '#253137', - color: 'white', + backgroundColor: theme.palette.mode === 'light' ? '#e9eff1' : '#253137', + color: theme.palette.text.primary, padding: '0.5rem', margin: '0', width: '100%' @@ -182,8 +182,8 @@ export const EnvironmentVariableValue = styled('span')({ }); export const CodeFormatterPre = styled('pre')(({ theme }) => ({ - backgroundColor: theme.palette.mode === 'light' ? TRANSPARENT_WHITE : '#212121', - color: 'white', + backgroundColor: theme.palette.mode === 'light' ? '#e9eff1' : '#212121', + color: theme.palette.text.primary, width: '100%', wordWrap: 'break-word', overflowWrap: 'break-word', @@ -193,8 +193,8 @@ export const CodeFormatterPre = styled('pre')(({ theme }) => ({ })); export const CodeFormatterCode = styled('code')(({ theme }) => ({ - backgroundColor: theme.palette.mode === 'light' ? TRANSPARENT_WHITE : '#212121', - color: 'white', + backgroundColor: theme.palette.mode === 'light' ? '#e9eff1' : '#212121', + color: theme.palette.text.primary, fontFamily: theme.typography.fontFamily })); @@ -290,7 +290,7 @@ export const OperatorDataContainer = styled('div')({ export const KeyValueGrid = styled(Grid)(({ theme }) => ({ borderBottom: `1px solid ${theme.palette.divider}`, - paddingBlock: '0.3rem' + paddingBlock: '0.5rem' })); export const KeyValueGridTitle = styled(Typography)({ diff --git a/src/custom/ResourceDetailFormatters/useResourceCleanData.ts b/src/custom/ResourceDetailFormatters/useResourceCleanData.ts index 0499b330..410e904e 100644 --- a/src/custom/ResourceDetailFormatters/useResourceCleanData.ts +++ b/src/custom/ResourceDetailFormatters/useResourceCleanData.ts @@ -66,7 +66,7 @@ export const useResourceCleanData = () => { resource, activeLabels, dispatchMsgToEditor, - showStatus + showStatus = true }: GetResourceCleanDataProps) => { const parsedStatus = resource?.status?.attribute && JSON.parse(resource?.status?.attribute); const parsedSpec = resource?.spec?.attribute && JSON.parse(resource?.spec.attribute); From 693950c0004dd873198e0cd113af16a85501ce98 Mon Sep 17 00:00:00 2001 From: Amit Amrutiya Date: Wed, 8 Jan 2025 23:28:37 +0530 Subject: [PATCH 10/10] chore: remove secret Signed-off-by: Amit Amrutiya --- src/custom/ResourceDetailFormatters/types.ts | 3 +++ .../ResourceDetailFormatters/useResourceCleanData.ts | 10 +++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/custom/ResourceDetailFormatters/types.ts b/src/custom/ResourceDetailFormatters/types.ts index f4e28683..9305cbaa 100644 --- a/src/custom/ResourceDetailFormatters/types.ts +++ b/src/custom/ResourceDetailFormatters/types.ts @@ -247,6 +247,9 @@ export interface Resource { annotations?: Array<{ key?: string; value?: string }>; }; kind?: string; + component?: { + kind?: string; + }; apiVersion?: string; configuration?: { spec?: { strategy?: { type?: string } }; diff --git a/src/custom/ResourceDetailFormatters/useResourceCleanData.ts b/src/custom/ResourceDetailFormatters/useResourceCleanData.ts index 410e904e..4d4a0f1e 100644 --- a/src/custom/ResourceDetailFormatters/useResourceCleanData.ts +++ b/src/custom/ResourceDetailFormatters/useResourceCleanData.ts @@ -38,7 +38,8 @@ export const useResourceCleanData = () => { return numberStates; }; - const getAge = (creationTimestamp: string): string => { + const getAge = (creationTimestamp?: string): string | undefined => { + if (!creationTimestamp) return undefined; const creationTime = moment(creationTimestamp); const currentTime = moment(); const ageInHours = currentTime.diff(creationTime, 'hours'); @@ -71,10 +72,9 @@ export const useResourceCleanData = () => { 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; + const kind = resource?.kind ?? resource?.component?.kind; const cleanData = { - age: getAge(resource?.metadata?.creationTimestamp || ''), + age: getAge(resource?.metadata?.creationTimestamp), kind: kind, status: showStatus && getStatus(parsedStatus), kubeletVersion: parsedStatus?.nodeInfo?.kubeletVersion, @@ -157,7 +157,7 @@ export const useResourceCleanData = () => { const value = annotation?.value !== undefined ? annotation?.value : ''; return `${annotation?.key}=${value}`; }), - secret: resource?.data, + // secret: resource?.data, //TODO: show it when we have the role based access control for secrets initContainers: parsedSpec?.initContainers && parsedStatus?.initContainerStatuses && { spec: parsedSpec?.initContainers,