From 9507d39f2c8027ed32bca44c41cbe4e46cf851b2 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Fri, 9 Feb 2018 13:14:11 +0000 Subject: [PATCH 01/23] Fix unregister endpoint - Ensure unregister request is treated as a delete --- .../endpoint/endpoints-list-config.service.ts | 24 +++++++++++++------ .../app/store/effects/endpoint.effects.ts | 20 +++++++++++----- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/src/frontend/app/shared/components/list/list-types/endpoint/endpoints-list-config.service.ts b/src/frontend/app/shared/components/list/list-types/endpoint/endpoints-list-config.service.ts index 65096584f6..b071159ad6 100644 --- a/src/frontend/app/shared/components/list/list-types/endpoint/endpoints-list-config.service.ts +++ b/src/frontend/app/shared/components/list/list-types/endpoint/endpoints-list-config.service.ts @@ -11,7 +11,7 @@ import { ShowSnackBar } from '../../../../../store/actions/snackBar.actions'; import { GetSystemInfo } from '../../../../../store/actions/system.actions'; import { AppState } from '../../../../../store/app-state'; import { EndpointsEffect } from '../../../../../store/effects/endpoint.effects'; -import { selectUpdateInfo } from '../../../../../store/selectors/api.selectors'; +import { selectDeletionInfo, selectUpdateInfo } from '../../../../../store/selectors/api.selectors'; import { EndpointModel, endpointStoreNames } from '../../../../../store/types/endpoint.types'; import { ITableColumn } from '../../list-table/table.types'; import { IListAction, IListConfig, IMultiListAction, ListViewTypes } from '../../list.component.types'; @@ -29,9 +29,8 @@ export class EndpointsListConfigService implements IListConfig { private listActionDelete: IListAction = { action: (item) => { this.store.dispatch(new UnregisterEndpoint(item.guid)); - this.handleAction(item, EndpointsEffect.unregisteringKey, ([oldVal, newVal]) => { + this.handleDeleteAction(item, ([oldVal, newVal]) => { this.store.dispatch(new ShowSnackBar(`Unregistered ${item.name}`)); - this.store.dispatch(new ResetPagination(this.dataSource.entityKey, this.dataSource.paginationKey)); }); }, icon: 'delete', @@ -55,7 +54,7 @@ export class EndpointsListConfigService implements IListConfig { private listActionDisconnect: IListAction = { action: (item) => { this.store.dispatch(new DisconnectEndpoint(item.guid)); - this.handleAction(item, EndpointsEffect.disconnectingKey, ([oldVal, newVal]) => { + this.handleUpdateAction(item, EndpointsEffect.disconnectingKey, ([oldVal, newVal]) => { this.store.dispatch(new ShowSnackBar(`Disconnected ${item.name}`)); this.store.dispatch(new GetSystemInfo()); }); @@ -151,12 +150,23 @@ export class EndpointsListConfigService implements IListConfig { enableTextFilter = true; tableFixedRowHeight = true; - private handleAction(item, effectKey, handleChange) { - const disSub = this.store.select(selectUpdateInfo( + private handleUpdateAction(item, effectKey, handleChange) { + this.handleAction(selectUpdateInfo( endpointStoreNames.type, item.guid, effectKey, - )) + ), handleChange); + } + + private handleDeleteAction(item, handleChange) { + this.handleAction(selectDeletionInfo( + endpointStoreNames.type, + item.guid, + ), handleChange); + } + + private handleAction(storeSelect, handleChange) { + const disSub = this.store.select(storeSelect) .pairwise() .subscribe(([oldVal, newVal]) => { // https://github.com/SUSE/stratos/issues/29 Generic way to handle errors ('Failed to disconnect X') diff --git a/src/frontend/app/store/effects/endpoint.effects.ts b/src/frontend/app/store/effects/endpoint.effects.ts index 174d565146..554d229828 100644 --- a/src/frontend/app/store/effects/endpoint.effects.ts +++ b/src/frontend/app/store/effects/endpoint.effects.ts @@ -47,7 +47,6 @@ export class EndpointsEffect { static connectingKey = 'connecting'; static disconnectingKey = 'disconnecting'; static registeringKey = 'registering'; - static unregisteringKey = 'unregistering'; constructor( private http: HttpClient, @@ -91,7 +90,7 @@ export class EndpointsEffect { @Effect() connectEndpoint$ = this.actions$.ofType(CONNECT_ENDPOINTS) .flatMap(action => { const actionType = 'update'; - const apiAction = this.getEndpointAction(action.guid, action.type, EndpointsEffect.connectingKey); + const apiAction = this.getEndpointUpdateAction(action.guid, action.type, EndpointsEffect.connectingKey); const params: HttpParams = new HttpParams({ fromObject: { 'cnsi_guid': action.guid, @@ -112,7 +111,7 @@ export class EndpointsEffect { @Effect() disconnect$ = this.actions$.ofType(DISCONNECT_ENDPOINTS) .flatMap(action => { - const apiAction = this.getEndpointAction(action.guid, action.type, EndpointsEffect.disconnectingKey); + const apiAction = this.getEndpointUpdateAction(action.guid, action.type, EndpointsEffect.disconnectingKey); const params: HttpParams = new HttpParams({ fromObject: { 'cnsi_guid': action.guid @@ -131,7 +130,7 @@ export class EndpointsEffect { @Effect() unregister$ = this.actions$.ofType(UNREGISTER_ENDPOINTS) .flatMap(action => { - const apiAction = this.getEndpointAction(action.guid, action.type, EndpointsEffect.unregisteringKey); + const apiAction = this.getEndpointDeleteAction(action.guid, action.type); const params: HttpParams = new HttpParams({ fromObject: { 'cnsi_guid': action.guid @@ -150,7 +149,7 @@ export class EndpointsEffect { @Effect() register$ = this.actions$.ofType(REGISTER_ENDPOINTS) .flatMap(action => { - const apiAction = this.getEndpointAction(action.guid(), action.type, EndpointsEffect.registeringKey); + const apiAction = this.getEndpointUpdateAction(action.guid(), action.type, EndpointsEffect.registeringKey); const params: HttpParams = new HttpParams({ fromObject: { 'cnsi_name': action.name, @@ -168,7 +167,8 @@ export class EndpointsEffect { ); }); - private getEndpointAction(guid, type, updatingKey) { + + private getEndpointUpdateAction(guid, type, updatingKey) { return { entityKey: endpointStoreNames.type, guid, @@ -177,6 +177,14 @@ export class EndpointsEffect { } as IRequestAction; } + private getEndpointDeleteAction(guid, type) { + return { + entityKey: endpointStoreNames.type, + guid, + type, + } as IRequestAction; + } + private doEndpointAction( apiAction: IRequestAction, url: string, From b042d69c2a13f913e04ee57f8751f55bfeeca767 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Fri, 9 Feb 2018 13:42:39 +0000 Subject: [PATCH 02/23] Non-magical fix - In previous commit the entity was being deleted.. which meant it wasn't shown in table - However it still existed in pagination. Ensure that when we delete we remove it from pagination collections --- src/frontend/app/core/entity-service.ts | 2 +- src/frontend/app/store/effects/endpoint.effects.ts | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/frontend/app/core/entity-service.ts b/src/frontend/app/core/entity-service.ts index 56a108d270..82d6cbb062 100644 --- a/src/frontend/app/core/entity-service.ts +++ b/src/frontend/app/core/entity-service.ts @@ -3,7 +3,7 @@ import { Store } from '@ngrx/store'; import { denormalize, Schema } from 'normalizr'; import { tag } from 'rxjs-spy/operators/tag'; import { interval } from 'rxjs/observable/interval'; -import { filter, map, publishReplay, refCount, shareReplay, tap, withLatestFrom, share } from 'rxjs/operators'; +import { filter, map, shareReplay, tap, withLatestFrom, share } from 'rxjs/operators'; import { Observable } from 'rxjs/Rx'; import { AppState } from '../store/app-state'; diff --git a/src/frontend/app/store/effects/endpoint.effects.ts b/src/frontend/app/store/effects/endpoint.effects.ts index 554d229828..00d9b30f27 100644 --- a/src/frontend/app/store/effects/endpoint.effects.ts +++ b/src/frontend/app/store/effects/endpoint.effects.ts @@ -40,6 +40,7 @@ import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'; import { SystemInfo } from '../types/system.types'; import { map, mergeMap, catchError } from 'rxjs/operators'; import { GetSystemInfo, GET_SYSTEM_INFO, GET_SYSTEM_INFO_SUCCESS, GetSystemSuccess } from '../actions/system.actions'; +import { ClearPaginationOfType, ClearPaginationOfEntity } from '../actions/pagination.actions'; @Injectable() export class EndpointsEffect { @@ -202,6 +203,9 @@ export class EndpointsEffect { if (actionStrings[0]) { this.store.dispatch({ type: actionStrings[0] }); } + if (apiActionType === 'delete') { + this.store.dispatch(new ClearPaginationOfEntity(apiAction.entityKey, apiAction.guid)); + } return new WrapperRequestActionSuccess(null, apiAction, apiActionType); }) .catch(e => { From d8c424a1ed5b60301ebfbc0f3cd498352e8d03a5 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Fri, 9 Feb 2018 14:48:20 +0000 Subject: [PATCH 03/23] Fix two spammy issues - shareReplay was not unsubbing again leading to increased Set Page actions - set page actions should only be used when the local list has been filtered --- .../data-sources-controllers/list-data-source.ts | 16 ++++++++++++---- .../endpoint/endpoints-list-config.service.ts | 1 - 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/frontend/app/shared/components/list/data-sources-controllers/list-data-source.ts b/src/frontend/app/shared/components/list/data-sources-controllers/list-data-source.ts index f96477add8..fd4ff7a8a4 100644 --- a/src/frontend/app/shared/components/list/data-sources-controllers/list-data-source.ts +++ b/src/frontend/app/shared/components/list/data-sources-controllers/list-data-source.ts @@ -7,7 +7,7 @@ import { OperatorFunction } from 'rxjs/interfaces'; import { Observable } from 'rxjs/Observable'; import { combineLatest } from 'rxjs/observable/combineLatest'; import { distinctUntilChanged } from 'rxjs/operators'; -import { map, shareReplay } from 'rxjs/operators'; +import { map, shareReplay, publish, refCount, publishReplay, share, filter } from 'rxjs/operators'; import { Subscription } from 'rxjs/Subscription'; import { SetResultCount } from '../../../../store/actions/pagination.actions'; @@ -242,23 +242,31 @@ export abstract class ListDataSource extends DataSource implements pagination$, page$ ).pipe( + filter(([paginationEntity, entities]) => !getCurrentPageRequestInfo(paginationEntity).busy), map(([paginationEntity, entities]) => { + + const entitiesPreFilter = entities.length; if (dataFunctions && dataFunctions.length) { entities = dataFunctions.reduce((value, fn) => { return fn(value, paginationEntity); }, entities); } + const entitiesPostFilter = entities.length; + const pages = this.splitClientPages(entities, paginationEntity.clientPagination.pageSize); if ( - paginationEntity.totalResults !== entities.length || - paginationEntity.clientPagination.totalResults !== entities.length + entitiesPreFilter !== entitiesPostFilter && + (paginationEntity.totalResults !== entities.length || + paginationEntity.clientPagination.totalResults !== entities.length) ) { this.store.dispatch(new SetResultCount(this.entityKey, this.paginationKey, entities.length)); } + const pageIndex = paginationEntity.clientPagination.currentPage - 1; return pages[pageIndex]; }), - shareReplay(1), + publishReplay(1), + refCount(), tag('local-list') ); } diff --git a/src/frontend/app/shared/components/list/list-types/endpoint/endpoints-list-config.service.ts b/src/frontend/app/shared/components/list/list-types/endpoint/endpoints-list-config.service.ts index b071159ad6..f24f5ca69c 100644 --- a/src/frontend/app/shared/components/list/list-types/endpoint/endpoints-list-config.service.ts +++ b/src/frontend/app/shared/components/list/list-types/endpoint/endpoints-list-config.service.ts @@ -6,7 +6,6 @@ import { ConnectEndpointDialogComponent, } from '../../../../../features/endpoints/connect-endpoint-dialog/connect-endpoint-dialog.component'; import { DisconnectEndpoint, UnregisterEndpoint } from '../../../../../store/actions/endpoint.actions'; -import { ResetPagination } from '../../../../../store/actions/pagination.actions'; import { ShowSnackBar } from '../../../../../store/actions/snackBar.actions'; import { GetSystemInfo } from '../../../../../store/actions/system.actions'; import { AppState } from '../../../../../store/app-state'; From c4eaf234a65dd18db4e2ad5012d8ef8853119c44 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Fri, 9 Feb 2018 16:25:40 +0000 Subject: [PATCH 04/23] Fix org/space shemas - Selecting cf/org/spaces in filter or add/deploy app should work again - Only have one type of Space/Org Schema --- .../applications/application.service.ts | 15 ++- .../create-application-step3.component.ts | 4 +- .../deploy-application-step3.component.ts | 95 +++++++++---------- .../cf-org-space-service.service.ts | 7 +- .../app/store/actions/application.actions.ts | 4 +- .../app/store/actions/organisation.action.ts | 30 ------ .../app/store/actions/organisation.actions.ts | 52 ++++++++++ .../app/store/actions/organization.actions.ts | 42 -------- .../app/store/actions/space.action.ts | 35 ------- .../app/store/actions/space.actions.ts | 43 ++++++--- 10 files changed, 145 insertions(+), 182 deletions(-) delete mode 100644 src/frontend/app/store/actions/organisation.action.ts create mode 100644 src/frontend/app/store/actions/organisation.actions.ts delete mode 100644 src/frontend/app/store/actions/organization.actions.ts delete mode 100644 src/frontend/app/store/actions/space.action.ts diff --git a/src/frontend/app/features/applications/application.service.ts b/src/frontend/app/features/applications/application.service.ts index 33d50f7ade..1267829c4f 100644 --- a/src/frontend/app/features/applications/application.service.ts +++ b/src/frontend/app/features/applications/application.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; import { Observable } from 'rxjs/Observable'; -import { map } from 'rxjs/operators'; +import { map, mergeMap } from 'rxjs/operators'; import { EntityService } from '../../core/entity-service'; import { EntityServiceFactory } from '../../core/entity-service-factory.service'; @@ -18,7 +18,6 @@ import { } from '../../store/actions/app-metadata.actions'; import { GetApplication, UpdateApplication, UpdateExistingApplication } from '../../store/actions/application.actions'; import { ApplicationSchema } from '../../store/actions/application.actions'; -import { SpaceSchema } from '../../store/actions/space.action'; import { AppState } from '../../store/app-state'; import { ActionState } from '../../store/reducers/api-request-reducer/types'; import { selectEntity } from '../../store/selectors/api.selectors'; @@ -46,6 +45,7 @@ import { } from './application/application-tabs-base/tabs/build-tab/application-env-vars.service'; import { getRoute, isTCPRoute } from './routes/routes.helper'; import { PaginationMonitor } from '../../shared/monitors/pagination-monitor'; +import { spaceSchemaKey, organisationSchemaKey } from '../../store/actions/action-types'; export interface ApplicationData { fetching: boolean; @@ -157,9 +157,14 @@ export class ApplicationService { .filter(entityInfo => entityInfo.entity && entityInfo.entity.entity && entityInfo.entity.entity.cfGuid) .map(entityInfo => entityInfo.entity.entity) .do(app => { - this.appSpace$ = this.store.select(selectEntity(SpaceSchema.key, app.space_guid)); - // See https://github.com/SUSE/stratos/issues/158 (Failing to populate entity store with a space's org) - this.appOrg$ = this.store.select(selectEntity(SpaceSchema.key, app.space_guid)).map(space => space.entity.organization); + + this.appSpace$ = this.store.select(selectEntity(spaceSchemaKey, app.space_guid)); + this.appOrg$ = this.appSpace$.pipe( + map(space => space.entity.organization_guid), + mergeMap(orgGuid => { + return this.store.select(selectEntity(organisationSchemaKey, orgGuid)); + }) + ); }) .take(1) .subscribe(); diff --git a/src/frontend/app/features/applications/create-application/create-application-step3/create-application-step3.component.ts b/src/frontend/app/features/applications/create-application/create-application-step3/create-application-step3.component.ts index 8464c650c1..f73bd5d9ed 100644 --- a/src/frontend/app/features/applications/create-application/create-application-step3/create-application-step3.component.ts +++ b/src/frontend/app/features/applications/create-application/create-application-step3/create-application-step3.component.ts @@ -18,8 +18,8 @@ import { AppState } from '../../../../store/app-state'; import { selectNewAppState } from '../../../../store/effects/create-app-effects'; import { CreateNewApplicationState } from '../../../../store/types/create-application.types'; import { RouterNav } from '../../../../store/actions/router.actions'; -import { OrganisationSchema } from '../../../../store/actions/organisation.action'; import { RequestInfoState } from '../../../../store/reducers/api-request-reducer/types'; +import { organisationSchemaKey } from '../../../../store/actions/action-types'; @Component({ selector: 'app-create-application-step3', @@ -124,7 +124,7 @@ export class CreateApplicationStep3Component implements OnInit { }) .filter(state => state.cloudFoundryDetails && state.cloudFoundryDetails.org) .mergeMap(state => { - return this.store.select(selectEntity(OrganisationSchema.key, state.cloudFoundryDetails.org)) + return this.store.select(selectEntity(organisationSchemaKey, state.cloudFoundryDetails.org)) .first() .map(org => org.entity.domains); }); diff --git a/src/frontend/app/features/applications/deploy-application/deploy-application-step3/deploy-application-step3.component.ts b/src/frontend/app/features/applications/deploy-application/deploy-application-step3/deploy-application-step3.component.ts index e8653b710d..c0aa14ad44 100644 --- a/src/frontend/app/features/applications/deploy-application/deploy-application-step3/deploy-application-step3.component.ts +++ b/src/frontend/app/features/applications/deploy-application/deploy-application-step3/deploy-application-step3.component.ts @@ -4,9 +4,7 @@ import { Store } from '@ngrx/store'; import { AppState } from '../../../../store/app-state'; import { tap, filter, map, mergeMap, combineLatest, switchMap, share, catchError } from 'rxjs/operators'; import { getEntityById, selectEntity, selectEntities } from '../../../../store/selectors/api.selectors'; -import { OrganizationSchema } from '../../../../store/actions/organization.actions'; import { DeleteDeployAppSection } from '../../../../store/actions/deploy-applications.actions'; -import { SpaceSchema } from '../../../../store/actions/space.actions'; import websocketConnect from 'rxjs-websockets'; import { QueueingSubject } from 'queueing-subject/lib'; import { Subscription } from 'rxjs/Subscription'; @@ -20,6 +18,7 @@ import { RouterNav } from '../../../../store/actions/router.actions'; import { GetAllApplications } from '../../../../store/actions/application.actions'; import { environment } from '../../../../../environments/environment'; import { CfOrgSpaceDataService } from '../../../../shared/data-services/cf-org-space-service.service'; +import { organisationSchemaKey, spaceSchemaKey } from '../../../../store/actions/action-types'; @Component({ selector: 'app-deploy-application-step3', @@ -58,9 +57,9 @@ export class DeployApplicationStep3Component implements OnInit, OnDestroy { && !!appDetail.applicationSource && !!appDetail.applicationSource.projectName), mergeMap(p => { - const orgSubscription = this.store.select(selectEntity(OrganizationSchema.key, p.cloudFoundryDetails.org)); - const spaceSubscription = this.store.select(selectEntity(SpaceSchema.key, p.cloudFoundryDetails.space)); - return Observable.of(p).combineLatest(orgSubscription, spaceSubscription ); + const orgSubscription = this.store.select(selectEntity(organisationSchemaKey, p.cloudFoundryDetails.org)); + const spaceSubscription = this.store.select(selectEntity(spaceSchemaKey, p.cloudFoundryDetails.space)); + return Observable.of(p).combineLatest(orgSubscription, spaceSubscription); }), tap(p => { const host = window.location.host; @@ -70,10 +69,10 @@ export class DeployApplicationStep3Component implements OnInit, OnDestroy { `?org=${p[1].entity.name}&space=${p[2].entity.name}` ); - const inputStream = new QueueingSubject(); + const inputStream = new QueueingSubject(); this.messages = websocketConnect(streamUrl, inputStream) - .messages.pipe( - catchError(e => { + .messages.pipe( + catchError(e => { return []; }), share(), @@ -88,21 +87,21 @@ export class DeployApplicationStep3Component implements OnInit, OnDestroy { this.updateTitle(log); } }), - filter((log ) => log.type === SocketEventTypes.DATA), + filter((log) => log.type === SocketEventTypes.DATA), map((log) => { const timesString = moment(log.timestamp * 1000).format('DD/MM/YYYY hh:mm:ss A'); return ( `${timesString}: ${log.message}` ); }) - ); + ); inputStream.next(this.sendProjectInfo(p[0].applicationSource)); }) ).subscribe(); } - sendProjectInfo = (appSource: DeployApplicationSource) => { + sendProjectInfo = (appSource: DeployApplicationSource) => { if (appSource.type.id === 'git') { if (appSource.type.subType === 'github') { return this.sendGitHubSourceMetadata(appSource); @@ -114,7 +113,7 @@ export class DeployApplicationStep3Component implements OnInit, OnDestroy { return ''; } - sendGitHubSourceMetadata = (appSource: DeployApplicationSource) => { + sendGitHubSourceMetadata = (appSource: DeployApplicationSource) => { const github = { project: appSource.projectName, branch: appSource.branch.name, @@ -129,7 +128,7 @@ export class DeployApplicationStep3Component implements OnInit, OnDestroy { return JSON.stringify(msg); } - sendGitUrlSourceMetadata = (appSource: DeployApplicationSource) => { + sendGitUrlSourceMetadata = (appSource: DeployApplicationSource) => { const giturl = { url: appSource.projectName, branch: appSource.branch.name, @@ -155,53 +154,53 @@ export class DeployApplicationStep3Component implements OnInit, OnDestroy { this.appData.org = this.cfOrgSpaceService.org.select.getValue(); this.appData.space = this.cfOrgSpaceService.space.select.getValue(); break; - case SocketEventTypes.EVENT_PUSH_STARTED : - this.streamTitle = 'Deploying...'; - this.store.dispatch(new GetAllApplications('applicationWall')); - break; - case SocketEventTypes.EVENT_PUSH_COMPLETED : - this.streamTitle = 'Deployed'; - this.apps$ = this.store.select(selectEntities('application')).pipe( - tap(apps => { - Object.values(apps).forEach(app => { - if ( - app.entity.space_guid === this.appData.space && - app.entity.cfGuid === this.appData.cloudFoundry && - app.entity.name === this.appData.Name - ) { - this.appGuid = app.entity.guid; - this.validate = Observable.of(true); - } - }); - }) - ).subscribe(); - break; - case SocketEventTypes.CLOSE_SUCCESS : - this.close(log, null, null, true); - break; + case SocketEventTypes.EVENT_PUSH_STARTED: + this.streamTitle = 'Deploying...'; + this.store.dispatch(new GetAllApplications('applicationWall')); + break; + case SocketEventTypes.EVENT_PUSH_COMPLETED: + this.streamTitle = 'Deployed'; + this.apps$ = this.store.select(selectEntities('application')).pipe( + tap(apps => { + Object.values(apps).forEach(app => { + if ( + app.entity.space_guid === this.appData.space && + app.entity.cfGuid === this.appData.cloudFoundry && + app.entity.name === this.appData.Name + ) { + this.appGuid = app.entity.guid; + this.validate = Observable.of(true); + } + }); + }) + ).subscribe(); + break; + case SocketEventTypes.CLOSE_SUCCESS: + this.close(log, null, null, true); + break; case SocketEventTypes.CLOSE_INVALID_MANIFEST: this.close(log, 'Deploy Failed - Invalid manifest!', - 'Failed to deploy app! Please make sure that a valid manifest.yaml was provided!', true); + 'Failed to deploy app! Please make sure that a valid manifest.yaml was provided!', true); break; case SocketEventTypes.CLOSE_NO_MANIFEST: - this.close(log, 'Deploy Failed - No manifest present!', - 'Failed to deploy app! Please make sure that a valid manifest.yaml is present!', true); + this.close(log, 'Deploy Failed - No manifest present!', + 'Failed to deploy app! Please make sure that a valid manifest.yaml is present!', true); break; case SocketEventTypes.CLOSE_FAILED_CLONE: - this.close(log, 'Deploy Failed - Failed to clone repository!', - 'Failed to deploy app! Please make sure the repository is public!', true); + this.close(log, 'Deploy Failed - Failed to clone repository!', + 'Failed to deploy app! Please make sure the repository is public!', true); break; case SocketEventTypes.CLOSE_FAILED_NO_BRANCH: - this.close(log, 'Deploy Failed - Failed to located branch!', - 'Failed to deploy app! Please make sure that branch exists!', true); + this.close(log, 'Deploy Failed - Failed to located branch!', + 'Failed to deploy app! Please make sure that branch exists!', true); break; case SocketEventTypes.CLOSE_FAILURE: case SocketEventTypes.CLOSE_PUSH_ERROR: case SocketEventTypes.CLOSE_NO_SESSION: case SocketEventTypes.CLOSE_NO_CNSI: case SocketEventTypes.CLOSE_NO_CNSI_USERTOKEN: - this.close(log, 'Deploy Failed!', - 'Failed to deploy app!', true); + this.close(log, 'Deploy Failed!', + 'Failed to deploy app!', true); break; case SocketEventTypes.SOURCE_REQUIRED: case SocketEventTypes.EVENT_CLONED: @@ -209,8 +208,8 @@ export class DeployApplicationStep3Component implements OnInit, OnDestroy { case SocketEventTypes.MANIFEST: break; default: - // noop - } + // noop + } } close(log, title, error, deleteAppSection) { diff --git a/src/frontend/app/shared/data-services/cf-org-space-service.service.ts b/src/frontend/app/shared/data-services/cf-org-space-service.service.ts index b1c80d6657..c4eceaecca 100644 --- a/src/frontend/app/shared/data-services/cf-org-space-service.service.ts +++ b/src/frontend/app/shared/data-services/cf-org-space-service.service.ts @@ -3,12 +3,13 @@ import { Store } from '@ngrx/store'; import { BehaviorSubject } from 'rxjs/BehaviorSubject'; import { Observable } from 'rxjs/Observable'; -import { GetAllOrganizations, OrganizationSchema } from '../../store/actions/organization.actions'; import { AppState } from '../../store/app-state'; import { getPaginationObservables, getCurrentPageRequestInfo } from '../../store/reducers/pagination-reducer/pagination-reducer.helper'; import { endpointsRegisteredEntitiesSelector } from '../../store/selectors/endpoint.selectors'; import { EndpointModel } from '../../store/types/endpoint.types'; import { PaginationMonitorFactory } from '../monitors/pagination-monitor.factory'; +import { GetAllOrganisations } from '../../store/actions/organisation.actions'; +import { OrganisationWithSpaceSchema } from '../../store/actions/action-types'; export interface CfOrgSpaceItem { list$: Observable; @@ -25,7 +26,7 @@ export class CfOrgSpaceDataService { public org: CfOrgSpaceItem; public space: CfOrgSpaceItem; - public paginationAction = new GetAllOrganizations(CfOrgSpaceDataService.CfOrgSpaceServicePaginationKey); + public paginationAction = new GetAllOrganisations(CfOrgSpaceDataService.CfOrgSpaceServicePaginationKey); // TODO: We should optimise this to only fetch the orgs for the current endpoint // (if we inline depth the get orgs request it could be hefty... or we could use a different action to only fetch required data.. @@ -35,7 +36,7 @@ export class CfOrgSpaceDataService { action: this.paginationAction, paginationMonitor: this.paginationMonitorFactory.create( this.paginationAction.paginationKey, - OrganizationSchema + OrganisationWithSpaceSchema ) }); diff --git a/src/frontend/app/store/actions/application.actions.ts b/src/frontend/app/store/actions/application.actions.ts index 3b2fd53de9..596ddb776e 100644 --- a/src/frontend/app/store/actions/application.actions.ts +++ b/src/frontend/app/store/actions/application.actions.ts @@ -5,7 +5,6 @@ import { Headers, RequestOptions, URLSearchParams } from '@angular/http'; import { schema } from 'normalizr'; import { ApiActionTypes } from './request.actions'; -import { SpaceSchema } from './space.actions'; import { StackSchema } from './stack.action'; import { ActionMergeFunction } from '../types/api.types'; import { PaginatedAction } from '../types/pagination.types'; @@ -14,6 +13,7 @@ import { pick } from '../helpers/reducer.helper'; import { AppMetadataTypes } from './app-metadata.actions'; import { AppStatSchema } from '../types/app-metadata.types'; import { getPaginationKey } from './pagination.actions'; +import { SpaceWithOrganisationSchema } from './action-types'; export const GET_ALL = '[Application] Get all'; export const GET_ALL_SUCCESS = '[Application] Get all success'; @@ -50,7 +50,7 @@ export const DELETE_INSTANCE_FAILED = '[Application Instance] Delete failed'; const ApplicationEntitySchema = { entity: { stack: StackSchema, - space: SpaceSchema + space: SpaceWithOrganisationSchema } }; diff --git a/src/frontend/app/store/actions/organisation.action.ts b/src/frontend/app/store/actions/organisation.action.ts deleted file mode 100644 index b59ddf576a..0000000000 --- a/src/frontend/app/store/actions/organisation.action.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { CFStartAction, IRequestAction, ICFAction } from '../types/request.types'; -import { getAPIResourceGuid } from '../selectors/api.selectors'; -import { schema } from 'normalizr'; -import { ApiActionTypes } from './request.actions'; -import { RequestOptions } from '@angular/http'; - -export const GET = '[Organisation] Get one'; -export const GET_SUCCESS = '[Organisation] Get one success'; -export const GET_FAILED = '[Organisation] Get one failed'; - -export const OrganisationSchema = new schema.Entity('organization', {}, { - idAttribute: getAPIResourceGuid -}); - -export class GetOrganisation extends CFStartAction implements ICFAction { - constructor(public guid: string, public endpointGuid: string) { - super(); - this.options = new RequestOptions(); - this.options.url = `organization/${guid}`; - this.options.method = 'get'; - } - actions = [ - GET, - GET_SUCCESS, - GET_FAILED - ]; - entity = [OrganisationSchema]; - entityKey = OrganisationSchema.key; - options: RequestOptions; -} diff --git a/src/frontend/app/store/actions/organisation.actions.ts b/src/frontend/app/store/actions/organisation.actions.ts new file mode 100644 index 0000000000..b86166eb86 --- /dev/null +++ b/src/frontend/app/store/actions/organisation.actions.ts @@ -0,0 +1,52 @@ +import { RequestOptions } from '@angular/http'; + +import { PaginatedAction } from '../types/pagination.types'; +import { CFStartAction, ICFAction } from '../types/request.types'; +import { OrganisationSchema, organisationSchemaKey, OrganisationWithSpaceSchema } from './action-types'; + +export const GET_ORGANISATION = '[Organisation] Get one'; +export const GET_ORGANISATION_SUCCESS = '[Organisation] Get one success'; +export const GET_ORGANISATION_FAILED = '[Organisation] Get one failed'; + +export const GET_ORGANISATIONS = '[Organization] Get all'; +export const GET_ORGANISATIONS_SUCCESS = '[Organization] Get all success'; +export const GET_ORGANISATIONS_FAILED = '[Organization] Get all failed'; + +export class GetOrganisation extends CFStartAction implements ICFAction { + constructor(public guid: string, public endpointGuid: string) { + super(); + this.options = new RequestOptions(); + this.options.url = `organization/${guid}`; + this.options.method = 'get'; + } + actions = [ + GET_ORGANISATION, + GET_ORGANISATION_SUCCESS, + GET_ORGANISATION_FAILED + ]; + entity = [OrganisationSchema]; + entityKey = organisationSchemaKey; + options: RequestOptions; +} + +export class GetAllOrganisations extends CFStartAction implements PaginatedAction { + constructor(public paginationKey: string) { + super(); + this.options = new RequestOptions(); + this.options.url = 'organizations'; + this.options.method = 'get'; + } + actions = [ + GET_ORGANISATIONS, + GET_ORGANISATIONS_SUCCESS, + GET_ORGANISATIONS_FAILED + ]; + entity = [OrganisationWithSpaceSchema]; + entityKey = organisationSchemaKey; + options: RequestOptions; + initialParams = { + page: 1, + 'results-per-page': 100, + 'inline-relations-depth': 1 + }; +} diff --git a/src/frontend/app/store/actions/organization.actions.ts b/src/frontend/app/store/actions/organization.actions.ts deleted file mode 100644 index d02305b3c1..0000000000 --- a/src/frontend/app/store/actions/organization.actions.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { CFStartAction } from '../types/request.types'; -import { getAPIResourceGuid } from '../selectors/api.selectors'; -import { RequestOptions, URLSearchParams } from '@angular/http'; -import { schema } from 'normalizr'; - -import { ApiActionTypes } from './request.actions'; -import { SpaceSchema } from './space.actions'; -import { PaginatedAction } from '../types/pagination.types'; - -export const GET_ALL = '[Organization] Get all'; -export const GET_ALL_SUCCESS = '[Organization] Get all success'; -export const GET_ALL_FAILED = '[Organization] Get all failed'; - -export const OrganizationSchema = new schema.Entity('organization', { - entity: { - spaces: [SpaceSchema] - } -}, { - idAttribute: getAPIResourceGuid - }); - -export class GetAllOrganizations extends CFStartAction implements PaginatedAction { - constructor(public paginationKey: string) { - super(); - this.options = new RequestOptions(); - this.options.url = 'organizations'; - this.options.method = 'get'; - } - actions = [ - GET_ALL, - GET_ALL_SUCCESS, - GET_ALL_FAILED - ]; - entity = [OrganizationSchema]; - entityKey = OrganizationSchema.key; - options: RequestOptions; - initialParams = { - page: 1, - 'results-per-page': 100, - 'inline-relations-depth': 1 - }; -} diff --git a/src/frontend/app/store/actions/space.action.ts b/src/frontend/app/store/actions/space.action.ts deleted file mode 100644 index 2aaa481675..0000000000 --- a/src/frontend/app/store/actions/space.action.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { CFStartAction, IRequestAction, ICFAction } from '../types/request.types'; -import { getAPIResourceGuid } from '../selectors/api.selectors'; -import { schema } from 'normalizr'; -import { ApiActionTypes } from './request.actions'; -import { RequestOptions } from '@angular/http'; -import { OrganisationSchema } from './organisation.action'; - -export const GET = '[Space] Get one'; -export const GET_SUCCESS = '[Space] Get one success'; -export const GET_FAILED = '[Space] Get one failed'; - -export const SpaceSchema = new schema.Entity('space', { - entity: { - organization: OrganisationSchema - } -}, { - idAttribute: getAPIResourceGuid - }); - -export class GetSpace extends CFStartAction implements ICFAction { - constructor(public guid: string, public endpointGuid: string) { - super(); - this.options = new RequestOptions(); - this.options.url = `space/${guid}`; - this.options.method = 'get'; - } - actions = [ - GET, - GET_SUCCESS, - GET_FAILED - ]; - entity = [SpaceSchema]; - entityKey = SpaceSchema.key; - options: RequestOptions; -} diff --git a/src/frontend/app/store/actions/space.actions.ts b/src/frontend/app/store/actions/space.actions.ts index e108607a98..d6b7aa5d07 100644 --- a/src/frontend/app/store/actions/space.actions.ts +++ b/src/frontend/app/store/actions/space.actions.ts @@ -1,19 +1,32 @@ -import { - CFStartAction, - IRequestAction, - ICFAction -} from '../types/request.types'; -import { getAPIResourceGuid } from '../selectors/api.selectors'; import { RequestOptions, URLSearchParams } from '@angular/http'; -import { schema } from 'normalizr'; -import { ApiActionTypes } from './request.actions'; +import { CFStartAction, ICFAction } from '../types/request.types'; +import { SpaceSchema, spaceSchemaKey, SpaceWithOrganisationSchema } from './action-types'; -export const GET_ALL = '[Space] Get all'; -export const GET_ALL_SUCCESS = '[Space] Get all success'; -export const GET_ALL_FAILED = '[Space] Get all failed'; +export const GET_SPACES = '[Space] Get all'; +export const GET_SPACES_SUCCESS = '[Space] Get all success'; +export const GET_SPACES_FAILED = '[Space] Get all failed'; -export const SpaceSchema = new schema.Entity('space'); +export const GET_SPACE = '[Space] Get one'; +export const GET_SPACE_SUCCESS = '[Space] Get one success'; +export const GET_SPACE_FAILED = '[Space] Get one failed'; + +export class GetSpace extends CFStartAction implements ICFAction { + constructor(public guid: string, public endpointGuid: string) { + super(); + this.options = new RequestOptions(); + this.options.url = `space/${guid}`; + this.options.method = 'get'; + } + actions = [ + GET_SPACE, + GET_SPACE_SUCCESS, + GET_SPACE_FAILED + ]; + entity = [SpaceSchema]; + entityKey = spaceSchemaKey; + options: RequestOptions; +} export class GetAllSpaces extends CFStartAction implements ICFAction { constructor(public paginationKey?: string) { @@ -26,8 +39,8 @@ export class GetAllSpaces extends CFStartAction implements ICFAction { this.options.params.set('results-per-page', '100'); this.options.params.set('inline-relations-depth', '1'); } - actions = [GET_ALL, GET_ALL_SUCCESS, GET_ALL_FAILED]; - entity = [SpaceSchema]; - entityKey = SpaceSchema.key; + actions = [GET_SPACES, GET_SPACES_SUCCESS, GET_SPACES_FAILED]; + entity = [SpaceWithOrganisationSchema]; + entityKey = spaceSchemaKey; options: RequestOptions; } From f8482ba595f50f962dbce0f0ff2d95316bf3271c Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Fri, 9 Feb 2018 16:47:22 +0000 Subject: [PATCH 05/23] Add missing file --- .../app/store/actions/action-types.ts | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 src/frontend/app/store/actions/action-types.ts diff --git a/src/frontend/app/store/actions/action-types.ts b/src/frontend/app/store/actions/action-types.ts new file mode 100644 index 0000000000..8c36f51a38 --- /dev/null +++ b/src/frontend/app/store/actions/action-types.ts @@ -0,0 +1,28 @@ +import { schema } from 'normalizr'; +import { getAPIResourceGuid } from '../selectors/api.selectors'; + +export const organisationSchemaKey = 'organization'; +export const OrganisationSchema = new schema.Entity(organisationSchemaKey, {}, { + idAttribute: getAPIResourceGuid +}); + +export const spaceSchemaKey = 'space'; +export const SpaceSchema = new schema.Entity(spaceSchemaKey, {}, { + idAttribute: getAPIResourceGuid +}); + +export const OrganisationWithSpaceSchema = new schema.Entity(organisationSchemaKey, { + entity: { + spaces: [SpaceSchema] + } +}, { + idAttribute: getAPIResourceGuid + }); + +export const SpaceWithOrganisationSchema = new schema.Entity(spaceSchemaKey, { + entity: { + organization: OrganisationSchema + } +}, { + idAttribute: getAPIResourceGuid + }); From fa47ff58ccfd7f46e31de6eae7d55d9f22cd9ee8 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Fri, 9 Feb 2018 17:09:05 +0000 Subject: [PATCH 06/23] Common treatment of all input fields and spacing across steppers --- .../create-application-step1.component.html | 44 +++++++++++-------- .../create-application-step3.component.html | 12 ++--- .../add-routes/add-routes.component.scss | 2 +- .../stepper/steppers/steppers.component.scss | 4 +- 4 files changed, 35 insertions(+), 27 deletions(-) diff --git a/src/frontend/app/features/applications/create-application/create-application-step1/create-application-step1.component.html b/src/frontend/app/features/applications/create-application/create-application-step1/create-application-step1.component.html index d2afd5df08..aacf9e74fd 100644 --- a/src/frontend/app/features/applications/create-application/create-application-step1/create-application-step1.component.html +++ b/src/frontend/app/features/applications/create-application/create-application-step1/create-application-step1.component.html @@ -1,22 +1,28 @@ Select a Cloud Foundry instance, organization and space for the app.
- - - {{ cf.name }} - - - - - - - {{ org.name }} - - - - - - - {{ space.name }} - - + + + + {{ cf.name }} + + + + + + + + + {{ org.name }} + + + + + + + + + {{ space.name }} + + +
diff --git a/src/frontend/app/features/applications/create-application/create-application-step3/create-application-step3.component.html b/src/frontend/app/features/applications/create-application/create-application-step3/create-application-step3.component.html index 5478dbe38d..b0e5369214 100644 --- a/src/frontend/app/features/applications/create-application/create-application-step3/create-application-step3.component.html +++ b/src/frontend/app/features/applications/create-application/create-application-step3/create-application-step3.component.html @@ -1,10 +1,12 @@ Create a route
- - - {{ domain.entity.name }} - - + + + + {{ domain.entity.name }} + + + diff --git a/src/frontend/app/features/applications/routes/add-routes/add-routes.component.scss b/src/frontend/app/features/applications/routes/add-routes/add-routes.component.scss index f1bf78842c..41846fea35 100644 --- a/src/frontend/app/features/applications/routes/add-routes/add-routes.component.scss +++ b/src/frontend/app/features/applications/routes/add-routes/add-routes.component.scss @@ -32,7 +32,7 @@ margin-bottom: 24px; } &__toggle { - margin-bottom: 24px; + margin-bottom: 10px; margin-top: 24px; mat-checkbox { margin-left: 12px; diff --git a/src/frontend/app/shared/components/stepper/steppers/steppers.component.scss b/src/frontend/app/shared/components/stepper/steppers/steppers.component.scss index 3c7e870957..3c801fe2cd 100644 --- a/src/frontend/app/shared/components/stepper/steppers/steppers.component.scss +++ b/src/frontend/app/shared/components/stepper/steppers/steppers.component.scss @@ -74,10 +74,10 @@ form { display: flex; flex-direction: column; + margin-top: 10px; max-width: 60%; - mat-select, mat-form-field { - margin-top: 20px; + padding-top: 10px; width: 100%; } } From 4b7c316d5dc745014f2d80ebba6188ab649bd7ac Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Fri, 9 Feb 2018 17:18:26 +0000 Subject: [PATCH 07/23] Ensure top level stepper form styles only apply to top level stopper forms - In the future these should be wrapped in their own component --- .../create-application-step1.component.html | 2 +- .../create-application-step2.component.html | 2 +- .../create-application-step3.component.html | 2 +- .../deploy-application-step2.component.html | 2 +- .../edit-application/edit-application.component.html | 6 +++--- .../routes/add-routes/add-routes.component.html | 4 ++-- .../create-endpoint-cf-step-1.component.html | 8 +++----- .../uaa-wizard/console-uaa-wizard.component.html | 7 +++---- .../components/stepper/steppers/steppers.component.scss | 3 ++- 9 files changed, 17 insertions(+), 19 deletions(-) diff --git a/src/frontend/app/features/applications/create-application/create-application-step1/create-application-step1.component.html b/src/frontend/app/features/applications/create-application/create-application-step1/create-application-step1.component.html index aacf9e74fd..a7ae7fd1d7 100644 --- a/src/frontend/app/features/applications/create-application/create-application-step1/create-application-step1.component.html +++ b/src/frontend/app/features/applications/create-application/create-application-step1/create-application-step1.component.html @@ -1,5 +1,5 @@ Select a Cloud Foundry instance, organization and space for the app. - + diff --git a/src/frontend/app/features/applications/create-application/create-application-step2/create-application-step2.component.html b/src/frontend/app/features/applications/create-application/create-application-step2/create-application-step2.component.html index 989afef1a1..8a85fed3e8 100644 --- a/src/frontend/app/features/applications/create-application/create-application-step2/create-application-step2.component.html +++ b/src/frontend/app/features/applications/create-application/create-application-step2/create-application-step2.component.html @@ -1,5 +1,5 @@ Please select a unique name for your application - +
diff --git a/src/frontend/app/features/applications/create-application/create-application-step3/create-application-step3.component.html b/src/frontend/app/features/applications/create-application/create-application-step3/create-application-step3.component.html index b0e5369214..2d256dc25a 100644 --- a/src/frontend/app/features/applications/create-application/create-application-step3/create-application-step3.component.html +++ b/src/frontend/app/features/applications/create-application/create-application-step3/create-application-step3.component.html @@ -1,5 +1,5 @@ Create a route - + diff --git a/src/frontend/app/features/applications/deploy-application/deploy-application-step2/deploy-application-step2.component.html b/src/frontend/app/features/applications/deploy-application/deploy-application-step2/deploy-application-step2.component.html index 6196c12c38..e84e320ac5 100644 --- a/src/frontend/app/features/applications/deploy-application/deploy-application-step2/deploy-application-step2.component.html +++ b/src/frontend/app/features/applications/deploy-application/deploy-application-step2/deploy-application-step2.component.html @@ -1,5 +1,5 @@ Please specify the source - +
diff --git a/src/frontend/app/features/applications/edit-application/edit-application.component.html b/src/frontend/app/features/applications/edit-application/edit-application.component.html index adf7442457..965a1589e8 100644 --- a/src/frontend/app/features/applications/edit-application/edit-application.component.html +++ b/src/frontend/app/features/applications/edit-application/edit-application.component.html @@ -9,14 +9,14 @@

Edit Application: {{ (applicationService.application$ | async)?.app.entity.n
- + Application name is required Application name already taken
-
+
@@ -28,7 +28,7 @@

Edit Application: {{ (applicationService.application$ | async)?.app.entity.n Enable SSH to Application Instances - Production Application + Production Application

There was an error while updating the application.

diff --git a/src/frontend/app/features/applications/routes/add-routes/add-routes.component.html b/src/frontend/app/features/applications/routes/add-routes/add-routes.component.html index 60d5af981c..9ad995b467 100644 --- a/src/frontend/app/features/applications/routes/add-routes/add-routes.component.html +++ b/src/frontend/app/features/applications/routes/add-routes/add-routes.component.html @@ -19,7 +19,7 @@ Create TCP Route
-
+
@@ -41,7 +41,7 @@
-
+
diff --git a/src/frontend/app/features/endpoints/create-endpoint/create-endpoint-cf-step-1/create-endpoint-cf-step-1.component.html b/src/frontend/app/features/endpoints/create-endpoint/create-endpoint-cf-step-1/create-endpoint-cf-step-1.component.html index 6a7f0bb10e..f7d8d14b03 100644 --- a/src/frontend/app/features/endpoints/create-endpoint/create-endpoint-cf-step-1/create-endpoint-cf-step-1.component.html +++ b/src/frontend/app/features/endpoints/create-endpoint/create-endpoint-cf-step-1/create-endpoint-cf-step-1.component.html @@ -2,21 +2,19 @@ Register an existing Cloud Foundry endpoint to allow your development team to create and manage their applications.

- When registering, choose a unique, recognizable name so that your developers can easily know which Cloud Foundry endpoint - they are working with. + When registering, choose a unique, recognizable name so that your developers can easily know which Cloud Foundry endpoint they are working with.

Supply the API Url for your Cloud Foundry endpoint

- + Name is required Name is not unique - + URL is required Invalid API URL URL is not unique diff --git a/src/frontend/app/features/uaa-setup/uaa-wizard/console-uaa-wizard.component.html b/src/frontend/app/features/uaa-setup/uaa-wizard/console-uaa-wizard.component.html index e4b78b3475..2a21614446 100644 --- a/src/frontend/app/features/uaa-setup/uaa-wizard/console-uaa-wizard.component.html +++ b/src/frontend/app/features/uaa-setup/uaa-wizard/console-uaa-wizard.component.html @@ -4,15 +4,14 @@

Welcome to SUSE Cloud Foundry Console.

- SUSE Cloud Foundry Console is an Open Source Web-based UI (Console) for managing Cloud Foundry. It allows users and administrators - to both manage applications running in the Cloud Foundry cluster and perform cluster management tasks. + SUSE Cloud Foundry Console is an Open Source Web-based UI (Console) for managing Cloud Foundry. It allows users and administrators to both manage applications running in the Cloud Foundry cluster and perform cluster management tasks.

Before accessing the console for the first time some configuration information is required. Press NEXT to get started.

- + UAA Endpoint @@ -33,7 +32,7 @@ -
+ {{ scope }} diff --git a/src/frontend/app/shared/components/stepper/steppers/steppers.component.scss b/src/frontend/app/shared/components/stepper/steppers/steppers.component.scss index 3c801fe2cd..3372b5febc 100644 --- a/src/frontend/app/shared/components/stepper/steppers/steppers.component.scss +++ b/src/frontend/app/shared/components/stepper/steppers/steppers.component.scss @@ -71,7 +71,8 @@ flex: 1; text-align: right; } - form { + .stepper-form { + // Use a specific style instead of form element selected. This prevents these generic styles bleading into child components of a stepper display: flex; flex-direction: column; margin-top: 10px; From fa032677e1485699270f14b53191aaa0bee04289 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Mon, 12 Feb 2018 11:36:52 +0000 Subject: [PATCH 08/23] Convert routes lists (app summary + map) to use local pagination - Converted to local lists - Applied sorting - Ensure route entity has values built in to support sorting --- .../applications/application.service.ts | 1 - .../list-data-source.ts | 5 ++ .../components/list/list-table/table.types.ts | 4 +- .../cf-app-map-routes-list-config.service.ts | 27 +++++++--- .../app-route/cf-app-routes-data-source.ts | 26 +++++++++- .../cf-app-routes-list-config.service.ts | 51 ++++++++++++------- .../table-cell-app-route.component.ts | 2 +- .../table-cell-tcproute.component.html | 4 +- .../table-cell-tcproute.component.ts | 8 +-- .../app/store/actions/route.actions.ts | 19 ++++--- 10 files changed, 101 insertions(+), 46 deletions(-) diff --git a/src/frontend/app/features/applications/application.service.ts b/src/frontend/app/features/applications/application.service.ts index 1267829c4f..1944002e79 100644 --- a/src/frontend/app/features/applications/application.service.ts +++ b/src/frontend/app/features/applications/application.service.ts @@ -157,7 +157,6 @@ export class ApplicationService { .filter(entityInfo => entityInfo.entity && entityInfo.entity.entity && entityInfo.entity.entity.cfGuid) .map(entityInfo => entityInfo.entity.entity) .do(app => { - this.appSpace$ = this.store.select(selectEntity(spaceSchemaKey, app.space_guid)); this.appOrg$ = this.appSpace$.pipe( map(space => space.entity.organization_guid), diff --git a/src/frontend/app/shared/components/list/data-sources-controllers/list-data-source.ts b/src/frontend/app/shared/components/list/data-sources-controllers/list-data-source.ts index f96477add8..a6d351634e 100644 --- a/src/frontend/app/shared/components/list/data-sources-controllers/list-data-source.ts +++ b/src/frontend/app/shared/components/list/data-sources-controllers/list-data-source.ts @@ -113,8 +113,13 @@ export abstract class ListDataSource extends DataSource implements // Add any additional functions via an optional listConfig, such as sorting from the column definition const listColumns = this.config.listConfig ? this.config.listConfig.getColumns() : []; listColumns.forEach(column => { + if (!column.sort) { + return; + } if (DataFunctionDefinition.is(column.sort)) { transformEntities.push(column.sort as DataFunctionDefinition); + } else if (typeof column.sort !== 'boolean') { + transformEntities.push(column.sort as DataFunction); } }); diff --git a/src/frontend/app/shared/components/list/list-table/table.types.ts b/src/frontend/app/shared/components/list/list-table/table.types.ts index 1e9a99f898..e8a51c8399 100644 --- a/src/frontend/app/shared/components/list/list-table/table.types.ts +++ b/src/frontend/app/shared/components/list/list-table/table.types.ts @@ -1,4 +1,4 @@ -import { DataFunctionDefinition } from '../data-sources-controllers/list-data-source'; +import { DataFunction, DataFunctionDefinition } from '../data-sources-controllers/list-data-source'; import { TableCellStatusDirective } from './table-cell-status.directive'; import { listTableCells, TableCellComponent } from './table-cell/table-cell.component'; import { TableRowComponent } from './table-row/table-row.component'; @@ -12,7 +12,7 @@ export interface ITableColumn { headerCell?: () => string; // Either headerCell OR headerCellComponent should be defined headerCellComponent?: any; class?: string; - sort?: boolean | DataFunctionDefinition; + sort?: boolean | DataFunctionDefinition | DataFunction; cellFlex?: string; } diff --git a/src/frontend/app/shared/components/list/list-types/app-route/cf-app-map-routes-list-config.service.ts b/src/frontend/app/shared/components/list/list-types/app-route/cf-app-map-routes-list-config.service.ts index 2954d71c91..34eeb0c719 100644 --- a/src/frontend/app/shared/components/list/list-types/app-route/cf-app-map-routes-list-config.service.ts +++ b/src/frontend/app/shared/components/list/list-types/app-route/cf-app-map-routes-list-config.service.ts @@ -1,3 +1,4 @@ +import { isTCPRoute } from '../../../../../features/applications/routes/routes.helper'; import { Injectable } from '@angular/core'; import { MatSnackBar } from '@angular/material'; import { ActivatedRoute } from '@angular/router'; @@ -25,10 +26,10 @@ import { TableCellAppRouteComponent } from './table-cell-app-route/table-cell-ap import { TableCellRadioComponent } from './table-cell-radio/table-cell-radio.component'; import { TableCellRouteComponent } from './table-cell-route/table-cell-route.component'; import { TableCellTCPRouteComponent } from './table-cell-tcproute/table-cell-tcproute.component'; +import { PaginationEntityState } from '../../../../../store/types/pagination.types'; @Injectable() -export class CfAppMapRoutesListConfigService - implements IListConfig { +export class CfAppMapRoutesListConfigService implements IListConfig { routesDataSource: CfAppRoutesDataSource; columns: Array> = [ @@ -43,21 +44,33 @@ export class CfAppMapRoutesListConfigService columnId: 'route', headerCell: () => 'Route', cellComponent: TableCellRouteComponent, - sort: true, + sort: { + type: 'sort', + orderKey: 'route', + field: 'entity.host' + }, cellFlex: '3' }, { columnId: 'tcproute', headerCell: () => 'TCP Route', cellComponent: TableCellTCPRouteComponent, - sort: true, + sort: { + type: 'sort', + orderKey: 'tcproute', + field: 'entity.isTCPRoute' + }, cellFlex: '3' }, { columnId: 'attachedApps', headerCell: () => 'Apps Attached', cellComponent: TableCellAppRouteComponent, - sort: true, + sort: { + type: 'sort', + orderKey: 'attachedApps', + field: 'entity.mappedAppsCount' + }, cellFlex: '3' } ]; @@ -67,6 +80,7 @@ export class CfAppMapRoutesListConfigService text: { title: 'Available Routes'; }; + isLocal: true; dispatchDeleteAction(route) { return this.store.dispatch( @@ -104,7 +118,8 @@ export class CfAppMapRoutesListConfigService this.appService, new GetSpaceRoutes(spaceGuid, appService.cfGuid), getPaginationKey('route', appService.cfGuid, spaceGuid), - true + true, + this ); } } diff --git a/src/frontend/app/shared/components/list/list-types/app-route/cf-app-routes-data-source.ts b/src/frontend/app/shared/components/list/list-types/app-route/cf-app-routes-data-source.ts index 113909c079..2f775ebcfd 100644 --- a/src/frontend/app/shared/components/list/list-types/app-route/cf-app-routes-data-source.ts +++ b/src/frontend/app/shared/components/list/list-types/app-route/cf-app-routes-data-source.ts @@ -8,6 +8,8 @@ import { APIResource, EntityInfo } from '../../../../../store/types/api.types'; import { PaginatedAction } from '../../../../../store/types/pagination.types'; import { ListDataSource } from '../../data-sources-controllers/list-data-source'; import { IListConfig } from '../../list.component.types'; +import { map } from 'rxjs/operators'; +import { isTCPRoute, getMappedApps } from '../../../../../features/applications/routes/routes.helper'; export const RouteSchema = new schema.Entity('route'); @@ -20,7 +22,8 @@ export class CfAppRoutesDataSource extends ListDataSource { appService: ApplicationService, action: PaginatedAction, paginationKey: string, - mapRoute = false + mapRoute = false, + listConfig: IListConfig ) { super({ store, @@ -28,7 +31,26 @@ export class CfAppRoutesDataSource extends ListDataSource { schema: RouteSchema, getRowUniqueId: (object: EntityInfo) => object.entity ? object.entity.guid : null, - paginationKey + paginationKey, + isLocal: true, + listConfig, + transformEntity: map((routes) => { + routes = routes.map(route => { + let newRoute = route; + if (!route.entity.isTCPRoute || !route.entity.mappedAppsCount) { + newRoute = { + ...route, + entity: { + ...route.entity, + isTCPRoute: isTCPRoute(route), + mappedAppsCount: getMappedApps(route).length + } + }; + } + return newRoute; + }); + return routes; + }) }); this.cfGuid = appService.cfGuid; diff --git a/src/frontend/app/shared/components/list/list-types/app-route/cf-app-routes-list-config.service.ts b/src/frontend/app/shared/components/list/list-types/app-route/cf-app-routes-list-config.service.ts index 521291e6f8..7aa8b52b26 100644 --- a/src/frontend/app/shared/components/list/list-types/app-route/cf-app-routes-list-config.service.ts +++ b/src/frontend/app/shared/components/list/list-types/app-route/cf-app-routes-list-config.service.ts @@ -87,22 +87,22 @@ export class CfAppRoutesListConfigService implements IListConfig { action: () => { this.appService.application$ .pipe( - take(1), - tap(app => { - this.store.dispatch( - new RouterNav({ - path: [ - 'applications', - this.appService.cfGuid, - this.appService.appGuid, - 'add-route' - ], - query: { - spaceGuid: app.app.entity.space_guid - } - }) - ); - }) + take(1), + tap(app => { + this.store.dispatch( + new RouterNav({ + path: [ + 'applications', + this.appService.cfGuid, + this.appService.appGuid, + 'add-route' + ], + query: { + spaceGuid: app.app.entity.space_guid + } + }) + ); + }) ) .subscribe(); }, @@ -118,13 +118,23 @@ export class CfAppRoutesListConfigService implements IListConfig { columnId: 'route', headerCell: () => 'Route', cellComponent: TableCellRouteComponent, - cellFlex: '4' + cellFlex: '4', + sort: { + type: 'sort', + orderKey: 'route', + field: 'entity.host' + } }, { columnId: 'tcproute', headerCell: () => 'TCP Route', cellComponent: TableCellTCPRouteComponent, - cellFlex: '4' + cellFlex: '4', + sort: { + type: 'sort', + orderKey: 'tcproute', + field: 'entity.isTCPRoute' + }, } ]; @@ -133,6 +143,7 @@ export class CfAppRoutesListConfigService implements IListConfig { text = { title: 'Routes' }; + isLocal = true; dispatchDeleteAction(route) { return this.store.dispatch( @@ -169,7 +180,9 @@ export class CfAppRoutesListConfigService implements IListConfig { this.store, this.appService, new GetAppRoutes(appService.appGuid, appService.cfGuid), - getPaginationKey('route', appService.cfGuid, appService.appGuid) + getPaginationKey('route', appService.cfGuid, appService.appGuid), + false, + this ); } diff --git a/src/frontend/app/shared/components/list/list-types/app-route/table-cell-app-route/table-cell-app-route.component.ts b/src/frontend/app/shared/components/list/list-types/app-route/table-cell-app-route/table-cell-app-route.component.ts index 3dee651648..256da4b317 100644 --- a/src/frontend/app/shared/components/list/list-types/app-route/table-cell-app-route/table-cell-app-route.component.ts +++ b/src/frontend/app/shared/components/list/list-types/app-route/table-cell-app-route/table-cell-app-route.component.ts @@ -20,7 +20,7 @@ export class TableCellAppRouteComponent extends TableCellCustom ngOnInit(): void { const apps = this.row.entity.apps; - this.mappedAppsCount = getMappedApps(this.row).length; + this.mappedAppsCount = this.row.entity.mappedAppsCount; const foundApp = apps && apps.find(a => a.metadata.guid === this.appService.appGuid); if (foundApp && foundApp.length !== 0) { diff --git a/src/frontend/app/shared/components/list/list-types/app-route/table-cell-tcproute/table-cell-tcproute.component.html b/src/frontend/app/shared/components/list/list-types/app-route/table-cell-tcproute/table-cell-tcproute.component.html index eb48b9781c..99a0cebf5a 100644 --- a/src/frontend/app/shared/components/list/list-types/app-route/table-cell-tcproute/table-cell-tcproute.component.html +++ b/src/frontend/app/shared/components/list/list-types/app-route/table-cell-tcproute/table-cell-tcproute.component.html @@ -1,8 +1,8 @@
-
+
Yes
-
+
No
diff --git a/src/frontend/app/shared/components/list/list-types/app-route/table-cell-tcproute/table-cell-tcproute.component.ts b/src/frontend/app/shared/components/list/list-types/app-route/table-cell-tcproute/table-cell-tcproute.component.ts index 4c68f3592a..217880e089 100644 --- a/src/frontend/app/shared/components/list/list-types/app-route/table-cell-tcproute/table-cell-tcproute.component.ts +++ b/src/frontend/app/shared/components/list/list-types/app-route/table-cell-tcproute/table-cell-tcproute.component.ts @@ -7,16 +7,10 @@ import { isTCPRoute } from '../../../../../../features/applications/routes/route templateUrl: './table-cell-tcproute.component.html', styleUrls: ['./table-cell-tcproute.component.scss'] }) -export class TableCellTCPRouteComponent extends TableCellCustom implements OnInit { +export class TableCellTCPRouteComponent extends TableCellCustom { @Input('row') row; - isRouteTCP: boolean; constructor() { super(); } - - ngOnInit() { - this.isRouteTCP = isTCPRoute(this.row); - } - } diff --git a/src/frontend/app/store/actions/route.actions.ts b/src/frontend/app/store/actions/route.actions.ts index a6d9c8980b..3663c86de0 100644 --- a/src/frontend/app/store/actions/route.actions.ts +++ b/src/frontend/app/store/actions/route.actions.ts @@ -131,7 +131,6 @@ export class CheckRouteExists extends CFStartAction implements ICFAction { endpointGuid: string; } -// Refactor to satisfy CodeClimate export class ListRoutes extends CFStartAction implements PaginatedAction { constructor( public guid: string, @@ -151,10 +150,8 @@ export class ListRoutes extends CFStartAction implements PaginatedAction { entity = [RouteSchema]; entityKey = RouteSchema.key; options: RequestOptions; - initialParams = { - 'inline-relations-depth': '2' - }; endpointGuid: string; + flattenPagination = true; } export class GetAppRoutes extends ListRoutes implements PaginatedAction { @@ -166,8 +163,11 @@ export class GetAppRoutes extends ListRoutes implements PaginatedAction { ]); } initialParams = { - 'results-per-page': 9, // Match that of the page size used by the matching list config - 'inline-relations-depth': '1' + 'results-per-page': 100, + 'inline-relations-depth': '1', + page: 1, + 'order-direction': 'desc', + 'order-direction-field': 'route', }; } @@ -179,6 +179,13 @@ export class GetSpaceRoutes extends ListRoutes implements PaginatedAction { RouteEvents.GET_SPACE_ALL_FAILED ]); } + initialParams = { + 'results-per-page': 100, + 'inline-relations-depth': '1', + page: 1, + 'order-direction': 'desc', + 'order-direction-field': 'attachedApps', + }; } export class MapRouteSelected implements Action { From e5cba863c7e50eba62191effd0a50c6235551090 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Mon, 12 Feb 2018 16:16:37 +0000 Subject: [PATCH 09/23] Show smaller page sizes + ensure any default page size is valid --- .../cf-app-map-routes-list-config.service.ts | 8 ++++---- .../app-route/cf-app-routes-list-config.service.ts | 2 +- .../app/shared/components/list/list.component.ts | 13 +++++++++++-- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/frontend/app/shared/components/list/list-types/app-route/cf-app-map-routes-list-config.service.ts b/src/frontend/app/shared/components/list/list-types/app-route/cf-app-map-routes-list-config.service.ts index 34eeb0c719..2075fb2fa0 100644 --- a/src/frontend/app/shared/components/list/list-types/app-route/cf-app-map-routes-list-config.service.ts +++ b/src/frontend/app/shared/components/list/list-types/app-route/cf-app-map-routes-list-config.service.ts @@ -75,12 +75,12 @@ export class CfAppMapRoutesListConfigService implements IListConfig } ]; - pageSizeOptions = [9, 45, 90]; + pageSizeOptions = [5, 15, 30]; viewType = ListViewTypes.TABLE_ONLY; - text: { - title: 'Available Routes'; + text = { + title: 'Available Routes' }; - isLocal: true; + isLocal = true; dispatchDeleteAction(route) { return this.store.dispatch( diff --git a/src/frontend/app/shared/components/list/list-types/app-route/cf-app-routes-list-config.service.ts b/src/frontend/app/shared/components/list/list-types/app-route/cf-app-routes-list-config.service.ts index 7aa8b52b26..82b5742ff0 100644 --- a/src/frontend/app/shared/components/list/list-types/app-route/cf-app-routes-list-config.service.ts +++ b/src/frontend/app/shared/components/list/list-types/app-route/cf-app-routes-list-config.service.ts @@ -138,7 +138,7 @@ export class CfAppRoutesListConfigService implements IListConfig { } ]; - pageSizeOptions = [9, 45, 90]; + pageSizeOptions = [5, 15, 30]; viewType = ListViewTypes.TABLE_ONLY; text = { title: 'Routes' diff --git a/src/frontend/app/shared/components/list/list.component.ts b/src/frontend/app/shared/components/list/list.component.ts index d2a2bc5856..c8fe2db7c6 100644 --- a/src/frontend/app/shared/components/list/list.component.ts +++ b/src/frontend/app/shared/components/list/list.component.ts @@ -82,11 +82,11 @@ export class ListComponent implements OnInit, OnDestroy, AfterViewInit { this.dataSource = this.config.getDataSource(); this.multiFilterConfigs = this.config.getMultiFiltersConfigs(); - // Set up an obervable containing the current view (card/table) + // Set up an observable containing the current view (card/table) const { view, } = getListStateObservables(this.store, this.dataSource.paginationKey); this.view$ = view; - // If this is the first time the user has used this lis then set the view to the default + // If this is the first time the user has used this list then set the view to the default this.view$.first().subscribe(listView => { if (!listView) { this.updateListView(this.config.defaultView || 'table'); @@ -96,6 +96,15 @@ export class ListComponent implements OnInit, OnDestroy, AfterViewInit { this.paginationController = new ListPaginationController(this.store, this.dataSource); this.paginator.pageSizeOptions = this.config.pageSizeOptions; + + // Ensure we set a pageSize that's relevant to the configured set of page sizes. The default is 9 and in some cases is not a valid + // pageSize + this.paginationController.pagination$.first().subscribe(pagination => { + if (this.paginator.pageSizeOptions.findIndex(pageSize => pageSize === pagination.pageSize) < 0) { + this.paginationController.pageSize(this.paginator.pageSizeOptions[0]); + } + }); + const paginationStoreToWidget = this.paginationController.pagination$.do((pagination: ListPagination) => { this.paginator.length = pagination.totalResults; this.paginator.pageIndex = pagination.pageIndex - 1; From c693428698bc58f110be129bcd25d6d5f7b58c10 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Mon, 12 Feb 2018 16:29:41 +0000 Subject: [PATCH 10/23] Fix delete --- src/frontend/app/store/actions/route.actions.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/frontend/app/store/actions/route.actions.ts b/src/frontend/app/store/actions/route.actions.ts index 3663c86de0..645ad13c79 100644 --- a/src/frontend/app/store/actions/route.actions.ts +++ b/src/frontend/app/store/actions/route.actions.ts @@ -63,14 +63,14 @@ export class CreateRoute extends CFStartAction implements ICFAction { export class DeleteRoute extends CFStartAction implements ICFAction { constructor( - public routeGuid: string, + public guid: string, public cfGuid: string, public async: boolean = false, public recursive: boolean = true ) { super(); this.options = new RequestOptions(); - this.options.url = `routes/${routeGuid}`; + this.options.url = `routes/${guid}`; this.options.method = 'delete'; this.options.params = new URLSearchParams(); this.options.params.append('recursive', recursive ? 'true' : 'false'); @@ -86,6 +86,7 @@ export class DeleteRoute extends CFStartAction implements ICFAction { entityKey = RouteSchema.key; options: RequestOptions; endpointGuid: string; + removeEntityOnDelete = true; } export class UnmapRoute extends CFStartAction implements ICFAction { From 9b571c6718b9658491d8cdc4c2569c5c5e662e7a Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Mon, 12 Feb 2018 16:33:39 +0000 Subject: [PATCH 11/23] Remove incorrect/missleading documentation left over from v1 (#1608) - We should update these in the future - We also need to validate all deploy types in new branch/folder structure --- README.md | 6 +- deploy/cloud-foundry/README.md | 33 +--- docs/developers-guide.md | 50 +++++- docs/development.md | 284 --------------------------------- docs/features.md | 2 + docs/i18n.md | 91 +---------- 6 files changed, 62 insertions(+), 404 deletions(-) delete mode 100644 docs/development.md diff --git a/README.md b/README.md index ac8a59471a..49c6cef10e 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Stratos is an Open Source Web-based UI (Console) for managing Cloud Foundry. It allows users and administrators to both manage applications running in the Cloud Foundry cluster and perform cluster management tasks. -![Stratos Application view](docs/images/screenshots/app-wall.png) + ## Deploying Stratos @@ -35,8 +35,8 @@ You can access the UI on `https://localhost:4443` ## Project Planning We use [ZenHub](https://zenhub.com) for project planning. Feel free to head over to the [Boards](https://github.com/SUSE/stratos#boards) -tab and have a look through our pipelines and milestones. Please note in order to view the ZenHub Boards tab you will need the [ZenHub -browser extension](https://www.zenhub.com/extension). +tab and have a look through our pipelines and milestones. Please note in order to view the Github ZenHub Boards tab you will need the [ZenHub +browser extension](https://www.zenhub.com/extension). Alternatively, to view the planning board without the extension visit our [ZenHub Project Page](https://app.zenhub.com/workspace/o/cloudfoundry-incubator/stratos/boards) ## Further Reading diff --git a/deploy/cloud-foundry/README.md b/deploy/cloud-foundry/README.md index dc8639f020..767e16e919 100644 --- a/deploy/cloud-foundry/README.md +++ b/deploy/cloud-foundry/README.md @@ -2,11 +2,11 @@ ## Deployment Steps -The quickest way to install Stratos UI is to deploy it as a Cloud Foundry application. To do so, clone the `stratos-ui` repository, cd into the newly cloned repository and push to Cloud Foundry. This can be done with: +The quickest way to install Stratos UI is to deploy it as a Cloud Foundry application. To do so, clone the `stratos` repository, cd into the newly cloned repository and push to Cloud Foundry. This can be done with: ``` -git clone https://github.com/SUSE/stratos-ui.git -cd stratos-ui +git clone https://github.com/cloudfoundry-incubator/stratos +cd stratos cf push ``` @@ -27,27 +27,8 @@ Note: 1. You need the cf CLI command line tool installed and available on the path. 2. You need to have configured the cf cli to point to your Cloud Foundry cluster, to be authenticated with your credentials and to be targeted at the organization and space where you want the console application be created. -3. You may need to configure Application Security Groups on your Cloud Foundry Cluster in order that Stratos UI can communicate with the Cloud Foundry API. See [below](#application-security-groups) for more information. -4. The Stratos UI Console will automatically detect the API endpoint for your Cloud Foundry. To do so, it relies on the `cf_api_url` value inside the `VCAP_APPLICATION` environment variable. If this is not provided by your Cloud Foundry platform, then you must manually update the application manifest as described [below](#console-fails-to-start). - -## Enable Endpoints Dashboard to register additional Cloud Foundry endpoints - -To enable the dashboard add the environment variable 'FORCE_ENDPOINT_DASHBOARD' to the manifest before the call to 'cf push' is made. For example - - ``` - applications: - - name: console - memory: 256M - disk_quota: 256M - host: console - timeout: 180 - buildpack: https://github.com/SUSE/stratos-buildpack - health-check-type: port - env: - FORCE_ENDPOINT_DASHBOARD: true - ``` - ->**NOTE** This step, on it's own, is meant for demonstration purposes only. Registered endpoints will be lost if the app is restarted and each app instance will have it's own lists. To remove these caveats see the section 'Associate Cloud Foundry database service' below. +3. You may need to configure Application Security Groups on your Cloud Foundry Cluster in order that Stratos can communicate with the Cloud Foundry API. See [below](#application-security-groups) for more information. +4. The Stratos Console will automatically detect the API endpoint for your Cloud Foundry. To do so, it relies on the `cf_api_url` value inside the `VCAP_APPLICATION` environment variable. If this is not provided by your Cloud Foundry platform, then you must manually update the application manifest as described [below](#console-fails-to-start). ## Associate Cloud Foundry database service Follow instructions [here](db-migration/README.md). @@ -149,7 +130,7 @@ applications: disk_quota: 256M host: console timeout: 180 - buildpack: https://github.com/SUSE/stratos-buildpack + buildpack: https://github.com/cloudfoundry-incubator/stratos-buildpack health-check-type: port env: CF_API_URL: https://<>> @@ -166,7 +147,7 @@ applications: disk_quota: 256M host: console timeout: 180 - buildpack: https://github.com/SUSE/stratos-buildpack + buildpack: https://github.com/cloudfoundry-incubator/stratos-buildpack health-check-type: port env: CF_API_FORCE_SECURE: true diff --git a/docs/developers-guide.md b/docs/developers-guide.md index d3528a0435..37892d4540 100644 --- a/docs/developers-guide.md +++ b/docs/developers-guide.md @@ -116,5 +116,53 @@ Run `npm run e2e` to execute the end-to-end tests via [Protractor](http://www.pr ## Backend Development The backend (more informally called the portal-proxy or 'pp' for short) is still to be ported over from V1 of -[Stratos](https://github.com/SUSE/stratos-ui). Once that's completed come back and check out this section for instructions on how to +[Stratos](https://github.com/cloudfoundry-incubator/stratos). Once that's completed come back and check out this section for instructions on how to make changes to it. + +WIP + +### Getting started + +The portal-proxy is the back-end for the Console UI. It is written in Go. + +### Automatically register and connect to an existing endpoint +To automatically register a Cloud Foundry add the environment variable below + +> **Note** On log in the console will also attempt to auto-connect to the cloud foundry using + the username/password provided. + +``` +AUTO_REG_CF_URL= +``` + +This env var can be set in `outputs/config.properties` if running the backend locally in the host machine, `./deploy/proxy.env` if running in docker-compose or `./manifest` if in cf push. + +> **NOTE** WIP Instructions! + +#### Introduction +* Golang +* Dependency Management (Glide) + +#### Dependencies +* go + * GOPATH, GOBIN env vars set +* glide +* UAA instance + +#### Running portal-proxy in a container +* Follow instructions in the deploy/docker-compose docs +* To apply changes (build and update docker image) simply run `deploy/tools/restart_proxy.sh` + +#### Running "like a dev" + +1. Set up developer certs + - Execute `deploy/tools/generate_cert.sh` + - Copy `portal-proxy-output/dev-certs` to `./` +1. Update `build/dev_config.json` with `"localDevBuild": true` +1. Run `gulp local-dev-build` +1. cd ./outputs +1. Run `gulp build-backend` +1. Update `config.propeties` and ensure that.. + - the UAA points to a valid instance + - the `CONSOLE_CLIENT` and `CONSOLE_ADMIN_SCOPE` are valid in the UAA instance +1. Run `portal-proxy` diff --git a/docs/development.md b/docs/development.md deleted file mode 100644 index 40cba69e92..0000000000 --- a/docs/development.md +++ /dev/null @@ -1,284 +0,0 @@ -# Developing the Stratos Console - -> **Note:** This document is work in progress. - - -The Stratos Console UI provides a single comprehensive and ubiquitous user -experience for: discovering, composing, developing and managing Cloud Native -workloads which are hosted in a myriad of: public, managed and private -cloud/compute providers. - -1. [Working on the front-end component](#working-on-the-front-end-component) -2. [Working on the back-end component](#working-on-the-backend-component) -3. [Testing](#testing) - -## Dependencies - -### Node -Please check the `engine` entry of the package.json file for the required node version. - > **Note:** To manage node versions we recommend using [nvm](https://github.com/creationix/nvm) - -## Components - -The top-level repository contains three folders worth calling out: - -Folder | Description --------|------------ -build | Contains build scripts (mainly gulp-based) for both the backend and frontend -components | Contains the source code for the Console -deploy | Contains scripts and artifacts for deploying the Console. - -The Console code can be found in the [components](../components) folder. The following components are currently included: - -Component Name | Description ----------------|------------ -about-app | Provides an "about" view for the Console -app-core | Contains core functionality, such as login and navigation -app-framework | Providers a set of UI widgets and utilities -app-theme | Style information for the app-framework -cloud-foundry | All Cloud Foundry specific code -cloud-foundry-hosting | Specific component used for hosting the Console as an app in a single Cloud Foundry -endpoints-dashboard | Manage Console endpoints, specifically Cloud Foundry's. The inclusion allows additional Cloud Foundry endpoints to be added -suse-branding | Overrides styles to show the Console with SUSE branding - -### Component architecture - -Components can include both frontend and backend code. The source code for these sits together under a single component folder. - -The Console Frontend is written using AngularJS and the backend written in Go. - -The Console build determines the location of the frontend and backend code via a configuration file -named `[component name].component.json`. -When no frontend configuration is found in *.component.json it is assumed the component is purely for the frontend and that its source -code sites directly in the component folder, father than a `frontend` subfolder. - -### Including your own components -Other components can be included to add additional items to the navigation bar and their associated content. This can include additional types of 'endpoints' (an existing endpoint for example is Cloud Foundry). Instructions on how to carry out this will be added at a later date. - -## Working on the front end component - -### Source Code Structure - -The frontend code is split into component directories as listed above. -The standard set of components that exist in the console contain functionality -to manage Cloud Foundry instances and their applications. - -The frontend code is usually found within a `frontend` folder and contains -a structure such as that in app-core/frontend component, for example: -``` -|-- frontend -| |-- assets -| |-- i18n -| | -- en -| |-- src -| | |-- api -| | |-- model -| | |-- utils -| | |-- view -| |-- test -| `-- index.html -|-- app-core.component.json -`-- bower.json -``` - -Directory | Contains -----------|------------ -assets | Any images required by the front end -i18n | Internationalization strings per locale. By default the console contains English (US) -src | Javascript, html and scss related to the component -test | Unit tests for the component - -> **Note:** The bower.json is in the root of the component - -### Style Sheets -The frontend defines styles in SCSS which is converted to CSS at build time. -Each component is responsible for specifying it's root scss as a 'main' file -in it's bower.json. From this all other -component scss are gathered. - -### Build Process -The build process uses gulp, see the the root gulpfile.js. Below is a list -of important gulp tasks. - -Gulp task name | Description -----------|------------ -clean | Removes the dist folder -dev | Executes a developer build and serves the console via browser sync -run | Executes a production build and serves the console via express -lint | Executes linting via eslint. See ./.eslintrc for rules - -> **Note:** When using the `dev` task, web sockets do not get forwarded, so log streaming and ssh access will not work - use the `run` task to test these. - -Some tasks can be accessed via npm, by running `npm script target` along with additional test -functionality: - -NPM script name | Description -----------------|------------ -lint | Same as gulp lint -coverage | Executes both unit and e2e tests and provides a combined coverage report in `./out/coverage-report` -gate-check | Executes lint and unit tests, very handy to use before creating a PR -e2e | Executes end to end tests via protractor, also handy to use before creating a PR. Screenshots of the console for each failure can be found in ./out/e2e-failures -test | Executes unit tests - - -### Run the frontend via gulp - -#### Requirements -The Console backend must be up and contactable by the developers machine. -This can be achieved via any of the methods -described in the [deploy](../deploy/README.md) instructions. - -#### Configuration -The Console frontend must know the address of the backend. This can be -set by creating the file ./build/dev_config.json -with contents such as -``` -{ - "pp": "/pp" -} -``` - -For example, if the console was deployed and accessible via `https://localhost` - -the following configuration should be used -``` -{ - "pp": "https://localhost/pp" -} -``` -Run the following commands to install the dependencies -``` -$ npm install -$ bower install - -``` - -#### Run -To run the frontend with bits as if it were production (uses minified resources) execute ... -``` -$ gulp run -``` - -To run the frontend in development mode (uses non-minified resources and serves via browsersync) execute ... -``` -$ gulp dev -``` - -In both cases the console should be available via https://localhost:3100 - -> **Note:** If you see the following error when running 'gulp dev' you may need to increase your OS ulimit. -``` -Error: ENFILE: file table overflow, scandir ; - at Error (native) --bash: /dev/null: Too many open files in system -``` - -### Linting -We use eslint to executing linting. To run these execute... -``` -$ gulp lint -``` - - -### Creating a successful Pull Request in github -For every new pull request, or commit to an existing request, the CI will -run a build against the requests HEAD. Before creating a PR or pushing to -one please ensure the following two requests execute successfully - -``` -$ npm run gate-check -``` -(lint + unit tests) - -``` -$ npm run e2e -``` -(e2e tests) - -## Working on the backend component - -### Getting started - -The portal-proxy is the back-end for the Console UI. It is written in Go. - -### Automatically register and connect to an existing endpoint -To automatically register a Cloud Foundry add the environment variable below - -> **Note** On log in the console will also attempt to auto-connect to the cloud foundry using - the username/password provided. - -``` -AUTO_REG_CF_URL= -``` - -This env var can be set in `outputs/config.properties` if running the backend locally in the host machine, `./deploy/proxy.env` if running in docker-compose or `./manifest` if in cf push. - -> **NOTE** WIP Instructions! - -#### Introduction -* Golang -* Dependency Management (Glide) - -#### Dependencies -* go - * GOPATH, GOBIN env vars set -* glide -* UAA instance - -#### Running portal-proxy in a container -* Follow instructions in the deploy/docker-compose docs -* To apply changes (build and update docker image) simply run `deploy/tools/restart_proxy.sh` - -#### Running "like a dev" - -1. Set up developer certs - - Execute `deploy/tools/generate_cert.sh` - - Copy `portal-proxy-output/dev-certs` to `./` -1. Update `build/dev_config.json` with `"localDevBuild": true` -1. Run `gulp local-dev-build` -1. cd ./outputs -1. Run `gulp build-backend` -1. Update `config.propeties` and ensure that.. - - the UAA points to a valid instance - - the `CONSOLE_CLIENT` and `CONSOLE_ADMIN_SCOPE` are valid in the UAA instance -1. Run `portal-proxy` - -#### Tests - -##### Unit Testing - - -## Testing - -### Front End Unit Tests -Unit test are written via jasmine and executed in karma. To run these execute... -``` -$ npm test -``` - -### End to End Tests -To run e2e tests a cloud foundry with specific orgs, spaces and users is required. - -To set this up -1. Ensure the cf cli tool is installed. See https://github.com/cloudfoundry/cli -2. cf tool has targeted the cf instance and logged in (cf api, cf login) -3. Execute the following script to set up the SUSE org, dev space and e2e user - -**NOTE** This will also create an application which will continually output log statements. This will be used to test - the log reader. -``` -$ ./test/e2e/config-cf.sh -``` -4. Copy ./build/secrets.json.sample to ./build/secrets.json and update cloudFoundry url and cf admin username/password -5. Execute the tests via... -``` -$ npm run e2e -``` - -#### Continuous Integration - -Pull request submitted to the stratos-ui project will run through -frontend unit tests, backend unit tests and integration tests. The -concourse server which executes these is currently not available -externally. The result however can still be seen by the usual -indications posted by github to the PR's page. diff --git a/docs/features.md b/docs/features.md index d1d3194d88..596729caa3 100644 --- a/docs/features.md +++ b/docs/features.md @@ -1,5 +1,7 @@ # Features +> **NOTE** This branch is a work-in-progress Angular 2.x version of Stratos. For the current Angular 1 based version see [https://github.com/cloudfoundry-incubator/stratos/tree/master](https://github.com/cloudfoundry-incubator/stratos/tree/master). This version is at an early development stage and we welcome feedback, input and contributions. This version does not currently have feature parity with the Angular 1 version specified below - see the [Development Roadmap](docs/roadmap.md) for more information. + Stratos provides the feature set outline below. Some of these are illustrated in the [Screenshots gallery](images/screenshots/README.md). * Authentication diff --git a/docs/i18n.md b/docs/i18n.md index 3f7bb092d4..0fc6e721e9 100644 --- a/docs/i18n.md +++ b/docs/i18n.md @@ -1,92 +1,3 @@ # Internationalization (i18n) -Stratos supports i18n via Angular's [$translate service](https://github.com/angular-translate/angular-translate). Dates and times are handled by [Moment](https://momentjs.com/)'s i18n process. -The locale shown will at first be selected from the browser. Any change of locale by the user will be retained within the browser's session. - -The source for all translations can be found in the relevant component's i18n folder. For example: - -``` -|-- i18n -| |-- en_US -``` - -or - -``` -|-- frontend -| |-- i18n -| |-- en_US -``` - -Within each locale will be one or more json files. All json files within a locale will be combined and served as i18n/locale-`locale name`.json, for example locale-en_US.json. - -## Supported Locales -The Stratos UI Console is currently provided with a single locale - US English (en-US). Other locales can easily be added as needed. - -## Localization (i10n) -To add a new locale run through the following steps. - -1. Copy the i18n/en_US directory in every plugin and rename it for the required locale. For instance here en_GB is added -``` -|-- frontend -| |-- i18n -| |-- en_US -| |-- en_GB -``` -2. Add the new locale to the selection of locales to choose. For example -* Open the file `components/app-core/frontend/i18n/en_US/locales.json` -* Add the new locale -``` -{ - "locales": { - "locales": "en_US,fr,en_GB", - "en_US": "English (US)", - "en_GB": "English (UK)" - } -} -``` -* Repeat for all versions of the 'locales' object in all other locale files. - -3. Start the stratos ui and the new locale should be visible in the change language drop down. -4. Go through all the new locale's json files and update with the required translations. - -## i10n Useful Information -* To find a string in HTML/JS flatten the property path. For example -``` -{ - "login": { - "timeout": { - "prompt": "Are you still there?" - } - } -} -``` -Becomes -``` -login.timeout.prompt -``` -* If any string is missing it's translation it will appear as a warning in the browser's console output -* Some strings use substitution. For example consider the following -``` -{ - "login": { - "login": "Login", - "console": "@:product.console", - "title": "[[@:product.name]] [[@:product.version]]", - "welcome": "Use the [[@:product.console]] to develop, compose, and manage Cloud Native workloads.", - } -} -``` -login.console will be replaced with the entry for product.console. If a string contains more than the direct replacement then the [[@:]] notation should be used. -* Some strings can contain dynamic content. For exammple -``` -{ - "login": { - "timeout": { - "notice": "You have been inactive for a while. For your protection, we will automatically log you out in {{timeout}}", - } - } -} -``` -login.timeout.notice contains a countdown in seconds until the user is automatically logged out '{{timeout}}'. The text within the curly brackets should not be changed but it's location within the string is for the translator to decide - \ No newline at end of file +WIP From 6192c7e7405bd738a0fe6ad3aeceff7a2c0359be Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Tue, 13 Feb 2018 16:47:26 +0000 Subject: [PATCH 12/23] Create neater, smaller list header - Moves smaller header flush with table - Smaller header look and feel the same when switching between app wall list and card view - BEMin --- .gitignore | 1 + .../variables-tab.component.html | 2 +- .../variables-tab.component.scss | 9 + .../list/list-cards/cards.component.scss | 1 + .../list/list-table/table.component.html | 2 - .../list/list-table/table.component.scss | 1 - .../components/list/list.component.html | 175 +++++++++--------- .../components/list/list.component.scss | 86 ++++----- .../shared/components/list/list.component.ts | 14 ++ 9 files changed, 150 insertions(+), 141 deletions(-) diff --git a/.gitignore b/.gitignore index 0578047c63..a4084e37b1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # compiled output /dist +.dist /tmp /out-tsc /out diff --git a/src/frontend/app/features/applications/application/application-tabs-base/tabs/variables-tab/variables-tab.component.html b/src/frontend/app/features/applications/application/application-tabs-base/tabs/variables-tab/variables-tab.component.html index 0c15bc32b3..f1ac432f0a 100644 --- a/src/frontend/app/features/applications/application/application-tabs-base/tabs/variables-tab/variables-tab.component.html +++ b/src/frontend/app/features/applications/application/application-tabs-base/tabs/variables-tab/variables-tab.component.html @@ -1,7 +1,7 @@
-
+
diff --git a/src/frontend/app/features/applications/application/application-tabs-base/tabs/variables-tab/variables-tab.component.scss b/src/frontend/app/features/applications/application/application-tabs-base/tabs/variables-tab/variables-tab.component.scss index a6278dcca7..76ca0d369b 100644 --- a/src/frontend/app/features/applications/application/application-tabs-base/tabs/variables-tab/variables-tab.component.scss +++ b/src/frontend/app/features/applications/application/application-tabs-base/tabs/variables-tab/variables-tab.component.scss @@ -11,4 +11,13 @@ } } } + &__env-table--add-form { + form { + display: flex; + flex-direction: row; + } + mat-form-field { + padding-right: 10px; + } + } } diff --git a/src/frontend/app/shared/components/list/list-cards/cards.component.scss b/src/frontend/app/shared/components/list/list-cards/cards.component.scss index 118ab7a93e..1a30ec14da 100644 --- a/src/frontend/app/shared/components/list/list-cards/cards.component.scss +++ b/src/frontend/app/shared/components/list/list-cards/cards.component.scss @@ -2,6 +2,7 @@ display: flex; flex-flow: wrap; justify-content: space-between; + padding-top: 20px; .mat-card { &::after { bottom: 0; diff --git a/src/frontend/app/shared/components/list/list-table/table.component.html b/src/frontend/app/shared/components/list/list-table/table.component.html index e73f029d41..f7eea57052 100644 --- a/src/frontend/app/shared/components/list/list-table/table.component.html +++ b/src/frontend/app/shared/components/list/list-table/table.component.html @@ -1,7 +1,5 @@
- - diff --git a/src/frontend/app/shared/components/list/list-table/table.component.scss b/src/frontend/app/shared/components/list/list-table/table.component.scss index 06a34312de..4b4c038849 100644 --- a/src/frontend/app/shared/components/list/list-table/table.component.scss +++ b/src/frontend/app/shared/components/list/list-table/table.component.scss @@ -2,7 +2,6 @@ $row-height: 62px; .app-table { &__card { padding: 0; - padding-top: 24px; } &__inner { &[hidden] * { diff --git a/src/frontend/app/shared/components/list/list.component.html b/src/frontend/app/shared/components/list/list.component.html index a9a37cb764..7ac9bcf22e 100644 --- a/src/frontend/app/shared/components/list/list.component.html +++ b/src/frontend/app/shared/components/list/list.component.html @@ -1,109 +1,104 @@
+ - -
-
-
{{ config.text?.title }}
-
{{dataSource.selectedRows.size}} Selected
-
-
-
- - - -
-
- - - -
-
-
- - - -
-
- -
-
- - -
-
+
+
{{ config.text?.title }}
+
{{dataSource.selectedRows.size}} Selected
+ +
+ + + All + + {{selectItem.label}} + + + {{multiFilterConfig.label}} +
-
-
-
- - - All - - {{selectItem.label}} - - - -
-
-
-
- - - - - - {{column.headerCell()}} - - - -
-
- - - -
-
+
+
+ +
+ + + +
+ +
+ + + + + + {{column.headerCell()}} + + + +
+ +
+ + + +
+ +
+ + + +
+ +
+
+ + + +
+ +
+ +
+ +
+ +
- - -
-
-
-
+
- - + +
- + - +
There are no entries.
diff --git a/src/frontend/app/shared/components/list/list.component.scss b/src/frontend/app/shared/components/list/list.component.scss index cc4c5428ca..088babff06 100644 --- a/src/frontend/app/shared/components/list/list.component.scss +++ b/src/frontend/app/shared/components/list/list.component.scss @@ -1,9 +1,11 @@ .list-component { - *[hidden], - .sort[hidden], - .filter[hidden], - .add-container[hidden] { - display: none; + *, + .sort, + .filter, + .add-container { + &[hidden] { + display: none; + } } &__blocker { bottom: 0; @@ -16,69 +18,59 @@ &__body-inner { position: relative; } - &__header { - &__left[hidden], - &__right[hidden], - &__top[hidden], - &__bottom[hidden] { - display: none; - } - } $title-left-padding: 25px; $title-content-padding: 10px; &__header { - mat-card-header { - padding: 15px; + position: relative; + mat-progress-bar { + position: absolute; + top: -5px; } - &__left, - &__right, - &__top, - &__bottom { + mat-card { display: flex; flex-direction: row; - align-items: center; - height: 55px; } &__left, &__right { - > div:not(:last-of-type) { - padding-right: $title-content-padding; + align-items: center; + display: flex; + flex: 0; + flex-direction: row; + > div { + &:not(:last-of-type) { + padding-right: $title-content-padding; + } + &:not([hidden]) { + align-items: center; + display: flex; + } } } - &__left .multi-filters mat-form-field { - padding-right: $title-content-padding; - } - &__right { - .add-container { - display: flex; - align-items: center; - } - .mat-paginator { - padding: 0; + &__left { + flex-grow: 99; + &--multi-filters { + mat-form-field { + padding-right: $title-content-padding; + } } } - &__bottom { + &__right { justify-content: flex-end; - align-items: flex-end; } - mat-card { - padding: 0 10px; - mat-card-header { - display: flex; - flex-direction: column; - div:not([hidden]).spacer { - flex-grow: 99; - } - } + & > mat-card { + min-height: 74px; + padding: 0 24px; + width: 100%; } } &__body { - padding-top: 15px; .no-rows { padding: 20px $title-left-padding; } - mat-card[hidden] { - display: none; + mat-card { + &[hidden] { + display: none; + } } } &__paginator { diff --git a/src/frontend/app/shared/components/list/list.component.ts b/src/frontend/app/shared/components/list/list.component.ts index c8fe2db7c6..67147cfccb 100644 --- a/src/frontend/app/shared/components/list/list.component.ts +++ b/src/frontend/app/shared/components/list/list.component.ts @@ -27,6 +27,8 @@ import { IMultiListAction, ListConfig, } from './list.component.types'; +import { combineLatest } from 'rxjs/observable/combineLatest'; +import { map } from 'rxjs/operators'; @Component({ @@ -62,6 +64,9 @@ export class ListComponent implements OnInit, OnDestroy, AfterViewInit { paginationController: IListPaginationController; multiFilterWidgetObservables = new Array(); + isAddingOrSelecting$: Observable; + hasRows$: Observable; + public safeAddForm() { // Something strange is afoot. When using addform in [disabled] it thinks this is null, even when initialised // When applying the question mark (addForm?) it's value is ignored by [disabled] @@ -82,6 +87,15 @@ export class ListComponent implements OnInit, OnDestroy, AfterViewInit { this.dataSource = this.config.getDataSource(); this.multiFilterConfigs = this.config.getMultiFiltersConfigs(); + // Create convenience observables that make the html clearer + this.isAddingOrSelecting$ = combineLatest( + this.dataSource.isAdding$, + this.dataSource.isSelecting$ + ).pipe( + map(([isAdding, isSelecting]) => isAdding || isSelecting) + ); + this.hasRows$ = this.dataSource.pagination$.map(pag => pag.totalResults > 0); + // Set up an observable containing the current view (card/table) const { view, } = getListStateObservables(this.store, this.dataSource.paginationKey); this.view$ = view; From a87163f79b7894894be63acca63c2d930c6034b3 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Tue, 13 Feb 2018 16:47:45 +0000 Subject: [PATCH 13/23] Make the header more responsive --- .../components/list/list.component.scss | 4 --- .../components/list/list.component.theme.scss | 29 +++++++++++++++++-- src/frontend/sass/_all-theme.scss | 16 ++-------- 3 files changed, 29 insertions(+), 20 deletions(-) diff --git a/src/frontend/app/shared/components/list/list.component.scss b/src/frontend/app/shared/components/list/list.component.scss index 088babff06..e020869d75 100644 --- a/src/frontend/app/shared/components/list/list.component.scss +++ b/src/frontend/app/shared/components/list/list.component.scss @@ -28,7 +28,6 @@ } mat-card { display: flex; - flex-direction: row; } &__left, &__right { @@ -54,9 +53,6 @@ } } } - &__right { - justify-content: flex-end; - } & > mat-card { min-height: 74px; padding: 0 24px; diff --git a/src/frontend/app/shared/components/list/list.component.theme.scss b/src/frontend/app/shared/components/list/list.component.theme.scss index 9e7681e42e..56ab0c5e93 100644 --- a/src/frontend/app/shared/components/list/list.component.theme.scss +++ b/src/frontend/app/shared/components/list/list.component.theme.scss @@ -1,9 +1,32 @@ @import '~@angular/material/theming'; -@mixin variables-tab-theme($theme, $app-theme) { +@import '../../../../sass/mixins'; +@mixin list-theme($theme, $app-theme) { $primary: map-get($theme, primary); $foreground-colors: map-get($theme, foreground); $background-colors: map-get($theme, background); - .list-component__header.selected .mat-card { - background-color: mat-color($primary, 50); + .list-component { + &__header { + &.selected { + .mat-card { + background-color: mat-color($primary, 50); + } + } + mat-card { + flex-direction: column; + @include breakpoint(desktop) { + flex-direction: row; + } + @include breakpoint(tablet) { + // background-color: yellow; + // flex-direction: row; + } + } + &__right { + justify-content: flex-start; + @include breakpoint(desktop) { + justify-content: flex-end; + } + } + } } } diff --git a/src/frontend/sass/_all-theme.scss b/src/frontend/sass/_all-theme.scss index a1bb56bbeb..02e946c050 100644 --- a/src/frontend/sass/_all-theme.scss +++ b/src/frontend/sass/_all-theme.scss @@ -47,25 +47,15 @@ $side-nav-light-active: #484848; } html { background-color: $app-background-color; - } - - // App Theme defines a set of colors used by stratos components - $app-theme: ( - app-background-color: $app-background-color, - app-background-text-color: rgba(mat-color($foreground-colors, base), .65), - side-nav: app-generate-nav-theme($theme, $nav-theme), - status: app-generate-status-theme($theme, $status-theme), - subdued-color: $subdued - ); - - // Pass the Material theme and the App Theme to components that need to be themed + } // App Theme defines a set of colors used by stratos components + $app-theme: ( app-background-color: $app-background-color, app-background-text-color: rgba(mat-color($foreground-colors, base), .65), side-nav: app-generate-nav-theme($theme, $nav-theme), status: app-generate-status-theme($theme, $status-theme), subdued-color: $subdued); // Pass the Material theme and the App Theme to components that need to be themed @include dialog-error-theme($theme, $app-theme); @include login-page-theme($theme, $app-theme); @include side-nav-theme($theme, $app-theme); @include dashboard-page-theme($theme, $app-theme); @include display-value-theme($theme, $app-theme); @include steppers-theme($theme, $app-theme); - @include variables-tab-theme($theme, $app-theme); + @include list-theme($theme, $app-theme); @include app-base-page-theme($theme, $app-theme); @include app-page-subheader-theme($theme, $app-theme); @include app-mat-tabs-theme($theme, $app-theme); From ad4d25b9e39b43e49d8f8ae09fdba1d0d18eaa78 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Tue, 13 Feb 2018 16:56:28 +0000 Subject: [PATCH 14/23] Lint fixes, fix list text when window width reduced --- .../app/shared/components/list/list.component.html | 6 +++--- .../app/shared/components/list/list.component.scss | 3 +++ .../shared/components/list/list.component.theme.scss | 10 ++-------- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/frontend/app/shared/components/list/list.component.html b/src/frontend/app/shared/components/list/list.component.html index 7ac9bcf22e..503525a46c 100644 --- a/src/frontend/app/shared/components/list/list.component.html +++ b/src/frontend/app/shared/components/list/list.component.html @@ -1,10 +1,10 @@
-
+
-
{{ config.text?.title }}
-
{{dataSource.selectedRows.size}} Selected
+
{{ config.text?.title }}
+
{{dataSource.selectedRows.size}} Selected
diff --git a/src/frontend/app/shared/components/list/list.component.scss b/src/frontend/app/shared/components/list/list.component.scss index e020869d75..029846f3ee 100644 --- a/src/frontend/app/shared/components/list/list.component.scss +++ b/src/frontend/app/shared/components/list/list.component.scss @@ -47,6 +47,9 @@ } &__left { flex-grow: 99; + &--text { + padding: 24px 0; // Valid when window shrunk + } &--multi-filters { mat-form-field { padding-right: $title-content-padding; diff --git a/src/frontend/app/shared/components/list/list.component.theme.scss b/src/frontend/app/shared/components/list/list.component.theme.scss index 56ab0c5e93..750a534718 100644 --- a/src/frontend/app/shared/components/list/list.component.theme.scss +++ b/src/frontend/app/shared/components/list/list.component.theme.scss @@ -6,20 +6,14 @@ $background-colors: map-get($theme, background); .list-component { &__header { - &.selected { - .mat-card { - background-color: mat-color($primary, 50); - } + &--selected { + background-color: mat-color($primary, 50); } mat-card { flex-direction: column; @include breakpoint(desktop) { flex-direction: row; } - @include breakpoint(tablet) { - // background-color: yellow; - // flex-direction: row; - } } &__right { justify-content: flex-start; From 4b4f1e7eb9b21cf80a2b7f559324b01eea97a55e Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Tue, 13 Feb 2018 17:01:18 +0000 Subject: [PATCH 15/23] Header colour when selected --- src/frontend/app/shared/components/list/list.component.html | 4 ++-- src/frontend/app/shared/components/list/list.component.scss | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/frontend/app/shared/components/list/list.component.html b/src/frontend/app/shared/components/list/list.component.html index 503525a46c..89286febc6 100644 --- a/src/frontend/app/shared/components/list/list.component.html +++ b/src/frontend/app/shared/components/list/list.component.html @@ -1,7 +1,7 @@
-
+
- +
{{ config.text?.title }}
{{dataSource.selectedRows.size}} Selected
diff --git a/src/frontend/app/shared/components/list/list.component.scss b/src/frontend/app/shared/components/list/list.component.scss index 029846f3ee..c181cb261d 100644 --- a/src/frontend/app/shared/components/list/list.component.scss +++ b/src/frontend/app/shared/components/list/list.component.scss @@ -48,7 +48,7 @@ &__left { flex-grow: 99; &--text { - padding: 24px 0; // Valid when window shrunk + padding: 12px 0; // Valid when window shrunk } &--multi-filters { mat-form-field { From 3e20eb06357ef1f2cc7de107e7c009d9787b904f Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Wed, 14 Feb 2018 11:20:24 +0000 Subject: [PATCH 16/23] Fix org/space shemas (#1601) * Fix org/space shemas - Selecting cf/org/spaces in filter or add/deploy app should work again - Only have one type of Space/Org Schema * Add missing file --- .../applications/application.service.ts | 15 ++- .../create-application-step3.component.ts | 4 +- .../deploy-application-step3.component.ts | 95 +++++++++---------- .../cf-org-space-service.service.ts | 7 +- .../app/store/actions/action-types.ts | 28 ++++++ .../app/store/actions/application.actions.ts | 4 +- .../app/store/actions/organisation.action.ts | 30 ------ .../app/store/actions/organisation.actions.ts | 52 ++++++++++ .../app/store/actions/organization.actions.ts | 42 -------- .../app/store/actions/space.action.ts | 35 ------- .../app/store/actions/space.actions.ts | 43 ++++++--- 11 files changed, 173 insertions(+), 182 deletions(-) create mode 100644 src/frontend/app/store/actions/action-types.ts delete mode 100644 src/frontend/app/store/actions/organisation.action.ts create mode 100644 src/frontend/app/store/actions/organisation.actions.ts delete mode 100644 src/frontend/app/store/actions/organization.actions.ts delete mode 100644 src/frontend/app/store/actions/space.action.ts diff --git a/src/frontend/app/features/applications/application.service.ts b/src/frontend/app/features/applications/application.service.ts index 33d50f7ade..1267829c4f 100644 --- a/src/frontend/app/features/applications/application.service.ts +++ b/src/frontend/app/features/applications/application.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; import { Observable } from 'rxjs/Observable'; -import { map } from 'rxjs/operators'; +import { map, mergeMap } from 'rxjs/operators'; import { EntityService } from '../../core/entity-service'; import { EntityServiceFactory } from '../../core/entity-service-factory.service'; @@ -18,7 +18,6 @@ import { } from '../../store/actions/app-metadata.actions'; import { GetApplication, UpdateApplication, UpdateExistingApplication } from '../../store/actions/application.actions'; import { ApplicationSchema } from '../../store/actions/application.actions'; -import { SpaceSchema } from '../../store/actions/space.action'; import { AppState } from '../../store/app-state'; import { ActionState } from '../../store/reducers/api-request-reducer/types'; import { selectEntity } from '../../store/selectors/api.selectors'; @@ -46,6 +45,7 @@ import { } from './application/application-tabs-base/tabs/build-tab/application-env-vars.service'; import { getRoute, isTCPRoute } from './routes/routes.helper'; import { PaginationMonitor } from '../../shared/monitors/pagination-monitor'; +import { spaceSchemaKey, organisationSchemaKey } from '../../store/actions/action-types'; export interface ApplicationData { fetching: boolean; @@ -157,9 +157,14 @@ export class ApplicationService { .filter(entityInfo => entityInfo.entity && entityInfo.entity.entity && entityInfo.entity.entity.cfGuid) .map(entityInfo => entityInfo.entity.entity) .do(app => { - this.appSpace$ = this.store.select(selectEntity(SpaceSchema.key, app.space_guid)); - // See https://github.com/SUSE/stratos/issues/158 (Failing to populate entity store with a space's org) - this.appOrg$ = this.store.select(selectEntity(SpaceSchema.key, app.space_guid)).map(space => space.entity.organization); + + this.appSpace$ = this.store.select(selectEntity(spaceSchemaKey, app.space_guid)); + this.appOrg$ = this.appSpace$.pipe( + map(space => space.entity.organization_guid), + mergeMap(orgGuid => { + return this.store.select(selectEntity(organisationSchemaKey, orgGuid)); + }) + ); }) .take(1) .subscribe(); diff --git a/src/frontend/app/features/applications/create-application/create-application-step3/create-application-step3.component.ts b/src/frontend/app/features/applications/create-application/create-application-step3/create-application-step3.component.ts index 8464c650c1..f73bd5d9ed 100644 --- a/src/frontend/app/features/applications/create-application/create-application-step3/create-application-step3.component.ts +++ b/src/frontend/app/features/applications/create-application/create-application-step3/create-application-step3.component.ts @@ -18,8 +18,8 @@ import { AppState } from '../../../../store/app-state'; import { selectNewAppState } from '../../../../store/effects/create-app-effects'; import { CreateNewApplicationState } from '../../../../store/types/create-application.types'; import { RouterNav } from '../../../../store/actions/router.actions'; -import { OrganisationSchema } from '../../../../store/actions/organisation.action'; import { RequestInfoState } from '../../../../store/reducers/api-request-reducer/types'; +import { organisationSchemaKey } from '../../../../store/actions/action-types'; @Component({ selector: 'app-create-application-step3', @@ -124,7 +124,7 @@ export class CreateApplicationStep3Component implements OnInit { }) .filter(state => state.cloudFoundryDetails && state.cloudFoundryDetails.org) .mergeMap(state => { - return this.store.select(selectEntity(OrganisationSchema.key, state.cloudFoundryDetails.org)) + return this.store.select(selectEntity(organisationSchemaKey, state.cloudFoundryDetails.org)) .first() .map(org => org.entity.domains); }); diff --git a/src/frontend/app/features/applications/deploy-application/deploy-application-step3/deploy-application-step3.component.ts b/src/frontend/app/features/applications/deploy-application/deploy-application-step3/deploy-application-step3.component.ts index e8653b710d..c0aa14ad44 100644 --- a/src/frontend/app/features/applications/deploy-application/deploy-application-step3/deploy-application-step3.component.ts +++ b/src/frontend/app/features/applications/deploy-application/deploy-application-step3/deploy-application-step3.component.ts @@ -4,9 +4,7 @@ import { Store } from '@ngrx/store'; import { AppState } from '../../../../store/app-state'; import { tap, filter, map, mergeMap, combineLatest, switchMap, share, catchError } from 'rxjs/operators'; import { getEntityById, selectEntity, selectEntities } from '../../../../store/selectors/api.selectors'; -import { OrganizationSchema } from '../../../../store/actions/organization.actions'; import { DeleteDeployAppSection } from '../../../../store/actions/deploy-applications.actions'; -import { SpaceSchema } from '../../../../store/actions/space.actions'; import websocketConnect from 'rxjs-websockets'; import { QueueingSubject } from 'queueing-subject/lib'; import { Subscription } from 'rxjs/Subscription'; @@ -20,6 +18,7 @@ import { RouterNav } from '../../../../store/actions/router.actions'; import { GetAllApplications } from '../../../../store/actions/application.actions'; import { environment } from '../../../../../environments/environment'; import { CfOrgSpaceDataService } from '../../../../shared/data-services/cf-org-space-service.service'; +import { organisationSchemaKey, spaceSchemaKey } from '../../../../store/actions/action-types'; @Component({ selector: 'app-deploy-application-step3', @@ -58,9 +57,9 @@ export class DeployApplicationStep3Component implements OnInit, OnDestroy { && !!appDetail.applicationSource && !!appDetail.applicationSource.projectName), mergeMap(p => { - const orgSubscription = this.store.select(selectEntity(OrganizationSchema.key, p.cloudFoundryDetails.org)); - const spaceSubscription = this.store.select(selectEntity(SpaceSchema.key, p.cloudFoundryDetails.space)); - return Observable.of(p).combineLatest(orgSubscription, spaceSubscription ); + const orgSubscription = this.store.select(selectEntity(organisationSchemaKey, p.cloudFoundryDetails.org)); + const spaceSubscription = this.store.select(selectEntity(spaceSchemaKey, p.cloudFoundryDetails.space)); + return Observable.of(p).combineLatest(orgSubscription, spaceSubscription); }), tap(p => { const host = window.location.host; @@ -70,10 +69,10 @@ export class DeployApplicationStep3Component implements OnInit, OnDestroy { `?org=${p[1].entity.name}&space=${p[2].entity.name}` ); - const inputStream = new QueueingSubject(); + const inputStream = new QueueingSubject(); this.messages = websocketConnect(streamUrl, inputStream) - .messages.pipe( - catchError(e => { + .messages.pipe( + catchError(e => { return []; }), share(), @@ -88,21 +87,21 @@ export class DeployApplicationStep3Component implements OnInit, OnDestroy { this.updateTitle(log); } }), - filter((log ) => log.type === SocketEventTypes.DATA), + filter((log) => log.type === SocketEventTypes.DATA), map((log) => { const timesString = moment(log.timestamp * 1000).format('DD/MM/YYYY hh:mm:ss A'); return ( `${timesString}: ${log.message}` ); }) - ); + ); inputStream.next(this.sendProjectInfo(p[0].applicationSource)); }) ).subscribe(); } - sendProjectInfo = (appSource: DeployApplicationSource) => { + sendProjectInfo = (appSource: DeployApplicationSource) => { if (appSource.type.id === 'git') { if (appSource.type.subType === 'github') { return this.sendGitHubSourceMetadata(appSource); @@ -114,7 +113,7 @@ export class DeployApplicationStep3Component implements OnInit, OnDestroy { return ''; } - sendGitHubSourceMetadata = (appSource: DeployApplicationSource) => { + sendGitHubSourceMetadata = (appSource: DeployApplicationSource) => { const github = { project: appSource.projectName, branch: appSource.branch.name, @@ -129,7 +128,7 @@ export class DeployApplicationStep3Component implements OnInit, OnDestroy { return JSON.stringify(msg); } - sendGitUrlSourceMetadata = (appSource: DeployApplicationSource) => { + sendGitUrlSourceMetadata = (appSource: DeployApplicationSource) => { const giturl = { url: appSource.projectName, branch: appSource.branch.name, @@ -155,53 +154,53 @@ export class DeployApplicationStep3Component implements OnInit, OnDestroy { this.appData.org = this.cfOrgSpaceService.org.select.getValue(); this.appData.space = this.cfOrgSpaceService.space.select.getValue(); break; - case SocketEventTypes.EVENT_PUSH_STARTED : - this.streamTitle = 'Deploying...'; - this.store.dispatch(new GetAllApplications('applicationWall')); - break; - case SocketEventTypes.EVENT_PUSH_COMPLETED : - this.streamTitle = 'Deployed'; - this.apps$ = this.store.select(selectEntities('application')).pipe( - tap(apps => { - Object.values(apps).forEach(app => { - if ( - app.entity.space_guid === this.appData.space && - app.entity.cfGuid === this.appData.cloudFoundry && - app.entity.name === this.appData.Name - ) { - this.appGuid = app.entity.guid; - this.validate = Observable.of(true); - } - }); - }) - ).subscribe(); - break; - case SocketEventTypes.CLOSE_SUCCESS : - this.close(log, null, null, true); - break; + case SocketEventTypes.EVENT_PUSH_STARTED: + this.streamTitle = 'Deploying...'; + this.store.dispatch(new GetAllApplications('applicationWall')); + break; + case SocketEventTypes.EVENT_PUSH_COMPLETED: + this.streamTitle = 'Deployed'; + this.apps$ = this.store.select(selectEntities('application')).pipe( + tap(apps => { + Object.values(apps).forEach(app => { + if ( + app.entity.space_guid === this.appData.space && + app.entity.cfGuid === this.appData.cloudFoundry && + app.entity.name === this.appData.Name + ) { + this.appGuid = app.entity.guid; + this.validate = Observable.of(true); + } + }); + }) + ).subscribe(); + break; + case SocketEventTypes.CLOSE_SUCCESS: + this.close(log, null, null, true); + break; case SocketEventTypes.CLOSE_INVALID_MANIFEST: this.close(log, 'Deploy Failed - Invalid manifest!', - 'Failed to deploy app! Please make sure that a valid manifest.yaml was provided!', true); + 'Failed to deploy app! Please make sure that a valid manifest.yaml was provided!', true); break; case SocketEventTypes.CLOSE_NO_MANIFEST: - this.close(log, 'Deploy Failed - No manifest present!', - 'Failed to deploy app! Please make sure that a valid manifest.yaml is present!', true); + this.close(log, 'Deploy Failed - No manifest present!', + 'Failed to deploy app! Please make sure that a valid manifest.yaml is present!', true); break; case SocketEventTypes.CLOSE_FAILED_CLONE: - this.close(log, 'Deploy Failed - Failed to clone repository!', - 'Failed to deploy app! Please make sure the repository is public!', true); + this.close(log, 'Deploy Failed - Failed to clone repository!', + 'Failed to deploy app! Please make sure the repository is public!', true); break; case SocketEventTypes.CLOSE_FAILED_NO_BRANCH: - this.close(log, 'Deploy Failed - Failed to located branch!', - 'Failed to deploy app! Please make sure that branch exists!', true); + this.close(log, 'Deploy Failed - Failed to located branch!', + 'Failed to deploy app! Please make sure that branch exists!', true); break; case SocketEventTypes.CLOSE_FAILURE: case SocketEventTypes.CLOSE_PUSH_ERROR: case SocketEventTypes.CLOSE_NO_SESSION: case SocketEventTypes.CLOSE_NO_CNSI: case SocketEventTypes.CLOSE_NO_CNSI_USERTOKEN: - this.close(log, 'Deploy Failed!', - 'Failed to deploy app!', true); + this.close(log, 'Deploy Failed!', + 'Failed to deploy app!', true); break; case SocketEventTypes.SOURCE_REQUIRED: case SocketEventTypes.EVENT_CLONED: @@ -209,8 +208,8 @@ export class DeployApplicationStep3Component implements OnInit, OnDestroy { case SocketEventTypes.MANIFEST: break; default: - // noop - } + // noop + } } close(log, title, error, deleteAppSection) { diff --git a/src/frontend/app/shared/data-services/cf-org-space-service.service.ts b/src/frontend/app/shared/data-services/cf-org-space-service.service.ts index b1c80d6657..c4eceaecca 100644 --- a/src/frontend/app/shared/data-services/cf-org-space-service.service.ts +++ b/src/frontend/app/shared/data-services/cf-org-space-service.service.ts @@ -3,12 +3,13 @@ import { Store } from '@ngrx/store'; import { BehaviorSubject } from 'rxjs/BehaviorSubject'; import { Observable } from 'rxjs/Observable'; -import { GetAllOrganizations, OrganizationSchema } from '../../store/actions/organization.actions'; import { AppState } from '../../store/app-state'; import { getPaginationObservables, getCurrentPageRequestInfo } from '../../store/reducers/pagination-reducer/pagination-reducer.helper'; import { endpointsRegisteredEntitiesSelector } from '../../store/selectors/endpoint.selectors'; import { EndpointModel } from '../../store/types/endpoint.types'; import { PaginationMonitorFactory } from '../monitors/pagination-monitor.factory'; +import { GetAllOrganisations } from '../../store/actions/organisation.actions'; +import { OrganisationWithSpaceSchema } from '../../store/actions/action-types'; export interface CfOrgSpaceItem { list$: Observable; @@ -25,7 +26,7 @@ export class CfOrgSpaceDataService { public org: CfOrgSpaceItem; public space: CfOrgSpaceItem; - public paginationAction = new GetAllOrganizations(CfOrgSpaceDataService.CfOrgSpaceServicePaginationKey); + public paginationAction = new GetAllOrganisations(CfOrgSpaceDataService.CfOrgSpaceServicePaginationKey); // TODO: We should optimise this to only fetch the orgs for the current endpoint // (if we inline depth the get orgs request it could be hefty... or we could use a different action to only fetch required data.. @@ -35,7 +36,7 @@ export class CfOrgSpaceDataService { action: this.paginationAction, paginationMonitor: this.paginationMonitorFactory.create( this.paginationAction.paginationKey, - OrganizationSchema + OrganisationWithSpaceSchema ) }); diff --git a/src/frontend/app/store/actions/action-types.ts b/src/frontend/app/store/actions/action-types.ts new file mode 100644 index 0000000000..8c36f51a38 --- /dev/null +++ b/src/frontend/app/store/actions/action-types.ts @@ -0,0 +1,28 @@ +import { schema } from 'normalizr'; +import { getAPIResourceGuid } from '../selectors/api.selectors'; + +export const organisationSchemaKey = 'organization'; +export const OrganisationSchema = new schema.Entity(organisationSchemaKey, {}, { + idAttribute: getAPIResourceGuid +}); + +export const spaceSchemaKey = 'space'; +export const SpaceSchema = new schema.Entity(spaceSchemaKey, {}, { + idAttribute: getAPIResourceGuid +}); + +export const OrganisationWithSpaceSchema = new schema.Entity(organisationSchemaKey, { + entity: { + spaces: [SpaceSchema] + } +}, { + idAttribute: getAPIResourceGuid + }); + +export const SpaceWithOrganisationSchema = new schema.Entity(spaceSchemaKey, { + entity: { + organization: OrganisationSchema + } +}, { + idAttribute: getAPIResourceGuid + }); diff --git a/src/frontend/app/store/actions/application.actions.ts b/src/frontend/app/store/actions/application.actions.ts index 3b2fd53de9..596ddb776e 100644 --- a/src/frontend/app/store/actions/application.actions.ts +++ b/src/frontend/app/store/actions/application.actions.ts @@ -5,7 +5,6 @@ import { Headers, RequestOptions, URLSearchParams } from '@angular/http'; import { schema } from 'normalizr'; import { ApiActionTypes } from './request.actions'; -import { SpaceSchema } from './space.actions'; import { StackSchema } from './stack.action'; import { ActionMergeFunction } from '../types/api.types'; import { PaginatedAction } from '../types/pagination.types'; @@ -14,6 +13,7 @@ import { pick } from '../helpers/reducer.helper'; import { AppMetadataTypes } from './app-metadata.actions'; import { AppStatSchema } from '../types/app-metadata.types'; import { getPaginationKey } from './pagination.actions'; +import { SpaceWithOrganisationSchema } from './action-types'; export const GET_ALL = '[Application] Get all'; export const GET_ALL_SUCCESS = '[Application] Get all success'; @@ -50,7 +50,7 @@ export const DELETE_INSTANCE_FAILED = '[Application Instance] Delete failed'; const ApplicationEntitySchema = { entity: { stack: StackSchema, - space: SpaceSchema + space: SpaceWithOrganisationSchema } }; diff --git a/src/frontend/app/store/actions/organisation.action.ts b/src/frontend/app/store/actions/organisation.action.ts deleted file mode 100644 index b59ddf576a..0000000000 --- a/src/frontend/app/store/actions/organisation.action.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { CFStartAction, IRequestAction, ICFAction } from '../types/request.types'; -import { getAPIResourceGuid } from '../selectors/api.selectors'; -import { schema } from 'normalizr'; -import { ApiActionTypes } from './request.actions'; -import { RequestOptions } from '@angular/http'; - -export const GET = '[Organisation] Get one'; -export const GET_SUCCESS = '[Organisation] Get one success'; -export const GET_FAILED = '[Organisation] Get one failed'; - -export const OrganisationSchema = new schema.Entity('organization', {}, { - idAttribute: getAPIResourceGuid -}); - -export class GetOrganisation extends CFStartAction implements ICFAction { - constructor(public guid: string, public endpointGuid: string) { - super(); - this.options = new RequestOptions(); - this.options.url = `organization/${guid}`; - this.options.method = 'get'; - } - actions = [ - GET, - GET_SUCCESS, - GET_FAILED - ]; - entity = [OrganisationSchema]; - entityKey = OrganisationSchema.key; - options: RequestOptions; -} diff --git a/src/frontend/app/store/actions/organisation.actions.ts b/src/frontend/app/store/actions/organisation.actions.ts new file mode 100644 index 0000000000..b86166eb86 --- /dev/null +++ b/src/frontend/app/store/actions/organisation.actions.ts @@ -0,0 +1,52 @@ +import { RequestOptions } from '@angular/http'; + +import { PaginatedAction } from '../types/pagination.types'; +import { CFStartAction, ICFAction } from '../types/request.types'; +import { OrganisationSchema, organisationSchemaKey, OrganisationWithSpaceSchema } from './action-types'; + +export const GET_ORGANISATION = '[Organisation] Get one'; +export const GET_ORGANISATION_SUCCESS = '[Organisation] Get one success'; +export const GET_ORGANISATION_FAILED = '[Organisation] Get one failed'; + +export const GET_ORGANISATIONS = '[Organization] Get all'; +export const GET_ORGANISATIONS_SUCCESS = '[Organization] Get all success'; +export const GET_ORGANISATIONS_FAILED = '[Organization] Get all failed'; + +export class GetOrganisation extends CFStartAction implements ICFAction { + constructor(public guid: string, public endpointGuid: string) { + super(); + this.options = new RequestOptions(); + this.options.url = `organization/${guid}`; + this.options.method = 'get'; + } + actions = [ + GET_ORGANISATION, + GET_ORGANISATION_SUCCESS, + GET_ORGANISATION_FAILED + ]; + entity = [OrganisationSchema]; + entityKey = organisationSchemaKey; + options: RequestOptions; +} + +export class GetAllOrganisations extends CFStartAction implements PaginatedAction { + constructor(public paginationKey: string) { + super(); + this.options = new RequestOptions(); + this.options.url = 'organizations'; + this.options.method = 'get'; + } + actions = [ + GET_ORGANISATIONS, + GET_ORGANISATIONS_SUCCESS, + GET_ORGANISATIONS_FAILED + ]; + entity = [OrganisationWithSpaceSchema]; + entityKey = organisationSchemaKey; + options: RequestOptions; + initialParams = { + page: 1, + 'results-per-page': 100, + 'inline-relations-depth': 1 + }; +} diff --git a/src/frontend/app/store/actions/organization.actions.ts b/src/frontend/app/store/actions/organization.actions.ts deleted file mode 100644 index d02305b3c1..0000000000 --- a/src/frontend/app/store/actions/organization.actions.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { CFStartAction } from '../types/request.types'; -import { getAPIResourceGuid } from '../selectors/api.selectors'; -import { RequestOptions, URLSearchParams } from '@angular/http'; -import { schema } from 'normalizr'; - -import { ApiActionTypes } from './request.actions'; -import { SpaceSchema } from './space.actions'; -import { PaginatedAction } from '../types/pagination.types'; - -export const GET_ALL = '[Organization] Get all'; -export const GET_ALL_SUCCESS = '[Organization] Get all success'; -export const GET_ALL_FAILED = '[Organization] Get all failed'; - -export const OrganizationSchema = new schema.Entity('organization', { - entity: { - spaces: [SpaceSchema] - } -}, { - idAttribute: getAPIResourceGuid - }); - -export class GetAllOrganizations extends CFStartAction implements PaginatedAction { - constructor(public paginationKey: string) { - super(); - this.options = new RequestOptions(); - this.options.url = 'organizations'; - this.options.method = 'get'; - } - actions = [ - GET_ALL, - GET_ALL_SUCCESS, - GET_ALL_FAILED - ]; - entity = [OrganizationSchema]; - entityKey = OrganizationSchema.key; - options: RequestOptions; - initialParams = { - page: 1, - 'results-per-page': 100, - 'inline-relations-depth': 1 - }; -} diff --git a/src/frontend/app/store/actions/space.action.ts b/src/frontend/app/store/actions/space.action.ts deleted file mode 100644 index 2aaa481675..0000000000 --- a/src/frontend/app/store/actions/space.action.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { CFStartAction, IRequestAction, ICFAction } from '../types/request.types'; -import { getAPIResourceGuid } from '../selectors/api.selectors'; -import { schema } from 'normalizr'; -import { ApiActionTypes } from './request.actions'; -import { RequestOptions } from '@angular/http'; -import { OrganisationSchema } from './organisation.action'; - -export const GET = '[Space] Get one'; -export const GET_SUCCESS = '[Space] Get one success'; -export const GET_FAILED = '[Space] Get one failed'; - -export const SpaceSchema = new schema.Entity('space', { - entity: { - organization: OrganisationSchema - } -}, { - idAttribute: getAPIResourceGuid - }); - -export class GetSpace extends CFStartAction implements ICFAction { - constructor(public guid: string, public endpointGuid: string) { - super(); - this.options = new RequestOptions(); - this.options.url = `space/${guid}`; - this.options.method = 'get'; - } - actions = [ - GET, - GET_SUCCESS, - GET_FAILED - ]; - entity = [SpaceSchema]; - entityKey = SpaceSchema.key; - options: RequestOptions; -} diff --git a/src/frontend/app/store/actions/space.actions.ts b/src/frontend/app/store/actions/space.actions.ts index e108607a98..d6b7aa5d07 100644 --- a/src/frontend/app/store/actions/space.actions.ts +++ b/src/frontend/app/store/actions/space.actions.ts @@ -1,19 +1,32 @@ -import { - CFStartAction, - IRequestAction, - ICFAction -} from '../types/request.types'; -import { getAPIResourceGuid } from '../selectors/api.selectors'; import { RequestOptions, URLSearchParams } from '@angular/http'; -import { schema } from 'normalizr'; -import { ApiActionTypes } from './request.actions'; +import { CFStartAction, ICFAction } from '../types/request.types'; +import { SpaceSchema, spaceSchemaKey, SpaceWithOrganisationSchema } from './action-types'; -export const GET_ALL = '[Space] Get all'; -export const GET_ALL_SUCCESS = '[Space] Get all success'; -export const GET_ALL_FAILED = '[Space] Get all failed'; +export const GET_SPACES = '[Space] Get all'; +export const GET_SPACES_SUCCESS = '[Space] Get all success'; +export const GET_SPACES_FAILED = '[Space] Get all failed'; -export const SpaceSchema = new schema.Entity('space'); +export const GET_SPACE = '[Space] Get one'; +export const GET_SPACE_SUCCESS = '[Space] Get one success'; +export const GET_SPACE_FAILED = '[Space] Get one failed'; + +export class GetSpace extends CFStartAction implements ICFAction { + constructor(public guid: string, public endpointGuid: string) { + super(); + this.options = new RequestOptions(); + this.options.url = `space/${guid}`; + this.options.method = 'get'; + } + actions = [ + GET_SPACE, + GET_SPACE_SUCCESS, + GET_SPACE_FAILED + ]; + entity = [SpaceSchema]; + entityKey = spaceSchemaKey; + options: RequestOptions; +} export class GetAllSpaces extends CFStartAction implements ICFAction { constructor(public paginationKey?: string) { @@ -26,8 +39,8 @@ export class GetAllSpaces extends CFStartAction implements ICFAction { this.options.params.set('results-per-page', '100'); this.options.params.set('inline-relations-depth', '1'); } - actions = [GET_ALL, GET_ALL_SUCCESS, GET_ALL_FAILED]; - entity = [SpaceSchema]; - entityKey = SpaceSchema.key; + actions = [GET_SPACES, GET_SPACES_SUCCESS, GET_SPACES_FAILED]; + entity = [SpaceWithOrganisationSchema]; + entityKey = spaceSchemaKey; options: RequestOptions; } From 257f14dfa5526a34bce9452c9ed5cb4ded2d1033 Mon Sep 17 00:00:00 2001 From: Nathan Jones Date: Thu, 15 Feb 2018 13:00:35 +0000 Subject: [PATCH 17/23] Fixing application and endpoints view when no endpoints are attached to the appliction --- .../list/data-sources-controllers/list-data-source.ts | 6 ++++-- src/frontend/app/shared/monitors/pagination-monitor.ts | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/frontend/app/shared/components/list/data-sources-controllers/list-data-source.ts b/src/frontend/app/shared/components/list/data-sources-controllers/list-data-source.ts index fd4ff7a8a4..7c1aa1b971 100644 --- a/src/frontend/app/shared/components/list/data-sources-controllers/list-data-source.ts +++ b/src/frontend/app/shared/components/list/data-sources-controllers/list-data-source.ts @@ -244,7 +244,9 @@ export abstract class ListDataSource extends DataSource implements ).pipe( filter(([paginationEntity, entities]) => !getCurrentPageRequestInfo(paginationEntity).busy), map(([paginationEntity, entities]) => { - + if (entities && !entities.length) { + return []; + } const entitiesPreFilter = entities.length; if (dataFunctions && dataFunctions.length) { entities = dataFunctions.reduce((value, fn) => { @@ -268,7 +270,7 @@ export abstract class ListDataSource extends DataSource implements publishReplay(1), refCount(), tag('local-list') - ); + ); } getPaginationCompareString(paginationEntity: PaginationEntityState) { diff --git a/src/frontend/app/shared/monitors/pagination-monitor.ts b/src/frontend/app/shared/monitors/pagination-monitor.ts index 32f20325f2..958137025b 100644 --- a/src/frontend/app/shared/monitors/pagination-monitor.ts +++ b/src/frontend/app/shared/monitors/pagination-monitor.ts @@ -114,10 +114,10 @@ export class PaginationMonitor { withLatestFrom(this.store.select(getAPIRequestDataState)), map(([[pagination, entities], allEntities]) => { const page = pagination.ids[pagination.currentPage] || []; - return page.length ? denormalize(page, [schema], allEntities).filter(ent => !!ent) : null; + return page.length ? denormalize(page, [schema], allEntities).filter(ent => !!ent) : []; }), shareReplay(1) - ); + ); } private createErrorObservable(pagination$: Observable) { From 51fd317b993e07ba1530d93e7cf5547533fadca8 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Mon, 12 Feb 2018 15:42:11 +0000 Subject: [PATCH 18/23] Show application page promptly - Don't wait for summary (used for total no. of services and routes) - Ensure stats show as soon as possible --- .../applications/application.service.ts | 11 +++----- .../application-tabs-base.component.html | 26 +++++-------------- 2 files changed, 11 insertions(+), 26 deletions(-) diff --git a/src/frontend/app/features/applications/application.service.ts b/src/frontend/app/features/applications/application.service.ts index 1944002e79..3759abc18f 100644 --- a/src/frontend/app/features/applications/application.service.ts +++ b/src/frontend/app/features/applications/application.service.ts @@ -230,13 +230,13 @@ export class ApplicationService { }).shareReplay(1); this.applicationState$ = this.waitForAppEntity$ - .withLatestFrom(this.appStats$) + .combineLatest(this.appStats$) .map(([appInfo, appStatsArray]: [EntityInfo, APIResource[]]) => { return this.appStateService.get(appInfo.entity.entity, appStatsArray.map(apiResource => apiResource.entity)); }).shareReplay(1); this.applicationInstanceState$ = this.waitForAppEntity$ - .withLatestFrom(this.appStats$) + .combineLatest(this.appStats$) .switchMap(([appInfo, appStatsArray]: [EntityInfo, APIResource[]]) => { return ApplicationService.getApplicationState(this.store, this.appStateService, appInfo.entity.entity, this.appGuid, this.cfGuid); }).shareReplay(1); @@ -250,11 +250,8 @@ export class ApplicationService { /** * An observable based on the core application entity */ - this.isFetchingApp$ = Observable.combineLatest( - this.app$.map(ei => ei.entityRequestInfo.fetching), - this.appSummary$.map(as => as.entityRequestInfo.fetching) - ) - .map((fetching) => fetching[0] || fetching[1]).shareReplay(1); + this.isFetchingApp$ = this.appEntityService.isFetchingEntity$; + this.isUpdatingApp$ = this.app$.map(a => { diff --git a/src/frontend/app/features/applications/application/application-tabs-base/application-tabs-base.component.html b/src/frontend/app/features/applications/application/application-tabs-base/application-tabs-base.component.html index cf6f35b141..0a18f748b6 100644 --- a/src/frontend/app/features/applications/application/application-tabs-base/application-tabs-base.component.html +++ b/src/frontend/app/features/applications/application/application-tabs-base/application-tabs-base.component.html @@ -1,28 +1,17 @@

{{ (applicationService.application$ | async)?.app.entity.name }}

- - - - - - - launch - - - - + -
From d4c886ee2d4845b4d775315cc6099f59fb68299d Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Thu, 15 Feb 2018 14:01:58 +0000 Subject: [PATCH 19/23] Ensure we always have an initial app state on app summary page --- .../features/applications/application.service.ts | 13 +++---------- .../card-app-instances.component.html | 2 +- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/src/frontend/app/features/applications/application.service.ts b/src/frontend/app/features/applications/application.service.ts index 3759abc18f..2cc76dd948 100644 --- a/src/frontend/app/features/applications/application.service.ts +++ b/src/frontend/app/features/applications/application.service.ts @@ -57,7 +57,6 @@ export interface ApplicationData { @Injectable() export class ApplicationService { - applicationInstanceState$: Observable; private appEntityService: EntityService; private appSummaryEntityService: EntityService; @@ -214,7 +213,7 @@ export class ApplicationService { this.application$ = this.waitForAppEntity$ .combineLatest( - this.store.select(endpointEntitiesSelector), + this.store.select(endpointEntitiesSelector), ) .filter(([{ entity, entityRequestInfo }, endpoints]: [EntityInfo, any]) => { return entity && entity.entity && entity.entity.cfGuid; @@ -230,15 +229,9 @@ export class ApplicationService { }).shareReplay(1); this.applicationState$ = this.waitForAppEntity$ - .combineLatest(this.appStats$) + .combineLatest(this.appStats$.startWith(null)) .map(([appInfo, appStatsArray]: [EntityInfo, APIResource[]]) => { - return this.appStateService.get(appInfo.entity.entity, appStatsArray.map(apiResource => apiResource.entity)); - }).shareReplay(1); - - this.applicationInstanceState$ = this.waitForAppEntity$ - .combineLatest(this.appStats$) - .switchMap(([appInfo, appStatsArray]: [EntityInfo, APIResource[]]) => { - return ApplicationService.getApplicationState(this.store, this.appStateService, appInfo.entity.entity, this.appGuid, this.cfGuid); + return this.appStateService.get(appInfo.entity.entity, appStatsArray ? appStatsArray.map(apiResource => apiResource.entity) : null); }).shareReplay(1); this.applicationStratProject$ = this.appEnvVars.entities$.map(applicationEnvVars => { diff --git a/src/frontend/app/shared/components/cards/card-app-instances/card-app-instances.component.html b/src/frontend/app/shared/components/cards/card-app-instances/card-app-instances.component.html index 526d47b0ce..28f4af150a 100644 --- a/src/frontend/app/shared/components/cards/card-app-instances/card-app-instances.component.html +++ b/src/frontend/app/shared/components/cards/card-app-instances/card-app-instances.component.html @@ -1,5 +1,5 @@ - + Instances From c5ad9715c382add92e89d24f5aa918811b912b8e Mon Sep 17 00:00:00 2001 From: Nathan Jones Date: Thu, 15 Feb 2018 15:14:19 +0000 Subject: [PATCH 20/23] Fetch app stats concurrent to application fetch, show uptime block on offline apps and don't poll app status or summary for offline apps --- src/frontend/app/core/utils.service.ts | 74 +++++++++---------- .../application-monitor.service.ts | 2 +- .../applications/application.service.ts | 19 +---- .../application-tabs-base.component.ts | 11 ++- .../card-app-uptime.component.html | 10 +-- .../card-app-uptime.component.ts | 28 +++++-- src/frontend/app/shared/pipes/uptime.pipe.ts | 5 +- 7 files changed, 80 insertions(+), 69 deletions(-) diff --git a/src/frontend/app/core/utils.service.ts b/src/frontend/app/core/utils.service.ts index bcb348e772..a83d6ed7d4 100644 --- a/src/frontend/app/core/utils.service.ts +++ b/src/frontend/app/core/utils.service.ts @@ -12,41 +12,41 @@ export class UtilsService { * */ public urlValidationExpression = - '^' + - // protocol identifier - 'http(s)?://' + - // user:pass authentication - '(?:\\S+(?::\\S*)?@)?' + - '(?:' + - // IP address exclusion - // private & local networks - '(?!(?:10|127)(?:\\.\\d{1,3}){3})' + - '(?!(?:169\\.254|192\\.168)(?:\\.\\d{1,3}){2})' + - '(?!172\\.(?:1[6-9]|2\\d|3[0-1])(?:\\.\\d{1,3}){2})' + - // IP address dotted notation octets - // excludes loopback network 0.0.0.0 - // excludes reserved space >= 224.0.0.0 - // excludes network & broadcast addresses - // (first & last IP address of each class) - '(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])' + - '(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}' + - '(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))' + - '|' + - // host name - '(?:(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)' + - // domain name - '(?:\\.(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)*' + - // TLD identifier - '(?:\\.(?:[a-z\\u00a1-\\uffff]{2,}))' + - // TLD may end with dot - '\\.?' + - ')' + - // port number - '(?::\\d{2,5})?' + - // resource path - '(?:[/?#]\\S*)?' + - '$' - ; + '^' + + // protocol identifier + 'http(s)?://' + + // user:pass authentication + '(?:\\S+(?::\\S*)?@)?' + + '(?:' + + // IP address exclusion + // private & local networks + '(?!(?:10|127)(?:\\.\\d{1,3}){3})' + + '(?!(?:169\\.254|192\\.168)(?:\\.\\d{1,3}){2})' + + '(?!172\\.(?:1[6-9]|2\\d|3[0-1])(?:\\.\\d{1,3}){2})' + + // IP address dotted notation octets + // excludes loopback network 0.0.0.0 + // excludes reserved space >= 224.0.0.0 + // excludes network & broadcast addresses + // (first & last IP address of each class) + '(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])' + + '(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}' + + '(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))' + + '|' + + // host name + '(?:(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)' + + // domain name + '(?:\\.(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)*' + + // TLD identifier + '(?:\\.(?:[a-z\\u00a1-\\uffff]{2,}))' + + // TLD may end with dot + '\\.?' + + ')' + + // port number + '(?::\\d{2,5})?' + + // resource path + '(?:[/?#]\\S*)?' + + '$' + ; constructor() { } @@ -146,14 +146,14 @@ export class UtilsService { const hours = Math.floor(uptime / 3600); uptime = uptime % 3600; const minutes = Math.floor(uptime / 60); - const seconds = uptime % 60; + const seconds = uptime % 60; return ( this.formatPart(days, 'd', 'd') + this.formatPart(hours, 'h', 'h') + this.formatPart(minutes, 'm', 'm') + this.formatPart(seconds, 's', 's') - .trim() + .trim() ); } diff --git a/src/frontend/app/features/applications/application-monitor.service.ts b/src/frontend/app/features/applications/application-monitor.service.ts index 9b41ad0419..5088fa1e81 100644 --- a/src/frontend/app/features/applications/application-monitor.service.ts +++ b/src/frontend/app/features/applications/application-monitor.service.ts @@ -63,7 +63,7 @@ export class AppMonitorState { @Injectable() export class ApplicationMonitorService { - appMonitor$: Observable; + appMonitor$: Observable; constructor( private applicationService: ApplicationService, diff --git a/src/frontend/app/features/applications/application.service.ts b/src/frontend/app/features/applications/application.service.ts index 2cc76dd948..7db2534a15 100644 --- a/src/frontend/app/features/applications/application.service.ts +++ b/src/frontend/app/features/applications/application.service.ts @@ -194,22 +194,11 @@ export class ApplicationService { AppStatSchema ) }, true); + // This will fail to fetch the app stats if the current app is not running but we're + // willing to do this to speed up the initial fetch for a running application. + this.appStats$ = appStats.entities$; - this.appStats$ = this.waitForAppEntity$ - .filter(ai => ai && ai.entity && ai.entity.entity) - .switchMap(ai => { - if (ai.entity.entity.state === 'STARTED') { - return appStats.entities$; - } else { - return Observable.of(new Array>()); - } - }); - - this.appStatsFetching$ = this.waitForAppEntity$ - .filter(ai => ai && ai.entity && ai.entity.entity && ai.entity.entity.state === 'STARTED') - .switchMap(ai => { - return appStats.pagination$; - }).shareReplay(1); + this.appStatsFetching$ = appStats.pagination$.shareReplay(1); this.application$ = this.waitForAppEntity$ .combineLatest( diff --git a/src/frontend/app/features/applications/application/application-tabs-base/application-tabs-base.component.ts b/src/frontend/app/features/applications/application/application-tabs-base/application-tabs-base.component.ts index bb9fb88c16..cf0a38279d 100644 --- a/src/frontend/app/features/applications/application/application-tabs-base/application-tabs-base.component.ts +++ b/src/frontend/app/features/applications/application/application-tabs-base/application-tabs-base.component.ts @@ -12,6 +12,7 @@ import { DeleteApplication } from '../../../../store/actions/application.actions import { RouterNav } from '../../../../store/actions/router.actions'; import { AppState } from '../../../../store/app-state'; import { ApplicationService } from '../../application.service'; +import { APIResource } from '../../../../store/types/api.types'; // Confirmation dialogs const appStopConfirmation = new ConfirmationDialog( @@ -42,7 +43,7 @@ export class ApplicationTabsBaseComponent implements OnInit, OnDestroy { private route: ActivatedRoute, private router: Router, private applicationService: ApplicationService, - private entityService: EntityService, + private entityService: EntityService, private store: Store, private confirmDialog: ConfirmationDialogService ) { } @@ -113,9 +114,11 @@ export class ApplicationTabsBaseComponent implements OnInit, OnDestroy { // Auto refresh this.entityServiceAppRefresh$ = this.entityService .poll(10000, this.autoRefreshString) - .do(() => { - this.store.dispatch(new GetAppSummaryAction(appGuid, cfGuid)); - this.store.dispatch(new GetAppStatsAction(appGuid, cfGuid)); + .do(({ resource }) => { + if (resource && resource.entity && resource.entity.state === 'STARTED') { + this.store.dispatch(new GetAppSummaryAction(appGuid, cfGuid)); + this.store.dispatch(new GetAppStatsAction(appGuid, cfGuid)); + } }) .subscribe(); diff --git a/src/frontend/app/shared/components/cards/card-app-uptime/card-app-uptime.component.html b/src/frontend/app/shared/components/cards/card-app-uptime/card-app-uptime.component.html index daed8f9887..bdd6549972 100644 --- a/src/frontend/app/shared/components/cards/card-app-uptime/card-app-uptime.component.html +++ b/src/frontend/app/shared/components/cards/card-app-uptime/card-app-uptime.component.html @@ -5,12 +5,12 @@ Uptime -
{{ appData.monitor.max.uptime | uptime }}
+
{{ appData.maxUptime | uptime }}
-
-
- {{ appData.monitor.avg.uptime | uptime }} - {{ appData.monitor.min.uptime | uptime }} +
+
+ {{ appData.averageUptime | uptime }} + {{ appData.minUptime | uptime }}
diff --git a/src/frontend/app/shared/components/cards/card-app-uptime/card-app-uptime.component.ts b/src/frontend/app/shared/components/cards/card-app-uptime/card-app-uptime.component.ts index b00fd924a1..66e6132d7e 100644 --- a/src/frontend/app/shared/components/cards/card-app-uptime/card-app-uptime.component.ts +++ b/src/frontend/app/shared/components/cards/card-app-uptime/card-app-uptime.component.ts @@ -1,7 +1,9 @@ import { Component, OnInit } from '@angular/core'; +import { Observable } from 'rxjs/Observable'; +import { map, startWith } from 'rxjs/operators'; + import { ApplicationMonitorService } from '../../../../features/applications/application-monitor.service'; import { ApplicationService } from '../../../../features/applications/application.service'; -import { Observable } from 'rxjs/Observable'; @Component({ selector: 'app-card-app-uptime', @@ -12,13 +14,27 @@ export class CardAppUptimeComponent implements OnInit { constructor(private appService: ApplicationService, private appMonitor: ApplicationMonitorService) { } - appData$: Observable; + appData$: Observable<{ + maxUptime: number, + minUptime: number, + averageUptime: number, + runningCount: number + }>; ngOnInit() { - this.appData$ = Observable.combineLatest( - this.appMonitor.appMonitor$, - this.appService.application$.map(data => data.app.entity.state === 'STARTED'), - (monitor, isRunning) => ({ monitor: monitor, isRunning: isRunning, status: !isRunning ? 'tentative' : monitor.status.usage }) + this.appData$ = this.appMonitor.appMonitor$.pipe( + map(monitor => ({ + maxUptime: monitor.max.uptime, + minUptime: monitor.min.uptime, + averageUptime: monitor.avg.uptime, + runningCount: monitor.running + })), + startWith({ + maxUptime: 0, + minUptime: 0, + averageUptime: 0, + runningCount: 0 + }) ); } } diff --git a/src/frontend/app/shared/pipes/uptime.pipe.ts b/src/frontend/app/shared/pipes/uptime.pipe.ts index 55ee425443..aac893b611 100644 --- a/src/frontend/app/shared/pipes/uptime.pipe.ts +++ b/src/frontend/app/shared/pipes/uptime.pipe.ts @@ -7,9 +7,12 @@ import { UtilsService } from '../../core/utils.service'; }) export class UptimePipe implements PipeTransform { - constructor(private utils: UtilsService) {} + constructor(private utils: UtilsService) { } transform(uptime): string { + if (uptime === 'offline') { + return 'Offline'; + } return this.utils.formatUptime(uptime); } } From a9d6ab99fd3403cf42bfbf33572d345746cbfdf6 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Thu, 15 Feb 2018 15:22:49 +0000 Subject: [PATCH 21/23] Only gate stats polling --- .../application-tabs-base/application-tabs-base.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frontend/app/features/applications/application/application-tabs-base/application-tabs-base.component.ts b/src/frontend/app/features/applications/application/application-tabs-base/application-tabs-base.component.ts index cf0a38279d..759d5b42c8 100644 --- a/src/frontend/app/features/applications/application/application-tabs-base/application-tabs-base.component.ts +++ b/src/frontend/app/features/applications/application/application-tabs-base/application-tabs-base.component.ts @@ -115,8 +115,8 @@ export class ApplicationTabsBaseComponent implements OnInit, OnDestroy { this.entityServiceAppRefresh$ = this.entityService .poll(10000, this.autoRefreshString) .do(({ resource }) => { + this.store.dispatch(new GetAppSummaryAction(appGuid, cfGuid)); if (resource && resource.entity && resource.entity.state === 'STARTED') { - this.store.dispatch(new GetAppSummaryAction(appGuid, cfGuid)); this.store.dispatch(new GetAppStatsAction(appGuid, cfGuid)); } }) From 063be6f2b958ba5ab112f4523bb7c157a08d7e22 Mon Sep 17 00:00:00 2001 From: Nathan Jones Date: Thu, 15 Feb 2018 16:23:26 +0000 Subject: [PATCH 22/23] Styling tweeks --- .../list/list-cards/cards.component.scss | 1 - .../components/list/list.component.html | 23 +++++++++---------- .../components/list/list.component.scss | 23 ++++++++++++++++--- 3 files changed, 31 insertions(+), 16 deletions(-) diff --git a/src/frontend/app/shared/components/list/list-cards/cards.component.scss b/src/frontend/app/shared/components/list/list-cards/cards.component.scss index 1a30ec14da..118ab7a93e 100644 --- a/src/frontend/app/shared/components/list/list-cards/cards.component.scss +++ b/src/frontend/app/shared/components/list/list-cards/cards.component.scss @@ -2,7 +2,6 @@ display: flex; flex-flow: wrap; justify-content: space-between; - padding-top: 20px; .mat-card { &::after { bottom: 0; diff --git a/src/frontend/app/shared/components/list/list.component.html b/src/frontend/app/shared/components/list/list.component.html index 89286febc6..457152e11e 100644 --- a/src/frontend/app/shared/components/list/list.component.html +++ b/src/frontend/app/shared/components/list/list.component.html @@ -1,7 +1,7 @@ -
+
- +
{{ config.text?.title }}
{{dataSource.selectedRows.size}} Selected
@@ -26,20 +26,20 @@
-
- - - +
+ {{column.headerCell()}} + +
@@ -87,7 +87,6 @@
-
@@ -98,7 +97,7 @@ - +
There are no entries.
diff --git a/src/frontend/app/shared/components/list/list.component.scss b/src/frontend/app/shared/components/list/list.component.scss index c181cb261d..f4791ad9e8 100644 --- a/src/frontend/app/shared/components/list/list.component.scss +++ b/src/frontend/app/shared/components/list/list.component.scss @@ -7,6 +7,21 @@ display: none; } } + &__cards { + .list-component__header-card { + margin-bottom: 20px; + } + } + &__table { + .list-component__header-card { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + } + .list-component__paginator { + border-top-left-radius: 0; + border-top-right-radius: 0; + } + } &__blocker { bottom: 0; left: 0; @@ -29,11 +44,13 @@ mat-card { display: flex; } + &-card { + justify-content: space-between; + } &__left, &__right { align-items: center; display: flex; - flex: 0; flex-direction: row; > div { &:not(:last-of-type) { @@ -46,9 +63,9 @@ } } &__left { - flex-grow: 99; + flex: 1; &--text { - padding: 12px 0; // Valid when window shrunk + padding: 10px; } &--multi-filters { mat-form-field { From e58332126f6badb9c7bd40801e51e38deb06d207 Mon Sep 17 00:00:00 2001 From: Nathan Jones Date: Thu, 15 Feb 2018 16:29:17 +0000 Subject: [PATCH 23/23] CC issues? --- src/frontend/app/shared/components/list/list.component.scss | 2 +- src/frontend/sass/_all-theme.scss | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/frontend/app/shared/components/list/list.component.scss b/src/frontend/app/shared/components/list/list.component.scss index f4791ad9e8..14e9a58caf 100644 --- a/src/frontend/app/shared/components/list/list.component.scss +++ b/src/frontend/app/shared/components/list/list.component.scss @@ -73,7 +73,7 @@ } } } - & > mat-card { + > mat-card { min-height: 74px; padding: 0 24px; width: 100%; diff --git a/src/frontend/sass/_all-theme.scss b/src/frontend/sass/_all-theme.scss index 02e946c050..dfb2597c9b 100644 --- a/src/frontend/sass/_all-theme.scss +++ b/src/frontend/sass/_all-theme.scss @@ -48,7 +48,7 @@ $side-nav-light-active: #484848; html { background-color: $app-background-color; } // App Theme defines a set of colors used by stratos components - $app-theme: ( app-background-color: $app-background-color, app-background-text-color: rgba(mat-color($foreground-colors, base), .65), side-nav: app-generate-nav-theme($theme, $nav-theme), status: app-generate-status-theme($theme, $status-theme), subdued-color: $subdued); // Pass the Material theme and the App Theme to components that need to be themed + $app-theme: (app-background-color: $app-background-color, app-background-text-color: rgba(mat-color($foreground-colors, base), .65), side-nav: app-generate-nav-theme($theme, $nav-theme), status: app-generate-status-theme($theme, $status-theme), subdued-color: $subdued); // Pass the Material theme and the App Theme to components that need to be themed @include dialog-error-theme($theme, $app-theme); @include login-page-theme($theme, $app-theme); @include side-nav-theme($theme, $app-theme);