diff --git a/package-lock.json b/package-lock.json index 20110d47..fcb84f92 100644 --- a/package-lock.json +++ b/package-lock.json @@ -59,6 +59,7 @@ "@eslint/js": "9.37.0", "@graphql-codegen/cli": "6.0.0", "@graphql-codegen/client-preset": "5.1.0", + "@testing-library/react": "16.3.0", "@types/dagre": "0.7.53", "@types/js-yaml": "4.0.9", "@types/node": "22.18.8", @@ -79,6 +80,7 @@ "eslint-plugin-react-hooks": "5.2.0", "fastify-tsconfig": "3.0.0", "globals": "16.4.0", + "jsdom": "27.0.0", "prettier": "3.6.2", "tsx": "4.20.6", "typescript": "5.9.3", @@ -159,6 +161,61 @@ "graphql": "*" } }, + "node_modules/@asamuzakjp/css-color": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.0.5.tgz", + "integrity": "sha512-lMrXidNhPGsDjytDy11Vwlb6OIGrT3CmLg3VWNFyWkLWtijKl7xjvForlh8vuj0SHGjgl4qZEQzUmYTeQA2JFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.4", + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "lru-cache": "^11.2.1" + } + }, + "node_modules/@asamuzakjp/css-color/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==", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.6.1.tgz", + "integrity": "sha512-8QT9pokVe1fUt1C8IrJketaeFOdRfTOS96DL3EBjE8CRZm3eHnwMlQe2NPoOSEYPwJ5Q25uYoX1+m9044l3ysQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.1.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.2.2" + } + }, + "node_modules/@asamuzakjp/dom-selector/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==", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@babel/code-frame": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", @@ -187,7 +244,6 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz", "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", @@ -451,6 +507,144 @@ "node": ">=6.9.0" } }, + "node_modules/@csstools/color-helpers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", + "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", + "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.1.0", + "@csstools/css-calc": "^2.1.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.14.tgz", + "integrity": "sha512-zSlIxa20WvMojjpCSy8WrNpcZ61RqfTfX3XTaOeVlGJrt/8HF3YbzgFZa01yTbT4GWQLwfTcC3EB8i3XnB647Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@cypress/request": { "version": "3.0.9", "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.9.tgz", @@ -1012,6 +1206,7 @@ "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@eslint/object-schema": "^2.1.6", "debug": "^4.3.1", @@ -1027,6 +1222,7 @@ "integrity": "sha512-+GPzk8PlG0sPpzdU5ZvIRMPidzAnZDl/s9L+y13iodqvb8leL53bTannOrQ/Im7UkpsmFU5Ily5U60LWixnmLg==", "dev": true, "license": "Apache-2.0", + "peer": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } @@ -1037,6 +1233,7 @@ "integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@types/json-schema": "^7.0.15" }, @@ -1100,6 +1297,7 @@ "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", "dev": true, "license": "Apache-2.0", + "peer": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } @@ -1110,6 +1308,7 @@ "integrity": "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@eslint/core": "^0.13.0", "levn": "^0.4.1" @@ -2429,6 +2628,7 @@ "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", "dev": true, "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=18.18.0" } @@ -2439,6 +2639,7 @@ "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.3.0" @@ -2453,6 +2654,7 @@ "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", "dev": true, "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=18.18" }, @@ -2467,6 +2669,7 @@ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=12.22" }, @@ -2481,6 +2684,7 @@ "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=18.18" }, @@ -3018,6 +3222,7 @@ "integrity": "sha512-rmOWVRUbUJD7iSvJugjUbFZshTAuJ48MXoZ80Osx1GM0K/H1w7rSEvmw8m6vdWxNASgtaHIhAgre4H/E9GJiYQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "content-type": "^1.0.5", "cors": "^2.8.5", @@ -3051,6 +3256,7 @@ "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", "dev": true, "license": "ISC", + "peer": true, "peerDependencies": { "zod": "^3.24.1" } @@ -3131,7 +3337,6 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=8.0.0" } @@ -3153,7 +3358,6 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.1.0.tgz", "integrity": "sha512-zOyetmZppnwTyPrt4S7jMfXiSX9yyfF0hxlA8B5oo2TtKl+/RGCy7fi4DrBfIf3lCPrkKsRBWZZD7RFojK7FDg==", "license": "Apache-2.0", - "peer": true, "engines": { "node": "^18.19.0 || >=20.6.0" }, @@ -3166,7 +3370,6 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.1.0.tgz", "integrity": "sha512-RMEtHsxJs/GiHHxYT58IY57UXAQTuUnZVco6ymDEqTNlJKTimM4qPUPVe8InNFyBjhHBEAx4k3Q8LtNayBsbUQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, @@ -4236,7 +4439,6 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.1.0.tgz", "integrity": "sha512-1CJjf3LCvoefUOgegxi8h6r4B/wLSzInyhGP2UmIBYNlo4Qk5CZ73e1eEyWmfXvFtm1ybkmfb2DqWvspsYLrWw==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@opentelemetry/core": "2.1.0", "@opentelemetry/semantic-conventions": "^1.29.0" @@ -4253,7 +4455,6 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.1.0.tgz", "integrity": "sha512-uTX9FBlVQm4S2gVQO1sb5qyBLq/FPjbp+tmGoxu4tIgtYGmBYB44+KX/725RFDe30yBSaA9Ml9fqphe1hbUyLQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@opentelemetry/core": "2.1.0", "@opentelemetry/resources": "2.1.0", @@ -4271,7 +4472,6 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.37.0.tgz", "integrity": "sha512-JD6DerIKdJGmRp4jQyX5FlrQjA4tjOw1cvfsPAZXfOOEErMUHjPcPSICS+6WnM0nB0efSFARh0KAZss+bvExOA==", "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=14" } @@ -5224,6 +5424,66 @@ "url": "https://github.com/sponsors/tannerlinsley" } }, + "node_modules/@testing-library/dom": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/dom/node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/@testing-library/react": { + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.0.tgz", + "integrity": "sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@tybys/wasm-util": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", @@ -5235,6 +5495,14 @@ "tslib": "^2.4.0" } }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -5437,7 +5705,8 @@ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/json5": { "version": "0.0.29", @@ -5506,7 +5775,6 @@ "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -5517,7 +5785,6 @@ "integrity": "sha512-/EEvYBdT3BflCWvTMO7YkYBHVE9Ci6XdqZciZANQgKpaiDRGOLIlRo91jbTNRQjgPFWVaRxcYc0luVNFitz57A==", "devOptional": true, "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -5629,7 +5896,6 @@ "integrity": "sha512-n1H6IcDhmmUEG7TNVSspGmiHHutt7iVKtZwRppD7e04wha5MrkV1h3pti9xQLcCMt6YWsncpoT0HMjkH1FNwWQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.46.0", "@typescript-eslint/types": "8.46.0", @@ -5873,7 +6139,6 @@ "resolved": "https://registry.npmjs.org/@ui5/webcomponents/-/webcomponents-2.15.0.tgz", "integrity": "sha512-wdxX3bXqrwMictoc/vOQ1MCLt2WtZal53J3iYDxJRn/xY5MZR2If1h2FNNzBSD8oePlVs8/hAFJLOI1inwu2Fw==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@ui5/webcomponents-base": "2.15.0", "@ui5/webcomponents-icons": "2.15.0", @@ -5919,7 +6184,6 @@ "resolved": "https://registry.npmjs.org/@ui5/webcomponents-fiori/-/webcomponents-fiori-2.15.0.tgz", "integrity": "sha512-W7vRJsJXDrA9FvjOiAN+DDyOtUoFou5HTNLo32v3smJnnBPB5Mond903l6idsVLlju5eKnb2gL8XECLuB7Xtag==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@ui5/webcomponents": "2.15.0", "@ui5/webcomponents-base": "2.15.0", @@ -6009,7 +6273,6 @@ "resolved": "https://registry.npmjs.org/@ui5/webcomponents-react-base/-/webcomponents-react-base-2.15.0.tgz", "integrity": "sha512-gkvxkAetcE2ixpLSp/LDOEru27fuVzJP9KFT8KARWqfteASDgq0vukmNHr/NedO4qFoqJkpm7+D1wDHJgN9YHw==", "license": "Apache-2.0", - "peer": true, "peerDependencies": { "@types/react": "*", "@ui5/webcomponents-base": "~2.15.0", @@ -6631,6 +6894,7 @@ "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" @@ -6645,6 +6909,7 @@ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.6" } @@ -6655,6 +6920,7 @@ "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "mime-db": "^1.54.0" }, @@ -6667,7 +6933,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -6694,6 +6959,16 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", @@ -7278,7 +7553,6 @@ "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.1.6.tgz", "integrity": "sha512-FgjDeR+/yDH34By4I0qB5NxAoWv7dOTYcOXwn73kr+c93HyC2lU6tnjifqUe33LKMJcDyCYPQjEAqgOQiXkE2Q==", "license": "Apache-2.0", - "peer": true, "dependencies": { "bare-path": "^3.0.0" } @@ -7314,6 +7588,16 @@ "tweetnacl": "^0.14.3" } }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -7346,6 +7630,7 @@ "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", @@ -7367,6 +7652,7 @@ "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.8" } @@ -7377,6 +7663,7 @@ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.6" } @@ -7387,6 +7674,7 @@ "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "mime-db": "^1.54.0" }, @@ -7400,6 +7688,7 @@ "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", @@ -7451,7 +7740,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001716", "electron-to-chromium": "^1.5.149", @@ -7516,6 +7804,7 @@ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.8" } @@ -8247,6 +8536,7 @@ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.6" } @@ -8272,6 +8562,7 @@ "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=6.6.0" } @@ -8289,6 +8580,7 @@ "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "object-assign": "^4", "vary": "^1" @@ -8359,6 +8651,35 @@ "node": ">= 8" } }, + "node_modules/css-tree": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", + "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.12.2", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/cssstyle": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.1.tgz", + "integrity": "sha512-g5PC9Aiph9eiczFpcgUhd9S4UUO3F+LHGRIi5NUMZ+4xtoIYbHNZwZnWA2JsFGe8OU8nl4WyaEFiZuGuxlutJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^4.0.3", + "@csstools/css-syntax-patches-for-csstree": "^1.0.14", + "css-tree": "^3.1.0" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", @@ -8372,7 +8693,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "dependencies": { "@cypress/request": "^3.0.9", "@cypress/xvfb": "^1.2.4", @@ -8619,7 +8939,6 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", - "peer": true, "engines": { "node": ">=12" } @@ -8743,33 +9062,84 @@ "node": ">= 12" } }, - "node_modules/data-view-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", - "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "node_modules/data-urls": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-6.0.0.tgz", + "integrity": "sha512-BnBS08aLUM+DKamupXs3w2tJJoqU+AkaE/+6vQxi/G/DPmIZFJJp9Dkb1kM03AZx8ADehDUZgsNxju3mPXZYIA==", "dev": true, "license": "MIT", "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^15.0.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=20" } }, - "node_modules/data-view-byte-length": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", - "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "node_modules/data-urls/node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", "dev": true, "license": "MIT", "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/data-urls/node_modules/webidl-conversions": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.0.tgz", + "integrity": "sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, + "node_modules/data-urls/node_modules/whatwg-url": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-15.1.0.tgz", + "integrity": "sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", "is-data-view": "^1.0.2" }, "engines": { @@ -8849,6 +9219,13 @@ "node": ">=0.10.0" } }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, "node_modules/decimal.js-light": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", @@ -8870,7 +9247,8 @@ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/define-data-property": { "version": "1.1.4", @@ -8990,6 +9368,14 @@ "node": ">=0.10.0" } }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/dom-helpers": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", @@ -9084,7 +9470,8 @@ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/electron-to-chromium": { "version": "1.5.155", @@ -9104,6 +9491,7 @@ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.8" } @@ -9123,7 +9511,6 @@ "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ansi-colors": "^4.1.1", "strip-ansi": "^6.0.1" @@ -9132,6 +9519,19 @@ "node": ">=8.6" } }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/env-paths": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", @@ -9464,6 +9864,7 @@ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10" }, @@ -9541,7 +9942,6 @@ "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, "license": "MIT", - "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -9702,7 +10102,6 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -9897,6 +10296,7 @@ "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -9927,6 +10327,7 @@ "integrity": "sha512-I9XlJawFdSMvWjDt6wksMCrgns5ggLNfFwFvnShsleWruvXM514Qxk8V246efTw+eo9JABvVz+u3q2RiAowKxQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } @@ -9937,6 +10338,7 @@ "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", "dev": true, "license": "Apache-2.0", + "peer": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -9950,6 +10352,7 @@ "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "dev": true, "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } @@ -9991,6 +10394,7 @@ "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "dev": true, "license": "BSD-3-Clause", + "peer": true, "dependencies": { "estraverse": "^5.1.0" }, @@ -10004,6 +10408,7 @@ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "estraverse": "^5.2.0" }, @@ -10047,6 +10452,7 @@ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.6" } @@ -10070,6 +10476,7 @@ "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "eventsource-parser": "^3.0.1" }, @@ -10083,6 +10490,7 @@ "integrity": "sha512-6RxOBZ/cYgd8usLwsEl+EC09Au/9BcmCKYF2/xbml6DNczf7nv0MQb+7BA2F+li6//I+28VNlQR37XfQtcAJuA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=18.0.0" } @@ -10140,6 +10548,7 @@ "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", @@ -10183,6 +10592,7 @@ "integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 16" }, @@ -10199,6 +10609,7 @@ "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "safe-buffer": "5.2.1" }, @@ -10212,6 +10623,7 @@ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.6" } @@ -10222,6 +10634,7 @@ "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.8" } @@ -10232,6 +10645,7 @@ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.6" } @@ -10242,6 +10656,7 @@ "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "mime-db": "^1.54.0" }, @@ -10255,6 +10670,7 @@ "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", @@ -10432,7 +10848,8 @@ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/fast-querystring": { "version": "1.1.2", @@ -10483,7 +10900,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "@fastify/ajv-compiler": "^4.0.0", "@fastify/error": "^4.0.0", @@ -10657,6 +11073,7 @@ "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "flat-cache": "^4.0.0" }, @@ -10682,6 +11099,7 @@ "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", @@ -10758,6 +11176,7 @@ "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" @@ -10771,7 +11190,8 @@ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", "dev": true, - "license": "ISC" + "license": "ISC", + "peer": true }, "node_modules/for-each": { "version": "0.3.5", @@ -10877,6 +11297,7 @@ "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.8" } @@ -11111,6 +11532,7 @@ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, "license": "ISC", + "peer": true, "dependencies": { "is-glob": "^4.0.3" }, @@ -11248,7 +11670,6 @@ "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.11.0.tgz", "integrity": "sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw==", "license": "MIT", - "peer": true, "engines": { "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" } @@ -11328,7 +11749,6 @@ "resolved": "https://registry.npmjs.org/graphql-ws/-/graphql-ws-6.0.5.tgz", "integrity": "sha512-HzYw057ch0hx2gZjkbgk1pur4kAtgljlWRP+Gccudqm3BRrTpExjWCQ9OHdIsq47Y6lHL++1lTvuQHhgRRcevw==", "license": "MIT", - "peer": true, "engines": { "node": ">=20" }, @@ -11503,6 +11923,19 @@ "react-is": "^16.7.0" } }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/html-parse-stringify": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", @@ -11534,6 +11967,20 @@ "node": ">= 0.8" } }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/http-signature": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.4.0.tgz", @@ -11549,6 +11996,20 @@ "node": ">=0.10" } }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/human-signals": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", @@ -11578,7 +12039,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.27.6" }, @@ -11700,6 +12160,7 @@ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=0.8.19" } @@ -12116,12 +12577,20 @@ "node": ">=8" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, "node_modules/is-promise": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/is-regex": { "version": "1.2.1", @@ -12414,7 +12883,6 @@ "resolved": "https://registry.npmjs.org/javascript-time-ago/-/javascript-time-ago-2.5.12.tgz", "integrity": "sha512-s8PPq2HQ3HIbSU0SjhNvTitf5VoXbQWof9q6k3gIX7F2il0ptjD5lONTDccpuKt/2U7RjbCp/TCHPK7eDwO7zQ==", "license": "MIT", - "peer": true, "dependencies": { "relative-time-format": "^1.1.7" } @@ -12453,6 +12921,116 @@ "dev": true, "license": "MIT" }, + "node_modules/jsdom": { + "version": "27.0.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.0.0.tgz", + "integrity": "sha512-lIHeR1qlIRrIN5VMccd8tI2Sgw6ieYXSVktcSHaNe3Z5nE/tcPQYQWOq00wxMvYOsz+73eAkNenVvmPC6bba9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/dom-selector": "^6.5.4", + "cssstyle": "^5.3.0", + "data-urls": "^6.0.0", + "decimal.js": "^10.5.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "parse5": "^7.3.0", + "rrweb-cssom": "^0.8.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^15.0.0", + "ws": "^8.18.2", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/tldts": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.17.tgz", + "integrity": "sha512-Y1KQBgDd/NUc+LfOtKS6mNsC9CCaH+m2P1RoIZy7RAPo3C3/t8X45+zgut31cRZtZ3xKPjfn3TkGTrctC2TQIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^7.0.17" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/jsdom/node_modules/tldts-core": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.17.tgz", + "integrity": "sha512-DieYoGrP78PWKsrXr8MZwtQ7GLCUeLxihtjC1jZsW1DnvSMdKPitJSe8OSYDM2u5H6g3kWJZpePqkp43TfLh0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsdom/node_modules/tough-cookie": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz", + "integrity": "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/jsdom/node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/jsdom/node_modules/webidl-conversions": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.0.tgz", + "integrity": "sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, + "node_modules/jsdom/node_modules/whatwg-url": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-15.1.0.tgz", + "integrity": "sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.0" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -12470,7 +13048,8 @@ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", @@ -12516,7 +13095,8 @@ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/json-stringify-safe": { "version": "5.0.1", @@ -12607,6 +13187,7 @@ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "json-buffer": "3.0.1" } @@ -12646,6 +13227,7 @@ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -13026,7 +13608,8 @@ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lodash.once": { "version": "4.1.1", @@ -13150,6 +13733,17 @@ "yallist": "^3.0.2" } }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "lz-string": "bin/bin.js" + } + }, "node_modules/magic-string": { "version": "0.30.17", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", @@ -13192,6 +13786,13 @@ "node": ">= 0.4" } }, + "node_modules/mdn-data": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", + "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", + "dev": true, + "license": "CC0-1.0" + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -13213,6 +13814,7 @@ "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -13365,7 +13967,6 @@ "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.54.0.tgz", "integrity": "sha512-hx45SEUoLatgWxHKCmlLJH81xBo0uXP4sRkESUpmDQevfi+e7K1VuiSprK6UpQ8u4zOcKNiH0pMvHvlMWA/4cw==", "license": "MIT", - "peer": true, "dependencies": { "dompurify": "3.1.7", "marked": "14.0.0" @@ -13496,6 +14097,7 @@ "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.6" } @@ -13733,6 +14335,7 @@ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ee-first": "1.1.1" }, @@ -13783,6 +14386,7 @@ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", @@ -13952,12 +14556,26 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.8" } @@ -14228,6 +14846,7 @@ "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=16.20.0" } @@ -14412,6 +15031,7 @@ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.8.0" } @@ -14421,7 +15041,6 @@ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -14458,6 +15077,44 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/pretty-format/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -14520,6 +15177,7 @@ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" @@ -14534,6 +15192,7 @@ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.10" } @@ -14623,6 +15282,7 @@ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.6" } @@ -14633,6 +15293,7 @@ "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -14648,7 +15309,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -14670,7 +15330,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -14695,7 +15354,6 @@ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.64.0.tgz", "integrity": "sha512-fnN+vvTiMLnRqKNTVhDysdrUay0kUUAymQnFIznmgDvapjveUWOOPqMNzPg+A+0yf9DuE2h6xzBjN1s+Qx8wcg==", "license": "MIT", - "peer": true, "engines": { "node": ">=18.0.0" }, @@ -15191,6 +15849,7 @@ "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", @@ -15202,6 +15861,13 @@ "node": ">= 18" } }, + "node_modules/rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "dev": true, + "license": "MIT" + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -15345,6 +16011,19 @@ "dev": true, "license": "MIT" }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/scheduler": { "version": "0.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", @@ -15382,6 +16061,7 @@ "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "debug": "^4.3.5", "encodeurl": "^2.0.0", @@ -15405,6 +16085,7 @@ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.6" } @@ -15415,6 +16096,7 @@ "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "mime-db": "^1.54.0" }, @@ -15440,6 +16122,7 @@ "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", @@ -16116,6 +16799,13 @@ "node": ">=0.10" } }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, "node_modules/sync-fetch": { "version": "0.6.0-2", "resolved": "https://registry.npmjs.org/sync-fetch/-/sync-fetch-0.6.0-2.tgz", @@ -16284,7 +16974,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -16500,7 +17189,6 @@ "integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "~0.25.0", "get-tsconfig": "^4.7.5" @@ -16541,6 +17229,7 @@ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "prelude-ls": "^1.2.1" }, @@ -16658,7 +17347,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -16789,6 +17477,7 @@ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.8" } @@ -16812,7 +17501,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "dependencies": { "napi-postinstall": "^0.2.2" }, @@ -16985,7 +17673,6 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.9.tgz", "integrity": "sha512-4nVGliEpxmhCL8DslSAUdxlB6+SMrhB0a1v5ijlh1xB1nEPuy1mxaHxysVucLHuWryAxLWg6a5ei+U4TLn/rFg==", "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -17133,7 +17820,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -17147,7 +17833,6 @@ "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", @@ -17274,6 +17959,19 @@ "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", "license": "MIT" }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/web-streams-polyfill": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", @@ -17304,6 +18002,19 @@ "integrity": "sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw==", "license": "MIT" }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/whatwg-mimetype": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", @@ -17456,6 +18167,7 @@ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -17504,7 +18216,6 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=10.0.0" }, @@ -17521,6 +18232,23 @@ } } }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/package.json b/package.json index 7002dc8c..cc66475b 100644 --- a/package.json +++ b/package.json @@ -73,6 +73,7 @@ "@eslint/js": "9.37.0", "@graphql-codegen/cli": "6.0.0", "@graphql-codegen/client-preset": "5.1.0", + "@testing-library/react": "16.3.0", "@types/dagre": "0.7.53", "@types/js-yaml": "4.0.9", "@types/node": "22.18.8", @@ -93,6 +94,7 @@ "eslint-plugin-react-hooks": "5.2.0", "fastify-tsconfig": "3.0.0", "globals": "16.4.0", + "jsdom": "27.0.0", "prettier": "3.6.2", "tsx": "4.20.6", "typescript": "5.9.3", diff --git a/public/locales/en.json b/public/locales/en.json index 8f8c31bf..9142a1f4 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -16,6 +16,33 @@ "tableComponentHeader": "Name", "tableVersionHeader": "Version" }, + "ComponentCard": { + "notInstalledLabel": "not installed", + "installButton": "Install {{component}}" + }, + "componentCardCrossplane": { + "description": "Compose cloud infrastructure", + "progress": "Healthy", + "progressCount": "Healthy: {{count}} of {{total}} resources" + }, + "componentCardFlux": { + "description": "GitOps for Kubernetes automating continuous sync and delivery", + "progress": "Managed", + "progressCount": "Managed: {{count}} of {{total}} resources" + }, + "componentCardLandscaper": { + "description": "Automate cross‑dependent Kubernetes deployments" + }, + "componentCardKyverno": { + "description": "Kubernetes-native policy as code for secure and compliant infrastructure" + }, + "componentCardEso": { + "description": "Manage and sync credentials from your secret store" + }, + "Kpi": { + "installed": "Installed", + "error": "There was a problem loading this data. Please try again later." + }, "FluxList": { "tableNameHeader": "Name", "tableStatusHeader": "Status", @@ -438,7 +465,7 @@ "hoverContent": { "totalResources": "Total Resources", "healthy": "Healthy", - "creating": "Creating", + "creating": "Creating", "failing": "Failing" } }, diff --git a/src/assets/images/logo-crossplane.svg b/src/assets/images/logo-crossplane.svg new file mode 100644 index 00000000..f551dc4a --- /dev/null +++ b/src/assets/images/logo-crossplane.svg @@ -0,0 +1,37 @@ + + + icecream-icon + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/assets/images/logo-eso.svg b/src/assets/images/logo-eso.svg new file mode 100644 index 00000000..6a32df82 --- /dev/null +++ b/src/assets/images/logo-eso.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/logo-flux.svg b/src/assets/images/logo-flux.svg new file mode 100644 index 00000000..f37d6723 --- /dev/null +++ b/src/assets/images/logo-flux.svg @@ -0,0 +1 @@ +flux-iconCreated with Sketch. \ No newline at end of file diff --git a/src/assets/images/logo-kyverno.png b/src/assets/images/logo-kyverno.png new file mode 100644 index 00000000..ed7cc81a Binary files /dev/null and b/src/assets/images/logo-kyverno.png differ diff --git a/src/assets/images/logo-landscaper.svg b/src/assets/images/logo-landscaper.svg new file mode 100644 index 00000000..ee28eb22 --- /dev/null +++ b/src/assets/images/logo-landscaper.svg @@ -0,0 +1,59 @@ + + + Landscraper + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/assets/images/logo-vault.svg b/src/assets/images/logo-vault.svg new file mode 100644 index 00000000..c14413fc --- /dev/null +++ b/src/assets/images/logo-vault.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/logo-velero.svg b/src/assets/images/logo-velero.svg new file mode 100644 index 00000000..10838818 --- /dev/null +++ b/src/assets/images/logo-velero.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/lib/api/types/crate/controlPlanes.ts b/src/lib/api/types/crate/controlPlanes.ts index 47275638..1176614e 100644 --- a/src/lib/api/types/crate/controlPlanes.ts +++ b/src/lib/api/types/crate/controlPlanes.ts @@ -31,7 +31,7 @@ export interface ControlPlaneComponentsType { externalSecretsOperator: ControlPlaneComponentsSpecType | undefined; kyverno: ControlPlaneComponentsSpecType | undefined; flux: ControlPlaneComponentsSpecType | undefined; - landscaper: ControlPlaneComponentsSpecType | undefined; + landscaper: unknown; } export interface ControlPlaneComponentsSpecType { diff --git a/src/lib/api/useApiResource.ts b/src/lib/api/useApiResource.ts index 74c32e8b..ef4ec87e 100644 --- a/src/lib/api/useApiResource.ts +++ b/src/lib/api/useApiResource.ts @@ -33,7 +33,7 @@ export const useApiResource = ( return { data, - error: error as APIError, + error: error as APIError | undefined, isLoading: isLoading, isValidating: isValidating, }; diff --git a/src/lib/shared/types.ts b/src/lib/shared/types.ts index 9e853d9f..6ddcdab3 100644 --- a/src/lib/shared/types.ts +++ b/src/lib/shared/types.ts @@ -59,7 +59,7 @@ export type ManagedResourceItem = { name: string; creationTimestamp: string; resourceVersion: string; - labels: []; + labels: Record; namespace?: string; }; apiVersion?: string; diff --git a/src/spaces/mcp/components/ComponentCard/ComponentCard.cy.tsx b/src/spaces/mcp/components/ComponentCard/ComponentCard.cy.tsx new file mode 100644 index 00000000..b8708d15 --- /dev/null +++ b/src/spaces/mcp/components/ComponentCard/ComponentCard.cy.tsx @@ -0,0 +1,73 @@ +import { ComponentCard, ComponentCardProps } from './ComponentCard'; + +describe('ComponentCard', () => { + const mount = (props: ComponentCardProps) => { + cy.mount(, {}); + }; + + it('renders an installed component', () => { + const props: ComponentCardProps = { + name: 'COMPONENT NAME', + description: 'COMPONENT DESCRIPTION', + logoImgSrc: '/logo.png', + isInstalled: true, + version: '1.2.3', + onNavigateToComponentSection: cy.stub().as('onNavigate'), + onInstallButtonClick: cy.stub().as('onInstall'), + kpiType: 'enabled', + }; + + mount(props); + + // Card header + cy.contains('COMPONENT NAME').should('be.visible'); + cy.contains('COMPONENT DESCRIPTION').should('be.visible'); + cy.contains('v1.2.3').should('be.visible'); + cy.contains('not installed').should('not.exist'); + cy.get('img').should('have.attr', 'src', '/logo.png'); + + // Card content + cy.get('[data-cy="kpi-container"]').should('be.visible'); + cy.get('[data-cy="uninstalled-container"]').should('not.exist'); + + // Card is interactive and calls navigate on click + cy.get('ui5-card').click(); + cy.get('@onNavigate').should('have.been.calledOnce'); + cy.get('@onInstall').should('not.have.been.called'); + }); + + it('renders an uninstalled component', () => { + const props: ComponentCardProps = { + name: 'COMPONENT NAME', + description: 'COMPONENT DESCRIPTION', + logoImgSrc: '/logo.png', + isInstalled: false, + version: undefined, + onNavigateToComponentSection: cy.stub().as('onNavigate'), + onInstallButtonClick: cy.stub().as('onInstall'), + kpiType: 'enabled', + }; + + mount(props); + + // Card header + cy.contains('COMPONENT NAME').should('be.visible'); + cy.contains('COMPONENT DESCRIPTION').should('be.visible'); + cy.contains('not installed').should('be.visible'); + cy.get('img').should('have.attr', 'src', '/logo.png'); + + // Card content + cy.get('[data-cy="kpi-container"]').should('not.exist'); + cy.get('[data-cy="uninstalled-container"]').should('be.visible'); + + // Card is not interactive and does not call navigate on click + cy.get('ui5-card').click(); + cy.get('@onNavigate').should('not.have.been.called'); + cy.get('@onInstall').should('not.have.been.called'); + + // Install button is visible and interactive + cy.get('[data-cy="install-button"]').click(); + cy.get('@onNavigate').should('not.have.been.called'); + cy.get('@onInstall').should('have.been.called'); + }); +}); diff --git a/src/spaces/mcp/components/ComponentCard/ComponentCard.module.css b/src/spaces/mcp/components/ComponentCard/ComponentCard.module.css new file mode 100644 index 00000000..3a6ce8c5 --- /dev/null +++ b/src/spaces/mcp/components/ComponentCard/ComponentCard.module.css @@ -0,0 +1,28 @@ +.cardInteractive { + cursor: pointer; +} +.cardNoninteractive { +} + +.avatar { + border-radius: 0; +} + +.content { + height: 2.5rem; + padding: 1.5rem; + display: flex; +} + +.kpiContainer { + width: 100%; + display: flex; + flex-direction: column; + justify-content: center; +} + +.uninstalledContainer { + width: 100%; + display: flex; + align-items: center; +} diff --git a/src/spaces/mcp/components/ComponentCard/ComponentCard.tsx b/src/spaces/mcp/components/ComponentCard/ComponentCard.tsx new file mode 100644 index 00000000..e6e935e1 --- /dev/null +++ b/src/spaces/mcp/components/ComponentCard/ComponentCard.tsx @@ -0,0 +1,68 @@ +import { Button, Card, CardHeader } from '@ui5/webcomponents-react'; +import { Kpi, KpiProps } from '../Kpi/Kpi.tsx'; +import styles from './ComponentCard.module.css'; +import { useTranslation } from 'react-i18next'; +import { clsx } from 'clsx'; + +export type ComponentCardProps = KpiProps & { + name: string; + description: string; + logoImgSrc: string; + isInstalled: boolean; + version?: string; + onNavigateToComponentSection?: () => void; + onInstallButtonClick?: () => void; +}; + +export function ComponentCard({ + name, + description, + logoImgSrc, + isInstalled, + version, + onNavigateToComponentSection, + onInstallButtonClick, + ...props +}: ComponentCardProps) { + const { t } = useTranslation(); + + const canNavigateToComponentDetails = isInstalled && !!onNavigateToComponentSection; + const prefixedVersion = version ? `v${version}` : undefined; + + return ( + } + subtitleText={description} + interactive={canNavigateToComponentDetails} + additionalText={isInstalled ? prefixedVersion : t('ComponentCard.notInstalledLabel')} + /> + } + className={canNavigateToComponentDetails ? styles.cardInteractive : styles.cardNoninteractive} + onClick={canNavigateToComponentDetails ? onNavigateToComponentSection : undefined} + > +
+ {isInstalled ? ( +
+ +
+ ) : ( +
+ {onInstallButtonClick && ( + + )} +
+ )} +
+
+ ); +} diff --git a/src/spaces/mcp/components/ComponentsDashboard/ComponentsDashboard.cy.tsx b/src/spaces/mcp/components/ComponentsDashboard/ComponentsDashboard.cy.tsx new file mode 100644 index 00000000..4c926b70 --- /dev/null +++ b/src/spaces/mcp/components/ComponentsDashboard/ComponentsDashboard.cy.tsx @@ -0,0 +1,69 @@ +import { ComponentsDashboard, ComponentsDashboardProps } from './ComponentsDashboard.tsx'; + +describe('ComponentsDashboard', () => { + const mount = (props?: Partial) => { + const components = {} as unknown as ComponentsDashboardProps['components']; + + cy.mount( + {}} + onNavigateToMcpSection={() => {}} + {...props} + />, + {}, + ); + }; + + it('renders all component cards with names, descriptions, and versions', () => { + mount(); + + cy.get('.ui5-card-header').should('have.length', 5); + + cy.get('.ui5-card-header') + .eq(0) + .should('contain.text', 'Crossplane') + .and('contain.text', 'Compose cloud infrastructure'); + + cy.get('.ui5-card-header') + .eq(1) + .should('contain.text', 'Flux') + .and('contain.text', 'GitOps for Kubernetes automating continuous sync and delivery'); + + cy.get('.ui5-card-header') + .eq(2) + .should('contain.text', 'Landscaper') + .and('contain.text', 'Automate cross‑dependent Kubernetes deployments'); + + cy.get('.ui5-card-header') + .eq(3) + .should('contain.text', 'Kyverno') + .and('contain.text', 'Kubernetes-native policy as code for secure and compliant infrastructure'); + + cy.get('.ui5-card-header') + .eq(4) + .should('contain.text', 'External Secrets Operator') + .and('contain.text', 'Manage and sync credentials from your secret store'); + }); + + it('calls onInstallButtonClick when Install is clicked on each card', () => { + mount({ + onInstallButtonClick: cy.stub().as('onInstall'), + }); + + cy.contains('ui5-card', 'Crossplane').within(() => { + cy.contains('Install').click(); + }); + cy.contains('ui5-card', 'Flux').within(() => { + cy.contains('Install').click(); + }); + cy.contains('ui5-card', 'Kyverno').within(() => { + cy.contains('Install').click(); + }); + cy.contains('ui5-card', 'External Secrets Operator').within(() => { + cy.contains('Install').click(); + }); + + cy.get('@onInstall').should('have.callCount', 4); + }); +}); diff --git a/src/spaces/mcp/components/ComponentsDashboard/ComponentsDashboard.module.css b/src/spaces/mcp/components/ComponentsDashboard/ComponentsDashboard.module.css new file mode 100644 index 00000000..1d6a5bf0 --- /dev/null +++ b/src/spaces/mcp/components/ComponentsDashboard/ComponentsDashboard.module.css @@ -0,0 +1,6 @@ +.container { + padding-inline: 0.5rem; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(25rem, 1fr)); + gap: 1.5rem; +} diff --git a/src/spaces/mcp/components/ComponentsDashboard/ComponentsDashboard.tsx b/src/spaces/mcp/components/ComponentsDashboard/ComponentsDashboard.tsx new file mode 100644 index 00000000..de0a74ee --- /dev/null +++ b/src/spaces/mcp/components/ComponentsDashboard/ComponentsDashboard.tsx @@ -0,0 +1,110 @@ +import { ComponentCard } from '../ComponentCard/ComponentCard.tsx'; + +import LogoCrossplane from '../../../../assets/images/logo-crossplane.svg'; +import LogoFlux from '../../../../assets/images/logo-flux.svg'; +import LogoLandscaper from '../../../../assets/images/logo-landscaper.svg'; +import LogoKyverno from '../../../../assets/images/logo-kyverno.png'; +import LogoEso from '../../../../assets/images/logo-eso.svg'; +import { McpPageSectionId } from '../../pages/McpPage.tsx'; +import { ControlPlaneComponentsType } from '../../../../lib/api/types/crate/controlPlanes.ts'; +import { useKpiCrossplane } from '../Kpi/useKpiCrossplane.ts'; +import { useKpiFlux } from '../Kpi/useKpiFlux.ts'; +import { Panel } from '@ui5/webcomponents-react'; + +import styles from './ComponentsDashboard.module.css'; +import { useTranslation } from 'react-i18next'; + +export interface ComponentsDashboardProps { + components?: ControlPlaneComponentsType; + onInstallButtonClick: () => void; + onNavigateToMcpSection: (sectionId: McpPageSectionId) => void; +} + +export function ComponentsDashboard({ + components, + onInstallButtonClick, + onNavigateToMcpSection, +}: ComponentsDashboardProps) { + const { t } = useTranslation(); + const crossplaneKpi = useKpiCrossplane(); + const fluxKpi = useKpiFlux(); + + return ( + +
+ onNavigateToMcpSection('crossplane')} + onInstallButtonClick={onInstallButtonClick} + {...crossplaneKpi} + /> + onNavigateToMcpSection('flux')} + onInstallButtonClick={onInstallButtonClick} + {...fluxKpi} + /> + {/* not yet available + */} + onNavigateToMcpSection('landscapers')} + onInstallButtonClick={undefined} + /> + + + {/* not yet available + */} +
+
+ ); +} diff --git a/src/spaces/mcp/components/Kpi/Kpi.cy.tsx b/src/spaces/mcp/components/Kpi/Kpi.cy.tsx new file mode 100644 index 00000000..803fe366 --- /dev/null +++ b/src/spaces/mcp/components/Kpi/Kpi.cy.tsx @@ -0,0 +1,72 @@ +import { Kpi, KpiProps } from './Kpi'; +import { APIError } from '../../../../lib/api/error.ts'; + +describe('', () => { + const mount = (props: KpiProps) => { + cy.mount(, {}); + }; + + context('kpiType="progress"', () => { + it('renders correctly', () => { + mount({ + kpiType: 'progress', + isLoading: false, + progressValue: 75, + progressLabel: 'Healthy: 3 / 4', + }); + + // BusyIndicator should be inactive + cy.get('[data-cy="busy-indicator"]').should('not.have.attr', 'active'); + + // Kpi should be shown + cy.contains('Healthy: 3 / 4').should('exist'); + cy.get('[data-cy="progress-indicator"]').should('have.attr', 'value', 75); + }); + + it('renders with "isLoading=true"', () => { + mount({ + kpiType: 'progress', + isLoading: true, + progressValue: 42, + progressLabel: 'progressLabel', + }); + + // BusyIndicator should be present and active + cy.get('[data-cy="busy-indicator"]').should('have.attr', 'active'); + + // Kpi should be shown + cy.contains('progressLabel').should('exist'); + cy.get('[data-cy="progress-indicator"]').should('have.attr', 'value', 42); + }); + + it('renders with "error" defined', () => { + mount({ + kpiType: 'progress', + isLoading: false, + error: new APIError('error message', 404), + progressValue: 10, + progressLabel: 'won’t show', + }); + + // Error message should be shown + cy.get('[role="status"]').should( + 'contain.text', + 'There was a problem loading this data. Please try again later.', + ); + + // Kpi should not be shown + cy.get('[data-cy="busy-indicator"]').should('not.exist'); + cy.get('body').should('not.contain', 'won’t show'); + cy.get('[data-cy="progress-indicator"]').should('not.exist'); + }); + }); + + context('kpiType="enabled"', () => { + it('renders nothing', () => { + mount({ kpiType: 'enabled' }); + + cy.get('[data-cy="busy-indicator"]').should('not.exist'); + cy.get('[data-cy="progress-indicator"]').should('not.exist'); + }); + }); +}); diff --git a/src/spaces/mcp/components/Kpi/Kpi.module.css b/src/spaces/mcp/components/Kpi/Kpi.module.css new file mode 100644 index 00000000..f0ed3f40 --- /dev/null +++ b/src/spaces/mcp/components/Kpi/Kpi.module.css @@ -0,0 +1,19 @@ +.progressContainer { + width: 100%; + display: flex; + flex-direction: column; + align-items: start; + gap: 4px; +} + +.progressIndicator { + padding-inline: 0; + padding-block: 0.5rem; +} + +.enabledContainer { + width: 100%; + height: 100%; + display: flex; + align-items: flex-end; +} diff --git a/src/spaces/mcp/components/Kpi/Kpi.spec.ts b/src/spaces/mcp/components/Kpi/Kpi.spec.ts new file mode 100644 index 00000000..e44ebc38 --- /dev/null +++ b/src/spaces/mcp/components/Kpi/Kpi.spec.ts @@ -0,0 +1,56 @@ +import { describe, it, expect } from 'vitest'; +import type { KpiProps, KpiPropsEnabled, KpiPropsProgress } from './Kpi'; +import { assertIsEnabled, assertIsProgress } from './Kpi'; + +describe('', () => { + describe('assertIsProgress', () => { + it('narrows type when kpiType is "progress"', () => { + const kpiPropsProgress: KpiProps = { + kpiType: 'progress', + isLoading: false, + progressValue: 42, + progressLabel: 'Loading', + }; + + expect(() => assertIsProgress(kpiPropsProgress)).not.toThrow(); + + // After assertion, kpi is KpiPropsProgress; failed narrowing triggers a compile-time error + expect(kpiPropsProgress.progressValue).toBe(42); + }); + + it('throws when kpiType is not "progress"', () => { + const kpi: KpiPropsEnabled = { kpiType: 'enabled' }; + + expect(() => assertIsProgress(kpi)).toThrowError( + "Assertion failed: Expected kpiType to be 'progress', but got 'enabled'.", + ); + }); + }); + + describe('assertIsEnabled', () => { + it('narrows type when kpiType is "enabled"', () => { + const kpi: KpiPropsEnabled = { kpiType: 'enabled' }; + + expect(() => assertIsEnabled(kpi)).not.toThrow(); + + // Type-level check: after assertion, kpi is KpiPropsEnabled + // Accessing progress-only fields would be a TS error if uncommented. + // @ts-expect-error - ensure narrowing prevents access to progress-only props + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + kpi.progressValue; + }); + + it('throws when kpiType is not "enabled"', () => { + const kpiPropsProgress: KpiPropsProgress = { + kpiType: 'progress', + isLoading: true, + progressValue: 100, + progressLabel: 'Done', + }; + + expect(() => assertIsEnabled(kpiPropsProgress)).toThrowError( + "Assertion failed: Expected kpiType to be 'enabled', but got 'progress'.", + ); + }); + }); +}); diff --git a/src/spaces/mcp/components/Kpi/Kpi.tsx b/src/spaces/mcp/components/Kpi/Kpi.tsx new file mode 100644 index 00000000..a8a2c543 --- /dev/null +++ b/src/spaces/mcp/components/Kpi/Kpi.tsx @@ -0,0 +1,63 @@ +import { BusyIndicator, ObjectStatus, ProgressIndicator, Text } from '@ui5/webcomponents-react'; + +import { APIError } from '../../../../lib/api/error'; + +import styles from './Kpi.module.css'; +import { useTranslation } from 'react-i18next'; + +export type KpiPropsProgress = { + kpiType: 'progress'; + isLoading: boolean; + error?: APIError; + progressValue: number; + progressLabel: string; +}; + +export type KpiPropsEnabled = { + kpiType: 'enabled'; +}; + +export type KpiProps = KpiPropsProgress | KpiPropsEnabled; + +export function assertIsProgress(kpi: KpiProps): asserts kpi is KpiPropsProgress { + if (kpi.kpiType !== 'progress') { + throw new Error(`Assertion failed: Expected kpiType to be 'progress', but got '${kpi.kpiType}'.`); + } +} +export function assertIsEnabled(kpi: KpiProps): asserts kpi is KpiPropsEnabled { + if (kpi.kpiType !== 'enabled') { + throw new Error(`Assertion failed: Expected kpiType to be 'enabled', but got '${kpi.kpiType}'.`); + } +} + +export function Kpi(props: KpiProps) { + const { t } = useTranslation(); + + switch (props.kpiType) { + case 'progress': + return props.error ? ( + {t('Kpi.error')} + ) : ( + +
+ {props.progressLabel} + +
+
+ ); + + case 'enabled': + return ( +
+ + {t('Kpi.installed')} + +
+ ); + } +} diff --git a/src/spaces/mcp/components/Kpi/useKpiCrossplane.spec.ts b/src/spaces/mcp/components/Kpi/useKpiCrossplane.spec.ts new file mode 100644 index 00000000..0b45c049 --- /dev/null +++ b/src/spaces/mcp/components/Kpi/useKpiCrossplane.spec.ts @@ -0,0 +1,232 @@ +import { renderHook } from '@testing-library/react'; +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { useKpiCrossplane } from './useKpiCrossplane'; +import { useApiResource } from '../../../../lib/api/useApiResource'; +import { assertIsProgress } from './Kpi'; +import { APIError } from '../../../../lib/api/error'; + +vi.mock('../../../../lib/api/useApiResource', () => ({ + useApiResource: vi.fn(), +})); + +describe('useKpiCrossplane', () => { + const mockedUseApiResource = vi.mocked(useApiResource); + + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('counts healthy items correctly', () => { + const data = [ + { + items: [ + { + status: { + conditions: [ + { type: 'Synced', status: 'True' }, + { type: 'Ready', status: 'True' }, + ], + }, + }, + { + status: { + conditions: [ + { type: 'Synced', status: 'False' }, + { type: 'Ready', status: 'True' }, + ], + }, + }, + ], + }, + { + items: [ + { + status: { + conditions: [ + { type: 'Synced', status: 'True' }, + { type: 'Ready', status: 'False' }, + ], + }, + }, + { + status: { + conditions: [ + { type: 'Synced', status: 'True' }, + { type: 'Ready', status: 'True' }, + ], + }, + }, + { + status: { + conditions: [ + { type: 'Synced', status: 'False' }, + { type: 'Ready', status: 'False' }, + ], + }, + }, + ], + }, + { + // undefined item + }, + ]; + + mockedUseApiResource.mockReturnValue({ + data, + error: undefined, + isLoading: false, + isValidating: false, + }); + + const { result } = renderHook(() => useKpiCrossplane()); + + expect(result.current.kpiType).toBe('progress'); + assertIsProgress(result.current); + expect(result.current.isLoading).toBe(false); + expect(result.current.error).toBeUndefined(); + expect(result.current.progressValue).toBe((100 * 2) / 5); + }); + + it('handles empty data array', () => { + mockedUseApiResource.mockReturnValue({ + isLoading: false, + error: undefined, + data: [], + isValidating: false, + }); + + const { result } = renderHook(() => useKpiCrossplane()); + + expect(result.current.kpiType).toBe('progress'); + assertIsProgress(result.current); + expect(result.current.isLoading).toBe(false); + expect(result.current.error).toBeUndefined(); + expect(result.current.progressValue).toBe(0); + }); + + it('handles when all items are healthy', () => { + const data = [ + { + items: [ + { + status: { + conditions: [ + { type: 'Synced', status: 'True' }, + { type: 'Ready', status: 'True' }, + ], + }, + }, + { + status: { + conditions: [ + { type: 'Synced', status: 'True' }, + { type: 'Ready', status: 'True' }, + ], + }, + }, + ], + }, + ]; + + mockedUseApiResource.mockReturnValue({ + data, + error: undefined, + isLoading: false, + isValidating: false, + }); + + const { result } = renderHook(() => useKpiCrossplane()); + + expect(result.current.kpiType).toBe('progress'); + assertIsProgress(result.current); + expect(result.current.isLoading).toBe(false); + expect(result.current.error).toBeUndefined(); + expect(result.current.progressValue).toBe(100); + }); + + it('handles when no items are healthy', () => { + const data = [ + { + items: [ + // Ready true, Synced false + { + status: { + conditions: [ + { type: 'Ready', status: 'True' }, + { type: 'Synced', status: 'False' }, + ], + }, + }, + // Ready false, Synced true + { + status: { + conditions: [ + { type: 'Ready', status: 'False' }, + { type: 'Synced', status: 'True' }, + ], + }, + }, + // Only Ready true present + { status: { conditions: [{ type: 'Ready', status: 'True' }] } }, + // Only Synced true present + { status: { conditions: [{ type: 'Synced', status: 'True' }] } }, + // No conditions + { status: {} }, + // Conditions other types + { status: { conditions: [{ type: 'SomethingElse', status: 'True' }] } }, + ], + }, + ]; + + mockedUseApiResource.mockReturnValue({ + data, + error: undefined, + isLoading: false, + isValidating: false, + }); + + const { result } = renderHook(() => useKpiCrossplane()); + + expect(result.current.kpiType).toBe('progress'); + assertIsProgress(result.current); + expect(result.current.isLoading).toBe(false); + expect(result.current.error).toBeUndefined(); + expect(result.current.progressValue).toBe(0); + }); + + it('propagates loading state', () => { + mockedUseApiResource.mockReturnValue({ + isLoading: true, + error: undefined, + data: undefined, + isValidating: false, + }); + + const { result } = renderHook(() => useKpiCrossplane()); + + expect(result.current.kpiType).toBe('progress'); + assertIsProgress(result.current); + expect(result.current.isLoading).toBe(true); + expect(result.current.error).toBeUndefined(); + expect(result.current.progressValue).toBe(0); + }); + + it('propagates error state', () => { + const error = new APIError('fetch failed', 500); + + mockedUseApiResource.mockReturnValue({ + isLoading: false, + error, + data: undefined, + isValidating: false, + }); + + const { result } = renderHook(() => useKpiCrossplane()); + + expect(result.current.kpiType).toBe('progress'); + assertIsProgress(result.current); + expect(result.current.isLoading).toBe(false); + expect(result.current.error).toBe(error); + expect(result.current.progressValue).toBe(0); + }); +}); diff --git a/src/spaces/mcp/components/Kpi/useKpiCrossplane.ts b/src/spaces/mcp/components/Kpi/useKpiCrossplane.ts new file mode 100644 index 00000000..e9a5e8ab --- /dev/null +++ b/src/spaces/mcp/components/Kpi/useKpiCrossplane.ts @@ -0,0 +1,40 @@ +import { useApiResource } from '../../../../lib/api/useApiResource.ts'; +import { ManagedResourcesRequest } from '../../../../lib/api/types/crossplane/listManagedResources.ts'; +import { resourcesInterval } from '../../../../lib/shared/constants.ts'; +import { KpiProps } from './Kpi.tsx'; +import { useTranslation } from 'react-i18next'; + +export function useKpiCrossplane(): KpiProps { + const { t } = useTranslation(); + const { data, error, isLoading } = useApiResource(ManagedResourcesRequest, { + refreshInterval: resourcesInterval, + }); + + const managedResources = + data + ?.filter((managedResource) => managedResource.items) + .flatMap((managedResource) => + managedResource.items?.map((item) => { + const conditionSynced = item.status?.conditions?.find((condition) => condition.type === 'Synced'); + const conditionReady = item.status?.conditions?.find((condition) => condition.type === 'Ready'); + + return { + synced: conditionSynced?.status === 'True', + ready: conditionReady?.status === 'True', + }; + }), + ) ?? []; + + const totalCount = managedResources.length; + const healthyCount = managedResources.filter((mr) => mr.ready && mr.synced).length; + + return { + kpiType: 'progress', + isLoading, + error, + progressValue: healthyCount !== 0 ? (100 * healthyCount) / totalCount : 0, + progressLabel: isLoading + ? t('componentCardCrossplane.progress') + : t('componentCardCrossplane.progressCount', { count: healthyCount, total: totalCount }), + }; +} diff --git a/src/spaces/mcp/components/Kpi/useKpiFlux.spec.ts b/src/spaces/mcp/components/Kpi/useKpiFlux.spec.ts new file mode 100644 index 00000000..d8979730 --- /dev/null +++ b/src/spaces/mcp/components/Kpi/useKpiFlux.spec.ts @@ -0,0 +1,176 @@ +import { renderHook } from '@testing-library/react'; +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +import { useKpiFlux } from './useKpiFlux'; +import { useApiResource } from '../../../../lib/api/useApiResource'; +import { assertIsProgress } from './Kpi'; +import { APIError } from '../../../../lib/api/error'; + +vi.mock('../../../../lib/api/useApiResource', () => ({ + useApiResource: vi.fn(), +})); + +describe('useKpiFlux', () => { + const mockedUseApiResource = vi.mocked(useApiResource); + + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('counts managed items correctly', () => { + const data = [ + { + items: [ + { + metadata: { + labels: { + 'kustomize.toolkit.fluxcd.io/name': 'FLUX-A', + }, + }, + }, + { + metadata: { + labels: { + other: 'x', + }, + }, + }, + ], + }, + { + items: [ + { + // no labels + }, + { + metadata: { + labels: { + 'kustomize.toolkit.fluxcd.io/name': 'FLUX-B', + }, + }, + }, + ], + }, + { + // undefined item + }, + ]; + + mockedUseApiResource.mockReturnValue({ + data, + error: undefined, + isLoading: false, + isValidating: false, + }); + + const { result } = renderHook(() => useKpiFlux()); + + expect(result.current.kpiType).toBe('progress'); + assertIsProgress(result.current); + expect(result.current.isLoading).toBe(false); + expect(result.current.error).toBeUndefined(); + expect(result.current.progressValue).toBe((100 * 2) / 4); + }); + + it('handles empty data array', () => { + mockedUseApiResource.mockReturnValue({ + isLoading: false, + error: undefined, + data: [], + isValidating: false, + }); + + const { result } = renderHook(() => useKpiFlux()); + + expect(result.current.kpiType).toBe('progress'); + assertIsProgress(result.current); + expect(result.current.isLoading).toBe(false); + expect(result.current.error).toBeUndefined(); + expect(result.current.progressValue).toBe(0); + }); + + it('handles when all items are managed', () => { + const data = [ + { + items: [ + { metadata: { labels: { 'kustomize.toolkit.fluxcd.io/name': 'app-a' } } }, + { metadata: { labels: { 'kustomize.toolkit.fluxcd.io/name': 'app-b' } } }, + ], + }, + ]; + + mockedUseApiResource.mockReturnValue({ + data, + error: undefined, + isLoading: false, + isValidating: false, + }); + + const { result } = renderHook(() => useKpiFlux()); + + expect(result.current.kpiType).toBe('progress'); + assertIsProgress(result.current); + expect(result.current.isLoading).toBe(false); + expect(result.current.error).toBeUndefined(); + expect(result.current.progressValue).toBe(100); + }); + + it('handles when no items are managed', () => { + const data = [ + { + items: [{ metadata: { labels: { a: 'x' } } }, { metadata: { labels: { b: 'y' } } }], + }, + ]; + + mockedUseApiResource.mockReturnValue({ + data, + error: undefined, + isLoading: false, + isValidating: false, + }); + + const { result } = renderHook(() => useKpiFlux()); + + expect(result.current.kpiType).toBe('progress'); + assertIsProgress(result.current); + expect(result.current.isLoading).toBe(false); + expect(result.current.error).toBeUndefined(); + expect(result.current.progressValue).toBe(0); + }); + + it('propagates loading state', () => { + mockedUseApiResource.mockReturnValue({ + isLoading: true, + error: undefined, + data: undefined, + isValidating: false, + }); + + const { result } = renderHook(() => useKpiFlux()); + + expect(result.current.kpiType).toBe('progress'); + assertIsProgress(result.current); + expect(result.current.isLoading).toBe(true); + expect(result.current.error).toBeUndefined(); + expect(result.current.progressValue).toBe(0); + }); + + it('propagates error state', () => { + const error = new APIError('fetch failed', 500); + + mockedUseApiResource.mockReturnValue({ + isLoading: false, + error, + data: undefined, + isValidating: false, + }); + + const { result } = renderHook(() => useKpiFlux()); + + expect(result.current.kpiType).toBe('progress'); + assertIsProgress(result.current); + expect(result.current.isLoading).toBe(false); + expect(result.current.error).toBe(error); + expect(result.current.progressValue).toBe(0); + }); +}); diff --git a/src/spaces/mcp/components/Kpi/useKpiFlux.ts b/src/spaces/mcp/components/Kpi/useKpiFlux.ts new file mode 100644 index 00000000..61f14f86 --- /dev/null +++ b/src/spaces/mcp/components/Kpi/useKpiFlux.ts @@ -0,0 +1,36 @@ +import { useApiResource } from '../../../../lib/api/useApiResource'; +import { ManagedResourcesRequest } from '../../../../lib/api/types/crossplane/listManagedResources'; +import { resourcesInterval } from '../../../../lib/shared/constants'; +import { KpiProps } from './Kpi'; +import { useTranslation } from 'react-i18next'; + +export function useKpiFlux(): KpiProps { + const { t } = useTranslation(); + const { data, error, isLoading } = useApiResource(ManagedResourcesRequest, { + refreshInterval: resourcesInterval, + }); + + const managedResources = + data + ?.filter((managedResource) => managedResource.items) + .flatMap((managedResource) => + managedResource.items?.map((item) => { + return { + isManaged: Boolean(item.metadata?.labels?.['kustomize.toolkit.fluxcd.io/name']), + }; + }), + ) ?? []; + + const totalCount = managedResources.length; + const managedCount = managedResources.filter((mr) => mr.isManaged).length; + + return { + kpiType: 'progress', + isLoading, + error, + progressValue: managedCount !== 0 ? (100 * managedCount) / totalCount : 0, + progressLabel: isLoading + ? t('componentCardFlux.progress') + : t('componentCardFlux.progressCount', { count: managedCount, total: totalCount }), + }; +} diff --git a/src/spaces/mcp/components/McpHeader.cy.tsx b/src/spaces/mcp/components/McpHeader/McpHeader.cy.tsx similarity index 92% rename from src/spaces/mcp/components/McpHeader.cy.tsx rename to src/spaces/mcp/components/McpHeader/McpHeader.cy.tsx index 08b1fcee..f7fd698b 100644 --- a/src/spaces/mcp/components/McpHeader.cy.tsx +++ b/src/spaces/mcp/components/McpHeader/McpHeader.cy.tsx @@ -1,5 +1,5 @@ -import { McpHeader } from './McpHeader'; -import { ControlPlaneType } from '../../../lib/api/types/crate/controlPlanes.ts'; +import { McpHeader } from './McpHeader.tsx'; +import { ControlPlaneType } from '../../../../lib/api/types/crate/controlPlanes.ts'; describe('McpHeader', () => { it('renders MCP metadata', () => { diff --git a/src/spaces/mcp/components/McpHeader.module.css b/src/spaces/mcp/components/McpHeader/McpHeader.module.css similarity index 100% rename from src/spaces/mcp/components/McpHeader.module.css rename to src/spaces/mcp/components/McpHeader/McpHeader.module.css diff --git a/src/spaces/mcp/components/McpHeader.tsx b/src/spaces/mcp/components/McpHeader/McpHeader.tsx similarity index 87% rename from src/spaces/mcp/components/McpHeader.tsx rename to src/spaces/mcp/components/McpHeader/McpHeader.tsx index d1d469e9..82bd751b 100644 --- a/src/spaces/mcp/components/McpHeader.tsx +++ b/src/spaces/mcp/components/McpHeader/McpHeader.tsx @@ -1,8 +1,8 @@ -import { ControlPlaneType } from '../../../lib/api/types/crate/controlPlanes.ts'; +import { ControlPlaneType } from '../../../../lib/api/types/crate/controlPlanes.ts'; import { Text } from '@ui5/webcomponents-react'; import styles from './McpHeader.module.css'; -import { formatDateAsTimeAgo } from '../../../utils/i18n/timeAgo.ts'; +import { formatDateAsTimeAgo } from '../../../../utils/i18n/timeAgo.ts'; import { useTranslation } from 'react-i18next'; export interface McpHeaderProps { diff --git a/src/spaces/mcp/pages/McpPage.tsx b/src/spaces/mcp/pages/McpPage.tsx index 5b4b0262..9a82d2b2 100644 --- a/src/spaces/mcp/pages/McpPage.tsx +++ b/src/spaces/mcp/pages/McpPage.tsx @@ -31,7 +31,6 @@ import { AuthProviderMcp } from '../auth/AuthContextMcp.tsx'; import { isNotFoundError } from '../../../lib/api/error.ts'; import { NotFoundBanner } from '../../../components/Ui/NotFoundBanner/NotFoundBanner.tsx'; import Graph from '../../../components/Graphs/Graph.tsx'; -import HintsCardsRow from '../../../components/HintsCardsRow/HintsCardsRow.tsx'; import { useState } from 'react'; import { EditManagedControlPlaneWizardDataLoader } from '../../../components/Wizards/CreateManagedControlPlane/EditManagedControlPlaneWizardDataLoader.tsx'; @@ -40,7 +39,8 @@ import { DISPLAY_NAME_ANNOTATION } from '../../../lib/api/types/shared/keyNames. import { WizardStepType } from '../../../components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx'; import { GitRepositories } from '../../../components/ControlPlane/GitRepositories.tsx'; import { Kustomizations } from '../../../components/ControlPlane/Kustomizations.tsx'; -import { McpHeader } from '../components/McpHeader.tsx'; +import { McpHeader } from '../components/McpHeader/McpHeader.tsx'; +import { ComponentsDashboard } from '../components/ComponentsDashboard/ComponentsDashboard.tsx'; export type McpPageSectionId = 'overview' | 'crossplane' | 'flux' | 'landscapers'; @@ -142,13 +142,12 @@ export default function McpPage() { onSelectedSectionChange={() => setSelectedSectionId(undefined)} > - - setSelectedSectionId(sectionId)} /> + + setSelectedSectionId(sectionId)} + /> diff --git a/vite.config.js b/vite.config.js index 382cb0aa..283c2549 100644 --- a/vite.config.js +++ b/vite.config.js @@ -30,6 +30,10 @@ export default defineConfig({ }), ], + test: { + environment: 'jsdom', + }, + build: { sourcemap: true, target: 'esnext', // Support top-level await