diff --git a/.gitignore b/.gitignore index 5be636033..a83ce6ccd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .devcontainer/dev.env .DS_Store +.vscode web/cypress/screenshots/ web/cypress/export-env.sh web/screenshots/ diff --git a/config/perses-dashboards.patch.json b/config/perses-dashboards.patch.json index 5420606a2..2b90e7ac9 100644 --- a/config/perses-dashboards.patch.json +++ b/config/perses-dashboards.patch.json @@ -8,7 +8,7 @@ "exact": false, "path": ["/multicloud/monitoring/v2/dashboards"], "component": { - "$codeRef": "DashboardsPage" + "$codeRef": "DashboardListPage" } } } @@ -36,7 +36,7 @@ "properties": { "exact": false, "path": ["/monitoring/v2/dashboards"], - "component": { "$codeRef": "DashboardsPage" } + "component": { "$codeRef": "DashboardListPage" } } } }, @@ -64,7 +64,7 @@ "properties": { "exact": false, "path": ["/virt-monitoring/v2/dashboards"], - "component": { "$codeRef": "DashboardsPage" } + "component": { "$codeRef": "DashboardListPage" } } } }, @@ -83,5 +83,41 @@ "insertAfter": "dashboards-virt" } } + }, + { + "op": "add", + "path": "/extensions/1", + "value": { + "type": "console.page/route", + "properties": { + "exact": false, + "path": ["/monitoring/v2/dashboards/view"], + "component": { "$codeRef": "DashboardPage" } + } + } + }, + { + "op": "add", + "path": "/extensions/1", + "value": { + "type": "console.page/route", + "properties": { + "exact": false, + "path": ["/virt-monitoring/v2/dashboards/view"], + "component": { "$codeRef": "DashboardPage" } + } + } + }, + { + "op": "add", + "path": "/extensions/1", + "value": { + "type": "console.page/route", + "properties": { + "exact": false, + "path": ["/multicloud/monitoring/v2/dashboards/view"], + "component": { "$codeRef": "DashboardPage" } + } + } } ] diff --git a/web/package-lock.json b/web/package-lock.json index d439cb4cc..eaaf45b95 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -31,32 +31,11 @@ "@patternfly/react-icons": "^6.2.0", "@patternfly/react-table": "^6.2.0", "@patternfly/react-templates": "^6.2.0", - "@perses-dev/bar-chart-plugin": "^0.9.0", - "@perses-dev/components": "^0.52.0", - "@perses-dev/core": "^0.52.0", - "@perses-dev/dashboards": "^0.52.0", - "@perses-dev/datasource-variable-plugin": "^0.3.2", - "@perses-dev/explore": "^0.52.0", - "@perses-dev/flame-chart-plugin": "^0.3.0", - "@perses-dev/gauge-chart-plugin": "^0.9.0", - "@perses-dev/heatmap-chart-plugin": "^0.2.1", - "@perses-dev/histogram-chart-plugin": "^0.9.0", - "@perses-dev/loki-plugin": "^0.1.1", - "@perses-dev/markdown-plugin": "^0.9.0", - "@perses-dev/pie-chart-plugin": "^0.9.0", - "@perses-dev/plugin-system": "^0.52.0", - "@perses-dev/prometheus-plugin": "^0.53.3", - "@perses-dev/pyroscope-plugin": "^0.3.1", - "@perses-dev/scatter-chart-plugin": "^0.8.0", - "@perses-dev/stat-chart-plugin": "^0.9.0", - "@perses-dev/static-list-variable-plugin": "^0.5.1", - "@perses-dev/status-history-chart-plugin": "^0.9.0", - "@perses-dev/table-plugin": "^0.8.0", - "@perses-dev/tempo-plugin": "^0.53.1", - "@perses-dev/timeseries-chart-plugin": "^0.10.1", - "@perses-dev/timeseries-table-plugin": "^0.9.0", - "@perses-dev/trace-table-plugin": "^0.8.1", - "@perses-dev/tracing-gantt-chart-plugin": "^0.9.2", + "@perses-dev/components": "0.53.0-rc.2", + "@perses-dev/core": "0.53.0-rc.1", + "@perses-dev/dashboards": "0.53.0-rc.2", + "@perses-dev/explore": "0.53.0-rc.2", + "@perses-dev/plugin-system": "0.53.0-rc.2", "@prometheus-io/codemirror-promql": "^0.37.0", "@tanstack/react-query": "^4.36.1", "@types/ajv": "^0.0.5", @@ -74,6 +53,7 @@ "murmurhash-js": "1.0.x", "react": "^17.0.1", "react-dom": "^17.0.1", + "react-hook-form": "^7.66.0", "react-i18next": "^11.8.11", "react-linkify": "^0.2.2", "react-modal": "^3.12.1", @@ -2342,37 +2322,6 @@ "react": ">=16.8.0" } }, - "node_modules/@emnapi/core": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.0.tgz", - "integrity": "sha512-pJdKGq/1iquWYtv1RRSljZklxHCOCAJFJrImO5ZLKPJVJlVUcs8yFwNQlqS0Lo8xT1VAXXTCZocF9n26FWEKsw==", - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.1.0", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.0.tgz", - "integrity": "sha512-oAYoQnCYaQZKVS53Fq23ceWMRxq5EhQsE0x0RdQ55jT7wagMu5k+fS39v1fiSLrtrLQlXwVINenqhLMtTrV/1Q==", - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", - "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@emotion/babel-plugin": { "version": "11.13.5", "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", @@ -2525,70 +2474,6 @@ "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", "license": "MIT" }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", - "integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz", - "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz", - "integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz", - "integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, "node_modules/@esbuild/darwin-arm64": { "version": "0.25.5", "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz", @@ -2596,6 +2481,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3069,24 +2955,6 @@ "integrity": "sha512-2hYR6r661Cq9B8zugtu6yxuOKqrVhAgfOSaPSq8XoxbC4ebsl0KOTy/vPoP+9U7JuQVLfrmikirW4a9Z0nDUug==", "license": "MIT" }, - "node_modules/@grafana/lezer-logql": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@grafana/lezer-logql/-/lezer-logql-0.2.9.tgz", - "integrity": "sha512-SB9E2LQ689PiI/OPuBoTF93O5hBb1n8DbS3uSXfH2YYTsQELHqwU2HSM8BAI/ThX1ggkvIN9y0JyNTqfMKjlBA==", - "license": "Apache-2.0", - "peerDependencies": { - "@lezer/lr": "^1.0.0" - } - }, - "node_modules/@grafana/lezer-traceql": { - "version": "0.0.20", - "resolved": "https://registry.npmjs.org/@grafana/lezer-traceql/-/lezer-traceql-0.0.20.tgz", - "integrity": "sha512-AqHLlceOEqDmZWV1FISBIR/l34rATHlPBuNGDA+2rmlvARHd+MS/DHm/K/53x0W+qZULF24JHzDrVPCHxQZ7cg==", - "license": "Apache-2.0", - "peerDependencies": { - "@lezer/lr": "^1.3.0" - } - }, "node_modules/@gulpjs/to-absolute-glob": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@gulpjs/to-absolute-glob/-/to-absolute-glob-4.0.0.tgz", @@ -4391,36 +4259,13 @@ "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", "license": "MIT" }, - "node_modules/@modern-js/node-bundle-require": { - "version": "2.68.2", - "resolved": "https://registry.npmjs.org/@modern-js/node-bundle-require/-/node-bundle-require-2.68.2.tgz", - "integrity": "sha512-MWk/pYx7KOsp+A/rN0as2ji/Ba8x0m129aqZ3Lj6T6CCTWdz0E/IsamPdTmF9Jnb6whQoBKtWSaLTCQlmCoY0Q==", - "license": "MIT", - "dependencies": { - "@modern-js/utils": "2.68.2", - "@swc/helpers": "^0.5.17", - "esbuild": "0.25.5" - } - }, - "node_modules/@modern-js/utils": { - "version": "2.68.2", - "resolved": "https://registry.npmjs.org/@modern-js/utils/-/utils-2.68.2.tgz", - "integrity": "sha512-revom/i/EhKfI0STNLo/AUbv7gY0JY0Ni2gO6P/Z4cTyZZRgd5j90678YB2DGn+LtmSrEWtUphyDH5Jn1RKjgg==", - "license": "MIT", - "dependencies": { - "@swc/helpers": "^0.5.17", - "caniuse-lite": "^1.0.30001520", - "lodash": "^4.17.21", - "rslog": "^1.1.0" - } - }, "node_modules/@module-federation/bridge-react-webpack-plugin": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@module-federation/bridge-react-webpack-plugin/-/bridge-react-webpack-plugin-0.19.1.tgz", - "integrity": "sha512-D+iFESodr/ohaXjmTOWBSFdjAz/WfN5Y5lIKB5Axh19FBUxvCy6Pj/We7C5JXc8CD9puqxXFOBNysJ7KNB89iw==", + "version": "0.21.6", + "resolved": "https://registry.npmjs.org/@module-federation/bridge-react-webpack-plugin/-/bridge-react-webpack-plugin-0.21.6.tgz", + "integrity": "sha512-lJMmdhD4VKVkeg8RHb+Jwe6Ou9zKVgjtb1inEURDG/sSS2ksdZA8pVKLYbRPRbdmjr193Y8gJfqFbI2dqoyc/g==", "license": "MIT", "dependencies": { - "@module-federation/sdk": "0.19.1", + "@module-federation/sdk": "0.21.6", "@types/semver": "7.5.8", "semver": "7.6.3" } @@ -4438,16 +4283,16 @@ } }, "node_modules/@module-federation/cli": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@module-federation/cli/-/cli-0.19.1.tgz", - "integrity": "sha512-WHEnqGLLtK3jFdAhhW5WMqF5TO4FUfgp6+ujuZLrB1iOnjJXwg/+3F/qjWQtfUPIUCJSAC+58TSKXo8FjNcxPA==", + "version": "0.21.6", + "resolved": "https://registry.npmjs.org/@module-federation/cli/-/cli-0.21.6.tgz", + "integrity": "sha512-qNojnlc8pTyKtK7ww3i/ujLrgWwgXqnD5DcDPsjADVIpu7STaoaVQ0G5GJ7WWS/ajXw6EyIAAGW/AMFh4XUxsQ==", "license": "MIT", "dependencies": { - "@modern-js/node-bundle-require": "2.68.2", - "@module-federation/dts-plugin": "0.19.1", - "@module-federation/sdk": "0.19.1", + "@module-federation/dts-plugin": "0.21.6", + "@module-federation/sdk": "0.21.6", "chalk": "3.0.0", - "commander": "11.1.0" + "commander": "11.1.0", + "jiti": "2.4.2" }, "bin": { "mf": "bin/mf.js" @@ -4524,13 +4369,13 @@ } }, "node_modules/@module-federation/data-prefetch": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@module-federation/data-prefetch/-/data-prefetch-0.19.1.tgz", - "integrity": "sha512-EXtEhYBw5XSHmtLp8Nu0sK2MMkdBtmvWQFfWmLDjPGGTeJHNE+fIHmef9hDbqXra8RpCyyZgwfTCUMZcwAGvzQ==", + "version": "0.21.6", + "resolved": "https://registry.npmjs.org/@module-federation/data-prefetch/-/data-prefetch-0.21.6.tgz", + "integrity": "sha512-8HD7ZhtWZ9vl6i3wA7M8cEeCRdtvxt09SbMTfqIPm+5eb/V4ijb8zGTYSRhNDb5RCB+BAixaPiZOWKXJ63/rVw==", "license": "MIT", "dependencies": { - "@module-federation/runtime": "0.19.1", - "@module-federation/sdk": "0.19.1", + "@module-federation/runtime": "0.21.6", + "@module-federation/sdk": "0.21.6", "fs-extra": "9.1.0" }, "peerDependencies": { @@ -4539,22 +4384,22 @@ } }, "node_modules/@module-federation/dts-plugin": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@module-federation/dts-plugin/-/dts-plugin-0.19.1.tgz", - "integrity": "sha512-/MV5gbEsiQiDwPmEq8WS24P/ibDtRwM7ejRKwZ+vWqv11jg75FlxHdzl71CMt5AatoPiUkrsPDQDO1EmKz/NXQ==", + "version": "0.21.6", + "resolved": "https://registry.npmjs.org/@module-federation/dts-plugin/-/dts-plugin-0.21.6.tgz", + "integrity": "sha512-YIsDk8/7QZIWn0I1TAYULniMsbyi2LgKTi9OInzVmZkwMC6644x/ratTWBOUDbdY1Co+feNkoYeot1qIWv2L7w==", "license": "MIT", "dependencies": { - "@module-federation/error-codes": "0.19.1", - "@module-federation/managers": "0.19.1", - "@module-federation/sdk": "0.19.1", - "@module-federation/third-party-dts-extractor": "0.19.1", + "@module-federation/error-codes": "0.21.6", + "@module-federation/managers": "0.21.6", + "@module-federation/sdk": "0.21.6", + "@module-federation/third-party-dts-extractor": "0.21.6", "adm-zip": "^0.5.10", "ansi-colors": "^4.1.3", - "axios": "^1.11.0", + "axios": "^1.12.0", "chalk": "3.0.0", "fs-extra": "9.1.0", "isomorphic-ws": "5.0.0", - "koa": "3.0.1", + "koa": "3.0.3", "lodash.clonedeepwith": "4.5.0", "log4js": "6.9.1", "node-schedule": "2.1.1", @@ -4639,22 +4484,22 @@ } }, "node_modules/@module-federation/enhanced": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@module-federation/enhanced/-/enhanced-0.19.1.tgz", - "integrity": "sha512-cSNbV5IFZRECpKEdIhIGNW9dNPjyDmSFlPIV0OG7aP4zAmUtz/oizpYtEE5r7hLAGxzWwBnj7zQIIxvmKgrSAQ==", - "license": "MIT", - "dependencies": { - "@module-federation/bridge-react-webpack-plugin": "0.19.1", - "@module-federation/cli": "0.19.1", - "@module-federation/data-prefetch": "0.19.1", - "@module-federation/dts-plugin": "0.19.1", - "@module-federation/error-codes": "0.19.1", - "@module-federation/inject-external-runtime-core-plugin": "0.19.1", - "@module-federation/managers": "0.19.1", - "@module-federation/manifest": "0.19.1", - "@module-federation/rspack": "0.19.1", - "@module-federation/runtime-tools": "0.19.1", - "@module-federation/sdk": "0.19.1", + "version": "0.21.6", + "resolved": "https://registry.npmjs.org/@module-federation/enhanced/-/enhanced-0.21.6.tgz", + "integrity": "sha512-8PFQxtmXc6ukBC4CqGIoc96M2Ly9WVwCPu4Ffvt+K/SB6rGbeFeZoYAwREV1zGNMJ5v5ly6+AHIEOBxNuSnzSg==", + "license": "MIT", + "dependencies": { + "@module-federation/bridge-react-webpack-plugin": "0.21.6", + "@module-federation/cli": "0.21.6", + "@module-federation/data-prefetch": "0.21.6", + "@module-federation/dts-plugin": "0.21.6", + "@module-federation/error-codes": "0.21.6", + "@module-federation/inject-external-runtime-core-plugin": "0.21.6", + "@module-federation/managers": "0.21.6", + "@module-federation/manifest": "0.21.6", + "@module-federation/rspack": "0.21.6", + "@module-federation/runtime-tools": "0.21.6", + "@module-federation/sdk": "0.21.6", "btoa": "^1.2.1", "schema-utils": "^4.3.0", "upath": "2.0.1" @@ -4680,40 +4525,40 @@ } }, "node_modules/@module-federation/error-codes": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@module-federation/error-codes/-/error-codes-0.19.1.tgz", - "integrity": "sha512-XtrOfaYPBD9UbdWb7O+gk295/5EFfC2/R6JmhbQmM2mt2axlrwUoy29LAEMSpyMkAD0NfRfQ3HaOsJQiUIy+Qg==", + "version": "0.21.6", + "resolved": "https://registry.npmjs.org/@module-federation/error-codes/-/error-codes-0.21.6.tgz", + "integrity": "sha512-MLJUCQ05KnoVl8xd6xs9a5g2/8U+eWmVxg7xiBMeR0+7OjdWUbHwcwgVFatRIwSZvFgKHfWEiI7wsU1q1XbTRQ==", "license": "MIT" }, "node_modules/@module-federation/inject-external-runtime-core-plugin": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@module-federation/inject-external-runtime-core-plugin/-/inject-external-runtime-core-plugin-0.19.1.tgz", - "integrity": "sha512-yOErRSKR60H4Zyk4nUqsc7u7eLaZ5KX3FXAyKxdGwIJ1B8jJJS+xRiQM8bwRansoF23rv7XWO62K5w/qONiTuQ==", + "version": "0.21.6", + "resolved": "https://registry.npmjs.org/@module-federation/inject-external-runtime-core-plugin/-/inject-external-runtime-core-plugin-0.21.6.tgz", + "integrity": "sha512-DJQne7NQ988AVi3QB8byn12FkNb+C2lBeU1NRf8/WbL0gmHsr6kW8hiEJCm8LYaURwtsQqtsEV7i+8+51qjSmQ==", "license": "MIT", "peerDependencies": { - "@module-federation/runtime-tools": "0.19.1" + "@module-federation/runtime-tools": "0.21.6" } }, "node_modules/@module-federation/managers": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@module-federation/managers/-/managers-0.19.1.tgz", - "integrity": "sha512-bZwiRqc0Cy76xSgKw8dFpVc0tpu6EG+paL0bAtHU5Kj9SBRGyCZ1JQY2W+S8z5tS/7M+gDNl9iIgQim+Kq6isg==", + "version": "0.21.6", + "resolved": "https://registry.npmjs.org/@module-federation/managers/-/managers-0.21.6.tgz", + "integrity": "sha512-BeV6m2/7kF5MDVz9JJI5T8h8lMosnXkH2bOxxFewcra7ZjvDOgQu7WIio0mgk5l1zjNPvnEVKhnhrenEdcCiWg==", "license": "MIT", "dependencies": { - "@module-federation/sdk": "0.19.1", + "@module-federation/sdk": "0.21.6", "find-pkg": "2.0.0", "fs-extra": "9.1.0" } }, "node_modules/@module-federation/manifest": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@module-federation/manifest/-/manifest-0.19.1.tgz", - "integrity": "sha512-6QruFQRpedVpHq2JpsYFMrFQvSbqe4QcGjk6zYWQCx+kcUvxYuKwfRzhyJt/Sorqz2rW92I2ckmlHKufCLOmTg==", + "version": "0.21.6", + "resolved": "https://registry.npmjs.org/@module-federation/manifest/-/manifest-0.21.6.tgz", + "integrity": "sha512-yg93+I1qjRs5B5hOSvjbjmIoI2z3th8/yst9sfwvx4UDOG1acsE3HHMyPN0GdoIGwplC/KAnU5NmUz4tREUTGQ==", "license": "MIT", "dependencies": { - "@module-federation/dts-plugin": "0.19.1", - "@module-federation/managers": "0.19.1", - "@module-federation/sdk": "0.19.1", + "@module-federation/dts-plugin": "0.21.6", + "@module-federation/managers": "0.21.6", + "@module-federation/sdk": "0.21.6", "chalk": "3.0.0", "find-pkg": "2.0.0" } @@ -4786,18 +4631,18 @@ } }, "node_modules/@module-federation/rspack": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@module-federation/rspack/-/rspack-0.19.1.tgz", - "integrity": "sha512-H/bmdHhK91JIar9juyxdGQkjk5fLwbfugoBwFzxCx0PybwKObs+ZHW7yZ1ZoVBsRkYmvV79R2Squgtn/aGReCA==", - "license": "MIT", - "dependencies": { - "@module-federation/bridge-react-webpack-plugin": "0.19.1", - "@module-federation/dts-plugin": "0.19.1", - "@module-federation/inject-external-runtime-core-plugin": "0.19.1", - "@module-federation/managers": "0.19.1", - "@module-federation/manifest": "0.19.1", - "@module-federation/runtime-tools": "0.19.1", - "@module-federation/sdk": "0.19.1", + "version": "0.21.6", + "resolved": "https://registry.npmjs.org/@module-federation/rspack/-/rspack-0.21.6.tgz", + "integrity": "sha512-SB+z1P+Bqe3R6geZje9dp0xpspX6uash+zO77nodmUy8PTTBlkL7800Cq2FMLKUdoTZHJTBVXf0K6CqQWSlItg==", + "license": "MIT", + "dependencies": { + "@module-federation/bridge-react-webpack-plugin": "0.21.6", + "@module-federation/dts-plugin": "0.21.6", + "@module-federation/inject-external-runtime-core-plugin": "0.21.6", + "@module-federation/managers": "0.21.6", + "@module-federation/manifest": "0.21.6", + "@module-federation/runtime-tools": "0.21.6", + "@module-federation/sdk": "0.21.6", "btoa": "1.2.1" }, "peerDependencies": { @@ -4815,46 +4660,46 @@ } }, "node_modules/@module-federation/runtime": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@module-federation/runtime/-/runtime-0.19.1.tgz", - "integrity": "sha512-eSXexdGGPpZnhiWCVfRlVLNWj7gHKp65beC4b8wddTvMBIrxnsdl9ae1ebwcIpbe9gOGDbaXBFtc3r5MH6l6Jg==", + "version": "0.21.6", + "resolved": "https://registry.npmjs.org/@module-federation/runtime/-/runtime-0.21.6.tgz", + "integrity": "sha512-+caXwaQqwTNh+CQqyb4mZmXq7iEemRDrTZQGD+zyeH454JAYnJ3s/3oDFizdH6245pk+NiqDyOOkHzzFQorKhQ==", "license": "MIT", "dependencies": { - "@module-federation/error-codes": "0.19.1", - "@module-federation/runtime-core": "0.19.1", - "@module-federation/sdk": "0.19.1" + "@module-federation/error-codes": "0.21.6", + "@module-federation/runtime-core": "0.21.6", + "@module-federation/sdk": "0.21.6" } }, "node_modules/@module-federation/runtime-core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@module-federation/runtime-core/-/runtime-core-0.19.1.tgz", - "integrity": "sha512-NLSlPnIzO2RoF6W1xq/x3t1j7jcglMaPSv2EIVOFvs5/ah7BeJmRhtH494tmjIwV0q+j1QEGGhijHxXZLK1HMQ==", + "version": "0.21.6", + "resolved": "https://registry.npmjs.org/@module-federation/runtime-core/-/runtime-core-0.21.6.tgz", + "integrity": "sha512-5Hd1Y5qp5lU/aTiK66lidMlM/4ji2gr3EXAtJdreJzkY+bKcI5+21GRcliZ4RAkICmvdxQU5PHPL71XmNc7Lsw==", "license": "MIT", "dependencies": { - "@module-federation/error-codes": "0.19.1", - "@module-federation/sdk": "0.19.1" + "@module-federation/error-codes": "0.21.6", + "@module-federation/sdk": "0.21.6" } }, "node_modules/@module-federation/runtime-tools": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@module-federation/runtime-tools/-/runtime-tools-0.19.1.tgz", - "integrity": "sha512-WjLZcuP7U5pSQobMEvaMH9pFrvfV3Kk2dfOUNza0tpj6vYtAxk6FU6TQ8WDjqG7yuglyAzq0bVEKVrdIB4Vd9Q==", + "version": "0.21.6", + "resolved": "https://registry.npmjs.org/@module-federation/runtime-tools/-/runtime-tools-0.21.6.tgz", + "integrity": "sha512-fnP+ZOZTFeBGiTAnxve+axGmiYn2D60h86nUISXjXClK3LUY1krUfPgf6MaD4YDJ4i51OGXZWPekeMe16pkd8Q==", "license": "MIT", "dependencies": { - "@module-federation/runtime": "0.19.1", - "@module-federation/webpack-bundler-runtime": "0.19.1" + "@module-federation/runtime": "0.21.6", + "@module-federation/webpack-bundler-runtime": "0.21.6" } }, "node_modules/@module-federation/sdk": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@module-federation/sdk/-/sdk-0.19.1.tgz", - "integrity": "sha512-0JTkYaa4qNLtYGc6ZQQ50BinWh4bAOgT8t17jB/6BqcWiza6fKz647wN0AK+VX3rtl6kvGAjhtqqZtRBc8aeiw==", + "version": "0.21.6", + "resolved": "https://registry.npmjs.org/@module-federation/sdk/-/sdk-0.21.6.tgz", + "integrity": "sha512-x6hARETb8iqHVhEsQBysuWpznNZViUh84qV2yE7AD+g7uIzHKiYdoWqj10posbo5XKf/147qgWDzKZoKoEP2dw==", "license": "MIT" }, "node_modules/@module-federation/third-party-dts-extractor": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@module-federation/third-party-dts-extractor/-/third-party-dts-extractor-0.19.1.tgz", - "integrity": "sha512-XBuujPLWgJjljm/QfShtI0pErqRL28iiJ7AsUpFsNbSRJiBlcXTDPKqFWiZXmp/lGmJigLV2wDgyK0cyKqoWcg==", + "version": "0.21.6", + "resolved": "https://registry.npmjs.org/@module-federation/third-party-dts-extractor/-/third-party-dts-extractor-0.21.6.tgz", + "integrity": "sha512-Il6x4hLsvCgZNk1DFwuMBNeoxD1BsZ5AW2BI/nUgu0k5FiAvfcz1OFawRFEHtaM/kVrCsymMOW7pCao90DaX3A==", "license": "MIT", "dependencies": { "find-pkg": "2.0.0", @@ -4863,13 +4708,13 @@ } }, "node_modules/@module-federation/webpack-bundler-runtime": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@module-federation/webpack-bundler-runtime/-/webpack-bundler-runtime-0.19.1.tgz", - "integrity": "sha512-pr9kgwvBoe8tvXELDCqu8ihvLJYwS+cfwJmvk99MTbespzK0nuOepkeRCy2gOpeATDNiWdy/2DJcw34qeAmhJw==", + "version": "0.21.6", + "resolved": "https://registry.npmjs.org/@module-federation/webpack-bundler-runtime/-/webpack-bundler-runtime-0.21.6.tgz", + "integrity": "sha512-7zIp3LrcWbhGuFDTUMLJ2FJvcwjlddqhWGxi/MW3ur1a+HaO8v5tF2nl+vElKmbG1DFLU/52l3PElVcWf/YcsQ==", "license": "MIT", "dependencies": { - "@module-federation/runtime": "0.19.1", - "@module-federation/sdk": "0.19.1" + "@module-federation/runtime": "0.21.6", + "@module-federation/sdk": "0.21.6" } }, "node_modules/@mui/core-downloads-tracker": { @@ -5076,50 +4921,6 @@ } } }, - "node_modules/@mui/x-data-grid": { - "version": "7.29.9", - "resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-7.29.9.tgz", - "integrity": "sha512-RfK7Fnuu4eyv/4eD3MEB1xxZsx0xRBsofb1kifghIjyQV1EKAeRcwvczyrzQggj7ZRT5AqkwCzhLsZDvE5O0nQ==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.25.7", - "@mui/utils": "^5.16.6 || ^6.0.0 || ^7.0.0", - "@mui/x-internals": "7.29.0", - "clsx": "^2.1.1", - "prop-types": "^15.8.1", - "reselect": "^5.1.1", - "use-sync-external-store": "^1.0.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@emotion/react": "^11.9.0", - "@emotion/styled": "^11.8.1", - "@mui/material": "^5.15.14 || ^6.0.0 || ^7.0.0", - "@mui/system": "^5.15.14 || ^6.0.0 || ^7.0.0", - "react": "^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@emotion/react": { - "optional": true - }, - "@emotion/styled": { - "optional": true - } - } - }, - "node_modules/@mui/x-data-grid/node_modules/reselect": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", - "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", - "license": "MIT" - }, "node_modules/@mui/x-date-pickers": { "version": "7.29.4", "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.29.4.tgz", @@ -5206,19 +5007,6 @@ "react": "^17.0.0 || ^18.0.0 || ^19.0.0" } }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.7.tgz", - "integrity": "sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@emnapi/core": "^1.5.0", - "@emnapi/runtime": "^1.5.0", - "@tybys/wasm-util": "^0.10.1" - } - }, "node_modules/@nexucis/fuzzy": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/@nexucis/fuzzy/-/fuzzy-0.5.1.tgz", @@ -5464,27 +5252,6 @@ "@parcel/watcher-win32-x64": "2.5.1" } }, - "node_modules/@parcel/watcher-android-arm64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", - "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, "node_modules/@parcel/watcher-darwin-arm64": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", @@ -5506,326 +5273,95 @@ "url": "https://opencollective.com/parcel" } }, - "node_modules/@parcel/watcher-darwin-x64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", - "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/@patternfly/react-charts": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@patternfly/react-charts/-/react-charts-8.4.0.tgz", + "integrity": "sha512-lxfH2gVDg4Pd+D6TQ2SSqc5fQPk1UvhHbuP+7YZJdPhk2PzhhbaT3CE+kp5ZEU2y/lJb8L5kZ5lOk8tvPn6PQw==", "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10.0.0" + "dependencies": { + "@patternfly/react-styles": "^6.4.0", + "@patternfly/react-tokens": "^6.4.0", + "hoist-non-react-statics": "^3.3.2", + "lodash": "^4.17.21", + "tslib": "^2.8.1" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-freebsd-x64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", - "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm-glibc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", - "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm-musl": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", - "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm64-glibc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", - "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm64-musl": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", - "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-x64-glibc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", - "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-x64-musl": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", - "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-win32-arm64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", - "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-win32-ia32": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", - "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-win32-x64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", - "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@patternfly/react-charts": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/@patternfly/react-charts/-/react-charts-8.4.0.tgz", - "integrity": "sha512-lxfH2gVDg4Pd+D6TQ2SSqc5fQPk1UvhHbuP+7YZJdPhk2PzhhbaT3CE+kp5ZEU2y/lJb8L5kZ5lOk8tvPn6PQw==", - "license": "MIT", - "dependencies": { - "@patternfly/react-styles": "^6.4.0", - "@patternfly/react-tokens": "^6.4.0", - "hoist-non-react-statics": "^3.3.2", - "lodash": "^4.17.21", - "tslib": "^2.8.1" - }, - "peerDependencies": { - "echarts": "^5.6.0 || ^6.0.0", - "react": "^17 || ^18 || ^19", - "react-dom": "^17 || ^18 || ^19", - "victory-area": "^37.3.6", - "victory-axis": "^37.3.6", - "victory-bar": "^37.3.6", - "victory-box-plot": "^37.3.6", - "victory-chart": "^37.3.6", - "victory-core": "^37.3.6", - "victory-create-container": "^37.3.6", - "victory-cursor-container": "^37.3.6", - "victory-group": "^37.3.6", - "victory-legend": "^37.3.6", - "victory-line": "^37.3.6", - "victory-pie": "^37.3.6", - "victory-scatter": "^37.3.6", - "victory-stack": "^37.3.6", - "victory-tooltip": "^37.3.6", - "victory-voronoi-container": "^37.3.6", - "victory-zoom-container": "^37.3.6" - }, - "peerDependenciesMeta": { - "echarts": { - "optional": true - }, - "victory-area": { - "optional": true - }, - "victory-axis": { - "optional": true - }, - "victory-bar": { - "optional": true - }, - "victory-box-plot": { - "optional": true - }, - "victory-chart": { - "optional": true - }, - "victory-core": { - "optional": true - }, - "victory-create-container": { - "optional": true - }, - "victory-cursor-container": { - "optional": true - }, - "victory-group": { - "optional": true - }, - "victory-legend": { - "optional": true - }, - "victory-line": { - "optional": true - }, - "victory-pie": { - "optional": true - }, - "victory-scatter": { - "optional": true - }, - "victory-stack": { - "optional": true - }, - "victory-tooltip": { - "optional": true - }, - "victory-voronoi-container": { - "optional": true - }, - "victory-zoom-container": { - "optional": true - } + "peerDependencies": { + "echarts": "^5.6.0 || ^6.0.0", + "react": "^17 || ^18 || ^19", + "react-dom": "^17 || ^18 || ^19", + "victory-area": "^37.3.6", + "victory-axis": "^37.3.6", + "victory-bar": "^37.3.6", + "victory-box-plot": "^37.3.6", + "victory-chart": "^37.3.6", + "victory-core": "^37.3.6", + "victory-create-container": "^37.3.6", + "victory-cursor-container": "^37.3.6", + "victory-group": "^37.3.6", + "victory-legend": "^37.3.6", + "victory-line": "^37.3.6", + "victory-pie": "^37.3.6", + "victory-scatter": "^37.3.6", + "victory-stack": "^37.3.6", + "victory-tooltip": "^37.3.6", + "victory-voronoi-container": "^37.3.6", + "victory-zoom-container": "^37.3.6" + }, + "peerDependenciesMeta": { + "echarts": { + "optional": true + }, + "victory-area": { + "optional": true + }, + "victory-axis": { + "optional": true + }, + "victory-bar": { + "optional": true + }, + "victory-box-plot": { + "optional": true + }, + "victory-chart": { + "optional": true + }, + "victory-core": { + "optional": true + }, + "victory-create-container": { + "optional": true + }, + "victory-cursor-container": { + "optional": true + }, + "victory-group": { + "optional": true + }, + "victory-legend": { + "optional": true + }, + "victory-line": { + "optional": true + }, + "victory-pie": { + "optional": true + }, + "victory-scatter": { + "optional": true + }, + "victory-stack": { + "optional": true + }, + "victory-tooltip": { + "optional": true + }, + "victory-voronoi-container": { + "optional": true + }, + "victory-zoom-container": { + "optional": true + } } }, "node_modules/@patternfly/react-component-groups": { @@ -5981,34 +5517,13 @@ }, "peerDependencies": { "react": "^17 || ^18 || ^19", - "react-dom": "^17 || ^18 || ^19" - } - }, - "node_modules/@perses-dev/bar-chart-plugin": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/@perses-dev/bar-chart-plugin/-/bar-chart-plugin-0.9.1.tgz", - "integrity": "sha512-LqUsDsz+JQf86HVt3zO6hv+cXMtplbuDNxdQ1eqFmsafLhZ/GeoaSXcQ3R7l5Nil1kgqKfh2r3LlTvQEwp4Z0Q==", - "peerDependencies": { - "@emotion/react": "^11.7.1", - "@emotion/styled": "^11.6.0", - "@hookform/resolvers": "^3.2.0", - "@perses-dev/components": "^0.52.0", - "@perses-dev/core": "^0.52.0", - "@perses-dev/plugin-system": "^0.52.0", - "date-fns": "^4.1.0", - "date-fns-tz": "^3.2.0", - "echarts": "5.5.0", - "immer": "^10.1.1", - "lodash": "^4.17.21", - "react": "^17.0.2 || ^18.0.0", - "react-dom": "^17.0.2 || ^18.0.0", - "use-resize-observer": "^9.0.0" + "react-dom": "^17 || ^18 || ^19" } }, "node_modules/@perses-dev/components": { - "version": "0.52.0", - "resolved": "https://registry.npmjs.org/@perses-dev/components/-/components-0.52.0.tgz", - "integrity": "sha512-SPWHI/DKdUFiP4b3tP1+MgU+N4Y2ZGMWIXN7Fd+qRvU0vU0J7RWTjvszKrznLBboo1pPZQpoFE+Y6V3A2TFgxA==", + "version": "0.53.0-rc.2", + "resolved": "https://registry.npmjs.org/@perses-dev/components/-/components-0.53.0-rc.2.tgz", + "integrity": "sha512-faH+rdmUIVjpkc4tJAWnOw0dx6DAHqikKh/ODCuVAnWUmZ8X25K1MBueLWreOABto6vzzL1ZVYkUaO2DuPjp0w==", "license": "Apache-2.0", "dependencies": { "@atlaskit/pragmatic-drag-and-drop": "^1.4.0", @@ -6016,7 +5531,7 @@ "@codemirror/lang-json": "^6.0.1", "@fontsource/lato": "^4.5.10", "@mui/x-date-pickers": "^7.23.1", - "@perses-dev/core": "0.52.0", + "@perses-dev/core": "0.53.0-rc.0", "@tanstack/react-table": "^8.20.5", "@uiw/react-codemirror": "^4.19.1", "date-fns": "^4.1.0", @@ -6034,14 +5549,28 @@ }, "peerDependencies": { "@mui/material": "^6.1.10", + "lodash": "^4.17.21", "react": "^17.0.2 || ^18.0.0", "react-dom": "^17.0.2 || ^18.0.0" } }, + "node_modules/@perses-dev/components/node_modules/@perses-dev/core": { + "version": "0.53.0-rc.0", + "resolved": "https://registry.npmjs.org/@perses-dev/core/-/core-0.53.0-rc.0.tgz", + "integrity": "sha512-hcFY/l7PlQZ9lz/uAh0J8Txw0l+W2mkalNcDu+CGvusc2sgQznrCyn7hh/UppSN7ls1JgwaCqjjVeqBEHEjsrQ==", + "license": "Apache-2.0", + "dependencies": { + "date-fns": "^4.1.0", + "lodash": "^4.17.21", + "mathjs": "^10.6.4", + "numbro": "^2.3.6", + "zod": "^3.21.4" + } + }, "node_modules/@perses-dev/core": { - "version": "0.52.0", - "resolved": "https://registry.npmjs.org/@perses-dev/core/-/core-0.52.0.tgz", - "integrity": "sha512-dtaNSgVx4YH3kmQLdHyFun+C2WH1Fp85lLKA2ZEyAMX5hQZ3GwL1cusb2J51R4SPHRJ79YCtkbwD7dYnJBnrsw==", + "version": "0.53.0-rc.1", + "resolved": "https://registry.npmjs.org/@perses-dev/core/-/core-0.53.0-rc.1.tgz", + "integrity": "sha512-jc8iaQ0N3GfA0LxBR88Wmv4pKIJH6TXYPRZeYDxKjKPIkZi4t0c/yYMVu6CV2BAFeKzPxc+R7wZ8LFVWj7SCPQ==", "license": "Apache-2.0", "dependencies": { "date-fns": "^4.1.0", @@ -6049,21 +5578,17 @@ "mathjs": "^10.6.4", "numbro": "^2.3.6", "zod": "^3.21.4" - }, - "peerDependencies": { - "react": "^17.0.2 || ^18.0.0", - "react-dom": "^17.0.2 || ^18.0.0" } }, "node_modules/@perses-dev/dashboards": { - "version": "0.52.0", - "resolved": "https://registry.npmjs.org/@perses-dev/dashboards/-/dashboards-0.52.0.tgz", - "integrity": "sha512-InOalnIIUJxqOaJI8t1+FPOXf0OhQ0+BczNU+CP28THUpIPAxliOZUzvLGQ/OqEFepnapb+RlOYCNKF04Cdcpw==", + "version": "0.53.0-rc.2", + "resolved": "https://registry.npmjs.org/@perses-dev/dashboards/-/dashboards-0.53.0-rc.2.tgz", + "integrity": "sha512-MNDgv3gGBa5hb3LZu/dlKRNoL1x2bQWnXxQlma7yOQ1J/m/W5ZB2DpkaZ2ttxori7yJyRvYYEFoT9fnVJOlZ/Q==", "license": "Apache-2.0", "dependencies": { - "@perses-dev/components": "0.52.0", - "@perses-dev/core": "0.52.0", - "@perses-dev/plugin-system": "0.52.0", + "@perses-dev/components": "0.53.0-rc.2", + "@perses-dev/core": "0.53.0-rc.0", + "@perses-dev/plugin-system": "0.53.0-rc.2", "@types/react-grid-layout": "^1.3.2", "date-fns": "^4.1.0", "immer": "^10.1.1", @@ -6084,37 +5609,30 @@ "react-dom": "^17.0.2 || ^18.0.0" } }, - "node_modules/@perses-dev/datasource-variable-plugin": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@perses-dev/datasource-variable-plugin/-/datasource-variable-plugin-0.3.2.tgz", - "integrity": "sha512-GOmL55qmumdzvYqrvFmnlVveEwiUJ8IFB58uRyTwpyRDRQDv37iFlD4CeJeVGy7KUHiGqZNwLPPgGt7JDtzb7Q==", - "peerDependencies": { - "@emotion/react": "^11.7.1", - "@emotion/styled": "^11.6.0", - "@hookform/resolvers": "^3.2.0", - "@perses-dev/components": "^0.52.0", - "@perses-dev/core": "^0.52.0", - "@perses-dev/plugin-system": "^0.52.0", + "node_modules/@perses-dev/dashboards/node_modules/@perses-dev/core": { + "version": "0.53.0-rc.0", + "resolved": "https://registry.npmjs.org/@perses-dev/core/-/core-0.53.0-rc.0.tgz", + "integrity": "sha512-hcFY/l7PlQZ9lz/uAh0J8Txw0l+W2mkalNcDu+CGvusc2sgQznrCyn7hh/UppSN7ls1JgwaCqjjVeqBEHEjsrQ==", + "license": "Apache-2.0", + "dependencies": { "date-fns": "^4.1.0", - "date-fns-tz": "^3.2.0", - "echarts": "5.5.0", "lodash": "^4.17.21", - "react": "^17.0.2 || ^18.0.0", - "react-dom": "^17.0.2 || ^18.0.0", - "use-resize-observer": "^9.0.0" + "mathjs": "^10.6.4", + "numbro": "^2.3.6", + "zod": "^3.21.4" } }, "node_modules/@perses-dev/explore": { - "version": "0.52.0", - "resolved": "https://registry.npmjs.org/@perses-dev/explore/-/explore-0.52.0.tgz", - "integrity": "sha512-J5ur8+LhRasOf57E5txGFrEurYC5wihkk019BHgPnBxPlusxYxuwdp/s25PGtRP/pXFHxQejogIS9GnyhX+xcA==", + "version": "0.53.0-rc.2", + "resolved": "https://registry.npmjs.org/@perses-dev/explore/-/explore-0.53.0-rc.2.tgz", + "integrity": "sha512-mehc1IxBcAFwzBR7vyG/AbrYprLK75FhgXzBIfhnQSIIQJFpjRyspcY3M2TTLaXxTUld+Jc5mlLYJFu1o10U8A==", "license": "Apache-2.0", "dependencies": { "@nexucis/fuzzy": "^0.5.1", - "@perses-dev/components": "0.52.0", - "@perses-dev/core": "0.52.0", - "@perses-dev/dashboards": "0.52.0", - "@perses-dev/plugin-system": "0.52.0", + "@perses-dev/components": "0.53.0-rc.2", + "@perses-dev/core": "0.53.0-rc.0", + "@perses-dev/dashboards": "0.53.0-rc.2", + "@perses-dev/plugin-system": "0.53.0-rc.2", "@types/react-grid-layout": "^1.3.2", "date-fns": "^4.1.0", "immer": "^10.1.1", @@ -6137,507 +5655,55 @@ "react-dom": "^17.0.2 || ^18.0.0" } }, - "node_modules/@perses-dev/flame-chart-plugin": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@perses-dev/flame-chart-plugin/-/flame-chart-plugin-0.3.1.tgz", - "integrity": "sha512-VLCSG+4ygKB6XSuQv5oitOOg5fZ7rexhP+7I7gmeRWVQaS1rA/wdIZc/H1mJpMT4sxB4lXewSSh0YaVPX+6R/Q==", - "peerDependencies": { - "@emotion/react": "^11.7.1", - "@emotion/styled": "^11.6.0", - "@hookform/resolvers": "^3.2.0", - "@perses-dev/components": "^0.52.0", - "@perses-dev/core": "^0.52.0", - "@perses-dev/plugin-system": "^0.52.0", - "date-fns": "^4.1.0", - "date-fns-tz": "^3.2.0", - "echarts": "5.5.0", - "immer": "^10.1.1", - "lodash": "^4.17.21", - "react": "^17.0.2 || ^18.0.0", - "react-dom": "^17.0.2 || ^18.0.0", - "use-resize-observer": "^9.0.0" - } - }, - "node_modules/@perses-dev/gauge-chart-plugin": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/@perses-dev/gauge-chart-plugin/-/gauge-chart-plugin-0.9.1.tgz", - "integrity": "sha512-+oAsx4F/TL1y5dY5FzSz3Ryzjl0Pd0DZxTGzD8OGgIXVEaJXyWRC7nqehv6bNsY43XyrYOJwmFicnNcdaos4LA==", - "peerDependencies": { - "@emotion/react": "^11.7.1", - "@emotion/styled": "^11.6.0", - "@hookform/resolvers": "^3.2.0", - "@perses-dev/components": "^0.52.0", - "@perses-dev/core": "^0.52.0", - "@perses-dev/plugin-system": "^0.52.0", - "date-fns": "^4.1.0", - "date-fns-tz": "^3.2.0", - "echarts": "5.5.0", - "immer": "^10.1.1", - "lodash": "^4.17.21", - "react": "^17.0.2 || ^18.0.0", - "react-dom": "^17.0.2 || ^18.0.0", - "use-resize-observer": "^9.0.0" - } - }, - "node_modules/@perses-dev/heatmap-chart-plugin": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@perses-dev/heatmap-chart-plugin/-/heatmap-chart-plugin-0.2.1.tgz", - "integrity": "sha512-4+izLo5i7e6M3XxXUegK9bx7xzOI05xKwt5/2+UsDWEj6r/PMTsJvdrXx++oN0gHdbBim8ot7HNSGCjDcBMjbw==", - "peerDependencies": { - "@perses-dev/components": "^0.52.0", - "@perses-dev/core": "^0.52.0", - "@perses-dev/plugin-system": "^0.52.0", - "echarts": "5.5.0", - "immer": "^10.1.1", - "lodash": "^4.17.21", - "react": "^17.0.2 || ^18.0.0", - "react-dom": "^17.0.2 || ^18.0.0" - } - }, - "node_modules/@perses-dev/histogram-chart-plugin": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/@perses-dev/histogram-chart-plugin/-/histogram-chart-plugin-0.9.1.tgz", - "integrity": "sha512-rnxKN2vXLnHkmhPYtEZRAkXDYAST57jwoA90rCLvv5vxnIqORGRMzHyTA0JjB0fyYxX799a/1DwPEob9MKuSBg==", - "peerDependencies": { - "@perses-dev/components": "^0.52.0", - "@perses-dev/core": "^0.52.0", - "@perses-dev/plugin-system": "^0.52.0", - "echarts": "5.5.0", - "immer": "^10.1.1", - "lodash": "^4.17.21", - "react": "^17.0.2 || ^18.0.0", - "react-dom": "^17.0.2 || ^18.0.0" - } - }, - "node_modules/@perses-dev/loki-plugin": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@perses-dev/loki-plugin/-/loki-plugin-0.1.2.tgz", - "integrity": "sha512-digWIK98wR/8woS+t3DrvA2lixNoMvfHQ8LkI+2VJ2bqB1AyeuthajjEb75oUfSbFk0c11wQcULfYpXOWHPk2w==", - "dependencies": { - "@grafana/lezer-logql": "^0.2.8" - }, - "peerDependencies": { - "@emotion/react": "^11.7.1", - "@emotion/styled": "^11.6.0", - "@hookform/resolvers": "^3.2.0", - "@perses-dev/components": "^0.52.0", - "@perses-dev/core": "^0.52.0", - "@perses-dev/dashboards": "^0.52.0", - "@perses-dev/explore": "^0.52.0", - "@perses-dev/plugin-system": "^0.52.0", - "@tanstack/react-query": "^4.39.1", - "date-fns": "^4.1.0", - "date-fns-tz": "^3.2.0", - "echarts": "5.5.0", - "immer": "^10.1.1", - "lodash": "^4.17.21", - "react": "^17.0.2 || ^18.0.0", - "react-dom": "^17.0.2 || ^18.0.0", - "react-hook-form": "^7.52.2", - "use-resize-observer": "^9.0.0" - } - }, - "node_modules/@perses-dev/markdown-plugin": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/@perses-dev/markdown-plugin/-/markdown-plugin-0.9.1.tgz", - "integrity": "sha512-OhttUbCr0r929xX5HCHr9myQoPB6p8flZ2AmxjTVmYPTHPyErjWyEN7KZQsBYmZ+uuayOEs4WFKl1xPXMIjU5Q==", - "dependencies": { - "dompurify": "^3.2.3", - "marked": "^15.0.6" - }, - "peerDependencies": { - "@emotion/react": "^11.7.1", - "@emotion/styled": "^11.6.0", - "@hookform/resolvers": "^3.2.0", - "@perses-dev/components": "^0.52.0", - "@perses-dev/core": "^0.52.0", - "@perses-dev/plugin-system": "^0.52.0", - "date-fns": "^4.1.0", - "date-fns-tz": "^3.2.0", - "echarts": "5.5.0", - "lodash": "^4.17.21", - "react": "^17.0.2 || ^18.0.0", - "react-dom": "^17.0.2 || ^18.0.0", - "use-resize-observer": "^9.0.0" - } - }, - "node_modules/@perses-dev/pie-chart-plugin": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/@perses-dev/pie-chart-plugin/-/pie-chart-plugin-0.9.1.tgz", - "integrity": "sha512-RFXEy/+OveZLvXWLrMZokmKgY+l51oGEC+Ml9i6n+k0NNmoeZFpByFMcMTiTugE+XX6DYhLL4gjXZPefi28oAA==", - "peerDependencies": { - "@emotion/react": "^11.7.1", - "@emotion/styled": "^11.6.0", - "@hookform/resolvers": "^3.2.0", - "@perses-dev/components": "^0.52.0", - "@perses-dev/core": "^0.52.0", - "@perses-dev/plugin-system": "^0.52.0", - "date-fns": "^4.1.0", - "date-fns-tz": "^3.2.0", - "echarts": "5.5.0", - "immer": "^10.1.1", - "lodash": "^4.17.21", - "react": "^17.0.2 || ^18.0.0", - "react-dom": "^17.0.2 || ^18.0.0", - "use-resize-observer": "^9.0.0" - } - }, - "node_modules/@perses-dev/plugin-system": { - "version": "0.52.0", - "resolved": "https://registry.npmjs.org/@perses-dev/plugin-system/-/plugin-system-0.52.0.tgz", - "integrity": "sha512-6wNTZQwlcpX3sCc/Fq1N9/waIh9YGmniCngkqnAw9zhu04UVNKT1ESFAg7IGjJNnIg70Ezx8L7lrI5gSdtjiMg==", + "node_modules/@perses-dev/explore/node_modules/@perses-dev/core": { + "version": "0.53.0-rc.0", + "resolved": "https://registry.npmjs.org/@perses-dev/core/-/core-0.53.0-rc.0.tgz", + "integrity": "sha512-hcFY/l7PlQZ9lz/uAh0J8Txw0l+W2mkalNcDu+CGvusc2sgQznrCyn7hh/UppSN7ls1JgwaCqjjVeqBEHEjsrQ==", "license": "Apache-2.0", "dependencies": { - "@module-federation/enhanced": "^0.19.1", - "@perses-dev/components": "0.52.0", - "@perses-dev/core": "0.52.0", - "date-fns": "^4.1.0", - "date-fns-tz": "^3.2.0", - "immer": "^10.1.1", - "react-hook-form": "^7.46.1", - "use-immer": "^0.11.0", - "use-query-params": "^2.2.1", - "zod": "^3.22.2" - }, - "peerDependencies": { - "@mui/material": "^6.1.10", - "@tanstack/react-query": "^4.39.1", - "react": "^17.0.2 || ^18.0.0", - "react-dom": "^17.0.2 || ^18.0.0" - } - }, - "node_modules/@perses-dev/prometheus-plugin": { - "version": "0.53.4", - "resolved": "https://registry.npmjs.org/@perses-dev/prometheus-plugin/-/prometheus-plugin-0.53.4.tgz", - "integrity": "sha512-mUkAVCnlpHmDzI7W2KeStjqpQrniO+sQtSemG4czNMee0ejO4OMcdZQchEXX9rXDKU2efS6yzYCiwyuZE0U21w==", - "dependencies": { - "@nexucis/fuzzy": "^0.5.1", - "@prometheus-io/codemirror-promql": "^0.304.2", - "color-hash": "^2.0.2", - "qs": "^6.13.0", - "react-virtuoso": "^4.12.2" - }, - "peerDependencies": { - "@emotion/react": "^11.7.1", - "@emotion/styled": "^11.6.0", - "@hookform/resolvers": "^3.2.0", - "@perses-dev/components": "^0.52.0", - "@perses-dev/core": "^0.52.0", - "@perses-dev/dashboards": "^0.52.0", - "@perses-dev/explore": "^0.52.0", - "@perses-dev/plugin-system": "^0.52.0", - "@tanstack/react-query": "^4.39.1", "date-fns": "^4.1.0", - "date-fns-tz": "^3.2.0", - "echarts": "5.5.0", - "immer": "^10.1.1", "lodash": "^4.17.21", - "react": "^17.0.2 || ^18.0.0", - "react-dom": "^17.0.2 || ^18.0.0", - "react-hook-form": "^7.52.2", - "react-router-dom": "^5 || ^6 || ^7", - "use-resize-observer": "^9.0.0" - } - }, - "node_modules/@perses-dev/prometheus-plugin/node_modules/@prometheus-io/codemirror-promql": { - "version": "0.304.2", - "resolved": "https://registry.npmjs.org/@prometheus-io/codemirror-promql/-/codemirror-promql-0.304.2.tgz", - "integrity": "sha512-dxTJMqkyNZMCg5jKCIdIAEp1jiENqAPUJcirEJF1ME1eC7oYOrq700RoXrsAb7i3SzH5vuRVUpemK1J0cjBg7A==", - "license": "Apache-2.0", - "dependencies": { - "@prometheus-io/lezer-promql": "0.304.2", - "lru-cache": "^11.1.0" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "@codemirror/autocomplete": "^6.4.0", - "@codemirror/language": "^6.3.0", - "@codemirror/lint": "^6.0.0", - "@codemirror/state": "^6.1.1", - "@codemirror/view": "^6.4.0", - "@lezer/common": "^1.0.1" + "mathjs": "^10.6.4", + "numbro": "^2.3.6", + "zod": "^3.21.4" } }, - "node_modules/@perses-dev/prometheus-plugin/node_modules/@prometheus-io/lezer-promql": { - "version": "0.304.2", - "resolved": "https://registry.npmjs.org/@prometheus-io/lezer-promql/-/lezer-promql-0.304.2.tgz", - "integrity": "sha512-ptsNfu6cvQ9KDfnUIeucKh9kbGXC81FGXW9jN0I0U+Ia+WRLLdhL8GBBgGZKF5U2G/VCdYiJjLuqYL/8P5JN0g==", + "node_modules/@perses-dev/plugin-system": { + "version": "0.53.0-rc.2", + "resolved": "https://registry.npmjs.org/@perses-dev/plugin-system/-/plugin-system-0.53.0-rc.2.tgz", + "integrity": "sha512-+Gr96xBt4+pf6MsKy1ZiEy1beSEJ2OJ7V7y1W2rEkO+pek0afu90EyEDWLUNfVS+3k5YcXf4BLUgKTA6XtQExQ==", "license": "Apache-2.0", - "peerDependencies": { - "@lezer/highlight": "^1.1.2", - "@lezer/lr": "^1.2.3" - } - }, - "node_modules/@perses-dev/prometheus-plugin/node_modules/lru-cache": { - "version": "11.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", - "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", - "license": "ISC", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@perses-dev/pyroscope-plugin": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@perses-dev/pyroscope-plugin/-/pyroscope-plugin-0.3.2.tgz", - "integrity": "sha512-A+5gDZC6M64agVD090djKCwM38w0xZL8r0IY1mmWrbuq5Ts9pIhl33Rbib2Tf+rLUO729BRwKkaktNIYE+seqw==", - "dependencies": { - "@codemirror/autocomplete": "^6.18.4", - "@lezer/highlight": "^1.2.1x" - }, - "peerDependencies": { - "@emotion/react": "^11.7.1", - "@emotion/styled": "^11.6.0", - "@hookform/resolvers": "^3.2.0", - "@perses-dev/components": "^0.52.0", - "@perses-dev/core": "^0.52.0", - "@perses-dev/dashboards": "^0.52.0", - "@perses-dev/explore": "^0.52.0", - "@perses-dev/plugin-system": "^0.52.0", - "@tanstack/react-query": "^4.39.1", - "@uiw/react-codemirror": "^4.19.1", - "date-fns": "^4.1.0", - "date-fns-tz": "^3.2.0", - "echarts": "5.5.0", - "immer": "^10.1.1", - "lodash": "^4.17.21", - "react": "^17.0.2 || ^18.0.0", - "react-dom": "^17.0.2 || ^18.0.0", - "react-hook-form": "^7.52.2", - "use-resize-observer": "^9.0.0" - } - }, - "node_modules/@perses-dev/scatter-chart-plugin": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@perses-dev/scatter-chart-plugin/-/scatter-chart-plugin-0.8.1.tgz", - "integrity": "sha512-aC/kj5EmkAapw9J6XBgjRALPW4ngZq5XALt8WN7JysRADY9DM3qqA9gI+o8QUTnkxbun2IafZjv7k8o+WpmTqA==", - "dependencies": { - "react-virtuoso": "^4.12.2" - }, - "peerDependencies": { - "@emotion/react": "^11.7.1", - "@emotion/styled": "^11.6.0", - "@hookform/resolvers": "^3.2.0", - "@perses-dev/components": "^0.52.0", - "@perses-dev/core": "^0.52.0", - "@perses-dev/plugin-system": "^0.52.0", - "date-fns": "^4.1.0", - "date-fns-tz": "^3.2.0", - "echarts": "5.5.0", - "lodash": "^4.17.21", - "react": "^17.0.2 || ^18.0.0", - "react-dom": "^17.0.2 || ^18.0.0", - "use-resize-observer": "^9.0.0" - } - }, - "node_modules/@perses-dev/stat-chart-plugin": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/@perses-dev/stat-chart-plugin/-/stat-chart-plugin-0.9.1.tgz", - "integrity": "sha512-wGd82cVpxVZtpLjKObKYJ4o/XwcvCtyw/0k8MYLvplJYfwDsXb0q0/AXW6U8EVGed1KTs60v7eOw1XgkB7KK6Q==", - "peerDependencies": { - "@emotion/react": "^11.7.1", - "@emotion/styled": "^11.6.0", - "@hookform/resolvers": "^3.2.0", - "@perses-dev/components": "^0.52.0", - "@perses-dev/core": "^0.52.0", - "@perses-dev/plugin-system": "^0.52.0", - "date-fns": "^4.1.0", - "date-fns-tz": "^3.2.0", - "echarts": "5.5.0", - "immer": "^10.1.1", - "lodash": "^4.17.21", - "react": "^17.0.2 || ^18.0.0", - "react-dom": "^17.0.2 || ^18.0.0", - "use-resize-observer": "^9.0.0" - } - }, - "node_modules/@perses-dev/static-list-variable-plugin": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@perses-dev/static-list-variable-plugin/-/static-list-variable-plugin-0.5.2.tgz", - "integrity": "sha512-LSt/qvRExL9wb52nAS65E+ALgK8mJzNUPkUX1N1VVC6Lq7u0+YazQyzWb7Vjx/o6sZjttbNgRcbxEtwZtGXU3A==", - "dependencies": { - "color-hash": "^2.0.2" - }, - "peerDependencies": { - "@emotion/react": "^11.7.1", - "@emotion/styled": "^11.6.0", - "@hookform/resolvers": "^3.2.0", - "@perses-dev/components": "^0.52.0", - "@perses-dev/core": "^0.52.0", - "@perses-dev/plugin-system": "^0.52.0", - "date-fns": "^4.1.0", - "date-fns-tz": "^3.2.0", - "echarts": "5.5.0", - "lodash": "^4.17.21", - "react": "^17.0.2 || ^18.0.0", - "react-dom": "^17.0.2 || ^18.0.0", - "use-resize-observer": "^9.0.0" - } - }, - "node_modules/@perses-dev/status-history-chart-plugin": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/@perses-dev/status-history-chart-plugin/-/status-history-chart-plugin-0.9.1.tgz", - "integrity": "sha512-flYKvE8L6lhLcAMH9ZLSsOTjgfwsqNLMaI/c/KWG0ZoDpkkkemiiaOTIPxALvS3WbaZv/NoPCZfNk3V5mJyisw==", - "peerDependencies": { - "@emotion/react": "^11.7.1", - "@emotion/styled": "^11.6.0", - "@hookform/resolvers": "^3.2.0", - "@perses-dev/components": "^0.52.0", - "@perses-dev/core": "^0.52.0", - "@perses-dev/plugin-system": "^0.52.0", - "date-fns": "^4.1.0", - "date-fns-tz": "^3.2.0", - "echarts": "5.5.0", - "immer": "^10.1.1", - "lodash": "^4.17.21", - "react": "^17.0.2 || ^18.0.0", - "react-dom": "^17.0.2 || ^18.0.0", - "use-resize-observer": "^9.0.0" - } - }, - "node_modules/@perses-dev/table-plugin": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@perses-dev/table-plugin/-/table-plugin-0.8.1.tgz", - "integrity": "sha512-TjxHql5lCyvp0w8n6TMryBoXM9ZaFdfJkygAk8slo+6rqS5rcXPls/LoLnSOZe5ItO2ZeKBsTazri52eBKR5Pg==", - "peerDependencies": { - "@emotion/react": "^11.7.1", - "@emotion/styled": "^11.6.0", - "@hookform/resolvers": "^3.2.0", - "@perses-dev/components": "^0.52.0", - "@perses-dev/core": "^0.52.0", - "@perses-dev/dashboards": "^0.52.0", - "@perses-dev/plugin-system": "^0.52.0", - "date-fns": "^4.1.0", - "date-fns-tz": "^3.2.0", - "echarts": "5.5.0", - "lodash": "^4.17.21", - "react": "^17.0.2 || ^18.0.0", - "react-dom": "^17.0.2 || ^18.0.0", - "use-resize-observer": "^9.0.0" - } - }, - "node_modules/@perses-dev/tempo-plugin": { - "version": "0.53.2", - "resolved": "https://registry.npmjs.org/@perses-dev/tempo-plugin/-/tempo-plugin-0.53.2.tgz", - "integrity": "sha512-mtX+gtAf5V1iZTFjG1uJDUgNlnCDCHJ4J30AVv04SgEVegcavT7/9RSqJSB0Fk+jGeX6Q8Bpdsv2ETMnsCzROQ==", - "dependencies": { - "@codemirror/autocomplete": "^6.18.4", - "@grafana/lezer-traceql": "^0.0.20", - "@lezer/highlight": "^1.2.1x" - }, - "peerDependencies": { - "@emotion/react": "^11.7.1", - "@emotion/styled": "^11.6.0", - "@hookform/resolvers": "^3.2.0", - "@perses-dev/components": "^0.52.0", - "@perses-dev/core": "^0.52.0", - "@perses-dev/dashboards": "^0.52.0", - "@perses-dev/explore": "^0.52.0", - "@perses-dev/plugin-system": "^0.52.0", - "@tanstack/react-query": "^4.39.1", - "@uiw/react-codemirror": "^4.19.1", - "date-fns": "^4.1.0", - "date-fns-tz": "^3.2.0", - "echarts": "5.5.0", - "immer": "^10.1.1", - "lodash": "^4.17.21", - "react": "^17.0.2 || ^18.0.0", - "react-dom": "^17.0.2 || ^18.0.0", - "react-hook-form": "^7.52.2", - "use-resize-observer": "^9.0.0" - } - }, - "node_modules/@perses-dev/timeseries-chart-plugin": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/@perses-dev/timeseries-chart-plugin/-/timeseries-chart-plugin-0.10.2.tgz", - "integrity": "sha512-8HbxCiiV1fcy6D5l/MV06Ru6Do4PLJNzQ/nOiA3VuUlZFRtOG7fGnVtNtGP7WYn1HfJgJDHKx1fkQIiLkEPS2w==", "dependencies": { - "color-hash": "^2.0.2" - }, - "peerDependencies": { - "@emotion/react": "^11.7.1", - "@emotion/styled": "^11.6.0", - "@hookform/resolvers": "^3.2.0", - "@perses-dev/components": "^0.52.0", - "@perses-dev/core": "^0.52.0", - "@perses-dev/plugin-system": "^0.52.0", - "date-fns": "^4.1.0", - "date-fns-tz": "^3.2.0", - "echarts": "5.5.0", - "immer": "^10.1.1", - "lodash": "^4.17.21", - "react": "^17.0.2 || ^18.0.0", - "react-dom": "^17.0.2 || ^18.0.0", - "use-resize-observer": "^9.0.0" - } - }, - "node_modules/@perses-dev/timeseries-table-plugin": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/@perses-dev/timeseries-table-plugin/-/timeseries-table-plugin-0.9.1.tgz", - "integrity": "sha512-l0B3QxaLLw9uQ/wDnOveCyyRJIC/D9+s/DSQcj6cgpapeOxg/cu59dGtjcDX0a+FIRAKOnAkS4QaLdihARSWHg==", - "peerDependencies": { - "@emotion/react": "^11.7.1", - "@emotion/styled": "^11.6.0", - "@hookform/resolvers": "^3.2.0", - "@perses-dev/components": "^0.52.0", - "@perses-dev/core": "^0.52.0", - "@perses-dev/dashboards": "^0.52.0", - "@perses-dev/plugin-system": "^0.52.0", + "@module-federation/enhanced": "^0.21.4", + "@perses-dev/components": "0.53.0-rc.2", + "@perses-dev/core": "0.53.0-rc.0", "date-fns": "^4.1.0", - "date-fns-tz": "^3.2.0", - "echarts": "5.5.0", - "lodash": "^4.17.21", - "react": "^17.0.2 || ^18.0.0", - "react-dom": "^17.0.2 || ^18.0.0", - "use-resize-observer": "^9.0.0" - } - }, - "node_modules/@perses-dev/trace-table-plugin": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/@perses-dev/trace-table-plugin/-/trace-table-plugin-0.8.2.tgz", - "integrity": "sha512-SXgN8lXcVHan3jbVh/XXNdcBkRyIfzti5Zzj+5NDHRSgTyDMyy5TNKWG7zI+Jes9E7P2CS2EcZNf1TnsIh7GCQ==", - "dependencies": { - "@mui/x-data-grid": "^7.20.0" + "date-fns-tz": "^3.2.0", + "immer": "^10.1.1", + "react-hook-form": "^7.46.1", + "use-immer": "^0.11.0", + "use-query-params": "^2.2.1", + "zod": "^3.22.2" }, "peerDependencies": { - "@emotion/react": "^11.7.1", - "@emotion/styled": "^11.6.0", "@hookform/resolvers": "^3.2.0", - "@perses-dev/components": "^0.52.0", - "@perses-dev/core": "^0.52.0", - "@perses-dev/plugin-system": "^0.52.0", - "date-fns": "^4.1.0", - "date-fns-tz": "^3.2.0", - "echarts": "5.5.0", - "lodash": "^4.17.21", + "@mui/material": "^6.1.10", + "@tanstack/react-query": "^4.39.1", "react": "^17.0.2 || ^18.0.0", - "react-dom": "^17.0.2 || ^18.0.0", - "use-resize-observer": "^9.0.0" + "react-dom": "^17.0.2 || ^18.0.0" } }, - "node_modules/@perses-dev/tracing-gantt-chart-plugin": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/@perses-dev/tracing-gantt-chart-plugin/-/tracing-gantt-chart-plugin-0.9.3.tgz", - "integrity": "sha512-64bX6eplfhHtHm1ZghDjgmLU24KlSr2LSA0+ls8PAomV57Ou2Qth5rIvXa5X82UXVp0KDExki8SVywR5+mjIzw==", + "node_modules/@perses-dev/plugin-system/node_modules/@perses-dev/core": { + "version": "0.53.0-rc.0", + "resolved": "https://registry.npmjs.org/@perses-dev/core/-/core-0.53.0-rc.0.tgz", + "integrity": "sha512-hcFY/l7PlQZ9lz/uAh0J8Txw0l+W2mkalNcDu+CGvusc2sgQznrCyn7hh/UppSN7ls1JgwaCqjjVeqBEHEjsrQ==", + "license": "Apache-2.0", "dependencies": { - "color-hash": "^2.0.2" - }, - "peerDependencies": { - "@emotion/react": "^11.7.1", - "@emotion/styled": "^11.6.0", - "@hookform/resolvers": "^3.2.0", - "@perses-dev/components": "^0.52.0", - "@perses-dev/core": "^0.52.0", - "@perses-dev/plugin-system": "^0.52.0", "date-fns": "^4.1.0", - "date-fns-tz": "^3.2.0", - "echarts": "5.5.0", "lodash": "^4.17.21", - "react": "^17.0.2 || ^18.0.0", - "react-dom": "^17.0.2 || ^18.0.0", - "use-resize-observer": "^9.0.0" + "mathjs": "^10.6.4", + "numbro": "^2.3.6", + "zod": "^3.21.4" } }, "node_modules/@pkgjs/parseargs": { @@ -6756,132 +5822,6 @@ ], "peer": true }, - "node_modules/@rspack/binding-darwin-x64": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@rspack/binding-darwin-x64/-/binding-darwin-x64-1.6.1.tgz", - "integrity": "sha512-uadcJOal5YTg191+kvi47I0b+U0sRKe8vKFjMXYOrSIcbXGVRdBxROt/HMlKnvg0u/A83f6AABiY6MA2fCs/gw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true - }, - "node_modules/@rspack/binding-linux-arm64-gnu": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.6.1.tgz", - "integrity": "sha512-n7UGSBzv7PiX+V1Q2bY3S1XWyN3RCykCQUgfhZ+xWietCM/1349jgN7DoXKPllqlof1GPGBjziHU0sQZTC4tag==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rspack/binding-linux-arm64-musl": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.6.1.tgz", - "integrity": "sha512-P7nx0jsKxx7g3QAnH9UnJDGVgs1M2H7ZQl68SRyrs42TKOd9Md22ynoMIgCK1zoy+skssU6MhWptluSggXqSrA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rspack/binding-linux-x64-gnu": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.6.1.tgz", - "integrity": "sha512-SdiurC1bV/QHnj7rmrBYJLdsat3uUDWl9KjkVjEbtc8kQV0Ri4/vZRH0nswgzx7hZNY2j0jYuCm5O8+3qeJEMg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rspack/binding-linux-x64-musl": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-1.6.1.tgz", - "integrity": "sha512-JoSJu29nV+auOePhe8x2Fzqxiga1YGNcOMWKJ5Uj8rHBZ8FPAiiE+CpLG8TwfpHsivojrY/sy6fE8JldYLV5TQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rspack/binding-wasm32-wasi": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@rspack/binding-wasm32-wasi/-/binding-wasm32-wasi-1.6.1.tgz", - "integrity": "sha512-u5NiSHxM7LtIo4cebq/hQPJ9o39u127am3eVJHDzdmBVhTYYO5l7XVUnFmcU8hNHuj/4lJzkFviWFbf3SaRSYA==", - "cpu": [ - "wasm32" - ], - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@napi-rs/wasm-runtime": "1.0.7" - } - }, - "node_modules/@rspack/binding-win32-arm64-msvc": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.6.1.tgz", - "integrity": "sha512-u2Lm4iyUstX/H4JavHnFLIlXQwMka6WVvG2XH8uRd6ziNTh0k/u9jlFADzhdZMvxj63L2hNXCs7TrMZTx2VObQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@rspack/binding-win32-ia32-msvc": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.6.1.tgz", - "integrity": "sha512-/rMU4pjnQeYnkrXmlqeEPiUNT1wHfJ8GR5v2zqcHXBQkAtic3ZsLwjHpucJjrfRsN5CcVChxJl/T7ozlITfcYw==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@rspack/binding-win32-x64-msvc": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.6.1.tgz", - "integrity": "sha512-8qsdb5COuZF5Trimo3HHz3N0KuRtrPtRCMK/wi7DOT1nR6CpUeUMPTjvtPl/O/QezQje+cpBFTa5BaQ1WKlHhw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, "node_modules/@rspack/core": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/@rspack/core/-/core-1.6.1.tgz", @@ -7057,159 +5997,6 @@ "node": ">=10" } }, - "node_modules/@swc/core-darwin-x64": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.3.tgz", - "integrity": "sha512-p68OeCz1ui+MZYG4wmfJGvcsAcFYb6Sl25H9TxWl+GkBgmNimIiRdnypK9nBGlqMZAcxngNPtnG3kEMNnvoJ2A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.3.tgz", - "integrity": "sha512-Nuj5iF4JteFgwrai97mUX+xUOl+rQRHqTvnvHMATL/l9xE6/TJfPBpd3hk/PVpClMXG3Uvk1MxUFOEzM1JrMYg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.3.tgz", - "integrity": "sha512-2Nc/s8jE6mW2EjXWxO/lyQuLKShcmTrym2LRf5Ayp3ICEMX6HwFqB1EzDhwoMa2DcUgmnZIalesq2lG3krrUNw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.3.tgz", - "integrity": "sha512-j4SJniZ/qaZ5g8op+p1G9K1z22s/EYGg1UXIb3+Cg4nsxEpF5uSIGEE4mHUfA70L0BR9wKT2QF/zv3vkhfpX4g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.3.tgz", - "integrity": "sha512-aKttAZnz8YB1VJwPQZtyU8Uk0BfMP63iDMkvjhJzRZVgySmqt/apWSdnoIcZlUoGheBrcqbMC17GGUmur7OT5A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-x64-musl": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.3.tgz", - "integrity": "sha512-oe8FctPu1gnUsdtGJRO2rvOUIkkIIaHqsO9xxN0bTR7dFTlPTGi2Fhk1tnvXeyAvCPxLIcwD8phzKg6wLv9yug==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.3.tgz", - "integrity": "sha512-L9AjzP2ZQ/Xh58e0lTRMLvEDrcJpR7GwZqAtIeNLcTK7JVE+QineSyHp0kLkO1rttCHyCy0U74kDTj0dRz6raA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.3.tgz", - "integrity": "sha512-B8UtogMzErUPDWUoKONSVBdsgKYd58rRyv2sHJWKOIMCHfZ22FVXICR4O/VwIYtlnZ7ahERcjayBHDlBZpR0aw==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.3.tgz", - "integrity": "sha512-SpZKMR9QBTecHeqpzJdYEfgw30Oo8b/Xl6rjSzBt1g0ZsXyy60KLXrp6IagQyfTYqNYE/caDvwtF2FPn7pomog==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=10" - } - }, "node_modules/@swc/counter": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", @@ -7221,6 +6008,7 @@ "version": "0.5.17", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", + "devOptional": true, "license": "Apache-2.0", "dependencies": { "tslib": "^2.8.0" @@ -7329,20 +6117,10 @@ }, "node_modules/@tsconfig/node16": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" }, "node_modules/@types/ajv": { "version": "0.0.5", @@ -8449,34 +7227,6 @@ "dev": true, "license": "ISC" }, - "node_modules/@unrs/resolver-binding-android-arm-eabi": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", - "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@unrs/resolver-binding-android-arm64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", - "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, "node_modules/@unrs/resolver-binding-darwin-arm64": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", @@ -8491,246 +7241,6 @@ "darwin" ] }, - "node_modules/@unrs/resolver-binding-darwin-x64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", - "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@unrs/resolver-binding-freebsd-x64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", - "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", - "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", - "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", - "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", - "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", - "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", - "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", - "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", - "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-x64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", - "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-x64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", - "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-wasm32-wasi": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", - "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@napi-rs/wasm-runtime": "^0.2.11" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@unrs/resolver-binding-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { - "version": "0.2.12", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", - "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@tybys/wasm-util": "^0.10.0" - } - }, - "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", - "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", - "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@unrs/resolver-binding-win32-x64-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", - "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, "node_modules/@webassemblyjs/ast": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", @@ -9474,13 +7984,13 @@ "license": "MIT" }, "node_modules/axios": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", - "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz", + "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, @@ -10767,12 +9277,6 @@ "color-name": "1.1.3" } }, - "node_modules/color-hash": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/color-hash/-/color-hash-2.0.2.tgz", - "integrity": "sha512-6exeENAqBTuIR1wIo36mR8xVVBv6l1hSLd7Qmvf6158Ld1L15/dbahR9VUOiX7GmGJBCnQyS0EY+I8x+wa7egg==", - "license": "MIT" - }, "node_modules/color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", @@ -12377,15 +10881,6 @@ "url": "https://github.com/fb55/domhandler?sponsor=1" } }, - "node_modules/dompurify": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.0.tgz", - "integrity": "sha512-r+f6MYR1gGN1eJv0TVQbhA7if/U7P87cdPl3HN5rikqaBSBxLiCb/b9O+2eG0cxz0ghyU+mU1QkbsOwERMYlWQ==", - "license": "(MPL-2.0 OR Apache-2.0)", - "optionalDependencies": { - "@types/trusted-types": "^2.0.7" - } - }, "node_modules/domutils": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", @@ -12804,6 +11299,7 @@ "version": "0.25.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz", "integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==", + "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -14558,9 +13054,9 @@ } }, "node_modules/form-data": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", - "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", @@ -18782,6 +17278,15 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/jiti": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", + "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -19114,7 +17619,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz", "integrity": "sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==", - "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", "license": "MIT", "dependencies": { "tsscmp": "1.0.6" @@ -19154,9 +17658,9 @@ } }, "node_modules/koa": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/koa/-/koa-3.0.1.tgz", - "integrity": "sha512-oDxVkRwPOHhGlxKIDiDB2h+/l05QPtefD7nSqRgDfZt8P+QVYFWjfeK8jANf5O2YXjk8egd7KntvXKYx82wOag==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/koa/-/koa-3.0.3.tgz", + "integrity": "sha512-MeuwbCoN1daWS32/Ni5qkzmrOtQO2qrnfdxDHjrm6s4b59yG4nexAJ0pTEFyzjLp0pBVO80CZp0vW8Ze30Ebow==", "license": "MIT", "dependencies": { "accepts": "^1.3.8", @@ -19198,15 +17702,19 @@ } }, "node_modules/koa/node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "license": "MIT", "dependencies": { "mime-db": "^1.54.0" }, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/launch-editor": { @@ -19735,18 +18243,6 @@ "tmpl": "1.0.5" } }, - "node_modules/marked": { - "version": "15.0.12", - "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz", - "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", - "license": "MIT", - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 18" - } - }, "node_modules/matcher-collection": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/matcher-collection/-/matcher-collection-2.0.1.tgz", @@ -23020,12 +21516,6 @@ "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", "license": "Unlicense" }, - "node_modules/rslog": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/rslog/-/rslog-1.3.0.tgz", - "integrity": "sha512-93DpwwaiRrLz7fJ5z6Uwb171hHBws1VVsWjU6IruLFX63BicLA44QNu0sfn3guKHnBHZMFSKO8akfx5QhjuegQ==", - "license": "MIT" - }, "node_modules/rsvp": { "version": "4.8.5", "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", @@ -25195,15 +23685,19 @@ } }, "node_modules/type-is/node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "license": "MIT", "dependencies": { "mime-db": "^1.54.0" }, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/typed-array-buffer": { diff --git a/web/package.json b/web/package.json index 1ce809016..f6906b0e5 100644 --- a/web/package.json +++ b/web/package.json @@ -70,32 +70,11 @@ "@patternfly/react-icons": "^6.2.0", "@patternfly/react-table": "^6.2.0", "@patternfly/react-templates": "^6.2.0", - "@perses-dev/bar-chart-plugin": "^0.9.0", - "@perses-dev/components": "^0.52.0", - "@perses-dev/core": "^0.52.0", - "@perses-dev/dashboards": "^0.52.0", - "@perses-dev/datasource-variable-plugin": "^0.3.2", - "@perses-dev/explore": "^0.52.0", - "@perses-dev/flame-chart-plugin": "^0.3.0", - "@perses-dev/gauge-chart-plugin": "^0.9.0", - "@perses-dev/heatmap-chart-plugin": "^0.2.1", - "@perses-dev/histogram-chart-plugin": "^0.9.0", - "@perses-dev/loki-plugin": "^0.1.1", - "@perses-dev/markdown-plugin": "^0.9.0", - "@perses-dev/pie-chart-plugin": "^0.9.0", - "@perses-dev/plugin-system": "^0.52.0", - "@perses-dev/prometheus-plugin": "^0.53.3", - "@perses-dev/pyroscope-plugin": "^0.3.1", - "@perses-dev/scatter-chart-plugin": "^0.8.0", - "@perses-dev/stat-chart-plugin": "^0.9.0", - "@perses-dev/static-list-variable-plugin": "^0.5.1", - "@perses-dev/status-history-chart-plugin": "^0.9.0", - "@perses-dev/table-plugin": "^0.8.0", - "@perses-dev/tempo-plugin": "^0.53.1", - "@perses-dev/timeseries-chart-plugin": "^0.10.1", - "@perses-dev/timeseries-table-plugin": "^0.9.0", - "@perses-dev/trace-table-plugin": "^0.8.1", - "@perses-dev/tracing-gantt-chart-plugin": "^0.9.2", + "@perses-dev/components": "0.53.0-rc.2", + "@perses-dev/core": "0.53.0-rc.1", + "@perses-dev/dashboards": "0.53.0-rc.2", + "@perses-dev/explore": "0.53.0-rc.2", + "@perses-dev/plugin-system": "0.53.0-rc.2", "@prometheus-io/codemirror-promql": "^0.37.0", "@tanstack/react-query": "^4.36.1", "@types/ajv": "^0.0.5", @@ -113,6 +92,7 @@ "murmurhash-js": "1.0.x", "react": "^17.0.1", "react-dom": "^17.0.1", + "react-hook-form": "^7.66.0", "react-i18next": "^11.8.11", "react-linkify": "^0.2.2", "react-modal": "^3.12.1", @@ -184,7 +164,8 @@ "displayName": "OpenShift console monitoring plugin", "description": "This plugin adds the monitoring UI to the OpenShift web console", "exposedModules": { - "DashboardsPage": "./components/dashboards/perses/dashboard-page", + "DashboardListPage": "./components/dashboards/perses/dashboard-list-page", + "DashboardPage": "./components/dashboards/perses/dashboard-page", "LegacyDashboardsPage": "./components/dashboards/legacy/legacy-dashboard-page", "SilencesPage": "./components/alerting/SilencesPage", "SilencesDetailsPage": "./components/alerting/SilencesDetailsPage", diff --git a/web/src/components/dashboards/legacy/graph.tsx b/web/src/components/dashboards/legacy/graph.tsx index 4ff9f6bb9..414a20f9d 100644 --- a/web/src/components/dashboards/legacy/graph.tsx +++ b/web/src/components/dashboards/legacy/graph.tsx @@ -8,7 +8,7 @@ import { MonitoringState } from '../../../store/store'; import { getObserveState } from '../../hooks/usePerspective'; import { DEFAULT_GRAPH_SAMPLES } from './utils'; import { CustomDataSource } from '@openshift-console/dynamic-plugin-sdk/lib/extensions/dashboard-data-source'; -import { GraphUnits } from 'src/components/metrics/units'; +import { GraphUnits } from '../../../components/metrics/units'; import { useMonitoring } from '../../../hooks/useMonitoring'; type Props = { diff --git a/web/src/components/dashboards/perses/PersesWrapper.tsx b/web/src/components/dashboards/perses/PersesWrapper.tsx index 043fc1efd..47589122a 100644 --- a/web/src/components/dashboards/perses/PersesWrapper.tsx +++ b/web/src/components/dashboards/perses/PersesWrapper.tsx @@ -1,3 +1,4 @@ +import '../../../perses-config'; import { ThemeOptions, ThemeProvider } from '@mui/material'; import { ChartThemeColor, getThemeColors } from '@patternfly/react-charts/victory'; import { @@ -22,35 +23,41 @@ import { } from '@perses-dev/dashboards'; import { DataQueriesProvider, + PluginLoader, PluginRegistry, TimeRangeProviderWithQueryParams, useInitialRefreshInterval, useInitialTimeRange, usePluginBuiltinVariableDefinitions, + ValidationProvider, } from '@perses-dev/plugin-system'; import React, { useMemo } from 'react'; import { usePatternFlyTheme } from '../../hooks/usePatternflyTheme'; import { OcpDatasourceApi } from './datasource-api'; import { PERSES_PROXY_BASE_PATH, useFetchPersesDashboard } from './perses-client'; -import { CachedDatasourceAPI } from './perses/datasource-api'; +import { CachedDatasourceAPI } from './perses/datasource-cache-api'; import { chart_color_blue_100, - chart_color_blue_200, chart_color_blue_300, + chart_color_blue_400, + chart_color_blue_500, t_color_gray_95, t_color_white, + t_global_background_color_100, + t_global_background_color_400, } from '@patternfly/react-tokens'; import { QueryParams } from '../../query-params'; import { StringParam, useQueryParam } from 'use-query-params'; import { useTranslation } from 'react-i18next'; import { LoadingBox } from '../../../components/console/console-shared/src/components/loading/LoadingBox'; -import { pluginLoader } from './persesPluginsLoader'; +import { remotePluginLoader } from '@perses-dev/plugin-system'; // Override eChart defaults with PatternFly colors. -const patternflyBlue300 = '#2b9af3'; -const patternflyBlue400 = '#0066cc'; -const patternflyBlue500 = '#004080'; -const patternflyBlue600 = '#002952'; +const patternflyBlue100 = chart_color_blue_100.value; +const patternflyBlue300 = chart_color_blue_300.value; +const patternflyBlue400 = chart_color_blue_400.value; +const patternflyBlue500 = chart_color_blue_500.value; +const patternflyBlue600 = chart_color_blue_100.value; const defaultPaletteColors = [patternflyBlue400, patternflyBlue500, patternflyBlue600]; const chartColorScale = getThemeColors(ChartThemeColor.multiUnordered).chart.colorScale; @@ -71,7 +78,9 @@ interface PersesWrapperProps { const mapPatterflyThemeToMUI = (theme: 'light' | 'dark'): ThemeOptions => { const isDark = theme === 'dark'; const primaryTextColor = isDark ? t_color_white.value : t_color_gray_95.value; - const primaryBackgroundColor = 'var(--pf-t--global--background--color--primary--default)'; + const primaryBackgroundColor = isDark + ? t_global_background_color_400.value + : t_global_background_color_100.value; return { typography: { @@ -86,17 +95,17 @@ const mapPatterflyThemeToMUI = (theme: 'light' | 'dark'): ThemeOptions => { }, h2: { // Panel Group Heading - color: 'var(--pf-t--global--text--color--brand--default)', fontWeight: 'var(--pf-t--global--font--weight--body--default)', fontSize: 'var(--pf-t--global--font--size--600)', }, }, palette: { + mode: isDark ? 'dark' : 'light', // Help CodeMirror detect theme mode primary: { light: chart_color_blue_100.value, - main: chart_color_blue_200.value, - dark: chart_color_blue_300.value, - contrastText: primaryTextColor, + main: patternflyBlue300, + dark: patternflyBlue500, + contrastText: t_color_white.value, }, secondary: { main: primaryTextColor, @@ -133,13 +142,6 @@ const mapPatterflyThemeToMUI = (theme: 'light' | 'dark'): ThemeOptions => { }, }, }, - MuiSvgIcon: { - styleOverrides: { - root: { - color: theme === 'dark' ? t_color_white.value : t_color_gray_95.value, - }, - }, - }, MuiCard: { styleOverrides: { root: { @@ -154,9 +156,9 @@ const mapPatterflyThemeToMUI = (theme: 'light' | 'dark'): ThemeOptions => { '&.MuiCardHeader-root': { borderBottom: 'none', paddingBlockEnd: 'var(--pf-t--global--spacer--md)', - paddingBlockStart: 'var(--pf-t--global--spacer--lg)', - paddingLeft: 'var(--pf-t--global--spacer--lg)', - paddingRight: 'var(--pf-t--global--spacer--lg)', + paddingBlockStart: 'var(--pf-t--global--spacer--md)', + paddingLeft: 'var(--pf-t--global--spacer--md)', + paddingRight: 'var(--pf-t--global--spacer--md)', }, }, }, @@ -167,9 +169,9 @@ const mapPatterflyThemeToMUI = (theme: 'light' | 'dark'): ThemeOptions => { '&.MuiCardContent-root': { borderTop: 'none', '&:last-child': { - paddingBottom: 'var(--pf-t--global--spacer--lg)', - paddingLeft: 'var(--pf-t--global--spacer--lg)', - paddingRight: 'var(--pf-t--global--spacer--lg)', + paddingBottom: 'var(--pf-t--global--spacer--md)', + paddingLeft: 'var(--pf-t--global--spacer--sm)', + paddingRight: 'var(--pf-t--global--spacer--md)', }, }, }, @@ -201,10 +203,144 @@ const mapPatterflyThemeToMUI = (theme: 'light' | 'dark'): ThemeOptions => { }, }, }, + MuiButton: { + styleOverrides: { + root: { + '&.MuiButton-colorPrimary': { + borderRadius: 'var(--pf-t--global--border--radius--pill)', + borderColor: 'var(--pf-t--global--border--color--default)', + color: isDark ? patternflyBlue100 : patternflyBlue300, + }, + // Buttons with colored backgrounds should have white text + '&.MuiButton-contained.MuiButton-colorPrimary': { + color: t_color_white.value, + }, + }, + }, + }, + MuiFormLabel: { + styleOverrides: { + root: { + // Align placeholder text in Editing Panel + '&.MuiFormLabel-root.MuiInputLabel-root.MuiInputLabel-formControl.MuiInputLabel-animated.MuiInputLabel-sizeMedium.MuiInputLabel-outlined.MuiFormLabel-colorPrimary[data-shrink="false"]': + { + top: '-7px', + }, + }, + }, + }, + MuiTab: { + styleOverrides: { + root: { + // Selected tab color + '&.MuiButtonBase-root.MuiTab-root.Mui-selected': { + color: isDark ? patternflyBlue100 : patternflyBlue300, + }, + }, + }, + }, + MuiTabs: { + styleOverrides: { + indicator: { + // Tab indicator should match color of selected MuiTab + '&.MuiTabs-indicator': { + backgroundColor: isDark ? patternflyBlue100 : patternflyBlue300, + }, + }, + }, + }, + MuiDrawer: { + styleOverrides: { + paper: { + // Editing Variables Panel + '&.MuiDrawer-paper.MuiDrawer-paperAnchorRight': { + borderTopLeftRadius: 'var(--pf-t--global--border--radius--medium) !important', + borderBottomLeftRadius: 'var(--pf-t--global--border--radius--medium) !important', + borderTopRightRadius: '0 !important', + borderBottomRightRadius: '0 !important', + }, + '&.MuiDrawer-paper.MuiDrawer-paperAnchorLeft': { + borderTopRightRadius: 'var(--pf-t--global--border--radius--medium) !important', + borderBottomRightRadius: 'var(--pf-t--global--border--radius--medium) !important', + borderTopLeftRadius: '0 !important', + borderBottomLeftRadius: '0 !important', + }, + // Editing Variable Panel - drawer cancel button + '& .MuiButton-colorSecondary': { + borderRadius: 'var(--pf-t--global--border--radius--pill) !important', + }, + }, + }, + }, + MuiAccordion: { + styleOverrides: { + root: { + // Editing Variables Panel + borderRadius: 'var(--pf-t--global--border--radius--medium) !important', + '&.MuiAccordion-root': { + borderRadius: 'var(--pf-t--global--border--radius--medium) !important', + }, + // Hide the separator line above accordion + '&::before': { + opacity: '0 !important', + }, + backgroundColor: + 'var(--pf-t--global--background--color--action--plain--default) !important', + }, + }, + }, + MuiAccordionSummary: { + styleOverrides: { + root: { + // Editing Variables Panel - accordion header + borderRadius: 'var(--pf-t--global--border--radius--medium) !important', + backgroundColor: 'var(--pf-t--global--background--color--floating--default) !important', + '&.Mui-expanded': { + borderBottomLeftRadius: '0 !important', + borderBottomRightRadius: '0 !important', + borderTopLeftRadius: 'var(--pf-t--global--border--radius--medium) !important', + borderTopRightRadius: 'var(--pf-t--global--border--radius--medium) !important', + }, + }, + }, + }, + MuiAccordionDetails: { + styleOverrides: { + root: { + // Editing Variables Panel - accordion contents + backgroundColor: 'var(--pf-t--global--background--color--floating--default) !important', + borderBottomLeftRadius: 'var(--pf-t--global--border--radius--medium) !important', + borderBottomRightRadius: 'var(--pf-t--global--border--radius--medium) !important', + borderTopLeftRadius: '0 !important', + borderTopRightRadius: '0 !important', + }, + }, + }, + MuiTableCell: { + styleOverrides: { + root: { + // Uniform font weight in all table cells + fontWeight: 'var(--pf-t--global--font--weight--body--default) !important', + }, + }, + }, }, }; }; +export function useRemotePluginLoader(): PluginLoader { + const pluginLoader = useMemo( + () => + remotePluginLoader({ + baseURL: window.PERSES_PLUGIN_ASSETS_PATH, + apiPrefix: window.PERSES_PLUGIN_ASSETS_PATH, + }), + [], + ); + + return pluginLoader; +} + export function PersesWrapper({ children, project }: PersesWrapperProps) { const { theme } = usePatternFlyTheme(); const [dashboardName] = useQueryParam(QueryParams.Dashboard, StringParam); @@ -225,13 +361,14 @@ export function PersesWrapper({ children, project }: PersesWrapperProps) { }, }); + const pluginLoader = useRemotePluginLoader(); + return ( {!project ? ( @@ -327,11 +464,10 @@ function InnerWrapper({ children, project, dashboardName }) { {clearedDashboardResource ? ( - {children} + {children} ) : ( <>{children} diff --git a/web/src/components/dashboards/perses/ToastProvider.tsx b/web/src/components/dashboards/perses/ToastProvider.tsx new file mode 100644 index 000000000..024ebce7c --- /dev/null +++ b/web/src/components/dashboards/perses/ToastProvider.tsx @@ -0,0 +1,67 @@ +import React, { createContext, useContext, useState, ReactNode } from 'react'; +import { useTranslation } from 'react-i18next'; +import { + Alert, + AlertProps, + AlertGroup, + AlertActionCloseButton, + AlertVariant, +} from '@patternfly/react-core'; + +interface ToastItem { + key: string; + title: string; + variant: AlertProps['variant']; +} + +interface ToastContextType { + addAlert: (title: string, variant: AlertProps['variant']) => void; + removeAlert: (key: string) => void; + alerts: ToastItem[]; +} + +const ToastContext = createContext(undefined); + +export const useToast = () => { + const { t } = useTranslation(process.env.I18N_NAMESPACE); + const context = useContext(ToastContext); + if (!context) { + throw new Error(t('useToast must be used within ToastProvider')); + } + return context; +}; + +export const ToastProvider: React.FC<{ children: ReactNode }> = ({ children }) => { + const [alerts, setAlerts] = useState([]); + + const addAlert = (title: string, variant: AlertProps['variant']) => { + const key = new Date().getTime().toString(); + setAlerts((prevAlerts) => [{ title, variant, key }, ...prevAlerts]); + }; + + const removeAlert = (key: string) => { + setAlerts((prevAlerts) => prevAlerts.filter((alert) => alert.key !== key)); + }; + + return ( + + {children} + + {alerts.map(({ key, variant, title }) => ( + removeAlert(key)} + /> + } + key={key} + /> + ))} + + + ); +}; diff --git a/web/src/components/dashboards/perses/dashboard-action-modals.tsx b/web/src/components/dashboards/perses/dashboard-action-modals.tsx new file mode 100644 index 000000000..8d7c54053 --- /dev/null +++ b/web/src/components/dashboards/perses/dashboard-action-modals.tsx @@ -0,0 +1,511 @@ +import { + Button, + Modal, + ModalBody, + ModalFooter, + ModalHeader, + FormGroup, + TextInput, + FormHelperText, + HelperText, + HelperTextItem, + ValidatedOptions, + HelperTextItemVariant, + ModalVariant, + AlertVariant, + Select, + SelectOption, + SelectList, + MenuToggle, + MenuToggleElement, + Stack, + StackItem, + Spinner, +} from '@patternfly/react-core'; +import { ExclamationCircleIcon } from '@patternfly/react-icons'; +import React, { useMemo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { + useUpdateDashboardMutation, + useCreateDashboardMutation, + useDeleteDashboardMutation, +} from './dashboard-api'; +import { + renameDashboardDialogValidationSchema, + RenameDashboardValidationType, + createDashboardDialogValidationSchema, + CreateDashboardValidationType, + useDashboardValidationSchema, +} from './dashboard-action-validations'; + +import { Controller, FormProvider, SubmitHandler, useForm } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { + DashboardResource, + getResourceDisplayName, + getResourceExtendedDisplayName, +} from '@perses-dev/core'; +import { useToast } from './ToastProvider'; +import { usePerses } from './hooks/usePerses'; +import { generateMetadataName } from './dashboard-utils'; +import { useProjectPermissions } from './dashboard-permissions'; +import { t_global_spacer_200, t_global_font_weight_200 } from '@patternfly/react-tokens'; +import { useNavigate } from 'react-router-dom-v5-compat'; +import { usePerspective, getDashboardUrl } from '../../hooks/usePerspective'; + +const formGroupStyle = { + fontWeight: t_global_font_weight_200.value, +} as React.CSSProperties; + +const LabelSpacer = () => { + return
; +}; + +interface ActionModalProps { + dashboard: DashboardResource; + isOpen: boolean; + onClose: () => void; + handleModalClose: () => void; +} + +export const RenameActionModal = ({ dashboard, isOpen, onClose }: ActionModalProps) => { + const { t } = useTranslation(process.env.I18N_NAMESPACE); + const { addAlert } = useToast(); + + const form = useForm({ + resolver: zodResolver(renameDashboardDialogValidationSchema(t)), + mode: 'onBlur', + defaultValues: { dashboardName: dashboard ? getResourceDisplayName(dashboard) : '' }, + }); + + const updateDashboardMutation = useUpdateDashboardMutation(); + + if (!dashboard) { + return null; + } + + const processForm: SubmitHandler = (data) => { + if (dashboard.spec?.display) { + dashboard.spec.display.name = data.dashboardName; + } else { + dashboard.spec.display = { name: data.dashboardName }; + } + + updateDashboardMutation.mutate(dashboard, { + onSuccess: (updatedDashboard: DashboardResource) => { + const msg = t( + `Dashboard ${getResourceExtendedDisplayName( + updatedDashboard, + )} has been successfully updated`, + ); + addAlert(msg, AlertVariant.success); + handleClose(); + }, + onError: (err) => { + const msg = t(`Could not rename dashboard. ${err}`); + addAlert(msg, AlertVariant.danger); + throw err; + }, + }); + }; + + const handleClose = () => { + onClose(); + form.reset(); + }; + + return ( + + + +
+ + ( + + + + {fieldState.error && ( + + + } + variant={HelperTextItemVariant.error} + > + {fieldState.error.message} + + + + )} + + )} + /> + + + + + +
+
+
+ ); +}; + +export const DuplicateActionModal = ({ dashboard, isOpen, onClose }: ActionModalProps) => { + const { t } = useTranslation(process.env.I18N_NAMESPACE); + const { addAlert } = useToast(); + + const navigate = useNavigate(); + const { perspective } = usePerspective(); + const [isProjectSelectOpen, setIsProjectSelectOpen] = useState(false); + + const { persesProjects, persesProjectsLoading } = usePerses(); + + const hookInput = useMemo(() => { + return persesProjects || []; + }, [persesProjects]); + + const { editableProjects } = useProjectPermissions(hookInput); + + const filteredProjects = useMemo(() => { + return persesProjects.filter((project) => editableProjects.includes(project.metadata.name)); + }, [persesProjects, editableProjects]); + + const defaultProject = useMemo(() => { + if (!dashboard) return ''; + + if (dashboard.metadata.project && editableProjects.includes(dashboard.metadata.project)) { + return dashboard.metadata.project; + } + + return filteredProjects[0]?.metadata.name || ''; + }, [dashboard, editableProjects, filteredProjects]); + + const { schema: validationSchema } = useDashboardValidationSchema(defaultProject, t); + + const form = useForm({ + resolver: validationSchema + ? zodResolver(validationSchema) + : zodResolver(createDashboardDialogValidationSchema(t)), + mode: 'onBlur', + defaultValues: { + projectName: defaultProject, + dashboardName: '', + }, + }); + + const createDashboardMutation = useCreateDashboardMutation(); + + React.useEffect(() => { + if (isOpen && dashboard && filteredProjects.length > 0 && defaultProject) { + form.reset({ + projectName: defaultProject, + dashboardName: '', + }); + } + }, [isOpen, dashboard, defaultProject, filteredProjects.length, form]); + + const selectedProjectName = form.watch('projectName'); + const selectedProjectDisplay = useMemo(() => { + const selectedProject = filteredProjects.find((p) => p.metadata.name === selectedProjectName); + return selectedProject + ? getResourceDisplayName(selectedProject) + : selectedProjectName || t('Select project'); + }, [filteredProjects, selectedProjectName, t]); + + if (!dashboard) { + return null; + } + + const processForm: SubmitHandler = (data) => { + const newDashboard: DashboardResource = { + ...dashboard, + metadata: { + ...dashboard.metadata, + name: generateMetadataName(data.dashboardName), + project: data.projectName, + }, + spec: { + ...dashboard.spec, + display: { + ...dashboard.spec.display, + name: data.dashboardName, + }, + }, + }; + + createDashboardMutation.mutate(newDashboard, { + onSuccess: (createdDashboard: DashboardResource) => { + const msg = t( + `Dashboard ${getResourceExtendedDisplayName( + createdDashboard, + )} has been successfully created`, + ); + addAlert(msg, AlertVariant.success); + + handleClose(); + + const dashboardUrl = getDashboardUrl(perspective); + const dashboardParam = `dashboard=${createdDashboard.metadata.name}`; + const projectParam = `project=${createdDashboard.metadata.project}`; + const editModeParam = `edit=true`; + navigate(`${dashboardUrl}?${dashboardParam}&${projectParam}&${editModeParam}`); + }, + onError: (err) => { + const msg = t(`Could not duplicate dashboard. ${err}`); + addAlert(msg, AlertVariant.danger); + }, + }); + }; + + const handleClose = () => { + onClose(); + form.reset(); + }; + + const onProjectToggle = () => { + setIsProjectSelectOpen(!isProjectSelectOpen); + }; + + const onProjectSelect = ( + _event: React.MouseEvent | undefined, + value: string | number | undefined, + ) => { + if (typeof value === 'string') { + form.setValue('projectName', value); + setIsProjectSelectOpen(false); + } + }; + + return ( + + + {persesProjectsLoading ? ( + + {t('Loading...')} + + ) : ( + +
+ + + + ( + + + + {fieldState.error && ( + + + } + variant={HelperTextItemVariant.error} + > + {fieldState.error.message} + + + + )} + + )} + /> + + + ( + + + + {fieldState.error && ( + + + } + variant={HelperTextItemVariant.error} + > + {fieldState.error.message} + + + + )} + + )} + /> + + + + + + + +
+
+ )} +
+ ); +}; + +export const DeleteActionModal = ({ dashboard, isOpen, onClose }: ActionModalProps) => { + const { t } = useTranslation(process.env.I18N_NAMESPACE); + const { addAlert } = useToast(); + + const deleteDashboardMutation = useDeleteDashboardMutation(); + const dashboardName = dashboard?.spec?.display?.name ?? t('this dashboard'); + + const handleDeleteConfirm = async () => { + if (!dashboard) return; + + deleteDashboardMutation.mutate(dashboard, { + onSuccess: (deletedDashboard: DashboardResource) => { + const msg = t( + `Dashboard ${getResourceExtendedDisplayName( + deletedDashboard, + )} has been successfully deleted`, + ); + addAlert(msg, AlertVariant.success); + onClose(); + }, + onError: (err) => { + const msg = t(`Could not delete dashboard. ${err}`); + addAlert(msg, AlertVariant.danger); + throw err; + }, + }); + }; + + return ( + + + + {t('Are you sure you want to delete ')} + {dashboardName} + {t('? This action can not be undone.')} + + + + + + + ); +}; diff --git a/web/src/components/dashboards/perses/dashboard-action-validations.ts b/web/src/components/dashboards/perses/dashboard-action-validations.ts new file mode 100644 index 000000000..0d69078fb --- /dev/null +++ b/web/src/components/dashboards/perses/dashboard-action-validations.ts @@ -0,0 +1,92 @@ +import { z } from 'zod'; +import { useMemo } from 'react'; +import { nameSchema } from '@perses-dev/core'; +import { useDashboardList } from './dashboard-api'; +import { generateMetadataName } from './dashboard-utils'; + +export const createDashboardDisplayNameValidationSchema = (t: (key: string) => string) => + z.string().min(1, t('Required')).max(75, t('Must be 75 or fewer characters long')); + +export const createDashboardDialogValidationSchema = (t: (key: string) => string) => + z.object({ + projectName: nameSchema, + dashboardName: createDashboardDisplayNameValidationSchema(t), + }); + +export const renameDashboardDialogValidationSchema = (t: (key: string) => string) => + z.object({ + dashboardName: createDashboardDisplayNameValidationSchema(t), + }); + +export type CreateDashboardValidationType = z.infer< + ReturnType +>; +export type RenameDashboardValidationType = z.infer< + ReturnType +>; + +export interface DashboardValidationSchema { + schema?: z.ZodSchema; + isSchemaLoading: boolean; + hasSchemaError: boolean; +} + +// Validate dashboard name and check if it doesn't already exist +export function useDashboardValidationSchema( + projectName?: string, + t?: (key: string, options?: any) => string, +): DashboardValidationSchema { + const { + data: dashboards, + isLoading: isDashboardsLoading, + isError, + } = useDashboardList({ project: projectName }); + return useMemo((): DashboardValidationSchema => { + if (isDashboardsLoading) + return { + schema: undefined, + isSchemaLoading: true, + hasSchemaError: false, + }; + + if (isError) { + return { + hasSchemaError: true, + isSchemaLoading: false, + schema: undefined, + }; + } + + if (!dashboards?.length) + return { + schema: createDashboardDialogValidationSchema(t), + isSchemaLoading: true, + hasSchemaError: false, + }; + + const refinedSchema = createDashboardDialogValidationSchema(t).refine( + (schema) => { + return !(dashboards ?? []).some((dashboard) => { + return ( + dashboard.metadata.project.toLowerCase() === schema.projectName.toLowerCase() && + dashboard.metadata.name.toLowerCase() === + generateMetadataName(schema.dashboardName).toLowerCase() + ); + }); + }, + (schema) => ({ + // eslint-disable-next-line max-len + message: t + ? t(`Dashboard name '{{dashboardName}}' already exists in '{{projectName}}' project!`, { + dashboardName: schema.dashboardName, + projectName: schema.projectName, + }) + : // eslint-disable-next-line max-len + `Dashboard name '${schema.dashboardName}' already exists in '${schema.projectName}' project!`, + path: ['dashboardName'], + }), + ); + + return { schema: refinedSchema, isSchemaLoading: true, hasSchemaError: false }; + }, [dashboards, isDashboardsLoading, isError, t]); +} diff --git a/web/src/components/dashboards/perses/dashboard-api.ts b/web/src/components/dashboards/perses/dashboard-api.ts new file mode 100644 index 000000000..69a3b073a --- /dev/null +++ b/web/src/components/dashboards/perses/dashboard-api.ts @@ -0,0 +1,123 @@ +import { DashboardResource } from '@perses-dev/core'; +import buildURL from './perses/url-builder'; +import { useMutation, UseMutationResult, useQueryClient } from '@tanstack/react-query'; +import { consoleFetchJSON } from '@openshift-console/dynamic-plugin-sdk'; +import { useQuery, UseQueryOptions, UseQueryResult } from '@tanstack/react-query'; +import { StatusError } from '@perses-dev/core'; + +const resource = 'dashboards'; + +const updateDashboard = async (entity: DashboardResource): Promise => { + const url = buildURL({ + resource: resource, + project: entity.metadata.project, + name: entity.metadata.name, + }); + + return consoleFetchJSON.put(url, entity); +}; + +export const useUpdateDashboardMutation = (): UseMutationResult< + DashboardResource, + Error, + DashboardResource +> => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationKey: [resource], + mutationFn: updateDashboard, + onSuccess: () => { + return queryClient.invalidateQueries({ queryKey: [resource] }); + }, + }); +}; + +const createDashboard = async (entity: DashboardResource): Promise => { + const url = buildURL({ + resource: resource, + project: entity.metadata.project, + }); + + return consoleFetchJSON.post(url, entity); +}; + +export const useCreateDashboardMutation = ( + onSuccess?: (data: DashboardResource, variables: DashboardResource) => Promise | unknown, +): UseMutationResult => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationKey: [resource], + mutationFn: (dashboard) => createDashboard(dashboard), + onSuccess: onSuccess, + onSettled: () => { + return queryClient.invalidateQueries({ queryKey: [resource] }); + }, + }); +}; + +const deleteDashboard = async (entity: DashboardResource): Promise => { + const url = buildURL({ + resource: resource, + project: entity.metadata.project, + name: entity.metadata.name, + }); + + await consoleFetchJSON.delete(url); +}; + +export function useDeleteDashboardMutation(): UseMutationResult< + DashboardResource, + Error, + DashboardResource +> { + const queryClient = useQueryClient(); + return useMutation({ + mutationKey: [resource], + mutationFn: (entity: DashboardResource) => { + return deleteDashboard(entity).then(() => { + return entity; + }); + }, + onSuccess: (dashboard) => { + queryClient.removeQueries({ + queryKey: [resource, dashboard.metadata.project, dashboard.metadata.name], + }); + return queryClient.invalidateQueries({ queryKey: [resource] }); + }, + }); +} + +export const getDashboards = async ( + project?: string, + metadataOnly: boolean = false, +): Promise => { + const queryParams = new URLSearchParams(); + if (metadataOnly) { + queryParams.set('metadata_only', 'true'); + } + const url = buildURL({ resource: resource, project: project, queryParams: queryParams }); + + return consoleFetchJSON(url); +}; + +type DashboardListOptions = Omit< + UseQueryOptions, + 'queryKey' | 'queryFn' +> & { + project?: string; + metadataOnly?: boolean; +}; + +export function useDashboardList( + options: DashboardListOptions, +): UseQueryResult { + return useQuery({ + queryKey: [resource, options.project, options.metadataOnly], + queryFn: () => { + return getDashboards(options.project, options.metadataOnly); + }, + ...options, + }); +} diff --git a/web/src/components/dashboards/perses/dashboard-app.tsx b/web/src/components/dashboards/perses/dashboard-app.tsx new file mode 100644 index 000000000..62b9e4616 --- /dev/null +++ b/web/src/components/dashboards/perses/dashboard-app.tsx @@ -0,0 +1,205 @@ +import { ReactElement, ReactNode, useState, useCallback, useEffect } from 'react'; +import { Box } from '@mui/material'; +import { ChartsProvider, ErrorAlert, ErrorBoundary, useChartsTheme } from '@perses-dev/components'; +import { + DashboardResource, + EphemeralDashboardResource, + getResourceExtendedDisplayName, +} from '@perses-dev/core'; +import { useDatasourceStore } from '@perses-dev/plugin-system'; +import { + PanelDrawer, + Dashboard, + PanelGroupDialog, + DeletePanelGroupDialog, + DashboardDiscardChangesConfirmationDialog, + DeletePanelDialog, + EmptyDashboardProps, + EditJsonDialog, + SaveChangesConfirmationDialog, + LeaveDialog, +} from '@perses-dev/dashboards'; +import { + useDashboard, + useDiscardChangesConfirmationDialog, + useEditMode, +} from '@perses-dev/dashboards'; +import { OCPDashboardToolbar } from './dashboard-toolbar'; +import { useUpdateDashboardMutation } from './dashboard-api'; +import { useTranslation } from 'react-i18next'; +import { useToast } from './ToastProvider'; +import { useSearchParams } from 'react-router-dom-v5-compat'; + +export interface DashboardAppProps { + dashboardResource: DashboardResource | EphemeralDashboardResource; + emptyDashboardProps?: Partial; + isReadonly: boolean; + isVariableEnabled: boolean; + isDatasourceEnabled: boolean; + isCreating?: boolean; + isInitialVariableSticky?: boolean; + // If true, browser confirmation dialog will be shown + // when navigating away with unsaved changes (closing tab, ...). + isLeavingConfirmDialogEnabled?: boolean; + dashboardTitleComponent?: ReactNode; + onDiscard?: (entity: DashboardResource) => void; +} + +export const OCPDashboardApp = (props: DashboardAppProps): ReactElement => { + const { + dashboardResource, + emptyDashboardProps, + isReadonly, + isVariableEnabled, + isDatasourceEnabled, + isCreating, + isInitialVariableSticky, + isLeavingConfirmDialogEnabled, + onDiscard, + } = props; + + const { t } = useTranslation(process.env.I18N_NAMESPACE); + + const chartsTheme = useChartsTheme(); + const { addAlert } = useToast(); + + const { isEditMode, setEditMode } = useEditMode(); + const { dashboard, setDashboard } = useDashboard(); + + const [originalDashboard, setOriginalDashboard] = useState< + DashboardResource | EphemeralDashboardResource | undefined + >(undefined); + const [saveErrorOccurred, setSaveErrorOccurred] = useState(false); + + useEffect(() => { + if (saveErrorOccurred && !isEditMode) { + setEditMode(true); + setSaveErrorOccurred(false); + } + }, [isEditMode, saveErrorOccurred, setEditMode]); + const { setSavedDatasources } = useDatasourceStore(); + + const { openDiscardChangesConfirmationDialog, closeDiscardChangesConfirmationDialog } = + useDiscardChangesConfirmationDialog(); + + const [searchParams] = useSearchParams(); + const isEdit = searchParams.get('edit'); + useEffect(() => { + if (isEdit === 'true') { + setEditMode(true); + } + }, [isEdit, setEditMode]); + + const handleDiscardChanges = (): void => { + // Reset to the original spec and exit edit mode + if (originalDashboard) { + setDashboard(originalDashboard); + } + setEditMode(false); + closeDiscardChangesConfirmationDialog(); + if (onDiscard) { + onDiscard(dashboard as unknown as DashboardResource); + } + }; + + const onEditButtonClick = (): void => { + setEditMode(true); + setOriginalDashboard(dashboard); + setSavedDatasources(dashboard.spec.datasources ?? {}); + }; + + const onCancelButtonClick = (): void => { + // check if dashboard has been modified + if (JSON.stringify(dashboard) === JSON.stringify(originalDashboard)) { + setEditMode(false); + } else { + openDiscardChangesConfirmationDialog({ + onDiscardChanges: () => { + handleDiscardChanges(); + }, + onCancel: () => { + closeDiscardChangesConfirmationDialog(); + }, + }); + } + }; + + const updateDashboardMutation = useUpdateDashboardMutation(); + + const onSave = useCallback( + async (data: DashboardResource | EphemeralDashboardResource) => { + if (data.kind !== 'Dashboard') { + throw new Error('Invalid kind'); + } + + try { + const result = await updateDashboardMutation.mutateAsync(data, { + onSuccess: (updatedDashboard: DashboardResource) => { + addAlert( + t( + `Dashboard ${getResourceExtendedDisplayName( + updatedDashboard, + )} has been successfully updated`, + ), + 'success', + ); + + setSaveErrorOccurred(false); + return updatedDashboard; + }, + }); + return result; + } catch (error) { + addAlert(`${error}`, 'danger'); + setSaveErrorOccurred(true); + return null; + } + }, + [updateDashboardMutation, addAlert, t], + ); + + return ( + + + + + + + + + + + + + + + + {isLeavingConfirmDialogEnabled && + isEditMode && + (LeaveDialog({ original: originalDashboard, current: dashboard }) as ReactElement)} + + + ); +}; diff --git a/web/src/components/dashboards/perses/dashboard-create-dialog.tsx b/web/src/components/dashboards/perses/dashboard-create-dialog.tsx new file mode 100644 index 000000000..803dbec82 --- /dev/null +++ b/web/src/components/dashboards/perses/dashboard-create-dialog.tsx @@ -0,0 +1,303 @@ +import { useEffect, useMemo, useState } from 'react'; +import { + Alert, + Button, + Dropdown, + DropdownList, + DropdownItem, + MenuToggle, + MenuToggleElement, + Modal, + ModalBody, + ModalHeader, + ModalFooter, + ModalVariant, + FormGroup, + Form, + TextInput, + FormHelperText, + HelperText, + HelperTextItem, + HelperTextItemVariant, + ValidatedOptions, +} from '@patternfly/react-core'; +import { ExclamationCircleIcon } from '@patternfly/react-icons'; +import { usePerses } from './hooks/usePerses'; +import { useTranslation } from 'react-i18next'; +import { useNavigate } from 'react-router-dom-v5-compat'; +import { StringParam, useQueryParam } from 'use-query-params'; +import { QueryParams } from '../../query-params'; + +import { DashboardResource } from '@perses-dev/core'; +import { useCreateDashboardMutation } from './dashboard-api'; +import { createNewDashboard } from './dashboard-utils'; +import { useToast } from './ToastProvider'; +import { usePerspective, getDashboardUrl } from '../../hooks/usePerspective'; +import { usePersesEditPermissions } from './dashboard-toolbar'; +import { persesDashboardDataTestIDs } from '../../data-test'; +import { useProjectPermissions } from './dashboard-permissions'; + +export const DashboardCreateDialog: React.FunctionComponent = () => { + const { t } = useTranslation(process.env.I18N_NAMESPACE); + const navigate = useNavigate(); + const { perspective } = usePerspective(); + const { addAlert } = useToast(); + const { persesProjects } = usePerses(); + const [activeProjectFromUrl] = useQueryParam(QueryParams.Project, StringParam); + const [isModalOpen, setIsModalOpen] = useState(false); + const [isDropdownOpen, setIsDropdownOpen] = useState(false); + const [selectedProject, setSelectedProject] = useState(null); + const [dashboardName, setDashboardName] = useState(''); + const [formErrors, setFormErrors] = useState<{ [key: string]: string }>({}); + const createDashboardMutation = useCreateDashboardMutation(); + + const { canEdit, loading } = usePersesEditPermissions(activeProjectFromUrl); + + const hookInput = useMemo(() => { + return persesProjects || []; + }, [persesProjects]); + + const { + editableProjects, + hasEditableProject, + loading: globalPermissionsLoading, + } = useProjectPermissions(hookInput); + + const disabled = activeProjectFromUrl ? !canEdit : !hasEditableProject; + + const filteredProjects = useMemo(() => { + return persesProjects.filter((project) => editableProjects.includes(project.metadata.name)); + }, [persesProjects, editableProjects]); + + useEffect(() => { + if ( + isModalOpen && + filteredProjects && + filteredProjects.length > 0 && + selectedProject === null + ) { + const projectToSelect = + activeProjectFromUrl && + filteredProjects.some((p) => p.metadata.name === activeProjectFromUrl) + ? activeProjectFromUrl + : filteredProjects[0].metadata.name; + + setSelectedProject(projectToSelect); + } + }, [isModalOpen, filteredProjects, selectedProject, activeProjectFromUrl]); + + const { persesProjectDashboards: dashboards } = usePerses( + isModalOpen && selectedProject ? selectedProject : undefined, + ); + + const handleSetDashboardName = (_event, dashboardName: string) => { + setDashboardName(dashboardName); + if (formErrors.dashboardName) { + setFormErrors((prev) => ({ ...prev, dashboardName: '' })); + } + }; + + const handleAdd = async () => { + setFormErrors({}); + + if (!selectedProject || !dashboardName.trim()) { + const errors: { [key: string]: string } = {}; + if (!selectedProject) errors.project = t('Project is required'); + if (!dashboardName.trim()) errors.dashboardName = t('Dashboard name is required'); + setFormErrors(errors); + return; + } + + try { + if ( + dashboards && + dashboards.some( + (d) => + d.metadata.project === selectedProject && + d.metadata.name.toLowerCase() === dashboardName.trim().toLowerCase(), + ) + ) { + setFormErrors({ + dashboardName: `Dashboard name "${dashboardName}" already exists in this project`, + }); + return; + } + + const newDashboard: DashboardResource = createNewDashboard( + dashboardName.trim(), + selectedProject as string, + ); + + const createdDashboard = await createDashboardMutation.mutateAsync(newDashboard); + + addAlert(`Dashboard "${dashboardName}" created successfully`, 'success'); + + const dashboardUrl = getDashboardUrl(perspective); + const dashboardParam = `dashboard=${createdDashboard.metadata.name}`; + const projectParam = `project=${createdDashboard.metadata.project}`; + const editModeParam = `edit=true`; + navigate(`${dashboardUrl}?${dashboardParam}&${projectParam}&${editModeParam}`); + + setIsModalOpen(false); + setDashboardName(''); + setFormErrors({}); + } catch (error) { + const errorMessage = error?.message || t('Failed to create dashboard. Please try again.'); + addAlert(`Error creating dashboard: ${errorMessage}`, 'danger'); + setFormErrors({ general: errorMessage }); + } + }; + + const handleModalToggle = () => { + setIsModalOpen(!isModalOpen); + setIsDropdownOpen(false); + if (isModalOpen) { + setDashboardName(''); + setFormErrors({}); + } + }; + + const handleDropdownToggle = () => { + setIsDropdownOpen(!isDropdownOpen); + }; + + const onFocus = () => { + const element = document.getElementById('modal-dropdown-toggle'); + (element as HTMLElement)?.focus(); + }; + + const onEscapePress = () => { + if (isDropdownOpen) { + setIsDropdownOpen(!isDropdownOpen); + onFocus(); + } else { + handleModalToggle(); + } + }; + + const onSelect = ( + event: React.MouseEvent | undefined, + value: string | number | undefined, + ) => { + setSelectedProject(typeof value === 'string' ? value : null); + setIsDropdownOpen(false); + onFocus(); + }; + + return ( + <> + + + + + {formErrors.general && ( + + )} +
{ + e.preventDefault(); + handleAdd(); + }} + > + + setIsDropdownOpen(isOpen)} + toggle={(toggleRef: React.Ref) => ( + + {selectedProject} + + )} + > + + {filteredProjects.map((project, i) => ( + + {project.metadata.name} + + ))} + + + + + + {formErrors.dashboardName && ( + + + } + variant={HelperTextItemVariant.error} + > + {formErrors.dashboardName} + + + + )} + +
+
+ + + + +
+ + ); +}; diff --git a/web/src/components/dashboards/perses/dashboard-frame.tsx b/web/src/components/dashboards/perses/dashboard-frame.tsx new file mode 100644 index 000000000..f454345c7 --- /dev/null +++ b/web/src/components/dashboards/perses/dashboard-frame.tsx @@ -0,0 +1,50 @@ +import React, { ReactNode } from 'react'; +import { DashboardEmptyState } from './emptystates/DashboardEmptyState'; +import { DashboardHeader } from './dashboard-header'; +import { CombinedDashboardMetadata } from './hooks/useDashboardsData'; +import { ProjectBar } from './project/ProjectBar'; +import { PersesWrapper } from './PersesWrapper'; +import { ToastProvider } from './ToastProvider'; +import { PagePadding } from './dashboard-page-padding'; + +interface DashboardFrameProps { + activeProject: string | null; + setActiveProject: (project: string | null) => void; + activeProjectDashboardsMetadata: CombinedDashboardMetadata[]; + changeBoard: (boardName: string) => void; + dashboardDisplayName: string; + children: ReactNode; +} + +export const DashboardFrame: React.FC = ({ + activeProject, + setActiveProject, + activeProjectDashboardsMetadata, + changeBoard, + dashboardDisplayName, + children, +}) => { + return ( + <> + + + + {activeProjectDashboardsMetadata?.length === 0 ? ( + + ) : ( + <> + + {children} + + + )} + + + + ); +}; diff --git a/web/src/components/dashboards/perses/dashboard-header.tsx b/web/src/components/dashboards/perses/dashboard-header.tsx new file mode 100644 index 000000000..fc28a992c --- /dev/null +++ b/web/src/components/dashboards/perses/dashboard-header.tsx @@ -0,0 +1,155 @@ +import type { FC, PropsWithChildren } from 'react'; +import React, { memo } from 'react'; +import { useTranslation } from 'react-i18next'; + +import { Divider, Split, SplitItem, Stack, StackItem } from '@patternfly/react-core'; + +import { DocumentTitle, ListPageHeader } from '@openshift-console/dynamic-plugin-sdk'; +import { CombinedDashboardMetadata } from './hooks/useDashboardsData'; + +import { Breadcrumb, BreadcrumbItem } from '@patternfly/react-core'; +import { useNavigate } from 'react-router-dom-v5-compat'; +import { getDashboardsListUrl, usePerspective } from '../../hooks/usePerspective'; + +import { + chart_color_blue_100, + chart_color_blue_300, + t_global_spacer_md, + t_global_spacer_xl, +} from '@patternfly/react-tokens'; +import { listPersesDashboardsDataTestIDs } from '../../data-test'; +import { usePatternFlyTheme } from '../../hooks/usePatternflyTheme'; +import { DashboardCreateDialog } from './dashboard-create-dialog'; +import { PagePadding } from './dashboard-page-padding'; + +const DASHBOARD_VIEW_PATH = 'v2/dashboards/view'; + +const shouldHideFavoriteButton = (): boolean => { + const currentUrl = window.location.href; + return currentUrl.includes(DASHBOARD_VIEW_PATH); +}; + +const DashboardBreadCrumb: React.FunctionComponent<{ dashboardDisplayName?: string }> = ({ + dashboardDisplayName, +}) => { + const { t } = useTranslation(process.env.I18N_NAMESPACE); + + const { perspective } = usePerspective(); + const { theme } = usePatternFlyTheme(); + const navigate = useNavigate(); + + const handleDashboardsClick = () => { + navigate(getDashboardsListUrl(perspective)); + }; + + const lightThemeColor = chart_color_blue_100.value; + + const darkThemeColor = chart_color_blue_300.value; + + const linkColor = theme == 'dark' ? lightThemeColor : darkThemeColor; + + return ( + + + {t('Dashboards')} + + {dashboardDisplayName && ( + + {dashboardDisplayName} + + )} + + ); +}; + +const DashboardPageHeader: React.FunctionComponent<{ dashboardDisplayName?: string }> = ({ + dashboardDisplayName, +}) => { + const { t } = useTranslation(process.env.I18N_NAMESPACE); + const hideFavBtn = shouldHideFavoriteButton(); + + return ( + + + + + + + + + + ); +}; + +const DashboardListPageHeader: React.FunctionComponent = () => { + const { t } = useTranslation(process.env.I18N_NAMESPACE); + const hideFavBtn = shouldHideFavoriteButton(); + + return ( + + + + + + + + ); +}; + +type MonitoringDashboardsPageProps = PropsWithChildren<{ + boardItems: CombinedDashboardMetadata[]; + changeBoard: (dashboardName: string) => void; + dashboardDisplayName: string; + activeProject?: string; +}>; + +export const DashboardHeader: FC = memo( + ({ children, dashboardDisplayName }) => { + const { t } = useTranslation(process.env.I18N_NAMESPACE); + + return ( + <> + {t('Metrics dashboards')} + + + + {children} + + ); + }, +); + +export const DashboardListHeader: FC = memo(({ children }) => { + const { t } = useTranslation(process.env.I18N_NAMESPACE); + + return ( + <> + {t('Metrics dashboards')} + + + + + {children} + + ); +}); diff --git a/web/src/components/dashboards/perses/dashboard-list-frame.tsx b/web/src/components/dashboards/perses/dashboard-list-frame.tsx new file mode 100644 index 000000000..6bce52cc6 --- /dev/null +++ b/web/src/components/dashboards/perses/dashboard-list-frame.tsx @@ -0,0 +1,36 @@ +import React, { ReactNode } from 'react'; +import { DashboardListHeader } from './dashboard-header'; +import { CombinedDashboardMetadata } from './hooks/useDashboardsData'; +import { ProjectBar } from './project/ProjectBar'; + +interface DashboardListFrameProps { + activeProject: string | null; + setActiveProject: (project: string | null) => void; + activeProjectDashboardsMetadata: CombinedDashboardMetadata[]; + changeBoard: (boardName: string) => void; + dashboardName: string; + children: ReactNode; +} + +export const DashboardListFrame: React.FC = ({ + activeProject, + setActiveProject, + activeProjectDashboardsMetadata, + changeBoard, + dashboardName, + children, +}) => { + return ( + <> + + + {children} + + + ); +}; diff --git a/web/src/components/dashboards/perses/dashboard-list-page.tsx b/web/src/components/dashboards/perses/dashboard-list-page.tsx new file mode 100644 index 000000000..92c3391e9 --- /dev/null +++ b/web/src/components/dashboards/perses/dashboard-list-page.tsx @@ -0,0 +1,29 @@ +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { type FC } from 'react'; +import { QueryParamProvider } from 'use-query-params'; +import { ReactRouter5Adapter } from 'use-query-params/adapters/react-router-5'; +import { DashboardList } from './dashboard-list'; +import { ToastProvider } from './ToastProvider'; + +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + refetchOnWindowFocus: false, + retry: false, + }, + }, +}); + +const DashboardListPage: FC = () => { + return ( + + + + + + + + ); +}; + +export default DashboardListPage; diff --git a/web/src/components/dashboards/perses/dashboard-list.tsx b/web/src/components/dashboards/perses/dashboard-list.tsx new file mode 100644 index 000000000..2154ab3e5 --- /dev/null +++ b/web/src/components/dashboards/perses/dashboard-list.tsx @@ -0,0 +1,454 @@ +import React, { ReactNode, useCallback, useMemo, useState, type FC } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useDashboardsData } from './hooks/useDashboardsData'; + +import { + Button, + EmptyState, + EmptyStateBody, + EmptyStateVariant, + Pagination, + Title, + Tooltip, +} from '@patternfly/react-core'; +import { DataView } from '@patternfly/react-data-view/dist/dynamic/DataView'; +import { DataViewFilters } from '@patternfly/react-data-view/dist/dynamic/DataViewFilters'; +import { + DataViewTable, + DataViewTh, + DataViewTr, +} from '@patternfly/react-data-view/dist/dynamic/DataViewTable'; +import { DataViewTextFilter } from '@patternfly/react-data-view/dist/dynamic/DataViewTextFilter'; +import { DataViewToolbar } from '@patternfly/react-data-view/dist/dynamic/DataViewToolbar'; +import { useDataViewFilters, useDataViewSort } from '@patternfly/react-data-view'; +import { useDataViewPagination } from '@patternfly/react-data-view/dist/dynamic/Hooks'; +import { ActionsColumn, ThProps } from '@patternfly/react-table'; +import { Link, useSearchParams } from 'react-router-dom-v5-compat'; + +import { getDashboardUrl, usePerspective } from '../../hooks/usePerspective'; +import { Timestamp } from '@openshift-console/dynamic-plugin-sdk'; +import { listPersesDashboardsDataTestIDs } from '../../../components/data-test'; +import { DashboardListFrame } from './dashboard-list-frame'; +import { usePersesEditPermissions } from './dashboard-toolbar'; +import { DashboardResource } from '@perses-dev/core'; +import { + DeleteActionModal, + DuplicateActionModal, + RenameActionModal, +} from './dashboard-action-modals'; +const perPageOptions = [ + { title: '10', value: 10 }, + { title: '20', value: 20 }, +]; + +const DashboardActionsCell = React.memo( + ({ + project, + dashboard, + onRename, + onDuplicate, + onDelete, + emptyActions, + }: { + project: string; + dashboard: DashboardResource; + onRename: (dashboard: DashboardResource) => void; + onDuplicate: (dashboard: DashboardResource) => void; + onDelete: (dashboard: DashboardResource) => void; + emptyActions: any[]; + }) => { + const { t } = useTranslation(process.env.I18N_NAMESPACE); + const { canEdit, loading } = usePersesEditPermissions(project); + const disabled = !canEdit; + + const rowSpecificActions = useMemo( + () => [ + { + title: t('Rename dashboard'), + onClick: () => onRename(dashboard), + }, + { + title: t('Duplicate dashboard'), + onClick: () => onDuplicate(dashboard), + }, + { + title: t('Delete dashboard'), + onClick: () => onDelete(dashboard), + }, + ], + [dashboard, onRename, onDuplicate, onDelete, t], + ); + + if (disabled || loading) { + return ( + +
+ +
+
+ ); + } + + return ; + }, +); + +interface DashboardRowNameLink { + link: ReactNode; + label: string; +} + +interface DashboardRow { + name: DashboardRowNameLink; + project: string; + created: ReactNode; + modified: ReactNode; + // Raw values for sorting + createdAt?: string; + updatedAt?: string; + // Reference to original dashboard data + dashboard: DashboardResource; +} + +interface DashboardRowFilters { + name?: string; + 'project-filter'?: string; +} + +const sortDashboardData = ( + data: DashboardRow[], + sortBy: keyof DashboardRow | undefined, + direction: 'asc' | 'desc' | undefined, +): DashboardRow[] => { + if (!sortBy || !direction) return data; + + return [...data].sort((a, b) => { + let aValue: any; + let bValue: any; + + if (sortBy === 'name') { + aValue = a.name.label; + bValue = b.name.label; + } else if (sortBy === 'created') { + aValue = a.createdAt; + bValue = b.createdAt; + } else if (sortBy === 'modified') { + aValue = a.updatedAt; + bValue = b.updatedAt; + } else { + aValue = a[sortBy]; + bValue = b[sortBy]; + } + + if (direction === 'asc') { + return aValue < bValue ? -1 : aValue > bValue ? 1 : 0; + } else { + return aValue > bValue ? -1 : aValue < bValue ? 1 : 0; + } + }); +}; + +interface DashboardsTableProps { + persesDashboards: DashboardResource[]; + persesDashboardsLoading: boolean; + activeProject: string | null; +} + +const DashboardsTable: React.FunctionComponent = ({ + persesDashboards, + persesDashboardsLoading, + activeProject, +}) => { + const { t } = useTranslation(process.env.I18N_NAMESPACE); + + const { perspective } = usePerspective(); + const dashboardBaseURL = getDashboardUrl(perspective); + + const [searchParams, setSearchParams] = useSearchParams(); + const { sortBy, direction, onSort } = useDataViewSort({ searchParams, setSearchParams }); + + const { filters, onSetFilters, clearAllFilters } = useDataViewFilters({ + initialFilters: { name: '', 'project-filter': '' }, + searchParams, + setSearchParams, + }); + const pagination = useDataViewPagination({ perPage: perPageOptions[0].value }); + const { page, perPage } = pagination; + + const DASHBOARD_COLUMNS = useMemo( + () => [ + { label: t('Dashboard'), key: 'name' as keyof DashboardRow, index: 0 }, + { label: t('Project'), key: 'project' as keyof DashboardRow, index: 1 }, + { label: t('Created on'), key: 'created' as keyof DashboardRow, index: 2 }, + { label: t('Last Modified'), key: 'modified' as keyof DashboardRow, index: 3 }, + ], + [t], + ); + const sortByIndex = useMemo(() => { + return DASHBOARD_COLUMNS.findIndex((item) => item.key === sortBy); + }, [DASHBOARD_COLUMNS, sortBy]); + + const getSortParams = (columnIndex: number): ThProps['sort'] => ({ + sortBy: { + index: sortByIndex, + direction, + defaultDirection: 'asc', + }, + onSort: (_event, index, direction) => onSort(_event, DASHBOARD_COLUMNS[index].key, direction), + columnIndex, + }); + + const tableColumns: DataViewTh[] = DASHBOARD_COLUMNS.map((column, index) => ({ + cell: t(column.label), + props: { sort: getSortParams(index) }, + })); + + const tableRows: DashboardRow[] = useMemo(() => { + if (persesDashboardsLoading) { + return []; + } + return persesDashboards.map((board) => { + const metadata = board?.metadata; + const displayName = board?.spec?.display?.name; + const dashboardsParams = `?dashboard=${metadata?.name}&project=${metadata?.project}`; + const dashboardName: DashboardRowNameLink = { + link: ( + + {displayName} + + ), + label: displayName || '', + }; + + return { + name: dashboardName, + project: board?.metadata?.project || '', + created: , + modified: , + createdAt: metadata?.createdAt, + updatedAt: metadata?.updatedAt, + dashboard: board, + }; + }); + }, [dashboardBaseURL, persesDashboards, persesDashboardsLoading]); + + const filteredData = useMemo( + () => + tableRows.filter( + (item) => + (!filters.name || + item.name?.label?.toLocaleLowerCase().includes(filters.name?.toLocaleLowerCase())) && + (!filters['project-filter'] || + item.project + ?.toLocaleLowerCase() + .includes(filters['project-filter']?.toLocaleLowerCase())) && + (!activeProject || item.project === activeProject), + ), + [filters, tableRows, activeProject], + ); + + const sortedAndFilteredData = useMemo( + () => sortDashboardData(filteredData, sortBy as keyof DashboardRow, direction), + [filteredData, sortBy, direction], + ); + + const [targetedDashboard, setTargetedDashboard] = useState(); + const [isRenameModalOpen, setIsRenameModalOpen] = useState(false); + const [isDuplicateModalOpen, setIsDuplicateModalOpen] = useState(false); + const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); + + const handleRenameModalOpen = useCallback((dashboard: DashboardResource) => { + setTargetedDashboard(dashboard); + setIsRenameModalOpen(true); + }, []); + + const handleRenameModalClose = useCallback(() => { + setIsRenameModalOpen(false); + setTargetedDashboard(undefined); + }, []); + + const handleDuplicateModalOpen = useCallback((dashboard: DashboardResource) => { + setTargetedDashboard(dashboard); + setIsDuplicateModalOpen(true); + }, []); + + const handleDuplicateModalClose = useCallback(() => { + setIsDuplicateModalOpen(false); + setTargetedDashboard(undefined); + }, []); + + const handleDeleteModalOpen = useCallback((dashboard: DashboardResource) => { + setTargetedDashboard(dashboard); + setIsDeleteModalOpen(true); + }, []); + + const handleDeleteModalClose = useCallback(() => { + setIsDeleteModalOpen(false); + setTargetedDashboard(undefined); + }, []); + + const emptyRowActions = useMemo( + () => [ + { + title: t("You don't have permissions to dashboard actions"), + onClick: () => {}, + }, + ], + [t], + ); + + const pageRows: DataViewTr[] = useMemo(() => { + return sortedAndFilteredData + .slice((page - 1) * perPage, (page - 1) * perPage + perPage) + .map(({ name, project, created, modified, dashboard }) => [ + name.link, + project, + created, + modified, + { + cell: ( + + ), + props: { isActionCell: true }, + }, + ]); + }, [ + sortedAndFilteredData, + page, + perPage, + emptyRowActions, + handleRenameModalOpen, + handleDuplicateModalOpen, + handleDeleteModalOpen, + ]); + + const PaginationTool = () => { + return ( + + ); + }; + + const hasFiltersApplied = filters.name || filters['project-filter']; + const hasData = sortedAndFilteredData.length > 0; + + return ( + + } + filters={ + onSetFilters(values)} values={filters}> + + + + } + /> + {hasData ? ( + <> + + + + + + ) : ( + + + {hasFiltersApplied ? t('No results found') : t('No dashboards found')} + + + {hasFiltersApplied + ? t('No results match the filter criteria. Clear filters to show results.') + : t('No Perses dashboards are currently available in this project.')} + + {hasFiltersApplied && ( + + )} + + )} + } /> + + ); +}; + +export const DashboardList: FC = () => { + const { + activeProjectDashboardsMetadata, + changeBoard, + dashboardName, + setActiveProject, + activeProject, + persesDashboards, + combinedInitialLoad, + } = useDashboardsData(); + + return ( + + + + ); +}; diff --git a/web/src/components/dashboards/perses/dashboard-page-padding.tsx b/web/src/components/dashboards/perses/dashboard-page-padding.tsx new file mode 100644 index 000000000..c0647b980 --- /dev/null +++ b/web/src/components/dashboards/perses/dashboard-page-padding.tsx @@ -0,0 +1,27 @@ +import { t_global_spacer_sm } from '@patternfly/react-tokens'; +import { FC, ReactNode } from 'react'; + +interface PagePaddingProps { + children: ReactNode; + top?: string; + bottom?: string; + left?: string; + right?: string; +} + +export const PagePadding: FC = ({ + children, + top = t_global_spacer_sm.value, + bottom = t_global_spacer_sm.value, + left = t_global_spacer_sm.value, + right = t_global_spacer_sm.value, +}) => { + const style = { + paddingTop: top, + paddingBottom: bottom, + paddingLeft: left, + paddingRight: right, + }; + + return
{children}
; +}; diff --git a/web/src/components/dashboards/perses/dashboard-page.tsx b/web/src/components/dashboards/perses/dashboard-page.tsx index d2d28a032..979482475 100644 --- a/web/src/components/dashboards/perses/dashboard-page.tsx +++ b/web/src/components/dashboards/perses/dashboard-page.tsx @@ -1,72 +1,113 @@ -import { Overview } from '@openshift-console/dynamic-plugin-sdk'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import type { FC } from 'react'; +import { useEffect, type FC } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useSearchParams } from 'react-router-dom-v5-compat'; +import { QueryParamProvider } from 'use-query-params'; +import { ReactRouter5Adapter } from 'use-query-params/adapters/react-router-5'; import { LoadingInline } from '../../console/console-shared/src/components/loading/LoadingInline'; -import { PersesWrapper } from './PersesWrapper'; -import { DashboardSkeleton } from './dashboard-skeleton'; -import { DashboardEmptyState } from './emptystates/DashboardEmptyState'; +import { OCPDashboardApp } from './dashboard-app'; +import { DashboardFrame } from './dashboard-frame'; import { ProjectEmptyState } from './emptystates/ProjectEmptyState'; import { useDashboardsData } from './hooks/useDashboardsData'; -import PersesBoard from './perses-dashboards'; -import { ProjectBar } from './project/ProjectBar'; +import { ToastProvider } from './ToastProvider'; const queryClient = new QueryClient({ defaultOptions: { queries: { + retry: false, refetchOnWindowFocus: false, - retry: 0, }, }, }); -const MonitoringDashboardsPage_: FC = () => { +const DashboardPage_: FC = () => { + const { t } = useTranslation(process.env.I18N_NAMESPACE); + const [searchParams] = useSearchParams(); + const { - changeBoard, activeProjectDashboardsMetadata, - combinedInitialLoad, - activeProject, - setActiveProject, + changeBoard, dashboardName, + setActiveProject, + activeProject, + combinedInitialLoad, } = useDashboardsData(); + // Get dashboard and project from URL parameters + const urlDashboard = searchParams.get('dashboard'); + const urlProject = searchParams.get('project'); + + // Set active project if provided in URL + if (urlProject && urlProject !== activeProject) { + setActiveProject(urlProject); + } + + useEffect(() => { + if (urlDashboard && urlDashboard !== dashboardName) { + changeBoard(urlDashboard); + } + }, [urlDashboard, dashboardName, changeBoard]); + if (combinedInitialLoad) { return ; } - if (!activeProject) { - // If we have loaded all of the requests fully and there are no projects, then - return ; // empty state + if (activeProjectDashboardsMetadata?.length === 0) { + return ; + } + + // Find the dashboard that matches either the URL parameter or the current dashboardName + const targetDashboardName = urlDashboard || dashboardName; + const currentDashboard = activeProjectDashboardsMetadata.find( + (d) => d.name === targetDashboardName, + ); + + if (!currentDashboard) { + return ( +
+

{t('Dashboard not found')}

+

+ {t('The dashboard "{{name}}" was not found in project "{{project}}".', { + name: targetDashboardName, + project: activeProject || urlProject, + })} +

+
+ ); } return ( - <> - - - {activeProjectDashboardsMetadata.length === 0 ? ( - - ) : ( - - - - - - )} - - + + + ); }; -const MonitoringDashboardsPageWrapper: FC = () => { +const DashboardPage: React.FC = () => { return ( - - - + + + + + + + ); }; -export default MonitoringDashboardsPageWrapper; +export default DashboardPage; diff --git a/web/src/components/dashboards/perses/dashboard-permissions.ts b/web/src/components/dashboards/perses/dashboard-permissions.ts new file mode 100644 index 000000000..df5688a0c --- /dev/null +++ b/web/src/components/dashboards/perses/dashboard-permissions.ts @@ -0,0 +1,92 @@ +import { checkAccess } from '@openshift-console/dynamic-plugin-sdk'; +import { useQuery } from '@tanstack/react-query'; +import { useMemo } from 'react'; + +const checkProjectPermissions = async (projects: any[]): Promise => { + if (!projects || projects.length === 0) { + return []; + } + + const editableProjectNames: string[] = []; + + for (const project of projects) { + const projectName = project?.metadata?.name; + if (!projectName) continue; + + try { + const [createResult, updateResult, deleteResult] = await Promise.all([ + checkAccess({ + group: 'perses.dev', + resource: 'persesdashboards', + verb: 'create', + namespace: projectName, + }), + checkAccess({ + group: 'perses.dev', + resource: 'persesdashboards', + verb: 'update', + namespace: projectName, + }), + checkAccess({ + group: 'perses.dev', + resource: 'persesdashboards', + verb: 'delete', + namespace: projectName, + }), + ]); + + const canEdit = + createResult.status.allowed && updateResult.status.allowed && deleteResult.status.allowed; + + if (canEdit) { + editableProjectNames.push(projectName); + } + } catch (error) { + // eslint-disable-next-line no-console + console.warn(`Failed to check permissions for project ${projectName}:`, error); + } + } + + return editableProjectNames; +}; + +export const useProjectPermissions = (projects: any[]) => { + const queryKey = useMemo(() => { + if (!projects || projects.length === 0) { + return ['project-permissions', 'empty']; + } + + const projectFingerprint = projects.map((p) => ({ + name: p?.metadata?.name, + version: p?.metadata?.version, + updatedAt: p?.metadata?.updatedAt, + })); + + return ['project-permissions', JSON.stringify(projectFingerprint)]; + }, [projects]); + + const { + data: editableProjects = [], + isLoading: loading, + error, + } = useQuery({ + queryKey, + queryFn: async () => { + try { + return await checkProjectPermissions(projects); + } catch (error) { + // eslint-disable-next-line no-console + console.warn('Failed to check project permissions:', error); + return []; + } + }, + enabled: !!projects && projects.length > 0, + staleTime: 5 * 60 * 1000, + refetchOnWindowFocus: true, + retry: 2, + }); + + const hasEditableProject = editableProjects.length > 0; + + return { editableProjects, hasEditableProject, loading, error }; +}; diff --git a/web/src/components/dashboards/perses/dashboard-skeleton.tsx b/web/src/components/dashboards/perses/dashboard-skeleton.tsx deleted file mode 100644 index 9f4853669..000000000 --- a/web/src/components/dashboards/perses/dashboard-skeleton.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import * as _ from 'lodash-es'; -import type { FC, PropsWithChildren } from 'react'; -import { memo, useCallback, useEffect } from 'react'; -import { useTranslation } from 'react-i18next'; - -import { Divider, PageSection, Split, SplitItem, Stack, StackItem } from '@patternfly/react-core'; -import { - DashboardStickyToolbar, - useDashboardActions, - useVariableDefinitions, -} from '@perses-dev/dashboards'; -import { TimeRangeControls } from '@perses-dev/plugin-system'; -import { DashboardDropdown } from '../shared/dashboard-dropdown'; -import { CombinedDashboardMetadata } from './hooks/useDashboardsData'; -import { DocumentTitle, ListPageHeader } from '@openshift-console/dynamic-plugin-sdk'; - -const HeaderTop: FC = memo(() => { - const { t } = useTranslation(process.env.I18N_NAMESPACE); - - return ( - - - - - - - - ); -}); - -type MonitoringDashboardsPageProps = PropsWithChildren<{ - boardItems: CombinedDashboardMetadata[]; - changeBoard: (dashboardName: string) => void; - dashboardName: string; - activeProject?: string; -}>; - -export const DashboardSkeleton: FC = memo( - ({ children, boardItems, changeBoard, dashboardName, activeProject }) => { - const { t } = useTranslation(process.env.I18N_NAMESPACE); - const { setDashboard } = useDashboardActions(); - const variables = useVariableDefinitions(); - - const onChangeBoard = useCallback( - (selectedDashboard: string) => { - changeBoard(selectedDashboard); - - const selectedBoard = boardItems.find( - (item) => - item.name.toLowerCase() === selectedDashboard.toLowerCase() && - item.project?.toLowerCase() === activeProject?.toLowerCase(), - ); - - if (selectedBoard) { - setDashboard(selectedBoard.persesDashboard); - } - }, - [changeBoard, boardItems, activeProject, setDashboard], - ); - - useEffect(() => { - onChangeBoard(dashboardName); - }, [dashboardName, onChangeBoard]); - - return ( - <> - {t('Metrics dashboards')} - - - - {!_.isEmpty(boardItems) && ( - - - - )} - {variables.length > 0 ? ( - - {t('Dashboard Variables')} - - - ) : null} - - - - - - - - - {children} - - ); - }, -); diff --git a/web/src/components/dashboards/perses/dashboard-toolbar.tsx b/web/src/components/dashboards/perses/dashboard-toolbar.tsx new file mode 100644 index 000000000..2791c6e66 --- /dev/null +++ b/web/src/components/dashboards/perses/dashboard-toolbar.tsx @@ -0,0 +1,268 @@ +import { Alert, Box, Button, Stack, Tooltip, useMediaQuery, useTheme } from '@mui/material'; +import { ErrorAlert, ErrorBoundary } from '@perses-dev/components'; +import { + AddGroupButton, + AddPanelButton, + DashboardStickyToolbar, + DownloadButton, + EditDatasourcesButton, + EditJsonButton, + EditVariablesButton, + OnSaveDashboard, + SaveDashboardButton, + useDashboardActions, + useEditMode, +} from '@perses-dev/dashboards'; +import { TimeRangeControls, useTimeZoneParams } from '@perses-dev/plugin-system'; +import { ReactElement, ReactNode, useCallback, useEffect } from 'react'; + +import { useAccessReview } from '@openshift-console/dynamic-plugin-sdk'; +import { StackItem } from '@patternfly/react-core'; +import * as _ from 'lodash-es'; +import PencilIcon from 'mdi-material-ui/PencilOutline'; +import { useTranslation } from 'react-i18next'; +import { DashboardDropdown } from '../shared/dashboard-dropdown'; +import { useDashboardsData } from './hooks/useDashboardsData'; + +import { persesDashboardDataTestIDs } from '../../data-test'; + +export interface DashboardToolbarProps { + dashboardName: string; + dashboardTitleComponent?: ReactNode; + initialVariableIsSticky?: boolean; + isReadonly: boolean; + isVariableEnabled: boolean; + isDatasourceEnabled: boolean; + onEditButtonClick: () => void; + onCancelButtonClick: () => void; + onSave?: OnSaveDashboard; +} + +export interface EditButtonProps { + /** + * The label used inside the button. + */ + label?: string; + + /** + * Handler that puts the dashboard into editing mode. + */ + onClick: () => void; + + /** + * Whether the button is disabled. + */ + disabled?: boolean; + + /** + * Tooltip text to show when button is disabled. + */ + disabledTooltip?: string; + + /** + * Whether permissions are still loading. + */ + loading?: boolean; + + /** + * The active project/namespace for permissions check. + */ + activeProject?: string | null; +} + +export const EditButton = ({ onClick, activeProject }: EditButtonProps): ReactElement => { + const { t } = useTranslation(process.env.I18N_NAMESPACE); + const { canEdit, loading } = usePersesEditPermissions(activeProject); + const disabled = !canEdit; + + const button = ( + + ); + + if (disabled && !loading) { + return ( + + {button} + + ); + } + + return button; +}; + +export const usePersesEditPermissions = (namespace: string | null = null) => { + const [canCreate, createLoading] = useAccessReview({ + group: 'perses.dev', + resource: 'persesdashboards', + verb: 'create', + namespace, + }); + + const [canUpdate, updateLoading] = useAccessReview({ + group: 'perses.dev', + resource: 'persesdashboards', + verb: 'update', + namespace, + }); + + const [canDelete, deleteLoading] = useAccessReview({ + group: 'perses.dev', + resource: 'persesdashboards', + verb: 'delete', + namespace, + }); + + const loading = createLoading || updateLoading || deleteLoading; + const canEdit = canUpdate && canCreate && canDelete; + + return { canEdit, loading }; +}; + +export const OCPDashboardToolbar = (props: DashboardToolbarProps): ReactElement => { + const { + initialVariableIsSticky, + isReadonly, + isVariableEnabled, + isDatasourceEnabled, + onEditButtonClick, + onCancelButtonClick, + onSave, + } = props; + + const { isEditMode } = useEditMode(); + const { timeZone, setTimeZone } = useTimeZoneParams('local'); + + const isBiggerThanSm = useMediaQuery(useTheme().breakpoints.up('sm')); + const isBiggerThanMd = useMediaQuery(useTheme().breakpoints.up('md')); + + const testId = 'dashboard-toolbar'; + + const { + changeBoard, + activeProjectDashboardsMetadata: boardItems, + activeProject, + dashboardName, + } = useDashboardsData(); + + const { setDashboard } = useDashboardActions(); + + const onChangeBoard = useCallback( + (selectedDashboard: string) => { + changeBoard(selectedDashboard); + + const selectedBoard = boardItems.find( + (item) => + item.name.toLowerCase() === selectedDashboard.toLowerCase() && + item.project?.toLowerCase() === activeProject?.toLowerCase(), + ); + + if (selectedBoard) { + setDashboard(selectedBoard.persesDashboard); + } + }, + [activeProject, boardItems, changeBoard, setDashboard], + ); + + useEffect(() => { + onChangeBoard(dashboardName); + }, [dashboardName, onChangeBoard]); + + return ( + <> + + theme.palette.primary.main + (isEditMode ? '30' : '0'), + alignItems: 'center', + }} + > + {!_.isEmpty(boardItems) && ( + + + + )} + {isEditMode ? ( + + {isReadonly && ( + + Dashboard managed via code only. Download JSON and commit changes to save. + + )} + + {isVariableEnabled && } + {isDatasourceEnabled && } + + + + + + + ) : ( + <> + {isBiggerThanSm && ( + + + + )} + + )} + + theme.spacing(1, 2, 0, 2), + flexDirection: isBiggerThanMd ? 'row' : 'column', + flexWrap: 'nowrap', + gap: 1, + }} + > + + + palette.background.default, + }} + /> + + + + + setTimeZone(tz.value)} + /> + + + + + + + + ); +}; diff --git a/web/src/components/dashboards/perses/dashboard-utils.ts b/web/src/components/dashboards/perses/dashboard-utils.ts new file mode 100644 index 000000000..2a8101aec --- /dev/null +++ b/web/src/components/dashboards/perses/dashboard-utils.ts @@ -0,0 +1,37 @@ +import { DashboardResource } from '@perses-dev/core'; + +/** + * Generated a resource name valid for the API. + * By removing accents from alpha characters and replace specials character by underscores. + */ +export const generateMetadataName = (name: string): string => { + return name + .normalize('NFD') + .replace(/\p{Diacritic}/gu, '') + .replace(/[^a-zA-Z0-9_.-]/g, '_'); +}; + +export const createNewDashboard = ( + dashboardName: string, + projectName: string, +): DashboardResource => { + return { + kind: 'Dashboard', + metadata: { + name: generateMetadataName(dashboardName), + project: projectName, + version: 0, + }, + spec: { + display: { + name: dashboardName, + }, + datasources: {}, + panels: {}, + layouts: [], + variables: [], + duration: '1h', + refreshInterval: '30s', + }, + }; +}; diff --git a/web/src/components/dashboards/perses/datasource-api.ts b/web/src/components/dashboards/perses/datasource-api.ts index f3adb79cd..5100c34f5 100644 --- a/web/src/components/dashboards/perses/datasource-api.ts +++ b/web/src/components/dashboards/perses/datasource-api.ts @@ -1,5 +1,9 @@ -import { DatasourceResource, DatasourceSelector, GlobalDatasourceResource } from '@perses-dev/core'; -import { DatasourceApi } from '@perses-dev/dashboards'; +import { + DatasourceResource, + DatasourceSelector, + GlobalDatasourceResource, + DatasourceApi, +} from '@perses-dev/core'; import { fetchDatasourceList } from './perses/datasource-client'; import { fetchGlobalDatasourceList } from './perses/global-datasource-client'; import { TFunction } from 'i18next'; diff --git a/web/src/components/dashboards/perses/hooks/useDashboardsData.ts b/web/src/components/dashboards/perses/hooks/useDashboardsData.ts index ca700d6a5..614093bbe 100644 --- a/web/src/components/dashboards/perses/hooks/useDashboardsData.ts +++ b/web/src/components/dashboards/perses/hooks/useDashboardsData.ts @@ -1,11 +1,11 @@ -import { useMemo, useCallback, useEffect } from 'react'; +import { useMemo, useCallback, useRef } from 'react'; import { DashboardResource } from '@perses-dev/core'; import { useNavigate } from 'react-router-dom-v5-compat'; import { StringParam, useQueryParam } from 'use-query-params'; import { getAllQueryArguments } from '../../../console/utils/router'; import { useBoolean } from '../../../hooks/useBoolean'; -import { getDashboardsUrl, usePerspective } from '../../../hooks/usePerspective'; +import { getDashboardUrl, usePerspective } from '../../../hooks/usePerspective'; import { QueryParams } from '../../../query-params'; import { useActiveProject } from '../project/useActiveProject'; import { usePerses } from './usePerses'; @@ -39,13 +39,33 @@ export const useDashboardsData = () => { return true; }, [persesProjectsLoading, persesDashboardsLoading, initialPageLoad, setInitialPageLoadFalse]); + const prevDashboardsRef = useRef([]); + const prevMetadataRef = useRef([]); + // Homogenize data needed for dashboards dropdown between legacy and perses dashboards // to enable both to use the same component const combinedDashboardsMetadata = useMemo(() => { if (combinedInitialLoad) { return []; } - return persesDashboards.map((persesDashboard) => { + + // Check if dashboards data has actually changed to avoid recreation + const dashboardsChanged = + persesDashboards.length !== prevDashboardsRef.current.length || + persesDashboards.some((dashboard, i) => { + const prevDashboard = prevDashboardsRef.current[i]; + return ( + dashboard?.metadata?.name !== prevDashboard?.metadata?.name || + dashboard?.spec?.display?.name !== prevDashboard?.spec?.display?.name || + dashboard?.metadata?.project !== prevDashboard?.metadata?.project + ); + }); + + if (!dashboardsChanged && prevMetadataRef.current.length > 0) { + return prevMetadataRef.current; + } + + const newMetadata = persesDashboards.map((persesDashboard) => { const name = persesDashboard?.metadata?.name; const displayName = persesDashboard?.spec?.display?.name || name; @@ -57,10 +77,17 @@ export const useDashboardsData = () => { persesDashboard, }; }); + + prevDashboardsRef.current = persesDashboards; + prevMetadataRef.current = newMetadata; + return newMetadata; }, [persesDashboards, combinedInitialLoad]); // Retrieve dashboard metadata for the currently selected project const activeProjectDashboardsMetadata = useMemo(() => { + if (!activeProject) { + return combinedDashboardsMetadata; + } return combinedDashboardsMetadata.filter((combinedDashboardMetadata) => { return combinedDashboardMetadata.project === activeProject; }); @@ -74,35 +101,31 @@ export const useDashboardsData = () => { } const queryArguments = getAllQueryArguments(); + delete queryArguments.edit; + const params = new URLSearchParams(queryArguments); - params.set(QueryParams.Project, activeProject); + + let projectToUse = activeProject; + if (!activeProject) { + const dashboardMetadata = combinedDashboardsMetadata.find((item) => item.name === newBoard); + projectToUse = dashboardMetadata?.project; + } + + if (projectToUse) { + params.set(QueryParams.Project, projectToUse); + } params.set(QueryParams.Dashboard, newBoard); - let url = getDashboardsUrl(perspective); + let url = getDashboardUrl(perspective); url = `${url}?${params.toString()}`; if (newBoard !== dashboardName) { navigate(url, { replace: true }); } }, - [perspective, dashboardName, navigate, activeProject], + [perspective, dashboardName, navigate, activeProject, combinedDashboardsMetadata], ); - // If a dashboard hasn't been selected yet, or if the current project doesn't have a - // matching board name then display the board present in the URL parameters or the first - // board in the dropdown list - useEffect(() => { - const metadataMatch = activeProjectDashboardsMetadata.find((activeProjectDashboardMetadata) => { - return ( - activeProjectDashboardMetadata.project === activeProject && - activeProjectDashboardMetadata.name === dashboardName - ); - }); - if (!dashboardName || !metadataMatch) { - changeBoard(activeProjectDashboardsMetadata?.[0]?.name); - } - }, [dashboardName, changeBoard, activeProject, activeProjectDashboardsMetadata]); - return { persesAvailable, persesProjectsLoading, diff --git a/web/src/components/dashboards/perses/hooks/usePerses.ts b/web/src/components/dashboards/perses/hooks/usePerses.ts index 643432418..cc3f0f103 100644 --- a/web/src/components/dashboards/perses/hooks/usePerses.ts +++ b/web/src/components/dashboards/perses/hooks/usePerses.ts @@ -1,9 +1,14 @@ -import { fetchPersesProjects, fetchPersesDashboardsMetadata } from '../perses-client'; +import { + fetchPersesProjects, + fetchPersesDashboardsMetadata, + fetchPersesDashboardsByProject, +} from '../perses-client'; import { useQuery } from '@tanstack/react-query'; import { NumberParam, useQueryParam } from 'use-query-params'; import { QueryParams } from '../../../query-params'; +import { t } from 'i18next'; -export const usePerses = () => { +export const usePerses = (project?: string | number) => { const [refreshInterval] = useQueryParam(QueryParams.RefreshInterval, NumberParam); const { @@ -24,16 +29,39 @@ export const usePerses = () => { } = useQuery({ queryKey: ['dashboards'], queryFn: fetchPersesDashboardsMetadata, - enabled: true, + enabled: !project, // Only fetch all dashboards when no specific project is requested + refetchInterval: refreshInterval, + }); + + const { + isLoading: persesProjectDashboardsLoading, + error: persesProjectDashboardsError, + data: persesProjectDashboards, + } = useQuery({ + queryKey: ['dashboards', 'project', project], + queryFn: () => { + if (project === undefined || project === null) { + throw new Error(t('Project is required for fetching project dashboards')); + } + return fetchPersesDashboardsByProject(String(project)); + }, + enabled: !!project, refetchInterval: refreshInterval, }); return { - persesDashboards: persesDashboards ?? [], - persesDashboardsError, - persesDashboardsLoading, + // All Dashboards - fallback to project dashboards when all dashboards query is disabled + persesDashboards: persesDashboards ?? persesProjectDashboards ?? [], + persesDashboardsError: persesDashboardsError ?? persesProjectDashboardsError, + persesDashboardsLoading: + persesDashboardsLoading || (!!project && persesProjectDashboardsLoading), + // All Projects persesProjectsLoading, persesProjects: persesProjects ?? [], persesProjectsError, + // Dashboards of a given project + persesProjectDashboards: persesProjectDashboards ?? [], + persesProjectDashboardsError, + persesProjectDashboardsLoading, }; }; diff --git a/web/src/components/dashboards/perses/perses-client.ts b/web/src/components/dashboards/perses/perses-client.ts index f1fae0b44..63bf526cf 100644 --- a/web/src/components/dashboards/perses/perses-client.ts +++ b/web/src/components/dashboards/perses/perses-client.ts @@ -13,6 +13,13 @@ export const fetchPersesDashboardsMetadata = (): Promise => return ocpPersesFetchJson(persesURL); }; +export const fetchPersesDashboardsByProject = (project: string): Promise => { + const dashboardsEndpoint = `${PERSES_PROXY_BASE_PATH}/api/v1/dashboards`; + const persesURL = `${dashboardsEndpoint}?project=${encodeURIComponent(project)}`; + + return ocpPersesFetchJson(persesURL); +}; + export const fetchPersesProjects = (): Promise => { const listProjectURL = '/api/v1/projects'; const persesURL = `${PERSES_PROXY_BASE_PATH}${listProjectURL}`; diff --git a/web/src/components/dashboards/perses/perses-dashboards.tsx b/web/src/components/dashboards/perses/perses-dashboards.tsx deleted file mode 100644 index c0ec617d2..000000000 --- a/web/src/components/dashboards/perses/perses-dashboards.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { Dashboard } from '@perses-dev/dashboards'; -import { useTranslation } from 'react-i18next'; - -/** - * This component is what we use to integrate perses into the openshift console - * It is a combinatoin of the ViewDashboard & DashboardApp components in the perses - * codebase. We can't use those components directly as they come bundled with the - * Dashboard toolbar, and are overall not needed for the non-editable dashboards. - * As we look to implement customizable dashboards we should look to remove the - * required DashboardsToolbar (as we will be providing our own) - */ - -function PersesBoard() { - const { t } = useTranslation(process.env.I18N_NAMESPACE); - - return ( - - ); -} - -export default PersesBoard; diff --git a/web/src/components/dashboards/perses/perses/datasource-api.ts b/web/src/components/dashboards/perses/perses/datasource-cache-api.ts similarity index 97% rename from web/src/components/dashboards/perses/perses/datasource-api.ts rename to web/src/components/dashboards/perses/perses/datasource-cache-api.ts index 5f7fff949..d0cf2110a 100644 --- a/web/src/components/dashboards/perses/perses/datasource-api.ts +++ b/web/src/components/dashboards/perses/perses/datasource-cache-api.ts @@ -12,8 +12,13 @@ // limitations under the License. import { getCSRFToken } from '@openshift-console/dynamic-plugin-sdk/lib/utils/fetch/console-fetch-utils'; -import { DatasourceResource, DatasourceSelector, GlobalDatasourceResource } from '@perses-dev/core'; -import { BuildDatasourceProxyUrlFunc, DatasourceApi } from '@perses-dev/dashboards'; +import { + DatasourceResource, + DatasourceSelector, + GlobalDatasourceResource, + BuildDatasourceProxyUrlFunc, + DatasourceApi, +} from '@perses-dev/core'; import LRUCache from 'lru-cache'; class Cache { diff --git a/web/src/components/dashboards/perses/persesPluginsLoader.tsx b/web/src/components/dashboards/perses/persesPluginsLoader.tsx deleted file mode 100644 index 2ede40b40..000000000 --- a/web/src/components/dashboards/perses/persesPluginsLoader.tsx +++ /dev/null @@ -1,110 +0,0 @@ -import { dynamicImportPluginLoader } from '@perses-dev/plugin-system'; - -import * as barchartPlugin from '@perses-dev/bar-chart-plugin'; -import * as datasourceVariablePlugin from '@perses-dev/datasource-variable-plugin'; -import * as flameChartPlugin from '@perses-dev/flame-chart-plugin'; -import * as gaugeChartPlugin from '@perses-dev/gauge-chart-plugin'; -import * as heatmapChartPlugin from '@perses-dev/heatmap-chart-plugin'; -import * as histogramChartPlugin from '@perses-dev/histogram-chart-plugin'; -import * as lokiPlugin from '@perses-dev/loki-plugin'; -import * as markdownPlugin from '@perses-dev/markdown-plugin'; -import * as pieChartPlugin from '@perses-dev/pie-chart-plugin'; -import * as prometheusPlugin from '@perses-dev/prometheus-plugin'; -import * as pyroscopePlugin from '@perses-dev/pyroscope-plugin'; -import * as scatterChartPlugin from '@perses-dev/scatter-chart-plugin'; -import * as statChartPlugin from '@perses-dev/stat-chart-plugin'; -import * as staticListVariablePlugin from '@perses-dev/static-list-variable-plugin'; -import * as statusHistoryChartPlugin from '@perses-dev/status-history-chart-plugin'; -import * as tablePlugin from '@perses-dev/table-plugin'; -import * as tempoPlugin from '@perses-dev/tempo-plugin'; -import * as timeseriesChartPlugin from '@perses-dev/timeseries-chart-plugin'; -import * as timeSeriesTablePlugin from '@perses-dev/timeseries-table-plugin'; -import * as traceTablePlugin from '@perses-dev/trace-table-plugin'; -import * as tracingGanttChartPlugin from '@perses-dev/tracing-gantt-chart-plugin'; - -export const pluginLoader = dynamicImportPluginLoader([ - { - resource: barchartPlugin.getPluginModule(), - importPlugin: () => Promise.resolve(barchartPlugin), - }, - { - resource: datasourceVariablePlugin.getPluginModule(), - importPlugin: () => Promise.resolve(datasourceVariablePlugin), - }, - { - resource: flameChartPlugin.getPluginModule(), - importPlugin: () => Promise.resolve(flameChartPlugin), - }, - { - resource: gaugeChartPlugin.getPluginModule(), - importPlugin: () => Promise.resolve(gaugeChartPlugin), - }, - { - resource: heatmapChartPlugin.getPluginModule(), - importPlugin: () => Promise.resolve(heatmapChartPlugin), - }, - { - resource: histogramChartPlugin.getPluginModule(), - importPlugin: () => Promise.resolve(histogramChartPlugin), - }, - { - resource: lokiPlugin.getPluginModule(), - importPlugin: () => Promise.resolve(lokiPlugin), - }, - { - resource: markdownPlugin.getPluginModule(), - importPlugin: () => Promise.resolve(markdownPlugin), - }, - { - resource: pieChartPlugin.getPluginModule(), - importPlugin: () => Promise.resolve(pieChartPlugin), - }, - { - resource: prometheusPlugin.getPluginModule(), - importPlugin: () => Promise.resolve(prometheusPlugin), - }, - { - resource: pyroscopePlugin.getPluginModule(), - importPlugin: () => Promise.resolve(pyroscopePlugin), - }, - { - resource: scatterChartPlugin.getPluginModule(), - importPlugin: () => Promise.resolve(scatterChartPlugin), - }, - { - resource: statChartPlugin.getPluginModule(), - importPlugin: () => Promise.resolve(statChartPlugin), - }, - { - resource: staticListVariablePlugin.getPluginModule(), - importPlugin: () => Promise.resolve(staticListVariablePlugin), - }, - { - resource: statusHistoryChartPlugin.getPluginModule(), - importPlugin: () => Promise.resolve(statusHistoryChartPlugin), - }, - { - resource: tablePlugin.getPluginModule(), - importPlugin: () => Promise.resolve(tablePlugin), - }, - { - resource: tempoPlugin.getPluginModule(), - importPlugin: () => Promise.resolve(tempoPlugin), - }, - { - resource: timeseriesChartPlugin.getPluginModule(), - importPlugin: () => Promise.resolve(timeseriesChartPlugin), - }, - { - resource: timeSeriesTablePlugin.getPluginModule(), - importPlugin: () => Promise.resolve(timeSeriesTablePlugin), - }, - { - resource: traceTablePlugin.getPluginModule(), - importPlugin: () => Promise.resolve(traceTablePlugin), - }, - { - resource: tracingGanttChartPlugin.getPluginModule(), - importPlugin: () => Promise.resolve(tracingGanttChartPlugin), - }, -]); diff --git a/web/src/components/dashboards/perses/project/ProjectBar.tsx b/web/src/components/dashboards/perses/project/ProjectBar.tsx index 1e0465979..1d785a7db 100644 --- a/web/src/components/dashboards/perses/project/ProjectBar.tsx +++ b/web/src/components/dashboards/perses/project/ProjectBar.tsx @@ -1,21 +1,34 @@ import type { SetStateAction, Dispatch, FC } from 'react'; +import { useNavigate } from 'react-router-dom-v5-compat'; import { KEYBOARD_SHORTCUTS } from './utils'; +import { getDashboardsListUrl, usePerspective } from '../../../hooks/usePerspective'; import ProjectDropdown from './ProjectDropdown'; export type ProjectBarProps = { - setActiveProject: Dispatch>; - activeProject: string; + setActiveProject: Dispatch>; + activeProject: string | null; }; export const ProjectBar: FC = ({ setActiveProject, activeProject }) => { + const navigate = useNavigate(); + const { perspective } = usePerspective(); + return (
{ - setActiveProject(newProject); + const params = new URLSearchParams(); + if (newProject === '') { + setActiveProject(null); + } else { + params.set('project', newProject); + setActiveProject(newProject); + } + const url = `${getDashboardsListUrl(perspective)}?${params.toString()}`; + navigate(url); }} - selected={activeProject} + selected={activeProject || ''} shortCut={KEYBOARD_SHORTCUTS.focusNamespaceDropdown} />
diff --git a/web/src/components/dashboards/perses/project/ProjectDropdown.tsx b/web/src/components/dashboards/perses/project/ProjectDropdown.tsx index 490bc2913..d79bc0fe3 100644 --- a/web/src/components/dashboards/perses/project/ProjectDropdown.tsx +++ b/web/src/components/dashboards/perses/project/ProjectDropdown.tsx @@ -116,13 +116,15 @@ const ProjectMenu: React.FC<{ const optionItems = useMemo(() => { const items = persesProjects.map((item) => { const { name } = item.metadata; - return { title: item?.spec?.display?.name ?? name, key: name }; + const title = item?.spec?.display?.name ?? name ?? ''; + return { title, key: name ?? '' }; }); - if (!items.some((option) => option.key === selected)) { + if (selected && !items.some((option) => option.key === selected)) { items.push({ title: selected, key: selected }); // Add current project if it isn't included } items.sort((a, b) => alphanumericCompare(a.title, b.title)); + items.unshift({ title: 'All Projects', key: '' }); return items; }, [persesProjects, selected]); @@ -207,7 +209,7 @@ const ProjectDropdown: React.FC = ({ const selectedProject = persesProjects.find( (persesProject) => persesProject.metadata.name === selected, ); - const title = selectedProject?.spec?.display?.name ?? t('Dashboards'); + const title = selectedProject?.spec?.display?.name ?? t('All Projects'); return (
diff --git a/web/src/components/dashboards/perses/project/useActiveProject.tsx b/web/src/components/dashboards/perses/project/useActiveProject.tsx index ddaf13cc3..5643ada83 100644 --- a/web/src/components/dashboards/perses/project/useActiveProject.tsx +++ b/web/src/components/dashboards/perses/project/useActiveProject.tsx @@ -11,7 +11,7 @@ import { QueryParams } from '../../../query-params'; import { StringParam, useQueryParam } from 'use-query-params'; export const useActiveProject = () => { - const [activeProject, setActiveProject] = useState(''); + const [activeProject, setActiveProject] = useState(null); const [activeNamespace, setActiveNamespace] = useActiveNamespace(); const { perspective } = usePerspective(); const { persesProjects, persesProjectsLoading } = usePerses(); @@ -24,7 +24,6 @@ export const useActiveProject = () => { // Sync the state and the URL param useEffect(() => { - // If data and url hasn't been set yet, default to legacy dashboard (for now) if (!activeProject && projectFromUrl) { setActiveProject(projectFromUrl); return; @@ -32,14 +31,10 @@ export const useActiveProject = () => { if (persesProjectsLoading) { return; } - if (!activeProject && !projectFromUrl) { - // set to first project - setActiveProject(persesProjects[0]?.metadata?.name); - return; - // If activeProject isn't set yet, but the url is, then load from url - } // If the url and the data is out of sync, follow the data - setProject(activeProject); + if (activeProject) { + setProject(activeProject); + } }, [ projectFromUrl, activeProject, diff --git a/web/src/components/dashboards/perses/project/utils.ts b/web/src/components/dashboards/perses/project/utils.ts index 16e3817ce..4a0352ea7 100644 --- a/web/src/components/dashboards/perses/project/utils.ts +++ b/web/src/components/dashboards/perses/project/utils.ts @@ -1,5 +1,8 @@ export const alphanumericCompare = (a: string, b: string): number => { - return a.localeCompare(b, undefined, { + const safeA = a || ''; + const safeB = b || ''; + + return safeA.localeCompare(safeB, undefined, { numeric: true, sensitivity: 'base', }); diff --git a/web/src/components/dashboards/shared/dashboard-dropdown.tsx b/web/src/components/dashboards/shared/dashboard-dropdown.tsx index bbaa5e402..41f51bf92 100644 --- a/web/src/components/dashboards/shared/dashboard-dropdown.tsx +++ b/web/src/components/dashboards/shared/dashboard-dropdown.tsx @@ -68,7 +68,7 @@ export const DashboardDropdown: FC = ({ items, onChange, }, [items, selectedKey, onChange]); return ( - + diff --git a/web/src/components/data-test.ts b/web/src/components/data-test.ts index 9a1ee6d90..f12105b90 100644 --- a/web/src/components/data-test.ts +++ b/web/src/components/data-test.ts @@ -284,6 +284,7 @@ export const persesMUIDataTestIDs = { }; export const persesDashboardDataTestIDs = { + createDashboardButtonToolbar: 'create-dashboard-button-list-page', editDashboardButtonToolbar: 'edit-dashboard-button-toolbar', cancelButtonToolbar: 'cancel-button-toolbar', }; diff --git a/web/src/components/hooks/usePerspective.tsx b/web/src/components/hooks/usePerspective.tsx index c090bb72c..b2794782d 100644 --- a/web/src/components/hooks/usePerspective.tsx +++ b/web/src/components/hooks/usePerspective.tsx @@ -13,7 +13,7 @@ import { } from '../utils'; import { GraphUnits } from '../metrics/units'; import { QueryParams } from '../query-params'; -import { MonitoringState } from 'src/store/store'; +import { MonitoringState } from '../../store/store'; export type UrlRoot = 'monitoring' | 'dev-monitoring' | 'multicloud/monitoring' | 'virt-monitoring'; @@ -238,7 +238,20 @@ export const getLegacyDashboardsUrl = (perspective: Perspective, boardName: stri } }; -export const getDashboardsUrl = (perspective: Perspective) => { +export const getDashboardUrl = (perspective: Perspective) => { + switch (perspective) { + case 'virtualization-perspective': + return `/virt-monitoring/v2/dashboards/view`; + case 'admin': + return `/monitoring/v2/dashboards/view`; + case 'acm': + return `/multicloud/monitoring/v2/dashboards/view`; + default: + return ''; + } +}; + +export const getDashboardsListUrl = (perspective: Perspective) => { switch (perspective) { case 'virtualization-perspective': return `/virt-monitoring/v2/dashboards`; diff --git a/web/src/components/metrics/promql-expression-input.tsx b/web/src/components/metrics/promql-expression-input.tsx index 8d1470399..4f2900464 100644 --- a/web/src/components/metrics/promql-expression-input.tsx +++ b/web/src/components/metrics/promql-expression-input.tsx @@ -9,28 +9,28 @@ import { } from '@codemirror/autocomplete'; import { defaultKeymap, - historyKeymap, history, + historyKeymap, insertNewlineAndIndent, } from '@codemirror/commands'; import { - indentOnInput, - HighlightStyle, bracketMatching, + HighlightStyle, + indentOnInput, syntaxHighlighting, } from '@codemirror/language'; -import { tags } from '@lezer/highlight'; import { lintKeymap } from '@codemirror/lint'; import { highlightSelectionMatches } from '@codemirror/search'; import { EditorState, Prec } from '@codemirror/state'; import { + placeholder as codeMirrorPlaceholder, EditorView, highlightSpecialChars, keymap, - placeholder as codeMirrorPlaceholder, ViewPlugin, ViewUpdate, } from '@codemirror/view'; +import { tags } from '@lezer/highlight'; import { PrometheusEndpoint, useActiveNamespace } from '@openshift-console/dynamic-plugin-sdk'; import { Button, @@ -46,31 +46,30 @@ import { import { CloseIcon, ExclamationCircleIcon } from '@patternfly/react-icons'; import { PromQLExtension } from '@prometheus-io/codemirror-promql'; import type { FC } from 'react'; -import { useRef, useState, useCallback, useEffect, useMemo } from 'react'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useSafeFetch } from '../console/utils/safe-fetch-hook'; -import { getPrometheusBasePath, PROMETHEUS_BASE_PATH } from '../utils'; -import { LabelNamesResponse } from '@perses-dev/prometheus-plugin'; import { + t_global_color_brand_default, + t_global_color_nonstatus_purple_default, + t_global_color_nonstatus_yellow_default, t_global_color_status_custom_default, t_global_color_status_danger_default, t_global_color_status_success_default, t_global_color_status_warning_default, + t_global_font_family_mono, + t_global_font_size_sm, t_global_font_weight_body_bold, + t_global_spacer_xs, t_global_text_color_disabled, t_global_text_color_regular, - t_global_spacer_xs, t_global_text_color_subtle, - t_global_font_family_mono, - t_global_font_size_sm, - t_global_color_brand_default, - t_global_color_nonstatus_yellow_default, - t_global_color_nonstatus_purple_default, } from '@patternfly/react-tokens'; -import { usePatternFlyTheme } from '../hooks/usePatternflyTheme'; import { useMonitoring } from '../../hooks/useMonitoring'; +import { usePatternFlyTheme } from '../hooks/usePatternflyTheme'; +import { getPrometheusBasePath, PROMETHEUS_BASE_PATH } from '../utils'; const box_shadow = ` var(--pf-t--global--box-shadow--X--md--default) @@ -327,6 +326,8 @@ export const PromQLExpressionInput: FC = ({ onValueChange, onSelectionChange, }) => { + type LabelNamesResponse = { data?: string[] }; + const { t } = useTranslation(process.env.I18N_NAMESPACE); const [namespace] = useActiveNamespace(); const { prometheus, accessCheckLoading, useMetricsTenancy } = useMonitoring(); diff --git a/web/src/index.d.ts b/web/src/index.d.ts index 953c07ef5..d77b674a9 100644 --- a/web/src/index.d.ts +++ b/web/src/index.d.ts @@ -10,6 +10,17 @@ declare interface Window { prometheusBaseURL: string; prometheusTenancyBaseURL: string; }; + /** + * Perses app configuration made available globally for plugin compatibility + */ + PERSES_APP_CONFIG: { + api_prefix: string; + }; + /** + * Plugin assets path used by module federation for loading plugin assets + * Set to the same value as the proxy base URL + */ + PERSES_PLUGIN_ASSETS_PATH: string; } // TODO: Remove when upgrading to TypeScript 4.1.2+, which has a type for ListFormat and diff --git a/web/src/perses-config.ts b/web/src/perses-config.ts new file mode 100644 index 000000000..276590f56 --- /dev/null +++ b/web/src/perses-config.ts @@ -0,0 +1,20 @@ +/** + * Perses Plugin Configuration + * + * This module configures global variables needed for Perses plugins to load assets + * through the OpenShift Console monitoring plugin proxy. The proxy path is injected + * at build time via webpack DefinePlugin. + */ + +// Build-time injected proxy URL for Perses plugins +declare const PERSES_PROXY_BASE_URL: string; + +// Configuration object for Perses app compatibility +const PERSES_APP_CONFIG = { + api_prefix: PERSES_PROXY_BASE_URL, +}; + +// Set up window globals for plugin system compatibility +// These are needed for plugins that use getPublicPath() in their Module Federation configs +window.PERSES_APP_CONFIG = PERSES_APP_CONFIG; +window.PERSES_PLUGIN_ASSETS_PATH = PERSES_PROXY_BASE_URL; diff --git a/web/webpack.config.ts b/web/webpack.config.ts index ea6659ca1..7515729c8 100644 --- a/web/webpack.config.ts +++ b/web/webpack.config.ts @@ -88,6 +88,8 @@ const config: Configuration = { patterns: [{ from: path.resolve(__dirname, 'locales'), to: 'locales' }], }), new DefinePlugin({ + // Build-time injection of proxy path for config module + PERSES_PROXY_BASE_URL: JSON.stringify('/api/proxy/plugin/monitoring-console-plugin/perses'), 'process.env.I18N_NAMESPACE': process.env.I18N_NAMESPACE ? JSON.stringify(process.env.I18N_NAMESPACE) : JSON.stringify('plugin__monitoring-plugin'),