From 0e7869f28b9c70b0893fc073b6bac9c7ff23ee09 Mon Sep 17 00:00:00 2001 From: Samiul Date: Wed, 20 May 2026 18:25:49 +0600 Subject: [PATCH 1/4] add oracle opsrequest forms Signed-off-by: Samiul --- .../ui/create-ui.yaml | 651 ++++++ .../ui/functions.js | 1942 +++++++++++++++++ .../ui/language.yaml | 307 +++ 3 files changed, 2900 insertions(+) create mode 100644 charts/opskubedbcom-oracleopsrequest-editor/ui/create-ui.yaml create mode 100644 charts/opskubedbcom-oracleopsrequest-editor/ui/functions.js create mode 100644 charts/opskubedbcom-oracleopsrequest-editor/ui/language.yaml diff --git a/charts/opskubedbcom-oracleopsrequest-editor/ui/create-ui.yaml b/charts/opskubedbcom-oracleopsrequest-editor/ui/create-ui.yaml new file mode 100644 index 0000000000..8a7d902c78 --- /dev/null +++ b/charts/opskubedbcom-oracleopsrequest-editor/ui/create-ui.yaml @@ -0,0 +1,651 @@ +step: +- elements: + - if: + name: showAndInitName + type: function + label: op_req_name + schema: schema/properties/metadata/properties/name + type: input + validation: + type: required + - disable: isNamespaceDisabled + hasGroup: isRancherManaged + if: + name: showAndInitNamespace + type: function + init: + type: func + value: initNamespace + label: Namespace + loader: getNamespaces + schema: schema/properties/metadata/properties/namespace + type: select + validation: + type: required + watcher: + func: onNamespaceChange + paths: + - schema/properties/metadata/properties/namespace + - disable: isDatabaseRefDisabled + if: + name: showAndInitDatabaseRef + type: function + init: + type: func + value: initDatabaseRef + label: Database Ref + loader: + name: getDbs + watchPaths: + - schema/properties/metadata/properties/namespace + refresh: true + schema: schema/properties/spec/properties/databaseRef/properties/name + type: select + validation: + type: required + watcher: + func: onDbChange + paths: + - schema/properties/spec/properties/databaseRef/properties/name + - if: + name: showConfigureOpsrequestLabel + type: function + label: config_ops_request + type: label-element + - disable: isDbDetailsLoading + if: + name: showAndInitOpsRequestType + type: function + init: + type: func + value: getRequestTypeFromRoute + isHorizontal: true + label: Type of Ops Request + options: + - description: Update your database to any version + text: Update Version + value: UpdateVersion + - description: Scale up or down pod count + text: Horizontal Scaling + value: HorizontalScaling + - description: Manage your CPU resources + text: Vertical Scaling + value: VerticalScaling + - description: Manage your database size + text: Volume Expansion + value: VolumeExpansion + - description: Restart your database + text: Restart + value: Restart + - description: Reconfigure your database + text: Reconfigure + value: Reconfigure + - description: Reconfigure your database tls configuration + text: Reconfigure TLS + value: ReconfigureTLS + schema: schema/properties/spec/properties/type + type: radio + watcher: + func: onRequestTypeChange + paths: + - schema/properties/spec/properties/type + - elements: + - init: + type: func + value: setValueFromDbDetails|/spec/version + label: Target Version + loader: getDbVersions + schema: schema/properties/spec/properties/updateVersion/properties/targetVersion + subtitle: Select the desired Oracle version to which you want to update your + database. + type: select-compare + - if: + name: isVersionEmpty + type: function + label: "" + loader: + name: getVersionInfo + watchPaths: + - temp/properties/filteredVersion + type: info + fixedBlock: true + if: + name: ifRequestTypeEqualsTo|UpdateVersion + type: function + label: Version + showLabels: true + type: block-layout + - elements: + - elements: + - header: Replica + init: + type: func + value: setValueFromDbDetails|/spec/replicas + label: Replicas + schema: schema/properties/spec/properties/horizontalScaling/properties/member + subtitle: Define the total number of replicas for the database. Increasing + replicas improves fault tolerance and load distribution , while reducing + replicas conserves resources + type: input-compare + - hasIcon: true + label: Each replica represents an independent copy of your database. For example, + setting this to 3 creates three copies of the database for better availability. + type: info + if: + name: ifDbTypeEqualsTo|cluster|horizontalScaling + type: function + type: horizontal-layout + fixedBlock: true + if: + name: ifRequestTypeEqualsTo|HorizontalScaling + type: function + label: Horizontal Scaling Form + showLabels: true + type: block-layout + - elements: + - elements: + - init: + type: func + value: setMachine + label: Resources + loader: getMachines + schema: temp/properties/machine + subtitle: Compare your current machine configuration with the proposed memory + adjustments and make informed decisions for your database setup + type: machine-compare + validation: + name: isMachineValid + type: custom + watcher: + func: onMachineChange|node|/spec/podTemplate/spec/resources + paths: + - temp/properties/machine + - elements: + - elements: + - elements: + - label: Node Selection Policy + subtitle: Control where your workloads runs by configuring node selection + criteria. Use label selectors to match specific nodes or taints to + avoid unsuitable nodes + type: label-element + - label: Node Selection Policy + options: + - text: LabelSelector + value: LabelSelector + - text: Taint + value: Taint + schema: schema/properties/spec/properties/verticalScaling/properties/node/properties/nodeSelectionPolicy + type: select + hideBorder: true + showLabels: false + type: block-layout + - customClass: mt-20 + label: Label Selector: Specify key-value pairs to target nodes + that match your workload's requirements.
Taints: Define + tolerations for node taints to ensure your workload runs only on compatible + nodes + type: info + type: horizontal-layout + - label: Topology + subtitle: Define node topology preferences, such as zone or region. For + example, 'zone=us-central1-a' ensures workloads are deployed in that zone. + type: label-element + - elements: + - label: Key + schema: temp/topologyKey + type: input + validation: + name: isVerticalScaleTopologyRequired + type: custom + - label: Value + schema: temp/topologyValue + type: input + validation: + name: isVerticalScaleTopologyRequired + type: custom + type: horizontal-layout + label: Node Selection + showLabels: true + type: block-layout + - elements: + - elements: + - init: + type: func + value: setExporter|cpu + label: CPU + schema: schema/properties/spec/properties/verticalScaling/properties/exporter/properties/resources/properties/requests/properties/cpu + type: input + - init: + type: func + value: setExporter|memory + label: Memory + schema: schema/properties/spec/properties/verticalScaling/properties/exporter/properties/resources/properties/limits/properties/memory + type: input + watcher: + func: onExporterResourceChange|memory + paths: + - schema/properties/spec/properties/verticalScaling/properties/exporter/properties/resources/properties/limits/properties/memory + showLabels: true + type: horizontal-layout + hideBlock: true + label: Exporter + showLabels: true + type: block-layout + label: Oracle vertical scaling + showLabels: false + type: block-layout + if: + name: ifRequestTypeEqualsTo|VerticalScaling + type: function + type: block-layout + - elements: + - elements: + - elements: + - label: Mode + subtitle: Not sure which mode to pick? Use Online Mode for smooth operations + with minimal disruption. Choose Offline Mode if you can afford a brief + downtime for added reliability during the volume expansion. + type: label-element + - label: Mode + options: + - text: Offline + value: Offline + - text: Online + value: Online + schema: schema/properties/spec/properties/volumeExpansion/properties/mode + type: select + validation: + type: required + type: block-layout + type: horizontal-layout + - elements: + - elements: + - header: Oracle + init: + type: func + value: setValueFromDbDetails|/spec/storage/resources/requests/storage + label: Storage Size + schema: schema/properties/spec/properties/volumeExpansion/properties/node + subtitle: How much extra storage does your database need? Specify the size(e.g. + 2Gi for 2 gigabytes) so we can allocate it correctly + type: input-compare + validation: + name: checkVolume|/spec/storage/resources/requests/storage|/spec/volumeExpansion/node + type: custom + type: horizontal-layout + fixedBlock: true + label: Oracle volume expansion + showLabels: true + type: block-layout + if: + name: ifRequestTypeEqualsTo|VolumeExpansion + type: function + label: Volume Expansion Form + type: block-layout + - elements: + - elements: + - label: "" + subtitle: Select a new configuration secret, apply a custom configuration, + or remove an existing setup to update your database settings + type: label-element + - elements: + - elements: + - customClass: mb-15 + label: Config Secret + subtitle: Select an existing secret or create a new one to apply to your + database configuration. + type: label-element + - customClass: mb-2 + init: + type: func + value: setValueFromDbDetails|/spec/configSecret/name + label: Config Secret + loader: + name: getConfigSecrets + watchPaths: + - schema/properties/metadata/properties/namespace + - temp/properties/createSecret/properties/status + refresh: fetchConfigSecrets + schema: schema/properties/spec/properties/configuration/properties/configSecret/properties/name + type: select + watcher: + func: onCreateSecretChange + paths: + - temp/properties/createSecret/properties/status + - customClass: mt-15 + elements: + - label: "" + subtitle: Enter a unique name to identify this secret. + type: label-element + - label: Secret Name + schema: temp/properties/createSecret/properties/name + type: input + validation: + type: required + - buttonClass: is-light is-outlined + elements: + - label: Key + loader: + name: onSelectedSecretChange + watchPaths: + - temp/properties/createSecret/properties/data + schema: key + type: select + validation: + type: required + - height: 120px + label: Value + schema: value + type: textarea + keepInitial: true + label: String Data + schema: temp/properties/createSecret/properties/data + type: array-object-form + validation: + type: required + hasButton: + action: createNewConfigSecret + hasCancel: cancelCreateSecret + text: Save + if: + name: isCreateSecret + type: function + label: Create a New Config Secret + showLabels: true + type: block-layout + - editorHeight: 500px + hideFormatButton: true + if: + name: isNotCreateSecret + type: function + loader: + name: onNewConfigSecretChange + watchPaths: + - schema/properties/spec/properties/configuration/properties/configSecret/properties/name + readonly: true + schema: temp/properties/newConfigSecret + type: multi-file-editor + validateContent: false + if: + name: ifReconfigurationTypeEqualsTo|selectNewConfigSecret + type: function + label: Config Secret + type: block-layout + - elements: + - customClass: mb-15 + label: New Apply Config + subtitle: Define custom configurations for your database using key-value + pairs. These parameters will overwrite existing settings.
Enter the + parameter you want to configure (e.g., max_connections). + type: label-element + - customClass: mb-2 + label: Configuration + loader: getConfigSecretsforAppyConfig + refresh: fetchConfigSecrets + schema: temp/properties/selectedConfiguration + type: select + - editorHeight: 500px + hideFormatButton: true + loader: + name: setApplyConfig + watchPaths: + - temp/properties/selectedConfiguration + schema: temp/properties/applyConfig + type: multi-file-editor + validateContent: false + watcher: + func: onApplyconfigChange + paths: + - temp/properties/applyConfig + if: + name: ifReconfigurationTypeEqualsTo|applyConfig + type: function + label: ApplyConfig + type: block-layout + - elements: + - customClass: mb-15 + label: Remove + subtitle: Selected a configuration secret from the available list to update + your database settings + type: label-element + - customClass: mb-2 + label: Configuration + loader: + name: getConfigSecretsforAppyConfig + watchPaths: + - schema/properties/metadata/properties/namespace + refresh: fetchConfigSecrets + schema: temp/properties/selectedConfigurationRemove + type: select + - editorHeight: 500px + hideFormatButton: true + init: + type: func + value: onRemoveConfigChange + readonly: true + schema: temp/properties/removeConfig + type: multi-file-editor + validateContent: false + watcher: + func: onRemoveConfigChange + paths: + - temp/properties/selectedConfigurationRemove + if: + name: ifReconfigurationTypeEqualsTo|remove + type: function + label: Remove + type: block-layout + label: New Config Secret + options: + - text: NEW CONFIG SECRET + value: selectNewConfigSecret + - text: APPLY CONFIG + value: applyConfig + - text: REMOVE + value: remove + schema: temp/properties/reconfigurationType + type: tab-layout + label: Configuration + type: block-layout + if: + name: ifRequestTypeEqualsTo|Reconfigure + type: function + label: Reconfigure Form + loader: + name: fetchConfigSecrets + watchPaths: + - schema/properties/metadata/properties/namespace + type: block-layout + - elements: + - if: + name: hasTlsField + type: function + init: + type: func + value: initTlsOperation + label: TLS Operation + options: + - text: Update + value: update + - text: Rotate + value: rotate + - text: Remove + value: remove + schema: temp/properties/tlsOperation + type: radio + watcher: + func: onTlsOperationChange + paths: + - temp/properties/tlsOperation + - fullwidth: true + if: + name: isTlsEnabled + type: function + label: Remove TLS + schema: schema/properties/spec/properties/tls/properties/remove + type: switch + - fullwidth: true + if: + name: isTlsEnabled + type: function + label: Rotate Certificates + schema: schema/properties/spec/properties/tls/properties/rotateCertificates + type: switch + - elements: + - fullwidth: true + init: + type: func + value: setValueFromDbDetails|/spec/tls/requireSSL + label: Require SSL + schema: schema/properties/spec/properties/tls/properties/requireSSL + type: switch + if: + name: showIssuerRefAndCertificates + type: function + label: Require SSL + type: block-layout + - elements: + - disable: true + init: + type: func + value: initIssuerRefApiGroup + label: API Group + schema: schema/properties/spec/properties/tls/properties/issuerRef/properties/apiGroup + type: input + watcher: + func: initIssuerRefApiGroup + paths: + - schema/properties/spec/properties/tls/properties/issuerRef/properties/kind + - init: + type: func + value: setValueFromDbDetails|/spec/tls/issuerRef/kind + label: Kind + options: + - text: Issuer + value: Issuer + - text: ClusterIssuer + value: ClusterIssuer + schema: schema/properties/spec/properties/tls/properties/issuerRef/properties/kind + type: select + validation: + name: isIssuerRefRequired + type: custom + - init: + type: func + value: setValueFromDbDetails|/spec/tls/issuerRef/name + label: Name + loader: + name: getIssuerRefsName + watchPaths: + - schema/properties/spec/properties/tls/properties/issuerRef/properties/kind + - schema/properties/metadata/properties/namespace + schema: schema/properties/spec/properties/tls/properties/issuerRef/properties/name + type: select + validation: + name: isIssuerRefRequired + type: custom + if: + name: showIssuerRefAndCertificates + type: function + label: Issuer Reference + showLabels: true + type: block-layout + - elements: + - buttonClass: is-light is-outlined + elements: + - disable: disableAlias + label: Alias + loader: fetchAliasOptions + schema: alias + type: select + validation: + type: required + - label: Secret Name + schema: secretName + type: input + - label: Duration + schema: duration + type: input + - label: Renew Before + schema: renewBefore + type: input + - buttonClass: is-light is-outlined + element: + label: Organization + type: input + label: Organizations + schema: subject/properties/organizations + type: array-item-form + - buttonClass: is-light is-outlined + element: + label: Country + type: input + label: Countries + schema: subject/properties/countries + type: array-item-form + - buttonClass: is-light is-outlined + element: + label: Organizational Unit + type: input + label: Organizational Units + schema: subject/properties/organizationalUnits + type: array-item-form + - buttonClass: is-light is-outlined + element: + label: Province + type: input + label: Provinces + schema: subject/properties/provinces + type: array-item-form + - buttonClass: is-light is-outlined + element: + label: DNS Name + type: input + label: DNS Names + schema: dnsNames + type: array-item-form + - buttonClass: is-light is-outlined + element: + label: IP Address + type: input + label: IP Addresses + schema: ipAddresses + type: array-item-form + label: Certificates + schema: schema/properties/spec/properties/tls/properties/certificates + type: array-object-form + if: + name: showIssuerRefAndCertificates + type: function + label: Certificates + loader: setValueFromDbDetails|/spec/tls/certificates|/spec/tls/certificates + showLabels: false + type: block-layout + if: + name: ifRequestTypeEqualsTo|ReconfigureTLS + type: function + label: TLS + type: block-layout + - elements: + - label: Timeout + schema: schema/properties/spec/properties/timeout + subtitle: Specify the maximum time allowed for the operation to complete before + it times out + type: time-picker + - init: + type: func + value: setApplyToIfReady + label: Apply + options: + - text: IfReady (OpsRequest will be applied if database is ready) + value: IfReady + - text: Always (OpsRequest will always be applied) + value: Always + schema: schema/properties/spec/properties/apply + type: radio + label: OpsRequest Options + showLabels: true + type: block-layout + loader: getDbDetails + type: single-step-form +type: multi-step-form diff --git a/charts/opskubedbcom-oracleopsrequest-editor/ui/functions.js b/charts/opskubedbcom-oracleopsrequest-editor/ui/functions.js new file mode 100644 index 0000000000..48ae9cdf1a --- /dev/null +++ b/charts/opskubedbcom-oracleopsrequest-editor/ui/functions.js @@ -0,0 +1,1942 @@ +const { axios, useOperator, store, useToast } = window.vueHelpers || {} +const machines = { + 'db.t.micro': { + resources: { + requests: { + cpu: '250m', + memory: '512Mi', + }, + limits: { + cpu: '500m', + memory: '1Gi', + }, + }, + }, + 'db.t.small': { + resources: { + requests: { + cpu: '1', + memory: '1Gi', + }, + limits: { + cpu: '2', + memory: '2Gi', + }, + }, + }, + 'db.t.medium': { + resources: { + requests: { + cpu: '1', + memory: '2Gi', + }, + limits: { + cpu: '2', + memory: '4Gi', + }, + }, + }, + 'db.t.large': { + resources: { + requests: { + cpu: '1', + memory: '4Gi', + }, + limits: { + cpu: '2', + memory: '8Gi', + }, + }, + }, + 'db.t.xlarge': { + resources: { + requests: { + cpu: '2', + memory: '8Gi', + }, + limits: { + cpu: '4', + memory: '16Gi', + }, + }, + }, + 'db.t.2xlarge': { + resources: { + requests: { + cpu: '4', + memory: '16Gi', + }, + limits: { + cpu: '8', + memory: '32Gi', + }, + }, + }, + 'db.m.small': { + resources: { + requests: { + cpu: '500m', + memory: '912680550', + }, + limits: { + cpu: '1', + memory: '1825361100', + }, + }, + }, + 'db.m.large': { + resources: { + requests: { + cpu: '1', + memory: '4Gi', + }, + limits: { + cpu: '2', + memory: '8Gi', + }, + }, + }, + 'db.m.xlarge': { + resources: { + requests: { + cpu: '2', + memory: '8Gi', + }, + limits: { + cpu: '4', + memory: '16Gi', + }, + }, + }, + 'db.m.2xlarge': { + resources: { + requests: { + cpu: '4', + memory: '16Gi', + }, + limits: { + cpu: '8', + memory: '32Gi', + }, + }, + }, + 'db.m.4xlarge': { + resources: { + requests: { + cpu: '8', + memory: '32Gi', + }, + limits: { + cpu: '16', + memory: '64Gi', + }, + }, + }, + 'db.m.8xlarge': { + resources: { + requests: { + cpu: '16', + memory: '64Gi', + }, + limits: { + cpu: '32', + memory: '128Gi', + }, + }, + }, + 'db.m.12xlarge': { + resources: { + requests: { + cpu: '24', + memory: '96Gi', + }, + limits: { + cpu: '48', + memory: '192Gi', + }, + }, + }, + 'db.m.16xlarge': { + resources: { + requests: { + cpu: '32', + memory: '128Gi', + }, + limits: { + cpu: '64', + memory: '256Gi', + }, + }, + }, + 'db.m.24xlarge': { + resources: { + requests: { + cpu: '48', + memory: '192Gi', + }, + limits: { + cpu: '96', + memory: '384Gi', + }, + }, + }, + 'db.r.large': { + resources: { + requests: { + cpu: '1', + memory: '8Gi', + }, + limits: { + cpu: '2', + memory: '16Gi', + }, + }, + }, + 'db.r.xlarge': { + resources: { + requests: { + cpu: '2', + memory: '16Gi', + }, + limits: { + cpu: '4', + memory: '32Gi', + }, + }, + }, + 'db.r.2xlarge': { + resources: { + requests: { + cpu: '4', + memory: '32Gi', + }, + limits: { + cpu: '8', + memory: '64Gi', + }, + }, + }, + 'db.r.4xlarge': { + resources: { + requests: { + cpu: '8', + memory: '96Gi', + }, + limits: { + cpu: '16', + memory: '192Gi', + }, + }, + }, + 'db.r.8xlarge': { + resources: { + requests: { + cpu: '16', + memory: '128Gi', + }, + limits: { + cpu: '32', + memory: '256Gi', + }, + }, + }, + 'db.r.12xlarge': { + resources: { + requests: { + cpu: '24', + memory: '192Gi', + }, + limits: { + cpu: '48', + memory: '384Gi', + }, + }, + }, + 'db.r.16xlarge': { + resources: { + requests: { + cpu: '32', + memory: '256Gi', + }, + limits: { + cpu: '64', + memory: '512Gi', + }, + }, + }, + 'db.r.24xlarge': { + resources: { + requests: { + cpu: '24', + memory: '384Gi', + }, + limits: { + cpu: '96', + memory: '768Gi', + }, + }, + }, +} + +const machineList = [ + 'custom', + 'db.t.micro', + 'db.t.small', + 'db.t.medium', + 'db.t.large', + 'db.t.xlarge', + 'db.t.2xlarge', + 'db.m.small', + 'db.m.large', + 'db.m.xlarge', + 'db.m.2xlarge', + 'db.m.4xlarge', + 'db.m.8xlarge', + 'db.m.12xlarge', + 'db.m.16xlarge', + 'db.m.24xlarge', + 'db.r.large', + 'db.r.xlarge', + 'db.r.2xlarge', + 'db.r.4xlarge', + 'db.r.8xlarge', + 'db.r.12xlarge', + 'db.r.16xlarge', + 'db.r.24xlarge', +] + +let machinesFromPreset = [] +const configSecretKeys = ['kubedb-user.cnf'] + +export const useFunc = (model) => { + const route = store.state?.route + const toast = useToast() + + const { getValue, storeGet, discriminator, setDiscriminatorValue, commit } = useOperator( + model, + store.state, + ) + + showAndInitOpsRequestType() + async function fetchJsons({ axios, itemCtx }) { + let ui = {} + let language = {} + let functions = {} + const { name, sourceRef, version, packageviewUrlPrefix } = itemCtx.chart + + try { + ui = await axios.get( + `${packageviewUrlPrefix}/create-ui.yaml?name=${name}&sourceApiGroup=${sourceRef.apiGroup}&sourceKind=${sourceRef.kind}&sourceNamespace=${sourceRef.namespace}&sourceName=${sourceRef.name}&version=${version}&format=json`, + ) + language = await axios.get( + `${packageviewUrlPrefix}/language.yaml?name=${name}&sourceApiGroup=${sourceRef.apiGroup}&sourceKind=${sourceRef.kind}&sourceNamespace=${sourceRef.namespace}&sourceName=${sourceRef.name}&version=${version}&format=json`, + ) + const functionString = await axios.get( + `${packageviewUrlPrefix}/functions.js?name=${name}&sourceApiGroup=${sourceRef.apiGroup}&sourceKind=${sourceRef.kind}&sourceNamespace=${sourceRef.namespace}&sourceName=${sourceRef.name}&version=${version}`, + ) + // declare evaluate the functionString to get the functions Object + const evalFunc = new Function(functionString.data || '') + functions = evalFunc() + } catch (e) { + console.log(e) + } + + return { + ui: ui.data || {}, + language: language.data || {}, + functions, + } + } + + function returnFalse() { + return false + } + + function isTlsEnabled() { + const dbDetails = getValue(discriminator, '/dbDetails') + return ( + (dbDetails?.spec?.sslMode && + dbDetails?.spec?.sslMode !== 'disabled' && + dbDetails?.spec?.sslMode !== 'disable') || + dbDetails?.spec?.tls + ) + } + + function isRancherManaged() { + const managers = storeGet('/cluster/clusterDefinition/result/clusterManagers') + const found = managers.find((item) => item === 'Rancher') + return !!found + } + + async function getNamespaces() { + if (storeGet('/route/params/actions')) return [] + const owner = storeGet('/route/params/user') + const cluster = storeGet('/route/params/cluster') + + const resp = await axios.get(`/clusters/${owner}/${cluster}/proxy/core/v1/namespaces`, { + params: { filter: { items: { metadata: { name: null } } } }, + }) + + const resources = (resp && resp.data && resp.data.items) || [] + + return resources.map((item) => { + const name = (item.metadata && item.metadata.name) || '' + return { + text: name, + value: name, + } + }) + } + + async function getDbs() { + if (storeGet('/route/params/actions')) return [] + const owner = storeGet('/route/params/user') + const cluster = storeGet('/route/params/cluster') + + const namespace = getValue(model, '/metadata/namespace') + // watchDependency('model#/metadata/namespace') + + const resp = await axios.get( + `/clusters/${owner}/${cluster}/proxy/kubedb.com/v1alpha2/namespaces/${namespace}/oracles`, + { + params: { filter: { items: { metadata: { name: null } } } }, + }, + ) + + const resources = (resp && resp.data && resp.data.items) || [] + + return resources.map((item) => { + const name = (item.metadata && item.metadata.name) || '' + return { + text: name, + value: name, + } + }) + } + + async function getDbDetails() { + machinesFromPreset = storeGet('/kubedbuiPresets')?.admin?.machineProfiles?.machines || [] + + const owner = storeGet('/route/params/user') + const cluster = storeGet('/route/params/cluster') + const namespace = storeGet('/route/query/namespace') || getValue(model, '/metadata/namespace') + const name = storeGet('/route/params/name') || getValue(model, '/spec/databaseRef/name') + + if (namespace && name) { + const url = `/clusters/${owner}/${cluster}/proxy/kubedb.com/v1alpha2/namespaces/${namespace}/oracles/${name}` + const resp = await axios.get(url) + + setDiscriminatorValue('/dbDetails', resp.data || {}) + + return resp.data || {} + } else return {} + } + + let presetVersions = [] + setDiscriminatorValue('/filteredVersion', []) + async function getDbVersions() { + const owner = storeGet('/route/params/user') + const cluster = storeGet('/route/params/cluster') + + const url = `/clusters/${owner}/${cluster}/proxy/charts.x-helm.dev/v1alpha1/clusterchartpresets/kubedb-ui-presets` + + let presets = storeGet('/kubedbuiPresets') || {} + if (!storeGet('/route/params/actions')) { + try { + const presetResp = await axios.get(url) + presets = presetResp.data?.spec?.values?.spec + } catch (e) { + console.log(e) + presets.status = String(e.status) + } + } + + try { + presetVersions = presets.admin?.databases?.Oracle?.versions?.available || [] + const queryParams = { + filter: { + items: { + metadata: { name: null }, + spec: { version: null, deprecated: null, updateConstraints: null }, + }, + }, + } + + const resp = await axios.get( + `/clusters/${owner}/${cluster}/proxy/catalog.kubedb.com/v1alpha1/oracleversions`, + { + params: queryParams, + }, + ) + + const resources = (resp && resp.data && resp.data.items) || [] + + const sortedVersions = resources.sort((a, b) => + versionCompare(a.spec.version, b.spec.version), + ) + + let ver = getValue(discriminator, '/dbDetails/spec/version') || '0' + const found = sortedVersions.find((item) => item.metadata.name === ver) + + if (found) ver = found.spec?.version + + const isGroupRepl = !!getValue(discriminator, '/dbDetails/spec/topology') + const allowed = isGroupRepl + ? found?.spec?.updateConstraints?.allowlist.groupReplication + : found?.spec?.updateConstraints?.allowlist.standalone + + const limit = allowed.length ? allowed[0] : '0.0' + + // keep only non deprecated & kubedb-ui-presets & within constraints of current version + // if presets.status is 404, it means no presets available, no need to filter with presets + const filteredOracleVersions = sortedVersions.filter((item) => { + // default limit 0.0 means no restrictions, show all higher versions + if (limit === '0.0') + return ( + !item.spec?.deprecated && + (presets.status === '404' || + presetVersions.length === 0 || + presetVersions.includes(item.metadata?.name)) && + versionCompare(item.spec?.version, ver) >= 0 + ) + // if limit doesn't have any operator, it's a single version + else if (!limit.match(/^(>=|<=|>|<)/)) + return ( + !item.spec?.deprecated && + (presets.status === '404' || + presetVersions.length === 0 || + presetVersions.includes(item.metadata?.name)) && + item.spec?.version === limit + ) + // if limit has operator, check version with constraints + else + return ( + !item.spec?.deprecated && + (presets.status === '404' || + presetVersions.length === 0 || + presetVersions.includes(item.metadata?.name)) && + isVersionWithinConstraints(item.spec?.version, limit) + ) + }) + setDiscriminatorValue('/filteredVersion', filteredOracleVersions) + + return filteredOracleVersions.map((item) => { + const name = (item.metadata && item.metadata.name) || '' + const specVersion = (item.spec && item.spec.version) || '' + return { + text: `${name} (${specVersion})`, + value: name, + } + }) + } catch (e) { + console.log(e) + return [] + } + } + + function getVersionInfo() { + const filteredVersion = getValue(discriminator, '/filteredVersion') + if (filteredVersion.length) return '' + + let txt = 'No versions from this list can be selected as the target version: [ ' + + presetVersions.forEach((v, idx) => { + txt = `${txt}"${v}"` + if (idx !== presetVersions.length - 1) txt = txt + ', ' + else txt = txt + ' ]' + }) + + return txt + } + + function getVersion() { + return filteredVersion.map((item) => { + const name = (item.metadata && item.metadata.name) || '' + const specVersion = (item.spec && item.spec.version) || '' + return { + text: `${name} (${specVersion})`, + value: name, + } + }) + } + + function isVersionEmpty() { + const val = getValue(discriminator, '/filteredVersion') + return val.length === 0 + } + + function versionCompare(v1, v2) { + const arr1 = v1.split('.').map(Number) + const arr2 = v2.split('.').map(Number) + + for (let i = 0; i < Math.max(arr1.length, arr2.length); i++) { + const num1 = arr1[i] || 0 + const num2 = arr2[i] || 0 + + if (num1 > num2) return 1 // v1 is higher + if (num1 < num2) return -1 // v2 is higher + } + return 0 // versions are equal + } + + function isVersionWithinConstraints(version, constraints) { + let constraintsArr = [] + if (constraints.includes(',')) constraintsArr = constraints?.split(',')?.map((c) => c.trim()) + else constraintsArr = [constraints] + + for (let constraint of constraintsArr) { + let match = constraint.match(/^(>=|<=|>|<)/) + let operator = match ? match[0] : '' + let constraintVersion = constraint.replace(/^(>=|<=|>|<)/, '').trim() + + let comparison = versionCompare(version, constraintVersion) + if ( + (operator === '>=' && comparison < 0) || + (operator === '<=' && comparison > 0) || + (operator === '>' && comparison <= 0) || + (operator === '<' && comparison >= 0) + ) + return false + } + return true + } + + function ifRequestTypeEqualsTo(type) { + const selectedType = getValue(model, '/spec/type') + // watchDependency('model#/spec/type') + + return selectedType === type + } + + function onRequestTypeChange() { + const selectedType = getValue(model, '/spec/type') + const reqTypeMapping = { + Upgrade: 'updateVersion', + UpdateVersion: 'updateVersion', + HorizontalScaling: 'horizontalScaling', + VerticalScaling: 'verticalScaling', + VolumeExpansion: 'volumeExpansion', + Restart: 'restart', + Reconfigure: 'configuration', + ReconfigureTLS: 'tls', + } + + Object.keys(reqTypeMapping).forEach((key) => { + if (key !== selectedType) commit('wizard/model$delete', `/spec/${reqTypeMapping[key]}`) + }) + } + + function disableOpsRequest() { + if (itemCtx.value === 'HorizontalScaling') { + const dbType = getDbType() + + if (dbType === 'standalone') return true + else return false + } else return false + } + + function getDbTls() { + // watchDependency('discriminator#/dbDetails') + const dbDetails = getValue(discriminator, '/dbDetails') + + const { spec } = dbDetails || {} + return spec?.tls || undefined + } + + function getDbType() { + // watchDependency('discriminator#/dbDetails') + const dbDetails = getValue(discriminator, '/dbDetails') + + const { spec } = dbDetails || {} + const { topology } = spec || {} + const { mode } = topology || {} + + const verd = mode ? 'cluster' : 'standalone' + + return verd + } + + function initNamespace() { + const { namespace } = route.query || {} + return namespace || null + } + + function initDatabaseRef() { + // watchDependency('model#/metadata/namespace') + const { name } = route.params || {} + return name + } + + function asDatabaseOperation() { + return !!route.params.actions + } + + function generateOpsRequestNameForClusterUI(getValue, model, route) { + const dbName = getValue(model, '/spec/databaseRef/name') + + const selectedType = getValue(model, '/spec/type') + const lowerType = selectedType ? String(selectedType).toLowerCase() : '' + + const resources = route.params.resource || '' + const resource = resources.slice(0, -1) + + const opsName = dbName ? dbName : resource + return `${opsName}-${Math.floor(Date.now() / 1000)}${lowerType ? '-' + lowerType : ''}` + } + + function showAndInitName() { + // watchDependency('model#/spec/type') + // watchDependency('model#/spec/databaseRef/name') + const ver = asDatabaseOperation() + + const selectedType = getValue(model, '/spec/type') + const lowerType = selectedType ? String(selectedType).toLowerCase() : '' + + if (ver) { + // For kubedb-ui + commit('wizard/model$update', { + path: '/metadata/name', + value: `${route.params.name}-${Math.floor(Date.now() / 1000)}-${lowerType}`, + force: true, + }) + } else { + // For cluster-ui + commit('wizard/model$update', { + path: '/metadata/name', + value: generateOpsRequestNameForClusterUI(getValue, model, route), + force: true, + }) + } + return !ver + } + + function showAndInitNamespace() { + const ver = asDatabaseOperation() + if (ver) { + commit('wizard/model$update', { + path: '/metadata/namespace', + value: `${route.query.namespace}`, + force: true, + }) + } + + return !ver + } + + function showAndInitDatabaseRef() { + const ver = asDatabaseOperation() + if (ver) { + commit('wizard/model$update', { + path: '/spec/databaseRef/name', + value: `${route.params.name}`, + force: true, + }) + } + + return !ver + } + + function showConfigureOpsrequestLabel() { + return !asDatabaseOperation() + } + + function showAndInitOpsRequestType() { + const ver = asDatabaseOperation() + const opMap = { + upgrade: 'UpdateVersion', + updateVersion: 'UpdateVersion', + horizontalscaling: 'HorizontalScaling', + verticalscaling: 'VerticalScaling', + volumeexpansion: 'VolumeExpansion', + restart: 'Restart', + reconfiguretls: 'ReconfigureTLS', + reconfigure: 'Reconfigure', + } + if (ver) { + const operation = storeGet('/resource/activeActionItem/result/operationId') || '' + + const match = /^(.*)-opsrequest-(.*)$/.exec(operation) + if (match) { + const opstype = match[2] + commit('wizard/model$update', { + path: '/spec/type', + value: opMap[opstype], + force: true, + }) + } + } + + return !ver + } + + // vertical scaling + function ifDbTypeEqualsTo(value, opsReqType) { + const verd = getDbType() + + return value === verd + } + + // machine profile stuffs + // let machinesFromPreset = [] + + function getMachines() { + const presets = storeGet('/kubedbuiPresets') || {} + const dbDetails = getValue(discriminator, '/dbDetails') + const containers = dbDetails?.spec?.podTemplate?.spec?.containers || [] + const limits = containers[0]?.resources?.limits || {} + + const avlMachines = presets.admin?.machineProfiles?.available || [] + let arr = [] + if (avlMachines.length) { + arr = avlMachines.map((machine) => { + if (machine === 'custom') + return { text: machine, value: { machine, cpu: limits.cpu, memory: limits.memory } } + else { + const machineData = machinesFromPreset.find((val) => val.id === machine) + if (machineData) { + const subtext = `CPU: ${machineData.limits.cpu}, Memory: ${machineData.limits.memory}` + const text = machineData.name ? machineData.name : machineData.id + return { + text, + subtext, + value: { + machine: text, + cpu: machineData.limits.cpu, + memory: machineData.limits.memory, + }, + } + } else + return { text: machine, value: { machine, cpu: limits.cpu, memory: limits.memory } } + } + }) + } else { + arr = machineList + .map((machine) => { + if (machine === 'custom') + return { text: machine, value: { machine, cpu: limits.cpu, memory: limits.memory } } + const subtext = `CPU: ${machines[machine].resources.limits.cpu}, Memory: ${machines[machine].resources.limits.memory}` + const text = machine + return { + text, + subtext, + value: { + machine: text, + cpu: machines[machine].resources.limits.cpu, + memory: machines[machine].resources.limits.memory, + }, + } + }) + .filter((val) => !!val) + } + return arr + } + + function setMachine() { + const dbDetails = getValue(discriminator, '/dbDetails') + const containers = dbDetails?.spec?.podTemplate?.spec?.containers || [] + const limits = containers[0]?.resources?.limits || {} + console.log(limits) + + const annotations = dbDetails?.metadata?.annotations || {} + const instance = annotations['kubernetes.io/instance-type'] + + let parsedInstance = {} + try { + if (instance) parsedInstance = JSON.parse(instance) + } catch (e) { + console.log(e) + parsedInstance = instance || {} + } + + const machine = parsedInstance || 'custom' + + const machinePresets = machinesFromPreset.find((item) => item.id === machine) + if (machinePresets) { + return { + machine: machine, + cpu: machinePresets.limits.cpu, + memory: machinePresets.limits.memory, + } + } else return { machine: 'custom', cpu: 4, memory: 2 } + } + + function onMachineChange(type, valPath) { + let selectedMachine = {} + selectedMachine = getValue(discriminator, '/machine') + const machine = machinesFromPreset.find((item) => item.id === selectedMachine.machine) + + let obj = {} + if (selectedMachine.machine !== 'custom') { + if (machine) obj = { limits: { ...machine?.limits }, requests: { ...machine?.limits } } + else obj = machines[selectedMachine.machine]?.resources + } else { + const cpu = selectedMachine.cpu || '' + const memory = selectedMachine.memory || '' + obj = { + limits: { cpu: cpu, memory: memory }, + requests: { cpu: cpu, memory: memory }, + } + } + + const path = `/spec/verticalScaling/${type}/resources` + + if (obj && Object.keys(obj).length) + commit('wizard/model$update', { + path: path, + value: obj, + force: true, + }) + + // update metadata.annotations + const annotations = getValue(model, '/metadata/annotations') || {} + annotations['kubernetes.io/instance-type'] = selectedMachine.machine + if (machinesFromPreset.length) + commit('wizard/model$update', { + path: '/metadata/annotations', + value: annotations, + force: true, + }) + } + + function isMachineCustom() { + // watchDependency('discriminator#/machine') + const machine = getValue(discriminator, '/machine') + return machine === 'custom' + } + + // Fetch and store database Infos + // for secret configurations in reconfigure + let configSecrets = [] + let secretConfigData = [] + let existingSecrets = [] + + async function fetchConfigSecrets() { + const owner = storeGet('/route/params/user') + const cluster = storeGet('/route/params/cluster') + const namespace = getValue(model, '/metadata/namespace') + // watchDependency('model#/metadata/namespace') + + const name = getValue(model, '/spec/databaseRef/name') + const dbGroup = getValue(model, '/route/params/group') + const dbKind = getValue(store.state, '/resource/definition/result/kind') + const dbResource = getValue(model, '/route/params/resource') + const dbVersion = getValue(model, '/route/params/version') + + try { + const resp = await axios.post( + `/clusters/${owner}/${cluster}/proxy/ui.kubedb.com/v1alpha1/databaseinfos`, + { + apiVersion: 'ui.kubedb.com/v1alpha1', + kind: 'DatabaseInfo', + request: { + source: { + ref: { + name: name, + namespace: namespace, + }, + resource: { + group: dbGroup, + kind: dbKind, + name: dbResource, + version: dbVersion, + }, + }, + keys: ['kubedb-user.cnf'], + }, + }, + ) + configSecrets = resp?.data?.response?.availableSecrets || [] + secretConfigData = resp?.data?.response?.configurations || [] + } catch (e) { + console.log(e) + } + + // Fetching all existing secrets + try { + const resp = await axios.get(`/clusters/${owner}/${cluster}/proxy/core/v1/secrets`) + resp.data?.items.forEach((item) => { + if (item.metadata?.name) { + existingSecrets.push(item.metadata.name) + } + }) + } catch (e) { + console.log(e) + } + } + + async function getConfigSecrets(type) { + type = type ? type + '/' : '' + const secretStatus = getValue(discriminator, `${type}createSecret/status`) + if (secretStatus === 'success') { + await fetchConfigSecrets() + } + const mappedSecrets = configSecrets.map((item) => { + return { text: item, value: item } + }) + mappedSecrets.push({ text: '+ Create a new Secret', value: 'Create' }) + return mappedSecrets + } + + async function getConfigSecretsforAppyConfig() { + const secrets = secretConfigData.map((item) => { + return { text: item.componentName, value: item.componentName } + }) + return secrets + } + + function getSelectedConfigurationData(type) { + type = type ? type + '/' : '' + const path = `/${type}selectedConfiguration` + const selectedConfiguration = getValue(discriminator, path) + + if (!selectedConfiguration) { + return [] + } + + const configuration = secretConfigData.find( + (item) => item.componentName === selectedConfiguration, + ) + + if (!configuration) { + return [] + } + + const result = [] + // Decode base64 and format as array of objects with name and content + Object.keys(configuration.data).forEach((fileName) => { + try { + // Decode base64 string + const decodedContent = atob(configuration.data[fileName]) + result.push({ + name: fileName, + content: decodedContent, + }) + } catch (e) { + console.error(`Error decoding ${fileName}:`, e) + result.push({ + name: fileName, + content: configuration.data[fileName], // Fallback to original if decode fails + }) + } + }) + + // Set the value to the model + commit('wizard/model$update', { + path: `/temp/${type}applyConfig`, + value: result, + force: true, + }) + + return result + } + + function getSelectedConfigurationName(configType, type) { + type = type ? type + '/' : '' + let path = '' + if (configType === 'create') path = `/spec/configuration/${type}/configSecret/name` + else if (configType === 'apply') path = `/${type}selectedConfiguration` + else if (configType === 'remove') path = `/${type}selectedConfigurationRemove` + + const selectedConfiguration = + configType === 'create' ? getValue(model, path) : getValue(discriminator, path) + + if (selectedConfiguration) + return { subtitle: ` You have selected ${selectedConfiguration} secret` } + else return { subtitle: 'No secret selected' } + } + + function getSelectedConfigurationValueForRemove(type) { + type = type ? type + '/' : '' + const path = `/${type}selectedConfigurationRemove` + const selectedConfiguration = getValue(discriminator, path) + + if (!selectedConfiguration) { + return '' + } + + const configuration = secretConfigData.find( + (item) => item.componentName === selectedConfiguration, + ) + + if (!configuration) { + return '' + } + + let data = {} + // Decode base64 and parse YAML for each key in the secret data + Object.keys(configuration.data).forEach((item) => { + try { + // Decode base64 string + const decodedString = atob(configuration.data[item]) + // Parse YAML string to object + const parsedYaml = yaml.load(decodedString) + // Store the parsed object with the filename as key + data[item] = parsedYaml + } catch (e) { + console.error(`Error parsing ${item}:`, e) + data[item] = atob(configuration.data[item]) // Fallback to decoded string + } + }) + + // Convert data object back to YAML string + return yaml.dump(data) + } + + async function createNewConfigSecret(type) { + type = type ? type + '/' : '' + const { user, cluster } = route.params + const url = `/clusters/${user}/${cluster}/resources` + const namespace = storeGet('/route/query/namespace') || getValue(model, '/metadata/namespace') + const secretName = getValue(discriminator, `${type}createSecret/name`) + const secretData = getValue(discriminator, `${type}createSecret/data`) + const secretDataObj = Object.fromEntries(secretData.map((item) => [item.key, item.value])) + + // Check uniqueness of secret name + if (existingSecrets.includes(secretName)) { + toast.error('A secret with this name already exists. Please choose another name.', { + timeout: 8000, + }) + return false + } + + try { + const res = await axios.post(url, { + apiVersion: 'v1', + stringData: secretDataObj, + kind: 'Secret', + metadata: { + name: secretName, + namespace: namespace, + }, + type: 'Opaque', + }) + commit('wizard/temp$update', { + path: `${type}createSecret/status`, + value: 'success', + }) + commit('wizard/temp$update', { + path: `${type}createSecret/lastCreatedSecret`, + value: secretName, + }) + toast.success('Secret created successfully') + } catch (error) { + const errMsg = decodeError(error, 'Failed to create secret') + toast.error(errMsg, { timeout: 5000 }) + cancelCreateSecret() + } + return true + } + + function decodeError(msg, defaultMsg) { + if (typeof msg === 'string') { + return msg || defaultMsg + } + return ( + (msg.response && msg.response.data && msg.response.data.message) || + (msg.response && msg.response.data) || + (msg.status && msg.status.status) || + defaultMsg + ) + } + + function isCreateSecret(type) { + type = type ? type + '/' : '' + const selectedSecret = getValue(model, `spec/configuration/${type}configSecret/name`) + const res = selectedSecret === 'Create' + + if (res === true) { + commit('wizard/temp$update', { + path: `${type}createSecret/status`, + value: 'pending', + }) + } + return res + } + + function isNotCreateSecret(type) { + return !isCreateSecret(type) + } + + function onCreateSecretChange(type) { + type = type ? type + '/' : '' + const secretStatus = getValue(discriminator, `${type}createSecret/status`) + if (secretStatus === 'cancelled') return '' + else if (secretStatus === 'success') { + const name = getValue(discriminator, `${type}createSecret/lastCreatedSecret`) + + const configFound = configSecrets.find((item) => item === name) + return configFound ? { text: name, value: name } : '' + } + } + + function cancelCreateSecret(type) { + type = type ? type + '/' : '' + commit('wizard/temp$delete', `${type}createSecret/name`) + commit('wizard/temp$delete', `${type}createSecret/data`) + commit('wizard/temp$update', { + path: `${type}createSecret/status`, + value: 'cancelled', + }) + } + + async function onApplyconfigChange(type) { + type = type ? type + '/' : '' + const configValue = getValue(discriminator, `${type}applyConfig`) + + if (!configValue) { + commit('wizard/model$delete', `/spec/configuration/${type}applyConfig`) + return + } + const tempConfigObj = {} + configValue.forEach((item) => { + if (item.name && item.content) { + tempConfigObj[item.name] = item.content + } + }) + if (Object.keys(tempConfigObj).length === 0) { + commit('wizard/model$delete', `/spec/configuration/${type}applyConfig`) + return + } + commit('wizard/model$update', { + path: `/spec/configuration/${type}applyConfig`, + value: tempConfigObj, + }) + } + + function setApplyConfig(type) { + type = type ? type + '/' : '' + const configPath = `/${type}selectedConfiguration` + const selectedConfig = getValue(discriminator, configPath) + if (!selectedConfig) { + return [{ name: '', content: '' }] + } + const applyconfigData = secretConfigData.find((item) => { + if (item.componentName === selectedConfig) { + return item + } + }) + const { applyConfig } = applyconfigData + const configObj = [] + + if (applyConfig) { + Object.keys(applyConfig).forEach((fileName) => { + configObj.push({ + name: fileName, + content: applyConfig[fileName], + }) + }) + } + configSecretKeys.forEach((key) => { + if (!configObj.find((item) => item.name === key)) { + configObj.push({ name: key, content: '' }) + } + }) + return configObj + } + + function onRemoveConfigChange(type) { + type = type ? type + '/' : '' + const configPath = `/${type}selectedConfigurationRemove` + const selectedConfig = getValue(discriminator, configPath) + + if (!selectedConfig) { + commit('wizard/model$delete', `/spec/configuration/${type}removeCustomConfig`) + return [{ name: '', content: '' }] + } + commit('wizard/model$update', { + path: `/spec/configuration/${type}removeCustomConfig`, + value: true, + }) + + const configuration = secretConfigData.find((item) => item.componentName === selectedConfig) + + if (!configuration.data) { + return [{ name: '', content: '' }] + } + + const configObj = [] + // Decode base64 and format as array of objects with name and content + Object.keys(configuration.data).forEach((fileName) => { + try { + // Decode base64 string + const decodedString = atob(configuration.data[fileName]) + configObj.push({ + name: fileName, + content: decodedString, + }) + } catch (e) { + console.error(`Error decoding ${fileName}:`, e) + configObj.push({ + name: fileName, + content: configuration.data[fileName], // Fallback to original if decode fails + }) + } + }) + return configObj + } + + async function onNewConfigSecretChange(type) { + type = type ? type + '/' : '' + const path = `/spec/configuration/${type}configSecret/name` + const selectedSecret = getValue(model, path) + + if (!selectedSecret) { + commit('wizard/model$delete', `/spec/configuration/${type}configSecret`) + return [{ name: '', content: '' }] + } + if (selectedSecret === 'Create') return [{ name: '', content: '' }] + + const owner = storeGet('/route/params/user') + const cluster = storeGet('/route/params/cluster') + const namespace = storeGet('/route/query/namespace') || getValue(model, '/metadata/namespace') + + try { + // Fetch the secret data from API + const secretResp = await axios.get( + `/clusters/${owner}/${cluster}/proxy/core/v1/namespaces/${namespace}/secrets/${selectedSecret}`, + ) + + const secretData = secretResp.data?.data || {} + const configObj = [] + + // Decode base64 and format as array of objects with name and content + Object.keys(secretData).forEach((fileName) => { + try { + // Decode base64 string + const decodedString = atob(secretData[fileName]) + configObj.push({ + name: fileName, + content: decodedString, + }) + } catch (e) { + console.error(`Error decoding ${fileName}:`, e) + configObj.push({ + name: fileName, + content: secretData[fileName], // Fallback to original if decode fails + }) + } + }) + + return configObj + } catch (e) { + console.error('Error fetching secret:', e) + return [{ name: '', content: '' }] + } + } + + function onSelectedSecretChange(index) { + const secretData = getValue(discriminator, 'createSecret/data') || [] + const selfSecrets = secretData.map((item) => item.key) + + const remainingSecrets = configSecretKeys.filter((item) => !selfSecrets.includes(item)) + + const selfKey = getValue(discriminator, `createSecret/data/${index}/key`) + if (selfKey) { + remainingSecrets.push(selfKey) + } + const resSecret = remainingSecrets.map((item) => { + return { text: item, value: item } + }) + return resSecret + } + + let secretArray = [] + + function createSecretUrl() { + const user = storeGet('/route/params/user') + const cluster = storeGet('/route/params/cluster') + + const domain = storeGet('/domain') || '' + if (domain.includes('bb.test')) { + return `http://console.bb.test:5990/console/${user}/kubernetes/${cluster}/core/v1/secrets/create` + } else { + const editedDomain = domain.replace('kubedb', 'console') + return `${editedDomain}/console/${user}/kubernetes/${cluster}/core/v1/secrets/create` + } + } + + function isConfigSelected() { + const secretName = getValue(model, '/spec/configuration/configSecret/name') + return !!secretName + } + + function isEqualToValueFromType(value) { + // watchDependency('discriminator#/valueFromType') + const valueFrom = getValue(discriminator, '/valueFromType') + return valueFrom === value + } + + async function getNamespacedResourceList({ namespace, group, version, resource }) { + const owner = storeGet('/route/params/user') + const cluster = storeGet('/route/params/cluster') + + const url = `/clusters/${owner}/${cluster}/proxy/${group}/${version}/namespaces/${namespace}/${resource}` + + let ans = [] + try { + const resp = await axios.get(url, { + params: { + filter: { items: { metadata: { name: null }, type: null } }, + }, + }) + + const items = (resp && resp.data && resp.data.items) || [] + ans = items + } catch (e) { + console.log(e) + } + + return ans + } + async function getResourceList({ group, version, resource }) { + const owner = storeGet('/route/params/user') + const cluster = storeGet('/route/params/cluster') + + const url = `/clusters/${owner}/${cluster}/proxy/${group}/${version}/${resource}` + + let ans = [] + try { + const resp = await axios.get(url, { + params: { + filter: { items: { metadata: { name: null }, type: null } }, + }, + }) + + const items = (resp && resp.data && resp.data.items) || [] + ans = items + } catch (e) { + console.log(e) + } + + return ans + } + async function resourceNames(group, version, resource) { + const namespace = getValue(model, '/metadata/namespace') + // watchDependency('model#/metadata/namespace') + + let resources = await getNamespacedResourceList({ + namespace, + group, + version, + resource, + }) + + if (resource === 'secrets') { + resources = resources.filter((item) => { + const validType = ['kubernetes.io/service-account-token', 'Opaque'] + return validType.includes(item.type) + }) + } + + return resources.map((resource) => { + const name = (resource.metadata && resource.metadata.name) || '' + return { + text: name, + value: name, + } + }) + } + async function unNamespacedResourceNames(group, version, resource) { + let resources = await getResourceList({ + group, + version, + resource, + }) + + if (resource === 'secrets') { + resources = resources.filter((item) => { + const validType = ['kubernetes.io/service-account-token', 'Opaque'] + return validType.includes(item.type) + }) + } + + return resources.map((resource) => { + const name = (resource.metadata && resource.metadata.name) || '' + return { + text: name, + value: name, + } + }) + } + + // reconfiguration type + function ifReconfigurationTypeEqualsTo(value) { + const reconfigurationType = getValue(discriminator, '/reconfigurationType') + // watchDependency('discriminator#/reconfigurationType') + + return reconfigurationType === value + } + + function onReconfigurationTypeChange() { + const reconfigurationType = getValue(discriminator, '/reconfigurationType') + setDiscriminatorValue('/applyConfig', []) + if (reconfigurationType === 'remove') { + commit('wizard/model$delete', `/spec/configuration`) + + commit('wizard/model$update', { + path: `/spec/configuration/removeCustomConfig`, + value: true, + force: true, + }) + } else { + commit('wizard/model$delete', `/spec/configuration/configSecret`) + commit('wizard/model$delete', `/spec/configuration/applyConfig`) + commit('wizard/model$delete', `/spec/configuration/removeCustomConfig`) + } + } + + // for tls + function hasTlsField() { + const tls = getDbTls() + + return !!tls + } + + function initIssuerRefApiGroup() { + const kind = getValue(model, '/spec/tls/issuerRef/kind') + // watchDependency('model#/spec/tls/issuerRef/kind') + + if (kind) { + const apiGroup = getValue(discriminator, '/dbDetails/spec/tls/issuerRef/apiGroup') + if (apiGroup) return apiGroup + return 'cert-manager.io' + } else return undefined + } + + async function getIssuerRefsName() { + const owner = storeGet('/route/params/user') + const cluster = storeGet('/route/params/cluster') + // watchDependency('model#/spec/tls/issuerRef/kind') + // watchDependency('model#/metadata/namespace') + const kind = getValue(model, '/spec/tls/issuerRef/kind') + const namespace = getValue(model, '/metadata/namespace') + + if (kind === 'Issuer') { + const url = `/clusters/${owner}/${cluster}/proxy/cert-manager.io/v1/namespaces/${namespace}/issuers` + return getIssuer(url) + } else if (kind === 'ClusterIssuer') { + const url = `/clusters/${owner}/${cluster}/proxy/charts.x-helm.dev/v1alpha1/clusterchartpresets/kubedb-ui-presets` + + let presets = storeGet('/kubedbuiPresets') || {} + if (!storeGet('/route/params/actions')) { + try { + const presetResp = await axios.get(url) + presets = presetResp.data?.spec?.values?.spec + } catch (e) { + console.log(e) + presets.status = String(e.status) + } + } + let clusterIssuers = presets.admin?.clusterIssuers?.available || [] + if (presets.status === '404') { + const url = `/clusters/${owner}/${cluster}/proxy/cert-manager.io/v1/clusterissuers` + return getIssuer(url) + } + return clusterIssuers + } else if (!kind) { + commit('wizard/model$delete', '/spec/tls/issuerRef/name') + return [] + } + + async function getIssuer(url) { + try { + const resp = await axios.get(url) + const resources = (resp && resp.data && resp.data.items) || [] + + resources.map((item) => { + const name = (item.metadata && item.metadata.name) || '' + item.text = name + item.value = name + return true + }) + return resources + } catch (e) { + console.log(e) + return [] + } + } + } + + function initTlsOperation() { + return 'update' + } + function onTlsOperationChange() { + const tlsOperation = getValue(discriminator, '/tlsOperation') + + commit('wizard/model$delete', '/spec/tls') + + if (tlsOperation === 'rotate') { + commit('wizard/model$update', { + path: '/spec/tls/rotateCertificates', + value: true, + force: true, + }) + commit('wizard/model$delete', '/spec/tls/certificates') + commit('wizard/model$delete', '/spec/tls/remove') + } else if (tlsOperation === 'remove') { + commit('wizard/model$update', { + path: '/spec/tls/remove', + value: true, + force: true, + }) + commit('wizard/model$delete', '/spec/tls/certificates') + commit('wizard/model$delete', '/spec/tls/rotateCertificates') + } + } + + function showIssuerRefAndCertificates() { + const tlsOperation = getValue(discriminator, '/tlsOperation') + // watchDependency('discriminator#/tlsOperation') + const verd = tlsOperation !== 'remove' && tlsOperation !== 'rotate' + + return verd + } + + function isIssuerRefRequired() { + const hasTls = hasTlsField() + return hasTls ? false : '' + } + + function getRequestTypeFromRoute() { + const isDbloading = isDbDetailsLoading() + const { query } = route || {} + const { requestType } = query || {} + return isDbloading ? '' : requestType || '' + } + + // ************************************** Set db details ***************************************** + + function isDbDetailsLoading() { + // watchDependency('discriminator#/dbDetails') + // watchDependency('model#/spec/databaseRef/name') + const dbDetails = getValue(discriminator, '/dbDetails') + const dbName = getValue(model, '/spec/databaseRef/name') + + return !dbDetails || !dbName + } + + function setValueFromDbDetails(path, commitPath) { + // watchDependency('discriminator#/dbDetails') + + const retValue = getValue(discriminator, `/dbDetails${path}`) + + if (commitPath && retValue) { + const tlsOperation = getValue(discriminator, '/tlsOperation') + + // computed called when tls fields is not visible + if (commitPath.includes('/spec/tls') && tlsOperation !== 'update') return undefined + + // direct model update required for reusable element. + // computed property is not applicable for reusable element + commit('wizard/model$update', { + path: commitPath, + value: retValue, + force: true, + }) + } + + return retValue || undefined + } + + function setConfigFiles() { + // watchDependency('model#/resources/secret_config/stringData') + const configFiles = getValue(model, '/resources/secret_config/stringData') + + const files = [] + + for (const item in configFiles) { + const obj = {} + obj.key = item + obj.value = configFiles[item] + files.push(obj) + } + + return files + } + + function getAliasOptions() { + return ['server', 'client', 'metrics-exporter'] + } + + function isNamespaceDisabled() { + const { namespace } = route.query || {} + return !!namespace + } + + function isDatabaseRefDisabled() { + const { name } = route.params || {} + return !!name + } + + function onNamespaceChange() { + commit('wizard/model$delete', '/spec/type') + } + + function onDbChange() { + commit('wizard/model$delete', '/spec/type') + getDbDetails() + } + + function setApplyToIfReady() { + return 'IfReady' + } + + function isVerticalScaleTopologyRequired() { + // watchDependency('discriminator#/topologyKey') + // watchDependency('discriminator#/topologyValue') + + const key = getValue(discriminator, '/topologyKey') + const value = getValue(discriminator, '/topologyValue') + const path = `/spec/verticalScaling/node/topology` + + if (key || value) { + commit('wizard/model$update', { + path: path, + value: { key, value }, + force: true, + }) + return '' + } else { + commit('wizard/model$delete', path) + return false + } + } + + function checkVolume(initpath, path) { + const volume = getValue(discriminator, `/dbDetails${initpath}`) + const input = getValue(model, path) + + try { + const sizeInBytes = parseSize(volume) + const inputSizeInBytes = parseSize(input) + + if (inputSizeInBytes >= sizeInBytes) return + else return 'Cannot expand to lower volume!' + } catch (err) { + return err.message || 'Invalid' + } + } + + function parseSize(sizeStr) { + const units = { + '': 1, + K: 1e3, + M: 1e6, + G: 1e9, + T: 1e12, + P: 1e15, + E: 1e18, + Ki: 1024, + Mi: 1024 ** 2, + Gi: 1024 ** 3, + Ti: 1024 ** 4, + Pi: 1024 ** 5, + Ei: 1024 ** 6, + } + + const match = String(sizeStr).match(/^([0-9]+(?:\.[0-9]*)?)\s*([A-Za-z]*)$/) + if (!match) throw new Error('Invalid size format') + + const value = parseFloat(match[1]) + const unit = match[2] + + if (!(unit in units)) + throw new Error('Unrecognized unit. Available units are K, Ki, M, Mi, G, Gi etc') + + return value * units[unit] + } + + function fetchAliasOptions() { + return getAliasOptions ? getAliasOptions() : [] + } + + function validateNewCertificates({ itemCtx }) { + const addedAliases = (model && model.map((item) => item.alias)) || [] + + if (addedAliases.includes(itemCtx.alias) && itemCtx.isCreate) { + return { isInvalid: true, message: 'Alias already exists' } + } + return {} + } + + function disableAlias() { + return !!(model && model.alias) + } + + function getSelectedConfigSecret(type) { + const path = `/spec/configuration/configSecret/name` + const selectedSecret = getValue(model, path) + // watchDependency(`model#${path}`) + return `You have selected ${selectedSecret} secret` || 'No secret selected' + } + + function objectToYaml(obj, indent = 0) { + if (obj === null || obj === undefined) return 'null' + if (typeof obj !== 'object') return JSON.stringify(obj) + + const spaces = ' '.repeat(indent) + + if (Array.isArray(obj)) { + return obj + .map((item) => `${spaces}- ${objectToYaml(item, indent + 1).trimStart()}`) + .join('\n') + } + + return Object.keys(obj) + .map((key) => { + const value = obj[key] + const keyLine = `${spaces}${key}:` + + if (value === null || value === undefined) { + return `${keyLine} null` + } + + if (typeof value === 'object') { + const nested = objectToYaml(value, indent + 1) + return `${keyLine}\n${nested}` + } + + if (typeof value === 'string') { + return `${keyLine} "${value}"` + } + + return `${keyLine} ${value}` + }) + .join('\n') + } + + function getSelectedConfigSecretValue(type) { + const path = `/spec/configuration/configSecret/name` + const selectedSecret = getValue(model, path) + let data + secretArray.forEach((item) => { + if (item.value === selectedSecret) { + data = objectToYaml(item.data).trim() || 'No Data Found' + } + }) + return data || 'No Data Found' + } + + function setExporter(type) { + let path = `/dbDetails/spec/monitor/prometheus/exporter/resources/limits/${type}` + const limitVal = getValue(discriminator, path) + + if (!limitVal) { + path = `/dbDetails/spec/monitor/prometheus/exporter/resources/requests/${type}` + const reqVal = getValue(discriminator, path) + + if (reqVal) return reqVal + } + return limitVal + } + + function onExporterResourceChange(type) { + const commitPath = `/spec/verticalScaling/exporter/resources/requests/${type}` + const valPath = `/spec/verticalScaling/exporter/resources/limits/${type}` + const val = getValue(model, valPath) + if (val) + commit('wizard/model$update', { + path: commitPath, + value: val, + force: true, + }) + } + + function isMachineValid() { + const dbDetails = getValue(discriminator, '/dbDetails') + const containers = dbDetails?.spec?.podTemplate?.spec?.containers || [] + const limits = containers[0]?.resources?.limits || {} + + const selectedMachine = getValue(discriminator, '/machine') + const selectedLimits = { cpu: selectedMachine.cpu, memory: selectedMachine.memory } + + if (JSON.stringify(limits) === JSON.stringify(selectedLimits)) { + return 'Resource limits are same as current machine configuration. Please select different resources or machine preset.' + } + return false + } + + return { + isMachineValid, + setExporter, + onExporterResourceChange, + fetchAliasOptions, + validateNewCertificates, + disableAlias, + isRancherManaged, + fetchJsons, + returnFalse, + getNamespaces, + getDbs, + getDbDetails, + getDbVersions, + getVersionInfo, + isVersionEmpty, + getVersion, + ifRequestTypeEqualsTo, + onRequestTypeChange, + getDbTls, + getDbType, + disableOpsRequest, + initNamespace, + initDatabaseRef, + showAndInitName, + showAndInitNamespace, + showAndInitDatabaseRef, + showConfigureOpsrequestLabel, + showAndInitOpsRequestType, + ifDbTypeEqualsTo, + getConfigSecrets, + getSelectedConfigSecret, + getSelectedConfigSecretValue, + createSecretUrl, + isEqualToValueFromType, + getNamespacedResourceList, + getResourceList, + resourceNames, + unNamespacedResourceNames, + ifReconfigurationTypeEqualsTo, + onReconfigurationTypeChange, + onApplyconfigChange, + hasTlsField, + initIssuerRefApiGroup, + getIssuerRefsName, + initTlsOperation, + onTlsOperationChange, + showIssuerRefAndCertificates, + isIssuerRefRequired, + getRequestTypeFromRoute, + isDbDetailsLoading, + setValueFromDbDetails, + getAliasOptions, + isDatabaseRefDisabled, + isNamespaceDisabled, + onNamespaceChange, + onDbChange, + setApplyToIfReady, + isVerticalScaleTopologyRequired, + getMachines, + setMachine, + onMachineChange, + isMachineCustom, + checkVolume, + setConfigFiles, + isConfigSelected, + fetchConfigSecrets, + getConfigSecretsforAppyConfig, + getSelectedConfigurationData, + getSelectedConfigurationName, + getSelectedConfigurationValueForRemove, + createNewConfigSecret, + decodeError, + isCreateSecret, + isNotCreateSecret, + onCreateSecretChange, + cancelCreateSecret, + setApplyConfig, + onRemoveConfigChange, + onNewConfigSecretChange, + onSelectedSecretChange, + isTlsEnabled, + } +} diff --git a/charts/opskubedbcom-oracleopsrequest-editor/ui/language.yaml b/charts/opskubedbcom-oracleopsrequest-editor/ui/language.yaml new file mode 100644 index 0000000000..23733bffc3 --- /dev/null +++ b/charts/opskubedbcom-oracleopsrequest-editor/ui/language.yaml @@ -0,0 +1,307 @@ +bn: {} +en: + labels: + agent: Select a Monitoring Method + oracle: Oracle + node_selection_policy: Node Selection Policy + topology: Topology + alias: Alias + annotations: + key: Key + label: Annotations + value: Value + api_group: API Group + apply: Apply + args: Args + backup: + invoker: Backup Invoker + title: Schedule a Backup? + backupBlueprint: + name: Blueprint Name + schedule: Schedule + taskParameters: Task Parameters + title: Backup Blueprint + backupConfiguration: + retentionPolicy: + keepLast: Keep Last + name: Name + prune: Prune + title: Retention Policy + schedule: Schedule + targetReference: + apiVersion: Target ApiVersion + kind: Target Kind + name: Reference Name + title: Target Reference + type: Reference Type + taskName: Task Name + title: Backup Configuration + basic_info: Basic Information + certificate: Certificate + certificates: Certificates + client_auth_mode: Client Auth Mode + cluster_ip: Cluster IP + configOptions: Configuration Options + configSecret: Config Secret + createConfig: Create Secret + configServer: Config Server + config_map_key: ConfigMap Key + config_map_name: ConfigMap Name + config_ops_request: Configure Ops Request + configuration: Configuration + configuration_files: Configuration Files + configuration_source: Configuration Source + controller_annotations: Controller Annotations + countries: Countries + country: Country + cpu: CPU + customize_exporter: Customize Exporter Sidecar + data_cold_node: DataCold Node + data_content_node: DataContent Node + data_frozen_node: DataFrozen Node + data_hot_node: DataHot Node + data_node: Data Node + data_warm_node: DataWarm Node + dataSource: DataWarm Source + database: + mode: Database Mode + name: Database Name + secret: Database Secret + version: Database Version + databaseRef: Select your database + dns_name: DNS Name + dns_names: DNS Names + duration: Duration + effect: Effect + enable_monitoring: Enable Monitoring + enable_ssl_question: Enable SSL? + enable_tls: Enable TLS + endpoint: Endpoint + endpoints: Endpoints + environmentVariablesFrom: Environemt Variables From + environment_variable: Environment Variable + environment_variables: Environment Variables + exporter: Exporter + exporter_configuration: Exporter Configuration + external_ip: External IP + external_ips: External IPs + external_traffic_policy: External Traffic Policy + fs_group: Fs Group + health_check_node_port: Health Check Node Port + honor_labels: Honor labels + image_pull_secrets: Image Pull Secrets + ingest_node: Ingest Node + initialization: Pre-populate your MongoDB from backup/another database + applyConfig: + key: Key + value: Value + label: Apply Config + interval: Interval + ip_address: IP Address + ip_addresses: IP Addresses + issuer_ref: Issuer Reference + key: Key + kind: Kind + labels: + key: Key + label: Labels + value: Value + level: Level + limit: Limit + limits: Limits + load_balancer_ip: Load Balancer IP + load_balancer_source_range: Load Balancer Source Range + load_balancer_source_ranges: Load Balancer Source Ranges + master_node: Master Node + match_expression: Match Expression + match_expressions: Match Expressions + match_field: Match Field + match_fields: Match Fields + memory: Memory + ml_node: ML Node + mongos: Mongos + name: Name + namespace: Namespace + new_secret_password: New Database Secret + node: Node + node_port: Node Port + node_selector: Node Selector + node_selector_terms: Node Selector Terms + op_req_name: Ops Request Name + operator: Operator + ops_request_type: Type of Ops Request + organization: Organization + organizational_unit: Organizational Unit + organizational_units: Organizational Units + organizations: Organizations + password: Password (Keep it empty to autogenerate) + path: Path + pod_annotations: Pod Annotations + pod_spec: Pod Spec + pod_template: Pod Template + port: Port + ports: Ports + postgres: Postgres + prePopulateDatabase: Do you want to pre-populate your database? + preferred_during_scheduling_ignored_during_execution: Preferred During Scheduling Ignored During Execution + prometheus: Prometheus + province: Province + provinces: Provinces + reconfigurationType: Reconfiguration Type + removeCustomConfig: Remove Custom Config? + renew_before: Renew Before + replicaSet: Replica Set + replicas: Replicas + replicaset: + name: Replicaset Name + number: Replica Number + repositories: + backend: + bucket: Bucket + container: Container + endPoint: End Point + maxConnections: Maximum Connections + mountPath: Mount Path + path: Path + prefix: Prefix + pvcName: Claim Name + region: Region + secret: Storage Secret + server: Server + subPath: Sub Path + title: Backend + type: Type + url: URL + volumeSource: Select Volume Source + choise: "" + name: Name + title: Repository + request: Request + requests: Requests + require_ssl_question: Require SSL? + resources: Resources + restoreSession: + name: Name + snapshot: Snapshot + title: Restore Session + role: Role + run_as_group: Run as Group + run_as_non_root: Run As Non Root? + run_as_user: Run as User + runtimeSettings: + choise: Customize Restore Job Runtime Settings? + container: + ionice: + class: Class + classData: Class Data + title: Ionice + nice: + adjustment: Adjustment + title: Nice + resources: + cpu: CPU + limits: Limits + memory: Memory + requests: Requests + title: Resources + title: Container Runtime Settings + pod: + imagePullSecrets: Image Pull Secrets + serviceAccountName: Service Account Name + title: Pod Runtime Settings + securityContext: + fsGroup: FS Group + privileged: Privileged + runAsGroup: Run As Group + runAsNonRoot: Run As Non Root + runAsUser: Run As User + seLinuxOptions: + level: LeveL + role: Role + title: SE Linux Options + type: Type + user: User + title: Security Context + scrapping_interval: Scrapping Interval + script: + path: Script Path + volume: Source Volume + volumeName: Name + volumeType: Type + se_linux_options: SE Linux Options + secret: Secret + secret_key: Secret Key + secret_name: Secret Name + security_context: Security Context + service_account_name: Service Account Name + service_monitor: Service Monitor + service_monitor_configuration: ServiceMonitor Configuration + service_template: Service Template + service_template_annotations: Service Template Annotations + service_templates: Service Templates + shard: Shard + shardNodes: Shard Nodes + shards: Shards + ssl_mode: SSL Mode + standalone: Standalone + storage: + class: Storage Class + size: Storage Size + master_size: Master Node + data_size: Data Node + ingest_size: Ingest Node + ml_size: ML Node + transform_size: Transform Node + data_cold_size: DataCold Node + data_content_size: DataContent Node + data_frozen_size: DataFrozen Node + data_hot_size: DataHot Node + data_warm_size: DataWarm Node + subject: Subject + targetVersion: Select Target Version + terminalPolicy: Terminal Policy + timeout: Timeout + timeout_seconds: Timeout Seconds + tls: Reconfigure TLS + tlsOperation: Choose TLS Operation + toleration: Toleration + toleration_seconds: Toleration in seconds + tolerations: Tolerations + transform_node: Transform Node + type: Type + user: User + value: Value + values: Values + waitForInitialRestore: Wait For Initial Restore? + weight: Weight + options: + HorizontalScaling: + description: Scale up or down pod count + text: Horizontal Scaling + Reconfigure: + description: Reconfigure your database + text: Reconfigure + ReconfigureTLS: + description: Reconfigure your database tls configuration + text: Reconfigure TLS + Restart: + description: Restart your database + text: Restart + Upgrade: + description: " Upgrade your database to any version" + text: Upgrade + UpdateVersion: + description: Update your database to any version + text: Update Version + VerticalScaling: + description: Manage your CPU resources + text: Vertical Scaling + VolumeExpansion: + description: Manage your database size + text: Volume Expansion + client_auth_mode: + md5: md5 + scram: scram + cert: cert + steps: + - label: Basic Information From be03ccb327eb016a61e28a1b5358e56540d08a52 Mon Sep 17 00:00:00 2001 From: Samiul Date: Fri, 22 May 2026 12:29:20 +0600 Subject: [PATCH 2/4] add clickhouse opsrequest forms Signed-off-by: Samiul --- .../ui/create-ui.yaml | 651 ++++++ .../ui/functions.js | 1940 +++++++++++++++++ .../ui/language.yaml | 307 +++ 3 files changed, 2898 insertions(+) create mode 100644 charts/opskubedbcom-clickhouseopsrequest-editor/ui/create-ui.yaml create mode 100644 charts/opskubedbcom-clickhouseopsrequest-editor/ui/functions.js create mode 100644 charts/opskubedbcom-clickhouseopsrequest-editor/ui/language.yaml diff --git a/charts/opskubedbcom-clickhouseopsrequest-editor/ui/create-ui.yaml b/charts/opskubedbcom-clickhouseopsrequest-editor/ui/create-ui.yaml new file mode 100644 index 0000000000..8aa14fac1c --- /dev/null +++ b/charts/opskubedbcom-clickhouseopsrequest-editor/ui/create-ui.yaml @@ -0,0 +1,651 @@ +step: +- elements: + - if: + name: showAndInitName + type: function + label: op_req_name + schema: schema/properties/metadata/properties/name + type: input + validation: + type: required + - disable: isNamespaceDisabled + hasGroup: isRancherManaged + if: + name: showAndInitNamespace + type: function + init: + type: func + value: initNamespace + label: Namespace + loader: getNamespaces + schema: schema/properties/metadata/properties/namespace + type: select + validation: + type: required + watcher: + func: onNamespaceChange + paths: + - schema/properties/metadata/properties/namespace + - disable: isDatabaseRefDisabled + if: + name: showAndInitDatabaseRef + type: function + init: + type: func + value: initDatabaseRef + label: Database Ref + loader: + name: getDbs + watchPaths: + - schema/properties/metadata/properties/namespace + refresh: true + schema: schema/properties/spec/properties/databaseRef/properties/name + type: select + validation: + type: required + watcher: + func: onDbChange + paths: + - schema/properties/spec/properties/databaseRef/properties/name + - if: + name: showConfigureOpsrequestLabel + type: function + label: config_ops_request + type: label-element + - disable: isDbDetailsLoading + if: + name: showAndInitOpsRequestType + type: function + init: + type: func + value: getRequestTypeFromRoute + isHorizontal: true + label: Type of Ops Request + options: + - description: Update your database to any version + text: Update Version + value: UpdateVersion + - description: Scale up or down pod count + text: Horizontal Scaling + value: HorizontalScaling + - description: Manage your CPU resources + text: Vertical Scaling + value: VerticalScaling + - description: Manage your database size + text: Volume Expansion + value: VolumeExpansion + - description: Restart your database + text: Restart + value: Restart + - description: Reconfigure your database + text: Reconfigure + value: Reconfigure + - description: Reconfigure your database tls configuration + text: Reconfigure TLS + value: ReconfigureTLS + schema: schema/properties/spec/properties/type + type: radio + watcher: + func: onRequestTypeChange + paths: + - schema/properties/spec/properties/type + - elements: + - init: + type: func + value: setValueFromDbDetails|/spec/version + label: Target Version + loader: getDbVersions + schema: schema/properties/spec/properties/updateVersion/properties/targetVersion + subtitle: Select the desired ClickHouse version to which you want to update your + database. + type: select-compare + - if: + name: isVersionEmpty + type: function + label: "" + loader: + name: getVersionInfo + watchPaths: + - temp/properties/filteredVersion + type: info + fixedBlock: true + if: + name: ifRequestTypeEqualsTo|UpdateVersion + type: function + label: Version + showLabels: true + type: block-layout + - elements: + - elements: + - header: Replica + init: + type: func + value: setValueFromDbDetails|/spec/replicas + label: Replicas + schema: schema/properties/spec/properties/horizontalScaling/properties/member + subtitle: Define the total number of replicas for the database. Increasing + replicas improves fault tolerance and load distribution , while reducing + replicas conserves resources + type: input-compare + - hasIcon: true + label: Each replica represents an independent copy of your database. For example, + setting this to 3 creates three copies of the database for better availability. + type: info + if: + name: ifDbTypeEqualsTo|cluster|horizontalScaling + type: function + type: horizontal-layout + fixedBlock: true + if: + name: ifRequestTypeEqualsTo|HorizontalScaling + type: function + label: Horizontal Scaling Form + showLabels: true + type: block-layout + - elements: + - elements: + - init: + type: func + value: setMachine + label: Resources + loader: getMachines + schema: temp/properties/machine + subtitle: Compare your current machine configuration with the proposed memory + adjustments and make informed decisions for your database setup + type: machine-compare + validation: + name: isMachineValid + type: custom + watcher: + func: onMachineChange|node|/spec/podTemplate/spec/resources + paths: + - temp/properties/machine + - elements: + - elements: + - elements: + - label: Node Selection Policy + subtitle: Control where your workloads runs by configuring node selection + criteria. Use label selectors to match specific nodes or taints to + avoid unsuitable nodes + type: label-element + - label: Node Selection Policy + options: + - text: LabelSelector + value: LabelSelector + - text: Taint + value: Taint + schema: schema/properties/spec/properties/verticalScaling/properties/node/properties/nodeSelectionPolicy + type: select + hideBorder: true + showLabels: false + type: block-layout + - customClass: mt-20 + label: Label Selector: Specify key-value pairs to target nodes + that match your workload's requirements.
Taints: Define + tolerations for node taints to ensure your workload runs only on compatible + nodes + type: info + type: horizontal-layout + - label: Topology + subtitle: Define node topology preferences, such as zone or region. For + example, 'zone=us-central1-a' ensures workloads are deployed in that zone. + type: label-element + - elements: + - label: Key + schema: temp/topologyKey + type: input + validation: + name: isVerticalScaleTopologyRequired + type: custom + - label: Value + schema: temp/topologyValue + type: input + validation: + name: isVerticalScaleTopologyRequired + type: custom + type: horizontal-layout + label: Node Selection + showLabels: true + type: block-layout + - elements: + - elements: + - init: + type: func + value: setExporter|cpu + label: CPU + schema: schema/properties/spec/properties/verticalScaling/properties/exporter/properties/resources/properties/requests/properties/cpu + type: input + - init: + type: func + value: setExporter|memory + label: Memory + schema: schema/properties/spec/properties/verticalScaling/properties/exporter/properties/resources/properties/limits/properties/memory + type: input + watcher: + func: onExporterResourceChange|memory + paths: + - schema/properties/spec/properties/verticalScaling/properties/exporter/properties/resources/properties/limits/properties/memory + showLabels: true + type: horizontal-layout + hideBlock: true + label: Exporter + showLabels: true + type: block-layout + label: ClickHouse vertical scaling + showLabels: false + type: block-layout + if: + name: ifRequestTypeEqualsTo|VerticalScaling + type: function + type: block-layout + - elements: + - elements: + - elements: + - label: Mode + subtitle: Not sure which mode to pick? Use Online Mode for smooth operations + with minimal disruption. Choose Offline Mode if you can afford a brief + downtime for added reliability during the volume expansion. + type: label-element + - label: Mode + options: + - text: Offline + value: Offline + - text: Online + value: Online + schema: schema/properties/spec/properties/volumeExpansion/properties/mode + type: select + validation: + type: required + type: block-layout + type: horizontal-layout + - elements: + - elements: + - header: ClickHouse + init: + type: func + value: setValueFromDbDetails|/spec/storage/resources/requests/storage + label: Storage Size + schema: schema/properties/spec/properties/volumeExpansion/properties/node + subtitle: How much extra storage does your database need? Specify the size(e.g. + 2Gi for 2 gigabytes) so we can allocate it correctly + type: input-compare + validation: + name: checkVolume|/spec/storage/resources/requests/storage|/spec/volumeExpansion/node + type: custom + type: horizontal-layout + fixedBlock: true + label: ClickHouse volume expansion + showLabels: true + type: block-layout + if: + name: ifRequestTypeEqualsTo|VolumeExpansion + type: function + label: Volume Expansion Form + type: block-layout + - elements: + - elements: + - label: "" + subtitle: Select a new configuration secret, apply a custom configuration, + or remove an existing setup to update your database settings + type: label-element + - elements: + - elements: + - customClass: mb-15 + label: Config Secret + subtitle: Select an existing secret or create a new one to apply to your + database configuration. + type: label-element + - customClass: mb-2 + init: + type: func + value: setValueFromDbDetails|/spec/configSecret/name + label: Config Secret + loader: + name: getConfigSecrets + watchPaths: + - schema/properties/metadata/properties/namespace + - temp/properties/createSecret/properties/status + refresh: fetchConfigSecrets + schema: schema/properties/spec/properties/configuration/properties/configSecret/properties/name + type: select + watcher: + func: onCreateSecretChange + paths: + - temp/properties/createSecret/properties/status + - customClass: mt-15 + elements: + - label: "" + subtitle: Enter a unique name to identify this secret. + type: label-element + - label: Secret Name + schema: temp/properties/createSecret/properties/name + type: input + validation: + type: required + - buttonClass: is-light is-outlined + elements: + - label: Key + loader: + name: onSelectedSecretChange + watchPaths: + - temp/properties/createSecret/properties/data + schema: key + type: select + validation: + type: required + - height: 120px + label: Value + schema: value + type: textarea + keepInitial: true + label: String Data + schema: temp/properties/createSecret/properties/data + type: array-object-form + validation: + type: required + hasButton: + action: createNewConfigSecret + hasCancel: cancelCreateSecret + text: Save + if: + name: isCreateSecret + type: function + label: Create a New Config Secret + showLabels: true + type: block-layout + - editorHeight: 500px + hideFormatButton: true + if: + name: isNotCreateSecret + type: function + loader: + name: onNewConfigSecretChange + watchPaths: + - schema/properties/spec/properties/configuration/properties/configSecret/properties/name + readonly: true + schema: temp/properties/newConfigSecret + type: multi-file-editor + validateContent: false + if: + name: ifReconfigurationTypeEqualsTo|selectNewConfigSecret + type: function + label: Config Secret + type: block-layout + - elements: + - customClass: mb-15 + label: New Apply Config + subtitle: Define custom configurations for your database using key-value + pairs. These parameters will overwrite existing settings.
Enter the + parameter you want to configure (e.g., max_connections). + type: label-element + - customClass: mb-2 + label: Configuration + loader: getConfigSecretsforAppyConfig + refresh: fetchConfigSecrets + schema: temp/properties/selectedConfiguration + type: select + - editorHeight: 500px + hideFormatButton: true + loader: + name: setApplyConfig + watchPaths: + - temp/properties/selectedConfiguration + schema: temp/properties/applyConfig + type: multi-file-editor + validateContent: false + watcher: + func: onApplyconfigChange + paths: + - temp/properties/applyConfig + if: + name: ifReconfigurationTypeEqualsTo|applyConfig + type: function + label: ApplyConfig + type: block-layout + - elements: + - customClass: mb-15 + label: Remove + subtitle: Selected a configuration secret from the available list to update + your database settings + type: label-element + - customClass: mb-2 + label: Configuration + loader: + name: getConfigSecretsforAppyConfig + watchPaths: + - schema/properties/metadata/properties/namespace + refresh: fetchConfigSecrets + schema: temp/properties/selectedConfigurationRemove + type: select + - editorHeight: 500px + hideFormatButton: true + init: + type: func + value: onRemoveConfigChange + readonly: true + schema: temp/properties/removeConfig + type: multi-file-editor + validateContent: false + watcher: + func: onRemoveConfigChange + paths: + - temp/properties/selectedConfigurationRemove + if: + name: ifReconfigurationTypeEqualsTo|remove + type: function + label: Remove + type: block-layout + label: New Config Secret + options: + - text: NEW CONFIG SECRET + value: selectNewConfigSecret + - text: APPLY CONFIG + value: applyConfig + - text: REMOVE + value: remove + schema: temp/properties/reconfigurationType + type: tab-layout + label: Configuration + type: block-layout + if: + name: ifRequestTypeEqualsTo|Reconfigure + type: function + label: Reconfigure Form + loader: + name: fetchConfigSecrets + watchPaths: + - schema/properties/metadata/properties/namespace + type: block-layout + - elements: + - if: + name: hasTlsField + type: function + init: + type: func + value: initTlsOperation + label: TLS Operation + options: + - text: Update + value: update + - text: Rotate + value: rotate + - text: Remove + value: remove + schema: temp/properties/tlsOperation + type: radio + watcher: + func: onTlsOperationChange + paths: + - temp/properties/tlsOperation + - fullwidth: true + if: + name: isTlsEnabled + type: function + label: Remove TLS + schema: schema/properties/spec/properties/tls/properties/remove + type: switch + - fullwidth: true + if: + name: isTlsEnabled + type: function + label: Rotate Certificates + schema: schema/properties/spec/properties/tls/properties/rotateCertificates + type: switch + - elements: + - fullwidth: true + init: + type: func + value: setValueFromDbDetails|/spec/tls/requireSSL + label: Require SSL + schema: schema/properties/spec/properties/tls/properties/requireSSL + type: switch + if: + name: showIssuerRefAndCertificates + type: function + label: Require SSL + type: block-layout + - elements: + - disable: true + init: + type: func + value: initIssuerRefApiGroup + label: API Group + schema: schema/properties/spec/properties/tls/properties/issuerRef/properties/apiGroup + type: input + watcher: + func: initIssuerRefApiGroup + paths: + - schema/properties/spec/properties/tls/properties/issuerRef/properties/kind + - init: + type: func + value: setValueFromDbDetails|/spec/tls/issuerRef/kind + label: Kind + options: + - text: Issuer + value: Issuer + - text: ClusterIssuer + value: ClusterIssuer + schema: schema/properties/spec/properties/tls/properties/issuerRef/properties/kind + type: select + validation: + name: isIssuerRefRequired + type: custom + - init: + type: func + value: setValueFromDbDetails|/spec/tls/issuerRef/name + label: Name + loader: + name: getIssuerRefsName + watchPaths: + - schema/properties/spec/properties/tls/properties/issuerRef/properties/kind + - schema/properties/metadata/properties/namespace + schema: schema/properties/spec/properties/tls/properties/issuerRef/properties/name + type: select + validation: + name: isIssuerRefRequired + type: custom + if: + name: showIssuerRefAndCertificates + type: function + label: Issuer Reference + showLabels: true + type: block-layout + - elements: + - buttonClass: is-light is-outlined + elements: + - disable: disableAlias + label: Alias + loader: fetchAliasOptions + schema: alias + type: select + validation: + type: required + - label: Secret Name + schema: secretName + type: input + - label: Duration + schema: duration + type: input + - label: Renew Before + schema: renewBefore + type: input + - buttonClass: is-light is-outlined + element: + label: Organization + type: input + label: Organizations + schema: subject/properties/organizations + type: array-item-form + - buttonClass: is-light is-outlined + element: + label: Country + type: input + label: Countries + schema: subject/properties/countries + type: array-item-form + - buttonClass: is-light is-outlined + element: + label: Organizational Unit + type: input + label: Organizational Units + schema: subject/properties/organizationalUnits + type: array-item-form + - buttonClass: is-light is-outlined + element: + label: Province + type: input + label: Provinces + schema: subject/properties/provinces + type: array-item-form + - buttonClass: is-light is-outlined + element: + label: DNS Name + type: input + label: DNS Names + schema: dnsNames + type: array-item-form + - buttonClass: is-light is-outlined + element: + label: IP Address + type: input + label: IP Addresses + schema: ipAddresses + type: array-item-form + label: Certificates + schema: schema/properties/spec/properties/tls/properties/certificates + type: array-object-form + if: + name: showIssuerRefAndCertificates + type: function + label: Certificates + loader: setValueFromDbDetails|/spec/tls/certificates|/spec/tls/certificates + showLabels: false + type: block-layout + if: + name: ifRequestTypeEqualsTo|ReconfigureTLS + type: function + label: TLS + type: block-layout + - elements: + - label: Timeout + schema: schema/properties/spec/properties/timeout + subtitle: Specify the maximum time allowed for the operation to complete before + it times out + type: time-picker + - init: + type: func + value: setApplyToIfReady + label: Apply + options: + - text: IfReady (OpsRequest will be applied if database is ready) + value: IfReady + - text: Always (OpsRequest will always be applied) + value: Always + schema: schema/properties/spec/properties/apply + type: radio + label: OpsRequest Options + showLabels: true + type: block-layout + loader: getDbDetails + type: single-step-form +type: multi-step-form diff --git a/charts/opskubedbcom-clickhouseopsrequest-editor/ui/functions.js b/charts/opskubedbcom-clickhouseopsrequest-editor/ui/functions.js new file mode 100644 index 0000000000..37bcd93579 --- /dev/null +++ b/charts/opskubedbcom-clickhouseopsrequest-editor/ui/functions.js @@ -0,0 +1,1940 @@ +const { axios, useOperator, store, useToast } = window.vueHelpers || {} +const machines = { + 'db.t.micro': { + resources: { + requests: { + cpu: '250m', + memory: '512Mi', + }, + limits: { + cpu: '500m', + memory: '1Gi', + }, + }, + }, + 'db.t.small': { + resources: { + requests: { + cpu: '1', + memory: '1Gi', + }, + limits: { + cpu: '2', + memory: '2Gi', + }, + }, + }, + 'db.t.medium': { + resources: { + requests: { + cpu: '1', + memory: '2Gi', + }, + limits: { + cpu: '2', + memory: '4Gi', + }, + }, + }, + 'db.t.large': { + resources: { + requests: { + cpu: '1', + memory: '4Gi', + }, + limits: { + cpu: '2', + memory: '8Gi', + }, + }, + }, + 'db.t.xlarge': { + resources: { + requests: { + cpu: '2', + memory: '8Gi', + }, + limits: { + cpu: '4', + memory: '16Gi', + }, + }, + }, + 'db.t.2xlarge': { + resources: { + requests: { + cpu: '4', + memory: '16Gi', + }, + limits: { + cpu: '8', + memory: '32Gi', + }, + }, + }, + 'db.m.small': { + resources: { + requests: { + cpu: '500m', + memory: '912680550', + }, + limits: { + cpu: '1', + memory: '1825361100', + }, + }, + }, + 'db.m.large': { + resources: { + requests: { + cpu: '1', + memory: '4Gi', + }, + limits: { + cpu: '2', + memory: '8Gi', + }, + }, + }, + 'db.m.xlarge': { + resources: { + requests: { + cpu: '2', + memory: '8Gi', + }, + limits: { + cpu: '4', + memory: '16Gi', + }, + }, + }, + 'db.m.2xlarge': { + resources: { + requests: { + cpu: '4', + memory: '16Gi', + }, + limits: { + cpu: '8', + memory: '32Gi', + }, + }, + }, + 'db.m.4xlarge': { + resources: { + requests: { + cpu: '8', + memory: '32Gi', + }, + limits: { + cpu: '16', + memory: '64Gi', + }, + }, + }, + 'db.m.8xlarge': { + resources: { + requests: { + cpu: '16', + memory: '64Gi', + }, + limits: { + cpu: '32', + memory: '128Gi', + }, + }, + }, + 'db.m.12xlarge': { + resources: { + requests: { + cpu: '24', + memory: '96Gi', + }, + limits: { + cpu: '48', + memory: '192Gi', + }, + }, + }, + 'db.m.16xlarge': { + resources: { + requests: { + cpu: '32', + memory: '128Gi', + }, + limits: { + cpu: '64', + memory: '256Gi', + }, + }, + }, + 'db.m.24xlarge': { + resources: { + requests: { + cpu: '48', + memory: '192Gi', + }, + limits: { + cpu: '96', + memory: '384Gi', + }, + }, + }, + 'db.r.large': { + resources: { + requests: { + cpu: '1', + memory: '8Gi', + }, + limits: { + cpu: '2', + memory: '16Gi', + }, + }, + }, + 'db.r.xlarge': { + resources: { + requests: { + cpu: '2', + memory: '16Gi', + }, + limits: { + cpu: '4', + memory: '32Gi', + }, + }, + }, + 'db.r.2xlarge': { + resources: { + requests: { + cpu: '4', + memory: '32Gi', + }, + limits: { + cpu: '8', + memory: '64Gi', + }, + }, + }, + 'db.r.4xlarge': { + resources: { + requests: { + cpu: '8', + memory: '96Gi', + }, + limits: { + cpu: '16', + memory: '192Gi', + }, + }, + }, + 'db.r.8xlarge': { + resources: { + requests: { + cpu: '16', + memory: '128Gi', + }, + limits: { + cpu: '32', + memory: '256Gi', + }, + }, + }, + 'db.r.12xlarge': { + resources: { + requests: { + cpu: '24', + memory: '192Gi', + }, + limits: { + cpu: '48', + memory: '384Gi', + }, + }, + }, + 'db.r.16xlarge': { + resources: { + requests: { + cpu: '32', + memory: '256Gi', + }, + limits: { + cpu: '64', + memory: '512Gi', + }, + }, + }, + 'db.r.24xlarge': { + resources: { + requests: { + cpu: '24', + memory: '384Gi', + }, + limits: { + cpu: '96', + memory: '768Gi', + }, + }, + }, +} + +const machineList = [ + 'custom', + 'db.t.micro', + 'db.t.small', + 'db.t.medium', + 'db.t.large', + 'db.t.xlarge', + 'db.t.2xlarge', + 'db.m.small', + 'db.m.large', + 'db.m.xlarge', + 'db.m.2xlarge', + 'db.m.4xlarge', + 'db.m.8xlarge', + 'db.m.12xlarge', + 'db.m.16xlarge', + 'db.m.24xlarge', + 'db.r.large', + 'db.r.xlarge', + 'db.r.2xlarge', + 'db.r.4xlarge', + 'db.r.8xlarge', + 'db.r.12xlarge', + 'db.r.16xlarge', + 'db.r.24xlarge', +] + +let machinesFromPreset = [] +const configSecretKeys = ['kubedb-user.cnf'] + +export const useFunc = (model) => { + const route = store.state?.route + const toast = useToast() + + const { getValue, storeGet, discriminator, setDiscriminatorValue, commit } = useOperator( + model, + store.state, + ) + + showAndInitOpsRequestType() + async function fetchJsons({ axios, itemCtx }) { + let ui = {} + let language = {} + let functions = {} + const { name, sourceRef, version, packageviewUrlPrefix } = itemCtx.chart + + try { + ui = await axios.get( + `${packageviewUrlPrefix}/create-ui.yaml?name=${name}&sourceApiGroup=${sourceRef.apiGroup}&sourceKind=${sourceRef.kind}&sourceNamespace=${sourceRef.namespace}&sourceName=${sourceRef.name}&version=${version}&format=json`, + ) + language = await axios.get( + `${packageviewUrlPrefix}/language.yaml?name=${name}&sourceApiGroup=${sourceRef.apiGroup}&sourceKind=${sourceRef.kind}&sourceNamespace=${sourceRef.namespace}&sourceName=${sourceRef.name}&version=${version}&format=json`, + ) + const functionString = await axios.get( + `${packageviewUrlPrefix}/functions.js?name=${name}&sourceApiGroup=${sourceRef.apiGroup}&sourceKind=${sourceRef.kind}&sourceNamespace=${sourceRef.namespace}&sourceName=${sourceRef.name}&version=${version}`, + ) + // declare evaluate the functionString to get the functions Object + const evalFunc = new Function(functionString.data || '') + functions = evalFunc() + } catch (e) { + console.log(e) + } + + return { + ui: ui.data || {}, + language: language.data || {}, + functions, + } + } + + function returnFalse() { + return false + } + + function isTlsEnabled() { + const dbDetails = getValue(discriminator, '/dbDetails') + return ( + (dbDetails?.spec?.sslMode && + dbDetails?.spec?.sslMode !== 'disabled' && + dbDetails?.spec?.sslMode !== 'disable') || + dbDetails?.spec?.tls + ) + } + + function isRancherManaged() { + const managers = storeGet('/cluster/clusterDefinition/result/clusterManagers') + const found = managers.find((item) => item === 'Rancher') + return !!found + } + + async function getNamespaces() { + if (storeGet('/route/params/actions')) return [] + const owner = storeGet('/route/params/user') + const cluster = storeGet('/route/params/cluster') + + const resp = await axios.get(`/clusters/${owner}/${cluster}/proxy/core/v1/namespaces`, { + params: { filter: { items: { metadata: { name: null } } } }, + }) + + const resources = (resp && resp.data && resp.data.items) || [] + + return resources.map((item) => { + const name = (item.metadata && item.metadata.name) || '' + return { + text: name, + value: name, + } + }) + } + + async function getDbs() { + if (storeGet('/route/params/actions')) return [] + const owner = storeGet('/route/params/user') + const cluster = storeGet('/route/params/cluster') + + const namespace = getValue(model, '/metadata/namespace') + // watchDependency('model#/metadata/namespace') + + const resp = await axios.get( + `/clusters/${owner}/${cluster}/proxy/kubedb.com/v1alpha2/namespaces/${namespace}/clickhouses`, + { + params: { filter: { items: { metadata: { name: null } } } }, + }, + ) + + const resources = (resp && resp.data && resp.data.items) || [] + + return resources.map((item) => { + const name = (item.metadata && item.metadata.name) || '' + return { + text: name, + value: name, + } + }) + } + + async function getDbDetails() { + machinesFromPreset = storeGet('/kubedbuiPresets')?.admin?.machineProfiles?.machines || [] + + const owner = storeGet('/route/params/user') + const cluster = storeGet('/route/params/cluster') + const namespace = storeGet('/route/query/namespace') || getValue(model, '/metadata/namespace') + const name = storeGet('/route/params/name') || getValue(model, '/spec/databaseRef/name') + + if (namespace && name) { + const url = `/clusters/${owner}/${cluster}/proxy/kubedb.com/v1alpha2/namespaces/${namespace}/clickhouses/${name}` + const resp = await axios.get(url) + + setDiscriminatorValue('/dbDetails', resp.data || {}) + + return resp.data || {} + } else return {} + } + + let presetVersions = [] + setDiscriminatorValue('/filteredVersion', []) + async function getDbVersions() { + const owner = storeGet('/route/params/user') + const cluster = storeGet('/route/params/cluster') + + const url = `/clusters/${owner}/${cluster}/proxy/charts.x-helm.dev/v1alpha1/clusterchartpresets/kubedb-ui-presets` + + let presets = storeGet('/kubedbuiPresets') || {} + if (!storeGet('/route/params/actions')) { + try { + const presetResp = await axios.get(url) + presets = presetResp.data?.spec?.values?.spec + } catch (e) { + console.log(e) + presets.status = String(e.status) + } + } + + try { + presetVersions = presets.admin?.databases?.ClickHouse?.versions?.available || [] + const queryParams = { + filter: { + items: { + metadata: { name: null }, + spec: { version: null, deprecated: null, updateConstraints: null }, + }, + }, + } + + const resp = await axios.get( + `/clusters/${owner}/${cluster}/proxy/catalog.kubedb.com/v1alpha1/clickhouseversions`, + { + params: queryParams, + }, + ) + + const resources = (resp && resp.data && resp.data.items) || [] + + const sortedVersions = resources.sort((a, b) => + versionCompare(a.spec.version, b.spec.version), + ) + + let ver = getValue(discriminator, '/dbDetails/spec/version') || '0' + const found = sortedVersions.find((item) => item.metadata.name === ver) + + if (found) ver = found.spec?.version + + const isGroupRepl = !!getValue(discriminator, '/dbDetails/spec/topology') + const allowed = isGroupRepl + ? found?.spec?.updateConstraints?.allowlist.groupReplication + : found?.spec?.updateConstraints?.allowlist.standalone + + const limit = allowed.length ? allowed[0] : '0.0' + + // keep only non deprecated & kubedb-ui-presets & within constraints of current version + // if presets.status is 404, it means no presets available, no need to filter with presets + const filteredClickHouseVersions = sortedVersions.filter((item) => { + // default limit 0.0 means no restrictions, show all higher versions + if (limit === '0.0') + return ( + !item.spec?.deprecated && + (presets.status === '404' || + presetVersions.length === 0 || + presetVersions.includes(item.metadata?.name)) && + versionCompare(item.spec?.version, ver) >= 0 + ) + // if limit doesn't have any operator, it's a single version + else if (!limit.match(/^(>=|<=|>|<)/)) + return ( + !item.spec?.deprecated && + (presets.status === '404' || + presetVersions.length === 0 || + presetVersions.includes(item.metadata?.name)) && + item.spec?.version === limit + ) + // if limit has operator, check version with constraints + else + return ( + !item.spec?.deprecated && + (presets.status === '404' || + presetVersions.length === 0 || + presetVersions.includes(item.metadata?.name)) && + isVersionWithinConstraints(item.spec?.version, limit) + ) + }) + setDiscriminatorValue('/filteredVersion', filteredClickHouseVersions) + + return filteredClickHouseVersions.map((item) => { + const name = (item.metadata && item.metadata.name) || '' + const specVersion = (item.spec && item.spec.version) || '' + return { + text: `${name} (${specVersion})`, + value: name, + } + }) + } catch (e) { + console.log(e) + return [] + } + } + + function getVersionInfo() { + const filteredVersion = getValue(discriminator, '/filteredVersion') + if (filteredVersion.length) return '' + + let txt = 'No versions from this list can be selected as the target version: [ ' + + presetVersions.forEach((v, idx) => { + txt = `${txt}"${v}"` + if (idx !== presetVersions.length - 1) txt = txt + ', ' + else txt = txt + ' ]' + }) + + return txt + } + + function getVersion() { + return filteredVersion.map((item) => { + const name = (item.metadata && item.metadata.name) || '' + const specVersion = (item.spec && item.spec.version) || '' + return { + text: `${name} (${specVersion})`, + value: name, + } + }) + } + + function isVersionEmpty() { + const val = getValue(discriminator, '/filteredVersion') + return val.length === 0 + } + + function versionCompare(v1, v2) { + const arr1 = v1.split('.').map(Number) + const arr2 = v2.split('.').map(Number) + + for (let i = 0; i < Math.max(arr1.length, arr2.length); i++) { + const num1 = arr1[i] || 0 + const num2 = arr2[i] || 0 + + if (num1 > num2) return 1 // v1 is higher + if (num1 < num2) return -1 // v2 is higher + } + return 0 // versions are equal + } + + function isVersionWithinConstraints(version, constraints) { + let constraintsArr = [] + if (constraints.includes(',')) constraintsArr = constraints?.split(',')?.map((c) => c.trim()) + else constraintsArr = [constraints] + + for (let constraint of constraintsArr) { + let match = constraint.match(/^(>=|<=|>|<)/) + let operator = match ? match[0] : '' + let constraintVersion = constraint.replace(/^(>=|<=|>|<)/, '').trim() + + let comparison = versionCompare(version, constraintVersion) + if ( + (operator === '>=' && comparison < 0) || + (operator === '<=' && comparison > 0) || + (operator === '>' && comparison <= 0) || + (operator === '<' && comparison >= 0) + ) + return false + } + return true + } + + function ifRequestTypeEqualsTo(type) { + const selectedType = getValue(model, '/spec/type') + // watchDependency('model#/spec/type') + + return selectedType === type + } + + function onRequestTypeChange() { + const selectedType = getValue(model, '/spec/type') + const reqTypeMapping = { + Upgrade: 'updateVersion', + UpdateVersion: 'updateVersion', + HorizontalScaling: 'horizontalScaling', + VerticalScaling: 'verticalScaling', + VolumeExpansion: 'volumeExpansion', + Restart: 'restart', + Reconfigure: 'configuration', + ReconfigureTLS: 'tls', + } + + Object.keys(reqTypeMapping).forEach((key) => { + if (key !== selectedType) commit('wizard/model$delete', `/spec/${reqTypeMapping[key]}`) + }) + } + + function disableOpsRequest() { + if (itemCtx.value === 'HorizontalScaling') { + const dbType = getDbType() + + if (dbType === 'standalone') return true + else return false + } else return false + } + + function getDbTls() { + // watchDependency('discriminator#/dbDetails') + const dbDetails = getValue(discriminator, '/dbDetails') + + const { spec } = dbDetails || {} + return spec?.tls || undefined + } + + function getDbType() { + // watchDependency('discriminator#/dbDetails') + const dbDetails = getValue(discriminator, '/dbDetails') + + const { spec } = dbDetails || {} + const { topology } = spec || {} + const { mode } = topology || {} + + const verd = mode ? 'cluster' : 'standalone' + + return verd + } + + function initNamespace() { + const { namespace } = route.query || {} + return namespace || null + } + + function initDatabaseRef() { + // watchDependency('model#/metadata/namespace') + const { name } = route.params || {} + return name + } + + function asDatabaseOperation() { + return !!route.params.actions + } + + function generateOpsRequestNameForClusterUI(getValue, model, route) { + const dbName = getValue(model, '/spec/databaseRef/name') + + const selectedType = getValue(model, '/spec/type') + const lowerType = selectedType ? String(selectedType).toLowerCase() : '' + + const resources = route.params.resource || '' + const resource = resources.slice(0, -1) + + const opsName = dbName ? dbName : resource + return `${opsName}-${Math.floor(Date.now() / 1000)}${lowerType ? '-' + lowerType : ''}` + } + + function showAndInitName() { + // watchDependency('model#/spec/type') + // watchDependency('model#/spec/databaseRef/name') + const ver = asDatabaseOperation() + + const selectedType = getValue(model, '/spec/type') + const lowerType = selectedType ? String(selectedType).toLowerCase() : '' + + if (ver) { + // For kubedb-ui + commit('wizard/model$update', { + path: '/metadata/name', + value: `${route.params.name}-${Math.floor(Date.now() / 1000)}-${lowerType}`, + force: true, + }) + } else { + // For cluster-ui + commit('wizard/model$update', { + path: '/metadata/name', + value: generateOpsRequestNameForClusterUI(getValue, model, route), + force: true, + }) + } + return !ver + } + + function showAndInitNamespace() { + const ver = asDatabaseOperation() + if (ver) { + commit('wizard/model$update', { + path: '/metadata/namespace', + value: `${route.query.namespace}`, + force: true, + }) + } + + return !ver + } + + function showAndInitDatabaseRef() { + const ver = asDatabaseOperation() + if (ver) { + commit('wizard/model$update', { + path: '/spec/databaseRef/name', + value: `${route.params.name}`, + force: true, + }) + } + + return !ver + } + + function showConfigureOpsrequestLabel() { + return !asDatabaseOperation() + } + + function showAndInitOpsRequestType() { + const ver = asDatabaseOperation() + const opMap = { + upgrade: 'UpdateVersion', + updateVersion: 'UpdateVersion', + horizontalscaling: 'HorizontalScaling', + verticalscaling: 'VerticalScaling', + volumeexpansion: 'VolumeExpansion', + restart: 'Restart', + reconfiguretls: 'ReconfigureTLS', + reconfigure: 'Reconfigure', + } + if (ver) { + const operation = storeGet('/resource/activeActionItem/result/operationId') || '' + + const match = /^(.*)-opsrequest-(.*)$/.exec(operation) + if (match) { + const opstype = match[2] + commit('wizard/model$update', { + path: '/spec/type', + value: opMap[opstype], + force: true, + }) + } + } + + return !ver + } + + // vertical scaling + function ifDbTypeEqualsTo(value, opsReqType) { + const verd = getDbType() + + return value === verd + } + + // machine profile stuffs + // let machinesFromPreset = [] + + function getMachines() { + const presets = storeGet('/kubedbuiPresets') || {} + const dbDetails = getValue(discriminator, '/dbDetails') + const containers = dbDetails?.spec?.podTemplate?.spec?.containers || [] + const limits = containers[0]?.resources?.limits || {} + + const avlMachines = presets.admin?.machineProfiles?.available || [] + let arr = [] + if (avlMachines.length) { + arr = avlMachines.map((machine) => { + if (machine === 'custom') + return { text: machine, value: { machine, cpu: limits.cpu, memory: limits.memory } } + else { + const machineData = machinesFromPreset.find((val) => val.id === machine) + if (machineData) { + const subtext = `CPU: ${machineData.limits.cpu}, Memory: ${machineData.limits.memory}` + const text = machineData.name ? machineData.name : machineData.id + return { + text, + subtext, + value: { + machine: text, + cpu: machineData.limits.cpu, + memory: machineData.limits.memory, + }, + } + } else + return { text: machine, value: { machine, cpu: limits.cpu, memory: limits.memory } } + } + }) + } else { + arr = machineList + .map((machine) => { + if (machine === 'custom') + return { text: machine, value: { machine, cpu: limits.cpu, memory: limits.memory } } + const subtext = `CPU: ${machines[machine].resources.limits.cpu}, Memory: ${machines[machine].resources.limits.memory}` + const text = machine + return { + text, + subtext, + value: { + machine: text, + cpu: machines[machine].resources.limits.cpu, + memory: machines[machine].resources.limits.memory, + }, + } + }) + .filter((val) => !!val) + } + return arr + } + + function setMachine() { + const dbDetails = getValue(discriminator, '/dbDetails') + const containers = dbDetails?.spec?.podTemplate?.spec?.containers || [] + const limits = containers[0]?.resources?.limits || {} + const annotations = dbDetails?.metadata?.annotations || {} + const instance = annotations['kubernetes.io/instance-type'] + + let parsedInstance = {} + try { + if (instance) parsedInstance = JSON.parse(instance) + } catch (e) { + console.log(e) + parsedInstance = instance || {} + } + + const machine = parsedInstance || 'custom' + + const machinePresets = machinesFromPreset.find((item) => item.id === machine) + if (machinePresets) { + return { + machine: machine, + cpu: machinePresets.limits.cpu, + memory: machinePresets.limits.memory, + } + } else return { machine: 'custom', cpu: limits.cpu, memory: limits.memory } + } + + function onMachineChange(type, valPath) { + let selectedMachine = {} + selectedMachine = getValue(discriminator, '/machine') + const machine = machinesFromPreset.find((item) => item.id === selectedMachine.machine) + + let obj = {} + if (selectedMachine.machine !== 'custom') { + if (machine) obj = { limits: { ...machine?.limits }, requests: { ...machine?.limits } } + else obj = machines[selectedMachine.machine]?.resources + } else { + const cpu = selectedMachine.cpu || '' + const memory = selectedMachine.memory || '' + obj = { + limits: { cpu: cpu, memory: memory }, + requests: { cpu: cpu, memory: memory }, + } + } + + const path = `/spec/verticalScaling/${type}/resources` + + if (obj && Object.keys(obj).length) + commit('wizard/model$update', { + path: path, + value: obj, + force: true, + }) + + // update metadata.annotations + const annotations = getValue(model, '/metadata/annotations') || {} + annotations['kubernetes.io/instance-type'] = selectedMachine.machine + if (machinesFromPreset.length) + commit('wizard/model$update', { + path: '/metadata/annotations', + value: annotations, + force: true, + }) + } + + function isMachineCustom() { + // watchDependency('discriminator#/machine') + const machine = getValue(discriminator, '/machine') + return machine === 'custom' + } + + // Fetch and store database Infos + // for secret configurations in reconfigure + let configSecrets = [] + let secretConfigData = [] + let existingSecrets = [] + + async function fetchConfigSecrets() { + const owner = storeGet('/route/params/user') + const cluster = storeGet('/route/params/cluster') + const namespace = getValue(model, '/metadata/namespace') + // watchDependency('model#/metadata/namespace') + + const name = getValue(model, '/spec/databaseRef/name') + const dbGroup = getValue(model, '/route/params/group') + const dbKind = getValue(store.state, '/resource/definition/result/kind') + const dbResource = getValue(model, '/route/params/resource') + const dbVersion = getValue(model, '/route/params/version') + + try { + const resp = await axios.post( + `/clusters/${owner}/${cluster}/proxy/ui.kubedb.com/v1alpha1/databaseinfos`, + { + apiVersion: 'ui.kubedb.com/v1alpha1', + kind: 'DatabaseInfo', + request: { + source: { + ref: { + name: name, + namespace: namespace, + }, + resource: { + group: dbGroup, + kind: dbKind, + name: dbResource, + version: dbVersion, + }, + }, + keys: ['kubedb-user.cnf'], + }, + }, + ) + configSecrets = resp?.data?.response?.availableSecrets || [] + secretConfigData = resp?.data?.response?.configurations || [] + } catch (e) { + console.log(e) + } + + // Fetching all existing secrets + try { + const resp = await axios.get(`/clusters/${owner}/${cluster}/proxy/core/v1/secrets`) + resp.data?.items.forEach((item) => { + if (item.metadata?.name) { + existingSecrets.push(item.metadata.name) + } + }) + } catch (e) { + console.log(e) + } + } + + async function getConfigSecrets(type) { + type = type ? type + '/' : '' + const secretStatus = getValue(discriminator, `${type}createSecret/status`) + if (secretStatus === 'success') { + await fetchConfigSecrets() + } + const mappedSecrets = configSecrets.map((item) => { + return { text: item, value: item } + }) + mappedSecrets.push({ text: '+ Create a new Secret', value: 'Create' }) + return mappedSecrets + } + + async function getConfigSecretsforAppyConfig() { + const secrets = secretConfigData.map((item) => { + return { text: item.componentName, value: item.componentName } + }) + return secrets + } + + function getSelectedConfigurationData(type) { + type = type ? type + '/' : '' + const path = `/${type}selectedConfiguration` + const selectedConfiguration = getValue(discriminator, path) + + if (!selectedConfiguration) { + return [] + } + + const configuration = secretConfigData.find( + (item) => item.componentName === selectedConfiguration, + ) + + if (!configuration) { + return [] + } + + const result = [] + // Decode base64 and format as array of objects with name and content + Object.keys(configuration.data).forEach((fileName) => { + try { + // Decode base64 string + const decodedContent = atob(configuration.data[fileName]) + result.push({ + name: fileName, + content: decodedContent, + }) + } catch (e) { + console.error(`Error decoding ${fileName}:`, e) + result.push({ + name: fileName, + content: configuration.data[fileName], // Fallback to original if decode fails + }) + } + }) + + // Set the value to the model + commit('wizard/model$update', { + path: `/temp/${type}applyConfig`, + value: result, + force: true, + }) + + return result + } + + function getSelectedConfigurationName(configType, type) { + type = type ? type + '/' : '' + let path = '' + if (configType === 'create') path = `/spec/configuration/${type}/configSecret/name` + else if (configType === 'apply') path = `/${type}selectedConfiguration` + else if (configType === 'remove') path = `/${type}selectedConfigurationRemove` + + const selectedConfiguration = + configType === 'create' ? getValue(model, path) : getValue(discriminator, path) + + if (selectedConfiguration) + return { subtitle: ` You have selected ${selectedConfiguration} secret` } + else return { subtitle: 'No secret selected' } + } + + function getSelectedConfigurationValueForRemove(type) { + type = type ? type + '/' : '' + const path = `/${type}selectedConfigurationRemove` + const selectedConfiguration = getValue(discriminator, path) + + if (!selectedConfiguration) { + return '' + } + + const configuration = secretConfigData.find( + (item) => item.componentName === selectedConfiguration, + ) + + if (!configuration) { + return '' + } + + let data = {} + // Decode base64 and parse YAML for each key in the secret data + Object.keys(configuration.data).forEach((item) => { + try { + // Decode base64 string + const decodedString = atob(configuration.data[item]) + // Parse YAML string to object + const parsedYaml = yaml.load(decodedString) + // Store the parsed object with the filename as key + data[item] = parsedYaml + } catch (e) { + console.error(`Error parsing ${item}:`, e) + data[item] = atob(configuration.data[item]) // Fallback to decoded string + } + }) + + // Convert data object back to YAML string + return yaml.dump(data) + } + + async function createNewConfigSecret(type) { + type = type ? type + '/' : '' + const { user, cluster } = route.params + const url = `/clusters/${user}/${cluster}/resources` + const namespace = storeGet('/route/query/namespace') || getValue(model, '/metadata/namespace') + const secretName = getValue(discriminator, `${type}createSecret/name`) + const secretData = getValue(discriminator, `${type}createSecret/data`) + const secretDataObj = Object.fromEntries(secretData.map((item) => [item.key, item.value])) + + // Check uniqueness of secret name + if (existingSecrets.includes(secretName)) { + toast.error('A secret with this name already exists. Please choose another name.', { + timeout: 8000, + }) + return false + } + + try { + const res = await axios.post(url, { + apiVersion: 'v1', + stringData: secretDataObj, + kind: 'Secret', + metadata: { + name: secretName, + namespace: namespace, + }, + type: 'Opaque', + }) + commit('wizard/temp$update', { + path: `${type}createSecret/status`, + value: 'success', + }) + commit('wizard/temp$update', { + path: `${type}createSecret/lastCreatedSecret`, + value: secretName, + }) + toast.success('Secret created successfully') + } catch (error) { + const errMsg = decodeError(error, 'Failed to create secret') + toast.error(errMsg, { timeout: 5000 }) + cancelCreateSecret() + } + return true + } + + function decodeError(msg, defaultMsg) { + if (typeof msg === 'string') { + return msg || defaultMsg + } + return ( + (msg.response && msg.response.data && msg.response.data.message) || + (msg.response && msg.response.data) || + (msg.status && msg.status.status) || + defaultMsg + ) + } + + function isCreateSecret(type) { + type = type ? type + '/' : '' + const selectedSecret = getValue(model, `spec/configuration/${type}configSecret/name`) + const res = selectedSecret === 'Create' + + if (res === true) { + commit('wizard/temp$update', { + path: `${type}createSecret/status`, + value: 'pending', + }) + } + return res + } + + function isNotCreateSecret(type) { + return !isCreateSecret(type) + } + + function onCreateSecretChange(type) { + type = type ? type + '/' : '' + const secretStatus = getValue(discriminator, `${type}createSecret/status`) + if (secretStatus === 'cancelled') return '' + else if (secretStatus === 'success') { + const name = getValue(discriminator, `${type}createSecret/lastCreatedSecret`) + + const configFound = configSecrets.find((item) => item === name) + return configFound ? { text: name, value: name } : '' + } + } + + function cancelCreateSecret(type) { + type = type ? type + '/' : '' + commit('wizard/temp$delete', `${type}createSecret/name`) + commit('wizard/temp$delete', `${type}createSecret/data`) + commit('wizard/temp$update', { + path: `${type}createSecret/status`, + value: 'cancelled', + }) + } + + async function onApplyconfigChange(type) { + type = type ? type + '/' : '' + const configValue = getValue(discriminator, `${type}applyConfig`) + + if (!configValue) { + commit('wizard/model$delete', `/spec/configuration/${type}applyConfig`) + return + } + const tempConfigObj = {} + configValue.forEach((item) => { + if (item.name && item.content) { + tempConfigObj[item.name] = item.content + } + }) + if (Object.keys(tempConfigObj).length === 0) { + commit('wizard/model$delete', `/spec/configuration/${type}applyConfig`) + return + } + commit('wizard/model$update', { + path: `/spec/configuration/${type}applyConfig`, + value: tempConfigObj, + }) + } + + function setApplyConfig(type) { + type = type ? type + '/' : '' + const configPath = `/${type}selectedConfiguration` + const selectedConfig = getValue(discriminator, configPath) + if (!selectedConfig) { + return [{ name: '', content: '' }] + } + const applyconfigData = secretConfigData.find((item) => { + if (item.componentName === selectedConfig) { + return item + } + }) + const { applyConfig } = applyconfigData + const configObj = [] + + if (applyConfig) { + Object.keys(applyConfig).forEach((fileName) => { + configObj.push({ + name: fileName, + content: applyConfig[fileName], + }) + }) + } + configSecretKeys.forEach((key) => { + if (!configObj.find((item) => item.name === key)) { + configObj.push({ name: key, content: '' }) + } + }) + return configObj + } + + function onRemoveConfigChange(type) { + type = type ? type + '/' : '' + const configPath = `/${type}selectedConfigurationRemove` + const selectedConfig = getValue(discriminator, configPath) + + if (!selectedConfig) { + commit('wizard/model$delete', `/spec/configuration/${type}removeCustomConfig`) + return [{ name: '', content: '' }] + } + commit('wizard/model$update', { + path: `/spec/configuration/${type}removeCustomConfig`, + value: true, + }) + + const configuration = secretConfigData.find((item) => item.componentName === selectedConfig) + + if (!configuration.data) { + return [{ name: '', content: '' }] + } + + const configObj = [] + // Decode base64 and format as array of objects with name and content + Object.keys(configuration.data).forEach((fileName) => { + try { + // Decode base64 string + const decodedString = atob(configuration.data[fileName]) + configObj.push({ + name: fileName, + content: decodedString, + }) + } catch (e) { + console.error(`Error decoding ${fileName}:`, e) + configObj.push({ + name: fileName, + content: configuration.data[fileName], // Fallback to original if decode fails + }) + } + }) + return configObj + } + + async function onNewConfigSecretChange(type) { + type = type ? type + '/' : '' + const path = `/spec/configuration/${type}configSecret/name` + const selectedSecret = getValue(model, path) + + if (!selectedSecret) { + commit('wizard/model$delete', `/spec/configuration/${type}configSecret`) + return [{ name: '', content: '' }] + } + if (selectedSecret === 'Create') return [{ name: '', content: '' }] + + const owner = storeGet('/route/params/user') + const cluster = storeGet('/route/params/cluster') + const namespace = storeGet('/route/query/namespace') || getValue(model, '/metadata/namespace') + + try { + // Fetch the secret data from API + const secretResp = await axios.get( + `/clusters/${owner}/${cluster}/proxy/core/v1/namespaces/${namespace}/secrets/${selectedSecret}`, + ) + + const secretData = secretResp.data?.data || {} + const configObj = [] + + // Decode base64 and format as array of objects with name and content + Object.keys(secretData).forEach((fileName) => { + try { + // Decode base64 string + const decodedString = atob(secretData[fileName]) + configObj.push({ + name: fileName, + content: decodedString, + }) + } catch (e) { + console.error(`Error decoding ${fileName}:`, e) + configObj.push({ + name: fileName, + content: secretData[fileName], // Fallback to original if decode fails + }) + } + }) + + return configObj + } catch (e) { + console.error('Error fetching secret:', e) + return [{ name: '', content: '' }] + } + } + + function onSelectedSecretChange(index) { + const secretData = getValue(discriminator, 'createSecret/data') || [] + const selfSecrets = secretData.map((item) => item.key) + + const remainingSecrets = configSecretKeys.filter((item) => !selfSecrets.includes(item)) + + const selfKey = getValue(discriminator, `createSecret/data/${index}/key`) + if (selfKey) { + remainingSecrets.push(selfKey) + } + const resSecret = remainingSecrets.map((item) => { + return { text: item, value: item } + }) + return resSecret + } + + let secretArray = [] + + function createSecretUrl() { + const user = storeGet('/route/params/user') + const cluster = storeGet('/route/params/cluster') + + const domain = storeGet('/domain') || '' + if (domain.includes('bb.test')) { + return `http://console.bb.test:5990/console/${user}/kubernetes/${cluster}/core/v1/secrets/create` + } else { + const editedDomain = domain.replace('kubedb', 'console') + return `${editedDomain}/console/${user}/kubernetes/${cluster}/core/v1/secrets/create` + } + } + + function isConfigSelected() { + const secretName = getValue(model, '/spec/configuration/configSecret/name') + return !!secretName + } + + function isEqualToValueFromType(value) { + // watchDependency('discriminator#/valueFromType') + const valueFrom = getValue(discriminator, '/valueFromType') + return valueFrom === value + } + + async function getNamespacedResourceList({ namespace, group, version, resource }) { + const owner = storeGet('/route/params/user') + const cluster = storeGet('/route/params/cluster') + + const url = `/clusters/${owner}/${cluster}/proxy/${group}/${version}/namespaces/${namespace}/${resource}` + + let ans = [] + try { + const resp = await axios.get(url, { + params: { + filter: { items: { metadata: { name: null }, type: null } }, + }, + }) + + const items = (resp && resp.data && resp.data.items) || [] + ans = items + } catch (e) { + console.log(e) + } + + return ans + } + async function getResourceList({ group, version, resource }) { + const owner = storeGet('/route/params/user') + const cluster = storeGet('/route/params/cluster') + + const url = `/clusters/${owner}/${cluster}/proxy/${group}/${version}/${resource}` + + let ans = [] + try { + const resp = await axios.get(url, { + params: { + filter: { items: { metadata: { name: null }, type: null } }, + }, + }) + + const items = (resp && resp.data && resp.data.items) || [] + ans = items + } catch (e) { + console.log(e) + } + + return ans + } + async function resourceNames(group, version, resource) { + const namespace = getValue(model, '/metadata/namespace') + // watchDependency('model#/metadata/namespace') + + let resources = await getNamespacedResourceList({ + namespace, + group, + version, + resource, + }) + + if (resource === 'secrets') { + resources = resources.filter((item) => { + const validType = ['kubernetes.io/service-account-token', 'Opaque'] + return validType.includes(item.type) + }) + } + + return resources.map((resource) => { + const name = (resource.metadata && resource.metadata.name) || '' + return { + text: name, + value: name, + } + }) + } + async function unNamespacedResourceNames(group, version, resource) { + let resources = await getResourceList({ + group, + version, + resource, + }) + + if (resource === 'secrets') { + resources = resources.filter((item) => { + const validType = ['kubernetes.io/service-account-token', 'Opaque'] + return validType.includes(item.type) + }) + } + + return resources.map((resource) => { + const name = (resource.metadata && resource.metadata.name) || '' + return { + text: name, + value: name, + } + }) + } + + // reconfiguration type + function ifReconfigurationTypeEqualsTo(value) { + const reconfigurationType = getValue(discriminator, '/reconfigurationType') + // watchDependency('discriminator#/reconfigurationType') + + return reconfigurationType === value + } + + function onReconfigurationTypeChange() { + const reconfigurationType = getValue(discriminator, '/reconfigurationType') + setDiscriminatorValue('/applyConfig', []) + if (reconfigurationType === 'remove') { + commit('wizard/model$delete', `/spec/configuration`) + + commit('wizard/model$update', { + path: `/spec/configuration/removeCustomConfig`, + value: true, + force: true, + }) + } else { + commit('wizard/model$delete', `/spec/configuration/configSecret`) + commit('wizard/model$delete', `/spec/configuration/applyConfig`) + commit('wizard/model$delete', `/spec/configuration/removeCustomConfig`) + } + } + + // for tls + function hasTlsField() { + const tls = getDbTls() + + return !!tls + } + + function initIssuerRefApiGroup() { + const kind = getValue(model, '/spec/tls/issuerRef/kind') + // watchDependency('model#/spec/tls/issuerRef/kind') + + if (kind) { + const apiGroup = getValue(discriminator, '/dbDetails/spec/tls/issuerRef/apiGroup') + if (apiGroup) return apiGroup + return 'cert-manager.io' + } else return undefined + } + + async function getIssuerRefsName() { + const owner = storeGet('/route/params/user') + const cluster = storeGet('/route/params/cluster') + // watchDependency('model#/spec/tls/issuerRef/kind') + // watchDependency('model#/metadata/namespace') + const kind = getValue(model, '/spec/tls/issuerRef/kind') + const namespace = getValue(model, '/metadata/namespace') + + if (kind === 'Issuer') { + const url = `/clusters/${owner}/${cluster}/proxy/cert-manager.io/v1/namespaces/${namespace}/issuers` + return getIssuer(url) + } else if (kind === 'ClusterIssuer') { + const url = `/clusters/${owner}/${cluster}/proxy/charts.x-helm.dev/v1alpha1/clusterchartpresets/kubedb-ui-presets` + + let presets = storeGet('/kubedbuiPresets') || {} + if (!storeGet('/route/params/actions')) { + try { + const presetResp = await axios.get(url) + presets = presetResp.data?.spec?.values?.spec + } catch (e) { + console.log(e) + presets.status = String(e.status) + } + } + let clusterIssuers = presets.admin?.clusterIssuers?.available || [] + if (presets.status === '404') { + const url = `/clusters/${owner}/${cluster}/proxy/cert-manager.io/v1/clusterissuers` + return getIssuer(url) + } + return clusterIssuers + } else if (!kind) { + commit('wizard/model$delete', '/spec/tls/issuerRef/name') + return [] + } + + async function getIssuer(url) { + try { + const resp = await axios.get(url) + const resources = (resp && resp.data && resp.data.items) || [] + + resources.map((item) => { + const name = (item.metadata && item.metadata.name) || '' + item.text = name + item.value = name + return true + }) + return resources + } catch (e) { + console.log(e) + return [] + } + } + } + + function initTlsOperation() { + return 'update' + } + function onTlsOperationChange() { + const tlsOperation = getValue(discriminator, '/tlsOperation') + + commit('wizard/model$delete', '/spec/tls') + + if (tlsOperation === 'rotate') { + commit('wizard/model$update', { + path: '/spec/tls/rotateCertificates', + value: true, + force: true, + }) + commit('wizard/model$delete', '/spec/tls/certificates') + commit('wizard/model$delete', '/spec/tls/remove') + } else if (tlsOperation === 'remove') { + commit('wizard/model$update', { + path: '/spec/tls/remove', + value: true, + force: true, + }) + commit('wizard/model$delete', '/spec/tls/certificates') + commit('wizard/model$delete', '/spec/tls/rotateCertificates') + } + } + + function showIssuerRefAndCertificates() { + const tlsOperation = getValue(discriminator, '/tlsOperation') + // watchDependency('discriminator#/tlsOperation') + const verd = tlsOperation !== 'remove' && tlsOperation !== 'rotate' + + return verd + } + + function isIssuerRefRequired() { + const hasTls = hasTlsField() + return hasTls ? false : '' + } + + function getRequestTypeFromRoute() { + const isDbloading = isDbDetailsLoading() + const { query } = route || {} + const { requestType } = query || {} + return isDbloading ? '' : requestType || '' + } + + // ************************************** Set db details ***************************************** + + function isDbDetailsLoading() { + // watchDependency('discriminator#/dbDetails') + // watchDependency('model#/spec/databaseRef/name') + const dbDetails = getValue(discriminator, '/dbDetails') + const dbName = getValue(model, '/spec/databaseRef/name') + + return !dbDetails || !dbName + } + + function setValueFromDbDetails(path, commitPath) { + // watchDependency('discriminator#/dbDetails') + + const retValue = getValue(discriminator, `/dbDetails${path}`) + + if (commitPath && retValue) { + const tlsOperation = getValue(discriminator, '/tlsOperation') + + // computed called when tls fields is not visible + if (commitPath.includes('/spec/tls') && tlsOperation !== 'update') return undefined + + // direct model update required for reusable element. + // computed property is not applicable for reusable element + commit('wizard/model$update', { + path: commitPath, + value: retValue, + force: true, + }) + } + + return retValue || undefined + } + + function setConfigFiles() { + // watchDependency('model#/resources/secret_config/stringData') + const configFiles = getValue(model, '/resources/secret_config/stringData') + + const files = [] + + for (const item in configFiles) { + const obj = {} + obj.key = item + obj.value = configFiles[item] + files.push(obj) + } + + return files + } + + function getAliasOptions() { + return ['server', 'client', 'metrics-exporter'] + } + + function isNamespaceDisabled() { + const { namespace } = route.query || {} + return !!namespace + } + + function isDatabaseRefDisabled() { + const { name } = route.params || {} + return !!name + } + + function onNamespaceChange() { + commit('wizard/model$delete', '/spec/type') + } + + function onDbChange() { + commit('wizard/model$delete', '/spec/type') + getDbDetails() + } + + function setApplyToIfReady() { + return 'IfReady' + } + + function isVerticalScaleTopologyRequired() { + // watchDependency('discriminator#/topologyKey') + // watchDependency('discriminator#/topologyValue') + + const key = getValue(discriminator, '/topologyKey') + const value = getValue(discriminator, '/topologyValue') + const path = `/spec/verticalScaling/node/topology` + + if (key || value) { + commit('wizard/model$update', { + path: path, + value: { key, value }, + force: true, + }) + return '' + } else { + commit('wizard/model$delete', path) + return false + } + } + + function checkVolume(initpath, path) { + const volume = getValue(discriminator, `/dbDetails${initpath}`) + const input = getValue(model, path) + + try { + const sizeInBytes = parseSize(volume) + const inputSizeInBytes = parseSize(input) + + if (inputSizeInBytes >= sizeInBytes) return + else return 'Cannot expand to lower volume!' + } catch (err) { + return err.message || 'Invalid' + } + } + + function parseSize(sizeStr) { + const units = { + '': 1, + K: 1e3, + M: 1e6, + G: 1e9, + T: 1e12, + P: 1e15, + E: 1e18, + Ki: 1024, + Mi: 1024 ** 2, + Gi: 1024 ** 3, + Ti: 1024 ** 4, + Pi: 1024 ** 5, + Ei: 1024 ** 6, + } + + const match = String(sizeStr).match(/^([0-9]+(?:\.[0-9]*)?)\s*([A-Za-z]*)$/) + if (!match) throw new Error('Invalid size format') + + const value = parseFloat(match[1]) + const unit = match[2] + + if (!(unit in units)) + throw new Error('Unrecognized unit. Available units are K, Ki, M, Mi, G, Gi etc') + + return value * units[unit] + } + + function fetchAliasOptions() { + return getAliasOptions ? getAliasOptions() : [] + } + + function validateNewCertificates({ itemCtx }) { + const addedAliases = (model && model.map((item) => item.alias)) || [] + + if (addedAliases.includes(itemCtx.alias) && itemCtx.isCreate) { + return { isInvalid: true, message: 'Alias already exists' } + } + return {} + } + + function disableAlias() { + return !!(model && model.alias) + } + + function getSelectedConfigSecret(type) { + const path = `/spec/configuration/configSecret/name` + const selectedSecret = getValue(model, path) + // watchDependency(`model#${path}`) + return `You have selected ${selectedSecret} secret` || 'No secret selected' + } + + function objectToYaml(obj, indent = 0) { + if (obj === null || obj === undefined) return 'null' + if (typeof obj !== 'object') return JSON.stringify(obj) + + const spaces = ' '.repeat(indent) + + if (Array.isArray(obj)) { + return obj + .map((item) => `${spaces}- ${objectToYaml(item, indent + 1).trimStart()}`) + .join('\n') + } + + return Object.keys(obj) + .map((key) => { + const value = obj[key] + const keyLine = `${spaces}${key}:` + + if (value === null || value === undefined) { + return `${keyLine} null` + } + + if (typeof value === 'object') { + const nested = objectToYaml(value, indent + 1) + return `${keyLine}\n${nested}` + } + + if (typeof value === 'string') { + return `${keyLine} "${value}"` + } + + return `${keyLine} ${value}` + }) + .join('\n') + } + + function getSelectedConfigSecretValue(type) { + const path = `/spec/configuration/configSecret/name` + const selectedSecret = getValue(model, path) + let data + secretArray.forEach((item) => { + if (item.value === selectedSecret) { + data = objectToYaml(item.data).trim() || 'No Data Found' + } + }) + return data || 'No Data Found' + } + + function setExporter(type) { + let path = `/dbDetails/spec/monitor/prometheus/exporter/resources/limits/${type}` + const limitVal = getValue(discriminator, path) + + if (!limitVal) { + path = `/dbDetails/spec/monitor/prometheus/exporter/resources/requests/${type}` + const reqVal = getValue(discriminator, path) + + if (reqVal) return reqVal + } + return limitVal + } + + function onExporterResourceChange(type) { + const commitPath = `/spec/verticalScaling/exporter/resources/requests/${type}` + const valPath = `/spec/verticalScaling/exporter/resources/limits/${type}` + const val = getValue(model, valPath) + if (val) + commit('wizard/model$update', { + path: commitPath, + value: val, + force: true, + }) + } + + function isMachineValid() { + const dbDetails = getValue(discriminator, '/dbDetails') + const containers = dbDetails?.spec?.podTemplate?.spec?.containers || [] + const limits = containers[0]?.resources?.limits || {} + + const selectedMachine = getValue(discriminator, '/machine') + const selectedLimits = { cpu: selectedMachine.cpu, memory: selectedMachine.memory } + + if (JSON.stringify(limits) === JSON.stringify(selectedLimits)) { + return 'Resource limits are same as current machine configuration. Please select different resources or machine preset.' + } + return false + } + + return { + isMachineValid, + setExporter, + onExporterResourceChange, + fetchAliasOptions, + validateNewCertificates, + disableAlias, + isRancherManaged, + fetchJsons, + returnFalse, + getNamespaces, + getDbs, + getDbDetails, + getDbVersions, + getVersionInfo, + isVersionEmpty, + getVersion, + ifRequestTypeEqualsTo, + onRequestTypeChange, + getDbTls, + getDbType, + disableOpsRequest, + initNamespace, + initDatabaseRef, + showAndInitName, + showAndInitNamespace, + showAndInitDatabaseRef, + showConfigureOpsrequestLabel, + showAndInitOpsRequestType, + ifDbTypeEqualsTo, + getConfigSecrets, + getSelectedConfigSecret, + getSelectedConfigSecretValue, + createSecretUrl, + isEqualToValueFromType, + getNamespacedResourceList, + getResourceList, + resourceNames, + unNamespacedResourceNames, + ifReconfigurationTypeEqualsTo, + onReconfigurationTypeChange, + onApplyconfigChange, + hasTlsField, + initIssuerRefApiGroup, + getIssuerRefsName, + initTlsOperation, + onTlsOperationChange, + showIssuerRefAndCertificates, + isIssuerRefRequired, + getRequestTypeFromRoute, + isDbDetailsLoading, + setValueFromDbDetails, + getAliasOptions, + isDatabaseRefDisabled, + isNamespaceDisabled, + onNamespaceChange, + onDbChange, + setApplyToIfReady, + isVerticalScaleTopologyRequired, + getMachines, + setMachine, + onMachineChange, + isMachineCustom, + checkVolume, + setConfigFiles, + isConfigSelected, + fetchConfigSecrets, + getConfigSecretsforAppyConfig, + getSelectedConfigurationData, + getSelectedConfigurationName, + getSelectedConfigurationValueForRemove, + createNewConfigSecret, + decodeError, + isCreateSecret, + isNotCreateSecret, + onCreateSecretChange, + cancelCreateSecret, + setApplyConfig, + onRemoveConfigChange, + onNewConfigSecretChange, + onSelectedSecretChange, + isTlsEnabled, + } +} diff --git a/charts/opskubedbcom-clickhouseopsrequest-editor/ui/language.yaml b/charts/opskubedbcom-clickhouseopsrequest-editor/ui/language.yaml new file mode 100644 index 0000000000..a53a33eca9 --- /dev/null +++ b/charts/opskubedbcom-clickhouseopsrequest-editor/ui/language.yaml @@ -0,0 +1,307 @@ +bn: {} +en: + labels: + agent: Select a Monitoring Method + clickhouse: ClickHouse + node_selection_policy: Node Selection Policy + topology: Topology + alias: Alias + annotations: + key: Key + label: Annotations + value: Value + api_group: API Group + apply: Apply + args: Args + backup: + invoker: Backup Invoker + title: Schedule a Backup? + backupBlueprint: + name: Blueprint Name + schedule: Schedule + taskParameters: Task Parameters + title: Backup Blueprint + backupConfiguration: + retentionPolicy: + keepLast: Keep Last + name: Name + prune: Prune + title: Retention Policy + schedule: Schedule + targetReference: + apiVersion: Target ApiVersion + kind: Target Kind + name: Reference Name + title: Target Reference + type: Reference Type + taskName: Task Name + title: Backup Configuration + basic_info: Basic Information + certificate: Certificate + certificates: Certificates + client_auth_mode: Client Auth Mode + cluster_ip: Cluster IP + configOptions: Configuration Options + configSecret: Config Secret + createConfig: Create Secret + configServer: Config Server + config_map_key: ConfigMap Key + config_map_name: ConfigMap Name + config_ops_request: Configure Ops Request + configuration: Configuration + configuration_files: Configuration Files + configuration_source: Configuration Source + controller_annotations: Controller Annotations + countries: Countries + country: Country + cpu: CPU + customize_exporter: Customize Exporter Sidecar + data_cold_node: DataCold Node + data_content_node: DataContent Node + data_frozen_node: DataFrozen Node + data_hot_node: DataHot Node + data_node: Data Node + data_warm_node: DataWarm Node + dataSource: DataWarm Source + database: + mode: Database Mode + name: Database Name + secret: Database Secret + version: Database Version + databaseRef: Select your database + dns_name: DNS Name + dns_names: DNS Names + duration: Duration + effect: Effect + enable_monitoring: Enable Monitoring + enable_ssl_question: Enable SSL? + enable_tls: Enable TLS + endpoint: Endpoint + endpoints: Endpoints + environmentVariablesFrom: Environemt Variables From + environment_variable: Environment Variable + environment_variables: Environment Variables + exporter: Exporter + exporter_configuration: Exporter Configuration + external_ip: External IP + external_ips: External IPs + external_traffic_policy: External Traffic Policy + fs_group: Fs Group + health_check_node_port: Health Check Node Port + honor_labels: Honor labels + image_pull_secrets: Image Pull Secrets + ingest_node: Ingest Node + initialization: Pre-populate your MongoDB from backup/another database + applyConfig: + key: Key + value: Value + label: Apply Config + interval: Interval + ip_address: IP Address + ip_addresses: IP Addresses + issuer_ref: Issuer Reference + key: Key + kind: Kind + labels: + key: Key + label: Labels + value: Value + level: Level + limit: Limit + limits: Limits + load_balancer_ip: Load Balancer IP + load_balancer_source_range: Load Balancer Source Range + load_balancer_source_ranges: Load Balancer Source Ranges + master_node: Master Node + match_expression: Match Expression + match_expressions: Match Expressions + match_field: Match Field + match_fields: Match Fields + memory: Memory + ml_node: ML Node + mongos: Mongos + name: Name + namespace: Namespace + new_secret_password: New Database Secret + node: Node + node_port: Node Port + node_selector: Node Selector + node_selector_terms: Node Selector Terms + op_req_name: Ops Request Name + operator: Operator + ops_request_type: Type of Ops Request + organization: Organization + organizational_unit: Organizational Unit + organizational_units: Organizational Units + organizations: Organizations + password: Password (Keep it empty to autogenerate) + path: Path + pod_annotations: Pod Annotations + pod_spec: Pod Spec + pod_template: Pod Template + port: Port + ports: Ports + postgres: Postgres + prePopulateDatabase: Do you want to pre-populate your database? + preferred_during_scheduling_ignored_during_execution: Preferred During Scheduling Ignored During Execution + prometheus: Prometheus + province: Province + provinces: Provinces + reconfigurationType: Reconfiguration Type + removeCustomConfig: Remove Custom Config? + renew_before: Renew Before + replicaSet: Replica Set + replicas: Replicas + replicaset: + name: Replicaset Name + number: Replica Number + repositories: + backend: + bucket: Bucket + container: Container + endPoint: End Point + maxConnections: Maximum Connections + mountPath: Mount Path + path: Path + prefix: Prefix + pvcName: Claim Name + region: Region + secret: Storage Secret + server: Server + subPath: Sub Path + title: Backend + type: Type + url: URL + volumeSource: Select Volume Source + choise: "" + name: Name + title: Repository + request: Request + requests: Requests + require_ssl_question: Require SSL? + resources: Resources + restoreSession: + name: Name + snapshot: Snapshot + title: Restore Session + role: Role + run_as_group: Run as Group + run_as_non_root: Run As Non Root? + run_as_user: Run as User + runtimeSettings: + choise: Customize Restore Job Runtime Settings? + container: + ionice: + class: Class + classData: Class Data + title: Ionice + nice: + adjustment: Adjustment + title: Nice + resources: + cpu: CPU + limits: Limits + memory: Memory + requests: Requests + title: Resources + title: Container Runtime Settings + pod: + imagePullSecrets: Image Pull Secrets + serviceAccountName: Service Account Name + title: Pod Runtime Settings + securityContext: + fsGroup: FS Group + privileged: Privileged + runAsGroup: Run As Group + runAsNonRoot: Run As Non Root + runAsUser: Run As User + seLinuxOptions: + level: LeveL + role: Role + title: SE Linux Options + type: Type + user: User + title: Security Context + scrapping_interval: Scrapping Interval + script: + path: Script Path + volume: Source Volume + volumeName: Name + volumeType: Type + se_linux_options: SE Linux Options + secret: Secret + secret_key: Secret Key + secret_name: Secret Name + security_context: Security Context + service_account_name: Service Account Name + service_monitor: Service Monitor + service_monitor_configuration: ServiceMonitor Configuration + service_template: Service Template + service_template_annotations: Service Template Annotations + service_templates: Service Templates + shard: Shard + shardNodes: Shard Nodes + shards: Shards + ssl_mode: SSL Mode + standalone: Standalone + storage: + class: Storage Class + size: Storage Size + master_size: Master Node + data_size: Data Node + ingest_size: Ingest Node + ml_size: ML Node + transform_size: Transform Node + data_cold_size: DataCold Node + data_content_size: DataContent Node + data_frozen_size: DataFrozen Node + data_hot_size: DataHot Node + data_warm_size: DataWarm Node + subject: Subject + targetVersion: Select Target Version + terminalPolicy: Terminal Policy + timeout: Timeout + timeout_seconds: Timeout Seconds + tls: Reconfigure TLS + tlsOperation: Choose TLS Operation + toleration: Toleration + toleration_seconds: Toleration in seconds + tolerations: Tolerations + transform_node: Transform Node + type: Type + user: User + value: Value + values: Values + waitForInitialRestore: Wait For Initial Restore? + weight: Weight + options: + HorizontalScaling: + description: Scale up or down pod count + text: Horizontal Scaling + Reconfigure: + description: Reconfigure your database + text: Reconfigure + ReconfigureTLS: + description: Reconfigure your database tls configuration + text: Reconfigure TLS + Restart: + description: Restart your database + text: Restart + Upgrade: + description: " Upgrade your database to any version" + text: Upgrade + UpdateVersion: + description: Update your database to any version + text: Update Version + VerticalScaling: + description: Manage your CPU resources + text: Vertical Scaling + VolumeExpansion: + description: Manage your database size + text: Volume Expansion + client_auth_mode: + md5: md5 + scram: scram + cert: cert + steps: + - label: Basic Information From 0251e8a7f35b4f4b501fe78868d3566207f8aabe Mon Sep 17 00:00:00 2001 From: Samiul Date: Fri, 22 May 2026 12:31:58 +0600 Subject: [PATCH 3/4] add clickhouse opsrequest forms Signed-off-by: Samiul --- .../ui/create-ui.yaml | 651 ------ .../ui/functions.js | 1942 ----------------- .../ui/language.yaml | 307 --- 3 files changed, 2900 deletions(-) delete mode 100644 charts/opskubedbcom-oracleopsrequest-editor/ui/create-ui.yaml delete mode 100644 charts/opskubedbcom-oracleopsrequest-editor/ui/functions.js delete mode 100644 charts/opskubedbcom-oracleopsrequest-editor/ui/language.yaml diff --git a/charts/opskubedbcom-oracleopsrequest-editor/ui/create-ui.yaml b/charts/opskubedbcom-oracleopsrequest-editor/ui/create-ui.yaml deleted file mode 100644 index 8a7d902c78..0000000000 --- a/charts/opskubedbcom-oracleopsrequest-editor/ui/create-ui.yaml +++ /dev/null @@ -1,651 +0,0 @@ -step: -- elements: - - if: - name: showAndInitName - type: function - label: op_req_name - schema: schema/properties/metadata/properties/name - type: input - validation: - type: required - - disable: isNamespaceDisabled - hasGroup: isRancherManaged - if: - name: showAndInitNamespace - type: function - init: - type: func - value: initNamespace - label: Namespace - loader: getNamespaces - schema: schema/properties/metadata/properties/namespace - type: select - validation: - type: required - watcher: - func: onNamespaceChange - paths: - - schema/properties/metadata/properties/namespace - - disable: isDatabaseRefDisabled - if: - name: showAndInitDatabaseRef - type: function - init: - type: func - value: initDatabaseRef - label: Database Ref - loader: - name: getDbs - watchPaths: - - schema/properties/metadata/properties/namespace - refresh: true - schema: schema/properties/spec/properties/databaseRef/properties/name - type: select - validation: - type: required - watcher: - func: onDbChange - paths: - - schema/properties/spec/properties/databaseRef/properties/name - - if: - name: showConfigureOpsrequestLabel - type: function - label: config_ops_request - type: label-element - - disable: isDbDetailsLoading - if: - name: showAndInitOpsRequestType - type: function - init: - type: func - value: getRequestTypeFromRoute - isHorizontal: true - label: Type of Ops Request - options: - - description: Update your database to any version - text: Update Version - value: UpdateVersion - - description: Scale up or down pod count - text: Horizontal Scaling - value: HorizontalScaling - - description: Manage your CPU resources - text: Vertical Scaling - value: VerticalScaling - - description: Manage your database size - text: Volume Expansion - value: VolumeExpansion - - description: Restart your database - text: Restart - value: Restart - - description: Reconfigure your database - text: Reconfigure - value: Reconfigure - - description: Reconfigure your database tls configuration - text: Reconfigure TLS - value: ReconfigureTLS - schema: schema/properties/spec/properties/type - type: radio - watcher: - func: onRequestTypeChange - paths: - - schema/properties/spec/properties/type - - elements: - - init: - type: func - value: setValueFromDbDetails|/spec/version - label: Target Version - loader: getDbVersions - schema: schema/properties/spec/properties/updateVersion/properties/targetVersion - subtitle: Select the desired Oracle version to which you want to update your - database. - type: select-compare - - if: - name: isVersionEmpty - type: function - label: "" - loader: - name: getVersionInfo - watchPaths: - - temp/properties/filteredVersion - type: info - fixedBlock: true - if: - name: ifRequestTypeEqualsTo|UpdateVersion - type: function - label: Version - showLabels: true - type: block-layout - - elements: - - elements: - - header: Replica - init: - type: func - value: setValueFromDbDetails|/spec/replicas - label: Replicas - schema: schema/properties/spec/properties/horizontalScaling/properties/member - subtitle: Define the total number of replicas for the database. Increasing - replicas improves fault tolerance and load distribution , while reducing - replicas conserves resources - type: input-compare - - hasIcon: true - label: Each replica represents an independent copy of your database. For example, - setting this to 3 creates three copies of the database for better availability. - type: info - if: - name: ifDbTypeEqualsTo|cluster|horizontalScaling - type: function - type: horizontal-layout - fixedBlock: true - if: - name: ifRequestTypeEqualsTo|HorizontalScaling - type: function - label: Horizontal Scaling Form - showLabels: true - type: block-layout - - elements: - - elements: - - init: - type: func - value: setMachine - label: Resources - loader: getMachines - schema: temp/properties/machine - subtitle: Compare your current machine configuration with the proposed memory - adjustments and make informed decisions for your database setup - type: machine-compare - validation: - name: isMachineValid - type: custom - watcher: - func: onMachineChange|node|/spec/podTemplate/spec/resources - paths: - - temp/properties/machine - - elements: - - elements: - - elements: - - label: Node Selection Policy - subtitle: Control where your workloads runs by configuring node selection - criteria. Use label selectors to match specific nodes or taints to - avoid unsuitable nodes - type: label-element - - label: Node Selection Policy - options: - - text: LabelSelector - value: LabelSelector - - text: Taint - value: Taint - schema: schema/properties/spec/properties/verticalScaling/properties/node/properties/nodeSelectionPolicy - type: select - hideBorder: true - showLabels: false - type: block-layout - - customClass: mt-20 - label: Label Selector: Specify key-value pairs to target nodes - that match your workload's requirements.
Taints: Define - tolerations for node taints to ensure your workload runs only on compatible - nodes - type: info - type: horizontal-layout - - label: Topology - subtitle: Define node topology preferences, such as zone or region. For - example, 'zone=us-central1-a' ensures workloads are deployed in that zone. - type: label-element - - elements: - - label: Key - schema: temp/topologyKey - type: input - validation: - name: isVerticalScaleTopologyRequired - type: custom - - label: Value - schema: temp/topologyValue - type: input - validation: - name: isVerticalScaleTopologyRequired - type: custom - type: horizontal-layout - label: Node Selection - showLabels: true - type: block-layout - - elements: - - elements: - - init: - type: func - value: setExporter|cpu - label: CPU - schema: schema/properties/spec/properties/verticalScaling/properties/exporter/properties/resources/properties/requests/properties/cpu - type: input - - init: - type: func - value: setExporter|memory - label: Memory - schema: schema/properties/spec/properties/verticalScaling/properties/exporter/properties/resources/properties/limits/properties/memory - type: input - watcher: - func: onExporterResourceChange|memory - paths: - - schema/properties/spec/properties/verticalScaling/properties/exporter/properties/resources/properties/limits/properties/memory - showLabels: true - type: horizontal-layout - hideBlock: true - label: Exporter - showLabels: true - type: block-layout - label: Oracle vertical scaling - showLabels: false - type: block-layout - if: - name: ifRequestTypeEqualsTo|VerticalScaling - type: function - type: block-layout - - elements: - - elements: - - elements: - - label: Mode - subtitle: Not sure which mode to pick? Use Online Mode for smooth operations - with minimal disruption. Choose Offline Mode if you can afford a brief - downtime for added reliability during the volume expansion. - type: label-element - - label: Mode - options: - - text: Offline - value: Offline - - text: Online - value: Online - schema: schema/properties/spec/properties/volumeExpansion/properties/mode - type: select - validation: - type: required - type: block-layout - type: horizontal-layout - - elements: - - elements: - - header: Oracle - init: - type: func - value: setValueFromDbDetails|/spec/storage/resources/requests/storage - label: Storage Size - schema: schema/properties/spec/properties/volumeExpansion/properties/node - subtitle: How much extra storage does your database need? Specify the size(e.g. - 2Gi for 2 gigabytes) so we can allocate it correctly - type: input-compare - validation: - name: checkVolume|/spec/storage/resources/requests/storage|/spec/volumeExpansion/node - type: custom - type: horizontal-layout - fixedBlock: true - label: Oracle volume expansion - showLabels: true - type: block-layout - if: - name: ifRequestTypeEqualsTo|VolumeExpansion - type: function - label: Volume Expansion Form - type: block-layout - - elements: - - elements: - - label: "" - subtitle: Select a new configuration secret, apply a custom configuration, - or remove an existing setup to update your database settings - type: label-element - - elements: - - elements: - - customClass: mb-15 - label: Config Secret - subtitle: Select an existing secret or create a new one to apply to your - database configuration. - type: label-element - - customClass: mb-2 - init: - type: func - value: setValueFromDbDetails|/spec/configSecret/name - label: Config Secret - loader: - name: getConfigSecrets - watchPaths: - - schema/properties/metadata/properties/namespace - - temp/properties/createSecret/properties/status - refresh: fetchConfigSecrets - schema: schema/properties/spec/properties/configuration/properties/configSecret/properties/name - type: select - watcher: - func: onCreateSecretChange - paths: - - temp/properties/createSecret/properties/status - - customClass: mt-15 - elements: - - label: "" - subtitle: Enter a unique name to identify this secret. - type: label-element - - label: Secret Name - schema: temp/properties/createSecret/properties/name - type: input - validation: - type: required - - buttonClass: is-light is-outlined - elements: - - label: Key - loader: - name: onSelectedSecretChange - watchPaths: - - temp/properties/createSecret/properties/data - schema: key - type: select - validation: - type: required - - height: 120px - label: Value - schema: value - type: textarea - keepInitial: true - label: String Data - schema: temp/properties/createSecret/properties/data - type: array-object-form - validation: - type: required - hasButton: - action: createNewConfigSecret - hasCancel: cancelCreateSecret - text: Save - if: - name: isCreateSecret - type: function - label: Create a New Config Secret - showLabels: true - type: block-layout - - editorHeight: 500px - hideFormatButton: true - if: - name: isNotCreateSecret - type: function - loader: - name: onNewConfigSecretChange - watchPaths: - - schema/properties/spec/properties/configuration/properties/configSecret/properties/name - readonly: true - schema: temp/properties/newConfigSecret - type: multi-file-editor - validateContent: false - if: - name: ifReconfigurationTypeEqualsTo|selectNewConfigSecret - type: function - label: Config Secret - type: block-layout - - elements: - - customClass: mb-15 - label: New Apply Config - subtitle: Define custom configurations for your database using key-value - pairs. These parameters will overwrite existing settings.
Enter the - parameter you want to configure (e.g., max_connections). - type: label-element - - customClass: mb-2 - label: Configuration - loader: getConfigSecretsforAppyConfig - refresh: fetchConfigSecrets - schema: temp/properties/selectedConfiguration - type: select - - editorHeight: 500px - hideFormatButton: true - loader: - name: setApplyConfig - watchPaths: - - temp/properties/selectedConfiguration - schema: temp/properties/applyConfig - type: multi-file-editor - validateContent: false - watcher: - func: onApplyconfigChange - paths: - - temp/properties/applyConfig - if: - name: ifReconfigurationTypeEqualsTo|applyConfig - type: function - label: ApplyConfig - type: block-layout - - elements: - - customClass: mb-15 - label: Remove - subtitle: Selected a configuration secret from the available list to update - your database settings - type: label-element - - customClass: mb-2 - label: Configuration - loader: - name: getConfigSecretsforAppyConfig - watchPaths: - - schema/properties/metadata/properties/namespace - refresh: fetchConfigSecrets - schema: temp/properties/selectedConfigurationRemove - type: select - - editorHeight: 500px - hideFormatButton: true - init: - type: func - value: onRemoveConfigChange - readonly: true - schema: temp/properties/removeConfig - type: multi-file-editor - validateContent: false - watcher: - func: onRemoveConfigChange - paths: - - temp/properties/selectedConfigurationRemove - if: - name: ifReconfigurationTypeEqualsTo|remove - type: function - label: Remove - type: block-layout - label: New Config Secret - options: - - text: NEW CONFIG SECRET - value: selectNewConfigSecret - - text: APPLY CONFIG - value: applyConfig - - text: REMOVE - value: remove - schema: temp/properties/reconfigurationType - type: tab-layout - label: Configuration - type: block-layout - if: - name: ifRequestTypeEqualsTo|Reconfigure - type: function - label: Reconfigure Form - loader: - name: fetchConfigSecrets - watchPaths: - - schema/properties/metadata/properties/namespace - type: block-layout - - elements: - - if: - name: hasTlsField - type: function - init: - type: func - value: initTlsOperation - label: TLS Operation - options: - - text: Update - value: update - - text: Rotate - value: rotate - - text: Remove - value: remove - schema: temp/properties/tlsOperation - type: radio - watcher: - func: onTlsOperationChange - paths: - - temp/properties/tlsOperation - - fullwidth: true - if: - name: isTlsEnabled - type: function - label: Remove TLS - schema: schema/properties/spec/properties/tls/properties/remove - type: switch - - fullwidth: true - if: - name: isTlsEnabled - type: function - label: Rotate Certificates - schema: schema/properties/spec/properties/tls/properties/rotateCertificates - type: switch - - elements: - - fullwidth: true - init: - type: func - value: setValueFromDbDetails|/spec/tls/requireSSL - label: Require SSL - schema: schema/properties/spec/properties/tls/properties/requireSSL - type: switch - if: - name: showIssuerRefAndCertificates - type: function - label: Require SSL - type: block-layout - - elements: - - disable: true - init: - type: func - value: initIssuerRefApiGroup - label: API Group - schema: schema/properties/spec/properties/tls/properties/issuerRef/properties/apiGroup - type: input - watcher: - func: initIssuerRefApiGroup - paths: - - schema/properties/spec/properties/tls/properties/issuerRef/properties/kind - - init: - type: func - value: setValueFromDbDetails|/spec/tls/issuerRef/kind - label: Kind - options: - - text: Issuer - value: Issuer - - text: ClusterIssuer - value: ClusterIssuer - schema: schema/properties/spec/properties/tls/properties/issuerRef/properties/kind - type: select - validation: - name: isIssuerRefRequired - type: custom - - init: - type: func - value: setValueFromDbDetails|/spec/tls/issuerRef/name - label: Name - loader: - name: getIssuerRefsName - watchPaths: - - schema/properties/spec/properties/tls/properties/issuerRef/properties/kind - - schema/properties/metadata/properties/namespace - schema: schema/properties/spec/properties/tls/properties/issuerRef/properties/name - type: select - validation: - name: isIssuerRefRequired - type: custom - if: - name: showIssuerRefAndCertificates - type: function - label: Issuer Reference - showLabels: true - type: block-layout - - elements: - - buttonClass: is-light is-outlined - elements: - - disable: disableAlias - label: Alias - loader: fetchAliasOptions - schema: alias - type: select - validation: - type: required - - label: Secret Name - schema: secretName - type: input - - label: Duration - schema: duration - type: input - - label: Renew Before - schema: renewBefore - type: input - - buttonClass: is-light is-outlined - element: - label: Organization - type: input - label: Organizations - schema: subject/properties/organizations - type: array-item-form - - buttonClass: is-light is-outlined - element: - label: Country - type: input - label: Countries - schema: subject/properties/countries - type: array-item-form - - buttonClass: is-light is-outlined - element: - label: Organizational Unit - type: input - label: Organizational Units - schema: subject/properties/organizationalUnits - type: array-item-form - - buttonClass: is-light is-outlined - element: - label: Province - type: input - label: Provinces - schema: subject/properties/provinces - type: array-item-form - - buttonClass: is-light is-outlined - element: - label: DNS Name - type: input - label: DNS Names - schema: dnsNames - type: array-item-form - - buttonClass: is-light is-outlined - element: - label: IP Address - type: input - label: IP Addresses - schema: ipAddresses - type: array-item-form - label: Certificates - schema: schema/properties/spec/properties/tls/properties/certificates - type: array-object-form - if: - name: showIssuerRefAndCertificates - type: function - label: Certificates - loader: setValueFromDbDetails|/spec/tls/certificates|/spec/tls/certificates - showLabels: false - type: block-layout - if: - name: ifRequestTypeEqualsTo|ReconfigureTLS - type: function - label: TLS - type: block-layout - - elements: - - label: Timeout - schema: schema/properties/spec/properties/timeout - subtitle: Specify the maximum time allowed for the operation to complete before - it times out - type: time-picker - - init: - type: func - value: setApplyToIfReady - label: Apply - options: - - text: IfReady (OpsRequest will be applied if database is ready) - value: IfReady - - text: Always (OpsRequest will always be applied) - value: Always - schema: schema/properties/spec/properties/apply - type: radio - label: OpsRequest Options - showLabels: true - type: block-layout - loader: getDbDetails - type: single-step-form -type: multi-step-form diff --git a/charts/opskubedbcom-oracleopsrequest-editor/ui/functions.js b/charts/opskubedbcom-oracleopsrequest-editor/ui/functions.js deleted file mode 100644 index 48ae9cdf1a..0000000000 --- a/charts/opskubedbcom-oracleopsrequest-editor/ui/functions.js +++ /dev/null @@ -1,1942 +0,0 @@ -const { axios, useOperator, store, useToast } = window.vueHelpers || {} -const machines = { - 'db.t.micro': { - resources: { - requests: { - cpu: '250m', - memory: '512Mi', - }, - limits: { - cpu: '500m', - memory: '1Gi', - }, - }, - }, - 'db.t.small': { - resources: { - requests: { - cpu: '1', - memory: '1Gi', - }, - limits: { - cpu: '2', - memory: '2Gi', - }, - }, - }, - 'db.t.medium': { - resources: { - requests: { - cpu: '1', - memory: '2Gi', - }, - limits: { - cpu: '2', - memory: '4Gi', - }, - }, - }, - 'db.t.large': { - resources: { - requests: { - cpu: '1', - memory: '4Gi', - }, - limits: { - cpu: '2', - memory: '8Gi', - }, - }, - }, - 'db.t.xlarge': { - resources: { - requests: { - cpu: '2', - memory: '8Gi', - }, - limits: { - cpu: '4', - memory: '16Gi', - }, - }, - }, - 'db.t.2xlarge': { - resources: { - requests: { - cpu: '4', - memory: '16Gi', - }, - limits: { - cpu: '8', - memory: '32Gi', - }, - }, - }, - 'db.m.small': { - resources: { - requests: { - cpu: '500m', - memory: '912680550', - }, - limits: { - cpu: '1', - memory: '1825361100', - }, - }, - }, - 'db.m.large': { - resources: { - requests: { - cpu: '1', - memory: '4Gi', - }, - limits: { - cpu: '2', - memory: '8Gi', - }, - }, - }, - 'db.m.xlarge': { - resources: { - requests: { - cpu: '2', - memory: '8Gi', - }, - limits: { - cpu: '4', - memory: '16Gi', - }, - }, - }, - 'db.m.2xlarge': { - resources: { - requests: { - cpu: '4', - memory: '16Gi', - }, - limits: { - cpu: '8', - memory: '32Gi', - }, - }, - }, - 'db.m.4xlarge': { - resources: { - requests: { - cpu: '8', - memory: '32Gi', - }, - limits: { - cpu: '16', - memory: '64Gi', - }, - }, - }, - 'db.m.8xlarge': { - resources: { - requests: { - cpu: '16', - memory: '64Gi', - }, - limits: { - cpu: '32', - memory: '128Gi', - }, - }, - }, - 'db.m.12xlarge': { - resources: { - requests: { - cpu: '24', - memory: '96Gi', - }, - limits: { - cpu: '48', - memory: '192Gi', - }, - }, - }, - 'db.m.16xlarge': { - resources: { - requests: { - cpu: '32', - memory: '128Gi', - }, - limits: { - cpu: '64', - memory: '256Gi', - }, - }, - }, - 'db.m.24xlarge': { - resources: { - requests: { - cpu: '48', - memory: '192Gi', - }, - limits: { - cpu: '96', - memory: '384Gi', - }, - }, - }, - 'db.r.large': { - resources: { - requests: { - cpu: '1', - memory: '8Gi', - }, - limits: { - cpu: '2', - memory: '16Gi', - }, - }, - }, - 'db.r.xlarge': { - resources: { - requests: { - cpu: '2', - memory: '16Gi', - }, - limits: { - cpu: '4', - memory: '32Gi', - }, - }, - }, - 'db.r.2xlarge': { - resources: { - requests: { - cpu: '4', - memory: '32Gi', - }, - limits: { - cpu: '8', - memory: '64Gi', - }, - }, - }, - 'db.r.4xlarge': { - resources: { - requests: { - cpu: '8', - memory: '96Gi', - }, - limits: { - cpu: '16', - memory: '192Gi', - }, - }, - }, - 'db.r.8xlarge': { - resources: { - requests: { - cpu: '16', - memory: '128Gi', - }, - limits: { - cpu: '32', - memory: '256Gi', - }, - }, - }, - 'db.r.12xlarge': { - resources: { - requests: { - cpu: '24', - memory: '192Gi', - }, - limits: { - cpu: '48', - memory: '384Gi', - }, - }, - }, - 'db.r.16xlarge': { - resources: { - requests: { - cpu: '32', - memory: '256Gi', - }, - limits: { - cpu: '64', - memory: '512Gi', - }, - }, - }, - 'db.r.24xlarge': { - resources: { - requests: { - cpu: '24', - memory: '384Gi', - }, - limits: { - cpu: '96', - memory: '768Gi', - }, - }, - }, -} - -const machineList = [ - 'custom', - 'db.t.micro', - 'db.t.small', - 'db.t.medium', - 'db.t.large', - 'db.t.xlarge', - 'db.t.2xlarge', - 'db.m.small', - 'db.m.large', - 'db.m.xlarge', - 'db.m.2xlarge', - 'db.m.4xlarge', - 'db.m.8xlarge', - 'db.m.12xlarge', - 'db.m.16xlarge', - 'db.m.24xlarge', - 'db.r.large', - 'db.r.xlarge', - 'db.r.2xlarge', - 'db.r.4xlarge', - 'db.r.8xlarge', - 'db.r.12xlarge', - 'db.r.16xlarge', - 'db.r.24xlarge', -] - -let machinesFromPreset = [] -const configSecretKeys = ['kubedb-user.cnf'] - -export const useFunc = (model) => { - const route = store.state?.route - const toast = useToast() - - const { getValue, storeGet, discriminator, setDiscriminatorValue, commit } = useOperator( - model, - store.state, - ) - - showAndInitOpsRequestType() - async function fetchJsons({ axios, itemCtx }) { - let ui = {} - let language = {} - let functions = {} - const { name, sourceRef, version, packageviewUrlPrefix } = itemCtx.chart - - try { - ui = await axios.get( - `${packageviewUrlPrefix}/create-ui.yaml?name=${name}&sourceApiGroup=${sourceRef.apiGroup}&sourceKind=${sourceRef.kind}&sourceNamespace=${sourceRef.namespace}&sourceName=${sourceRef.name}&version=${version}&format=json`, - ) - language = await axios.get( - `${packageviewUrlPrefix}/language.yaml?name=${name}&sourceApiGroup=${sourceRef.apiGroup}&sourceKind=${sourceRef.kind}&sourceNamespace=${sourceRef.namespace}&sourceName=${sourceRef.name}&version=${version}&format=json`, - ) - const functionString = await axios.get( - `${packageviewUrlPrefix}/functions.js?name=${name}&sourceApiGroup=${sourceRef.apiGroup}&sourceKind=${sourceRef.kind}&sourceNamespace=${sourceRef.namespace}&sourceName=${sourceRef.name}&version=${version}`, - ) - // declare evaluate the functionString to get the functions Object - const evalFunc = new Function(functionString.data || '') - functions = evalFunc() - } catch (e) { - console.log(e) - } - - return { - ui: ui.data || {}, - language: language.data || {}, - functions, - } - } - - function returnFalse() { - return false - } - - function isTlsEnabled() { - const dbDetails = getValue(discriminator, '/dbDetails') - return ( - (dbDetails?.spec?.sslMode && - dbDetails?.spec?.sslMode !== 'disabled' && - dbDetails?.spec?.sslMode !== 'disable') || - dbDetails?.spec?.tls - ) - } - - function isRancherManaged() { - const managers = storeGet('/cluster/clusterDefinition/result/clusterManagers') - const found = managers.find((item) => item === 'Rancher') - return !!found - } - - async function getNamespaces() { - if (storeGet('/route/params/actions')) return [] - const owner = storeGet('/route/params/user') - const cluster = storeGet('/route/params/cluster') - - const resp = await axios.get(`/clusters/${owner}/${cluster}/proxy/core/v1/namespaces`, { - params: { filter: { items: { metadata: { name: null } } } }, - }) - - const resources = (resp && resp.data && resp.data.items) || [] - - return resources.map((item) => { - const name = (item.metadata && item.metadata.name) || '' - return { - text: name, - value: name, - } - }) - } - - async function getDbs() { - if (storeGet('/route/params/actions')) return [] - const owner = storeGet('/route/params/user') - const cluster = storeGet('/route/params/cluster') - - const namespace = getValue(model, '/metadata/namespace') - // watchDependency('model#/metadata/namespace') - - const resp = await axios.get( - `/clusters/${owner}/${cluster}/proxy/kubedb.com/v1alpha2/namespaces/${namespace}/oracles`, - { - params: { filter: { items: { metadata: { name: null } } } }, - }, - ) - - const resources = (resp && resp.data && resp.data.items) || [] - - return resources.map((item) => { - const name = (item.metadata && item.metadata.name) || '' - return { - text: name, - value: name, - } - }) - } - - async function getDbDetails() { - machinesFromPreset = storeGet('/kubedbuiPresets')?.admin?.machineProfiles?.machines || [] - - const owner = storeGet('/route/params/user') - const cluster = storeGet('/route/params/cluster') - const namespace = storeGet('/route/query/namespace') || getValue(model, '/metadata/namespace') - const name = storeGet('/route/params/name') || getValue(model, '/spec/databaseRef/name') - - if (namespace && name) { - const url = `/clusters/${owner}/${cluster}/proxy/kubedb.com/v1alpha2/namespaces/${namespace}/oracles/${name}` - const resp = await axios.get(url) - - setDiscriminatorValue('/dbDetails', resp.data || {}) - - return resp.data || {} - } else return {} - } - - let presetVersions = [] - setDiscriminatorValue('/filteredVersion', []) - async function getDbVersions() { - const owner = storeGet('/route/params/user') - const cluster = storeGet('/route/params/cluster') - - const url = `/clusters/${owner}/${cluster}/proxy/charts.x-helm.dev/v1alpha1/clusterchartpresets/kubedb-ui-presets` - - let presets = storeGet('/kubedbuiPresets') || {} - if (!storeGet('/route/params/actions')) { - try { - const presetResp = await axios.get(url) - presets = presetResp.data?.spec?.values?.spec - } catch (e) { - console.log(e) - presets.status = String(e.status) - } - } - - try { - presetVersions = presets.admin?.databases?.Oracle?.versions?.available || [] - const queryParams = { - filter: { - items: { - metadata: { name: null }, - spec: { version: null, deprecated: null, updateConstraints: null }, - }, - }, - } - - const resp = await axios.get( - `/clusters/${owner}/${cluster}/proxy/catalog.kubedb.com/v1alpha1/oracleversions`, - { - params: queryParams, - }, - ) - - const resources = (resp && resp.data && resp.data.items) || [] - - const sortedVersions = resources.sort((a, b) => - versionCompare(a.spec.version, b.spec.version), - ) - - let ver = getValue(discriminator, '/dbDetails/spec/version') || '0' - const found = sortedVersions.find((item) => item.metadata.name === ver) - - if (found) ver = found.spec?.version - - const isGroupRepl = !!getValue(discriminator, '/dbDetails/spec/topology') - const allowed = isGroupRepl - ? found?.spec?.updateConstraints?.allowlist.groupReplication - : found?.spec?.updateConstraints?.allowlist.standalone - - const limit = allowed.length ? allowed[0] : '0.0' - - // keep only non deprecated & kubedb-ui-presets & within constraints of current version - // if presets.status is 404, it means no presets available, no need to filter with presets - const filteredOracleVersions = sortedVersions.filter((item) => { - // default limit 0.0 means no restrictions, show all higher versions - if (limit === '0.0') - return ( - !item.spec?.deprecated && - (presets.status === '404' || - presetVersions.length === 0 || - presetVersions.includes(item.metadata?.name)) && - versionCompare(item.spec?.version, ver) >= 0 - ) - // if limit doesn't have any operator, it's a single version - else if (!limit.match(/^(>=|<=|>|<)/)) - return ( - !item.spec?.deprecated && - (presets.status === '404' || - presetVersions.length === 0 || - presetVersions.includes(item.metadata?.name)) && - item.spec?.version === limit - ) - // if limit has operator, check version with constraints - else - return ( - !item.spec?.deprecated && - (presets.status === '404' || - presetVersions.length === 0 || - presetVersions.includes(item.metadata?.name)) && - isVersionWithinConstraints(item.spec?.version, limit) - ) - }) - setDiscriminatorValue('/filteredVersion', filteredOracleVersions) - - return filteredOracleVersions.map((item) => { - const name = (item.metadata && item.metadata.name) || '' - const specVersion = (item.spec && item.spec.version) || '' - return { - text: `${name} (${specVersion})`, - value: name, - } - }) - } catch (e) { - console.log(e) - return [] - } - } - - function getVersionInfo() { - const filteredVersion = getValue(discriminator, '/filteredVersion') - if (filteredVersion.length) return '' - - let txt = 'No versions from this list can be selected as the target version: [ ' - - presetVersions.forEach((v, idx) => { - txt = `${txt}"${v}"` - if (idx !== presetVersions.length - 1) txt = txt + ', ' - else txt = txt + ' ]' - }) - - return txt - } - - function getVersion() { - return filteredVersion.map((item) => { - const name = (item.metadata && item.metadata.name) || '' - const specVersion = (item.spec && item.spec.version) || '' - return { - text: `${name} (${specVersion})`, - value: name, - } - }) - } - - function isVersionEmpty() { - const val = getValue(discriminator, '/filteredVersion') - return val.length === 0 - } - - function versionCompare(v1, v2) { - const arr1 = v1.split('.').map(Number) - const arr2 = v2.split('.').map(Number) - - for (let i = 0; i < Math.max(arr1.length, arr2.length); i++) { - const num1 = arr1[i] || 0 - const num2 = arr2[i] || 0 - - if (num1 > num2) return 1 // v1 is higher - if (num1 < num2) return -1 // v2 is higher - } - return 0 // versions are equal - } - - function isVersionWithinConstraints(version, constraints) { - let constraintsArr = [] - if (constraints.includes(',')) constraintsArr = constraints?.split(',')?.map((c) => c.trim()) - else constraintsArr = [constraints] - - for (let constraint of constraintsArr) { - let match = constraint.match(/^(>=|<=|>|<)/) - let operator = match ? match[0] : '' - let constraintVersion = constraint.replace(/^(>=|<=|>|<)/, '').trim() - - let comparison = versionCompare(version, constraintVersion) - if ( - (operator === '>=' && comparison < 0) || - (operator === '<=' && comparison > 0) || - (operator === '>' && comparison <= 0) || - (operator === '<' && comparison >= 0) - ) - return false - } - return true - } - - function ifRequestTypeEqualsTo(type) { - const selectedType = getValue(model, '/spec/type') - // watchDependency('model#/spec/type') - - return selectedType === type - } - - function onRequestTypeChange() { - const selectedType = getValue(model, '/spec/type') - const reqTypeMapping = { - Upgrade: 'updateVersion', - UpdateVersion: 'updateVersion', - HorizontalScaling: 'horizontalScaling', - VerticalScaling: 'verticalScaling', - VolumeExpansion: 'volumeExpansion', - Restart: 'restart', - Reconfigure: 'configuration', - ReconfigureTLS: 'tls', - } - - Object.keys(reqTypeMapping).forEach((key) => { - if (key !== selectedType) commit('wizard/model$delete', `/spec/${reqTypeMapping[key]}`) - }) - } - - function disableOpsRequest() { - if (itemCtx.value === 'HorizontalScaling') { - const dbType = getDbType() - - if (dbType === 'standalone') return true - else return false - } else return false - } - - function getDbTls() { - // watchDependency('discriminator#/dbDetails') - const dbDetails = getValue(discriminator, '/dbDetails') - - const { spec } = dbDetails || {} - return spec?.tls || undefined - } - - function getDbType() { - // watchDependency('discriminator#/dbDetails') - const dbDetails = getValue(discriminator, '/dbDetails') - - const { spec } = dbDetails || {} - const { topology } = spec || {} - const { mode } = topology || {} - - const verd = mode ? 'cluster' : 'standalone' - - return verd - } - - function initNamespace() { - const { namespace } = route.query || {} - return namespace || null - } - - function initDatabaseRef() { - // watchDependency('model#/metadata/namespace') - const { name } = route.params || {} - return name - } - - function asDatabaseOperation() { - return !!route.params.actions - } - - function generateOpsRequestNameForClusterUI(getValue, model, route) { - const dbName = getValue(model, '/spec/databaseRef/name') - - const selectedType = getValue(model, '/spec/type') - const lowerType = selectedType ? String(selectedType).toLowerCase() : '' - - const resources = route.params.resource || '' - const resource = resources.slice(0, -1) - - const opsName = dbName ? dbName : resource - return `${opsName}-${Math.floor(Date.now() / 1000)}${lowerType ? '-' + lowerType : ''}` - } - - function showAndInitName() { - // watchDependency('model#/spec/type') - // watchDependency('model#/spec/databaseRef/name') - const ver = asDatabaseOperation() - - const selectedType = getValue(model, '/spec/type') - const lowerType = selectedType ? String(selectedType).toLowerCase() : '' - - if (ver) { - // For kubedb-ui - commit('wizard/model$update', { - path: '/metadata/name', - value: `${route.params.name}-${Math.floor(Date.now() / 1000)}-${lowerType}`, - force: true, - }) - } else { - // For cluster-ui - commit('wizard/model$update', { - path: '/metadata/name', - value: generateOpsRequestNameForClusterUI(getValue, model, route), - force: true, - }) - } - return !ver - } - - function showAndInitNamespace() { - const ver = asDatabaseOperation() - if (ver) { - commit('wizard/model$update', { - path: '/metadata/namespace', - value: `${route.query.namespace}`, - force: true, - }) - } - - return !ver - } - - function showAndInitDatabaseRef() { - const ver = asDatabaseOperation() - if (ver) { - commit('wizard/model$update', { - path: '/spec/databaseRef/name', - value: `${route.params.name}`, - force: true, - }) - } - - return !ver - } - - function showConfigureOpsrequestLabel() { - return !asDatabaseOperation() - } - - function showAndInitOpsRequestType() { - const ver = asDatabaseOperation() - const opMap = { - upgrade: 'UpdateVersion', - updateVersion: 'UpdateVersion', - horizontalscaling: 'HorizontalScaling', - verticalscaling: 'VerticalScaling', - volumeexpansion: 'VolumeExpansion', - restart: 'Restart', - reconfiguretls: 'ReconfigureTLS', - reconfigure: 'Reconfigure', - } - if (ver) { - const operation = storeGet('/resource/activeActionItem/result/operationId') || '' - - const match = /^(.*)-opsrequest-(.*)$/.exec(operation) - if (match) { - const opstype = match[2] - commit('wizard/model$update', { - path: '/spec/type', - value: opMap[opstype], - force: true, - }) - } - } - - return !ver - } - - // vertical scaling - function ifDbTypeEqualsTo(value, opsReqType) { - const verd = getDbType() - - return value === verd - } - - // machine profile stuffs - // let machinesFromPreset = [] - - function getMachines() { - const presets = storeGet('/kubedbuiPresets') || {} - const dbDetails = getValue(discriminator, '/dbDetails') - const containers = dbDetails?.spec?.podTemplate?.spec?.containers || [] - const limits = containers[0]?.resources?.limits || {} - - const avlMachines = presets.admin?.machineProfiles?.available || [] - let arr = [] - if (avlMachines.length) { - arr = avlMachines.map((machine) => { - if (machine === 'custom') - return { text: machine, value: { machine, cpu: limits.cpu, memory: limits.memory } } - else { - const machineData = machinesFromPreset.find((val) => val.id === machine) - if (machineData) { - const subtext = `CPU: ${machineData.limits.cpu}, Memory: ${machineData.limits.memory}` - const text = machineData.name ? machineData.name : machineData.id - return { - text, - subtext, - value: { - machine: text, - cpu: machineData.limits.cpu, - memory: machineData.limits.memory, - }, - } - } else - return { text: machine, value: { machine, cpu: limits.cpu, memory: limits.memory } } - } - }) - } else { - arr = machineList - .map((machine) => { - if (machine === 'custom') - return { text: machine, value: { machine, cpu: limits.cpu, memory: limits.memory } } - const subtext = `CPU: ${machines[machine].resources.limits.cpu}, Memory: ${machines[machine].resources.limits.memory}` - const text = machine - return { - text, - subtext, - value: { - machine: text, - cpu: machines[machine].resources.limits.cpu, - memory: machines[machine].resources.limits.memory, - }, - } - }) - .filter((val) => !!val) - } - return arr - } - - function setMachine() { - const dbDetails = getValue(discriminator, '/dbDetails') - const containers = dbDetails?.spec?.podTemplate?.spec?.containers || [] - const limits = containers[0]?.resources?.limits || {} - console.log(limits) - - const annotations = dbDetails?.metadata?.annotations || {} - const instance = annotations['kubernetes.io/instance-type'] - - let parsedInstance = {} - try { - if (instance) parsedInstance = JSON.parse(instance) - } catch (e) { - console.log(e) - parsedInstance = instance || {} - } - - const machine = parsedInstance || 'custom' - - const machinePresets = machinesFromPreset.find((item) => item.id === machine) - if (machinePresets) { - return { - machine: machine, - cpu: machinePresets.limits.cpu, - memory: machinePresets.limits.memory, - } - } else return { machine: 'custom', cpu: 4, memory: 2 } - } - - function onMachineChange(type, valPath) { - let selectedMachine = {} - selectedMachine = getValue(discriminator, '/machine') - const machine = machinesFromPreset.find((item) => item.id === selectedMachine.machine) - - let obj = {} - if (selectedMachine.machine !== 'custom') { - if (machine) obj = { limits: { ...machine?.limits }, requests: { ...machine?.limits } } - else obj = machines[selectedMachine.machine]?.resources - } else { - const cpu = selectedMachine.cpu || '' - const memory = selectedMachine.memory || '' - obj = { - limits: { cpu: cpu, memory: memory }, - requests: { cpu: cpu, memory: memory }, - } - } - - const path = `/spec/verticalScaling/${type}/resources` - - if (obj && Object.keys(obj).length) - commit('wizard/model$update', { - path: path, - value: obj, - force: true, - }) - - // update metadata.annotations - const annotations = getValue(model, '/metadata/annotations') || {} - annotations['kubernetes.io/instance-type'] = selectedMachine.machine - if (machinesFromPreset.length) - commit('wizard/model$update', { - path: '/metadata/annotations', - value: annotations, - force: true, - }) - } - - function isMachineCustom() { - // watchDependency('discriminator#/machine') - const machine = getValue(discriminator, '/machine') - return machine === 'custom' - } - - // Fetch and store database Infos - // for secret configurations in reconfigure - let configSecrets = [] - let secretConfigData = [] - let existingSecrets = [] - - async function fetchConfigSecrets() { - const owner = storeGet('/route/params/user') - const cluster = storeGet('/route/params/cluster') - const namespace = getValue(model, '/metadata/namespace') - // watchDependency('model#/metadata/namespace') - - const name = getValue(model, '/spec/databaseRef/name') - const dbGroup = getValue(model, '/route/params/group') - const dbKind = getValue(store.state, '/resource/definition/result/kind') - const dbResource = getValue(model, '/route/params/resource') - const dbVersion = getValue(model, '/route/params/version') - - try { - const resp = await axios.post( - `/clusters/${owner}/${cluster}/proxy/ui.kubedb.com/v1alpha1/databaseinfos`, - { - apiVersion: 'ui.kubedb.com/v1alpha1', - kind: 'DatabaseInfo', - request: { - source: { - ref: { - name: name, - namespace: namespace, - }, - resource: { - group: dbGroup, - kind: dbKind, - name: dbResource, - version: dbVersion, - }, - }, - keys: ['kubedb-user.cnf'], - }, - }, - ) - configSecrets = resp?.data?.response?.availableSecrets || [] - secretConfigData = resp?.data?.response?.configurations || [] - } catch (e) { - console.log(e) - } - - // Fetching all existing secrets - try { - const resp = await axios.get(`/clusters/${owner}/${cluster}/proxy/core/v1/secrets`) - resp.data?.items.forEach((item) => { - if (item.metadata?.name) { - existingSecrets.push(item.metadata.name) - } - }) - } catch (e) { - console.log(e) - } - } - - async function getConfigSecrets(type) { - type = type ? type + '/' : '' - const secretStatus = getValue(discriminator, `${type}createSecret/status`) - if (secretStatus === 'success') { - await fetchConfigSecrets() - } - const mappedSecrets = configSecrets.map((item) => { - return { text: item, value: item } - }) - mappedSecrets.push({ text: '+ Create a new Secret', value: 'Create' }) - return mappedSecrets - } - - async function getConfigSecretsforAppyConfig() { - const secrets = secretConfigData.map((item) => { - return { text: item.componentName, value: item.componentName } - }) - return secrets - } - - function getSelectedConfigurationData(type) { - type = type ? type + '/' : '' - const path = `/${type}selectedConfiguration` - const selectedConfiguration = getValue(discriminator, path) - - if (!selectedConfiguration) { - return [] - } - - const configuration = secretConfigData.find( - (item) => item.componentName === selectedConfiguration, - ) - - if (!configuration) { - return [] - } - - const result = [] - // Decode base64 and format as array of objects with name and content - Object.keys(configuration.data).forEach((fileName) => { - try { - // Decode base64 string - const decodedContent = atob(configuration.data[fileName]) - result.push({ - name: fileName, - content: decodedContent, - }) - } catch (e) { - console.error(`Error decoding ${fileName}:`, e) - result.push({ - name: fileName, - content: configuration.data[fileName], // Fallback to original if decode fails - }) - } - }) - - // Set the value to the model - commit('wizard/model$update', { - path: `/temp/${type}applyConfig`, - value: result, - force: true, - }) - - return result - } - - function getSelectedConfigurationName(configType, type) { - type = type ? type + '/' : '' - let path = '' - if (configType === 'create') path = `/spec/configuration/${type}/configSecret/name` - else if (configType === 'apply') path = `/${type}selectedConfiguration` - else if (configType === 'remove') path = `/${type}selectedConfigurationRemove` - - const selectedConfiguration = - configType === 'create' ? getValue(model, path) : getValue(discriminator, path) - - if (selectedConfiguration) - return { subtitle: ` You have selected ${selectedConfiguration} secret` } - else return { subtitle: 'No secret selected' } - } - - function getSelectedConfigurationValueForRemove(type) { - type = type ? type + '/' : '' - const path = `/${type}selectedConfigurationRemove` - const selectedConfiguration = getValue(discriminator, path) - - if (!selectedConfiguration) { - return '' - } - - const configuration = secretConfigData.find( - (item) => item.componentName === selectedConfiguration, - ) - - if (!configuration) { - return '' - } - - let data = {} - // Decode base64 and parse YAML for each key in the secret data - Object.keys(configuration.data).forEach((item) => { - try { - // Decode base64 string - const decodedString = atob(configuration.data[item]) - // Parse YAML string to object - const parsedYaml = yaml.load(decodedString) - // Store the parsed object with the filename as key - data[item] = parsedYaml - } catch (e) { - console.error(`Error parsing ${item}:`, e) - data[item] = atob(configuration.data[item]) // Fallback to decoded string - } - }) - - // Convert data object back to YAML string - return yaml.dump(data) - } - - async function createNewConfigSecret(type) { - type = type ? type + '/' : '' - const { user, cluster } = route.params - const url = `/clusters/${user}/${cluster}/resources` - const namespace = storeGet('/route/query/namespace') || getValue(model, '/metadata/namespace') - const secretName = getValue(discriminator, `${type}createSecret/name`) - const secretData = getValue(discriminator, `${type}createSecret/data`) - const secretDataObj = Object.fromEntries(secretData.map((item) => [item.key, item.value])) - - // Check uniqueness of secret name - if (existingSecrets.includes(secretName)) { - toast.error('A secret with this name already exists. Please choose another name.', { - timeout: 8000, - }) - return false - } - - try { - const res = await axios.post(url, { - apiVersion: 'v1', - stringData: secretDataObj, - kind: 'Secret', - metadata: { - name: secretName, - namespace: namespace, - }, - type: 'Opaque', - }) - commit('wizard/temp$update', { - path: `${type}createSecret/status`, - value: 'success', - }) - commit('wizard/temp$update', { - path: `${type}createSecret/lastCreatedSecret`, - value: secretName, - }) - toast.success('Secret created successfully') - } catch (error) { - const errMsg = decodeError(error, 'Failed to create secret') - toast.error(errMsg, { timeout: 5000 }) - cancelCreateSecret() - } - return true - } - - function decodeError(msg, defaultMsg) { - if (typeof msg === 'string') { - return msg || defaultMsg - } - return ( - (msg.response && msg.response.data && msg.response.data.message) || - (msg.response && msg.response.data) || - (msg.status && msg.status.status) || - defaultMsg - ) - } - - function isCreateSecret(type) { - type = type ? type + '/' : '' - const selectedSecret = getValue(model, `spec/configuration/${type}configSecret/name`) - const res = selectedSecret === 'Create' - - if (res === true) { - commit('wizard/temp$update', { - path: `${type}createSecret/status`, - value: 'pending', - }) - } - return res - } - - function isNotCreateSecret(type) { - return !isCreateSecret(type) - } - - function onCreateSecretChange(type) { - type = type ? type + '/' : '' - const secretStatus = getValue(discriminator, `${type}createSecret/status`) - if (secretStatus === 'cancelled') return '' - else if (secretStatus === 'success') { - const name = getValue(discriminator, `${type}createSecret/lastCreatedSecret`) - - const configFound = configSecrets.find((item) => item === name) - return configFound ? { text: name, value: name } : '' - } - } - - function cancelCreateSecret(type) { - type = type ? type + '/' : '' - commit('wizard/temp$delete', `${type}createSecret/name`) - commit('wizard/temp$delete', `${type}createSecret/data`) - commit('wizard/temp$update', { - path: `${type}createSecret/status`, - value: 'cancelled', - }) - } - - async function onApplyconfigChange(type) { - type = type ? type + '/' : '' - const configValue = getValue(discriminator, `${type}applyConfig`) - - if (!configValue) { - commit('wizard/model$delete', `/spec/configuration/${type}applyConfig`) - return - } - const tempConfigObj = {} - configValue.forEach((item) => { - if (item.name && item.content) { - tempConfigObj[item.name] = item.content - } - }) - if (Object.keys(tempConfigObj).length === 0) { - commit('wizard/model$delete', `/spec/configuration/${type}applyConfig`) - return - } - commit('wizard/model$update', { - path: `/spec/configuration/${type}applyConfig`, - value: tempConfigObj, - }) - } - - function setApplyConfig(type) { - type = type ? type + '/' : '' - const configPath = `/${type}selectedConfiguration` - const selectedConfig = getValue(discriminator, configPath) - if (!selectedConfig) { - return [{ name: '', content: '' }] - } - const applyconfigData = secretConfigData.find((item) => { - if (item.componentName === selectedConfig) { - return item - } - }) - const { applyConfig } = applyconfigData - const configObj = [] - - if (applyConfig) { - Object.keys(applyConfig).forEach((fileName) => { - configObj.push({ - name: fileName, - content: applyConfig[fileName], - }) - }) - } - configSecretKeys.forEach((key) => { - if (!configObj.find((item) => item.name === key)) { - configObj.push({ name: key, content: '' }) - } - }) - return configObj - } - - function onRemoveConfigChange(type) { - type = type ? type + '/' : '' - const configPath = `/${type}selectedConfigurationRemove` - const selectedConfig = getValue(discriminator, configPath) - - if (!selectedConfig) { - commit('wizard/model$delete', `/spec/configuration/${type}removeCustomConfig`) - return [{ name: '', content: '' }] - } - commit('wizard/model$update', { - path: `/spec/configuration/${type}removeCustomConfig`, - value: true, - }) - - const configuration = secretConfigData.find((item) => item.componentName === selectedConfig) - - if (!configuration.data) { - return [{ name: '', content: '' }] - } - - const configObj = [] - // Decode base64 and format as array of objects with name and content - Object.keys(configuration.data).forEach((fileName) => { - try { - // Decode base64 string - const decodedString = atob(configuration.data[fileName]) - configObj.push({ - name: fileName, - content: decodedString, - }) - } catch (e) { - console.error(`Error decoding ${fileName}:`, e) - configObj.push({ - name: fileName, - content: configuration.data[fileName], // Fallback to original if decode fails - }) - } - }) - return configObj - } - - async function onNewConfigSecretChange(type) { - type = type ? type + '/' : '' - const path = `/spec/configuration/${type}configSecret/name` - const selectedSecret = getValue(model, path) - - if (!selectedSecret) { - commit('wizard/model$delete', `/spec/configuration/${type}configSecret`) - return [{ name: '', content: '' }] - } - if (selectedSecret === 'Create') return [{ name: '', content: '' }] - - const owner = storeGet('/route/params/user') - const cluster = storeGet('/route/params/cluster') - const namespace = storeGet('/route/query/namespace') || getValue(model, '/metadata/namespace') - - try { - // Fetch the secret data from API - const secretResp = await axios.get( - `/clusters/${owner}/${cluster}/proxy/core/v1/namespaces/${namespace}/secrets/${selectedSecret}`, - ) - - const secretData = secretResp.data?.data || {} - const configObj = [] - - // Decode base64 and format as array of objects with name and content - Object.keys(secretData).forEach((fileName) => { - try { - // Decode base64 string - const decodedString = atob(secretData[fileName]) - configObj.push({ - name: fileName, - content: decodedString, - }) - } catch (e) { - console.error(`Error decoding ${fileName}:`, e) - configObj.push({ - name: fileName, - content: secretData[fileName], // Fallback to original if decode fails - }) - } - }) - - return configObj - } catch (e) { - console.error('Error fetching secret:', e) - return [{ name: '', content: '' }] - } - } - - function onSelectedSecretChange(index) { - const secretData = getValue(discriminator, 'createSecret/data') || [] - const selfSecrets = secretData.map((item) => item.key) - - const remainingSecrets = configSecretKeys.filter((item) => !selfSecrets.includes(item)) - - const selfKey = getValue(discriminator, `createSecret/data/${index}/key`) - if (selfKey) { - remainingSecrets.push(selfKey) - } - const resSecret = remainingSecrets.map((item) => { - return { text: item, value: item } - }) - return resSecret - } - - let secretArray = [] - - function createSecretUrl() { - const user = storeGet('/route/params/user') - const cluster = storeGet('/route/params/cluster') - - const domain = storeGet('/domain') || '' - if (domain.includes('bb.test')) { - return `http://console.bb.test:5990/console/${user}/kubernetes/${cluster}/core/v1/secrets/create` - } else { - const editedDomain = domain.replace('kubedb', 'console') - return `${editedDomain}/console/${user}/kubernetes/${cluster}/core/v1/secrets/create` - } - } - - function isConfigSelected() { - const secretName = getValue(model, '/spec/configuration/configSecret/name') - return !!secretName - } - - function isEqualToValueFromType(value) { - // watchDependency('discriminator#/valueFromType') - const valueFrom = getValue(discriminator, '/valueFromType') - return valueFrom === value - } - - async function getNamespacedResourceList({ namespace, group, version, resource }) { - const owner = storeGet('/route/params/user') - const cluster = storeGet('/route/params/cluster') - - const url = `/clusters/${owner}/${cluster}/proxy/${group}/${version}/namespaces/${namespace}/${resource}` - - let ans = [] - try { - const resp = await axios.get(url, { - params: { - filter: { items: { metadata: { name: null }, type: null } }, - }, - }) - - const items = (resp && resp.data && resp.data.items) || [] - ans = items - } catch (e) { - console.log(e) - } - - return ans - } - async function getResourceList({ group, version, resource }) { - const owner = storeGet('/route/params/user') - const cluster = storeGet('/route/params/cluster') - - const url = `/clusters/${owner}/${cluster}/proxy/${group}/${version}/${resource}` - - let ans = [] - try { - const resp = await axios.get(url, { - params: { - filter: { items: { metadata: { name: null }, type: null } }, - }, - }) - - const items = (resp && resp.data && resp.data.items) || [] - ans = items - } catch (e) { - console.log(e) - } - - return ans - } - async function resourceNames(group, version, resource) { - const namespace = getValue(model, '/metadata/namespace') - // watchDependency('model#/metadata/namespace') - - let resources = await getNamespacedResourceList({ - namespace, - group, - version, - resource, - }) - - if (resource === 'secrets') { - resources = resources.filter((item) => { - const validType = ['kubernetes.io/service-account-token', 'Opaque'] - return validType.includes(item.type) - }) - } - - return resources.map((resource) => { - const name = (resource.metadata && resource.metadata.name) || '' - return { - text: name, - value: name, - } - }) - } - async function unNamespacedResourceNames(group, version, resource) { - let resources = await getResourceList({ - group, - version, - resource, - }) - - if (resource === 'secrets') { - resources = resources.filter((item) => { - const validType = ['kubernetes.io/service-account-token', 'Opaque'] - return validType.includes(item.type) - }) - } - - return resources.map((resource) => { - const name = (resource.metadata && resource.metadata.name) || '' - return { - text: name, - value: name, - } - }) - } - - // reconfiguration type - function ifReconfigurationTypeEqualsTo(value) { - const reconfigurationType = getValue(discriminator, '/reconfigurationType') - // watchDependency('discriminator#/reconfigurationType') - - return reconfigurationType === value - } - - function onReconfigurationTypeChange() { - const reconfigurationType = getValue(discriminator, '/reconfigurationType') - setDiscriminatorValue('/applyConfig', []) - if (reconfigurationType === 'remove') { - commit('wizard/model$delete', `/spec/configuration`) - - commit('wizard/model$update', { - path: `/spec/configuration/removeCustomConfig`, - value: true, - force: true, - }) - } else { - commit('wizard/model$delete', `/spec/configuration/configSecret`) - commit('wizard/model$delete', `/spec/configuration/applyConfig`) - commit('wizard/model$delete', `/spec/configuration/removeCustomConfig`) - } - } - - // for tls - function hasTlsField() { - const tls = getDbTls() - - return !!tls - } - - function initIssuerRefApiGroup() { - const kind = getValue(model, '/spec/tls/issuerRef/kind') - // watchDependency('model#/spec/tls/issuerRef/kind') - - if (kind) { - const apiGroup = getValue(discriminator, '/dbDetails/spec/tls/issuerRef/apiGroup') - if (apiGroup) return apiGroup - return 'cert-manager.io' - } else return undefined - } - - async function getIssuerRefsName() { - const owner = storeGet('/route/params/user') - const cluster = storeGet('/route/params/cluster') - // watchDependency('model#/spec/tls/issuerRef/kind') - // watchDependency('model#/metadata/namespace') - const kind = getValue(model, '/spec/tls/issuerRef/kind') - const namespace = getValue(model, '/metadata/namespace') - - if (kind === 'Issuer') { - const url = `/clusters/${owner}/${cluster}/proxy/cert-manager.io/v1/namespaces/${namespace}/issuers` - return getIssuer(url) - } else if (kind === 'ClusterIssuer') { - const url = `/clusters/${owner}/${cluster}/proxy/charts.x-helm.dev/v1alpha1/clusterchartpresets/kubedb-ui-presets` - - let presets = storeGet('/kubedbuiPresets') || {} - if (!storeGet('/route/params/actions')) { - try { - const presetResp = await axios.get(url) - presets = presetResp.data?.spec?.values?.spec - } catch (e) { - console.log(e) - presets.status = String(e.status) - } - } - let clusterIssuers = presets.admin?.clusterIssuers?.available || [] - if (presets.status === '404') { - const url = `/clusters/${owner}/${cluster}/proxy/cert-manager.io/v1/clusterissuers` - return getIssuer(url) - } - return clusterIssuers - } else if (!kind) { - commit('wizard/model$delete', '/spec/tls/issuerRef/name') - return [] - } - - async function getIssuer(url) { - try { - const resp = await axios.get(url) - const resources = (resp && resp.data && resp.data.items) || [] - - resources.map((item) => { - const name = (item.metadata && item.metadata.name) || '' - item.text = name - item.value = name - return true - }) - return resources - } catch (e) { - console.log(e) - return [] - } - } - } - - function initTlsOperation() { - return 'update' - } - function onTlsOperationChange() { - const tlsOperation = getValue(discriminator, '/tlsOperation') - - commit('wizard/model$delete', '/spec/tls') - - if (tlsOperation === 'rotate') { - commit('wizard/model$update', { - path: '/spec/tls/rotateCertificates', - value: true, - force: true, - }) - commit('wizard/model$delete', '/spec/tls/certificates') - commit('wizard/model$delete', '/spec/tls/remove') - } else if (tlsOperation === 'remove') { - commit('wizard/model$update', { - path: '/spec/tls/remove', - value: true, - force: true, - }) - commit('wizard/model$delete', '/spec/tls/certificates') - commit('wizard/model$delete', '/spec/tls/rotateCertificates') - } - } - - function showIssuerRefAndCertificates() { - const tlsOperation = getValue(discriminator, '/tlsOperation') - // watchDependency('discriminator#/tlsOperation') - const verd = tlsOperation !== 'remove' && tlsOperation !== 'rotate' - - return verd - } - - function isIssuerRefRequired() { - const hasTls = hasTlsField() - return hasTls ? false : '' - } - - function getRequestTypeFromRoute() { - const isDbloading = isDbDetailsLoading() - const { query } = route || {} - const { requestType } = query || {} - return isDbloading ? '' : requestType || '' - } - - // ************************************** Set db details ***************************************** - - function isDbDetailsLoading() { - // watchDependency('discriminator#/dbDetails') - // watchDependency('model#/spec/databaseRef/name') - const dbDetails = getValue(discriminator, '/dbDetails') - const dbName = getValue(model, '/spec/databaseRef/name') - - return !dbDetails || !dbName - } - - function setValueFromDbDetails(path, commitPath) { - // watchDependency('discriminator#/dbDetails') - - const retValue = getValue(discriminator, `/dbDetails${path}`) - - if (commitPath && retValue) { - const tlsOperation = getValue(discriminator, '/tlsOperation') - - // computed called when tls fields is not visible - if (commitPath.includes('/spec/tls') && tlsOperation !== 'update') return undefined - - // direct model update required for reusable element. - // computed property is not applicable for reusable element - commit('wizard/model$update', { - path: commitPath, - value: retValue, - force: true, - }) - } - - return retValue || undefined - } - - function setConfigFiles() { - // watchDependency('model#/resources/secret_config/stringData') - const configFiles = getValue(model, '/resources/secret_config/stringData') - - const files = [] - - for (const item in configFiles) { - const obj = {} - obj.key = item - obj.value = configFiles[item] - files.push(obj) - } - - return files - } - - function getAliasOptions() { - return ['server', 'client', 'metrics-exporter'] - } - - function isNamespaceDisabled() { - const { namespace } = route.query || {} - return !!namespace - } - - function isDatabaseRefDisabled() { - const { name } = route.params || {} - return !!name - } - - function onNamespaceChange() { - commit('wizard/model$delete', '/spec/type') - } - - function onDbChange() { - commit('wizard/model$delete', '/spec/type') - getDbDetails() - } - - function setApplyToIfReady() { - return 'IfReady' - } - - function isVerticalScaleTopologyRequired() { - // watchDependency('discriminator#/topologyKey') - // watchDependency('discriminator#/topologyValue') - - const key = getValue(discriminator, '/topologyKey') - const value = getValue(discriminator, '/topologyValue') - const path = `/spec/verticalScaling/node/topology` - - if (key || value) { - commit('wizard/model$update', { - path: path, - value: { key, value }, - force: true, - }) - return '' - } else { - commit('wizard/model$delete', path) - return false - } - } - - function checkVolume(initpath, path) { - const volume = getValue(discriminator, `/dbDetails${initpath}`) - const input = getValue(model, path) - - try { - const sizeInBytes = parseSize(volume) - const inputSizeInBytes = parseSize(input) - - if (inputSizeInBytes >= sizeInBytes) return - else return 'Cannot expand to lower volume!' - } catch (err) { - return err.message || 'Invalid' - } - } - - function parseSize(sizeStr) { - const units = { - '': 1, - K: 1e3, - M: 1e6, - G: 1e9, - T: 1e12, - P: 1e15, - E: 1e18, - Ki: 1024, - Mi: 1024 ** 2, - Gi: 1024 ** 3, - Ti: 1024 ** 4, - Pi: 1024 ** 5, - Ei: 1024 ** 6, - } - - const match = String(sizeStr).match(/^([0-9]+(?:\.[0-9]*)?)\s*([A-Za-z]*)$/) - if (!match) throw new Error('Invalid size format') - - const value = parseFloat(match[1]) - const unit = match[2] - - if (!(unit in units)) - throw new Error('Unrecognized unit. Available units are K, Ki, M, Mi, G, Gi etc') - - return value * units[unit] - } - - function fetchAliasOptions() { - return getAliasOptions ? getAliasOptions() : [] - } - - function validateNewCertificates({ itemCtx }) { - const addedAliases = (model && model.map((item) => item.alias)) || [] - - if (addedAliases.includes(itemCtx.alias) && itemCtx.isCreate) { - return { isInvalid: true, message: 'Alias already exists' } - } - return {} - } - - function disableAlias() { - return !!(model && model.alias) - } - - function getSelectedConfigSecret(type) { - const path = `/spec/configuration/configSecret/name` - const selectedSecret = getValue(model, path) - // watchDependency(`model#${path}`) - return `You have selected ${selectedSecret} secret` || 'No secret selected' - } - - function objectToYaml(obj, indent = 0) { - if (obj === null || obj === undefined) return 'null' - if (typeof obj !== 'object') return JSON.stringify(obj) - - const spaces = ' '.repeat(indent) - - if (Array.isArray(obj)) { - return obj - .map((item) => `${spaces}- ${objectToYaml(item, indent + 1).trimStart()}`) - .join('\n') - } - - return Object.keys(obj) - .map((key) => { - const value = obj[key] - const keyLine = `${spaces}${key}:` - - if (value === null || value === undefined) { - return `${keyLine} null` - } - - if (typeof value === 'object') { - const nested = objectToYaml(value, indent + 1) - return `${keyLine}\n${nested}` - } - - if (typeof value === 'string') { - return `${keyLine} "${value}"` - } - - return `${keyLine} ${value}` - }) - .join('\n') - } - - function getSelectedConfigSecretValue(type) { - const path = `/spec/configuration/configSecret/name` - const selectedSecret = getValue(model, path) - let data - secretArray.forEach((item) => { - if (item.value === selectedSecret) { - data = objectToYaml(item.data).trim() || 'No Data Found' - } - }) - return data || 'No Data Found' - } - - function setExporter(type) { - let path = `/dbDetails/spec/monitor/prometheus/exporter/resources/limits/${type}` - const limitVal = getValue(discriminator, path) - - if (!limitVal) { - path = `/dbDetails/spec/monitor/prometheus/exporter/resources/requests/${type}` - const reqVal = getValue(discriminator, path) - - if (reqVal) return reqVal - } - return limitVal - } - - function onExporterResourceChange(type) { - const commitPath = `/spec/verticalScaling/exporter/resources/requests/${type}` - const valPath = `/spec/verticalScaling/exporter/resources/limits/${type}` - const val = getValue(model, valPath) - if (val) - commit('wizard/model$update', { - path: commitPath, - value: val, - force: true, - }) - } - - function isMachineValid() { - const dbDetails = getValue(discriminator, '/dbDetails') - const containers = dbDetails?.spec?.podTemplate?.spec?.containers || [] - const limits = containers[0]?.resources?.limits || {} - - const selectedMachine = getValue(discriminator, '/machine') - const selectedLimits = { cpu: selectedMachine.cpu, memory: selectedMachine.memory } - - if (JSON.stringify(limits) === JSON.stringify(selectedLimits)) { - return 'Resource limits are same as current machine configuration. Please select different resources or machine preset.' - } - return false - } - - return { - isMachineValid, - setExporter, - onExporterResourceChange, - fetchAliasOptions, - validateNewCertificates, - disableAlias, - isRancherManaged, - fetchJsons, - returnFalse, - getNamespaces, - getDbs, - getDbDetails, - getDbVersions, - getVersionInfo, - isVersionEmpty, - getVersion, - ifRequestTypeEqualsTo, - onRequestTypeChange, - getDbTls, - getDbType, - disableOpsRequest, - initNamespace, - initDatabaseRef, - showAndInitName, - showAndInitNamespace, - showAndInitDatabaseRef, - showConfigureOpsrequestLabel, - showAndInitOpsRequestType, - ifDbTypeEqualsTo, - getConfigSecrets, - getSelectedConfigSecret, - getSelectedConfigSecretValue, - createSecretUrl, - isEqualToValueFromType, - getNamespacedResourceList, - getResourceList, - resourceNames, - unNamespacedResourceNames, - ifReconfigurationTypeEqualsTo, - onReconfigurationTypeChange, - onApplyconfigChange, - hasTlsField, - initIssuerRefApiGroup, - getIssuerRefsName, - initTlsOperation, - onTlsOperationChange, - showIssuerRefAndCertificates, - isIssuerRefRequired, - getRequestTypeFromRoute, - isDbDetailsLoading, - setValueFromDbDetails, - getAliasOptions, - isDatabaseRefDisabled, - isNamespaceDisabled, - onNamespaceChange, - onDbChange, - setApplyToIfReady, - isVerticalScaleTopologyRequired, - getMachines, - setMachine, - onMachineChange, - isMachineCustom, - checkVolume, - setConfigFiles, - isConfigSelected, - fetchConfigSecrets, - getConfigSecretsforAppyConfig, - getSelectedConfigurationData, - getSelectedConfigurationName, - getSelectedConfigurationValueForRemove, - createNewConfigSecret, - decodeError, - isCreateSecret, - isNotCreateSecret, - onCreateSecretChange, - cancelCreateSecret, - setApplyConfig, - onRemoveConfigChange, - onNewConfigSecretChange, - onSelectedSecretChange, - isTlsEnabled, - } -} diff --git a/charts/opskubedbcom-oracleopsrequest-editor/ui/language.yaml b/charts/opskubedbcom-oracleopsrequest-editor/ui/language.yaml deleted file mode 100644 index 23733bffc3..0000000000 --- a/charts/opskubedbcom-oracleopsrequest-editor/ui/language.yaml +++ /dev/null @@ -1,307 +0,0 @@ -bn: {} -en: - labels: - agent: Select a Monitoring Method - oracle: Oracle - node_selection_policy: Node Selection Policy - topology: Topology - alias: Alias - annotations: - key: Key - label: Annotations - value: Value - api_group: API Group - apply: Apply - args: Args - backup: - invoker: Backup Invoker - title: Schedule a Backup? - backupBlueprint: - name: Blueprint Name - schedule: Schedule - taskParameters: Task Parameters - title: Backup Blueprint - backupConfiguration: - retentionPolicy: - keepLast: Keep Last - name: Name - prune: Prune - title: Retention Policy - schedule: Schedule - targetReference: - apiVersion: Target ApiVersion - kind: Target Kind - name: Reference Name - title: Target Reference - type: Reference Type - taskName: Task Name - title: Backup Configuration - basic_info: Basic Information - certificate: Certificate - certificates: Certificates - client_auth_mode: Client Auth Mode - cluster_ip: Cluster IP - configOptions: Configuration Options - configSecret: Config Secret - createConfig: Create Secret - configServer: Config Server - config_map_key: ConfigMap Key - config_map_name: ConfigMap Name - config_ops_request: Configure Ops Request - configuration: Configuration - configuration_files: Configuration Files - configuration_source: Configuration Source - controller_annotations: Controller Annotations - countries: Countries - country: Country - cpu: CPU - customize_exporter: Customize Exporter Sidecar - data_cold_node: DataCold Node - data_content_node: DataContent Node - data_frozen_node: DataFrozen Node - data_hot_node: DataHot Node - data_node: Data Node - data_warm_node: DataWarm Node - dataSource: DataWarm Source - database: - mode: Database Mode - name: Database Name - secret: Database Secret - version: Database Version - databaseRef: Select your database - dns_name: DNS Name - dns_names: DNS Names - duration: Duration - effect: Effect - enable_monitoring: Enable Monitoring - enable_ssl_question: Enable SSL? - enable_tls: Enable TLS - endpoint: Endpoint - endpoints: Endpoints - environmentVariablesFrom: Environemt Variables From - environment_variable: Environment Variable - environment_variables: Environment Variables - exporter: Exporter - exporter_configuration: Exporter Configuration - external_ip: External IP - external_ips: External IPs - external_traffic_policy: External Traffic Policy - fs_group: Fs Group - health_check_node_port: Health Check Node Port - honor_labels: Honor labels - image_pull_secrets: Image Pull Secrets - ingest_node: Ingest Node - initialization: Pre-populate your MongoDB from backup/another database - applyConfig: - key: Key - value: Value - label: Apply Config - interval: Interval - ip_address: IP Address - ip_addresses: IP Addresses - issuer_ref: Issuer Reference - key: Key - kind: Kind - labels: - key: Key - label: Labels - value: Value - level: Level - limit: Limit - limits: Limits - load_balancer_ip: Load Balancer IP - load_balancer_source_range: Load Balancer Source Range - load_balancer_source_ranges: Load Balancer Source Ranges - master_node: Master Node - match_expression: Match Expression - match_expressions: Match Expressions - match_field: Match Field - match_fields: Match Fields - memory: Memory - ml_node: ML Node - mongos: Mongos - name: Name - namespace: Namespace - new_secret_password: New Database Secret - node: Node - node_port: Node Port - node_selector: Node Selector - node_selector_terms: Node Selector Terms - op_req_name: Ops Request Name - operator: Operator - ops_request_type: Type of Ops Request - organization: Organization - organizational_unit: Organizational Unit - organizational_units: Organizational Units - organizations: Organizations - password: Password (Keep it empty to autogenerate) - path: Path - pod_annotations: Pod Annotations - pod_spec: Pod Spec - pod_template: Pod Template - port: Port - ports: Ports - postgres: Postgres - prePopulateDatabase: Do you want to pre-populate your database? - preferred_during_scheduling_ignored_during_execution: Preferred During Scheduling Ignored During Execution - prometheus: Prometheus - province: Province - provinces: Provinces - reconfigurationType: Reconfiguration Type - removeCustomConfig: Remove Custom Config? - renew_before: Renew Before - replicaSet: Replica Set - replicas: Replicas - replicaset: - name: Replicaset Name - number: Replica Number - repositories: - backend: - bucket: Bucket - container: Container - endPoint: End Point - maxConnections: Maximum Connections - mountPath: Mount Path - path: Path - prefix: Prefix - pvcName: Claim Name - region: Region - secret: Storage Secret - server: Server - subPath: Sub Path - title: Backend - type: Type - url: URL - volumeSource: Select Volume Source - choise: "" - name: Name - title: Repository - request: Request - requests: Requests - require_ssl_question: Require SSL? - resources: Resources - restoreSession: - name: Name - snapshot: Snapshot - title: Restore Session - role: Role - run_as_group: Run as Group - run_as_non_root: Run As Non Root? - run_as_user: Run as User - runtimeSettings: - choise: Customize Restore Job Runtime Settings? - container: - ionice: - class: Class - classData: Class Data - title: Ionice - nice: - adjustment: Adjustment - title: Nice - resources: - cpu: CPU - limits: Limits - memory: Memory - requests: Requests - title: Resources - title: Container Runtime Settings - pod: - imagePullSecrets: Image Pull Secrets - serviceAccountName: Service Account Name - title: Pod Runtime Settings - securityContext: - fsGroup: FS Group - privileged: Privileged - runAsGroup: Run As Group - runAsNonRoot: Run As Non Root - runAsUser: Run As User - seLinuxOptions: - level: LeveL - role: Role - title: SE Linux Options - type: Type - user: User - title: Security Context - scrapping_interval: Scrapping Interval - script: - path: Script Path - volume: Source Volume - volumeName: Name - volumeType: Type - se_linux_options: SE Linux Options - secret: Secret - secret_key: Secret Key - secret_name: Secret Name - security_context: Security Context - service_account_name: Service Account Name - service_monitor: Service Monitor - service_monitor_configuration: ServiceMonitor Configuration - service_template: Service Template - service_template_annotations: Service Template Annotations - service_templates: Service Templates - shard: Shard - shardNodes: Shard Nodes - shards: Shards - ssl_mode: SSL Mode - standalone: Standalone - storage: - class: Storage Class - size: Storage Size - master_size: Master Node - data_size: Data Node - ingest_size: Ingest Node - ml_size: ML Node - transform_size: Transform Node - data_cold_size: DataCold Node - data_content_size: DataContent Node - data_frozen_size: DataFrozen Node - data_hot_size: DataHot Node - data_warm_size: DataWarm Node - subject: Subject - targetVersion: Select Target Version - terminalPolicy: Terminal Policy - timeout: Timeout - timeout_seconds: Timeout Seconds - tls: Reconfigure TLS - tlsOperation: Choose TLS Operation - toleration: Toleration - toleration_seconds: Toleration in seconds - tolerations: Tolerations - transform_node: Transform Node - type: Type - user: User - value: Value - values: Values - waitForInitialRestore: Wait For Initial Restore? - weight: Weight - options: - HorizontalScaling: - description: Scale up or down pod count - text: Horizontal Scaling - Reconfigure: - description: Reconfigure your database - text: Reconfigure - ReconfigureTLS: - description: Reconfigure your database tls configuration - text: Reconfigure TLS - Restart: - description: Restart your database - text: Restart - Upgrade: - description: " Upgrade your database to any version" - text: Upgrade - UpdateVersion: - description: Update your database to any version - text: Update Version - VerticalScaling: - description: Manage your CPU resources - text: Vertical Scaling - VolumeExpansion: - description: Manage your database size - text: Volume Expansion - client_auth_mode: - md5: md5 - scram: scram - cert: cert - steps: - - label: Basic Information From c6f9fafea6af983a7f0111d4746e8397b85a456e Mon Sep 17 00:00:00 2001 From: Samiul Date: Fri, 22 May 2026 12:43:50 +0600 Subject: [PATCH 4/4] editor Signed-off-by: Samiul --- .../ui/create-ui.yaml | 966 +++++++++++++ .../ui/edit-ui.yaml | 631 ++++++++- .../ui/functions.js | 1208 +++++++++++++++-- .../ui/language.yaml | 66 +- 4 files changed, 2710 insertions(+), 161 deletions(-) diff --git a/charts/kubedbcom-clickhouse-editor/ui/create-ui.yaml b/charts/kubedbcom-clickhouse-editor/ui/create-ui.yaml index a50be45e97..8d016bd4ee 100644 --- a/charts/kubedbcom-clickhouse-editor/ui/create-ui.yaml +++ b/charts/kubedbcom-clickhouse-editor/ui/create-ui.yaml @@ -13,7 +13,973 @@ steps: schema: $ref: schema#/properties/metadata/properties/release/properties/name type: input + - add_new_button: + label: labels.add_new_namespace + target: _blank + url: + function: getCreateNameSpaceUrl + disabled: isVariantAvailable + fetch: getResources|core|v1|namespaces + label: + text: labels.namespace + onChange: onNamespaceChange + refresh: true + schema: + $ref: schema#/properties/metadata/properties/release/properties/namespace + type: select + - disableUnselect: true + fetch: getClickHouseVersions|catalog.kubedb.com|v1alpha1|clickhouseversions + label: + text: labels.database.version + schema: + $ref: schema#/properties/resources/properties/kubedbComClickHouse/properties/spec/properties/version + type: select + - individualItemDisabilityCheck: disableLableChecker + isArray: true + keys: + label: + text: labels.labels.key + label: + text: labels.labels.label + onChange: onLabelChange + schema: + $ref: schema#/properties/resources/properties/kubedbComClickHouse/properties/metadata/properties/labels + type: key-value-input-form + values: + label: + text: labels.labels.value + schema: + $ref: schema#/properties/resources/properties/kubedbComClickHouse/properties/metadata/properties/labels/additionalProperties + type: input + - isArray: true + keys: + label: + text: labels.annotations.key + label: + text: labels.annotations.label + schema: + $ref: schema#/properties/resources/properties/kubedbComClickHouse/properties/metadata/properties/annotations + type: key-value-input-form + values: + label: + text: labels.annotations.value + schema: + $ref: schema#/properties/resources/properties/kubedbComClickHouse/properties/metadata/properties/annotations/additionalProperties + type: input + - hasDescription: true + label: + text: labels.deletionPolicy + onChange: setStorageClass + options: + - description: options.deletionPolicy.delete.description + text: options.deletionPolicy.delete.label + value: Delete + - description: options.deletionPolicy.halt.description + text: options.deletionPolicy.halt.label + value: Halt + - description: options.deletionPolicy.wipeOut.description + text: options.deletionPolicy.wipeOut.label + value: WipeOut + - description: options.deletionPolicy.doNotTerminate.description + text: options.deletionPolicy.doNotTerminate.label + value: DoNotTerminate + schema: + $ref: schema#/properties/resources/properties/kubedbComClickHouse/properties/spec/properties/deletionPolicy + type: radio + - label: + text: labels.database.secret + type: label-element + - computed: getCreateAuthSecret + onChange: onCreateAuthSecretChange + options: + - text: options.database.secret.existingSecret.label + value: false + - text: options.database.secret.customSecret.label + value: true + schema: + $ref: discriminator#/createAuthSecret + type: radio + - allowUserDefinedOption: true + fetch: getSecrets + if: showExistingSecretSection + label: + text: labels.secret + schema: + $ref: schema#/properties/resources/properties/kubedbComClickHouse/properties/spec/properties/authSecret/properties/name + type: select + - computed: setAuthSecretPassword + hideValue: true + if: showPasswordSection + label: + text: Password + onChange: onAuthSecretPasswordChange + schema: + $ref: discriminator#/properties/password + type: input + - computed: setAddressType + label: + text: labels.use_address_type + options: + - text: DNS + value: DNS + - text: IP + value: IP + - text: IPv4 + value: IPv4 + - text: IPv6 + value: IPv6 + schema: + $ref: schema#/properties/resources/properties/kubedbComClickHouse/properties/spec/properties/useAddressType + type: select type: single-step-form id: basic title: steps.0.label +- form: + elements: + - alias: reusable_alert + chart: + name: uibytebuildersdev-component-alert + version: v0.30.0 + moduleResolver: fetchJsons + schema: + $ref: schema#/properties/form/properties/alert + type: reusable-element + type: single-step-form + id: alert + title: labels.alert +- form: + discriminator: + activeDatabaseMode: + default: Standalone + type: string + elements: + - computed: setDatabaseMode + hasDescription: true + label: + text: labels.database.mode + onChange: deleteDatabaseModePath + options: + - description: options.database.mode.Standalone.description + text: options.database.mode.Standalone.label + value: Standalone + - description: options.database.mode.GroupReplication.description + text: options.database.mode.GroupReplication.label + value: GroupReplication + - description: options.database.mode.InnoDBCluster.description + text: options.database.mode.InnoDBCluster.label + value: InnoDBCluster + schema: + $ref: discriminator#/activeDatabaseMode + type: radio + - if: isNotEqualToDatabaseMode|Standalone + label: + text: labels.replicaset.number + schema: + $ref: schema#/properties/resources/properties/kubedbComClickHouse/properties/spec/properties/replicas + type: input + - elements: + - fetch: getStorageClassNames + label: + text: labels.storage.class + schema: + $ref: schema#/properties/resources/properties/kubedbComClickHouse/properties/spec/properties/storage/properties/storageClassName + type: select + - label: + text: labels.storage.size + schema: + $ref: schema#/properties/resources/properties/kubedbComClickHouse/properties/spec/properties/storage/properties/resources/properties/requests/properties/storage + type: input + type: single-step-form + - elements: + - label: + text: labels.group_name + schema: + $ref: schema#/properties/resources/properties/kubedbComClickHouse/properties/spec/properties/topology/properties/group/properties/name + type: input + - label: + text: labels.group_mode + options: + - text: options.groupMode.SinglePrimary + value: Single-Primary + - text: options.groupMode.MultiPrimary + value: Multi-Primary + schema: + $ref: schema#/properties/resources/properties/kubedbComClickHouse/properties/spec/properties/topology/properties/group/properties/mode + type: radio + if: isEqualToDatabaseMode|GroupReplication + label: + text: labels.topology + show_label: true + type: single-step-form + type: single-step-form + id: topology + title: steps.1.label +- form: + discriminator: + configureTLS: + default: true + type: boolean + elements: + - computed: returnTrue + label: + text: labels.enable_tls + onChange: onTlsConfigureChange + schema: + $ref: discriminator#/configureTLS + type: switch + - elements: + - label: + text: labels.requireSSL_question + schema: + $ref: schema#/properties/resources/properties/kubedbComClickHouse/properties/spec/properties/requireSSL + type: switch + - elements: + - computed: setApiGroup + disabled: true + label: + text: labels.api_group + schema: + $ref: schema#/properties/resources/properties/kubedbComClickHouse/properties/spec/properties/tls/properties/issuerRef/properties/apiGroup + type: input + - label: + text: labels.kind + options: + - text: Issuer + value: Issuer + - text: ClusterIssuer + value: ClusterIssuer + schema: + $ref: schema#/properties/resources/properties/kubedbComClickHouse/properties/spec/properties/tls/properties/issuerRef/properties/kind + type: select + - allowUserDefinedOption: true + fetch: getIssuerRefsName + label: + text: labels.name + schema: + $ref: schema#/properties/resources/properties/kubedbComClickHouse/properties/spec/properties/tls/properties/issuerRef/properties/name + type: select + label: + text: labels.issuer_ref + schema: + $ref: schema#/properties/resources/properties/kubedbComClickHouse/properties/spec/properties/tls/properties/issuerRef + type: single-step-form + - alias: reusable_certificates + chart: + name: uibytebuildersdev-component-certificates + version: v0.30.0 + functionCallbacks: + getAliasOptions: + $ref: functions#/getAliasOptions + moduleResolver: fetchJsons + schema: + $ref: schema#/properties/resources/properties/kubedbComClickHouse/properties/spec/properties/tls/properties/certificates + type: reusable-element + if: showTlsConfigureSection + type: single-step-form + type: single-step-form + id: tls + title: steps.2.label +- form: + discriminator: + prePopulateDatabase: + type: string + elements: + - computed: returnStringYes + label: + text: labels.prePopulateDatabase + onChange: onPrePopulateDatabaseChange + options: + - text: options.yesOrNo.yes.text + value: "yes" + - text: options.yesOrNo.no.text + value: "no" + schema: + $ref: discriminator#/properties/prePopulateDatabase + type: radio + - discriminator: + dataSource: + type: string + elements: + - computed: initDataSource + label: + text: labels.dataSource + onChange: onDataSourceChange + options: + - text: options.dataSource.script.text + value: script + - text: options.dataSource.stashBackup.text + value: stashBackup + schema: + $ref: discriminator#/properties/dataSource + type: select + - discriminator: + sourceVolumeType: + type: string + elements: + - label: + text: labels.script.path + schema: + $ref: schema#/properties/resources/properties/kubedbComClickHouse/properties/spec/properties/init/properties/script/properties/scriptPath + type: input + - label: + text: labels.script.volume + type: label-element + - computed: initVolumeType + label: + text: labels.script.volumeType + onChange: onVolumeTypeChange + options: + - text: options.scriptSourceVolumeType.configMap.text + value: configMap + - text: options.scriptSourceVolumeType.secret.text + value: secret + schema: + $ref: discriminator#/properties/sourceVolumeType + type: select + - allowUserDefinedOption: true + fetch: resourceNames|core|v1|configmaps + if: showConfigMapOrSecretName|configMap + label: + text: labels.script.volumeName + schema: + $ref: schema#/properties/resources/properties/kubedbComClickHouse/properties/spec/properties/init/properties/script/properties/configMap/properties/name + type: select + - allowUserDefinedOption: true + fetch: resourceNames|core|v1|secrets + if: showConfigMapOrSecretName|secret + label: + text: labels.script.volumeName + schema: + $ref: schema#/properties/resources/properties/kubedbComClickHouse/properties/spec/properties/init/properties/script/properties/secret/properties/secretName + type: select + if: showScriptOrStashForm|script + type: single-step-form + - elements: + - label: + text: labels.restoreSession.snapshot + schema: + $ref: schema#/properties/resources/properties/stashAppscodeComRestoreSession_init/properties/spec/properties/rules/properties/0/properties/snapshots/properties/0 + type: input + - discriminator: + repositoryChoise: + type: string + elements: + - label: + text: labels.repositories.title + type: label-element + - computed: setInitialRestoreSessionRepo + onChange: onInitRepositoryChoiseChange + options: + - text: options.createOrSelect.select.text + value: select + - text: options.createOrSelect.create.text + value: create + schema: + $ref: discriminator#/properties/repositoryChoise + type: radio + - allowUserDefinedOption: true + fetch: resourceNames|stash.appscode.com|v1alpha1|repositories + if: showRepositorySelectOrCreate|select + label: + text: labels.repositories.name + schema: + $ref: schema#/properties/resources/properties/stashAppscodeComRestoreSession_init/properties/spec/properties/repository/properties/name + type: select + - alias: repository_create_init + chart: + name: uibytebuildersdev-component-repository-create + version: v0.30.0 + dataContext: + namespace: + $ref: schema#/properties/metadata/properties/release/properties/namespace + if: showRepositorySelectOrCreate|create + moduleResolver: fetchJsons + schema: + $ref: schema#/properties/resources/properties/stashAppscodeComRepository_init_repo/properties/spec/properties/backend + type: reusable-element + type: single-step-form + - if: returnFalse + label: + text: labels.backupConfiguration.targetReference.name + schema: + $ref: schema#/properties/resources/properties/stashAppscodeComRestoreSession_init/properties/spec/properties/target/properties/ref/properties/name + type: input + - discriminator: + customizeRestoreJobRuntimeSettings: + type: string + elements: + - computed: initCustomizeRestoreJobRuntimeSettings + label: + isSubsection: true + text: labels.runtimeSettings.choise + onChange: onCustomizeRestoreJobRuntimeSettingsChange + options: + - text: options.yesOrNo.yes.text + value: "yes" + - text: options.yesOrNo.no.text + value: "no" + schema: + $ref: discriminator#/properties/customizeRestoreJobRuntimeSettings + type: radio + - alias: runtime_settings_init + chart: + name: uibytebuildersdev-component-runtime-settings + version: v0.30.0 + dataContext: + namespace: + $ref: schema#/properties/metadata/properties/release/properties/namespace + if: showRuntimeForm|yes + moduleResolver: fetchJsons + schema: + $ref: schema#/properties/resources/properties/stashAppscodeComRestoreSession_init/properties/spec/properties/runtimeSettings + type: reusable-element + type: single-step-form + if: showScriptOrStashForm|stashBackup + type: single-step-form + - if: returnFalse + label: + text: labels.waitForInitialRestore + schema: + $ref: schema#/properties/resources/properties/kubedbComClickHouse/properties/spec/properties/init/properties/waitForInitialRestore + type: switch + if: showInitializationForm + type: single-step-form + type: single-step-form + id: initialization + title: steps.3.label +- form: + discriminator: + scheduleBackup: + type: string + elements: + - computed: returnStringYes + label: + text: labels.backup.title + onChange: onScheduleBackupChange + options: + - text: options.yesOrNo.yes.text + value: "yes" + - text: options.yesOrNo.no.text + value: "no" + schema: + $ref: discriminator#/properties/scheduleBackup + type: radio + - discriminator: + backupInvoker: + type: string + elements: + - computed: initBackupInvoker + label: + text: labels.backup.invoker + onChange: onBackupInvokerChange + options: + - text: Backup Configuration + value: backupConfiguration + - text: Backup Blueprint + value: backupBlueprint + schema: + $ref: discriminator#/properties/backupInvoker + type: select + - discriminator: + targetType: + type: string + elements: + - discriminator: + repositoryChoise: + type: string + elements: + - label: + isSubsection: true + text: labels.repositories.title + type: label-element + - computed: initRepositoryChoise + onChange: onRepositoryChoiseChange + options: + - text: options.createOrSelect.select.text + value: select + - text: options.createOrSelect.create.text + value: create + schema: + $ref: discriminator#/properties/repositoryChoise + type: radio + - allowUserDefinedOption: true + fetch: resourceNames|stash.appscode.com|v1alpha1|repositories + if: showRepositorySelectOrCreate|select + label: + text: labels.repositories.title + schema: + $ref: schema#/properties/resources/properties/stashAppscodeComBackupConfiguration/properties/spec/properties/repository/properties/name + type: select + - alias: repository_create_backup + chart: + name: uibytebuildersdev-component-repository-create + version: v0.30.0 + dataContext: + namespace: + $ref: schema#/properties/metadata/properties/release/properties/namespace + if: showRepositorySelectOrCreate|create + moduleResolver: fetchJsons + schema: + $ref: schema#/properties/resources/properties/stashAppscodeComRepository_repo/properties/spec/properties/backend + type: reusable-element + type: single-step-form + - if: returnFalse + label: + text: labels.backupConfiguration.targetReference.name + schema: + $ref: schema#/properties/resources/properties/stashAppscodeComBackupConfiguration/properties/spec/properties/target/properties/ref/properties/name + type: input + - label: + text: labels.backupConfiguration.schedule + schema: + $ref: schema#/properties/resources/properties/stashAppscodeComBackupConfiguration/properties/spec/properties/schedule + type: input + - label: + isSubsection: true + text: labels.backupConfiguration.retentionPolicy.title + type: label-element + - label: + text: labels.backupConfiguration.retentionPolicy.name + schema: + $ref: schema#/properties/resources/properties/stashAppscodeComBackupConfiguration/properties/spec/properties/retentionPolicy/properties/name + type: input + - label: + text: labels.backupConfiguration.retentionPolicy.keepLast + schema: + $ref: schema#/properties/resources/properties/stashAppscodeComBackupConfiguration/properties/spec/properties/retentionPolicy/properties/keepLast + type: input + - label: + text: labels.backupConfiguration.retentionPolicy.keepHourly + schema: + $ref: schema#/properties/resources/properties/stashAppscodeComBackupConfiguration/properties/spec/properties/retentionPolicy/properties/keepHourly + type: input + - label: + text: labels.backupConfiguration.retentionPolicy.keepDaily + schema: + $ref: schema#/properties/resources/properties/stashAppscodeComBackupConfiguration/properties/spec/properties/retentionPolicy/properties/keepDaily + type: input + - label: + text: labels.backupConfiguration.retentionPolicy.keepWeekly + schema: + $ref: schema#/properties/resources/properties/stashAppscodeComBackupConfiguration/properties/spec/properties/retentionPolicy/properties/keepWeekly + type: input + - label: + text: labels.backupConfiguration.retentionPolicy.keepMonthly + schema: + $ref: schema#/properties/resources/properties/stashAppscodeComBackupConfiguration/properties/spec/properties/retentionPolicy/properties/keepMonthly + type: input + - label: + text: labels.backupConfiguration.retentionPolicy.keepYearly + schema: + $ref: schema#/properties/resources/properties/stashAppscodeComBackupConfiguration/properties/spec/properties/retentionPolicy/properties/keepYearly + type: input + - if: returnFalse + label: + text: labels.backupConfiguration.retentionPolicy.prune + schema: + $ref: schema#/properties/resources/properties/stashAppscodeComBackupConfiguration/properties/spec/properties/retentionPolicy/properties/prune + type: switch + - discriminator: + customizeRestoreJobRuntimeSettings: + type: string + elements: + - computed: initCustomizeRestoreJobRuntimeSettingsForBackup + label: + isSubsection: true + text: labels.runtimeSettings.choiseForBackup + onChange: onCustomizeRestoreJobRuntimeSettingsChangeForBackup + options: + - text: options.yesOrNo.yes.text + value: "yes" + - text: options.yesOrNo.no.text + value: "no" + schema: + $ref: discriminator#/properties/customizeRestoreJobRuntimeSettings + type: radio + - alias: runtime_settings_backup + chart: + name: uibytebuildersdev-component-runtime-settings + version: v0.30.0 + dataContext: + namespace: + $ref: schema#/properties/metadata/properties/release/properties/namespace + if: showRuntimeForm|yes + moduleResolver: fetchJsons + schema: + $ref: schema#/properties/resources/properties/stashAppscodeComBackupConfiguration/properties/spec/properties/runtimeSettings + type: reusable-element + type: single-step-form + if: showInvokerForm|backupConfiguration + label: + text: Backup Configuration form + type: single-step-form + - discriminator: + backupBlueprintName: + type: string + schedule: + type: string + taskParameters: + additionalProperties: + type: string + type: object + elements: + - label: + text: labels.backupBlueprint.title + type: label-element + - allowUserDefinedOption: true + computed: initFromAnnotationValue|stash.appscode.com/backup-blueprint + fetch: unNamespacedResourceNames|stash.appscode.com|v1beta1|backupblueprints + label: + text: labels.backupBlueprint.name + onChange: onBackupBlueprintNameChange + schema: + $ref: discriminator#/properties/backupBlueprintName + type: select + - computed: initFromAnnotationValue|stash.appscode.com/schedule + label: + text: labels.backupBlueprint.schedule + onChange: onBackupBlueprintScheduleChange + schema: + $ref: discriminator#/properties/schedule + type: input + - computed: initFromAnnotationKeyValue|params.stash.appscode.com/ + isArray: true + keys: + label: + text: Key + label: + text: labels.backupBlueprint.taskParameters + onChange: onTaskParametersChange + schema: + $ref: discriminator#/properties/taskParameters + type: key-value-input-form + values: + label: + text: Value + schema: + $ref: discriminator#/properties/taskParameters/additionalProperties + type: input + if: showInvokerForm|backupBlueprint + label: + text: Backup Blueprint form + type: single-step-form + if: showBackupForm + label: + text: Backup Form + type: single-step-form + type: single-step-form + id: backupconfiguration + title: steps.4.label +- form: + discriminator: + enableMonitoring: + default: true + type: boolean + elements: + - computed: returnTrue + label: + text: labels.enable_monitoring + onChange: onEnableMonitoringChange + schema: + $ref: discriminator#/enableMonitoring + type: switch + - discriminator: + customizeExporter: + default: true + type: boolean + elements: + - hasDescription: true + label: + text: labels.agent + onChange: onAgentChange + options: + - description: options.agent.prometheus_operator.description + text: options.agent.prometheus_operator.label + value: prometheus.io/operator + - description: options.agent.prometheus.description + text: options.agent.prometheus.label + value: prometheus.io + - description: options.agent.prometheus_builtin.description + text: options.agent.prometheus_builtin.label + value: prometheus.io/builtin + schema: + $ref: schema#/properties/resources/properties/kubedbComClickHouse/properties/spec/properties/monitor/properties/agent + type: radio + - elements: + - label: + text: labels.scrapping_interval + schema: + $ref: schema#/properties/resources/properties/kubedbComClickHouse/properties/spec/properties/monitor/properties/prometheus/properties/serviceMonitor/properties/interval + type: input + if: isEqualToModelPathValue|prometheus.io/operator|/resources/kubedbComClickHouse/spec/monitor/agent + label: + text: labels.service_monitor_configuration + show_label: true + type: single-step-form + - elements: + - elements: + - addFormLabel: labels.endpoint + element: + elements: + - label: + text: labels.honor_labels + schema: + $ref: schema#/properties/resources/properties/monitoringCoreosComServiceMonitor/properties/spec/properties/endpoints/items/properties/honorLabels + type: switch + - label: + text: labels.interval + schema: + $ref: schema#/properties/resources/properties/monitoringCoreosComServiceMonitor/properties/spec/properties/endpoints/items/properties/interval + type: input + - label: + text: labels.path + schema: + $ref: schema#/properties/resources/properties/monitoringCoreosComServiceMonitor/properties/spec/properties/endpoints/items/properties/path + type: input + - label: + text: labels.port + schema: + $ref: schema#/properties/resources/properties/monitoringCoreosComServiceMonitor/properties/spec/properties/endpoints/items/properties/port + type: input + type: single-step-form + label: + text: labels.endpoints + schema: + $ref: schema#/properties/resources/properties/monitoringCoreosComServiceMonitor/properties/spec/properties/endpoints + tableContents: + - inTableColumn: true + label: + text: labels.honor_labels + path: honorLabels + type: value + typeOfValue: string + - inTableColumn: true + label: + text: labels.interval + path: interval + type: value + typeOfValue: string + - inTableColumn: true + label: + text: labels.path + path: path + type: value + typeOfValue: string + - inTableColumn: true + label: + text: labels.port + path: port + type: value + typeOfValue: string + type: single-step-form-array + - elements: + - fetch: getResources|core|v1|namespaces + label: + text: labels.matchNames + schema: + $ref: schema#/properties/resources/properties/monitoringCoreosComServiceMonitor/properties/spec/properties/namespaceSelector/properties/matchNames + type: multiselect + if: returnFalse + schema: + $ref: schema#/properties/resources/properties/monitoringCoreosComServiceMonitor/properties/spec/properties/namespaceSelector + type: single-step-form + - elements: + - isArray: true + keys: + label: + text: labels.labels.key + label: + text: labels.labels.label + schema: + $ref: schema#/properties/resources/properties/monitoringCoreosComServiceMonitor/properties/spec/properties/selector/properties/matchLabels + type: key-value-input-form + values: + label: + text: labels.labels.value + schema: + $ref: schema#/properties/resources/properties/monitoringCoreosComServiceMonitor/properties/spec/properties/selector/properties/matchLabels/additionalProperties + type: input + if: returnFalse + schema: + $ref: schema#/properties/resources/properties/monitoringCoreosComServiceMonitor/properties/spec/properties/selector + type: single-step-form + schema: + $ref: schema#/properties/resources/properties/monitoringCoreosComServiceMonitor/properties/spec + type: single-step-form + if: isEqualToModelPathValue|prometheus.io|/resources/kubedbComClickHouse/spec/monitor/agent + label: + text: labels.service_monitor + schema: + $ref: schema#/properties/resources/properties/monitoringCoreosComServiceMonitor + show_label: true + type: single-step-form + - if: isEqualToModelPathValue|prometheus.io|/resources/kubedbComClickHouse/spec/monitor/agent + individualItemDisabilityCheck: disableLableChecker + isArray: true + keys: + label: + text: labels.labels.key + label: + text: labels.labels.label + schema: + $ref: schema#/properties/resources/properties/monitoringCoreosComServiceMonitor/properties/metadata/properties/labels + type: key-value-input-form + values: + label: + text: labels.labels.value + schema: + $ref: schema#/properties/resources/properties/monitoringCoreosComServiceMonitor/properties/metadata/properties/labels/additionalProperties + type: input + - label: + text: labels.exporter_configuration + type: label-element + - label: + text: labels.customize_exporter + onChange: onCustomizeExporterChange + schema: + $ref: discriminator#/customizeExporter + type: switch + - elements: + - label: + text: labels.resources + schema: + $ref: schema#/properties/resources/properties/kubedbComClickHouse/properties/spec/properties/monitor/properties/prometheus/properties/exporter/properties/resources + type: resource-input-form + - label: + text: labels.security_context + type: label-element + - label: + text: labels.run_as_user + schema: + $ref: schema#/properties/resources/properties/kubedbComClickHouse/properties/spec/properties/monitor/properties/prometheus/properties/exporter/properties/securityContext/properties/runAsUser + type: input + - customClass: mb-0 + label: + text: labels.run_as_group + schema: + $ref: schema#/properties/resources/properties/kubedbComClickHouse/properties/spec/properties/monitor/properties/prometheus/properties/exporter/properties/securityContext/properties/runAsGroup + type: input + - label: + text: labels.port + schema: + $ref: schema#/properties/resources/properties/kubedbComClickHouse/properties/spec/properties/monitor/properties/prometheus/properties/exporter/properties/port + type: input + - element: + label: + isSubsection: true + text: labels.args + schema: + $ref: schema#/properties/resources/properties/kubedbComClickHouse/properties/spec/properties/monitor/properties/prometheus/properties/exporter/properties/args/items + type: input + label: + text: labels.args + schema: + $ref: schema#/properties/resources/properties/kubedbComClickHouse/properties/spec/properties/monitor/properties/prometheus/properties/exporter/properties/args + type: list-input-form + - alias: reusable_env + chart: + name: uibytebuildersdev-component-env + version: v0.30.0 + dataContext: + namespace: + $ref: schema#/properties/metadata/properties/release/properties/namespace + moduleResolver: fetchJsons + schema: + $ref: schema#/properties/resources/properties/kubedbComClickHouse/properties/spec/properties/monitor/properties/prometheus/properties/exporter/properties/env + type: reusable-element + if: showCustomizeExporterSection + type: single-step-form + if: showMonitoringSection + type: single-step-form + type: single-step-form + id: monitoring + title: steps.5.label +- form: + elements: + - alias: pod_template_standalone + chart: + name: uibytebuildersdev-component-pod-template + version: v0.30.0 + dataContext: + namespace: + $ref: schema#/properties/metadata/properties/release/properties/namespace + moduleResolver: fetchJsons + schema: + $ref: schema#/properties/resources/properties/kubedbComClickHouse/properties/spec/properties/podTemplate + type: reusable-element + type: single-step-form + id: pod-template + title: steps.6.label +- form: + elements: + - alias: reusable_service_templates + chart: + name: uibytebuildersdev-component-service-templates + version: v0.30.0 + moduleResolver: fetchJsons + schema: + $ref: schema#/properties/resources/properties/kubedbComClickHouse/properties/spec/properties/serviceTemplates + type: reusable-element + type: single-step-form + id: networking + title: steps.7.label +- form: + elements: + - discriminator: + setCustomConfig: + type: string + elements: + - computed: returnStringYes + label: + text: labels.setCustomConfig + onChange: onSetCustomConfigChange + options: + - text: options.yesOrNo.yes.text + value: "yes" + - text: options.yesOrNo.no.text + value: "no" + schema: + $ref: discriminator#/properties/setCustomConfig + type: radio + - discriminator: + configuration: + type: string + configurationSource: + default: use-existing-config + type: string + elements: + - computed: setConfigurationSource + label: + text: labels.custom_config + onChange: onConfigurationSourceChange + options: + - text: options.configuration_source.use_existing_config.label + value: use-existing-config + - text: options.configuration_source.create_new_config.label + value: create-new-config + schema: + $ref: discriminator#/configurationSource + type: radio + - allowUserDefinedOption: true + fetch: getSecrets + if: isEqualToDiscriminatorPath|use-existing-config|/configurationSource + label: + text: labels.name + schema: + $ref: schema#/properties/resources/properties/kubedbComClickHouse/properties/spec/properties/configSecret/properties/name + type: select + - computed: setConfiguration + if: isEqualToDiscriminatorPath|create-new-config|/configurationSource + label: + text: labels.my_config_cnf + onChange: onConfigurationChange + schema: + $ref: discriminator#/properties/configuration + type: editor + if: isEqualToDiscriminatorPath|yes|/setCustomConfig + type: single-step-form + type: single-step-form + type: single-step-form + id: custom-config + title: steps.8.label type: multi-step-form diff --git a/charts/kubedbcom-clickhouse-editor/ui/edit-ui.yaml b/charts/kubedbcom-clickhouse-editor/ui/edit-ui.yaml index 35aa46c455..9bd792e8f8 100644 --- a/charts/kubedbcom-clickhouse-editor/ui/edit-ui.yaml +++ b/charts/kubedbcom-clickhouse-editor/ui/edit-ui.yaml @@ -1,4 +1,150 @@ step: +- elements: + - if: + name: showScheduleBackup + type: function + init: + type: func + value: initScheduleBackupForEdit + isHorizontal: true + label: Schedule a Backup? + options: + - text: "Yes" + value: "yes" + - text: "No" + value: "no" + schema: temp/properties/scheduleBackup + type: radio + watcher: + func: onScheduleBackupChange + paths: + - temp/properties/scheduleBackup + - elements: + - if: + name: isBackupDataLoadedTrue + type: function + init: + type: func + value: setBackupType + isHorizontal: true + label: Select Backup Type + loader: getTypes + options: + - description: Create, Delete or Modify BackupConfig + text: BackupConfig + value: BackupConfig + - description: Enable/Disable BackupBlueprint + text: BackupBlueprint + value: BackupBlueprint + schema: temp/properties/backupType + type: radio + watcher: + func: onBackupTypeChange + paths: + - temp/properties/backupType + - elements: + - label: Select Context + loader: getContext + schema: temp/properties/backupConfigContext + type: select + validation: + type: required + watcher: + func: onContextChange + paths: + - temp/properties/backupConfigContext + - if: + name: showConfigList + type: function + label: Select BackupConfig + loader: getConfigList + schema: temp/properties/config + type: select + validation: + type: required + watcher: + func: onConfigChange + paths: + - temp/properties/config + - if: + name: showSchedule + type: function + label: Schedule + loader: + name: getDefaultSchedule|/resources/coreKubestashComBackupConfiguration/spec/sessions + watchPaths: + - temp/properties/config + schema: temp/properties/schedule + type: input + validation: + type: required + watcher: + func: onInputChangeSchedule|/resources/coreKubestashComBackupConfiguration/spec/sessions|schedule + paths: + - temp/properties/schedule + - fullwidth: true + if: + name: showPause + type: function + init: + type: func + value: setPausedValue + label: Paused + schema: schema/properties/resources/properties/coreKubestashComBackupConfiguration/properties/spec/properties/paused + type: switch + watcher: + func: setPausedValue + paths: + - temp/properties/config + if: + name: isBackupType|BackupConfig + type: function + label: Config + type: block-layout + - elements: + - fullwidth: true + init: + type: func + value: setBlueprintSwitch + label: Enable Backup Blueprint + schema: temp/properties/blueprintEnabled + type: switch + watcher: + func: onBlueprintChange + paths: + - temp/properties/blueprintEnabled + if: + name: isBackupType|BackupBlueprint + type: function + label: Blueprint + type: block-layout + - elements: + - fullwidth: true + init: + type: func + value: setArchiverSwitch + label: Enable Archiver + schema: temp/properties/archiverEnabled + type: switch + watcher: + func: onArchiverChange + paths: + - temp/properties/archiverEnabled + if: + name: isBackupType|Archiver + type: function + label: Archiver + type: block-layout + if: + name: showBackupForm + type: function + label: Backup Form + loader: initBackupData + showLabels: false + type: block-layout + id: backupconfiguration + schema: schema/ + type: single-step-form - elements: - elements: - label: Name @@ -16,7 +162,7 @@ step: paths: - schema/properties/resources/properties/autoscalingKubedbComClickHouseAutoscaler/properties/metadata/properties/namespace - label: Select Db - loader: getDbs + loader: getClickHouseDbs refresh: true schema: schema/properties/resources/properties/autoscalingKubedbComClickHouseAutoscaler/properties/spec/properties/databaseRef/properties/name type: select @@ -50,44 +196,98 @@ step: paths: - temp/properties/autoscalingType - elements: + - fullwidth: true + init: + type: func + value: setTrigger|autoscalingKubedbComClickHouseAutoscaler/spec/compute/clickhouse/trigger + label: Trigger + schema: temp/properties/compute/properties/clickhouse/properties/trigger + type: switch + watcher: + func: onTriggerChange|compute/clickhouse + paths: + - temp/properties/compute/properties/clickhouse/properties/trigger + - label: Pod lifetime threshold + subtitle: Specifies the duration a pod can exist before using considered for + scaling decisions, ensuring resource optimization and workload stability + type: label-element + - customClass: width-300 + label: Pod LifeTime Threshold (e.g., 10m 30s) + schema: schema/properties/resources/properties/autoscalingKubedbComClickHouseAutoscaler/properties/spec/properties/compute/properties/clickhouse/properties/podLifeTimeThreshold + type: input - elements: - - init: - type: func - value: setTrigger|autoscalingKubedbComClickHouseAutoscaler/spec/compute/clickhouse/trigger - label: Trigger - options: - - text: "On" - value: "On" - - text: "Off" - value: "Off" - schema: schema/properties/resources/properties/autoscalingKubedbComClickHouseAutoscaler/properties/spec/properties/compute/properties/clickhouse/properties/trigger - type: select - - label: Pod LifeTime Threshold - schema: schema/properties/resources/properties/autoscalingKubedbComClickHouseAutoscaler/properties/spec/properties/compute/properties/clickhouse/properties/podLifeTimeThreshold - type: input - - label: Resource Diff Percentage + - customClass: width-300 + label: ResourceDiff Percentage schema: schema/properties/resources/properties/autoscalingKubedbComClickHouseAutoscaler/properties/spec/properties/compute/properties/clickhouse/properties/resourceDiffPercentage type: threshold-input + - label: Resource Configuration + subtitle: Define minimum and maximum allowed resources to ensure optimal database + performance + type: label-element - elements: + - header: Minimum Resource Limit + if: + name: hasAnnotations + type: function + init: + type: func + value: setAllowedMachine|clickhouse|min + label: Min Allowed Profile + loader: + name: getMachines|clickhouse|min + watchPaths: + - temp/properties/topologyMachines + - temp/properties/allowedMachine-max + schema: temp/properties/allowedMachine-min + type: machine-compare + watcher: + func: onMachineChange|clickhouse + paths: + - temp/properties/allowedMachine-min - elements: - label: Cpu schema: schema/properties/resources/properties/autoscalingKubedbComClickHouseAutoscaler/properties/spec/properties/compute/properties/clickhouse/properties/minAllowed/properties/cpu - type: input + type: input-compare - label: Memory schema: schema/properties/resources/properties/autoscalingKubedbComClickHouseAutoscaler/properties/spec/properties/compute/properties/clickhouse/properties/minAllowed/properties/memory - type: input + type: input-compare fixedBlock: true + if: + name: hasNoAnnotations + type: function label: Min Allowed showLabels: true type: block-layout + - header: Maximum Resource Limit + if: + name: hasAnnotations + type: function + init: + type: func + value: setAllowedMachine|clickhouse|max + label: Max Allowed Profile + loader: + name: getMachines|clickhouse|max + watchPaths: + - temp/properties/topologyMachines + - temp/properties/allowedMachine-min + schema: temp/properties/allowedMachine-max + type: machine-compare + watcher: + func: onMachineChange|clickhouse + paths: + - temp/properties/allowedMachine-max - elements: - label: Cpu schema: schema/properties/resources/properties/autoscalingKubedbComClickHouseAutoscaler/properties/spec/properties/compute/properties/clickhouse/properties/maxAllowed/properties/cpu - type: input + type: input-compare - label: Memory schema: schema/properties/resources/properties/autoscalingKubedbComClickHouseAutoscaler/properties/spec/properties/compute/properties/clickhouse/properties/maxAllowed/properties/memory - type: input + type: input-compare fixedBlock: true + if: + name: hasNoAnnotations + type: function label: Max Allowed showLabels: true type: block-layout @@ -98,11 +298,32 @@ step: multiple: true schema: schema/properties/resources/properties/autoscalingKubedbComClickHouseAutoscaler/properties/spec/properties/compute/properties/clickhouse/properties/controlledResources type: select + - label: Container Controlled Values + options: + - text: Requests And Limits + value: RequestsAndLimits + - text: Requests Only + value: RequestsOnly + schema: schema/properties/resources/properties/autoscalingKubedbComClickHouseAutoscaler/properties/spec/properties/compute/properties/clickhouse/properties/containerControlledValues + type: select + - elements: + - label: Scaling Factor Percentage + schema: schema/properties/resources/properties/autoscalingKubedbComClickHouseAutoscaler/properties/spec/properties/compute/properties/clickhouse/properties/inMemoryStorage/properties/scalingFactorPercentage + type: threshold-input + - label: Usage Threshold Percentage + schema: schema/properties/resources/properties/autoscalingKubedbComClickHouseAutoscaler/properties/spec/properties/compute/properties/clickhouse/properties/inMemoryStorage/properties/usageThresholdPercentage + type: threshold-input + if: + name: showStorageMemoryOption + type: function + label: In Memory Storage + showLabels: true + type: block-layout label: ClickHouse showLabels: true type: block-layout - elements: - - label: Select Node Topology + - label: Select NodeTopology loader: fetchNodeTopology schema: schema/properties/resources/properties/autoscalingKubedbComClickHouseAutoscaler/properties/spec/properties/compute/properties/nodeTopology/properties/name type: select @@ -118,6 +339,9 @@ step: label: ScaleDown Diff Percentage schema: schema/properties/resources/properties/autoscalingKubedbComClickHouseAutoscaler/properties/spec/properties/compute/properties/nodeTopology/properties/scaleDownDiffPercentage type: threshold-input + if: + name: hasNoAnnotations + type: function label: Node Topology showLabels: true type: block-layout @@ -142,56 +366,62 @@ step: label: Ops Request Options showLabels: true type: block-layout + loader: fetchTopologyMachines showLabels: false type: block-layout id: compute-autoscaler - loader: getDbDetails + loader: getClickHouseDbs type: single-step-form - elements: - elements: + - fullwidth: true + init: + type: func + value: setTrigger|autoscalingKubedbComClickHouseAutoscaler/spec/storage/clickhouse/trigger + label: Trigger + schema: temp/properties/storage/properties/clickhouse/properties/trigger + type: switch + watcher: + func: onTriggerChange|storage/clickhouse + paths: + - temp/properties/storage/properties/clickhouse/properties/trigger + - label: Expansion Mode (Online/Offline) + subtitle: For Online Mode enables scaling without downtime by dynamically resizing + storage while the database remains operational and Offline Mode performs storage + scaling during scheduled downtime, ensuring consistency but requiring database + restarts. + type: label-element + - description: Select how the storage expansion should be handled. + label: Mode + options: + - text: Online + value: Online + - text: Offline + value: Offline + schema: schema/properties/resources/properties/autoscalingKubedbComClickHouseAutoscaler/properties/spec/properties/storage/properties/clickhouse/properties/expansionMode + type: select - elements: - - elements: - - init: - type: func - value: setTrigger|autoscalingKubedbComClickHouseAutoscaler/spec/storage/clickhouse/trigger - label: Trigger - options: - - text: "On" - value: "On" - - text: "Off" - value: "Off" - schema: schema/properties/resources/properties/autoscalingKubedbComClickHouseAutoscaler/properties/spec/properties/storage/properties/clickhouse/properties/trigger - type: select - - label: Expansion Mode - options: - - text: Online - value: Online - - text: Offline - value: Offline - schema: schema/properties/resources/properties/autoscalingKubedbComClickHouseAutoscaler/properties/spec/properties/storage/properties/clickhouse/properties/expansionMode - type: select - - label: UsageThreshold (%) - schema: schema/properties/resources/properties/autoscalingKubedbComClickHouseAutoscaler/properties/spec/properties/storage/properties/clickhouse/properties/usageThreshold - type: threshold-input - - label: Scaling Rules - loader: setValueFromDbDetails|resources/kubedbComClickHouse/spec/storage/resources/requests/storage - schema: schema/properties/resources/properties/autoscalingKubedbComClickHouseAutoscaler/properties/spec/properties/storage/properties/clickhouse/properties/scalingRules - type: scaling-rules - watcher: - func: handleUnit|autoscalingKubedbComClickHouseAutoscaler/spec/storage/clickhouse/scalingRules|scalingRules - paths: - - schema/properties/resources/properties/autoscalingKubedbComClickHouseAutoscaler/properties/spec/properties/storage/properties/clickhouse/properties/scalingRules - - label: UpperBound - schema: schema/properties/resources/properties/autoscalingKubedbComClickHouseAutoscaler/properties/spec/properties/storage/properties/clickhouse/properties/upperBound - type: input - watcher: - func: handleUnit|autoscalingKubedbComClickHouseAutoscaler/spec/storage/clickhouse/upperBound - paths: - - schema/properties/resources/properties/autoscalingKubedbComClickHouseAutoscaler/properties/spec/properties/storage/properties/clickhouse/properties/upperBound - label: ClickHouse - showLabels: true - type: block-layout - showLabels: false + - label: UsageThreshold (%) + schema: schema/properties/resources/properties/autoscalingKubedbComClickHouseAutoscaler/properties/spec/properties/storage/properties/clickhouse/properties/usageThreshold + subtitle: Set the threshold percentage of storage usage to trigger scaling. + type: threshold-input + - label: Scaling Rules + loader: setValueFromDbDetails|resources/kubedbComClickHouse/spec/storage/resources/requests/storage + schema: schema/properties/resources/properties/autoscalingKubedbComClickHouseAutoscaler/properties/spec/properties/storage/properties/clickhouse/properties/scalingRules + type: scaling-rules + watcher: + func: handleUnit|autoscalingKubedbComClickHouseAutoscaler/spec/storage/clickhouse/scalingRules|scalingRules + paths: + - schema/properties/resources/properties/autoscalingKubedbComClickHouseAutoscaler/properties/spec/properties/storage/properties/clickhouse/properties/scalingRules + - label: UpperBound + schema: schema/properties/resources/properties/autoscalingKubedbComClickHouseAutoscaler/properties/spec/properties/storage/properties/clickhouse/properties/upperBound + type: input + watcher: + func: handleUnit|autoscalingKubedbComClickHouseAutoscaler/spec/storage/clickhouse/upperBound + paths: + - schema/properties/resources/properties/autoscalingKubedbComClickHouseAutoscaler/properties/spec/properties/storage/properties/clickhouse/properties/upperBound + label: ClickHouse + showLabels: true type: block-layout - elements: - label: Timeout @@ -218,4 +448,277 @@ step: type: block-layout id: storage-autoscaler type: single-step-form +- elements: + - label: To update Exporter Resource section click on Create OpsRequest + type: label-element + - init: + type: func + value: getOpsRequestUrl|scale-vertically + label: Create OpsRequest + schema: temp/properties/opsRequestUrl + type: anchor + - fullwidth: true + init: + type: func + value: isValueExistInModel|/resources/kubedbComClickHouse/spec/monitor + label: Enable Monitoring + schema: temp/properties/enableMonitoring + type: switch + watcher: + func: onEnableMonitoringChange + paths: + - temp/properties/enableMonitoring + - elements: + - init: + type: static + value: prometheus.io/operator + isHorizontal: true + label: Select a Monitoring Method + options: + - description: Inject metric exporter sidecar and creates a ServiceMonitor + text: Prometheus Operator + value: prometheus.io/operator + - description: Injects the metric exporter sidecar and let you customize ServiceMonitor + text: Custom ServiceMonitor + value: prometheus.io + - description: Inject metric exporter sidecar and add Prometheus annotations + to the stats Service + text: Custom Scrapper + value: prometheus.io/builtin + schema: schema/properties/resources/properties/kubedbComClickHouse/properties/spec/properties/monitor/properties/agent + type: radio + watcher: + func: onAgentChange + paths: + - schema/properties/resources/properties/kubedbComClickHouse/properties/spec/properties/monitor/properties/agent + - elements: + - label: Scrapping Interval + schema: schema/properties/resources/properties/kubedbComClickHouse/properties/spec/properties/monitor/properties/prometheus/properties/serviceMonitor/properties/interval + type: input + if: + name: isEqualToModelPathValue|prometheus.io/operator|/resources/kubedbComClickHouse/spec/monitor/agent + type: function + label: ServiceMonitor Configuration + showLabels: true + type: block-layout + - elements: + - buttonClass: is-light is-outlined + elements: + - fullwidth: true + label: Honor labels + schema: honorLabels + type: switch + - label: Interval + schema: interval + type: input + validation: + type: required + - label: Path + schema: path + type: input + validation: + type: required + - label: Port + schema: port + type: input + validation: + type: required + label: Endpoints + schema: schema/properties/resources/properties/monitoringCoreosComServiceMonitor/properties/spec/properties/endpoints + type: array-object-form + - if: + name: returnFalse + type: function + label: Match Namespaces + loader: getResources|core|v1|namespaces + multiple: true + schema: schema/properties/resources/properties/monitoringCoreosComServiceMonitor/properties/spec/properties/namespaceSelector/properties/matchNames + type: select + - elements: + - label: Labels + schema: schema/properties/resources/properties/monitoringCoreosComServiceMonitor/properties/spec/properties/selector/properties/matchLabels + type: object-item + if: + name: returnFalse + type: function + showLabels: false + type: block-layout + if: + name: isEqualToModelPathValue|prometheus.io|/resources/kubedbComClickHouse/spec/monitor/agent + type: function + label: Service Monitor + showLabels: true + type: block-layout + - buttonClass: is-light is-outlined + if: + name: isEqualToModelPathValue|prometheus.io|/resources/kubedbComClickHouse/spec/monitor/agent + type: function + label: Labels + schema: schema/properties/resources/properties/monitoringCoreosComServiceMonitor/properties/metadata/properties/labels + type: object-item + - label: Exporter Configuration + schema: "" + type: label-element + - fullwidth: true + init: + type: static + value: true + label: Customize Exporter Sidecar + schema: temp/properties/customizeExporter + type: switch + watcher: + func: onCustomizeExporterChange + paths: + - temp/properties/customizeExporter + - elements: + - if: + name: returnFalse + type: function + label: Resources + schema: schema/properties/resources/properties/kubedbComClickHouse/properties/spec/properties/monitor/properties/prometheus/properties/exporter/properties/resources + type: machine-compare + - label: Security Context + schema: "" + type: label-element + - label: Run as User + schema: schema/properties/resources/properties/kubedbComClickHouse/properties/spec/properties/monitor/properties/prometheus/properties/exporter/properties/securityContext/properties/runAsUser + type: input + - label: Run as Group + schema: schema/properties/resources/properties/kubedbComClickHouse/properties/spec/properties/monitor/properties/prometheus/properties/exporter/properties/securityContext/properties/runAsGroup + type: input + - label: Port + schema: schema/properties/resources/properties/kubedbComClickHouse/properties/spec/properties/monitor/properties/prometheus/properties/exporter/properties/port + type: input + - buttonClass: is-light is-outlined + element: + label: Args + type: input + init: + type: static + value: + - --compatible-mode + label: Args + schema: schema/properties/resources/properties/kubedbComClickHouse/properties/spec/properties/monitor/properties/prometheus/properties/exporter/properties/args + type: array-item-form + - elements: + - buttonClass: is-light is-outlined + elements: + - label: Name + schema: name + type: input + validation: + type: required + - init: + type: func + value: setValueFrom + label: Value From + options: + - text: Input + value: input + - text: Secret + value: secret + - text: ConfigMap + value: configMap + schema: temp/properties/valueFromType + type: radio + validation: + type: required + watcher: + func: onValueFromChange + paths: + - temp/properties/valueFromType + - if: + name: isEqualToTemp|input + type: function + label: Value + schema: value + type: input + validation: + type: required + - if: + name: isEqualToTemp|configMap + type: function + label: ConfigMap Name + loader: + name: resourceNames|core|v1|configmaps + watchPaths: + - schema/metadata/release/namespace + schema: valueFrom/properties/configMapKeyRef/properties/name + type: select + validation: + type: required + - if: + name: isEqualToTemp|configMap + type: function + label: ConfigMap Key + loader: + name: getConfigMapKeys + watchPaths: + - schema/resources/kubedbComClickHouse/spec/monitor/prometheus/exporter/env/dynamicIndex/valueFrom/configMapKeyRef/name + - schema/metadata/release/namespace + schema: valueFrom/properties/configMapKeyRef/properties/key + type: select + - if: + name: isEqualToTemp|secret + type: function + label: Secret Name + loader: + name: getSecrets + watchPaths: + - schema/metadata/release/namespace + schema: valueFrom/properties/secretKeyRef/properties/name + type: select + validation: + type: required + - if: + name: isEqualToTemp|secret + type: function + label: Secret Key + loader: + name: getSecretKeys + watchPaths: + - schema/resources/kubedbComClickHouse/spec/monitor/prometheus/exporter/env/dynamicIndex/valueFrom/secretKeyRef/name + - schema/metadata/release/namespace + schema: valueFrom/properties/secretKeyRef/properties/key + type: select + init: + type: func + value: initEnvArray + label: Environment Variables + schema: temp/properties/env + type: array-object-form + watcher: + func: onEnvArrayChange + paths: + - temp/properties/env + label: Metadata + showLabels: false + type: block-layout + if: + name: showCustomizeExporterSection + type: function + label: Customer Exporter Section + showLabels: false + type: block-layout + if: + name: showMonitoringSection + type: function + type: block-layout + id: monitoring + loader: initMonitoring + type: single-step-form +- elements: + - fullwidth: true + init: + type: func + value: isBindingAlreadyOn + label: Expose Database + schema: temp/properties/binding + type: switch + watcher: + func: addOrRemoveBinding + paths: + - temp/properties/binding + id: binding + type: single-step-form type: multi-step-form diff --git a/charts/kubedbcom-clickhouse-editor/ui/functions.js b/charts/kubedbcom-clickhouse-editor/ui/functions.js index 8fe7a58cfc..da1b3e4505 100644 --- a/charts/kubedbcom-clickhouse-editor/ui/functions.js +++ b/charts/kubedbcom-clickhouse-editor/ui/functions.js @@ -10,9 +10,24 @@ export const useFunc = (model) => { /********** Initialize Discriminator **************/ + setDiscriminatorValue('repoInitialSelectionStatus', '') + setDiscriminatorValue('scheduleBackup', 'yes') + setDiscriminatorValue('backupType', '') + setDiscriminatorValue('isBackupDataLoaded', false) + setDiscriminatorValue('backupConfigContext', '') + setDiscriminatorValue('config', '') + setDiscriminatorValue('paused', false) + setDiscriminatorValue('schedule', '') + setDiscriminatorValue('blueprintEnabled', false) + setDiscriminatorValue('archiverEnabled', false) + + setDiscriminatorValue('binding', false) + setDiscriminatorValue('hidePreviewFromWizard', undefined) + setDiscriminatorValue('/enableMonitoring', false) setDiscriminatorValue('/customizeExporter', true) setDiscriminatorValue('/valueFromType', 'input') + setDiscriminatorValue('/env', []) // Autoscaler Discriminators setDiscriminatorValue('/dbDetails', false) @@ -27,66 +42,509 @@ export const useFunc = (model) => { setDiscriminatorValue('/allowedMachine-configServer-max', '') setDiscriminatorValue('/allowedMachine-mongos-min', '') setDiscriminatorValue('/allowedMachine-mongos-max', '') + let showStoragememory = false - let autoscaleType = '' - let dbDetails = {} - let instance = {} + function initScheduleBackupForEdit() { + const { stashAppscodeComBackupConfiguration, isBluePrint } = getBackupConfigsAndAnnotations( + getValue, + model, + ) - async function getDbDetails() { - const annotations = - getValue(model, '/resources/autoscalingKubedbComClickHouseAutoscaler/metadata/annotations') || - {} - instance = annotations['kubernetes.io/instance-type'] - const owner = storeGet('/route/params/user') || '' - const cluster = storeGet('/route/params/cluster') || '' + initRepositoryChoiseForEdit() - const namespace = - storeGet('/route/query/namespace') || - getValue(model, '/resources/autoscalingKubedbComClickHouseAutoscaler/metadata/namespace') || - '' - const name = - storeGet('/route/params/name') || - getValue( - model, - '/resources/autoscalingKubedbComClickHouseAutoscaler/spec/databaseRef/name', - ) || - '' + if (stashAppscodeComBackupConfiguration || isBluePrint) return 'yes' + else return 'no' + } - if (namespace && name) { - try { - const resp = await axios.get( - `/clusters/${owner}/${cluster}/proxy/kubedb.com/v1alpha2/namespaces/${namespace}/clickhouses/${name}`, - ) - dbDetails = resp.data || {} + function initScheduleBackup() { + const { stashAppscodeComBackupConfiguration, isBluePrint } = getBackupConfigsAndAnnotations( + getValue, + model, + ) + + if (stashAppscodeComBackupConfiguration || isBluePrint) return 'yes' + else return 'no' + } + + function onScheduleBackupChange() { + const scheduleBackup = getValue(discriminator, '/scheduleBackup') + + if (scheduleBackup === 'no') { + // delete stashAppscodeComBackupConfiguration + commit('wizard/model$delete', '/resources/stashAppscodeComBackupConfiguration') + commit('wizard/model$delete', '/resources/stashAppscodeComRepository_repo') + // delete annotation from kubedbComClickHouse annotation + deleteKubeDbComClickHouseDbAnnotation(getValue, model, commit) + } else { + const { isBluePrint } = getBackupConfigsAndAnnotations(getValue, model) + + // create stashAppscodeComBackupConfiguration and initialize it if not exists + + const dbName = getValue(model, '/metadata/release/name') + + if ( + !valueExists(model, getValue, '/resources/stashAppscodeComBackupConfiguration') && + !isBluePrint + ) { + commit('wizard/model$update', { + path: '/resources/stashAppscodeComBackupConfiguration', + value: stashAppscodeComBackupConfiguration, + }) + commit('wizard/model$update', { + path: '/resources/stashAppscodeComBackupConfiguration/spec/target/ref/name', + value: dbName, + force: true, + }) + } + } + } + + function valueExists(value, getValue, path) { + const val = getValue(value, path) + if (val) return true + else return false + } + + function getBackupConfigsAndAnnotations(getValue, model) { + const stashAppscodeComBackupConfiguration = getValue( + model, + '/resources/stashAppscodeComBackupConfiguration', + ) + const kubedbComClickHouseAnnotations = + getValue(model, '/resources/kubedbComClickHouse/metadata/annotations') || {} + + const isBluePrint = Object.keys(kubedbComClickHouseAnnotations).some( + (k) => + k === 'stash.appscode.com/backup-blueprint' || + k === 'stash.appscode.com/schedule' || + k.startsWith('params.stash.appscode.com/'), + ) + + return { + stashAppscodeComBackupConfiguration, + isBluePrint, + } + } + + function deleteKubeDbComClickHouseDbAnnotation(getValue, model, commit) { + const annotations = getValue(model, '/resources/kubedbComClickHouse/metadata/annotations') || {} + const filteredKeyList = + Object.keys(annotations).filter( + (k) => + k !== 'stash.appscode.com/backup-blueprint' && + k !== 'stash.appscode.com/schedule' && + !k.startsWith('params.stash.appscode.com/'), + ) || [] + const filteredAnnotations = {} + filteredKeyList.forEach((k) => { + filteredAnnotations[k] = annotations[k] + }) + commit('wizard/model$update', { + path: '/resources/kubedbComClickHouse/metadata/annotations', + value: filteredAnnotations, + }) + } + + // backup form + function showBackupForm() { + const scheduleBackup = getValue(discriminator, '/scheduleBackup') + // watchDependency('discriminator#/scheduleBackup') + if (scheduleBackup === 'yes') return true + else return false + } + + let initialModel = {} + let isBackupOn = false + let isBackupOnModel = false + let dbResource = {} + let initialDbMetadata = {} + let namespaceList = [] + let backupConfigurationsFromStore = {} + let valuesFromWizard = {} + let initialArchiver = {} + let isArchiverAvailable = false + let archiverObjectToCommit = {} + + async function initBackupData() { + // set initial model for further usage + initialModel = getValue(model, '/resources/coreKubestashComBackupConfiguration') + isBackupOnModel = !!initialModel + + // check db backup is enabled or not + backupConfigurationsFromStore = storeGet('/backup/backupConfigurations') + const configs = objectCopy(backupConfigurationsFromStore) + const { name, cluster, user, group, resource, spoke } = storeGet('/route/params') + const namespace = storeGet('/route/query/namespace') + const kind = storeGet('/resource/layout/result/resource/kind') + dbResource = getValue(model, '/resources/kubedbComClickHouse') + initialDbMetadata = objectCopy(dbResource.metadata) + initialArchiver = dbResource.spec?.archiver ? objectCopy(dbResource.spec?.archiver) : undefined + + // get values.yaml to populate data when backup-config is being created + try { + const actionArray = storeGet('/resource/actions/result') + const editorDetails = actionArray[0]?.items[0]?.editor + const chartName = editorDetails?.name + const sourceApiGroup = editorDetails?.sourceRef?.apiGroup + const sourceKind = editorDetails?.sourceRef?.kind + const sourceNamespace = editorDetails?.sourceRef?.namespace + const sourceName = editorDetails?.sourceRef?.name + const chartVersion = editorDetails?.version + + let url = `/clusters/${user}/${cluster}/helm/packageview/values?name=${chartName}&sourceApiGroup=${sourceApiGroup}&sourceKind=${sourceKind}&sourceNamespace=${sourceNamespace}&sourceName=${sourceName}&version=${chartVersion}&format=json` + + if (spoke) + url = `/clusters/${user}/${spoke}/helm/packageview/values?name=${chartName}&sourceApiGroup=${sourceApiGroup}&sourceKind=${sourceKind}&sourceNamespace=${sourceNamespace}&sourceName=${sourceName}&version=${chartVersion}&format=json` + + const resp = await axios.get(url) - setDiscriminatorValue('/dbDetails', true) + valuesFromWizard = objectCopy(resp.data?.resources?.coreKubestashComBackupConfiguration) || {} + } catch (e) { + console.log(e) + } + + // check storageclass archiver annotation + if (initialArchiver) { + isArchiverAvailable = true + } else { + const storageClassName = dbResource?.spec?.storage?.storageClassName + const url = `/clusters/${user}/${cluster}/proxy/storage.k8s.io/v1/storageclasses/${storageClassName}` + try { + const resp = await axios.get(url) + const archAnnotation = resp.data?.metadata?.annotations + const annotationKeyToFind = `${resource}.${group}/archiver` + if (archAnnotation[annotationKeyToFind]) { + isArchiverAvailable = true + archiverObjectToCommit = { + ref: { + name: archAnnotation[annotationKeyToFind], + namespace: 'kubedb', + }, + } + } } catch (e) { console.log(e) } } + // check config with metadata name first + let config = configs?.find( + (item) => + item.metadata?.name === name && + item.spec?.target?.name === name && + item.spec?.target?.namespace === namespace && + item.spec?.target?.kind === kind && + item.spec?.target?.apiGroup === group, + ) + + // check config without metadata name if not found with metadata name + if (!config) + config = configs?.find( + (item) => + item.spec?.target?.name === name && + item.spec?.target?.namespace === namespace && + item.spec?.target?.kind === kind && + item.spec?.target?.apiGroup === group, + ) + + // set backup switch here + isBackupOn = !!config + + // set initial data from stash-presets + const stashPreset = storeGet('/backup/stashPresets') + if (stashPreset) { + const { retentionPolicy, encryptionSecret, schedule, storageRef } = stashPreset + + const tempBackends = valuesFromWizard.spec?.backends + tempBackends[0]['storageRef'] = storageRef + tempBackends[0]['retentionPolicy'] = retentionPolicy + valuesFromWizard.spec['backends'] = tempBackends + + const tempSessions = valuesFromWizard.spec?.sessions + const tempRepositories = valuesFromWizard.spec?.sessions[0]?.repositories + tempRepositories[0]['encryptionSecret'] = encryptionSecret + tempRepositories[0].name = name + tempRepositories[0]['directory'] = `${namespace}/${name}` + + tempSessions[0]['repositories'] = tempRepositories + tempSessions[0]['scheduler']['schedule'] = schedule + valuesFromWizard.spec['sessions'] = tempSessions + } + + const apiGroup = storeGet('/route/params/group') + valuesFromWizard.spec['target'] = { name, namespace, apiGroup, kind } + const labels = dbResource.metadata?.labels + valuesFromWizard['metadata'] = { + name: `${name}-${Math.floor(Date.now() / 1000)}`, + namespace, + labels, + } + + setDiscriminatorValue('isBackupDataLoaded', true) + } + + function isBackupDataLoadedTrue() { + // watchDependency('discriminator#/isBackupDataLoaded') + return !!getValue(discriminator, '/isBackupDataLoaded') + } + + function setBackupType() { + return 'BackupConfig' + } + + function getTypes() { + const arr = [ + { + description: 'Create, Delete or Modify BackupConfig', + text: 'BackupConfig', + value: 'BackupConfig', + }, + { + description: 'Enable/Disable BackupBlueprint', + text: 'BackupBlueprint', + value: 'BackupBlueprint', + }, + ] + + if ((dbResource?.spec?.replicaSet || dbResource?.spec?.shardTopology) && isArchiverAvailable) { + arr.push({ + description: 'Enable/Disable Archiver', + text: 'Archiver', + value: 'Archiver', + }) + } + return arr + } + + function onBackupTypeChange() { + const type = getValue(discriminator, '/backupType') commit('wizard/model$update', { - path: `/metadata/release/name`, - value: name, + path: '/backupType', + value: type, force: true, }) + if (!isBackupOnModel) { + commit('wizard/model$delete', '/resources/coreKubestashComBackupConfiguration') + } else { + commit('wizard/model$update', { + path: '/resources/coreKubestashComBackupConfiguration', + value: objectCopy(initialModel), + force: true, + }) + } + commit('wizard/model$delete', '/context') commit('wizard/model$update', { - path: `/metadata/release/namespace`, - value: namespace, + path: '/resources/kubedbComClickHouse', + value: objectCopy(dbResource), force: true, }) + } + + function isBackupType(type) { + // watchDependency('discriminator#/backupType') + const selectedType = getValue(discriminator, '/backupType') + + return selectedType === type + } + + function setBlueprintSwitch() { + const annotations = initialDbMetadata?.annotations + + return !!( + annotations['blueprint.kubestash.com/name'] && + annotations['blueprint.kubestash.com/namespace'] + ) + } + + function onBlueprintChange() { + const blueprintSwitch = getValue(discriminator, '/blueprintEnabled') + if (blueprintSwitch) addLabelAnnotation('annotations') + else deleteLabelAnnotation('annotations') + } + + function setArchiverSwitch() { + const archiver = dbResource?.spec?.archiver + return !!archiver + } + + function onArchiverChange() { + const archiverSwitch = getValue(discriminator, '/archiverEnabled') + const path = 'resources/kubedbComClickHouse/spec/archiver' + if (archiverSwitch) { + commit('wizard/model$update', { + path: path, + value: initialArchiver ? initialArchiver : archiverObjectToCommit, + }) + } else { + commit('wizard/model$delete', path) + } + } + + function addLabelAnnotation(type) { + const obj = objectCopy(initialDbMetadata[type]) + + if (type === 'annotations') { + const kind = storeGet('/resource/layout/result/resource/kind') + obj['blueprint.kubestash.com/name'] = 'kubedb' + obj['blueprint.kubestash.com/namespace'] = `${kind.toLowerCase()}-blueprint` + } else { + obj['kubedb.com/archiver'] = 'true' + } + + commit('wizard/model$update', { + path: `/resources/kubedbComClickHouse/metadata/${type}`, + value: obj, + force: true, + }) + } + + function deleteLabelAnnotation(type) { + const obj = initialDbMetadata[type] + + if (type === 'annotations') { + delete obj['blueprint.kubestash.com/name'] + delete obj['blueprint.kubestash.com/namespace'] + } else delete obj['kubedb.com/archiver'] + + commit('wizard/model$update', { + path: `/resources/kubedbComClickHouse/metadata/${type}`, + value: obj, + force: true, + }) + } + + function getContext() { + if (isBackupOn) return ['Create', 'Delete', 'Modify'] + return ['Create'] + } + + function onContextChange() { + const context = getValue(discriminator, '/backupConfigContext') commit('wizard/model$update', { - path: `/resources/autoscalingKubedbComClickHouseAutoscaler/spec/databaseRef/name`, - value: name, + path: '/context', + value: context, force: true, }) + if (context === 'Create') { + commit('wizard/model$update', { + path: '/resources/coreKubestashComBackupConfiguration', + value: valuesFromWizard, + force: true, + }) + } + if (context === 'Delete') setDiscriminatorValue('hidePreviewFromWizard', true) + else setDiscriminatorValue('hidePreviewFromWizard', undefined) + } + + function getConfigList() { + const configs = objectCopy(backupConfigurationsFromStore) + const { name, group } = storeGet('/route/params') + const namespace = storeGet('/route/query/namespace') + const kind = storeGet('/resource/layout/result/resource/kind') + const filteredList = configs?.filter( + (item) => + item.spec?.target?.name === name && + item.spec?.target?.namespace === namespace && + item.spec?.target?.kind === kind && + item.spec?.target?.apiGroup === group, + ) + const list = filteredList?.map((ele) => ele.metadata.name) + return list + } + + function onConfigChange() { + const configName = getValue(discriminator, '/config') + const configs = objectCopy(backupConfigurationsFromStore) + const configDetails = configs?.find((item) => item?.metadata?.name === configName) + commit('wizard/model$update', { - path: `/resources/autoscalingKubedbComClickHouseAutoscaler/metadata/labels`, - value: dbDetails.metadata.labels, + path: '/resources/coreKubestashComBackupConfiguration', + value: configDetails, force: true, }) } + function showPause() { + // watchDependency('discriminator#/backupConfigContext') + // watchDependency('discriminator#/config') + const contex = getValue(discriminator, '/backupConfigContext') + const configName = getValue(discriminator, '/config') + return !!configName && contex === 'Modify' + } + + function setPausedValue() { + const backupConfig = storeGet('backup/backupConfigurations') || [] + const selectedConfigName = getValue(discriminator, '/config') + const namespace = storeGet('/route/query/namespace') + const selectedConfig = backupConfig.find( + (item) => item.metadata.name === selectedConfigName && item.metadata.namespace === namespace, + ) + return !!selectedConfig?.spec?.paused + } + + function showConfigList() { + // watchDependency('discriminator#/backupConfigContext') + const contex = getValue(discriminator, '/backupConfigContext') + return contex === 'Modify' || contex === 'Delete' + } + + function showSchedule() { + // watchDependency('discriminator#/backupConfigContext') + // watchDependency('discriminator#/config') + const configName = getValue(discriminator, '/config') + const contex = getValue(discriminator, '/backupConfigContext') + if (contex === 'Create') return true + else if (contex === 'Delete') return false + else return !!configName + } + + function showScheduleBackup() { + const operationQuery = storeGet('/route/params/actions') || '' + const isBackupOperation = operationQuery === 'edit-self-backupconfiguration' ? true : false + return !isBackupOperation + } + + function getDefaultSchedule(modelPath) { + // watchDependency('discriminator#/config') + const config = getValue(discriminator, '/config') // only for computed behaviour + const session = getValue(model, modelPath) + return session?.length ? session[0]?.scheduler.schedule : '' + } + + function initRepositoryChoiseForEdit() { + const stashAppscodeComRepository_repo = getValue( + model, + '/resources/stashAppscodeComRepository_repo', + ) + const repoInitialSelectionStatus = stashAppscodeComRepository_repo ? 'yes' : 'no' + setDiscriminatorValue('/repoInitialSelectionStatus', repoInitialSelectionStatus) + + return repoInitialSelectionStatus + } + + function onInputChangeSchedule(modelPath, discriminatorName) { + const value = getValue(discriminator, `/${discriminatorName}`) + const session = getValue(model, modelPath) || [] + if (session.length) { + session[0].scheduler.schedule = value + commit('wizard/model$update', { + path: modelPath, + value: session, + }) + } + } + + function objectCopy(obj) { + const temp = JSON.stringify(obj) + return JSON.parse(temp) + } + + /*********** Compute Autoscaling ************/ + + let autoscaleType = '' + let dbDetails = {} + let instance = '' + function isKubedb() { return !!storeGet('/route/params/actions') } @@ -163,12 +621,70 @@ export const useFunc = (model) => { } } + async function getClickHouseDbs() { + // watchDependency('model#/resources/autoscalingKubedbComClickHouseAutoscaler/metadata/namespace') + const namespace = getValue( + model, + '/resources/autoscalingKubedbComClickHouseAutoscaler/metadata/namespace', + ) + const owner = storeGet('/route/params/user') + const cluster = storeGet('/route/params/cluster') + const storageEngine = getValue(model, '/resources/kubedbComClickHouse/spec/storageEngine') + showStoragememory = storageEngine === 'inMemory' + const resp = await axios.get( + `/clusters/${owner}/${cluster}/proxy/kubedb.com/v1alpha2/namespaces/${namespace}/clickhouses`, + { + params: { filter: { items: { metadata: { name: null } } } }, + }, + ) + + const resources = (resp && resp.data && resp.data.items) || [] + + return resources.map((item) => { + const name = (item.metadata && item.metadata.name) || '' + return { + text: name, + value: name, + } + }) + } + + async function getClickHouseVersions(group, version, resource) { + const owner = storeGet('/route/params/user') + const cluster = storeGet('/route/params/cluster') + + const queryParams = { + filter: { + items: { + metadata: { name: null }, + spec: { version: null, deprecated: null }, + }, + }, + } + + const resp = await axios.get( + `/clusters/${owner}/${cluster}/proxy/${group}/${version}/${resource}`, + { + params: queryParams, + }, + ) + + const resources = (resp && resp.data && resp.data.items) || [] + const filteredClickHouseVersions = resources.filter((item) => item.spec && !item.spec.deprecated) + + filteredClickHouseVersions.map((item) => { + const name = (item.metadata && item.metadata.name) || '' + const specVersion = (item.spec && item.spec.version) || '' + item.text = `${name} (${specVersion})` + item.value = name + return true + }) + return filteredClickHouseVersions + } + function initMetadata() { const dbName = - getValue( - model, - '/resources/autoscalingKubedbComClickHouseAutoscaler/spec/databaseRef/name', - ) || '' + getValue(model, '/resources/autoscalingKubedbComClickHouseAutoscaler/spec/databaseRef/name') || '' const type = getValue(discriminator, '/autoscalingType') || '' const date = Math.floor(Date.now() / 1000) const resource = storeGet('/route/params/resource') @@ -183,20 +699,15 @@ export const useFunc = (model) => { // delete the other type object from vuex wizard model if (type === 'compute') - commit( - 'wizard/model$delete', - '/resources/autoscalingKubedbComClickHouseAutoscaler/spec/storage', - ) + commit('wizard/model$delete', '/resources/autoscalingKubedbComClickHouseAutoscaler/spec/storage') if (type === 'storage') - commit( - 'wizard/model$delete', - '/resources/autoscalingKubedbComClickHouseAutoscaler/spec/compute', - ) + commit('wizard/model$delete', '/resources/autoscalingKubedbComClickHouseAutoscaler/spec/compute') } async function fetchTopologyMachines() { - const instance = hasAnnotations() - + const annotations = + getValue(model, '/resources/autoscalingKubedbComClickHouseAutoscaler/metadata/annotations') || {} + instance = annotations['kubernetes.io/instance-type'] const user = storeGet('/route/params/user') const cluster = storeGet('/route/params/cluster') if (instance) { @@ -216,14 +727,23 @@ export const useFunc = (model) => { function setTrigger(path) { let value = getValue(model, `/resources/${path}`) - if (value) return value - return 'On' + return value === 'On' + } + + function onTriggerChange(type) { + const trigger = getValue(discriminator, `/${type}/trigger`) + const commitPath = `/resources/autoscalingKubedbComClickHouseAutoscaler/spec/${type}/trigger` + + commit('wizard/model$update', { + path: commitPath, + value: trigger ? 'On' : 'Off', + force: true, + }) } function hasAnnotations() { const annotations = - getValue(model, '/resources/autoscalingKubedbComClickHouseAutoscaler/metadata/annotations') || - {} + getValue(model, '/resources/autoscalingKubedbComClickHouseAutoscaler/metadata/annotations') || {} const instance = annotations['kubernetes.io/instance-type'] return !!instance @@ -234,8 +754,11 @@ export const useFunc = (model) => { const mn = instance?.includes(',') ? instance.split(',')[0] : '' const machineName = minmax === 'min' ? mn : mx + // Find the machine details from topologyMachines const nodeGroups = getValue(discriminator, '/topologyMachines') || [] const machineData = nodeGroups.find((item) => item.topologyValue === machineName) + + // Return object with machine, cpu, memory (expected format for machine-compare init) if (machineData) { return { machine: machineName, @@ -243,10 +766,15 @@ export const useFunc = (model) => { memory: machineData.allocatable?.memory, } } - return { machine: machineName || '', cpu: '', memory: '' } + // Return empty object if no machine found + return { + machine: machineName || '', + cpu: '', + memory: '', + } } - async function getMachines(minmax) { + function getMachines(minmax) { // watchDependency('discriminator#/topologyMachines') const depends = minmax === 'min' ? 'max' : 'min' const dependantPath = `/allowedMachine-${depends}` @@ -259,6 +787,7 @@ export const useFunc = (model) => { const dependantIndex = nodeGroups?.findIndex((item) => item.topologyValue === dependantMachine) + // Return array with text and value object (expected format for machine-compare loader) const machines = nodeGroups?.map((item) => { const text = item.topologyValue const subtext = `CPU: ${item.allocatable?.cpu}, Memory: ${item.allocatable?.memory}` @@ -285,6 +814,7 @@ export const useFunc = (model) => { const annotations = getValue(model, annoPath) || {} const instance = annotations['kubernetes.io/instance-type'] + // Now discriminator values are objects with { machine, cpu, memory } const minMachineObj = getValue(discriminator, '/allowedMachine-min') const maxMachineObj = getValue(discriminator, '/allowedMachine-max') const minMachine = minMachineObj?.machine || '' @@ -292,6 +822,7 @@ export const useFunc = (model) => { const minMaxMachine = `${minMachine},${maxMachine}` annotations['kubernetes.io/instance-type'] = minMaxMachine + // Use cpu/memory directly from the machine objects const minMachineAllocatable = minMachineObj ? { cpu: minMachineObj.cpu, memory: minMachineObj.memory } : null @@ -313,7 +844,7 @@ export const useFunc = (model) => { }) commit('wizard/model$update', { path: annoPath, - value: annotations, + value: { ...annotations }, force: true, }) } @@ -369,10 +900,8 @@ export const useFunc = (model) => { // watchDependency('model#/resources/autoscalingKubedbComClickHouseAutoscaler/spec/databaseRef/name') // watchDependency('discriminator#/autoscalingType') return ( - !!getValue( - model, - '/resources/autoscalingKubedbComClickHouseAutoscaler/spec/databaseRef/name', - ) && !!getValue(discriminator, '/autoscalingType') + !!getValue(model, '/resources/autoscalingKubedbComClickHouseAutoscaler/spec/databaseRef/name') && + !!getValue(discriminator, '/autoscalingType') ) } @@ -380,26 +909,279 @@ export const useFunc = (model) => { return 'IfReady' } - async function getDbs() { - // watchDependency('model#/resources/autoscalingKubedbComClickHouseAutoscaler/metadata/namespace') + function isEqualToModelPathValue(value, modelPath) { + const modelPathValue = getValue(model, modelPath) + // watchDependency('model#' + modelPath) + return modelPathValue === value + } + + async function getResources(group, version, resource) { + const owner = storeGet('/route/params/user') + const cluster = storeGet('/route/params/cluster') + + try { + const resp = await axios.get( + `/clusters/${owner}/${cluster}/proxy/${group}/${version}/${resource}`, + { + params: { filter: { items: { metadata: { name: null } } } }, + }, + ) + + const resources = (resp && resp.data && resp.data.items) || [] + + resources.map((item) => { + const name = (item.metadata && item.metadata.name) || '' + item.text = name + item.value = name + return true + }) + return resources + } catch (e) { + console.log(e) + return [] + } + } + + async function getNamespacedResourceList( + axios, + storeGet, + { namespace, group, version, resource }, + ) { + const owner = storeGet('/route/params/user') + const cluster = storeGet('/route/params/cluster') + + const url = `/clusters/${owner}/${cluster}/proxy/${group}/${version}/namespaces/${namespace}/${resource}` + + let ans = [] + try { + const resp = await axios.get(url, { + params: { + filter: { items: { metadata: { name: null }, type: null } }, + }, + }) + + const items = (resp && resp.data && resp.data.items) || [] + ans = items + } catch (e) { + console.log(e) + } + + return ans + } + + /********** Monitoring **********/ + + function showMonitoringSection() { + // watchDependency('discriminator#/enableMonitoring') + const configureStatus = getValue(discriminator, '/enableMonitoring') + return configureStatus + } + + function onEnableMonitoringChange() { + const configureStatus = getValue(discriminator, '/enableMonitoring') + if (configureStatus) { + commit('wizard/model$update', { + path: '/resources/kubedbComClickHouse/spec/monitor', + value: {}, + force: true, + }) + } else { + commit('wizard/model$delete', '/resources/kubedbComClickHouse/spec/monitor') + } + + // update alert value depend on monitoring profile + commit('wizard/model$update', { + path: '/form/alert/enabled', + value: configureStatus ? 'warning' : 'none', + force: true, + }) + } + + function showCustomizeExporterSection() { + // watchDependency('discriminator#/customizeExporter') + const configureStatus = getValue(discriminator, '/customizeExporter') + return configureStatus + } + + function onCustomizeExporterChange() { + const configureStatus = getValue(discriminator, '/customizeExporter') + if (configureStatus) { + commit('wizard/model$update', { + path: '/resources/kubedbComClickHouse/spec/monitor/prometheus/exporter', + value: {}, + force: true, + }) + } else { + commit('wizard/model$delete', '/resources/kubedbComClickHouse/spec/monitor/prometheus/exporter') + } + } + + function isValueExistInModel(path) { + const modelValue = getValue(model, path) || null + return !!modelValue + } + + // function onNamespaceChange() { + // const namespace = getValue(model, '/metadata/release/namespace') + // const agent = getValue(model, '/resources/kubedbComClickHouse/spec/monitor/agent') + // if (agent === 'prometheus.io') { + // commit('wizard/model$update', { + // path: '/resources/monitoringCoreosComServiceMonitor/spec/namespaceSelector/matchNames', + // value: [namespace], + // force: true, + // }) + // } + // } + + function onLabelChange() { + const labels = getValue(model, '/resources/kubedbComClickHouse/spec/metadata/labels') + + const agent = getValue(model, '/resources/kubedbComClickHouse/spec/monitor/agent') + + if (agent === 'prometheus.io') { + commit('wizard/model$update', { + path: '/resources/monitoringCoreosComServiceMonitor/spec/selector/matchLabels', + value: labels, + force: true, + }) + } + } + + function onAgentChange() { + const agent = getValue(model, '/resources/kubedbComClickHouse/spec/monitor/agent') + if (agent === 'prometheus.io') { + commit('wizard/model$update', { + path: '/resources/monitoringCoreosComServiceMonitor/spec/endpoints', + value: [], + force: true, + }) + + onNamespaceChange() + onLabelChange() + } else { + commit('wizard/model$delete', '/resources/monitoringCoreosComServiceMonitor') + } + } + + function getOpsRequestUrl(reqType) { + const cluster = storeGet('/route/params/cluster') + const domain = storeGet('/domain') || '' + const owner = storeGet('/route/params/user') + const dbname = getValue(model, '/metadata/release/name') + const group = getValue(model, '/metadata/resource/group') + const kind = getValue(model, '/metadata/resource/kind') + const namespace = getValue(model, '/metadata/release/namespace') + const resource = getValue(model, '/metadata/resource/name') + const version = getValue(model, '/metadata/resource/version') + const routeRootPath = storeGet('/route/path') + const pathPrefix = `${domain}/db${routeRootPath}` + const pathSplit = pathPrefix.split('/').slice(0, -1).join('/') + const pathConstructedForKubedb = pathSplit + `/${reqType.toLowerCase()}?namespace=${namespace}` + + const isKube = !!storeGet('/route/params/actions') + + if (isKube) return pathConstructedForKubedb + else + return `${domain}/console/${owner}/kubernetes/${cluster}/ops.kubedb.com/v1alpha1/clickhouseopsrequests/create?name=${dbname}&namespace=${namespace}&group=${group}&version=${version}&resource=${resource}&kind=${kind}&page=operations&requestType=VerticalScaling` + } + + function onNamespaceChange() { const namespace = getValue( model, '/resources/autoscalingKubedbComClickHouseAutoscaler/metadata/namespace', ) - const owner = storeGet('/route/params/user') - const cluster = storeGet('/route/params/cluster') + if (!namespace) { + commit( + 'wizard/model$delete', + '/resources/autoscalingKubedbComClickHouseAutoscaler/spec/databaseRef/name', + ) + } + } - const resp = await axios.get( - `/clusters/${owner}/${cluster}/proxy/kubedb.com/v1alpha2/namespaces/${namespace}/redises`, - { - params: { filter: { items: { metadata: { name: null } } } }, - }, - ) + function setValueFrom() { + if (isConfigMapTypeValueFrom()) { + return 'configMap' + } else if (isSecretTypeValueFrom()) { + return 'secret' + } else { + return 'input' + } + } - const resources = (resp && resp.data && resp.data.items) || [] + function isConfigMapTypeValueFrom() { + const valueFrom = getValue(discriminator, '/valueFrom') + return !!(valueFrom && valueFrom.configMapKeyRef) + } - return resources.map((item) => { - const name = (item.metadata && item.metadata.name) || '' + function isSecretTypeValueFrom() { + const valueFrom = getValue(discriminator, '/valueFrom') + return !!(valueFrom && valueFrom.secretKeyRef) + } + + function onValueFromChange() { + const valueFrom = getValue(discriminator, '/valueFromType') + if (valueFrom === 'input') { + if (isConfigMapTypeValueFrom()) + commit('wizard/model$update', { + path: 'temp/valueFrom/configMapKeyRef', + value: true, + }) + if (isSecretTypeValueFrom()) + commit('wizard/model$update', { + path: 'temp/valueFrom/secretKeyRef', + value: true, + }) + } else if (valueFrom === 'secret') { + if (!isSecretTypeValueFrom()) + commit('wizard/model$update', { + path: 'temp/valueFrom/secretKeyRef', + value: false, + }) + if (isConfigMapTypeValueFrom()) + commit('wizard/model$update', { + path: 'temp/valueFrom/configMapKeyRef', + value: true, + }) + } else if (valueFrom === 'configMap') { + if (!isConfigMapTypeValueFrom()) + commit('wizard/model$update', { + path: 'temp/valueFrom/configMapKeyRef', + value: false, + }) + if (isSecretTypeValueFrom()) + commit('wizard/model$update', { + path: 'temp/valueFrom/secretKeyRef', + value: true, + }) + } + } + + // function isEqualToValueFromType(value) { + // //watchDependency('discriminator#/valueFromType') + // const valueFrom = getValue(discriminator, '/valueFromType') + // return valueFrom === value + // } + + async function resourceNames(group, version, resource) { + const namespace = getValue(model, '/metadata/release/namespace') + // watchDependency('model#/metadata/release/namespace') + + let resources = await getNamespacedResourceList(axios, storeGet, { + namespace, + group, + version, + resource, + }) + + if (resource === 'secrets') { + resources = resources.filter((item) => { + const validType = ['kubernetes.io/service-account-token', 'Opaque'] + return validType.includes(item.type) + }) + } + + return resources.map((resource) => { + const name = (resource.metadata && resource.metadata.name) || '' return { text: name, value: name, @@ -407,6 +1189,155 @@ export const useFunc = (model) => { }) } + async function getConfigMapKeys(index) { + const owner = storeGet('/route/params/user') + const cluster = storeGet('/route/params/cluster') + // const namespace = getValue(reusableElementCtx, '/dataContext/namespace') // not supported + const namespace = getValue(model, '/metadata/release/namespace') + const configMapName = getValue( + model, + `/resources/kubedbComClickHouse/spec/monitor/prometheus/exporter/env/${index}/valueFrom/configMapKeyRef/name`, + ) + + // watchDependency('data#/namespace') + // watchDependency('rootModel#/valueFrom/configMapKeyRef/name') + + if (!configMapName) return [] + + try { + const resp = await axios.get( + `/clusters/${owner}/${cluster}/proxy/core/v1/namespaces/${namespace}/configmaps/${configMapName}`, + ) + + const configMaps = (resp && resp.data && resp.data.data) || {} + + const configMapKeys = Object.keys(configMaps).map((item) => ({ + text: item, + value: item, + })) + + return configMapKeys + } catch (e) { + console.log(e) + return [] + } + } + + async function getSecrets() { + const owner = storeGet('/route/params/user') + const cluster = storeGet('/route/params/cluster') + const namespace = getValue(model, '/metadata/release/namespace') + // watchDependency('model#/metadata/release/namespace') + + try { + const resp = await axios.get( + `/clusters/${owner}/${cluster}/proxy/core/v1/namespaces/${namespace}/secrets`, + { + params: { + filter: { items: { metadata: { name: null }, type: null } }, + }, + }, + ) + + const secrets = (resp && resp.data && resp.data.items) || [] + + const filteredSecrets = secrets.filter((item) => { + const validType = ['kubernetes.io/service-account-token', 'Opaque'] + return validType.includes(item.type) + }) + + filteredSecrets.map((item) => { + const name = (item.metadata && item.metadata.name) || '' + item.text = name + item.value = name + return true + }) + return filteredSecrets + } catch (e) { + console.log(e) + return [] + } + } + + async function getSecretKeys(index) { + const owner = storeGet('/route/params/user') + const cluster = storeGet('/route/params/cluster') + // const namespace = getValue(reusableElementCtx, '/dataContext/namespace') // not supported + const namespace = getValue(model, '/metadata/release/namespace') + const secretName = getValue( + model, + `/resources/kubedbComClickHouse/spec/monitor/prometheus/exporter/env/${index}/valueFrom/secretKeyRef/name`, + ) + + // watchDependency('data#/namespace') + // watchDependency('rootModel#/valueFrom/secretKeyRef/name') + + if (!secretName) return [] + + try { + const resp = await axios.get( + `/clusters/${owner}/${cluster}/proxy/core/v1/namespaces/${namespace}/secrets/${secretName}`, + ) + + const secret = (resp && resp.data && resp.data.data) || {} + + const secretKeys = Object.keys(secret).map((item) => ({ + text: item, + value: item, + })) + + return secretKeys + } catch (e) { + console.log(e) + return [] + } + } + + function returnFalse() { + return false + } + + /********** Binding **********/ + + function isBindingAlreadyOn() { + const value = getValue(model, '/resources') + const keys = Object.keys(value) + const isExposeBinding = !!keys.find((str) => str === 'catalogAppscodeComClickHouseBinding') + return isExposeBinding + } + + function addOrRemoveBinding() { + const value = getValue(discriminator, `/binding`) + const dbName = getValue(model, '/metadata/release/name') + const dbNamespace = getValue(model, '/metadata/release/namespace') + const labels = getValue(model, '/resources/kubedbComClickHouse/metadata/labels') + const bindingValues = { + apiVersion: 'catalog.appscode.com/v1alpha1', + kind: 'ClickHouseBinding', + metadata: { + labels, + name: dbName, + namespace: dbNamespace, + }, + spec: { + sourceRef: { + name: dbName, + namespace: dbNamespace, + }, + }, + } + + if (value) { + commit('wizard/model$update', { + path: '/resources/catalogAppscodeComClickHouseBinding', + value: bindingValues, + force: true, + }) + } else { + commit('wizard/model$delete', '/resources/catalogAppscodeComClickHouseBinding') + } + } + function handleUnit(path, type = 'bound') { let value = getValue(model, `/resources/${path}`) if (type === 'scalingRules') { @@ -441,16 +1372,107 @@ export const useFunc = (model) => { } } + function setValueFromDbDetails(path) { + const value = getValue(model, path) + return value + } + function showStorageMemoryOption() { + return showStoragememory + } + + function onEnvArrayChange() { + const env = getValue(discriminator, '/env') || [] + let ret = {} + // filter out temp values + const filteredEnv = env?.map((item) => { + const { temp, ...rest } = item + if (temp?.valueFromType === 'input') { + const { name, value } = rest + ret = { name, value } + } else if (temp?.valueFromType === 'configMap') { + const { name } = rest + const { configMapKeyRef } = rest?.valueFrom || {} + ret = { name, valueFrom: { configMapKeyRef } } + } else if (temp?.valueFromType === 'secret') { + const { name } = rest + const { secretKeyRef } = rest?.valueFrom || {} + ret = { name, valueFrom: { secretKeyRef } } + } + return ret + }) + + if (filteredEnv.length) + commit('wizard/model$update', { + path: '/resources/kubedbComClickHouse/spec/monitor/prometheus/exporter/env', + value: filteredEnv, + force: true, + }) + } + + function initEnvArray() { + const env = getValue(model, '/resources/kubedbComClickHouse/spec/monitor/prometheus/exporter/env') + + return env || [] + } + + function isEqualToTemp(value, index) { + //watchDependency('discriminator#/valueFromType') + const valueFrom = getValue(discriminator, `/env/${index}/temp/valueFromType`) + return valueFrom === value + } + + function initMonitoring() { + const env = + getValue(model, '/resources/kubedbComClickHouse/spec/monitor/prometheus/exporter/env') || [] + setDiscriminatorValue('/env', env) + let tempEnv = [] + env.forEach((item) => { + let radio = '' + if (item.value) radio = 'input' + else if (item.valueFrom && item.valueFrom.configMapKeyRef) radio = 'configMap' + else if (item.valueFrom && item.valueFrom.secretKeyRef) radio = 'secret' + tempEnv.push({ ...item, temp: { valueFromType: radio } }) + }) + setDiscriminatorValue('/env', tempEnv) + } + return { - getDbDetails, + initScheduleBackup, + initScheduleBackupForEdit, + onScheduleBackupChange, + showBackupForm, + initBackupData, + isBackupDataLoadedTrue, + setBackupType, + getTypes, + onBackupTypeChange, + isBackupType, + setBlueprintSwitch, + onBlueprintChange, + setArchiverSwitch, + onArchiverChange, + getContext, + onContextChange, + getConfigList, + onConfigChange, + showPause, + showConfigList, + showSchedule, + showScheduleBackup, + getDefaultSchedule, + onInputChangeSchedule, + setPausedValue, + isKubedb, isConsole, getNamespaces, isRancherManaged, - onNamespaceChange, + getClickHouseDbs, + getClickHouseVersions, initMetadata, fetchTopologyMachines, setTrigger, + onTriggerChange, hasAnnotations, setAllowedMachine, getMachines, @@ -461,7 +1483,39 @@ export const useFunc = (model) => { isNodeTopologySelected, showOpsRequestOptions, setApplyToIfReady, - getDbs, + handleUnit, + + getOpsRequestUrl, + isValueExistInModel, + onEnableMonitoringChange, + showMonitoringSection, + onAgentChange, + getResources, + isEqualToModelPathValue, + onCustomizeExporterChange, + showCustomizeExporterSection, + onNamespaceChange, + onLabelChange, + setValueFrom, + onValueFromChange, + resourceNames, + getConfigMapKeys, + getSecrets, + getSecretKeys, + isConfigMapTypeValueFrom, + isSecretTypeValueFrom, + getNamespacedResourceList, + returnFalse, + onEnvArrayChange, + initEnvArray, + isEqualToTemp, + initMonitoring, + + isBindingAlreadyOn, + addOrRemoveBinding, + + setValueFromDbDetails, + showStorageMemoryOption, } } diff --git a/charts/kubedbcom-clickhouse-editor/ui/language.yaml b/charts/kubedbcom-clickhouse-editor/ui/language.yaml index f0e8063644..8a99457f59 100644 --- a/charts/kubedbcom-clickhouse-editor/ui/language.yaml +++ b/charts/kubedbcom-clickhouse-editor/ui/language.yaml @@ -25,7 +25,7 @@ en: class: Storage Class Name size: Storage Size terminalPolicy: Terminal Policy - deletionPolicy: Termination Policy + deletionPolicy: Deletion Policy labels: agent: Select a Monitoring Method alert: Alert @@ -67,8 +67,11 @@ en: basic_info: Basic Information certificate: Certificate certificates: Certificates + client_auth_mode: Client Auth Mode cluster_auth_mode: Cluster Authentication Mode cluster_ip: Cluster IP + config_file: Configuration File + config_files: Configuration Files configOptions: Configuration Options configServer: Config Server configServerNodes: Config Server Nodes @@ -83,7 +86,6 @@ en: cpu: CPU create_opsrequest: Create OpsRequest custom_config: Select Custom Config Type - custom_config_not_allowed: 'Specifying ConfigSecret here is not allowed for sharded cluster. Please, use shard specific ConfigSecret from the Topology section.' customize_config_server_pod_template: Customize Config Server Pod Template customize_exporter: Customize Exporter Sidecar customize_mongos_pod_template: Customize Mongos Pod Template @@ -98,6 +100,7 @@ en: dns_names: DNS Names duration: Duration effect: Effect + electionTick: Election Tick enable_monitoring: Enable Monitoring enable_tls: Enable TLS endpoint: Endpoint @@ -109,11 +112,15 @@ en: external_ip: External IP external_ips: External IPs external_traffic_policy: External Traffic Policy + filename: File Name fs_group: Fs Group + group_mode: Group Mode + group_name: Group Name health_check_node_port: Health Check Node Port + heartbeatTick: Heartbeat Tick honor_labels: Honor labels image_pull_secrets: Image Pull Secrets - initialization: Pre-populate your MongoDB from backup/another database + initialization: Pre-populate your ClickHouse from backup/another database interval: Interval ip_address: IP Address ip_addresses: IP Addresses @@ -124,6 +131,7 @@ en: key: Key label: Labels value: Value + leaderElection: Leader Election level: Level limit: Limit load_balancer_ip: Load Balancer IP @@ -134,10 +142,13 @@ en: match_expressions: Match Expressions match_field: Match Field match_fields: Match Fields + maximumLagBeforeFailover: Maximum Lag Before Failover memory: Memory - mongo_conf: mongod.conf + user_conf: user.conf + mode: Mode mongos: Mongos mongosNodes: Mongos Nodes + my_config_cnf: my-config.cnf name: Name namespace: Namespace new_secret_password: New Database Secret @@ -151,6 +162,7 @@ en: organizations: Organizations password: Password (Keep it empty to autogenerate) path: Path + period: Period pod_annotations: Pod Annotations pod_spec: Pod Spec pod_template: Pod Template @@ -166,6 +178,7 @@ en: replicaset: name: Replicaset Name number: Replica Number + router_number: Router Replica Number repositories: backend: bucket: Bucket @@ -188,6 +201,7 @@ en: name: Name title: Repository request: Request + requireSSL_question: Require SSL? resources: Resources restoreSession: name: Name @@ -254,22 +268,26 @@ en: shardNodes: Shard Nodes shards: Shards ssl_mode: SSL Mode + standbyMode: Standby Mode storage: class: Storage Class size: Storage Size subject: Subject terminalPolicy: Terminal Policy - deletionPolicy: Termination Policy + deletionPolicy: Deletion Policy timeout_seconds: Timeout Seconds toleration: Toleration toleration_seconds: Toleration in seconds tolerations: Tolerations - to_update_config: To update this section go to Create OpsRequest - to_update_replicas: To update replicas go to Create OpsRequest - to_update_tls: To update this section go to Create OpsRequest + topology: Topology + to_update_config: To update this section click on Create OpsRequest + to_update_replicas: To update replicas click on Create OpsRequest + to_update_tls: To update this section click on Create OpsRequest + to_update_disabled_section: To update disabled section click on Create OpsRequest to_update_exporter_resources: To update Exporter Resource section click on Create OpsRequest type: Type user: User + use_address_type: Use Address Type value: Value values: Values waitForInitialRestore: Wait For Initial Restore? @@ -291,7 +309,7 @@ en: description: Schedule periodic backup via Stash. label: Backup customConfig: - description: Configure MongoDB with configuration files. + description: Configure ClickHouse with configuration files. label: Custom Config initialization: description: Initialize database from backup or script. @@ -334,25 +352,31 @@ en: text: Stash Backup database: mode: - Replicaset: - description: MongoDB ReplicaSet for high availability. - label: Replicated Cluster - Sharded: - description: MongoDB sharded cluster for high performance and high availability. - label: Sharded Cluster + GroupReplication: + description: GroupReplication for fault-tolerant replication topologies + label: GroupReplication + InnoDBCluster: + description: InnoDBCluster for high availability. + label: InnoDBCluster Standalone: - description: Single node MongoDB without high availability and sharding. + description: Single node ClickHouse without high availability and sharding. label: Standalone secret: customSecret: label: Auto-generate auth secret existingSecret: label: Use existing auth secret + groupMode: + SinglePrimary: Single-Primary + MultiPrimary: Multi-Primary scriptSourceVolumeType: configMap: text: ConfigMap secret: text: Secret + standbyMode: + hot: Hot + warm: Warm storageClass: standard: label: Standard @@ -361,7 +385,7 @@ en: description: Keep only database Secrets and backed up data. label: Delete doNotTerminate: - description: Prevent deletion of the MongoDB CRD. + description: Prevent deletion of the ClickHouse CRD. label: DoNotTerminate halt: description: 'Keep PVCs, database Secrets and backed up data.' @@ -369,6 +393,9 @@ en: wipeOut: description: Delete everything including backed up data. label: WipeOut + topologyMode: + GroupReplication: GroupReplication + InnoDBCluster: InnoDBCluster yesOrNo: 'no': text: 'No' @@ -376,12 +403,11 @@ en: text: 'Yes' steps: - label: Basic Information - - label: Topology + - label: Configure Cluster - label: TLS Configuration - label: Initialization - label: Backup - label: Monitoring - label: Pod Template - - label: Pod Template - label: Networking - - label: Custom Config + - label: Custom Config \ No newline at end of file