diff --git a/metron-interface/metron-alerts/cypress/integration/search/auto-polling.feature.spec.js b/metron-interface/metron-alerts/cypress/integration/search/auto-polling.feature.spec.js
index c99f700bc5..fd901333c6 100644
--- a/metron-interface/metron-alerts/cypress/integration/search/auto-polling.feature.spec.js
+++ b/metron-interface/metron-alerts/cypress/integration/search/auto-polling.feature.spec.js
@@ -29,6 +29,18 @@ describe('Automatic data polling on Alerts View', () => {
cy.route('GET', '/api/v1/global/config', 'fixture:config.json');
cy.route('GET', appConfigJSON.contextMenuConfigURL, 'fixture:context-menu.conf.json');
+
+ cy.route({
+ url: '/api/v1/hdfs?path=user-settings',
+ method: 'GET',
+ response: {},
+ });
+
+ cy.route({
+ url: '/api/v1/hdfs?path=user-settings',
+ method: 'POST',
+ response: {},
+ });
};
beforeEach(() => {
diff --git a/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-details.component.spec.ts b/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-details.component.spec.ts
index 9b0487a8bc..f2edf53dc6 100644
--- a/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-details.component.spec.ts
+++ b/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-details.component.spec.ts
@@ -28,8 +28,6 @@ import { SearchService } from 'app/service/search.service';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { AppConfigService } from 'app/service/app-config.service';
import { GlobalConfigService } from 'app/service/global-config.service';
-import { DataSource } from 'app/service/data-source';
-import { ElasticSearchLocalstorageImpl } from 'app/service/elasticsearch-localstorage-impl';
import { DialogService } from 'app/service/dialog.service';
import { By } from '@angular/platform-browser';
import { AlertComment } from './alert-comment';
@@ -39,6 +37,7 @@ import { CommentAddRemoveRequest } from '../../model/comment-add-remove-request'
import { AlertSource } from '../../model/alert-source';
import { of } from 'rxjs/index';
import { Router } from '@angular/router';
+import { UserSettingsService } from 'app/service/user-settings.service';
const alertDetail = {
'enrichments:geo:ip_dst_addr:locID': '5308655',
@@ -126,10 +125,7 @@ describe('AlertDetailsComponent', () => {
appConfigStatic: {},
getApiRoot: () => {},
} },
- {
- provide: DataSource,
- useClass: ElasticSearchLocalstorageImpl
- },
+ UserSettingsService,
{ provide: AlertsService,
useValue: {
escalate: () => {}
diff --git a/metron-interface/metron-alerts/src/app/alerts/alerts-list/auto-polling/auto-polling.service.spec.ts b/metron-interface/metron-alerts/src/app/alerts/alerts-list/auto-polling/auto-polling.service.spec.ts
index 029d0e47e0..217e5f1336 100644
--- a/metron-interface/metron-alerts/src/app/alerts/alerts-list/auto-polling/auto-polling.service.spec.ts
+++ b/metron-interface/metron-alerts/src/app/alerts/alerts-list/auto-polling/auto-polling.service.spec.ts
@@ -27,6 +27,9 @@ import { DialogService } from 'app/service/dialog.service';
import { RestError } from 'app/model/rest-error';
import { DialogType } from 'app/model/dialog-type';
import { RefreshInterval } from 'app/alerts/configure-rows/configure-rows-enums';
+import { UserSettingsService } from 'app/service/user-settings.service';
+import { HttpClientTestingModule } from '@angular/common/http/testing';
+import { AppConfigService } from 'app/service/app-config.service';
const DEFAULT_POLLING_INTERVAL = RefreshInterval.TEN_MIN;
@@ -63,11 +66,18 @@ describe('AutoPollingService', () => {
}
beforeEach(() => {
- localStorage.getItem = () => null;
- localStorage.setItem = () => {};
-
TestBed.configureTestingModule({
+ imports: [
+ HttpClientTestingModule,
+ ],
providers: [
+ {
+ provide: AppConfigService,
+ useValue: {
+ getApiRoot() { return ''; }
+ }
+ },
+ UserSettingsService,
AutoPollingService,
{ provide: DialogService, useClass: () => {} },
{ provide: SearchService, useClass: () => { return {
@@ -447,35 +457,27 @@ describe('AutoPollingService', () => {
describe('polling state persisting and restoring', () => {
- it('should persist polling state on start', () => {
- spyOn(localStorage, 'setItem');
- autoPollingService.start();
- expect(localStorage.setItem).toHaveBeenCalledWith('autoPolling', `{"isActive":true,"refreshInterval":${DEFAULT_POLLING_INTERVAL}}`);
- });
-
- it('should persist polling state on stop', () => {
- spyOn(localStorage, 'setItem');
- autoPollingService.stop();
- expect(localStorage.setItem).toHaveBeenCalledWith('autoPolling', `{"isActive":false,"refreshInterval":${DEFAULT_POLLING_INTERVAL}}`);
- });
-
it('should persist polling state on interval change', () => {
- spyOn(localStorage, 'setItem');
+ const userSettingsService = TestBed.get(UserSettingsService);
+ spyOn(userSettingsService, 'save').and.callThrough();
autoPollingService.setInterval(4);
- expect(localStorage.setItem).toHaveBeenCalledWith('autoPolling', '{"isActive":false,"refreshInterval":4}');
+ expect(userSettingsService.save).toHaveBeenCalledWith({
+ autoPolling: '{"isActive":false,"refreshInterval":4}'
+ });
});
it('should restore polling state on construction with a delay', fakeAsync(() => {
const queryBuilderFake = TestBed.get(QueryBuilder);
const dialogServiceFake = TestBed.get(DialogService);
+ const userSettingsService = TestBed.get(UserSettingsService);
- spyOn(localStorage, 'getItem').and.returnValue('{"isActive":true,"refreshInterval":443}');
+ spyOn(userSettingsService, 'get').and.returnValue(of('{"isActive":true,"refreshInterval":443}'));
- const localAutoPollingSvc = new AutoPollingService(searchServiceFake, queryBuilderFake, dialogServiceFake);
+ const localAutoPollingSvc = new AutoPollingService(searchServiceFake, queryBuilderFake, dialogServiceFake, userSettingsService);
tick(localAutoPollingSvc.AUTO_START_DELAY);
- expect(localStorage.getItem).toHaveBeenCalledWith('autoPolling');
+ expect(userSettingsService.get).toHaveBeenCalledWith('autoPolling');
expect(localAutoPollingSvc.getIsPollingActive()).toBe(true);
expect(localAutoPollingSvc.getInterval()).toBe(443);
@@ -485,11 +487,12 @@ describe('AutoPollingService', () => {
it('should start polling on construction when persisted isActive==true', fakeAsync(() => {
const queryBuilderFake = TestBed.get(QueryBuilder);
const dialogServiceFake = TestBed.get(DialogService);
+ const userSettingsService = TestBed.get(UserSettingsService);
spyOn(searchServiceFake, 'search').and.callThrough();
- spyOn(localStorage, 'getItem').and.returnValue('{"isActive":true,"refreshInterval":10}');
+ spyOn(userSettingsService, 'get').and.returnValue(of('{"isActive":true,"refreshInterval":10}'));
- const localAutoPollingSvc = new AutoPollingService(searchServiceFake, queryBuilderFake, dialogServiceFake);
+ const localAutoPollingSvc = new AutoPollingService(searchServiceFake, queryBuilderFake, dialogServiceFake, userSettingsService);
tick(localAutoPollingSvc.AUTO_START_DELAY);
@@ -507,11 +510,12 @@ describe('AutoPollingService', () => {
it('should start polling on construction with the persisted interval', fakeAsync(() => {
const queryBuilderFake = TestBed.get(QueryBuilder);
const dialogServiceFake = TestBed.get(DialogService);
+ const userSettingsService = TestBed.get(UserSettingsService);
spyOn(searchServiceFake, 'search').and.callThrough();
- spyOn(localStorage, 'getItem').and.returnValue('{"isActive":true,"refreshInterval":4}');
+ spyOn(userSettingsService, 'get').and.returnValue(of('{"isActive":true,"refreshInterval":4}'));
- const localAutoPollingSvc = new AutoPollingService(searchServiceFake, queryBuilderFake, dialogServiceFake);
+ const localAutoPollingSvc = new AutoPollingService(searchServiceFake, queryBuilderFake, dialogServiceFake, userSettingsService);
tick(localAutoPollingSvc.AUTO_START_DELAY);
@@ -526,5 +530,4 @@ describe('AutoPollingService', () => {
localAutoPollingSvc.stop();
}));
});
-
});
diff --git a/metron-interface/metron-alerts/src/app/alerts/alerts-list/auto-polling/auto-polling.service.ts b/metron-interface/metron-alerts/src/app/alerts/alerts-list/auto-polling/auto-polling.service.ts
index 23cd572714..1bf076a9fe 100755
--- a/metron-interface/metron-alerts/src/app/alerts/alerts-list/auto-polling/auto-polling.service.ts
+++ b/metron-interface/metron-alerts/src/app/alerts/alerts-list/auto-polling/auto-polling.service.ts
@@ -16,7 +16,7 @@
* limitations under the License.
*/
import { Injectable } from '@angular/core';
-import { Subscription, Subject, Observable, interval, onErrorResumeNext, timer } from 'rxjs';
+import { Subscription, Subject, Observable, interval, timer } from 'rxjs';
import { SearchService } from 'app/service/search.service';
import { QueryBuilder } from '../query-builder';
import { SearchResponse } from 'app/model/search-response';
@@ -26,6 +26,7 @@ import { RestError } from 'app/model/rest-error';
import { DialogType } from 'app/shared/metron-dialog/metron-dialog.component';
import { DialogService } from 'app/service/dialog.service';
import { RefreshInterval } from '../../configure-rows/configure-rows-enums';
+import { UserSettingsService } from 'app/service/user-settings.service';
interface AutoPollingStateModel {
isActive: boolean,
@@ -49,6 +50,7 @@ export class AutoPollingService {
constructor(private searchService: SearchService,
private queryBuilder: QueryBuilder,
private dialogService: DialogService,
+ private userSettingsService: UserSettingsService
) {
this.restoreState();
}
@@ -108,19 +110,24 @@ export class AutoPollingService {
}
private persistState(key = this.AUTO_POLLING_STORAGE_KEY): void {
- localStorage.setItem(key, JSON.stringify(this.getStateModel()));
+ this.userSettingsService.save({
+ [key]: JSON.stringify(this.getStateModel())
+ }).subscribe();
}
private restoreState(key = this.AUTO_POLLING_STORAGE_KEY): void {
- const persistedState = JSON.parse(localStorage.getItem(key)) as AutoPollingStateModel;
-
- if (persistedState) {
- this.refreshInterval = persistedState.refreshInterval;
-
- if (persistedState.isActive) {
- timer(this.AUTO_START_DELAY).subscribe(this.start.bind(this));
- }
- }
+ this.userSettingsService.get(key)
+ .subscribe((autoPollingState) => {
+ const persistedState = autoPollingState ? JSON.parse(autoPollingState) as AutoPollingStateModel : null;
+
+ if (persistedState) {
+ this.refreshInterval = persistedState.refreshInterval;
+
+ if (persistedState.isActive) {
+ timer(this.AUTO_START_DELAY).subscribe(this.start.bind(this));
+ }
+ }
+ });
}
private getStateModel(): AutoPollingStateModel {
diff --git a/metron-interface/metron-alerts/src/app/alerts/alerts-list/table-view/table-view.component.spec.ts b/metron-interface/metron-alerts/src/app/alerts/alerts-list/table-view/table-view.component.spec.ts
index 604359b990..a13c11d22e 100644
--- a/metron-interface/metron-alerts/src/app/alerts/alerts-list/table-view/table-view.component.spec.ts
+++ b/metron-interface/metron-alerts/src/app/alerts/alerts-list/table-view/table-view.component.spec.ts
@@ -34,6 +34,7 @@ import { DialogService } from 'app/service/dialog.service';
import { AppConfigService } from '../../../service/app-config.service';
import { ContextMenuComponent } from 'app/shared/context-menu/context-menu.component';
import { of } from 'rxjs';
+import { UserSettingsService } from 'app/service/user-settings.service';
@Component({selector: 'metron-table-pagination', template: ''})
class MetronTablePaginationComponent {
@@ -63,7 +64,8 @@ describe('TableViewComponent', () => {
{ provide: AppConfigService, useClass: FakeAppConfigService },
{ provide: GlobalConfigService, useValue: {
get: () => { return of({})}
- }}
+ }},
+ UserSettingsService
],
declarations: [
MetronTableDirective,
diff --git a/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-view.component.spec.ts b/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-view.component.spec.ts
index 0d33eba96d..c48dc27ba3 100644
--- a/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-view.component.spec.ts
+++ b/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-view.component.spec.ts
@@ -34,6 +34,7 @@ import { MetaAlertService } from '../../../service/meta-alert.service';
import { DialogService } from 'app/service/dialog.service';
import { AppConfigService } from '../../../service/app-config.service';
import { of } from 'rxjs';
+import { UserSettingsService } from 'app/service/user-settings.service';
class FakeAppConfigService {
@@ -57,7 +58,8 @@ describe('TreeViewComponent', () => {
{ provide: AppConfigService, useClass: FakeAppConfigService },
{ provide: GlobalConfigService, useValue: {
get: () => { return of({})}
- }}
+ }},
+ UserSettingsService
],
declarations: [
MetronTableDirective,
diff --git a/metron-interface/metron-alerts/src/app/alerts/configure-rows/configure-rows.module.ts b/metron-interface/metron-alerts/src/app/alerts/configure-rows/configure-rows.module.ts
index 9915007bcb..7f7b5b7d35 100644
--- a/metron-interface/metron-alerts/src/app/alerts/configure-rows/configure-rows.module.ts
+++ b/metron-interface/metron-alerts/src/app/alerts/configure-rows/configure-rows.module.ts
@@ -24,15 +24,13 @@ import { QueryBuilder } from '../alerts-list/query-builder';
import { ShowHideService } from './show-hide/show-hide.service';
import { TimezoneConfigComponent } from './timezone-config/timezone-config.component';
import { TimezoneConfigService } from './timezone-config/timezone-config.service';
+import { AppConfigService } from 'app/service/app-config.service';
+import { UserSettingsService } from 'app/service/user-settings.service';
@NgModule({
imports: [ SharedModule, SwitchModule ],
declarations: [ ConfigureRowsComponent, ShowHideAlertEntriesComponent, TimezoneConfigComponent ],
exports: [ ConfigureRowsComponent ],
- providers: [ QueryBuilder, ShowHideService, TimezoneConfigService, ],
+ providers: [ AppConfigService, UserSettingsService, QueryBuilder, ShowHideService, TimezoneConfigService, ],
})
-export class ConfigureRowsModule {
-
- constructor(private showHideService: ShowHideService, private timezoneConfigService: TimezoneConfigService) {}
-
-}
+export class ConfigureRowsModule {}
diff --git a/metron-interface/metron-alerts/src/app/alerts/configure-rows/show-hide/show-hide.service.spec.ts b/metron-interface/metron-alerts/src/app/alerts/configure-rows/show-hide/show-hide.service.spec.ts
index e2c99d44e2..61f63010e3 100644
--- a/metron-interface/metron-alerts/src/app/alerts/configure-rows/show-hide/show-hide.service.spec.ts
+++ b/metron-interface/metron-alerts/src/app/alerts/configure-rows/show-hide/show-hide.service.spec.ts
@@ -22,7 +22,9 @@ import { QueryBuilder, FilteringMode } from 'app/alerts/alerts-list/query-builde
import { Spy } from 'jasmine-core';
import { Filter } from 'app/model/filter';
-import { serializePath } from '@angular/router/src/url_tree';
+import { UserSettingsService } from 'app/service/user-settings.service';
+import { HttpClientTestingModule } from '@angular/common/http/testing';
+import { AppConfigService } from 'app/service/app-config.service';
class QueryBuilderMock {
addOrUpdateFilter = () => {};
@@ -32,21 +34,31 @@ class QueryBuilderMock {
describe('ShowHideService', () => {
let queryBuilderMock: QueryBuilderMock;
+ let userSettingsService: UserSettingsService;
beforeEach(() => {
- spyOn(localStorage, 'getItem').and.returnValues('true', 'false');
- spyOn(localStorage, 'setItem');
spyOn(ShowHideService.prototype, 'setFilterFor').and.callThrough();
+ spyOn(UserSettingsService.prototype, 'get').and.callThrough();
+ spyOn(UserSettingsService.prototype, 'save').and.callThrough();
TestBed.configureTestingModule({
+ imports: [ HttpClientTestingModule ],
providers: [
+ {
+ provide: AppConfigService,
+ useValue: {
+ getApiRoot() { return ''; }
+ }
+ },
+ UserSettingsService,
ShowHideService,
{ provide: QueryBuilder, useClass: QueryBuilderMock },
]
});
queryBuilderMock = getTestBed().get(QueryBuilder);
+ userSettingsService = getTestBed().get(UserSettingsService);
});
it('should be created', inject([ShowHideService], (service: ShowHideService) => {
@@ -57,36 +69,38 @@ describe('ShowHideService', () => {
expect(service.queryBuilder).toBeTruthy();
}));
- it('should get persisted state from localStorage', inject([ShowHideService], (service: ShowHideService) => {
- expect(localStorage.getItem).toHaveBeenCalledWith(service.HIDE_RESOLVE_STORAGE_KEY);
- expect(localStorage.getItem).toHaveBeenCalledWith(service.HIDE_DISMISS_STORAGE_KEY);
+ it('should get persisted state', inject([ShowHideService], (service: ShowHideService) => {
+ expect(userSettingsService.get).toHaveBeenCalledWith(service.HIDE_RESOLVE_STORAGE_KEY);
+ expect(userSettingsService.get).toHaveBeenCalledWith(service.HIDE_DISMISS_STORAGE_KEY);
}));
it('should set initial filter state', inject([ShowHideService], (service: ShowHideService) => {
- expect((service.setFilterFor as Spy).calls.argsFor(0)[1]).toBe(true);
- expect((service.setFilterFor as Spy).calls.argsFor(0)[0]).toBe('RESOLVE');
- expect((service.setFilterFor as Spy).calls.argsFor(1)[0]).toBe('DISMISS');
- expect((service.setFilterFor as Spy).calls.argsFor(1)[1]).toBe(false);
+ expect((service.setFilterFor as Spy).calls.argsFor(0)).toEqual(['RESOLVE', false]);
+ expect((service.setFilterFor as Spy).calls.argsFor(1)).toEqual(['DISMISS', false]);
}));
- it('should set value loaded from localStorage to hideDismissed ', inject([ShowHideService], (service: ShowHideService) => {
+ it('should set value to hideDismissed ', inject([ShowHideService], (service: ShowHideService) => {
expect(service.hideDismissed).toBe(false);
}));
- it('should set value loaded from localStorage to hideResolved', inject([ShowHideService], (service: ShowHideService) => {
- expect(service.hideResolved).toBe(true);
+ it('should set value to hideResolved', inject([ShowHideService], (service: ShowHideService) => {
+ expect(service.hideResolved).toBe(false);
}));
- it('should save state to localStorage on change for RESOLVE', inject([ShowHideService], (service: ShowHideService) => {
+ it('should save state on change for RESOLVE', inject([ShowHideService], (service: ShowHideService) => {
service.setFilterFor('RESOLVE', true);
- expect(localStorage.setItem).toHaveBeenCalledWith(service.HIDE_RESOLVE_STORAGE_KEY, true);
+ expect(userSettingsService.save).toHaveBeenCalledWith({
+ [service.HIDE_RESOLVE_STORAGE_KEY]: true
+ });
}));
- it('should save state to localStorage on change for DISMISS', inject([ShowHideService], (service: ShowHideService) => {
+ it('should save state for DISMISS', inject([ShowHideService], (service: ShowHideService) => {
service.setFilterFor('DISMISS', true);
- expect(localStorage.setItem).toHaveBeenCalledWith(service.HIDE_DISMISS_STORAGE_KEY, true);
+ expect(userSettingsService.save).toHaveBeenCalledWith({
+ [service.HIDE_DISMISS_STORAGE_KEY]: true
+ });
}));
it('should be able to add RESOLVE filter to QueryBuilder', inject([ShowHideService], (service: ShowHideService) => {
diff --git a/metron-interface/metron-alerts/src/app/alerts/configure-rows/show-hide/show-hide.service.ts b/metron-interface/metron-alerts/src/app/alerts/configure-rows/show-hide/show-hide.service.ts
index f5244d1e90..e3c2d0c1c0 100644
--- a/metron-interface/metron-alerts/src/app/alerts/configure-rows/show-hide/show-hide.service.ts
+++ b/metron-interface/metron-alerts/src/app/alerts/configure-rows/show-hide/show-hide.service.ts
@@ -18,6 +18,7 @@
import { Injectable } from '@angular/core';
import { QueryBuilder, FilteringMode } from 'app/alerts/alerts-list/query-builder';
import { Filter } from 'app/model/filter';
+import { UserSettingsService } from 'app/service/user-settings.service';
@Injectable({
providedIn: 'root'
@@ -37,12 +38,18 @@ export class ShowHideService {
hideResolved = false;
hideDismissed = false;
- constructor(public queryBuilder: QueryBuilder) {
- this.hideResolved = localStorage.getItem(this.HIDE_RESOLVE_STORAGE_KEY) === 'true';
- this.setFilterFor(this.RESOLVE, this.hideResolved);
+ constructor(public queryBuilder: QueryBuilder, private userSettingsService: UserSettingsService) {
+ this.userSettingsService.get(this.HIDE_RESOLVE_STORAGE_KEY)
+ .subscribe((hideResolved) => {
+ this.hideResolved = !!hideResolved;
+ this.setFilterFor(this.RESOLVE, this.hideResolved);
+ });
- this.hideDismissed = localStorage.getItem(this.HIDE_DISMISS_STORAGE_KEY) === 'true';
- this.setFilterFor(this.DISMISS, this.hideDismissed);
+ this.userSettingsService.get(this.HIDE_DISMISS_STORAGE_KEY)
+ .subscribe((hideDismissed) => {
+ this.hideDismissed = !!hideDismissed;
+ this.setFilterFor(this.DISMISS, this.hideDismissed);
+ });
}
setFilterFor(alertStatus, isHide) {
@@ -58,12 +65,16 @@ export class ShowHideService {
case this.DISMISS:
filterOperation(this.dismissFilter);
this.hideDismissed = isHide;
- localStorage.setItem(this.HIDE_DISMISS_STORAGE_KEY, isHide);
+ this.userSettingsService.save({
+ [this.HIDE_DISMISS_STORAGE_KEY]: isHide
+ }).subscribe();
break;
case this.RESOLVE:
filterOperation(this.resolveFilter);
this.hideResolved = isHide;
- localStorage.setItem(this.HIDE_RESOLVE_STORAGE_KEY, isHide);
+ this.userSettingsService.save({
+ [this.HIDE_RESOLVE_STORAGE_KEY]: isHide
+ }).subscribe();
break;
}
}
diff --git a/metron-interface/metron-alerts/src/app/alerts/configure-rows/timezone-config/timezone-config.service.spec.ts b/metron-interface/metron-alerts/src/app/alerts/configure-rows/timezone-config/timezone-config.service.spec.ts
index 758336b189..7f1f0d1eae 100644
--- a/metron-interface/metron-alerts/src/app/alerts/configure-rows/timezone-config/timezone-config.service.spec.ts
+++ b/metron-interface/metron-alerts/src/app/alerts/configure-rows/timezone-config/timezone-config.service.spec.ts
@@ -19,37 +19,49 @@ import { TestBed } from '@angular/core/testing';
import { TimezoneConfigService } from './timezone-config.service';
import { SwitchComponent } from 'app/shared/switch/switch.component';
+import { UserSettingsService } from 'app/service/user-settings.service';
+import { HttpClientTestingModule } from '@angular/common/http/testing';
+import { AppConfigService } from 'app/service/app-config.service';
describe('TimezoneConfigService', () => {
let service: TimezoneConfigService;
+ let userSettingsService: UserSettingsService;
beforeEach(() => {
- spyOn(localStorage, 'getItem').and.returnValues('true');
TestBed.configureTestingModule({
+ imports: [ HttpClientTestingModule ],
declarations: [ SwitchComponent ],
- providers: [ TimezoneConfigService ]
+ providers: [
+ {
+ provide: AppConfigService,
+ useValue: {
+ getApiRoot() { return ''; }
+ }
+ },
+ UserSettingsService,
+ TimezoneConfigService
+ ]
});
spyOn(TimezoneConfigService.prototype, 'toggleUTCtoLocal').and.callThrough();
+ spyOn(UserSettingsService.prototype, 'get').and.callThrough();
+
service = TestBed.get(TimezoneConfigService);
+ userSettingsService = TestBed.get(UserSettingsService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
- it('should get persisted state from localStorage', () => {
- expect(localStorage.getItem).toHaveBeenCalledWith(service.CONVERT_UTC_TO_LOCAL_KEY);
- });
-
- it('should set initial switch state', () => {
- expect(service.toggleUTCtoLocal).toHaveBeenCalledWith(true);
+ it('should get persisted state', () => {
+ expect(userSettingsService.get).toHaveBeenCalledWith(service.CONVERT_UTC_TO_LOCAL_KEY);
});
it('should return the current timezone configuration with getTimezoneConfig()', () => {
- expect(service.getTimezoneConfig()).toBe(true);
- service.showLocal = false;
expect(service.getTimezoneConfig()).toBe(false);
+ service.showLocal = true;
+ expect(service.getTimezoneConfig()).toBe(true);
});
});
diff --git a/metron-interface/metron-alerts/src/app/alerts/configure-rows/timezone-config/timezone-config.service.ts b/metron-interface/metron-alerts/src/app/alerts/configure-rows/timezone-config/timezone-config.service.ts
index db736f1520..c56b077164 100644
--- a/metron-interface/metron-alerts/src/app/alerts/configure-rows/timezone-config/timezone-config.service.ts
+++ b/metron-interface/metron-alerts/src/app/alerts/configure-rows/timezone-config/timezone-config.service.ts
@@ -16,6 +16,7 @@
* limitations under the License.
*/
import { Injectable } from '@angular/core';
+import { UserSettingsService } from 'app/service/user-settings.service';
@Injectable({
providedIn: 'root'
@@ -26,14 +27,18 @@ export class TimezoneConfigService {
showLocal = false;
- constructor() {
- this.showLocal = localStorage.getItem(this.CONVERT_UTC_TO_LOCAL_KEY) === 'true';
- this.toggleUTCtoLocal(this.showLocal);
+ constructor(private userSettingsService: UserSettingsService) {
+ this.userSettingsService.get(this.CONVERT_UTC_TO_LOCAL_KEY)
+ .subscribe((showLocal) => {
+ this.showLocal = !!showLocal;
+ });
}
toggleUTCtoLocal(isLocal: boolean) {
this.showLocal = isLocal;
- localStorage.setItem(this.CONVERT_UTC_TO_LOCAL_KEY, isLocal.toString());
+ this.userSettingsService.save({
+ [this.CONVERT_UTC_TO_LOCAL_KEY]: isLocal
+ }).subscribe();
}
getTimezoneConfig() {
diff --git a/metron-interface/metron-alerts/src/app/alerts/saved-searches/saved-searches.component.html b/metron-interface/metron-alerts/src/app/alerts/saved-searches/saved-searches.component.html
index 71fb1dbde6..0cdeb1f58e 100644
--- a/metron-interface/metron-alerts/src/app/alerts/saved-searches/saved-searches.component.html
+++ b/metron-interface/metron-alerts/src/app/alerts/saved-searches/saved-searches.component.html
@@ -22,7 +22,7 @@
+ (onSelect)="onSaveRecentSearchSelect($event)">
diff --git a/metron-interface/metron-alerts/src/app/alerts/saved-searches/saved-searches.component.ts b/metron-interface/metron-alerts/src/app/alerts/saved-searches/saved-searches.component.ts
index ab182c0527..a9d5c5615b 100644
--- a/metron-interface/metron-alerts/src/app/alerts/saved-searches/saved-searches.component.ts
+++ b/metron-interface/metron-alerts/src/app/alerts/saved-searches/saved-searches.component.ts
@@ -43,17 +43,8 @@ export class SavedSearchesComponent implements OnInit {
private dialogService: DialogService) {
}
- doDeleteRecentSearch(selectedSearch: SaveSearch) {
- this.saveSearchService.deleteRecentSearch(selectedSearch).subscribe(() => {
- this.ngOnInit();
- },
- error => {
- this.ngOnInit();
- });
- }
doDeleteSearch(selectedSearch: SaveSearch) {
this.saveSearchService.deleteSavedSearch(selectedSearch).subscribe(() => {
- this.doDeleteRecentSearch(selectedSearch);
this.ngOnInit();
},
error => {
@@ -61,22 +52,6 @@ export class SavedSearchesComponent implements OnInit {
});
}
- deleteRecentSearch($event) {
- let selectedSearch = this.recentSearcheObj.find(
- savedSearch => savedSearch.name === $event.key
- );
- const confirmedSubscription = this.dialogService
- .launchDialog(
- 'Do you wish to delete recent search ' + selectedSearch.name
- )
- .subscribe(action => {
- if (action === ConfirmationType.Confirmed) {
- this.doDeleteRecentSearch(selectedSearch);
- }
- confirmedSubscription.unsubscribe();
- });
- }
-
deleteSearch($event) {
let selectedSearch = this.searches.find(
savedSearch => savedSearch.name === $event.key
diff --git a/metron-interface/metron-alerts/src/app/app-routing.module.ts b/metron-interface/metron-alerts/src/app/app-routing.module.ts
index f899a15839..de344a4d69 100644
--- a/metron-interface/metron-alerts/src/app/app-routing.module.ts
+++ b/metron-interface/metron-alerts/src/app/app-routing.module.ts
@@ -15,19 +15,55 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { NgModule } from '@angular/core';
-import { Routes, RouterModule } from '@angular/router';
-import {AuthGuard} from './shared/auth-guard';
-import {LoginGuard} from './shared/login-guard';
+import { NgModule, Injectable } from '@angular/core';
+import { Routes, RouterModule, Resolve } from '@angular/router';
+import { AuthGuard } from './shared/auth-guard';
+import { LoginGuard } from './shared/login-guard';
+import { UserSettingsService } from './service/user-settings.service';
+
+@Injectable()
+export class UserSettingsResolver implements Resolve {
+ constructor(private userSettingsService: UserSettingsService) {}
+ resolve(): Promise {
+ return this.userSettingsService.load();
+ }
+}
const routes: Routes = [
{ path: '', redirectTo: 'alerts-list', pathMatch: 'full'},
{ path: 'login', loadChildren: 'app/login/login.module#LoginModule', canActivate: [LoginGuard]},
- { path: 'alerts-list', loadChildren: 'app/alerts/alerts-list/alerts-list.module#AlertsListModule', canActivate: [AuthGuard]},
- { path: 'save-search', loadChildren: 'app/alerts/save-search/save-search.module#SaveSearchModule', canActivate: [AuthGuard]},
- { path: 'saved-searches', loadChildren: 'app/alerts/saved-searches/saved-searches.module#SavedSearchesModule',
- canActivate: [AuthGuard]},
- { path: 'pcap', loadChildren: 'app/pcap/pcap.module#PcapModule', canActivate: [AuthGuard] }
+ {
+ path: 'alerts-list',
+ loadChildren: 'app/alerts/alerts-list/alerts-list.module#AlertsListModule',
+ canActivate: [AuthGuard],
+ resolve: {
+ userSettings: UserSettingsResolver
+ }
+ },
+ {
+ path: 'save-search',
+ loadChildren: 'app/alerts/save-search/save-search.module#SaveSearchModule',
+ canActivate: [AuthGuard],
+ resolve: {
+ userSettings: UserSettingsResolver
+ }
+ },
+ {
+ path: 'saved-searches',
+ loadChildren: 'app/alerts/saved-searches/saved-searches.module#SavedSearchesModule',
+ canActivate: [AuthGuard],
+ resolve: {
+ userSettings: UserSettingsResolver
+ }
+ },
+ {
+ path: 'pcap',
+ loadChildren: 'app/pcap/pcap.module#PcapModule',
+ canActivate: [AuthGuard],
+ resolve: {
+ userSettings: UserSettingsResolver
+ }
+ }
];
@NgModule({
diff --git a/metron-interface/metron-alerts/src/app/app.module.ts b/metron-interface/metron-alerts/src/app/app.module.ts
index efba0e34e2..e606966d32 100644
--- a/metron-interface/metron-alerts/src/app/app.module.ts
+++ b/metron-interface/metron-alerts/src/app/app.module.ts
@@ -19,29 +19,27 @@ import { Router } from '@angular/router';
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
-import {HttpClientModule, HTTP_INTERCEPTORS} from '@angular/common/http';
+import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { APP_INITIALIZER } from '@angular/core';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { AppComponent } from './app.component';
-import {MetronAlertsRoutingModule} from './app-routing.module';
-import {AlertsListModule} from './alerts/alerts-list/alerts-list.module';
-import {AlertDetailsModule} from './alerts/alert-details/alerts-details.module';
-import {ConfigureTableModule} from './alerts/configure-table/configure-table.module';
-import {ConfigureTableService} from './service/configure-table.service';
-import {SaveSearchModule} from './alerts/save-search/save-search.module';
-import {SaveSearchService} from './service/save-search.service';
-import {SavedSearchesModule} from './alerts/saved-searches/saved-searches.module';
-import {ConfigureRowsModule} from './alerts/configure-rows/configure-rows.module';
-import {ColumnNamesService} from './service/column-names.service';
-import {DataSource} from './service/data-source';
-import {ElasticSearchLocalstorageImpl} from './service/elasticsearch-localstorage-impl';
-import {LoginModule} from './login/login.module';
-import {AuthGuard} from './shared/auth-guard';
-import {AuthenticationService} from './service/authentication.service';
-import {LoginGuard} from './shared/login-guard';
-import {UpdateService} from './service/update.service';
-import {MetaAlertService} from './service/meta-alert.service';
+import { MetronAlertsRoutingModule, UserSettingsResolver } from './app-routing.module';
+import { AlertsListModule } from './alerts/alerts-list/alerts-list.module';
+import { AlertDetailsModule } from './alerts/alert-details/alerts-details.module';
+import { ConfigureTableModule } from './alerts/configure-table/configure-table.module';
+import { ConfigureTableService } from './service/configure-table.service';
+import { SaveSearchModule } from './alerts/save-search/save-search.module';
+import { SaveSearchService } from './service/save-search.service';
+import { SavedSearchesModule } from './alerts/saved-searches/saved-searches.module';
+import { ConfigureRowsModule } from './alerts/configure-rows/configure-rows.module';
+import { ColumnNamesService } from './service/column-names.service';
+import { LoginModule } from './login/login.module';
+import { AuthGuard } from './shared/auth-guard';
+import { AuthenticationService } from './service/authentication.service';
+import { LoginGuard } from './shared/login-guard';
+import { UpdateService } from './service/update.service';
+import { MetaAlertService } from './service/meta-alert.service';
import { MetaAlertsModule } from './alerts/meta-alerts/meta-alerts.module';
import { SearchService } from './service/search.service';
import { GlobalConfigService } from './service/global-config.service';
@@ -94,7 +92,6 @@ const icons: IconDefinition[] = [ ToolOutline, WarningOutline, FileOutline ];
NgZorroAntdModule,
],
providers: [{ provide: APP_INITIALIZER, useFactory: initConfig, deps: [AppConfigService], multi: true },
- { provide: DataSource, useClass: ElasticSearchLocalstorageImpl },
{ provide: HTTP_INTERCEPTORS, useClass: DefaultHeadersInterceptor, multi: true },
{ provide: NZ_ICONS, useValue: icons },
AppConfigService,
@@ -109,6 +106,7 @@ const icons: IconDefinition[] = [ ToolOutline, WarningOutline, FileOutline ];
MetaAlertService,
GlobalConfigService,
DialogService,
+ UserSettingsResolver
],
bootstrap: [AppComponent]
})
diff --git a/metron-interface/metron-alerts/src/app/service/authentication.service.ts b/metron-interface/metron-alerts/src/app/service/authentication.service.ts
index f54dee2709..9a3327b48c 100644
--- a/metron-interface/metron-alerts/src/app/service/authentication.service.ts
+++ b/metron-interface/metron-alerts/src/app/service/authentication.service.ts
@@ -18,12 +18,11 @@
*/
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
-import {Router} from '@angular/router';
+import { Router } from '@angular/router';
import { ReplaySubject } from 'rxjs';
import { GlobalConfigService } from './global-config.service';
-import { DataSource } from './data-source';
import { AppConfigService } from './app-config.service';
-import {HttpUtil} from "../utils/httpUtil";
+import { HttpUtil } from '../utils/httpUtil';
@Injectable()
export class AuthenticationService {
@@ -38,7 +37,6 @@ export class AuthenticationService {
constructor(private http: HttpClient,
private router: Router,
private globalConfigService: GlobalConfigService,
- private dataSource: DataSource,
private appConfigService: AppConfigService) {
this.init();
}
@@ -48,22 +46,23 @@ export class AuthenticationService {
this.currentUser = response.toString();
if (this.currentUser) {
this.onLoginEvent.next(true);
- this.dataSource.getDefaultAlertTableColumnNames();
}
- }, error => {
+ }, () => {
this.onLoginEvent.next(false);
});
}
public login(username: string, password: string, onError): void {
let credentials = btoa(username + ':' + password);
- this.getCurrentUser({ headers: new HttpHeaders({'Authorization': `Basic ${credentials}`, 'Accept': 'text/plain'}), responseType: 'text' })
+ this.getCurrentUser({
+ headers: new HttpHeaders({'Authorization': `Basic ${credentials}`, 'Accept': 'text/plain'}),
+ responseType: 'text'
+ })
.subscribe((response) => {
this.currentUser = response.toString();
this.router.navigateByUrl('/alerts-list');
this.onLoginEvent.next(true);
this.globalConfigService.get();
- this.dataSource.getDefaultAlertTableColumnNames();
},
error => {
onError(error);
diff --git a/metron-interface/metron-alerts/src/app/service/cluster-metadata.service.ts b/metron-interface/metron-alerts/src/app/service/cluster-metadata.service.ts
index 2a857007c3..d5726d32f4 100644
--- a/metron-interface/metron-alerts/src/app/service/cluster-metadata.service.ts
+++ b/metron-interface/metron-alerts/src/app/service/cluster-metadata.service.ts
@@ -15,18 +15,31 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import {Injectable} from '@angular/core';
-import {Observable} from 'rxjs';
+import { Injectable } from '@angular/core';
+import { Observable, of } from 'rxjs';
-import {ColumnMetadata} from '../model/column-metadata';
-import {DataSource} from './data-source';
+import { ColumnMetadata } from '../model/column-metadata';
+import { UserSettingsService } from './user-settings.service';
@Injectable()
export class ClusterMetaDataService {
- constructor(private dataSource: DataSource) {}
+ private defaultColumnMetadata = [
+ new ColumnMetadata('guid', 'string'),
+ new ColumnMetadata('timestamp', 'date'),
+ new ColumnMetadata('source:type', 'string'),
+ new ColumnMetadata('ip_src_addr', 'ip'),
+ new ColumnMetadata('enrichments:geo:ip_dst_addr:country', 'string'),
+ new ColumnMetadata('ip_dst_addr', 'ip'),
+ new ColumnMetadata('host', 'string'),
+ new ColumnMetadata('alert_status', 'string')
+ ];
+
+ constructor(
+ private userSettingsService: UserSettingsService
+ ) {}
getDefaultColumns(): Observable {
- return this.dataSource.getDefaultAlertTableColumnNames();
+ return of(JSON.parse(JSON.stringify(this.defaultColumnMetadata)));
}
}
diff --git a/metron-interface/metron-alerts/src/app/service/column-names.service.ts b/metron-interface/metron-alerts/src/app/service/column-names.service.ts
index afdcd1cbdb..9589d6fcf5 100644
--- a/metron-interface/metron-alerts/src/app/service/column-names.service.ts
+++ b/metron-interface/metron-alerts/src/app/service/column-names.service.ts
@@ -15,11 +15,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import {Injectable} from '@angular/core';
-import {Observable} from 'rxjs';
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
-import {ColumnNames} from '../model/column-names';
-import {DataSource} from './data-source';
+import { ColumnNames } from '../model/column-names';
+import { UserSettingsService } from './user-settings.service';
+import { ALERTS_COLUMN_NAMES } from '../utils/constants';
@Injectable()
export class ColumnNamesService {
@@ -57,9 +58,16 @@ export class ColumnNamesService {
});
}
- constructor(private dataSource: DataSource) {}
+ constructor(private userSettingsService: UserSettingsService) {}
save(columns: ColumnNames[]): Observable<{}> {
- return this.dataSource.saveAlertTableColumnNames(columns);
+ return new Observable((observer) => {
+ this.userSettingsService.save({
+ [ALERTS_COLUMN_NAMES]: columns
+ }).subscribe(() => {
+ observer.next({});
+ observer.complete();
+ });
+ });
}
}
diff --git a/metron-interface/metron-alerts/src/app/service/configure-table.service.ts b/metron-interface/metron-alerts/src/app/service/configure-table.service.ts
index a9309eaf08..c3a1b56e53 100644
--- a/metron-interface/metron-alerts/src/app/service/configure-table.service.ts
+++ b/metron-interface/metron-alerts/src/app/service/configure-table.service.ts
@@ -15,12 +15,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import {Injectable} from '@angular/core';
-import {Observable} from 'rxjs';
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
import { Subject } from 'rxjs';
-import {ColumnMetadata} from '../model/column-metadata';
-import {TableMetadata} from '../model/table-metadata';
-import {DataSource} from './data-source';
+import { ColumnMetadata } from '../model/column-metadata';
+import { TableMetadata } from '../model/table-metadata';
+import { UserSettingsService } from './user-settings.service';
+import { ALERTS_TABLE_METADATA } from '../utils/constants';
+
@Injectable()
export class ConfigureTableService {
@@ -28,21 +30,49 @@ export class ConfigureTableService {
private tableChangedSource = new Subject();
tableChanged$ = this.tableChangedSource.asObservable();
- constructor(private dataSource: DataSource) {}
+ constructor(
+ private userSettingsService: UserSettingsService
+ ) {}
fireTableChanged() {
this.tableChangedSource.next('table changed');
}
- getTableMetadata(): Observable {
- return this.dataSource.getAlertTableSettings();
+ getTableMetadata(): Observable<{}> {
+ return new Observable((observer) => {
+ this.userSettingsService.get(ALERTS_TABLE_METADATA)
+ .subscribe((tableMetadata) => {
+ tableMetadata = TableMetadata.fromJSON(tableMetadata);
+ observer.next(tableMetadata);
+ observer.complete();
+ });
+ });
}
saveColumnMetaData(columns: ColumnMetadata[]): Observable<{}> {
- return this.dataSource.saveColumnMetaDataInAlertTableSettings(columns);
+ return new Observable((observer) => {
+ this.userSettingsService.get(ALERTS_TABLE_METADATA)
+ .subscribe((tableMetadata) => {
+ tableMetadata = TableMetadata.fromJSON(tableMetadata);
+ tableMetadata.tableColumns = columns;
+ this.userSettingsService.save({
+ [ALERTS_TABLE_METADATA]: tableMetadata
+ }).subscribe(() => {
+ observer.next({});
+ observer.complete();
+ });
+ });
+ });
}
- saveTableMetaData(tableMetadata: TableMetadata): Observable {
- return this.dataSource.saveAlertTableSettings(tableMetadata);
+ saveTableMetaData(tableMetadata): Observable<{}> {
+ return new Observable((observer) => {
+ this.userSettingsService.save({
+ [ALERTS_TABLE_METADATA]: tableMetadata
+ }).subscribe(() => {
+ observer.next({});
+ observer.complete();
+ });
+ });
}
}
diff --git a/metron-interface/metron-alerts/src/app/service/data-source.ts b/metron-interface/metron-alerts/src/app/service/data-source.ts
deleted file mode 100644
index 70423ebcad..0000000000
--- a/metron-interface/metron-alerts/src/app/service/data-source.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-/**
- * 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 {Observable} from 'rxjs';
-import {Injectable} from '@angular/core';
-import { HttpClient } from '@angular/common/http';
-import {ColumnMetadata} from '../model/column-metadata';
-import {ColumnNames} from '../model/column-names';
-import {TableMetadata} from '../model/table-metadata';
-import {SaveSearch} from '../model/save-search';
-
-@Injectable()
-export abstract class DataSource {
-
- constructor(protected http: HttpClient) {}
-
- // Calls to fetch default alert table column names and all the field names across all indexes
- abstract getDefaultAlertTableColumnNames(): Observable
-
- // Calls to rename field names and to fetch the renamed field names
- abstract getAlertTableColumnNames(): Observable
- abstract saveAlertTableColumnNames(columns: ColumnNames[]): Observable<{}>
-
- // Calls to fetch and save alerts table settings like refresh interval, page size, default selected table column names
- abstract getAlertTableSettings(): Observable
- abstract saveColumnMetaDataInAlertTableSettings(columns: ColumnMetadata[]): Observable<{}>
- abstract saveAlertTableSettings(tableMetadata): Observable
-
- // Calls to save search, last 10 searches, saved searches
- abstract deleteRecentSearch(saveSearch: SaveSearch): Observable<{}>
- abstract deleteSavedSearch(saveSearch: SaveSearch): Observable<{}>
- abstract listRecentSearches(): Observable
- abstract listSavedSearches(): Observable
- abstract saveRecentSearch(saveSearch: SaveSearch): Observable<{}>
- abstract saveSearch(saveSearch: SaveSearch): Observable<{}>
- abstract updateSearch(saveSearch: SaveSearch): Observable<{}>
-}
diff --git a/metron-interface/metron-alerts/src/app/service/elasticsearch-localstorage-impl.ts b/metron-interface/metron-alerts/src/app/service/elasticsearch-localstorage-impl.ts
deleted file mode 100644
index 5544e755ab..0000000000
--- a/metron-interface/metron-alerts/src/app/service/elasticsearch-localstorage-impl.ts
+++ /dev/null
@@ -1,299 +0,0 @@
-/**
- * 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 {Observable} from 'rxjs';
-import {throwError as observableThrowError} from 'rxjs';
-import {catchError, map, onErrorResumeNext} from 'rxjs/operators';
-import { Injectable } from '@angular/core';
-import {HttpUtil} from '../utils/httpUtil';
-import {DataSource} from './data-source';
-import {ColumnMetadata} from '../model/column-metadata';
-import {ElasticsearchUtils} from '../utils/elasticsearch-utils';
-import {
- ALERTS_COLUMN_NAMES, ALERTS_TABLE_METADATA, ALERTS_RECENT_SEARCH,
- ALERTS_SAVED_SEARCH, NUM_SAVED_SEARCH
-} from '../utils/constants';
-import {ColumnNames} from '../model/column-names';
-import {ColumnNamesService} from './column-names.service';
-import {TableMetadata} from '../model/table-metadata';
-import {SaveSearch} from '../model/save-search';
-import {SearchResponse} from '../model/search-response';
-import {SearchRequest} from '../model/search-request';
-import {AlertSource} from '../model/alert-source';
-import { RestError } from '../model/rest-error';
-
-@Injectable()
-export class ElasticSearchLocalstorageImpl extends DataSource {
-
- globalConfig: {} = {};
- sourceType: 'source:type';
-
- private defaultColumnMetadata = [
- new ColumnMetadata('guid', 'string'),
- new ColumnMetadata('timestamp', 'date'),
- new ColumnMetadata('source:type', 'string'),
- new ColumnMetadata('ip_src_addr', 'ip'),
- new ColumnMetadata('enrichments:geo:ip_dst_addr:country', 'string'),
- new ColumnMetadata('ip_dst_addr', 'ip'),
- new ColumnMetadata('host', 'string'),
- new ColumnMetadata('alert_status', 'string')
- ];
-
- getAlerts(searchRequest: SearchRequest): Observable {
- let url = '/search/*' + ElasticsearchUtils.excludeIndexName + '/_search';
- let request: any = JSON.parse(JSON.stringify(searchRequest));
- request.query = { query_string: { query: searchRequest.query } };
-
- return this.http.post(url, request).pipe(
- map(HttpUtil.extractData),
- map(ElasticsearchUtils.extractAlertsData),
- catchError(HttpUtil.handleError),
- onErrorResumeNext());
- }
-
- getAlert(sourceType: string, alertId: string): Observable {
- return observableThrowError('Method not implemented in ElasticSearchLocalstorageImpl');
- }
-
- updateAlertState(request: any): Observable<{}> {
- return this.http.post('/search/_bulk', request).pipe(
- map(HttpUtil.extractData),
- catchError(HttpUtil.handleError));
- }
-
- getDefaultAlertTableColumnNames(): Observable {
- return Observable.create(observer => {
- observer.next(JSON.parse(JSON.stringify(this.defaultColumnMetadata)));
- observer.complete();
- });
- }
-
- getAllFieldNames(): Observable {
- let url = '_cluster/state';
- return this.http.get(url).pipe(
- map(HttpUtil.extractData),
- map(ElasticsearchUtils.extractColumnNameData),
- catchError(HttpUtil.handleError));
- }
-
- getAlertTableColumnNames(): Observable {
- return Observable.create(observer => {
- let columnNames: ColumnNames[];
- try {
- columnNames = JSON.parse(localStorage.getItem(ALERTS_COLUMN_NAMES));
- ColumnNamesService.toMap(columnNames);
- } catch (e) {}
-
- columnNames = columnNames || [];
-
- observer.next(columnNames);
- observer.complete();
-
- });
- }
-
- saveAlertTableColumnNames(columns: ColumnNames[]): Observable<{}> {
- return Observable.create(observer => {
- try {
- localStorage.setItem(ALERTS_COLUMN_NAMES, JSON.stringify(columns));
- } catch (e) {}
- ColumnNamesService.toMap(columns);
- observer.next({});
- observer.complete();
-
- });
- }
-
- getAlertTableSettings(): Observable {
- return Observable.create(observer => {
- let tableMetadata: TableMetadata;
- try {
- tableMetadata = TableMetadata.fromJSON(JSON.parse(localStorage.getItem(ALERTS_TABLE_METADATA)));
- } catch (e) {}
-
- observer.next(tableMetadata);
- observer.complete();
-
- });
- }
-
- saveColumnMetaDataInAlertTableSettings(columns: ColumnMetadata[]): Observable<{}> {
- return Observable.create(observer => {
- try {
- let tableMetadata = TableMetadata.fromJSON(JSON.parse(localStorage.getItem(ALERTS_TABLE_METADATA)));
- tableMetadata.tableColumns = columns;
- localStorage.setItem(ALERTS_TABLE_METADATA, JSON.stringify(tableMetadata));
- } catch (e) {}
-
- observer.next({});
- observer.complete();
-
- });
- }
-
- saveAlertTableSettings(tableMetadata): Observable {
- return Observable.create(observer => {
- try {
- localStorage.setItem(ALERTS_TABLE_METADATA, JSON.stringify(tableMetadata));
- } catch (e) {}
-
- observer.next({});
- observer.complete();
-
- });
- }
-
- deleteRecentSearch(saveSearch: SaveSearch): Observable<{}> {
- return Observable.create(observer => {
- let recentSearches: SaveSearch[] = [];
- try {
- recentSearches = JSON.parse(localStorage.getItem(ALERTS_RECENT_SEARCH));
- recentSearches = recentSearches.filter(search => search.name !== saveSearch.name);
- } catch (e) {}
-
- localStorage.setItem(ALERTS_RECENT_SEARCH, JSON.stringify(recentSearches));
-
- observer.next({});
- observer.complete();
-
- });
- }
-
- deleteSavedSearch(saveSearch: SaveSearch): Observable<{}> {
- return Observable.create(observer => {
- let savedSearches: SaveSearch[] = [];
- try {
- savedSearches = JSON.parse(localStorage.getItem(ALERTS_SAVED_SEARCH));
- savedSearches = savedSearches.filter(search => search.name !== saveSearch.name);
- } catch (e) {}
-
- localStorage.setItem(ALERTS_SAVED_SEARCH, JSON.stringify(savedSearches));
-
- observer.next({});
- observer.complete();
-
- });
- }
-
- listRecentSearches(): Observable {
- return Observable.create(observer => {
- let savedSearches: SaveSearch[] = [];
- try {
- savedSearches = JSON.parse(localStorage.getItem(ALERTS_RECENT_SEARCH));
- } catch (e) {}
-
- savedSearches = savedSearches || [];
- savedSearches = savedSearches.map(tSaveSeacrh => SaveSearch.fromJSON(tSaveSeacrh));
-
- observer.next(savedSearches);
- observer.complete();
-
- });
- }
-
- listSavedSearches(): Observable {
- return Observable.create(observer => {
- let savedSearches: SaveSearch[] = [];
- try {
- savedSearches = JSON.parse(localStorage.getItem(ALERTS_SAVED_SEARCH));
- } catch (e) {}
-
- savedSearches = savedSearches || [];
- savedSearches = savedSearches.map(tSaveSeacrh => SaveSearch.fromJSON(tSaveSeacrh));
-
- observer.next(savedSearches);
- observer.complete();
-
- });
- }
-
- saveRecentSearch(saveSearch: SaveSearch): Observable<{}> {
- return Observable.create(observer => {
- let savedSearches: SaveSearch[] = [];
- saveSearch.lastAccessed = new Date().getTime();
-
- try {
- savedSearches = JSON.parse(localStorage.getItem(ALERTS_RECENT_SEARCH));
- } catch (e) {}
-
- savedSearches = savedSearches || [];
- savedSearches = savedSearches.map(tSaveSeacrh => SaveSearch.fromJSON(tSaveSeacrh));
-
- if (savedSearches.length === 0) {
- savedSearches.push(saveSearch);
- } else {
- let found = false;
- for ( let tSaveSearch of savedSearches) {
- if (saveSearch.name === tSaveSearch.name) {
- tSaveSearch.lastAccessed = new Date().getTime();
- found = true;
- break;
- }
- }
- if (!found ) {
- if (savedSearches.length < NUM_SAVED_SEARCH) {
- savedSearches.push(saveSearch);
- } else {
- savedSearches.sort((s1, s2) => s1.lastAccessed - s2.lastAccessed).shift();
- savedSearches.push(saveSearch);
- }
- }
- }
-
- localStorage.setItem(ALERTS_RECENT_SEARCH, JSON.stringify(savedSearches));
-
- observer.next({});
- observer.complete();
-
- });
- }
-
- saveSearch(saveSearch: SaveSearch): Observable<{}> {
- return Observable.create(observer => {
- let savedSearches: SaveSearch[] = [];
- try {
- savedSearches = JSON.parse(localStorage.getItem(ALERTS_SAVED_SEARCH));
- } catch (e) {}
-
- savedSearches = savedSearches || [];
- savedSearches.push(saveSearch);
- localStorage.setItem(ALERTS_SAVED_SEARCH, JSON.stringify(savedSearches));
-
- observer.next({});
- observer.complete();
-
- });
- }
-
- updateSearch(saveSearch: SaveSearch): Observable<{}> {
- return Observable.create(observer => {
- let savedSearches: SaveSearch[] = [];
- try {
- savedSearches = JSON.parse(localStorage.getItem(ALERTS_SAVED_SEARCH));
- let savedItem = savedSearches.find(search => search.name === saveSearch.name);
- savedItem.lastAccessed = saveSearch.lastAccessed;
- savedItem.searchRequest = saveSearch.searchRequest;
- } catch (e) {}
-
- localStorage.setItem(ALERTS_SAVED_SEARCH, JSON.stringify(savedSearches));
-
- observer.next({});
- observer.complete();
-
- });
- }
-}
diff --git a/metron-interface/metron-alerts/src/app/service/save-search.service.ts b/metron-interface/metron-alerts/src/app/service/save-search.service.ts
index 9fa189189c..84c130461a 100644
--- a/metron-interface/metron-alerts/src/app/service/save-search.service.ts
+++ b/metron-interface/metron-alerts/src/app/service/save-search.service.ts
@@ -15,13 +15,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import {Injectable, } from '@angular/core';
-import {Observable} from 'rxjs';
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
import { Subject } from 'rxjs';
-import {QueryBuilder} from '../alerts/alerts-list/query-builder';
-import {SaveSearch} from '../model/save-search';
-import {ColumnMetadata} from '../model/column-metadata';
-import {DataSource} from './data-source';
+import { QueryBuilder } from '../alerts/alerts-list/query-builder';
+import { SaveSearch } from '../model/save-search';
+import { ColumnMetadata } from '../model/column-metadata';
+import { UserSettingsService } from './user-settings.service';
+import { ALERTS_RECENT_SEARCH, ALERTS_SAVED_SEARCH, NUM_SAVED_SEARCH } from '../utils/constants';
@Injectable()
export class SaveSearchService {
@@ -32,14 +33,29 @@ export class SaveSearchService {
private loadSavedSearch = new Subject();
loadSavedSearch$ = this.loadSavedSearch.asObservable();
- constructor(private dataSource: DataSource) {}
-
- deleteRecentSearch(saveSearch: SaveSearch): Observable<{}> {
- return this.dataSource.deleteRecentSearch(saveSearch);
- }
+ constructor(
+ private userSettingsService: UserSettingsService
+ ) {}
deleteSavedSearch(saveSearch: SaveSearch): Observable<{}> {
- return this.dataSource.deleteSavedSearch(saveSearch);
+ return new Observable((observer) => {
+ this.userSettingsService.get(ALERTS_SAVED_SEARCH)
+ .subscribe((savedSearches) => {
+ this.userSettingsService.get(ALERTS_RECENT_SEARCH)
+ .subscribe((recentSearches) => {
+ if (recentSearches) {
+ recentSearches = recentSearches.filter(search => search.name !== saveSearch.name);
+ }
+ savedSearches = savedSearches.filter(search => search.name !== saveSearch.name);
+ this.userSettingsService.save({
+ [ALERTS_RECENT_SEARCH]: recentSearches,
+ [ALERTS_SAVED_SEARCH]: savedSearches
+ }).subscribe();
+ observer.next({});
+ observer.complete();
+ });
+ });
+ });
}
fireLoadSavedSearch(savedSearch: SaveSearch) {
@@ -47,19 +63,82 @@ export class SaveSearchService {
}
listRecentSearches(): Observable {
- return this.dataSource.listRecentSearches();
+ return new Observable((observer) => {
+ this.userSettingsService.get(ALERTS_RECENT_SEARCH)
+ .subscribe((recentSearches) => {
+ recentSearches = recentSearches || [];
+ recentSearches = recentSearches.map(search => SaveSearch.fromJSON(search));
+ observer.next(recentSearches);
+ observer.complete();
+ });
+ });
}
listSavedSearches(): Observable {
- return this.dataSource.listSavedSearches();
+ return new Observable((observer) => {
+ this.userSettingsService.get(ALERTS_SAVED_SEARCH)
+ .subscribe((savedSearches) => {
+ savedSearches = savedSearches || [];
+ savedSearches = savedSearches.map(search => SaveSearch.fromJSON(search));
+ observer.next(savedSearches);
+ observer.complete();
+ });
+ });
}
saveAsRecentSearches(saveSearch: SaveSearch): Observable<{}> {
- return this.dataSource.saveRecentSearch(saveSearch);
+ return new Observable((observer) => {
+ saveSearch.lastAccessed = new Date().getTime();
+ this.userSettingsService.get(ALERTS_RECENT_SEARCH)
+ .subscribe((recentSearches) => {
+ if (!recentSearches) {
+ recentSearches = [];
+ }
+ if (recentSearches.length === 0) {
+ recentSearches.push(saveSearch);
+ } else {
+ let found = false;
+ for (let recentSearch of recentSearches) {
+ if (saveSearch.name === recentSearch.name) {
+ recentSearch.lastAccessed = new Date().getTime();
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ if (recentSearches.length < NUM_SAVED_SEARCH) {
+ recentSearches.push(saveSearch);
+ } else {
+ recentSearches.sort((s1, s2) => s1.lastAccessed - s2.lastAccessed).shift();
+ recentSearches.push(saveSearch);
+ }
+ }
+ }
+
+ this.userSettingsService.save({
+ [ALERTS_RECENT_SEARCH]: recentSearches
+ }).subscribe();
+ observer.next({});
+ observer.complete();
+ });
+ });
}
saveSearch(saveSearch: SaveSearch): Observable<{}> {
- return this.dataSource.saveSearch(saveSearch);
+ return new Observable((observer) => {
+ this.userSettingsService.get(ALERTS_SAVED_SEARCH)
+ .subscribe((savedSearches) => {
+ if (!savedSearches) {
+ savedSearches = [];
+ }
+ savedSearches.push(saveSearch);
+ this.userSettingsService.save({
+ [ALERTS_SAVED_SEARCH]: savedSearches
+ }).subscribe();
+ observer.next({});
+ observer.complete();
+ });
+ });
}
setCurrentQueryBuilderAndTableColumns(queryBuilder: QueryBuilder, tableColumns: ColumnMetadata[]) {
@@ -68,6 +147,30 @@ export class SaveSearchService {
}
updateSearch(saveSearch: SaveSearch): Observable<{}> {
- return this.dataSource.updateSearch(saveSearch);
+ return new Observable((observer) => {
+ this.userSettingsService.get(ALERTS_SAVED_SEARCH)
+ .subscribe((savedSearches) => {
+ debugger;
+ if (!savedSearches) {
+ savedSearches = [];
+ }
+ savedSearches = savedSearches.map(search => {
+ if (search.name === saveSearch.name) {
+ return {
+ ...search,
+ lastAccessed: saveSearch.lastAccessed,
+ searchRequest: saveSearch.searchRequest
+ };
+ }
+ return search;
+ });
+ this.userSettingsService.save({
+ [ALERTS_SAVED_SEARCH]: savedSearches
+ }).subscribe(() => {
+ observer.next({});
+ observer.complete();
+ });
+ });
+ });
}
}
diff --git a/metron-interface/metron-alerts/src/app/service/user-settings.service.spec.ts b/metron-interface/metron-alerts/src/app/service/user-settings.service.spec.ts
new file mode 100644
index 0000000000..896073038a
--- /dev/null
+++ b/metron-interface/metron-alerts/src/app/service/user-settings.service.spec.ts
@@ -0,0 +1,78 @@
+/**
+ * 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 { TestBed } from '@angular/core/testing';
+import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
+import { AppConfigService } from './app-config.service';
+import { UserSettingsService } from './user-settings.service';
+
+describe('UserSettingsService', () => {
+ let service: UserSettingsService;
+ let httpTestingController: HttpTestingController;
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [ HttpClientTestingModule ],
+ providers: [
+ UserSettingsService,
+ { provide: AppConfigService, useClass: () => {
+ return {
+ getApiRoot: () => '/api',
+ }
+ } },
+ ]
+ });
+ service = TestBed.get(UserSettingsService);
+ httpTestingController = TestBed.get(HttpTestingController);
+ });
+
+ it('should load from server', (done) => {
+ service.load()
+ .then(() => {
+ service.get('foo').subscribe((value) => {
+ expect(value).toBe('bar');
+ done();
+ });
+ });
+
+ const req = httpTestingController.expectOne('/api/hdfs?path=user-settings');
+ expect(req.request.method).toEqual('GET');
+ req.flush({
+ foo: 'bar'
+ });
+
+ httpTestingController.verify();
+ });
+
+ it('should save properly', () => {
+ service.save({
+ foo: 'bar'
+ }).subscribe();
+
+ service.get('foo').subscribe((value) => {
+ expect(value).toBe('bar');
+ });
+
+ const req = httpTestingController.expectOne('/api/hdfs?path=user-settings');
+ expect(req.request.method).toEqual('POST');
+ expect(req.request.body).toEqual({
+ foo: 'bar'
+ });
+ req.flush({});
+
+ httpTestingController.verify();
+ });
+});
diff --git a/metron-interface/metron-alerts/src/app/service/user-settings.service.ts b/metron-interface/metron-alerts/src/app/service/user-settings.service.ts
new file mode 100644
index 0000000000..e7344171e2
--- /dev/null
+++ b/metron-interface/metron-alerts/src/app/service/user-settings.service.ts
@@ -0,0 +1,58 @@
+/**
+ * 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 { HttpClient } from '@angular/common/http';
+import { AppConfigService } from './app-config.service';
+import { Observable } from 'rxjs';
+
+let settings = {};
+
+@Injectable()
+export class UserSettingsService {
+
+ constructor(
+ private http: HttpClient,
+ private appConfigService: AppConfigService
+ ) {}
+
+ load(): Promise {
+ return this.http.get(this.appConfigService.getApiRoot() + '/hdfs?path=user-settings')
+ .toPromise()
+ .then((result) => {
+ settings = result;
+ })
+ .catch(() => ({}));
+ }
+
+ save(data: any): Observable {
+ const newSettings = {
+ ...settings,
+ ...data
+ };
+ settings = newSettings;
+ return this.http.post(this.appConfigService.getApiRoot() + '/hdfs?path=user-settings', newSettings);
+ }
+
+ get(key: string): Observable {
+ return new Observable((observer) => {
+ observer.next(settings[key]);
+ observer.complete();
+ });
+
+ }
+}
diff --git a/metron-interface/metron-alerts/src/app/utils/constants.ts b/metron-interface/metron-alerts/src/app/utils/constants.ts
index 6e05de0a21..59d561848a 100644
--- a/metron-interface/metron-alerts/src/app/utils/constants.ts
+++ b/metron-interface/metron-alerts/src/app/utils/constants.ts
@@ -21,8 +21,8 @@ import { environment } from '../../environments/environment';
export const META_ALERTS_SENSOR_TYPE = 'metaalert';
export const NUM_SAVED_SEARCH = 10;
-export const ALERTS_RECENT_SEARCH = 'metron-alerts-recent-saved-search';
-export const ALERTS_SAVED_SEARCH = 'metron-alerts-saved-search';
+export const ALERTS_RECENT_SEARCH = 'recentSavedSearches';
+export const ALERTS_SAVED_SEARCH = 'savedSearches';
export const ALERTS_TABLE_METADATA = 'metron-alerts-table-metadata';
export const ALERTS_COLUMN_NAMES = 'metron-alerts-column-names';