diff --git a/ui/package.json b/ui/package.json
index 0a986a67e..05b506bb9 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -4,7 +4,8 @@
"license": "MIT",
"scripts": {
"ng": "ng",
- "start": "ng serve --proxy-config proxy.conf.json --i18nFile=./src/locale/en.xlf --i18nFormat=xlf --locale=en --aot",
+ "start": "ng serve --proxy-config proxy.conf.json",
+ "startProd": "ng serve --proxy-config proxy.conf.json --i18nFile=./src/locale/en.xlf --i18nFormat=xlf --locale=en --aot --environment=prod",
"build": "ng build",
"test": "ng test --code-coverage",
"lint": "ng lint",
@@ -27,11 +28,11 @@
"@ngrx/effects": "^5.1.0",
"@ngrx/router-store": "^5.0.1",
"@ngrx/store": "^5.1.0",
+ "@reactivex/rxjs": "^5.5.6",
"bootstrap": "4.0.0",
"core-js": "^2.4.1",
"file-saver": "^1.3.3",
"font-awesome": "^4.7.0",
- "@reactivex/rxjs": "^5.5.6",
"xml-formatter": "^1.0.1",
"zone.js": "^0.8.20"
},
diff --git a/ui/proxy.conf.json b/ui/proxy.conf.json
index 72708a90b..be9b3e364 100644
--- a/ui/proxy.conf.json
+++ b/ui/proxy.conf.json
@@ -4,6 +4,11 @@
"secure": false,
"logLevel": "debug"
},
+ "/actuator": {
+ "target": "http://localhost:8080",
+ "secure": false,
+ "logLevel": "debug"
+ },
"/login": {
"target": "http://localhost:8080",
"secure": false,
diff --git a/ui/src/app/app.component.html b/ui/src/app/app.component.html
index 0cbc98e1d..8361b6f80 100644
--- a/ui/src/app/app.component.html
+++ b/ui/src/app/app.component.html
@@ -68,7 +68,13 @@
Issue Tracker
Mailing List
- Copyright © 2017 Internet2
+
+
+ {{ version }}
+ |
+
+ Copyright © {{ today | date:'yyyy' }} Internet2
+

diff --git a/ui/src/app/app.component.ts b/ui/src/app/app.component.ts
index 33d1acba3..851158697 100644
--- a/ui/src/app/app.component.ts
+++ b/ui/src/app/app.component.ts
@@ -1,10 +1,13 @@
import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { Store } from '@ngrx/store';
+import 'rxjs/add/operator/takeWhile';
-import * as fromUser from './core/reducer/user.reducer';
+import * as fromRoot from './core/reducer';
+import { VersionInfo } from './core/model/version';
import { LoadProviderRequest } from './metadata-provider/action/provider.action';
import { LoadDraftRequest } from './metadata-provider/action/draft.action';
+import { VersionInfoLoadRequestAction } from './core/action/version.action';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
@@ -12,11 +15,23 @@ import { LoadDraftRequest } from './metadata-provider/action/draft.action';
})
export class AppComponent implements OnInit {
title = 'Shib UI';
+ version$: Observable
;
+ version: string;
+ today = new Date();
- constructor(private store: Store) { }
+ constructor(private store: Store) {
+ this.version$ = this.store.select(fromRoot.getVersionInfo);
+ }
ngOnInit(): void {
this.store.dispatch(new LoadProviderRequest());
this.store.dispatch(new LoadDraftRequest());
+ this.store.dispatch(new VersionInfoLoadRequestAction());
+
+ this.version$.subscribe(v => {
+ if (v && v.build) {
+ this.version = `${v.build.version}-${v.git.commit.id}`;
+ }
+ });
}
}
diff --git a/ui/src/app/app.module.ts b/ui/src/app/app.module.ts
index d49cf30af..f143a46cc 100644
--- a/ui/src/app/app.module.ts
+++ b/ui/src/app/app.module.ts
@@ -12,27 +12,26 @@ import { AppComponent } from './app.component';
import { CoreModule } from './core/core.module';
import { MetadataProviderModule } from './metadata-provider/metadata-provider.module';
-import { reducers } from './core/reducer';
+import { reducers, metaReducers } from './app.reducer';
import { CustomRouterStateSerializer } from './shared/util';
-import { UserEffects } from './core/effect/user.effect';
import { CachingInterceptor } from './core/service/cache.interceptor';
import { AuthorizedInterceptor } from './core/service/authorized.interceptor';
import { NotificationModule } from './notification/notification.module';
import { ErrorInterceptor } from './core/service/error.interceptor';
import { NavigatorService } from './core/service/navigator.service';
+import { environment } from '../environments/environment';
+
@NgModule({
declarations: [
AppComponent
],
imports: [
- StoreModule.forRoot({
- ...reducers
+ StoreModule.forRoot(reducers, {
+ metaReducers
}),
- EffectsModule.forRoot([
- UserEffects
- ]),
+ EffectsModule.forRoot([]),
BrowserModule,
AppRoutingModule,
CoreModule.forRoot(),
diff --git a/ui/src/app/app.reducer.ts b/ui/src/app/app.reducer.ts
new file mode 100644
index 000000000..bd5aa2946
--- /dev/null
+++ b/ui/src/app/app.reducer.ts
@@ -0,0 +1,15 @@
+import { ActionReducerMap, MetaReducer, ActionReducer } from '@ngrx/store';
+import { routerReducer, RouterReducerState } from '@ngrx/router-store';
+import * as fromRouter from '@ngrx/router-store';
+
+import { RouterStateUrl } from './shared/util';
+
+export interface State {
+ router: fromRouter.RouterReducerState;
+}
+
+export const reducers: ActionReducerMap = {
+ router: fromRouter.routerReducer,
+};
+
+export const metaReducers: MetaReducer[] = [];
diff --git a/ui/src/app/core/action/version.action.ts b/ui/src/app/core/action/version.action.ts
new file mode 100644
index 000000000..82d945547
--- /dev/null
+++ b/ui/src/app/core/action/version.action.ts
@@ -0,0 +1,31 @@
+import { Action } from '@ngrx/store';
+
+import { VersionInfo } from '../model/version';
+
+export const VERSION_LOAD_REQUEST = '[Version] Load REQUEST';
+export const VERSION_LOAD_SUCCESS = '[Version] Load SUCCESS';
+export const VERSION_LOAD_ERROR = '[Version] Load ERROR';
+
+/**
+ * Add User to Collection Actions
+ */
+export class VersionInfoLoadRequestAction implements Action {
+ readonly type = VERSION_LOAD_REQUEST;
+}
+
+export class VersionInfoLoadSuccessAction implements Action {
+ readonly type = VERSION_LOAD_SUCCESS;
+
+ constructor (public payload: VersionInfo) { }
+}
+
+export class VersionInfoLoadErrorAction implements Action {
+ readonly type = VERSION_LOAD_ERROR;
+
+ constructor(public payload: Error) { }
+}
+
+export type Actions =
+ | VersionInfoLoadRequestAction
+ | VersionInfoLoadSuccessAction
+ | VersionInfoLoadErrorAction;
diff --git a/ui/src/app/core/core.module.ts b/ui/src/app/core/core.module.ts
index e5d3e3284..73c2afa0b 100644
--- a/ui/src/app/core/core.module.ts
+++ b/ui/src/app/core/core.module.ts
@@ -1,19 +1,25 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
-import { HttpModule } from '@angular/http';
+import { StoreModule } from '@ngrx/store';
+import { EffectsModule } from '@ngrx/effects';
import { UserService } from './service/user.service';
import { CanDeactivateGuard } from './service/can-deactivate.guard';
import { FileService } from './service/file.service';
+import { reducers } from './reducer';
+import { VersionEffects } from './effect/version.effect';
+import { UserEffects } from './effect/user.effect';
+import { HttpClientModule } from '@angular/common/http';
+
export const COMPONENTS = [];
@NgModule({
imports: [
CommonModule,
RouterModule,
- HttpModule
+ HttpClientModule
],
declarations: COMPONENTS,
entryComponents: COMPONENTS,
@@ -22,7 +28,7 @@ export const COMPONENTS = [];
export class CoreModule {
static forRoot() {
return {
- ngModule: CoreModule,
+ ngModule: RootCoreModule,
providers: [
UserService,
FileService,
@@ -31,3 +37,11 @@ export class CoreModule {
};
}
}
+
+@NgModule({
+ imports: [
+ StoreModule.forFeature('core', reducers),
+ EffectsModule.forFeature([UserEffects, VersionEffects]),
+ ],
+})
+export class RootCoreModule { }
diff --git a/ui/src/app/core/effect/version.effect.ts b/ui/src/app/core/effect/version.effect.ts
new file mode 100644
index 000000000..f4e9a455d
--- /dev/null
+++ b/ui/src/app/core/effect/version.effect.ts
@@ -0,0 +1,34 @@
+import 'rxjs/add/operator/switchMap';
+import 'rxjs/add/operator/map';
+import 'rxjs/add/operator/catch';
+import 'rxjs/add/operator/do';
+import { of } from 'rxjs/observable/of';
+import { Injectable } from '@angular/core';
+import { Effect, Actions } from '@ngrx/effects';
+import { Location } from '@angular/common';
+import { HttpClient } from '@angular/common/http';
+
+import * as version from '../action/version.action';
+import { VersionInfo } from '../model/version';
+
+@Injectable()
+export class VersionEffects {
+
+ private endpoint = '/info';
+ private base = '/actuator';
+
+ @Effect()
+ loadVersionInfo$ = this.actions$
+ .ofType(version.VERSION_LOAD_REQUEST)
+ .switchMap(() =>
+ this.http
+ .get(`${this.base}${this.endpoint}`)
+ .map(info => new version.VersionInfoLoadSuccessAction(info) )
+ .catch(error => of(new version.VersionInfoLoadErrorAction(error)))
+ );
+
+ constructor(
+ private http: HttpClient,
+ private actions$: Actions
+ ) { }
+} /* istanbul ignore next */
diff --git a/ui/src/app/core/model/version.ts b/ui/src/app/core/model/version.ts
new file mode 100644
index 000000000..4e931bc17
--- /dev/null
+++ b/ui/src/app/core/model/version.ts
@@ -0,0 +1,16 @@
+export interface VersionInfo {
+ git: {
+ commit: {
+ time: string,
+ id: string
+ },
+ branch: string
+ };
+ build: {
+ version: string,
+ artifact: string,
+ name: string,
+ group: string,
+ time: string
+ };
+}
diff --git a/ui/src/app/core/reducer/index.ts b/ui/src/app/core/reducer/index.ts
index 360b704b5..16d5ae45f 100644
--- a/ui/src/app/core/reducer/index.ts
+++ b/ui/src/app/core/reducer/index.ts
@@ -2,28 +2,38 @@ import {
ActionReducerMap,
createSelector,
createFeatureSelector,
+ createSelectorFactory,
ActionReducer,
MetaReducer,
combineReducers
} from '@ngrx/store';
-import { routerReducer, RouterReducerState } from '@ngrx/router-store';
-import * as fromRouter from '@ngrx/router-store';
import * as fromUser from './user.reducer';
-import { RouterStateUrl } from '../../shared/util';
+import * as fromVersion from './version.reducer';
+import * as fromRoot from '../../app.reducer';
-export interface State {
- routerReducer: fromRouter.RouterReducerState;
+export interface CoreState {
user: fromUser.UserState;
+ version: fromVersion.VersionState;
}
-export const reducers: ActionReducerMap = {
- routerReducer: fromRouter.routerReducer,
- user: fromUser.reducer
+export interface State extends fromRoot.State {
+ core: CoreState;
+}
+
+export const reducers = {
+ user: fromUser.reducer,
+ version: fromVersion.reducer
};
-export const getState = createFeatureSelector('user');
-export const getUserState = createSelector(getState, (state: State) => state.user);
+export const getCoreFeature = createFeatureSelector('core');
+
+export const getUserState = createSelector(getCoreFeature, (state: CoreState) => state.user);
export const getUser = createSelector(getUserState, fromUser.getUser);
export const isFetching = createSelector(getUserState, fromUser.isFetching);
-export const getError = createSelector(getUserState, fromUser.getError);
+export const getUserError = createSelector(getUserState, fromUser.getError);
+
+export const getVersionState = createSelector(getCoreFeature, (state: CoreState) => state.version);
+export const getVersionInfo = createSelector(getVersionState, fromVersion.getVersionInfo);
+export const getVersionLoading = createSelector(getVersionState, fromVersion.getVersionIsLoading);
+export const getVersionError = createSelector(getVersionState, fromVersion.getVersionError);
diff --git a/ui/src/app/core/reducer/version.reducer.ts b/ui/src/app/core/reducer/version.reducer.ts
new file mode 100644
index 000000000..4b40ddf77
--- /dev/null
+++ b/ui/src/app/core/reducer/version.reducer.ts
@@ -0,0 +1,47 @@
+import { createSelector, createFeatureSelector } from '@ngrx/store';
+import * as version from '../action/version.action';
+import * as fromRoot from '../../core/reducer';
+
+export interface VersionState {
+ info: any;
+ loading: boolean;
+ error: Error | null;
+}
+
+export const initialState: VersionState = {
+ info: {},
+ loading: false,
+ error: null
+};
+
+export function reducer(state = initialState, action: version.Actions): VersionState {
+ switch (action.type) {
+ case version.VERSION_LOAD_REQUEST: {
+ return {
+ ...state,
+ loading: true
+ };
+ }
+ case version.VERSION_LOAD_SUCCESS: {
+ return {
+ ...state,
+ loading: false,
+ info: action.payload
+ };
+ }
+ case version.VERSION_LOAD_ERROR: {
+ return {
+ ...state,
+ loading: false,
+ error: action.payload
+ };
+ }
+ default: {
+ return state;
+ }
+ }
+}
+
+export const getVersionInfo = (state: VersionState) => state.info;
+export const getVersionIsLoading = (state: VersionState) => state.loading;
+export const getVersionError = (state: VersionState) => state.error;
diff --git a/ui/src/app/core/service/user.service.ts b/ui/src/app/core/service/user.service.ts
index f9dcdffd4..5e92c667b 100644
--- a/ui/src/app/core/service/user.service.ts
+++ b/ui/src/app/core/service/user.service.ts
@@ -1,13 +1,12 @@
import 'rxjs/add/observable/of';
import { Injectable } from '@angular/core';
-import { Http } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import { User } from '../model/user';
@Injectable()
export class UserService {
- constructor(private http: Http) { }
+ constructor() { }
get(): Observable {
const defUser = Object.assign({}, {
diff --git a/ui/src/app/metadata-provider/reducer/index.ts b/ui/src/app/metadata-provider/reducer/index.ts
index b36639f60..a536cf8b0 100644
--- a/ui/src/app/metadata-provider/reducer/index.ts
+++ b/ui/src/app/metadata-provider/reducer/index.ts
@@ -1,7 +1,7 @@
import { createSelector, createFeatureSelector } from '@ngrx/store';
import * as fromProvider from './provider.reducer';
import * as fromDraft from './draft.reducer';
-import * as fromRoot from '../../core/reducer';
+import * as fromRoot from '../../app.reducer';
import { DraftState } from './draft.reducer';
export interface ProviderState {
@@ -32,6 +32,7 @@ export const getSelectedProviderId = createSelector(getProviderEntityState, from
export const getSelectedProvider = createSelector(getProviderEntities, getSelectedProviderId, getInCollectionFn);
export const getProviderIds = createSelector(getProviderEntityState, fromProvider.getIds);
export const getProviderCollection = createSelector(getProviderEntityState, getProviderIds, fromProvider.getAll);
+
export const getDraftState = createFeatureSelector('providers');
export const getDraftEntityState = createSelector(getDraftState, getDraftsFromStateFn);
export const getDraftEntities = createSelector(getDraftEntityState, fromDraft.getEntities);