This is an automated email from the ASF dual-hosted git repository.
ababiichuk pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/ambari.git
The following commit(s) were added to refs/heads/trunk by this push:
new f9429ae [AMBARI-23648] - Log Search UI: Various fixes for Shipper
Configuration
f9429ae is described below
commit f9429ae6868beb3cd51659cda4ce5c1f5741fea4
Author: Istvan Tobias <[email protected]>
AuthorDate: Sat Apr 21 13:37:22 2018 +0200
[AMBARI-23648] - Log Search UI: Various fixes for Shipper Configuration
---
.../ambari-logsearch-web/src/app/app.module.ts | 4 +-
.../log-index-filter.component.html | 4 --
.../src/app/modules/shared/notifications.less | 1 -
.../shared/services/notification.service.ts | 19 ++++++--
.../shipper-configuration.component.less | 4 ++
.../shipper-configuration.component.ts | 8 ++-
...ipper-service-configuration-form.component.html | 7 ++-
...shipper-service-configuration-form.component.ts | 28 ++++++++---
.../shipper/directives/validator.directive.ts | 17 ++++++-
.../app/modules/shipper/services/shipper.guard.ts | 57 ++++++++++++++++++++++
.../app/modules/shipper/shipper-routing.module.ts | 5 +-
.../src/app/modules/shipper/shipper.module.ts | 4 +-
.../ambari-logsearch-web/src/assets/i18n/en.json | 13 ++---
13 files changed, 138 insertions(+), 33 deletions(-)
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/app.module.ts
b/ambari-logsearch/ambari-logsearch-web/src/app/app.module.ts
index b0612fe..8db8dea 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/app.module.ts
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/app.module.ts
@@ -24,11 +24,11 @@ import {InMemoryBackendService} from
'angular-in-memory-web-api';
import {TypeaheadModule, TooltipModule} from 'ngx-bootstrap';
import {TranslateModule, TranslateLoader} from '@ngx-translate/core';
import {StoreModule} from '@ngrx/store';
-import { StoreDevtoolsModule } from '@ngrx/store-devtools';
+import {StoreDevtoolsModule} from '@ngrx/store-devtools';
import {MomentModule} from 'angular2-moment';
import {MomentTimezoneModule} from 'angular-moment-timezone';
import {NgStringPipesModule} from 'angular-pipes';
-import { SimpleNotificationsModule } from 'angular2-notifications';
+import {SimpleNotificationsModule} from 'angular2-notifications';
import {environment} from '@envs/environment';
diff --git
a/ambari-logsearch/ambari-logsearch-web/src/app/components/log-index-filter/log-index-filter.component.html
b/ambari-logsearch/ambari-logsearch-web/src/app/components/log-index-filter/log-index-filter.component.html
index 50d7742..5137275 100644
---
a/ambari-logsearch/ambari-logsearch-web/src/app/components/log-index-filter/log-index-filter.component.html
+++
b/ambari-logsearch/ambari-logsearch-web/src/app/components/log-index-filter/log-index-filter.component.html
@@ -69,10 +69,6 @@
<div class="col-md-6">
<div class="text-uppercase">{{'logIndexFilter.hostname' |
translate}}</div>
<input type="text" class="form-control"
[(ngModel)]="component.hosts" (change)="updateValue()">
- <!-- TODO implement typeahead similar to search box
- (some functionality and appearance most likely will be
shared) -->
- <!-- <input type="text" class="form-control"
[(ngModel)]="component.hosts" (change)="updateValue()"
- [typeahead]="hosts | async" typeaheadOptionField="name">
-->
</div>
<div class=" col-md-6">
<div class="text-uppercase">{{'logIndexFilter.expiryDate' |
translate}}</div>
diff --git
a/ambari-logsearch/ambari-logsearch-web/src/app/modules/shared/notifications.less
b/ambari-logsearch/ambari-logsearch-web/src/app/modules/shared/notifications.less
index e9f4fd9..7a0a045 100644
---
a/ambari-logsearch/ambari-logsearch-web/src/app/modules/shared/notifications.less
+++
b/ambari-logsearch/ambari-logsearch-web/src/app/modules/shared/notifications.less
@@ -56,7 +56,6 @@
.sn-content {
font-size: @notification-content-font-size;
padding: 0;
- white-space: pre-wrap;
}
.sn-progress-loader {
height: 2px;
diff --git
a/ambari-logsearch/ambari-logsearch-web/src/app/modules/shared/services/notification.service.ts
b/ambari-logsearch/ambari-logsearch-web/src/app/modules/shared/services/notification.service.ts
index b149e38..df6ca2a 100644
---
a/ambari-logsearch/ambari-logsearch-web/src/app/modules/shared/services/notification.service.ts
+++
b/ambari-logsearch/ambari-logsearch-web/src/app/modules/shared/services/notification.service.ts
@@ -40,6 +40,14 @@ export const notificationIcons: Icons = {
};
Object.assign(defaultIcons, notificationIcons);
+export const messageTemplate = `
+<div class="notification-wrapper">
+ <div class="notification-header sn-title">{{title}}</div>
+ <div class="notification-body sn-content">{{message}}</div>
+ {{icon}}
+</div>
+`;
+
@Injectable()
export class NotificationService {
@@ -60,11 +68,12 @@ export class NotificationService {
...config
});
}
- return this.notificationService[method](
- this.translateService.instant(title),
- this.translateService.instant(message),
- {...config, icon: notificationIcons[method] || notificationIcons['info']}
- );
+ const icon = notificationIcons[method] || notificationIcons['info'];
+ const htmlMsg = messageTemplate
+ .replace(/{{title}}/gi, this.translateService.instant(title))
+ .replace(/{{message}}/gi, this.translateService.instant(message))
+ .replace(/{{icon}}/gi, icon);
+ return this.notificationService.html(htmlMsg, method, {icon, ...config});
}
}
diff --git
a/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/components/shipper-configuration/shipper-configuration.component.less
b/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/components/shipper-configuration/shipper-configuration.component.less
index a08f650..f416b07 100644
---
a/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/components/shipper-configuration/shipper-configuration.component.less
+++
b/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/components/shipper-configuration/shipper-configuration.component.less
@@ -27,3 +27,7 @@
a.btn, a.btn:focus, a.btn:visited {
color: #fff;
}
+
+/deep/ .navigation-bar-container {
+ padding-bottom: 1em;
+}
diff --git
a/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/components/shipper-configuration/shipper-configuration.component.ts
b/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/components/shipper-configuration/shipper-configuration.component.ts
index 761dc3b..33466a1 100644
---
a/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/components/shipper-configuration/shipper-configuration.component.ts
+++
b/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/components/shipper-configuration/shipper-configuration.component.ts
@@ -142,6 +142,7 @@ export class ShipperConfigurationComponent implements
CanComponentDeactivate, On
getResponseHandler(cmd: string, type: string, msgVariables?: {[key: string]:
any}) {
return (response: Response) => {
const result = response.json();
+ // @ToDo change the backend response status to some error code if the
configuration is not valid and don't use the .errorMessage prop
const resultType = response ? (response.ok && !result.errorMessage ?
NotificationType.SUCCESS : NotificationType.ERROR) : type;
const translateParams = {errorMessage: '', ...msgVariables, ...result};
const title =
this.translate.instant(`shipperConfiguration.action.${cmd}.title`,
translateParams);
@@ -175,7 +176,12 @@ export class ShipperConfigurationComponent implements
CanComponentDeactivate, On
this.getResponseHandler('validate', NotificationType.SUCCESS, rawValue),
this.getResponseHandler('validate', NotificationType.ERROR, rawValue)
);
- request$.map((response: Response) =>
response.json()).subscribe(this.setValidationResult);
+ request$
+ .filter((response: Response): boolean => response.ok)
+ .map((response: Response) => response.json())
+ // @ToDo change the backend response status to some error code if the
configuration is not valid and don't use the .errorMessage prop
+ .filter(result => result.errorMessage === undefined)
+ .subscribe(this.setValidationResult);
}
canDeactivate() {
diff --git
a/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/components/shipper-service-configuration-form/shipper-service-configuration-form.component.html
b/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/components/shipper-service-configuration-form/shipper-service-configuration-form.component.html
index 42d364b..765aa50 100644
---
a/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/components/shipper-service-configuration-form/shipper-service-configuration-form.component.html
+++
b/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/components/shipper-service-configuration-form/shipper-service-configuration-form.component.html
@@ -75,10 +75,15 @@
class="help-block validation-block pull-right">
{{'common.form.errors.required' | translate}}
</span>
+ <span *ngIf="validatorForm.controls.componentName.value &&
componentNameField.errors &&
componentNameField.errors.serviceNameDoesNotExistInConfiguration"
+ class="help-block validation-block pull-right">
+
{{'shipperConfiguration.form.errors.componentNameField.serviceNameDoesNotExistInConfiguration'
| translate}}
+ </span>
</label>
<input class="form-control component-name" name="componentName"
formControlName="componentName"
[typeahead]="configurationComponents$ | async"
[typeaheadItemTemplate]="typeAheadTpl"
+ [typeaheadMinLength]="0"
[disableControl]="configurationForm.invalid">
</div>
<div [ngClass]="{'has-warning': sampleDataField.invalid,
'form-group': true}">
@@ -96,8 +101,6 @@
<label>
{{'shipperConfiguration.validator.result' | translate}}
</label>
- <!--textarea class="form-control validation-result"
name="validationResult"
- [disableControl]="configurationForm.invalid"
[value]="validationResponse | json"></textarea-->
<pre>{{validationResponse | json}}</pre>
</div>
<button class="btn btn-default pull-right" type="submit"
diff --git
a/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/components/shipper-service-configuration-form/shipper-service-configuration-form.component.ts
b/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/components/shipper-service-configuration-form/shipper-service-configuration-form.component.ts
index 23ee07c..b28c0d9 100644
---
a/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/components/shipper-service-configuration-form/shipper-service-configuration-form.component.ts
+++
b/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/components/shipper-service-configuration-form/shipper-service-configuration-form.component.ts
@@ -16,7 +16,7 @@
* limitations under the License.
*/
-import {ChangeDetectorRef, Component, EventEmitter, Input, OnChanges,
OnDestroy, OnInit, Output, SimpleChanges} from '@angular/core';
+import {Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output,
SimpleChanges} from '@angular/core';
import {AbstractControl, FormBuilder, FormGroup, ValidatorFn, Validators} from
'@angular/forms';
import {Observable} from 'rxjs/Observable';
import {Subject} from 'rxjs/Subject';
@@ -94,9 +94,11 @@ export class ShipperServiceConfigurationFormComponent
implements OnInit, OnDestr
private canDeactivateModalResult: Subject<boolean> = new Subject<boolean>();
private canDeactivateObservable$: Observable<boolean> =
Observable.create((observer: Observer<boolean>) => {
- this.canDeactivateModalResult.subscribe((result: boolean) => {
- observer.next(result);
- });
+ this.subscriptions.push(
+ this.canDeactivateModalResult.subscribe((result: boolean) => {
+ observer.next(result);
+ })
+ );
});
private serviceNamesListSubject: BehaviorSubject<ShipperClusterService[]> =
new BehaviorSubject<ShipperClusterService[]>([]);
@@ -105,8 +107,7 @@ export class ShipperServiceConfigurationFormComponent
implements OnInit, OnDestr
constructor(
private utilsService: UtilsService,
- private formBuilder: FormBuilder,
- private changeDetectorRef: ChangeDetectorRef
+ private formBuilder: FormBuilder
) {}
ngOnInit() {
@@ -205,10 +206,21 @@ export class ShipperServiceConfigurationFormComponent
implements OnInit, OnDestr
});
this.validatorForm = this.formBuilder.group({
- clusterName: this.formBuilder.control(this.clusterName,
Validators.required),
- componentName: this.formBuilder.control('', Validators.required),
+ clusterName: this.formBuilder.control(
+ this.clusterName,
+ [Validators.required]
+ ),
+ componentName: this.formBuilder.control('', [
+ Validators.required,
+
formValidators.getConfigurationServiceValidator(this.configurationForm.controls.configuration)
+ ]),
sampleData: this.formBuilder.control('', Validators.required)
});
+ this.subscriptions.push(
+ this.configurationForm.valueChanges.subscribe(() => {
+ this.validatorForm.controls.componentName.updateValueAndValidity();
+ })
+ );
}
onConfigurationSubmit(configurationForm: FormGroup): void {
diff --git
a/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/directives/validator.directive.ts
b/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/directives/validator.directive.ts
index 649acc0..ad6f009 100644
---
a/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/directives/validator.directive.ts
+++
b/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/directives/validator.directive.ts
@@ -16,10 +16,11 @@
* limitations under the License.
*/
-import {AbstractControl, ValidatorFn} from '@angular/forms';
+import {AbstractControl, AsyncValidatorFn, ValidatorFn} from '@angular/forms';
import {ShipperClusterService} from
'@modules/shipper/models/shipper-cluster-service.type';
import {ValidationErrors} from '@angular/forms/src/directives/validators';
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
+import {Observable} from 'rxjs/Observable';
export function configurationValidator(): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
@@ -45,3 +46,17 @@ export function uniqueServiceNameValidator(
};
}
+export function getConfigurationServiceValidator(configControl:
AbstractControl): ValidatorFn {
+ return (control: AbstractControl): ValidationErrors | null => {
+ let components: string[];
+ try {
+ const inputs: {[key: string]: any}[] = (configControl.value ?
JSON.parse(configControl.value) : {}).input;
+ components = inputs && inputs.length ? inputs.map(input => input.type) :
[];
+ } catch (error) {
+ components = [];
+ }
+ return components.length && components.indexOf(control.value) === -1 ? {
+ serviceNameDoesNotExistInConfiguration: {value: control.value}
+ } : null;
+ };
+}
diff --git
a/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/services/shipper.guard.ts
b/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/services/shipper.guard.ts
new file mode 100644
index 0000000..f57a6c7
--- /dev/null
+++
b/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/services/shipper.guard.ts
@@ -0,0 +1,57 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {Injectable} from '@angular/core';
+import {CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router} from
'@angular/router';
+import {Observable} from 'rxjs/Observable';
+import {RoutingUtilsService} from '@app/services/routing-utils.service';
+import {ClustersService} from '@app/services/storage/clusters.service';
+import {ShipperClusterServiceListService} from
'@modules/shipper/services/shipper-cluster-service-list.service';
+
+@Injectable()
+export class ShipperGuard implements CanActivate {
+
+ constructor (
+ private routingUtilsService: RoutingUtilsService,
+ private router: Router,
+ private clustersStoreService: ClustersService,
+ private shipperClusterServiceListService: ShipperClusterServiceListService
+ ) {}
+
+ getFirstCluster(): Observable<string> {
+ return this.clustersStoreService.getAll().map((clusters: string[]) =>
Array.isArray(clusters) ? clusters[0] : clusters);
+ }
+
+ getFirstServiceForCluster(cluster: string): Observable<string> {
+ return this.shipperClusterServiceListService.getServicesForCluster(cluster)
+ .map((services: string[]) => Array.isArray(services) ? services[0] :
services);
+ }
+
+ canActivate(
+ next: ActivatedRouteSnapshot,
+ state: RouterStateSnapshot
+ ): Observable<boolean> | Promise<boolean> | boolean {
+ const cluster: string =
this.routingUtilsService.getParamFromActivatedRouteSnapshot(next, 'cluster');
+ const service: string =
this.routingUtilsService.getParamFromActivatedRouteSnapshot(next, 'service');
+ (cluster ? Observable.of(cluster) :
this.getFirstCluster()).first().subscribe((firstCluster: string) => {
+
this.getFirstServiceForCluster(firstCluster).first().subscribe((firstService:
string) => {
+ this.router.navigate(['/shipper', firstCluster, firstService]);
+ });
+ });
+ return !!(cluster && service);
+ }
+}
diff --git
a/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/shipper-routing.module.ts
b/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/shipper-routing.module.ts
index 35aed14..85d0bb9 100644
---
a/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/shipper-routing.module.ts
+++
b/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/shipper-routing.module.ts
@@ -23,6 +23,7 @@ import {AuthGuardService} from
'@app/services/auth-guard.service';
import {CanDeactivateGuardService} from
'@modules/shared/services/can-deactivate-guard.service';
import {ShipperConfigurationComponent} from
'./components/shipper-configuration/shipper-configuration.component';
+import {ShipperGuard} from '@modules/shipper/services/shipper.guard';
const shipperRoutes: Routes = [{
path: 'shipper/:cluster/add',
@@ -49,7 +50,7 @@ const shipperRoutes: Routes = [{
breadcrumbs: 'shipperConfiguration.breadcrumbs.title',
multiClusterFilter: false
},
- canActivate: [AuthGuardService]
+ canActivate: [AuthGuardService, ShipperGuard]
}, {
path: 'shipper',
component: ShipperConfigurationComponent,
@@ -57,7 +58,7 @@ const shipperRoutes: Routes = [{
breadcrumbs: 'shipperConfiguration.breadcrumbs.title',
multiClusterFilter: false
},
- canActivate: [AuthGuardService]
+ canActivate: [AuthGuardService, ShipperGuard]
}];
@NgModule({
diff --git
a/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/shipper.module.ts
b/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/shipper.module.ts
index 682b739..1f6eddf 100644
---
a/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/shipper.module.ts
+++
b/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/shipper.module.ts
@@ -35,6 +35,7 @@ import {ShipperConfigurationStore} from
'./stores/shipper-configuration.store';
import {ShipperConfigurationComponent} from
'./components/shipper-configuration/shipper-configuration.component';
import {ShipperClusterServiceListService} from
'./services/shipper-cluster-service-list.service';
import {ShipperConfigurationService} from
'./services/shipper-configuration.service';
+import {ShipperGuard} from '@modules/shipper/services/shipper.guard';
@NgModule({
imports: [
@@ -59,7 +60,8 @@ import {ShipperConfigurationService} from
'./services/shipper-configuration.serv
providers: [
ShipperConfigurationStore,
ShipperConfigurationService,
- ShipperClusterServiceListService
+ ShipperClusterServiceListService,
+ ShipperGuard
]
})
export class ShipperModule {}
diff --git a/ambari-logsearch/ambari-logsearch-web/src/assets/i18n/en.json
b/ambari-logsearch/ambari-logsearch-web/src/assets/i18n/en.json
index bc96827..e70ab9b 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/assets/i18n/en.json
+++ b/ambari-logsearch/ambari-logsearch-web/src/assets/i18n/en.json
@@ -213,8 +213,8 @@
"logIndexFilter.hostname": "Hostname",
"logIndexFilter.expiryDate": "Expiry Date",
"logIndexFilter.update.title": "Log Index Filter Update",
- "logIndexFilter.update.success": "Log Index Filter for cluster {{cluster}}
has been successfully updated.",
- "logIndexFilter.update.error": "Error at updating Log Index Filter for
cluster {{cluster}}. {{message}}",
+ "logIndexFilter.update.success": "Log Index Filter for cluster <span
class='cluster-name'>{{cluster}}</span> has been successfully updated.",
+ "logIndexFilter.update.error": "Error at updating Log Index Filter for
cluster <span class='cluster-name'>{{cluster}}</span>. {{message}}",
"login.title": "Login",
@@ -235,6 +235,7 @@
"shipperConfiguration.form.testBtn.label": "Test",
"shipperConfiguration.form.errors.configuration.invalidJSON": "Invalid
JSON!",
"shipperConfiguration.form.errors.serviceName.exists": "This service name
already exists.",
+
"shipperConfiguration.form.errors.componentNameField.serviceNameDoesNotExistInConfiguration":
"This component is not in the configuration.",
"shipperConfiguration.form.leavingDirty.title": "You have unsaved changes",
"shipperConfiguration.form.leavingDirty.message": "Are you sure that you
cancel the changes?",
@@ -246,12 +247,12 @@
"shipperConfiguration.action.add.title": "New Configuration",
"shipperConfiguration.action.add.success.message": "New configuration has
been added successfully.",
- "shipperConfiguration.action.add.error.message": "Error at adding new
configuration.\nCluster: {{clusterName}}\nService: {{componentName}}",
+ "shipperConfiguration.action.add.error.message": "Error at adding new
configuration.<div class='cluster-name'>Cluster: {{clusterName}}</div><div
class='service-name'>Service: {{componentName}}</div>",
"shipperConfiguration.action.update.title": "Update Configuration",
- "shipperConfiguration.action.update.success.message": "The configuration has
been updated successfully.\nCluster: {{clusterName}}\nService:
{{componentName}}",
- "shipperConfiguration.action.update.error.message": "Error at updating the
configuration.\nCluster: {{clusterName}}\nService: {{componentName}}",
+ "shipperConfiguration.action.update.success.message": "The configuration has
been updated successfully.<div class='cluster-name'>Cluster:
{{clusterName}}</div><div class='service-name'>Service:
{{componentName}}</div>",
+ "shipperConfiguration.action.update.error.message": "Error at updating the
configuration.<div class='cluster-name'>Cluster: {{clusterName}}</div><div
class='service-name'>Service: {{componentName}}</div>",
"shipperConfiguration.action.validate.title": "Validate Configuration",
"shipperConfiguration.action.validate.success.message": "The configuration
is valid.",
- "shipperConfiguration.action.validate.error.message": "The configuration is
not valid.\nCluster: {{clusterName}}\nService:
{{componentName}}\n{{errorMessage}}"
+ "shipperConfiguration.action.validate.error.message": "The configuration is
not valid.<div class='cluster-name'>Cluster: {{clusterName}}</div><div
class='service-name'>Service: {{componentName}}</div><div
class='error-message'>{{errorMessage}}</div>"
}
--
To stop receiving notification emails like this one, please contact
[email protected].