diff --git a/.github/workflows/accessibility-tests.yml b/.github/workflows/accessibility-tests.yml
index 78b66b255f..e0f30e0a59 100644
--- a/.github/workflows/accessibility-tests.yml
+++ b/.github/workflows/accessibility-tests.yml
@@ -13,11 +13,9 @@ on:
jobs:
run-accessibility-tests:
runs-on: ubuntu-latest
+ if: github.event.pull_request.draft == false
steps:
- uses: actions/checkout@v4
- with:
- ref: ${{ github.event.pull_request.head.sha }}
- fetch-depth: 0
- name: Install k3d
env:
K3D_URL: https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh
@@ -29,30 +27,17 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: 20
- - name: setup_busola
+ - name: Setup busola
shell: bash
run: |
- set -e
- npm ci
- npm run build
- npm i -g serve
- - name: run_tests
+ .github/scripts/setup_local_busola.sh
+ - name: Run tests
shell: bash
env:
ACC_AMP_TOKEN: ${{ secrets.ACC_AMP_TOKEN }}
run: |
- k3d kubeconfig get kyma > tests/integration/fixtures/kubeconfig.yaml
- export CYPRESS_DOMAIN=http://localhost:3000
- serve -s build > busola.log &
-
- pushd backend
- npm start > backend.log &
- popd
-
- echo "waiting for server to be up..."
- while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' "$CYPRESS_DOMAIN")" != "200" ]]; do sleep 5; done
- sleep 10
-
+ k3d kubeconfig get k3dCluster > tests/integration/fixtures/kubeconfig.yaml
+ export CYPRESS_DOMAIN=http://localhost:3001
cd tests/integration
npm ci && ACC_AMP_TOKEN=$ACC_AMP_TOKEN npm run "test:accesibility"
- name: Uploads artifacts
diff --git a/README.md b/README.md
index 7e1a6e5259..46dcb5ce8b 100644
--- a/README.md
+++ b/README.md
@@ -180,7 +180,13 @@ For the information on how to run tests and configure them, go to the [`tests`](
## Deploy Busola in the Kubernetes Cluster
-To install Busola in the Kubernetes cluster, run:
+To install Busola from release in the Kubernetes cluster set `VERSION` shell environment variable with desired release and run:
+
+```shell
+kubectl apply -f https://github.com/kyma-project/busola/releases/download/${VERSION}/busola.yaml
+```
+
+To install Busola from main branch in the Kubernetes cluster, run:
```shell
(cd resources && kustomize build base/ | kubectl apply -f- )
diff --git a/docs/extensibility/100-jsonata.md b/docs/extensibility/100-jsonata.md
new file mode 100644
index 0000000000..2e1e52bf74
--- /dev/null
+++ b/docs/extensibility/100-jsonata.md
@@ -0,0 +1,63 @@
+# Use JSONata Expressions with Resource-Based Extensions
+
+## Scoping
+
+The primary data source of [JSONata](https://docs.jsonata.org/overview.html) expressions changes depending on where it's used. Starting with the root, it contains the whole resource, but whenever it's in a child whose parent has a **source** (in lists and details) or **path** (in forms) parameter, the scope changes to data from that source or path.
+
+Additionally, the scope in arrays changes to the array item.
+
+For example, for this resource:
+
+```yaml
+spec:
+ name: foo
+ description: bar
+ items:
+ - name: item-name
+ details:
+ status: ok
+```
+
+The following definition has their scope changed as follows:
+
+```yaml
+- source: spec.name # top level, scope is the same as a resource
+
+- source: spec # top level, scope is the same as a resource
+ children:
+ - source: name # parent has source=spec, therefore this refers to spec.name
+
+- children:
+ - source: spec.name # As there's no parent source here, the scope is still the resource
+
+- source: spec.items
+ children:
+ - source: name # parent data is an array, therefore scope changes to its item - this refers to spec.items[0].name
+ - source: details.status # refers to spec.items[0].details.status (same as above)
+ - source: details # this changes scope for its children again
+ children:
+ source: status # this refers to spec.items[0].details.status
+```
+
+## Common Variables
+
+Common variables are the primary means to bypass the default scoping.
+
+- **\$root** - always contains the reference to the resource, so any JSONata in the example above can always be `$root.spec.name`.
+- **\$item** - refers to the most recent array item. When not in an array, it's equal to **\$root**.
+- **\$items** - contains an array of references to all parent array items (with the last item being equal to **\$item**).
+- **\$value** - when used in a JSONata other than **source** (for example **visibility**, but also other widget-specific formulas), contains the value returned by the source.
+- **\$index** - exists in array components, refers to the index of the current item of an array.
+
+### Example
+
+```yaml
+- widget: Table
+ source: spec.rules
+ visibility: $exists($value)
+ collapsibleTitle: "'Rule #' & $string($index + 1)"
+```
+
+## Data Sources
+
+Whenever data sources are provided, they are available as corresponding variable names. For more information, see [Configure the dataSources Section](90-datasources.md).
diff --git a/docs/extensibility/101-preset-functions.md b/docs/extensibility/101-preset-functions.md
new file mode 100644
index 0000000000..104761153a
--- /dev/null
+++ b/docs/extensibility/101-preset-functions.md
@@ -0,0 +1,87 @@
+# JSONata Preset Functions for Resource-Based Extensions
+
+## canI (resourceGroupAndVersion, resourceKind)
+
+You can use the **canI** function to determine if a user has access rights to list a specified resource. The function comes with the following parameters:
+
+- **resourceGroupAndVersion**: Determines the first part of a resource URL following the pattern: `${resource group}/${resource version}`.
+- **resourceKind**: Describes a resource kind.
+
+### Example
+
+```yaml
+- path: spec.gateway
+ name: gateway
+ visibility: $not($canI('networking.istio.io/v1beta1', 'Gateway'))
+```
+
+## compareStrings (first, second)
+
+You can use this function to sort two strings alphabetically. The function comes with the following parameters:
+
+- **first**: Determines the first string to compare.
+- **second**: Determines the second string to compare.
+
+### Example
+
+Here is an example from the [ResourceList widget](./50-list-and-details-widgets.md#resourcelist):
+
+```yaml
+- widget: ResourceList
+ source: '$myDeployments()'
+ name: Example ResourceList Deployments
+ sort:
+ - source: '$item.spec.strategy.type'
+ compareFunction: '$compareStrings($second, $first)'
+ default: true
+```
+
+## matchByLabelSelector (item, selectorPath)
+
+You can use this function to match Pods using a resource selector. The function comes with the following parameters:
+
+- **item**: Describes a Pod to be used.
+- **selectorPath**: Defines a path to selector labels from `$root`.
+
+### Example
+
+Example from [dataSources](90-datasources.md).
+
+```yaml
+- podSelector:
+ resource:
+ kind: Pod
+ version: v1
+ filter: '$matchByLabelSelector($item, $root.spec.selector)'
+```
+
+## matchEvents (item, kind, name)
+
+You can use this function to match Events using a resource selector. The function comes with the following parameters:
+
+- **item**: Describes an Event to be checked.
+- **kind**: Describes the kind of the Event emitting resource.
+- **name**: Describes the name of the Event emitting resource.
+
+### Example
+
+```yaml
+- widget: EventList
+ filter: '$matchEvents($item, $root.kind, $root.metadata.name)'
+ name: events
+ defaultType: NORMAL
+ hideInvolvedObjects: true
+```
+
+## readableTimestamp (timestamp)
+
+You can use this function to convert time to readable time. The function comes with the following parameters:
+
+- **timestamp**: Defines a timestamp to convert.
+
+### Example
+
+```yaml
+- source: '$readableTimestamp($item.lastTransitionTime)'
+ name: status.conditions.lastTransitionTime
+```
diff --git a/docs/extensibility/110-presets.md b/docs/extensibility/110-presets.md
new file mode 100644
index 0000000000..d0522019ef
--- /dev/null
+++ b/docs/extensibility/110-presets.md
@@ -0,0 +1,37 @@
+# Configure the presets Section
+
+The **presets** section contains a list of objects that define which preset and template are used in the form view. If you specify a preset, it is displayed in the dropdown list along with the **Clear** option. When you select a preset, the form is filled with the values defined in the **value** property.
+
+## Available Parameters
+
+| Parameter | Required | Type | Description |
+| ----------- | -------- | ------- | ------------------------------------------------------------------------------------------------ |
+| **name** | **Yes** | string | A name to display on the preset's dropdown. |
+| **value** | **Yes** | | It contains the fields that are set when you choose the given preset from the list. |
+| **default** | No | boolean | If set to `true`, it prefills the form with values defined in **value**. It defaults to `false`. |
+
+## Example
+
+```yaml
+- name: template
+ default: true
+ value:
+ metadata:
+ name: my-name
+ spec:
+ description: A set description
+- name: preset
+ value:
+ metadata:
+ name: second-one
+ spec:
+ data: regex
+ description: A different description
+ items:
+ - name: item-1
+ value: 10
+ - name: item-2
+ value: 11
+ - name: item-3
+ value: 5
+```
diff --git a/docs/extensibility/120-resource-extensions.md b/docs/extensibility/120-resource-extensions.md
new file mode 100644
index 0000000000..a2ff23c04b
--- /dev/null
+++ b/docs/extensibility/120-resource-extensions.md
@@ -0,0 +1,72 @@
+# Configure a Config Map for Resource-Based Extensions
+
+You can set up your ConfigMap to handle your UI page by adding objects to the **general** section. This section contains basic information about the resource and additional options.
+You can provide all the ConfigMap data sections as either JSON or YAML.
+
+## Extension Version
+
+The version is a string value that defines in which version the extension is configured. It is stored as a value of the `busola.io/extension-version` label. If the configuration is created with the **Create Extension** button, this value is provided automatically. When created manually, use the latest version number, for example, `'0.5'`.
+
+> [!NOTE]
+> Busola supports only the two latest versions of the configuration. Whenever a new version of the configuration is proposed, go to your Extension and migrate your configuration to the latest version.
+
+## Available Parameters
+
+| Parameter | Required | Type | Description | | | | | |
+| ---------------------------------- | -------- | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------ | --- | ------ | ------------------------------------------------------------------------------------------------------------------ | --- |
+| **resource** | **Yes** | | A resource defined based on the following properties: | | | | | |
+| **resource.kind** | **Yes** | string | A Kubernetes resource kind. | | | | | |
+| **resource.version** | **Yes** | string | A Kubernetes resource version. | **resource.group** | No | string | An API group used for all the requests. It's not provided for the Kubernetes resources in the core (legacy) group. | |
+| **name** | No | string | A name used a title in the navigation and on the list screen. It defaults to its resource kind. | | | | | |
+| **category** | No | string | A name of the category used for the left-hand menu. By default, it's placed in the `Custom Resources` category. | | | | | |
+| **icon** | No | | A suffix of an icon name used for the left-hand menu. The default value is `customized`. You can find the list of icons [here](https://sap.github.io/fundamental-react/?path=/docs/component-api-icon--primary). | | | | | |
+| **scope** | No | string | It can be `namespace` or `cluster`. It defaults to `cluster`. | | | | | |
+| **urlPath** | No | string | A path fragment for this resource used in the URL. Defaults to pluralized lowercase **kind**. It is used to provide an alternative URL to avoid conflicts with other resources. | | | | | |
+| **defaultPlaceholder** | No | string | It is visible in an empty text placeholder. Overridden by the widget-level **placeholder**. Defaults to `-`. | | | | | |
+| **description** | No | string | It displays a custom description on the resource list page. It can contain links. If the **translations** section has a translation entry with the ID that is the same as the **description** string, the translation is used. | | | | | |
+| **filter** | No | string, [JSONata](100-jsonata.md) expression | An optional JSONata [filter](https://docs.jsonata.org/higher-order-functions#filter) used to filter the resources shown at the list section property. | | | | | |
+| **features** | No | boolean | An object for the features configuration. | | | | | |
+| **features.actions** | No | boolean | An object for the actions configuration. | | | | | |
+| **features.actions.disableCreate** | No | boolean | When set to `true`, it disables the **Create** button. It defaults to `false`. | | | | | |
+| **features.actions.disableEdit** | No | boolean | When set to `true`, it disables the **Edit** button. It defaults to `false`. | | | | | |
+| **features.actions.disableDelete** | No | boolean | When set to `true`, it disables the **Delete** button. It defaults to `false`. | | | | | |
+| **externalNodes** | No | string | A list of links to external websites. | | | | | |
+| **externalNodes.category** | No | string | A name of the category. | | | | | |
+| **externalNodes.scope** | No | string | It can be `namespace` or `cluster`. It defaults to `cluster`. | | | | | |
+| **externalNodes.icon** | No | string | An icon that you can choose from the [Icon Explorer](https://sdk.openui5.org/test-resources/sap/m/demokit/iconExplorer/webapp/index.html#/overview). | | | | | |
+| **externalNodes.children** | No | string | A list of child nodes containing details about the links. | | | | | |
+| **externalNodes.children.label** | No | string | A displayed label. | | | | | |
+| **externalNodes.children.link** | No | string, [JSONata](100-jsonata.md) expression | A link to an external website. | | | | | |
+
+### Example
+
+```yaml
+resource:
+ kind: MyResource
+ version: v1alpha3
+ group: networking.istio.io
+name: MyResourceName
+category: My Category
+scope: namespace
+defaultPlaceholder: '- not set -'
+description: See the {{[docs](https://github.com/kyma-project/busola)}} for more information.
+filter: "$filter(data, function($data) {$data.type = 'Opaque'})"
+features:
+ actions:
+ disableCreate: true
+ disableDelete: true
+externalNodes:
+ - category: My Category
+ icon: course-book
+ children:
+ - label: Example Node Label
+ link: 'https://github.com/kyma-project/busola'
+ - category: My Second Category
+ icon: bar-chart
+ scope: namespace
+ children:
+ - label: Example Node Label
+ link: '$string($exampleResource().link)'
+```
+
+For more information, see [Additional Sections for Resource-Based Extensions](130-additional-sections-resources.md).
diff --git a/docs/extensibility/130-additional-sections-resources.md b/docs/extensibility/130-additional-sections-resources.md
new file mode 100644
index 0000000000..a5011b41ea
--- /dev/null
+++ b/docs/extensibility/130-additional-sections-resources.md
@@ -0,0 +1,29 @@
+# Additional Sections for Resource-Based Extensions
+
+## Form Section
+
+To customize the **form** section see the [Create forms with extensibility](./40-form-fields.md) documentation.
+Views created with the extensibility [ConfigMap wizard](README.md) have a straightforward form configuration by default.
+
+## List Section
+
+The **list** section presents the resources of a kind, that is, Secrets or ConfigMaps, and comes with a few predefined columns: **Name**, **Created**, and **Labels**.
+If you want to add your own columns, see [Customize UI display](./30-details-summary.md) to learn how to customize both list and details views.
+
+## Details Section
+
+The **details** section presents the resource details. To customize it, see [Customize UI display](./30-details-summary.md). The default details header contains some basic information. By default, the body is empty.
+
+## Value Preprocessors
+
+Value preprocessors are used as a middleware between a value and the actual renderer. They can transform a given value and pass it to the widget, or stop processing and render it so you can view it immediately, without passing it to the widget.
+
+### List of Value Preprocessors
+
+- **PendingWrapper** - useful when value resolves to a triple of `{loading, error, data}`:
+
+ - For `loading` equal to `true`, it displays a loading indicator.
+ - For truthy `error`, it displays an error message.
+ - Otherwise, it passes `data` to the display component.
+
+ Unless you need custom handling of error or loading state, we recommend using **PendingWrapper**, for example, for fields that use [data sources](90-datasources.md).
diff --git a/docs/extensibility/140-static-extensions.md b/docs/extensibility/140-static-extensions.md
new file mode 100644
index 0000000000..3f8e783bad
--- /dev/null
+++ b/docs/extensibility/140-static-extensions.md
@@ -0,0 +1,36 @@
+# Configure a Config Map for Static Extensions
+
+You can define a static extension by adding the `busola.io/extension:statics` label to the ConfigMap. You don't need the **general** section as static extensions present data that are not connected to any resource. Instead, they may use information from the page they are displayed on using the `$embedResource` variable. You can provide all the ConfigMap data sections as either JSON or YAML.
+
+## Extension Version
+
+The version is a string value that defines in which version the extension is configured. It is stored as a value of the `busola.io/extension-version` label. When created manually, use the latest version number, for example, `'0.5'`.
+
+> [!NOTE]
+> Busola supports only the two latest versions of the configuration. Whenever a new version of the configuration is proposed, go to your Extension and migrate your configuration to the latest version.
+
+## Available Parameters
+
+| Parameter | Required | Type | Description |
+| -------------------------------- | -------- | -------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
+| **externalNodes** | No | string | It is used to define optional links to external websites that appear in the navigation menu. |
+| **externalNodes.catagory** | No | string | A name of the category. |
+| **externalNodes.scope** | No | string | It can be `namespace` or `cluster`. It defaults to `cluster`. |
+| **externalNodes.icon** | No | string | An icon that you can choose from the [Icon Explorer](https://sdk.openui5.org/test-resources/sap/m/demokit/iconExplorer/webapp/index.html#/overview). |
+| **externalNodes.children** | No | string | a list of child Nodes containing details about the links. |
+| **externalNodes.children.label** | No | string | a displayed label. |
+| **externalNodes.children.link** | No | string, [JSONata](100-jsonata.md) expression | a link to an external website. |
+
+### Example
+
+```yaml
+general:
+ externalNodes:
+ - category: My Category
+ icon: course-book
+ children:
+ - label: Example Node Label
+ link: 'https://github.com/kyma-project/busola'
+```
+
+For more information on an exemplary configuration of the `External Nodes` feature in static extensions, see the [configuration example](examples/../../../examples/statics/statics-external-nodes.yaml).
diff --git a/docs/extensibility/translations-section.md b/docs/extensibility/150-translations.md
similarity index 97%
rename from docs/extensibility/translations-section.md
rename to docs/extensibility/150-translations.md
index 1c155f397b..74b3670fd2 100644
--- a/docs/extensibility/translations-section.md
+++ b/docs/extensibility/150-translations.md
@@ -1,4 +1,4 @@
-# _translations_ section
+# Configure Translations
This optional section contains all available languages formatted for [i18next](https://www.i18next.com/) either as YAML or JSON, based on their paths. When a name is provided for a widget, that value can be used as the key, and the value is the translation for a specific language.
diff --git a/docs/extensibility/160-wizard-extensions.md b/docs/extensibility/160-wizard-extensions.md
new file mode 100644
index 0000000000..1b736d550a
--- /dev/null
+++ b/docs/extensibility/160-wizard-extensions.md
@@ -0,0 +1,41 @@
+# Configure a Config Map for Wizard Extensions
+
+You can set up your ConfigMap to handle a custom wizard by adding objects to the **general** section. This section contains basic information about the created resources and additional options.
+You can provide all the ConfigMap data sections as either JSON or YAML.
+
+## Extension Version
+
+The version is a string value that defines in which version the extension is configured. It is stored as a value of the `busola.io/extension-version` label. If the configuration is created with the **Create Extension** button, this value is provided automatically. When created manually, use the latest version number, for example, `'0.5'`.
+
+> [!NOTE]
+> Busola supports only the two latest versions of the configuration. Whenever a new version of the configuration is proposed, go to your Extension and migrate your configuration to the latest version.
+
+## Available Parameters
+
+| Parameter | Required | Type | Description |
+| -------------------------------- | -------- | -------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
+| **externalNodes** | No | string | It is used to define optional links to external websites that appear in the navigation menu. |
+| **externalNodes.catagory** | No | string | A name of the category. |
+| **externalNodes.scope** | No | string | It can be `namespace` or `cluster`. It defaults to `cluster`. |
+| **externalNodes.icon** | No | string | An icon that you can choose from the [Icon Explorer](https://sdk.openui5.org/test-resources/sap/m/demokit/iconExplorer/webapp/index.html#/overview). |
+| **externalNodes.children** | No | string | a list of child Nodes containing details about the links. |
+| **externalNodes.children.label** | No | string | a displayed label. |
+| **externalNodes.children.link** | No | string, [JSONata](100-jsonata.md) expression | a link to an external website. |
+
+### Example
+
+```yaml
+id: mywizard
+name: Create a MyResource
+resources:
+ myresource:
+ kind: MyResource
+ group: busola.example.com
+ version: v1
+ myservice:
+ kind: MyService
+ group: busola.example.com
+ version: v1
+```
+
+See [Additional Sections for Wizard Extensions](170-additional-sections-wizard.md) for more information on the available sections.
diff --git a/docs/extensibility/170-additional-sections-wizard.md b/docs/extensibility/170-additional-sections-wizard.md
new file mode 100644
index 0000000000..abdd0404fd
--- /dev/null
+++ b/docs/extensibility/170-additional-sections-wizard.md
@@ -0,0 +1,50 @@
+# Additional Sections for Wizard Extensions
+
+## steps Section
+
+Each wizard consists of steps. The steps section contains their definitions.
+
+### Available Parameters
+
+| Parameter | Required | Type | Description | |
+| --------------- | -------- | ------ | ---------------------------------------------------------------------------------------------------------------------------- | --- |
+| **name** | **Yes** | string | The step name displayed on the step navigation and in the step header. | |
+| **resource** | **Yes** | string | The default resource identifier for this step. | |
+| **form** | **Yes** | string | the form definition. This is analogous to the contents of the [form section](./40-form-fields.md) of the resource extension. | |
+| **description** | No | string | Additional details about the step, shown only when the step is active. | |
+
+## defaults Section
+
+The defaults section is optional. If present, not all resources must be covered. This section contains a map of default values for specific resources. It is appended to the basic skeleton resources created based on the data provided in the [general section](160-wizard-extensions.md).
+
+## Example
+
+```yaml
+data:
+ defaults:
+ qqq:
+ spec:
+ string-ref: foo
+ subqqq:
+ metadata:
+ labels:
+ example: example
+ steps:
+ - name: First step
+ description: this is the first step
+ resource: qqq
+ form:
+ - id: foo
+ path: spec.string-ref
+ name: string ref
+ trigger: [sr]
+ - path: spec.double-ref.name
+ name: double ref name
+ visibility: false
+ overwrite: false
+ subscribe:
+ init: spec."string-ref"
+ sr: spec."string-ref"
+```
+
+For the example of usage, check the [Get started with functions](../../examples/wizard/README.md) wizard.
diff --git a/docs/extensibility/custom-extensions.md b/docs/extensibility/80-custom-extensions.md
similarity index 51%
rename from docs/extensibility/custom-extensions.md
rename to docs/extensibility/80-custom-extensions.md
index ef645b8a57..a98c3ceddc 100644
--- a/docs/extensibility/custom-extensions.md
+++ b/docs/extensibility/80-custom-extensions.md
@@ -1,6 +1,6 @@
# 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.
+With Busola's custom extension feature, you can design fully custom user interfaces beyond the built-in extensibility functionality. This feature is ideal for creating unique and specialized displays that are not covered by the built-in components.
## Getting Started
@@ -15,10 +15,10 @@ EXTENSIBILITY_CUSTOM_COMPONENTS:
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.
+- **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.
+Once your ConfigMap is ready, add it to your cluster. Then, Busola loads and displays your custom UI.
-See this [example](./../../examples/custom-extension/README.md), to learn more.
+For more information, see this [example](./../../examples/custom-extension/README.md).
diff --git a/docs/extensibility/90-datasources.md b/docs/extensibility/90-datasources.md
new file mode 100644
index 0000000000..2c7490570d
--- /dev/null
+++ b/docs/extensibility/90-datasources.md
@@ -0,0 +1,59 @@
+# Configure the dataSources Section
+
+The optional **dataSources** section contains an object that maps a data source name to a data source configuration object. The data source name, preceded by a dollar sign `$`, is used in the **source** expression.
+
+Data sources are provided in all [JSONata](100-jsonata.md) formulas as functions to call. For example, `{ "source": $myRelatedResource().metadata.labels }` returns the `metadata.labels` of the related resource.
+
+When you provide the whole request, you can access individual resources using the `items` field, for example `{ "widget": "Table", "source": "$myRelatedResources().items" }`.
+
+## Available Parameters
+
+| Parameter | Required | Type | Description |
+| -------------------------- | -------- | ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| **resource** | **Yes** | | A resource defined based on the following properties: |
+| **resource.kind** | **Yes** | string | A Kubernetes resource kind. |
+| **resource.version** | **Yes** | string | A Kubernetes resource version. |
+| **resource.name** | No | string | A resource name. If left empty, all resources of a given type are matched. |
+| **resource.group** | No | string | A Kubernetes resource group. It's not provided for the Kubernetes resources in the core (legacy) group. |
+| **resource.namespace** | No | string | The name of the resource's namespace. It defaults to the original resource's namespace. If set to `null`, cluster-wide resources or resources in all namespaces are matched. |
+| **ownerLabelSelectorPath** | No | | The path to original object's selector type property. For example, `spec.selector.matchLabels` for Deployment, used to select matching Pods. |
+| **filter** | No | [JSONata](100-jsonata.md) expression | It allows you to write a custom matching logic. It uses the `item` variable to point to the current item of the related kind, and the `root` variable to point to the original resource. It returns a boolean value. You can also filter using the [`matchByLabelSelector` function](101-preset-functions.md#matchbylabelselectoritem-selectorpath) to see the matched Pods. To do that, provide the Pods as `$item`, and path to the labels. |
+
+## Examples
+
+```yaml
+details:
+ body:
+ - widget: ResourceList
+ source: '$myPods()'
+dataSources:
+ myPods:
+ resource:
+ kind: Pod
+ version: v1
+ ownerLabelSelectorPath: spec.selector.matchLabels
+```
+
+```yaml
+details:
+ body:
+ - widget: ResourceList
+ path: '$mySecrets'
+dataSources:
+ mySecrets:
+ resource:
+ kind: Secret
+ version: v1
+ namespace:
+ filter:
+ '$root.spec.secretName = $item.metadata.name and $root.metadata.namespace
+ = $item.metadata.namespace'
+```
+
+```yaml
+podSelector:
+ resource:
+ kind: Pod
+ version: v1
+ filter: '$matchByLabelSelector($item, $root.spec.selector)'
+```
diff --git a/docs/extensibility/datasources-section.md b/docs/extensibility/datasources-section.md
deleted file mode 100644
index 495e82991e..0000000000
--- a/docs/extensibility/datasources-section.md
+++ /dev/null
@@ -1,65 +0,0 @@
-# _dataSources_ section
-
-The optional **dataSources** section contains an object that maps a data source name to a data source configuration object. The data source name, preceded by a dollar sign '\$', is used in the **source** expression.
-
-Data sources are provided in all [JSONata](jsonata.md) formulas as functions to call. For example, `{ "source": $myRelatedResource().metadata.labels }` returns the `metadata.labels` of the related resource.
-
-When you provide the whole request, you can access individual resources using the `items` field, for example `{ "widget": "Table", "source": "$myRelatedResources().items" }`.
-
-## Data source configuration object fields
-
-Busola uses the following fields to build the related resource URL and filter the received data.
-
-- **resource**:
- - **kind** - _[required]_ Kubernetes resource kind.
- - **group** - Kubernetes resource group. Not provided for Kubernetes resources in the core (also called legacy) group.
- - **version** - _[required]_ Kubernetes resource version.
- - **namespace** - the resource's Namespace name; it defaults to the original resource's Namespace. If set to `null`, cluster-wide resources or resources in all Namespaces are matched.
- - **name** - a specific resource name; leave empty to match all resources of a given type.
-- **ownerLabelSelectorPath** - the path to original object's **selector** type property; for example, `spec.selector.matchLabels` for Deployment, used to select matching Pods.
-- **filter** - a [JSONata](jsonata.md) function enabling the user to write a custom matching logic. It uses the following variables:
-
- - **item** - the current item of the related kind.
- - **root** - the original resource.
-
- This function should return a boolean value.
- You can also use the [`matchByLabelSelector` function](jsonata.md#matchbylabelselectoritem-selectorpath) to see the matched Pods. To do that, provide the Pods as `$item`, and path to the labels.
-
-## Examples
-
-```yaml
-details:
- body:
- - widget: ResourceList
- source: '$myPods()'
-dataSources:
- myPods:
- resource:
- kind: Pod
- version: v1
- ownerLabelSelectorPath: spec.selector.matchLabels
-```
-
-```yaml
-details:
- body:
- - widget: ResourceList
- path: '$mySecrets'
-dataSources:
- mySecrets:
- resource:
- kind: Secret
- version: v1
- namespace:
- filter:
- '$root.spec.secretName = $item.metadata.name and $root.metadata.namespace
- = $item.metadata.namespace'
-```
-
-```yaml
-podSelector:
- resource:
- kind: Pod
- version: v1
- filter: '$matchByLabelSelector($item, $root.spec.selector)'
-```
diff --git a/docs/extensibility/jsonata.md b/docs/extensibility/jsonata.md
deleted file mode 100644
index 2e08c8bd90..0000000000
--- a/docs/extensibility/jsonata.md
+++ /dev/null
@@ -1,175 +0,0 @@
-# Jsonata preset functions for resource-based extensions
-
-**Table of Contents**
-
-- [Overview](#overview)
-- [Scoping](#scoping)
-- [Preset functions](#preset-functions)
- - [_canI_](#caniresourcegroupandversion-resourcekind)
- - [_compareStrings_](#comparestringsfirst-second)
- - [_matchByLabelSelector_](#matchbylabelselectoritem-selectorpath)
- - [_matchEvents_](#matcheventsitem-kind-name)
-
-## Overview
-
-This document describes how to use JSONata expressions [JSONata](https://docs.jsonata.org/overview.html) throughout the extensions.
-
-## Scoping
-
-The primary data source of JSONata expressions changes depending on where it's used. Starting with the root, it contains the whole resource, but whenever it's in a child whose parent has a **source** (in lists and details) or **path** (in forms) parameter, the scope changes to data from that source or path.
-
-Additionally, scope in arrays changes to the array item.
-
-For example, for this resource:
-
-```yaml
-spec:
- name: foo
- description: bar
- items:
- - name: item-name
- details:
- status: ok
-```
-
-The following definition has their scope changed as follows:
-
-```yaml
-- source: spec.name # top level, scope is the same as resource
-
-- source: spec # top level, scope is the same as resource
- children:
- - source: name # parent has source=spec, therefore this refers to spec.name
-
-- children:
- - source: spec.name # however there's no parent source here, therefore scope is still the resource
-
-- source: spec.items
- children:
- - source: name # parent data is an array, therefore scope changes to it's item - this refers to spec.items[0].name
- - source: details.status # refers to spec.items[0].details.status (same as above)
- - source: details # this changes scope for it's children again
- children:
- source: status # so this refers to spec.items[0].details.status
-```
-
-## Common variables
-
-Common variables are the primary means to bypass the default scoping.
-
-- **\$root** - always contains the reference to the resource, so any JSONata in the example above can always be `$root.spec.name`.
-- **\$item** - refers to the most recent array item. When not in an array, it's equal to **\$root**.
-- **\$items** - contains an array of references to all parent array items (with the last item being equal to **\$item**).
-- **\$value** - when used in a JSONata other than **source** (for example **visibility**, but also other widget-specific formulas), contains the value returned by the source.
-- **\$index** - exists in array components, refers to the index of the current item of an array.
-
-#### Example
-
-```yaml
-- widget: Table
- source: spec.rules
- visibility: $exists($value)
- collapsibleTitle: "'Rule #' & $string($index + 1)"
-```
-
-## Data sources
-
-Whenever data sources are provided, they are available as corresponding variable names. See [data sources](datasources-section.md) section for more details.
-
-## Preset functions
-
-### canI(resourceGroupAndVersion, resourceKind)
-
-You can use this function to determine if a user has access rights for listing a specified resource.
-
-#### Function parameters
-
-- **resourceGroupAndVersion** - the first part of a resource URL following the pattern: `${resource group}/${resource version}`.
-- **resourceKind** - resource kind.
-
-#### Example
-
-```yaml
-- path: spec.gateway
- name: gateway
- visibility: $not($canI('networking.istio.io/v1beta1', 'Gateway'))
-```
-
-### compareStrings(first, second)
-
-You can use this function to sort two strings alphabetically.
-
-#### Function parameters
-
-- **first** - first string to compare.
-- **second** - second string to compare.
-
-#### Example
-
-Here is an example from the [ResourceList widget](./50-list-and-details-widgets.md#resourcelist):
-
-```yaml
-- widget: ResourceList
- source: '$myDeployments()'
- name: Example ResourceList Deployments
- sort:
- - source: '$item.spec.strategy.type'
- compareFunction: '$compareStrings($second, $first)'
- default: true
-```
-
-### matchByLabelSelector(item, selectorPath)
-
-You can use this function to match Pods using a resource selector.
-
-#### Function parameters
-
-- **item** - Pod to be used.
-- **selectorPath** - path to selector labels from `$root`.
-
-#### Example
-
-Example from [dataSources](datasources-section.md).
-
-```yaml
-- podSelector:
- resource:
- kind: Pod
- version: v1
- filter: '$matchByLabelSelector($item, $root.spec.selector)'
-```
-
-### matchEvents(item, kind, name)
-
-You can use this function to match Events using a resource selector.
-
-#### Function parameters
-
-- **item** - Event to be checked.
-- **kind** - kind of the Event emitting resource.
-- **name** - name of the Event emitting resource.
-
-#### Example
-
-```yaml
-- widget: EventList
- filter: '$matchEvents($item, $root.kind, $root.metadata.name)'
- name: events
- defaultType: NORMAL
- hideInvolvedObjects: true
-```
-
-### readableTimestamp(timestamp)
-
-You can use this function to convert time to readable time.
-
-#### Function parameters
-
-- **timestamp** - timestamp to convert.
-
-#### Example
-
-```yaml
-- source: '$readableTimestamp($item.lastTransitionTime)'
- name: status.conditions.lastTransitionTime
-```
diff --git a/docs/extensibility/presets-section.md b/docs/extensibility/presets-section.md
deleted file mode 100644
index 76b9e952a2..0000000000
--- a/docs/extensibility/presets-section.md
+++ /dev/null
@@ -1,37 +0,0 @@
-# _presets_ section
-
-The **presets** section contains a list of objects that define which preset and template are used in the form view. If you specify a preset, it is displayed in the dropdown list along with the **Clear** option. When you select a preset, the form is filled with the values defined in the **value** property.
-
-## preset configuration object fields
-
-- **name** - _[required]_ a name to display on the preset's dropdown,
-- **value** - _[required]_ contains fields that are set when you choose this preset from the list.
-- **default** - For `default` equal to `true`, it prefills form with values defined in the **value** property. Defaults to `false`.
-
-## Example
-
-```yaml
-- name: template
- default: true
- value:
- metadata:
- name: my-name
- spec:
- description: A set description
-- name: preset
- value:
- metadata:
- name: second-one
- spec:
- data: regex
- description: A different description
- items:
- - name: item-1
- value: 10
- - name: item-2
- value: 11
- - name: item-3
- value: 5
-```
-
-
diff --git a/docs/extensibility/resources.md b/docs/extensibility/resources.md
deleted file mode 100644
index 3439a0b2fa..0000000000
--- a/docs/extensibility/resources.md
+++ /dev/null
@@ -1,113 +0,0 @@
-# Config Map for resource-based extensions
-
-**Table of Contents**
-
-- [Overview](#overview)
-- [Extension version](#extension-version)
-- [_general_ section](#general-section)
-- [_form_ section](#form-section)
-- [_list_ section](#list-section)
-- [_details_ section](#details-section)
-- [Value preprocessors](#value-preprocessors)
- - [List of value preprocessors](#list-of-value-preprocessors)
-
-## Overview
-
-This document describes the required ConfigMap setup that you need to configure in order to handle your CRD UI page.
-You can provide all the ConfigMap data sections as either JSON or YAML.
-
-## Extension version
-
-The version is a string value that defines in which version the extension is configured. It is stored as a value of the `busola.io/extension-version` label. If the configuration is created with the **Create Extension** button, this value is provided automatically. When created manually, use the latest version number, for example, `'0.5'`.
-
-> **NOTE:**: Busola supports only the two latest versions of the configuration. Whenever a new version of the configuration is proposed, go to your Extension and migrate your configuration to the latest version.
-
-## _general_ section
-
-The **general** section is required and contains basic information about the resource and additional options.
-
-### Item parameters
-
-- **resource** - _[required]_ - information about the resource.
- - **kind** - _[required]_ Kubernetes kind of the resource.
- - **version** - _[required]_ API version used for all requests.
- - **group** - API group used for all requests. Not provided for Kubernetes resources in the core (also called legacy) group.
-- **name** - title used in the navigation and on the list screen. It defaults to its resource kind.
-- **category** - the name of a category used for the left-hand menu. By default, it's placed in the `Custom Resources` category.
-- **icon** - suffix of an icon name used for the left-hand menu. The default value is `customized`. You can find the list of icons [here](https://sap.github.io/fundamental-react/?path=/docs/component-api-icon--primary).
-- **scope** - either `namespace` or `cluster`. Defaults to `cluster`.
-- **urlPath** - path fragment for this resource used in the URL. Defaults to pluralized lowercase **kind**. Used to provide an alternative URL to avoid conflicts with other resources.
-- **defaultPlaceholder** - to be shown in place of an empty text placeholder. Overridden by the widget-level **placeholder**. Defaults to `-`.
-- **description** - displays a custom description on the resource list page. It can contain links. If the **translations** section has a translation entry with the ID that is the same as the **description** string, the translation is used.
-- **filter** - optional [JSONata](jsonata.md) [filter](https://docs.jsonata.org/higher-order-functions#filter) used to filter the resources shown at the list section property.
-- **features** - an optional object for the features configuration.
- - **actions** - an optional object for the actions configuration.
- - **disableCreate** - when set to `true`, it disables the **Create** button. Defaults to `false`.
- - **disableEdit** - when set to `true`, it disables the **Edit** button. Defaults to `false`.
- - **disableDelete** - when set to `true`, it disables the **Delete** button. Defaults to `false`.
-- **externalNodes** - an optional list of links to external websites.
- - **category** - a category name
- - **scope** - either `namespace` or `cluster`. Defaults to `cluster`.
- - **icon** - an optional icon. Go to [Icon Explorer](https://sdk.openui5.org/test-resources/sap/m/demokit/iconExplorer/webapp/index.html#/overview) to find a list of the available icons.
- - **children** - a list of child nodes containing details about the links
- - **label** - a displayed label
- - **link** - a link to an external website. You can provide a [JSONata](jsonata.md) function.
-
-### Example
-
-```yaml
-resource:
- kind: MyResource
- version: v1alpha3
- group: networking.istio.io
-name: MyResourceName
-category: My Category
-scope: namespace
-defaultPlaceholder: '- not set -'
-description: See the {{[docs](https://github.com/kyma-project/busola)}} for more information.
-filter: "$filter(data, function($data) {$data.type = 'Opaque'})"
-features:
- actions:
- disableCreate: true
- disableDelete: true
-externalNodes:
- - category: My Category
- icon: course-book
- children:
- - label: Example Node Label
- link: 'https://github.com/kyma-project/busola'
- - category: My Second Category
- icon: bar-chart
- scope: namespace
- children:
- - label: Example Node Label
- link: '$string($exampleResource().link)'
-```
-
-## _form_ section
-
-To customize the **form** section see the [Create forms with extensibility](./40-form-fields.md) documentation.
-Views created with the extensibility [ConfigMap wizard](README.md) have a straightforward form configuration by default.
-
-## _list_ section
-
-The **list** section presents the resources of a kind, that is, Secrets or ConfigMaps, and comes with a few predefined columns: **Name**, **Created**, and **Labels**.
-If you want to add your own columns, see [Customize UI display](./30-details-summary.md) to learn how to customize both list and details views.
-
-## _details_ section
-
-The **details** section presents the resource details. To customize it, see [Customize UI display](./30-details-summary.md). The default details header contains some basic information. By default, the body is empty.
-
-## Value preprocessors
-
-Value preprocessors are used as a middleware between a value and the actual renderer. They can transform a given value and pass it to the widget; or stop processing and render it so you can view it immediately, without passing it to the widget.
-
-### List of value preprocessors
-
-- **PendingWrapper** - useful when value resolves to a triple of `{loading, error, data}`:
-
- - For `loading` equal to `true`, it displays a loading indicator.
- - For truthy `error`, it displays an error message.
- - Otherwise, it passes `data` to the display component.
-
- Unless you need custom handling of error or loading state, we recommend using **PendingWrapper**, for example, for fields that use [data sources](./datasources-section.md).
diff --git a/docs/extensibility/statics.md b/docs/extensibility/statics.md
deleted file mode 100644
index 2acd49847b..0000000000
--- a/docs/extensibility/statics.md
+++ /dev/null
@@ -1,58 +0,0 @@
-# Config Map for static extensions
-
-**Table of Contents**
-
-- [Overview](#overview)
-- [Static extension label](#static-extension-label)
-- [Extension version](#extension-version)
-- [_general_ section](#general-section)
-- [_injections_ section](#injections-section)
-
-## Overview
-
-This document describes the required ConfigMap setup that you need to configure to handle a static extension.
-You can provide all the ConfigMap data sections as either JSON or YAML.
-
-## Static extension label
-
-To define a static extension, add the `busola.io/extension:statics` label to the ConfigMap.
-
-## Extension version
-
-The version is a string value that defines in which version the extension is configured. It is stored as a value of the `busola.io/extension-version` label. When created manually, use the latest version number, for example, `'0.5'`.
-
-> **NOTE:** Busola supports only the two latest versions of the configuration. Whenever a new version of the configuration is proposed, go to your Extension and migrate your configuration to the latest version.
-
-## _general_ section
-
-The **general** section is not required as static extensions present data that are not connected to any resource. Instead, they may use information from the page they are displayed on via variable `$embedResource`.
-
-### _externalNodes_
-
-The **externalNodes** parameter allows you to define optional links to external websites that appear in the navigation menu.
-
-- **externalNodes** - an optional list of links to external websites.
- - **category** - a category name.
- - **scope** - either `namespace` or `cluster`. Defaults to `cluster`.
- - **icon** - an optional icon. Go to [Icon Explorer](https://sdk.openui5.org/test-resources/sap/m/demokit/iconExplorer/webapp/index.html#/overview) to find the list of the available icons.
- - **children** - a list of child Nodes containing details about the links.
- - **label** - a displayed label
- - **link** - a link to an external website. You can provide a [JSONata](jsonata.md) function.
-
-### Example
-
-```yaml
-general:
- externalNodes:
- - category: My Category
- icon: course-book
- children:
- - label: Example Node Label
- link: 'https://github.com/kyma-project/busola'
-```
-
-To see an exemplary configuration of the `External Nodes` feature in static extensions, check the [configuration example](examples/../../../examples/statics/statics-external-nodes.yaml).
-
-## _injections_ section
-
-For more information, read the [widget injections overview](./70-widget-injection.md).
diff --git a/docs/extensibility/wizard.md b/docs/extensibility/wizard.md
deleted file mode 100644
index 6975d949cd..0000000000
--- a/docs/extensibility/wizard.md
+++ /dev/null
@@ -1,76 +0,0 @@
-# Config Map for wizard extensions
-
-**Table of Contents**
-
-- [Overview](#overview)
-- [Extension version](#extension-version)
-- [_general_ section](#general-section)
-- [_steps_ section](#steps-section)
-- [_defaults_ section](#defaults-section)
-
-## Overview
-
-This document describes the required ConfigMap setup that you need to configure in order to handle a custom wizard.
-You can provide all the ConfigMap data sections as either JSON or YAML.
-
-## Extension version
-
-The version is a string value that defines in which version the extension is configured. It is stored as a value of the `busola.io/extension-version` label. If the configuration is created with the **Create Extension** button, this value is provided automatically. When created manually, use the latest version number, for example, `'0.5'`.
-
-> **NOTE:**: Busola supports only the two latest versions of the configuration. Whenever a new version of the configuration is proposed, go to your Extension and migrate your configuration to the latest version.
-
-## _general_ section
-
-The **general** section is required and contains basic information about the created resources and additional options.
-
-### Item parameters
-
-- **id** - _[required]_ - an identifier used to reference the wizard to trigger its opening.
-- **resources** - _[required]_ - information about the resources created by the wizard. This is a key value map with values consisting of:
- - **kind** - _[required]_ Kubernetes kind of the resource.
- - **version** - _[required]_ API version used for all requests.
- - **group** - API group used for all requests. Not provided for Kubernetes resources in the core (also called legacy) group.
-- **name** - wizard window title.
-
-### Example
-
-```yaml
-id: mywizard
-name: Create a MyResource
-resources:
- myresource:
- kind: MyResource
- group: busola.example.com
- version: v1
- myservice:
- kind: MyService
- group: busola.example.com
- version: v1
-```
-
-## _steps_ section
-
-Each wizard consists of steps. This section contains their definitions.
-
-### Item parameters
-
-Each step contains the following parameters:
-
-- **name** - _[required]_ - the name of the step displayed on the step navigation and in the step header
-- **description** - extra details about the step, shown only when the step is active
-- **resource** - _[required]_ - the identifier of the default resource for this step
-- **form** - _[required]_ - the definition of the form - this is analogous to the contents of the [_form_ section](./40-form-fields.md) of the resource extension
-
-## _defaults_ section
-
-This section is optional; if present, not all resources must be covered. This section contains a map of default values for specific resources. It is appended to the basic skeleton resources created based on the data provided in the [_general_ section](#general-section).
-
-### Example
-
-```yaml
-myresource:
- spec:
- enabled: true
-```
-
-For the example of usage, check the [Get started with functions](../../examples/wizard/README.md) wizard.
diff --git a/src/components/Clusters/views/ClusterOverview/ClusterStats.js b/src/components/Clusters/views/ClusterOverview/ClusterStats.js
index e235f2214b..2a3655a1a6 100644
--- a/src/components/Clusters/views/ClusterOverview/ClusterStats.js
+++ b/src/components/Clusters/views/ClusterOverview/ClusterStats.js
@@ -7,6 +7,7 @@ import { Card, CardHeader, Title } from '@ui5/webcomponents-react';
import { CountingCard } from 'shared/components/CountingCard/CountingCard';
import {
bytesToHumanReadable,
+ cpusToHumanReadable,
getBytes,
} from 'resources/Namespaces/ResourcesUsage';
import {
@@ -128,9 +129,11 @@ export default function ClusterStats({ nodesData }) {
color="var(--sapChart_OrderedColor_5)"
value={roundTwoDecimals(cpu.usage)}
max={roundTwoDecimals(cpu.capacity)}
- additionalInfo={`${roundTwoDecimals(
- cpu.usage,
- )}m / ${roundTwoDecimals(cpu.capacity)}m`}
+ additionalInfo={`${cpusToHumanReadable(cpu.usage, {
+ unit: 'm',
+ })} / ${cpusToHumanReadable(cpu.capacity, {
+ unit: 'm',
+ })}`}
/>
diff --git a/src/components/Nodes/NodeResources/NodeResources.js b/src/components/Nodes/NodeResources/NodeResources.js
index fa2807735b..d0426d9625 100644
--- a/src/components/Nodes/NodeResources/NodeResources.js
+++ b/src/components/Nodes/NodeResources/NodeResources.js
@@ -3,6 +3,7 @@ import { UI5RadialChart } from 'shared/components/UI5RadialChart/UI5RadialChart'
import { Card, CardHeader } from '@ui5/webcomponents-react';
import { roundTwoDecimals } from 'shared/utils/helpers';
import './NodeResources.scss';
+import { cpusToHumanReadable } from '../../../resources/Namespaces/ResourcesUsage.js';
export function NodeResources({ metrics, resources }) {
const { t } = useTranslation();
@@ -19,10 +20,10 @@ export function NodeResources({ metrics, resources }) {
- Math.round(num * Math.pow(10, places)) / Math.pow(10, places);
+ Math.round((num + Number.EPSILON) * Math.pow(10, places)) /
+ Math.pow(10, places);
const getPercentageFromUsage = (value, total) => {
if (total === 0) {
@@ -15,16 +16,16 @@ const getPercentageFromUsage = (value, total) => {
return Math.round((100 * value) / total);
};
-const formatCpu = cpuStr => Math.ceil(parseInt(cpuStr || '0') / 1000_000);
-const formatMemory = memoryStr =>
+const formatKiToGiMemory = memoryStr =>
round(parseInt(memoryStr || '0') / 1024 / 1024, 1);
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 cpuUsage = getCpus(metricsForNode?.usage.cpu);
+ const memoryUsage = formatKiToGiMemory(metricsForNode?.usage.memory);
+ const cpuCapacity = getCpus(node.status.allocatable?.cpu || '0');
+ const memoryCapacity = formatKiToGiMemory(node.status.allocatable?.memory);
+ console.log(cpuCapacity);
const cpuPercentage = getPercentageFromUsage(cpuUsage, cpuCapacity);
const memoryPercentage = getPercentageFromUsage(memoryUsage, memoryCapacity);
@@ -176,11 +177,11 @@ export function calcNodeResources(pods) {
return {
limits: {
- cpu: nodeResources.limits.cpu * 1000,
+ cpu: nodeResources.limits.cpu,
memory: nodeResources.limits.memory / Math.pow(1024, 3),
},
requests: {
- cpu: nodeResources.requests.cpu * 1000,
+ cpu: nodeResources.requests.cpu,
memory: nodeResources.requests.memory / Math.pow(1024, 3),
},
};
diff --git a/src/components/Nodes/nodeQueries.test.js b/src/components/Nodes/nodeQueries.test.js
index 9e073be4cc..935cad7850 100644
--- a/src/components/Nodes/nodeQueries.test.js
+++ b/src/components/Nodes/nodeQueries.test.js
@@ -24,11 +24,11 @@ describe('Calculate resources for node', () => {
},
expectedValue: {
limits: {
- cpu: 10,
+ cpu: 0.01,
memory: 100.0 / 1024,
},
requests: {
- cpu: 20,
+ cpu: 0.02,
memory: 200.0 / 1024,
},
},
@@ -49,11 +49,11 @@ describe('Calculate resources for node', () => {
},
expectedValue: {
limits: {
- cpu: 22,
+ cpu: 0.022,
memory: 220.0 / 1024,
},
requests: {
- cpu: 45,
+ cpu: 0.045,
memory: 450.0 / 1024,
},
},
@@ -68,11 +68,11 @@ describe('Calculate resources for node', () => {
},
expectedValue: {
limits: {
- cpu: 7,
+ cpu: 0.007,
memory: 70.0 / 1024,
},
requests: {
- cpu: 14,
+ cpu: 0.014,
memory: 140.0 / 1024,
},
},
diff --git a/src/resources/Namespaces/ResourcesUsage.js b/src/resources/Namespaces/ResourcesUsage.js
index 188c32b9fe..0dbe1cd3d1 100644
--- a/src/resources/Namespaces/ResourcesUsage.js
+++ b/src/resources/Namespaces/ResourcesUsage.js
@@ -3,7 +3,7 @@ import { useGetList } from 'shared/hooks/BackendAPI/useGet';
import { Spinner } from 'shared/components/Spinner/Spinner';
import { useTranslation } from 'react-i18next';
-import { getSIPrefix } from 'shared/helpers/siPrefixes';
+import { formatResourceUnit } from 'shared/helpers/resources.js';
import { Card, CardHeader } from '@ui5/webcomponents-react';
const MEMORY_SUFFIX_POWER = {
@@ -21,6 +21,7 @@ const MEMORY_SUFFIX_POWER = {
const CPU_SUFFIX_POWER = {
m: 1e-3,
+ n: 1e-9,
};
export function getBytes(memoryStr) {
@@ -50,12 +51,12 @@ export function getCpus(cpuString) {
export function bytesToHumanReadable(bytes) {
if (!bytes) return bytes;
- return getSIPrefix(bytes, true, { withoutSpace: true }).string;
+ return formatResourceUnit(bytes, true, { withoutSpace: true });
}
-export function cpusToHumanReadable(cpus) {
+export function cpusToHumanReadable(cpus, { fixed = 0, unit = '' } = {}) {
if (!cpus) return cpus;
- return cpus / MEMORY_SUFFIX_POWER['m'] + 'm';
+ return formatResourceUnit(cpus, false, { withoutSpace: true, fixed, unit });
}
const MemoryRequestsCircle = ({ resourceQuotas, isLoading }) => {
diff --git a/src/shared/helpers/resources.js b/src/shared/helpers/resources.js
new file mode 100644
index 0000000000..37583352bc
--- /dev/null
+++ b/src/shared/helpers/resources.js
@@ -0,0 +1,56 @@
+const SI_PREFIXES = {
+ p: 1e-12,
+ n: 1e-9,
+ µ: 1e-6,
+ m: 1e-3,
+ '': 1,
+ k: 1e3,
+ M: 1e6,
+ G: 1e9,
+ T: 1e12,
+ P: 1e15,
+ E: 1e18,
+};
+
+const SI_PREFIXES_BINARY = {
+ Ki: 2 ** 10,
+ Mi: 2 ** 20,
+ Gi: 2 ** 30,
+ Ti: 2 ** 40,
+ Pi: 2 ** 50,
+};
+
+/*
+More precise round method.
+Want 1.005 to be rounded to 1.01 we need to add Number.EPSILON to fix the float inaccuracy
+ */
+const preciseRound = (num, places) =>
+ Math.round((num + Number.EPSILON) * Math.pow(10, places)) /
+ Math.pow(10, places);
+
+export function formatResourceUnit(
+ amount,
+ binary = false,
+ { unit = '', withoutSpace = true, fixed = 2 } = {},
+) {
+ const prefixMap = binary ? SI_PREFIXES_BINARY : SI_PREFIXES;
+ const infix = withoutSpace ? '' : ' ';
+
+ if (unit && prefixMap[unit]) {
+ const value = (amount / prefixMap[unit]).toFixed(fixed);
+ return `${value}${infix}${unit}`;
+ }
+
+ const coreValue = preciseRound(amount, 2).toFixed(fixed);
+
+ let output = `${coreValue}${infix}${unit}`;
+ Object.entries(prefixMap).forEach(([prefix, power]) => {
+ const tmpValue = amount / power;
+ if (tmpValue >= 1) {
+ const value = preciseRound(tmpValue, 2).toFixed(fixed);
+ output = `${value}${infix}${prefix}${unit}`;
+ }
+ });
+
+ return output.string;
+}
diff --git a/src/shared/helpers/siPrefixes.js b/src/shared/helpers/siPrefixes.js
deleted file mode 100644
index a2693926b4..0000000000
--- a/src/shared/helpers/siPrefixes.js
+++ /dev/null
@@ -1,59 +0,0 @@
-const SI_PREFIXES = {
- p: 1e-12,
- n: 1e-9,
- µ: 1e-6,
- m: 1e-3,
- '': 1,
- k: 1e3,
- M: 1e6,
- G: 1e9,
- T: 1e12,
- P: 1e15,
- E: 1e18,
-};
-
-const SI_PREFIXES_BINARY = {
- Ki: 2 ** 10,
- Mi: 2 ** 20,
- Gi: 2 ** 30,
- Ti: 2 ** 40,
- Pi: 2 ** 50,
-};
-
-export function getSIPrefix(
- amount,
- binary = false,
- { unit = '', withoutSpace = true, fixed = 2 } = {},
-) {
- const prefixMap = binary ? SI_PREFIXES_BINARY : SI_PREFIXES;
- const infix = withoutSpace ? '' : ' ';
-
- const coreValue = (
- Math.round((+amount + Number.EPSILON) * 100) / 100
- ).toFixed(fixed);
- let output = {
- raw: amount,
- value: coreValue,
- rounded: coreValue,
- prefix: '',
- string: `${coreValue}${infix}${unit}`,
- };
- Object.entries(prefixMap).forEach(([prefix, power]) => {
- const tmpValue = amount / power;
- if (tmpValue >= 1) {
- const value = (
- Math.round((tmpValue + Number.EPSILON) * 100) / 100
- ).toFixed(fixed);
- output = {
- raw: tmpValue,
- value,
- rounded: value * power,
- prefix,
- unit: `${prefix}${unit}`,
- string: `${value}${infix}${prefix}${unit}`,
- };
- }
- });
-
- return output;
-}