This is an automated email from the ASF dual-hosted git repository.
oleewere pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ambari-logsearch.git
The following commit(s) were added to refs/heads/master by this push:
new 2ba9216 [AMBARI-24656] [Log Search UI] Handle the 401 and the 403
response status at login (#2)
2ba9216 is described below
commit 2ba921635e054e188631b9b1f780035ab070a11c
Author: Istvan Tobias <[email protected]>
AuthorDate: Fri Oct 5 18:16:16 2018 +0200
[AMBARI-24656] [Log Search UI] Handle the 401 and the 403 response status
at login (#2)
---
ambari-logsearch-web/karma.conf.js | 1 +
ambari-logsearch-web/package.json | 3 +
ambari-logsearch-web/src/app/app.module.ts | 16 +-
.../src/app/classes/models/app-state.ts | 10 +-
.../src/app/classes/models/store.ts | 35 ++--
.../models/user.ts} | 9 +-
ambari-logsearch-web/src/app/classes/string.ts | 1 -
.../action-menu/action-menu.component.html | 2 +-
.../action-menu/action-menu.component.spec.ts | 16 +-
.../action-menu/action-menu.component.ts | 12 +-
.../src/app/components/app.component.html | 11 +-
.../src/app/components/app.component.less | 7 +
.../src/app/components/app.component.ts | 59 ++++--
.../audit-logs-entries.component.spec.ts | 14 +-
.../audit-logs-table.component.spec.ts | 16 +-
.../cluster-filter.component.spec.ts | 5 +-
.../cluster-filter/cluster-filter.component.ts | 6 +-
.../context-menu/context-menu.component.spec.ts | 10 +-
.../filters-panel/filters-panel.component.html | 62 +++++--
.../filters-panel/filters-panel.component.spec.ts | 14 +-
.../log-context/log-context.component.spec.ts | 14 +-
.../log-index-filter.component.html | 4 +-
.../log-index-filter.component.less | 6 +
.../log-index-filter.component.spec.ts | 16 +-
.../log-index-filter/log-index-filter.component.ts | 4 +-
.../login-form/login-form.component.html | 19 +-
.../login-form/login-form.component.less | 13 +-
.../login-form/login-form.component.spec.ts | 56 ++----
.../components/login-form/login-form.component.ts | 69 +++----
.../logs-container.component.spec.ts | 19 +-
.../logs-container/logs-container.component.ts | 2 +-
.../main-container/main-container.component.ts | 29 +--
.../service-logs-table.component.spec.ts | 10 +-
.../time-range-picker.component.spec.ts | 16 +-
.../timezone-picker.component.spec.ts | 10 +-
.../components/top-menu/top-menu.component.spec.ts | 10 +-
.../app/components/top-menu/top-menu.component.ts | 15 +-
.../src/app/modules/app-load/app-load.module.ts | 23 ++-
.../modules/app-load/services/app-load.service.ts | 41 +++--
.../dropdown-button/dropdown-button.component.ts | 10 +-
.../dropdown-list/dropdown-list.component.html | 8 +-
.../dropdown-list/dropdown-list.component.spec.ts | 10 +-
.../dropdown-list/dropdown-list.component.ts | 6 +-
.../filter-dropdown.component.spec.ts | 10 +-
.../modal-dialog/modal-dialog.component.spec.ts | 20 ++-
.../shared/interfaces/notification.interface.ts | 7 +-
.../shared/services/notification.service.ts | 2 +-
.../src/app/modules/shared/variables.less | 3 +-
.../src/app/services/auth-guard.service.ts | 12 +-
.../src/app/services/auth.service.spec.ts | 84 ++++-----
.../src/app/services/auth.service.ts | 143 ++-------------
.../services/component-generator.service.spec.ts | 14 +-
.../app/services/history-manager.service.spec.ts | 16 +-
.../src/app/services/http-client.service.ts | 18 +-
.../app/services/log-index-filter.service.spec.ts | 7 +-
.../src/app/services/login-screen-guard.service.ts | 11 +-
.../app/services/logs-container.service.spec.ts | 14 +-
.../src/app/services/logs-container.service.ts | 98 +++++-----
.../src/app/services/storage/reducers.service.ts | 5 +-
.../src/app/services/user-settings.service.spec.ts | 12 +-
.../src/app/store/actions/auth.actions.ts | 101 +++++++++++
.../actions/notification.actions.ts} | 19 +-
.../src/app/store/effects/auth.effects.ts | 198 +++++++++++++++++++++
.../effects/notification.effects.ts} | 40 +++--
.../src/app/store/reducers/auth.reducers.ts | 120 +++++++++++++
.../src/app/store/selectors/auth.selectors.ts | 47 +++++
ambari-logsearch-web/src/app/test-config.spec.ts | 41 +++--
ambari-logsearch-web/src/assets/i18n/en.json | 8 +-
ambari-logsearch-web/yarn.lock | 31 ++++
69 files changed, 1256 insertions(+), 544 deletions(-)
diff --git a/ambari-logsearch-web/karma.conf.js
b/ambari-logsearch-web/karma.conf.js
index 08608d8..b7e9d03 100644
--- a/ambari-logsearch-web/karma.conf.js
+++ b/ambari-logsearch-web/karma.conf.js
@@ -26,6 +26,7 @@ module.exports = function (config) {
plugins: [
require('karma-jasmine'),
require('karma-phantomjs-launcher'),
+ require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular/cli/plugins/karma')
diff --git a/ambari-logsearch-web/package.json
b/ambari-logsearch-web/package.json
index 3639b54..2b1e7ef 100644
--- a/ambari-logsearch-web/package.json
+++ b/ambari-logsearch-web/package.json
@@ -23,6 +23,7 @@
"@angular/platform-browser-dynamic": "^4.0.0",
"@angular/router": "^4.0.0",
"@ngrx/core": "^1.2.0",
+ "@ngrx/effects": "2.0.5",
"@ngrx/store": "^2.2.3",
"@ngrx/store-devtools": "3.2.4",
"@ngx-translate/core": "^6.0.1",
@@ -37,9 +38,11 @@
"d3-scale-chromatic": "^1.1.1",
"font-awesome": "^4.7.0",
"jquery": "^1.12.4",
+ "karma-chrome-launcher": "^2.2.0",
"moment": "^2.18.1",
"moment-timezone": "^0.5.13",
"ngx-bootstrap": "^2.0.5",
+ "reselect": "^3.0.1",
"rxjs": "^5.4.3",
"zone.js": "^0.8.4"
},
diff --git a/ambari-logsearch-web/src/app/app.module.ts
b/ambari-logsearch-web/src/app/app.module.ts
index b72980e..097bf04 100644
--- a/ambari-logsearch-web/src/app/app.module.ts
+++ b/ambari-logsearch-web/src/app/app.module.ts
@@ -17,10 +17,9 @@
*/
import {BrowserModule} from '@angular/platform-browser';
-import {NgModule, CUSTOM_ELEMENTS_SCHEMA, Injector} from '@angular/core';
+import {NgModule, CUSTOM_ELEMENTS_SCHEMA, APP_INITIALIZER, Injector} from
'@angular/core';
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
-import {HttpModule, Http, XHRBackend, BrowserXhr, ResponseOptions,
XSRFStrategy} from '@angular/http';
-import {InMemoryBackendService} from 'angular-in-memory-web-api';
+import { HttpModule, Http } from '@angular/http';
import {TypeaheadModule, TooltipModule} from 'ngx-bootstrap';
import {TranslateModule, TranslateLoader} from '@ngx-translate/core';
import {StoreModule} from '@ngrx/store';
@@ -30,7 +29,7 @@ import {MomentTimezoneModule} from 'angular-moment-timezone';
import {NgStringPipesModule} from 'angular-pipes';
import {SimpleNotificationsModule} from 'angular2-notifications';
-import {environment} from '@envs/environment';
+import { EffectsModule } from '@ngrx/effects';
import {SharedModule} from '@modules/shared/shared.module';
import {AppLoadModule} from '@modules/app-load/app-load.module';
@@ -115,6 +114,9 @@ import {LogsFilteringUtilsService} from
'@app/services/logs-filtering-utils.serv
import {LogsStateService} from '@app/services/storage/logs-state.service';
import {LoginScreenGuardService} from
'@app/services/login-screen-guard.service';
+import { AuthEffects } from '@app/store/effects/auth.effects';
+import { NotificationEffects } from '@app/store/effects/notification.effects';
+
@NgModule({
declarations: [
AppComponent,
@@ -186,7 +188,11 @@ import {LoginScreenGuardService} from
'@app/services/login-screen-guard.service'
maxAge: 5
}),
- AppRoutingModule
+ AppRoutingModule,
+
+ EffectsModule.run(AuthEffects),
+ EffectsModule.run(NotificationEffects)
+
],
providers: [
HttpClientService,
diff --git a/ambari-logsearch-web/src/app/classes/models/app-state.ts
b/ambari-logsearch-web/src/app/classes/models/app-state.ts
index 2a4d4cc..374e15d 100644
--- a/ambari-logsearch-web/src/app/classes/models/app-state.ts
+++ b/ambari-logsearch-web/src/app/classes/models/app-state.ts
@@ -16,9 +16,9 @@
* limitations under the License.
*/
-import {ActiveServiceLogEntry} from '@app/classes/active-service-log-entry';
-import {ListItem} from '@app/classes/list-item';
-import {DataAvailability, DataAvailabilityValues, LogsType} from
'@app/classes/string';
+import { ActiveServiceLogEntry } from '@app/classes/active-service-log-entry';
+import { ListItem } from '@app/classes/list-item';
+import { DataAvailabilityValues, LogsType } from '@app/classes/string';
export interface History {
items: ListItem[];
@@ -26,10 +26,9 @@ export interface History {
}
export interface AppState {
- isAuthorized: boolean;
isInitialLoading: boolean;
isLoginInProgress: boolean;
- baseDataSetState: DataAvailability;
+ baseDataSetState: DataAvailabilityValues;
activeLogsType?: LogsType;
isServiceLogsFileView: boolean;
isServiceLogContextView: boolean;
@@ -39,7 +38,6 @@ export interface AppState {
}
export const initialState: AppState = {
- isAuthorized: false,
isInitialLoading: false,
isLoginInProgress: false,
baseDataSetState: DataAvailabilityValues.NOT_AVAILABLE,
diff --git a/ambari-logsearch-web/src/app/classes/models/store.ts
b/ambari-logsearch-web/src/app/classes/models/store.ts
index 9e34b14..f106b17 100644
--- a/ambari-logsearch-web/src/app/classes/models/store.ts
+++ b/ambari-logsearch-web/src/app/classes/models/store.ts
@@ -16,24 +16,26 @@
* limitations under the License.
*/
-import {ReflectiveInjector} from '@angular/core';
-import {Observable} from 'rxjs/Observable';
-import {Store, Action} from '@ngrx/store';
-import {AppSettings} from '@app/classes/models/app-settings';
-import {AppState} from '@app/classes/models/app-state';
-import {AuditLog} from '@app/classes/models/audit-log';
-import {ServiceLog} from '@app/classes/models/service-log';
-import {BarGraph} from '@app/classes/models/bar-graph';
-import {Graph} from '@app/classes/models/graph';
-import {NodeItem} from '@app/classes/models/node-item';
-import {UserConfig} from '@app/classes/models/user-config';
-import {LogTypeTab} from '@app/classes/models/log-type-tab';
-import {LogField} from '@app/classes/object';
-import {UtilsService} from '@app/services/utils.service';
-import {NotificationInterface} from
'@modules/shared/interfaces/notification.interface';
-import {LogsState} from '@app/classes/models/logs-state';
+import { ReflectiveInjector } from '@angular/core';
+import { Observable } from 'rxjs/Observable';
+import { Store, Action } from '@ngrx/store';
+import { AppSettings } from '@app/classes/models/app-settings';
+import { AppState } from '@app/classes/models/app-state';
+import { AuditLog } from '@app/classes/models/audit-log';
+import { ServiceLog } from '@app/classes/models/service-log';
+import { BarGraph } from '@app/classes/models/bar-graph';
+import { Graph } from '@app/classes/models/graph';
+import { NodeItem } from '@app/classes/models/node-item';
+import { UserConfig } from '@app/classes/models/user-config';
+import { LogTypeTab } from '@app/classes/models/log-type-tab';
+import { LogField } from '@app/classes/object';
+import { UtilsService } from '@app/services/utils.service';
+import { NotificationInterface } from
'@modules/shared/interfaces/notification.interface';
+import { LogsState } from '@app/classes/models/logs-state';
import { DataAvaibilityStatesModel } from
'@app/modules/app-load/models/data-availability-state.model';
+import * as auth from '@app/store/reducers/auth.reducers';
+
const storeActions = {
'ARRAY.ADD': 'ADD',
'ARRAY.ADD.START': 'ADD_TO_START',
@@ -68,6 +70,7 @@ export interface AppStore {
notifications: NotificationInterface[];
logsState: LogsState;
dataAvailabilityStates: DataAvaibilityStatesModel;
+ auth: auth.State;
}
export class ModelService {
diff --git
a/ambari-logsearch-web/src/app/modules/shared/interfaces/notification.interface.ts
b/ambari-logsearch-web/src/app/classes/models/user.ts
similarity index 82%
copy from
ambari-logsearch-web/src/app/modules/shared/interfaces/notification.interface.ts
copy to ambari-logsearch-web/src/app/classes/models/user.ts
index b8b3d6a..1743865 100644
---
a/ambari-logsearch-web/src/app/modules/shared/interfaces/notification.interface.ts
+++ b/ambari-logsearch-web/src/app/classes/models/user.ts
@@ -15,10 +15,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import {Options} from 'angular2-notifications/src/options.type';
-export interface NotificationInterface extends Options {
- type: string;
- message: string;
- title: string;
+export interface User {
+ username: string;
+ name?: string;
+ email?: string;
}
diff --git a/ambari-logsearch-web/src/app/classes/string.ts
b/ambari-logsearch-web/src/app/classes/string.ts
index db1311f..2e57740 100644
--- a/ambari-logsearch-web/src/app/classes/string.ts
+++ b/ambari-logsearch-web/src/app/classes/string.ts
@@ -26,7 +26,6 @@ export type ScrollType = 'before' | 'after' | '';
export type LogLevel = 'FATAL' | 'ERROR' | 'WARN' | 'INFO' | 'DEBUG' | 'TRACE'
| 'UNKNOWN';
-export type DataAvailability = 'NOT_AVAILABLE' | 'LOADING' | 'AVAILABLE' |
'ERROR';
export enum DataAvailabilityValues {
NOT_AVAILABLE = 'NOT_AVAILABLE',
LOADING = 'LOADING',
diff --git
a/ambari-logsearch-web/src/app/components/action-menu/action-menu.component.html
b/ambari-logsearch-web/src/app/components/action-menu/action-menu.component.html
index 8970316..e64a89c 100644
---
a/ambari-logsearch-web/src/app/components/action-menu/action-menu.component.html
+++
b/ambari-logsearch-web/src/app/components/action-menu/action-menu.component.html
@@ -35,7 +35,7 @@
(buttonClick)="stopCapture()"></menu-button>
<menu-button label="{{'topMenu.refresh' | translate}}" iconClass="fa
fa-refresh"
(buttonClick)="refresh()"></menu-button>
-<modal-dialog
+<modal-dialog *ngIf="isLogIndexFilterDisplayed$ | async"
class="log-index-filter"
[visible]="isLogIndexFilterDisplayed$ | async"
[showCloseBtn]="false"
diff --git
a/ambari-logsearch-web/src/app/components/action-menu/action-menu.component.spec.ts
b/ambari-logsearch-web/src/app/components/action-menu/action-menu.component.spec.ts
index 4d84bb7..133c4bb 100644
---
a/ambari-logsearch-web/src/app/components/action-menu/action-menu.component.spec.ts
+++
b/ambari-logsearch-web/src/app/components/action-menu/action-menu.component.spec.ts
@@ -56,6 +56,12 @@ import {NotificationService} from
'@modules/shared/services/notification.service
import { DataAvailabilityStatesStore, dataAvailabilityStates } from
'@app/modules/app-load/stores/data-availability-state.store';
+import * as auth from '@app/store/reducers/auth.reducers';
+import { AuthService } from '@app/services/auth.service';
+import { EffectsModule } from '@ngrx/effects';
+import { AuthEffects } from '@app/store/effects/auth.effects';
+import { NotificationEffects } from '@app/store/effects/notification.effects';
+
describe('ActionMenuComponent', () => {
let component: ActionMenuComponent;
let fixture: ComponentFixture<ActionMenuComponent>;
@@ -81,8 +87,11 @@ describe('ActionMenuComponent', () => {
hosts,
serviceLogsTruncated,
tabs,
- dataAvailabilityStates
- })
+ dataAvailabilityStates,
+ auth: auth.reducer
+ }),
+ EffectsModule.run(AuthEffects),
+ EffectsModule.run(NotificationEffects)
],
declarations: [
LogIndexFilterComponent,
@@ -116,7 +125,8 @@ describe('ActionMenuComponent', () => {
LogsStateService,
NotificationsService,
NotificationService,
- DataAvailabilityStatesStore
+ DataAvailabilityStatesStore,
+ AuthService
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
diff --git
a/ambari-logsearch-web/src/app/components/action-menu/action-menu.component.ts
b/ambari-logsearch-web/src/app/components/action-menu/action-menu.component.ts
index a293e95..46a0a76 100644
---
a/ambari-logsearch-web/src/app/components/action-menu/action-menu.component.ts
+++
b/ambari-logsearch-web/src/app/components/action-menu/action-menu.component.ts
@@ -63,7 +63,7 @@ export class ActionMenuComponent implements OnInit,
OnDestroy {
subscriptions: Subscription[] = [];
constructor(
- private logsContainer: LogsContainerService,
+ private logsContainerService: LogsContainerService,
private historyManager: HistoryManagerService,
private settings: UserSettingsService,
private route: ActivatedRoute,
@@ -98,7 +98,7 @@ export class ActionMenuComponent implements OnInit,
OnDestroy {
}
get captureSeconds(): number {
- return this.logsContainer.captureSeconds;
+ return this.logsContainerService.captureSeconds;
}
setModalSubmitDisabled(isDisabled: boolean): void {
@@ -126,7 +126,7 @@ export class ActionMenuComponent implements OnInit,
OnDestroy {
}
refresh(): void {
- this.logsContainer.loadLogs();
+ this.logsContainerService.loadLogs();
}
onSelectCluster(cluster: string) {
@@ -157,15 +157,15 @@ export class ActionMenuComponent implements OnInit,
OnDestroy {
}
startCapture(): void {
- this.logsContainer.startCaptureTimer();
+ this.logsContainerService.startCaptureTimer();
}
stopCapture(): void {
- this.logsContainer.stopCaptureTimer();
+ this.logsContainerService.stopCaptureTimer();
}
cancelCapture(): void {
- this.logsContainer.cancelCapture();
+ this.logsContainerService.cancelCapture();
}
}
diff --git a/ambari-logsearch-web/src/app/components/app.component.html
b/ambari-logsearch-web/src/app/components/app.component.html
index c9f8313..47d461b 100644
--- a/ambari-logsearch-web/src/app/components/app.component.html
+++ b/ambari-logsearch-web/src/app/components/app.component.html
@@ -22,6 +22,11 @@
<top-menu *ngIf="(isAuthorized$ | async) && (isBaseDataAvailable$ |
async)"></top-menu>
</nav>
</header>
-<data-loading-indicator *ngIf="!(isBaseDataAvailable$ | async) &&
(isAuthorized$ | async)"></data-loading-indicator>
-<main-container *ngIf="!(isAuthorized$ | async) || (isBaseDataAvailable$ |
async)"></main-container>
-<simple-notifications
[options]="notificationServiceOptions"></simple-notifications>
+
+<ng-container *ngIf="(authorizationStatus$ | async) !==
authorizationStatuses.LOGGED_OUT">
+ <data-loading-indicator *ngIf="!(isBaseDataAvailable$ | async) &&
(isAuthorized$ | async)"></data-loading-indicator>
+
+ <main-container *ngIf="!(isAuthorized$ | async) || (isBaseDataAvailable$ |
async)"></main-container>
+
+ <simple-notifications
[options]="notificationServiceOptions"></simple-notifications>
+</ng-container>
diff --git a/ambari-logsearch-web/src/app/components/app.component.less
b/ambari-logsearch-web/src/app/components/app.component.less
index b9eb907..3e56671 100644
--- a/ambari-logsearch-web/src/app/components/app.component.less
+++ b/ambari-logsearch-web/src/app/components/app.component.less
@@ -59,4 +59,11 @@
}
}
}
+ .auth-checking-in-progress {
+ align-content: center;
+ align-items: center;
+ display: flex;
+ justify-content: center;
+ margin: 1rem 0;
+ }
}
diff --git a/ambari-logsearch-web/src/app/components/app.component.ts
b/ambari-logsearch-web/src/app/components/app.component.ts
index 09a82c2..68d220e 100644
--- a/ambari-logsearch-web/src/app/components/app.component.ts
+++ b/ambari-logsearch-web/src/app/components/app.component.ts
@@ -16,23 +16,41 @@
* limitations under the License.
*/
-import {Component} from '@angular/core';
-import {AppStateService} from '@app/services/storage/app-state.service';
-import {Observable} from 'rxjs/Observable';
-import {Options} from 'angular2-notifications/src/options.type';
-import {notificationIcons} from
'@modules/shared/services/notification.service';
-import { DataAvailability, DataAvailabilityValues } from '@app/classes/string';
+import { Component, OnInit, OnDestroy } from '@angular/core';
+import { Observable } from 'rxjs/Observable';
+import { Subject } from 'rxjs/Subject';
+
+import { Options } from 'angular2-notifications/src/options.type';
+
+import { AppStateService } from '@app/services/storage/app-state.service';
+import { DataAvailabilityValues } from '@app/classes/string';
+import { notificationIcons } from
'@modules/shared/services/notification.service';
+
+import { Store } from '@ngrx/store';
+import { AppStore } from '@app/classes/models/store';
+import { AuthorizationStatuses } from '@app/store/reducers/auth.reducers';
+import { isAuthorizedSelector, authStatusSelector,
isCheckingAuthStatusInProgressSelector } from
'@app/store/selectors/auth.selectors';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
- styleUrls: ['./app.component.less', '../modules/shared/notifications.less']
+ styleUrls: ['./app.component.less', '../modules/shared/notifications.less'],
+ host: {
+ '[class]': 'hostCssClasses'
+ }
})
-export class AppComponent {
+export class AppComponent implements OnInit, OnDestroy {
- isAuthorized$: Observable<boolean> =
this.appState.getParameter('isAuthorized');
+ authorizationStatuses = AuthorizationStatuses;
+
+ isAuthorized$: Observable<boolean> = this.store.select(isAuthorizedSelector);
+ authorizationStatus$: Observable<AuthorizationStatuses> =
this.store.select(authStatusSelector);
+ isCheckingAuthStatusInProgress$: Observable<boolean> =
this.store.select(isCheckingAuthStatusInProgressSelector);
+ authorizationCode$: Observable<number> =
this.appState.getParameter('authorizationCode');
isBaseDataAvailable$: Observable<boolean> =
this.appState.getParameter('baseDataSetState')
- .map((dataSetState: DataAvailability) => dataSetState ===
DataAvailabilityValues.AVAILABLE);
+ .map((dataSetState: DataAvailabilityValues) => dataSetState ===
DataAvailabilityValues.AVAILABLE);
+
+ destroyed$ = new Subject();
notificationServiceOptions: Options = {
timeOut: 2000,
@@ -44,8 +62,27 @@ export class AppComponent {
position: ['top', 'left']
};
+ hostCssClasses = '';
+
constructor(
- private appState: AppStateService
+ private appState: AppStateService,
+ private store: Store<AppStore>
) {}
+ ngOnInit() {
+
this.authorizationStatus$.distinctUntilChanged().takeUntil(this.destroyed$).subscribe(this.onAuthStatusChange);
+ }
+
+ ngOnDestroy() {
+ this.destroyed$.next(true);
+ }
+
+ onAuthStatusChange = (status: AuthorizationStatuses): void => {
+ this.setHostCssClasses(status ? status.replace(/\s/,
'-').toLocaleLowerCase() : '');
+ }
+
+ setHostCssClasses(cls: string) {
+ this.hostCssClasses = cls;
+ }
+
}
diff --git
a/ambari-logsearch-web/src/app/components/audit-logs-entries/audit-logs-entries.component.spec.ts
b/ambari-logsearch-web/src/app/components/audit-logs-entries/audit-logs-entries.component.spec.ts
index 51d1fda..e6191f1 100644
---
a/ambari-logsearch-web/src/app/components/audit-logs-entries/audit-logs-entries.component.spec.ts
+++
b/ambari-logsearch-web/src/app/components/audit-logs-entries/audit-logs-entries.component.spec.ts
@@ -47,6 +47,12 @@ import {LogsFilteringUtilsService} from
'@app/services/logs-filtering-utils.serv
import {NotificationService} from
'@modules/shared/services/notification.service';
import {NotificationsService} from
'angular2-notifications/src/notifications.service';
+import * as auth from '@app/store/reducers/auth.reducers';
+import { AuthService } from '@app/services/auth.service';
+import { EffectsModule } from '@ngrx/effects';
+import { AuthEffects } from '@app/store/effects/auth.effects';
+import { NotificationEffects } from '@app/store/effects/notification.effects';
+
describe('AuditLogsEntriesComponent', () => {
let component: AuditLogsEntriesComponent;
let fixture: ComponentFixture<AuditLogsEntriesComponent>;
@@ -73,8 +79,11 @@ describe('AuditLogsEntriesComponent', () => {
components,
hosts,
serviceLogsTruncated,
- tabs
+ tabs,
+ auth: auth.reducer
}),
+ EffectsModule.run(AuthEffects),
+ EffectsModule.run(NotificationEffects)
],
providers: [
...MockHttpRequestModules,
@@ -98,7 +107,8 @@ describe('AuditLogsEntriesComponent', () => {
LogsFilteringUtilsService,
LogsStateService,
NotificationsService,
- NotificationService
+ NotificationService,
+ AuthService
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
diff --git
a/ambari-logsearch-web/src/app/components/audit-logs-table/audit-logs-table.component.spec.ts
b/ambari-logsearch-web/src/app/components/audit-logs-table/audit-logs-table.component.spec.ts
index f65180d..60c6e9b 100644
---
a/ambari-logsearch-web/src/app/components/audit-logs-table/audit-logs-table.component.spec.ts
+++
b/ambari-logsearch-web/src/app/components/audit-logs-table/audit-logs-table.component.spec.ts
@@ -52,6 +52,12 @@ import {LogsFilteringUtilsService} from
'@app/services/logs-filtering-utils.serv
import {NotificationService} from
'@modules/shared/services/notification.service';
import {NotificationsService} from
'angular2-notifications/src/notifications.service';
+import { AuthService } from '@app/services/auth.service';
+import * as auth from '@app/store/reducers/auth.reducers';
+import { EffectsModule } from '@ngrx/effects';
+import { AuthEffects } from '@app/store/effects/auth.effects';
+import { NotificationEffects } from '@app/store/effects/notification.effects';
+
describe('AuditLogsTableComponent', () => {
let component: AuditLogsTableComponent;
let fixture: ComponentFixture<AuditLogsTableComponent>;
@@ -83,8 +89,11 @@ describe('AuditLogsTableComponent', () => {
tabs,
clusters,
components,
- hosts
- })
+ hosts,
+ auth: auth.reducer
+ }),
+ EffectsModule.run(AuthEffects),
+ EffectsModule.run(NotificationEffects)
],
providers: [
...MockHttpRequestModules,
@@ -108,7 +117,8 @@ describe('AuditLogsTableComponent', () => {
LogsFilteringUtilsService,
LogsStateService,
NotificationsService,
- NotificationService
+ NotificationService,
+ AuthService
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
diff --git
a/ambari-logsearch-web/src/app/components/cluster-filter/cluster-filter.component.spec.ts
b/ambari-logsearch-web/src/app/components/cluster-filter/cluster-filter.component.spec.ts
index 8a6cbc6..ac6efb1 100644
---
a/ambari-logsearch-web/src/app/components/cluster-filter/cluster-filter.component.spec.ts
+++
b/ambari-logsearch-web/src/app/components/cluster-filter/cluster-filter.component.spec.ts
@@ -52,6 +52,8 @@ import {NotificationService} from
'@modules/shared/services/notification.service
import {NotificationsService} from
'angular2-notifications/src/notifications.service';
import { DataAvailabilityStatesStore, dataAvailabilityStates } from
'@app/modules/app-load/stores/data-availability-state.store';
+import * as auth from '@app/store/reducers/auth.reducers';
+
describe('ClusterFilterComponent', () => {
let component: ClusterFilterComponent;
let fixture: ComponentFixture<ClusterFilterComponent>;
@@ -84,7 +86,8 @@ describe('ClusterFilterComponent', () => {
clusters,
components,
hosts,
- dataAvailabilityStates
+ dataAvailabilityStates,
+ auth: auth.reducer
})
],
providers: [
diff --git
a/ambari-logsearch-web/src/app/components/cluster-filter/cluster-filter.component.ts
b/ambari-logsearch-web/src/app/components/cluster-filter/cluster-filter.component.ts
index 9921d41..086160b 100644
---
a/ambari-logsearch-web/src/app/components/cluster-filter/cluster-filter.component.ts
+++
b/ambari-logsearch-web/src/app/components/cluster-filter/cluster-filter.component.ts
@@ -45,7 +45,7 @@ export class ClusterFilterComponent implements OnInit,
OnDestroy {
private clusterSelectionStoreKey: BehaviorSubject<string> = new
BehaviorSubject('');
- private clustersAsListItems$: Observable<ListItem[]> =
this.clusterSelectionStoreKey.distinctUntilChanged()
+ clustersAsListItems$: Observable<ListItem[]> =
this.clusterSelectionStoreKey.distinctUntilChanged()
.switchMap((selectionStoreKey: string) => Observable.combineLatest(
this.clusterSelectionStoreService.getParameter(selectionStoreKey),
this.clusterStoreService.getAll()
@@ -58,8 +58,8 @@ export class ClusterFilterComponent implements OnInit,
OnDestroy {
})
).startWith([]);
- private readonly defaultUseMultiSelection = true;
- private useMultiSelection: BehaviorSubject<boolean> = new
BehaviorSubject(false);
+ readonly defaultUseMultiSelection = true;
+ useMultiSelection: BehaviorSubject<boolean> = new BehaviorSubject(false);
private subscriptions: Subscription[] = [];
diff --git
a/ambari-logsearch-web/src/app/components/context-menu/context-menu.component.spec.ts
b/ambari-logsearch-web/src/app/components/context-menu/context-menu.component.spec.ts
index afca603..0dfc49a 100644
---
a/ambari-logsearch-web/src/app/components/context-menu/context-menu.component.spec.ts
+++
b/ambari-logsearch-web/src/app/components/context-menu/context-menu.component.spec.ts
@@ -51,6 +51,11 @@ import {LogsFilteringUtilsService} from
'@app/services/logs-filtering-utils.serv
import {NotificationService} from
'@modules/shared/services/notification.service';
import {NotificationsService} from
'angular2-notifications/src/notifications.service';
+import * as auth from '@app/store/reducers/auth.reducers';
+import { EffectsModule } from '@ngrx/effects';
+import { AuthEffects } from '@app/store/effects/auth.effects';
+import { NotificationEffects } from '@app/store/effects/notification.effects';
+
describe('ContextMenuComponent', () => {
let component: ContextMenuComponent;
let fixture: ComponentFixture<ContextMenuComponent>;
@@ -86,8 +91,11 @@ describe('ContextMenuComponent', () => {
clusters,
components,
serviceLogsTruncated,
- tabs
+ tabs,
+ auth: auth.reducer
}),
+ EffectsModule.run(AuthEffects),
+ EffectsModule.run(NotificationEffects),
FormsModule
],
providers: [
diff --git
a/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.html
b/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.html
index 7385305..657d1ea 100644
---
a/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.html
+++
b/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.html
@@ -32,25 +32,49 @@
<dropdown-button iconClass="fa fa-search-minus" label="{{'filter.exclude'
| translate}}" [hideCaret]="true"
[showSelectedValue]="false"
[showCommonLabelWithSelection]="true"
[options]="searchBoxItems$ | async"
(selectItem)="proceedWithExclude($event)"></dropdown-button>
- <filter-button *ngIf="isFilterConditionDisplayed('hosts')"
formControlName="hosts"
- label="{{filters.hosts.label | translate}}"
[iconClass]="filters.hosts.iconClass"
- [subItems]="filters.hosts.options"
[isMultipleChoice]="true" [isRightAlign]="true"
- additionalLabelComponentSetter="getDataForHostsNodeBar"
- [class.disabled]="isServiceLogsFileView$ | async"
[isDisabled]="isServiceLogsFileView$ | async"
- [useDropDownLocalFilter]="true"></filter-button>
- <filter-button *ngIf="isFilterConditionDisplayed('users')"
formControlName="users"
- label="{{filters.users.label | translate}}"
[iconClass]="filters.users.iconClass"
- [subItems]="filters.users.options"
[isMultipleChoice]="true" [isRightAlign]="true"
- [useDropDownLocalFilter]="true"></filter-button>
- <filter-button *ngIf="isFilterConditionDisplayed('components')"
formControlName="components" [useDropDownLocalFilter]="true"
- label="{{filters.components.label | translate}}"
[iconClass]="filters.components.iconClass"
- [subItems]="filters.components.options"
[isMultipleChoice]="true" [isRightAlign]="true"
- [class.disabled]="isServiceLogsFileView$ | async"
[isDisabled]="isServiceLogsFileView$ | async"
-
additionalLabelComponentSetter="getDataForComponentsNodeBar"></filter-button>
- <filter-button *ngIf="isFilterConditionDisplayed('levels')"
formControlName="levels"
- label="{{filters.levels.label | translate}}"
[iconClass]="filters.levels.iconClass"
- [subItems]="filters.levels.options"
[isMultipleChoice]="true" [isRightAlign]="true"
- [useDropDownLocalFilter]="true"></filter-button>
+
+ <filter-button *ngIf="isFilterConditionDisplayed('hosts')"
+ formControlName="hosts"
+ [subItems]="filters.hosts.options"
+ label="{{filters.hosts.label | translate}}"
+ [useDropDownLocalFilter]="true"
+ [isMultipleChoice]="true"
+ [iconClass]="filters.hosts.iconClass"
+ [isRightAlign]="true"
+ [class.disabled]="isServiceLogsFileView$ | async"
+ [isDisabled]="isServiceLogsFileView$ | async"
+ additionalLabelComponentSetter="getDataForHostsNodeBar"></filter-button>
+
+ <filter-button *ngIf="isFilterConditionDisplayed('users')"
+ formControlName="users"
+ label="{{filters.users.label | translate}}"
+ [iconClass]="filters.users.iconClass"
+ [subItems]="filters.users.options"
+ [isMultipleChoice]="true"
+ [isRightAlign]="true"
+ [useDropDownLocalFilter]="true"></filter-button>
+
+ <filter-button *ngIf="isFilterConditionDisplayed('components')"
+ formControlName="components"
+ [subItems]="filters.components.options"
+ label="{{filters.components.label | translate}}"
+ [useDropDownLocalFilter]="true"
+ [isMultipleChoice]="true"
+ [iconClass]="filters.components.iconClass"
+ [isRightAlign]="true"
+ [class.disabled]="isServiceLogsFileView$ | async"
+ [isDisabled]="isServiceLogsFileView$ | async"
+
additionalLabelComponentSetter="getDataForComponentsNodeBar"></filter-button>
+
+ <filter-button *ngIf="isFilterConditionDisplayed('levels')"
+ formControlName="levels"
+ label="{{filters.levels.label | translate}}"
+ [iconClass]="filters.levels.iconClass"
+ [subItems]="filters.levels.options"
+ [isMultipleChoice]="true"
+ [isRightAlign]="true"
+ [useDropDownLocalFilter]="true"></filter-button>
+
<menu-button class="clear-filter-btn" iconClass="fa fa-times"
label="{{'filters.clear' | translate}}"
(buttonClick)="onClearBtnClick($event)"></menu-button>
</div>
diff --git
a/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.spec.ts
b/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.spec.ts
index 3b85377..fa73ce0 100644
---
a/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.spec.ts
+++
b/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.spec.ts
@@ -48,6 +48,12 @@ import {LogsFilteringUtilsService} from
'@app/services/logs-filtering-utils.serv
import {NotificationService} from
'@modules/shared/services/notification.service';
import {NotificationsService} from
'angular2-notifications/src/notifications.service';
+import * as auth from '@app/store/reducers/auth.reducers';
+import { AuthService } from '@app/services/auth.service';
+import { EffectsModule } from '@ngrx/effects';
+import { AuthEffects } from '@app/store/effects/auth.effects';
+import { NotificationEffects } from '@app/store/effects/notification.effects';
+
describe('FiltersPanelComponent', () => {
let component: FiltersPanelComponent;
let fixture: ComponentFixture<FiltersPanelComponent>;
@@ -80,8 +86,11 @@ describe('FiltersPanelComponent', () => {
serviceLogsHistogramData,
appState,
serviceLogsTruncated,
- tabs
+ tabs,
+ auth: auth.reducer
}),
+ EffectsModule.run(AuthEffects),
+ EffectsModule.run(NotificationEffects),
...TranslationModules
],
providers: [
@@ -106,7 +115,8 @@ describe('FiltersPanelComponent', () => {
LogsFilteringUtilsService,
LogsStateService,
NotificationsService,
- NotificationService
+ NotificationService,
+ AuthService
],
schemas: [NO_ERRORS_SCHEMA]
})
diff --git
a/ambari-logsearch-web/src/app/components/log-context/log-context.component.spec.ts
b/ambari-logsearch-web/src/app/components/log-context/log-context.component.spec.ts
index 82201ba..e2e10ba 100644
---
a/ambari-logsearch-web/src/app/components/log-context/log-context.component.spec.ts
+++
b/ambari-logsearch-web/src/app/components/log-context/log-context.component.spec.ts
@@ -47,6 +47,12 @@ import {LogsFilteringUtilsService} from
'@app/services/logs-filtering-utils.serv
import {NotificationsService} from
'angular2-notifications/src/notifications.service';
import {NotificationService} from
'@modules/shared/services/notification.service';
+import * as auth from '@app/store/reducers/auth.reducers';
+import { AuthService } from '@app/services/auth.service';
+import { EffectsModule } from '@ngrx/effects';
+import { AuthEffects } from '@app/store/effects/auth.effects';
+import { NotificationEffects } from '@app/store/effects/notification.effects';
+
describe('LogContextComponent', () => {
let component: LogContextComponent;
let fixture: ComponentFixture<LogContextComponent>;
@@ -72,8 +78,11 @@ describe('LogContextComponent', () => {
components,
hosts,
serviceLogsTruncated,
- tabs
+ tabs,
+ auth: auth.reducer
}),
+ EffectsModule.run(AuthEffects),
+ EffectsModule.run(NotificationEffects),
...TranslationModules
],
providers: [
@@ -98,7 +107,8 @@ describe('LogContextComponent', () => {
LogsFilteringUtilsService,
LogsStateService,
NotificationsService,
- NotificationService
+ NotificationService,
+ AuthService
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
diff --git
a/ambari-logsearch-web/src/app/components/log-index-filter/log-index-filter.component.html
b/ambari-logsearch-web/src/app/components/log-index-filter/log-index-filter.component.html
index f5dc84b..2096687 100644
---
a/ambari-logsearch-web/src/app/components/log-index-filter/log-index-filter.component.html
+++
b/ambari-logsearch-web/src/app/components/log-index-filter/log-index-filter.component.html
@@ -20,10 +20,10 @@
<tr>
<th class="component-column">{{'filter.components' | translate}}</th>
<th *ngFor="let column of columns" class="checkbox-column">
- <input type="checkbox" attr.id="{{column.name}}"
+ <input type="checkbox"
attr.id="log-index-filter-component-{{column.name}}"
[attr.checked]="isAllComponentsCheckedForLevel(column.name) ?
'checked' : null"
(change)="processAllComponentsForLevel(column.name,
$event.target.checked)">
- <label attr.for="{{column.name}}">
+ <label attr.for="log-index-filter-component-{{column.name}}">
<graph-legend-item label="{{column.label | translate}}"
color="{{column.color}}"></graph-legend-item>
</label>
</th>
diff --git
a/ambari-logsearch-web/src/app/components/log-index-filter/log-index-filter.component.less
b/ambari-logsearch-web/src/app/components/log-index-filter/log-index-filter.component.less
index a5c3957..efdb2c6 100644
---
a/ambari-logsearch-web/src/app/components/log-index-filter/log-index-filter.component.less
+++
b/ambari-logsearch-web/src/app/components/log-index-filter/log-index-filter.component.less
@@ -21,12 +21,18 @@
:host {
div.log-index-filter-content {
table {
+ td {
+ vertical-align: middle;
+ }
&.table-header {
background-color: #fff;
margin-bottom: 0;
position: sticky;
top: -1px;
z-index: 10;
+ tr {
+ box-shadow: -2px 2px 2px fadeout(@fluid-gray-1, 50%);
+ }
th {
padding: 8px 0;
}
diff --git
a/ambari-logsearch-web/src/app/components/log-index-filter/log-index-filter.component.spec.ts
b/ambari-logsearch-web/src/app/components/log-index-filter/log-index-filter.component.spec.ts
index 3b042ae..19d0b57 100644
---
a/ambari-logsearch-web/src/app/components/log-index-filter/log-index-filter.component.spec.ts
+++
b/ambari-logsearch-web/src/app/components/log-index-filter/log-index-filter.component.spec.ts
@@ -55,6 +55,12 @@ import {ComponentLabelPipe} from
'@app/pipes/component-label';
import { dataAvailabilityStates, DataAvailabilityStatesStore } from
'@app/modules/app-load/stores/data-availability-state.store';
+import * as auth from '@app/store/reducers/auth.reducers';
+import { AuthService } from '@app/services/auth.service';
+import { EffectsModule } from '@ngrx/effects';
+import { AuthEffects } from '@app/store/effects/auth.effects';
+import { NotificationEffects } from '@app/store/effects/notification.effects';
+
describe('LogIndexFilterComponent', () => {
let component: LogIndexFilterComponent;
let fixture: ComponentFixture<LogIndexFilterComponent>;
@@ -79,8 +85,11 @@ describe('LogIndexFilterComponent', () => {
hosts,
serviceLogsTruncated,
tabs,
- dataAvailabilityStates
- })
+ dataAvailabilityStates,
+ auth: auth.reducer
+ }),
+ EffectsModule.run(AuthEffects),
+ EffectsModule.run(NotificationEffects)
],
declarations: [
LogIndexFilterComponent,
@@ -113,7 +122,8 @@ describe('LogIndexFilterComponent', () => {
LogsStateService,
NotificationsService,
NotificationService,
- DataAvailabilityStatesStore
+ DataAvailabilityStatesStore,
+ AuthService
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
diff --git
a/ambari-logsearch-web/src/app/components/log-index-filter/log-index-filter.component.ts
b/ambari-logsearch-web/src/app/components/log-index-filter/log-index-filter.component.ts
index 65c22a4..73c8604 100644
---
a/ambari-logsearch-web/src/app/components/log-index-filter/log-index-filter.component.ts
+++
b/ambari-logsearch-web/src/app/components/log-index-filter/log-index-filter.component.ts
@@ -33,7 +33,7 @@ import { UtilsService } from '@app/services/utils.service';
import { ClustersService } from '@app/services/storage/clusters.service';
import { HostsService } from '@app/services/storage/hosts.service';
import { DataAvailabilityStatesStore } from
'@app/modules/app-load/stores/data-availability-state.store';
-import { DataAvailabilityValues, DataAvailability } from '@app/classes/string';
+import { DataAvailabilityValues } from '@app/classes/string';
@Component({
selector: 'log-index-filter',
@@ -68,7 +68,7 @@ export class LogIndexFilterComponent implements OnInit,
OnDestroy, OnChanges, Co
configsAvailabilityState$: Observable<string> =
this.dataAvailablilityStore.getParameter('logIndexFilter');
configsAreLoading$: Observable<boolean> =
this.configsAvailabilityState$.distinctUntilChanged().map(
- (state: DataAvailability) => state === DataAvailabilityValues.LOADING
+ (state: DataAvailabilityValues) => state === DataAvailabilityValues.LOADING
);
@Input()
diff --git
a/ambari-logsearch-web/src/app/components/login-form/login-form.component.html
b/ambari-logsearch-web/src/app/components/login-form/login-form.component.html
index 3db75c6..344ac0a 100644
---
a/ambari-logsearch-web/src/app/components/login-form/login-form.component.html
+++
b/ambari-logsearch-web/src/app/components/login-form/login-form.component.html
@@ -16,18 +16,25 @@
-->
<div class="login-form well col-md-4 col-md-offset-4 col-sm-offset-4">
- <div class="alert alert-danger"
*ngIf="isLoginAlertDisplayed">{{errorMessage}}</div>
+ <div class="alert alert-danger" *ngIf="authorizationMessage$ | async">{{
(authorizationMessage$ | async) | translate}}</div>
<form #loginForm="ngForm" (ngSubmit)="login()">
<div class="form-group">
<label for="username">{{'authorization.name' | translate}}</label>
- <input class="form-control" type="text" id="username" name="username"
required [(ngModel)]="username">
+ <input class="form-control" type="text" id="username" name="username"
+ [disabled]="isInputDisabled$ | async" required [(ngModel)]="username">
</div>
<div class="form-group">
<label for="password">{{'authorization.password' | translate}}</label>
- <input class="form-control" type="password" id="password"
name="password" required [(ngModel)]="password">
+ <input class="form-control" type="password" id="password" name="password"
+ [disabled]="isInputDisabled$ | async" required [(ngModel)]="password">
</div>
- <button class="btn btn-success" [disabled]="!loginForm.form.valid ||
(isLoginInProgress$ | async)">
- {{'authorization.signIn' | translate}}
- </button>
+ <footer>
+ <button class="btn btn-success" [disabled]="!loginForm.form.valid ||
(isInputDisabled$ | async)">
+ {{'authorization.signIn' | translate}}
+ </button>
+ <span *ngIf="isCheckingAuthStatusInProgress$ | async"
class="checking-auth-in-progress">
+ <i class="fa fa-spinner fa-spin"></i>
{{'authorization.checkingAuthorization' | translate}}
+ </span>
+ </footer>
</form>
</div>
diff --git
a/ambari-logsearch-web/src/app/components/login-form/login-form.component.less
b/ambari-logsearch-web/src/app/components/login-form/login-form.component.less
index 19d800d..f7a4265 100644
---
a/ambari-logsearch-web/src/app/components/login-form/login-form.component.less
+++
b/ambari-logsearch-web/src/app/components/login-form/login-form.component.less
@@ -16,7 +16,14 @@
*/
@import '../../modules/shared/variables';
-
-.login-form {
- margin-top: @block-margin-top;
+:host {
+ footer {
+ display: flex;
+ .checking-auth-in-progress {
+ margin-left: auto;
+ }
+ }
+ .login-form {
+ margin-top: @block-margin-top;
+ }
}
diff --git
a/ambari-logsearch-web/src/app/components/login-form/login-form.component.spec.ts
b/ambari-logsearch-web/src/app/components/login-form/login-form.component.spec.ts
index 3ec55fd..eb6a21e 100644
---
a/ambari-logsearch-web/src/app/components/login-form/login-form.component.spec.ts
+++
b/ambari-logsearch-web/src/app/components/login-form/login-form.component.spec.ts
@@ -16,19 +16,26 @@
* limitations under the License.
*/
-import {async, ComponentFixture, TestBed} from '@angular/core/testing';
+import {async, inject, ComponentFixture, TestBed} from '@angular/core/testing';
import {FormsModule} from '@angular/forms';
-import {TranslationModules} from '@app/test-config.spec';
+import {TranslationModules, MockHttpRequestModules} from
'@app/test-config.spec';
import {StoreModule} from '@ngrx/store';
import {AppStateService, appState} from
'@app/services/storage/app-state.service';
import {HttpClientService} from '@app/services/http-client.service';
-import {AuthService} from '@app/services/auth.service';
import {LoginFormComponent} from './login-form.component';
import {RouterTestingModule} from '@angular/router/testing';
import {NotificationsService} from 'angular2-notifications';
import {NotificationService} from
'@app/modules/shared/services/notification.service';
+import {Store} from '@ngrx/store';
+import { AppStore } from '@app/classes/models/store';
+import * as auth from '@app/store/reducers/auth.reducers';
+import { AuthService } from '@app/services/auth.service';
+import { EffectsModule } from '@ngrx/effects';
+import { AuthEffects } from '@app/store/effects/auth.effects';
+import { NotificationEffects } from '@app/store/effects/notification.effects';
+
describe('LoginFormComponent', () => {
let component: LoginFormComponent;
let fixture: ComponentFixture<LoginFormComponent>;
@@ -56,17 +63,22 @@ describe('LoginFormComponent', () => {
FormsModule,
...TranslationModules,
StoreModule.provideStore({
- appState
- })
+ appState,
+ auth: auth.reducer
+ }),
+ EffectsModule.run(AuthEffects),
+ EffectsModule.run(NotificationEffects)
],
providers: [
+ ...MockHttpRequestModules,
AppStateService,
{
provide: AuthService,
useValue: AuthServiceMock
},
NotificationsService,
- NotificationService
+ NotificationService,
+ AuthService
]
})
.compileComponents();
@@ -82,36 +94,4 @@ describe('LoginFormComponent', () => {
expect(component).toBeTruthy();
});
- describe('#login()', () => {
- const cases = [
- {
- isError: true,
- isLoginAlertDisplayed: true,
- isAuthorized: false,
- title: 'login failure'
- },
- {
- isError: false,
- isLoginAlertDisplayed: false,
- isAuthorized: true,
- title: 'login success'
- }
- ];
-
- cases.forEach(test => {
- describe(test.title, () => {
- beforeEach(() => {
- authMock.isError = test.isError;
- authMock.isAuthorized = test.isAuthorized;
- component.login();
- });
-
- it('isLoginAlertDisplayed', () => {
-
expect(component.isLoginAlertDisplayed).toEqual(test.isLoginAlertDisplayed);
- });
-
- });
- });
-
- });
});
diff --git
a/ambari-logsearch-web/src/app/components/login-form/login-form.component.ts
b/ambari-logsearch-web/src/app/components/login-form/login-form.component.ts
index 2f28411..8d5070b 100644
--- a/ambari-logsearch-web/src/app/components/login-form/login-form.component.ts
+++ b/ambari-logsearch-web/src/app/components/login-form/login-form.component.ts
@@ -16,71 +16,56 @@
* limitations under the License.
*/
-import {Component, ViewChild, OnInit, OnDestroy} from '@angular/core';
-import {Observable} from 'rxjs/Observable';
+import { Component, ViewChild } from '@angular/core';
+import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/finally';
-import {Subscription} from 'rxjs/Subscription';
-import {AppStateService} from '@app/services/storage/app-state.service';
-import {AuthService} from '@app/services/auth.service';
-import {TranslateService} from '@ngx-translate/core';
-import {FormGroup} from '@angular/forms';
+import 'rxjs/add/operator/combineLatest';
+
+import { Store } from '@ngrx/store';
+import { AppStore } from '@app/classes/models/store';
+import {
+ isLoginInProgressSelector,
+ isCheckingAuthStatusInProgressSelector,
+ authMessageSelector
+} from '@app/store/selectors/auth.selectors';
+import { LogInAction } from '@app/store/actions/auth.actions';
+
+
+import { FormGroup } from '@angular/forms';
+
@Component({
selector: 'login-form',
templateUrl: './login-form.component.html',
styleUrls: ['./login-form.component.less']
})
-export class LoginFormComponent implements OnInit, OnDestroy {
+export class LoginFormComponent {
username: string;
password: string;
- isLoginAlertDisplayed: boolean;
+ authorizationMessage$: Observable<string> =
this.store.select(authMessageSelector);
+ isLoginInProgress$: Observable<boolean> =
this.store.select(isLoginInProgressSelector);
+ isCheckingAuthStatusInProgress$: Observable<boolean> =
this.store.select(isCheckingAuthStatusInProgressSelector);
- isLoginInProgress$: Observable<boolean> =
this.appState.getParameter('isLoginInProgress');
+ isInputDisabled$: Observable<boolean> =
Observable.combineLatest(this.isLoginInProgress$,
this.isCheckingAuthStatusInProgress$)
+ .map(([loginInProgress, checkAuthInProgress]) => loginInProgress ||
checkAuthInProgress);
errorMessage: string;
@ViewChild('loginForm')
loginForm: FormGroup;
- subscriptions: Subscription[] = [];
-
constructor(
- private authService: AuthService,
- private appState: AppStateService,
- private translateService: TranslateService
+ private store: Store<AppStore>
) {}
- ngOnInit(): void {
- this.subscriptions.push(
- this.loginForm.valueChanges.subscribe(this.onLoginFormChange)
- );
- }
-
- ngOnDestroy(): void {
- this.subscriptions.forEach((subscription: Subscription) =>
subscription.unsubscribe());
- }
-
- onLoginFormChange = (event) => {
- this.isLoginAlertDisplayed = false;
- }
-
- private onLoginSuccess = (result: Boolean): void => {
- this.isLoginAlertDisplayed = false;
- this.errorMessage = '';
- }
-
- private onLoginError = (resp: Boolean): void => {
-
this.translateService.get('authorization.error.401').first().subscribe((message:
string) => {
- this.errorMessage = message;
- this.isLoginAlertDisplayed = true;
- });
- }
-
login() {
- this.authService.login(this.username,
this.password).subscribe(this.onLoginSuccess, this.onLoginError);
+ this.store.dispatch(new LogInAction({
+ username: this.username,
+ password: this.password
+ }));
}
}
diff --git
a/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.spec.ts
b/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.spec.ts
index 78245e4..0051db7 100644
---
a/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.spec.ts
+++
b/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.spec.ts
@@ -43,12 +43,19 @@ import {TabsComponent} from
'@app/components/tabs/tabs.component';
import {LogsContainerComponent} from './logs-container.component';
import {ClusterSelectionService} from
'@app/services/storage/cluster-selection.service';
import {RouterTestingModule} from '@angular/router/testing';
-import {LogsStateService} from '@app/services/storage/logs-state.service';
import {RoutingUtilsService} from '@app/services/routing-utils.service';
import {LogsFilteringUtilsService} from
'@app/services/logs-filtering-utils.service';
import {NotificationService} from
'@modules/shared/services/notification.service';
import {NotificationsService} from
'angular2-notifications/src/notifications.service';
+import {LogsStateService} from '@app/services/storage/logs-state.service';
+
+import * as auth from '@app/store/reducers/auth.reducers';
+import { AuthService } from '@app/services/auth.service';
+import { EffectsModule } from '@ngrx/effects';
+import { AuthEffects } from '@app/store/effects/auth.effects';
+import { NotificationEffects } from '@app/store/effects/notification.effects';
+
describe('LogsContainerComponent', () => {
let component: LogsContainerComponent;
let fixture: ComponentFixture<LogsContainerComponent>;
@@ -74,8 +81,11 @@ describe('LogsContainerComponent', () => {
serviceLogsHistogramData,
tabs,
hosts,
- serviceLogsTruncated
+ serviceLogsTruncated,
+ auth: auth.reducer
}),
+ EffectsModule.run(AuthEffects),
+ EffectsModule.run(NotificationEffects),
...TranslationModules,
TooltipModule.forRoot(),
],
@@ -99,9 +109,10 @@ describe('LogsContainerComponent', () => {
ClusterSelectionService,
RoutingUtilsService,
LogsFilteringUtilsService,
- LogsStateService,
NotificationsService,
- NotificationService
+ NotificationService,
+ LogsStateService,
+ AuthService
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
diff --git
a/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.ts
b/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.ts
index 6b983fc..34eb2a4 100644
---
a/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.ts
+++
b/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.ts
@@ -313,7 +313,7 @@ export class LogsContainerComponent implements OnInit,
OnDestroy {
filtersParams,
tab.appState.activeLogsType
);
- // we dont't have to reset the form with the new values when there
is tab changes
+ // we don't have to reset the form with the new values when there is
tab changes
// because the onActiveTabIdChange will call the setActiveTabById on
LogsContainerService
// which will reset the form to the tab's activeFilters prop.
// If we do reset wvery time then the form will be reseted twice
with every tab changes... not a big deal anyway
diff --git
a/ambari-logsearch-web/src/app/components/main-container/main-container.component.ts
b/ambari-logsearch-web/src/app/components/main-container/main-container.component.ts
index cd0f1be..adec4a7 100644
---
a/ambari-logsearch-web/src/app/components/main-container/main-container.component.ts
+++
b/ambari-logsearch-web/src/app/components/main-container/main-container.component.ts
@@ -16,35 +16,10 @@
* limitations under the License.
*/
-import {Component, OnDestroy, OnInit} from '@angular/core';
-import {AppStateService} from '@app/services/storage/app-state.service';
-import {Subscription} from 'rxjs/Subscription';
+import { Component } from '@angular/core';
@Component({
selector: 'main-container',
templateUrl: './main-container.component.html'
})
-export class MainContainerComponent implements OnInit, OnDestroy{
-
- private subscriptions: Subscription[] = [];
-
- constructor(private appState: AppStateService) {}
-
- ngOnInit() {
- this.subscriptions.push(
- this.appState.getParameter('isAuthorized').subscribe((value: boolean) =>
this.isAuthorized = value)
- );
- this.subscriptions.push(
- this.appState.getParameter('isInitialLoading').subscribe((value:
boolean) => this.isInitialLoading = value)
- );
- }
-
- ngOnDestroy() {
- this.subscriptions.forEach((subscription: Subscription) =>
subscription.unsubscribe());
- }
-
- isAuthorized: boolean = false;
-
- isInitialLoading: boolean = false;
-
-}
+export class MainContainerComponent {}
diff --git
a/ambari-logsearch-web/src/app/components/service-logs-table/service-logs-table.component.spec.ts
b/ambari-logsearch-web/src/app/components/service-logs-table/service-logs-table.component.spec.ts
index 7745781..5bf2969 100644
---
a/ambari-logsearch-web/src/app/components/service-logs-table/service-logs-table.component.spec.ts
+++
b/ambari-logsearch-web/src/app/components/service-logs-table/service-logs-table.component.spec.ts
@@ -56,6 +56,11 @@ import {RouterTestingModule} from '@angular/router/testing';
import {NotificationsService} from
'angular2-notifications/src/notifications.service';
import {NotificationService} from
'@modules/shared/services/notification.service';
+import * as auth from '@app/store/reducers/auth.reducers';
+import { EffectsModule } from '@ngrx/effects';
+import { AuthEffects } from '@app/store/effects/auth.effects';
+import { NotificationEffects } from '@app/store/effects/notification.effects';
+
describe('ServiceLogsTableComponent', () => {
let component: ServiceLogsTableComponent;
let fixture: ComponentFixture<ServiceLogsTableComponent>;
@@ -88,8 +93,11 @@ describe('ServiceLogsTableComponent', () => {
tabs,
clusters,
components,
- hosts
+ hosts,
+ auth: auth.reducer
}),
+ EffectsModule.run(AuthEffects),
+ EffectsModule.run(NotificationEffects),
TooltipModule.forRoot()
],
providers: [
diff --git
a/ambari-logsearch-web/src/app/components/time-range-picker/time-range-picker.component.spec.ts
b/ambari-logsearch-web/src/app/components/time-range-picker/time-range-picker.component.spec.ts
index f076861..d568354 100644
---
a/ambari-logsearch-web/src/app/components/time-range-picker/time-range-picker.component.spec.ts
+++
b/ambari-logsearch-web/src/app/components/time-range-picker/time-range-picker.component.spec.ts
@@ -35,7 +35,7 @@ import {
} from '@app/services/storage/service-logs-histogram-data.service';
import {ServiceLogsTruncatedService, serviceLogsTruncated} from
'@app/services/storage/service-logs-truncated.service';
import {TabsService, tabs} from '@app/services/storage/tabs.service';
-import {HttpClientService} from '@app/services/http-client.service';
+// import {HttpClientService} from '@app/services/http-client.service';
import {LogsContainerService} from '@app/services/logs-container.service';
import {UtilsService} from '@app/services/utils.service';
@@ -48,6 +48,12 @@ import {LogsFilteringUtilsService} from
'@app/services/logs-filtering-utils.serv
import {NotificationService} from
'@modules/shared/services/notification.service';
import {NotificationsService} from
'angular2-notifications/src/notifications.service';
+import * as auth from '@app/store/reducers/auth.reducers';
+import { AuthService } from '@app/services/auth.service';
+import { EffectsModule } from '@ngrx/effects';
+import { AuthEffects } from '@app/store/effects/auth.effects';
+import { NotificationEffects } from '@app/store/effects/notification.effects';
+
describe('TimeRangePickerComponent', () => {
let component: TimeRangePickerComponent;
let fixture: ComponentFixture<TimeRangePickerComponent>;
@@ -70,8 +76,11 @@ describe('TimeRangePickerComponent', () => {
serviceLogsFields,
serviceLogsHistogramData,
serviceLogsTruncated,
- tabs
+ tabs,
+ auth: auth.reducer
}),
+ EffectsModule.run(AuthEffects),
+ EffectsModule.run(NotificationEffects),
...TranslationModules
],
providers: [
@@ -96,7 +105,8 @@ describe('TimeRangePickerComponent', () => {
LogsFilteringUtilsService,
LogsStateService,
NotificationsService,
- NotificationService
+ NotificationService,
+ AuthService
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
diff --git
a/ambari-logsearch-web/src/app/components/timezone-picker/timezone-picker.component.spec.ts
b/ambari-logsearch-web/src/app/components/timezone-picker/timezone-picker.component.spec.ts
index 736c7ef..2de7d33 100644
---
a/ambari-logsearch-web/src/app/components/timezone-picker/timezone-picker.component.spec.ts
+++
b/ambari-logsearch-web/src/app/components/timezone-picker/timezone-picker.component.spec.ts
@@ -54,6 +54,11 @@ import {NotificationService} from
'@modules/shared/services/notification.service
import { dataAvailabilityStates, DataAvailabilityStatesStore } from
'@app/modules/app-load/stores/data-availability-state.store';
+import * as auth from '@app/store/reducers/auth.reducers';
+import { EffectsModule } from '@ngrx/effects';
+import { AuthEffects } from '@app/store/effects/auth.effects';
+import { NotificationEffects } from '@app/store/effects/notification.effects';
+
describe('TimeZonePickerComponent', () => {
let component: TimeZonePickerComponent;
let fixture: ComponentFixture<TimeZonePickerComponent>;
@@ -81,8 +86,11 @@ describe('TimeZonePickerComponent', () => {
serviceLogsHistogramData,
serviceLogsTruncated,
tabs,
- dataAvailabilityStates
+ dataAvailabilityStates,
+ auth: auth.reducer
}),
+ EffectsModule.run(AuthEffects),
+ EffectsModule.run(NotificationEffects),
...TranslationModules
],
providers: [
diff --git
a/ambari-logsearch-web/src/app/components/top-menu/top-menu.component.spec.ts
b/ambari-logsearch-web/src/app/components/top-menu/top-menu.component.spec.ts
index 3ae2f97..9f263a4 100644
---
a/ambari-logsearch-web/src/app/components/top-menu/top-menu.component.spec.ts
+++
b/ambari-logsearch-web/src/app/components/top-menu/top-menu.component.spec.ts
@@ -50,6 +50,11 @@ import {LogsFilteringUtilsService} from
'@app/services/logs-filtering-utils.serv
import {NotificationService} from
'@modules/shared/services/notification.service';
import {NotificationsService} from
'angular2-notifications/src/notifications.service';
+import * as auth from '@app/store/reducers/auth.reducers';
+import { EffectsModule } from '@ngrx/effects';
+import { AuthEffects } from '@app/store/effects/auth.effects';
+import { NotificationEffects } from '@app/store/effects/notification.effects';
+
describe('TopMenuComponent', () => {
let component: TopMenuComponent;
let fixture: ComponentFixture<TopMenuComponent>;
@@ -72,8 +77,11 @@ describe('TopMenuComponent', () => {
tabs,
clusters,
components,
- hosts
+ hosts,
+ auth: auth.reducer
}),
+ EffectsModule.run(AuthEffects),
+ EffectsModule.run(NotificationEffects),
...TranslationModules
],
declarations: [TopMenuComponent],
diff --git
a/ambari-logsearch-web/src/app/components/top-menu/top-menu.component.ts
b/ambari-logsearch-web/src/app/components/top-menu/top-menu.component.ts
index a87ca2f..76af57b 100644
--- a/ambari-logsearch-web/src/app/components/top-menu/top-menu.component.ts
+++ b/ambari-logsearch-web/src/app/components/top-menu/top-menu.component.ts
@@ -21,10 +21,13 @@ import {FormGroup} from '@angular/forms';
import {FilterCondition, TimeUnitListItem} from '@app/classes/filtering';
import {ListItem} from '@app/classes/list-item';
import {HomogeneousObject} from '@app/classes/object';
-import {AuthService} from '@app/services/auth.service';
import {LogsContainerService} from '@app/services/logs-container.service';
import {Router} from '@angular/router';
+import { Store } from '@ngrx/store';
+import { AppStore } from '@app/classes/models/store';
+import { LogOutAction } from '@app/store/actions/auth.actions';
+
@Component({
selector: 'top-menu',
templateUrl: './top-menu.component.html',
@@ -32,7 +35,11 @@ import {Router} from '@angular/router';
})
export class TopMenuComponent {
- constructor(private authService: AuthService, private logsContainer:
LogsContainerService, private router: Router) {}
+ constructor(
+ private logsContainer: LogsContainerService,
+ private router: Router,
+ private store: Store<AppStore>
+ ) {}
get filtersForm(): FormGroup {
return this.logsContainer.filtersForm;
@@ -45,10 +52,10 @@ export class TopMenuComponent {
openSettings = (): void => {};
/**
- * Request a logout action from AuthService
+ * Dispatch the LogOutAction.
*/
logout = (): void => {
- this.authService.logout();
+ this.store.dispatch(new LogOutAction());
}
navigateToShipperConfig = (): void => {
diff --git a/ambari-logsearch-web/src/app/modules/app-load/app-load.module.ts
b/ambari-logsearch-web/src/app/modules/app-load/app-load.module.ts
index ade23da..2f93cb3 100644
--- a/ambari-logsearch-web/src/app/modules/app-load/app-load.module.ts
+++ b/ambari-logsearch-web/src/app/modules/app-load/app-load.module.ts
@@ -22,13 +22,26 @@ import { HttpClientModule } from '@angular/common/http';
import { AppLoadService } from './services/app-load.service';
import { DataAvailabilityStatesStore } from
'@app/modules/app-load/stores/data-availability-state.store';
-export function check_if_authorized(appLoadService: AppLoadService) {
- return () => appLoadService.syncAuthorizedStateWithBackend();
-}
+import { Store } from '@ngrx/store';
+import { AppStore } from '@app/classes/models/store';
+import { CheckAuthorizationStatusAction } from
'@app/store/actions/auth.actions';
+import { authStatusSelector } from '@app/store/selectors/auth.selectors';
+import { AuthorizationStatuses } from '@app/store/reducers/auth.reducers';
+
export function set_translation_service(appLoadService: AppLoadService) {
return () => appLoadService.setTranslationService();
}
+export function check_auth_status(store: Store<AppStore>) {
+ return () => new Promise((resolve) => {
+ store.select(authStatusSelector)
+ .filter(
+ (status: AuthorizationStatuses): boolean => (status !== null &&
AuthorizationStatuses.CHEKCING_AUTHORIZATION_STATUS !== status)
+ ).first().subscribe(resolve);
+ store.dispatch(new CheckAuthorizationStatusAction());
+ });
+}
+
@NgModule({
imports: [
HttpClientModule
@@ -36,8 +49,8 @@ export function set_translation_service(appLoadService:
AppLoadService) {
providers: [
AppLoadService,
DataAvailabilityStatesStore,
-{ provide: APP_INITIALIZER, useFactory: set_translation_service, deps:
[AppLoadService], multi: true },
- { provide: APP_INITIALIZER, useFactory: check_if_authorized, deps:
[AppLoadService], multi: true }
+ { provide: APP_INITIALIZER, useFactory: set_translation_service, deps:
[AppLoadService], multi: true },
+ { provide: APP_INITIALIZER, useFactory: check_auth_status, deps: [Store],
multi: true }
]
})
export class AppLoadModule { }
diff --git
a/ambari-logsearch-web/src/app/modules/app-load/services/app-load.service.ts
b/ambari-logsearch-web/src/app/modules/app-load/services/app-load.service.ts
index 9405c9b..cc107af 100644
--- a/ambari-logsearch-web/src/app/modules/app-load/services/app-load.service.ts
+++ b/ambari-logsearch-web/src/app/modules/app-load/services/app-load.service.ts
@@ -17,24 +17,28 @@
*/
import { Injectable } from '@angular/core';
-import {Response} from '@angular/http';
+import { Response } from '@angular/http';
import 'rxjs/add/operator/toPromise';
-import {TranslateService} from '@ngx-translate/core';
+import { TranslateService } from '@ngx-translate/core';
-import {AppStateService} from 'app/services/storage/app-state.service';
-import {HttpClientService} from 'app/services/http-client.service';
-import {ClustersService} from 'app/services/storage/clusters.service';
-import {ServiceLogsFieldsService} from
'app/services/storage/service-logs-fields.service';
-import {AuditLogsFieldsService} from
'app/services/storage/audit-logs-fields.service';
-import {AuditFieldsDefinitionSet, LogField} from 'app/classes/object';
-import {Observable} from 'rxjs/Observable';
-import {HostsService} from 'app/services/storage/hosts.service';
-import {NodeItem} from 'app/classes/models/node-item';
-import {ComponentsService} from 'app/services/storage/components.service';
-import {DataAvailabilityValues} from 'app/classes/string';
+import { AppStateService } from 'app/services/storage/app-state.service';
+import { HttpClientService } from 'app/services/http-client.service';
+import { ClustersService } from 'app/services/storage/clusters.service';
+import { ServiceLogsFieldsService } from
'app/services/storage/service-logs-fields.service';
+import { AuditLogsFieldsService } from
'app/services/storage/audit-logs-fields.service';
+import { AuditFieldsDefinitionSet, LogField } from 'app/classes/object';
+import { Observable } from 'rxjs/Observable';
+import { HostsService } from 'app/services/storage/hosts.service';
+import { NodeItem } from 'app/classes/models/node-item';
+import { ComponentsService } from 'app/services/storage/components.service';
+import { DataAvailabilityValues } from 'app/classes/string';
import { DataAvaibilityStatesModel } from
'@app/modules/app-load/models/data-availability-state.model';
import { DataAvailabilityStatesStore } from
'@app/modules/app-load/stores/data-availability-state.store';
+import { Store } from '@ngrx/store';
+import { AppStore } from '@app/classes/models/store';
+import { isAuthorizedSelector } from '@app/store/selectors/auth.selectors';
+
// @ToDo create a separate data state enrty in the store with keys of the
model names
export enum DataStateStoreKeys {
CLUSTERS_DATA_KEY = 'clustersDataState',
@@ -64,9 +68,10 @@ export class AppLoadService {
private translationService: TranslateService,
private hostStoreService: HostsService,
private componentsStorageService: ComponentsService,
- private dataAvaibilityStateStore: DataAvailabilityStatesStore
+ private dataAvaibilityStateStore: DataAvailabilityStatesStore,
+ private store: Store<AppStore>
) {
-
this.appStateService.getParameter('isAuthorized').subscribe(this.initOnAuthorization);
+
this.store.select(isAuthorizedSelector).subscribe(this.initOnAuthorization);
this.appStateService.setParameter('isInitialLoading', true);
Observable.combineLatest(
@@ -83,11 +88,9 @@ export class AppLoadService {
let nextDataState: DataAvailabilityValues =
DataAvailabilityValues.NOT_AVAILABLE;
if (values.indexOf(DataAvailabilityValues.ERROR) > -1) {
nextDataState = DataAvailabilityValues.ERROR;
- }
- if (values.indexOf(DataAvailabilityValues.LOADING) > -1) {
+ } else if (values.indexOf(DataAvailabilityValues.LOADING) > -1) {
nextDataState = DataAvailabilityValues.LOADING;
- }
- if ( values.filter((value: DataAvailabilityValues) => value !==
DataAvailabilityValues.AVAILABLE).length === 0 ) {
+ } else if ( values.filter((value: DataAvailabilityValues) => value !==
DataAvailabilityValues.AVAILABLE).length === 0 ) {
nextDataState = DataAvailabilityValues.AVAILABLE;
}
return nextDataState;
diff --git
a/ambari-logsearch-web/src/app/modules/shared/components/dropdown-button/dropdown-button.component.ts
b/ambari-logsearch-web/src/app/modules/shared/components/dropdown-button/dropdown-button.component.ts
index 534b69d..ab519d0 100644
---
a/ambari-logsearch-web/src/app/modules/shared/components/dropdown-button/dropdown-button.component.ts
+++
b/ambari-logsearch-web/src/app/modules/shared/components/dropdown-button/dropdown-button.component.ts
@@ -16,9 +16,9 @@
* limitations under the License.
*/
-import {Component, Input, Output, EventEmitter} from '@angular/core';
-import {ListItem} from '@app/classes/list-item';
-import {UtilsService} from '@app/services/utils.service';
+import { Component, Input, Output, EventEmitter } from '@angular/core';
+import { ListItem } from '@app/classes/list-item';
+import { UtilsService } from '@app/services/utils.service';
@Component({
selector: 'dropdown-button',
@@ -87,7 +87,9 @@ export class DropdownButtonComponent {
return this.showSelectedValue && !this.isMultipleChoice &&
this.selection.length > 0;
}
- constructor(protected utils: UtilsService) {}
+ constructor(
+ protected utils: UtilsService
+ ) {}
updateSelection(updates: ListItem | ListItem[]): void {
if (updates && (!Array.isArray(updates) || updates.length)) {
diff --git
a/ambari-logsearch-web/src/app/modules/shared/components/dropdown-list/dropdown-list.component.html
b/ambari-logsearch-web/src/app/modules/shared/components/dropdown-list/dropdown-list.component.html
index a15b1c3..fac626f 100644
---
a/ambari-logsearch-web/src/app/modules/shared/components/dropdown-list/dropdown-list.component.html
+++
b/ambari-logsearch-web/src/app/modules/shared/components/dropdown-list/dropdown-list.component.html
@@ -18,15 +18,15 @@
<li [class.divider]="item.isDivider" [class.filtered]="isFiltered(item)"
[attr.role]="item.isDivider ? 'separator' : null"
[class]="(item.cssClass || '')">
<ng-container *ngIf="!item.isDivider">
- <label class="list-item-label" *ngIf="isMultipleChoice">
- <input type="checkbox" [attr.id]="item.id || item.value"
[(ngModel)]="item.isChecked"
+ <span class="list-item-label" *ngIf="isMultipleChoice">
+ <input type="checkbox" [attr.id]="(instanceId) + '-' + (item.id ||
item.value)" [(ngModel)]="item.isChecked"
(change)="changeSelectedItem({value: item.value, isChecked:
$event.currentTarget.checked}, $event)">
- <label [attr.for]="item.id || item.value" class="label-container">
+ <label [attr.for]="(instanceId) + '-' + (item.id || item.value)"
class="label-container">
<span *ngIf="item.iconClass" [ngClass]="item.iconClass"></span>
<span class="item-label-text">{{item.label | translate}}</span>
<span #additionalComponent></span>
</label>
- </label>
+ </span>
<span class="list-item-label label-container" *ngIf="!isMultipleChoice"
(click)="changeSelectedItem(item, $event)">
<span *ngIf="item.iconClass" [ngClass]="item.iconClass"></span>
<span class="item-label-text">{{item.label | translate}}</span>
diff --git
a/ambari-logsearch-web/src/app/modules/shared/components/dropdown-list/dropdown-list.component.spec.ts
b/ambari-logsearch-web/src/app/modules/shared/components/dropdown-list/dropdown-list.component.spec.ts
index 8b3b13b..6f74de0 100644
---
a/ambari-logsearch-web/src/app/modules/shared/components/dropdown-list/dropdown-list.component.spec.ts
+++
b/ambari-logsearch-web/src/app/modules/shared/components/dropdown-list/dropdown-list.component.spec.ts
@@ -49,6 +49,11 @@ import {LogsFilteringUtilsService} from
'@app/services/logs-filtering-utils.serv
import {NotificationService} from
'@modules/shared/services/notification.service';
import {NotificationsService} from
'angular2-notifications/src/notifications.service';
+import * as auth from '@app/store/reducers/auth.reducers';
+import { EffectsModule } from '@ngrx/effects';
+import { AuthEffects } from '@app/store/effects/auth.effects';
+import { NotificationEffects } from '@app/store/effects/notification.effects';
+
describe('DropdownListComponent', () => {
let component: DropdownListComponent;
let fixture: ComponentFixture<DropdownListComponent>;
@@ -80,8 +85,11 @@ describe('DropdownListComponent', () => {
clusters,
components,
serviceLogsTruncated,
- tabs
+ tabs,
+ auth: auth.reducer
}),
+ EffectsModule.run(AuthEffects),
+ EffectsModule.run(NotificationEffects),
FormsModule
],
providers: [
diff --git
a/ambari-logsearch-web/src/app/modules/shared/components/dropdown-list/dropdown-list.component.ts
b/ambari-logsearch-web/src/app/modules/shared/components/dropdown-list/dropdown-list.component.ts
index 651578a..1809637 100644
---
a/ambari-logsearch-web/src/app/modules/shared/components/dropdown-list/dropdown-list.component.ts
+++
b/ambari-logsearch-web/src/app/modules/shared/components/dropdown-list/dropdown-list.component.ts
@@ -79,10 +79,14 @@ export class DropdownListComponent implements OnInit,
OnChanges, AfterViewChecke
private subscriptions: Subscription[] = [];
+ instanceId: string;
+
constructor(
private componentGenerator: ComponentGeneratorService,
private changeDetector: ChangeDetectorRef
- ) {}
+ ) {
+ this.instanceId = `dropdown-list-${Date.now()}`;
+ }
ngOnInit() {
this.separateSelections();
diff --git
a/ambari-logsearch-web/src/app/modules/shared/components/filter-dropdown/filter-dropdown.component.spec.ts
b/ambari-logsearch-web/src/app/modules/shared/components/filter-dropdown/filter-dropdown.component.spec.ts
index 1b081c8..c1bde14 100644
---
a/ambari-logsearch-web/src/app/modules/shared/components/filter-dropdown/filter-dropdown.component.spec.ts
+++
b/ambari-logsearch-web/src/app/modules/shared/components/filter-dropdown/filter-dropdown.component.spec.ts
@@ -47,6 +47,11 @@ import {RouterTestingModule} from '@angular/router/testing';
import {NotificationService} from
'@modules/shared/services/notification.service';
import {NotificationsService} from
'angular2-notifications/src/notifications.service';
+import * as auth from '@app/store/reducers/auth.reducers';
+import { EffectsModule } from '@ngrx/effects';
+import { AuthEffects } from '@app/store/effects/auth.effects';
+import { NotificationEffects } from '@app/store/effects/notification.effects';
+
describe('FilterDropdownComponent', () => {
let component: FilterDropdownComponent;
let fixture: ComponentFixture<FilterDropdownComponent>;
@@ -93,8 +98,11 @@ describe('FilterDropdownComponent', () => {
tabs,
clusters,
components,
- hosts
+ hosts,
+ auth: auth.reducer
}),
+ EffectsModule.run(AuthEffects),
+ EffectsModule.run(NotificationEffects),
...TranslationModules
],
providers: [
diff --git
a/ambari-logsearch-web/src/app/modules/shared/components/modal-dialog/modal-dialog.component.spec.ts
b/ambari-logsearch-web/src/app/modules/shared/components/modal-dialog/modal-dialog.component.spec.ts
index 19cd74d..f13872c 100644
---
a/ambari-logsearch-web/src/app/modules/shared/components/modal-dialog/modal-dialog.component.spec.ts
+++
b/ambari-logsearch-web/src/app/modules/shared/components/modal-dialog/modal-dialog.component.spec.ts
@@ -16,12 +16,21 @@
*/
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
-
+import {StoreModule} from '@ngrx/store';
+import {AppStateService, appState} from
'@app/services/storage/app-state.service';
import {
getCommonTestingBedConfiguration, MockHttpRequestModules,
TranslationModules
} from '@app/test-config.spec';
+import {NotificationsService} from
'angular2-notifications/src/notifications.service';
+import {NotificationService} from
'@modules/shared/services/notification.service';
+
+import * as auth from '@app/store/reducers/auth.reducers';
+import { EffectsModule } from '@ngrx/effects';
+import { AuthEffects } from '@app/store/effects/auth.effects';
+import { NotificationEffects } from '@app/store/effects/notification.effects';
+
import { ModalDialogComponent } from './modal-dialog.component';
describe('ModalDialogComponent', () => {
@@ -31,8 +40,15 @@ describe('ModalDialogComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule(getCommonTestingBedConfiguration({
imports: [
- ...TranslationModules
+ ...TranslationModules,
+ StoreModule.provideStore({
+ appState,
+ auth: auth.reducer
+ }),
+ EffectsModule.run(AuthEffects),
+ EffectsModule.run(NotificationEffects)
],
+ providers: [AppStateService, NotificationsService, NotificationService],
declarations: [ ModalDialogComponent ]
}))
.compileComponents();
diff --git
a/ambari-logsearch-web/src/app/modules/shared/interfaces/notification.interface.ts
b/ambari-logsearch-web/src/app/modules/shared/interfaces/notification.interface.ts
index b8b3d6a..096214c 100644
---
a/ambari-logsearch-web/src/app/modules/shared/interfaces/notification.interface.ts
+++
b/ambari-logsearch-web/src/app/modules/shared/interfaces/notification.interface.ts
@@ -15,10 +15,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import {Options} from 'angular2-notifications/src/options.type';
+import { Options } from 'angular2-notifications/src/options.type';
+import { NotificationType } from
'@modules/shared/services/notification.service';
export interface NotificationInterface extends Options {
- type: string;
+ type: NotificationType | string;
message: string;
- title: string;
+ title?: string;
}
diff --git
a/ambari-logsearch-web/src/app/modules/shared/services/notification.service.ts
b/ambari-logsearch-web/src/app/modules/shared/services/notification.service.ts
index df6ca2a..ef340bd 100644
---
a/ambari-logsearch-web/src/app/modules/shared/services/notification.service.ts
+++
b/ambari-logsearch-web/src/app/modules/shared/services/notification.service.ts
@@ -70,7 +70,7 @@ export class NotificationService {
}
const icon = notificationIcons[method] || notificationIcons['info'];
const htmlMsg = messageTemplate
- .replace(/{{title}}/gi, this.translateService.instant(title))
+ .replace(/{{title}}/gi, title ? 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-web/src/app/modules/shared/variables.less
b/ambari-logsearch-web/src/app/modules/shared/variables.less
index 7ffd20c..b917527 100644
--- a/ambari-logsearch-web/src/app/modules/shared/variables.less
+++ b/ambari-logsearch-web/src/app/modules/shared/variables.less
@@ -19,6 +19,7 @@
// Variables
@blue: #1491C1;
@grey: #DDD;
+@white: rgba(255, 255, 255, 1);
@fluid-gray-1: #ccc;
@fluid-gray-2: #999;
@@ -39,7 +40,7 @@
@grey-color: #DDD;
@default-line-height: 1.42857143;
@main-background-color: #ECECEC;
-@filters-panel-background-color: #FFF;
+@filters-panel-background-color: @white;
@filters-panel-padding: 10px 0;
@list-header-background-color: #F2F2F2;
@checkbox-top: 4px;
diff --git a/ambari-logsearch-web/src/app/services/auth-guard.service.ts
b/ambari-logsearch-web/src/app/services/auth-guard.service.ts
index 8b56239..f8f8e5c 100644
--- a/ambari-logsearch-web/src/app/services/auth-guard.service.ts
+++ b/ambari-logsearch-web/src/app/services/auth-guard.service.ts
@@ -22,16 +22,24 @@ import {Observable} from 'rxjs/Observable';
import {AuthService} from '@app/services/auth.service';
+import { Store } from '@ngrx/store';
+import { AppStore } from '@app/classes/models/store';
+import { isAuthorizedSelector } from '@app/store/selectors/auth.selectors';
+
/**
* This guard goal is to prevent to display screens where authorization needs.
*/
@Injectable()
export class AuthGuardService implements CanActivate {
- constructor(private authService: AuthService, private router: Router) {}
+ constructor(
+ private authService: AuthService,
+ private router: Router,
+ private store: Store<AppStore>
+ ) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot):
Observable<boolean> {
- return this.authService.isAuthorized().map((isAuthorized: boolean) => {
+ return this.store.select(isAuthorizedSelector).map((isAuthorized: boolean)
=> {
this.authService.redirectUrl = state.url;
if (!isAuthorized) {
this.router.navigate(['/login']);
diff --git a/ambari-logsearch-web/src/app/services/auth.service.spec.ts
b/ambari-logsearch-web/src/app/services/auth.service.spec.ts
index 74a7125..0d30281 100644
--- a/ambari-logsearch-web/src/app/services/auth.service.spec.ts
+++ b/ambari-logsearch-web/src/app/services/auth.service.spec.ts
@@ -23,6 +23,8 @@ import 'rxjs/add/operator/first';
import 'rxjs/add/operator/last';
import 'rxjs/add/operator/take';
import {StoreModule} from '@ngrx/store';
+import {Store} from '@ngrx/store';
+import {AppStore} from '@app/classes/models/store';
import {AppStateService, appState} from
'@app/services/storage/app-state.service';
import {AuthService} from '@app/services/auth.service';
import {HttpClientService} from '@app/services/http-client.service';
@@ -30,6 +32,11 @@ import {RouterTestingModule} from '@angular/router/testing';
import {Routes} from '@angular/router';
import {Component} from '@angular/core';
+
+import * as auth from '@app/store/reducers/auth.reducers';
+import { isAuthorizedSelector } from '@app/store/selectors/auth.selectors';
+import { LogInAction, LogOutAction, AuthorizedAction } from
'@app/store/actions/auth.actions';
+
describe('AuthService', () => {
const successResponse = {
@@ -41,8 +48,8 @@ describe('AuthService', () => {
bytesLoaded: 100,
totalBytes: 100,
headers: null
- },
- errorResponse = {
+ };
+ const errorResponse = {
type: 'error',
ok: false,
url: '/',
@@ -54,11 +61,20 @@ describe('AuthService', () => {
};
// Note: We add delay to help the isLoginInProgress test case.
- let httpServiceStub = {
+ const httpServiceStub = {
isError: false,
- postFormData: function () {
+ request: function () {
const isError = this.isError;
return Observable.create(observer => observer.next(isError ?
errorResponse : successResponse)).delay(1);
+ },
+ postFormData: function () {
+ return this.request();
+ },
+ post: function () {
+ return this.request();
+ },
+ get: function () {
+ return this.request();
}
};
@@ -74,7 +90,8 @@ describe('AuthService', () => {
imports: [
HttpModule,
StoreModule.provideStore({
- appState
+ appState,
+ auth: auth.reducer
}),
RouterTestingModule.withRoutes(testRoutes)
],
@@ -90,54 +107,27 @@ describe('AuthService', () => {
expect(service).toBeTruthy();
}));
- it('should set the isAuthorized state to true in appState when the login is
success', async(inject(
- [AuthService, AppStateService, HttpClientService],
- (authService: AuthService, appStateService: AppStateService,
httpClientService) => {
- httpClientService.isError = false;
- authService.login('test', 'test')
- .subscribe(() => {
- appStateService.getParameter('isAuthorized').subscribe((value:
Boolean): void => {
- expect(value).toBe(true);
- });
- }, value => {
- throw value;
- });
- }
- )));
-
-
- it('should set the isAuthorized state to false in appState when the login is
failed', async(inject(
- [AuthService, AppStateService, HttpClientService],
- (authService: AuthService, appStateService: AppStateService,
httpClientService) => {
- httpClientService.isError = true;
- authService.login('test', 'test')
- .subscribe(() => {
- appStateService.getParameter('isAuthorized').subscribe((value:
Boolean): void => {
- expect(value).toBe(false);
- });
- });
+ it('should return with Observable<Response> when login called', async(inject(
+ [AuthService, Store],
+ (authService: AuthService) => {
+ const response = authService.login('test', 'test');
+ expect(response instanceof Observable).toBe(true);
}
)));
- it('should set the isLoginInProgress state to true when the login started',
async(inject(
- [AuthService, AppStateService, HttpClientService],
- (authService: AuthService, appStateService: AppStateService,
httpClientService) => {
- httpClientService.isError = false;
- authService.login('test', 'test');
-
appStateService.getParameter('isLoginInProgress').first().subscribe((value:
Boolean): void => {
- expect(value).toBe(true);
- });
+ it('should return with Observable<Response> when logout called',
async(inject(
+ [AuthService, Store],
+ (authService: AuthService) => {
+ const response = authService.logout();
+ expect(response instanceof Observable).toBe(true);
}
)));
- it('should set the isLoginInProgress state to true after the login is
success', async(inject(
- [AuthService, AppStateService, HttpClientService],
- (authService: AuthService, appStateService: AppStateService,
httpClientService) => {
- httpClientService.isError = false;
- authService.login('test', 'test');
-
appStateService.getParameter('isLoginInProgress').take(2).last().subscribe((value:
Boolean): void => {
- expect(value).toBe(false);
- });
+ it('should return with Observable<Response> when checkAuthorizationState
called', async(inject(
+ [AuthService, Store],
+ (authService: AuthService) => {
+ const response = authService.checkAuthorizationState();
+ expect(response instanceof Observable).toBe(true);
}
)));
diff --git a/ambari-logsearch-web/src/app/services/auth.service.ts
b/ambari-logsearch-web/src/app/services/auth.service.ts
index 1bf1875..c7c74ba 100644
--- a/ambari-logsearch-web/src/app/services/auth.service.ts
+++ b/ambari-logsearch-web/src/app/services/auth.service.ts
@@ -16,19 +16,14 @@
* limitations under the License.
*/
-import {Injectable} from '@angular/core';
-import {Response} from '@angular/http';
+import { Injectable } from '@angular/core';
+import { Response } from '@angular/http';
-import {Observable} from 'rxjs/Observable';
+import { Observable } from 'rxjs/Observable';
-import {HttpClientService} from '@app/services/http-client.service';
-import {AppStateService} from '@app/services/storage/app-state.service';
-import {Router} from '@angular/router';
-import {Subscription} from 'rxjs/Subscription';
-import { Observer } from 'rxjs/Observer';
-
-export const IS_AUTHORIZED_APP_STATE_KEY: string = 'isAuthorized';
-export const IS_LOGIN_IN_PROGRESS_APP_STATE_KEY: string = 'isLoginInProgress';
+import { HttpClientService } from '@app/services/http-client.service';
+import { AppStateService } from '@app/services/storage/app-state.service';
+import { Subscription } from 'rxjs/Subscription';
/**
* This service meant to be a single place where the authorization should
happen.
@@ -47,140 +42,32 @@ export class AuthService {
constructor(
private httpClient: HttpClientService,
- private appState: AppStateService,
- private router: Router
- ) {
-
this.subscriptions.push(this.appState.getParameter(IS_AUTHORIZED_APP_STATE_KEY).subscribe(
- this.onAppStateIsAuthorizedChanged
- ));
- }
+ private appState: AppStateService
+ ) {}
- onAppStateIsAuthorizedChanged = (isAuthorized): void => {
- if (isAuthorized) {
- const redirectTo = this.redirectUrl ||
(this.router.routerState.snapshot.url === '/login' ? '/' : null);
- if (redirectTo) {
- if (Array.isArray(redirectTo)) {
- this.router.navigate(redirectTo);
- } else {
- this.router.navigateByUrl(redirectTo);
- }
- }
- this.redirectUrl = '';
- } else {
- this.router.navigate(['/login']);
- }
- }
/**
* The single entry point to request a login action.
* @param {string} username
* @param {string} password
* @returns {Observable<Response>}
*/
- login(username: string, password: string): Observable<Boolean> {
- this.setLoginInProgressAppState(true);
- const response$ = this.httpClient.postFormData('login', {
+ login(username: string, password: string): Observable<Response> {
+ return this.httpClient.postFormData('login', {
username: username,
password: password
});
- response$.subscribe(
- (resp: Response) => this.onLoginResponse(resp),
- (resp: Response) => this.onLoginError(resp)
- );
- return response$.switchMap((resp: Response) => {
- return Observable.create((observer: Observer<boolean>) => {
- if (resp.ok) {
- observer.next(resp.ok);
- } else {
- observer.error(resp);
- }
- observer.complete();
- });
- });
}
/**
* The single unique entry point to request a logout action
- * @returns {Observable<boolean | Error>}
- */
- logout(): Observable<Boolean> {
- const response$ = this.httpClient.get('logout');
- response$.subscribe(
- (resp: Response) => this.onLogoutResponse(resp),
- (resp: Response) => this.onLogoutError(resp)
- );
- return response$.switchMap((resp: Response) => {
- return Observable.create((observer) => {
- if (resp.ok) {
- observer.next(resp.ok);
- } else {
- observer.error(resp);
- }
- observer.complete();
- });
- });
- }
-
- /**
- * Set the isLoginInProgress state in AppState. The reason behind create a
function for this is that we set this app
- * state from two different places so let's do always the same way.
- * @param {boolean} state the new value of the isLoginInProgress app state.
- */
- private setLoginInProgressAppState(state: boolean) {
- this.appState.setParameter(IS_LOGIN_IN_PROGRESS_APP_STATE_KEY, state);
- }
-
- /**
- * Set the isAuthorized state in AppState. The reason behind create a
function for this is that we set this app
- * state from two different places so let's do always the same way.
- * @param {boolean} state The new value of the isAuthorized app state.
- */
- private setAuthorizedAppState(state: boolean) {
- this.appState.setParameter(IS_AUTHORIZED_APP_STATE_KEY, state);
- }
-
- /**
- * Handling the login success response. The goal is to set the authorized
property of the appState.
- * @param resp
- */
- private onLoginResponse(resp: Response): void {
- this.setLoginInProgressAppState(false);
- if (resp && resp.ok) {
- this.setAuthorizedAppState(resp.ok);
- }
- }
-
- /**
- * Handling the login error response. The goal is to set the authorized
property correctly of the appState.
- * @ToDo decide if we should have a loginError app state.
- * @param {Reponse} resp
- */
- private onLoginError(resp: Response): void {
- this.setLoginInProgressAppState(false);
- this.setAuthorizedAppState(false);
- }
-
- /**
- * Handling the logout success response. The goal is to set the authorized
property of the appState.
- * @param {Response} resp
+ * @returns {Observable<Response>}
*/
- private onLogoutResponse(resp: Response): void {
- if (resp && resp.ok) {
- this.setAuthorizedAppState(false);
- }
+ logout(): Observable<Response> {
+ return this.httpClient.get('logout');
}
- /**
- * Handling the logout error response.
- * @ToDo decide if we should create a logoutError app state or not
- * @param {Response} resp
- */
- private onLogoutError(resp: Response): void {}
-
- /**
- * Simply return with the boolean value of the isAuthorized application
state key.
- */
- public isAuthorized(): Observable<boolean> {
- return this.appState.getParameter(IS_AUTHORIZED_APP_STATE_KEY);
+ checkAuthorizationState(): Observable<Response> {
+ return this.httpClient.get('status');
}
}
diff --git
a/ambari-logsearch-web/src/app/services/component-generator.service.spec.ts
b/ambari-logsearch-web/src/app/services/component-generator.service.spec.ts
index e5584e3..989994c 100644
--- a/ambari-logsearch-web/src/app/services/component-generator.service.spec.ts
+++ b/ambari-logsearch-web/src/app/services/component-generator.service.spec.ts
@@ -47,6 +47,12 @@ import {LogsFilteringUtilsService} from
'@app/services/logs-filtering-utils.serv
import {NotificationsService} from
'angular2-notifications/src/notifications.service';
import {NotificationService} from
'@modules/shared/services/notification.service';
+import { AuthService } from '@app/services/auth.service';
+import * as auth from '@app/store/reducers/auth.reducers';
+import { EffectsModule } from '@ngrx/effects';
+import { AuthEffects } from '@app/store/effects/auth.effects';
+import { NotificationEffects } from '@app/store/effects/notification.effects';
+
describe('ComponentGeneratorService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
@@ -65,8 +71,11 @@ describe('ComponentGeneratorService', () => {
clusters,
components,
serviceLogsTruncated,
- tabs
+ tabs,
+ auth: auth.reducer
}),
+ EffectsModule.run(AuthEffects),
+ EffectsModule.run(NotificationEffects),
...TranslationModules
],
providers: [
@@ -93,7 +102,8 @@ describe('ComponentGeneratorService', () => {
LogsFilteringUtilsService,
LogsStateService,
NotificationsService,
- NotificationService
+ NotificationService,
+ AuthService
]
});
});
diff --git
a/ambari-logsearch-web/src/app/services/history-manager.service.spec.ts
b/ambari-logsearch-web/src/app/services/history-manager.service.spec.ts
index ccfe611..70f05ad 100644
--- a/ambari-logsearch-web/src/app/services/history-manager.service.spec.ts
+++ b/ambari-logsearch-web/src/app/services/history-manager.service.spec.ts
@@ -47,6 +47,12 @@ import {LogsStateService} from
'@app/services/storage/logs-state.service';
import {NotificationsService} from
'angular2-notifications/src/notifications.service';
import {NotificationService} from
'@modules/shared/services/notification.service';
+import { AuthService } from '@app/services/auth.service';
+import * as auth from '@app/store/reducers/auth.reducers';
+import { EffectsModule } from '@ngrx/effects';
+import { AuthEffects } from '@app/store/effects/auth.effects';
+import { NotificationEffects } from '@app/store/effects/notification.effects';
+
describe('HistoryManagerService', () => {
beforeEach(() => {
@@ -67,8 +73,11 @@ describe('HistoryManagerService', () => {
components,
hosts,
serviceLogsTruncated,
- tabs
- })
+ tabs,
+ auth: auth.reducer
+ }),
+ EffectsModule.run(AuthEffects),
+ EffectsModule.run(NotificationEffects)
],
providers: [
...MockHttpRequestModules,
@@ -93,7 +102,8 @@ describe('HistoryManagerService', () => {
LogsFilteringUtilsService,
LogsStateService,
NotificationsService,
- NotificationService
+ NotificationService,
+ AuthService
]
});
});
diff --git a/ambari-logsearch-web/src/app/services/http-client.service.ts
b/ambari-logsearch-web/src/app/services/http-client.service.ts
index c65278b..2d68977 100644
--- a/ambari-logsearch-web/src/app/services/http-client.service.ts
+++ b/ambari-logsearch-web/src/app/services/http-client.service.ts
@@ -32,6 +32,11 @@ import {ServiceLogsHistogramQueryParams} from
'@app/classes/queries/service-logs
import {ServiceLogsTruncatedQueryParams} from
'@app/classes/queries/service-logs-truncated-query-params';
import {AppStateService} from '@app/services/storage/app-state.service';
+import { Store } from '@ngrx/store';
+import { AppStore } from '@app/classes/models/store';
+import { HttpAuthorizationErrorResponseAction } from
'@app/store/actions/auth.actions';
+import { isAuthorizedSelector } from '@app/store/selectors/auth.selectors';
+
@Injectable()
export class HttpClientService extends Http {
@@ -100,7 +105,12 @@ export class HttpClientService extends Http {
private readonly unauthorizedStatuses = [401, 403, 419];
- constructor(backend: XHRBackend, defaultOptions: RequestOptions, private
appState: AppStateService) {
+ constructor(
+ backend: XHRBackend,
+ defaultOptions: RequestOptions,
+ private appState: AppStateService,
+ private store: Store<AppStore>
+ ) {
super(backend, defaultOptions);
}
@@ -166,7 +176,11 @@ export class HttpClientService extends Http {
const handleResponseError = (error) => {
let handled = false;
if (this.unauthorizedStatuses.indexOf(error.status) > -1) {
- this.appState.setParameter('isAuthorized', false);
+
this.store.select(isAuthorizedSelector).first().subscribe((isAuthorized:
boolean) => {
+ if (isAuthorized) {
+ this.store.dispatch(new
HttpAuthorizationErrorResponseAction({response: error}));
+ }
+ });
handled = true;
}
return handled;
diff --git
a/ambari-logsearch-web/src/app/services/log-index-filter.service.spec.ts
b/ambari-logsearch-web/src/app/services/log-index-filter.service.spec.ts
index 924deee..eb4bf66 100644
--- a/ambari-logsearch-web/src/app/services/log-index-filter.service.spec.ts
+++ b/ambari-logsearch-web/src/app/services/log-index-filter.service.spec.ts
@@ -24,6 +24,9 @@ import {
import { AppStateService } from '@app/services/storage/app-state.service';
+import {NotificationService} from
'@modules/shared/services/notification.service';
+import {NotificationsService} from
'angular2-notifications/src/notifications.service';
+
import { LogIndexFilterService } from './log-index-filter.service';
describe('LogIndexFilterService', () => {
@@ -34,7 +37,9 @@ describe('LogIndexFilterService', () => {
],
providers: [
AppStateService,
- LogIndexFilterService
+ LogIndexFilterService,
+ NotificationService,
+ NotificationsService
]
}));
});
diff --git
a/ambari-logsearch-web/src/app/services/login-screen-guard.service.ts
b/ambari-logsearch-web/src/app/services/login-screen-guard.service.ts
index 8dbe1d7..99e886e 100644
--- a/ambari-logsearch-web/src/app/services/login-screen-guard.service.ts
+++ b/ambari-logsearch-web/src/app/services/login-screen-guard.service.ts
@@ -22,16 +22,23 @@ import {Observable} from 'rxjs/Observable';
import {AuthService} from '@app/services/auth.service';
+import { Store } from '@ngrx/store';
+import { AppStore } from '@app/classes/models/store';
+import { isAuthorizedSelector } from '@app/store/selectors/auth.selectors';
+
/**
* The goal of this guard service is to prevent to display the login screen
when the user is logged in.
*/
@Injectable()
export class LoginScreenGuardService implements CanActivate {
- constructor(private authService: AuthService, private router: Router) {}
+ constructor(
+ private router: Router,
+ private store: Store<AppStore>
+ ) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot):
Observable<boolean> {
- return this.authService.isAuthorized().map((isAuthorized: boolean) => {
+ return this.store.select(isAuthorizedSelector).map((isAuthorized: boolean)
=> {
if (isAuthorized && state.url === '/login') {
this.router.navigate(['/']);
}
diff --git
a/ambari-logsearch-web/src/app/services/logs-container.service.spec.ts
b/ambari-logsearch-web/src/app/services/logs-container.service.spec.ts
index 3e70644..ee3f5da 100644
--- a/ambari-logsearch-web/src/app/services/logs-container.service.spec.ts
+++ b/ambari-logsearch-web/src/app/services/logs-container.service.spec.ts
@@ -45,6 +45,12 @@ import {LogsStateService} from
'@app/services/storage/logs-state.service';
import {NotificationService} from
'@modules/shared/services/notification.service';
import {NotificationsService} from
'angular2-notifications/src/notifications.service';
+import { AuthService } from '@app/services/auth.service';
+import * as auth from '@app/store/reducers/auth.reducers';
+import { EffectsModule } from '@ngrx/effects';
+import { AuthEffects } from '@app/store/effects/auth.effects';
+import { NotificationEffects } from '@app/store/effects/notification.effects';
+
describe('LogsContainerService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
@@ -63,8 +69,11 @@ describe('LogsContainerService', () => {
components,
hosts,
serviceLogsTruncated,
- tabs
+ tabs,
+ auth: auth.reducer
}),
+ EffectsModule.run(AuthEffects),
+ EffectsModule.run(NotificationEffects),
...TranslationModules
],
providers: [
@@ -89,7 +98,8 @@ describe('LogsContainerService', () => {
LogsFilteringUtilsService,
LogsStateService,
NotificationsService,
- NotificationService
+ NotificationService,
+ AuthService
]
});
});
diff --git a/ambari-logsearch-web/src/app/services/logs-container.service.ts
b/ambari-logsearch-web/src/app/services/logs-container.service.ts
index 9fba951..d550fbb 100644
--- a/ambari-logsearch-web/src/app/services/logs-container.service.ts
+++ b/ambari-logsearch-web/src/app/services/logs-container.service.ts
@@ -16,11 +16,11 @@
* limitations under the License.
*/
-import {Injectable} from '@angular/core';
-import {FormGroup, FormControl} from '@angular/forms';
-import {Response} from '@angular/http';
-import {Subject} from 'rxjs/Subject';
-import {Observable} from 'rxjs/Observable';
+import { Injectable } from '@angular/core';
+import { FormGroup, FormControl } from '@angular/forms';
+import { Response } from '@angular/http';
+import { Subject } from 'rxjs/Subject';
+import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/timer';
import 'rxjs/add/observable/combineLatest';
import 'rxjs/add/operator/distinctUntilChanged';
@@ -28,42 +28,46 @@ import 'rxjs/add/operator/first';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/takeUntil';
import * as moment from 'moment-timezone';
-import {HttpClientService} from '@app/services/http-client.service';
-import {UtilsService} from '@app/services/utils.service';
-import {AuditLogsService} from '@app/services/storage/audit-logs.service';
-import {AuditLogsFieldsService, ResponseRootProperties} from
'@app/services/storage/audit-logs-fields.service';
-import {AuditLogsGraphDataService} from
'@app/services/storage/audit-logs-graph-data.service';
-import {ServiceLogsService} from '@app/services/storage/service-logs.service';
-import {ServiceLogsFieldsService} from
'@app/services/storage/service-logs-fields.service';
-import {ServiceLogsHistogramDataService} from
'@app/services/storage/service-logs-histogram-data.service';
-import {ServiceLogsTruncatedService} from
'@app/services/storage/service-logs-truncated.service';
-import {AppStateService} from '@app/services/storage/app-state.service';
-import {AppSettingsService} from '@app/services/storage/app-settings.service';
-import {TabsService} from '@app/services/storage/tabs.service';
-import {ClustersService} from '@app/services/storage/clusters.service';
-import {ComponentsService} from '@app/services/storage/components.service';
-import {HostsService} from '@app/services/storage/hosts.service';
-import {ActiveServiceLogEntry} from '@app/classes/active-service-log-entry';
+import { HttpClientService } from '@app/services/http-client.service';
+import { UtilsService } from '@app/services/utils.service';
+import { AuditLogsService } from '@app/services/storage/audit-logs.service';
+import { AuditLogsFieldsService, ResponseRootProperties } from
'@app/services/storage/audit-logs-fields.service';
+import { AuditLogsGraphDataService } from
'@app/services/storage/audit-logs-graph-data.service';
+import { ServiceLogsService } from
'@app/services/storage/service-logs.service';
+import { ServiceLogsFieldsService } from
'@app/services/storage/service-logs-fields.service';
+import { ServiceLogsHistogramDataService } from
'@app/services/storage/service-logs-histogram-data.service';
+import { ServiceLogsTruncatedService } from
'@app/services/storage/service-logs-truncated.service';
+import { AppStateService } from '@app/services/storage/app-state.service';
+import { AppSettingsService } from
'@app/services/storage/app-settings.service';
+import { TabsService } from '@app/services/storage/tabs.service';
+import { ClustersService } from '@app/services/storage/clusters.service';
+import { ComponentsService } from '@app/services/storage/components.service';
+import { HostsService } from '@app/services/storage/hosts.service';
+import { ActiveServiceLogEntry } from '@app/classes/active-service-log-entry';
import {
FilterCondition, TimeUnitListItem, SearchBoxParameter,
SearchBoxParameterTriggered
} from '@app/classes/filtering';
-import {ListItem} from '@app/classes/list-item';
-import {HomogeneousObject, LogLevelObject} from '@app/classes/object';
-import {DataAvailability, DataAvailabilityValues, LogsType, ScrollType} from
'@app/classes/string';
-import {LogTypeTab} from '@app/classes/models/log-type-tab';
-import {AuditFieldsDefinitionSet} from '@app/classes/object';
-import {AuditLog} from '@app/classes/models/audit-log';
-import {ServiceLog} from '@app/classes/models/service-log';
-import {BarGraph} from '@app/classes/models/bar-graph';
-import {NodeItem} from '@app/classes/models/node-item';
-import {CommonEntry} from '@app/classes/models/common-entry';
-import {ClusterSelectionService} from
'@app/services/storage/cluster-selection.service';
-import {ActivatedRoute, Router} from '@angular/router';
-import {LogsFilteringUtilsService} from
'@app/services/logs-filtering-utils.service';
-import {BehaviorSubject} from 'rxjs/BehaviorSubject';
-import {LogsStateService} from '@app/services/storage/logs-state.service';
-import {LogLevelComponent} from
'@app/components/log-level/log-level.component';
-import {NotificationService, NotificationType} from
'@modules/shared/services/notification.service';
+import { ListItem } from '@app/classes/list-item';
+import { HomogeneousObject, LogLevelObject } from '@app/classes/object';
+import { DataAvailabilityValues, LogsType, ScrollType } from
'@app/classes/string';
+import { LogTypeTab } from '@app/classes/models/log-type-tab';
+import { AuditFieldsDefinitionSet } from '@app/classes/object';
+import { AuditLog } from '@app/classes/models/audit-log';
+import { ServiceLog } from '@app/classes/models/service-log';
+import { BarGraph } from '@app/classes/models/bar-graph';
+import { NodeItem } from '@app/classes/models/node-item';
+import { CommonEntry } from '@app/classes/models/common-entry';
+import { ClusterSelectionService } from
'@app/services/storage/cluster-selection.service';
+import { Router } from '@angular/router';
+import { LogsFilteringUtilsService } from
'@app/services/logs-filtering-utils.service';
+import { BehaviorSubject } from 'rxjs/BehaviorSubject';
+import { LogsStateService } from '@app/services/storage/logs-state.service';
+import { LogLevelComponent } from
'@app/components/log-level/log-level.component';
+import { NotificationService, NotificationType } from
'@modules/shared/services/notification.service';
+
+import { Store } from '@ngrx/store';
+import { AppStore } from '@app/classes/models/store';
+import { isAuthorizedSelector } from '@app/store/selectors/auth.selectors';
@Injectable()
export class LogsContainerService {
@@ -377,11 +381,11 @@ export class LogsContainerService {
private serviceLogsTruncatedStorage: ServiceLogsTruncatedService, private
appSettings: AppSettingsService,
private clusterSelectionStoreService: ClusterSelectionService,
private router: Router,
- private activatedRoute: ActivatedRoute,
private logsFilteringUtilsService: LogsFilteringUtilsService,
private logsStateService: LogsStateService,
private notificationService: NotificationService,
- private componentsService: ComponentsService
+ private componentsService: ComponentsService,
+ private store: Store<AppStore>
) {
const formItems = Object.keys(this.filters).reduce((currentObject: any,
key: string): HomogeneousObject<FormControl> => {
const formControl = new FormControl();
@@ -402,10 +406,18 @@ export class LogsContainerService {
appState.getParameter('activeLogsType').subscribe((value: LogsType) => {
if (this.isLogsTypeSupported(value)) {
this.activeLogsType = value;
- this.loadLogs(this.activeLogsType);
}
});
+ Observable.combineLatest(
+ this.store.select(isAuthorizedSelector),
+ this.appState.getParameter('baseDataSetState')
+ .map((dataSetState: DataAvailabilityValues) => dataSetState ===
DataAvailabilityValues.AVAILABLE),
+ appState.getParameter('activeLogsType')
+ ).filter(([isAuthorized, dataAvailable, activeLogsType]) => isAuthorized
&& dataAvailable)
+ .map(([isAuthorized, dataAvailable, activeLogsType]) => activeLogsType)
+ .subscribe(this.loadLogs);
+
appSettings.getParameter('timeZone').subscribe((value: string) =>
this.timeZone = value || this.defaultTimeZone);
tabsStorage.mapCollection((tab: LogTypeTab): LogTypeTab => {
return Object.assign({}, tab, {
@@ -445,7 +457,7 @@ export class LogsContainerService {
resetFiltersForms(filters): void {
this.appState.getParameter('baseDataSetState')
// do it only when the base data set is available so that the dropdowns
can set the selections
- .filter((dataSetState: DataAvailability) => dataSetState ===
DataAvailabilityValues.AVAILABLE)
+ .filter((dataSetState: DataAvailabilityValues) => dataSetState ===
DataAvailabilityValues.AVAILABLE)
.first()
.subscribe(() => {
this.filtersFormSyncInProgress.next(true);
@@ -510,7 +522,7 @@ export class LogsContainerService {
*/
setActiveTabById(tabId: string): void {
this.tabsStorage.findInCollection((tab: LogTypeTab) => tab.id ===
tabId).first().subscribe((tab: LogTypeTab | null) => {
- if (tab) {
+ if (tab && !tab.isActive) {
this.switchTab(tab);
this.logsStateService.setParameter('activeTabId', tabId);
}
diff --git a/ambari-logsearch-web/src/app/services/storage/reducers.service.ts
b/ambari-logsearch-web/src/app/services/storage/reducers.service.ts
index cd67461..1d18406 100644
--- a/ambari-logsearch-web/src/app/services/storage/reducers.service.ts
+++ b/ambari-logsearch-web/src/app/services/storage/reducers.service.ts
@@ -36,6 +36,8 @@ import {clusterSelections} from
'@app/services/storage/cluster-selection.service
import {logsState} from '@app/services/storage/logs-state.service';
import {dataAvailabilityStates} from
'@app/modules/app-load/stores/data-availability-state.store';
+import * as auth from '@app/store/reducers/auth.reducers';
+
export const reducers = {
appSettings,
appState,
@@ -54,7 +56,8 @@ export const reducers = {
tabs,
clusterSelections,
logsState,
- dataAvailabilityStates
+ dataAvailabilityStates,
+ auth: auth.reducer
};
export function reducer(state: any, action: any) {
diff --git
a/ambari-logsearch-web/src/app/services/user-settings.service.spec.ts
b/ambari-logsearch-web/src/app/services/user-settings.service.spec.ts
index 8dce161..953d7fe 100644
--- a/ambari-logsearch-web/src/app/services/user-settings.service.spec.ts
+++ b/ambari-logsearch-web/src/app/services/user-settings.service.spec.ts
@@ -48,6 +48,11 @@ import {NotificationService} from
'@modules/shared/services/notification.service
import { dataAvailabilityStates, DataAvailabilityStatesStore } from
'@app/modules/app-load/stores/data-availability-state.store';
+import { AuthService } from '@app/services/auth.service';
+import * as auth from '@app/store/reducers/auth.reducers';
+import { EffectsModule } from '@ngrx/effects';
+import { AuthEffects } from '@app/store/effects/auth.effects';
+
describe('UserSettingsService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
@@ -67,8 +72,10 @@ describe('UserSettingsService', () => {
hosts,
serviceLogsTruncated,
tabs,
- dataAvailabilityStates
+ dataAvailabilityStates,
+ auth: auth.reducer
}),
+ EffectsModule.run(AuthEffects),
...TranslationModules
],
providers: [
@@ -95,7 +102,8 @@ describe('UserSettingsService', () => {
LogsStateService,
NotificationsService,
NotificationService,
- DataAvailabilityStatesStore
+ DataAvailabilityStatesStore,
+ AuthService
]
});
});
diff --git a/ambari-logsearch-web/src/app/store/actions/auth.actions.ts
b/ambari-logsearch-web/src/app/store/actions/auth.actions.ts
new file mode 100644
index 0000000..4bfc7ca
--- /dev/null
+++ b/ambari-logsearch-web/src/app/store/actions/auth.actions.ts
@@ -0,0 +1,101 @@
+/**
+ * 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 { Response } from '@angular/http';
+import { Action } from '@ngrx/store';
+
+
+export enum AuthActionTypes {
+ LOGIN = '[Auth] Login',
+ AUTHORIZED = '[Auth] Authorized',
+ UNAUTHORIZED = '[Auth] Unauthorized',
+ AUTHORIZATION_ERROR = '[Auth] Authorization Error',
+ FORBIDDEN = '[Auth] Forbidden',
+ LOGOUT = '[Auth] Logout',
+ LOGGED_OUT = '[Auth] Logged out',
+ LOGOUT_ERROR = '[Auth] Logout error',
+ CHECK_AUTHORIZATION_STATUS = '[Auth] Check status',
+ AUTHORIZATION_TIMEOUT = '[Auth] Authorization Timeout',
+ HTTP_AUTHORIZATION_ERROR_RESPONSE = '[Auth] HTTP Authorization Error
Response'
+}
+
+export class LogInAction implements Action {
+ readonly type = AuthActionTypes.LOGIN;
+ constructor(public payload: any) {}
+}
+
+export class LogOutAction implements Action {
+ readonly type = AuthActionTypes.LOGOUT;
+ constructor() {}
+}
+
+export class AuthorizedAction implements Action {
+ readonly type = AuthActionTypes.AUTHORIZED;
+ constructor(public payload: any) {}
+}
+
+export class UnauthorizedAction implements Action {
+ readonly type = AuthActionTypes.UNAUTHORIZED;
+ constructor(public payload: any) {}
+}
+
+export class AuthorizationErrorAction implements Action {
+ readonly type = AuthActionTypes.AUTHORIZATION_ERROR;
+ constructor(public payload: any) {}
+}
+
+export class ForbiddenAction implements Action {
+ readonly type = AuthActionTypes.FORBIDDEN;
+ constructor(public payload: {response?: Response, [key: string]: any}) {}
+}
+
+export class LoggedOutAction implements Action {
+ readonly type = AuthActionTypes.LOGGED_OUT;
+ constructor(public payload?: any) {}
+}
+
+export class LogoutErrorAction implements Action {
+ readonly type = AuthActionTypes.LOGOUT_ERROR;
+ constructor(public payload: any) {}
+}
+
+export class CheckAuthorizationStatusAction implements Action {
+ readonly type = AuthActionTypes.CHECK_AUTHORIZATION_STATUS;
+ constructor() {}
+}
+
+export class AuthorizationTimeoutAction implements Action {
+ readonly type = AuthActionTypes.AUTHORIZATION_TIMEOUT;
+ constructor(public payload: any) {}
+}
+
+export class HttpAuthorizationErrorResponseAction implements Action {
+ readonly type = AuthActionTypes.HTTP_AUTHORIZATION_ERROR_RESPONSE;
+ constructor(public payload: {response: Response}) {}
+}
+
+export type AuthActions =
+ | LogInAction
+ | LogOutAction
+ | AuthorizedAction
+ | UnauthorizedAction
+ | ForbiddenAction
+ | LoggedOutAction
+ | LogoutErrorAction
+ | HttpAuthorizationErrorResponseAction
+ | CheckAuthorizationStatusAction;
diff --git
a/ambari-logsearch-web/src/app/modules/shared/interfaces/notification.interface.ts
b/ambari-logsearch-web/src/app/store/actions/notification.actions.ts
similarity index 64%
copy from
ambari-logsearch-web/src/app/modules/shared/interfaces/notification.interface.ts
copy to ambari-logsearch-web/src/app/store/actions/notification.actions.ts
index b8b3d6a..c807343 100644
---
a/ambari-logsearch-web/src/app/modules/shared/interfaces/notification.interface.ts
+++ b/ambari-logsearch-web/src/app/store/actions/notification.actions.ts
@@ -15,10 +15,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import {Options} from 'angular2-notifications/src/options.type';
-export interface NotificationInterface extends Options {
- type: string;
- message: string;
- title: string;
+import { Action } from '@ngrx/store';
+
+import { NotificationInterface } from
'@modules/shared/interfaces/notification.interface';
+
+export enum NotificationActionTypes {
+ ADD_NOTIFICATION = '[Notification] Add'
}
+
+export class AddNotificationAction implements Action {
+ readonly type = NotificationActionTypes.ADD_NOTIFICATION;
+ constructor(public payload: NotificationInterface) {}
+}
+
+export type NotificationActions =
+ | AddNotificationAction;
diff --git a/ambari-logsearch-web/src/app/store/effects/auth.effects.ts
b/ambari-logsearch-web/src/app/store/effects/auth.effects.ts
new file mode 100644
index 0000000..99d9bbb
--- /dev/null
+++ b/ambari-logsearch-web/src/app/store/effects/auth.effects.ts
@@ -0,0 +1,198 @@
+/**
+ * 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 { Response } from '@angular/http';
+import { Actions, Effect } from '@ngrx/effects';
+import { Observable } from 'rxjs/Observable';
+import { Router } from '@angular/router';
+
+import 'rxjs/add/observable/of';
+import 'rxjs/add/operator/map';
+import 'rxjs/add/operator/do';
+import 'rxjs/add/operator/switchMap';
+import 'rxjs/add/operator/catch';
+
+import { AuthService } from '@app/services/auth.service';
+import {
+ AuthActionTypes,
+ LogInAction,
+ AuthorizedAction,
+ UnauthorizedAction,
+ AuthorizationErrorAction,
+ AuthorizationTimeoutAction,
+ ForbiddenAction,
+ LoggedOutAction,
+ LogoutErrorAction,
+ HttpAuthorizationErrorResponseAction
+} from '../actions/auth.actions';
+
+import { AddNotificationAction } from
'@app/store/actions/notification.actions';
+import { NotificationType } from
'@modules/shared/services/notification.service';
+
+
+@Injectable()
+export class AuthEffects {
+
+ @Effect()
+ CheckAuthorizationStatus: Observable<any> = this.actions$
+ .ofType(AuthActionTypes.CHECK_AUTHORIZATION_STATUS)
+ .switchMap(() => {
+ return this.authService.checkAuthorizationState()
+ .map((response: Response) => {
+ return response.ok ? new AuthorizedAction({response}) : new
UnauthorizedAction({response});
+ })
+ .catch((error) => {
+ return Observable.of(new UnauthorizedAction({error}));
+ });
+ });
+
+ @Effect()
+ LogIn: Observable<any> = this.actions$
+ .ofType(AuthActionTypes.LOGIN)
+ .map((action: LogInAction) => action.payload)
+ .switchMap(payload => {
+ return this.authService.login(payload.username, payload.password)
+ .map((response: Response) => {
+ let message = '';
+ switch (response.status) {
+ case 401 :
+ message = 'authorization.error.unauthorized';
+ break;
+ case 403 :
+ message = 'authorization.error.forbidden';
+ break;
+ case 419 :
+ message = 'authorization.error.authorizationTimeout';
+ break;
+ }
+ const nextPayload = { message, response, user: {username:
payload.username} };
+ return response.ok ? new AuthorizedAction(nextPayload) : (
+ response.status === 401 ? new UnauthorizedAction(nextPayload) : (
+ response.status === 403 ? new ForbiddenAction(nextPayload) : new
AuthorizationErrorAction(nextPayload)
+ )
+ );
+ })
+ .catch((error) => {
+ return Observable.of(new AuthorizationErrorAction({error: error}));
+ });
+ });
+
+ @Effect({ dispatch: false })
+ Authorized: Observable<any> = this.actions$
+ .ofType(AuthActionTypes.AUTHORIZED)
+ .do(() => {
+ if (this.router.url === '/login') {
+ const url = this.authService.redirectUrl || '/';
+ if (typeof url === 'string') {
+ this.router.navigateByUrl(url);
+ } else if (Array.isArray(url)) {
+ this.router.navigate(url);
+ }
+ }
+ });
+
+ @Effect()
+ LogOut: Observable<any> = this.actions$
+ .ofType(AuthActionTypes.LOGOUT)
+ .switchMap(payload => {
+ return this.authService.logout()
+ .map((response: Response) => {
+ return response.ok ? new LoggedOutAction() : new
LogoutErrorAction({response});
+ })
+ .catch((error) => {
+ return Observable.of(new LogoutErrorAction({error: error}));
+ });
+ });
+
+ @Effect({ dispatch: false })
+ LoggedOut: Observable<any> = this.actions$
+ .ofType(AuthActionTypes.LOGGED_OUT)
+ .do(() => {
+ window.location.reload(true);
+ });
+
+ @Effect()
+ AuthorizationError: Observable<any> = this.actions$
+ .ofType(AuthActionTypes.AUTHORIZATION_ERROR)
+ .map((action: AuthorizationErrorAction) => action.payload)
+ .switchMap((payload) => {
+ const response: Response = payload.response;
+ let message = 'authorization.error.authorizationError';
+ if (response) {
+ const body = response.json();
+ message = body.message || message;
+ }
+ return Observable.of(
+ new AddNotificationAction({
+ type: NotificationType.ERROR,
+ message: message
+ })
+ );
+ });
+
+ @Effect()
+ HttpAuthorizationErrorReponse: Observable<any> = this.actions$
+ .ofType(AuthActionTypes.HTTP_AUTHORIZATION_ERROR_RESPONSE)
+ .map((action: HttpAuthorizationErrorResponseAction) => action.payload)
+ .switchMap(payload => {
+ const response = payload.response;
+ let action;
+ switch (response.status) {
+ case 401 :
+ action = new LoggedOutAction({response});
+ break;
+ case 403 :
+ action = new ForbiddenAction({response});
+ break;
+ case 419 :
+ action = new AuthorizationTimeoutAction({response});
+ break;
+ }
+ return Observable.of(action);
+ });
+
+ @Effect()
+ Forbidden: Observable<any> = this.actions$
+ .ofType(AuthActionTypes.FORBIDDEN)
+ .map((action: ForbiddenAction) => action.payload.response)
+ .switchMap((response: Response) => Observable.of(
+ new AddNotificationAction({
+ type: NotificationType.ERROR,
+ message: 'authorization.error.forbidden'
+ })
+ ));
+
+ @Effect()
+ AuthorizationTimeoutAction: Observable<any> = this.actions$
+ .ofType(AuthActionTypes.AUTHORIZATION_TIMEOUT)
+ .map((action: AuthorizationTimeoutAction) => action.payload.response)
+ .switchMap((response: Response) => Observable.of(
+ new AddNotificationAction({
+ type: NotificationType.ERROR,
+ message: 'authorization.error.authorizationTimeout'
+ })
+ ));
+
+ constructor(
+ private actions$: Actions,
+ private authService: AuthService,
+ private router: Router
+ ) {}
+
+}
diff --git a/ambari-logsearch-web/src/app/services/auth-guard.service.ts
b/ambari-logsearch-web/src/app/store/effects/notification.effects.ts
similarity index 51%
copy from ambari-logsearch-web/src/app/services/auth-guard.service.ts
copy to ambari-logsearch-web/src/app/store/effects/notification.effects.ts
index 8b56239..a4bf62e 100644
--- a/ambari-logsearch-web/src/app/services/auth-guard.service.ts
+++ b/ambari-logsearch-web/src/app/store/effects/notification.effects.ts
@@ -16,28 +16,32 @@
* limitations under the License.
*/
-import {Injectable} from '@angular/core';
-import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot} from
'@angular/router';
-import {Observable} from 'rxjs/Observable';
+import { Injectable } from '@angular/core';
+import { Actions, Effect } from '@ngrx/effects';
+import { Observable } from 'rxjs/Observable';
-import {AuthService} from '@app/services/auth.service';
+import 'rxjs/add/operator/do';
-/**
- * This guard goal is to prevent to display screens where authorization needs.
- */
-@Injectable()
-export class AuthGuardService implements CanActivate {
+import { NotificationService } from
'@modules/shared/services/notification.service';
+import {
+ NotificationActionTypes,
+ AddNotificationAction
+} from '../actions/notification.actions';
- constructor(private authService: AuthService, private router: Router) {}
- canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot):
Observable<boolean> {
- return this.authService.isAuthorized().map((isAuthorized: boolean) => {
- this.authService.redirectUrl = state.url;
- if (!isAuthorized) {
- this.router.navigate(['/login']);
- }
- return isAuthorized;
+@Injectable()
+export class NotificationEffects {
+
+ @Effect({ dispatch: false })
+ AddNotificationAction: Observable<any> = this.actions$
+ .ofType(NotificationActionTypes.ADD_NOTIFICATION)
+ .do((action: AddNotificationAction) => {
+ this.notificationService.addNotification(action.payload);
});
- }
+
+ constructor(
+ private actions$: Actions,
+ private notificationService: NotificationService
+ ) {}
}
diff --git a/ambari-logsearch-web/src/app/store/reducers/auth.reducers.ts
b/ambari-logsearch-web/src/app/store/reducers/auth.reducers.ts
new file mode 100644
index 0000000..09ba8d9
--- /dev/null
+++ b/ambari-logsearch-web/src/app/store/reducers/auth.reducers.ts
@@ -0,0 +1,120 @@
+/**
+ * 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 { User } from '../../classes/models/user';
+
+import { AuthActionTypes, AuthActions } from '../actions/auth.actions';
+
+export enum AuthorizationStatuses {
+ UNAUTHORIZED = 'Unauthorized', // no authorization attempt yet
+ NOT_AUTHORIZED = 'Not Authorized', // there were authorization attempt(s)
but it was unsuccessful
+ CHEKCING_AUTHORIZATION_STATUS = 'Checking Authorization Status', // checking
if the user already authenticated (eg. refreshed the page)
+ FORBIDDEN = 'Forbidden', // no access to Log Search
+ LOGGING_IN = 'Logging In', // login in progress
+ AUTHORIZED = 'Authorized', // authorized
+ LOGGING_OUT = 'Logging Out', // logout in progress
+ LOGGED_OUT = 'Logged Out', // logged out, the user was authorized before
+ LOGOUT_ERROR = 'Logout Error', // logged out, the user was authorized before
+ AUTHORIZATION_ERROR = 'Authorization Error' // there were some server error
during the authorization
+};
+
+export interface State {
+ status: AuthorizationStatuses; // the status of the authorization
+ code?: number; // the last status code of the authorization response
+ user?: User | null; // the user model from the app or from the server
+ message?: string | null; // message from the server after the authentication
attempt
+}
+
+export const initialState: State = {
+ status: null,
+ user: null
+};
+
+export function reducer(state = initialState, action: AuthActions): State {
+switch (action.type) {
+ case AuthActionTypes.LOGIN: {
+ return {
+ ...state,
+ status: AuthorizationStatuses.LOGGING_IN
+ };
+ }
+ case AuthActionTypes.CHECK_AUTHORIZATION_STATUS: {
+ return {
+ ...state,
+ status: AuthorizationStatuses.CHEKCING_AUTHORIZATION_STATUS
+ };
+ }
+ case AuthActionTypes.AUTHORIZED: {
+ const payload = action.payload;
+ return {
+ ...state,
+ status: AuthorizationStatuses.AUTHORIZED,
+ message: payload.message || '',
+ user: payload.user || null
+ };
+ }
+ case AuthActionTypes.UNAUTHORIZED: {
+ const payload = action.payload;
+ return {
+ ...state,
+ message: payload.message || '',
+ status: AuthorizationStatuses.UNAUTHORIZED
+ };
+ }
+ case AuthActionTypes.FORBIDDEN: {
+ const payload = action.payload;
+ return {
+ ...state,
+ status: AuthorizationStatuses.FORBIDDEN,
+ message: payload.message || ''
+ };
+ }
+ case AuthActionTypes.LOGOUT: {
+ return {
+ ...state,
+ status: AuthorizationStatuses.LOGGING_OUT
+ };
+ }
+ case AuthActionTypes.LOGGED_OUT: {
+ return {
+ ...state,
+ status: AuthorizationStatuses.LOGGED_OUT
+ };
+ }
+ case AuthActionTypes.LOGOUT_ERROR: {
+ const payload = action.payload;
+ return {
+ ...state,
+ status: AuthorizationStatuses.LOGOUT_ERROR,
+ message: payload.message || ''
+ };
+ }
+ default: {
+ return state;
+ }
+ }
+};
+
+export const getStatus = (state: State): AuthorizationStatuses => state.status;
+export const getMessage = (state: State): string => (state.message || '');
+export const isAuthorized = (status: AuthorizationStatuses): boolean =>
AuthorizationStatuses.AUTHORIZED === status;
+export const isLoginInProgress = (status: AuthorizationStatuses): boolean =>
AuthorizationStatuses.LOGGING_IN === status;
+export const isLoggedOut = (status: AuthorizationStatuses): boolean =>
AuthorizationStatuses.LOGGED_OUT === status;
+export const isCheckingAuthInProgress = (status: AuthorizationStatuses):
boolean => (
+ AuthorizationStatuses.CHEKCING_AUTHORIZATION_STATUS === status
+);
diff --git a/ambari-logsearch-web/src/app/store/selectors/auth.selectors.ts
b/ambari-logsearch-web/src/app/store/selectors/auth.selectors.ts
new file mode 100644
index 0000000..23decd9
--- /dev/null
+++ b/ambari-logsearch-web/src/app/store/selectors/auth.selectors.ts
@@ -0,0 +1,47 @@
+/**
+ * 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 { createSelector } from 'reselect';
+
+import * as fromAuth from '@app/store/reducers/auth.reducers';
+import { AppStore } from '@app/classes/models/store';
+
+export const getAuthState = (state: AppStore): fromAuth.State => state.auth;
+
+export const authStatusSelector = createSelector( getAuthState,
fromAuth.getStatus );
+export const authMessageSelector = createSelector( getAuthState,
fromAuth.getMessage );
+
+export const isAuthorizedSelector = createSelector(
+ authStatusSelector,
+ fromAuth.isAuthorized
+);
+
+export const isLoginInProgressSelector = createSelector(
+ authStatusSelector,
+ fromAuth.isLoginInProgress
+);
+
+export const isLoggedOutSelector = createSelector(
+ authStatusSelector,
+ fromAuth.isLoggedOut
+);
+
+export const isCheckingAuthStatusInProgressSelector = createSelector(
+ authStatusSelector,
+ fromAuth.isCheckingAuthInProgress
+);
diff --git a/ambari-logsearch-web/src/app/test-config.spec.ts
b/ambari-logsearch-web/src/app/test-config.spec.ts
index 9a53a37..0bad5f8 100644
--- a/ambari-logsearch-web/src/app/test-config.spec.ts
+++ b/ambari-logsearch-web/src/app/test-config.spec.ts
@@ -16,20 +16,27 @@
* limitations under the License.
*/
-import {HttpModule, Http, BrowserXhr, XSRFStrategy, ResponseOptions,
XHRBackend} from '@angular/http';
-import {TranslateModule, TranslateLoader} from '@ngx-translate/core';
-import {TranslateHttpLoader} from '@ngx-translate/http-loader';
-import {Injector} from '@angular/core';
-import {InMemoryBackendService} from 'angular-in-memory-web-api';
-import {MockApiDataService} from '@app/services/mock-api-data.service';
-import {HttpClientService} from '@app/services/http-client.service';
-import {RouterTestingModule} from '@angular/router/testing';
-import {clusters, ClustersService} from
'@app/services/storage/clusters.service';
-import {StoreModule} from '@ngrx/store';
-import {UtilsService} from '@app/services/utils.service';
-import {ComponentGeneratorService} from
'@app/services/component-generator.service';
-import {HostsService} from '@app/services/storage/hosts.service';
-import {ComponentsService} from '@app/services/storage/components.service';
+import { HttpModule, Http, BrowserXhr, XSRFStrategy, ResponseOptions,
XHRBackend } from '@angular/http';
+import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
+import { TranslateHttpLoader } from '@ngx-translate/http-loader';
+import { Injector } from '@angular/core';
+import { InMemoryBackendService } from 'angular-in-memory-web-api';
+import { MockApiDataService } from '@app/services/mock-api-data.service';
+import { HttpClientService } from '@app/services/http-client.service';
+import { RouterTestingModule } from '@angular/router/testing';
+import { clusters, ClustersService } from
'@app/services/storage/clusters.service';
+import { StoreModule } from '@ngrx/store';
+import { UtilsService } from '@app/services/utils.service';
+import { ComponentGeneratorService } from
'@app/services/component-generator.service';
+import { HostsService } from '@app/services/storage/hosts.service';
+import { ComponentsService } from '@app/services/storage/components.service';
+
+import { AuthService } from '@app/services/auth.service';
+import { EffectsModule } from '@ngrx/effects';
+import { AuthEffects } from '@app/store/effects/auth.effects';
+import { NotificationEffects } from '@app/store/effects/notification.effects';
+
+import * as auth from '@app/store/reducers/auth.reducers';
function HttpLoaderFactory(http: Http) {
return new TranslateHttpLoader(http, 'assets/i18n/', '.json');
@@ -62,8 +69,11 @@ export const getCommonTestingBedConfiguration = (
...TranslationModules,
RouterTestingModule,
StoreModule.provideStore({
- clusters
+ clusters,
+ auth: auth.reducer
}),
+ EffectsModule.run(AuthEffects),
+ EffectsModule.run(NotificationEffects),
...imports
],
providers: [
@@ -73,6 +83,7 @@ export const getCommonTestingBedConfiguration = (
HostsService,
ComponentsService,
UtilsService,
+ AuthService,
...providers
],
declarations: [
diff --git a/ambari-logsearch-web/src/assets/i18n/en.json
b/ambari-logsearch-web/src/assets/i18n/en.json
index a0796ac..d8b3801 100644
--- a/ambari-logsearch-web/src/assets/i18n/en.json
+++ b/ambari-logsearch-web/src/assets/i18n/en.json
@@ -25,7 +25,13 @@
"authorization.name": "Username",
"authorization.password": "Password",
"authorization.signIn": "Sign In",
- "authorization.error.401": "Unable to sign in. Invalid username/password
combination.",
+ "authorization.checkingAuthorization": "Checking authorization...",
+ "authorization.authorized": "Successful authorization.",
+ "authorization.loggedOut": "Successfuly logged out.",
+ "authorization.error.authorizationError": "Error during authorization. Maybe
your session expired.",
+ "authorization.error.unauthorized": "Unable to sign in. Invalid
username/password combination.",
+ "authorization.error.forbidden": "Access forbidden.",
+ "authorization.error.authorizationTimeout": "Authorization timeout.",
"login.title": "Login",
diff --git a/ambari-logsearch-web/yarn.lock b/ambari-logsearch-web/yarn.lock
index ae4bb5a..32e3a25 100644
--- a/ambari-logsearch-web/yarn.lock
+++ b/ambari-logsearch-web/yarn.lock
@@ -158,6 +158,10 @@
version "1.2.0"
resolved
"https://registry.yarnpkg.com/@ngrx/core/-/core-1.2.0.tgz#882b46abafa2e0e6d887cb71a1b2c2fa3e6d0dc6"
+"@ngrx/[email protected]":
+ version "2.0.5"
+ resolved
"https://registry.yarnpkg.com/@ngrx/effects/-/effects-2.0.5.tgz#10986923b7193af9b08944e80c5a661ba93a7936"
+
"@ngrx/[email protected]":
version "3.2.4"
resolved
"https://registry.yarnpkg.com/@ngrx/store-devtools/-/store-devtools-3.2.4.tgz#2ce4d13bf34848a9e51ec87e3b125ed67b51e550"
@@ -2674,6 +2678,12 @@ [email protected]:
version "0.5.0"
resolved
"https://registry.yarnpkg.com/fresh/-/fresh-0.5.0.tgz#f474ca5e6a9246d6fd8e0953cfa9b9c805afa78e"
+fs-access@^1.0.0:
+ version "1.0.1"
+ resolved
"https://registry.yarnpkg.com/fs-access/-/fs-access-1.0.1.tgz#d6a87f262271cefebec30c553407fb995da8777a"
+ dependencies:
+ null-check "^1.0.0"
+
fs-extra@^0.23.1:
version "0.23.1"
resolved
"https://registry.yarnpkg.com/fs-extra/-/fs-extra-0.23.1.tgz#6611dba6adf2ab8dc9c69fab37cddf8818157e3d"
@@ -3667,6 +3677,13 @@ jsprim@^1.2.2:
json-schema "0.2.3"
verror "1.3.6"
+karma-chrome-launcher@^2.2.0:
+ version "2.2.0"
+ resolved
"https://registry.yarnpkg.com/karma-chrome-launcher/-/karma-chrome-launcher-2.2.0.tgz#cf1b9d07136cc18fe239327d24654c3dbc368acf"
+ dependencies:
+ fs-access "^1.0.0"
+ which "^1.2.1"
+
karma-cli@~1.0.1:
version "1.0.1"
resolved
"https://registry.yarnpkg.com/karma-cli/-/karma-cli-1.0.1.tgz#ae6c3c58a313a1d00b45164c455b9b86ce17f960"
@@ -4376,6 +4393,10 @@ nth-check@~1.0.1:
dependencies:
boolbase "~1.0.0"
+null-check@^1.0.0:
+ version "1.0.0"
+ resolved
"https://registry.yarnpkg.com/null-check/-/null-check-1.0.0.tgz#977dffd7176012b9ec30d2a39db5cf72a0439edd"
+
num2fraction@^1.2.2:
version "1.2.2"
resolved
"https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede"
@@ -5391,6 +5412,10 @@ [email protected], [email protected]:
version "1.0.0"
resolved
"https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
+reselect@^3.0.1:
+ version "3.0.1"
+ resolved
"https://registry.yarnpkg.com/reselect/-/reselect-3.0.1.tgz#efdaa98ea7451324d092b2b2163a6a1d7a9a2147"
+
resolve@^1.1.6, resolve@^1.1.7:
version "1.3.3"
resolved
"https://registry.yarnpkg.com/resolve/-/resolve-1.3.3.tgz#655907c3469a8680dc2de3a275a8fdd69691f0e5"
@@ -6601,6 +6626,12 @@ which@1, which@^1.2.9:
dependencies:
isexe "^2.0.0"
+which@^1.2.1:
+ version "1.3.1"
+ resolved
"https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
+ dependencies:
+ isexe "^2.0.0"
+
which@^1.2.8, which@~1.2.10:
version "1.2.14"
resolved
"https://registry.yarnpkg.com/which/-/which-1.2.14.tgz#9a87c4378f03e827cecaf1acdf56c736c01c14e5"