diff --git a/.github/actions/rebase/action.yml b/.github/actions/rebase/action.yml deleted file mode 100644 index 4e6d0cec0b..0000000000 --- a/.github/actions/rebase/action.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: 'Rebase' -description: 'Action for rebasing to the main branch' - -runs: - using: 'composite' - steps: - # Setup identity to avoid errors when git is trying to rebase without identity set - - name: Setup git identity - run: | - git config --global user.email "rebase@action.com" - git config --global user.name "Rebase Action" - shell: bash - - # Update origin/main branch locally to cover case when repo is not fully cloned - # for example when using `actions/checkout@v4` action with default `fetch-depth` value (1) - # read more: https://github.com/actions/checkout?tab=readme-ov-file#usage - - name: Update origin/main - run: | - git fetch origin main - git checkout origin/main - git checkout -B main - git pull --unshallow origin main - - # go back to the HEAD commit - git switch --detach ${{ github.sha }} - shell: bash - - - name: Rebase to main - run: | - git rebase main - shell: bash - - - name: Print branch log - run: | - git log origin/main~1.. - shell: bash diff --git a/.github/workflows/accessibility-tests.yml b/.github/workflows/accessibility-tests.yml index 78b66b255f..b0ecc60536 100644 --- a/.github/workflows/accessibility-tests.yml +++ b/.github/workflows/accessibility-tests.yml @@ -3,12 +3,6 @@ name: Accessibility Tests on: schedule: - cron: "0 6 * * 1" - pull_request_target: - types: [opened, edited, synchronize, reopened, ready_for_review] - paths: - - ".github/workflows/accessibility-tests.yml" - - "tests/integration/tests/accessibility/**" - - "tests/integration/support/**" jobs: run-accessibility-tests: diff --git a/.github/workflows/busola-backend-build.yml b/.github/workflows/busola-backend-build.yml index f7f0619bba..35186b5bae 100644 --- a/.github/workflows/busola-backend-build.yml +++ b/.github/workflows/busola-backend-build.yml @@ -22,7 +22,6 @@ permissions: jobs: build-backend-image: uses: kyma-project/test-infra/.github/workflows/image-builder.yml@main # Usage: kyma-project/test-infra/.github/workflows/image-builder.yml@main - if: github.event.pull_request.draft == false with: name: busola-backend dockerfile: Dockerfile diff --git a/.github/workflows/busola-build.yml b/.github/workflows/busola-build.yml index 9400d3f090..4e490b0b11 100644 --- a/.github/workflows/busola-build.yml +++ b/.github/workflows/busola-build.yml @@ -34,7 +34,6 @@ permissions: jobs: build-busola-image: uses: kyma-project/test-infra/.github/workflows/image-builder.yml@main # Usage: kyma-project/test-infra/.github/workflows/image-builder.yml@main - if: github.event.pull_request.draft == false with: name: busola dockerfile: Dockerfile diff --git a/.github/workflows/busola-web-build.yml b/.github/workflows/busola-web-build.yml index 50bf2ca2c6..c3f60c28f3 100644 --- a/.github/workflows/busola-web-build.yml +++ b/.github/workflows/busola-web-build.yml @@ -33,7 +33,6 @@ permissions: jobs: build-web-image: uses: kyma-project/test-infra/.github/workflows/image-builder.yml@main # Usage: kyma-project/test-infra/.github/workflows/image-builder.yml@main - if: github.event.pull_request.draft == false with: name: busola-web dockerfile: Dockerfile.web diff --git a/.github/workflows/lint-check-pr.yml b/.github/workflows/lint-check-pr.yml index 6bec9a1185..47e5f6da83 100644 --- a/.github/workflows/lint-check-pr.yml +++ b/.github/workflows/lint-check-pr.yml @@ -7,7 +7,6 @@ on: jobs: run-lint-check: runs-on: ubuntu-latest - if: github.event.pull_request.draft == false steps: - uses: actions/checkout@v4 with: diff --git a/.github/workflows/pull-integration-cluster-k3d.yml b/.github/workflows/pull-integration-cluster-k3d.yml index 5b0180e2fa..54535e08c4 100644 --- a/.github/workflows/pull-integration-cluster-k3d.yml +++ b/.github/workflows/pull-integration-cluster-k3d.yml @@ -9,18 +9,17 @@ on: - "tests/**" - "nginx/**" - "src/**" - - "backend/**" jobs: run-cluster-test: runs-on: ubuntu-latest - if: github.event.pull_request.draft == false steps: - uses: gardenlinux/workflow-telemetry-action@v2 with: comment_on_pr: false - uses: actions/checkout@v4 - - uses: ./.github/actions/rebase + with: + fetch-depth: 0 - name: Create Single Cluster uses: AbsaOSS/k3d-action@4e8b3239042be1dc0aed6c5eb80c13b18200fc79 #v2.4.0 with: diff --git a/.github/workflows/pull-integration-namespace-k3d.yml b/.github/workflows/pull-integration-namespace-k3d.yml index 1359c8c519..7d944a5fbe 100644 --- a/.github/workflows/pull-integration-namespace-k3d.yml +++ b/.github/workflows/pull-integration-namespace-k3d.yml @@ -9,18 +9,17 @@ on: - "tests/**" - "nginx/**" - "src/**" - - "backend/**" jobs: run-namespace-test: runs-on: ubuntu-latest - if: github.event.pull_request.draft == false steps: - uses: gardenlinux/workflow-telemetry-action@v2 with: comment_on_pr: false - uses: actions/checkout@v4 - - uses: ./.github/actions/rebase + with: + fetch-depth: 0 - name: Create Single Cluster uses: AbsaOSS/k3d-action@4e8b3239042be1dc0aed6c5eb80c13b18200fc79 #v2.4.0 with: diff --git a/.github/workflows/pull-kyma-integration-tests.yml b/.github/workflows/pull-kyma-integration-tests.yml index 60fc82f6bc..71704fc859 100644 --- a/.github/workflows/pull-kyma-integration-tests.yml +++ b/.github/workflows/pull-kyma-integration-tests.yml @@ -9,19 +9,18 @@ on: - "tests/integration/**" - "nginx/**" - "src/**" - - "backend/**" - "kyma/**" - "Dockerfile*" jobs: run-integration-test: runs-on: ubuntu-latest - if: github.event.pull_request.draft == false steps: - uses: gardenlinux/workflow-telemetry-action@v2 with: comment_on_pr: false - uses: actions/checkout@v4 - - uses: ./.github/actions/rebase + with: + fetch-depth: 0 - name: Install k3d env: K3D_URL: https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh diff --git a/.github/workflows/pull-lighthouse.yml b/.github/workflows/pull-lighthouse.yml index d1d71ccf07..a68193dc12 100644 --- a/.github/workflows/pull-lighthouse.yml +++ b/.github/workflows/pull-lighthouse.yml @@ -13,13 +13,13 @@ on: jobs: run-lighthouse-test: runs-on: ubuntu-latest - if: github.event.pull_request.draft == false steps: - uses: gardenlinux/workflow-telemetry-action@v2 with: comment_on_pr: false - uses: actions/checkout@v4 - - uses: ./.github/actions/rebase + with: + fetch-depth: 0 - name: Create Single Cluster uses: AbsaOSS/k3d-action@4e8b3239042be1dc0aed6c5eb80c13b18200fc79 #v2.4.0 with: diff --git a/.github/workflows/pull-smoke-test-prod.yml b/.github/workflows/pull-smoke-test-prod.yml index 9ba14d5f26..64020107a7 100644 --- a/.github/workflows/pull-smoke-test-prod.yml +++ b/.github/workflows/pull-smoke-test-prod.yml @@ -15,13 +15,13 @@ on: jobs: run-smoke-test-prod: runs-on: ubuntu-latest - if: github.event.pull_request.draft == false steps: - uses: gardenlinux/workflow-telemetry-action@v2 with: comment_on_pr: false - uses: actions/checkout@v4 - - uses: ./.github/actions/rebase + with: + fetch-depth: 0 - name: Install k3d env: K3D_URL: https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh diff --git a/.github/workflows/pull-smoke-test-stage.yml b/.github/workflows/pull-smoke-test-stage.yml index 0d1b6d4015..5198cdebbe 100644 --- a/.github/workflows/pull-smoke-test-stage.yml +++ b/.github/workflows/pull-smoke-test-stage.yml @@ -15,13 +15,13 @@ on: jobs: run-smoke-test-stage: runs-on: ubuntu-latest - if: github.event.pull_request.draft == false steps: - uses: gardenlinux/workflow-telemetry-action@v2 with: comment_on_pr: false - uses: actions/checkout@v4 - - uses: ./.github/actions/rebase + with: + fetch-depth: 0 - name: Install k3d env: K3D_URL: https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh diff --git a/.github/workflows/pull-unit-tests.yml b/.github/workflows/pull-unit-tests.yml index c233864f01..4038d1ae7d 100644 --- a/.github/workflows/pull-unit-tests.yml +++ b/.github/workflows/pull-unit-tests.yml @@ -12,13 +12,13 @@ on: jobs: run-unit-test: runs-on: ubuntu-latest - if: github.event.pull_request.draft == false steps: - uses: gardenlinux/workflow-telemetry-action@v2 with: comment_on_pr: false - uses: actions/checkout@v4 - - uses: ./.github/actions/rebase + with: + fetch-depth: 0 - uses: actions/setup-node@v4 with: node-version: 20 diff --git a/backend/common.js b/backend/common.js index 0129f0d01b..4a562926e1 100644 --- a/backend/common.js +++ b/backend/common.js @@ -142,12 +142,10 @@ function extractHeadersData(req) { const clientCAHeader = 'x-client-certificate-data'; const clientKeyDataHeader = 'x-client-key-data'; const authorizationHeader = 'x-k8s-authorization'; - let targetApiServer; - if (req.headers[urlHeader]) { - targetApiServer = handleDockerDesktopSubsitution( - new URL(req.headers[urlHeader]), - ); - } + + const targetApiServer = handleDockerDesktopSubsitution( + new URL(req.headers[urlHeader]), + ); const ca = decodeHeaderToBuffer(req.headers[caHeader]) || certs; const cert = decodeHeaderToBuffer(req.headers[clientCAHeader]); const key = decodeHeaderToBuffer(req.headers[clientKeyDataHeader]); diff --git a/backend/index.js b/backend/index.js index 82934c016d..d98c47589a 100644 --- a/backend/index.js +++ b/backend/index.js @@ -1,5 +1,4 @@ import { makeHandleRequest, serveStaticApp, serveMonaco } from './common'; -import { proxyHandler } from './proxy.js'; import { handleTracking } from './tracking.js'; import jsyaml from 'js-yaml'; //import { requestLogger } from './utils/other'; //uncomment this to log the outgoing traffic @@ -54,8 +53,6 @@ if (process.env.NODE_ENV === 'development') { app.use(cors({ origin: '*' })); } -app.get('/proxy', proxyHandler); - let server = null; if ( @@ -84,15 +81,13 @@ const isDocker = process.env.IS_DOCKER === 'true'; const handleRequest = makeHandleRequest(); if (isDocker) { - // Running in dev mode // yup, order matters here serveMonaco(app); app.use('/backend', handleRequest); serveStaticApp(app, '/', '/core-ui'); } else { - // Running in prod mode handleTracking(app); - app.use('/backend', handleRequest); + app.use(handleRequest); } process.on('SIGINT', function() { diff --git a/backend/package-lock.json b/backend/package-lock.json index d8cfcc0359..a0477a86ad 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -12,7 +12,7 @@ "@babel/runtime": "^7.13.10", "compression": "^1.7.4", "cors": "^2.8.5", - "express": "^4.21.2", + "express": "^4.21.0", "https": "^1.0.0", "jose": "^5.2.4", "js-yaml": "^4.1.0", @@ -4481,9 +4481,9 @@ } }, "node_modules/cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", "engines": { "node": ">= 0.6" } @@ -5174,16 +5174,16 @@ } }, "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", + "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.7.1", + "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -5197,7 +5197,7 @@ "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", + "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", @@ -5212,10 +5212,6 @@ }, "engines": { "node": ">= 0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" } }, "node_modules/express/node_modules/safe-buffer": { @@ -9237,9 +9233,9 @@ "dev": true }, "node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" }, "node_modules/picocolors": { "version": "1.1.0", @@ -14515,9 +14511,9 @@ } }, "cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==" + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==" }, "cookie-signature": { "version": "1.0.6", @@ -15023,16 +15019,16 @@ } }, "express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", + "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", "requires": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.7.1", + "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -15046,7 +15042,7 @@ "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", + "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", @@ -18052,9 +18048,9 @@ "dev": true }, "path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" }, "picocolors": { "version": "1.1.0", diff --git a/backend/package.json b/backend/package.json index a56ca31360..42fb1cc89c 100644 --- a/backend/package.json +++ b/backend/package.json @@ -16,7 +16,7 @@ "@babel/runtime": "^7.13.10", "compression": "^1.7.4", "cors": "^2.8.5", - "express": "^4.21.2", + "express": "^4.21.0", "https": "^1.0.0", "jose": "^5.2.4", "js-yaml": "^4.1.0", diff --git a/backend/proxy.js b/backend/proxy.js deleted file mode 100644 index ad4a35c94b..0000000000 --- a/backend/proxy.js +++ /dev/null @@ -1,45 +0,0 @@ -import { request as httpsRequest } from 'https'; -import { request as httpRequest } from 'http'; -import { URL } from 'url'; - -async function proxyHandler(req, res) { - const targetUrl = req.query.url; - if (!targetUrl) { - return res.status(400).send('Target URL is required as a query parameter'); - } - - try { - const parsedUrl = new URL(targetUrl); - const isHttps = parsedUrl.protocol === 'https:'; - const libRequest = isHttps ? httpsRequest : httpRequest; - - const options = { - hostname: parsedUrl.hostname, - port: parsedUrl.port || (isHttps ? 443 : 80), - path: parsedUrl.pathname + parsedUrl.search, - method: req.method, - headers: { ...req.headers, host: parsedUrl.host }, - }; - - const proxyReq = libRequest(options, proxyRes => { - // Forward status and headers from the target response - res.writeHead(proxyRes.statusCode, proxyRes.headers); - // Pipe the response data from the target back to the client - proxyRes.pipe(res); - }); - - proxyReq.on('error', () => { - res.status(500).send('An error occurred while making the proxy request.'); - }); - - if (Buffer.isBuffer(req.body)) { - proxyReq.end(req.body); // If the body is already buffered, use it directly. - } else { - req.pipe(proxyReq); // Otherwise, pipe the request for streamed or chunked data. - } - } catch (error) { - res.status(500).send('An error occurred while processing the request.'); - } -} - -export { proxyHandler }; diff --git a/docs/contributor/testing-strategy.md b/docs/contributor/testing-strategy.md index 113ef87f1b..6ee09aa3d3 100644 --- a/docs/contributor/testing-strategy.md +++ b/docs/contributor/testing-strategy.md @@ -2,27 +2,19 @@ Each pull request (PR) to the repository triggers CI/CD jobs that verify the Busola configuration, build and run integration tests. -- `Busola Web Build / build-web-image / Build image` - Checks the ESLint code quality and builds the web Docker image. -- `Busola Build / build-busola-image / Build image` - Checks the ESLint code quality and builds the web and backend Docker image. -- `Busola Backend Build / build-backend-image / Build image` - Builds the backend Docker image. The job runs only when changes affect the backend. -- `PR Integration Cluster Tests / run-cluster-test` - Performs integration testing for Busola related to cluster-level functionalities using a k3d cluster. -- `PR Integration Namespace Tests / run-namespace-test` - Performs integration testing for Busola related to namespace-level functionalities using a k3d cluster. -- `PR Kyma Dashboard Integration Tests Dev / run-integration-test` - Performs integration testing for Busola with DEV environement and configuration related to the Kyma functionalities using a k3d cluster with installed Kyma. -- `PR Kyma Dashboard Smoke Tests Stage / run-smoke-test-stage` - Performs smoke testing for Busola with STAGE environement and configuration related to the Kyma functionalities using a k3d cluster with installed Kyma. -- `PR Kyma Dashboard Smoke Tests Prod / run-smoke-test-prod` - Performs smoke testing for Busola with PROD environement and configuration related to the Kyma functionalities using a k3d cluster with installed Kyma. -- `PR Lighthouse Test / run-lighthouse-test` - Performs performance testing for Busola - threshold for accessibility: 80, best-practices: 100. -- `PR Lint Check / run-lint-check` - Performing ESlint and Prettier code quality. -- `PR Unit Tests / run-unit-test` - Performs unit tests of the Busola. +- `pre-busola-web-deployment-check` - Checks if the Busola web image in deployment is bumped correctly. +- `pre-busola-backend-deployment-check` - Checks if the Busola backend image in deployment is bumped correctly. The check runs only when PR changes affect the backend. +- `pull-busola-web-build` - Unit tests of Busola checking the ESLint code quality, and building the web Docker image. +- `pull-busola-local-build` - Unit tests of Busola checking the ESLint code quality and building the web and backend Docker image. +- `pull-busola-backend-build` - Builds the backend Docker image. The job runs only when changes affect the backend. +- `pull-busola-integration-cluster-k3d` - Performs integration testing for Busola related to cluster-level functionalities using a k3d cluster. +- `pull-busola-integration-namespace-k3d` - Performs integration testing for Busola related to namespace-level functionalities using a k3d cluster. +- `pull-busola-lighthouse` - Performs performance testing for Busola - threshold for accessibility: 80, best-practices: 100. - `Lint Markdown Links PR / markdown-link-check` - Checks links in documentation. - `CodeQL / Analyze (javascript)` - Code quality static code check. After the pull request is merged, the following CI/CD jobs are executed: -- `Busola Web Build / build-web-image / Build image` - Performs Busola unit tests and builds the web Docker image. -- `Busola Build / build-busola-image / Build image` - Performs Busola unit tests and builds the web and backend Docker image. -- `Busola Backend Build / build-backend-image / Build image` - Builds the backend Docker image. -- `CodeQL / Analyze (javascript)` - Code quality static code check. - -Following CI/CD jobs are executed once a week: - -- `Accessibility Tests - run-accessibility-tests` - Performs accessibility tests of the Busola using k3d cluster with Kyma installed. +- `post-busola-web-build` - Performs Busola unit tests and builds the web Docker image. +- `post-busola-local-build` - Performs Busola unit tests and builds the web and backend Docker image. +- `post-busola-backend-build` - Builds the backend Docker image. diff --git a/docs/extensibility/README.md b/docs/extensibility/README.md index 011cea37a2..c3dcc60050 100644 --- a/docs/extensibility/README.md +++ b/docs/extensibility/README.md @@ -4,8 +4,6 @@ With Busola's extensibility feature, you can create a dedicated user interface (UI) page for your CustomResourceDefinition (CRD). It enables you to add navigation nodes, on cluster or namespace level, and to configure your [UI display](./30-details-summary.md), for example, a resource list page, and details pages. You can also [create and edit forms](./40-form-fields.md). To create a UI component, you need a ConfigMap. -You can also leverage Busola's [custom extension feature](./custom-extensions.md) to design entirely custom user interfaces tailored to your specific needs. - ## Create a ConfigMap for Your UI To create a ConfigMap with your CRD's UI configuration, you can either use the Extensions feature or do it manually. diff --git a/docs/extensibility/custom-extensions.md b/docs/extensibility/custom-extensions.md deleted file mode 100644 index ef645b8a57..0000000000 --- a/docs/extensibility/custom-extensions.md +++ /dev/null @@ -1,24 +0,0 @@ -# Custom Extensions - -Busola's custom extension feature allows you to design fully custom user interfaces beyond the built-in extensibility functionality. This feature is ideal for creating unique and specialized displays not covered by the built-in components. - -## Getting Started - -To enable the custom extension feature, you must set the corresponding feature flag in your Busola config, which is disabled by default. - -```yaml -EXTENSIBILITY_CUSTOM_COMPONENTS: - isEnabled: true -``` - -## Creating Custom Extensions - -Creating a custom extension is as straightforward as setting up a ConfigMap with the following sections: - -- `data.general`: Contains configuration details -- `data.customHtml`: Defines static HTML content -- `data.customScript`: Adds dynamic behavior to your extension. - -Once your ConfigMap is ready, add it to your cluster, and Busola will load and display your custom UI. - -See this [example](./../../examples/custom-extension/README.md), to learn more. diff --git a/docs/features.md b/docs/features.md index 6878f63f0b..91283979ae 100644 --- a/docs/features.md +++ b/docs/features.md @@ -56,15 +56,6 @@ EXTENSIBILITY: isEnabled: true ``` -- **EXTENSIBILITY_CUSTOM_COMPONENTS** - is used to indicate whether entirely custom extensions can be added to Busola. See [this example](../examples/custom-extension/README.md). - -Default settings: - -```yaml -EXTENSIBILITY_CUSTOM_COMPONENTS: - isEnabled: false -``` - - **EXTERNAL_NODES** - a list of links to external websites. `category`: a category name, `icon`: an optional icon, `scope`: either `namespace` or `cluster` (defaults to `cluster`), `children`: a list of pairs (label and link). Default settings: diff --git a/examples/custom-extension/README.md b/examples/custom-extension/README.md deleted file mode 100644 index 9f3e7c7c35..0000000000 --- a/examples/custom-extension/README.md +++ /dev/null @@ -1,44 +0,0 @@ -# Set Up Your Custom Busola Extension - -This example contains a basic custom extension that queries all deployments of a selected namespace of your cluster. Additionally, it retrieves the current weather data for Munich, Germany, from an external weather API. - -To set up and deploy your own custom Busola extension, follow these steps. - -1. Adjust the static HTML content. - -Edit the `ui.html` file to define the static HTML content for your custom extension. - -2. Configure dynamic components. - -Set up dynamic or behavioral components by modifying the custom element defined in the `script.js` file. - -- **Accessing Kubernetes resources**: Use the `fetchWrapper` function to interact with cluster resources through the Kubernetes API. - -- **Making external API requests**: Use the `proxyFetch` function to handle requests to external APIs that are subject to CORS regulations. - -3. Define extension metadata - -Update the `general.yaml` file to define metadata for your custom extension. - -> [! WARNING] -> Ensure that the `general.customElement` property matches the name of the custom element defined in `script.js`. The script is loaded only once, and this property is used to determine whether the custom element is already defined. - -4. Deploy your extension - -Before running the deployment command, ensure that your `kubeconfig` is correctly exported and points to the desired cluster. You can check the current context by running: - -```bash -kubectl config current-context -``` - -Run `./deploy-custom-extension.sh` to create a ConfigMap and deploy it to your cluster - -Alternatively, you can use the following command: - -```bash -kubectl kustomize . | kubectl apply -n kyma-system -f - -``` - -### 5. Test your changes locally - -Run `npm start` to start the development server. diff --git a/examples/custom-extension/deploy-custom-extension.sh b/examples/custom-extension/deploy-custom-extension.sh deleted file mode 100755 index 8861b96b68..0000000000 --- a/examples/custom-extension/deploy-custom-extension.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash - -kubectl kustomize . > ./custom-ui.yaml -kubectl apply -f ./custom-ui.yaml -n kyma-system diff --git a/examples/custom-extension/general.yaml b/examples/custom-extension/general.yaml deleted file mode 100644 index fce5e5daeb..0000000000 --- a/examples/custom-extension/general.yaml +++ /dev/null @@ -1,10 +0,0 @@ -resource: - kind: Secret - version: v1 -urlPath: custom-busola-extension-example -category: Kyma -name: Custom busola extension example -scope: cluster -customElement: my-custom-element -description: >- - Custom busola extension example diff --git a/examples/custom-extension/kustomization.yaml b/examples/custom-extension/kustomization.yaml deleted file mode 100644 index 331b64b818..0000000000 --- a/examples/custom-extension/kustomization.yaml +++ /dev/null @@ -1,11 +0,0 @@ -configMapGenerator: - - name: custom-ui - files: - - customHtml=ui.html - - customScript=script.js - - general=general.yaml - options: - disableNameSuffixHash: true - labels: - busola.io/extension: 'resource' - busola.io/extension-version: '0.5' diff --git a/examples/custom-extension/script.js b/examples/custom-extension/script.js deleted file mode 100644 index bc3c6bd601..0000000000 --- a/examples/custom-extension/script.js +++ /dev/null @@ -1,220 +0,0 @@ -function fetchWrapper(url, options = {}) { - if (window.extensionProps?.kymaFetchFn) { - return window.extensionProps.kymaFetchFn(url, options); - } - return fetch(url, options); -} - -function proxyFetch(url, options = {}) { - const baseUrl = window.location.hostname.startsWith('localhost') - ? 'http://localhost:3001/proxy' - : '/proxy'; - const encodedUrl = encodeURIComponent(url); - const proxyUrl = `${baseUrl}?url=${encodedUrl}`; - return fetch(proxyUrl, options); -} - -class MyCustomElement extends HTMLElement { - connectedCallback() { - const shadow = this.attachShadow({ mode: 'open' }); - - // Add basic styling - const style = document.createElement('style'); - style.textContent = ` - .container { - padding: 1rem;lu - } - .deployments-list { - margin-top: 1rem; - } - .deployment-item { - padding: 0.5rem; - margin: 0.5rem 0; - background: #f5f5f5; - border-radius: 4px; - } - .weather-container { - margin-top: 2rem; - padding: 1rem; - background: #e0f7fa; - border-radius: 8px; - } - .weather-item { - padding: 0.5rem 0; - margin: 0.5rem 0; - font-size: 1rem; - } - `; - shadow.appendChild(style); - - // Create container - const container = document.createElement('div'); - container.className = 'container'; - - // Create namespace dropdown - const namespaceSelect = document.createElement('ui5-select'); - namespaceSelect.id = 'namespaceSelect'; - container.appendChild(namespaceSelect); - - // Create deployments container - const deploymentsList = document.createElement('div'); - deploymentsList.className = 'deployments-list'; - container.appendChild(deploymentsList); - - // Create weather container - const weatherContainer = document.createElement('div'); - weatherContainer.className = 'weather-container'; - weatherContainer.id = 'weatherContainer'; - container.appendChild(weatherContainer); - - shadow.appendChild(container); - - // Load initial data - this.loadData(namespaceSelect, deploymentsList); - - // Add change listener - namespaceSelect.addEventListener('change', () => { - this.updateDeploymentsList(namespaceSelect.value, deploymentsList); - }); - - // Fetch and update weather data - fetchMunichWeatherData().then(weatherData => { - this.updateWeatherUI(weatherData, weatherContainer); - }); - } - - async loadData(namespaceSelect, deploymentsList) { - try { - // Get namespaces - const namespaces = await getNamespaces(); - - // Populate namespace dropdown - namespaces.forEach(namespace => { - const option = document.createElement('ui5-option'); - option.value = namespace.metadata.name; - option.innerHTML = namespace.metadata.name; - namespaceSelect.appendChild(option); - }); - - // Load deployments for first namespace - if (namespaces.length > 0) { - this.updateDeploymentsList( - namespaces[0].metadata.name, - deploymentsList, - ); - } - } catch (error) { - console.error('Failed to load data:', error); - } - } - - async updateDeploymentsList(namespace, deploymentsList) { - try { - const deployments = await getDeployments(namespace); - - // Clear current list - deploymentsList.innerHTML = ''; - - // Add deployment to list - deployments.forEach(deployment => { - const deploymentItem = document.createElement('div'); - deploymentItem.className = 'deployment-item'; - deploymentItem.innerHTML = ` -
Name: ${deployment.metadata.name}
- `; - deploymentsList.appendChild(deploymentItem); - }); - - // Show message if no deployments - if (deployments.length === 0) { - const messageStrip = document.createElement('ui5-message-strip'); - messageStrip.innerHTML = 'No deployments found in this namespace'; - - deploymentsList.innerHTML = messageStrip.outerHTML; - } - } catch (error) { - console.error('Failed to update deployments:', error); - deploymentsList.innerHTML = '
Error loading deployments
'; - } - } - - async updateWeatherUI(weatherData, weatherContainer) { - const { temperature, condition } = weatherData; - weatherContainer.innerHTML = ` - Current weather in Munich: -
Temperature: ${temperature}°C
-
Condition: ${condition}
- `; - } -} - -async function getNamespaces() { - const resp = await fetchWrapper('/api/v1/namespaces'); - const data = await resp.json(); - return data.items; -} - -async function getDeployments(namespace) { - const resp = await fetchWrapper( - `/apis/apps/v1/namespaces/${namespace}/deployments`, - ); - const data = await resp.json(); - return data.items; -} - -async function fetchMunichWeatherData() { - const latitude = 48.1351; - const longitude = 11.582; - const url = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}¤t_weather=true`; - - const response = await proxyFetch(url); - if (!response.ok) { - console.error(`Error fetching weather: ${response.status}`); - return; - } - const data = await response.json(); - - const currentWeather = data.current_weather; - const temperature = currentWeather.temperature; - const weatherCode = currentWeather.weathercode; - - const weatherConditions = { - 0: 'Clear sky', - 1: 'Mainly clear', - 2: 'Partly cloudy', - 3: 'Overcast', - 45: 'Fog', - 48: 'Depositing rime fog', - 51: 'Light drizzle', - 53: 'Moderate drizzle', - 55: 'Dense drizzle', - 56: 'Light freezing drizzle', - 57: 'Dense freezing drizzle', - 61: 'Slight rain', - 63: 'Moderate rain', - 65: 'Heavy rain', - 66: 'Light freezing rain', - 67: 'Heavy freezing rain', - 71: 'Slight snow fall', - 73: 'Moderate snow fall', - 75: 'Heavy snow fall', - 77: 'Snow grains', - 80: 'Slight rain showers', - 81: 'Moderate rain showers', - 82: 'Violent rain showers', - 85: 'Slight snow showers', - 86: 'Heavy snow showers', - 95: 'Thunderstorm', - 96: 'Thunderstorm with slight hail', - 99: 'Thunderstorm with heavy hail', - }; - - const condition = - weatherConditions[weatherCode] || 'Unknown weather condition'; - - return { temperature, condition }; -} - -if (!customElements.get('my-custom-element')) { - customElements.define('my-custom-element', MyCustomElement); -} diff --git a/examples/custom-extension/ui.html b/examples/custom-extension/ui.html deleted file mode 100644 index 7f2d43e838..0000000000 --- a/examples/custom-extension/ui.html +++ /dev/null @@ -1,6 +0,0 @@ -
- - Deployments in Namespace - - -
diff --git a/index.html b/index.html index 12afa0bd60..fcd8962a7f 100644 --- a/index.html +++ b/index.html @@ -19,10 +19,6 @@ content="width=device-width, initial-scale=1, shrink-to-fit=no" /> - Busola diff --git a/kyma/environments/dev/config.yaml b/kyma/environments/dev/config.yaml index a29e6a3976..a5651671db 100644 --- a/kyma/environments/dev/config.yaml +++ b/kyma/environments/dev/config.yaml @@ -66,8 +66,6 @@ config: isEnabled: true EXTENSIBILITY_INJECTIONS: isEnabled: true - EXTENSIBILITY_CUSTOM_COMPONENTS: - isEnabled: false EXTENSIBILITY_WIZARD: isEnabled: true TRACKING: diff --git a/kyma/environments/prod/config.yaml b/kyma/environments/prod/config.yaml index 2b239ac776..6c570ccd12 100644 --- a/kyma/environments/prod/config.yaml +++ b/kyma/environments/prod/config.yaml @@ -68,8 +68,6 @@ config: isEnabled: true EXTENSIBILITY_INJECTIONS: isEnabled: true - EXTENSIBILITY_CUSTOM_COMPONENTS: - isEnabled: false EXTENSIBILITY_WIZARD: isEnabled: true EVENTING: diff --git a/kyma/environments/stage/config.yaml b/kyma/environments/stage/config.yaml index a1bf90088b..8de69d54aa 100644 --- a/kyma/environments/stage/config.yaml +++ b/kyma/environments/stage/config.yaml @@ -66,8 +66,6 @@ config: isEnabled: true EXTENSIBILITY_INJECTIONS: isEnabled: true - EXTENSIBILITY_CUSTOM_COMPONENTS: - isEnabled: false EXTENSIBILITY_WIZARD: isEnabled: true EVENTING: diff --git a/kyma/extensions/kyma/kyma.yaml b/kyma/extensions/kyma/kyma.yaml index 51ae50792f..93294856ea 100644 --- a/kyma/extensions/kyma/kyma.yaml +++ b/kyma/extensions/kyma/kyma.yaml @@ -77,20 +77,7 @@ data: - name: Version source: version widget: Text - - name: Module State - widget: Badge - source: "$getModuleState($$)" - description: "$getModuleDescription($$)" - highlights: - positive: - - 'Ready' - critical: - - 'Error' - none: - - 'Processing' - - 'Deleting' - - 'Unknown' - - name: Installation State + - name: State widget: Badge source: 'state ? state : "UNNKOWN"' description: 'message ? message : ""' diff --git a/public/defaultConfig.yaml b/public/defaultConfig.yaml index a59bdaad55..15c3504ba3 100644 --- a/public/defaultConfig.yaml +++ b/public/defaultConfig.yaml @@ -49,8 +49,6 @@ config: isEnabled: true EXTENSIBILITY_INJECTIONS: isEnabled: true - EXTENSIBILITY_CUSTOM_COMPONENTS: - isEnabled: false EXTENSIBILITY_WIZARD: isEnabled: true TRACKING: diff --git a/sec-scanners-config.yaml b/sec-scanners-config.yaml index 97f1684e2e..7534b973de 100644 --- a/sec-scanners-config.yaml +++ b/sec-scanners-config.yaml @@ -5,8 +5,6 @@ protecode: whitesource: language: javascript exclude: - - 'package-lock.json' - - '**/backend/package-lock.json' - '**/backend/config/config.yaml' - '**/tests/**' - '**/kyma/enviroments/**' diff --git a/src/components/Extensibility/ExtensibilityList.js b/src/components/Extensibility/ExtensibilityList.js index 90864aab30..3c1bb5024b 100644 --- a/src/components/Extensibility/ExtensibilityList.js +++ b/src/components/Extensibility/ExtensibilityList.js @@ -1,6 +1,5 @@ import pluralize from 'pluralize'; import { useTranslation } from 'react-i18next'; -import { useEffect } from 'react'; import { ResourcesList } from 'shared/components/ResourcesList/ResourcesList'; import { usePrepareListProps } from 'resources/helpers'; @@ -23,9 +22,6 @@ import { sortBy } from './helpers/sortBy'; import { Widget } from './components/Widget'; import { DataSourcesContextProvider } from './contexts/DataSources'; import { useJsonata } from './hooks/useJsonata'; -import { useFeature } from 'hooks/useFeature'; -import { createPortal } from 'react-dom'; -import YamlUploadDialog from 'resources/Namespaces/YamlUpload/YamlUploadDialog'; export const ExtensibilityListCore = ({ resMetaData, @@ -144,43 +140,6 @@ const ExtensibilityList = ({ overrideResMetadata, ...props }) => { const defaultResMetadata = useGetCRbyPath(); const resMetaData = overrideResMetadata || defaultResMetadata; const { urlPath, defaultPlaceholder } = resMetaData?.general ?? {}; - const { isEnabled: isExtensibilityCustomComponentsEnabled } = useFeature( - 'EXTENSIBILITY_CUSTOM_COMPONENTS', - ); - - useEffect(() => { - const customElement = resMetaData?.general?.customElement; - const customScript = resMetaData?.customScript; - - if ( - isExtensibilityCustomComponentsEnabled && - customElement && - customScript && - !customElements.get(customElement) - ) { - const script = document.createElement('script'); - script.type = 'module'; - const scriptBlob = new Blob([customScript], { - type: 'application/javascript', - }); - const blobURL = URL.createObjectURL(scriptBlob); - script.src = blobURL; - - // Clean up the Blob URL after the script loads - script.onload = () => { - URL.revokeObjectURL(blobURL); - }; - - script.onerror = e => { - console.error('Script loading or execution error:', e); - }; - document.head.appendChild(script); - - return () => { - document.head.removeChild(script); - }; - } - }, [resMetaData, isExtensibilityCustomComponentsEnabled]); return ( { > - {isExtensibilityCustomComponentsEnabled && resMetaData.customHtml ? ( - <> -
- {createPortal(, document.body)} - - ) : ( - - )} +
diff --git a/src/components/Extensibility/components/Badge.js b/src/components/Extensibility/components/Badge.js index f2561f4fc9..ad07f66233 100644 --- a/src/components/Extensibility/components/Badge.js +++ b/src/components/Extensibility/components/Badge.js @@ -67,7 +67,6 @@ export function Badge({ else if (type === 'informative') type = 'Information'; else if (type === 'positive') type = 'Positive'; else if (type === 'critical') type = 'Negative'; - else if (type === 'none') type = 'None'; type = TYPE_FALLBACK.get(type) || type; diff --git a/src/components/Extensibility/helpers/jsonataWrapper.ts b/src/components/Extensibility/helpers/jsonataWrapper.ts index b9cefac11e..fdf12715cc 100644 --- a/src/components/Extensibility/helpers/jsonataWrapper.ts +++ b/src/components/Extensibility/helpers/jsonataWrapper.ts @@ -6,7 +6,6 @@ import { doesUserHavePermission } from 'state/navigation/filters/permissions'; import { permissionSetsSelector } from 'state/permissionSetsSelector'; import { jwtDecode } from 'jwt-decode'; import { AuthDataState, authDataState } from 'state/authDataAtom'; -import { useModuleStatus } from '../../KymaModules/support'; /* Turns jsonata expressions like @@ -65,16 +64,6 @@ export function jsonataWrapper(expression: string) { }, ); - exp.registerFunction('getModuleState', resource => { - const { data: status } = useModuleStatus(resource); - return status?.state || 'Unknown'; - }); - - exp.registerFunction('getModuleDescription', resource => { - const { data: status } = useModuleStatus(resource); - return status?.description; - }); - exp.registerFunction('compareStrings', (first, second) => { return first?.localeCompare(second) ?? 1; }); diff --git a/src/components/KymaModules/KymaModulesAddModule.js b/src/components/KymaModules/KymaModulesAddModule.js index 6c4f9df6c4..e0ca44c083 100644 --- a/src/components/KymaModules/KymaModulesAddModule.js +++ b/src/components/KymaModules/KymaModulesAddModule.js @@ -25,18 +25,11 @@ export default function KymaModulesAddModule({ const modulesResourceUrl = `/apis/operator.kyma-project.io/v1beta2/moduletemplates`; - const modulesReleaseMetaResourceUrl = `/apis/operator.kyma-project.io/v1beta2/modulereleasemetas`; - const { data: modules } = useGet(modulesResourceUrl, { pollingInterval: 3000, skip: !resourceName, }); - const { data: moduleReleaseMetas } = useGet(modulesReleaseMetaResourceUrl, { - pollingInterval: 3000, - skip: !resourceName, - }); - const [columnsCount, setColumnsCount] = useState(2); const [cardsContainerRef, setCardsContainerRef] = useState(null); @@ -84,65 +77,29 @@ export default function KymaModulesAddModule({ const isAlreadyInstalled = initialUnchangedResource?.spec?.modules?.find( installedModule => installedModule.name === name, ); - const moduleMetaRelase = moduleReleaseMetas?.items.find( - item => item.spec.moduleName === name, - ); - if (module.spec.channel) { - if (!existingModule && !isAlreadyInstalled) { - acc.push({ - name: name, - channels: [ - { - channel: module.spec.channel, - version: module.spec.descriptor.component.version, - isBeta: - module.metadata.labels['operator.kyma-project.io/beta'] === - 'true', - }, - ], - docsUrl: - module.metadata.annotations['operator.kyma-project.io/doc-url'], - }); - } else if (existingModule) { - existingModule.channels?.push({ - channel: module.spec.channel, - version: module.spec.descriptor.component.version, - isBeta: - module.metadata.labels['operator.kyma-project.io/beta'] === 'true', - }); - } - } else { - if (!existingModule && !isAlreadyInstalled) { - moduleMetaRelase?.spec.channels.forEach(channel => { - if (!acc.find(item => item.name === name)) { - acc.push({ - name: name, - channels: [ - { - channel: channel.channel, - version: channel.version, - isBeta: - module.metadata.labels['operator.kyma-project.io/beta'] === - 'true', - }, - ], - docsUrl: - module.metadata.annotations['operator.kyma-project.io/doc-url'], - }); - } else { - acc - .find(item => item.name === name) - .channels.push({ - channel: channel.channel, - version: channel.version, - isBeta: - module.metadata.labels['operator.kyma-project.io/beta'] === - 'true', - }); - } - }); - } + if (!existingModule && !isAlreadyInstalled) { + acc.push({ + name: name, + channels: [ + { + channel: module.spec.channel, + version: module.spec.descriptor.component.version, + isBeta: + module.metadata.labels['operator.kyma-project.io/beta'] === + 'true', + }, + ], + docsUrl: + module.metadata.annotations['operator.kyma-project.io/doc-url'], + }); + } else if (existingModule) { + existingModule.channels?.push({ + channel: module.spec.channel, + version: module.spec.descriptor.component.version, + isBeta: + module.metadata.labels['operator.kyma-project.io/beta'] === 'true', + }); } return acc ?? []; diff --git a/src/components/KymaModules/KymaModulesCreate.js b/src/components/KymaModules/KymaModulesCreate.js index f926979c88..8f98f8c70a 100644 --- a/src/components/KymaModules/KymaModulesCreate.js +++ b/src/components/KymaModules/KymaModulesCreate.js @@ -44,17 +44,7 @@ export default function KymaModulesCreate({ resource, ...props }) { const resourceName = kymaResource?.metadata.name; const modulesResourceUrl = `/apis/operator.kyma-project.io/v1beta2/moduletemplates`; - const modulesReleaseMetaResourceUrl = `/apis/operator.kyma-project.io/v1beta2/modulereleasemetas`; - - const { data: modules, loading: lodingModules } = useGet(modulesResourceUrl, { - pollingInterval: 3000, - skip: !resourceName, - }); - - const { - data: moduleReleaseMetas, - loading: loadingModulesReleaseMetas, - } = useGet(modulesReleaseMetaResourceUrl, { + const { data: modules, loading } = useGet(modulesResourceUrl, { pollingInterval: 3000, skip: !resourceName, }); @@ -78,7 +68,7 @@ export default function KymaModulesCreate({ resource, ...props }) { onSave: false, }); - if (lodingModules || loadingModulesReleaseMetas) { + if (loading) { return (
@@ -141,65 +131,29 @@ export default function KymaModulesCreate({ resource, ...props }) { const name = module.metadata?.labels['operator.kyma-project.io/module-name']; const existingModule = acc.find(item => item.name === name); - const moduleMetaRelase = moduleReleaseMetas?.items.find( - item => item.spec.moduleName === name, - ); - if (module.spec.channel) { - if (!existingModule) { - acc.push({ - name: name, - channels: [ - { - channel: module.spec.channel, - version: module.spec.descriptor.component.version, - isBeta: - module.metadata.labels['operator.kyma-project.io/beta'] === - 'true', - }, - ], - docsUrl: - module.metadata.annotations['operator.kyma-project.io/doc-url'], - }); - } else if (existingModule) { - existingModule.channels?.push({ - channel: module.spec.channel, - version: module.spec.descriptor.component.version, - isBeta: - module.metadata.labels['operator.kyma-project.io/beta'] === 'true', - }); - } + if (!existingModule) { + acc.push({ + name: name, + channels: [ + { + channel: module.spec.channel, + version: module.spec.descriptor.component.version, + isBeta: + module.metadata.labels['operator.kyma-project.io/beta'] === + 'true', + }, + ], + docsUrl: + module.metadata.annotations['operator.kyma-project.io/doc-url'], + }); } else { - if (!existingModule) { - moduleMetaRelase?.spec.channels.forEach(channel => { - if (!acc.find(item => item.name === name)) { - acc.push({ - name: name, - channels: [ - { - channel: channel.channel, - version: channel.version, - isBeta: - module.metadata.labels['operator.kyma-project.io/beta'] === - 'true', - }, - ], - docsUrl: - module.metadata.annotations['operator.kyma-project.io/doc-url'], - }); - } else { - acc - .find(item => item.name === name) - .channels.push({ - channel: channel.channel, - version: channel.version, - isBeta: - module.metadata.labels['operator.kyma-project.io/beta'] === - 'true', - }); - } - }); - } + existingModule.channels?.push({ + channel: module.spec.channel, + version: module.spec.descriptor.component.version, + isBeta: + module.metadata.labels['operator.kyma-project.io/beta'] === 'true', + }); } return acc; }, []); @@ -282,11 +236,9 @@ export default function KymaModulesCreate({ resource, ...props }) { value={channel.channel} additionalText={channel?.isBeta ? 'Beta' : ''} > - {`${( - channel?.channel[0] || '' - ).toUpperCase()}${channel.channel.slice(1)} (v${ - channel.version - })`}{' '} + {`${channel.channel[0].toUpperCase()}${channel.channel.slice( + 1, + )} (v${channel.version})`}{' '} ))} diff --git a/src/components/KymaModules/KymaModulesList.js b/src/components/KymaModules/KymaModulesList.js index 5952056e45..3f7800fa4e 100644 --- a/src/components/KymaModules/KymaModulesList.js +++ b/src/components/KymaModules/KymaModulesList.js @@ -201,7 +201,7 @@ export default function KymaModulesList({ <> {moduleStatus?.channel ? moduleStatus?.channel - : kymaResource?.spec?.modules?.[moduleIndex]?.channel} + : EMPTY_TEXT_PLACEHOLDER} {isChannelOverriden ? ( - {`${( - channel?.channel[0] || '' - ).toUpperCase()}${channel.channel.slice(1)} (v${ - channel.version - })`}{' '} + {`${channel.channel[0].toUpperCase()}${channel.channel.slice( + 1, + )} (v${channel.version})`}{' '} ))} diff --git a/src/components/Nodes/NodeResources/NodeResources.js b/src/components/Nodes/NodeResources/NodeResources.js index 28996dbf83..9a1e44cfc0 100644 --- a/src/components/Nodes/NodeResources/NodeResources.js +++ b/src/components/Nodes/NodeResources/NodeResources.js @@ -39,7 +39,7 @@ export function NodeResources({ metrics, resources }) { max={memory.capacity} additionalInfo={`${roundTwoDecimals( memory.usage, - )}Gi / ${roundTwoDecimals(memory.capacity)}Gi`} + )}GiB / ${roundTwoDecimals(memory.capacity)}GiB`} /> diff --git a/src/components/Nodes/nodeQueries.js b/src/components/Nodes/nodeQueries.js index 55585c57d1..335e35bf15 100644 --- a/src/components/Nodes/nodeQueries.js +++ b/src/components/Nodes/nodeQueries.js @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useState } from 'react'; +import React, { useMemo } from 'react'; import { useGet } from 'shared/hooks/BackendAPI/useGet'; import { getBytes, @@ -22,30 +22,27 @@ const formatMemory = memoryStr => const createUsageMetrics = (node, metricsForNode) => { const cpuUsage = formatCpu(metricsForNode?.usage.cpu); const memoryUsage = formatMemory(metricsForNode?.usage.memory); - const cpuCapacity = parseInt(node.status.allocatable?.cpu || '0'); - const memoryCapacity = formatMemory(node.status.allocatable?.memory); - - const cpuPercentage = getPercentageFromUsage(cpuUsage, cpuCapacity); - const memoryPercentage = getPercentageFromUsage(memoryUsage, memoryCapacity); + const cpuCapacity = parseInt(node.status.capacity?.cpu || '0') * 1000; + const memoryCapacity = formatMemory(node.status.capacity?.memory); return { cpu: { usage: cpuUsage, capacity: cpuCapacity, - percentage: cpuPercentage + '%', - percentageValue: cpuPercentage, + percentage: getPercentageFromUsage(cpuUsage, cpuCapacity) + '%', + percentageValue: getPercentageFromUsage(cpuUsage, cpuCapacity), }, memory: { usage: memoryUsage, capacity: memoryCapacity, - percentage: memoryPercentage + '%', - percentageValue: memoryPercentage, + percentage: getPercentageFromUsage(memoryUsage, memoryCapacity) + '%', + percentageValue: getPercentageFromUsage(memoryUsage, memoryCapacity), }, }; }; export function useNodesQuery(skip = false) { - const [data, setData] = useState(null); + const [data, setData] = React.useState(null); const { data: nodeMetrics, loading: metricsLoading } = useGet( '/apis/metrics.k8s.io/v1beta1/nodes', { @@ -60,7 +57,7 @@ export function useNodesQuery(skip = false) { loading: nodesLoading, } = useGet('/api/v1/nodes', { pollingInterval: 5500, skip }); - useEffect(() => { + React.useEffect(() => { if (nodes) { const getNodeMetrics = node => { const metricsForNode = nodeMetrics.items.find( @@ -86,7 +83,7 @@ export function useNodesQuery(skip = false) { } export function useNodeQuery(nodeName) { - const [data, setData] = useState(null); + const [data, setData] = React.useState(null); const { data: nodeMetrics, error: metricsError, @@ -101,7 +98,7 @@ export function useNodeQuery(nodeName) { loading: nodeLoading, } = useGet(`/api/v1/nodes/${nodeName}`, { pollingInterval: 3000 }); - useEffect(() => { + React.useEffect(() => { if (node) { setData({ node, @@ -155,9 +152,7 @@ function addResources(a, b) { function sumContainersResources(containers) { return containers?.reduce((containerAccu, container) => { - const containerResources = container.resources; - const updatedResources = addResources(containerAccu, containerResources); - return updatedResources; + return addResources(containerAccu, container.resources); }, structuredClone(emptyResources)); } @@ -186,14 +181,14 @@ export function calcNodeResources(pods) { } export function useResourceByNode(nodeName) { - const [data, setData] = useState(null); + const [data, setData] = React.useState(null); const { data: pods, error, loading } = useGet( `/api/v1/pods?fieldSelector=spec.nodeName=${nodeName},status.phase!=Failed,status.phase!=Succeeded&limit=500`, ); const nodeResources = useMemo(() => calcNodeResources(pods), [pods]); - useEffect(() => { + React.useEffect(() => { if (nodeResources) { setData(nodeResources); } diff --git a/src/resources/Deployments/DeploymentDetails.js b/src/resources/Deployments/DeploymentDetails.js index c6448828a9..5740639484 100644 --- a/src/resources/Deployments/DeploymentDetails.js +++ b/src/resources/Deployments/DeploymentDetails.js @@ -64,12 +64,7 @@ export function DeploymentDetails(props) { const statusConditions = deployment => { return deployment?.status?.conditions?.map(condition => { return { - header: { - titleText: condition.type, - status: condition.status, - overrideStatusType: - condition.type === 'ReplicaFailure' ? 'False' : condition.status, - }, + header: { titleText: condition.type, status: condition.status }, message: condition.message, }; }); diff --git a/src/resources/Deployments/DeploymentStatus.js b/src/resources/Deployments/DeploymentStatus.js index 0fb4fe10d3..5bf2456caf 100644 --- a/src/resources/Deployments/DeploymentStatus.js +++ b/src/resources/Deployments/DeploymentStatus.js @@ -4,7 +4,7 @@ import { RunningPodsStatus } from 'shared/components/RunningPodsStatus'; export function DeploymentStatus({ deployment }) { const running = deployment.status.readyReplicas || 0; - const expected = deployment.status.replicas || deployment.spec.replicas || 0; + const expected = deployment.status.replicas || 0; return ; } diff --git a/src/resources/Namespaces/AllNamespacesDetails.js b/src/resources/Namespaces/AllNamespacesDetails.js index 8302f233d6..0fb112b522 100644 --- a/src/resources/Namespaces/AllNamespacesDetails.js +++ b/src/resources/Namespaces/AllNamespacesDetails.js @@ -44,7 +44,7 @@ export function AllNamespacesDetails() { const Events = ; const headerActions = ( -
+ <> {createPortal(, document.body)} -
+ ); return ( diff --git a/src/resources/Namespaces/NamespaceWorkloads/NamespaceWorkloadsHelpers.js b/src/resources/Namespaces/NamespaceWorkloads/NamespaceWorkloadsHelpers.js index d935525d1f..2e72432885 100644 --- a/src/resources/Namespaces/NamespaceWorkloads/NamespaceWorkloadsHelpers.js +++ b/src/resources/Namespaces/NamespaceWorkloads/NamespaceWorkloadsHelpers.js @@ -1,12 +1,8 @@ import { calculatePodState } from 'resources/Pods/PodStatus'; export function getHealthyReplicasCount(resource) { - return resource?.filter(r => { - const running = r.status.readyReplicas || 0; - const expected = r.status.replicas || r.spec.replicas || 0; - - return running === expected; - })?.length; + return resource?.filter(r => r.status.replicas === r.status.readyReplicas) + ?.length; } export const PodStatusCounterKey = { diff --git a/src/resources/Namespaces/ResourcesUsage.js b/src/resources/Namespaces/ResourcesUsage.js index 3bc6eb5426..718403e83f 100644 --- a/src/resources/Namespaces/ResourcesUsage.js +++ b/src/resources/Namespaces/ResourcesUsage.js @@ -9,7 +9,6 @@ import { Card, CardHeader } from '@ui5/webcomponents-react'; const MEMORY_SUFFIX_POWER = { // must be sorted from the smallest to the largest; it is case sensitive; more info: https://medium.com/swlh/understanding-kubernetes-resource-cpu-and-memory-units-30284b3cc866 m: 1e-3, - k: 1e3, K: 1e3, Ki: 2 ** 10, M: 1e6, @@ -23,13 +22,24 @@ const CPU_SUFFIX_POWER = { m: 1e-3, }; -export function getBytes(memoryStr) { - if (!memoryStr) return 0; +export function getBytes(memoryString) { + if (!memoryString || memoryString === '0') { + return 0; + } + const suffixMatch = String(memoryString).match(/\D+$/); + + if (!suffixMatch?.length) { + return memoryString; + } + const suffix = suffixMatch[0]; + const number = String(memoryString).replace(suffix, ''); - const unit = String(memoryStr).match(/[a-zA-Z]+/g)?.[0]; - const value = parseFloat(memoryStr); - const bytes = value * (MEMORY_SUFFIX_POWER[unit] || 1); - return bytes; + const suffixPower = MEMORY_SUFFIX_POWER[suffix]; + if (!suffixPower) { + return number; + } + + return number * suffixPower; } export function getCpus(cpuString) { diff --git a/src/resources/other/kymaModules.routes.js b/src/resources/other/kymaModules.routes.js index 2f0fef3eda..d0a90e2222 100644 --- a/src/resources/other/kymaModules.routes.js +++ b/src/resources/other/kymaModules.routes.js @@ -112,7 +112,7 @@ const ColumnWraper = (defaultColumn = 'list') => { let startColumnComponent = null; const headerActions = ( -
+ <> @@ -139,7 +139,7 @@ const ColumnWraper = (defaultColumn = 'list') => { />, document.body, )} -
+ ); if (!layout && defaultColumn === 'details') { diff --git a/src/shared/components/ConditionList/ConditionList.tsx b/src/shared/components/ConditionList/ConditionList.tsx index dae0b0dab3..397c7390b5 100644 --- a/src/shared/components/ConditionList/ConditionList.tsx +++ b/src/shared/components/ConditionList/ConditionList.tsx @@ -18,7 +18,6 @@ type ConditionItem = { type ConditionHeader = { titleText: string | ReactNode; status?: string; - overrideStatusType?: string; }; export const ConditionList = ({ @@ -35,7 +34,6 @@ export const ConditionList = ({ key={index} header={cond.header?.titleText} status={cond.header?.status} - overrideStatusType={cond.header?.overrideStatusType} content={cond.message} customContent={cond.customContent} /> diff --git a/src/shared/components/DynamicPageComponent/DynamicPageComponent.js b/src/shared/components/DynamicPageComponent/DynamicPageComponent.js index 4bc029aaab..3328464404 100644 --- a/src/shared/components/DynamicPageComponent/DynamicPageComponent.js +++ b/src/shared/components/DynamicPageComponent/DynamicPageComponent.js @@ -164,7 +164,7 @@ export const DynamicPageComponent = ({ > {actions && ( - <> +
{actions} {(window.location.search.includes('layout') || (!window.location.search.includes('layout') && @@ -172,7 +172,7 @@ export const DynamicPageComponent = ({ layoutNumber !== 'StartColumn' ? ( ) : null} - +
)} {window.location.search.includes('layout') || (!window.location.search.includes('layout') && @@ -290,7 +290,6 @@ export const DynamicPageComponent = ({ } actionsBar={actionsBar} - actionsToolbarProps={{ numberOfAlwaysVisibleItems: 2 }} /> ); diff --git a/src/shared/components/DynamicPageComponent/DynamicPageComponent.scss b/src/shared/components/DynamicPageComponent/DynamicPageComponent.scss index 5dc45917cd..a1122a9d0b 100644 --- a/src/shared/components/DynamicPageComponent/DynamicPageComponent.scss +++ b/src/shared/components/DynamicPageComponent/DynamicPageComponent.scss @@ -32,15 +32,11 @@ display: flex; align-items: center; justify-content: flex-end; - } - - [data-component-name='ObjectPageTitleMiddleSection'] > div { - flex-basis: 100%; - } - ui5-button { - &:not(:last-of-type) { - margin-right: 0.5rem; + ui5-button { + &:not(:last-of-type) { + margin-right: 0.5rem; + } } } @@ -54,18 +50,6 @@ } } - .separator { - display: inline-block; - width: 1px; - position: absolute; - top: 50%; - height: 1.5rem; - margin-top: -0.75rem; - margin-left: 5px; - background-color: var(--sapList_BorderColor); - border-radius: 5px; - } - &::part(fit-content) { position: static; padding: unset; diff --git a/src/shared/components/ExpandableListItem/ExpandableListItem.tsx b/src/shared/components/ExpandableListItem/ExpandableListItem.tsx index bc05a2aef3..0f8ad6751a 100644 --- a/src/shared/components/ExpandableListItem/ExpandableListItem.tsx +++ b/src/shared/components/ExpandableListItem/ExpandableListItem.tsx @@ -8,7 +8,6 @@ import './ExpandableListItem.scss'; type ExpandableListItemProps = { header: string | ReactNode; status?: string; - overrideStatusType?: string; content?: string; customContent?: CustomContent[]; }; @@ -22,18 +21,12 @@ export type CustomContent = { export const ExpandableListItem = ({ header, status, - overrideStatusType, content, customContent, }: ExpandableListItemProps) => { const { t } = useTranslation(); const [expanded, setExpanded] = useState(false); - let statusType = status === 'True' ? 'Positive' : 'Negative'; - if (overrideStatusType !== undefined) { - statusType = overrideStatusType === 'True' ? 'Positive' : 'Negative'; - } - return ( <> + {status} )} diff --git a/src/shared/components/ResourceDetails/ResourceDetails.js b/src/shared/components/ResourceDetails/ResourceDetails.js index 698a9304b0..db2588d4ce 100644 --- a/src/shared/components/ResourceDetails/ResourceDetails.js +++ b/src/shared/components/ResourceDetails/ResourceDetails.js @@ -208,33 +208,27 @@ function Resource({ {headerActions} {resourceHeaderActions.map(resourceAction => resourceAction(resource))} -
- {!disableDelete && ( - <> - - - {createPortal( - , - document.body, - )} - - )} - {createPortal(, document.body)} -
+ {!disableDelete && ( + <> + + {createPortal( + , + document.body, + )} + + )} + {createPortal(, document.body)} ); diff --git a/src/state/navigation/extensionsAtom.ts b/src/state/navigation/extensionsAtom.ts index 426102e79e..591e35701d 100644 --- a/src/state/navigation/extensionsAtom.ts +++ b/src/state/navigation/extensionsAtom.ts @@ -45,8 +45,6 @@ type ConfigMapData = { dataSources: string; translations: string; presets: string; - customScript: string; - customHtml: string; }; type ConfigMapResponse = K8sResource & { @@ -63,10 +61,6 @@ type ConfigMapListResponse = } | undefined; -interface ExtensionProps { - kymaFetchFn: (url: string, options?: any) => Promise; -} - const isTheSameNameAndUrl = ( firstCM: Partial, secondCM: Partial, @@ -294,7 +288,6 @@ const getExtensions = async ( kubeconfigNamespace = 'kube-public', currentNamespace: string, permissionSet: PermissionSetState, - extCustomComponentsEnabled: boolean, ) => { if (!fetchFn) { return null; @@ -331,12 +324,6 @@ const getExtensions = async ( ) as ExtResource, }; - if (extCustomComponentsEnabled) { - extResourceWithMetadata.data.customHtml = - currentConfigMap.data.customHtml || ''; - extResourceWithMetadata.data.customScript = - currentConfigMap.data.customScript || ''; - } if (!extResourceWithMetadata.data) return accumulator; const indexOfTheSameExtension = accumulator.findIndex(ext => @@ -419,43 +406,10 @@ export const useGetExtensions = () => { const { isEnabled: isExtensibilityWizardEnabled } = useFeature( 'EXTENSIBILITY_WIZARD', ); - const { isEnabled: isExtensibilityCustomComponentsEnabled } = useFeature( - 'EXTENSIBILITY_CUSTOM_COMPONENTS', - ); const { data: crds } = useGet( `/apis/apiextensions.k8s.io/v1/customresourcedefinitions`, ); - useEffect(() => { - if (isExtensibilityCustomComponentsEnabled) { - // Wrap busola fetch function to be able to use it in the extensions as regular fetch. - // It reduces the learning curve for the extension developers and introduces loose coupling between Busola and the extensions. - function asRegularFetch(busolaFetch: FetchFn, url: string, options: any) { - return busolaFetch({ - relativeUrl: url, - init: options, - abortController: options?.signal - ? { signal: options?.signal, abort: () => {} } - : undefined, - }); - } - - if (fetchFn) { - (window as Window & { - extensionProps?: ExtensionProps; - }).extensionProps = { - kymaFetchFn: (url: string, options: any) => - asRegularFetch(fetchFn, url, options), - }; - } - } - - return () => { - delete (window as Window & { extensionProps?: ExtensionProps }) - .extensionProps; - }; - }, [fetchFn, auth, isExtensibilityCustomComponentsEnabled]); - useEffect(() => { (crds as any)?.items.forEach((crd: CustomResourceDefinition) => { RESOURCE_PATH[crd?.spec.names.kind as keyof typeof RESOURCE_PATH] = @@ -479,7 +433,6 @@ export const useGetExtensions = () => { cluster.currentContext.namespace || 'kube-public', namespace, permissionSet, - isExtensibilityCustomComponentsEnabled ?? false, ); const statics = await getStatics( diff --git a/src/state/types.ts b/src/state/types.ts index 3d5341721f..453b9ae06d 100644 --- a/src/state/types.ts +++ b/src/state/types.ts @@ -22,7 +22,6 @@ export const configFeaturesNames = { VISUAL_RESOURCES: 'VISUAL_RESOURCES', EXTENSIBILITY: 'EXTENSIBILITY', EXTENSIBILITY_INJECTIONS: 'EXTENSIBILITY_INJECTIONS', - EXTENSIBILITY_CUSTOM_COMPONENTS: 'EXTENSIBILITY_CUSTOM_COMPONENTS', EXTENSIBILITY_WIZARD: 'EXTENSIBILITY_WIZARD', TRACKING: 'TRACKING', PROTECTED_RESOURCES: 'PROTECTED_RESOURCES', @@ -99,8 +98,6 @@ export type ExtResource = { presets: any[]; dataSources: Record; injections?: ExtInjection[]; - customHtml: {}; - customScript: {}; }; export type ExtensibilityNodesExt = { diff --git a/tests/integration/tests/accessibility/test-acc-cron-jobs.spec.js b/tests/integration/tests/accessibility/test-acc-cron-jobs.spec.js index ec3540e1db..c76440b73b 100644 --- a/tests/integration/tests/accessibility/test-acc-cron-jobs.spec.js +++ b/tests/integration/tests/accessibility/test-acc-cron-jobs.spec.js @@ -91,9 +91,8 @@ context('Accessibility test Cron Jobs', () => { .clear() .type('*', { force: true }); - cy.contains( - 'ui5-label', - 'Schedule: At 12:00 AM, on day 1 of the month', + cy.get( + '[aria-label="Schedule: At 12:00 AM, on day 1 of the month, expanded"]', ).click(); cy.contains('Command').click(); diff --git a/tests/integration/tests/cluster/test-custom-resources.spec.js b/tests/integration/tests/cluster/test-custom-resources.spec.js index c590c4b1f0..774871fde4 100644 --- a/tests/integration/tests/cluster/test-custom-resources.spec.js +++ b/tests/integration/tests/cluster/test-custom-resources.spec.js @@ -107,7 +107,6 @@ context('Test Custom Resources', () => { cy.get('ui5-input[id="search-input"]:visible') .find('input') .wait(1000) - .clear() .type('cypress'); cy.clickGenericListLink('Tclusters'); diff --git a/tests/integration/tests/namespace/test-service-accounts.spec.js b/tests/integration/tests/namespace/test-service-accounts.spec.js index e1babfd67e..16866d3ed1 100644 --- a/tests/integration/tests/namespace/test-service-accounts.spec.js +++ b/tests/integration/tests/namespace/test-service-accounts.spec.js @@ -98,7 +98,12 @@ context('Test Service Accounts', () => { }); it('Generate TokenRequest', () => { - cy.get('[data-component-name="ToolbarChildContainer"]') + cy.getMidColumn() + .find('header') + .find('ui5-toggle-button:visible') + .click(); + + cy.get('[data-component-name="ToolbarOverflowPopoverContent"]') .contains('ui5-button', 'Generate TokenRequest') .click();