diff --git a/package-lock.json b/package-lock.json index f4bb8517d..fd1534c88 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@devtron-labs/devtron-fe-common-lib", - "version": "1.21.1", + "version": "1.22.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@devtron-labs/devtron-fe-common-lib", - "version": "1.21.1", + "version": "1.22.0", "hasInstallScript": true, "license": "ISC", "dependencies": { @@ -18,6 +18,7 @@ "@codemirror/lint": "6.8.4", "@codemirror/merge": "^6.10.0", "@codemirror/search": "6.5.8", + "@datasert/cronjs-parser": "^1.4.0", "@lezer/highlight": "1.2.1", "@replit/codemirror-indentation-markers": "6.5.3", "@replit/codemirror-vscode-keymap": "6.0.2", @@ -27,7 +28,9 @@ "@uiw/react-codemirror": "4.23.7", "@xyflow/react": "12.4.2", "ansi_up": "^5.2.1", + "chart.js": "^4.5.0", "codemirror-json-schema": "0.8.0", + "cronstrue": "^3.9.0", "dayjs": "^1.11.13", "fast-json-patch": "^3.1.1", "focus-trap-react": "^10.3.1", @@ -37,6 +40,7 @@ "nanoid": "^3.3.8", "qrcode.react": "^4.2.0", "react-canvas-confetti": "^2.0.7", + "react-csv": "^2.2.2", "react-dates": "^21.8.0", "react-draggable": "^4.4.5", "react-international-phone": "^4.5.0", @@ -57,6 +61,7 @@ "@types/dompurify": "^3.0.5", "@types/json-schema": "^7.0.15", "@types/react": "17.0.39", + "@types/react-csv": "^1.1.10", "@types/react-dates": "^21.8.6", "@types/react-dom": "17.0.13", "@types/react-router-dom": "^5.3.3", @@ -694,6 +699,12 @@ "w3c-keyname": "^2.2.4" } }, + "node_modules/@datasert/cronjs-parser": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@datasert/cronjs-parser/-/cronjs-parser-1.4.0.tgz", + "integrity": "sha512-zHGlrWanS4Zjgf0aMi/sp/HTSa2xWDEtXW9xshhlGf/jPx3zTIqfX14PZnoFF7XVOwzC49Zy0SFWG90rlRY36Q==", + "license": "MIT" + }, "node_modules/@emnapi/runtime": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz", @@ -2037,6 +2048,12 @@ "jsep": "^0.4.0||^1.0.0" } }, + "node_modules/@kurkle/color": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", + "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", + "license": "MIT" + }, "node_modules/@laynezh/vite-plugin-lib-assets": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@laynezh/vite-plugin-lib-assets/-/vite-plugin-lib-assets-1.1.0.tgz", @@ -4132,6 +4149,16 @@ "csstype": "^3.0.2" } }, + "node_modules/@types/react-csv": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@types/react-csv/-/react-csv-1.1.10.tgz", + "integrity": "sha512-PESAyASL7Nfi/IyBR3ufd8qZkyoS+7jOylKmJxRZUZLFASLo4NZaRsJ8rNP8pCcbIziADyWBbLPD1nPddhsL4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/react-dates": { "version": "21.8.6", "resolved": "https://registry.npmjs.org/@types/react-dates/-/react-dates-21.8.6.tgz", @@ -5297,6 +5324,18 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/chart.js": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.0.tgz", + "integrity": "sha512-aYeC/jDgSEx8SHWZvANYMioYMZ2KX02W6f6uVfyteuCGcadDLcYVHdfdygsTQkQ4TKn5lghoojAsPj5pu0SnvQ==", + "license": "MIT", + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=8" + } + }, "node_modules/chokidar": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz", @@ -5602,6 +5641,15 @@ "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==" }, + "node_modules/cronstrue": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/cronstrue/-/cronstrue-3.9.0.tgz", + "integrity": "sha512-T3S35zmD0Ai2B4ko6+mEM+k9C6tipe2nB9RLiGT6QL2Wn0Vsn2cCZAC8Oeuf4CaE00GZWVdpYitbpWCNlIWqdA==", + "license": "MIT", + "bin": { + "cronstrue": "bin/cli.js" + } + }, "node_modules/cross-spawn": { "version": "7.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.5.tgz", @@ -10479,6 +10527,12 @@ "react": "*" } }, + "node_modules/react-csv": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/react-csv/-/react-csv-2.2.2.tgz", + "integrity": "sha512-RG5hOcZKZFigIGE8LxIEV/OgS1vigFQT4EkaHeKgyuCbUAu9Nbd/1RYq++bJcJJ9VOqO/n9TZRADsXNDR4VEpw==", + "license": "MIT" + }, "node_modules/react-dates": { "version": "21.8.0", "resolved": "https://registry.npmjs.org/react-dates/-/react-dates-21.8.0.tgz", diff --git a/package.json b/package.json index e3506004c..745d27b21 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@devtron-labs/devtron-fe-common-lib", - "version": "1.21.1", + "version": "1.22.0", "description": "Supporting common component library", "type": "module", "main": "dist/index.js", @@ -49,6 +49,7 @@ "@types/dompurify": "^3.0.5", "@types/json-schema": "^7.0.15", "@types/react": "17.0.39", + "@types/react-csv": "^1.1.10", "@types/react-dates": "^21.8.6", "@types/react-dom": "17.0.13", "@types/react-router-dom": "^5.3.3", @@ -107,6 +108,7 @@ "@codemirror/lint": "6.8.4", "@codemirror/merge": "^6.10.0", "@codemirror/search": "6.5.8", + "@datasert/cronjs-parser": "^1.4.0", "@lezer/highlight": "1.2.1", "@replit/codemirror-indentation-markers": "6.5.3", "@replit/codemirror-vscode-keymap": "6.0.2", @@ -116,7 +118,9 @@ "@uiw/react-codemirror": "4.23.7", "@xyflow/react": "12.4.2", "ansi_up": "^5.2.1", + "chart.js": "^4.5.0", "codemirror-json-schema": "0.8.0", + "cronstrue": "^3.9.0", "dayjs": "^1.11.13", "fast-json-patch": "^3.1.1", "focus-trap-react": "^10.3.1", @@ -126,6 +130,7 @@ "nanoid": "^3.3.8", "qrcode.react": "^4.2.0", "react-canvas-confetti": "^2.0.7", + "react-csv": "^2.2.2", "react-dates": "^21.8.0", "react-draggable": "^4.4.5", "react-international-phone": "^4.5.0", diff --git a/src/Assets/IconV2/ic-ai.svg b/src/Assets/IconV2/ic-ai.svg new file mode 100644 index 000000000..f93692125 --- /dev/null +++ b/src/Assets/IconV2/ic-ai.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/Assets/IconV2/ic-alibaba.svg b/src/Assets/IconV2/ic-alibaba.svg new file mode 100644 index 000000000..0138d9fa1 --- /dev/null +++ b/src/Assets/IconV2/ic-alibaba.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/Assets/IconV2/ic-application-group.svg b/src/Assets/IconV2/ic-application-group.svg new file mode 100644 index 000000000..8d40602e0 --- /dev/null +++ b/src/Assets/IconV2/ic-application-group.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/Assets/IconV2/ic-application-management.svg b/src/Assets/IconV2/ic-application-management.svg new file mode 100644 index 000000000..82271ef73 --- /dev/null +++ b/src/Assets/IconV2/ic-application-management.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/Assets/IconV2/ic-application-template.svg b/src/Assets/IconV2/ic-application-template.svg new file mode 100644 index 000000000..c0512f5e4 --- /dev/null +++ b/src/Assets/IconV2/ic-application-template.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/Assets/IconV2/ic-application.svg b/src/Assets/IconV2/ic-application.svg new file mode 100644 index 000000000..904dc7366 --- /dev/null +++ b/src/Assets/IconV2/ic-application.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/Assets/IconV2/ic-argocd-app.svg b/src/Assets/IconV2/ic-argocd-app.svg new file mode 100644 index 000000000..75c322e39 --- /dev/null +++ b/src/Assets/IconV2/ic-argocd-app.svg @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Assets/IconV2/ic-arrow-line-down.svg b/src/Assets/IconV2/ic-arrow-line-down.svg new file mode 100644 index 000000000..4038476ff --- /dev/null +++ b/src/Assets/IconV2/ic-arrow-line-down.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/Assets/IconV2/ic-aws.svg b/src/Assets/IconV2/ic-aws.svg new file mode 100644 index 000000000..ac1774eb8 --- /dev/null +++ b/src/Assets/IconV2/ic-aws.svg @@ -0,0 +1,12 @@ + + + + + \ No newline at end of file diff --git a/src/Assets/IconV2/ic-backup-and-schedule.svg b/src/Assets/IconV2/ic-backup-and-schedule.svg new file mode 100644 index 000000000..b179bf455 --- /dev/null +++ b/src/Assets/IconV2/ic-backup-and-schedule.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/Assets/IconV2/ic-backup-color.svg b/src/Assets/IconV2/ic-backup-color.svg new file mode 100644 index 000000000..f3a5b9a08 --- /dev/null +++ b/src/Assets/IconV2/ic-backup-color.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/Assets/IconV2/ic-backup-location.svg b/src/Assets/IconV2/ic-backup-location.svg new file mode 100644 index 000000000..abd200ec9 --- /dev/null +++ b/src/Assets/IconV2/ic-backup-location.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/Assets/IconV2/ic-backup-schedule-color.svg b/src/Assets/IconV2/ic-backup-schedule-color.svg new file mode 100644 index 000000000..fc97dcef3 --- /dev/null +++ b/src/Assets/IconV2/ic-backup-schedule-color.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/Assets/IconV2/ic-bg-backup-schedule.svg b/src/Assets/IconV2/ic-bg-backup-schedule.svg new file mode 100644 index 000000000..52595a7f3 --- /dev/null +++ b/src/Assets/IconV2/ic-bg-backup-schedule.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/Assets/IconV2/ic-bg-backups.svg b/src/Assets/IconV2/ic-bg-backups.svg new file mode 100644 index 000000000..d998fe4e9 --- /dev/null +++ b/src/Assets/IconV2/ic-bg-backups.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/Assets/IconV2/ic-bg-build.svg b/src/Assets/IconV2/ic-bg-build.svg new file mode 100644 index 000000000..2fca974aa --- /dev/null +++ b/src/Assets/IconV2/ic-bg-build.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/Assets/IconV2/ic-bg-cpu.svg b/src/Assets/IconV2/ic-bg-cpu.svg new file mode 100644 index 000000000..473271152 --- /dev/null +++ b/src/Assets/IconV2/ic-bg-cpu.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/Assets/IconV2/ic-bg-deploy.svg b/src/Assets/IconV2/ic-bg-deploy.svg new file mode 100644 index 000000000..ec77c9df8 --- /dev/null +++ b/src/Assets/IconV2/ic-bg-deploy.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Assets/IconV2/ic-bg-memory.svg b/src/Assets/IconV2/ic-bg-memory.svg new file mode 100644 index 000000000..602a61406 --- /dev/null +++ b/src/Assets/IconV2/ic-bg-memory.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/Assets/IconV2/ic-bg-pause-schedule.svg b/src/Assets/IconV2/ic-bg-pause-schedule.svg new file mode 100644 index 000000000..1f318903b --- /dev/null +++ b/src/Assets/IconV2/ic-bg-pause-schedule.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/Assets/IconV2/ic-bg-production-pipelines.svg b/src/Assets/IconV2/ic-bg-production-pipelines.svg new file mode 100644 index 000000000..0248e38a2 --- /dev/null +++ b/src/Assets/IconV2/ic-bg-production-pipelines.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Assets/IconV2/ic-bg-project.svg b/src/Assets/IconV2/ic-bg-project.svg new file mode 100644 index 000000000..b04b76c1d --- /dev/null +++ b/src/Assets/IconV2/ic-bg-project.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/Assets/IconV2/ic-bg-ransomware-vulnerable-cluster.svg b/src/Assets/IconV2/ic-bg-ransomware-vulnerable-cluster.svg new file mode 100644 index 000000000..37d09c422 --- /dev/null +++ b/src/Assets/IconV2/ic-bg-ransomware-vulnerable-cluster.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/Assets/IconV2/ic-bg-restore.svg b/src/Assets/IconV2/ic-bg-restore.svg new file mode 100644 index 000000000..f064977d1 --- /dev/null +++ b/src/Assets/IconV2/ic-bg-restore.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Assets/IconV2/ic-bg-scan.svg b/src/Assets/IconV2/ic-bg-scan.svg new file mode 100644 index 000000000..40274a363 --- /dev/null +++ b/src/Assets/IconV2/ic-bg-scan.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Assets/IconV2/ic-bg-storage-locations.svg b/src/Assets/IconV2/ic-bg-storage-locations.svg new file mode 100644 index 000000000..3a0692b0d --- /dev/null +++ b/src/Assets/IconV2/ic-bg-storage-locations.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Assets/IconV2/ic-bg-webhook.svg b/src/Assets/IconV2/ic-bg-webhook.svg new file mode 100644 index 000000000..e3c37b241 --- /dev/null +++ b/src/Assets/IconV2/ic-bg-webhook.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/Assets/IconV2/ic-bot.svg b/src/Assets/IconV2/ic-bot.svg new file mode 100644 index 000000000..572f8b129 --- /dev/null +++ b/src/Assets/IconV2/ic-bot.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/Assets/IconV2/ic-building.svg b/src/Assets/IconV2/ic-building.svg new file mode 100644 index 000000000..09610d844 --- /dev/null +++ b/src/Assets/IconV2/ic-building.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/Assets/IconV2/ic-chart-line-up.svg b/src/Assets/IconV2/ic-chart-line-up.svg new file mode 100644 index 000000000..f3aeba4e8 --- /dev/null +++ b/src/Assets/IconV2/ic-chart-line-up.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/Assets/IconV2/ic-chart-repo.svg b/src/Assets/IconV2/ic-chart-repo.svg new file mode 100644 index 000000000..8d0dff3f9 --- /dev/null +++ b/src/Assets/IconV2/ic-chart-repo.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/Assets/IconV2/ic-check-circle.svg b/src/Assets/IconV2/ic-check-circle.svg new file mode 100644 index 000000000..d5c3f07dd --- /dev/null +++ b/src/Assets/IconV2/ic-check-circle.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/Assets/IconV2/ic-circle-small.svg b/src/Assets/IconV2/ic-circle-small.svg new file mode 100644 index 000000000..3f2fb3825 --- /dev/null +++ b/src/Assets/IconV2/ic-circle-small.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/Assets/IconV2/ic-clipboard.svg b/src/Assets/IconV2/ic-clipboard.svg new file mode 100644 index 000000000..d0c9fac99 --- /dev/null +++ b/src/Assets/IconV2/ic-clipboard.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/Assets/IconV2/ic-clock-counterclockwise.svg b/src/Assets/IconV2/ic-clock-counterclockwise.svg new file mode 100644 index 000000000..bb6d8058d --- /dev/null +++ b/src/Assets/IconV2/ic-clock-counterclockwise.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/Assets/IconV2/ic-cloud-upload.svg b/src/Assets/IconV2/ic-cloud-upload.svg new file mode 100644 index 000000000..23462e3e7 --- /dev/null +++ b/src/Assets/IconV2/ic-cloud-upload.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/Assets/IconV2/ic-coins-color-animated.svg b/src/Assets/IconV2/ic-coins-color-animated.svg new file mode 100644 index 000000000..f813ff218 --- /dev/null +++ b/src/Assets/IconV2/ic-coins-color-animated.svg @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Assets/IconV2/ic-coins.svg b/src/Assets/IconV2/ic-coins.svg new file mode 100644 index 000000000..848b4b283 --- /dev/null +++ b/src/Assets/IconV2/ic-coins.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/Assets/IconV2/ic-cost-visibility.svg b/src/Assets/IconV2/ic-cost-visibility.svg new file mode 100644 index 000000000..435424bc8 --- /dev/null +++ b/src/Assets/IconV2/ic-cost-visibility.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/Assets/IconV2/ic-database-backup.svg b/src/Assets/IconV2/ic-database-backup.svg new file mode 100644 index 000000000..f8d218f32 --- /dev/null +++ b/src/Assets/IconV2/ic-database-backup.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/Assets/IconV2/ic-devtron-text.svg b/src/Assets/IconV2/ic-devtron-text.svg new file mode 100644 index 000000000..213c93ec3 --- /dev/null +++ b/src/Assets/IconV2/ic-devtron-text.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/Assets/IconV2/ic-edit-lines.svg b/src/Assets/IconV2/ic-edit-lines.svg new file mode 100644 index 000000000..4923b7a9b --- /dev/null +++ b/src/Assets/IconV2/ic-edit-lines.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/Assets/IconV2/ic-external-link.svg b/src/Assets/IconV2/ic-external-link.svg new file mode 100644 index 000000000..ddcdbcf6f --- /dev/null +++ b/src/Assets/IconV2/ic-external-link.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/Assets/IconV2/ic-file-download.svg b/src/Assets/IconV2/ic-file-download.svg new file mode 100644 index 000000000..9375934ac --- /dev/null +++ b/src/Assets/IconV2/ic-file-download.svg @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/src/Assets/IconV2/ic-files-changed.svg b/src/Assets/IconV2/ic-files-changed.svg new file mode 100644 index 000000000..abb6397f3 --- /dev/null +++ b/src/Assets/IconV2/ic-files-changed.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/Assets/IconV2/ic-floppy-disk.svg b/src/Assets/IconV2/ic-floppy-disk.svg new file mode 100644 index 000000000..d5bf1b1b9 --- /dev/null +++ b/src/Assets/IconV2/ic-floppy-disk.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/Assets/IconV2/ic-fluxcd-app.svg b/src/Assets/IconV2/ic-fluxcd-app.svg new file mode 100644 index 000000000..0ff6057c7 --- /dev/null +++ b/src/Assets/IconV2/ic-fluxcd-app.svg @@ -0,0 +1,22 @@ + + + + + + + + diff --git a/src/Assets/IconV2/ic-global-overview.svg b/src/Assets/IconV2/ic-global-overview.svg new file mode 100644 index 000000000..c5f0e0e15 --- /dev/null +++ b/src/Assets/IconV2/ic-global-overview.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/Assets/IconV2/ic-google-cloud.svg b/src/Assets/IconV2/ic-google-cloud.svg new file mode 100644 index 000000000..7b8531c8d --- /dev/null +++ b/src/Assets/IconV2/ic-google-cloud.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/Assets/IconV2/ic-infrastructure-management.svg b/src/Assets/IconV2/ic-infrastructure-management.svg new file mode 100644 index 000000000..66436baaf --- /dev/null +++ b/src/Assets/IconV2/ic-infrastructure-management.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/Assets/IconV2/ic-kubernetes.svg b/src/Assets/IconV2/ic-kubernetes.svg new file mode 100644 index 000000000..33ddebd62 --- /dev/null +++ b/src/Assets/IconV2/ic-kubernetes.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/Assets/IconV2/ic-magic-wand.svg b/src/Assets/IconV2/ic-magic-wand.svg new file mode 100644 index 000000000..7f691dc78 --- /dev/null +++ b/src/Assets/IconV2/ic-magic-wand.svg @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/src/Assets/IconV2/ic-oracle-cloud.svg b/src/Assets/IconV2/ic-oracle-cloud.svg new file mode 100644 index 000000000..b9eb0fee4 --- /dev/null +++ b/src/Assets/IconV2/ic-oracle-cloud.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/Assets/IconV2/ic-otc-cloud.svg b/src/Assets/IconV2/ic-otc-cloud.svg new file mode 100644 index 000000000..1114c716e --- /dev/null +++ b/src/Assets/IconV2/ic-otc-cloud.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/Assets/IconV2/ic-party.svg b/src/Assets/IconV2/ic-party.svg new file mode 100644 index 000000000..3658a9bf2 --- /dev/null +++ b/src/Assets/IconV2/ic-party.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/Assets/IconV2/ic-pause.svg b/src/Assets/IconV2/ic-pause.svg new file mode 100644 index 000000000..23a6f2755 --- /dev/null +++ b/src/Assets/IconV2/ic-pause.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/Assets/IconV2/ic-priority-medium-fill.svg b/src/Assets/IconV2/ic-priority-medium-fill.svg new file mode 100644 index 000000000..07f524fb4 --- /dev/null +++ b/src/Assets/IconV2/ic-priority-medium-fill.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/Assets/IconV2/ic-release-hub.svg b/src/Assets/IconV2/ic-release-hub.svg new file mode 100644 index 000000000..4044eeeb0 --- /dev/null +++ b/src/Assets/IconV2/ic-release-hub.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/Assets/IconV2/ic-resource-browser.svg b/src/Assets/IconV2/ic-resource-browser.svg new file mode 100644 index 000000000..7295f3ccf --- /dev/null +++ b/src/Assets/IconV2/ic-resource-browser.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/Assets/IconV2/ic-resource-watcher.svg b/src/Assets/IconV2/ic-resource-watcher.svg new file mode 100644 index 000000000..de5672cb2 --- /dev/null +++ b/src/Assets/IconV2/ic-resource-watcher.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/Assets/IconV2/ic-right-panel-collapse.svg b/src/Assets/IconV2/ic-right-panel-collapse.svg new file mode 100644 index 000000000..e08d43646 --- /dev/null +++ b/src/Assets/IconV2/ic-right-panel-collapse.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/Assets/IconV2/ic-security-fixable.svg b/src/Assets/IconV2/ic-security-fixable.svg new file mode 100644 index 000000000..c5aa54b56 --- /dev/null +++ b/src/Assets/IconV2/ic-security-fixable.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/Assets/IconV2/ic-security-not-fixable.svg b/src/Assets/IconV2/ic-security-not-fixable.svg new file mode 100644 index 000000000..824c5a46f --- /dev/null +++ b/src/Assets/IconV2/ic-security-not-fixable.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/Assets/IconV2/ic-security-policy.svg b/src/Assets/IconV2/ic-security-policy.svg new file mode 100644 index 000000000..d88d42c11 --- /dev/null +++ b/src/Assets/IconV2/ic-security-policy.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/Assets/IconV2/ic-security-scan.svg b/src/Assets/IconV2/ic-security-scan.svg new file mode 100644 index 000000000..54864d08a --- /dev/null +++ b/src/Assets/IconV2/ic-security-scan.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/Assets/IconV2/ic-security-vulnerability.svg b/src/Assets/IconV2/ic-security-vulnerability.svg new file mode 100644 index 000000000..d0f3b68e6 --- /dev/null +++ b/src/Assets/IconV2/ic-security-vulnerability.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/Assets/IconV2/ic-software-release-management.svg b/src/Assets/IconV2/ic-software-release-management.svg new file mode 100644 index 000000000..1cc679821 --- /dev/null +++ b/src/Assets/IconV2/ic-software-release-management.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/Assets/IconV2/ic-symbol-greater-than.svg b/src/Assets/IconV2/ic-symbol-greater-than.svg new file mode 100644 index 000000000..e7eea0b44 --- /dev/null +++ b/src/Assets/IconV2/ic-symbol-greater-than.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/Assets/IconV2/ic-tenants.svg b/src/Assets/IconV2/ic-tenants.svg new file mode 100644 index 000000000..5e0113e4a --- /dev/null +++ b/src/Assets/IconV2/ic-tenants.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/Assets/IconV2/ic-trend-up.svg b/src/Assets/IconV2/ic-trend-up.svg new file mode 100644 index 000000000..90ca5c6a5 --- /dev/null +++ b/src/Assets/IconV2/ic-trend-up.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/Assets/IconV2/ic-warning-stroke.svg b/src/Assets/IconV2/ic-warning-stroke.svg new file mode 100644 index 000000000..1951bcbb7 --- /dev/null +++ b/src/Assets/IconV2/ic-warning-stroke.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/Assets/Illustration/create-backup-schedule.webp b/src/Assets/Illustration/create-backup-schedule.webp new file mode 100644 index 000000000..a50c8a1b4 Binary files /dev/null and b/src/Assets/Illustration/create-backup-schedule.webp differ diff --git a/src/Assets/Illustration/create-backup-snapshot.webp b/src/Assets/Illustration/create-backup-snapshot.webp new file mode 100644 index 000000000..9f23f053f Binary files /dev/null and b/src/Assets/Illustration/create-backup-snapshot.webp differ diff --git a/src/Assets/Illustration/img-folder-empty.svg b/src/Assets/Illustration/img-folder-empty.svg new file mode 100644 index 000000000..4a4cf95ab --- /dev/null +++ b/src/Assets/Illustration/img-folder-empty.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Assets/Illustration/img-no-backup-location.svg b/src/Assets/Illustration/img-no-backup-location.svg new file mode 100644 index 000000000..55426b793 --- /dev/null +++ b/src/Assets/Illustration/img-no-backup-location.svg @@ -0,0 +1,180 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Assets/Illustration/img-no-restores.svg b/src/Assets/Illustration/img-no-restores.svg new file mode 100644 index 000000000..ab181ff2f --- /dev/null +++ b/src/Assets/Illustration/img-no-restores.svg @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Assets/Illustration/no-cluster-cost-enabled.webp b/src/Assets/Illustration/no-cluster-cost-enabled.webp new file mode 100644 index 000000000..06ed5eb01 Binary files /dev/null and b/src/Assets/Illustration/no-cluster-cost-enabled.webp differ diff --git a/src/Common/API/CoreAPI.ts b/src/Common/API/CoreAPI.ts index 2e71cdb35..c5056e8d9 100644 --- a/src/Common/API/CoreAPI.ts +++ b/src/Common/API/CoreAPI.ts @@ -49,6 +49,7 @@ class CoreAPI { preventLicenseRedirect = false, shouldParseServerErrorForUnauthorizedUser = false, isMultipartRequest, + isProxyHost = false, }: FetchAPIParamsType): Promise => { const options: RequestInit = { method: type, @@ -58,7 +59,7 @@ class CoreAPI { // eslint-disable-next-line dot-notation options['credentials'] = 'include' as RequestCredentials return fetch( - `${this.host}/${url}`, + `${isProxyHost ? '/proxy' : this.host}/${url}`, !isMultipartRequest ? options : ({ @@ -239,6 +240,7 @@ class CoreAPI { preventLicenseRedirect: options?.preventLicenseRedirect || false, shouldParseServerErrorForUnauthorizedUser: options?.shouldParseServerErrorForUnauthorizedUser, isMultipartRequest, + isProxyHost: options?.isProxyHost || false, }), timeoutPromise, ]).catch((err) => { diff --git a/src/Common/API/types.ts b/src/Common/API/types.ts index 955c6934b..aa640edd7 100644 --- a/src/Common/API/types.ts +++ b/src/Common/API/types.ts @@ -39,6 +39,9 @@ export interface FetchInTimeParamsType { export interface FetchAPIParamsType extends Omit, 'options'>, - Pick { + Pick< + APIOptions, + 'preventAutoLogout' | 'preventLicenseRedirect' | 'shouldParseServerErrorForUnauthorizedUser' | 'isProxyHost' + > { signal: AbortSignal } diff --git a/src/Common/BreadCrumb/BreadCrumb.tsx b/src/Common/BreadCrumb/BreadCrumb.tsx index f94cdd7ca..fa253c31c 100644 --- a/src/Common/BreadCrumb/BreadCrumb.tsx +++ b/src/Common/BreadCrumb/BreadCrumb.tsx @@ -16,7 +16,7 @@ import React, { useMemo, useEffect } from 'react' import { Link, useRouteMatch, useParams } from 'react-router-dom' -import { getBreadCrumbSeparator, useBreadcrumbContext } from './BreadcrumbStore' +import { useBreadcrumbContext, getBreadCrumbSeparator } from './BreadcrumbStore' import { ConditionalWrap } from '../Helper' import { Breadcrumb, Breadcrumbs, UseBreadcrumbOptionalProps, UseBreadcrumbState } from './Types' @@ -61,7 +61,7 @@ export function useBreadcrumb(props?: UseBreadcrumbOptionalProps, deps?: any[]): const { res: breadcrumbs } = useMemo( () => levels.reduce( - (agg, curr, idx) => { + (agg, curr) => { const { res, prefix } = agg const { to, name } = curr res.push({ @@ -102,7 +102,7 @@ export const BreadCrumb: React.FC = ({ condition={!!breadcrumb.to} wrap={(children) => ( = ({ {breadcrumb.name} - {idx + 1 !== filteredCrumbs.length && breadcrumb.name && getBreadCrumbSeparator()} + {idx + 1 !== filteredCrumbs.length && breadcrumb.name && getBreadCrumbSeparator(sep)} ))} diff --git a/src/Common/BreadCrumb/BreadcrumbStore.tsx b/src/Common/BreadCrumb/BreadcrumbStore.tsx index 006488a62..62f45428c 100644 --- a/src/Common/BreadCrumb/BreadcrumbStore.tsx +++ b/src/Common/BreadCrumb/BreadcrumbStore.tsx @@ -29,7 +29,6 @@ export const getBreadCrumbSeparator = (sep: string = '/') => ( {sep} ) - const Store = ({ children }) => { const [state, setState] = useState(initialState) return {children} diff --git a/src/Common/BreadCrumb/NestedBreadCrumb.tsx b/src/Common/BreadCrumb/NestedBreadCrumb.tsx new file mode 100644 index 000000000..aa3d4213b --- /dev/null +++ b/src/Common/BreadCrumb/NestedBreadCrumb.tsx @@ -0,0 +1,49 @@ +import React from 'react' +import { Link } from 'react-router-dom' + +import { BreadcrumbText, getBreadCrumbSeparator } from './BreadcrumbStore' +import { NestedBreadCrumbProps } from './Types' + +export const NestedBreadCrumb = ({ + redirectUrl, + linkText, + profileName, + nestedBreadCrumbsText, +}: NestedBreadCrumbProps) => { + const breadcrumbLinkClass = 'active dc__devtron-breadcrumb__item fs-16 fw-4 lh-1-5 dc__ellipsis-right dc__mxw-155' + + const breadcrumbs = [ + { type: 'link', label: linkText, to: redirectUrl }, + ...(profileName + ? [ + ...(nestedBreadCrumbsText ? [{ type: 'link', label: nestedBreadCrumbsText, to: redirectUrl }] : []), + { + type: 'text', + label: profileName, + }, + ] + : [ + { + type: 'text', + label: 'Create Profile', + }, + ]), + ] + + return ( +
+ {breadcrumbs.map((crumb, index) => ( + + {crumb.type === 'link' ? ( + + {crumb.label} + + ) : ( + + )} + {index < breadcrumbs.length - 1 && getBreadCrumbSeparator()} + + ))} +
+ ) +} diff --git a/src/Common/BreadCrumb/Types.ts b/src/Common/BreadCrumb/Types.ts index 8ad1f4f9d..52fb8cd37 100644 --- a/src/Common/BreadCrumb/Types.ts +++ b/src/Common/BreadCrumb/Types.ts @@ -52,3 +52,24 @@ export interface BreadcrumbTextProps { */ shouldTruncate?: boolean } + +export interface NestedBreadCrumbProps { + /** + * It is the url to which the link should redirect + */ + redirectUrl: string + /** + * It is the text of the link + */ + linkText: string + /** + * It is the name of the profile + * If not given, would show "Create Profile" + */ + profileName: string + /** + * @default Profiles + * It is the text of the nested breadcrumb + */ + nestedBreadCrumbsText?: string +} diff --git a/src/Common/ClipboardButton/ClipboardButton.tsx b/src/Common/ClipboardButton/ClipboardButton.tsx index d0b76f895..db62bd6da 100644 --- a/src/Common/ClipboardButton/ClipboardButton.tsx +++ b/src/Common/ClipboardButton/ClipboardButton.tsx @@ -17,11 +17,12 @@ import { useEffect, useRef, useState } from 'react' import Tooltip from '@Common/Tooltip/Tooltip' +import { Button, ButtonStyleType, ButtonVariantType } from '@Shared/Components/Button' import { ReactComponent as Check } from '../../Assets/Icon/ic-check.svg' import { ReactComponent as ICCopy } from '../../Assets/Icon/ic-copy.svg' import { copyToClipboard, noop, stopPropagation } from '../Helper' -import ClipboardProps from './types' +import { ClipboardProps } from './types' /** * @param content - Content to be copied @@ -40,6 +41,8 @@ export const ClipboardButton = ({ rootClassName = '', iconSize = 16, handleSuccess, + variant = 'default', + size, }: ClipboardProps) => { const [copied, setCopied] = useState(false) const setCopiedFalseTimeoutRef = useRef>(-1) @@ -96,18 +99,44 @@ export const ClipboardButton = ({ const iconClassName = `icon-dim-${iconSize} dc__no-shrink` + const ariaLabel = `Copy ${content}` + + const renderIcon = () => ( +
+ {copied ? : } +
+ ) + + const tooltipContent = copied ? copiedTippyText : initialTippyText + + if (variant === 'button--secondary') { + return ( + ) diff --git a/src/Common/ClipboardButton/types.ts b/src/Common/ClipboardButton/types.ts index 5667515f7..5bd89a5e1 100644 --- a/src/Common/ClipboardButton/types.ts +++ b/src/Common/ClipboardButton/types.ts @@ -14,7 +14,21 @@ * limitations under the License. */ -export default interface ClipboardProps { +import { ComponentSizeType } from '@Shared/constants' + +export type ClipboardProps = ( + | { + /** + * @default 'default' + */ + variant?: 'default' + size?: never + } + | { + variant: 'button--secondary' + size: ComponentSizeType + } +) & { content: string /** * tippy text before copying diff --git a/src/Common/Common.service.ts b/src/Common/Common.service.ts index 73c2efe1d..990d3fa31 100644 --- a/src/Common/Common.service.ts +++ b/src/Common/Common.service.ts @@ -23,7 +23,7 @@ import { sanitizeUserApprovalList, stringComparatorBySortOrder, } from '@Shared/Helpers' -import { PolicyBlockInfo, RuntimeParamsAPIResponseType, RuntimePluginVariables } from '@Shared/types' +import { EnvListMinDTO, PolicyBlockInfo, RuntimeParamsAPIResponseType, RuntimePluginVariables } from '@Shared/types' import { GitProviderType, ROUTES } from './Constants' import { getUrlWithSearchParams, sortCallback } from './Helper' import { @@ -51,11 +51,16 @@ import { GetAppsInfoForEnvProps, AppMeta, ApprovalRuntimeStateType, + EnvironmentsGroupedByClustersType, + AppsGroupedByProjectsType, + ClusterDetailListType, + ClusterDetailDTO, } from './Types' import { ApiResourceType, STAGE_MAP } from '../Pages' import { RefVariableType, VariableTypeFormat } from './CIPipeline.Types' import { get, post } from './API' import { StatusType } from '@Shared/Components' +import { EnvironmentTypeEnum } from '@Shared/constants' export const getTeamListMin = (): Promise => { // ignore active field @@ -136,9 +141,10 @@ const cdMaterialListModal = ({ } const isConsumedNonApprovedImage = - !isExceptionUser && isApprovalConfigured && + !isExceptionUser && + isApprovalConfigured && (!material.userApprovalMetadata || - material.userApprovalMetadata.approvalRuntimeState !== ApprovalRuntimeStateType.approved) + material.userApprovalMetadata.approvalRuntimeState !== ApprovalRuntimeStateType.approved) const selectImage = !isImageMarked && markFirstSelected && filterState === FilterStates.ALLOWED && !isConsumedNonApprovedImage @@ -344,9 +350,7 @@ export const processCDMaterialServiceResponse = ( cdMaterialsResult, ) - const isApprovalConfigured = getIsApprovalPolicyConfigured( - approvalInfo?.deploymentApprovalInfo?.approvalConfigData, - ) + const isApprovalConfigured = getIsApprovalPolicyConfigured(approvalInfo?.deploymentApprovalInfo?.approvalConfigData) const isExceptionUser = approvalInfo?.deploymentApprovalInfo?.approvalConfigData?.isExceptionUser ?? false @@ -564,3 +568,118 @@ export const getAppsInfoForEnv = async ({ envId, appIds }: GetAppsInfoForEnvProp }, []), } } + +export const getAppOptionsGroupedByProjects = async (): Promise => { + const { result } = await get(ROUTES.APP_LIST_MIN) + + if (!result) { + return [] + } + + return result + .map((project) => ({ + ...project, + appList: project.appList.sort((a, b) => stringComparatorBySortOrder(a.name, b.name)), + })) + .sort((a, b) => stringComparatorBySortOrder(a.projectName, b.projectName)) +} + +export const getEnvironmentOptionsGroupedByClusters = async (): Promise => { + const { result } = (await get(ROUTES.ENVIRONMENT_LIST_MIN)) as ResponseType + + if (!result) { + return [] + } + + const sortedEnvList = result + .map( + ({ + id, + environment_name: name, + isVirtualEnvironment, + cluster_name: cluster, + default: isDefault, + namespace, + }) => ({ + id, + name, + isVirtual: isVirtualEnvironment ?? false, + cluster, + environmentType: isDefault ? EnvironmentTypeEnum.production : EnvironmentTypeEnum.nonProduction, + namespace, + }), + ) + .sort((a, b) => stringComparatorBySortOrder(a.name, b.name)) + + const envGroupedByCluster = Object.values( + sortedEnvList.reduce< + Record + >((acc, env) => { + if (!acc[env.cluster]) { + acc[env.cluster] = { + clusterName: env.cluster, + envList: [], + } + } + + acc[env.cluster].envList.push(env) + + return acc + }, {}), + ).sort((a, b) => stringComparatorBySortOrder(a.clusterName, b.clusterName)) + + return envGroupedByCluster +} + +export const getDetailedClusterList = async ( + clusterIds?: number[], + signal?: AbortSignal, +): Promise => { + const url = getUrlWithSearchParams(ROUTES.CLUSTER, { clusterId: clusterIds?.join() }) + const { result } = await get(url, { signal }) + + return (result ?? []) + .map( + ({ + id, + server_url: serverUrl, + cluster_name: clusterName, + prometheus_url: prometheusUrl, + category, + clusterStatus, + costModuleConfig, + ...res + }) => { + const costModuleInstallationStatus = costModuleConfig?.installationStatus || 'NotInstalled' + + return { + ...res, + clusterId: id, + serverUrl, + clusterName, + prometheusUrl, + category: category?.name + ? { + label: category.name, + value: category.id, + } + : null, + status: clusterStatus, + costModuleConfig: { + enabled: costModuleConfig?.enabled || false, + config: { + ...(costModuleConfig?.config || {}), + detectedProvider: costModuleConfig?.config?.detectedProvider, + }, + installationStatus: costModuleInstallationStatus, + ...(costModuleInstallationStatus === 'Failed' + ? { + installationError: costModuleConfig?.installationError || 'Some error occurred', + } + : {}), + } satisfies ClusterDetailListType['costModuleConfig'], + } + }, + ) + .sort((a, b) => stringComparatorBySortOrder(a.clusterName, b.clusterName)) +} diff --git a/src/Common/Constants.ts b/src/Common/Constants.ts index 39127a5a9..9d8c58914 100644 --- a/src/Common/Constants.ts +++ b/src/Common/Constants.ts @@ -15,13 +15,17 @@ */ import { SelectPickerOptionType } from '@Shared/Components' +import { CostBreakdownItemViewParamsType, CostBreakdownViewType } from '@PagesDevtron2.0/CostVisibility' +import { BackupAndScheduleListViewEnum, BackupLocationsTypes } from '@PagesDevtron2.0/DataProtectionManagement' + +import { InfrastructureManagementAppListType } from './Types' export const FALLBACK_REQUEST_TIMEOUT = 60000 export const Host = window?.__ORCHESTRATOR_ROOT__ ?? '/orchestrator' export const DOCUMENTATION_HOME_PAGE = 'https://docs.devtron.ai' export const DEVTRON_HOME_PAGE = 'https://devtron.ai/' -export const DOCUMENTATION_VERSION = '/devtron/v1.7' +export const DOCUMENTATION_VERSION = '/devtron/v2.0' export const DISCORD_LINK = 'https://discord.devtron.ai/' export const DEFAULT_JSON_SCHEMA_URI = 'https://json-schema.org/draft/2020-12/schema' export const LICENSE_DASHBOARD_HOME_PAGE = 'https://license.devtron.ai/dashboard' @@ -48,17 +52,23 @@ export const PATTERNS = { EMAIL: /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/, } -const GLOBAL_CONFIG_TEMPLATES_DEVTRON_APP = '/global-config/templates/devtron-apps' -const OBSERVABILITY_ROOT = '/observability' +const APPLICATION_MANAGEMENT_ROOT = '/application-management' +const APPLICATION_MANAGEMENT_TEMPLATES_DEVTRON_APP = `${APPLICATION_MANAGEMENT_ROOT}/templates/devtron-app` +const APPLICATION_MANAGEMENT_CONFIGURATIONS = `${APPLICATION_MANAGEMENT_ROOT}/configurations` +const INFRASTRUCTURE_MANAGEMENT_ROOT = '/infrastructure-management' +const SOFTWARE_RELEASE_MANAGEMENT_ROOT = '/software-release-management' +const COST_VISIBILITY_ROOT = '/cost-visibility' +const SECURITY_CENTER_ROOT = '/security-center' +const AUTOMATION_AND_ENABLEMENT_ROOT = '/automation-and-enablement' +const DATA_PROTECTION_ROOT = '/data-protection-management' +const DATA_PROTECTION_BACKUP_AND_SCHEDULE = + `${DATA_PROTECTION_ROOT}/backup-and-schedule/:view(${Object.values(BackupAndScheduleListViewEnum).join('|')})` as const +const GLOBAL_CONFIG_ROOT = '/global-configuration' export const URLS = { LOGIN: '/login', LOGIN_SSO: '/login/sso', - PERMISSION_GROUPS: '/global-config/auth/groups', - APP: '/app', APP_LIST: 'list', - CHARTS_DISCOVER: '/chart-store/discover', - JOB: '/job', CREATE_JOB: 'create-job', GETTING_STARTED: 'getting-started', STACK_MANAGER_ABOUT: '/stack-manager/about', @@ -73,26 +83,76 @@ export const URLS = { DETAILS: '/details', CD_DETAILS: 'cd-details', APP_TRIGGER: 'trigger', - GLOBAL_CONFIG_DOCKER: '/global-config/docker', DEPLOYMENT_HISTORY_CONFIGURATIONS: '/configuration', - GLOBAL_CONFIG_SCOPED_VARIABLES: '/global-config/scoped-variables', - GLOBAL_CONFIG_DEPLOYMENT_CHARTS_LIST: '/global-config/deployment-charts', - GLOBAL_CONFIG_DEPLOYMENT_CHARTS_UPLOAD_CHART: '/global-config/deployment-charts/upload-chart', NETWORK_STATUS_INTERFACE: '/network-status-interface', - RESOURCE_BROWSER: '/resource-browser', COMPARE_CLUSTERS: '/compare-clusters', APP_CONFIG: 'edit', - GLOBAL_CONFIG: '/global-config', - GLOBAL_CONFIG_TEMPLATES_DEVTRON_APP, - GLOBAL_CONFIG_TEMPLATES_DEVTRON_APP_CREATE: `${GLOBAL_CONFIG_TEMPLATES_DEVTRON_APP}/create`, - // NOTE: using appId since we are re-using AppConfig component - GLOBAL_CONFIG_TEMPLATES_DEVTRON_APP_DETAIL: `${GLOBAL_CONFIG_TEMPLATES_DEVTRON_APP}/detail/:appId`, LICENSE_AUTH: '/license-auth', - GLOBAL_CONFIG_EDIT_CLUSTER: '/global-config/cluster-env/edit/:clusterId', - // OBSERVABILITY - OBSERVABILITY: OBSERVABILITY_ROOT, - OBSERVABILITY_OVERVIEW: `${OBSERVABILITY_ROOT}/overview`, - OBSERVABILITY_CUSTOMER_LIST: `${OBSERVABILITY_ROOT}/tenants`, + // APPLICATION MANAGEMENT + APPLICATION_MANAGEMENT: APPLICATION_MANAGEMENT_ROOT, + APPLICATION_MANAGEMENT_OVERVIEW: `${APPLICATION_MANAGEMENT_ROOT}/overview`, + APPLICATION_MANAGEMENT_APP: `${APPLICATION_MANAGEMENT_ROOT}/devtron-app`, + APPLICATION_MANAGEMENT_APP_LIST: `${APPLICATION_MANAGEMENT_ROOT}/devtron-app/list`, + APPLICATION_MANAGEMENT_CREATE_DEVTRON_APP: `${APPLICATION_MANAGEMENT_ROOT}/devtron-app/list/create-app`, + APPLICATION_MANAGEMENT_APPLICATION_GROUP: `${APPLICATION_MANAGEMENT_ROOT}/application-group`, + APPLICATION_MANAGEMENT_TEMPLATES_DEVTRON_APP, + APPLICATION_MANAGEMENT_TEMPLATES_DEVTRON_APP_CREATE: `${APPLICATION_MANAGEMENT_TEMPLATES_DEVTRON_APP}/create`, + // NOTE: using appId since we are re-using AppConfig component + APPLICATION_MANAGEMENT_TEMPLATES_DEVTRON_APP_DETAIL: `${APPLICATION_MANAGEMENT_TEMPLATES_DEVTRON_APP}/detail/:appId`, + APPLICATION_MANAGEMENT_PROJECTS: `${APPLICATION_MANAGEMENT_ROOT}/projects`, + APPLICATION_MANAGEMENT_CONFIGURATIONS, + APPLICATION_MANAGEMENT_CONFIGURATIONS_DEPLOYMENT_CHARTS: `${APPLICATION_MANAGEMENT_CONFIGURATIONS}/deployment-charts`, + APPLICATION_MANAGEMENT_CONFIGURATIONS_SCOPED_VARIABLES: `${APPLICATION_MANAGEMENT_CONFIGURATIONS}/scoped-variables`, + APPLICATION_MANAGEMENT_CONFIGURATIONS_BUILD_INFRA: `${APPLICATION_MANAGEMENT_CONFIGURATIONS}/build-infra`, + APPLICATION_MANAGEMENT_CONFIGURATIONS_BUILD_INFRA_PROFILES: `${APPLICATION_MANAGEMENT_CONFIGURATIONS}/build-infra/profiles`, + APPLICATION_MANAGEMENT_CONFIGURATIONS_NOTIFICATIONS: `${APPLICATION_MANAGEMENT_CONFIGURATIONS}/notifications`, + // INFRASTRUCTURE MANAGEMENT + INFRASTRUCTURE_MANAGEMENT: INFRASTRUCTURE_MANAGEMENT_ROOT, + INFRASTRUCTURE_MANAGEMENT_OVERVIEW: `${INFRASTRUCTURE_MANAGEMENT_ROOT}/overview`, + INFRASTRUCTURE_MANAGEMENT_APP_LIST: `${INFRASTRUCTURE_MANAGEMENT_ROOT}/apps/:appType(${Object.values(InfrastructureManagementAppListType).join('|')})`, + INFRASTRUCTURE_MANAGEMENT_APP: `${INFRASTRUCTURE_MANAGEMENT_ROOT}/apps`, + INFRASTRUCTURE_MANAGEMENT_CHART_STORE: `${INFRASTRUCTURE_MANAGEMENT_ROOT}/chart-store`, + INFRASTRUCTURE_MANAGEMENT_CHART_STORE_DISCOVER: `${INFRASTRUCTURE_MANAGEMENT_ROOT}/chart-store/discover`, + INFRASTRUCTURE_MANAGEMENT_RESOURCE_BROWSER: `${INFRASTRUCTURE_MANAGEMENT_ROOT}/resource-browser`, + INFRASTRUCTURE_MANAGEMENT_RESOURCE_WATCHER: `${INFRASTRUCTURE_MANAGEMENT_ROOT}/resource-watcher`, + // SOFTWARE RELEASE MANAGEMENT + SOFTWARE_RELEASE_MANAGEMENT: SOFTWARE_RELEASE_MANAGEMENT_ROOT, + // COST VISIBILITY + COST_VISIBILITY: COST_VISIBILITY_ROOT, + COST_VISIBILITY_OVERVIEW: `${COST_VISIBILITY_ROOT}/overview`, + COST_BREAKDOWN_ROUTE: `${COST_VISIBILITY_ROOT}/breakdown/:breakdownViewType`, + COST_BREAKDOWN_CLUSTERS: `${COST_VISIBILITY_ROOT}/breakdown/${CostBreakdownViewType.CLUSTERS}`, + COST_BREAKDOWN_ENVIRONMENTS: `${COST_VISIBILITY_ROOT}/breakdown/${CostBreakdownViewType.ENVIRONMENTS}`, + COST_BREAKDOWN_PROJECTS: `${COST_VISIBILITY_ROOT}/breakdown/${CostBreakdownViewType.PROJECTS}`, + COST_BREAKDOWN_APPLICATIONS: `${COST_VISIBILITY_ROOT}/breakdown/${CostBreakdownViewType.APPLICATIONS}`, + COST_BREAKDOWN_DETAIL: `:${CostBreakdownItemViewParamsType.ITEM_NAME}/:${CostBreakdownItemViewParamsType.VIEW}/:${CostBreakdownItemViewParamsType.DETAIL}?`, + COST_CONFIGURATIONS: `${COST_VISIBILITY_ROOT}/configurations`, + // SECURITY CENTER + SECURITY_CENTER: SECURITY_CENTER_ROOT, + SECURITY_CENTER_OVERVIEW: `${SECURITY_CENTER_ROOT}/overview`, + SECURITY_CENTER_VULNERABILITIES: `${SECURITY_CENTER_ROOT}/vulnerabilities`, + SECURITY_CENTER_VULNERABILITY_DEPLOYMENTS: `${SECURITY_CENTER_ROOT}/vulnerabilities/deployments`, + SECURITY_CENTER_VULNERABILITY_CVES: `${SECURITY_CENTER_ROOT}/vulnerabilities/cves`, + SECURITY_CENTER_SECURITY_ENABLEMENT: `${SECURITY_CENTER_ROOT}/security-enablement`, + SECURITY_CENTER_POLICIES: `${SECURITY_CENTER_ROOT}/policies`, + // AUTOMATION AND ENABLEMENT + AUTOMATION_AND_ENABLEMENT: AUTOMATION_AND_ENABLEMENT_ROOT, + AUTOMATION_AND_ENABLEMENT_JOB: `${AUTOMATION_AND_ENABLEMENT_ROOT}/job`, + // DATA PROTECTION + DATA_PROTECTION: DATA_PROTECTION_ROOT, + DATA_PROTECTION_OVERVIEW: `${DATA_PROTECTION_ROOT}/overview`, + DATA_PROTECTION_BACKUP_AND_SCHEDULE, + DATA_PROTECTION_BACKUP_AND_SCHEDULE_DETAIL: `${DATA_PROTECTION_BACKUP_AND_SCHEDULE}/detail/:id`, + DATA_PROTECTION_RESTORES: `${DATA_PROTECTION_ROOT}/restores`, + DATA_PROTECTION_RESTORES_DETAIL: `${DATA_PROTECTION_ROOT}/restores/:restoreId`, + DATA_PROTECTION_BACKUP_LOCATIONS: `${DATA_PROTECTION_ROOT}/backup-locations/:type(${Object.values(BackupLocationsTypes).join('|')})`, + BACKUP_LOCATION_DETAILS: `/:locationId`, + // GLOBAL CONFIGURATION + GLOBAL_CONFIG: GLOBAL_CONFIG_ROOT, + GLOBAL_CONFIG_DOCKER: `${GLOBAL_CONFIG_ROOT}/docker`, + GLOBAL_CONFIG_EDIT_CLUSTER: `${GLOBAL_CONFIG_ROOT}/cluster-env/edit/:clusterId`, + PERMISSION_GROUPS: `${GLOBAL_CONFIG_ROOT}/auth/groups`, + EXTERNAL_APPS: 'ea', } as const export const ROUTES = { @@ -120,6 +180,7 @@ export const ROUTES = { PATCH: 'patch', ENVIRONMENT_LIST_MIN: 'env/autocomplete', CLUSTER: 'cluster', + CLUSTER_MIN: 'cluster/min', API_RESOURCE: 'k8s/api-resources', GVK: 'gvk', NAMESPACE: 'env/namespace', @@ -425,6 +486,15 @@ export const DATE_TIME_FORMATS = { DD_MMM_YYYY_HH_MM: 'DD MMM YYYY, hh:mm', DD_MMM_YYYY: 'DD MMM YYYY', 'DD/MM/YYYY': 'DD/MM/YYYY', + DD_MMM: 'DD MMM', + TWENTY_FOUR_HOUR_FORMAT_HOUR: 'HH', + ABBREVIATED_MONTH: 'MMM', + DATE_WITH_ABBREVIATED_MONTH: 'DD MMM', + WEEKDAY_WITH_DATE_MONTH_AND_YEAR: 'ddd, DD MMM YYYY', + WEEKDAY_DATE_MONTH_YEAR_AND_HOUR: 'ddd, DD MMM YYYY, HH:00', + DAY_OF_MONTH_WITH_ORDINAL: 'Do', + ABBREVIATED_WEEKDAY: 'ddd', + DAY_OF_MONTH: 'DD', } export const SEMANTIC_VERSION_DOCUMENTATION_LINK = 'https://semver.org/' diff --git a/src/Common/Hooks/UseRegisterShortcut/types.ts b/src/Common/Hooks/UseRegisterShortcut/types.ts index 2508933d4..7ef054792 100644 --- a/src/Common/Hooks/UseRegisterShortcut/types.ts +++ b/src/Common/Hooks/UseRegisterShortcut/types.ts @@ -47,6 +47,7 @@ export const KEYBOARD_KEYS_MAP = { Delete: '⌦', '.': '.', Space: 'Space', + '>': '>', } as const export type SupportedKeyboardKeysType = keyof typeof KEYBOARD_KEYS_MAP diff --git a/src/Common/Hooks/useStateFilters/useStateFilters.tsx b/src/Common/Hooks/useStateFilters/useStateFilters.tsx index 485e3c7ac..786077cf4 100644 --- a/src/Common/Hooks/useStateFilters/useStateFilters.tsx +++ b/src/Common/Hooks/useStateFilters/useStateFilters.tsx @@ -103,7 +103,7 @@ const useStateFilters = ({ sortBy: initialSortKey, }) setPagination({ - pageSize: DEFAULT_BASE_PAGE_SIZE, + pageSize: defaultPageSize, pageNumber: DEFAULT_PAGE_NUMBER, }) } diff --git a/src/Common/Modals/VisibleModal2.tsx b/src/Common/Modals/VisibleModal2.tsx index 89ddcbcb7..94800554d 100644 --- a/src/Common/Modals/VisibleModal2.tsx +++ b/src/Common/Modals/VisibleModal2.tsx @@ -17,6 +17,7 @@ import React, { SyntheticEvent } from 'react' import { Backdrop } from '@Shared/Components' import { DTFocusTrapType } from '@Shared/Components/DTFocusTrap' +import { noop } from '@Common/Helper' export class VisibleModal2 extends React.Component<{ className?: string @@ -35,7 +36,7 @@ export class VisibleModal2 extends React.Component<{ render() { return ( diff --git a/src/Common/SearchBar/SearchBar.component.tsx b/src/Common/SearchBar/SearchBar.component.tsx index 6df9fbf23..43ba71f93 100644 --- a/src/Common/SearchBar/SearchBar.component.tsx +++ b/src/Common/SearchBar/SearchBar.component.tsx @@ -16,10 +16,8 @@ import { ChangeEvent, KeyboardEvent, useCallback, useEffect, useRef, useState } from 'react' -import { ReactComponent as ICCross } from '@Icons/ic-cross.svg' -import { ReactComponent as Search } from '@Icons/ic-search.svg' import { useRegisterShortcut } from '@Common/Hooks' -import { Button, ButtonStyleType, ButtonVariantType } from '@Shared/Components' +import { Button, ButtonStyleType, ButtonVariantType, Icon } from '@Shared/Components' import { ComponentSizeType } from '@Shared/constants' import { debounce, noop } from '../Helper' @@ -74,6 +72,8 @@ const SearchBar = ({ noBackgroundAndBorder = false, size = ComponentSizeType.medium, keyboardShortcut, + variant = 'default', + isLoading = false, }: SearchBarProps) => { const [showClearButton, setShowClearButton] = useState(!!initialSearchText) const inputRef = useRef() @@ -157,11 +157,22 @@ const SearchBar = ({ return (
- + + + {/* TODO: Sync with product since it should have ic-enter in case of not applied */} {showClearButton ? ( -
+
+) + +const navLinkWrap = (redirectionLink: string) => (children: ReactNode) => ( + {children} +) + +export const MetricsInfoLoadingCard = ({ withSubtitle }: { withSubtitle?: boolean }) => ( +
+
+
+ + +
+
+
+ {withSubtitle && ( +
+ +
+ )} +
+) + +export const MetricsInfoCard = ({ + dataTestId, + metricTitle, + metricValue, + metricUnit, + valueOutOf, + iconName, + subtitle, + redirectionLink, + tooltipContent, + subtitleRedirection, +}: MetricsInfoCardProps) => { + const [isHovering, setIsHovering] = useState(false) + + const handleHoverStart = () => setIsHovering(true) + const handleHoverEnd = () => setIsHovering(false) + + return ( + + +
+
+ + + {metricTitle} + + +
+ {metricValue} + {valueOutOf && ( + / {valueOutOf} + )} + {metricUnit && ( + {metricUnit} + )} +
+
+
+ {redirectionLink && isHovering ? ( +
+
+ {subtitle && ( +
+ + + {subtitle} + + +
+ )} +
+
+ ) +} + +export const LoadingDonutChart = () => ( +
+
+
+ + +
+
+) diff --git a/src/Pages-Devtron-2.0/Shared/Overview/constants.ts b/src/Pages-Devtron-2.0/Shared/Overview/constants.ts new file mode 100644 index 000000000..c83e60d16 --- /dev/null +++ b/src/Pages-Devtron-2.0/Shared/Overview/constants.ts @@ -0,0 +1,27 @@ +const OVERVIEW_DEFAULT_PAGE_SIZE = { + SMALL: 5, + MEDIUM: 10, + LARGE: 20, +} + +export const OVERVIEW_PAGE_SIZE_OPTIONS = [ + { + value: OVERVIEW_DEFAULT_PAGE_SIZE.MEDIUM, + selected: true, + }, + { + value: OVERVIEW_DEFAULT_PAGE_SIZE.LARGE, + selected: false, + }, +] + +export const OVERVIEW_PAGE_SIZE_OPTIONS_SMALL = [ + { + value: OVERVIEW_DEFAULT_PAGE_SIZE.SMALL, + selected: true, + }, + { + value: OVERVIEW_DEFAULT_PAGE_SIZE.MEDIUM, + selected: false, + }, +] diff --git a/src/Pages-Devtron-2.0/Shared/Overview/index.ts b/src/Pages-Devtron-2.0/Shared/Overview/index.ts new file mode 100644 index 000000000..d3f2deaa4 --- /dev/null +++ b/src/Pages-Devtron-2.0/Shared/Overview/index.ts @@ -0,0 +1,3 @@ +export * from './components' +export * from './constants' +export { type MetricsInfoCardProps, ProdNonProdSelectValueTypes } from './types' diff --git a/src/Pages-Devtron-2.0/Shared/Overview/types.ts b/src/Pages-Devtron-2.0/Shared/Overview/types.ts new file mode 100644 index 000000000..bf8556b87 --- /dev/null +++ b/src/Pages-Devtron-2.0/Shared/Overview/types.ts @@ -0,0 +1,29 @@ +import { ButtonComponentType, ButtonProps, IconName } from '@Shared/Components' +import { IconBaseColorType } from '@Shared/types' + +export interface SectionEmptyStateProps { + iconColor?: IconBaseColorType + iconName?: IconName + title: string + subtitle?: string + buttonConfig?: ButtonProps +} + +export interface MetricsInfoCardProps { + dataTestId: string + metricTitle: string + metricValue: string + metricUnit?: string + valueOutOf?: string + iconName: IconName + subtitle?: string + tooltipContent: string + redirectionLink?: string + subtitleRedirection?: string +} + +export enum ProdNonProdSelectValueTypes { + ALL = 'All', + PRODUCTION = 'Prod', + NON_PRODUCTION = 'Non-Prod', +} diff --git a/src/Pages-Devtron-2.0/Shared/index.ts b/src/Pages-Devtron-2.0/Shared/index.ts new file mode 100644 index 000000000..bb81fdde1 --- /dev/null +++ b/src/Pages-Devtron-2.0/Shared/index.ts @@ -0,0 +1 @@ +export * from './Overview' diff --git a/src/Pages-Devtron-2.0/SoftwareReleaseManagement/Overview/Overview.tsx b/src/Pages-Devtron-2.0/SoftwareReleaseManagement/Overview/Overview.tsx new file mode 100644 index 000000000..a39a994ae --- /dev/null +++ b/src/Pages-Devtron-2.0/SoftwareReleaseManagement/Overview/Overview.tsx @@ -0,0 +1 @@ +export const Overview = () =>
Overview
diff --git a/src/Pages-Devtron-2.0/SoftwareReleaseManagement/Overview/index.ts b/src/Pages-Devtron-2.0/SoftwareReleaseManagement/Overview/index.ts new file mode 100644 index 000000000..bb81fdde1 --- /dev/null +++ b/src/Pages-Devtron-2.0/SoftwareReleaseManagement/Overview/index.ts @@ -0,0 +1 @@ +export * from './Overview' diff --git a/src/Pages-Devtron-2.0/SoftwareReleaseManagement/index.ts b/src/Pages-Devtron-2.0/SoftwareReleaseManagement/index.ts new file mode 100644 index 000000000..bb81fdde1 --- /dev/null +++ b/src/Pages-Devtron-2.0/SoftwareReleaseManagement/index.ts @@ -0,0 +1 @@ +export * from './Overview' diff --git a/src/Pages-Devtron-2.0/index.ts b/src/Pages-Devtron-2.0/index.ts new file mode 100644 index 000000000..df634e1ce --- /dev/null +++ b/src/Pages-Devtron-2.0/index.ts @@ -0,0 +1,7 @@ +export * from './ApplicationManagement' +export * from './CostVisibility' +export * from './DataProtectionManagement' +export * from './InfrastructureManagement' +export * from './Navigation' +export * from './SecurityCenter' +export * from './Shared' diff --git a/src/Pages/GlobalConfigurations/BuildInfra/Descriptor.tsx b/src/Pages/GlobalConfigurations/BuildInfra/Descriptor.tsx index 37853c59f..4a3979bfc 100644 --- a/src/Pages/GlobalConfigurations/BuildInfra/Descriptor.tsx +++ b/src/Pages/GlobalConfigurations/BuildInfra/Descriptor.tsx @@ -14,26 +14,31 @@ * limitations under the License. */ +import { NestedBreadCrumb, URLS } from '@Common/index' import { InfoIconTippy } from '@Shared/Components/InfoIconTippy' -import { BreadCrumb } from '../../../Common' import { BUILD_INFRA_TEXT } from './constants' import { BuildInfraDescriptorProps } from './types' const Descriptor = ({ additionalContainerClasses, - breadCrumbs, children, + tooltipNode, tippyInfoText, tippyAdditionalContent, - tooltipNode, tooltipHeading, + profileName, }: BuildInfraDescriptorProps) => (
{tooltipNode || ( <> - + {BUILD_INFRA_TEXT.HEADING}, - linked: false, - }, - }, -} - export const BUILD_INFRA_LOCATOR_MARKER_MAP: Readonly> = { [BuildInfraLocators.CPU]: ICCpu, [BuildInfraLocators.MEMORY]: ICMemory, diff --git a/src/Pages/GlobalConfigurations/BuildInfra/types.tsx b/src/Pages/GlobalConfigurations/BuildInfra/types.tsx index f0abdba0a..53e8dfa1f 100644 --- a/src/Pages/GlobalConfigurations/BuildInfra/types.tsx +++ b/src/Pages/GlobalConfigurations/BuildInfra/types.tsx @@ -19,7 +19,6 @@ import { FormEvent, FunctionComponent, ReactNode, SyntheticEvent } from 'react' import { BUILD_INFRA_INHERIT_ACTIONS, useBuildInfraForm } from '@Pages/index' import { ServerErrors } from '../../../Common' -import { Breadcrumb } from '../../../Common/BreadCrumb/Types' import { CMSecretComponentType, CMSecretConfigData, @@ -76,21 +75,31 @@ export enum BuildInfraProfileVariants { CUSTOM = 'CUSTOM', } -export interface BuildInfraDescriptorProps { +export type BuildInfraDescriptorProps = { /** * In case we want to restrict the max-width */ additionalContainerClasses?: string - breadCrumbs: Breadcrumb[] /** * Would stick at right of div */ children?: ReactNode - tippyInfoText?: string - tippyAdditionalContent?: ReactNode - tooltipNode?: ReactNode - tooltipHeading?: string -} +} & ( + | { + tooltipNode: ReactNode + tippyInfoText?: never + tooltipHeading?: never + tippyAdditionalContent?: never + profileName?: never + } + | { + tooltipNode?: never + tippyInfoText: string + tooltipHeading?: string + tippyAdditionalContent?: ReactNode + profileName?: string + } +) export type NumericBuildInfraConfigTypes = Extract< BuildInfraConfigTypes, diff --git a/src/Pages/GlobalConfigurations/ClustersAndEnvironments/VirtualClusterSidebar.tsx b/src/Pages/GlobalConfigurations/ClustersAndEnvironments/VirtualClusterSidebar.tsx new file mode 100644 index 000000000..fd2e32aa5 --- /dev/null +++ b/src/Pages/GlobalConfigurations/ClustersAndEnvironments/VirtualClusterSidebar.tsx @@ -0,0 +1,18 @@ +import { Icon, ModalSidebarPanel } from '@Shared/Components' + +const VirtualClusterSidebar = () => ( + } + heading="Create Isolated Cluster" + documentationLink="GLOBAL_CONFIG_CLUSTER" + rootClassName="p-20 dc__no-background-imp dc__no-shrink" + > +

An isolated cluster in Devtron is an air-gapped Kubernetes cluster with restricted network access.

+

+ Since Devtron does not have connectivity to these clusters, deployments are managed by packaging manifests + and images for manual installation or retrieval from an OCI registry (if enabled). +

+
+) + +export default VirtualClusterSidebar diff --git a/src/Pages/GlobalConfigurations/ClustersAndEnvironments/index.ts b/src/Pages/GlobalConfigurations/ClustersAndEnvironments/index.ts index 4796bc9ea..62961d51f 100644 --- a/src/Pages/GlobalConfigurations/ClustersAndEnvironments/index.ts +++ b/src/Pages/GlobalConfigurations/ClustersAndEnvironments/index.ts @@ -14,4 +14,4 @@ * limitations under the License. */ -export type { NewClusterFormFooterProps, NewClusterFormProps } from './types' +export { default as VirtualClusterSidebar } from './VirtualClusterSidebar' diff --git a/src/Pages/GlobalConfigurations/ClustersAndEnvironments/types.ts b/src/Pages/GlobalConfigurations/ClustersAndEnvironments/types.ts deleted file mode 100644 index 581b15190..000000000 --- a/src/Pages/GlobalConfigurations/ClustersAndEnvironments/types.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 2024. Devtron Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export interface NewClusterFormFooterProps { - apiCallInProgress: boolean - handleModalClose: () => void - closeButtonText?: string -} - -export interface NewClusterFormProps extends Pick { - FooterComponent: React.FunctionComponent & - Record<'CTA' | 'Start', React.FunctionComponent<{}>> -} diff --git a/src/Pages/ResourceBrowser/ClusterMap/utils.ts b/src/Pages/ResourceBrowser/ClusterMap/utils.ts index b0a129af9..6891b46bf 100644 --- a/src/Pages/ResourceBrowser/ClusterMap/utils.ts +++ b/src/Pages/ResourceBrowser/ClusterMap/utils.ts @@ -103,7 +103,7 @@ export const getEntities = (filteredList: ClusterStatusAndType[]): ClusterEntiti { value: filteredList.length - prodCount, label: 'Non-Production', - color: 'var(--N300', + color: 'var(--N300)', proportionalValue: `${filteredList.length - prodCount}/${filteredList.length}`, }, ] diff --git a/src/Pages/ResourceBrowser/ResourceBrowser.Types.ts b/src/Pages/ResourceBrowser/ResourceBrowser.Types.ts index f5069541c..0bf7fc3f9 100644 --- a/src/Pages/ResourceBrowser/ResourceBrowser.Types.ts +++ b/src/Pages/ResourceBrowser/ResourceBrowser.Types.ts @@ -14,8 +14,9 @@ * limitations under the License. */ -import { ReactNode, RefObject } from 'react' +import { RefObject } from 'react' +import { FiltersTypeEnum, TableViewWrapperProps } from '@Shared/Components' import { Nodes, NodeType } from '@Shared/types' export interface GVKType { @@ -200,7 +201,7 @@ export interface GVKOptionValueType { apiVersion: string } -export interface ResourceRecommenderActionMenuProps { - children: ReactNode +export interface ResourceRecommenderActionMenuProps + extends Pick, 'filteredRows'> { lastScannedOn: string } diff --git a/src/Pages/ResourceBrowser/constants.tsx b/src/Pages/ResourceBrowser/constants.tsx index fb6faaf45..8d701b90e 100644 --- a/src/Pages/ResourceBrowser/constants.tsx +++ b/src/Pages/ResourceBrowser/constants.tsx @@ -117,14 +117,14 @@ export const GVK_FILTER_KIND_QUERY_PARAM_KEY = 'gvkFilterKind' export const GVK_FILTER_API_VERSION_QUERY_PARAM_KEY = 'gvkFilterApiVersion' export const RESOURCE_BROWSER_ROUTES = { - OVERVIEW: `${URLS.RESOURCE_BROWSER}/:clusterId/overview`, - MONITORING_DASHBOARD: `${URLS.RESOURCE_BROWSER}/:clusterId/monitoring-dashboard`, - TERMINAL: `${URLS.RESOURCE_BROWSER}/:clusterId/terminal`, - CLUSTER_UPGRADE: `${URLS.RESOURCE_BROWSER}/:clusterId/cluster-upgrade`, - NODE_DETAIL: `${URLS.RESOURCE_BROWSER}/:clusterId/node/detail/:name`, - K8S_RESOURCE_DETAIL: `${URLS.RESOURCE_BROWSER}/:clusterId/:namespace/:kind/:group/:name`, - K8S_RESOURCE_LIST: `${URLS.RESOURCE_BROWSER}/:clusterId/:kind/:group`, - RESOURCE_RECOMMENDER: `${URLS.RESOURCE_BROWSER}/:clusterId/resource-recommender`, + OVERVIEW: `${URLS.INFRASTRUCTURE_MANAGEMENT_RESOURCE_BROWSER}/:clusterId/overview`, + MONITORING_DASHBOARD: `${URLS.INFRASTRUCTURE_MANAGEMENT_RESOURCE_BROWSER}/:clusterId/monitoring-dashboard`, + TERMINAL: `${URLS.INFRASTRUCTURE_MANAGEMENT_RESOURCE_BROWSER}/:clusterId/terminal`, + CLUSTER_UPGRADE: `${URLS.INFRASTRUCTURE_MANAGEMENT_RESOURCE_BROWSER}/:clusterId/cluster-upgrade`, + NODE_DETAIL: `${URLS.INFRASTRUCTURE_MANAGEMENT_RESOURCE_BROWSER}/:clusterId/node/detail/:name`, + K8S_RESOURCE_DETAIL: `${URLS.INFRASTRUCTURE_MANAGEMENT_RESOURCE_BROWSER}/:clusterId/:namespace/:kind/:group/:name`, + K8S_RESOURCE_LIST: `${URLS.INFRASTRUCTURE_MANAGEMENT_RESOURCE_BROWSER}/:clusterId/:kind/:group`, + RESOURCE_RECOMMENDER: `${URLS.INFRASTRUCTURE_MANAGEMENT_RESOURCE_BROWSER}/:clusterId/resource-recommender`, } as const export const K8S_EMPTY_GROUP = 'k8sEmptyGroup' diff --git a/src/Pages/ResourceBrowser/types.ts b/src/Pages/ResourceBrowser/types.ts index b731b324d..fef5afb7b 100644 --- a/src/Pages/ResourceBrowser/types.ts +++ b/src/Pages/ResourceBrowser/types.ts @@ -25,6 +25,7 @@ export enum ClusterFiltersType { ALL_CLUSTERS = 'all', HEALTHY = 'healthy', UNHEALTHY = 'unhealthy', + CONNECTION_FAILED = 'connectionFailed', } export enum InstallationClusterStatus { diff --git a/src/Shared/Components/ActionMenu/ActionMenu.component.tsx b/src/Shared/Components/ActionMenu/ActionMenu.component.tsx index 70f4429bf..eadb01844 100644 --- a/src/Shared/Components/ActionMenu/ActionMenu.component.tsx +++ b/src/Shared/Components/ActionMenu/ActionMenu.component.tsx @@ -67,7 +67,9 @@ export const ActionMenu = ({ const handleOptionOnClick: ActionMenuItemProps['onClick'] = (item, e) => { onClick(item, e) - closePopover() + if (!item.doNotCloseMenuOnClick) { + closePopover() + } } return ( diff --git a/src/Shared/Components/ActionMenu/types.ts b/src/Shared/Components/ActionMenu/types.ts index a18b7ae1f..fd067e601 100644 --- a/src/Shared/Components/ActionMenu/types.ts +++ b/src/Shared/Components/ActionMenu/types.ts @@ -62,6 +62,9 @@ export type ActionMenuItemType = Om startIcon?: ActionMenuItemIconType /** Defines the item to be displayed at the end of the menu item. */ trailingItem?: TrailingItemType + /** Prevents the menu from closing when the item is clicked. */ + doNotCloseMenuOnClick?: boolean + dataAttributesId?: number } & ConditionalActionMenuComponentType export type ActionMenuOptionType = { diff --git a/src/Shared/Components/Backdrop/Backdrop.tsx b/src/Shared/Components/Backdrop/Backdrop.tsx index 0299de2bc..323424d69 100644 --- a/src/Shared/Components/Backdrop/Backdrop.tsx +++ b/src/Shared/Components/Backdrop/Backdrop.tsx @@ -32,6 +32,7 @@ const Backdrop = ({ onBackdropMount, deactivateFocusOnEscape = true, initialFocus, + returnFocusOnDeactivate, }: BackdropProps) => { // STATES const [portalContainer, setPortalContainer] = useState(null) @@ -90,6 +91,7 @@ const Backdrop = ({ onEscape={onEscape} deactivateFocusOnEscape={deactivateFocusOnEscape} initialFocus={initialFocus ?? undefined} + returnFocusOnDeactivate={returnFocusOnDeactivate} > { +export interface BackdropProps + extends Pick { /** * The content to be rendered within the backdrop component. */ diff --git a/src/Shared/Components/Badge/Badge.tsx b/src/Shared/Components/Badge/Badge.tsx index a7cabbb26..7f22c52fc 100644 --- a/src/Shared/Components/Badge/Badge.tsx +++ b/src/Shared/Components/Badge/Badge.tsx @@ -18,7 +18,7 @@ import { ComponentSizeType } from '@Shared/constants' import { Icon } from '../Icon' import { BadgeProps } from './types' -import { getClassNameAccToSize, getClassNameAccToVariant } from './utils' +import { COMPONENT_SIZE_TO_ICON_SIZE_MAP, getClassNameAccToSize, getClassNameAccToVariant } from './utils' const Badge = ({ label, @@ -30,11 +30,11 @@ const Badge = ({ size = ComponentSizeType.xs, }: BadgeProps) => { const { styles, iconColor } = getClassNameAccToVariant(variant) - const iconSize = size === ComponentSizeType.xs ? 20 : 16 + const iconSize = COMPONENT_SIZE_TO_ICON_SIZE_MAP[size] return (
{ return 'fs-13 lh-20 px-6 py-2' } } + +export const COMPONENT_SIZE_TO_ICON_SIZE_MAP: Record = { + [ComponentSizeType.xxxs]: 12, + [ComponentSizeType.xxs]: 16, + [ComponentSizeType.xs]: 20, +} diff --git a/src/Shared/Components/BulkSelection/BulkSelection.tsx b/src/Shared/Components/BulkSelection/BulkSelection.tsx index 128ad5ece..94185a3c1 100644 --- a/src/Shared/Components/BulkSelection/BulkSelection.tsx +++ b/src/Shared/Components/BulkSelection/BulkSelection.tsx @@ -96,11 +96,11 @@ const BulkSelection = forwardRef( diff --git a/src/Shared/Components/CICDHistory/DeploymentDetailSteps.tsx b/src/Shared/Components/CICDHistory/DeploymentDetailSteps.tsx index 860bbf5d3..84ce3045a 100644 --- a/src/Shared/Components/CICDHistory/DeploymentDetailSteps.tsx +++ b/src/Shared/Components/CICDHistory/DeploymentDetailSteps.tsx @@ -138,13 +138,13 @@ const DeploymentDetailSteps = ({ const redirectToDeploymentStatus = () => { if (isHelmApps) { getHandleOpenURL( - `${window.__BASE_URL__}${URLS.APP}/${URLS.DEVTRON_CHARTS}/${URLS.APP_DEPLOYMNENT_HISTORY}/${appId}/env/${envId}/${URLS.DETAILS}/${URLS.APP_DETAILS_K8}?${DEPLOYMENT_STATUS_QUERY_PARAM}`, + `${window.__BASE_URL__}${URLS.INFRASTRUCTURE_MANAGEMENT_APP}/${URLS.DEVTRON_CHARTS}/${URLS.APP_DEPLOYMNENT_HISTORY}/${appId}/env/${envId}/${URLS.DETAILS}/${URLS.APP_DETAILS_K8}?${DEPLOYMENT_STATUS_QUERY_PARAM}`, )() return } getHandleOpenURL( - `${window.__BASE_URL__}${URLS.APP}/${appId}/${URLS.APP_DETAILS}/${envId}/${URLS.APP_DETAILS_K8}?${DEPLOYMENT_STATUS_QUERY_PARAM}`, + `${window.__BASE_URL__}${URLS.APPLICATION_MANAGEMENT_APP}/${appId}/${URLS.APP_DETAILS}/${envId}/${URLS.APP_DETAILS_K8}?${DEPLOYMENT_STATUS_QUERY_PARAM}`, )() } diff --git a/src/Shared/Components/CICDHistory/ResourceConflictDeployDialog.tsx b/src/Shared/Components/CICDHistory/ResourceConflictDeployDialog.tsx index 7310d994e..b102547cf 100644 --- a/src/Shared/Components/CICDHistory/ResourceConflictDeployDialog.tsx +++ b/src/Shared/Components/CICDHistory/ResourceConflictDeployDialog.tsx @@ -24,7 +24,7 @@ const ResourceConflictDeployDialog = ({ appName, environmentName, handleClose }: appId, }) setIsLoading(false) - history.push(`${URLS.APP}/${appId}/details/${envId}`) + history.push(`${URLS.APPLICATION_MANAGEMENT_APP}/${appId}/details/${envId}`) } catch (error) { showError(error) setIsLoading(false) diff --git a/src/Shared/Components/CICDHistory/ResourceConflictDetailsModal.tsx b/src/Shared/Components/CICDHistory/ResourceConflictDetailsModal.tsx index 8ca373b72..3b9e49982 100644 --- a/src/Shared/Components/CICDHistory/ResourceConflictDetailsModal.tsx +++ b/src/Shared/Components/CICDHistory/ResourceConflictDetailsModal.tsx @@ -43,7 +43,7 @@ const ResourceConflictDetailsModal = ({ appName, environmentName, handleClose }: appId, }) setIsDeploying(false) - history.push(`${URLS.APP}/${appId}/details/${envId}`) + history.push(`${URLS.APPLICATION_MANAGEMENT_APP}/${appId}/details/${envId}`) } catch (error) { showError(error) setIsDeploying(false) diff --git a/src/Shared/Components/CICDHistory/utils.tsx b/src/Shared/Components/CICDHistory/utils.tsx index d24e486f4..430d4622f 100644 --- a/src/Shared/Components/CICDHistory/utils.tsx +++ b/src/Shared/Components/CICDHistory/utils.tsx @@ -234,7 +234,7 @@ export const getHistoryItemStatusIconFromWorkflowStages = ( } export const getWorkerPodBaseUrl = (clusterId: number = DEFAULT_CLUSTER_ID, podNamespace: string = DEFAULT_NAMESPACE) => - `${URLS.RESOURCE_BROWSER}/${clusterId}/${podNamespace}/pod/${K8S_EMPTY_GROUP}` + `${URLS.INFRASTRUCTURE_MANAGEMENT_RESOURCE_BROWSER}/${clusterId}/${podNamespace}/pod/${K8S_EMPTY_GROUP}` export const getWorkflowNodeStatusTitle = (status: string) => { if (!status) { diff --git a/src/Shared/Components/Card/Card.component.tsx b/src/Shared/Components/Card/Card.component.tsx new file mode 100644 index 000000000..6a891f935 --- /dev/null +++ b/src/Shared/Components/Card/Card.component.tsx @@ -0,0 +1,85 @@ +import { HTMLProps } from 'react' + +import { DEFAULT_BORDER_RADIUS, DEFAULT_FLEX_GAP, VARIANT_TO_BORDER_CLASS_MAP } from './constants' +import { CardProps } from './types' + +const Card = ({ + onClick, + children, + isLoading, + shimmerVariant, + variant = 'primary--outlined', + flexGap = DEFAULT_FLEX_GAP, + flexDirection = 'column', + padding = 0, +}: CardProps) => { + const divProps: HTMLProps = onClick + ? { + onClick, + role: 'button', + tabIndex: 0, + onKeyDown: (e) => { + if (e.key === 'Enter' || e.key === ' ') { + onClick() + } + }, + } + : {} + + const renderShimmerVariant = () => { + switch (shimmerVariant) { + case 'B': + return ( +
+
+
+
+
+
+
+ ) + case 'C': + return ( +
+
+
+
+ ) + case 'A': + default: + return ( +
+
+
+
+
+ ) + } + } + + const cardStyles = { + borderRadius: `${DEFAULT_BORDER_RADIUS}px`, + gap: `${flexGap}px`, + padding: `${padding}px`, + } + + const cardClasses = [ + VARIANT_TO_BORDER_CLASS_MAP[variant], + flexDirection === 'column' ? 'flexbox-col' : 'flexbox', + 'dc__gap-16', + 'bg__primary', + 'dc__overflow-hidden', + 'dc__no-shrink', + onClick ? 'cursor' : '', + ] + .filter(Boolean) + .join(' ') + + return ( +
+ {isLoading ? renderShimmerVariant() : children} +
+ ) +} + +export default Card diff --git a/src/Shared/Components/Card/constants.ts b/src/Shared/Components/Card/constants.ts new file mode 100644 index 000000000..6a3c64670 --- /dev/null +++ b/src/Shared/Components/Card/constants.ts @@ -0,0 +1,10 @@ +import { CardProps } from './types' + +export const DEFAULT_BORDER_RADIUS = 8 + +export const DEFAULT_FLEX_GAP = 0 + +export const VARIANT_TO_BORDER_CLASS_MAP: Record = { + 'primary--outlined': 'border__primary', + 'secondary--outlined': 'border__secondary', +} diff --git a/src/Shared/Components/Card/index.ts b/src/Shared/Components/Card/index.ts new file mode 100644 index 000000000..718e7d788 --- /dev/null +++ b/src/Shared/Components/Card/index.ts @@ -0,0 +1,2 @@ +export { default as Card } from './Card.component' +export type { CardProps } from './types' diff --git a/src/Shared/Components/Card/types.ts b/src/Shared/Components/Card/types.ts new file mode 100644 index 000000000..ff432791c --- /dev/null +++ b/src/Shared/Components/Card/types.ts @@ -0,0 +1,30 @@ +import { ReactNode } from 'react' + +import { ComponentLayoutType } from '@Shared/types' + +export type CardProps = { + children: ReactNode + onClick?: () => void + /** + * @default primary--outlined + */ + variant?: 'primary--outlined' | 'secondary--outlined' + /** + * @default 'column' + */ + flexDirection?: ComponentLayoutType + /** + * @default 0px + */ + flexGap?: 0 | 2 | 4 | 6 | 8 | 12 | 16 | 20 | 24 | 32 + padding?: 0 | 2 | 4 | 6 | 8 | 12 | 16 | 20 | 24 | 32 +} & ( + | { + isLoading: boolean + shimmerVariant: 'A' | 'B' | 'C' + } + | { + isLoading?: never + shimmerVariant?: never + } +) diff --git a/src/Shared/Components/Charts/Chart.component.tsx b/src/Shared/Components/Charts/Chart.component.tsx new file mode 100644 index 000000000..b874adcc5 --- /dev/null +++ b/src/Shared/Components/Charts/Chart.component.tsx @@ -0,0 +1,361 @@ +import { ReactNode, useEffect, useRef, useState } from 'react' +import Tippy from '@tippyjs/react' +import { + ArcElement, + BarController, + BarElement, + CategoryScale, + Chart as ChartJS, + DoughnutController, + Filler, + Legend, + LinearScale, + LineController, + LineElement, + PointElement, + TimeScale, + Title, + Tooltip as ChartJSTooltip, +} from 'chart.js' + +import { noop, useDebounce } from '@Common/Helper' +import { DEVTRON_BASE_MAIN_ID } from '@Shared/constants' +import { useTheme } from '@Shared/Providers' + +import { drawCenterText, drawReferenceLine, htmlLegendPlugin } from './plugins' +import { ChartProps, GetDefaultOptionsParams, TypeAndDatasetsType } from './types' +import { + buildChartTooltipFromContext, + distanceBetweenPoints, + getChartJSType, + getDefaultOptions, + transformDataForChart, +} from './utils' + +// Register Chart.js components +ChartJS.register( + CategoryScale, + LinearScale, + BarController, + BarElement, + LineController, + LineElement, + PointElement, + DoughnutController, + ArcElement, + TimeScale, + Title, + ChartJSTooltip, + Legend, + Filler, +) + +ChartJSTooltip.positioners.barElementCenterPositioner = (items, eventPosition) => { + if (!items.length) { + return false + } + + let { x } = eventPosition + let { y } = eventPosition + let minDistance = Number.POSITIVE_INFINITY + let i: number + let len: number + let nearestElement: BarElement + + for (i = 0, len = items.length; i < len; ++i) { + const el = items[i].element + if (el && el.hasValue()) { + const center = (el as BarElement).getCenterPoint() + const d = distanceBetweenPoints(eventPosition, center) + + if (d < minDistance) { + minDistance = d + nearestElement = el as BarElement + } + } + } + + if (nearestElement) { + const tp = nearestElement.getCenterPoint() + x = tp.x + y = tp.y + } + + return { + x, + y, + } +} + +/** + * A versatile Chart component that renders different types of charts using Chart.js. + * Supports area charts, pie charts, semi-pie charts, stacked bar charts (vertical/horizontal), and line charts. + * + * The component automatically adapts to theme changes and provides consistent styling + * across all chart types. Colors are provided by the user through the CHART_COLORS constant + * or custom color tokens. + * + * @example + * ```tsx + * [Area Chart Example] + * + * + * [Pie Chart Example] + * + * + * [Semi-Pie Chart Example with Center Text] + * + * + * [Line Chart Example (non-stacked, non-filled)] + * + * + * [Stacked Bar Chart Example] + * + * ``` + * + * @param id - Unique identifier for the chart canvas element + * @param type - Chart type: 'area', 'pie', 'semiPie', 'stackedBar', 'stackedBarHorizontal', or 'line' + * @param xAxisLabels - Array of labels for the x-axis (or categories for pie/semi-pie charts) + * @param datasets - Chart data: array of datasets for most charts, single dataset object for pie/semi-pie charts + * + * @performance + * **Memoization Recommendations:** + * - `xAxisLabels`: Should be memoized with useMemo() if derived from complex calculations + * - `datasets`: Should be memoized with useMemo() as it contains arrays and objects that cause re-renders + * - Avoid passing inline objects or arrays directly to these props + * + * @example + * ```tsx + * [Good: Memoized props prevent unnecessary re-renders] + * const labels = useMemo(() => quarters.map(q => `Q${q}`), [quarters]) + * const chartDatasets = useMemo(() => [ + * { + * datasetName: 'Revenue', + * yAxisValues: revenueData, + * backgroundColor: 'LavenderPurple300' + * } + * ], [revenueData]) + * + * return + * ``` + * + * @notes + * - Chart automatically re-renders when theme changes (light/dark mode) + * - Line charts are rendered as non-stacked and non-filled by default + * - Pie and semi-pie charts expect a single dataset object instead of an array + * - Semi-pie charts render as half-circles, ideal for gauges or progress indicators + * - Center text can be added to pie and semi-pie charts using the centerText prop + * - Colors should reference CHART_COLORS tokens for consistency + * - Component destroys and recreates Chart.js instance on prop changes for optimal performance + */ +const Chart = (props: ChartProps) => { + /** Using this technique for typing in transformDataForChart */ + const { + id, + xAxisLabels: labels, + hideAxis = false, + referenceLines, + tooltipConfig, + type, + datasets, + xAxisMax, + xScaleTitle, + yAxisMax, + yScaleTitle, + centerText, + } = props + const { getTooltipContent, placement } = tooltipConfig || { placement: 'top' } + + const canvasRef = useRef(null) + const chartRef = useRef(null) + const tooltipRef = useRef(null) + const legendRef = useRef(null) + + const [tooltipVisible, setTooltipVisible] = useState(false) + const [tooltipContent, setTooltipContent] = useState(null) + + /** Trigger a re-render when the theme changes to reflect the latest changes */ + const { appTheme } = useTheme() + + // NOTE: Do not refer any state variable inside this function, it will only have its initial value + const externalTooltipHandler: GetDefaultOptionsParams['externalTooltipHandler'] = (context) => { + const tooltipModel = context.tooltip + + if ( + !tooltipRef.current || + !chartRef.current || + !tooltipModel || + tooltipModel.opacity === 0 || + !tooltipModel.body + ) { + // Note: Not setting content to null, since would render empty tooltip on next hover for a split second + setTooltipVisible(false) + return + } + + // Move reference element to caret position + const { caretX, caretY } = tooltipModel + const { offsetLeft, offsetTop } = context.chart.canvas + tooltipRef.current.style.transform = `translate(${offsetLeft + caretX}px, ${offsetTop + caretY}px)` + tooltipRef.current.style.pointerEvents = 'none' + + const content = getTooltipContent + ? getTooltipContent(context) + : buildChartTooltipFromContext({ + title: tooltipModel.title, + body: tooltipModel.body, + labelColors: tooltipModel.labelColors, + }) + + setTooltipContent(content) + setTooltipVisible(true) + } + + const debouncedExternalTooltipHandler = useDebounce(externalTooltipHandler, 50) + + useEffect(() => { + const ctx = canvasRef.current?.getContext('2d') + if (!ctx) { + return noop + } + + // Get Chart.js type and transform data + const chartJSType = getChartJSType(type) + const transformedData = { + labels, + datasets: transformDataForChart({ ...({ type, datasets } as TypeAndDatasetsType), appTheme }), + } + const defaultOptions = getDefaultOptions({ + chartProps: props, + appTheme, + externalTooltipHandler: debouncedExternalTooltipHandler, + setTooltipVisible, + }) + + chartRef.current = new ChartJS(ctx, { + type: chartJSType, + data: transformedData, + options: { + ...defaultOptions, + }, + plugins: [ + ...(referenceLines ?? []).map((rl, idx) => drawReferenceLine(rl, `reference-line-${idx}`, appTheme)), + ...(centerText && (type === 'pie' || type === 'semiPie') ? [drawCenterText(centerText, appTheme)] : []), + ...(!hideAxis ? [htmlLegendPlugin(id, legendRef, type)] : []), + ], + }) + + return () => { + chartRef.current.destroy() + } + }, [ + type, + datasets, + labels, + appTheme, + hideAxis, + referenceLines, + xAxisMax, + xScaleTitle, + yAxisMax, + yScaleTitle, + centerText, + ]) + + return ( +
+
+ +
+ + {!hideAxis && ( +
+ )} + + + + +
+ ) +} + +export default Chart diff --git a/src/Shared/Components/Charts/constants.ts b/src/Shared/Components/Charts/constants.ts new file mode 100644 index 000000000..7531ec114 --- /dev/null +++ b/src/Shared/Components/Charts/constants.ts @@ -0,0 +1,346 @@ +import { AppThemeType } from '@Shared/Providers' + +import { ChartColorKey } from './types' + +export const CHART_COLORS: Record> = { + [AppThemeType.light]: { + // Sky Blue + SkyBlue50: 'rgba(238, 248, 255, 1)', + SkyBlue100: 'rgba(217, 239, 255, 1)', + SkyBlue200: 'rgba(188, 228, 255, 1)', + SkyBlue300: 'rgba(142, 212, 255, 1)', + SkyBlue400: 'rgba(89, 187, 255, 1)', + SkyBlue500: 'rgba(45, 153, 254, 1)', + SkyBlue600: 'rgba(28, 126, 244, 1)', + SkyBlue700: 'rgba(21, 102, 224, 1)', + SkyBlue800: 'rgba(24, 83, 181, 1)', + SkyBlue900: 'rgba(25, 72, 143, 1)', + SkyBlue950: 'rgba(20, 45, 87, 1)', + + // Aqua Teal + AquaTeal50: '#f0fdfa', + AquaTeal100: '#ccfbf1', + AquaTeal200: '#99f6e4', + AquaTeal300: '#5eead4', + AquaTeal400: '#2dd4bf', + AquaTeal500: '#14b8a6', + AquaTeal600: '#0d9488', + AquaTeal700: '#0f766e', + AquaTeal800: '#115e59', + AquaTeal900: '#134e4a', + AquaTeal950: '#042f2e', + + // Lavender Purple + Lavender50: '#faf5ff', + Lavender100: '#f3e8ff', + Lavender200: '#e9d5ff', + Lavender300: '#d8b4fe', + Lavender400: '#c084fc', + Lavender500: '#a855f7', + Lavender600: '#9333ea', + Lavender700: '#7c3aed', + Lavender800: '#6b21c8', + Lavender900: '#581c87', + Lavender950: '#3b0764', + + // Slate + Slate50: '#f8fafc', + Slate100: '#f1f5f9', + Slate200: '#e2e8f0', + Slate300: '#cbd5e1', + Slate400: '#94a3b8', + Slate500: '#64748b', + Slate600: '#475569', + Slate700: '#334155', + Slate800: '#1e293b', + Slate900: '#0f172a', + Slate950: '#020617', + + // Deep Plum + DeepPlum50: '#fdf2f8', + DeepPlum100: '#fdf2f8', + DeepPlum200: '#fce7f3', + DeepPlum300: '#f9a8d4', + DeepPlum400: '#f472b6', + DeepPlum500: '#ec4899', + DeepPlum600: '#db2777', + DeepPlum700: '#be185d', + DeepPlum800: '#9d174d', + DeepPlum900: '#831843', + DeepPlum950: '#500724', + + // Magenta + Magenta50: '#fdf2f8', + Magenta100: '#fdf2f8', + Magenta200: '#fce7f3', + Magenta300: '#f9a8d4', + Magenta400: '#f472b6', + Magenta500: '#ec4899', + Magenta600: '#db2777', + Magenta700: '#be185d', + Magenta800: '#9d174d', + Magenta900: '#831843', + Magenta950: '#500724', + + // Lime Green + LimeGreen50: 'rgba(245, 252, 233, 1)', + LimeGreen100: 'rgba(233, 247, 208, 1)', + LimeGreen200: 'rgba(212, 239, 167, 1)', + LimeGreen300: 'rgba(191, 231, 130, 1)', + LimeGreen400: 'rgba(168, 227, 74, 1)', + LimeGreen500: 'rgba(148, 209, 61, 1)', + LimeGreen600: 'rgba(114, 178, 36, 1)', + LimeGreen700: 'rgba(74, 132, 26, 1)', + LimeGreen800: 'rgba(65, 99, 29, 1)', + LimeGreen900: 'rgba(51, 76, 27, 1)', + LimeGreen950: 'rgba(25, 42, 9, 1)', + + // Coral Red + CoralRed50: 'rgba(254, 242, 243, 1)', + CoralRed100: 'rgba(254, 226, 228, 1)', + CoralRed200: 'rgba(255, 201, 205, 1)', + CoralRed300: 'rgba(247, 171, 182, 1)', + CoralRed400: 'rgba(244, 134, 147, 1)', + CoralRed500: 'rgba(249, 93, 106, 1)', + CoralRed600: 'rgba(207, 48, 59, 1)', + CoralRed700: 'rgba(183, 26, 39, 1)', + CoralRed800: 'rgba(154, 24, 35, 1)', + CoralRed900: 'rgba(121, 27, 35, 1)', + CoralRed950: 'rgba(70, 9, 14, 1)', + + // Golden Yellow + GoldenYellow50: 'rgba(255, 254, 234, 1)', + GoldenYellow100: 'rgba(255, 248, 197, 1)', + GoldenYellow200: 'rgba(255, 242, 133, 1)', + GoldenYellow300: 'rgba(255, 229, 70, 1)', + GoldenYellow400: 'rgba(255, 212, 26, 1)', + GoldenYellow500: 'rgba(255, 179, 0, 1)', + GoldenYellow600: 'rgba(226, 137, 0, 1)', + GoldenYellow700: 'rgba(187, 96, 2, 1)', + GoldenYellow800: 'rgba(152, 74, 7, 1)', + GoldenYellow900: 'rgba(124, 59, 11, 1)', + GoldenYellow950: 'rgba(72, 30, 0, 1)', + + // Charcoal Gray + CharcoalGray50: 'rgba(246, 246, 246, 1)', + CharcoalGray100: 'rgba(231, 231, 231, 1)', + CharcoalGray200: 'rgba(209, 209, 209, 1)', + CharcoalGray300: 'rgba(176, 176, 176, 1)', + CharcoalGray400: 'rgba(136, 136, 136, 1)', + CharcoalGray500: 'rgba(109, 109, 109, 1)', + CharcoalGray600: 'rgba(93, 93, 93, 1)', + CharcoalGray700: 'rgba(76, 76, 76, 1)', + CharcoalGray800: '#334155', + CharcoalGray900: '#1e293b', + CharcoalGray950: '#0f172a', + + // Gray + Gray50: '#f9fafb', + Gray100: '#f7f8f9', + Gray200: '#f1f3f4', + Gray300: '#e8eaed', + Gray400: '#dadce0', + Gray500: '#9aa0a6', + Gray600: '#5f6368', + Gray700: '#3c4043', + Gray800: '#202124', + Gray900: '#1a1a1a', + Gray950: '#0d0d0d', + + // Sunset Orange + SunsetOrange50: '#FFF3ED', + SunsetOrange100: '#FFE7D4', + SunsetOrange200: '#FFCAA8', + SunsetOrange300: '#FFAE80', + SunsetOrange400: '#FF905A', + SunsetOrange500: '#FF7C43', + SunsetOrange600: '#F5572A', + SunsetOrange700: '#D22E10', + SunsetOrange800: '#A81F0D', + SunsetOrange900: '#7E1C10', + SunsetOrange950: '#411311', + }, + [AppThemeType.dark]: { + // Sky Blue - Adjusted for dark theme + SkyBlue50: '#0c1420', + SkyBlue100: '#0f2027', + SkyBlue200: '#164e63', + SkyBlue300: '#0369a1', + SkyBlue400: '#0284c7', + SkyBlue500: '#0ea5e9', + SkyBlue600: '#38bdf8', + SkyBlue700: '#7dd3fc', + SkyBlue800: '#bae6fd', + SkyBlue900: '#e0f2fe', + SkyBlue950: '#f0f9ff', + + // Aqua Teal - Adjusted for dark theme + AquaTeal50: '#042f2e', + AquaTeal100: '#134e4a', + AquaTeal200: '#115e59', + AquaTeal300: '#0f766e', + AquaTeal400: '#0d9488', + AquaTeal500: '#14b8a6', + AquaTeal600: '#2dd4bf', + AquaTeal700: '#5eead4', + AquaTeal800: '#99f6e4', + AquaTeal900: '#ccfbf1', + AquaTeal950: '#f0fdfa', + + // Lavender Purple - Adjusted for dark theme + Lavender50: '#3b0764', + Lavender100: '#581c87', + Lavender200: '#6b21c8', + Lavender300: '#7c3aed', + Lavender400: '#9333ea', + Lavender500: '#a855f7', + Lavender600: '#c084fc', + Lavender700: '#d8b4fe', + Lavender800: '#e9d5ff', + Lavender900: '#f3e8ff', + Lavender950: '#faf5ff', + + // Slate - Adjusted for dark theme + Slate50: '#020617', + Slate100: '#0f172a', + Slate200: '#1e293b', + Slate300: '#334155', + Slate400: '#475569', + Slate500: '#64748b', + Slate600: '#94a3b8', + Slate700: '#cbd5e1', + Slate800: '#e2e8f0', + Slate900: '#f1f5f9', + Slate950: '#f8fafc', + + // Deep Plum - Adjusted for dark theme + DeepPlum50: '#500724', + DeepPlum100: '#831843', + DeepPlum200: '#9d174d', + DeepPlum300: '#be185d', + DeepPlum400: '#db2777', + DeepPlum500: '#ec4899', + DeepPlum600: '#f472b6', + DeepPlum700: '#f9a8d4', + DeepPlum800: '#fce7f3', + DeepPlum900: '#fdf2f8', + DeepPlum950: '#fdf2f8', + + // Magenta - Adjusted for dark theme + Magenta50: '#500724', + Magenta100: '#831843', + Magenta200: '#9d174d', + Magenta300: '#be185d', + Magenta400: '#db2777', + Magenta500: '#ec4899', + Magenta600: '#f472b6', + Magenta700: '#f9a8d4', + Magenta800: '#fce7f3', + Magenta900: '#fdf2f8', + Magenta950: '#fdf2f8', + + // Lime Green - Adjusted for dark theme + LimeGreen50: '#1a2e05', + LimeGreen100: '#365314', + LimeGreen200: '#4d7c0f', + LimeGreen300: '#65a30d', + LimeGreen400: '#84cc16', + LimeGreen500: '#a3e635', + LimeGreen600: '#bef264', + LimeGreen700: '#d9f99d', + LimeGreen800: '#ecfccb', + LimeGreen900: '#f7fee7', + LimeGreen950: '#f7fee7', + + // Coral Red - Adjusted for dark theme + CoralRed50: '#450a0a', + CoralRed100: '#7f1d1d', + CoralRed200: '#991b1b', + CoralRed300: '#b91c1c', + CoralRed400: '#dc2626', + CoralRed500: '#ef4444', + CoralRed600: '#f87171', + CoralRed700: '#fca5a5', + CoralRed800: '#fecaca', + CoralRed900: '#fef2f2', + CoralRed950: '#fef2f2', + + // Golden Yellow - Adjusted for dark theme + GoldenYellow50: '#422006', + GoldenYellow100: '#713f12', + GoldenYellow200: '#854d0e', + GoldenYellow300: '#a16207', + GoldenYellow400: '#ca8a04', + GoldenYellow500: '#eab308', + GoldenYellow600: '#facc15', + GoldenYellow700: '#fde047', + GoldenYellow800: '#fef3c7', + GoldenYellow900: '#fefce8', + GoldenYellow950: '#fefce8', + + // Charcoal Gray - Adjusted for dark theme + CharcoalGray50: '#0f172a', + CharcoalGray100: '#1e293b', + CharcoalGray200: '#334155', + CharcoalGray300: '#475569', + CharcoalGray400: '#64748b', + CharcoalGray500: '#94a3b8', + CharcoalGray600: '#cbd5e1', + CharcoalGray700: '#e2e8f0', + CharcoalGray800: '#f1f5f9', + CharcoalGray900: '#f8fafc', + CharcoalGray950: '#f8fafc', + + // Gray - Adjusted for dark theme + Gray50: '#0d0d0d', + Gray100: '#1a1a1a', + Gray200: '#202124', + Gray300: '#3c4043', + Gray400: '#5f6368', + Gray500: '#9aa0a6', + Gray600: '#dadce0', + Gray700: '#e8eaed', + Gray800: '#f1f3f4', + Gray900: '#f7f8f9', + Gray950: '#f9fafb', + + // Sunset Orange - Adjusted for dark theme + SunsetOrange50: '#2F1D1E', + SunsetOrange100: '#53301F', + SunsetOrange200: '#794421', + SunsetOrange300: '#9C5623', + SunsetOrange400: '#C36A25', + SunsetOrange500: '#E87D27', + SunsetOrange600: '#EC9345', + SunsetOrange700: '#F1A862', + SunsetOrange800: '#F4BD7F', + SunsetOrange900: '#F8D29C', + SunsetOrange950: '#FDE9BC', + }, +} as const + +export const CHART_GRID_LINES_COLORS: Record = { + [AppThemeType.light]: 'rgb(237, 241, 245)', + [AppThemeType.dark]: 'rgb(40, 43, 51)', +} + +export const CHART_AXIS_COLORS: Record = { + [AppThemeType.light]: 'rgb(208, 212, 217)', + [AppThemeType.dark]: 'rgb(60, 63, 71)', +} + +export const CHART_AXIS_LABELS_COLOR: Record = { + [AppThemeType.dark]: 'rgb(248, 248, 249)', + [AppThemeType.light]: 'rgb(0, 10, 20)', +} + +export const CHART_CANVAS_BACKGROUND_COLORS: Record = { + [AppThemeType.light]: 'rgb(255, 255, 255)', + [AppThemeType.dark]: 'rgb(30, 31, 40)', +} + +export const LINE_DASH = [6, 6] + +export const MAX_BAR_THICKNESS = 96 + +export const DIS_JOINT_CHART_POINT_RADIUS = 4 diff --git a/src/Shared/Components/Charts/index.ts b/src/Shared/Components/Charts/index.ts new file mode 100644 index 000000000..8c8c9dff4 --- /dev/null +++ b/src/Shared/Components/Charts/index.ts @@ -0,0 +1,12 @@ +export { default as Chart } from './Chart.component' +export { CHART_COLORS } from './constants' +export type { + CenterTextConfig, + ChartColorKey, + ChartProps, + ChartType, + ReferenceLineConfigType, + SimpleDataset, + SimpleDatasetForPie, +} from './types' +export { chartColorGenerator } from './utils' diff --git a/src/Shared/Components/Charts/plugins.tsx b/src/Shared/Components/Charts/plugins.tsx new file mode 100644 index 000000000..9fa587c2f --- /dev/null +++ b/src/Shared/Components/Charts/plugins.tsx @@ -0,0 +1,98 @@ +import { RefObject } from 'react' +import { render } from 'react-dom' +import { LegendItem, Plugin } from 'chart.js' + +import { Tooltip } from '@Common/Tooltip' +import { AppThemeType } from '@Shared/Providers' + +import { CHART_AXIS_LABELS_COLOR, CHART_COLORS } from './constants' +import { CenterTextConfig, ChartType, HTMLLegendProps, ReferenceLineConfigType } from './types' + +export const drawReferenceLine = (config: ReferenceLineConfigType, id: string, appTheme: AppThemeType): Plugin => ({ + id, + afterDraw: (chart) => { + const { ctx, chartArea, scales } = chart + if (!scales || !scales.y || !config?.value) { + return + } + const yValue = scales.y.getPixelForValue(config.value) + ctx.save() + ctx.beginPath() + ctx.setLineDash([6, 6]) + ctx.strokeStyle = CHART_COLORS[appTheme][config.color ?? 'CharcoalGray700'] + ctx.lineWidth = config.strokeWidth ?? 1 + ctx.moveTo(chartArea.left, yValue) + ctx.lineTo(chartArea.right, yValue) + ctx.stroke() + ctx.setLineDash([]) + ctx.restore() + }, +}) + +const HTMLLegend = ({ backgroundColor, label, onClick, strikeThrough, variant }: HTMLLegendProps) => ( + +) + +export const htmlLegendPlugin = (id: string, ref: RefObject, type: ChartType): Plugin => ({ + id, + afterUpdate(chart) { + const getOnClickHandler = (item: LegendItem) => () => { + if (type === 'pie') { + // Pie and doughnut charts only have a single dataset and visibility is per item + chart.toggleDataVisibility(item.index) + } else { + chart.setDatasetVisibility(item.datasetIndex, !chart.isDatasetVisible(item.datasetIndex)) + } + chart.update() + } + + render( + chart.options.plugins.legend.labels + .generateLabels(chart) + .map((item) => ( + + )), + ref.current, + ) + }, +}) + +export const drawCenterText = (config: CenterTextConfig, appTheme: AppThemeType): Plugin => ({ + id: 'centerText', + afterDraw: (chart) => { + const { ctx, chartArea } = chart + if (!config?.text) { + return + } + + const centerX = (chartArea.left + chartArea.right) / 2 + + ctx.save() + ctx.font = `${config.fontWeight || '600'} ${config.fontSize || 24}px ${config.fontFamily || "'IBM Plex Sans', 'Open Sans', 'Roboto'"}` + ctx.fillStyle = config.color || CHART_AXIS_LABELS_COLOR[appTheme] + ctx.textAlign = 'center' + ctx.textBaseline = 'middle' + ctx.fillText(config.text, centerX, chartArea.bottom - 20) + ctx.restore() + }, +}) diff --git a/src/Shared/Components/Charts/types.ts b/src/Shared/Components/Charts/types.ts new file mode 100644 index 000000000..00304ec0e --- /dev/null +++ b/src/Shared/Components/Charts/types.ts @@ -0,0 +1,181 @@ +import { Dispatch, ReactNode, SetStateAction } from 'react' +import { TooltipOptions, TooltipPositionerFunction } from 'chart.js' + +import { TooltipProps } from '@Common/Tooltip' +import { AppThemeType } from '@Shared/Providers' +import { Never } from '@Shared/types' + +export type ChartType = 'area' | 'pie' | 'semiPie' | 'stackedBar' | 'stackedBarHorizontal' | 'line' + +export type ColorTokensType = + | 'DeepPlum' + | 'Magenta' + | 'Slate' + | 'Lavender' + | 'SkyBlue' + | 'AquaTeal' + | 'LimeGreen' + | 'CoralRed' + | 'GoldenYellow' + | 'CharcoalGray' + | 'Gray' + | 'SunsetOrange' +export type VariantsType = 50 | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | 950 + +export type ChartColorKey = `${ColorTokensType}${VariantsType}` + +export type ChartTypeWithoutPie = Exclude + +interface BaseSimpleDataset { + datasetName: string + yAxisValues: number[] +} + +export interface SimpleDataset extends BaseSimpleDataset { + color: ChartColorKey + isClickable?: boolean +} + +export interface SimpleDatasetForLineAndArea extends Omit { + isDashed?: boolean +} + +export interface SimpleDatasetForPie extends BaseSimpleDataset { + colors: Array + isClickable?: boolean[] +} + +export interface ReferenceLineConfigType { + strokeWidth?: number + color?: ChartColorKey + value: number +} + +export interface CenterTextConfig { + text: string + fontSize?: number + fontWeight?: string | number + color?: string + fontFamily?: string +} + +type XYAxisMax = { + xAxisMax?: number + yAxisMax?: number + /** + * Optional reference lines to draw across the chart + */ + referenceLines?: ReferenceLineConfigType[] +} + +type OnChartClickHandler = (datasetName: string, value: number) => void + +type ScaleTickFormatCallbacks = Partial<{ + xScaleTickFormat: (label: string, index: number) => string | string[] | number | number[] + yScaleTickFormat: (value: number, index: number) => string | string[] | number | number[] +}> + +export type TypeAndDatasetsType = + | ({ + type: 'pie' | 'semiPie' + /** + * Needs to be memoized + */ + datasets: SimpleDatasetForPie + onChartClick?: OnChartClickHandler + /** Configuration for center text (only for doughnut/pie charts) */ + centerText?: CenterTextConfig + } & Never & + Never) + | ({ + type: 'line' + datasets: SimpleDatasetForLineAndArea[] + onChartClick?: never + centerText?: never + } & XYAxisMax & + ScaleTickFormatCallbacks) + | ({ + type: 'area' + datasets: SimpleDatasetForLineAndArea + /* onChartClick is not applicable for area charts */ + onChartClick?: never + centerText?: never + } & XYAxisMax & + ScaleTickFormatCallbacks) + | ({ + type: Exclude + datasets: SimpleDataset[] + onChartClick?: OnChartClickHandler + centerText?: never + } & XYAxisMax & + ScaleTickFormatCallbacks) + +export type ChartProps = { + id: string + /** + * The x-axis labels. Needs to be memoized + */ + xAxisLabels: string[] + /** + * Hide the x and y axis and grid lines + * @default false + */ + hideAxis?: boolean + hideXAxisLabels?: boolean + tooltipConfig?: { + getTooltipContent?: (args: Parameters[0]) => ReactNode + /** + * @default 'top' + */ + placement?: TooltipProps['placement'] + datasetValueFormatter?: (value: number) => string | number + } + /** A title for x axis */ + xScaleTitle?: string + /** A title for y axis */ + yScaleTitle?: string +} & TypeAndDatasetsType + +export type TransformDatasetProps = { + appTheme: AppThemeType +} & ( + | { + type: 'pie' | 'semiPie' + dataset: SimpleDatasetForPie + } + | { + type: 'line' | 'area' + dataset: SimpleDatasetForLineAndArea + } + | { + type: Exclude + dataset: SimpleDataset + } +) + +export type GetBackgroundAndBorderColorProps = TransformDatasetProps + +export type TransformDataForChartProps = { + appTheme: AppThemeType +} & TypeAndDatasetsType + +export interface GetDefaultOptionsParams { + chartProps: ChartProps + appTheme: AppThemeType + externalTooltipHandler: TooltipOptions['external'] + setTooltipVisible: Dispatch> +} + +declare module 'chart.js' { + interface TooltipPositionerMap { + barElementCenterPositioner: TooltipPositionerFunction<'bar'> + } +} + +export interface HTMLLegendProps { + backgroundColor: string + label: string + onClick: () => void + strikeThrough: boolean + variant: 'line' | 'square' +} diff --git a/src/Shared/Components/Charts/utils.tsx b/src/Shared/Components/Charts/utils.tsx new file mode 100644 index 000000000..516aa1687 --- /dev/null +++ b/src/Shared/Components/Charts/utils.tsx @@ -0,0 +1,586 @@ +import { ReactNode } from 'react' +import { + ActiveElement, + ChartDataset, + ChartOptions, + ChartType as ChartJSChartType, + Point, + ScaleOptions, + TooltipLabelStyle, + TooltipOptions, +} from 'chart.js' + +import { AppThemeType } from '@Shared/Providers' + +import { + CHART_AXIS_COLORS, + CHART_AXIS_LABELS_COLOR, + CHART_CANVAS_BACKGROUND_COLORS, + CHART_COLORS, + CHART_GRID_LINES_COLORS, + DIS_JOINT_CHART_POINT_RADIUS, + LINE_DASH, + MAX_BAR_THICKNESS, +} from './constants' +import { + ChartColorKey, + ChartProps, + ChartType, + ColorTokensType, + GetBackgroundAndBorderColorProps, + GetDefaultOptionsParams, + SimpleDatasetForLineAndArea, + TransformDataForChartProps, + TransformDatasetProps, + VariantsType, +} from './types' + +// Map our chart types to Chart.js types +export const getChartJSType = (type: ChartType): ChartJSChartType => { + switch (type) { + case 'area': + case 'line': + return 'line' + case 'pie': + case 'semiPie': + return 'doughnut' + case 'stackedBar': + case 'stackedBarHorizontal': + return 'bar' + default: + return type as ChartJSChartType + } +} + +const handleChartClick = + ({ + type, + onChartClick, + datasets, + xAxisLabels, + setTooltipVisible, + }: ChartProps & Pick) => + (_, elements: ActiveElement[]) => { + if (!elements || elements.length === 0 || !datasets || (Array.isArray(datasets) && datasets.length === 0)) { + return + } + + const { datasetIndex, index } = elements[0] + + if (type === 'pie' || type === 'semiPie') { + if (!datasets.isClickable?.[index]) { + return + } + + onChartClick?.(xAxisLabels[index], index) + } else if (type !== 'area' && type !== 'line') { + if (!datasets[datasetIndex]?.isClickable) { + return + } + + onChartClick?.(datasets[datasetIndex].datasetName, index) + } + setTooltipVisible(false) + } + +const handleChartHover = + ({ type, datasets }: ChartProps): ChartOptions['onHover'] => + (_, elements: ActiveElement[], chart) => { + const { canvas } = chart + + if (!elements || elements.length === 0 || !datasets || (Array.isArray(datasets) && datasets.length === 0)) { + // eslint-disable-next-line no-param-reassign + canvas.style.cursor = 'default' + return + } + + const { datasetIndex, index } = elements[0] + + if (type === 'pie' || type === 'semiPie') { + if (!datasets.isClickable?.[index]) { + // eslint-disable-next-line no-param-reassign + canvas.style.cursor = 'default' + return + } + + // eslint-disable-next-line no-param-reassign + canvas.style.cursor = 'pointer' + } else if (type !== 'area' && type !== 'line') { + if (!datasets[datasetIndex]?.isClickable) { + // eslint-disable-next-line no-param-reassign + canvas.style.cursor = 'default' + return + } + + // eslint-disable-next-line no-param-reassign + canvas.style.cursor = 'pointer' + } + } + +const getScaleTickTitleConfig = (title: string, appTheme: AppThemeType): ScaleOptions<'linear'>['title'] => ({ + display: !!title, + text: title, + align: 'center', + font: { + family: "'IBM Plex Sans', 'Open Sans', 'Roboto'", + size: 13, + lineHeight: '150%', + weight: 400, + }, + color: CHART_AXIS_LABELS_COLOR[appTheme], +}) + +// Get default options based on chart type +export const getDefaultOptions = ({ + chartProps, + appTheme, + externalTooltipHandler, + setTooltipVisible, +}: GetDefaultOptionsParams): ChartOptions => { + const { + onChartClick, + type, + hideAxis, + hideXAxisLabels = false, + xAxisMax, + yAxisMax, + xScaleTitle, + yScaleTitle, + yScaleTickFormat, + xScaleTickFormat, + xAxisLabels, + tooltipConfig, + } = chartProps + + const { datasetValueFormatter } = tooltipConfig ?? {} + + const baseOptions: ChartOptions = { + responsive: true, + devicePixelRatio: 3, + maintainAspectRatio: false, + plugins: { + legend: { + display: false, + }, + title: { + display: false, + }, + tooltip: { + enabled: false, + position: 'nearest', + external: externalTooltipHandler, + ...(datasetValueFormatter + ? { + callbacks: { + label({ dataset, raw }) { + // only number values are expected in raw as input in our types for yAxisValues is number[] + return `${dataset.label}: ${datasetValueFormatter(raw as number)}` + }, + }, + } + : {}), + }, + }, + elements: { + line: { + fill: type === 'area', + tension: 0, + }, + bar: { + borderSkipped: 'start' as const, + borderWidth: 2, + borderColor: 'transparent', + borderRadius: 4, + }, + }, + ...(onChartClick + ? { + onClick: handleChartClick({ ...chartProps, setTooltipVisible }), + onHover: handleChartHover(chartProps), + } + : {}), + } + + const commonScaleConfig = { + display: !hideAxis, + border: { + color: CHART_AXIS_COLORS[appTheme], + }, + grid: { + color: CHART_GRID_LINES_COLORS[appTheme], + }, + ticks: { + display: !hideXAxisLabels, + color: CHART_AXIS_LABELS_COLOR[appTheme], + font: { + family: "'IBM Plex Sans', 'Open Sans', 'Roboto'", + size: 12, + lineHeight: '150%', + weight: 400, + }, + }, + } satisfies ScaleOptions<'linear'> + + const commonXScaleConfig = { + ...commonScaleConfig, + max: xAxisMax, + title: getScaleTickTitleConfig(xScaleTitle, appTheme), + ticks: { + ...commonScaleConfig.ticks, + ...((type !== 'stackedBarHorizontal' && typeof xScaleTickFormat === 'function') || + (type === 'stackedBarHorizontal' && typeof yScaleTickFormat === 'function') + ? { + callback: + type === 'stackedBarHorizontal' + ? (value, index) => yScaleTickFormat(Number(value), index) + : (_, index) => xScaleTickFormat(xAxisLabels[index], index), + } + : {}), + autoSkip: false, + }, + } satisfies ScaleOptions<'linear'> + + const commonYScaleConfig = { + ...commonScaleConfig, + max: yAxisMax, + title: getScaleTickTitleConfig(yScaleTitle, appTheme), + // for stackedBarHorizon + ticks: { + ...commonScaleConfig.ticks, + ...((type === 'stackedBarHorizontal' && typeof xScaleTickFormat === 'function') || + (type !== 'stackedBarHorizontal' && typeof yScaleTickFormat === 'function') + ? { + callback: + type !== 'stackedBarHorizontal' + ? (value, index) => yScaleTickFormat(Number(value), index) + : (_, index) => xScaleTickFormat(xAxisLabels[index], index), + } + : {}), + }, + } satisfies ScaleOptions<'linear'> + + const commonPieConfig = { + ...baseOptions, + plugins: { + ...baseOptions.plugins, + legend: { + display: false, + labels: { + textAlign: 'left', + }, + position: 'right', + }, + }, + cutout: '60%', + radius: '100%', + } + + switch (type) { + case 'area': + case 'line': + return { + ...baseOptions, + plugins: { + ...baseOptions.plugins, + tooltip: { + mode: 'index', + ...baseOptions.plugins.tooltip, + }, + }, + interaction: { + axis: 'x', + intersect: false, + }, + scales: { + y: { + ...commonYScaleConfig, + stacked: type === 'area', + beginAtZero: true, + }, + x: commonXScaleConfig, + }, + } satisfies ChartOptions<'line'> + case 'stackedBar': + case 'stackedBarHorizontal': + return { + ...baseOptions, + plugins: { + ...baseOptions.plugins, + tooltip: { + ...baseOptions.plugins.tooltip, + position: 'barElementCenterPositioner', + }, + }, + scales: { + x: { + ...commonXScaleConfig, + stacked: true, + }, + y: { + ...commonYScaleConfig, + stacked: true, + }, + }, + datasets: { + bar: { + maxBarThickness: MAX_BAR_THICKNESS, + }, + }, + indexAxis: type === 'stackedBarHorizontal' ? 'y' : 'x', + } satisfies ChartOptions<'bar'> + case 'pie': + return commonPieConfig as ChartOptions<'doughnut'> + case 'semiPie': + return { + ...commonPieConfig, + cutout: '70%', + rotation: -90, + circumference: 180, + } as ChartOptions<'doughnut'> + default: + return baseOptions + } +} + +// Get color value from chart color key +const getColorValue = (colorKey: ChartColorKey, appTheme: AppThemeType): string => CHART_COLORS[appTheme][colorKey] + +// Generates a slightly darker shade for a given color key +const getDarkerShadeBy = (colorKey: ChartColorKey, appTheme: AppThemeType, delta: VariantsType = 100): string => { + // Extract the base color name and shade number + const colorName = colorKey.replace(/\d+$/, '') + const shadeMatch = colorKey.match(/\d+$/) + const currentShade = shadeMatch ? parseInt(shadeMatch[0], 10) : 500 + + // Try to get a darker shade (higher number) + const darkerShade = Math.min(currentShade + delta, 900) + const borderColorKey = `${colorName}${darkerShade}` as ChartColorKey + + // If the darker shade exists, use it; otherwise, use the current color + return CHART_COLORS[appTheme][borderColorKey] || CHART_COLORS[appTheme][colorKey] +} + +const getBackgroundAndBorderColor = ({ type, dataset, appTheme }: GetBackgroundAndBorderColorProps) => { + if (type === 'pie' || type === 'semiPie') { + return { + backgroundColor: dataset.colors.map((colorKey) => getColorValue(colorKey, appTheme)), + hoverBackgroundColor: dataset.colors.map((colorKey) => getDarkerShadeBy(colorKey, appTheme)), + borderColor: CHART_CANVAS_BACKGROUND_COLORS[appTheme], + borderWidth: 1, + } satisfies Pick< + ChartDataset<'doughnut'>, + 'hoverBackgroundColor' | 'backgroundColor' | 'borderColor' | 'borderWidth' + > + } + + if (type === 'stackedBar' || type === 'stackedBarHorizontal') { + return { + backgroundColor: getColorValue(dataset.color, appTheme), + hoverBackgroundColor: getDarkerShadeBy(dataset.color, appTheme), + borderColor: 'transparent', + } satisfies Pick, 'backgroundColor' | 'borderColor' | 'hoverBackgroundColor'> + } + + if (type === 'line') { + const borderColor = getColorValue(dataset.color, appTheme) + + return { + backgroundColor: borderColor, + borderColor, + pointBackgroundColor: borderColor, + pointBorderColor: borderColor, + } satisfies Pick< + ChartDataset<'line'>, + 'backgroundColor' | 'borderColor' | 'pointBackgroundColor' | 'pointBorderColor' + > + } + + // At this point, we know it's an area chart (not pie/semiPie) + const areaDataset = dataset as SimpleDatasetForLineAndArea + const bgColor = getColorValue(areaDataset.color, appTheme) + + return { + backgroundColor(context) { + const { ctx, chartArea } = context.chart + + if (!chartArea) { + // happens on initial render + return null + } + + const gradient = ctx.createLinearGradient(0, chartArea.top, 0, chartArea.bottom) + gradient.addColorStop(0, bgColor) + gradient.addColorStop(1, CHART_CANVAS_BACKGROUND_COLORS[appTheme]) + + return gradient + }, + borderColor: getDarkerShadeBy(areaDataset.color, appTheme), + pointBackgroundColor: bgColor, + pointBorderColor: bgColor, + } as Pick, 'backgroundColor' | 'borderColor' | 'pointBackgroundColor' | 'pointBorderColor'> +} + +const transformDataset = (props: TransformDatasetProps) => { + const { dataset, type } = props + + const styles = getBackgroundAndBorderColor(props) + + const baseDataset = { + label: dataset.datasetName, + data: dataset.yAxisValues, + ...styles, + } + + const commonLineAndAreaConfig = { + ...baseDataset, + fill: type === 'area', + pointRadius: ({ dataIndex }) => { + if (dataIndex === 0 && Number.isNaN(dataset.yAxisValues[dataIndex + 1])) { + return DIS_JOINT_CHART_POINT_RADIUS + } + if (dataIndex === dataset.yAxisValues.length - 1 && Number.isNaN(dataset.yAxisValues[dataIndex - 1])) { + return DIS_JOINT_CHART_POINT_RADIUS + } + return Number.isNaN(dataset.yAxisValues[dataIndex - 1]) && Number.isNaN(dataset.yAxisValues[dataIndex + 1]) + ? DIS_JOINT_CHART_POINT_RADIUS + : 0 + }, + pointHoverRadius: 8, + pointHitRadius: 20, + pointStyle: 'rectRounded', + borderWidth: 2, + } as ChartDataset<'line'> + + switch (type) { + case 'line': + return { + ...commonLineAndAreaConfig, + borderDash: dataset.isDashed ? LINE_DASH : undefined, + } satisfies ChartDataset<'line'> + case 'area': + return commonLineAndAreaConfig + case 'pie': + case 'stackedBar': + case 'stackedBarHorizontal': + default: + return baseDataset + } +} + +export const transformDataForChart = (props: TransformDataForChartProps) => { + const { type, datasets, appTheme } = props + + if (!datasets) { + // eslint-disable-next-line no-console + console.error('No datasets provided for chart transformation') + return [] + } + + if (type !== 'pie' && type !== 'semiPie' && type !== 'area' && !Array.isArray(datasets)) { + // eslint-disable-next-line no-console + console.error('Invalid datasets format. Expected an array.') + return [] + } + + switch (type) { + /** Not not clubbing it with the default case for better typing */ + case 'pie': + case 'semiPie': + return [transformDataset({ type, dataset: datasets, appTheme })] + case 'area': + return [transformDataset({ type, dataset: datasets, appTheme })] + case 'line': + return datasets.map((dataset) => transformDataset({ type, dataset, appTheme })) + default: + return datasets.map((dataset) => transformDataset({ type, dataset, appTheme })) + } +} + +export function* chartColorGenerator() { + const WEIGHTS: VariantsType[] = [400, 500, 600, 700, 300, 800, 200, 900, 100, 50, 950] + const TOKENS: ColorTokensType[] = [ + 'SkyBlue', + 'DeepPlum', + 'AquaTeal', + 'GoldenYellow', + 'Lavender', + 'CharcoalGray', + 'Magenta', + 'CoralRed', + 'LimeGreen', + 'Slate', + 'Gray', + ] + + for (let i = 0; i < WEIGHTS.length; i++) { + for (let j = 0; j < TOKENS.length; j++) { + // Yield a color key like 'SkyBlue500' + yield `${TOKENS[j]}${WEIGHTS[i]}` as ChartColorKey + } + } +} + +const getBorderRadiusForTooltip = (labelBorderRadius: TooltipLabelStyle['borderRadius']) => { + if (!labelBorderRadius) { + return null + } + + if (typeof labelBorderRadius === 'number') { + return `${labelBorderRadius}px` + } + + return `${labelBorderRadius.topLeft}px ${labelBorderRadius.topRight}px ${labelBorderRadius.bottomRight}px ${labelBorderRadius.bottomLeft}px` +} + +export const buildChartTooltipFromContext = ({ + title, + body, + labelColors: labelColorsProp, +}: Pick[0]['tooltip'], 'title' | 'body' | 'labelColors'>): ReactNode => { + const titleLines = title || [] + const bodyLines = body.map((b) => b.lines) + const labelColors = labelColorsProp || [] + + return ( +
+
+ {titleLines.map((titleLine, index) => ( + // eslint-disable-next-line react/no-array-index-key +
+ {titleLine} +
+ ))} + + {/* Will show a rounded label color and next it will paste bodyline */} + {bodyLines.map((bodyLine, index) => ( + // eslint-disable-next-line react/no-array-index-key +
+ {labelColors[index] && + typeof labelColors[index].backgroundColor === 'string' && + typeof labelColors[index].borderColor === 'string' && ( + + )} + {bodyLine} +
+ ))} +
+
+ ) +} + +export const distanceBetweenPoints = (pt1: Point, pt2: Point) => Math.sqrt((pt2.x - pt1.x) ** 2 + (pt2.y - pt1.y) ** 2) diff --git a/src/Shared/Components/ClusterStatusIcon/ClusterStatusIcon.tsx b/src/Shared/Components/ClusterStatusIcon/ClusterStatusIcon.tsx new file mode 100644 index 000000000..73cdf6fee --- /dev/null +++ b/src/Shared/Components/ClusterStatusIcon/ClusterStatusIcon.tsx @@ -0,0 +1,20 @@ +import { Icon } from '../Icon' +import { ClusterStatusIconProps } from './types' +import { getBulletColorAccToStatus } from './utils' + +const ClusterStatusIcon = ({ clusterStatus, isVirtualCluster }: ClusterStatusIconProps) => { + const statusColor = getBulletColorAccToStatus(clusterStatus) + return ( + + + {!isVirtualCluster && ( + + )} + + ) +} + +export default ClusterStatusIcon diff --git a/src/Shared/Components/ClusterStatusIcon/index.ts b/src/Shared/Components/ClusterStatusIcon/index.ts new file mode 100644 index 000000000..1aac22393 --- /dev/null +++ b/src/Shared/Components/ClusterStatusIcon/index.ts @@ -0,0 +1 @@ +export { default as ClusterStatusIcon } from './ClusterStatusIcon' diff --git a/src/Shared/Components/ClusterStatusIcon/types.ts b/src/Shared/Components/ClusterStatusIcon/types.ts new file mode 100644 index 000000000..cc736d942 --- /dev/null +++ b/src/Shared/Components/ClusterStatusIcon/types.ts @@ -0,0 +1,6 @@ +import { ClusterStatusType } from '@Pages/ResourceBrowser' + +export interface ClusterStatusIconProps { + clusterStatus: ClusterStatusType + isVirtualCluster: boolean +} diff --git a/src/Shared/Components/ClusterStatusIcon/utils.ts b/src/Shared/Components/ClusterStatusIcon/utils.ts new file mode 100644 index 000000000..20ce20bed --- /dev/null +++ b/src/Shared/Components/ClusterStatusIcon/utils.ts @@ -0,0 +1,12 @@ +import { ClusterStatusType } from '@Pages/ResourceBrowser' + +export const getBulletColorAccToStatus = (status: ClusterStatusType) => { + switch (status) { + case ClusterStatusType.HEALTHY: + return 'bcg-5' + case ClusterStatusType.UNHEALTHY: + return 'bcy-5' + default: + return 'bcr-5' + } +} diff --git a/src/Shared/Components/DTFocusTrap/DTFocusTrap.tsx b/src/Shared/Components/DTFocusTrap/DTFocusTrap.tsx index e044e7584..3bb280cd2 100644 --- a/src/Shared/Components/DTFocusTrap/DTFocusTrap.tsx +++ b/src/Shared/Components/DTFocusTrap/DTFocusTrap.tsx @@ -44,6 +44,7 @@ const DTFocusTrap = ({ deactivateFocusOnEscape = true, children, initialFocus = undefined, + returnFocusOnDeactivate = true, }: DTFocusTrapType) => { const [isFocusEnabled, setIsFocusEnabled] = useState(true) @@ -91,6 +92,7 @@ const DTFocusTrap = ({ } return false }, + returnFocusOnDeactivate, }} > {children} diff --git a/src/Shared/Components/DTFocusTrap/types.ts b/src/Shared/Components/DTFocusTrap/types.ts index 9a598ef5d..b2ddd5d6b 100644 --- a/src/Shared/Components/DTFocusTrap/types.ts +++ b/src/Shared/Components/DTFocusTrap/types.ts @@ -15,6 +15,7 @@ */ import { ReactNode } from 'react' +import { Props as FocusTrapProps } from 'focus-trap-react' /* Mimicking types from Focus React Library */ type FocusTargetValue = HTMLElement | SVGElement | string @@ -27,7 +28,7 @@ type FocusTargetValueOrFalse = FocusTargetValue | false */ type FocusTargetOrFalse = FocusTargetValueOrFalse | (() => FocusTargetValueOrFalse) -export interface DTFocusTrapType { +export interface DTFocusTrapType extends Pick { /** * Callback function that gets triggered when the Escape key is pressed. \ * Should be wrapped in useCallback to prevent unnecessary re-renders. diff --git a/src/Shared/Components/DeploymentConfigDiff/DeploymentConfigDiffNavigation.tsx b/src/Shared/Components/DeploymentConfigDiff/DeploymentConfigDiffNavigation.tsx index 432b91b2e..4a4ba9171 100644 --- a/src/Shared/Components/DeploymentConfigDiff/DeploymentConfigDiffNavigation.tsx +++ b/src/Shared/Components/DeploymentConfigDiff/DeploymentConfigDiffNavigation.tsx @@ -14,14 +14,16 @@ * limitations under the License. */ -import { ChangeEvent, useEffect, useState } from 'react' +import { useEffect, useState } from 'react' import { Link, NavLink } from 'react-router-dom' import Tippy from '@tippyjs/react' import { ReactComponent as ICClose } from '@Icons/ic-close.svg' import { ReactComponent as ICError } from '@Icons/ic-error.svg' import { ReactComponent as ICInfoOutlined } from '@Icons/ic-info-outlined.svg' -import { StyledRadioGroup } from '@Common/index' +import { SegmentedControl } from '@Common/index' +import { SegmentType } from '@Common/SegmentedControl/types' +import { ComponentSizeType } from '@Shared/constants' import { CollapsibleList, CollapsibleListConfig } from '../CollapsibleList' import { diffStateIconMap, diffStateTooltipTextMap } from './DeploymentConfigDiff.constants' @@ -87,11 +89,8 @@ export const DeploymentConfigDiffNavigation = ({ } /** Handles tab click. */ - const onTabClick = (e: ChangeEvent) => { - const { value } = e.target - if (tabConfig?.activeTab !== value) { - tabConfig?.onClick?.(value) - } + const onTabClick = (segment: SegmentType) => { + tabConfig?.onClick?.(segment.value) } // RENDERERS @@ -113,19 +112,13 @@ export const DeploymentConfigDiffNavigation = ({ return (
- ({ label: tab, value: tab }))} + value={activeTab} onChange={onTabClick} - disabled={isLoading} - className="gui-yaml-switch deployment-config-diff__tab-list" - > - {tabs.map((tab) => ( - - {tab} - - ))} - + name="deployment-config-diff-tab-list" + size={ComponentSizeType.xs} + />
) } diff --git a/src/Shared/Components/DocLink/constants.ts b/src/Shared/Components/DocLink/constants.ts index 1e79d94b2..029e3d3af 100644 --- a/src/Shared/Components/DocLink/constants.ts +++ b/src/Shared/Components/DocLink/constants.ts @@ -42,23 +42,24 @@ export const DOCUMENTATION = { CHART_STORE: 'usage/deploy-chart', CHART_STORE_METRICS_SERVER: 'dashboard//chart-store/discover?appStoreName=metrics-server', CUSTOM_VALUES: 'usage/deploy-chart/overview-of-charts#custom-values', + CONFIGURING_WEBHOOK: 'usage/applications/creating-application/workflow/ci-pipeline#configuring-webhook', DEPLOYMENT: 'usage/applications/creating-application/deployment-template/deployment', DEPLOYMENT_TEMPLATE: 'usage/applications/creating-application/deployment-template', DEVTRON_UPGRADE: 'getting-started/upgrade', - CONFIGURING_WEBHOOK: 'usage/applications/creating-application/workflow/ci-pipeline#configuring-webhook', + DOC_HOME_PAGE: DOCUMENTATION_HOME_PAGE, ENTERPRISE_LICENSE: 'enterprise-license', EXECUTE_CUSTOM_SCRIPT: 'usage/applications/creating-application/workflow/ci-pipeline/ci-build-pre-post-plugins#execute-custom-script', EXTERNAL_LINKS: 'getting-started/global-configurations/external-links', EXTERNAL_SECRET: 'usage/applications/creating-application/secrets#external-secrets', HOME_PAGE: 'https://devtron.ai', - DOC_HOME_PAGE: DOCUMENTATION_HOME_PAGE, - KUBE_CONFIG: 'usage/resource-browser#running-kubectl-commands-locally', JOBS: 'usage/jobs', - TAINT: 'usage/resource-browser#taint-a-node', + KUBE_CONFIG: 'usage/resource-browser#running-kubectl-commands-locally', RESOURCE_BROWSER: 'usage/resource-browser', + TAINT: 'usage/resource-browser#taint-a-node', // Global Configurations + GLOBAL_CONFIGUDATIONS: 'getting-started/global-configurations', GLOBAL_CONFIG_API_TOKEN: 'getting-started/global-configurations/authorization/api-tokens', GLOBAL_CONFIG_BUILD_INFRA: 'global-configurations/build-infra', GLOBAL_CONFIG_CHART: 'getting-started/global-configurations/chart-repo', @@ -117,10 +118,33 @@ export const DOCUMENTATION = { GLOBAL_CONFIG_PULL_IMAGE_DIGEST: 'global-configurations/pull-image-digest', GLOBAL_CONFIG_TAGS: 'getting-started/global-configurations/tags-policy', - // Software distribution hub + // Application Management + APP_MANAGEMENT: 'docs/user-guide/app-management', + + // Software Release Management SOFTWARE_DISTRIBUTION_HUB: 'usage/software-distribution-hub', RELEASE_TRACKS: 'usage/software-distribution-hub/release-hub#creating-release-tracks-and-versions', RELEASES: 'usage/software-distribution-hub/release-hub#creating-release-tracks-and-versions', + RELEASE_HUB: 'usage/software-distribution-hub/release-hub', TENANTS: 'usage/software-distribution-hub/tenants#adding-installation', TENANTS_INSTALLATION: 'usage/software-distribution-hub/tenants', + + // Infrastructure Management + AUTOSCALER_DETECTION: + 'docs/user-guide/infra-management/infrastructure-overview#troubleshooting-autoscaler-detection', + INFRA_MANAGEMENT: 'docs/user-guide/infra-management', + + // Cost Visibility + COST_BREAKDOWN: 'docs/user-guide/finops', + COST_CALCULATION: 'docs/user-guide/finops/overview-cost-visibility#how-is-the-cost-calculated', + COST_VISIBILITY_OVERVIEW: 'docs/user-guide/finops/overview-cost-visibility', + + // Security Center + SECURITY_CENTER: 'docs/user-guide/security-features', + + // AI Recommendations + AI_RECOMMENDATIONS: 'docs/user-guide/ai-recommendations', + + // Automation & Enablement + AUTOMATION_AND_ENABLEMENT: 'docs/user-guide/automation', } as const diff --git a/src/Shared/Components/ExportToCsv/ExportToCsv.tsx b/src/Shared/Components/ExportToCsv/ExportToCsv.tsx new file mode 100644 index 000000000..99118972c --- /dev/null +++ b/src/Shared/Components/ExportToCsv/ExportToCsv.tsx @@ -0,0 +1,183 @@ +import { useEffect, useRef, useState } from 'react' +import { CSVLink } from 'react-csv' +import moment from 'moment' + +import { getIsRequestAborted } from '@Common/API' +import { DATE_TIME_FORMATS } from '@Common/Constants' +import { showError } from '@Common/Helper' +import { ServerErrors } from '@Common/ServerError' +import { ALLOW_ACTION_OUTSIDE_FOCUS_TRAP, ComponentSizeType } from '@Shared/constants' +import { isNullOrUndefined } from '@Shared/Helpers' + +import { Button, ButtonVariantType } from '../Button' +import { Icon } from '../Icon' +import ExportToCsvDialog from './ExportToCsvDialog' +import { ExportToCsvProps } from './types' + +const ExportToCsv = ({ + apiPromise, + fileName, + triggerElementConfig, + disabled, + modalConfig, + headers, + downloadRequestId, +}: ExportToCsvProps) => { + const csvRef = useRef(null) + const abortControllerRef = useRef(new AbortController()) + + const [dataToExport, setDataToExport] = useState>>([]) + const [confirmationModalType, setConfirmationModalType] = useState<'default' | 'custom' | null>(null) + const [isLoading, setIsLoading] = useState(false) + const [dataFetchError, setDataFetchError] = useState(null) + + const handleInitiateDownload = async () => { + if (disabled) { + return + } + + try { + setIsLoading(true) + setDataFetchError(null) + const data = await apiPromise({ signal: abortControllerRef.current.signal }) + setDataToExport(data) + + csvRef.current?.link?.click() + } catch (error) { + if (!getIsRequestAborted(error)) { + showError(error) + setDataFetchError(error) + } + } finally { + abortControllerRef.current = new AbortController() + setIsLoading(false) + } + } + + const handleExportButtonClick = async () => { + if (!modalConfig?.hideDialog) { + setConfirmationModalType(!modalConfig ? 'default' : 'custom') + } + + if (!modalConfig || modalConfig.hideDialog) { + await handleInitiateDownload() + } + } + + useEffect( + () => () => { + abortControllerRef.current.abort() + }, + [], + ) + + useEffect(() => { + if (!isNullOrUndefined(downloadRequestId)) { + // eslint-disable-next-line @typescript-eslint/no-floating-promises + handleExportButtonClick() + } + }, [downloadRequestId]) + + const handleCancelRequest = () => { + abortControllerRef.current.abort() + abortControllerRef.current = new AbortController() + setConfirmationModalType(null) + setIsLoading(false) + } + + const renderTriggerButton = () => { + if (!triggerElementConfig || triggerElementConfig.showOnlyIcon) { + return ( + + ) + } + + return null + } + + const proceedWithDownloadFromCustomModal = async (shouldProceed: boolean) => { + if (!shouldProceed) { + setConfirmationModalType(null) + return + } + + setConfirmationModalType('default') + await handleInitiateDownload() + } + + const renderModal = () => { + if (!confirmationModalType || modalConfig?.hideDialog) { + return null + } + + if (confirmationModalType === 'custom' && modalConfig?.renderCustomModal) { + return modalConfig.renderCustomModal(proceedWithDownloadFromCustomModal) + } + + return ( + + ) + } + + return ( +
+ {renderTriggerButton()} + + + + {renderModal()} +
+ ) +} + +export default ExportToCsv diff --git a/src/Shared/Components/ExportToCsv/ExportToCsvDialog.tsx b/src/Shared/Components/ExportToCsv/ExportToCsvDialog.tsx new file mode 100644 index 000000000..2536d5e04 --- /dev/null +++ b/src/Shared/Components/ExportToCsv/ExportToCsvDialog.tsx @@ -0,0 +1,84 @@ +import { DetailsProgressing, VisibleModal } from '@Common/index' +import { ComponentSizeType } from '@Shared/constants' + +import { Button, ButtonStyleType, ButtonVariantType } from '../Button' +import { GenericSectionErrorState } from '../GenericSectionErrorState' +import { Icon } from '../Icon' +import { ExportToCsvDialogProps } from './types' + +const ExportToCsvDialog = ({ + exportDataError, + isLoading, + initiateDownload, + handleCancelRequest, +}: ExportToCsvDialogProps) => { + const renderExportStatus = () => { + if (exportDataError) { + return ( + + ) + } + + if (isLoading) { + return ( + + Please do not reload or press the browser back button. + + ) + } + + return ( +
+ + Your export is ready + If download does not start automatically, +
+ ) + } + + const renderModalCTA = () => ( + <> +
) } diff --git a/src/Shared/Components/GenericSectionErrorState/types.ts b/src/Shared/Components/GenericSectionErrorState/types.ts index df99b7e97..f68776423 100644 --- a/src/Shared/Components/GenericSectionErrorState/types.ts +++ b/src/Shared/Components/GenericSectionErrorState/types.ts @@ -16,13 +16,10 @@ import { ReactNode } from 'react' +import { ButtonComponentType, ButtonProps } from '../Button' import { IconsProps } from '../Icon' -export type GenericSectionErrorStateProps = { - /** - * Handler for reloading the section - */ - reload?: () => void +export type GenericSectionErrorStateProps = { /** * If true, border is added to the section * @@ -56,6 +53,7 @@ export type GenericSectionErrorStateProps = { */ progressingProps: Omit useInfoIcon?: false + customIcon?: never } | { progressingProps?: never @@ -65,9 +63,33 @@ export type GenericSectionErrorStateProps = { * @default false */ useInfoIcon: true + customIcon?: never + } + | { + progressingProps?: never + useInfoIcon?: never + customIcon?: never } | { + customIcon: JSX.Element progressingProps?: never useInfoIcon?: never } -) +) & + ( + | { + /** + * Handler for reloading the section + */ + reload: () => void + buttonProps?: never + } + | { + reload?: never + buttonProps: ButtonProps + } + | { + reload?: never + buttonProps?: never + } + ) diff --git a/src/Shared/Components/Header/HeaderWithCreateButton/HeaderWithCreateButon.tsx b/src/Shared/Components/Header/HeaderWithCreateButton/HeaderWithCreateButon.tsx index cdb738421..f7000d997 100644 --- a/src/Shared/Components/Header/HeaderWithCreateButton/HeaderWithCreateButon.tsx +++ b/src/Shared/Components/Header/HeaderWithCreateButton/HeaderWithCreateButon.tsx @@ -14,29 +14,33 @@ * limitations under the License. */ -import { useLocation, useParams } from 'react-router-dom' +import { useLocation } from 'react-router-dom' import { SERVER_MODE, URLS } from '@Common/Constants' import { noop } from '@Common/Helper' +import { BreadCrumb, BreadcrumbText, useBreadcrumb } from '@Common/index' import { ActionMenu } from '@Shared/Components/ActionMenu' import { ButtonComponentType } from '@Shared/Components/Button' import Button from '@Shared/Components/Button/Button.component' +import { DOCUMENTATION } from '@Shared/Components/DocLink' import { Icon } from '@Shared/Components/Icon' -import { AppListConstants, ComponentSizeType } from '@Shared/constants' +import { ComponentSizeType } from '@Shared/constants' import { useMainContext } from '@Shared/Providers' +import { getApplicationManagementBreadcrumb } from '@PagesDevtron2.0/ApplicationManagement' +import { getInfrastructureManagementBreadcrumb } from '@PagesDevtron2.0/InfrastructureManagement' +import { getAutomationEnablementBreadcrumbConfig } from '@PagesDevtron2.0/InfrastructureManagement/utils' import PageHeader from '../PageHeader' import { HeaderWithCreateButtonProps } from './types' import { getCreateActionMenuOptions } from './utils' -export const HeaderWithCreateButton = ({ headerName, additionalHeaderInfo }: HeaderWithCreateButtonProps) => { +export const HeaderWithCreateButton = ({ viewType }: HeaderWithCreateButtonProps) => { // HOOKS - const { serverMode } = useMainContext() - const params = useParams<{ appType: string }>() const location = useLocation() + const { serverMode } = useMainContext() // CONSTANTS - const createCustomAppURL = `${URLS.APP}/${URLS.APP_LIST}/${params.appType ?? AppListConstants.AppType.DEVTRON_APPS}/${AppListConstants.CREATE_DEVTRON_APP_URL}${location.search}` + const createCustomAppURL = `${URLS.APPLICATION_MANAGEMENT_CREATE_DEVTRON_APP}${location.search}` const renderActionButtons = () => serverMode === SERVER_MODE.FULL ? ( @@ -56,18 +60,68 @@ export const HeaderWithCreateButton = ({ headerName, additionalHeaderInfo }: Hea
) -export const HelpButton = ({ serverInfo, fetchingServerInfo, onClick, hideGettingStartedCard }: HelpButtonProps) => { +export const HelpButton = ({ + serverInfo, + fetchingServerInfo, + onClick, + hideGettingStartedCard, + docPath, +}: HelpButtonProps) => { // STATES const [isActionMenuOpen, setIsActionMenuOpen] = useState(false) const [expiryDate, setExpiryDate] = useState(0) @@ -96,7 +102,7 @@ export const HelpButton = ({ serverInfo, fetchingServerInfo, onClick, hideGettin setSidePanelConfig((prev) => ({ ...prev, state: SidePanelTab.DOCUMENTATION, - docLink: DOCUMENTATION_HOME_PAGE, + docLink: `${DOCUMENTATION_HOME_PAGE}${docPath ? `/${docPath}` : ''}`, reinitialize: true, })) } @@ -150,6 +156,7 @@ export const HelpButton = ({ serverInfo, fetchingServerInfo, onClick, hideGettin options={getHelpActionMenuOptions({ isTrialOrFreemium: (licenseData?.isTrial || licenseData?.isFreemium) ?? false, isEnterprise, + docPath, })} onClick={handleActionMenuClick} onOpen={setIsActionMenuOpen} diff --git a/src/Shared/Components/Header/PageHeader.tsx b/src/Shared/Components/Header/PageHeader.tsx index b0916a840..fd3f84b5d 100644 --- a/src/Shared/Components/Header/PageHeader.tsx +++ b/src/Shared/Components/Header/PageHeader.tsx @@ -49,12 +49,22 @@ const PageHeader = ({ renderActionButtons, onClose, tippyProps, + closeIcon, + docPath, }: PageHeaderType) => { - const { setLoginCount, setShowGettingStartedCard, setSidePanelConfig, sidePanelConfig } = useMainContext() + const { setLoginCount, setShowGettingStartedCard, setSidePanelConfig, sidePanelConfig, tempAppWindowConfig } = + useMainContext() const { showSwitchThemeLocationTippy, handleShowSwitchThemeLocationTippyChange } = useTheme() - const { isTippyCustomized, tippyRedirectLink, TippyIcon, tippyMessage, onClickTippyButton, additionalContent } = - tippyProps || {} + const { + isTippyCustomized, + tippyRedirectLink, + TippyIcon, + tippyMessage, + onClickTippyButton, + additionalContent, + tippyHeader, + } = tippyProps || {} const { email } = useUserEmail() const [currentServerInfo, setCurrentServerInfo] = useState<{ serverInfo: ServerInfo; fetchingServerInfo: boolean }>( { @@ -130,23 +140,26 @@ const PageHeader = ({ const renderLogoutHelpSection = () => ( <> - {window._env_?.FEATURE_ASK_DEVTRON_EXPERT && sidePanelConfig.state === 'closed' && ( - - - - )} + {window._env_?.FEATURE_ASK_DEVTRON_EXPERT && + sidePanelConfig.state === 'closed' && + !tempAppWindowConfig.open && ( + + + + )} {!window._env_.K8S_CLIENT && ( } + icon={closeIcon ?? } variant={ButtonVariantType.secondary} style={ButtonStyleType.negativeGrey} size={ComponentSizeType.xs} @@ -226,8 +239,8 @@ const PageHeader = ({ (isTippyCustomized ? ( ): HelpButtonActionMenuProps['options'][number]['items'] => [ ...((!window._env_?.K8S_CLIENT ? [ { @@ -36,7 +38,7 @@ export const COMMON_HELP_ACTION_MENU_ITEMS: HelpButtonActionMenuProps['options'] label: 'View documentation', startIcon: { name: 'ic-book-open' }, componentType: 'anchor', - href: DOCUMENTATION_HOME_PAGE, + href: `${DOCUMENTATION_HOME_PAGE}${docPath ? `/${docPath}` : ''}`, }, { id: HelpMenuItems.DEVTRON_GPT, diff --git a/src/Shared/Components/Header/types.ts b/src/Shared/Components/Header/types.ts index d142a3950..4065c8a4e 100644 --- a/src/Shared/Components/Header/types.ts +++ b/src/Shared/Components/Header/types.ts @@ -35,8 +35,11 @@ export interface PageHeaderType { TippyIcon?: React.FunctionComponent tippyMessage?: string onClickTippyButton?: () => void + tippyHeader?: string } onClose?: () => void + closeIcon?: JSX.Element + docPath?: string } export interface ServerInfo { @@ -50,7 +53,7 @@ export interface ServerInfoResponse extends ResponseType { result?: ServerInfo } -export interface HelpButtonProps { +export interface HelpButtonProps extends Pick { serverInfo: ServerInfo fetchingServerInfo: boolean onClick: () => void @@ -77,3 +80,8 @@ export interface ProfileMenuProps { user: string onClick?: () => void } + +export interface HelpActionOptionTypes extends Pick { + isEnterprise: boolean + isTrialOrFreemium: boolean +} diff --git a/src/Shared/Components/Header/utils.ts b/src/Shared/Components/Header/utils.ts index 50dae36d9..af79adaf6 100644 --- a/src/Shared/Components/Header/utils.ts +++ b/src/Shared/Components/Header/utils.ts @@ -17,13 +17,13 @@ import { LOGIN_COUNT } from '@Common/Constants' import { - COMMON_HELP_ACTION_MENU_ITEMS, ENTERPRISE_HELP_ACTION_MENU_ITEMS, ENTERPRISE_TRIAL_HELP_ACTION_MENU_ITEMS, + getCommonHelpActionMenuItems, OSS_HELP_ACTION_MENU_ITEMS, } from './constants' import { updatePostHogEvent } from './service' -import { HelpButtonActionMenuProps } from './types' +import { HelpActionOptionTypes, HelpButtonActionMenuProps } from './types' const millisecondsInDay = 86400000 export const getDateInMilliseconds = (days) => 1 + new Date().valueOf() + (days ?? 0) * millisecondsInDay @@ -45,12 +45,10 @@ export const setActionWithExpiry = (key: string, days: number): void => { export const getHelpActionMenuOptions = ({ isEnterprise, isTrialOrFreemium, -}: { - isEnterprise: boolean - isTrialOrFreemium: boolean -}): HelpButtonActionMenuProps['options'] => [ + docPath, +}: HelpActionOptionTypes): HelpButtonActionMenuProps['options'] => [ { - items: COMMON_HELP_ACTION_MENU_ITEMS, + items: getCommonHelpActionMenuItems({ docPath }), }, ...(isEnterprise ? [ diff --git a/src/Shared/Components/Icon/Icon.tsx b/src/Shared/Components/Icon/Icon.tsx index 9126256d7..f9517d50e 100644 --- a/src/Shared/Components/Icon/Icon.tsx +++ b/src/Shared/Components/Icon/Icon.tsx @@ -4,29 +4,58 @@ import { ReactComponent as IC73strings } from '@IconsV2/ic-73strings.svg' import { ReactComponent as ICAborted } from '@IconsV2/ic-aborted.svg' import { ReactComponent as ICActivity } from '@IconsV2/ic-activity.svg' import { ReactComponent as ICAdd } from '@IconsV2/ic-add.svg' +import { ReactComponent as ICAi } from '@IconsV2/ic-ai.svg' +import { ReactComponent as ICAlibaba } from '@IconsV2/ic-alibaba.svg' import { ReactComponent as ICAmazonEks } from '@IconsV2/ic-amazon-eks.svg' import { ReactComponent as ICAppGroup } from '@IconsV2/ic-app-group.svg' import { ReactComponent as ICAppTemplate } from '@IconsV2/ic-app-template.svg' +import { ReactComponent as ICApplication } from '@IconsV2/ic-application.svg' +import { ReactComponent as ICApplicationGroup } from '@IconsV2/ic-application-group.svg' +import { ReactComponent as ICApplicationManagement } from '@IconsV2/ic-application-management.svg' +import { ReactComponent as ICApplicationTemplate } from '@IconsV2/ic-application-template.svg' +import { ReactComponent as ICArgocdApp } from '@IconsV2/ic-argocd-app.svg' import { ReactComponent as ICArrowClockwise } from '@IconsV2/ic-arrow-clockwise.svg' +import { ReactComponent as ICArrowLineDown } from '@IconsV2/ic-arrow-line-down.svg' import { ReactComponent as ICArrowRight } from '@IconsV2/ic-arrow-right.svg' import { ReactComponent as ICArrowSquareOut } from '@IconsV2/ic-arrow-square-out.svg' import { ReactComponent as ICArrowsClockwise } from '@IconsV2/ic-arrows-clockwise.svg' import { ReactComponent as ICArrowsLeftRight } from '@IconsV2/ic-arrows-left-right.svg' import { ReactComponent as ICAsterisk } from '@IconsV2/ic-asterisk.svg' import { ReactComponent as ICAther } from '@IconsV2/ic-ather.svg' +import { ReactComponent as ICAws } from '@IconsV2/ic-aws.svg' import { ReactComponent as ICAwsCodecommit } from '@IconsV2/ic-aws-codecommit.svg' import { ReactComponent as ICAzure } from '@IconsV2/ic-azure.svg' import { ReactComponent as ICAzureAks } from '@IconsV2/ic-azure-aks.svg' +import { ReactComponent as ICBackupAndSchedule } from '@IconsV2/ic-backup-and-schedule.svg' +import { ReactComponent as ICBackupColor } from '@IconsV2/ic-backup-color.svg' +import { ReactComponent as ICBackupLocation } from '@IconsV2/ic-backup-location.svg' +import { ReactComponent as ICBackupScheduleColor } from '@IconsV2/ic-backup-schedule-color.svg' +import { ReactComponent as ICBgBackupSchedule } from '@IconsV2/ic-bg-backup-schedule.svg' +import { ReactComponent as ICBgBackups } from '@IconsV2/ic-bg-backups.svg' +import { ReactComponent as ICBgBuild } from '@IconsV2/ic-bg-build.svg' import { ReactComponent as ICBgCluster } from '@IconsV2/ic-bg-cluster.svg' +import { ReactComponent as ICBgCpu } from '@IconsV2/ic-bg-cpu.svg' +import { ReactComponent as ICBgDeploy } from '@IconsV2/ic-bg-deploy.svg' import { ReactComponent as ICBgEnvironment } from '@IconsV2/ic-bg-environment.svg' +import { ReactComponent as ICBgMemory } from '@IconsV2/ic-bg-memory.svg' +import { ReactComponent as ICBgPauseSchedule } from '@IconsV2/ic-bg-pause-schedule.svg' +import { ReactComponent as ICBgProductionPipelines } from '@IconsV2/ic-bg-production-pipelines.svg' +import { ReactComponent as ICBgProject } from '@IconsV2/ic-bg-project.svg' +import { ReactComponent as ICBgRansomwareVulnerableCluster } from '@IconsV2/ic-bg-ransomware-vulnerable-cluster.svg' +import { ReactComponent as ICBgRestore } from '@IconsV2/ic-bg-restore.svg' +import { ReactComponent as ICBgScan } from '@IconsV2/ic-bg-scan.svg' +import { ReactComponent as ICBgStorageLocations } from '@IconsV2/ic-bg-storage-locations.svg' +import { ReactComponent as ICBgWebhook } from '@IconsV2/ic-bg-webhook.svg' import { ReactComponent as ICBharatpe } from '@IconsV2/ic-bharatpe.svg' import { ReactComponent as ICBinoculars } from '@IconsV2/ic-binoculars.svg' import { ReactComponent as ICBitbucket } from '@IconsV2/ic-bitbucket.svg' import { ReactComponent as ICBookOpen } from '@IconsV2/ic-book-open.svg' +import { ReactComponent as ICBot } from '@IconsV2/ic-bot.svg' import { ReactComponent as ICBrain } from '@IconsV2/ic-brain.svg' import { ReactComponent as ICBrowser } from '@IconsV2/ic-browser.svg' import { ReactComponent as ICBug } from '@IconsV2/ic-bug.svg' import { ReactComponent as ICBuildColor } from '@IconsV2/ic-build-color.svg' +import { ReactComponent as ICBuilding } from '@IconsV2/ic-building.svg' import { ReactComponent as ICCalendar } from '@IconsV2/ic-calendar.svg' import { ReactComponent as ICCancelled } from '@IconsV2/ic-cancelled.svg' import { ReactComponent as ICCardStack } from '@IconsV2/ic-card-stack.svg' @@ -34,29 +63,40 @@ import { ReactComponent as ICCaretDownSmall } from '@IconsV2/ic-caret-down-small import { ReactComponent as ICCaretLeft } from '@IconsV2/ic-caret-left.svg' import { ReactComponent as ICCaretRight } from '@IconsV2/ic-caret-right.svg' import { ReactComponent as ICCd } from '@IconsV2/ic-cd.svg' +import { ReactComponent as ICChartLineUp } from '@IconsV2/ic-chart-line-up.svg' +import { ReactComponent as ICChartRepo } from '@IconsV2/ic-chart-repo.svg' import { ReactComponent as ICChatCircleDots } from '@IconsV2/ic-chat-circle-dots.svg' import { ReactComponent as ICChatCircleOnline } from '@IconsV2/ic-chat-circle-online.svg' import { ReactComponent as ICCheck } from '@IconsV2/ic-check.svg' import { ReactComponent as ICCheckAll } from '@IconsV2/ic-check-all.svg' +import { ReactComponent as ICCheckCircle } from '@IconsV2/ic-check-circle.svg' import { ReactComponent as ICCheckSquare } from '@IconsV2/ic-check-square.svg' import { ReactComponent as ICChecks } from '@IconsV2/ic-checks.svg' import { ReactComponent as ICCiLinked } from '@IconsV2/ic-ci-linked.svg' import { ReactComponent as ICCircleLoader } from '@IconsV2/ic-circle-loader.svg' +import { ReactComponent as ICCircleSmall } from '@IconsV2/ic-circle-small.svg' import { ReactComponent as ICCleanBrush } from '@IconsV2/ic-clean-brush.svg' +import { ReactComponent as ICClipboard } from '@IconsV2/ic-clipboard.svg' import { ReactComponent as ICClock } from '@IconsV2/ic-clock.svg' +import { ReactComponent as ICClockCounterclockwise } from '@IconsV2/ic-clock-counterclockwise.svg' import { ReactComponent as ICCloseLarge } from '@IconsV2/ic-close-large.svg' import { ReactComponent as ICCloseSmall } from '@IconsV2/ic-close-small.svg' +import { ReactComponent as ICCloudUpload } from '@IconsV2/ic-cloud-upload.svg' import { ReactComponent as ICCloudVms } from '@IconsV2/ic-cloud-vms.svg' import { ReactComponent as ICCluster } from '@IconsV2/ic-cluster.svg' import { ReactComponent as ICClusterIsolated } from '@IconsV2/ic-cluster-isolated.svg' import { ReactComponent as ICCode } from '@IconsV2/ic-code.svg' +import { ReactComponent as ICCoins } from '@IconsV2/ic-coins.svg' +import { ReactComponent as ICCoinsColorAnimated } from '@IconsV2/ic-coins-color-animated.svg' import { ReactComponent as ICContainer } from '@IconsV2/ic-container.svg' import { ReactComponent as ICContainerRegistry } from '@IconsV2/ic-container-registry.svg' import { ReactComponent as ICCookr } from '@IconsV2/ic-cookr.svg' import { ReactComponent as ICCopy } from '@IconsV2/ic-copy.svg' +import { ReactComponent as ICCostVisibility } from '@IconsV2/ic-cost-visibility.svg' import { ReactComponent as ICCpu } from '@IconsV2/ic-cpu.svg' import { ReactComponent as ICCrown } from '@IconsV2/ic-crown.svg' import { ReactComponent as ICCube } from '@IconsV2/ic-cube.svg' +import { ReactComponent as ICDatabaseBackup } from '@IconsV2/ic-database-backup.svg' import { ReactComponent as ICDelete } from '@IconsV2/ic-delete.svg' import { ReactComponent as ICDeleteDots } from '@IconsV2/ic-delete-dots.svg' import { ReactComponent as ICDeleteLightning } from '@IconsV2/ic-delete-lightning.svg' @@ -68,6 +108,7 @@ import { ReactComponent as ICDevtronAi } from '@IconsV2/ic-devtron-ai.svg' import { ReactComponent as ICDevtronApp } from '@IconsV2/ic-devtron-app.svg' import { ReactComponent as ICDevtronHeaderLogo } from '@IconsV2/ic-devtron-header-logo.svg' import { ReactComponent as ICDevtronJob } from '@IconsV2/ic-devtron-job.svg' +import { ReactComponent as ICDevtronText } from '@IconsV2/ic-devtron-text.svg' import { ReactComponent as ICDiffAdded } from '@IconsV2/ic-diff-added.svg' import { ReactComponent as ICDiffDeleted } from '@IconsV2/ic-diff-deleted.svg' import { ReactComponent as ICDiffUpdated } from '@IconsV2/ic-diff-updated.svg' @@ -77,6 +118,7 @@ import { ReactComponent as ICDockerhub } from '@IconsV2/ic-dockerhub.svg' import { ReactComponent as ICDownload } from '@IconsV2/ic-download.svg' import { ReactComponent as ICEcr } from '@IconsV2/ic-ecr.svg' import { ReactComponent as ICEdit } from '@IconsV2/ic-edit.svg' +import { ReactComponent as ICEditLines } from '@IconsV2/ic-edit-lines.svg' import { ReactComponent as ICEmail } from '@IconsV2/ic-email.svg' import { ReactComponent as ICEnterFullscreen } from '@IconsV2/ic-enter-fullscreen.svg' import { ReactComponent as ICEnterpriseFeat } from '@IconsV2/ic-enterprise-feat.svg' @@ -88,16 +130,21 @@ import { ReactComponent as ICError } from '@IconsV2/ic-error.svg' import { ReactComponent as ICExitFullscreen } from '@IconsV2/ic-exit-fullscreen.svg' import { ReactComponent as ICExpandRightSm } from '@IconsV2/ic-expand-right-sm.svg' import { ReactComponent as ICExpandSm } from '@IconsV2/ic-expand-sm.svg' +import { ReactComponent as ICExternalLink } from '@IconsV2/ic-external-link.svg' import { ReactComponent as ICFailure } from '@IconsV2/ic-failure.svg' import { ReactComponent as ICFastForward } from '@IconsV2/ic-fast-forward.svg' import { ReactComponent as ICFile } from '@IconsV2/ic-file.svg' import { ReactComponent as ICFileCode } from '@IconsV2/ic-file-code.svg' +import { ReactComponent as ICFileDownload } from '@IconsV2/ic-file-download.svg' import { ReactComponent as ICFileEdit } from '@IconsV2/ic-file-edit.svg' import { ReactComponent as ICFileKey } from '@IconsV2/ic-file-key.svg' import { ReactComponent as ICFiles } from '@IconsV2/ic-files.svg' +import { ReactComponent as ICFilesChanged } from '@IconsV2/ic-files-changed.svg' import { ReactComponent as ICFilter } from '@IconsV2/ic-filter.svg' import { ReactComponent as ICFilterApplied } from '@IconsV2/ic-filter-applied.svg' import { ReactComponent as ICFlask } from '@IconsV2/ic-flask.svg' +import { ReactComponent as ICFloppyDisk } from '@IconsV2/ic-floppy-disk.svg' +import { ReactComponent as ICFluxcdApp } from '@IconsV2/ic-fluxcd-app.svg' import { ReactComponent as ICFolder } from '@IconsV2/ic-folder.svg' import { ReactComponent as ICFolderColor } from '@IconsV2/ic-folder-color.svg' import { ReactComponent as ICFolderUser } from '@IconsV2/ic-folder-user.svg' @@ -110,8 +157,10 @@ import { ReactComponent as ICGit } from '@IconsV2/ic-git.svg' import { ReactComponent as ICGitBranch } from '@IconsV2/ic-git-branch.svg' import { ReactComponent as ICGithub } from '@IconsV2/ic-github.svg' import { ReactComponent as ICGitlab } from '@IconsV2/ic-gitlab.svg' +import { ReactComponent as ICGlobalOverview } from '@IconsV2/ic-global-overview.svg' import { ReactComponent as ICGoogle } from '@IconsV2/ic-google.svg' import { ReactComponent as ICGoogleArtifactRegistry } from '@IconsV2/ic-google-artifact-registry.svg' +import { ReactComponent as ICGoogleCloud } from '@IconsV2/ic-google-cloud.svg' import { ReactComponent as ICGoogleContainerRegistry } from '@IconsV2/ic-google-container-registry.svg' import { ReactComponent as ICGoogleGke } from '@IconsV2/ic-google-gke.svg' import { ReactComponent as ICGridView } from '@IconsV2/ic-grid-view.svg' @@ -131,6 +180,7 @@ import { ReactComponent as ICInProgress } from '@IconsV2/ic-in-progress.svg' import { ReactComponent as ICInfoFilled } from '@IconsV2/ic-info-filled.svg' import { ReactComponent as ICInfoFilledColor } from '@IconsV2/ic-info-filled-color.svg' import { ReactComponent as ICInfoOutline } from '@IconsV2/ic-info-outline.svg' +import { ReactComponent as ICInfrastructureManagement } from '@IconsV2/ic-infrastructure-management.svg' import { ReactComponent as ICInput } from '@IconsV2/ic-input.svg' import { ReactComponent as ICInstall } from '@IconsV2/ic-install.svg' import { ReactComponent as ICJobColor } from '@IconsV2/ic-job-color.svg' @@ -139,6 +189,7 @@ import { ReactComponent as ICK8sJob } from '@IconsV2/ic-k8s-job.svg' import { ReactComponent as ICKey } from '@IconsV2/ic-key.svg' import { ReactComponent as ICKeyEnter } from '@IconsV2/ic-key-enter.svg' import { ReactComponent as ICKind } from '@IconsV2/ic-kind.svg' +import { ReactComponent as ICKubernetes } from '@IconsV2/ic-kubernetes.svg' import { ReactComponent as ICLaptop } from '@IconsV2/ic-laptop.svg' import { ReactComponent as ICLdap } from '@IconsV2/ic-ldap.svg' import { ReactComponent as ICLego } from '@IconsV2/ic-lego.svg' @@ -151,6 +202,7 @@ import { ReactComponent as ICLivspace } from '@IconsV2/ic-livspace.svg' import { ReactComponent as ICLocked } from '@IconsV2/ic-locked.svg' import { ReactComponent as ICLogout } from '@IconsV2/ic-logout.svg' import { ReactComponent as ICLogs } from '@IconsV2/ic-logs.svg' +import { ReactComponent as ICMagicWand } from '@IconsV2/ic-magic-wand.svg' import { ReactComponent as ICMagnifyingGlass } from '@IconsV2/ic-magnifying-glass.svg' import { ReactComponent as ICMediumDelete } from '@IconsV2/ic-medium-delete.svg' import { ReactComponent as ICMediumPaintbucket } from '@IconsV2/ic-medium-paintbucket.svg' @@ -172,23 +224,38 @@ import { ReactComponent as ICOpenBox } from '@IconsV2/ic-open-box.svg' import { ReactComponent as ICOpenInNew } from '@IconsV2/ic-open-in-new.svg' import { ReactComponent as ICOpenai } from '@IconsV2/ic-openai.svg' import { ReactComponent as ICOpenshift } from '@IconsV2/ic-openshift.svg' +import { ReactComponent as ICOracleCloud } from '@IconsV2/ic-oracle-cloud.svg' +import { ReactComponent as ICOtcCloud } from '@IconsV2/ic-otc-cloud.svg' import { ReactComponent as ICOutOfSync } from '@IconsV2/ic-out-of-sync.svg' import { ReactComponent as ICPaperPlane } from '@IconsV2/ic-paper-plane.svg' import { ReactComponent as ICPaperPlaneColor } from '@IconsV2/ic-paper-plane-color.svg' +import { ReactComponent as ICParty } from '@IconsV2/ic-party.svg' import { ReactComponent as ICPath } from '@IconsV2/ic-path.svg' +import { ReactComponent as ICPause } from '@IconsV2/ic-pause.svg' import { ReactComponent as ICPauseCircle } from '@IconsV2/ic-pause-circle.svg' import { ReactComponent as ICPencil } from '@IconsV2/ic-pencil.svg' import { ReactComponent as ICPlayOutline } from '@IconsV2/ic-play-outline.svg' +import { ReactComponent as ICPriorityMediumFill } from '@IconsV2/ic-priority-medium-fill.svg' import { ReactComponent as ICQuay } from '@IconsV2/ic-quay.svg' import { ReactComponent as ICQuote } from '@IconsV2/ic-quote.svg' import { ReactComponent as ICRatings } from '@IconsV2/ic-ratings.svg' +import { ReactComponent as ICReleaseHub } from '@IconsV2/ic-release-hub.svg' import { ReactComponent as ICResizeHandle } from '@IconsV2/ic-resize-handle.svg' +import { ReactComponent as ICResourceBrowser } from '@IconsV2/ic-resource-browser.svg' +import { ReactComponent as ICResourceWatcher } from '@IconsV2/ic-resource-watcher.svg' +import { ReactComponent as ICRightPanelCollapse } from '@IconsV2/ic-right-panel-collapse.svg' import { ReactComponent as ICRocketGear } from '@IconsV2/ic-rocket-gear.svg' import { ReactComponent as ICRocketLaunch } from '@IconsV2/ic-rocket-launch.svg' +import { ReactComponent as ICSecurityFixable } from '@IconsV2/ic-security-fixable.svg' +import { ReactComponent as ICSecurityNotFixable } from '@IconsV2/ic-security-not-fixable.svg' +import { ReactComponent as ICSecurityPolicy } from '@IconsV2/ic-security-policy.svg' +import { ReactComponent as ICSecurityScan } from '@IconsV2/ic-security-scan.svg' +import { ReactComponent as ICSecurityVulnerability } from '@IconsV2/ic-security-vulnerability.svg' import { ReactComponent as ICSelected } from '@IconsV2/ic-selected.svg' import { ReactComponent as ICShapes } from '@IconsV2/ic-shapes.svg' import { ReactComponent as ICShieldCheck } from '@IconsV2/ic-shield-check.svg' import { ReactComponent as ICSlidersVertical } from '@IconsV2/ic-sliders-vertical.svg' +import { ReactComponent as ICSoftwareReleaseManagement } from '@IconsV2/ic-software-release-management.svg' import { ReactComponent as ICSortAscending } from '@IconsV2/ic-sort-ascending.svg' import { ReactComponent as ICSortDescending } from '@IconsV2/ic-sort-descending.svg' import { ReactComponent as ICSortable } from '@IconsV2/ic-sortable.svg' @@ -213,8 +280,10 @@ import { ReactComponent as ICSuccess } from '@IconsV2/ic-success.svg' import { ReactComponent as ICSuccessBlue } from '@IconsV2/ic-success-blue.svg' import { ReactComponent as ICSun } from '@IconsV2/ic-sun.svg' import { ReactComponent as ICSuspended } from '@IconsV2/ic-suspended.svg' +import { ReactComponent as ICSymbolGreaterThan } from '@IconsV2/ic-symbol-greater-than.svg' import { ReactComponent as ICTag } from '@IconsV2/ic-tag.svg' import { ReactComponent as ICTata1mg } from '@IconsV2/ic-tata1mg.svg' +import { ReactComponent as ICTenants } from '@IconsV2/ic-tenants.svg' import { ReactComponent as ICTerminal } from '@IconsV2/ic-terminal.svg' import { ReactComponent as ICTerminalFill } from '@IconsV2/ic-terminal-fill.svg' import { ReactComponent as ICThermometer } from '@IconsV2/ic-thermometer.svg' @@ -224,6 +293,7 @@ import { ReactComponent as ICTimeoutDash } from '@IconsV2/ic-timeout-dash.svg' import { ReactComponent as ICTimer } from '@IconsV2/ic-timer.svg' import { ReactComponent as ICTrafficSignal } from '@IconsV2/ic-traffic-signal.svg' import { ReactComponent as ICTravclan } from '@IconsV2/ic-travclan.svg' +import { ReactComponent as ICTrendUp } from '@IconsV2/ic-trend-up.svg' import { ReactComponent as ICTwoCubes } from '@IconsV2/ic-two-cubes.svg' import { ReactComponent as ICUbuntu } from '@IconsV2/ic-ubuntu.svg' import { ReactComponent as ICUnknown } from '@IconsV2/ic-unknown.svg' @@ -236,6 +306,7 @@ import { ReactComponent as ICVisibilityOff } from '@IconsV2/ic-visibility-off.sv import { ReactComponent as ICVisibilityOn } from '@IconsV2/ic-visibility-on.svg' import { ReactComponent as ICWarning } from '@IconsV2/ic-warning.svg' import { ReactComponent as ICWarningFill } from '@IconsV2/ic-warning-fill.svg' +import { ReactComponent as ICWarningStroke } from '@IconsV2/ic-warning-stroke.svg' import { ReactComponent as ICWebhook } from '@IconsV2/ic-webhook.svg' import { ReactComponent as ICWifiSlash } from '@IconsV2/ic-wifi-slash.svg' import { ReactComponent as ICWorldGlobe } from '@IconsV2/ic-world-globe.svg' @@ -249,10 +320,18 @@ export const iconMap = { 'ic-aborted': ICAborted, 'ic-activity': ICActivity, 'ic-add': ICAdd, + 'ic-ai': ICAi, + 'ic-alibaba': ICAlibaba, 'ic-amazon-eks': ICAmazonEks, 'ic-app-group': ICAppGroup, 'ic-app-template': ICAppTemplate, + 'ic-application-group': ICApplicationGroup, + 'ic-application-management': ICApplicationManagement, + 'ic-application-template': ICApplicationTemplate, + 'ic-application': ICApplication, + 'ic-argocd-app': ICArgocdApp, 'ic-arrow-clockwise': ICArrowClockwise, + 'ic-arrow-line-down': ICArrowLineDown, 'ic-arrow-right': ICArrowRight, 'ic-arrow-square-out': ICArrowSquareOut, 'ic-arrows-clockwise': ICArrowsClockwise, @@ -260,18 +339,39 @@ export const iconMap = { 'ic-asterisk': ICAsterisk, 'ic-ather': ICAther, 'ic-aws-codecommit': ICAwsCodecommit, + 'ic-aws': ICAws, 'ic-azure-aks': ICAzureAks, 'ic-azure': ICAzure, + 'ic-backup-and-schedule': ICBackupAndSchedule, + 'ic-backup-color': ICBackupColor, + 'ic-backup-location': ICBackupLocation, + 'ic-backup-schedule-color': ICBackupScheduleColor, + 'ic-bg-backup-schedule': ICBgBackupSchedule, + 'ic-bg-backups': ICBgBackups, + 'ic-bg-build': ICBgBuild, 'ic-bg-cluster': ICBgCluster, + 'ic-bg-cpu': ICBgCpu, + 'ic-bg-deploy': ICBgDeploy, 'ic-bg-environment': ICBgEnvironment, + 'ic-bg-memory': ICBgMemory, + 'ic-bg-pause-schedule': ICBgPauseSchedule, + 'ic-bg-production-pipelines': ICBgProductionPipelines, + 'ic-bg-project': ICBgProject, + 'ic-bg-ransomware-vulnerable-cluster': ICBgRansomwareVulnerableCluster, + 'ic-bg-restore': ICBgRestore, + 'ic-bg-scan': ICBgScan, + 'ic-bg-storage-locations': ICBgStorageLocations, + 'ic-bg-webhook': ICBgWebhook, 'ic-bharatpe': ICBharatpe, 'ic-binoculars': ICBinoculars, 'ic-bitbucket': ICBitbucket, 'ic-book-open': ICBookOpen, + 'ic-bot': ICBot, 'ic-brain': ICBrain, 'ic-browser': ICBrowser, 'ic-bug': ICBug, 'ic-build-color': ICBuildColor, + 'ic-building': ICBuilding, 'ic-calendar': ICCalendar, 'ic-cancelled': ICCancelled, 'ic-card-stack': ICCardStack, @@ -279,29 +379,40 @@ export const iconMap = { 'ic-caret-left': ICCaretLeft, 'ic-caret-right': ICCaretRight, 'ic-cd': ICCd, + 'ic-chart-line-up': ICChartLineUp, + 'ic-chart-repo': ICChartRepo, 'ic-chat-circle-dots': ICChatCircleDots, 'ic-chat-circle-online': ICChatCircleOnline, 'ic-check-all': ICCheckAll, + 'ic-check-circle': ICCheckCircle, 'ic-check-square': ICCheckSquare, 'ic-check': ICCheck, 'ic-checks': ICChecks, 'ic-ci-linked': ICCiLinked, 'ic-circle-loader': ICCircleLoader, + 'ic-circle-small': ICCircleSmall, 'ic-clean-brush': ICCleanBrush, + 'ic-clipboard': ICClipboard, + 'ic-clock-counterclockwise': ICClockCounterclockwise, 'ic-clock': ICClock, 'ic-close-large': ICCloseLarge, 'ic-close-small': ICCloseSmall, + 'ic-cloud-upload': ICCloudUpload, 'ic-cloud-vms': ICCloudVms, 'ic-cluster-isolated': ICClusterIsolated, 'ic-cluster': ICCluster, 'ic-code': ICCode, + 'ic-coins-color-animated': ICCoinsColorAnimated, + 'ic-coins': ICCoins, 'ic-container-registry': ICContainerRegistry, 'ic-container': ICContainer, 'ic-cookr': ICCookr, 'ic-copy': ICCopy, + 'ic-cost-visibility': ICCostVisibility, 'ic-cpu': ICCpu, 'ic-crown': ICCrown, 'ic-cube': ICCube, + 'ic-database-backup': ICDatabaseBackup, 'ic-delete-dots': ICDeleteDots, 'ic-delete-lightning': ICDeleteLightning, 'ic-delete': ICDelete, @@ -312,6 +423,7 @@ export const iconMap = { 'ic-devtron-app': ICDevtronApp, 'ic-devtron-header-logo': ICDevtronHeaderLogo, 'ic-devtron-job': ICDevtronJob, + 'ic-devtron-text': ICDevtronText, 'ic-devtron': ICDevtron, 'ic-diff-added': ICDiffAdded, 'ic-diff-deleted': ICDiffDeleted, @@ -321,6 +433,7 @@ export const iconMap = { 'ic-dockerhub': ICDockerhub, 'ic-download': ICDownload, 'ic-ecr': ICEcr, + 'ic-edit-lines': ICEditLines, 'ic-edit': ICEdit, 'ic-email': ICEmail, 'ic-enter-fullscreen': ICEnterFullscreen, @@ -333,16 +446,21 @@ export const iconMap = { 'ic-exit-fullscreen': ICExitFullscreen, 'ic-expand-right-sm': ICExpandRightSm, 'ic-expand-sm': ICExpandSm, + 'ic-external-link': ICExternalLink, 'ic-failure': ICFailure, 'ic-fast-forward': ICFastForward, 'ic-file-code': ICFileCode, + 'ic-file-download': ICFileDownload, 'ic-file-edit': ICFileEdit, 'ic-file-key': ICFileKey, 'ic-file': ICFile, + 'ic-files-changed': ICFilesChanged, 'ic-files': ICFiles, 'ic-filter-applied': ICFilterApplied, 'ic-filter': ICFilter, 'ic-flask': ICFlask, + 'ic-floppy-disk': ICFloppyDisk, + 'ic-fluxcd-app': ICFluxcdApp, 'ic-folder-color': ICFolderColor, 'ic-folder-user': ICFolderUser, 'ic-folder': ICFolder, @@ -355,7 +473,9 @@ export const iconMap = { 'ic-git': ICGit, 'ic-github': ICGithub, 'ic-gitlab': ICGitlab, + 'ic-global-overview': ICGlobalOverview, 'ic-google-artifact-registry': ICGoogleArtifactRegistry, + 'ic-google-cloud': ICGoogleCloud, 'ic-google-container-registry': ICGoogleContainerRegistry, 'ic-google-gke': ICGoogleGke, 'ic-google': ICGoogle, @@ -376,6 +496,7 @@ export const iconMap = { 'ic-info-filled-color': ICInfoFilledColor, 'ic-info-filled': ICInfoFilled, 'ic-info-outline': ICInfoOutline, + 'ic-infrastructure-management': ICInfrastructureManagement, 'ic-input': ICInput, 'ic-install': ICInstall, 'ic-job-color': ICJobColor, @@ -384,6 +505,7 @@ export const iconMap = { 'ic-key-enter': ICKeyEnter, 'ic-key': ICKey, 'ic-kind': ICKind, + 'ic-kubernetes': ICKubernetes, 'ic-laptop': ICLaptop, 'ic-ldap': ICLdap, 'ic-lego': ICLego, @@ -396,6 +518,7 @@ export const iconMap = { 'ic-locked': ICLocked, 'ic-logout': ICLogout, 'ic-logs': ICLogs, + 'ic-magic-wand': ICMagicWand, 'ic-magnifying-glass': ICMagnifyingGlass, 'ic-medium-delete': ICMediumDelete, 'ic-medium-paintbucket': ICMediumPaintbucket, @@ -417,23 +540,38 @@ export const iconMap = { 'ic-open-in-new': ICOpenInNew, 'ic-openai': ICOpenai, 'ic-openshift': ICOpenshift, + 'ic-oracle-cloud': ICOracleCloud, + 'ic-otc-cloud': ICOtcCloud, 'ic-out-of-sync': ICOutOfSync, 'ic-paper-plane-color': ICPaperPlaneColor, 'ic-paper-plane': ICPaperPlane, + 'ic-party': ICParty, 'ic-path': ICPath, 'ic-pause-circle': ICPauseCircle, + 'ic-pause': ICPause, 'ic-pencil': ICPencil, 'ic-play-outline': ICPlayOutline, + 'ic-priority-medium-fill': ICPriorityMediumFill, 'ic-quay': ICQuay, 'ic-quote': ICQuote, 'ic-ratings': ICRatings, + 'ic-release-hub': ICReleaseHub, 'ic-resize-handle': ICResizeHandle, + 'ic-resource-browser': ICResourceBrowser, + 'ic-resource-watcher': ICResourceWatcher, + 'ic-right-panel-collapse': ICRightPanelCollapse, 'ic-rocket-gear': ICRocketGear, 'ic-rocket-launch': ICRocketLaunch, + 'ic-security-fixable': ICSecurityFixable, + 'ic-security-not-fixable': ICSecurityNotFixable, + 'ic-security-policy': ICSecurityPolicy, + 'ic-security-scan': ICSecurityScan, + 'ic-security-vulnerability': ICSecurityVulnerability, 'ic-selected': ICSelected, 'ic-shapes': ICShapes, 'ic-shield-check': ICShieldCheck, 'ic-sliders-vertical': ICSlidersVertical, + 'ic-software-release-management': ICSoftwareReleaseManagement, 'ic-sort-ascending': ICSortAscending, 'ic-sort-descending': ICSortDescending, 'ic-sortable': ICSortable, @@ -458,8 +596,10 @@ export const iconMap = { 'ic-success': ICSuccess, 'ic-sun': ICSun, 'ic-suspended': ICSuspended, + 'ic-symbol-greater-than': ICSymbolGreaterThan, 'ic-tag': ICTag, 'ic-tata1mg': ICTata1mg, + 'ic-tenants': ICTenants, 'ic-terminal-fill': ICTerminalFill, 'ic-terminal': ICTerminal, 'ic-thermometer': ICThermometer, @@ -469,6 +609,7 @@ export const iconMap = { 'ic-timer': ICTimer, 'ic-traffic-signal': ICTrafficSignal, 'ic-travclan': ICTravclan, + 'ic-trend-up': ICTrendUp, 'ic-two-cubes': ICTwoCubes, 'ic-ubuntu': ICUbuntu, 'ic-unknown': ICUnknown, @@ -480,6 +621,7 @@ export const iconMap = { 'ic-visibility-off': ICVisibilityOff, 'ic-visibility-on': ICVisibilityOn, 'ic-warning-fill': ICWarningFill, + 'ic-warning-stroke': ICWarningStroke, 'ic-warning': ICWarning, 'ic-webhook': ICWebhook, 'ic-wifi-slash': ICWifiSlash, diff --git a/src/Shared/Components/Icon/IconBase.tsx b/src/Shared/Components/Icon/IconBase.tsx index a42bdce0b..03c8c7bd8 100644 --- a/src/Shared/Components/Icon/IconBase.tsx +++ b/src/Shared/Components/Icon/IconBase.tsx @@ -38,6 +38,7 @@ export const IconBase = ({ dataTestId, rotateBy, fillSpace = false, + flip, }: IconBaseProps) => { const IconComponent = iconMap[name] @@ -45,17 +46,33 @@ export const IconBase = ({ throw new Error(`Icon with name "${name}" does not exist.`) } + const getFlipVariableValue = (): Record => { + if (isNullOrUndefined(flip)) { + return { + '--flip-x': 1, + '--flip-y': 1, + } + } + + if (flip === 'horizontal') { + return { '--flip-x': -1, '--flip-y': 1 } + } + + return { '--flip-x': 1, '--flip-y': -1 } + } + return ( diff --git a/src/Shared/Components/Icon/styles.scss b/src/Shared/Components/Icon/styles.scss index 6db101c04..b3dfbe2cb 100644 --- a/src/Shared/Components/Icon/styles.scss +++ b/src/Shared/Components/Icon/styles.scss @@ -31,3 +31,8 @@ stroke-width: var(--strokeWidth); } } + +.icon-component-transform { + transform: rotate(var(--rotateBy)) scaleX(var(--flip-x)) scaleY(var(--flip-y)); + transition: transform 0.3s; +} diff --git a/src/Shared/Components/Icon/types.ts b/src/Shared/Components/Icon/types.ts index 18515af27..f939a8e14 100644 --- a/src/Shared/Components/Icon/types.ts +++ b/src/Shared/Components/Icon/types.ts @@ -57,6 +57,12 @@ export interface IconBaseProps { * @example 90, 180, 270 */ rotateBy?: number + /** + * Flips the icon in the specified direction + * + * @default undefined + */ + flip?: 'horizontal' | 'vertical' /** * If true, the icon will expand to fill the available space of its container. */ diff --git a/src/Shared/Components/Illustration/Illustration.tsx b/src/Shared/Components/Illustration/Illustration.tsx index 8beee2a4b..63649ea9e 100644 --- a/src/Shared/Components/Illustration/Illustration.tsx +++ b/src/Shared/Components/Illustration/Illustration.tsx @@ -1,21 +1,33 @@ // NOTE: This file is auto-generated. Do not edit directly. Run the script `npm run generate-illustration` to update. +import CreateBackupSchedule from '@Illustrations/create-backup-schedule.webp' +import CreateBackupSnapshot from '@Illustrations/create-backup-snapshot.webp' import ImgCode from '@Illustrations/img-code.webp' import ImgDevtronFreemium from '@Illustrations/img-devtron-freemium.webp' +import { ReactComponent as ImgFolderEmpty } from '@Illustrations/img-folder-empty.svg' import ImgManOnRocket from '@Illustrations/img-man-on-rocket.webp' import { ReactComponent as ImgMechanicalOperation } from '@Illustrations/img-mechanical-operation.svg' +import { ReactComponent as ImgNoBackupLocation } from '@Illustrations/img-no-backup-location.svg' +import { ReactComponent as ImgNoRestores } from '@Illustrations/img-no-restores.svg' import ImgNoResult from '@Illustrations/img-no-result.webp' +import NoClusterCostEnabled from '@Illustrations/no-cluster-cost-enabled.webp' // eslint-disable-next-line no-restricted-imports import { IllustrationBase } from './IllustrationBase' import { IllustrationBaseProps } from './types' export const illustrationMap = { + 'img-folder-empty': ImgFolderEmpty, 'img-mechanical-operation': ImgMechanicalOperation, + 'img-no-backup-location': ImgNoBackupLocation, + 'img-no-restores': ImgNoRestores, + 'create-backup-schedule': CreateBackupSchedule, + 'create-backup-snapshot': CreateBackupSnapshot, 'img-code': ImgCode, 'img-devtron-freemium': ImgDevtronFreemium, 'img-man-on-rocket': ImgManOnRocket, 'img-no-result': ImgNoResult, + 'no-cluster-cost-enabled': NoClusterCostEnabled, } export type IllustrationName = keyof typeof illustrationMap diff --git a/src/Shared/Components/KeyboardShortcut/KeyboardShortcut.component.tsx b/src/Shared/Components/KeyboardShortcut/KeyboardShortcut.component.tsx new file mode 100644 index 000000000..ccbf82e0f --- /dev/null +++ b/src/Shared/Components/KeyboardShortcut/KeyboardShortcut.component.tsx @@ -0,0 +1,20 @@ +import { Icon } from '../Icon' +import { KEY_TO_UI_MAP } from './constants' +import { KeyboardShortcutProps } from './types' + +const KeyboardShortcut = ({ keyboardKey }: KeyboardShortcutProps) => ( + + {typeof KEY_TO_UI_MAP[keyboardKey] === 'string' ? ( + KEY_TO_UI_MAP[keyboardKey] + ) : ( + + )} + +) + +export default KeyboardShortcut diff --git a/src/Shared/Components/KeyboardShortcut/constants.tsx b/src/Shared/Components/KeyboardShortcut/constants.tsx new file mode 100644 index 000000000..c78220bb6 --- /dev/null +++ b/src/Shared/Components/KeyboardShortcut/constants.tsx @@ -0,0 +1,29 @@ +import { SupportedKeyboardKeysType } from '@Common/Hooks' +import { KEYBOARD_KEYS_MAP } from '@Common/Hooks/UseRegisterShortcut/types' + +import { IconsProps } from '../Icon' + +export const KEY_TO_UI_MAP: Record> = { + ...KEYBOARD_KEYS_MAP, + ArrowUp: { + name: 'ic-arrow-right', + rotateBy: -90, + }, + ArrowDown: { + name: 'ic-arrow-right', + rotateBy: 90, + }, + ArrowRight: { + name: 'ic-arrow-right', + }, + ArrowLeft: { + name: 'ic-arrow-right', + rotateBy: 180, + }, + Enter: { + name: 'ic-key-enter', + }, + '>': { + name: 'ic-symbol-greater-than', + }, +} diff --git a/src/Shared/Components/KeyboardShortcut/index.ts b/src/Shared/Components/KeyboardShortcut/index.ts new file mode 100644 index 000000000..02361cca2 --- /dev/null +++ b/src/Shared/Components/KeyboardShortcut/index.ts @@ -0,0 +1 @@ +export { default as KeyboardShortcut } from './KeyboardShortcut.component' diff --git a/src/Shared/Components/KeyboardShortcut/types.ts b/src/Shared/Components/KeyboardShortcut/types.ts new file mode 100644 index 000000000..9445e8b6a --- /dev/null +++ b/src/Shared/Components/KeyboardShortcut/types.ts @@ -0,0 +1,5 @@ +import { SupportedKeyboardKeysType } from '@Common/Hooks/UseRegisterShortcut/types' + +export interface KeyboardShortcutProps { + keyboardKey: SupportedKeyboardKeysType +} diff --git a/src/Shared/Components/ModalSidebarPanel/ModalSidebarPanel.component.tsx b/src/Shared/Components/ModalSidebarPanel/ModalSidebarPanel.component.tsx index 032f2f69b..7016f78ef 100644 --- a/src/Shared/Components/ModalSidebarPanel/ModalSidebarPanel.component.tsx +++ b/src/Shared/Components/ModalSidebarPanel/ModalSidebarPanel.component.tsx @@ -25,15 +25,17 @@ const ModalSidebarPanel = ({ documentationLink, }: ModalSidebarPanelProps) => (
-
- {(icon || heading) && ( -
- {icon && icon} - {heading &&

{heading}

} -
- )} - {children &&
{children}
} -
+ {(icon || heading || children) && ( +
+ {(icon || heading) && ( +
+ {icon && icon} + {heading &&

{heading}

} +
+ )} + {children &&
{children}
} +
+ )}
📙 Need help? { + const { endpoint, authType, userName, password, prometheusTlsClientCert, prometheusTlsClientKey } = prometheusConfig + const isBasicAuthError = authType.value === AuthenticationType.BASIC && (userName.error || password.error) + + return ( +
+
+ Prometheus configurations + + Devtron uses prometheus to store and track cluster metrics + +
+ {(isBasicAuthError || endpoint.error) && ( + + )} + +
+ Authentication Type + + Basic + Anonymous + +
+ {authType.value === AuthenticationType.BASIC ? ( + <> +
+ +
+
+ +
+ + ) : null} +