diff --git a/coral/package.json b/coral/package.json index 4ecad70b1b..cf436b32fb 100644 --- a/coral/package.json +++ b/coral/package.json @@ -40,7 +40,7 @@ ] }, "dependencies": { - "@aivenio/aquarium": "^1.55.0", + "@aivenio/aquarium": "1.57.1", "@hookform/resolvers": "^2.9.10", "@monaco-editor/react": "^4.6.0", "@tanstack/react-query": "^4.29.5", diff --git a/coral/pnpm-lock.yaml b/coral/pnpm-lock.yaml index fc3f4bf5cb..18914da8bf 100644 --- a/coral/pnpm-lock.yaml +++ b/coral/pnpm-lock.yaml @@ -6,8 +6,8 @@ settings: dependencies: '@aivenio/aquarium': - specifier: ^1.55.0 - version: 1.55.0(@types/react-dom@18.2.19)(@types/react@18.2.35)(less@4.2.0)(lodash@4.17.21)(postcss@8.4.31)(prop-types@15.8.1)(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.3)(webpack@5.89.0) + specifier: 1.57.1 + version: 1.57.1(@types/react-dom@18.2.19)(@types/react@18.2.35)(less@4.2.0)(lodash@4.17.21)(postcss@8.4.31)(prop-types@15.8.1)(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.3)(webpack@5.89.0) '@hookform/resolvers': specifier: ^2.9.10 version: 2.9.10(react-hook-form@7.50.1) @@ -211,8 +211,8 @@ packages: resolution: {integrity: sha512-DA5a1C0gD/pLOvhv33YMrbf2FK3oUzwNl9oOJqE4XVjuEtt6XIakRcsd7eLiOSPkp1kTRQGICTA8cKra/vFbjw==} dev: true - /@aivenio/aquarium@1.55.0(@types/react-dom@18.2.19)(@types/react@18.2.35)(less@4.2.0)(lodash@4.17.21)(postcss@8.4.31)(prop-types@15.8.1)(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.3)(webpack@5.89.0): - resolution: {integrity: sha512-UfzVUYLyjprsPJ5F4nAX5AbzAjQSL8fL+fwBPlswATJB1F/pNTIN4FOCYKW3YOuk/u3/9799mq0qUhX3UBnQmQ==} + /@aivenio/aquarium@1.57.1(@types/react-dom@18.2.19)(@types/react@18.2.35)(less@4.2.0)(lodash@4.17.21)(postcss@8.4.31)(prop-types@15.8.1)(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.3)(webpack@5.89.0): + resolution: {integrity: sha512-YPaxj1duqQHPBvRfWy6o54GEFguHygQynio2/3wJnU4sdMIAcRV7gVmqBTjhFxbhzIfvCYon/Kk5fWFDz5JNHQ==} peerDependencies: lodash: 4.x react: 16.x || 17.x || 18.x @@ -1067,12 +1067,6 @@ packages: engines: {node: '>=18'} dev: true - /@internationalized/date@3.5.0: - resolution: {integrity: sha512-nw0Q+oRkizBWMioseI8+2TeUPEyopJVz5YxoYVzR0W1v+2YytiYah7s/ot35F149q/xAg4F1gT/6eTd+tsUpFQ==} - dependencies: - '@swc/helpers': 0.5.1 - dev: false - /@internationalized/date@3.5.2: resolution: {integrity: sha512-vo1yOMUt2hzp63IutEaTUxROdvQg1qlMRsbCvbay2AK2Gai7wIgCyK5weEX3nHkiLgo4qCXHijFNC/ILhlRpOQ==} dependencies: @@ -1084,23 +1078,11 @@ packages: '@swc/helpers': 0.5.1 intl-messageformat: 10.2.5 - /@internationalized/number@3.4.0: - resolution: {integrity: sha512-8TvotW3qVDHC4uv/BVoN6Qx0Dm8clHY1/vpH+dh+XRiPW/9NVpKn1P8d1A+WLphWrMwyqyWXI7uWehJPviaeIw==} - dependencies: - '@swc/helpers': 0.5.1 - dev: false - /@internationalized/number@3.5.1: resolution: {integrity: sha512-N0fPU/nz15SwR9IbfJ5xaS9Ss/O5h1sVXMZf43vc9mxEG48ovglvvzBjF53aHlq20uoR6c+88CrIXipU/LSzwg==} dependencies: '@swc/helpers': 0.5.1 - /@internationalized/string@3.1.1: - resolution: {integrity: sha512-fvSr6YRoVPgONiVIUhgCmIAlifMVCeej/snPZVzbzRPxGpHl3o1GRe+d/qh92D8KhgOciruDUH8I5mjdfdjzfA==} - dependencies: - '@swc/helpers': 0.5.1 - dev: false - /@internationalized/string@3.2.1: resolution: {integrity: sha512-vWQOvRIauvFMzOO+h7QrdsJmtN1AXAFVcaLWP9AseRN2o7iHceZ6bIXhBD4teZl8i91A3gxKnWBlGgjCwU6MFQ==} dependencies: @@ -2716,19 +2698,6 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false - /@react-stately/calendar@3.4.2(react@18.2.0): - resolution: {integrity: sha512-RfH40rVa2EhUnQgqH3HTZL+YhL+6tZ8T9GbN1K3AbIM5BBEtkb3P8qGhcaI7WpwNy1rlRFFFXGcqFAMUncDg2Q==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - dependencies: - '@internationalized/date': 3.5.0 - '@react-stately/utils': 3.9.0(react@18.2.0) - '@react-types/calendar': 3.4.2(react@18.2.0) - '@react-types/shared': 3.22.0(react@18.2.0) - '@swc/helpers': 0.5.1 - react: 18.2.0 - dev: false - /@react-stately/calendar@3.4.4(react@18.2.0): resolution: {integrity: sha512-f9ZOd096gGGD+3LmU1gkmfqytGyQtrgi+Qjn+70GbM2Jy65pwOR4I9YrobbmeAFov5Tff13mQEa0yqWvbcDLZQ==} peerDependencies: @@ -2741,19 +2710,6 @@ packages: '@swc/helpers': 0.5.1 react: 18.2.0 - /@react-stately/checkbox@3.6.0(react@18.2.0): - resolution: {integrity: sha512-e1ChMwGovcOEDcdizqXDT6eDZixIMiPQOzNV5wPQ91SlGaIry9b0lQnK18tHg3yv2iiS6Ipj96cGBUKLJqQ+cQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - dependencies: - '@react-stately/form': 3.0.0(react@18.2.0) - '@react-stately/utils': 3.9.0(react@18.2.0) - '@react-types/checkbox': 3.6.0(react@18.2.0) - '@react-types/shared': 3.22.0(react@18.2.0) - '@swc/helpers': 0.5.1 - react: 18.2.0 - dev: false - /@react-stately/checkbox@3.6.3(react@18.2.0): resolution: {integrity: sha512-hWp0GXVbMI4sS2NbBjWgOnHNrRqSV4jeftP8zc5JsIYRmrWBUZitxluB34QuVPzrBO29bGsF0GTArSiQZt6BWw==} peerDependencies: @@ -2766,16 +2722,6 @@ packages: '@swc/helpers': 0.5.1 react: 18.2.0 - /@react-stately/collections@3.10.3(react@18.2.0): - resolution: {integrity: sha512-fA28HIApAIz9sNGeOVXZJPgV5Kig6M72KI1t9sUbnRUr9Xq9OMJTR6ElDMXNe0iTeZffRFDOPYyqnX9zkxof6Q==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - dependencies: - '@react-types/shared': 3.22.0(react@18.2.0) - '@swc/helpers': 0.5.1 - react: 18.2.0 - dev: false - /@react-stately/collections@3.10.5(react@18.2.0): resolution: {integrity: sha512-k8Q29Nnvb7iAia1QvTanZsrWP2aqVNBy/1SlE6kLL6vDqtKZC+Esd1SDLHRmIcYIp5aTdfwIGd0NuiRQA7a81Q==} peerDependencies: @@ -2785,23 +2731,6 @@ packages: '@swc/helpers': 0.5.1 react: 18.2.0 - /@react-stately/combobox@3.8.0(react@18.2.0): - resolution: {integrity: sha512-F74Avf7+8ruRqEB+3Lh6/C5jXc3ESJbRf9ovUxhmNAzBGeFKesPn5HpEpo87C+3OukGb+/Buvi3Rhib9+HVBKA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - dependencies: - '@react-stately/collections': 3.10.3(react@18.2.0) - '@react-stately/form': 3.0.0(react@18.2.0) - '@react-stately/list': 3.10.1(react@18.2.0) - '@react-stately/menu': 3.5.7(react@18.2.0) - '@react-stately/select': 3.6.0(react@18.2.0) - '@react-stately/utils': 3.9.0(react@18.2.0) - '@react-types/combobox': 3.9.0(react@18.2.0) - '@react-types/shared': 3.22.0(react@18.2.0) - '@swc/helpers': 0.5.1 - react: 18.2.0 - dev: false - /@react-stately/combobox@3.8.2(react@18.2.0): resolution: {integrity: sha512-f+IHuFW848VoMbvTfSakn2WIh2urDxO355LrKxnisXPCkpQHpq3lvT2mJtKJwkPxjAy7xPjpV8ejgga2R6p53Q==} peerDependencies: @@ -2823,23 +2752,7 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 dependencies: - '@react-types/shared': 3.22.0(react@18.2.0) - '@swc/helpers': 0.5.1 - react: 18.2.0 - dev: false - - /@react-stately/datepicker@3.9.0(react@18.2.0): - resolution: {integrity: sha512-p6BuxPbDxjIgBZmskdv2dR6XIdPEftCjS7kYe/+iLZxfz1vYiDqpJVb3ascLyBjl84bDDyr4z2vWcKhdDwyhEA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - dependencies: - '@internationalized/date': 3.5.0 - '@internationalized/string': 3.1.1 - '@react-stately/form': 3.0.0(react@18.2.0) - '@react-stately/overlays': 3.6.4(react@18.2.0) - '@react-stately/utils': 3.9.0(react@18.2.0) - '@react-types/datepicker': 3.7.0(react@18.2.0) - '@react-types/shared': 3.22.0(react@18.2.0) + '@react-types/shared': 3.22.1(react@18.2.0) '@swc/helpers': 0.5.1 react: 18.2.0 dev: false @@ -2859,17 +2772,6 @@ packages: '@swc/helpers': 0.5.1 react: 18.2.0 - /@react-stately/dnd@3.2.6(react@18.2.0): - resolution: {integrity: sha512-ex3Pjn+9uIoqsBb9F4ZFJb3fB0YadN8uYBOEiBb9N4UXWyANibGUYJ2FvIbvq1nFDU7On7MW1J9e3vkGglX4FQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - dependencies: - '@react-stately/selection': 3.14.1(react@18.2.0) - '@react-types/shared': 3.22.0(react@18.2.0) - '@swc/helpers': 0.5.1 - react: 18.2.0 - dev: false - /@react-stately/dnd@3.2.8(react@18.2.0): resolution: {integrity: sha512-oSo+2Bzum3Q1/d+3FuaDmpVHqqBB004tycuQDDFtad3N1BKm+fNfmslRK1ioLkPLK4sm1130V+BZBY3JXLe80A==} peerDependencies: @@ -2880,27 +2782,11 @@ packages: '@swc/helpers': 0.5.1 react: 18.2.0 - /@react-stately/flags@3.0.0: - resolution: {integrity: sha512-e3i2ItHbIa0eEwmSXAnPdD7K8syW76JjGe8ENxwFJPW/H1Pu9RJfjkCb/Mq0WSPN/TpxBb54+I9TgrGhbCoZ9w==} - dependencies: - '@swc/helpers': 0.4.36 - dev: false - /@react-stately/flags@3.0.1: resolution: {integrity: sha512-h5PcDMj54aipQNO18ig/IMI1kzPwcvSwVq5M6Ib6XE1WIkOH0dIuW2eADdAOhcGi3KXJtXVdD29zh0Eox1TKgQ==} dependencies: '@swc/helpers': 0.4.36 - /@react-stately/form@3.0.0(react@18.2.0): - resolution: {integrity: sha512-C8wkfFmtx1escizibhdka5JvTy9/Vp173CS9cakjvWTmnjYYC1nOlzwp7BsYWTgerCFbRY/BU/Cf/bJDxPiUKQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - dependencies: - '@react-types/shared': 3.22.0(react@18.2.0) - '@swc/helpers': 0.5.1 - react: 18.2.0 - dev: false - /@react-stately/form@3.0.1(react@18.2.0): resolution: {integrity: sha512-T1Ul2Ou0uE/S4ECLcGKa0OfXjffdjEHfUFZAk7OZl0Mqq/F7dl5WpoLWJ4d4IyvZzGO6anFNenP+vODWbrF3NA==} peerDependencies: @@ -2910,19 +2796,6 @@ packages: '@swc/helpers': 0.5.1 react: 18.2.0 - /@react-stately/grid@3.8.3(react@18.2.0): - resolution: {integrity: sha512-JceGSJcuO6Zv+Aq5s2NZvmbMjdPjTtGNQR9kTgXKC/pOfM6FJ58bJiOmEllyN6oawqh4Ey8Xdqk9NuW4l2ctuw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - dependencies: - '@react-stately/collections': 3.10.3(react@18.2.0) - '@react-stately/selection': 3.14.1(react@18.2.0) - '@react-types/grid': 3.2.3(react@18.2.0) - '@react-types/shared': 3.22.0(react@18.2.0) - '@swc/helpers': 0.5.1 - react: 18.2.0 - dev: false - /@react-stately/grid@3.8.5(react@18.2.0): resolution: {integrity: sha512-KCzi0x0p1ZKK+OptonvJqMbn6Vlgo6GfOIlgcDd0dNYDP8TJ+3QFJAFre5mCr7Fubx7LcAOio4Rij0l/R8fkXQ==} peerDependencies: @@ -2935,19 +2808,6 @@ packages: '@swc/helpers': 0.5.1 react: 18.2.0 - /@react-stately/list@3.10.1(react@18.2.0): - resolution: {integrity: sha512-iVarLMd7FmMT0H20dRWsFOHHX5+c4gK51AXP2BSr1VtDSfbL4dgaGgu7IaAMVc/rO0au1e1tPM2hutiIFvPcnA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - dependencies: - '@react-stately/collections': 3.10.3(react@18.2.0) - '@react-stately/selection': 3.14.1(react@18.2.0) - '@react-stately/utils': 3.9.0(react@18.2.0) - '@react-types/shared': 3.22.0(react@18.2.0) - '@swc/helpers': 0.5.1 - react: 18.2.0 - dev: false - /@react-stately/list@3.10.3(react@18.2.0): resolution: {integrity: sha512-Ul8el0tQy2Ucl3qMQ0fiqdJ874W1ZNjURVSgSxN+pGwVLNBVRjd6Fl7YwZFCXER2YOlzkwg+Zqozf/ZlS0EdXA==} peerDependencies: @@ -2960,18 +2820,6 @@ packages: '@swc/helpers': 0.5.1 react: 18.2.0 - /@react-stately/menu@3.5.7(react@18.2.0): - resolution: {integrity: sha512-bzTmAqzcMNatvyruWlvOdZSmMhz3+mkdxtqaZzYHq+DpR6ka57lIRj8dBnZWQGwV3RypMZfz+X6aIX4kruGVbw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - dependencies: - '@react-stately/overlays': 3.6.4(react@18.2.0) - '@react-types/menu': 3.9.6(react@18.2.0) - '@react-types/shared': 3.22.0(react@18.2.0) - '@swc/helpers': 0.5.1 - react: 18.2.0 - dev: false - /@react-stately/menu@3.6.1(react@18.2.0): resolution: {integrity: sha512-3v0vkTm/kInuuG8jG7jbxXDBnMQcoDZKWvYsBQq7+POt0LmijbLdbdZPBoz9TkZ3eo/OoP194LLHOaFTQyHhlw==} peerDependencies: @@ -2983,19 +2831,6 @@ packages: '@swc/helpers': 0.5.1 react: 18.2.0 - /@react-stately/numberfield@3.7.0(react@18.2.0): - resolution: {integrity: sha512-DOz4jL7T30KGUXpGh/z80aHf+DEOQfvCHVDfll+IU7p3sd+bbM5uj7JdwXpZgIYUK8KTf2N49sL6lq5uCoxh8w==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - dependencies: - '@internationalized/number': 3.4.0 - '@react-stately/form': 3.0.0(react@18.2.0) - '@react-stately/utils': 3.9.0(react@18.2.0) - '@react-types/numberfield': 3.7.0(react@18.2.0) - '@swc/helpers': 0.5.1 - react: 18.2.0 - dev: false - /@react-stately/numberfield@3.9.1(react@18.2.0): resolution: {integrity: sha512-btBIcBEfSVCUm6NwJrMrMygoIu/fQGazzD0RhF7PNsfvkFiWn+TSOyQqSXcsUJVOnBfoS/dVWj6r57KA7zl3FA==} peerDependencies: @@ -3008,17 +2843,6 @@ packages: '@swc/helpers': 0.5.1 react: 18.2.0 - /@react-stately/overlays@3.6.4(react@18.2.0): - resolution: {integrity: sha512-tHEaoAGpE9dSnsskqLPVKum59yGteoSqsniTopodM+miQozbpPlSjdiQnzGLroy5Afx5OZYClE616muNHUILXA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - dependencies: - '@react-stately/utils': 3.9.0(react@18.2.0) - '@react-types/overlays': 3.8.4(react@18.2.0) - '@swc/helpers': 0.5.1 - react: 18.2.0 - dev: false - /@react-stately/overlays@3.6.5(react@18.2.0): resolution: {integrity: sha512-U4rCFj6TPJPXLUvYXAcvh+yP/CO2W+7f0IuqP7ZZGE+Osk9qFkT+zRK5/6ayhBDFpmueNfjIEAzT9gYPQwNHFw==} peerDependencies: @@ -3029,19 +2853,6 @@ packages: '@swc/helpers': 0.5.1 react: 18.2.0 - /@react-stately/radio@3.10.0(react@18.2.0): - resolution: {integrity: sha512-d8IgZtUq/4vhE7YhyBVg1QdVoFS0caIcvPumXqtp/5vlDgpUsVy9jSeWtbk0H4FyUcmJlQhRcTylKB9THXY1YQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - dependencies: - '@react-stately/form': 3.0.0(react@18.2.0) - '@react-stately/utils': 3.9.0(react@18.2.0) - '@react-types/radio': 3.6.0(react@18.2.0) - '@react-types/shared': 3.22.0(react@18.2.0) - '@swc/helpers': 0.5.1 - react: 18.2.0 - dev: false - /@react-stately/radio@3.10.2(react@18.2.0): resolution: {integrity: sha512-JW5ZWiNMKcZvMTsuPeWJQLHXD5rlqy7Qk6fwUx/ZgeibvMBW/NnW19mm2+IMinzmbtERXvR6nsiA837qI+4dew==} peerDependencies: @@ -3054,17 +2865,6 @@ packages: '@swc/helpers': 0.5.1 react: 18.2.0 - /@react-stately/searchfield@3.5.0(react@18.2.0): - resolution: {integrity: sha512-SStjChkn/33pEn40slKQPnBnmQYyxVazVwPjiBkdeVejC42lUVairUTrGJgF0PNoZTbxn0so2/XzjqTC9T8iCw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - dependencies: - '@react-stately/utils': 3.9.0(react@18.2.0) - '@react-types/searchfield': 3.5.2(react@18.2.0) - '@swc/helpers': 0.5.1 - react: 18.2.0 - dev: false - /@react-stately/searchfield@3.5.1(react@18.2.0): resolution: {integrity: sha512-9A8Wghx1avRHhMpNH1Nj+jFfiF1bhsff2GEC5PZgWYzhCykw3G5bywn3JAuUS4kh7Vpqhbu4KpHAhmWPSv4B/Q==} peerDependencies: @@ -3075,20 +2875,6 @@ packages: '@swc/helpers': 0.5.1 react: 18.2.0 - /@react-stately/select@3.6.0(react@18.2.0): - resolution: {integrity: sha512-GvSE4DXmcvdRNUc+ciPU7gedt7LfRO8FFFIzhB/bCQhUlK6/xihUPrGXayzqxLeTQKttMH323LuYFKfwpJRhsA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - dependencies: - '@react-stately/form': 3.0.0(react@18.2.0) - '@react-stately/list': 3.10.1(react@18.2.0) - '@react-stately/menu': 3.5.7(react@18.2.0) - '@react-types/select': 3.9.0(react@18.2.0) - '@react-types/shared': 3.22.0(react@18.2.0) - '@swc/helpers': 0.5.1 - react: 18.2.0 - dev: false - /@react-stately/select@3.6.2(react@18.2.0): resolution: {integrity: sha512-duOxdHKol93h6Ew6fap6Amz+zngoERKZLSKVm/8I8uaBgkoBhEeTFv7mlpHTgINxymMw3mMrvy6GL/gfKFwkqg==} peerDependencies: @@ -3102,18 +2888,6 @@ packages: '@swc/helpers': 0.5.1 react: 18.2.0 - /@react-stately/selection@3.14.1(react@18.2.0): - resolution: {integrity: sha512-96/CerrB6yH4Ad9FkzBzyVerSPjcIj1NBTWTFHo1N+oHECvyGsDxZl7Y4LQR++teFK66FhX5KjCJQGae4IZd6A==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - dependencies: - '@react-stately/collections': 3.10.3(react@18.2.0) - '@react-stately/utils': 3.9.0(react@18.2.0) - '@react-types/shared': 3.22.0(react@18.2.0) - '@swc/helpers': 0.5.1 - react: 18.2.0 - dev: false - /@react-stately/selection@3.14.3(react@18.2.0): resolution: {integrity: sha512-d/t0rIWieqQ7wjLoMoWnuHEUSMoVXxkPBFuSlJF3F16289FiQ+b8aeKFDzFTYN7fFD8rkZTnpuE4Tcxg3TmA+w==} peerDependencies: @@ -3125,18 +2899,6 @@ packages: '@swc/helpers': 0.5.1 react: 18.2.0 - /@react-stately/slider@3.4.5(react@18.2.0): - resolution: {integrity: sha512-lJPZC8seYbnZDqAlZm3/QC95I5iluG8ouwkPMmvtWCz1baayV/jJtfxA/74zR7Vcob9Fe7O57g8Edhz/hv9xOQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - dependencies: - '@react-stately/utils': 3.9.0(react@18.2.0) - '@react-types/shared': 3.22.0(react@18.2.0) - '@react-types/slider': 3.7.0(react@18.2.0) - '@swc/helpers': 0.5.1 - react: 18.2.0 - dev: false - /@react-stately/slider@3.5.2(react@18.2.0): resolution: {integrity: sha512-ntH3NLRG+AwVC7q4Dx9DcmMkMh9vmHjHNXAgaoqNjhvwfSIae7sQ69CkVe6XeJjIBy6LlH81Kgapz+ABe5a1ZA==} peerDependencies: @@ -3148,23 +2910,6 @@ packages: '@swc/helpers': 0.5.1 react: 18.2.0 - /@react-stately/table@3.11.3(react@18.2.0): - resolution: {integrity: sha512-r0rzSKbtMG4tjFpCGtXb8p6hOuek03c6rheJE88z4I/ujZ5EmEO6Ps8q0JMNEDCY2qigvKM+ODisMBeZCEkIJg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - dependencies: - '@react-stately/collections': 3.10.3(react@18.2.0) - '@react-stately/flags': 3.0.0 - '@react-stately/grid': 3.8.3(react@18.2.0) - '@react-stately/selection': 3.14.1(react@18.2.0) - '@react-stately/utils': 3.9.0(react@18.2.0) - '@react-types/grid': 3.2.3(react@18.2.0) - '@react-types/shared': 3.22.0(react@18.2.0) - '@react-types/table': 3.9.1(react@18.2.0) - '@swc/helpers': 0.5.1 - react: 18.2.0 - dev: false - /@react-stately/table@3.11.6(react@18.2.0): resolution: {integrity: sha512-34YsfOILXusj3p6QNcKEaDWVORhM6WEhwPSLCZlkwAJvkxuRQFdih5rQKoIDc0uV5aZsB6bYBqiFhnjY0VERhw==} peerDependencies: @@ -3181,18 +2926,6 @@ packages: '@swc/helpers': 0.5.1 react: 18.2.0 - /@react-stately/tabs@3.6.2(react@18.2.0): - resolution: {integrity: sha512-f+U4D1FAVfVVcNRbtKIv4GrO37CLFClYQlXx9zIuSXjHsviapVD2IQSyAmpKo/CbgXhYRMdGwENZdOsmF/Ns7g==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - dependencies: - '@react-stately/list': 3.10.1(react@18.2.0) - '@react-types/shared': 3.22.0(react@18.2.0) - '@react-types/tabs': 3.3.4(react@18.2.0) - '@swc/helpers': 0.5.1 - react: 18.2.0 - dev: false - /@react-stately/tabs@3.6.4(react@18.2.0): resolution: {integrity: sha512-WZJgMBqzLgN88RN8AxhY4aH1+I+4w1qQA0Lh3LRSDegaytd+NHixCWaP3IPjePgCB5N1UsPe96Xglw75zjHmDg==} peerDependencies: @@ -3204,17 +2937,6 @@ packages: '@swc/helpers': 0.5.1 react: 18.2.0 - /@react-stately/toggle@3.7.0(react@18.2.0): - resolution: {integrity: sha512-TRksHkCJk/Xogq4181g3CYgJf+EfsJCqX5UZDSw1Z1Kgpvonjmdf6FAfQfCh9QR2OuXUL6hOLUDVLte5OPI+5g==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - dependencies: - '@react-stately/utils': 3.9.0(react@18.2.0) - '@react-types/checkbox': 3.6.0(react@18.2.0) - '@swc/helpers': 0.5.1 - react: 18.2.0 - dev: false - /@react-stately/toggle@3.7.2(react@18.2.0): resolution: {integrity: sha512-SHCF2btcoK57c4lyhucRbyPBAFpp0Pdp0vcPdn3hUgqbu6e5gE0CwG/mgFmZRAQoc7PRc7XifL0uNw8diJJI0Q==} peerDependencies: @@ -3225,17 +2947,6 @@ packages: '@swc/helpers': 0.5.1 react: 18.2.0 - /@react-stately/tooltip@3.4.6(react@18.2.0): - resolution: {integrity: sha512-uL93bmsXf+OOgpKLPEKfpDH4z+MK2CuqlqVxx7rshN0vjWOSoezE5nzwgee90+RpDrLNNNWTNa7n+NkDRpI1jA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - dependencies: - '@react-stately/overlays': 3.6.4(react@18.2.0) - '@react-types/tooltip': 3.4.6(react@18.2.0) - '@swc/helpers': 0.5.1 - react: 18.2.0 - dev: false - /@react-stately/tooltip@3.4.7(react@18.2.0): resolution: {integrity: sha512-ACtRgBQ8rphBtsUaaxvEAM0HHN9PvMuyvL0vUHd7jvBDCVZJ6it1BKu9SBKjekBkoBOw9nemtkplh9R2CA6V8Q==} peerDependencies: @@ -3246,19 +2957,6 @@ packages: '@swc/helpers': 0.5.1 react: 18.2.0 - /@react-stately/tree@3.7.4(react@18.2.0): - resolution: {integrity: sha512-0yvVODBS8WnSivLFX5ccEjCl2NA/8lbEt1E48wVcY1xcXgISNpw5MSGK5jC6YrtJPIqVolQIkNSbMreXGBktIg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - dependencies: - '@react-stately/collections': 3.10.3(react@18.2.0) - '@react-stately/selection': 3.14.1(react@18.2.0) - '@react-stately/utils': 3.9.0(react@18.2.0) - '@react-types/shared': 3.22.0(react@18.2.0) - '@swc/helpers': 0.5.1 - react: 18.2.0 - dev: false - /@react-stately/tree@3.7.6(react@18.2.0): resolution: {integrity: sha512-y8KvEoZX6+YvqjNCVGS3zA/BKw4D3XrUtUKIDme3gu5Mn6z97u+hUXKdXVCniZR7yvV3fHAIXwE5V2K8Oit4aw==} peerDependencies: @@ -3271,15 +2969,6 @@ packages: '@swc/helpers': 0.5.1 react: 18.2.0 - /@react-stately/utils@3.9.0(react@18.2.0): - resolution: {integrity: sha512-yPKFY1F88HxuZ15BG2qwAYxtpE4HnIU0Ofi4CuBE0xC6I8mwo4OQjDzi+DZjxQngM9D6AeTTD6F1V8gkozA0Gw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - dependencies: - '@swc/helpers': 0.5.1 - react: 18.2.0 - dev: false - /@react-stately/utils@3.9.1(react@18.2.0): resolution: {integrity: sha512-yzw75GE0iUWiyps02BOAPTrybcsMIxEJlzXqtvllAb01O9uX5n0i3X+u2eCpj2UoDF4zS08Ps0jPgWxg8xEYtA==} peerDependencies: @@ -3315,16 +3004,6 @@ packages: '@react-types/shared': 3.22.1(react@18.2.0) react: 18.2.0 - /@react-types/calendar@3.4.2(react@18.2.0): - resolution: {integrity: sha512-tCZ21un/8OAhpNtmSXDkOVvS5Pzp+y/JwNr6VGFi8HBC5F/c8SzuwV0jKN8ymsZSWbDQ68xXGNWxFaG43Bw8Pg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - dependencies: - '@internationalized/date': 3.5.0 - '@react-types/shared': 3.22.0(react@18.2.0) - react: 18.2.0 - dev: false - /@react-types/calendar@3.4.4(react@18.2.0): resolution: {integrity: sha512-hV1Thmb/AES5OmfPvvmyjSkmsEULjiDfA7Yyy70L/YKuSNKb7Su+Bf2VnZuDW3ec+GxO4JJNlpJ0AkbphWBvcg==} peerDependencies: @@ -3334,15 +3013,6 @@ packages: '@react-types/shared': 3.22.1(react@18.2.0) react: 18.2.0 - /@react-types/checkbox@3.6.0(react@18.2.0): - resolution: {integrity: sha512-vgbuJzQpVCNT5AZWV0OozXCnihqrXxoZKfJFIw0xro47pT2sn3t5UC4RA9wfjDGMoK4frw1K/4HQLsQIOsPBkw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - dependencies: - '@react-types/shared': 3.22.0(react@18.2.0) - react: 18.2.0 - dev: false - /@react-types/checkbox@3.7.1(react@18.2.0): resolution: {integrity: sha512-kuGqjQFex0As/3gfWyk+e9njCcad/ZdnYLLiNvhlk15730xfa0MmnOdpqo9jfuFSXBjOcpxoofvEhvrRMtEdUA==} peerDependencies: @@ -3359,27 +3029,6 @@ packages: '@react-types/shared': 3.22.1(react@18.2.0) react: 18.2.0 - /@react-types/combobox@3.9.0(react@18.2.0): - resolution: {integrity: sha512-VAQWM2jrIWROgcTKxj4k37WWpK/1zRjj1HfGeuenAQyOQwImqDwCHx5YxQR1GiUEFne4v1yXe2khT0T5Kt2vDg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - dependencies: - '@react-types/shared': 3.22.0(react@18.2.0) - react: 18.2.0 - dev: false - - /@react-types/datepicker@3.7.0(react@18.2.0): - resolution: {integrity: sha512-Uh+p6pZpMFc5ZBOns5TXCBbUvJp1KVROLBn2gk5dMEFVq78Qs1VFuAt4lwr9gQBOJrX5I/l65pRTwwWwAKxYtQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - dependencies: - '@internationalized/date': 3.5.0 - '@react-types/calendar': 3.4.2(react@18.2.0) - '@react-types/overlays': 3.8.4(react@18.2.0) - '@react-types/shared': 3.22.0(react@18.2.0) - react: 18.2.0 - dev: false - /@react-types/datepicker@3.7.2(react@18.2.0): resolution: {integrity: sha512-zThqFAdhQL1dqyVDsDSSTdfCjoD6634eyg/B0ZJfQxcLUR/5pch3v/gxBhbyCVDGMNHRWUWIJvY9DVOepuoSug==} peerDependencies: @@ -3400,15 +3049,6 @@ packages: '@react-types/shared': 3.22.1(react@18.2.0) react: 18.2.0 - /@react-types/grid@3.2.3(react@18.2.0): - resolution: {integrity: sha512-GQM4RDmYhstcYZ0Odjq+xUwh1fhLmRebG6qMM8OXHTPQ77nhl3wc1UTGRhZm6mzEionplSRx4GCpEMEHMJIU0w==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - dependencies: - '@react-types/shared': 3.22.0(react@18.2.0) - react: 18.2.0 - dev: false - /@react-types/grid@3.2.4(react@18.2.0): resolution: {integrity: sha512-sDVoyQcH7MoGdx5nBi5ZOU/mVFBt9YTxhvr0PZ97dMdEHZtJC1w9SuezwWS34f50yb8YAXQRTICbZYcK4bAlDA==} peerDependencies: @@ -3433,16 +3073,6 @@ packages: '@react-types/shared': 3.22.1(react@18.2.0) react: 18.2.0 - /@react-types/menu@3.9.6(react@18.2.0): - resolution: {integrity: sha512-w/RbFInOf4nNayQDv5c2L8IMJbcFOkBhsT3xvvpTy+CHvJcQdjggwaV1sRiw7eF/PwB81k2CwigmidUzHJhKDg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - dependencies: - '@react-types/overlays': 3.8.4(react@18.2.0) - '@react-types/shared': 3.22.0(react@18.2.0) - react: 18.2.0 - dev: false - /@react-types/menu@3.9.7(react@18.2.0): resolution: {integrity: sha512-K6KhloJVoGsqwkdeez72fkNI9dfrmLI/sNrB4XuOKo2crDQ/eyZYWyJmzz8giz/tHME9w774k487rVoefoFh5w==} peerDependencies: @@ -3460,15 +3090,6 @@ packages: '@react-types/progress': 3.5.2(react@18.2.0) react: 18.2.0 - /@react-types/numberfield@3.7.0(react@18.2.0): - resolution: {integrity: sha512-gaGi+vqm1Y8LCWRsWYUjcGftPIzl+8W2VOfkgKMLM8y76nnwTPtmAqs+Ap1cg7sEJSfsiKMq93e9yvP3udrC2w==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - dependencies: - '@react-types/shared': 3.22.0(react@18.2.0) - react: 18.2.0 - dev: false - /@react-types/numberfield@3.8.1(react@18.2.0): resolution: {integrity: sha512-GaCjLQgXUGCt40SLjKk3/COMWFlN2vV/3Xs3VSLAEdFZpk99b+Ik1oR21+7ZP5/iMHuQDc1MJRWdFfIjxCvVDQ==} peerDependencies: @@ -3477,15 +3098,6 @@ packages: '@react-types/shared': 3.22.1(react@18.2.0) react: 18.2.0 - /@react-types/overlays@3.8.4(react@18.2.0): - resolution: {integrity: sha512-pfgNlQnbF6RB/R2oSxyqAP3Uzz0xE/k5q4n5gUeCDNLjY5qxFHGE8xniZZ503nZYw6VBa9XMN1efDOKQyeiO0w==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - dependencies: - '@react-types/shared': 3.22.0(react@18.2.0) - react: 18.2.0 - dev: false - /@react-types/overlays@3.8.5(react@18.2.0): resolution: {integrity: sha512-4D7EEBQigD/m8hE68Ys8eloyyZFHHduqykSIgINJ0edmo0jygRbWlTwuhWFR9USgSP4dK54duN0Mvq0m4HEVEw==} peerDependencies: @@ -3502,15 +3114,6 @@ packages: '@react-types/shared': 3.22.1(react@18.2.0) react: 18.2.0 - /@react-types/radio@3.6.0(react@18.2.0): - resolution: {integrity: sha512-VOZzegxxZS55gHRVyWu278Q4y/rEQGiAVQCUqi25GmpbMe4MlHrzg16c76RiZMUK9PPoyv+XNUgAaPmxebkn7g==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - dependencies: - '@react-types/shared': 3.22.0(react@18.2.0) - react: 18.2.0 - dev: false - /@react-types/radio@3.7.1(react@18.2.0): resolution: {integrity: sha512-Zut3rN1odIUBLZdijeyou+UqsLeRE76d9A+npykYGu29ndqmo3w4sLn8QeQcdj1IR71ZnG0pW2Y2BazhK5XrrQ==} peerDependencies: @@ -3519,16 +3122,6 @@ packages: '@react-types/shared': 3.22.1(react@18.2.0) react: 18.2.0 - /@react-types/searchfield@3.5.2(react@18.2.0): - resolution: {integrity: sha512-JAK2/Kg4Dr393FYfbRw0TlXKnJPX77sq1x/ZBxtO6p64+MuuIYKqw0i9PwDlo1PViw2QI5u8GFhKA2TgemY9uA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - dependencies: - '@react-types/shared': 3.22.0(react@18.2.0) - '@react-types/textfield': 3.9.0(react@18.2.0) - react: 18.2.0 - dev: false - /@react-types/searchfield@3.5.3(react@18.2.0): resolution: {integrity: sha512-gBfsT1WpY8UIb74yyYmnjiHpVasph2mdmGj9i8cGF2HUYwx5p+Fr85mtCGDph0uirvRoM5ExMp4snD+ueNAVCg==} peerDependencies: @@ -3538,15 +3131,6 @@ packages: '@react-types/textfield': 3.9.1(react@18.2.0) react: 18.2.0 - /@react-types/select@3.9.0(react@18.2.0): - resolution: {integrity: sha512-0nalGmcoma4jreICLSJae/uKAuMiVyWgqWjGrGiUGGcdDchH4limKVEqNDaBwLvxVT6NB5LLsaipCTCAEEl4Rg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - dependencies: - '@react-types/shared': 3.22.0(react@18.2.0) - react: 18.2.0 - dev: false - /@react-types/select@3.9.2(react@18.2.0): resolution: {integrity: sha512-fGFrunednY3Pq/BBwVOf87Fsuyo/SlevL0wFIE9OOl2V5NXVaTY7/7RYA8hIOHPzmvsMbndy419BEudiNGhv4A==} peerDependencies: @@ -3555,14 +3139,6 @@ packages: '@react-types/shared': 3.22.1(react@18.2.0) react: 18.2.0 - /@react-types/shared@3.22.0(react@18.2.0): - resolution: {integrity: sha512-yVOekZWbtSmmiThGEIARbBpnmUIuePFlLyctjvCbgJgGhz8JnEJOipLQ/a4anaWfzAgzSceQP8j/K+VOOePleA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - dependencies: - react: 18.2.0 - dev: false - /@react-types/shared@3.22.1(react@18.2.0): resolution: {integrity: sha512-PCpa+Vo6BKnRMuOEzy5zAZ3/H5tnQg1e80khMhK2xys0j6ZqzkgQC+fHMNZ7VDFNLqqNMj/o0eVeSBDh2POjkw==} peerDependencies: @@ -3570,15 +3146,6 @@ packages: dependencies: react: 18.2.0 - /@react-types/slider@3.7.0(react@18.2.0): - resolution: {integrity: sha512-uyQXUVFfqc9SPUW0LZLMan2n232F/OflRafiHXz9viLFa9tVOupVa7GhASRAoHojwkjoJ1LjFlPih7g5dOZ0/Q==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - dependencies: - '@react-types/shared': 3.22.0(react@18.2.0) - react: 18.2.0 - dev: false - /@react-types/slider@3.7.1(react@18.2.0): resolution: {integrity: sha512-FKO3YZYdrBs00XbBW5acP+0L1cCdevl/uRJiXbnLpGysO5PrSFIRS7Wlv4M7ztf6gT7b1Ao4FNC9crbxBr6BzA==} peerDependencies: @@ -3595,16 +3162,6 @@ packages: '@react-types/shared': 3.22.1(react@18.2.0) react: 18.2.0 - /@react-types/table@3.9.1(react@18.2.0): - resolution: {integrity: sha512-3e+Oouw9jGqNDg+JRg7v7fgPqDZd6DtST9S/UPp81f32ntnQ8Wsu7S/J4eyLHu5CVQDqcHkf4xPeeXBgPx4qmw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - dependencies: - '@react-types/grid': 3.2.3(react@18.2.0) - '@react-types/shared': 3.22.0(react@18.2.0) - react: 18.2.0 - dev: false - /@react-types/table@3.9.3(react@18.2.0): resolution: {integrity: sha512-Hs/pMbxJdga2zBol4H5pV1FVIiRjCuSTXst6idJjkctanTexR4xkyrtBwl+rdLNoGwQ2pGii49vgklc5bFK7zA==} peerDependencies: @@ -3614,15 +3171,6 @@ packages: '@react-types/shared': 3.22.1(react@18.2.0) react: 18.2.0 - /@react-types/tabs@3.3.4(react@18.2.0): - resolution: {integrity: sha512-4mCTtFrwMRypyGTZCvNYVT9CkknexO/UYvqwDm2jMYb8JgjRvxnomu776Yh7uyiYKWyql2upm20jqasEOm620w==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - dependencies: - '@react-types/shared': 3.22.0(react@18.2.0) - react: 18.2.0 - dev: false - /@react-types/tabs@3.3.5(react@18.2.0): resolution: {integrity: sha512-6NTSZBOWekCtApdZrhu5tHhE/8q52oVohQN+J5T7shAXd6ZAtu8PABVR/nH4BWucc8FL0OUajRqunqzQMU13gA==} peerDependencies: @@ -3631,15 +3179,6 @@ packages: '@react-types/shared': 3.22.1(react@18.2.0) react: 18.2.0 - /@react-types/textfield@3.9.0(react@18.2.0): - resolution: {integrity: sha512-D/DiwzsfkwlAg3uv8hoIfwju+zhB/hWDEdTvxQbPkntDr0kmN/QfI17NMSzbOBCInC4ABX87ViXLGxr940ykGA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - dependencies: - '@react-types/shared': 3.22.0(react@18.2.0) - react: 18.2.0 - dev: false - /@react-types/textfield@3.9.1(react@18.2.0): resolution: {integrity: sha512-JBHY9M2CkL6xFaGSfWmUJVu3tEK09FaeB1dU3IEh6P41xxbFnPakYHSSAdnwMXBtXPoSHIVsUBickW/pjgfe5g==} peerDependencies: @@ -3648,16 +3187,6 @@ packages: '@react-types/shared': 3.22.1(react@18.2.0) react: 18.2.0 - /@react-types/tooltip@3.4.6(react@18.2.0): - resolution: {integrity: sha512-RaZewdER7ZcsNL99RhVHs8kSLyzIBkwc0W6eFZrxST2MD9J5GzkVWRhIiqtFOd5U1aYnxdJ6woq72Ef+le6Vfw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - dependencies: - '@react-types/overlays': 3.8.4(react@18.2.0) - '@react-types/shared': 3.22.0(react@18.2.0) - react: 18.2.0 - dev: false - /@react-types/tooltip@3.4.7(react@18.2.0): resolution: {integrity: sha512-rV4HZRQxLRNhe24yATOxnFQtGRUmsR7mqxMupXCmd1vrw8h+rdKlQv1zW2q8nALAKNmnRXZJHxYQ1SFzb98fgg==} peerDependencies: @@ -9627,28 +9156,28 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 dependencies: - '@react-stately/calendar': 3.4.2(react@18.2.0) - '@react-stately/checkbox': 3.6.0(react@18.2.0) - '@react-stately/collections': 3.10.3(react@18.2.0) - '@react-stately/combobox': 3.8.0(react@18.2.0) + '@react-stately/calendar': 3.4.4(react@18.2.0) + '@react-stately/checkbox': 3.6.3(react@18.2.0) + '@react-stately/collections': 3.10.5(react@18.2.0) + '@react-stately/combobox': 3.8.2(react@18.2.0) '@react-stately/data': 3.10.0(react@18.2.0) - '@react-stately/datepicker': 3.9.0(react@18.2.0) - '@react-stately/dnd': 3.2.6(react@18.2.0) - '@react-stately/list': 3.10.1(react@18.2.0) - '@react-stately/menu': 3.5.7(react@18.2.0) - '@react-stately/numberfield': 3.7.0(react@18.2.0) - '@react-stately/overlays': 3.6.4(react@18.2.0) - '@react-stately/radio': 3.10.0(react@18.2.0) - '@react-stately/searchfield': 3.5.0(react@18.2.0) - '@react-stately/select': 3.6.0(react@18.2.0) - '@react-stately/selection': 3.14.1(react@18.2.0) - '@react-stately/slider': 3.4.5(react@18.2.0) - '@react-stately/table': 3.11.3(react@18.2.0) - '@react-stately/tabs': 3.6.2(react@18.2.0) - '@react-stately/toggle': 3.7.0(react@18.2.0) - '@react-stately/tooltip': 3.4.6(react@18.2.0) - '@react-stately/tree': 3.7.4(react@18.2.0) - '@react-types/shared': 3.22.0(react@18.2.0) + '@react-stately/datepicker': 3.9.2(react@18.2.0) + '@react-stately/dnd': 3.2.8(react@18.2.0) + '@react-stately/list': 3.10.3(react@18.2.0) + '@react-stately/menu': 3.6.1(react@18.2.0) + '@react-stately/numberfield': 3.9.1(react@18.2.0) + '@react-stately/overlays': 3.6.5(react@18.2.0) + '@react-stately/radio': 3.10.2(react@18.2.0) + '@react-stately/searchfield': 3.5.1(react@18.2.0) + '@react-stately/select': 3.6.2(react@18.2.0) + '@react-stately/selection': 3.14.3(react@18.2.0) + '@react-stately/slider': 3.5.2(react@18.2.0) + '@react-stately/table': 3.11.6(react@18.2.0) + '@react-stately/tabs': 3.6.4(react@18.2.0) + '@react-stately/toggle': 3.7.2(react@18.2.0) + '@react-stately/tooltip': 3.4.7(react@18.2.0) + '@react-stately/tree': 3.7.6(react@18.2.0) + '@react-types/shared': 3.22.1(react@18.2.0) react: 18.2.0 dev: false diff --git a/coral/src/app/components/Clipboard.test.tsx b/coral/src/app/components/Clipboard.test.tsx new file mode 100644 index 0000000000..bc54a369a6 --- /dev/null +++ b/coral/src/app/components/Clipboard.test.tsx @@ -0,0 +1,64 @@ +import { render, screen, cleanup, waitFor } from "@testing-library/react"; +import { userEvent } from "@testing-library/user-event"; +import ClipBoard from "src/app/components/Clipboard"; + +describe("ClipBoard", () => { + const mockCopyToClipboard = jest.fn(); + Object.defineProperty(global.navigator, "clipboard", { + value: { writeText: mockCopyToClipboard }, + writable: true, + }); + + const props = { + text: "Test text", + accessibleCopyDescription: "Copy to clipboard", + accessibleCopiedDescription: "Copied to clipboard", + description: "Test description", + }; + + beforeEach(() => { + render(); + }); + + afterEach(() => { + cleanup(); + jest.clearAllMocks(); + }); + + it("renders correctly", () => { + const button = screen.getByRole("button", { + name: props.accessibleCopyDescription, + }); + expect(button).toBeVisible(); + }); + + it("copies text to clipboard when button is clicked", async () => { + const button = screen.getByRole("button", { + name: props.accessibleCopyDescription, + }); + await userEvent.click(button); + await waitFor(() => + expect(mockCopyToClipboard).toHaveBeenCalledWith(props.text) + ); + }); + + it("renders feedback tooltip after button is clicked", async () => { + const button = screen.getByRole("button", { + name: props.accessibleCopyDescription, + }); + await userEvent.click(button); + await waitFor(() => expect(screen.getByText("Copied")).toBeVisible()); + }); + + it("renders accessible feedback after button is clicked", async () => { + const button = screen.getByRole("button", { + name: props.accessibleCopyDescription, + }); + await userEvent.click(button); + await waitFor(() => + expect( + screen.getByLabelText(props.accessibleCopiedDescription) + ).toBeInTheDocument() + ); + }); +}); diff --git a/coral/src/app/components/Clipboard.tsx b/coral/src/app/components/Clipboard.tsx new file mode 100644 index 0000000000..55bb361609 --- /dev/null +++ b/coral/src/app/components/Clipboard.tsx @@ -0,0 +1,85 @@ +import { Tooltip, PositionerPlacement, Button } from "@aivenio/aquarium"; +import duplicate from "@aivenio/aquarium/icons/duplicate"; +import { useRef, useState, useEffect, FormEvent } from "react"; + +const copyToClipboard = async (text: string): Promise => { + try { + await navigator.clipboard.writeText(text); + } catch (error) { + console.error("Failed to copy to clipboard:", error); + } +}; + +const ClipBoard = ({ + text, + accessibleCopyDescription, + accessibleCopiedDescription, + description, +}: { + text: string; + accessibleCopyDescription: string; + accessibleCopiedDescription: string; + description?: string; +}) => { + const feedbackTimerRef = useRef(); + + const [showCopyFeedback, setShowCopyFeedback] = useState(false); + + const clearTimeouts = () => { + window.clearTimeout(feedbackTimerRef.current); + }; + + useEffect(() => () => clearTimeouts(), []); + + function handleCopy(event: FormEvent) { + event.preventDefault(); + copyToClipboard(text); + setShowCopyFeedback(true); + feedbackTimerRef.current = window.setTimeout( + () => setShowCopyFeedback(false), + 1000 + ); + } + + if (showCopyFeedback) { + return ( + + + {description} + +
+ {accessibleCopiedDescription} +
+
+ ); + } + + return ( + + + {description} + + + ); +}; + +export default ClipBoard; diff --git a/coral/src/app/components/Modal.tsx b/coral/src/app/components/Modal.tsx index f3003c6cc9..76237c7d12 100644 --- a/coral/src/app/components/Modal.tsx +++ b/coral/src/app/components/Modal.tsx @@ -125,7 +125,7 @@ function Modal(props: ModalProps) { flexDirection={"column"} borderRadius={"4px"} backgroundColor={"white"} - width={"6/12"} + width={"7/12"} // value is arbitrary, it should prevent buttons overflowing // the modal in a very small screen //eslint-disable-next-line @typescript-eslint/ban-ts-comment diff --git a/coral/src/app/features/components/filters/SearchClusterParamFilter.test.tsx b/coral/src/app/features/components/filters/SearchClusterParamFilter.test.tsx index 79066cb929..3926993b0d 100644 --- a/coral/src/app/features/components/filters/SearchClusterParamFilter.test.tsx +++ b/coral/src/app/features/components/filters/SearchClusterParamFilter.test.tsx @@ -13,7 +13,7 @@ describe("SearchClusterParamFilter.tsx", () => { it("renders a search input", () => { const searchInput = screen.getByRole("search", { - name: "Search Cluster params", + name: "Search Cluster parameters", }); expect(searchInput).toBeEnabled(); @@ -21,7 +21,7 @@ describe("SearchClusterParamFilter.tsx", () => { it("shows a placeholder with an example search value", () => { const searchInput = screen.getByRole("search", { - name: "Search Cluster params", + name: "Search Cluster parameters", }); expect(searchInput.placeholder).toEqual("kafkaconnect"); @@ -29,7 +29,7 @@ describe("SearchClusterParamFilter.tsx", () => { it("shows a description", () => { const searchInput = screen.getByRole("search", { - name: "Search Cluster params", + name: "Search Cluster parameters", }); expect(searchInput).toHaveAccessibleDescription( diff --git a/coral/src/app/features/components/filters/SearchClusterParamFilter.tsx b/coral/src/app/features/components/filters/SearchClusterParamFilter.tsx index 617a51cc91..404943dbb8 100644 --- a/coral/src/app/features/components/filters/SearchClusterParamFilter.tsx +++ b/coral/src/app/features/components/filters/SearchClusterParamFilter.tsx @@ -3,7 +3,7 @@ import { SearchFilter } from "src/app/features/components/filters/SearchFilter"; function SearchClusterParamFilter() { return ( ", () => { name: "Kafka Connect", }); const schemaRegistryOption = screen.getByRole("radio", { - name: "Schema registry", + name: "Schema Registry", }); const protocolSelect = screen.getByRole("combobox", { name: "Protocol *", @@ -280,7 +280,7 @@ describe("", () => { position: "bottom-left", }); expect(mockedUsedNavigate).toHaveBeenCalledWith( - "/configuration/clusters?search=MyCluster" + "/configuration/clusters?search=MyCluster&showConnectHelp=true" ); }); diff --git a/coral/src/app/features/configuration/clusters/AddNewClusterForm.tsx b/coral/src/app/features/configuration/clusters/AddNewClusterForm.tsx index 7af1a81cdb..919a30fd47 100644 --- a/coral/src/app/features/configuration/clusters/AddNewClusterForm.tsx +++ b/coral/src/app/features/configuration/clusters/AddNewClusterForm.tsx @@ -24,6 +24,8 @@ import { import { addNewCluster } from "src/domain/cluster"; import { parseErrorMsg } from "src/services/mutation-utils"; import { Routes } from "src/services/router-utils/types"; +import { kafkaFlavorToString } from "src/services/formatter/kafka-flavor-formatter"; +import { clusterTypeToString } from "src/services/formatter/cluster-type-formatter"; const AddNewClusterForm = () => { const navigate = useNavigate(); @@ -54,7 +56,7 @@ const AddNewClusterForm = () => { const addNewClusterMutation = useMutation(addNewCluster, { onSuccess: () => { - navigate(`${Routes.CLUSTERS}?search=${clusterName}`); + navigate(`${Routes.CLUSTERS}?search=${clusterName}&showConnectHelp=true`); toast({ message: "Cluster successfully added", position: "bottom-left", @@ -89,13 +91,13 @@ const AddNewClusterForm = () => { required > - Kafka + {clusterTypeToString["KAFKA"]} - Kafka Connect + {clusterTypeToString["KAFKA_CONNECT"]} - Schema registry + {clusterTypeToString["SCHEMA_REGISTRY"]} @@ -136,11 +138,17 @@ const AddNewClusterForm = () => { placeholder="-- Please select --" required > - - - - - + + + + + {kafkaFlavor === "AIVEN_FOR_APACHE_KAFKA" && clusterType === "KAFKA" && ( diff --git a/coral/src/app/features/configuration/clusters/Clusters.test.tsx b/coral/src/app/features/configuration/clusters/Clusters.test.tsx index c680990f87..e45da2af90 100644 --- a/coral/src/app/features/configuration/clusters/Clusters.test.tsx +++ b/coral/src/app/features/configuration/clusters/Clusters.test.tsx @@ -160,7 +160,7 @@ describe("Clusters.tsx", () => { it("renders a search field for cluster params", () => { const search = screen.getByRole("search", { - name: "Search Cluster params", + name: "Search Cluster parameters", }); expect(search).toBeEnabled(); @@ -289,7 +289,7 @@ describe("Clusters.tsx", () => { const testSearchInput = "MyCluster"; const search = screen.getByRole("search", { - name: "Search Cluster params", + name: "Search Cluster parameters", }); await user.type(search, testSearchInput); @@ -304,4 +304,44 @@ describe("Clusters.tsx", () => { ); }); }); + + describe("renders ClusterConnectHelpModal on first load when needed", () => { + beforeAll(async () => { + mockGetClustersPaginated.mockResolvedValue({ + currentPage: 1, + totalPages: 1, + entries: [testCluster[0]], + }); + + customRender(, { + queryClient: true, + memoryRouter: true, + customRoutePath: `/configuration/clusters?search=${testCluster[0].clusterName}&showConnectHelp=true`, + }); + + await waitForElementToBeRemoved(screen.getByTestId("skeleton-table")); + }); + + afterAll(() => { + cleanup(); + jest.resetAllMocks(); + }); + + it("renders ClusterConnectHelpModal on first render if correct query params are set", () => { + const modal = screen.getByRole("dialog"); + expect(modal).toBeVisible(); + expect(within(modal).getByText("Connect cluster to Klaw")).toBeVisible(); + }); + + it("remove showConnectHelp query param when closing ClusterConnectHelpModal ", async () => { + const modal = screen.getByRole("dialog"); + expect(modal).toBeVisible(); + + const closeButton = within(modal).getByRole("button", { name: "Done" }); + + await userEvent.click(closeButton); + + expect(window.location.search).not.toContain("showConnectHelp=true"); + }); + }); }); diff --git a/coral/src/app/features/configuration/clusters/Clusters.tsx b/coral/src/app/features/configuration/clusters/Clusters.tsx index d168361b59..18f1f2c298 100644 --- a/coral/src/app/features/configuration/clusters/Clusters.tsx +++ b/coral/src/app/features/configuration/clusters/Clusters.tsx @@ -1,16 +1,30 @@ import { useQuery } from "@tanstack/react-query"; -import { TableLayout } from "src/app/features/components/layouts/TableLayout"; -import { getClustersPaginated } from "src/domain/cluster"; -import { ClustersTable } from "src/app/features/configuration/clusters/components/ClustersTable"; +import { useEffect, useState } from "react"; import { useSearchParams } from "react-router-dom"; import { Pagination } from "src/app/components/Pagination"; +import { useAuthContext } from "src/app/context-provider/AuthProvider"; +import { SearchClusterParamFilter } from "src/app/features/components/filters/SearchClusterParamFilter"; import { useFiltersContext, withFiltersContext, } from "src/app/features/components/filters/useFiltersContext"; -import { SearchClusterParamFilter } from "src/app/features/components/filters/SearchClusterParamFilter"; +import { TableLayout } from "src/app/features/components/layouts/TableLayout"; +import ClusterConnectHelpModal from "src/app/features/configuration/clusters/components/ClusterConnectHelpModal"; +import { ClustersTable } from "src/app/features/configuration/clusters/components/ClustersTable"; +import { ClusterDetails, getClustersPaginated } from "src/domain/cluster"; + +// We add this default data to avoid dealing with the fact that it may be undefined when closing the modal... +// ... and resetting the state +const DEFAULT_MODAL_DATA: ClusterModal["data"] = { + kafkaFlavor: "AIVEN_FOR_APACHE_KAFKA", + protocol: "PLAINTEXT", + clusterType: "KAFKA", + clusterName: "", + clusterId: 0, +}; function Clusters() { + const { permissions } = useAuthContext(); const [searchParams, setSearchParams] = useSearchParams(); const currentPage = searchParams.get("page") ? Number(searchParams.get("page")) @@ -32,6 +46,56 @@ function Clusters() { keepPreviousData: true, }); + const [modal, setModal] = useState({ + show: false, + data: DEFAULT_MODAL_DATA, + }); + + // Automatically open the Connect mhelp modal after adding a cluster. + // We know how to populate the modal data from the fact that we redirect... + // ... to a filtered table from the AddNewClusterForm, along with a specific query param + // For example: /configuration/clusters?search=MyCluster&showConnectHelp=true + useEffect(() => { + const showConnectHelp = searchParams.get("showConnectHelp") !== null; + const latestAddedCluster = showConnectHelp + ? clusters?.entries[0] + : undefined; + const isShowingOnlyLatestAddedCluster = + // If there is more than one cluster resulting from the searchClusterParam filter, we should not show the modal + // As we currently can't know which one was the latest added + clusters?.entries.length === 1 && + latestAddedCluster !== undefined && + search.length !== 0; + + if (showConnectHelp && isShowingOnlyLatestAddedCluster) { + const { kafkaFlavor, protocol, clusterType, clusterName, clusterId } = + latestAddedCluster; + + handleShowModal({ + show: true, + data: { + kafkaFlavor, + protocol, + clusterType, + clusterName, + clusterId, + }, + }); + } + }); + + function handleShowModal({ show, data }: ClusterModal) { + setModal({ + show, + data, + }); + + // If showConnectHelp is present, remove it on next call of handleShowModal + // Most probably when closing the modal opened automatically after adding a cluster + searchParams.delete("showConnectHelp"); + setSearchParams(searchParams); + } + function handleChangePage(page: number) { searchParams.set("page", page.toString()); setSearchParams(searchParams); @@ -47,22 +111,50 @@ function Clusters() { ) : undefined; return ( - ]} - table={ - + {modal.show && ( + + setModal({ + show: false, + data: DEFAULT_MODAL_DATA, + }) + } + modalData={modal.data} /> - } - isLoading={isLoading} - isErrorLoading={isError} - errorMessage={error} - pagination={pagination} - /> + )} + + ]} + table={ + + } + isLoading={isLoading} + isErrorLoading={isError} + errorMessage={error} + pagination={pagination} + /> + ); } +export interface ClusterModal { + show: boolean; + data: { + kafkaFlavor: ClusterDetails["kafkaFlavor"]; + protocol: ClusterDetails["protocol"]; + clusterType: ClusterDetails["clusterType"]; + clusterName: ClusterDetails["clusterName"]; + clusterId: ClusterDetails["clusterId"]; + }; +} + export default withFiltersContext({ element: }); diff --git a/coral/src/app/features/configuration/clusters/components/ClusterConnectHelpModal.test.tsx b/coral/src/app/features/configuration/clusters/components/ClusterConnectHelpModal.test.tsx new file mode 100644 index 0000000000..bffc840b16 --- /dev/null +++ b/coral/src/app/features/configuration/clusters/components/ClusterConnectHelpModal.test.tsx @@ -0,0 +1,101 @@ +import { cleanup, render, screen } from "@testing-library/react"; +import { userEvent } from "@testing-library/user-event"; +import ClusterConnectHelpModal from "src/app/features/configuration/clusters/components/ClusterConnectHelpModal"; +import { ClusterDetails } from "src/domain/cluster"; + +const onClose = jest.fn(); +const modalDataWithApplicationPropertiesToCopy: { + kafkaFlavor: ClusterDetails["kafkaFlavor"]; + protocol: ClusterDetails["protocol"]; + clusterType: ClusterDetails["clusterType"]; + clusterName: ClusterDetails["clusterName"]; + clusterId: ClusterDetails["clusterId"]; +} = { + kafkaFlavor: "APACHE_KAFKA", + protocol: "SSL", + clusterType: "KAFKA", + clusterName: "TestCluster", + clusterId: 123, +}; +const modalDataWithoutApplicationPropertiesToCopy: { + kafkaFlavor: ClusterDetails["kafkaFlavor"]; + protocol: ClusterDetails["protocol"]; + clusterType: ClusterDetails["clusterType"]; + clusterName: ClusterDetails["clusterName"]; + clusterId: ClusterDetails["clusterId"]; +} = { + kafkaFlavor: "APACHE_KAFKA", + protocol: "PLAINTEXT", + clusterType: "KAFKA", + clusterName: "TestCluster", + clusterId: 123, +}; + +describe("ClusterConnectHelpModal", () => { + afterEach(() => { + cleanup(); + jest.clearAllMocks(); + }); + + it("renders with applicationPropertiesString", async () => { + render( + + ); + const expectedApplicationPropertiesString = `${modalDataWithApplicationPropertiesToCopy.clusterName.toLowerCase() + modalDataWithApplicationPropertiesToCopy.clusterId}.kafkassl.keystore.location=path/to/client.keystore.p12\n${modalDataWithApplicationPropertiesToCopy.clusterName.toLowerCase() + modalDataWithApplicationPropertiesToCopy.clusterId}.kafkassl.keystore.pwd=keystorePw\n${modalDataWithApplicationPropertiesToCopy.clusterName.toLowerCase() + modalDataWithApplicationPropertiesToCopy.clusterId}.kafkassl.key.pwd=keyPw\n${modalDataWithApplicationPropertiesToCopy.clusterName.toLowerCase() + modalDataWithApplicationPropertiesToCopy.clusterId}.kafkassl.truststore.location=path/to/client.truststore.jks\n${modalDataWithApplicationPropertiesToCopy.clusterName.toLowerCase() + modalDataWithApplicationPropertiesToCopy.clusterId}.kafkassl.truststore.pwd=truststorePw\n${modalDataWithApplicationPropertiesToCopy.clusterName.toLowerCase() + modalDataWithApplicationPropertiesToCopy.clusterId}.kafkassl.keystore.type=pkcs12\n${modalDataWithApplicationPropertiesToCopy.clusterName.toLowerCase() + modalDataWithApplicationPropertiesToCopy.clusterId}.kafkassl.truststore.type=JKS`; + + expect(screen.getByText("Connect cluster to Klaw")).toBeVisible(); + expect( + screen.getByText(/Copy, paste, and replace placeholders/) + ).toBeVisible(); + expect(screen.getByRole("link", { name: /Learn more/ })).toHaveAttribute( + "href", + "https://www.klaw-project.io/docs/cluster-connectivity-setup/" + ); + // Contraption needed to assert the expectedApplicationPropertiesString when it is broken on multiple lines + expect( + screen.getByText((_, node) => { + const hasText = (node: Element) => + node.textContent === expectedApplicationPropertiesString; + const nodeHasText = node ? hasText(node) : false; + const childrenDontHaveText = Array.from(node?.children || []).every( + (child) => !hasText(child) + ); + + return nodeHasText && childrenDontHaveText; + }) + ).toBeVisible(); + + await userEvent.click(screen.getByRole("button", { name: "Done" })); + + expect(onClose).toHaveBeenCalledTimes(1); + }); + + it("renders without applicationPropertiesString", async () => { + render( + + ); + + expect(screen.getByText("Connect cluster to Klaw")).toBeVisible(); + expect( + screen.queryByText(/Copy, paste, and replace placeholders/) + ).toBeNull(); + expect( + screen.getByRole("link", { + name: /Learn more about cluster connectivity setup/, + }) + ).toHaveAttribute( + "href", + "https://www.klaw-project.io/docs/cluster-connectivity-setup/" + ); + + await userEvent.click(screen.getByRole("button", { name: "Done" })); + + expect(onClose).toHaveBeenCalledTimes(1); + }); +}); diff --git a/coral/src/app/features/configuration/clusters/components/ClusterConnectHelpModal.tsx b/coral/src/app/features/configuration/clusters/components/ClusterConnectHelpModal.tsx new file mode 100644 index 0000000000..e26aca07d4 --- /dev/null +++ b/coral/src/app/features/configuration/clusters/components/ClusterConnectHelpModal.tsx @@ -0,0 +1,160 @@ +import { Box, Icon, Spacing, Typography } from "@aivenio/aquarium"; +import link from "@aivenio/aquarium/icons/link"; +import ClipBoard from "src/app/components/Clipboard"; +import { Modal } from "src/app/components/Modal"; +import { ClusterDetails } from "src/domain/cluster"; + +const getApplicationPropertiesString = ({ + clusterName, + clusterId, + kafkaFlavor, + clusterType, + protocol, +}: { + clusterName: ClusterDetails["clusterName"]; + clusterId: ClusterDetails["clusterId"]; + kafkaFlavor: ClusterDetails["kafkaFlavor"]; + clusterType: ClusterDetails["clusterType"]; + protocol: ClusterDetails["protocol"]; +}) => { + if (clusterType === "KAFKA" && kafkaFlavor === "APACHE_KAFKA") { + if (protocol === "SSL") { + return `${clusterName.toLowerCase() + clusterId}.kafkassl.keystore.location=path/to/client.keystore.p12\n${clusterName.toLowerCase() + clusterId}.kafkassl.keystore.pwd=keystorePw\n${clusterName.toLowerCase() + clusterId}.kafkassl.key.pwd=keyPw\n${clusterName.toLowerCase() + clusterId}.kafkassl.truststore.location=path/to/client.truststore.jks\n${clusterName.toLowerCase() + clusterId}.kafkassl.truststore.pwd=truststorePw\n${clusterName.toLowerCase() + clusterId}.kafkassl.keystore.type=pkcs12\n${clusterName.toLowerCase() + clusterId}.kafkassl.truststore.type=JKS`; + } + if (protocol === "SASL_PLAIN" || protocol === "SASL_SSL_PLAIN_MECHANISM") { + return `${clusterName.toLowerCase() + clusterId}.kafkasasl.jaasconfig.plain=org.apache.kafka.common.security.plain.PlainLoginModule\nrequired username='kwuser' password='kwuser-secret'`; + } + if ( + protocol === "SASL_SSL_SCRAM_MECHANISM_256" || + protocol === "SASL_SSL_SCRAM_MECHANISM_512" + ) { + return `${clusterName.toLowerCase() + clusterId}.kafkasasl.jaasconfig.scram=org.apache.kafka.common.security.scram.ScramLoginModule\nrequired username='kwuser' password='kwuser-secret'`; + } + if (protocol === "SASL_SSL_GSSAPI_MECHANISM") { + return `${clusterName.toLowerCase() + clusterId}.kafkasasl.jaasconfig.gssapi=com.sun.security.auth.module.Krb5LoginModule\nrequired useKeyTab=true storeKey=true keyTab="/path/to/kafka_client.keytab"\n"`; + } + } + if (clusterType === "KAFKA" && kafkaFlavor === "AIVEN_FOR_APACHE_KAFKA") { + if (protocol === "SSL") { + return `${clusterName.toLowerCase() + clusterId}.kafkassl.keystore.location=path/to/client.keystore.p12\n${clusterName.toLowerCase() + clusterId}.kafkassl.keystore.pwd=keystorePw\n${clusterName.toLowerCase() + clusterId}.kafkassl.key.pwd=keyPw\n${clusterName.toLowerCase() + clusterId}.kafkassl.truststore.location=path/to/client.truststore.jks\n${clusterName.toLowerCase() + clusterId}.kafkassl.truststore.pwd=truststorePw\n${clusterName.toLowerCase() + clusterId}.kafkassl.keystore.type=pkcs12\n${clusterName.toLowerCase() + clusterId}.kafkassl.truststore.type=JKS\nklaw.clusters.accesstoken=yourAivenAccessToken`; + } + if (protocol === "SASL_PLAIN" || protocol === "SASL_SSL_PLAIN_MECHANISM") { + return `${clusterName.toLowerCase() + clusterId}.kafkasasl.jaasconfig.plain=org.apache.kafka.common.security.plain.PlainLoginModule\nrequired username='kwuser' password='kwuser-secret'\nklaw.clusters.accesstoken=yourAivenAccessToken`; + } + if ( + protocol === "SASL_SSL_SCRAM_MECHANISM_256" || + protocol === "SASL_SSL_SCRAM_MECHANISM_512" + ) { + return `${clusterName.toLowerCase() + clusterId}.kafkasasl.jaasconfig.scram=org.apache.kafka.common.security.scram.ScramLoginModule\nrequired username='kwuser' password='kwuser-secret'\nklaw.clusters.accesstoken=yourAivenAccessToken`; + } + if (protocol === "SASL_SSL_GSSAPI_MECHANISM") { + return `${clusterName.toLowerCase() + clusterId}.kafkasasl.jaasconfig.gssapi=com.sun.security.auth.module.Krb5LoginModule\nrequired useKeyTab=true storeKey=true keyTab="/path/to/kafka_client.keytab"\n\nklaw.clusters.accesstoken=yourAivenAccessToken"`; + } + } + + if (clusterType === "SCHEMA_REGISTRY" && protocol === "SSL") { + return `${clusterName.toLowerCase() + clusterId}.klaw.schemaregistry.credentials=username:password`; + } + + if (clusterType === "KAFKA_CONNECT") { + if (kafkaFlavor === "APACHE_KAFKA" && protocol === "SSL") { + return `${clusterName.toLowerCase() + clusterId}.klaw.kafkaconnect.credentials=username:password`; + } + if (kafkaFlavor === "AIVEN_FOR_APACHE_KAFKA") { + return `${clusterName.toLowerCase() + clusterId}.klaw.kafkaconnect.credentials=aivenUsername:aivenPassword`; + } + if (kafkaFlavor === "CONFLUENT_CLOUD") { + return `${clusterName.toLowerCase() + clusterId}.klaw.confluentcloud.credentials=confluentApikey:confluentApisecret\n${clusterName.toLowerCase() + clusterId}.klaw.clusters.counfluentcloud.acls.api=/kafka/v3/clusters/${clusterId}/acls\n${clusterName.toLowerCase() + clusterId}.klaw.clusters.counfluentcloud.topics.api=/kafka/v3/clusters/${clusterId}/topics\n`; + } + } + + // Default case: no necessary application.properties to copy + return ""; +}; + +interface ClusterConnectHelpModalProps { + onClose: () => void; + modalData: { + kafkaFlavor: ClusterDetails["kafkaFlavor"]; + protocol: ClusterDetails["protocol"]; + clusterType: ClusterDetails["clusterType"]; + clusterName: ClusterDetails["clusterName"]; + clusterId: ClusterDetails["clusterId"]; + }; +} + +const ClusterConnectHelpModal = ({ + onClose, + modalData: { kafkaFlavor, protocol, clusterType, clusterName, clusterId }, +}: ClusterConnectHelpModalProps) => { + const applicationPropertiesString = getApplicationPropertiesString({ + clusterName, + clusterId, + kafkaFlavor, + clusterType, + protocol, + }); + return ( + + {applicationPropertiesString.length > 0 ? ( + + + Copy, paste, and replace placeholders in{" "} +
+              klaw/cluster-api/src/main/resources/application.properties
+            
+ . +
+ + + Learn more + + +
+ + +
{applicationPropertiesString}
+
+
+ ) : ( + + + + Learn more about cluster connectivity setup + + + + )} +
+ ); +}; + +export default ClusterConnectHelpModal; diff --git a/coral/src/app/features/configuration/clusters/components/ClustersTable.test.tsx b/coral/src/app/features/configuration/clusters/components/ClustersTable.test.tsx index 8a04e18e4d..85a9371889 100644 --- a/coral/src/app/features/configuration/clusters/components/ClustersTable.test.tsx +++ b/coral/src/app/features/configuration/clusters/components/ClustersTable.test.tsx @@ -1,8 +1,11 @@ import { cleanup, render, screen, within } from "@testing-library/react"; +import { userEvent } from "@testing-library/user-event"; import { mockIntersectionObserver } from "src/services/test-utils/mock-intersection-observer"; import { customRender } from "src/services/test-utils/render-with-wrappers"; import { ClusterDetails } from "src/domain/cluster"; import { ClustersTable } from "src/app/features/configuration/clusters/components/ClustersTable"; +import { clusterTypeToString } from "src/services/formatter/cluster-type-formatter"; +import { kafkaFlavorToString } from "src/services/formatter/kafka-flavor-formatter"; const testCluster: ClusterDetails[] = [ { @@ -41,11 +44,13 @@ const tableRowHeader = [ "Cluster", "Bootstrap servers", "Protocol", - "Type", - "RestApi server", - "Other params", + "Cluster type", + "REST API servers", + "Other parameters", ]; +const mockHandleShowModal = jest.fn(); + describe("ClusterTable.tsx", () => { beforeAll(() => { mockIntersectionObserver(); @@ -72,7 +77,7 @@ describe("ClusterTable.tsx", () => { }); }); - describe("shows all clusters as a table", () => { + describe("shows all clusters as a table (user without permissions.addDeleteEditClusters)", () => { const tableLabel = "Cluster overview"; beforeAll(() => { customRender( @@ -154,7 +159,169 @@ describe("ClusterTable.tsx", () => { name: new RegExp(`${cluster.clusterName}`, "i"), }); const type = within(row).getByRole("cell", { - name: cluster.clusterType, + name: clusterTypeToString[cluster.clusterType], + }); + + expect(type).toBeVisible(); + }); + + it(`renders the kafka flavor for ${cluster.clusterName}`, () => { + const table = screen.getByRole("table", { + name: tableLabel, + }); + const row = within(table).getByRole("row", { + name: new RegExp(`${cluster.clusterName}`, "i"), + }); + const kafkaFlavor = within(row).getByRole("cell", { + name: kafkaFlavorToString[cluster.kafkaFlavor], + }); + + expect(kafkaFlavor).toBeVisible(); + }); + + if (cluster.associatedServers) + it(`renders the rest api servers for ${cluster.clusterName}`, () => { + const table = screen.getByRole("table", { + name: tableLabel, + }); + const row = within(table).getByRole("row", { + name: new RegExp(`${cluster.clusterName}`, "i"), + }); + const restApiServer = within(row).getByRole("cell", { + name: cluster.associatedServers, + }); + + expect(restApiServer).toBeVisible(); + }); + + if (cluster.serviceName) { + it(`optionally renders the serviceName as other params for ${cluster.clusterName}`, () => { + const table = screen.getByRole("table", { + name: tableLabel, + }); + const row = within(table).getByRole("row", { + name: new RegExp(`${cluster.clusterName}`, "i"), + }); + const serviceNameRegex = new RegExp( + `serviceName=${cluster.serviceName}` + ); + + const serviceName = within(row).getByRole("cell", { + name: serviceNameRegex, + }); + + expect(serviceName).toBeVisible(); + }); + } + + if (cluster.projectName) { + it(`optionally renders the projectName as other params for ${cluster.clusterName}`, () => { + const table = screen.getByRole("table", { + name: tableLabel, + }); + const row = within(table).getByRole("row", { + name: new RegExp(`${cluster.clusterName}`, "i"), + }); + const projectNameRegex = new RegExp( + `projectName=${cluster.projectName}` + ); + + const projectName = within(row).getByRole("cell", { + name: projectNameRegex, + }); + + expect(projectName).toBeVisible(); + }); + } + }); + }); + describe("shows all clusters as a table (user with permissions.addDeleteEditClusters)", () => { + const tableLabel = "Cluster overview"; + beforeAll(() => { + customRender( + , + { queryClient: true } + ); + }); + + afterAll(cleanup); + + it("renders a table with an acessible name", async () => { + const table = screen.getByRole("table", { + name: tableLabel, + }); + + expect(table).toBeVisible(); + }); + + tableRowHeader.forEach((header) => { + it(`renders a column header for ${header}`, () => { + const table = screen.getByRole("table", { + name: tableLabel, + }); + const colHeader = within(table).getByRole("columnheader", { + name: header, + }); + + expect(colHeader).toBeVisible(); + }); + }); + + testCluster.forEach((cluster) => { + it(`renders the cluster name "${cluster.clusterName}" as row header`, () => { + const table = screen.getByRole("table", { + name: tableLabel, + }); + const rowHeader = within(table).getByRole("cell", { + name: cluster.clusterName, + }); + const name = within(rowHeader).getByText(cluster.clusterName); + + expect(rowHeader).toBeVisible(); + expect(name).toBeVisible(); + }); + + it(`renders the bootstrap servers for ${cluster.clusterName} `, () => { + const table = screen.getByRole("table", { + name: tableLabel, + }); + const row = within(table).getByRole("row", { + name: new RegExp(`${cluster.clusterName}`, "i"), + }); + const bootstrapServer = within(row).getByRole("cell", { + name: cluster.bootstrapServers, + }); + + expect(bootstrapServer).toBeVisible(); + }); + + it(`renders the protocol for ${cluster.clusterName}`, () => { + const table = screen.getByRole("table", { + name: tableLabel, + }); + const row = within(table).getByRole("row", { + name: new RegExp(`${cluster.clusterName}`, "i"), + }); + const protocol = within(row).getByRole("cell", { + name: cluster.protocol, + }); + + expect(protocol).toBeVisible(); + }); + + it(`renders the type for ${cluster.clusterName}`, () => { + const table = screen.getByRole("table", { + name: tableLabel, + }); + const row = within(table).getByRole("row", { + name: new RegExp(`${cluster.clusterName}`, "i"), + }); + const type = within(row).getByRole("cell", { + name: clusterTypeToString[cluster.clusterType], }); expect(type).toBeVisible(); @@ -168,7 +335,7 @@ describe("ClusterTable.tsx", () => { name: new RegExp(`${cluster.clusterName}`, "i"), }); const kafkaFlavor = within(row).getByRole("cell", { - name: cluster.kafkaFlavor, + name: kafkaFlavorToString[cluster.kafkaFlavor], }); expect(kafkaFlavor).toBeVisible(); @@ -228,6 +395,45 @@ describe("ClusterTable.tsx", () => { expect(projectName).toBeVisible(); }); } + + it(`renders the Connect help button for ${cluster.clusterName}`, () => { + const table = screen.getByRole("table", { + name: tableLabel, + }); + const row = within(table).getByRole("row", { + name: new RegExp(`${cluster.clusterName}`, "i"), + }); + const connectButton = within(row).getByRole("button", { + name: `Show help for connecting the cluster ${cluster.clusterName} to Klaw`, + }); + + expect(connectButton).toBeEnabled(); + }); + + it(`displays Connect help modal when clicking the Connect help button for ${cluster.clusterName}`, async () => { + const table = screen.getByRole("table", { + name: tableLabel, + }); + const row = within(table).getByRole("row", { + name: new RegExp(`${cluster.clusterName}`, "i"), + }); + const connectButton = within(row).getByRole("button", { + name: `Show help for connecting the cluster ${cluster.clusterName} to Klaw`, + }); + + await userEvent.click(connectButton); + + expect(mockHandleShowModal).toHaveBeenCalledWith({ + show: true, + data: { + kafkaFlavor: cluster.kafkaFlavor, + protocol: cluster.protocol, + clusterType: cluster.clusterType, + clusterName: cluster.clusterName, + clusterId: cluster.clusterId, + }, + }); + }); }); }); }); diff --git a/coral/src/app/features/configuration/clusters/components/ClustersTable.tsx b/coral/src/app/features/configuration/clusters/components/ClustersTable.tsx index 84e8ebb39c..0e2d4d2578 100644 --- a/coral/src/app/features/configuration/clusters/components/ClustersTable.tsx +++ b/coral/src/app/features/configuration/clusters/components/ClustersTable.tsx @@ -1,9 +1,13 @@ import { Box, DataTable, DataTableColumn, EmptyState } from "@aivenio/aquarium"; +import type { ClusterModal } from "src/app/features/configuration/clusters/Clusters"; import { ClusterDetails } from "src/domain/cluster"; +import { clusterTypeToString } from "src/services/formatter/cluster-type-formatter"; +import { kafkaFlavorToString } from "src/services/formatter/kafka-flavor-formatter"; type ClustersTableProps = { clusters: ClusterDetails[]; ariaLabel: string; + handleShowModal?: ({ show, data }: ClusterModal) => void; }; interface ClustersTableRow { @@ -21,7 +25,7 @@ interface ClustersTableRow { } const ClustersTable = (props: ClustersTableProps) => { - const { clusters, ariaLabel } = props; + const { clusters, ariaLabel, handleShowModal } = props; const columns: Array> = [ { @@ -44,9 +48,9 @@ const ClustersTable = (props: ClustersTableProps) => { }, { type: "status", - headerName: "Type", + headerName: "Cluster type", status: ({ clusterType }) => ({ - text: clusterType, + text: clusterTypeToString[clusterType], status: "neutral", }), }, @@ -55,15 +59,16 @@ const ClustersTable = (props: ClustersTableProps) => { field: "kafkaFlavor", headerName: "Kafka flavor", width: 180, + formatter: (value) => kafkaFlavorToString[value], }, { type: "text", field: "restApiServer", - headerName: "RestApi server", + headerName: "REST API servers", }, { type: "custom", - headerName: "Other params", + headerName: "Other parameters", UNSAFE_render: ({ otherParams }: ClustersTableRow) => { if (!otherParams.projectName && !otherParams.serviceName) { return
-NA-
; @@ -82,6 +87,29 @@ const ClustersTable = (props: ClustersTableProps) => { }, ]; + if (handleShowModal !== undefined) { + columns.push({ + type: "action", + headerName: "", + headerInvisible: true, + action: ({ kafkaFlavor, protocol, clusterType, clusterName, id }) => ({ + text: "Connect", + onClick: () => + handleShowModal({ + show: true, + data: { + kafkaFlavor, + protocol, + clusterType, + clusterName, + clusterId: id, + }, + }), + "aria-label": `Show help for connecting the cluster ${clusterName} to Klaw`, + }), + }); + } + const rows: ClustersTableRow[] = clusters.map((cluster) => { return { id: cluster.clusterId, diff --git a/coral/src/app/features/dashboard/__snapshots__/Dasboard.test.tsx.snap b/coral/src/app/features/dashboard/__snapshots__/Dasboard.test.tsx.snap index 2d18e51d94..3a690e3c0e 100644 --- a/coral/src/app/features/dashboard/__snapshots__/Dasboard.test.tsx.snap +++ b/coral/src/app/features/dashboard/__snapshots__/Dasboard.test.tsx.snap @@ -6,7 +6,7 @@ exports[`Dashboard.tsx renders correct DOM according to data received from getRe style="display: grid; gap: 24px; grid-template-columns: 1fr;" >
{ expect(term).toBeVisible(); expect(definition[0]).toHaveTextContent( - String(testClusterDetails.kafkaFlavor) + kafkaFlavorToString[testClusterDetails.kafkaFlavor] ); }); diff --git a/coral/src/app/features/topics/details/overview/components/ClusterDetails.tsx b/coral/src/app/features/topics/details/overview/components/ClusterDetails.tsx index 373abc5c50..cd71fafe6b 100644 --- a/coral/src/app/features/topics/details/overview/components/ClusterDetails.tsx +++ b/coral/src/app/features/topics/details/overview/components/ClusterDetails.tsx @@ -8,6 +8,7 @@ import { } from "@aivenio/aquarium"; import React from "react"; import { ClusterDetails as ClusterDetailsType } from "src/domain/cluster"; +import { kafkaFlavorToString } from "src/services/formatter/kafka-flavor-formatter"; function DefinitionBlock({ term, @@ -92,7 +93,11 @@ function ClusterDetails({ clusterDetails, isUpdating }: ClusterDetailsProps) { ): Promise { const params: KlawApiRequestQueryParameters<"getClustersPaginated"> = { - clusterType: "all", + clusterType: "ALL", pageNo, ...(searchClusterParam && { searchClusterParam: searchClusterParam }), }; diff --git a/coral/src/domain/cluster/cluster-types.ts b/coral/src/domain/cluster/cluster-types.ts index 57ac45dea8..c0b85606ad 100644 --- a/coral/src/domain/cluster/cluster-types.ts +++ b/coral/src/domain/cluster/cluster-types.ts @@ -10,9 +10,16 @@ type ClustersPaginatedApiResponse = ResolveIntersectionTypes< type AddNewClusterPayload = KlawApiModel<"KwClustersModel">; +type ClusterKafkaFlavor = + KlawApiModel<"KwClustersModelResponse">["kafkaFlavor"]; + +type ClusterType = KlawApiModel<"KwClustersModelResponse">["clusterType"]; + export type { ClusterInfoFromEnvironment, ClusterDetails, ClustersPaginatedApiResponse, AddNewClusterPayload, + ClusterKafkaFlavor, + ClusterType, }; diff --git a/coral/src/domain/cluster/index.ts b/coral/src/domain/cluster/index.ts index d81eb412fe..757f2f5004 100644 --- a/coral/src/domain/cluster/index.ts +++ b/coral/src/domain/cluster/index.ts @@ -8,6 +8,8 @@ import { AddNewClusterPayload, ClusterInfoFromEnvironment, ClusterDetails, + ClusterKafkaFlavor, + ClusterType, } from "src/domain/cluster/cluster-types"; export { @@ -20,4 +22,6 @@ export type { AddNewClusterPayload, ClusterInfoFromEnvironment, ClusterDetails, + ClusterKafkaFlavor, + ClusterType, }; diff --git a/coral/src/services/formatter/cluster-type-formatter.ts b/coral/src/services/formatter/cluster-type-formatter.ts new file mode 100644 index 0000000000..5702be22fd --- /dev/null +++ b/coral/src/services/formatter/cluster-type-formatter.ts @@ -0,0 +1,11 @@ +import { ClusterType } from "src/domain/cluster"; + +type ClusterTypeMap = Record; +const clusterTypeToString: ClusterTypeMap = { + ALL: "All", + KAFKA: "Kafka", + SCHEMA_REGISTRY: "Schema Registry", + KAFKA_CONNECT: "Kafka Connect", +}; + +export { clusterTypeToString }; diff --git a/coral/src/services/formatter/kafka-flavor-formatter.ts b/coral/src/services/formatter/kafka-flavor-formatter.ts new file mode 100644 index 0000000000..863847f2d1 --- /dev/null +++ b/coral/src/services/formatter/kafka-flavor-formatter.ts @@ -0,0 +1,12 @@ +import { ClusterKafkaFlavor } from "src/domain/cluster"; + +type ClusterKafkaFlavorMap = Record; +const kafkaFlavorToString: ClusterKafkaFlavorMap = { + APACHE_KAFKA: "Apache Kafka", + AIVEN_FOR_APACHE_KAFKA: "Aiven for Apache Kafka", + CONFLUENT: "Confluent", + CONFLUENT_CLOUD: "Confluent Cloud", + OTHERS: "others", +}; + +export { kafkaFlavorToString }; diff --git a/coral/types/api.d.ts b/coral/types/api.d.ts index f4f6108f72..00fcc1aaca 100644 --- a/coral/types/api.d.ts +++ b/coral/types/api.d.ts @@ -4021,7 +4021,7 @@ export type operations = { getClustersPaginated: { parameters: { query: { - clusterType: string; + clusterType: "ALL" | "KAFKA" | "SCHEMA_REGISTRY" | "KAFKA_CONNECT"; pageNo: string; clusterId?: string; searchClusterParam?: string; diff --git a/core/src/main/java/io/aiven/klaw/controller/EnvsClustersTenantsController.java b/core/src/main/java/io/aiven/klaw/controller/EnvsClustersTenantsController.java index c4ffd835cf..d6792caf84 100644 --- a/core/src/main/java/io/aiven/klaw/controller/EnvsClustersTenantsController.java +++ b/core/src/main/java/io/aiven/klaw/controller/EnvsClustersTenantsController.java @@ -48,7 +48,7 @@ public ResponseEntity> getClusters( method = RequestMethod.GET, produces = {MediaType.APPLICATION_JSON_VALUE}) public ResponseEntity> getClustersPaginated( - @RequestParam(value = "clusterType") String clusterType, + @RequestParam(value = "clusterType") KafkaClustersType clusterType, @RequestParam("pageNo") String pageNo, @RequestParam(value = "clusterId", defaultValue = "") String clusterId, @RequestParam(value = "searchClusterParam", defaultValue = "") String searchClusterParam) { diff --git a/core/src/main/java/io/aiven/klaw/service/EnvsClustersTenantsControllerService.java b/core/src/main/java/io/aiven/klaw/service/EnvsClustersTenantsControllerService.java index 5cc7a9c2b6..d26c118165 100644 --- a/core/src/main/java/io/aiven/klaw/service/EnvsClustersTenantsControllerService.java +++ b/core/src/main/java/io/aiven/klaw/service/EnvsClustersTenantsControllerService.java @@ -169,8 +169,10 @@ public List getClusters(String typeOfCluster) { } public List getClustersPaginated( - String typeOfCluster, String clusterId, String pageNo, String searchClusterParam) { - List kwClustersModelList = getClusters("all"); + KafkaClustersType typeOfCluster, String clusterId, String pageNo, String searchClusterParam) { + + String clusterTypeValue = typeOfCluster.value; + List kwClustersModelList = getClusters(clusterTypeValue); if (clusterId != null && !clusterId.equals("")) { kwClustersModelList = diff --git a/core/src/main/resources/static/js/envs.js b/core/src/main/resources/static/js/envs.js index c63da19a67..6a1728f0d0 100644 --- a/core/src/main/resources/static/js/envs.js +++ b/core/src/main/resources/static/js/envs.js @@ -4,6 +4,7 @@ // edit // solution for transaction // message store / key / gui + var app = angular.module('envsApp',[]); app.directive('onReadFile', function ($parse) { @@ -32,6 +33,14 @@ app.controller("envsCtrl", function($scope, $http, $location, $window) { $scope.kafkaClusters = [ { label: 'Non-Aiven', value: 'nonaiven' }, { label: 'Aiven', value: 'aiven' } ]; + $scope.kafkaFlavorToString = { + APACHE_KAFKA: "Apache Kafka", + AIVEN_FOR_APACHE_KAFKA: "Aiven for Apache Kafka", + CONFLUENT: "Confluent", + CONFLUENT_CLOUD: "Confluent Cloud", + OTHERS: "others", + }; + // Set http service defaults // We force the "Accept" header to be only "application/json" // otherwise we risk the Accept header being set by default to: @@ -238,9 +247,9 @@ app.controller("envsCtrl", function($scope, $http, $location, $window) { method: "GET", url: "getClustersPaginated", headers : { 'Content-Type' : 'application/json' }, - params: {'clusterType' : 'all', 'clusterId' : $scope.clusterIdFromUrl, + params: {'clusterType' : 'ALL', 'clusterId' : $scope.clusterIdFromUrl, 'pageNo' : pageNo, 'searchClusterParam' : $scope.searchClusterParam}, - data: {'clusterType' : 'all'} + data: {'clusterType' : 'ALL'} }).success(function(output) { $scope.allclustersset = output; if(output && output.length > 0 && output[0] != null){ diff --git a/core/src/main/resources/static/js/modifyEnvs.js b/core/src/main/resources/static/js/modifyEnvs.js index e1e7111b55..e5009e6b5f 100644 --- a/core/src/main/resources/static/js/modifyEnvs.js +++ b/core/src/main/resources/static/js/modifyEnvs.js @@ -7,7 +7,15 @@ var app = angular.module('modifyEnvsApp',[]); app.controller("modifyEnvsCtrl", function($scope, $http, $location, $window) { - + + $scope.kafkaFlavorToString = { + APACHE_KAFKA: "Apache Kafka", + AIVEN_FOR_APACHE_KAFKA: "Aiven for Apache Kafka", + CONFLUENT: "Confluent", + CONFLUENT_CLOUD: "Confluent Cloud", + OTHERS: "others", + }; + // Set http service defaults // We force the "Accept" header to be only "application/json" // otherwise we risk the Accept header being set by default to: diff --git a/core/src/main/resources/templates/clusters.html b/core/src/main/resources/templates/clusters.html index dfbfb60d10..9682e05a31 100644 --- a/core/src/main/resources/templates/clusters.html +++ b/core/src/main/resources/templates/clusters.html @@ -518,16 +518,16 @@

Shortcuts

{{ allenv.protocol}} - {{ allenv.clusterType }} + Kafka - {{ allenv.clusterType }} + Schema Registry - {{ allenv.clusterType }} + Kafka Connect - {{ allenv.kafkaFlavor }} + {{ kafkaFlavorToString[allenv.kafkaFlavor] }} {{ allenv.associatedServers }} diff --git a/core/src/main/resources/templates/modifyCluster.html b/core/src/main/resources/templates/modifyCluster.html index 35b1a957d9..0477766927 100644 --- a/core/src/main/resources/templates/modifyCluster.html +++ b/core/src/main/resources/templates/modifyCluster.html @@ -525,8 +525,8 @@

Shortcuts

- - + +
@@ -636,8 +636,8 @@

Shortcuts

- - + +
@@ -712,8 +712,8 @@

Shortcuts

- - + +
diff --git a/openapi.yaml b/openapi.yaml index 2011e65c22..557b90a861 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -4827,7 +4827,8 @@ "in" : "query", "required" : true, "schema" : { - "type" : "string" + "type" : "string", + "enum" : [ "ALL", "KAFKA", "SCHEMA_REGISTRY", "KAFKA_CONNECT" ] } }, { "name" : "pageNo",