From a9b009490569dc9ed72b2b4cefc6ecd27b2a9bbf Mon Sep 17 00:00:00 2001 From: Sourav-Kumar-19 Date: Fri, 5 Jun 2026 12:08:50 +0600 Subject: [PATCH 1/3] add hanadb ops request forms Add UI forms for HanaDB ops requests including edit UI, functions, and language files for the HanaDB editor and ops request editor. Signed-off-by: Sourav-Kumar-19 --- .../kubedbcom-hanadb-editor/ui/edit-ui.yaml | 564 +++++ .../kubedbcom-hanadb-editor/ui/functions.js | 571 +++++ .../kubedbcom-hanadb-editor/ui/language.yaml | 27 + .../ui/create-ui.yaml | 508 +++++ .../ui/functions.js | 1924 +++++++++++++++++ .../ui/language.yaml | 307 +++ 6 files changed, 3901 insertions(+) create mode 100644 charts/kubedbcom-hanadb-editor/ui/edit-ui.yaml create mode 100644 charts/kubedbcom-hanadb-editor/ui/functions.js create mode 100644 charts/kubedbcom-hanadb-editor/ui/language.yaml create mode 100644 charts/opskubedbcom-hanadbopsrequest-editor/ui/create-ui.yaml create mode 100644 charts/opskubedbcom-hanadbopsrequest-editor/ui/functions.js create mode 100644 charts/opskubedbcom-hanadbopsrequest-editor/ui/language.yaml diff --git a/charts/kubedbcom-hanadb-editor/ui/edit-ui.yaml b/charts/kubedbcom-hanadb-editor/ui/edit-ui.yaml new file mode 100644 index 0000000000..ac463758e2 --- /dev/null +++ b/charts/kubedbcom-hanadb-editor/ui/edit-ui.yaml @@ -0,0 +1,564 @@ +step: +- 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/kubedbComHanaDB/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/kubedbComHanaDB/properties/spec/properties/monitor/properties/agent + type: radio + watcher: + func: onAgentChange + paths: + - schema/properties/resources/properties/kubedbComHanaDB/properties/spec/properties/monitor/properties/agent + - elements: + - label: Scrapping Interval + schema: schema/properties/resources/properties/kubedbComHanaDB/properties/spec/properties/monitor/properties/prometheus/properties/serviceMonitor/properties/interval + type: input + if: + name: isEqualToModelPathValue|prometheus.io/operator|/resources/kubedbComHanaDB/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/kubedbComHanaDB/spec/monitor/agent + type: function + label: Service Monitor + showLabels: true + type: block-layout + - buttonClass: is-light is-outlined + if: + name: isEqualToModelPathValue|prometheus.io|/resources/kubedbComHanaDB/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/kubedbComHanaDB/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/kubedbComHanaDB/properties/spec/properties/monitor/properties/prometheus/properties/exporter/properties/securityContext/properties/runAsUser + type: input + - label: Run as Group + schema: schema/properties/resources/properties/kubedbComHanaDB/properties/spec/properties/monitor/properties/prometheus/properties/exporter/properties/securityContext/properties/runAsGroup + type: input + - label: Port + schema: schema/properties/resources/properties/kubedbComHanaDB/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/kubedbComHanaDB/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/kubedbComHanaDB/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/kubedbComHanaDB/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: + - elements: + - label: Name + schema: schema/properties/resources/properties/autoscalingKubedbComHanaDBAutoscaler/properties/metadata/properties/name + type: input + - if: + name: isRancherManaged + type: function + label: Select Namespace + loader: getNamespaces + schema: schema/properties/resources/properties/autoscalingKubedbComHanaDBAutoscaler/properties/metadata/properties/namespace + type: select + watcher: + func: onNamespaceChange + paths: + - schema/properties/resources/properties/autoscalingKubedbComHanaDBAutoscaler/properties/metadata/properties/namespace + - label: Select Db + loader: getHanaDBDbs + refresh: true + schema: schema/properties/resources/properties/autoscalingKubedbComHanaDBAutoscaler/properties/spec/properties/databaseRef/properties/name + type: select + validation: + type: required + watcher: + func: initMetadata + paths: + - schema/properties/resources/properties/autoscalingKubedbComHanaDBAutoscaler/properties/spec/properties/databaseRef/properties/name + if: + name: isConsole + type: function + type: block-layout + - if: + name: isConsole + type: function + label: Select Type + options: + - description: Scale your CPU Memory based on resource usage + text: Compute + value: compute + - description: Expand your database size based on volume usage + text: Storage + value: storage + schema: temp/properties/autoscalingType + type: radio + validation: + type: required + watcher: + func: initMetadata + paths: + - temp/properties/autoscalingType + - elements: + - fullwidth: true + init: + type: func + value: setTrigger|autoscalingKubedbComHanaDBAutoscaler/spec/compute/hanadb/trigger + label: Trigger + schema: temp/properties/compute/properties/hanadb/properties/trigger + type: switch + watcher: + func: onTriggerChange|compute/hanadb + paths: + - temp/properties/compute/properties/hanadb/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/autoscalingKubedbComHanaDBAutoscaler/properties/spec/properties/compute/properties/hanadb/properties/podLifeTimeThreshold + type: input + - elements: + - customClass: width-300 + label: ResourceDiff Percentage + schema: schema/properties/resources/properties/autoscalingKubedbComHanaDBAutoscaler/properties/spec/properties/compute/properties/hanadb/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|hanadb|min + label: Min Allowed Profile + loader: + name: getMachines|hanadb|min + watchPaths: + - temp/properties/topologyMachines + - temp/properties/allowedMachine-max + schema: temp/properties/allowedMachine-min + type: machine-compare + watcher: + func: onMachineChange|hanadb + paths: + - temp/properties/allowedMachine-min + - elements: + - label: Cpu + schema: schema/properties/resources/properties/autoscalingKubedbComHanaDBAutoscaler/properties/spec/properties/compute/properties/hanadb/properties/minAllowed/properties/cpu + type: input-compare + - label: Memory + schema: schema/properties/resources/properties/autoscalingKubedbComHanaDBAutoscaler/properties/spec/properties/compute/properties/hanadb/properties/minAllowed/properties/memory + 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|hanadb|max + label: Max Allowed Profile + loader: + name: getMachines|hanadb|max + watchPaths: + - temp/properties/topologyMachines + - temp/properties/allowedMachine-min + schema: temp/properties/allowedMachine-max + type: machine-compare + watcher: + func: onMachineChange|hanadb + paths: + - temp/properties/allowedMachine-max + - elements: + - label: Cpu + schema: schema/properties/resources/properties/autoscalingKubedbComHanaDBAutoscaler/properties/spec/properties/compute/properties/hanadb/properties/maxAllowed/properties/cpu + type: input-compare + - label: Memory + schema: schema/properties/resources/properties/autoscalingKubedbComHanaDBAutoscaler/properties/spec/properties/compute/properties/hanadb/properties/maxAllowed/properties/memory + type: input-compare + fixedBlock: true + if: + name: hasNoAnnotations + type: function + label: Max Allowed + showLabels: true + type: block-layout + showLabels: false + type: block-layout + - label: Controlled Resources + loader: setControlledResources|hanadb + multiple: true + schema: schema/properties/resources/properties/autoscalingKubedbComHanaDBAutoscaler/properties/spec/properties/compute/properties/hanadb/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/autoscalingKubedbComHanaDBAutoscaler/properties/spec/properties/compute/properties/hanadb/properties/containerControlledValues + type: select + - elements: + - label: Scaling Factor Percentage + schema: schema/properties/resources/properties/autoscalingKubedbComHanaDBAutoscaler/properties/spec/properties/compute/properties/hanadb/properties/inMemoryStorage/properties/scalingFactorPercentage + type: threshold-input + - label: Usage Threshold Percentage + schema: schema/properties/resources/properties/autoscalingKubedbComHanaDBAutoscaler/properties/spec/properties/compute/properties/hanadb/properties/inMemoryStorage/properties/usageThresholdPercentage + type: threshold-input + if: + name: showStorageMemoryOption + type: function + label: In Memory Storage + showLabels: true + type: block-layout + label: HanaDB + showLabels: true + type: block-layout + - elements: + - label: Select NodeTopology + loader: fetchNodeTopology + schema: schema/properties/resources/properties/autoscalingKubedbComHanaDBAutoscaler/properties/spec/properties/compute/properties/nodeTopology/properties/name + type: select + - if: + name: isNodeTopologySelected + type: function + label: ScaleUp Diff Percentage + schema: schema/properties/resources/properties/autoscalingKubedbComHanaDBAutoscaler/properties/spec/properties/compute/properties/nodeTopology/properties/scaleUpDiffPercentage + type: threshold-input + - if: + name: isNodeTopologySelected + type: function + label: ScaleDown Diff Percentage + schema: schema/properties/resources/properties/autoscalingKubedbComHanaDBAutoscaler/properties/spec/properties/compute/properties/nodeTopology/properties/scaleDownDiffPercentage + type: threshold-input + if: + name: hasNoAnnotations + type: function + label: Node Topology + showLabels: true + type: block-layout + - elements: + - label: Timeout + schema: schema/properties/resources/properties/autoscalingKubedbComHanaDBAutoscaler/properties/spec/properties/opsRequestOptions/properties/timeout + 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/resources/properties/autoscalingKubedbComHanaDBAutoscaler/properties/spec/properties/opsRequestOptions/properties/apply + type: radio + if: + name: showOpsRequestOptions + type: function + label: Ops Request Options + showLabels: true + type: block-layout + loader: fetchTopologyMachines + showLabels: false + type: block-layout + id: compute-autoscaler + loader: getHanaDBDbs + type: single-step-form +- elements: + - elements: + - fullwidth: true + init: + type: func + value: setTrigger|autoscalingKubedbComHanaDBAutoscaler/spec/storage/hanadb/trigger + label: Trigger + schema: temp/properties/storage/properties/hanadb/properties/trigger + type: switch + watcher: + func: onTriggerChange|storage/hanadb + paths: + - temp/properties/storage/properties/hanadb/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/autoscalingKubedbComHanaDBAutoscaler/properties/spec/properties/storage/properties/hanadb/properties/expansionMode + type: select + - elements: + - label: UsageThreshold (%) + schema: schema/properties/resources/properties/autoscalingKubedbComHanaDBAutoscaler/properties/spec/properties/storage/properties/hanadb/properties/usageThreshold + subtitle: Set the threshold percentage of storage usage to trigger scaling. + type: threshold-input + - label: Scaling Rules + loader: setValueFromDbDetails|resources/kubedbComHanaDB/spec/storage/resources/requests/storage + schema: schema/properties/resources/properties/autoscalingKubedbComHanaDBAutoscaler/properties/spec/properties/storage/properties/hanadb/properties/scalingRules + type: scaling-rules + watcher: + func: handleUnit|autoscalingKubedbComHanaDBAutoscaler/spec/storage/hanadb/scalingRules|scalingRules + paths: + - schema/properties/resources/properties/autoscalingKubedbComHanaDBAutoscaler/properties/spec/properties/storage/properties/hanadb/properties/scalingRules + - label: UpperBound + schema: schema/properties/resources/properties/autoscalingKubedbComHanaDBAutoscaler/properties/spec/properties/storage/properties/hanadb/properties/upperBound + type: input + watcher: + func: handleUnit|autoscalingKubedbComHanaDBAutoscaler/spec/storage/hanadb/upperBound + paths: + - schema/properties/resources/properties/autoscalingKubedbComHanaDBAutoscaler/properties/spec/properties/storage/properties/hanadb/properties/upperBound + label: HanaDB + showLabels: true + type: block-layout + - elements: + - label: Timeout + schema: schema/properties/resources/properties/autoscalingKubedbComHanaDBAutoscaler/properties/spec/properties/opsRequestOptions/properties/timeout + 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/resources/properties/autoscalingKubedbComHanaDBAutoscaler/properties/spec/properties/opsRequestOptions/properties/apply + type: radio + if: + name: showOpsRequestOptions + type: function + label: OpsRequest Options + showLabels: true + type: block-layout + showLabels: false + type: block-layout + id: storage-autoscaler + type: single-step-form +type: multi-step-form diff --git a/charts/kubedbcom-hanadb-editor/ui/functions.js b/charts/kubedbcom-hanadb-editor/ui/functions.js new file mode 100644 index 0000000000..7341cbec6e --- /dev/null +++ b/charts/kubedbcom-hanadb-editor/ui/functions.js @@ -0,0 +1,571 @@ +const { axios, useOperator, store } = window.vueHelpers || {} + +export const useFunc = (model) => { + const { getValue, setDiscriminatorValue, commit, storeGet, discriminator } = useOperator( + model, + store.state, + ) + + setDiscriminatorValue('/enableMonitoring', false) + setDiscriminatorValue('/customizeExporter', true) + setDiscriminatorValue('/valueFromType', 'input') + setDiscriminatorValue('/env', []) + + let autoscaleType = 'compute' + let instance = '' + let showStoragememory = false + + // ************************* Autoscaler ************************* + + function isKubedb() { + return !!storeGet('/route/params/actions') + } + + function isConsole() { + const isKube = isKubedb() + if (isKube) { + const dbName = storeGet('/route/params/name') || '' + commit('wizard/model$update', { + path: '/resources/autoscalingKubedbComHanaDBAutoscaler/spec/databaseRef/name', + value: dbName, + force: true, + }) + const operation = storeGet('/route/params/actions') || '' + if (operation.length) { + const splitOp = operation.split('-') + if (splitOp.length > 2) autoscaleType = splitOp[2] + } + const date = Math.floor(Date.now() / 1000) + const modifiedName = `${dbName}-${date}-autoscaling-${autoscaleType}` + commit('wizard/model$update', { + path: '/resources/autoscalingKubedbComHanaDBAutoscaler/metadata/name', + value: modifiedName, + force: true, + }) + const namespace = storeGet('/route/query/namespace') || '' + if (namespace) { + commit('wizard/model$update', { + path: '/resources/autoscalingKubedbComHanaDBAutoscaler/metadata/namespace', + value: namespace, + force: true, + }) + } + } + return !isKube + } + + async function getHanaDBDbs() { + const namespace = getValue( + model, + '/resources/autoscalingKubedbComHanaDBAutoscaler/metadata/namespace', + ) + const owner = storeGet('/route/params/user') + const cluster = storeGet('/route/params/cluster') + const resp = await axios.get( + `/clusters/${owner}/${cluster}/proxy/kubedb.com/v1alpha2/namespaces/${namespace}/hanadbs`, + { 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 } + }) + } + + function initMetadata() { + const dbName = + getValue(model, '/resources/autoscalingKubedbComHanaDBAutoscaler/spec/databaseRef/name') || '' + const type = getValue(discriminator, '/autoscalingType') || '' + const date = Math.floor(Date.now() / 1000) + const resource = storeGet('/route/params/resource') + const scalingName = dbName ? dbName : resource + const modifiedName = `${scalingName}-${date}-autoscaling-${type ? type : ''}` + if (modifiedName) + commit('wizard/model$update', { + path: '/resources/autoscalingKubedbComHanaDBAutoscaler/metadata/name', + value: modifiedName, + force: true, + }) + if (type === 'compute') + commit('wizard/model$delete', '/resources/autoscalingKubedbComHanaDBAutoscaler/spec/storage') + if (type === 'storage') + commit('wizard/model$delete', '/resources/autoscalingKubedbComHanaDBAutoscaler/spec/compute') + } + + async function fetchTopologyMachines() { + const annotations = + getValue(model, '/resources/autoscalingKubedbComHanaDBAutoscaler/metadata/annotations') || {} + instance = annotations['kubernetes.io/instance-type'] + const user = storeGet('/route/params/user') + const cluster = storeGet('/route/params/cluster') + if (instance) { + try { + const url = `/clusters/${user}/${cluster}/proxy/node.k8s.appscode.com/v1alpha1/nodetopologies/kubedb-ui-machine-profiles` + const resp = await axios.get(url) + const nodeGroups = resp.data?.spec?.nodeGroups || [] + setDiscriminatorValue('/topologyMachines', nodeGroups) + return nodeGroups + } catch (e) { + console.log(e) + return [] + } + } + } + + function setTrigger(path) { + let value = getValue(model, `/resources/${path}`) + return value === 'On' + } + + function onTriggerChange(type) { + const trigger = getValue(discriminator, `/${type}/trigger`) + const commitPath = `/resources/autoscalingKubedbComHanaDBAutoscaler/spec/${type}/trigger` + commit('wizard/model$update', { path: commitPath, value: trigger ? 'On' : 'Off', force: true }) + } + + function hasAnnotations() { + const annotations = + getValue(model, '/resources/autoscalingKubedbComHanaDBAutoscaler/metadata/annotations') || {} + const inst = annotations['kubernetes.io/instance-type'] + return !!inst + } + + function hasNoAnnotations() { + return !hasAnnotations() + } + + function setAllowedMachine(minmax) { + const mx = instance?.includes(',') ? instance.split(',')[1] : '' + const mn = instance?.includes(',') ? instance.split(',')[0] : '' + const machineName = minmax === 'min' ? mn : mx + const nodeGroups = getValue(discriminator, '/topologyMachines') || [] + const machineData = nodeGroups.find((item) => item.topologyValue === machineName) + if (machineData) { + return { machine: machineName, cpu: machineData.allocatable?.cpu, memory: machineData.allocatable?.memory } + } + return { machine: machineName || '', cpu: '', memory: '' } + } + + function getMachines(minmax) { + const depends = minmax === 'min' ? 'max' : 'min' + const dependantMachineObj = getValue(discriminator, `/allowedMachine-${depends}`) + const dependantMachine = dependantMachineObj?.machine || '' + const nodeGroups = getValue(discriminator, '/topologyMachines') || [] + const dependantIndex = nodeGroups?.findIndex((item) => item.topologyValue === dependantMachine) + const machines = nodeGroups?.map((item) => ({ + text: item.topologyValue, + subtext: `CPU: ${item.allocatable?.cpu}, Memory: ${item.allocatable?.memory}`, + value: { machine: item.topologyValue, cpu: item.allocatable?.cpu, memory: item.allocatable?.memory }, + })) + const filteredMachine = machines?.filter((item, ind) => + minmax === 'min' ? ind <= dependantIndex : ind >= dependantIndex, + ) + return dependantIndex === -1 ? machines : filteredMachine + } + + function onMachineChange(type) { + const annoPath = '/resources/autoscalingKubedbComHanaDBAutoscaler/metadata/annotations' + const annotations = getValue(model, annoPath) || {} + const inst = annotations['kubernetes.io/instance-type'] + const minMachineObj = getValue(discriminator, '/allowedMachine-min') + const maxMachineObj = getValue(discriminator, '/allowedMachine-max') + const minMachine = minMachineObj?.machine || '' + const maxMachine = maxMachineObj?.machine || '' + const minMaxMachine = `${minMachine},${maxMachine}` + annotations['kubernetes.io/instance-type'] = minMaxMachine + const minMachineAllocatable = minMachineObj ? { cpu: minMachineObj.cpu, memory: minMachineObj.memory } : null + const maxMachineAllocatable = maxMachineObj ? { cpu: maxMachineObj.cpu, memory: maxMachineObj.memory } : null + const allowedPath = `/resources/autoscalingKubedbComHanaDBAutoscaler/spec/compute/${type}` + if (minMachine && maxMachine && inst !== minMaxMachine) { + commit('wizard/model$update', { path: `${allowedPath}/maxAllowed`, value: maxMachineAllocatable, force: true }) + commit('wizard/model$update', { path: `${allowedPath}/minAllowed`, value: minMachineAllocatable, force: true }) + commit('wizard/model$update', { path: annoPath, value: { ...annotations }, force: true }) + } + } + + function setControlledResources(type) { + const list = ['cpu', 'memory'] + const path = `/resources/autoscalingKubedbComHanaDBAutoscaler/spec/compute/${type}/controlledResources` + commit('wizard/model$update', { path, value: list, force: true }) + return list + } + + async function fetchNodeTopology() { + const owner = storeGet('/route/params/user') || '' + const cluster = storeGet('/route/params/cluster') || '' + const url = `/clusters/${owner}/${cluster}/proxy/node.k8s.appscode.com/v1alpha1/nodetopologies` + try { + const resp = await axios.get(url) + const list = (resp && resp.data?.items) || [] + return list.map((item) => (item.metadata && item.metadata.name) || '') + } catch (e) { + console.log(e) + } + return [] + } + + function isNodeTopologySelected() { + const nodeTopologyName = + getValue(model, '/resources/autoscalingKubedbComHanaDBAutoscaler/spec/compute/nodeTopology/name') || '' + return !!nodeTopologyName.length + } + + function showOpsRequestOptions() { + if (isKubedb() === true) return true + return ( + !!getValue(model, '/resources/autoscalingKubedbComHanaDBAutoscaler/spec/databaseRef/name') && + !!getValue(discriminator, '/autoscalingType') + ) + } + + function setApplyToIfReady() { + return 'IfReady' + } + + function showStorageMemoryOption() { + return showStoragememory + } + + // ************************* Monitoring ************************* + + function showMonitoringSection() { + const configureStatus = getValue(discriminator, '/enableMonitoring') + return configureStatus + } + + function onEnableMonitoringChange() { + const configureStatus = getValue(discriminator, '/enableMonitoring') + if (configureStatus) { + commit('wizard/model$update', { + path: '/resources/kubedbComHanaDB/spec/monitor', + value: {}, + force: true, + }) + } else { + commit('wizard/model$delete', '/resources/kubedbComHanaDB/spec/monitor') + } + + commit('wizard/model$update', { + path: '/form/alert/enabled', + value: configureStatus ? 'warning' : 'none', + force: true, + }) + } + + function showCustomizeExporterSection() { + const configureStatus = getValue(discriminator, '/customizeExporter') + return configureStatus + } + + function onCustomizeExporterChange() { + const configureStatus = getValue(discriminator, '/customizeExporter') + if (configureStatus) { + commit('wizard/model$update', { + path: '/resources/kubedbComHanaDB/spec/monitor/prometheus/exporter', + value: {}, + force: true, + }) + } else { + commit('wizard/model$delete', '/resources/kubedbComHanaDB/spec/monitor/prometheus/exporter') + } + } + + function isValueExistInModel(path) { + const modelValue = getValue(model, path) || null + return !!modelValue + } + + function onAgentChange() { + const agent = getValue(model, '/resources/kubedbComHanaDB/spec/monitor/agent') + if (agent === 'prometheus.io') { + commit('wizard/model$update', { + path: '/resources/monitoringCoreosComServiceMonitor/spec/endpoints', + value: [], + force: true, + }) + } else { + commit('wizard/model$delete', '/resources/monitoringCoreosComServiceMonitor') + } + } + + function isEqualToModelPathValue(value, modelPath) { + const modelPathValue = getValue(model, modelPath) + return modelPathValue === value + } + + function initMonitoring() { + const env = + getValue(model, '/resources/kubedbComHanaDB/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) + } + + function onEnvArrayChange() { + const env = getValue(discriminator, '/env') || [] + let ret = {} + 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/kubedbComHanaDB/spec/monitor/prometheus/exporter/env', + value: filteredEnv, + force: true, + }) + } + + function initEnvArray() { + const env = getValue( + model, + '/resources/kubedbComHanaDB/spec/monitor/prometheus/exporter/env', + ) + return env || [] + } + + function isEqualToTemp(value, index) { + const valueFrom = getValue(discriminator, `/env/${index}/temp/valueFromType`) + return valueFrom === value + } + + function setValueFrom() { + if (isConfigMapTypeValueFrom()) return 'configMap' + else if (isSecretTypeValueFrom()) return 'secret' + else return 'input' + } + + function isConfigMapTypeValueFrom() { + const valueFrom = getValue(discriminator, '/valueFrom') + return !!(valueFrom && valueFrom.configMapKeyRef) + } + + 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 }) + } + } + + // ************************* Utilities ************************* + + function returnFalse() { + return false + } + + function isRancherManaged() { + const managers = storeGet('/cluster/clusterDefinition/result/clusterManagers') + const found = managers.find((item) => item === 'Rancher') + return !!found + } + + async function getNamespaces() { + 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 resourceNames(group, version, resource) { + const owner = storeGet('/route/params/user') + const cluster = storeGet('/route/params/cluster') + const namespace = getValue(model, '/metadata/release/namespace') + + try { + const resp = await axios.get( + `/clusters/${owner}/${cluster}/proxy/${group}/${version}/namespaces/${namespace}/${resource}`, + { params: { filter: { items: { metadata: { name: null }, type: null } } } }, + ) + let items = (resp && resp.data && resp.data.items) || [] + if (resource === 'secrets') { + items = items.filter((item) => { + const validType = ['kubernetes.io/service-account-token', 'Opaque'] + return validType.includes(item.type) + }) + } + return items.map((item) => { + const name = (item.metadata && item.metadata.name) || '' + return { text: name, value: name } + }) + } catch (e) { + console.log(e) + return [] + } + } + + async function getConfigMapKeys(index) { + const owner = storeGet('/route/params/user') + const cluster = storeGet('/route/params/cluster') + const namespace = getValue(model, '/metadata/release/namespace') + const configMapName = getValue( + model, + `/resources/kubedbComHanaDB/spec/monitor/prometheus/exporter/env/${index}/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) || {} + return Object.keys(configMaps).map((item) => ({ text: item, value: item })) + } 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') + try { + const resp = await axios.get( + `/clusters/${owner}/${cluster}/proxy/core/v1/namespaces/${namespace}/secrets`, + { params: { filter: { items: { metadata: { name: null }, type: null } } } }, + ) + const items = (resp && resp.data && resp.data.items) || [] + return items + .filter((item) => ['kubernetes.io/service-account-token', 'Opaque'].includes(item.type)) + .map((item) => { + const name = (item.metadata && item.metadata.name) || '' + return { text: name, value: name } + }) + } 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(model, '/metadata/release/namespace') + const secretName = getValue( + model, + `/resources/kubedbComHanaDB/spec/monitor/prometheus/exporter/env/${index}/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) || {} + return Object.keys(secret).map((item) => ({ text: item, value: item })) + } catch (e) { + console.log(e) + return [] + } + } + + 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/hanadbopsrequests/create?name=${dbname}&namespace=${namespace}&group=${group}&version=${version}&resource=${resource}&kind=${kind}&page=operations&requestType=VerticalScaling` + } + + return { + returnFalse, + isKubedb, + isConsole, + isRancherManaged, + getNamespaces, + getHanaDBDbs, + initMetadata, + fetchTopologyMachines, + setTrigger, + onTriggerChange, + hasAnnotations, + hasNoAnnotations, + setAllowedMachine, + getMachines, + onMachineChange, + setControlledResources, + fetchNodeTopology, + isNodeTopologySelected, + showOpsRequestOptions, + setApplyToIfReady, + showStorageMemoryOption, + resourceNames, + getConfigMapKeys, + getSecrets, + getSecretKeys, + getOpsRequestUrl, + isValueExistInModel, + isEqualToModelPathValue, + onEnableMonitoringChange, + showMonitoringSection, + onAgentChange, + onCustomizeExporterChange, + showCustomizeExporterSection, + initMonitoring, + onEnvArrayChange, + initEnvArray, + isEqualToTemp, + setValueFrom, + isConfigMapTypeValueFrom, + isSecretTypeValueFrom, + onValueFromChange, + } +} diff --git a/charts/kubedbcom-hanadb-editor/ui/language.yaml b/charts/kubedbcom-hanadb-editor/ui/language.yaml new file mode 100644 index 0000000000..17499110f6 --- /dev/null +++ b/charts/kubedbcom-hanadb-editor/ui/language.yaml @@ -0,0 +1,27 @@ +en: + labels: + agent: Select a Monitoring Method + hanadb: HanaDB + customize_exporter: Customize Exporter Sidecar + enable_monitoring: Enable Monitoring + exporter_configuration: Exporter Configuration + honor_labels: Honor labels + interval: Interval + path: Path + port: Port + run_as_group: Run as Group + run_as_user: Run as User + scrapping_interval: Scrapping Interval + service_monitor: Service Monitor + service_monitor_configuration: ServiceMonitor Configuration + options: + agent: + prometheus: + description: Injects the metric exporter sidecar and let you customize ServiceMonitor + label: Custom ServiceMonitor + prometheus_builtin: + description: Inject metric exporter sidecar and add Prometheus annotations to the stats Service + label: Custom Scrapper + prometheus_operator: + description: Inject metric exporter sidecar and creates a ServiceMonitor + label: Prometheus Operator diff --git a/charts/opskubedbcom-hanadbopsrequest-editor/ui/create-ui.yaml b/charts/opskubedbcom-hanadbopsrequest-editor/ui/create-ui.yaml new file mode 100644 index 0000000000..3fc614b536 --- /dev/null +++ b/charts/opskubedbcom-hanadbopsrequest-editor/ui/create-ui.yaml @@ -0,0 +1,508 @@ +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: Manage your CPU resources + text: Vertical Scaling + value: VerticalScaling + - 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: + - 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 + label: HanaDB vertical scaling + showLabels: false + type: block-layout + if: + name: ifRequestTypeEqualsTo|VerticalScaling + type: function + 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: + - 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-hanadbopsrequest-editor/ui/functions.js b/charts/opskubedbcom-hanadbopsrequest-editor/ui/functions.js new file mode 100644 index 0000000000..c85db2aa24 --- /dev/null +++ b/charts/opskubedbcom-hanadbopsrequest-editor/ui/functions.js @@ -0,0 +1,1924 @@ +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 = ['hanadb.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?.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}/hanadbs`, + { + 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}/hanadbs/${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?.HanaDB?.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/hanadbversions`, + { + 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 filteredHanaDBVersions = 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', filteredHanaDBVersions) + + return filteredHanaDBVersions.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 = { + VerticalScaling: 'verticalScaling', + 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 = { + verticalscaling: 'VerticalScaling', + 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 limits = dbDetails?.spec?.podTemplate?.spec?.resources?.requests || {} + + 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 limits = dbDetails?.spec?.podTemplate?.spec?.resources?.requests || {} + 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: ['hanadb.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 limits = dbDetails?.spec?.podTemplate?.spec?.resources?.requests || {} + + 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-hanadbopsrequest-editor/ui/language.yaml b/charts/opskubedbcom-hanadbopsrequest-editor/ui/language.yaml new file mode 100644 index 0000000000..34fccb3ac2 --- /dev/null +++ b/charts/opskubedbcom-hanadbopsrequest-editor/ui/language.yaml @@ -0,0 +1,307 @@ +bn: {} +en: + labels: + agent: Select a Monitoring Method + hanadb: HanaDB + 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 HanaDB 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 756e847d50f7871a858c3ff4373d52ae9e6faa2a Mon Sep 17 00:00:00 2001 From: Sourav-Kumar-19 Date: Fri, 5 Jun 2026 12:13:56 +0600 Subject: [PATCH 2/3] add hanadb create-ui.yaml Signed-off-by: Sourav-Kumar-19 --- .../kubedbcom-hanadb-editor/ui/create-ui.yaml | 530 ++++++++++++++++++ 1 file changed, 530 insertions(+) create mode 100644 charts/kubedbcom-hanadb-editor/ui/create-ui.yaml diff --git a/charts/kubedbcom-hanadb-editor/ui/create-ui.yaml b/charts/kubedbcom-hanadb-editor/ui/create-ui.yaml new file mode 100644 index 0000000000..1b5eb62842 --- /dev/null +++ b/charts/kubedbcom-hanadb-editor/ui/create-ui.yaml @@ -0,0 +1,530 @@ +steps: +- form: + discriminator: + createAuthSecret: + type: boolean + password: + type: string + elements: + - disabled: isVariantAvailable + label: + text: labels.database.name + onChange: onNameChange + 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: getHanaDBVersions|catalog.kubedb.com|v1alpha1|hanadbversions + label: + text: labels.database.version + schema: + $ref: schema#/properties/resources/properties/kubedbComHanaDB/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/kubedbComHanaDB/properties/metadata/properties/labels + type: key-value-input-form + values: + label: + text: labels.labels.value + schema: + $ref: schema#/properties/resources/properties/kubedbComHanaDB/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/kubedbComHanaDB/properties/metadata/properties/annotations + type: key-value-input-form + values: + label: + text: labels.annotations.value + schema: + $ref: schema#/properties/resources/properties/kubedbComHanaDB/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/kubedbComHanaDB/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/kubedbComHanaDB/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 + 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: + elements: + - label: + text: labels.replicaset.number + schema: + $ref: schema#/properties/resources/properties/kubedbComHanaDB/properties/spec/properties/replicas + type: input + - elements: + - fetch: getStorageClassNames + label: + text: labels.storage.class + schema: + $ref: schema#/properties/resources/properties/kubedbComHanaDB/properties/spec/properties/storage/properties/storageClassName + type: select + - label: + text: labels.storage.size + schema: + $ref: schema#/properties/resources/properties/kubedbComHanaDB/properties/spec/properties/storage/properties/resources/properties/requests/properties/storage + type: input + 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: + - elements: + - computed: setApiGroup + disabled: true + label: + text: labels.api_group + schema: + $ref: schema#/properties/resources/properties/kubedbComHanaDB/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/kubedbComHanaDB/properties/spec/properties/tls/properties/issuerRef/properties/kind + type: select + - allowUserDefinedOption: true + fetch: getIssuerRefsName + label: + text: labels.name + schema: + $ref: schema#/properties/resources/properties/kubedbComHanaDB/properties/spec/properties/tls/properties/issuerRef/properties/name + type: select + label: + text: labels.issuer_ref + schema: + $ref: schema#/properties/resources/properties/kubedbComHanaDB/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/kubedbComHanaDB/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: + 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/kubedbComHanaDB/properties/spec/properties/monitor/properties/agent + type: radio + - elements: + - label: + text: labels.scrapping_interval + schema: + $ref: schema#/properties/resources/properties/kubedbComHanaDB/properties/spec/properties/monitor/properties/prometheus/properties/serviceMonitor/properties/interval + type: input + if: isEqualToModelPathValue|prometheus.io/operator|/resources/kubedbComHanaDB/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/kubedbComHanaDB/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/kubedbComHanaDB/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/kubedbComHanaDB/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/kubedbComHanaDB/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/kubedbComHanaDB/properties/spec/properties/monitor/properties/prometheus/properties/exporter/properties/securityContext/properties/runAsGroup + type: input + - label: + text: labels.port + schema: + $ref: schema#/properties/resources/properties/kubedbComHanaDB/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/kubedbComHanaDB/properties/spec/properties/monitor/properties/prometheus/properties/exporter/properties/args/items + type: input + label: + text: labels.args + schema: + $ref: schema#/properties/resources/properties/kubedbComHanaDB/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/kubedbComHanaDB/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.3.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/kubedbComHanaDB/properties/spec/properties/podTemplate + type: reusable-element + type: single-step-form + id: pod-template + title: steps.4.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/kubedbComHanaDB/properties/spec/properties/serviceTemplates + type: reusable-element + type: single-step-form + id: networking + title: steps.5.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/kubedbComHanaDB/properties/spec/properties/configuration/properties/secretName + type: select + - computed: setConfiguration + if: isEqualToDiscriminatorPath|create-new-config|/configurationSource + label: + text: labels.hanadb_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.6.label +type: multi-step-form From a7ca820100b83bf77c2159fbdd7e40d9f4e36a58 Mon Sep 17 00:00:00 2001 From: Sourav-Kumar-19 Date: Tue, 9 Jun 2026 17:30:38 +0600 Subject: [PATCH 3/3] fix(hanadb-ops): fix JS bugs, remove dead getDbVersions function - Fix getVersion(): declare filteredVersion from discriminator in scope - Fix disableOpsRequest(): remove undefined itemCtx reference (HanaDB has no topology and no HorizontalScaling op type) - Remove getDbVersions(): HanaDB schema has no UpdateVersion ops type so this function is dead code; also contained wrong isGroupRepl logic using allowlist.groupReplication which is MySQL-specific Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Sourav-Kumar-19 --- .../ui/functions.js | 109 +----------------- 1 file changed, 2 insertions(+), 107 deletions(-) diff --git a/charts/opskubedbcom-hanadbopsrequest-editor/ui/functions.js b/charts/opskubedbcom-hanadbopsrequest-editor/ui/functions.js index c85db2aa24..45a9b412b7 100644 --- a/charts/opskubedbcom-hanadbopsrequest-editor/ui/functions.js +++ b/charts/opskubedbcom-hanadbopsrequest-editor/ui/functions.js @@ -427,107 +427,7 @@ export const useFunc = (model) => { } 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?.HanaDB?.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/hanadbversions`, - { - 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 filteredHanaDBVersions = 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', filteredHanaDBVersions) - - return filteredHanaDBVersions.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') @@ -545,6 +445,7 @@ export const useFunc = (model) => { } function getVersion() { + const filteredVersion = getValue(discriminator, '/filteredVersion') return filteredVersion.map((item) => { const name = (item.metadata && item.metadata.name) || '' const specVersion = (item.spec && item.spec.version) || '' @@ -618,12 +519,7 @@ export const useFunc = (model) => { } function disableOpsRequest() { - if (itemCtx.value === 'HorizontalScaling') { - const dbType = getDbType() - - if (dbType === 'standalone') return true - else return false - } else return false + return false } function getDbTls() { @@ -1851,7 +1747,6 @@ export const useFunc = (model) => { getNamespaces, getDbs, getDbDetails, - getDbVersions, getVersionInfo, isVersionEmpty, getVersion,