diff --git a/electron/app/js/userSettings.js b/electron/app/js/userSettings.js index ae224bd69..4cb528aa7 100644 --- a/electron/app/js/userSettings.js +++ b/electron/app/js/userSettings.js @@ -54,7 +54,8 @@ let _userSettingsFileName; // "dividers": { // "modelMain": 0.68, // "modelRight": 0.48 -// } +// }, +// "navCollapsed": true // } // } // @@ -185,7 +186,7 @@ function getDividerLocations() { function setNavigationCollapsed(collapsed) { const window = getOrCreateWindowSettings(); - window['navCollapsed'] = collapsed; + window['navCollapsed'] = Boolean(collapsed); } function getNavigationCollapsed() { diff --git a/electron/app/locales/en/webui.json b/electron/app/locales/en/webui.json index 3678c516a..60fff198c 100644 --- a/electron/app/locales/en/webui.json +++ b/electron/app/locales/en/webui.json @@ -566,6 +566,10 @@ "ingress-design-ingress-route-annotation-add-row": "Add Annotation", "ingress-design-ingress-route-annotation-delete-row": "Delete Annotation", + "ingress-design-ingress-route-name-field-validation-error": "Route {{routeName}}", + "ingress-design-ingress-route-field-validation-error": "Route: {{routeName}}, Field: {{fieldName}}", + "ingress-design-ingress-route-field-tls-config-error": "The route {{routeName}} has the {{fieldName}} field enabled but the {{specifyTlsSecretFieldName}} field is disabled.", + "ingress-design-ingress-tls-secret-title": "TLS Secret for Ingress Routes", "ingress-design-specify-tls-secret-label": "Use Ingress TLS Secret", "ingress-design-specify-tls-secret-help": "To secure traffic to the ingress controller with TLS, specify the secret that contains the TLS private key and certificate. The Ingress resource only supports a single TLS port, 443, and assumes TLS termination at the ingress (traffic between the ingress and the service and its pods is in clear text).", @@ -934,6 +938,8 @@ "validation-helper-ingress-annotation-hint": "Enter a valid annotation name value pair", "validation-helper-ingress-annotation-message-detail": "The annotation ({{annotation}}) must be a name value pair separate by colon", + "validation-error-dialog-default-title": "Unknown action failed", + "vz-config-title": "Kubernetes Client Configuration for Verrazzano", "vz-config-coming-soon": "Coming Soon...", "vz-application-title": "Verrazzano Application", diff --git a/webui/src/js/models/ingress-definition.js b/webui/src/js/models/ingress-definition.js index 0ec2ee814..039e03453 100644 --- a/webui/src/js/models/ingress-definition.js +++ b/webui/src/js/models/ingress-definition.js @@ -34,6 +34,8 @@ define(['knockout', 'utils/observable-properties', 'utils/validation-helper'], this.dockerRegSecretUserId = props.createProperty('').asCredential(); this.dockerRegSecretUserPwd = props.createProperty('').asCredential(); this.dockerRegSecretUserEmail = props.createProperty(''); + this.dockerRegSecretUserEmail.addValidator(...validationHelper.getEmailAddressValidators()); + this.createDockerRegSecret = props.createProperty(false); this.specifyDockerRegSecret = props.createProperty(false); this.specifyIngressTLSSecret = props.createProperty(false); @@ -46,10 +48,11 @@ define(['knockout', 'utils/observable-properties', 'utils/validation-helper'], this.opensslExecutableFilePath = props.createProperty(window.api.k8s.getOpenSSLFilePath()); - this.validators = { TargetPortValidator: validationHelper.getPortNumberValidators(), + this.validators = { + targetPortValidator: validationHelper.getPortNumberValidators(), k8sNameValidator: validationHelper.getK8sNameValidators(), - VirtualHostNameValidator: validationHelper.getHostNameValidators(), - IngressPathValidator: validationHelper.getIngressPathValidators() + virtualHostNameValidator: validationHelper.getHostNameValidators(), + ingressPathValidator: validationHelper.getIngressPathValidators() }; this.voyagerProvider = props.createProperty('OKE'); diff --git a/webui/src/js/utils/ingress-controller-installer.js b/webui/src/js/utils/ingress-controller-installer.js index 033ed65af..ea551495c 100644 --- a/webui/src/js/utils/ingress-controller-installer.js +++ b/webui/src/js/utils/ingress-controller-installer.js @@ -228,7 +228,6 @@ function(project, wktConsole, k8sHelper, i18n, projectIo, dialogHelper, validati if (ingressControllerProvider === 'traefik' && dockerSecretName) { args['deployment.imagePullSecrets[0].name'] = dockerSecretName; } - if (ingressControllerProvider === 'traefik' || ingressControllerProvider === 'nginx') { args['kubernetes.namespaces'] = '{' + ingressControllerNamespace + ',' + this.project.k8sDomain.kubernetesNamespace.value + '}'; } @@ -256,13 +255,8 @@ function(project, wktConsole, k8sHelper, i18n, projectIo, dialogHelper, validati await window.api.ipc.invoke('show-error-message', errTitle, errMessage); return Promise.resolve(false); } - } - - } - - } catch(err) { dialogHelper.closeBusyDialog(); throw err; @@ -271,7 +265,6 @@ function(project, wktConsole, k8sHelper, i18n, projectIo, dialogHelper, validati } }; - this.getHelmOptions = () => { const options = {}; if (this.project.kubectl.kubeConfig.value) { @@ -303,40 +296,31 @@ function(project, wktConsole, k8sHelper, i18n, projectIo, dialogHelper, validati validationHelper.validateRequiredField(this.project.ingress.ingressControllerProvider.value)); if (this.project.ingress.installIngressController.value === true) { - validationObject.addField('ingress-design-ingress-namespace-label', - validationHelper.validateRequiredField(this.project.ingress.ingressControllerNamespace.value)); + this.project.ingress.ingressControllerNamespace.validate(true)); validationObject.addField('ingress-design-ingress-name-label', - validationHelper.validateRequiredField(this.project.ingress.ingressControllerName.value)); + this.project.ingress.ingressControllerName.validate(true)); const ingressControllerProvider = this.project.ingress.ingressControllerProvider.value; - if (ingressControllerProvider === 'traefik' || ingressControllerProvider === 'voyager' ) { - if (this.project.ingress.specifyDockerRegSecret.value === true) { validationObject.addField('ingress-design-ingress-docker-reg-secret-name', - validationHelper.validateRequiredField(this.project.ingress.dockerRegSecretName.value)); - } - - if (this.project.ingress.createDockerRegSecret.value == true) { - validationObject.addField('ingress-design-ingress-docker-reg-secret-useremail', - validationHelper.validateRequiredField(this.project.ingress.dockerRegSecretUserEmail.value)); - validationObject.addField('ingress-design-ingress-docker-reg-secret-useremail', - validationHelper.validateEmailAddress(this.project.ingress.dockerRegSecretUserEmail.value)); - validationObject.addField('ingress-design-ingress-docker-reg-secret-userid', - validationHelper.validateRequiredField(this.project.ingress.dockerRegSecretUserId.value)); - validationObject.addField('ingress-design-ingress-docker-reg-secret-userpwd', - validationHelper.validateRequiredField(this.project.ingress.dockerRegSecretUserPwd.value)); + this.project.ingress.dockerRegSecretName.validate(true)); + + if (this.project.ingress.createDockerRegSecret.value === true) { + validationObject.addField('ingress-design-ingress-docker-reg-secret-useremail', + this.project.ingress.dockerRegSecretUserEmail.validate(true)); + validationObject.addField('ingress-design-ingress-docker-reg-secret-userid', + validationHelper.validateRequiredField(this.project.ingress.dockerRegSecretUserId.value)); + validationObject.addField('ingress-design-ingress-docker-reg-secret-userpwd', + validationHelper.validateRequiredField(this.project.ingress.dockerRegSecretUserPwd.value)); + } } - } } - return validationObject; }; - } - return new IngressInstaller(); }); diff --git a/webui/src/js/utils/ingress-resource-generator.js b/webui/src/js/utils/ingress-resource-generator.js index 5f88b4f35..c9d6c3487 100644 --- a/webui/src/js/utils/ingress-resource-generator.js +++ b/webui/src/js/utils/ingress-resource-generator.js @@ -19,15 +19,15 @@ define(['models/wkt-project', 'js-yaml'], for (const route of this.project.ingress.ingressRoutes.value) { switch (this.project.ingress.ingressControllerProvider.value) { case 'voyager': - ingressRouteData = this.createVoyagerRoutesAsYaml(route, this.project); + ingressRouteData = this.createVoyagerRoutesAsYaml(route); break; case 'traefik': - ingressRouteData = this.createTraefikRoutesAsYaml(route, this.project); + ingressRouteData = this.createTraefikRoutesAsYaml(route); break; case 'nginx': - ingressRouteData = this.createNginxRoutesAsYaml(route, this.project); + ingressRouteData = this.createNginxRoutesAsYaml(route); break; } lines.push(ingressRouteData, ''); @@ -39,7 +39,7 @@ define(['models/wkt-project', 'js-yaml'], return lines; } - createVoyagerRoutesAsYaml(item, wktProject) { + createVoyagerRoutesAsYaml(item) { const namespace = item['targetServiceNameSpace'] || 'default'; const result = { @@ -67,21 +67,21 @@ define(['models/wkt-project', 'js-yaml'], ] } }; - this.addTlsSpec(result, item, wktProject); + this.addTlsSpec(result, item); this.addVirtualHost(result, item); this.addAnnotations(result, item); return jsYaml.dump(result); } - createNginxRoutesAsYaml(item, wktProject) { - return this._createStandardRoutesAsYaml(item, wktProject); + createNginxRoutesAsYaml(item) { + return this._createStandardRoutesAsYaml(item); } - createTraefikRoutesAsYaml(item, wktProject) { - return this._createStandardRoutesAsYaml(item, wktProject); + createTraefikRoutesAsYaml(item) { + return this._createStandardRoutesAsYaml(item); } - _createStandardRoutesAsYaml(item, wktProject) { + _createStandardRoutesAsYaml(item) { const namespace = item['targetServiceNameSpace'] || 'default'; const result = { @@ -114,16 +114,17 @@ define(['models/wkt-project', 'js-yaml'], ] } }; - this.addTlsSpec(result, item, wktProject); + this.addTlsSpec(result, item); this.addVirtualHost(result, item); this.addAnnotations(result, item); return jsYaml.dump(result); } - addTlsSpec(result, item, wktProject) { - if (item && item['tlsEnabled'] === true) { + addTlsSpec(result, item) { + // If the Ingress TLS secret is not enabled, do not add the ingress TLS secret name even if it exists. + if (this.project.ingress.specifyIngressTLSSecret.value && item && item['tlsEnabled'] === true) { if (!item['tlsSecretName']) { - item['tlsSecretName'] = wktProject.ingress.ingressTLSSecretName.value; + item['tlsSecretName'] = this.project.ingress.ingressTLSSecretName.value; } const obj = { secretName: item['tlsSecretName'] }; diff --git a/webui/src/js/utils/ingress-routes-helper.js b/webui/src/js/utils/ingress-routes-helper.js index 3f9a7df9d..6fc436964 100644 --- a/webui/src/js/utils/ingress-routes-helper.js +++ b/webui/src/js/utils/ingress-routes-helper.js @@ -101,11 +101,11 @@ function(project, wktConsole, k8sHelper, i18n, projectIo, dialogHelper, validati // Best effort sanity check to see if ingress controller has been installed // If user install their own ingress controller then we are not going to scan all namespaces for // controller to verify it. - + // if (this.project.ingress.installIngressController.value === true) { busyDialogMessage = i18n.t('ingress-installer-check-ingress-controller-service', {}); - dialogHelper.updateBusyDialog(busyDialogMessage, 4 / totalSteps); + dialogHelper.updateBusyDialog(busyDialogMessage, 4/totalSteps); let serviceName = ''; if (this.project.ingress.ingressControllerProvider.value === 'nginx') { serviceName = this.project.ingress.ingressControllerName.value + '-ingress-nginx-controller'; @@ -116,21 +116,21 @@ function(project, wktConsole, k8sHelper, i18n, projectIo, dialogHelper, validati const result = await this.checkIngressControllerService(kubectlExe, serviceName, this.project.ingress.ingressControllerNamespace.value, kubectlOptions); if (!result.isSuccess) { - const errMessage = i18n.t('ingress-installer-check-ingress-controller-service-not-installed', - {error: result.reason, - serviceName: serviceName, - ingressControllerName: this.project.ingress.ingressControllerName.value, - namespace: this.project.ingress.ingressControllerNamespace.value}); + const errMessage = i18n.t('ingress-installer-check-ingress-controller-service-not-installed', { + error: result.reason, + serviceName: serviceName, + ingressControllerName: this.project.ingress.ingressControllerName.value, + namespace: this.project.ingress.ingressControllerNamespace.value + }); dialogHelper.closeBusyDialog(); await window.api.ipc.invoke('show-error-message', errTitle, errMessage); return Promise.resolve(false); } } - if (this.project.ingress.createTLSSecret.value === true) { busyDialogMessage = i18n.t('ingress-installer-create-tls-secret-in-progress', {}); - dialogHelper.updateBusyDialog(busyDialogMessage, 4 / totalSteps); + dialogHelper.updateBusyDialog(busyDialogMessage, 5/totalSteps); let tlsKeyFile = 'tls1.key'; let tlsCertFile = 'tls1.crt'; @@ -150,15 +150,12 @@ function(project, wktConsole, k8sHelper, i18n, projectIo, dialogHelper, validati if (!certGenResults.isSuccess) { const errMessage = i18n.t('ingress-installer-generate-tls-files-error-message', - { - error: certGenResults.reason - }); + { error: certGenResults.reason }); dialogHelper.closeBusyDialog(); await window.api.ipc.invoke('show-error-message', errTitle, errMessage); return Promise.resolve(false); } } - } else { tlsKeyFile = this.project.ingress.ingressTLSKeyFile.value; tlsCertFile = this.project.ingress.ingressTLSCertFile.value; @@ -268,7 +265,7 @@ function(project, wktConsole, k8sHelper, i18n, projectIo, dialogHelper, validati busyDialogMessage = i18n.t('ingress-installer-create-ingress-route-in-progress', {ingressRoute: item['name']}); - dialogHelper.updateBusyDialog(busyDialogMessage, 5 / totalSteps); + dialogHelper.updateBusyDialog(busyDialogMessage, 6/totalSteps); const ingressRouteResult = await (window.api.ipc.invoke('k8s-apply', kubectlExe, ingressRouteData, kubectlOptions)); @@ -291,7 +288,6 @@ function(project, wktConsole, k8sHelper, i18n, projectIo, dialogHelper, validati item['accessPoint'] = 'Route has been created but cannot determine access point at the moment'; } } - } else { errTitle = i18n.t('ingress-installer-create-ingress-route-error-title'); const errMessage = i18n.t('ingress-installer-create-ingress-route-error-message', @@ -299,21 +295,14 @@ function(project, wktConsole, k8sHelper, i18n, projectIo, dialogHelper, validati await window.api.ipc.invoke('show-error-message', errTitle, errMessage); return Promise.resolve(false); } - } const title = i18n.t('ingress-installer-routes-update-complete-title'); const message = i18n.t('ingress-installer-routes-update-complete-message'); await window.api.ipc.invoke('show-info-message', title, message); - - dialogHelper.closeBusyDialog(); - - } else { - dialogHelper.closeBusyDialog(); } - } catch(err) { dialogHelper.closeBusyDialog(); - throw err; + return Promise.reject(err); } finally { dialogHelper.closeBusyDialog(); } @@ -336,68 +325,122 @@ function(project, wktConsole, k8sHelper, i18n, projectIo, dialogHelper, validati validationObject.addField('ingress-design-ingress-provider-label', validationHelper.validateRequiredField(this.project.ingress.ingressControllerProvider.value)); - const ingressRoutesArrayLength = this.project.ingress.ingressRoutes.value.length; - let hasTLSRoutes = false; - for (let i = 0; i < ingressRoutesArrayLength ; i++) { - const item = this.project.ingress.ingressRoutes.value[i]; - if (item['tlsEnabled'] === true) { - hasTLSRoutes = true; - } - this.checkIngressData(item, validationObject, settingsFormConfig); - } - if (this.project.ingress.specifyIngressTLSSecret.value === true) { validationObject.addField('ingress-design-tls-secret-name-label', - validationHelper.validateRequiredField(this.project.ingress.ingressTLSSecretName.value)); - } + this.project.ingress.ingressTLSSecretName.validate(true)); - if (this.project.ingress.createTLSSecret.value === true && this.project.ingress.generateTLSFiles.value === false) { - validationObject.addField('ingress-design-ingress-tlskeyfile-label', - validationHelper.validateRequiredField(this.project.ingress.ingressTLSKeyFile.value)); - validationObject.addField('ingress-design-ingress-tlscertfile-label', - validationHelper.validateRequiredField(this.project.ingress.ingressTLSCertFile.value)); + if (this.project.ingress.createTLSSecret.value === true) { + if (this.project.ingress.generateTLSFiles.value === true) { + validationObject.addField('ingress-design-openssl-exe-file-path-label', + validationHelper.validateRequiredField(this.project.ingress.opensslExecutableFilePath.value)); + validationObject.addField('ingress-design-generate-tls-subject-label', + validationHelper.validateRequiredField(this.project.ingress.ingressTLSSubject.value)); + } else { + validationObject.addField('ingress-design-ingress-tlskeyfile-label', + validationHelper.validateRequiredField(this.project.ingress.ingressTLSKeyFile.value)); + validationObject.addField('ingress-design-ingress-tlscertfile-label', + validationHelper.validateRequiredField(this.project.ingress.ingressTLSCertFile.value)); + } + } } - if (hasTLSRoutes) { - validationObject.addField('ingress-design-tls-secret-name-label', - validationHelper.validateRequiredField(this.project.ingress.ingressTLSSecretName.value)); + const ingressRoutesArrayLength = this.project.ingress.ingressRoutes.value.length; + for (let i = 0; i < ingressRoutesArrayLength ; i++) { + const item = this.project.ingress.ingressRoutes.value[i]; + this.checkIngressData(item, validationObject, this.project.ingress.specifyIngressTLSSecret.value); } return validationObject; }; - this.checkTLSFileExists = async (file)=> { - if (typeof file !== 'undefined' && file !== '') { - const result = await window.api.ipc.invoke('verify-file-exists', file); - - if (result.isValid) { - return Promise.resolve(true); + this.checkTLSFileExists = async (file) => { + return new Promise(resolve => { + if (file) { + window.api.ipc.invoke('verify-file-exists', file).then(result => { + if (result.isValid) { + resolve(true); + } else { + resolve(false); + } + }); } else { - return Promise.resolve(false); + resolve(false); } - } - return Promise.resolve(false); + }); }; - this.checkIngressData = (data, validationObject)=> { - const items = ['name', 'targetServiceNameSpace', 'targetService', 'targetPort', 'path']; + this.checkIngressData = (data, validationObject, tlsSecretSpecified) => { + const routeConfig = validationObject.getDefaultConfigObject(); + routeConfig.fieldNameIsKey = false; + + const items = ['name', 'targetServiceNameSpace', 'targetService', 'targetPort', 'path', 'virtualHost']; + let errFieldMessage; + items.forEach(attribute => { - let errFieldMessage = 'route name: '; - if (attribute === 'targetServiceNameSpace') { - errFieldMessage += data['name'] + ' ' + i18n.t('ingress-design-ingress-route-targetservicenamespace-label'); - } - if (attribute === 'targetService') { - errFieldMessage += data['name'] + ' ' + i18n.t('ingress-design-ingress-route-targetservice-label'); - } - if (attribute === 'targetPort') { - errFieldMessage += data['name'] + ' ' + i18n.t('ingress-design-ingress-route-targetport-label'); - } - if (attribute === 'path') { - errFieldMessage += data['name'] + ' ' + i18n.t('ingress-design-ingress-route-targetport-label'); - } + let validators; + let isRequired = true; + switch (attribute) { + case 'name': + errFieldMessage = i18n.t('ingress-design-ingress-route-name-field-validation-error', { routeName: data['name'] }); + validators = this.project.ingress.validators.k8sNameValidator; + break; + + case 'targetServiceNameSpace': + errFieldMessage = i18n.t('ingress-design-ingress-route-field-validation-error', { + routeName: data['name'], + fieldName: i18n.t('ingress-design-ingress-route-targetservicenamespace-label') + }); + validators = this.project.ingress.validators.k8sNameValidator; + break; + case 'targetService': + errFieldMessage = i18n.t('ingress-design-ingress-route-field-validation-error', { + routeName: data['name'], + fieldName: i18n.t('ingress-design-ingress-route-targetservice-label') + }); + validators = this.project.ingress.validators.k8sNameValidator; + break; + + case 'targetPort': + errFieldMessage = i18n.t('ingress-design-ingress-route-field-validation-error', { + routeName: data['name'], + fieldName: i18n.t('ingress-design-ingress-route-targetport-label') + }); + validators = this.project.ingress.validators.targetPortValidator; + break; + + case 'path': + errFieldMessage = i18n.t('ingress-design-ingress-route-field-validation-error', { + routeName: data['name'], + fieldName: i18n.t('ingress-design-ingress-route-path-label') + }); + validators = this.project.ingress.validators.ingressPathValidator; + break; + + case 'virtualHost': + errFieldMessage = i18n.t('ingress-design-ingress-route-field-validation-error', { + routeName: data['name'], + fieldName: i18n.t('ingress-design-ingress-route-virtualhost-label') + }); + validators = this.project.ingress.validators.virtualHostNameValidator; + isRequired = false; + break; + } validationObject.addField(errFieldMessage, - validationHelper.validateRequiredField(data[attribute])); + validationHelper.validateField(validators, data[attribute], isRequired), routeConfig); }); + + if (data['tlsEnabled'] && !tlsSecretSpecified) { + errFieldMessage = i18n.t('ingress-design-ingress-route-field-validation-error', { + routeName: data['name'], + fieldName: i18n.t('ingress-design-ingress-route-tls-label') + }); + const errMessage = i18n.t('ingress-design-ingress-route-field-tls-config-error', { + routeName: data['name'], + fieldName: i18n.t('ingress-design-ingress-route-tls-label'), + specifyTlsSecretFieldName: i18n.t('ingress-design-specify-tls-secret-label') + }); + validationObject.addField(errFieldMessage, errMessage, routeConfig); + } }; this.getHelmOptions = () => { diff --git a/webui/src/js/utils/observable-properties.js b/webui/src/js/utils/observable-properties.js index 31580d9ae..a91a33397 100644 --- a/webui/src/js/utils/observable-properties.js +++ b/webui/src/js/utils/observable-properties.js @@ -204,42 +204,7 @@ define(['knockout', 'utils/common-utilities', 'utils/validation-helper', 'utils/ } validate(isRequired) { - const currentValue = this.value; - let errMessages = []; - - function toErrorMessage(err) { - if (err instanceof Error) { - return err.message; - } else if (err instanceof String) { - return err; - } else { - return err.toString(); - } - } - - // Optional fields whose value is empty should not be validated. - if (isRequired || (currentValue && currentValue.toString().length > 0)) { - for (const validator of this._validators) { - try { - validator.validate(currentValue); - } catch (err) { - // Another option might be to try to get the hint, messageSummary, or messageDetail fields from the validator... - errMessages.push(toErrorMessage(err)); - } - } - } - - if (isRequired) { - const requiredMessage = validationHelper.validateRequiredField(currentValue); - if (requiredMessage) { - errMessages.push(requiredMessage); - } - } - - if (errMessages.length === 0) { - errMessages = undefined; - } - return errMessages; + return validationHelper.validateField(this._validators, this.value, isRequired); } validators() { diff --git a/webui/src/js/utils/validation-helper.js b/webui/src/js/utils/validation-helper.js index abae0ecfe..5101c5eea 100644 --- a/webui/src/js/utils/validation-helper.js +++ b/webui/src/js/utils/validation-helper.js @@ -91,18 +91,21 @@ function(i18n, Validator, ojvalidationError, RegExpValidator, LengthValidator, N return new ValidatableObject(flowName); }; + this.validateField = (validators, currentValue, isRequired = false) => { + return this._validateSpecialField(validators, currentValue, isRequired); + }; + this.validateRequiredField = (currentValue) => { - let requiredMessage; - if (currentValue === undefined || currentValue === null) { - requiredMessage = i18n.t('validation-helper-validate-field-value-is-not-defined'); - } else if (currentValue === '') { - requiredMessage = i18n.t('validation-helper-validate-string-field-value-is-empty'); - } else if (Array.isArray(currentValue) && currentValue.length === 0) { - requiredMessage = i18n.t('validation-helper-validate-array-field-value-is-empty'); - } - return requiredMessage; + return _validateRequiredFieldValue(currentValue); }; + this.getRequiredFieldValidators = () => { + return [ + { + validate: _validateRequiredFieldValue + } + ]; + }; this.getK8sCpuValidators = () => { return [ @@ -330,6 +333,18 @@ function(i18n, Validator, ojvalidationError, RegExpValidator, LengthValidator, N return message; } + function _validateRequiredFieldValue(value) { + let requiredMessage; + if (value === undefined || value === null) { + requiredMessage = i18n.t('validation-helper-validate-field-value-is-not-defined'); + } else if (value === '') { + requiredMessage = i18n.t('validation-helper-validate-string-field-value-is-empty'); + } else if (Array.isArray(value) && value.length === 0) { + requiredMessage = i18n.t('validation-helper-validate-array-field-value-is-empty'); + } + return requiredMessage; + } + const K8S_CPU_REGEX = [ /^[1-9]\d*[Mm]?$/, /^\d+(\.\d{1,3})?$/, /^0\.\d{1,3}$/ ]; const K8S_CPU_HELP_URL = 'https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#meaning-of-cpu'; function _validateK8sCpuValue(value) { diff --git a/webui/src/js/views/route-edit-dialog.html b/webui/src/js/views/route-edit-dialog.html index db72cb1ec..ee045917b 100644 --- a/webui/src/js/views/route-edit-dialog.html +++ b/webui/src/js/views/route-edit-dialog.html @@ -20,12 +20,12 @@ + validators="[[project.ingress.validators.virtualHostNameValidator]]"> + validators="[[project.ingress.validators.ingressPathValidator]]"> + value="{{targetService.observable}}" + validators="[[project.ingress.validators.k8sNameValidator]]"> + validators="[[project.ingress.validators.targetPortValidator]]">