diff --git a/custom-src/frontend/app/custom/kubernetes/helm-release/helm-release-summary/helm-release-summary-card/helm-release-summary-card.component.spec.ts b/custom-src/frontend/app/custom/kubernetes/helm-release/helm-release-summary/helm-release-summary-card/helm-release-summary-card.component.spec.ts index d8e3071e3e..2908d0be29 100644 --- a/custom-src/frontend/app/custom/kubernetes/helm-release/helm-release-summary/helm-release-summary-card/helm-release-summary-card.component.spec.ts +++ b/custom-src/frontend/app/custom/kubernetes/helm-release/helm-release-summary/helm-release-summary-card/helm-release-summary-card.component.spec.ts @@ -1,7 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { BaseKubeGuid } from '../../../kubernetes-page.types'; -import { KubernetesBaseTestModules } from '../../../kubernetes.testing.module'; +import { KubernetesBaseTestModules, KubernetesGuidMock } from '../../../kubernetes.testing.module'; import { HelmReleaseService } from '../../../services/helm-release.service'; import { KubernetesEndpointService } from '../../../services/kubernetes-endpoint.service'; import { HelmReleaseSummaryCardComponent } from './helm-release-summary-card.component'; @@ -14,7 +13,11 @@ describe('HelmReleaseSummaryCardComponent', () => { TestBed.configureTestingModule({ declarations: [HelmReleaseSummaryCardComponent], imports: KubernetesBaseTestModules, - providers: [BaseKubeGuid, HelmReleaseService, KubernetesEndpointService] + providers: [ + KubernetesEndpointService, + KubernetesGuidMock, + HelmReleaseService, + ] }) .compileComponents(); })); diff --git a/custom-src/frontend/app/custom/kubernetes/helm-release/helm-release-summary/helm-release-summary.component.spec.ts b/custom-src/frontend/app/custom/kubernetes/helm-release/helm-release-summary/helm-release-summary.component.spec.ts index f7c78bf281..c6d735f5cc 100644 --- a/custom-src/frontend/app/custom/kubernetes/helm-release/helm-release-summary/helm-release-summary.component.spec.ts +++ b/custom-src/frontend/app/custom/kubernetes/helm-release/helm-release-summary/helm-release-summary.component.spec.ts @@ -1,7 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { BaseKubeGuid } from '../../kubernetes-page.types'; -import { KubernetesBaseTestModules } from '../../kubernetes.testing.module'; +import { KubernetesBaseTestModules, KubernetesGuidMock } from '../../kubernetes.testing.module'; import { HelmReleaseService } from '../../services/helm-release.service'; import { KubernetesEndpointService } from '../../services/kubernetes-endpoint.service'; import { HelmReleaseSummaryCardComponent } from './helm-release-summary-card/helm-release-summary-card.component'; @@ -18,7 +17,11 @@ describe('HelmReleaseSummaryComponent', () => { HelmReleaseSummaryCardComponent ], imports: KubernetesBaseTestModules, - providers: [BaseKubeGuid, HelmReleaseService, KubernetesEndpointService] + providers: [ + KubernetesGuidMock, + HelmReleaseService, + KubernetesEndpointService + ] }) .compileComponents(); })); diff --git a/custom-src/frontend/app/custom/kubernetes/kubernetes-dashboard/kubernetes-dashboard.component.spec.ts b/custom-src/frontend/app/custom/kubernetes/kubernetes-dashboard/kubernetes-dashboard.component.spec.ts index 971bc1bbef..f0e86c42d8 100644 --- a/custom-src/frontend/app/custom/kubernetes/kubernetes-dashboard/kubernetes-dashboard.component.spec.ts +++ b/custom-src/frontend/app/custom/kubernetes/kubernetes-dashboard/kubernetes-dashboard.component.spec.ts @@ -1,7 +1,8 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { TabNavService } from '../../../../tab-nav.service'; -import { KubernetesBaseTestModules } from '../kubernetes.testing.module'; +import { BaseKubeGuid } from '../kubernetes-page.types'; +import { KubernetesBaseTestModules, KubernetesGuidMock } from '../kubernetes.testing.module'; import { KubernetesDashboardTabComponent } from './kubernetes-dashboard.component'; describe('KubernetesDashboardTabComponent', () => { @@ -9,6 +10,7 @@ describe('KubernetesDashboardTabComponent', () => { let fixture: ComponentFixture; beforeEach(async(() => { + TestBed.overrideProvider(BaseKubeGuid, KubernetesGuidMock); TestBed.configureTestingModule({ declarations: [KubernetesDashboardTabComponent], imports: [...KubernetesBaseTestModules], diff --git a/custom-src/frontend/app/custom/kubernetes/kubernetes-endpoint-preview/kubernetes-endpoint-preview.component.html b/custom-src/frontend/app/custom/kubernetes/kubernetes-endpoint-preview/kubernetes-endpoint-preview.component.html new file mode 100644 index 0000000000..e216ce4ad7 --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kubernetes-endpoint-preview/kubernetes-endpoint-preview.component.html @@ -0,0 +1,72 @@ + + + + + +
+ + + Summary + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
\ No newline at end of file diff --git a/custom-src/frontend/app/custom/kubernetes/kubernetes-endpoint-preview/kubernetes-endpoint-preview.component.scss b/custom-src/frontend/app/custom/kubernetes/kubernetes-endpoint-preview/kubernetes-endpoint-preview.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/custom-src/frontend/app/custom/kubernetes/kubernetes-endpoint-preview/kubernetes-endpoint-preview.component.spec.ts b/custom-src/frontend/app/custom/kubernetes/kubernetes-endpoint-preview/kubernetes-endpoint-preview.component.spec.ts new file mode 100644 index 0000000000..f30f9d653a --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kubernetes-endpoint-preview/kubernetes-endpoint-preview.component.spec.ts @@ -0,0 +1,33 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { KubernetesActivatedRouteMock, KubernetesBaseTestModules, KubernetesGuidMock } from '../kubernetes.testing.module'; +import { KubernetesEndpointService } from '../services/kubernetes-endpoint.service'; +import { KubernetesEndpointPreviewComponent } from './kubernetes-endpoint-preview.component'; + +describe('KubernetesEndpointPreviewComponent', () => { + let component: KubernetesEndpointPreviewComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [KubernetesEndpointPreviewComponent], + providers: [ + KubernetesEndpointService, + KubernetesActivatedRouteMock, + KubernetesGuidMock + ], + imports: [...KubernetesBaseTestModules] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(KubernetesEndpointPreviewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/custom-src/frontend/app/custom/kubernetes/kubernetes-endpoint-preview/kubernetes-endpoint-preview.component.ts b/custom-src/frontend/app/custom/kubernetes/kubernetes-endpoint-preview/kubernetes-endpoint-preview.component.ts new file mode 100644 index 0000000000..3f8047cf0c --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kubernetes-endpoint-preview/kubernetes-endpoint-preview.component.ts @@ -0,0 +1,74 @@ +import { Component } from '@angular/core'; +import { combineLatest, Observable } from 'rxjs'; +import { map, startWith } from 'rxjs/operators'; + +import { + ISimpleUsageChartData, +} from '../../../../../core/src/shared/components/simple-usage-chart/simple-usage-chart.types'; +import { PreviewableComponent } from '../../../../../core/src/shared/previewable-component'; +import { KubernetesEndpointService } from '../services/kubernetes-endpoint.service'; + +@Component({ + selector: 'app-kubernetes-endpoint-preview-component', + templateUrl: './kubernetes-endpoint-preview.component.html', + styleUrls: ['./kubernetes-endpoint-preview.component.scss'] +}) +export class KubernetesEndpointPreviewComponent implements PreviewableComponent { + + title: string = null; + detailsLoading$: Observable; + kubeVersion$: Observable; + podCount$: Observable; + nodeCount$: Observable; + appCount$: Observable; + podCapacity$: Observable; + diskPressure$: Observable; + memoryPressure$: Observable; + outOfDisk$: Observable; + nodesReady$: Observable; + podsLink: string; + nodesLink: string; + appsLink: string; + + constructor( + public kubeEndpointService: KubernetesEndpointService, + ) { } + + setProps(props: { [key: string]: any }) { + const kubeGuid = props.cfGuid; + + this.title = props.title; + + this.kubeEndpointService.initialize(kubeGuid); + const nodes$ = this.kubeEndpointService.nodes$; + + this.kubeVersion$ = this.kubeEndpointService.getNodeKubeVersions(); + this.podCount$ = this.kubeEndpointService.getCountObservable(this.kubeEndpointService.pods$); + this.nodeCount$ = this.kubeEndpointService.getCountObservable(nodes$); + this.appCount$ = this.kubeEndpointService.getCountObservable(this.kubeEndpointService.apps$); + this.podCapacity$ = this.kubeEndpointService.getPodCapacity(); + this.diskPressure$ = this.kubeEndpointService.getNodeStatusCount(this.kubeEndpointService.nodes$, 'DiskPressure'); + this.memoryPressure$ = this.kubeEndpointService.getNodeStatusCount(this.kubeEndpointService.nodes$, 'MemoryPressure'); + this.outOfDisk$ = this.kubeEndpointService.getNodeStatusCount(nodes$, 'OutOfDisk'); + this.nodesReady$ = this.kubeEndpointService.getNodeStatusCount(nodes$, 'Ready'); + + this.podsLink = `/kubernetes/${kubeGuid}/pods`; + this.nodesLink = `/kubernetes/${kubeGuid}/nodes`; + this.appsLink = `/kubernetes/${kubeGuid}/apps`; + + this.detailsLoading$ = combineLatest([ + this.podCount$, + this.nodeCount$, + this.appCount$, + this.podCapacity$, + this.diskPressure$, + this.memoryPressure$, + this.outOfDisk$, + this.nodesReady$, + // this.networkUnavailable$, + ]).pipe( + map(() => false), + startWith(true), + ); + } +} diff --git a/custom-src/frontend/app/custom/kubernetes/kubernetes-entity-generator.ts b/custom-src/frontend/app/custom/kubernetes/kubernetes-entity-generator.ts index 969e93b54f..fa54271c13 100644 --- a/custom-src/frontend/app/custom/kubernetes/kubernetes-entity-generator.ts +++ b/custom-src/frontend/app/custom/kubernetes/kubernetes-entity-generator.ts @@ -17,6 +17,7 @@ import { KubernetesConfigAuthFormComponent, } from './auth-forms/kubernetes-config-auth-form/kubernetes-config-auth-form.component'; import { KubernetesGKEAuthFormComponent } from './auth-forms/kubernetes-gke-auth-form/kubernetes-gke-auth-form.component'; +import { KubernetesEndpointPreviewComponent } from './kubernetes-endpoint-preview/kubernetes-endpoint-preview.component'; import { KUBERNETES_ENDPOINT_TYPE, kubernetesAppsEntityType, @@ -154,6 +155,7 @@ function generateEndpointEntity(endpointDefinition: StratosEndpointExtensionDefi return new StratosCatalogEndpointEntity( endpointDefinition, metadata => `/kubernetes/${metadata.guid}`, + () => KubernetesEndpointPreviewComponent, ); } diff --git a/custom-src/frontend/app/custom/kubernetes/kubernetes-node/kubernetes-node-preview/kubernetes-node-preview.component.html b/custom-src/frontend/app/custom/kubernetes/kubernetes-node/kubernetes-node-preview/kubernetes-node-preview.component.html new file mode 100644 index 0000000000..4345287b4d --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kubernetes-node/kubernetes-node-preview/kubernetes-node-preview.component.html @@ -0,0 +1,57 @@ + + + + + +
+ + + Summary + + + + + +
+
+
+ + + + + + + + + + + + + +
+
+
\ No newline at end of file diff --git a/custom-src/frontend/app/custom/kubernetes/kubernetes-node/kubernetes-node-preview/kubernetes-node-preview.component.scss b/custom-src/frontend/app/custom/kubernetes/kubernetes-node/kubernetes-node-preview/kubernetes-node-preview.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/custom-src/frontend/app/custom/kubernetes/kubernetes-node/kubernetes-node-preview/kubernetes-node-preview.component.spec.ts b/custom-src/frontend/app/custom/kubernetes/kubernetes-node/kubernetes-node-preview/kubernetes-node-preview.component.spec.ts new file mode 100644 index 0000000000..fee6264b3f --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kubernetes-node/kubernetes-node-preview/kubernetes-node-preview.component.spec.ts @@ -0,0 +1,39 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { + KubernetesActivatedRouteMock, + KubernetesBaseTestModules, + KubernetesGuidMock, +} from '../../kubernetes.testing.module'; +import { KubernetesEndpointService } from '../../services/kubernetes-endpoint.service'; +import { KubernetesNodeService } from '../../services/kubernetes-node.service'; +import { KubernetesNodePreviewComponent } from './kubernetes-node-preview.component'; + +describe('KubernetesNodePreviewComponent', () => { + let component: KubernetesNodePreviewComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [KubernetesNodePreviewComponent], + providers: [ + KubernetesEndpointService, + KubernetesNodeService, + KubernetesActivatedRouteMock, + KubernetesGuidMock + ], + imports: [...KubernetesBaseTestModules] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(KubernetesNodePreviewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/custom-src/frontend/app/custom/kubernetes/kubernetes-node/kubernetes-node-preview/kubernetes-node-preview.component.ts b/custom-src/frontend/app/custom/kubernetes/kubernetes-node/kubernetes-node-preview/kubernetes-node-preview.component.ts new file mode 100644 index 0000000000..0509ec489b --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kubernetes-node/kubernetes-node-preview/kubernetes-node-preview.component.ts @@ -0,0 +1,61 @@ +import { Component } from '@angular/core'; +import { Observable } from 'rxjs'; +import { map, startWith } from 'rxjs/operators'; + +import { PreviewableComponent } from '../../../../../../core/src/shared/previewable-component'; +import { KubernetesEndpointService } from '../../services/kubernetes-endpoint.service'; +import { KubernetesNodeService } from '../../services/kubernetes-node.service'; + +@Component({ + selector: 'app-kubernetes-node-preview-component', + templateUrl: './kubernetes-node-preview.component.html', + styleUrls: ['./kubernetes-node-preview.component.scss'] +}) +export class KubernetesNodePreviewComponent implements PreviewableComponent { + + title: string = null; + detailsLoading$: Observable; + kubeVersion$: Observable; + podCount$: Observable; + memoryCapacity$: Observable; + podsLink: string; + + constructor( + public kubeEndpointService: KubernetesEndpointService, + public kubeNodeService: KubernetesNodeService, + ) { + } + + setProps(props: { [key: string]: any }) { + const nodeName = props.nodeName; + const kubeGuid = props.kubeGuid; + + this.title = nodeName; + + this.kubeEndpointService.initialize(kubeGuid); + this.kubeNodeService.initialize(nodeName, kubeGuid); + + this.podCount$ = this.kubeEndpointService.pods$.pipe( + map(pods => pods.filter(p => p.spec.nodeName === nodeName).length) + ); + + this.memoryCapacity$ = this.kubeNodeService.nodeEntity$.pipe( + map(node => this.getMemory(node.status.capacity.memory)) + ); + + this.podsLink = `/kubernetes/${kubeGuid}/nodes/minikube/pods`; + + this.detailsLoading$ = this.kubeNodeService.nodeEntity$.pipe( + map((entity) => !entity), + startWith(true), + ); + } + + private getMemory(memoryCapacity: string) { + if (memoryCapacity.endsWith('Ki')) { + const value = parseInt(memoryCapacity, 10); + return (value * 1024); + } + return memoryCapacity; + } +} diff --git a/custom-src/frontend/app/custom/kubernetes/kubernetes-node/kubernetes-node.component.spec.ts b/custom-src/frontend/app/custom/kubernetes/kubernetes-node/kubernetes-node.component.spec.ts index ee709c0ca1..688f299d4e 100644 --- a/custom-src/frontend/app/custom/kubernetes/kubernetes-node/kubernetes-node.component.spec.ts +++ b/custom-src/frontend/app/custom/kubernetes/kubernetes-node/kubernetes-node.component.spec.ts @@ -1,7 +1,8 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { TabNavService } from '../../../../tab-nav.service'; -import { KubernetesBaseTestModules } from '../kubernetes.testing.module'; +import { BaseKubeGuid } from '../kubernetes-page.types'; +import { KubernetesBaseTestModules, KubernetesGuidMock } from '../kubernetes.testing.module'; import { KubernetesNodeComponent } from './kubernetes-node.component'; describe('KubernetesNodeComponent', () => { @@ -9,6 +10,7 @@ describe('KubernetesNodeComponent', () => { let fixture: ComponentFixture; beforeEach(async(() => { + TestBed.overrideProvider(BaseKubeGuid, KubernetesGuidMock); TestBed.configureTestingModule({ declarations: [KubernetesNodeComponent], imports: KubernetesBaseTestModules, diff --git a/custom-src/frontend/app/custom/kubernetes/kubernetes-pod-preview/kubernetes-pod-preview.component.html b/custom-src/frontend/app/custom/kubernetes/kubernetes-pod-preview/kubernetes-pod-preview.component.html new file mode 100644 index 0000000000..aa937d21ba --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kubernetes-pod-preview/kubernetes-pod-preview.component.html @@ -0,0 +1,29 @@ + + + + + +
+ + + Summary + + + + + +
+
+
+
+
+
\ No newline at end of file diff --git a/custom-src/frontend/app/custom/kubernetes/kubernetes-pod-preview/kubernetes-pod-preview.component.scss b/custom-src/frontend/app/custom/kubernetes/kubernetes-pod-preview/kubernetes-pod-preview.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/custom-src/frontend/app/custom/kubernetes/kubernetes-pod-preview/kubernetes-pod-preview.component.spec.ts b/custom-src/frontend/app/custom/kubernetes/kubernetes-pod-preview/kubernetes-pod-preview.component.spec.ts new file mode 100644 index 0000000000..bf2065f533 --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kubernetes-pod-preview/kubernetes-pod-preview.component.spec.ts @@ -0,0 +1,35 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { KubernetesActivatedRouteMock, KubernetesBaseTestModules, KubernetesGuidMock } from '../kubernetes.testing.module'; +import { KubernetesEndpointService } from '../services/kubernetes-endpoint.service'; +import { KubernetesNodeService } from '../services/kubernetes-node.service'; +import { KubernetesPodPreviewComponent } from './kubernetes-pod-preview.component'; + +describe('KubernetesPodPreviewComponent', () => { + let component: KubernetesPodPreviewComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [KubernetesPodPreviewComponent], + providers: [ + KubernetesEndpointService, + KubernetesNodeService, + KubernetesActivatedRouteMock, + KubernetesGuidMock + ], + imports: [...KubernetesBaseTestModules] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(KubernetesPodPreviewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/custom-src/frontend/app/custom/kubernetes/kubernetes-pod-preview/kubernetes-pod-preview.component.ts b/custom-src/frontend/app/custom/kubernetes/kubernetes-pod-preview/kubernetes-pod-preview.component.ts new file mode 100644 index 0000000000..f9b4c397f0 --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kubernetes-pod-preview/kubernetes-pod-preview.component.ts @@ -0,0 +1,57 @@ +import { Component } from '@angular/core'; +import { Observable } from 'rxjs'; +import { filter, first, map, shareReplay } from 'rxjs/operators'; + +import { EntityServiceFactory } from '../../../../../core/src/core/entity-service-factory.service'; +import { PreviewableComponent } from '../../../../../core/src/shared/previewable-component'; +import { EntityInfo } from '../../../../../store/src/types/api.types'; +import { KubernetesEndpointService } from '../services/kubernetes-endpoint.service'; +import { KubernetesNodeService } from '../services/kubernetes-node.service'; +import { KubernetesPod } from '../store/kube.types'; +import { GetKubernetesPod } from '../store/kubernetes.actions'; + +@Component({ + selector: 'app-kubernetes-pod-preview-component', + templateUrl: './kubernetes-pod-preview.component.html', + styleUrls: ['./kubernetes-pod-preview.component.scss'] +}) +export class KubernetesPodPreviewComponent implements PreviewableComponent { + + title: string = null; + detailsLoading$: Observable; + pod$: Observable>; + podEntity$: Observable; + + constructor( + public kubeEndpointService: KubernetesEndpointService, + public kubeNodeService: KubernetesNodeService, + private entityServiceFactory: EntityServiceFactory, + ) { + } + + setProps(props: { [key: string]: any }) { + const { podName, namespaceName, kubeGuid, podGuid } = props; + + this.title = podName; + + const podEntityService = this.entityServiceFactory.create( + podGuid, + new GetKubernetesPod(podName, namespaceName, kubeGuid), + ); + + this.pod$ = podEntityService.entityObs$.pipe( + filter(p => !!p && !!p.entity), + first(), + shareReplay(1), + ); + + this.podEntity$ = this.pod$.pipe( + map(p => p.entity) + ); + + // this.detailsLoading$ = this.kubeNodeService.nodeEntity$.pipe( + // map((entity) => !entity), + // startWith(false), + // ); + } +} diff --git a/custom-src/frontend/app/custom/kubernetes/kubernetes-tab-base/kubernetes-tab-base.component.spec.ts b/custom-src/frontend/app/custom/kubernetes/kubernetes-tab-base/kubernetes-tab-base.component.spec.ts index f783def07d..69515f58c0 100644 --- a/custom-src/frontend/app/custom/kubernetes/kubernetes-tab-base/kubernetes-tab-base.component.spec.ts +++ b/custom-src/frontend/app/custom/kubernetes/kubernetes-tab-base/kubernetes-tab-base.component.spec.ts @@ -1,7 +1,8 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { TabNavService } from '../../../../tab-nav.service'; -import { KubernetesBaseTestModules } from '../kubernetes.testing.module'; +import { BaseKubeGuid } from '../kubernetes-page.types'; +import { KubernetesBaseTestModules, KubernetesGuidMock } from '../kubernetes.testing.module'; import { KubernetesTabBaseComponent } from './kubernetes-tab-base.component'; describe('KubernetesTabBaseComponent', () => { @@ -9,6 +10,7 @@ describe('KubernetesTabBaseComponent', () => { let fixture: ComponentFixture; beforeEach(async(() => { + TestBed.overrideProvider(BaseKubeGuid, KubernetesGuidMock); TestBed.configureTestingModule({ declarations: [KubernetesTabBaseComponent], imports: KubernetesBaseTestModules, diff --git a/custom-src/frontend/app/custom/kubernetes/kubernetes.setup.module.ts b/custom-src/frontend/app/custom/kubernetes/kubernetes.setup.module.ts index 5f3a929c63..b0977e301a 100644 --- a/custom-src/frontend/app/custom/kubernetes/kubernetes.setup.module.ts +++ b/custom-src/frontend/app/custom/kubernetes/kubernetes.setup.module.ts @@ -16,9 +16,15 @@ import { KubernetesConfigAuthFormComponent, } from './auth-forms/kubernetes-config-auth-form/kubernetes-config-auth-form.component'; import { KubernetesGKEAuthFormComponent } from './auth-forms/kubernetes-gke-auth-form/kubernetes-gke-auth-form.component'; +import { KubernetesEndpointPreviewComponent } from './kubernetes-endpoint-preview/kubernetes-endpoint-preview.component'; import { KUBERNETES_ENDPOINT_TYPE } from './kubernetes-entity-factory'; import { generateKubernetesEntities } from './kubernetes-entity-generator'; +import { KubernetesNodePreviewComponent } from './kubernetes-node/kubernetes-node-preview/kubernetes-node-preview.component'; +import { BaseKubeGuid } from './kubernetes-page.types'; +import { KubernetesPodPreviewComponent } from './kubernetes-pod-preview/kubernetes-pod-preview.component'; import { KubernetesStoreModule } from './kubernetes.store.module'; +import { KubernetesEndpointService } from './services/kubernetes-endpoint.service'; +import { KubernetesNodeService } from './services/kubernetes-node.service'; import { KubeHealthCheck } from './store/kubernetes.actions'; @@ -28,19 +34,30 @@ import { KubeHealthCheck } from './store/kubernetes.actions'; CoreModule, CommonModule, SharedModule, - KubernetesStoreModule, + KubernetesStoreModule ], declarations: [ KubernetesCertsAuthFormComponent, KubernetesAWSAuthFormComponent, KubernetesConfigAuthFormComponent, KubernetesGKEAuthFormComponent, + KubernetesEndpointPreviewComponent, + KubernetesNodePreviewComponent, + KubernetesPodPreviewComponent, + ], + providers: [ + BaseKubeGuid, + KubernetesEndpointService, + KubernetesNodeService, ], entryComponents: [ KubernetesCertsAuthFormComponent, KubernetesAWSAuthFormComponent, KubernetesConfigAuthFormComponent, KubernetesGKEAuthFormComponent, + KubernetesEndpointPreviewComponent, + KubernetesNodePreviewComponent, + KubernetesPodPreviewComponent, ] }) export class KubernetesSetupModule { diff --git a/custom-src/frontend/app/custom/kubernetes/kubernetes.testing.module.ts b/custom-src/frontend/app/custom/kubernetes/kubernetes.testing.module.ts index d84f853192..94a42ddc00 100644 --- a/custom-src/frontend/app/custom/kubernetes/kubernetes.testing.module.ts +++ b/custom-src/frontend/app/custom/kubernetes/kubernetes.testing.module.ts @@ -1,6 +1,7 @@ import { HttpClientModule } from '@angular/common/http'; import { NgModule } from '@angular/core'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { ActivatedRoute } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; import { CATALOGUE_ENTITIES, EntityCatalogFeatureModule } from '../../../../store/src/entity-catalog.module'; @@ -10,6 +11,7 @@ import { generateStratosEntities } from '../../base-entity-types'; import { CoreModule } from '../../core/core.module'; import { SharedModule } from '../../shared/shared.module'; import { generateKubernetesEntities } from './kubernetes-entity-generator'; +import { BaseKubeGuid } from './kubernetes-page.types'; @NgModule({ imports: [{ @@ -39,3 +41,27 @@ export const KubernetesBaseTestModules = [ HttpClientModule, SharedModule, ]; + +export const KubernetesActivatedRouteMock = { + provide: ActivatedRoute, + useValue: { + snapshot: { + queryParams: {}, + params: { + podName: 'podName123', + nodeName: 'nodeName123', + namespaceName: 'namespaceName123', + }, + } + } +}; + +export const KubernetesGuidMock = { + provide: BaseKubeGuid, + useValue: { + guid: '5123:4' + }, + deps: [ + KubernetesActivatedRouteMock, + ] +}; diff --git a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-namespaces/kube-namespace-pod-count/kube-namespace-pod-count.component.spec.ts b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-namespaces/kube-namespace-pod-count/kube-namespace-pod-count.component.spec.ts index d3ad45391a..c1d6821192 100644 --- a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-namespaces/kube-namespace-pod-count/kube-namespace-pod-count.component.spec.ts +++ b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-namespaces/kube-namespace-pod-count/kube-namespace-pod-count.component.spec.ts @@ -1,7 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { BaseKubeGuid } from '../../../kubernetes-page.types'; -import { KubernetesBaseTestModules } from '../../../kubernetes.testing.module'; +import { KubernetesBaseTestModules, KubernetesGuidMock } from '../../../kubernetes.testing.module'; import { HelmReleaseService } from '../../../services/helm-release.service'; import { KubernetesEndpointService } from '../../../services/kubernetes-endpoint.service'; import { KubeNamespacePodCountComponent } from './kube-namespace-pod-count.component'; @@ -14,7 +13,7 @@ describe('KubeNamespacePodCountComponent', () => { TestBed.configureTestingModule({ declarations: [KubeNamespacePodCountComponent], imports: KubernetesBaseTestModules, - providers: [BaseKubeGuid, HelmReleaseService, KubernetesEndpointService] + providers: [KubernetesGuidMock, HelmReleaseService, KubernetesEndpointService] }) .compileComponents(); })); diff --git a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.html b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.html index 99161c715c..453d1491c3 100644 --- a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.html +++ b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.html @@ -1 +1,2 @@ -{{row.metadata.name}} + +{{ row.metadata.name }} diff --git a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.spec.ts b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.spec.ts index 4a6acb5d3e..3ddcbb9c59 100644 --- a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.spec.ts +++ b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.spec.ts @@ -1,7 +1,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { BaseKubeGuid } from '../../../kubernetes-page.types'; -import { KubernetesBaseTestModules } from '../../../kubernetes.testing.module'; +import { PanelPreviewService } from '../../../../../../../core/src/shared/services/panel-preview.service'; +import { KubernetesBaseTestModules, KubernetesGuidMock } from '../../../kubernetes.testing.module'; import { KubernetesEndpointService } from '../../../services/kubernetes-endpoint.service'; import { KubernetesNodeLinkComponent } from './kubernetes-node-link.component'; @@ -13,7 +13,7 @@ describe('KubernetesNodeLinkComponent', () => { TestBed.configureTestingModule({ declarations: [KubernetesNodeLinkComponent], imports: KubernetesBaseTestModules, - providers: [KubernetesEndpointService, BaseKubeGuid] + providers: [KubernetesEndpointService, KubernetesGuidMock, PanelPreviewService] }) .compileComponents(); })); diff --git a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.ts b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.ts index 21d7d00432..fcf6c775d7 100644 --- a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.ts +++ b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.ts @@ -1,6 +1,10 @@ import { Component, OnInit } from '@angular/core'; +import { PanelPreviewService } from '../../../../../../../core/src/shared/services/panel-preview.service'; import { TableCellCustom } from '../../../../../shared/components/list/list.types'; +import { + KubernetesNodePreviewComponent, +} from '../../../kubernetes-node/kubernetes-node-preview/kubernetes-node-preview.component'; import { KubernetesEndpointService } from '../../../services/kubernetes-endpoint.service'; import { KubernetesNode } from '../../../store/kube.types'; @@ -13,7 +17,8 @@ export class KubernetesNodeLinkComponent extends TableCellCustom public nodeLink; constructor( - private kubeEndpointService: KubernetesEndpointService + private kubeEndpointService: KubernetesEndpointService, + private panelPreviewService: PanelPreviewService, ) { super(); } @@ -22,4 +27,13 @@ export class KubernetesNodeLinkComponent extends TableCellCustom this.nodeLink = `/kubernetes/${this.kubeEndpointService.kubeGuid}/nodes/${this.row.metadata.name}`; } + openSidepanelPreview($event: MouseEvent) { + $event.preventDefault(); + $event.stopPropagation(); + this.panelPreviewService.show(KubernetesNodePreviewComponent, { + nodeName: this.row.metadata.name, + kubeGuid: this.kubeEndpointService.kubeGuid, + }); + } + } diff --git a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-condition-card/kubernetes-node-condition-card.component.spec.ts b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-condition-card/kubernetes-node-condition-card.component.spec.ts index 5d22a63f1b..8199ac4d08 100644 --- a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-condition-card/kubernetes-node-condition-card.component.spec.ts +++ b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-condition-card/kubernetes-node-condition-card.component.spec.ts @@ -1,7 +1,10 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { BaseKubeGuid } from '../../../../kubernetes-page.types'; -import { KubernetesBaseTestModules } from '../../../../kubernetes.testing.module'; +import { + KubernetesActivatedRouteMock, + KubernetesBaseTestModules, + KubernetesGuidMock, +} from '../../../../kubernetes.testing.module'; import { KubernetesEndpointService } from '../../../../services/kubernetes-endpoint.service'; import { KubernetesNodeService } from '../../../../services/kubernetes-node.service'; import { KubernetesNodeConditionCardComponent } from './kubernetes-node-condition-card.component'; @@ -16,7 +19,11 @@ describe('KubernetesNodeConditionCardComponent', () => { TestBed.configureTestingModule({ declarations: [KubernetesNodeConditionCardComponent, KubernetesNodeConditionComponent], imports: KubernetesBaseTestModules, - providers: [BaseKubeGuid, KubernetesEndpointService, KubernetesNodeService] + providers: [ + KubernetesEndpointService, + KubernetesNodeService, + KubernetesActivatedRouteMock, + KubernetesGuidMock] }) .compileComponents(); })); diff --git a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary.component.spec.ts b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary.component.spec.ts index 629d1c742b..0183f76e48 100644 --- a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary.component.spec.ts +++ b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary.component.spec.ts @@ -1,7 +1,10 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { BaseKubeGuid } from '../../../kubernetes-page.types'; -import { KubernetesBaseTestModules } from '../../../kubernetes.testing.module'; +import { + KubernetesActivatedRouteMock, + KubernetesBaseTestModules, + KubernetesGuidMock, +} from '../../../kubernetes.testing.module'; import { KubernetesEndpointService } from '../../../services/kubernetes-endpoint.service'; import { KubernetesNodeService } from '../../../services/kubernetes-node.service'; import { @@ -29,7 +32,12 @@ describe('KubernetesNodeSummaryComponent', () => { KubernetesNodeTagsCardComponent, ], imports: KubernetesBaseTestModules, - providers: [BaseKubeGuid, KubernetesEndpointService, KubernetesNodeService] + providers: [ + KubernetesEndpointService, + KubernetesNodeService, + KubernetesActivatedRouteMock, + KubernetesGuidMock + ] }) .compileComponents(); })); diff --git a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-tags-card/kubernetes-node-tags-card.component.spec.ts b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-tags-card/kubernetes-node-tags-card.component.spec.ts index 95a2a45250..4b18665029 100644 --- a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-tags-card/kubernetes-node-tags-card.component.spec.ts +++ b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-tags-card/kubernetes-node-tags-card.component.spec.ts @@ -1,7 +1,10 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { BaseKubeGuid } from '../../../../kubernetes-page.types'; -import { KubernetesBaseTestModules } from '../../../../kubernetes.testing.module'; +import { + KubernetesActivatedRouteMock, + KubernetesBaseTestModules, + KubernetesGuidMock, +} from '../../../../kubernetes.testing.module'; import { KubernetesEndpointService } from '../../../../services/kubernetes-endpoint.service'; import { KubernetesNodeService } from '../../../../services/kubernetes-node.service'; import { KubernetesNodeTagsCardComponent } from './kubernetes-node-tags-card.component'; @@ -14,8 +17,12 @@ describe('KubernetesNodeTagsCardComponent', () => { TestBed.configureTestingModule({ declarations: [KubernetesNodeTagsCardComponent], imports: KubernetesBaseTestModules, - providers: [BaseKubeGuid, KubernetesEndpointService, KubernetesNodeService], - + providers: [ + KubernetesGuidMock, + KubernetesActivatedRouteMock, + KubernetesEndpointService, + KubernetesNodeService + ], }) .compileComponents(); })); diff --git a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/node-pod-count/node-pod-count.component.spec.ts b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/node-pod-count/node-pod-count.component.spec.ts index 671d7234ec..c91854ff9d 100644 --- a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/node-pod-count/node-pod-count.component.spec.ts +++ b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/node-pod-count/node-pod-count.component.spec.ts @@ -1,7 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { BaseKubeGuid } from '../../../kubernetes-page.types'; -import { KubernetesBaseTestModules } from '../../../kubernetes.testing.module'; +import { KubernetesBaseTestModules, KubernetesGuidMock } from '../../../kubernetes.testing.module'; import { KubernetesEndpointService } from '../../../services/kubernetes-endpoint.service'; import { NodePodCountComponent } from './node-pod-count.component'; @@ -13,7 +12,7 @@ describe('NodePodCountComponent', () => { TestBed.configureTestingModule({ declarations: [NodePodCountComponent], imports: KubernetesBaseTestModules, - providers: [BaseKubeGuid, KubernetesEndpointService] + providers: [KubernetesGuidMock, KubernetesEndpointService] }) .compileComponents(); })); diff --git a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-pods/pod-name-link/pod-name-link.component.html b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-pods/pod-name-link/pod-name-link.component.html index c035910aa9..3fa3aa832a 100644 --- a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-pods/pod-name-link/pod-name-link.component.html +++ b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-pods/pod-name-link/pod-name-link.component.html @@ -1,2 +1,2 @@ {{row.metadata.name}} -{{row.metadata.name}} \ No newline at end of file +{{row.metadata.name}} \ No newline at end of file diff --git a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-pods/pod-name-link/pod-name-link.component.spec.ts b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-pods/pod-name-link/pod-name-link.component.spec.ts index 515736cc10..845ddba952 100644 --- a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-pods/pod-name-link/pod-name-link.component.spec.ts +++ b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-pods/pod-name-link/pod-name-link.component.spec.ts @@ -1,7 +1,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { BaseKubeGuid } from '../../../kubernetes-page.types'; -import { KubernetesBaseTestModules } from '../../../kubernetes.testing.module'; +import { PanelPreviewService } from '../../../../../../../core/src/shared/services/panel-preview.service'; +import { KubernetesBaseTestModules, KubernetesGuidMock } from '../../../kubernetes.testing.module'; import { HelmReleaseService } from '../../../services/helm-release.service'; import { KubernetesEndpointService } from '../../../services/kubernetes-endpoint.service'; import { KubernetesStatus } from '../../../store/kube.types'; @@ -15,8 +15,12 @@ describe('PodNameLinkComponent', () => { TestBed.configureTestingModule({ declarations: [PodNameLinkComponent], imports: KubernetesBaseTestModules, - providers: [BaseKubeGuid, HelmReleaseService, KubernetesEndpointService] - + providers: [ + HelmReleaseService, + KubernetesEndpointService, + KubernetesGuidMock, + PanelPreviewService, + ] }) .compileComponents(); })); diff --git a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-pods/pod-name-link/pod-name-link.component.ts b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-pods/pod-name-link/pod-name-link.component.ts index 0b7495c562..8da4f6e361 100644 --- a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-pods/pod-name-link/pod-name-link.component.ts +++ b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-pods/pod-name-link/pod-name-link.component.ts @@ -3,9 +3,11 @@ import { ActivatedRoute } from '@angular/router'; import { Observable } from 'rxjs'; import { first } from 'rxjs/operators'; +import { PanelPreviewService } from '../../../../../../../core/src/shared/services/panel-preview.service'; import { EndpointsService } from '../../../../../core/endpoints.service'; import { getIdFromRoute } from '../../../../../core/utils.service'; import { TableCellCustom } from '../../../../../shared/components/list/list.types'; +import { KubernetesPodPreviewComponent } from '../../../kubernetes-pod-preview/kubernetes-pod-preview.component'; import { KubernetesEndpointService } from '../../../services/kubernetes-endpoint.service'; import { KubernetesPod } from '../../../store/kube.types'; @@ -20,7 +22,8 @@ export class PodNameLinkComponent extends TableCellCustom impleme constructor( private activatedRoute: ActivatedRoute, private endpointsService: EndpointsService, - private kubeEndpointService: KubernetesEndpointService + private kubeEndpointService: KubernetesEndpointService, + private panelPreviewService: PanelPreviewService, ) { super(); } @@ -36,4 +39,15 @@ export class PodNameLinkComponent extends TableCellCustom impleme this.routerLink = [this.row.metadata.name]; } } + + openSidepanelPreview($event: MouseEvent) { + $event.preventDefault(); + $event.stopPropagation(); + this.panelPreviewService.show(KubernetesPodPreviewComponent, { + podGuid: this.row.metadata.uid, + podName: this.row.metadata.name, + namespaceName: this.row.metadata.namespace, + kubeGuid: this.kubeEndpointService.kubeGuid, + }); + } } diff --git a/custom-src/frontend/app/custom/kubernetes/pod-metrics/pod-metrics.component.spec.ts b/custom-src/frontend/app/custom/kubernetes/pod-metrics/pod-metrics.component.spec.ts index eba50aaea2..712e3ca714 100644 --- a/custom-src/frontend/app/custom/kubernetes/pod-metrics/pod-metrics.component.spec.ts +++ b/custom-src/frontend/app/custom/kubernetes/pod-metrics/pod-metrics.component.spec.ts @@ -1,7 +1,8 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { TabNavService } from '../../../../tab-nav.service'; -import { KubernetesBaseTestModules } from '../kubernetes.testing.module'; +import { BaseKubeGuid } from '../kubernetes-page.types'; +import { KubernetesBaseTestModules, KubernetesGuidMock } from '../kubernetes.testing.module'; import { PodMetricsComponent } from './pod-metrics.component'; @@ -10,6 +11,7 @@ describe('PodMetricsComponent', () => { let fixture: ComponentFixture; beforeEach(async(() => { + TestBed.overrideProvider(BaseKubeGuid, KubernetesGuidMock); TestBed.configureTestingModule({ declarations: [PodMetricsComponent], imports: KubernetesBaseTestModules, diff --git a/custom-src/frontend/app/custom/kubernetes/services/kubernetes-endpoint.service.ts b/custom-src/frontend/app/custom/kubernetes/services/kubernetes-endpoint.service.ts index dd2e4c7ccd..7c11950b57 100644 --- a/custom-src/frontend/app/custom/kubernetes/services/kubernetes-endpoint.service.ts +++ b/custom-src/frontend/app/custom/kubernetes/services/kubernetes-endpoint.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; -import { Observable } from 'rxjs'; +import { combineLatest, Observable } from 'rxjs'; import { filter, first, map, shareReplay, startWith, switchMap } from 'rxjs/operators'; import { GetAllEndpoints } from '../../../../../store/src/actions/endpoint.actions'; @@ -12,16 +12,27 @@ import { getPaginationObservables } from '../../../../../store/src/reducers/pagi import { EntityInfo } from '../../../../../store/src/types/api.types'; import { EndpointModel, EndpointUser } from '../../../../../store/src/types/endpoint.types'; import { + kubernetesAppsEntityType, kubernetesDeploymentsEntityType, + kubernetesNodesEntityType, kubernetesPodsEntityType, kubernetesServicesEntityType, kubernetesStatefulSetsEntityType, } from '../kubernetes-entity-factory'; import { BaseKubeGuid } from '../kubernetes-page.types'; -import { KubernetesDeployment, KubernetesPod, KubernetesStatefulSet, KubeService } from '../store/kube.types'; +import { + KubernetesApp, + KubernetesDeployment, + KubernetesNode, + KubernetesPod, + KubernetesStatefulSet, + KubeService, +} from '../store/kube.types'; import { GeKubernetesDeployments, + GetKubernetesApps, GetKubernetesDashboard, + GetKubernetesNodes, GetKubernetesPods, GetKubernetesServices, GetKubernetesStatefulSets, @@ -42,6 +53,8 @@ export class KubernetesEndpointService { statefulSets$: Observable; services$: Observable; pods$: Observable; + apps$: Observable; + nodes$: Observable; kubeDashboardEnabled$: Observable; constructor( @@ -50,7 +63,16 @@ export class KubernetesEndpointService { private entityServiceFactory: EntityServiceFactory, private paginationMonitorFactory: PaginationMonitorFactory ) { - this.kubeGuid = baseKube.guid; + const kubeGuid = baseKube.guid; + + if (kubeGuid) { + this.initialize(kubeGuid); + } + } + + initialize(kubeGuid) { + this.kubeGuid = kubeGuid; + this.kubeEndpointEntityService = this.entityServiceFactory.create( this.kubeGuid, new GetAllEndpoints() @@ -59,7 +81,77 @@ export class KubernetesEndpointService { this.constructCoreObservables(); } - constructCoreObservables() { + getNodeKubeVersions(nodes$: Observable = this.nodes$) { + return nodes$.pipe( + map(nodes => { + const versions = {}; + nodes.forEach(node => { + const v = node.status.nodeInfo.kubeletVersion; + if (!versions[v]) { + versions[v] = v; + } + }); + return Object.keys(versions).join(','); + }) + ); + } + + getCountObservable(entities$: Observable) { + return entities$.pipe( + map(entities => entities.length), + startWith(null) + ); + } + + getPodCapacity(nodes$: Observable = this.nodes$, pods$: Observable = this.pods$) { + return combineLatest(nodes$, pods$).pipe( + map(([nodes, pods]) => ({ + total: nodes.reduce((cap, node) => { + return cap + parseInt(node.status.capacity.pods, 10); + }, 0), + used: pods.length + })) + ); + } + + getNodeStatusCount( + nodes$: Observable, + conditionType: string, + valueLabels: object = {}, + countStatus = 'True' + ) { + return nodes$.pipe( + map(nodes => { + const total = nodes.length; + const { unknown, unavailable, used } = nodes.reduce((cap, node) => { + const conditionStatus = node.status.conditions.find(con => con.type === conditionType); + if (!conditionStatus || !conditionStatus.status) { + ++cap.unavailable; + } else { + if (conditionStatus.status === countStatus) { + ++cap.used; + } else if (conditionStatus.status === 'Unknown') { + ++cap.unknown; + } + } + return cap; + }, { unavailable: 0, used: 0, unknown: 0 }); + const result = { + total, + supported: total !== unavailable, + // Depends on K8S version as to what is supported + unavailable, + used, + unknown, + ...valueLabels + }; + result.supported = result.total !== result.unavailable; + return result; + }) + ); + } + + private constructCoreObservables() { this.endpoint$ = this.kubeEndpointEntityService.waitForEntity$; this.connected$ = this.endpoint$.pipe( @@ -78,6 +170,16 @@ export class KubernetesEndpointService { kubernetesPodsEntityType ); + this.nodes$ = this.getObservable( + new GetKubernetesNodes(this.kubeGuid), + kubernetesNodesEntityType + ); + + this.apps$ = this.getObservable( + new GetKubernetesApps(this.kubeGuid), + kubernetesAppsEntityType + ); + this.statefulSets$ = this.getObservable( new GetKubernetesStatefulSets(this.kubeGuid), kubernetesStatefulSetsEntityType diff --git a/custom-src/frontend/app/custom/kubernetes/services/kubernetes-node.service.ts b/custom-src/frontend/app/custom/kubernetes/services/kubernetes-node.service.ts index 1cb1073ec1..734cde2ad5 100644 --- a/custom-src/frontend/app/custom/kubernetes/services/kubernetes-node.service.ts +++ b/custom-src/frontend/app/custom/kubernetes/services/kubernetes-node.service.ts @@ -37,8 +37,17 @@ export class KubernetesNodeService { public entityServiceFactory: EntityServiceFactory, public entityMonitorFactory: EntityMonitorFactory ) { - this.nodeName = getIdFromRoute(activatedRoute, 'nodeName'); - this.kubeGuid = kubeEndpointService.kubeGuid; + const nodeName = getIdFromRoute(activatedRoute, 'nodeName'); + const kubeGuid = kubeEndpointService.kubeGuid; + + if (nodeName && kubeGuid) { + this.initialize(nodeName, kubeGuid); + } + } + + initialize(nodeName, kubeGuid) { + this.nodeName = nodeName; + this.kubeGuid = kubeGuid; const nodeEntityService = this.entityServiceFactory.create( this.nodeName, @@ -56,8 +65,6 @@ export class KubernetesNodeService { ); } - - public setupMetricObservable(metric: KubeNodeMetric, metricStatistic: MetricStatistic) { const query = `${metricStatistic}(${metricStatistic}_over_time(${metric}{kubernetes_io_hostname="${this.nodeName}"}[1h]))`; diff --git a/custom-src/frontend/app/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.spec.ts b/custom-src/frontend/app/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.spec.ts index 7c7ac7079f..19bcb896be 100644 --- a/custom-src/frontend/app/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.spec.ts +++ b/custom-src/frontend/app/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.spec.ts @@ -2,8 +2,7 @@ import { HttpClient, HttpHandler } from '@angular/common/http'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { TabNavService } from '../../../../../tab-nav.service'; -import { BaseKubeGuid } from '../../kubernetes-page.types'; -import { KubernetesBaseTestModules } from '../../kubernetes.testing.module'; +import { KubernetesBaseTestModules, KubernetesGuidMock } from '../../kubernetes.testing.module'; import { KubernetesEndpointService } from '../../services/kubernetes-endpoint.service'; import { KubernetesSummaryTabComponent } from './kubernetes-summary.component'; @@ -17,7 +16,7 @@ describe('KubernetesSummaryTabComponent', () => { imports: [...KubernetesBaseTestModules], providers: [ KubernetesEndpointService, - BaseKubeGuid, + KubernetesGuidMock, HttpClient, HttpHandler, TabNavService diff --git a/custom-src/frontend/app/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.ts b/custom-src/frontend/app/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.ts index fba869bcd4..104583bc30 100644 --- a/custom-src/frontend/app/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.ts +++ b/custom-src/frontend/app/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.ts @@ -17,8 +17,6 @@ import { } from '../../../../shared/components/simple-usage-chart/simple-usage-chart.types'; import { KubernetesEndpointService } from '../../services/kubernetes-endpoint.service'; import { GetKubernetesNodes, GetKubernetesPods } from '../../store/kubernetes.actions'; -import { KubernetesNode } from './../../../../../../../../../custom-src/frontend/app/custom/kubernetes/store/kube.types'; -import { KubernetesPod } from './../../store/kube.types'; import { GetKubernetesApps } from './../../store/kubernetes.actions'; interface IValueLabels { @@ -95,7 +93,6 @@ export class KubernetesSummaryTabComponent implements OnInit, OnDestroy { public isLoading$: Observable; - constructor( public kubeEndpointService: KubernetesEndpointService, public httpClient: HttpClient, @@ -105,99 +102,6 @@ export class KubernetesSummaryTabComponent implements OnInit, OnDestroy { ) { } - private getPaginationObservable(action: PaginatedAction) { - const paginationMonitor = this.paginationMonitorFactory.create( - action.paginationKey, - action - ); - - this.ngZone.runOutsideAngular(() => { - this.polls.push( - interval(10000).subscribe(() => { - this.ngZone.run(() => { - this.store.dispatch(action); - }); - }) - ); - }); - - return getPaginationObservables({ - store: this.store, - action, - paginationMonitor - }).entities$; - } - - private getCountObservable(entities$: Observable) { - return entities$.pipe( - map(entities => entities.length), - startWith(null) - ); - } - private getPodCapacity(nodes$: Observable, pods$: Observable) { - return combineLatest(nodes$, pods$).pipe( - map(([nodes, pods]) => ({ - total: nodes.reduce((cap, node) => { - return cap + parseInt(node.status.capacity.pods, 10); - }, 0), - used: pods.length - })) - ); - } - - private getNodeStatusCount( - nodes$: Observable, - conditionType: string, - valueLabels: IValueLabels = {}, - countStatus = 'True' - ) { - return nodes$.pipe( - map(nodes => { - const total = nodes.length; - const { unknown, unavailable, used } = nodes.reduce((cap, node) => { - const conditionStatus = node.status.conditions.find(con => con.type === conditionType); - if (!conditionStatus || !conditionStatus.status) { - ++cap.unavailable; - } else { - if (conditionStatus.status === countStatus) { - ++cap.used; - } else if (conditionStatus.status === 'Unknown') { - ++cap.unknown; - } - } - return cap; - }, { unavailable: 0, used: 0, unknown: 0 }); - const result = { - total, - supported: total !== unavailable, - // Depends on K8S version as to what is supported - unavailable, - used, - unknown, - ...valueLabels - }; - result.supported = result.total !== result.unavailable; - return result; - }) - ); - } - - private getNodeKubeVersions(nodes$: Observable) { - return nodes$.pipe( - map(nodes => { - const versions = {}; - nodes.forEach(node => { - const v = node.status.nodeInfo.kubeletVersion; - if (!versions[v]) { - versions[v] = v; - } - }); - return Object.keys(versions).join(','); - }) - ); - } - - ngOnInit() { const guid = this.kubeEndpointService.baseKube.guid; @@ -208,36 +112,36 @@ export class KubernetesSummaryTabComponent implements OnInit, OnDestroy { const pods$ = this.getPaginationObservable(podCountAction); const nodes$ = this.getPaginationObservable(nodeCountAction); - this.podCount$ = this.getCountObservable(pods$); - this.nodeCount$ = this.getCountObservable(nodes$); - this.appCount$ = this.getCountObservable(applications$); + this.podCount$ = this.kubeEndpointService.getCountObservable(pods$); + this.nodeCount$ = this.kubeEndpointService.getCountObservable(nodes$); + this.appCount$ = this.kubeEndpointService.getCountObservable(applications$); - this.podCapacity$ = this.getPodCapacity(nodes$, pods$); - this.diskPressure$ = this.getNodeStatusCount(nodes$, 'DiskPressure', { + this.podCapacity$ = this.kubeEndpointService.getPodCapacity(nodes$, pods$); + this.diskPressure$ = this.kubeEndpointService.getNodeStatusCount(nodes$, 'DiskPressure', { usedLabel: 'Nodes with disk pressure', remainingLabel: 'Nodes with no disk pressure', unknownLabel: 'Nodes with unknown disk pressure', warningText: 'Nodes with unknown disk pressure found' }); - this.memoryPressure$ = this.getNodeStatusCount(nodes$, 'MemoryPressure', { + this.memoryPressure$ = this.kubeEndpointService.getNodeStatusCount(nodes$, 'MemoryPressure', { usedLabel: 'Nodes with memory pressure', remainingLabel: 'Nodes with no memory pressure', unknownLabel: 'Nodes with unknown memory pressure', warningText: 'Nodes with unknown memory pressure found' }); - this.outOfDisk$ = this.getNodeStatusCount(nodes$, 'OutOfDisk', { + this.outOfDisk$ = this.kubeEndpointService.getNodeStatusCount(nodes$, 'OutOfDisk', { usedLabel: 'Nodes that are out of disk space', remainingLabel: 'Nodes that have disk space remaining', unknownLabel: 'Nodes with unknown remaining disk space', warningText: 'Nodes with unknown remaining disk space found' }); - this.networkUnavailable$ = this.getNodeStatusCount(nodes$, 'NetworkUnavailable', { + this.networkUnavailable$ = this.kubeEndpointService.getNodeStatusCount(nodes$, 'NetworkUnavailable', { usedLabel: 'Nodes with available networks', remainingLabel: 'Nodes with unavailable networks', unknownLabel: 'Nodes with unknown networks availability', warningText: 'Nodes with unknown networks availability found' }, 'False'); - this.nodesReady$ = this.getNodeStatusCount(nodes$, 'Ready', { + this.nodesReady$ = this.kubeEndpointService.getNodeStatusCount(nodes$, 'Ready', { usedLabel: 'Nodes are ready', remainingLabel: 'Nodes are not ready', unknownLabel: 'Nodes with unknown ready status', @@ -245,7 +149,7 @@ export class KubernetesSummaryTabComponent implements OnInit, OnDestroy { }); this.dashboardLink = `/kubernetes/${guid}/dashboard`; - this.kubeNodeVersions$ = this.getNodeKubeVersions(nodes$).pipe(startWith('-')); + this.kubeNodeVersions$ = this.kubeEndpointService.getNodeKubeVersions(nodes$).pipe(startWith('-')); this.isLoading$ = combineLatest([ this.endpointDetails$, @@ -264,6 +168,29 @@ export class KubernetesSummaryTabComponent implements OnInit, OnDestroy { ); } + private getPaginationObservable(action: PaginatedAction) { + const paginationMonitor = this.paginationMonitorFactory.create( + action.paginationKey, + action + ); + + this.ngZone.runOutsideAngular(() => { + this.polls.push( + interval(10000).subscribe(() => { + this.ngZone.run(() => { + this.store.dispatch(action); + }); + }) + ); + }); + + return getPaginationObservables({ + store: this.store, + action, + paginationMonitor + }).entities$; + } + ngOnDestroy() { safeUnsubscribe(...(this.polls || [])); } diff --git a/src/frontend/packages/cloud-foundry/src/cf-entity-generator.ts b/src/frontend/packages/cloud-foundry/src/cf-entity-generator.ts index 5e38128d1d..b0848852f6 100644 --- a/src/frontend/packages/cloud-foundry/src/cf-entity-generator.ts +++ b/src/frontend/packages/cloud-foundry/src/cf-entity-generator.ts @@ -119,7 +119,11 @@ import { spaceActionBuilders } from './entity-action-builders/space.action-build import { stackActionBuilders } from './entity-action-builders/stack-action-builders'; import { userProvidedServiceActionBuilder } from './entity-action-builders/user-provided-service.action-builders'; import { userActionBuilders } from './entity-action-builders/user.action-builders'; +import { ApplicationPreviewComponent } from './shared/components/application-preview/application-preview.component'; import { CfEndpointDetailsComponent } from './shared/components/cf-endpoint-details/cf-endpoint-details.component'; +import { CfEndpointPreviewComponent } from './shared/components/cf-endpoint-preview/cf-endpoint-preview.component'; +import { OrganizationPreviewComponent } from './shared/components/organization-preview/organization-preview.component'; +import { SpacePreviewComponent } from './shared/components/space-preview/space-preview.component'; import { updateApplicationRoutesReducer } from './store/reducers/application-route.reducer'; import { updateOrganizationQuotaReducer } from './store/reducers/organization-quota.reducer'; import { updateOrganizationSpaceReducer } from './store/reducers/organization-space.reducer'; @@ -862,6 +866,7 @@ function generateCfEndpointEntity(endpointDefinition: StratosEndpointExtensionDe return new StratosCatalogEndpointEntity( endpointDefinition, metadata => `/cloud-foundry/${metadata.guid}`, + () => CfEndpointPreviewComponent, ); } @@ -896,6 +901,7 @@ function generateCfApplicationEntity(endpointDefinition: StratosEndpointExtensio }), getLink: metadata => `/applications/${metadata.cfGuid}/${metadata.guid}/summary`, getGuid: metadata => metadata.guid, + getPreviewableComponent: () => ApplicationPreviewComponent, getLines: () => ([ ['Creation Date', (meta) => meta.createdAt] ]) @@ -933,6 +939,7 @@ function generateCfSpaceEntity(endpointDefinition: StratosEndpointExtensionDefin cfGuid: space.entity.cfGuid, createdAt: moment(space.metadata.created_at).format('LLL'), }), + getPreviewableComponent: () => SpacePreviewComponent, getLines: () => ([ ['Creation Date', (meta) => meta.createdAt] ]), @@ -969,6 +976,7 @@ function generateCfOrgEntity(endpointDefinition: StratosEndpointExtensionDefinit cfGuid: org.entity.cfGuid, createdAt: moment(org.metadata.created_at).format('LLL'), }), + getPreviewableComponent: () => OrganizationPreviewComponent, getLink: metadata => `/cloud-foundry/${metadata.cfGuid}/organizations/${metadata.guid}`, getLines: () => ([ ['Creation Date', (meta) => meta.createdAt] diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts b/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts index 1f05fae9d4..b440c12b64 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts @@ -92,21 +92,9 @@ export class ApplicationService { private appEnvVarsService: ApplicationEnvVarsHelper, private paginationMonitorFactory: PaginationMonitorFactory, ) { - this.appEntityService = this.entityServiceFactory.create>( - appGuid, - createGetApplicationAction(appGuid, cfGuid) - ); - const appSummaryEntity = entityCatalog.getEntity(CF_ENDPOINT_TYPE, appSummaryEntityType); - const actionBuilder = appSummaryEntity.actionOrchestrator.getActionBuilder('get'); - const getAppSummaryAction = actionBuilder(appGuid, cfGuid); - this.appSummaryEntityService = this.entityServiceFactory.create( - appGuid, - getAppSummaryAction - ); - - this.constructCoreObservables(); - this.constructAmalgamatedObservables(); - this.constructStatusObservables(); + if (cfGuid && appGuid) { + this.initialize(cfGuid, appGuid); + } } // NJ: This needs to be cleaned up. So much going on! @@ -162,6 +150,27 @@ export class ApplicationService { ).pipe(publishReplay(1), refCount()); } + public initialize(cfGuid, appGuid) { + this.cfGuid = cfGuid; + this.appGuid = appGuid; + + this.appEntityService = this.entityServiceFactory.create>( + appGuid, + createGetApplicationAction(appGuid, cfGuid) + ); + const appSummaryEntity = entityCatalog.getEntity(CF_ENDPOINT_TYPE, appSummaryEntityType); + const actionBuilder = appSummaryEntity.actionOrchestrator.getActionBuilder('get'); + const getAppSummaryAction = actionBuilder(appGuid, cfGuid); + this.appSummaryEntityService = this.entityServiceFactory.create( + appGuid, + getAppSummaryAction + ); + + this.constructCoreObservables(); + this.constructAmalgamatedObservables(); + this.constructStatusObservables(); + } + private constructCoreObservables() { // First set up all the base observables this.app$ = this.appEntityService.waitForEntity$; diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry.module.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry.module.ts index b03615d07a..7383b46dac 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry.module.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry.module.ts @@ -41,6 +41,7 @@ import { CloudFoundryComponentsModule } from '../../shared/components/components import { CFEndpointsListConfigService, } from '../../shared/components/list/list-types/cf-endpoints/cf-endpoints-list-config.service'; +import { ApplicationService } from '../applications/application.service'; import { AddOrganizationComponent } from './add-organization/add-organization.component'; import { CreateOrganizationStepComponent, @@ -60,6 +61,7 @@ import { EditSpaceComponent } from './edit-space/edit-space.component'; import { QuotaDefinitionComponent } from './quota-definition/quota-definition.component'; import { CloudFoundryEndpointService } from './services/cloud-foundry-endpoint.service'; import { CloudFoundryOrganizationService } from './services/cloud-foundry-organization.service'; +import { CloudFoundrySpaceService } from './services/cloud-foundry-space.service'; import { SpaceQuotaDefinitionComponent } from './space-quota-definition/space-quota-definition.component'; import { CfAdminAddUserWarningComponent } from './tabs/cf-admin-add-user-warning/cf-admin-add-user-warning.component'; import { CloudFoundryBuildPacksComponent } from './tabs/cloud-foundry-build-packs/cloud-foundry-build-packs.component'; @@ -230,6 +232,8 @@ import { CloudFoundrySpaceEventsComponent } from './tabs/cloud-foundry-organizat provide: ActiveRouteCfCell, useValue: {} }, + ApplicationService, + CloudFoundrySpaceService, CloudFoundryOrganizationService, CloudFoundryEndpointService, // CfRolesService, diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-organization/edit-organization-step/edit-organization-step.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-organization/edit-organization-step/edit-organization-step.component.spec.ts index fe3c60c481..9c64c4f2d8 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-organization/edit-organization-step/edit-organization-step.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-organization/edit-organization-step/edit-organization-step.component.spec.ts @@ -4,7 +4,6 @@ import { generateCfBaseTestModules, generateTestCfEndpointServiceProvider, } from '../../../../../test-framework/cloud-foundry-endpoint-service.helper'; -import { ActiveRouteCfOrgSpace } from '../../cf-page.types'; import { EditOrganizationStepComponent } from './edit-organization-step.component'; describe('EditOrganizationStepComponent', () => { @@ -15,7 +14,7 @@ describe('EditOrganizationStepComponent', () => { TestBed.configureTestingModule({ declarations: [EditOrganizationStepComponent], imports: generateCfBaseTestModules(), - providers: [ActiveRouteCfOrgSpace, generateTestCfEndpointServiceProvider()] + providers: generateTestCfEndpointServiceProvider(), }) .compileComponents(); })); diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-organization/edit-organization.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-organization/edit-organization.component.spec.ts index ecead39a28..4582bdeab3 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-organization/edit-organization.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-organization/edit-organization.component.spec.ts @@ -5,7 +5,7 @@ import { generateCfBaseTestModules, generateTestCfEndpointServiceProvider, } from '../../../../test-framework/cloud-foundry-endpoint-service.helper'; -import { ActiveRouteCfOrgSpace } from '../cf-page.types'; +import { CloudFoundryOrganizationService } from '../services/cloud-foundry-organization.service'; import { EditOrganizationStepComponent } from './edit-organization-step/edit-organization-step.component'; import { EditOrganizationComponent } from './edit-organization.component'; @@ -17,8 +17,11 @@ describe('EditOrganizationComponent', () => { TestBed.configureTestingModule({ declarations: [EditOrganizationComponent, EditOrganizationStepComponent], imports: generateCfBaseTestModules(), - providers: [ActiveRouteCfOrgSpace, generateTestCfEndpointServiceProvider(), TabNavService] - + providers: [ + ...generateTestCfEndpointServiceProvider(), + CloudFoundryOrganizationService, + TabNavService + ] }) .compileComponents(); })); diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-space/edit-space-step/edit-space-step.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-space/edit-space-step/edit-space-step.component.spec.ts index 4dd6fbacb2..ac25e3cc86 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-space/edit-space-step/edit-space-step.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-space/edit-space-step/edit-space-step.component.spec.ts @@ -18,7 +18,7 @@ describe('EditSpaceStepComponent', () => { imports: generateCfBaseTestModules(), providers: [ { provide: CloudFoundrySpaceService, useClass: CloudFoundrySpaceServiceMock }, - generateTestCfEndpointServiceProvider(), + ...generateTestCfEndpointServiceProvider(), ] }) .compileComponents(); diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-space/edit-space.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-space/edit-space.component.spec.ts index a1c01fa14c..25d884f194 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-space/edit-space.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-space/edit-space.component.spec.ts @@ -5,7 +5,6 @@ import { generateCfBaseTestModules, generateTestCfEndpointServiceProvider, } from '../../../../test-framework/cloud-foundry-endpoint-service.helper'; -import { ActiveRouteCfOrgSpace } from '../cf-page.types'; import { CloudFoundryOrganizationService } from '../services/cloud-foundry-organization.service'; import { EditSpaceStepComponent } from './edit-space-step/edit-space-step.component'; import { EditSpaceComponent } from './edit-space.component'; @@ -18,7 +17,11 @@ describe('EditSpaceComponent', () => { TestBed.configureTestingModule({ declarations: [EditSpaceComponent, EditSpaceStepComponent], imports: generateCfBaseTestModules(), - providers: [ActiveRouteCfOrgSpace, generateTestCfEndpointServiceProvider(), TabNavService, CloudFoundryOrganizationService] + providers: [ + ...generateTestCfEndpointServiceProvider(), + TabNavService, + CloudFoundryOrganizationService + ] }) .compileComponents(); })); diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-endpoint.service.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-endpoint.service.ts index 504663a05b..0c5a25dd79 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-endpoint.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-endpoint.service.ts @@ -63,6 +63,8 @@ export function appDataSort(app1: APIResource, app2: APIResource): n export class CloudFoundryEndpointService { hasSSHAccess$: Observable; totalMem$: Observable; + description$: Observable; + apiUrl$: Observable; paginationSubscription: any; appsPagObs: PaginationObservables>; usersCount$: Observable; @@ -98,6 +100,7 @@ export class CloudFoundryEndpointService { }) as PaginatedAction; return getAllOrganizationsAction; } + static createGetAllOrganizationsLimitedSchema(cfGuid: string) { const paginationKey = cfGuid ? createEntityRelationPaginationKey(endpointSchemaKey, cfGuid) @@ -138,7 +141,14 @@ export class CloudFoundryEndpointService { private endpointService: EndpointsService, private paginationMonitorFactory: PaginationMonitorFactory ) { - this.cfGuid = activeRouteCfOrgSpace.cfGuid; + const cfGuid = activeRouteCfOrgSpace.cfGuid; + if (cfGuid) { + this.initialize(cfGuid); + } + } + + public initialize(cfGuid) { + this.cfGuid = cfGuid; this.getAllOrgsAction = CloudFoundryEndpointService.createGetAllOrganizations(this.cfGuid) as GetAllOrganizations; this.getAllAppsAction = new GetAllApplications(createEntityRelationPaginationKey('cf', this.cfGuid), this.cfGuid); @@ -197,6 +207,14 @@ export class CloudFoundryEndpointService { ); this.totalMem$ = this.appsPagObs.entities$.pipe(map(apps => this.getMetricFromApps(apps, 'memory'))); + this.apiUrl$ = this.endpoint$.pipe( + map(endpoint => this.getApiEndpointUrl(endpoint.entity.api_endpoint)) + ); + + this.description$ = this.info$.pipe( + map(entity => this.getDescription(entity)) + ); + this.connected$ = this.endpoint$.pipe( map(p => p.entity.connectionStatus === 'connected') ); @@ -256,6 +274,28 @@ export class CloudFoundryEndpointService { this.store.dispatch(this.getAllAppsAction); } + private getApiEndpointUrl(apiEndpoint) { + const path = apiEndpoint.Path ? `/${apiEndpoint.Path}` : ''; + return `${apiEndpoint.Scheme}://${apiEndpoint.Host}${path}`; + } + + private getMetadataFromInfo(entity: EntityInfo>) { + return entity && entity.entity && entity.entity.entity ? entity.entity.entity : null; + } + + private getDescription(entity: EntityInfo>): string { + const metadata = this.getMetadataFromInfo(entity); + if (metadata) { + if (metadata.description) { + return metadata.description + (metadata.build ? ` (${metadata.build})` : ''); + } + if (metadata.support === 'pcfdev@pivotal.io') { + return 'PCF Dev'; + } + } + return '-'; + } + hasCellMetrics(endpointId: string): Observable { return this.endpointService.hasMetrics(endpointId).pipe( switchMap(hasMetrics => { diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-organization.service.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-organization.service.ts index 1eddb01acf..7a31cdc09e 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-organization.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-organization.service.ts @@ -93,10 +93,12 @@ export class CloudFoundryOrganizationService { private cfEndpointService: CloudFoundryEndpointService, private cfUserProvidedServicesService: CloudFoundryUserProvidedServicesService ) { - this.orgGuid = activeRouteCfOrgSpace.orgGuid; - this.cfGuid = activeRouteCfOrgSpace.cfGuid; + const orgGuid = activeRouteCfOrgSpace.orgGuid; + const cfGuid = activeRouteCfOrgSpace.cfGuid; - this.initialiseObservables(); + if (cfGuid && orgGuid) { + this.initialize(cfGuid, orgGuid); + } } public deleteSpace(spaceGuid: string, orgGuid: string, endpointGuid: string) { @@ -110,7 +112,10 @@ export class CloudFoundryOrganizationService { this.cfEndpointService.fetchApps(); } - private initialiseObservables() { + public initialize(cfGuid, orgGuid) { + this.cfGuid = cfGuid; + this.orgGuid = orgGuid; + this.org$ = this.cfUserService.isConnectedUserAdmin(this.cfGuid).pipe( switchMap(isAdmin => { const relations = [ diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-space.service.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-space.service.ts index 8b34067470..d140faf08d 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-space.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-space.service.ts @@ -48,6 +48,7 @@ export class CloudFoundrySpaceService { */ spaceQuotaDefinition$: Observable; allowSsh$: Observable; + allowSshStatus$: Observable; totalMem$: Observable; routes$: Observable[]>; serviceInstancesCount$: Observable; @@ -71,9 +72,9 @@ export class CloudFoundrySpaceService { private cfOrgService: CloudFoundryOrganizationService ) { - this.spaceGuid = activeRouteCfOrgSpace.spaceGuid; - this.orgGuid = activeRouteCfOrgSpace.orgGuid; - this.cfGuid = activeRouteCfOrgSpace.cfGuid; + const spaceGuid = activeRouteCfOrgSpace.spaceGuid; + const orgGuid = activeRouteCfOrgSpace.orgGuid; + const cfGuid = activeRouteCfOrgSpace.cfGuid; this.initialiseObservables(); } @@ -82,7 +83,11 @@ export class CloudFoundrySpaceService { this.cfEndpointService.fetchApps(); } - private initialiseObservables() { + public initialize(cfGuid: string, orgGuid: string, spaceGuid: string) { + this.cfGuid = cfGuid; + this.orgGuid = orgGuid; + this.spaceGuid = spaceGuid; + this.initialiseSpaceObservables(); this.initialiseAppObservables(); @@ -100,6 +105,10 @@ export class CloudFoundrySpaceService { this.usersCount$ = this.cfUserService.fetchTotalUsers(this.cfGuid, this.orgGuid, this.spaceGuid); } + public fetchApps() { + this.cfEndpointService.fetchApps(); + } + private initialiseSpaceObservables() { this.space$ = this.cfUserService.isConnectedUserAdmin(this.cfGuid).pipe( switchMap(isAdmin => { @@ -142,6 +151,7 @@ export class CloudFoundrySpaceService { this.cfUserProvidedServicesService.fetchUserProvidedServiceInstancesCount(this.cfGuid, this.orgGuid, this.spaceGuid); this.routes$ = this.space$.pipe(map(o => o.entity.entity.routes)); this.allowSsh$ = this.space$.pipe(map(o => o.entity.entity.allow_ssh ? 'true' : 'false')); + this.allowSshStatus$ = this.allowSsh$.pipe(map(status => status === 'false' ? 'Disabled' : 'Enabled')); this.spaceQuotaDefinition$ = this.space$.pipe( map(q => q.entity.entity.space_quota_definition ? q.entity.entity.space_quota_definition.entity : null) ); @@ -211,9 +221,9 @@ export class CloudFoundrySpaceService { return CloudFoundryEndpointService.fetchAppCount( this.store, this.paginationMonitorFactory, - this.activeRouteCfOrgSpace.cfGuid, - this.activeRouteCfOrgSpace.orgGuid, - this.activeRouteCfOrgSpace.spaceGuid + this.cfGuid, + this.orgGuid, + this.spaceGuid ); } } diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/cloud-foundry-space-base/cloud-foundry-space-base.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/cloud-foundry-space-base/cloud-foundry-space-base.component.spec.ts index 38dfaf8ce3..261c5c7d3d 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/cloud-foundry-space-base/cloud-foundry-space-base.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/cloud-foundry-space-base/cloud-foundry-space-base.component.spec.ts @@ -15,7 +15,7 @@ describe('CloudFoundrySpaceBaseComponent', () => { TestBed.configureTestingModule({ declarations: [CloudFoundrySpaceBaseComponent], imports: generateCfBaseTestModules(), - providers: [generateTestCfEndpointServiceProvider(), TabNavService] + providers: [...generateTestCfEndpointServiceProvider(), TabNavService] }) .compileComponents(); })); diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.spec.ts index 37401db631..1faa90a3fe 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.spec.ts @@ -1,12 +1,13 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { TabNavService } from '../../../../../../../../../core/tab-nav.service'; -import { - generateCfBaseTestModules, -} from '../../../../../../../../test-framework/cloud-foundry-endpoint-service.helper'; import { CloudFoundrySpaceServiceMock, } from '../../../../../../../../../core/test-framework/cloud-foundry-space.service.mock'; +import { + generateCfBaseTestModules, + generateTestCfEndpointServiceProvider, +} from '../../../../../../../../test-framework/cloud-foundry-endpoint-service.helper'; import { CardCfRecentAppsComponent, } from '../../../../../../../shared/components/cards/card-cf-recent-apps/card-cf-recent-apps.component'; @@ -17,7 +18,6 @@ import { CardCfSpaceDetailsComponent, } from '../../../../../../../shared/components/cards/card-cf-space-details/card-cf-space-details.component'; import { CfUserService } from '../../../../../../../shared/data-services/cf-user.service'; -import { ActiveRouteCfOrgSpace } from '../../../../../cf-page.types'; import { CloudFoundryEndpointService } from '../../../../../services/cloud-foundry-endpoint.service'; import { CloudFoundryOrganizationService } from '../../../../../services/cloud-foundry-organization.service'; import { CloudFoundrySpaceService } from '../../../../../services/cloud-foundry-space.service'; @@ -32,10 +32,10 @@ describe('CloudFoundrySpaceSummaryComponent', () => { declarations: [CloudFoundrySpaceSummaryComponent, CardCfSpaceDetailsComponent, CardCfRecentAppsComponent, CompactAppCardComponent], imports: generateCfBaseTestModules(), providers: [ - ActiveRouteCfOrgSpace, CloudFoundryEndpointService, { provide: CloudFoundrySpaceService, useClass: CloudFoundrySpaceServiceMock }, CloudFoundryOrganizationService, + ...generateTestCfEndpointServiceProvider(), TabNavService, CfUserService ] diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/user-invites/configuration-dialog/user-invite-configuration-dialog.component.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/user-invites/configuration-dialog/user-invite-configuration-dialog.component.ts index f5d1ecd678..0e1061cae3 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/user-invites/configuration-dialog/user-invite-configuration-dialog.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/user-invites/configuration-dialog/user-invite-configuration-dialog.component.ts @@ -6,13 +6,14 @@ import { Observable, Subscription } from 'rxjs'; import { first } from 'rxjs/operators'; import { ActionState } from '../../../../../../store/src/reducers/api-request-reducer/types'; +import { CloudFoundryEndpointService } from '../../services/cloud-foundry-endpoint.service'; import { UserInviteService } from '../user-invite.service'; @Component({ selector: 'app-user-invite-configuration-dialog', templateUrl: './user-invite-configuration-dialog.component.html', - styleUrls: ['./user-invite-configuration-dialog.component.scss'] + styleUrls: ['./user-invite-configuration-dialog.component.scss'], }) export class UserInviteConfigurationDialogComponent { connecting$: Observable; @@ -42,6 +43,7 @@ export class UserInviteConfigurationDialogComponent { public fb: FormBuilder, public dialogRef: MatDialogRef, public snackBar: MatSnackBar, + private cfEndpointService: CloudFoundryEndpointService, public userInviteService: UserInviteService, @Inject(MAT_DIALOG_DATA) public data: { guid: string @@ -51,6 +53,10 @@ export class UserInviteConfigurationDialogComponent { clientID: ['', Validators.required], clientSecret: ['', Validators.required], }); + + console.log('cfEndpointService dialog', this.cfEndpointService); + console.log('userInviteService dialog', this.userInviteService); + this.userInviteService.initialize(this.data.guid); } submit() { diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/user-invites/user-invite.service.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/user-invites/user-invite.service.ts index c91612ba71..96b903d4ad 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/user-invites/user-invite.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/user-invites/user-invite.service.ts @@ -64,12 +64,21 @@ export class UserInviteService { private store: Store, private http: HttpClient, private snackBar: MatSnackBar, - cfEndpointService: CloudFoundryEndpointService, + private cfEndpointService: CloudFoundryEndpointService, private currentUserPermissionsService: CurrentUserPermissionsService, private activeRouteCfOrgSpace: ActiveRouteCfOrgSpace, private confirmDialog: ConfirmationDialogService, ) { - this.configured$ = cfEndpointService.endpoint$.pipe( + const { cfGuid } = activeRouteCfOrgSpace; + + if (cfGuid) { + this.initialize(cfGuid); + } + } + + initialize(cfGuid) { + this.cfEndpointService.initialize(cfGuid); + this.configured$ = this.cfEndpointService.endpoint$.pipe( filter(v => !!v && !!v.entity), // Note - metadata could be falsy if smtp server not configured/other metadata properties are missing map(v => v.entity.metadata && v.entity.metadata.userInviteAllowed === 'true') @@ -85,11 +94,11 @@ export class UserInviteService { ); } - configure(cfGUID: string, clientID: string, clientSecret: string): Observable { + configure(cfGuid: string, clientID: string, clientSecret: string): Observable { const formData: FormData = new FormData(); formData.append('client_id', clientID); formData.append('client_secret', clientSecret); - const url = `/pp/${proxyAPIVersion}/invite/${cfGUID}`; + const url = `/pp/${proxyAPIVersion}/invite/${cfGuid}`; const obs$ = this.http.post(url, formData).pipe( map(v => { this.store.dispatch(new GetSystemInfo()); diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/invite-users/invite-users-create/invite-users-create.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/invite-users/invite-users-create/invite-users-create.component.spec.ts index 2cf5ba6863..2be0341fab 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/invite-users/invite-users-create/invite-users-create.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/invite-users/invite-users-create/invite-users-create.component.spec.ts @@ -1,11 +1,10 @@ -import { HttpClient, HttpHandler } from '@angular/common/http'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { generateCfBaseTestModules } from '../../../../../../test-framework/cloud-foundry-endpoint-service.helper'; +import { + generateCfBaseTestModules, + generateTestCfEndpointServiceProvider, +} from '../../../../../../test-framework/cloud-foundry-endpoint-service.helper'; import { CfUserService } from '../../../../../shared/data-services/cf-user.service'; -import { ActiveRouteCfOrgSpace } from '../../../cf-page.types'; -import { CloudFoundryEndpointService } from '../../../services/cloud-foundry-endpoint.service'; -import { UserInviteService } from '../../../user-invites/user-invite.service'; import { InviteUsersCreateComponent } from './invite-users-create.component'; describe('InviteUsersCreateComponent', () => { @@ -17,11 +16,7 @@ describe('InviteUsersCreateComponent', () => { declarations: [InviteUsersCreateComponent], imports: generateCfBaseTestModules(), providers: [ - ActiveRouteCfOrgSpace, - CloudFoundryEndpointService, - UserInviteService, - HttpClient, - HttpHandler, + generateTestCfEndpointServiceProvider(), CfUserService ] }) diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/invite-users/invite-users.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/invite-users/invite-users.component.spec.ts index 1976d2f4ad..c1fb430e63 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/invite-users/invite-users.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/invite-users/invite-users.component.spec.ts @@ -1,8 +1,10 @@ -import { HttpClient, HttpHandler } from '@angular/common/http'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { TabNavService } from '../../../../../../core/tab-nav.service'; -import { generateCfBaseTestModules } from '../../../../../test-framework/cloud-foundry-endpoint-service.helper'; +import { + generateCfBaseTestModules, + generateTestCfEndpointServiceProvider, +} from '../../../../../test-framework/cloud-foundry-endpoint-service.helper'; import { InviteUsersCreateComponent } from './invite-users-create/invite-users-create.component'; import { InviteUsersComponent } from './invite-users.component'; @@ -18,8 +20,7 @@ describe('InviteUsersComponent', () => { ], imports: generateCfBaseTestModules(), providers: [ - HttpClient, - HttpHandler, + ...generateTestCfEndpointServiceProvider(), TabNavService ] }) diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.html b/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.html new file mode 100644 index 0000000000..9dbb965c20 --- /dev/null +++ b/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.html @@ -0,0 +1,68 @@ + + + + + +
+ + + Summary + + + + + + {{ appSvc.cf?.name}} + + + {{ sshStatus$ | async }} + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + +
+
+
\ No newline at end of file diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.scss b/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.spec.ts b/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.spec.ts new file mode 100644 index 0000000000..ca38589a41 --- /dev/null +++ b/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.spec.ts @@ -0,0 +1,39 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { + generateCfBaseTestModules, + generateTestCfEndpointServiceProvider, +} from '../../../../test-framework/cloud-foundry-endpoint-service.helper'; +import { ApplicationService } from '../../../features/applications/application.service'; +import { + ApplicationEnvVarsHelper, +} from '../../../features/applications/application/application-tabs-base/tabs/build-tab/application-env-vars.service'; +import { ApplicationPreviewComponent } from './application-preview.component'; + +describe('ApplicationPreviewComponent', () => { + let component: ApplicationPreviewComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ApplicationPreviewComponent], + providers: [ + ...generateTestCfEndpointServiceProvider(), + ApplicationService, + ApplicationEnvVarsHelper, + ], + imports: generateCfBaseTestModules(), + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ApplicationPreviewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.ts new file mode 100644 index 0000000000..649e38af6f --- /dev/null +++ b/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.ts @@ -0,0 +1,72 @@ +import { Component } from '@angular/core'; +import { Observable } from 'rxjs'; +import { combineLatest, distinct, map } from 'rxjs/operators'; + +import { IAppSummary } from '../../../../../core/src/core/cf-api.types'; +import { getFullEndpointApiUrl } from '../../../../../core/src/features/endpoints/endpoint-helpers'; +import { APP_GUID, CF_GUID } from '../../../../../core/src/shared/entity.tokens'; +import { PreviewableComponent } from '../../../../../core/src/shared/previewable-component'; +import { EntityInfo } from '../../../../../store/src/types/api.types'; +import { ApplicationData, ApplicationService } from '../../../features/applications/application.service'; + +@Component({ + selector: 'app-application-preview-component', + templateUrl: './application-preview.component.html', + styleUrls: ['./application-preview.component.scss'], + providers: [ + ApplicationService, + { + provide: CF_GUID, + useValue: '', + }, + { + provide: APP_GUID, + useValue: '', + }, + ] +}) +export class ApplicationPreviewComponent implements PreviewableComponent { + + title = null; + cfEndpointService: object; + sshStatus$: Observable; + detailsLoading$: Observable; + + getFullEndpointApiUrl = getFullEndpointApiUrl; + + constructor(public applicationService: ApplicationService) { + } + + setProps(props: { [key: string]: any }) { + this.title = props.title; + + this.applicationService.initialize(props.cfGuid, props.guid); + this.sshStatus$ = this.applicationService.application$.pipe( + combineLatest(this.applicationService.appSpace$), + map(([app, space]) => { + if (!space.entity.allow_ssh) { + return 'Disabled by the space'; + } else { + return app.app.entity.enable_ssh ? 'Yes' : 'No'; + } + }) + ); + + this.detailsLoading$ = this.applicationService.application$.pipe( + combineLatest( + this.applicationService.appSummary$ + ), + map(([app, appSummary]: [ApplicationData, EntityInfo]) => { + return app.fetching || appSummary.entityRequestInfo.fetching; + }), distinct()); + + // // Wait for the apps to have been fetched, this will determine if multiple small cards are shown or now + // this.cfEndpointService.appsPagObs.fetchingEntities$.pipe( + // filter(loading => !loading) + // ), + // ]).pipe( + // map(() => false), + // startWith(true) + // ); + } +} diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-info/card-cf-info.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-info/card-cf-info.component.ts index c84b987a60..4f091539ab 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-info/card-cf-info.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-info/card-cf-info.component.ts @@ -1,12 +1,10 @@ -import { Component, OnDestroy, OnInit } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; -import { Observable, Subscription } from 'rxjs'; -import { map, tap } from 'rxjs/operators'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; import { fetchAutoscalerInfo } from '../../../../../../cf-autoscaler/src/core/autoscaler-helpers/autoscaler-available'; -import { ICfV2Info } from '../../../../../../core/src/core/cf-api.types'; import { EntityServiceFactory } from '../../../../../../store/src/entity-service-factory.service'; -import { APIResource, EntityInfo } from '../../../../../../store/src/types/api.types'; import { CloudFoundryEndpointService } from '../../../../features/cloud-foundry/services/cloud-foundry-endpoint.service'; import { UserInviteConfigurationDialogComponent, @@ -19,9 +17,8 @@ import { UserInviteService } from '../../../../features/cloud-foundry/user-invit templateUrl: './card-cf-info.component.html', styleUrls: ['./card-cf-info.component.scss'] }) -export class CardCfInfoComponent implements OnInit, OnDestroy { +export class CardCfInfoComponent implements OnInit { public apiUrl: string; - private subs: Subscription[] = []; public autoscalerVersion$: Observable; constructor( @@ -34,17 +31,6 @@ export class CardCfInfoComponent implements OnInit, OnDestroy { description$: Observable; ngOnInit() { - const obs$ = this.cfEndpointService.endpoint$.pipe( - tap(endpoint => { - this.apiUrl = this.getApiEndpointUrl(endpoint.entity.api_endpoint); - }) - ); - this.subs.push(obs$.subscribe()); - - this.description$ = this.cfEndpointService.info$.pipe( - map(entity => this.getDescription(entity)) - ); - // FIXME: CF should not depend on autoscaler. See #3916 this.autoscalerVersion$ = fetchAutoscalerInfo(this.cfEndpointService.cfGuid, this.esf).pipe( map(e => e.entityRequestInfo.error ? @@ -53,32 +39,6 @@ export class CardCfInfoComponent implements OnInit, OnDestroy { ); } - getApiEndpointUrl(apiEndpoint) { - const path = apiEndpoint.Path ? `/${apiEndpoint.Path}` : ''; - return `${apiEndpoint.Scheme}://${apiEndpoint.Host}${path}`; - } - - ngOnDestroy(): void { - this.subs.forEach(s => s.unsubscribe()); - } - - private getMetadataFromInfo(entity: EntityInfo>) { - return entity && entity.entity && entity.entity.entity ? entity.entity.entity : null; - } - - private getDescription(entity: EntityInfo>): string { - const metadata = this.getMetadataFromInfo(entity); - if (metadata) { - if (metadata.description) { - return metadata.description + (metadata.build ? ` (${metadata.build})` : ''); - } - if (metadata.support === 'pcfdev@pivotal.io') { - return 'PCF Dev'; - } - } - return '-'; - } - configureUserInvites() { this.dialog.open(UserInviteConfigurationDialogComponent, { data: { diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-recent-apps/card-cf-recent-apps.component.spec.ts b/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-recent-apps/card-cf-recent-apps.component.spec.ts index 475f446cae..a70ac9c3c7 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-recent-apps/card-cf-recent-apps.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-recent-apps/card-cf-recent-apps.component.spec.ts @@ -14,9 +14,8 @@ import { EntityMonitorFactory } from '../../../../../../store/src/monitors/entit import { PaginationMonitorFactory } from '../../../../../../store/src/monitors/pagination-monitor.factory'; import { generateCfBaseTestModulesNoShared, + generateTestCfEndpointServiceProvider, } from '../../../../../test-framework/cloud-foundry-endpoint-service.helper'; -import { ActiveRouteCfOrgSpace } from '../../../../features/cloud-foundry/cf-page.types'; -import { CloudFoundryEndpointService } from '../../../../features/cloud-foundry/services/cloud-foundry-endpoint.service'; import { CfUserService } from '../../../data-services/cf-user.service'; import { CardCfRecentAppsComponent } from './card-cf-recent-apps.component'; import { CompactAppCardComponent } from './compact-app-card/compact-app-card.component'; @@ -36,8 +35,7 @@ describe('CardCfRecentAppsComponent', () => { ], imports: generateCfBaseTestModulesNoShared(), providers: [ - CloudFoundryEndpointService, - ActiveRouteCfOrgSpace, + ...generateTestCfEndpointServiceProvider(), EntityMonitorFactory, CfUserService, PaginationMonitorFactory diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-space-details/card-cf-space-details.component.html b/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-space-details/card-cf-space-details.component.html index f2b722ebda..92c2869e70 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-space-details/card-cf-space-details.component.html +++ b/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-space-details/card-cf-space-details.component.html @@ -31,7 +31,7 @@ - +
- - +
\ No newline at end of file diff --git a/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.scss b/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.scss index 8c10e82191..eebacc5611 100644 --- a/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.scss +++ b/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.scss @@ -15,6 +15,10 @@ $app-sub-header-height: 48px; } &__side-help-outer { z-index: 9999; + + @include breakpoint(mobileonly) { + left: 0; + } } &__side-help-button { position: absolute; @@ -51,6 +55,11 @@ $app-sub-header-height: 48px; } &__side-help { max-width: 600px; + min-width: 600px; + + @include breakpoint(mobileonly) { + min-width: auto; + } } } diff --git a/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.spec.ts b/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.spec.ts index f22295bbe2..75586ea67d 100644 --- a/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.spec.ts +++ b/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.spec.ts @@ -10,6 +10,7 @@ import { appReducers } from '../../../../../store/src/reducers.module'; import { TabNavService } from '../../../../tab-nav.service'; import { CoreModule } from '../../../core/core.module'; import { PageHeaderService } from '../../../core/page-header-service/page-header.service'; +import { PanelPreviewService } from '../../../shared/services/panel-preview.service'; import { SharedModule } from '../../../shared/shared.module'; import { MetricsService } from '../../metrics/services/metrics-service'; import { PageSideNavComponent } from '../page-side-nav/page-side-nav.component'; @@ -40,7 +41,8 @@ describe('DashboardBaseComponent', () => { MetricsService, TabNavService, HttpClient, - HttpHandler + HttpHandler, + PanelPreviewService ], }) .compileComponents(); diff --git a/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.ts b/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.ts index 3956ba0f80..5caa0bad1c 100644 --- a/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.ts +++ b/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.ts @@ -1,6 +1,6 @@ import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout'; import { Portal } from '@angular/cdk/portal'; -import { Component, NgZone, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { AfterViewInit, Component, NgZone, OnDestroy, OnInit, ViewChild, ViewContainerRef } from '@angular/core'; import { MatDrawer } from '@angular/material'; import { ActivatedRoute, ActivatedRouteSnapshot, NavigationEnd, Route, Router } from '@angular/router'; import { Store } from '@ngrx/store'; @@ -13,12 +13,7 @@ import { CF_ENDPOINT_TYPE } from '../../../../../cloud-foundry/src/cf-types'; import { CfInfoDefinitionActionBuilders, } from '../../../../../cloud-foundry/src/entity-action-builders/cf-info.action-builders'; -import { - CloseSideHelp, - CloseSideNav, - DisableMobileNav, - EnableMobileNav, -} from '../../../../../store/src/actions/dashboard-actions'; +import { CloseSideNav, DisableMobileNav, EnableMobileNav } from '../../../../../store/src/actions/dashboard-actions'; import { GetUserFavoritesAction } from '../../../../../store/src/actions/user-favourites-actions/get-user-favorites-action'; import { DashboardOnlyAppState } from '../../../../../store/src/app-state'; import { entityCatalog } from '../../../../../store/src/entity-catalog/entity-catalog.service'; @@ -29,6 +24,7 @@ import { EndpointHealthCheck } from '../../../../endpoints-health-checks'; import { TabNavService } from '../../../../tab-nav.service'; import { CustomizationService } from '../../../core/customizations.types'; import { EndpointsService } from '../../../core/endpoints.service'; +import { PanelPreviewService } from '../../../shared/services/panel-preview.service'; import { PageHeaderService } from './../../../core/page-header-service/page-header.service'; import { SideNavItem } from './../side-nav/side-nav.component'; @@ -39,16 +35,28 @@ import { SideNavItem } from './../side-nav/side-nav.component'; styleUrls: ['./dashboard-base.component.scss'] }) -export class DashboardBaseComponent implements OnInit, OnDestroy { +export class DashboardBaseComponent implements OnInit, OnDestroy, AfterViewInit { public activeTabLabel$: Observable; public subNavData$: Observable<[string, Portal]>; public isMobile$: Observable; public sideNavMode$: Observable; public sideNavMode: string; public mainNavState$: Observable<{ mode: string; opened: boolean; iconMode: boolean }>; - public rightNavState$: Observable<{ opened: boolean; documentUrl: string; }>; + public rightNavState$: Observable<{ opened: boolean, component?: object, props?: object }>; private dashboardState$: Observable; + public noMargin$: Observable; + private closeSub: Subscription; + private mobileSub: Subscription; private drawer: MatDrawer; + public iconModeOpen = false; + public sideNavWidth = 54; + + sideNavTabs: SideNavItem[] = this.getNavigationRoutes(); + sideNaveMode = 'side'; + + @ViewChild('previewPanelContainer', { read: ViewContainerRef, static: false }) previewPanelContainer: ViewContainerRef; + + @ViewChild('content', { static: false }) public content; constructor( public pageHeaderService: PageHeaderService, @@ -59,6 +67,7 @@ export class DashboardBaseComponent implements OnInit, OnDestroy { private endpointsService: EndpointsService, public tabNavService: TabNavService, private ngZone: NgZone, + public panelPreviewService: PanelPreviewService, private cs: CustomizationService ) { this.noMargin$ = this.router.events.pipe( @@ -89,24 +98,11 @@ export class DashboardBaseComponent implements OnInit, OnDestroy { } }) ); - this.rightNavState$ = this.dashboardState$.pipe( - map(state => ({ - opened: !!state.sideHelpDocument && state.sideHelpOpen, - documentUrl: state.sideHelpDocument - })) - ); + this.mobileSub = this.isMobile$ .subscribe(isMobile => isMobile ? this.store.dispatch(new EnableMobileNav()) : this.store.dispatch(new DisableMobileNav())); } - public helpDocumentUrl: string; - - private closeSub: Subscription; - - public noMargin$: Observable; - - private mobileSub: Subscription; - @ViewChild('sidenav', { static: false }) set sidenav(drawer: MatDrawer) { this.drawer = drawer; if (!this.closeSub) { @@ -119,15 +115,6 @@ export class DashboardBaseComponent implements OnInit, OnDestroy { } } - @ViewChild('content', { static: true }) public content; - - sideNavTabs: SideNavItem[] = this.getNavigationRoutes(); - - sideNaveMode = 'side'; - - public iconModeOpen = false; - public sideNavWidth = 54; - public redrawSideNav() { // We need to do this to ensure there isn't a space left behind // when going from mobile to desktop @@ -140,6 +127,14 @@ export class DashboardBaseComponent implements OnInit, OnDestroy { this.store.dispatch(new GetCurrentUsersRelations()); } + sideHelpClosed() { + this.panelPreviewService.hide(); + } + + ngAfterViewInit() { + this.panelPreviewService.setContainer(this.previewPanelContainer); + } + ngOnInit() { this.subNavData$ = combineLatest( this.tabNavService.getCurrentTabHeaderObservable().pipe( @@ -173,10 +168,6 @@ export class DashboardBaseComponent implements OnInit, OnDestroy { return false; } - public sideHelpClosed() { - this.store.dispatch(new CloseSideHelp()); - } - private getNavigationRoutes(): SideNavItem[] { let navItems = this.collectNavigationRoutes('', this.router.config); diff --git a/src/frontend/packages/core/src/features/endpoints/connect-endpoint-dialog/connect-endpoint-dialog.component.ts b/src/frontend/packages/core/src/features/endpoints/connect-endpoint-dialog/connect-endpoint-dialog.component.ts index e418b05aff..788d2bc4ae 100644 --- a/src/frontend/packages/core/src/features/endpoints/connect-endpoint-dialog/connect-endpoint-dialog.component.ts +++ b/src/frontend/packages/core/src/features/endpoints/connect-endpoint-dialog/connect-endpoint-dialog.component.ts @@ -3,10 +3,11 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Store } from '@ngrx/store'; import { Subscription } from 'rxjs'; -import { ShowSideHelp } from '../../../../../store/src/actions/dashboard-actions'; import { ShowSnackBar } from '../../../../../store/src/actions/snackBar.actions'; import { EndpointOnlyAppState } from '../../../../../store/src/app-state'; import { EndpointsService } from '../../../core/endpoints.service'; +import { MarkdownPreviewComponent } from '../../../shared/components/markdown-preview/markdown-preview.component'; +import { PanelPreviewService } from '../../../shared/services/panel-preview.service'; import { ConnectEndpointConfig, ConnectEndpointService } from '../connect.service'; @@ -28,6 +29,7 @@ export class ConnectEndpointDialogComponent implements OnDestroy { @Inject(MAT_DIALOG_DATA) public data: ConnectEndpointConfig, private store: Store, endpointsService: EndpointsService, + private panelPreviewService: PanelPreviewService, ) { this.connectService = new ConnectEndpointService(store, endpointsService, data); @@ -38,7 +40,7 @@ export class ConnectEndpointDialogComponent implements OnDestroy { } showHelp() { - this.store.dispatch(new ShowSideHelp(this.helpDocumentUrl)); + this.panelPreviewService.show(MarkdownPreviewComponent, { documentUrl: this.helpDocumentUrl }); } ngOnDestroy(): void { diff --git a/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-connect/create-endpoint-connect.component.spec.ts b/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-connect/create-endpoint-connect.component.spec.ts index bc70537cf2..faa9efbd52 100644 --- a/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-connect/create-endpoint-connect.component.spec.ts +++ b/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-connect/create-endpoint-connect.component.spec.ts @@ -1,8 +1,10 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; import { CoreTestingModule } from '../../../../../test-framework/core-test.modules'; import { createBasicStoreModule } from '@stratos/store/testing'; import { CoreModule } from '../../../../core/core.module'; +import { PanelPreviewService } from '../../../../shared/services/panel-preview.service'; import { SharedModule } from '../../../../shared/shared.module'; import { ConnectEndpointComponent } from '../../connect-endpoint/connect-endpoint.component'; import { CreateEndpointConnectComponent } from './create-endpoint-connect.component'; @@ -21,8 +23,10 @@ describe('CreateEndpointConnectComponent', () => { CoreModule, SharedModule, CoreTestingModule, + RouterTestingModule, createBasicStoreModule(), - ] + ], + providers: [PanelPreviewService], }) .compileComponents(); })); diff --git a/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-connect/create-endpoint-connect.component.ts b/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-connect/create-endpoint-connect.component.ts index d513432c29..ff128ef73c 100644 --- a/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-connect/create-endpoint-connect.component.ts +++ b/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-connect/create-endpoint-connect.component.ts @@ -3,10 +3,11 @@ import { Store } from '@ngrx/store'; import { Observable, of } from 'rxjs'; import { map } from 'rxjs/operators'; -import { ShowSideHelp } from '../../../../../../store/src/actions/dashboard-actions'; import { EndpointOnlyAppState } from '../../../../../../store/src/app-state'; import { EndpointsService } from '../../../../core/endpoints.service'; +import { MarkdownPreviewComponent } from '../../../../shared/components/markdown-preview/markdown-preview.component'; import { IStepperStep, StepOnNextResult } from '../../../../shared/components/stepper/step/step.component'; +import { PanelPreviewService } from '../../../../shared/services/panel-preview.service'; import { ConnectEndpointConfig, ConnectEndpointService } from '../../connect.service'; @@ -26,12 +27,13 @@ export class CreateEndpointConnectComponent implements OnDestroy, IStepperStep { constructor( private store: Store, - private endpointsService: EndpointsService + private endpointsService: EndpointsService, + private panelPreviewService: PanelPreviewService, ) { } showHelp() { - this.store.dispatch(new ShowSideHelp(this.helpDocumentUrl)); + this.panelPreviewService.show(MarkdownPreviewComponent, { documentUrl: this.helpDocumentUrl }); } onEnter = (data: ConnectEndpointConfig) => { diff --git a/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.html b/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.html index 53a2b5e295..9af11474b9 100644 --- a/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.html +++ b/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.html @@ -1,5 +1,5 @@ diff --git a/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.spec.ts b/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.spec.ts index e76f49a277..f302e22b2d 100644 --- a/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.spec.ts +++ b/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.spec.ts @@ -1,6 +1,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { BaseTestModules } from '../../../../test-framework/core-test.helper'; +import { PanelPreviewService } from '../../services/panel-preview.service'; import { FavoritesMetaCardComponent } from './favorites-meta-card.component'; describe('FavoritesMetaCardComponent', () => { @@ -10,6 +11,7 @@ describe('FavoritesMetaCardComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ imports: [...BaseTestModules], + providers: [PanelPreviewService], }) .compileComponents(); })); diff --git a/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.ts b/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.ts index a252d8b69e..5a91f03f22 100644 --- a/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.ts +++ b/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.ts @@ -7,10 +7,15 @@ import { CFAppState } from '../../../../../cloud-foundry/src/cf-app-state'; import { RemoveUserFavoriteAction, } from '../../../../../store/src/actions/user-favourites-actions/remove-user-favorite-action'; -import { endpointEntitiesSelector } from '../../../../../store/src/selectors/endpoint.selectors'; +import { + endpointEntitiesSelector, + endpointsEntityRequestDataSelector, +} from '../../../../../store/src/selectors/endpoint.selectors'; import { IFavoriteMetadata, UserFavorite } from '../../../../../store/src/types/user-favorites.types'; -import { userFavoritesEntitySchema } from '../../../base-entity-schemas'; +import { ENDPOINT_TYPE, userFavoritesEntitySchema } from '../../../base-entity-schemas'; +import { entityCatalogue } from '../../../core/entity-catalogue/entity-catalogue.service'; import { IFavoriteEntity } from '../../../core/user-favorite-manager'; +import { PanelPreviewService } from '../../services/panel-preview.service'; import { ComponentEntityMonitorConfig, StratosStatus } from '../../shared.types'; import { ConfirmationDialogConfig } from '../confirmation-dialog.config'; import { ConfirmationDialogService } from '../confirmation-dialog.service'; @@ -97,7 +102,33 @@ export class FavoritesMetaCardComponent { } } - constructor(private store: Store, private confirmDialog: ConfirmationDialogService) { } + constructor( + private store: Store, + private confirmDialog: ConfirmationDialogService, + private panelPreviewService: PanelPreviewService + ) { } + + previewPanel() { + const catalogueEntity = entityCatalogue.getEntity(this.favorite.endpointType, this.favorite.entityType); + const previewComponent = catalogueEntity.builders.entityBuilder.getPreviewableComponent(); + + // TODO: use 'endpoint' as constant + if (this.favorite.entityType === ENDPOINT_TYPE) { + const entity$ = this.store.select(endpointsEntityRequestDataSelector(this.favorite.endpointId)); + this.panelPreviewService.show(previewComponent, { + title: this.favorite.metadata.name, + entity$, + cfGuid: this.favorite.endpointId + }); + } else { + this.panelPreviewService.show(previewComponent, { + title: this.favorite.metadata.name, + cfGuid: this.favorite.metadata.cfGuid, + orgGuid: this.favorite.metadata.orgGuid, + guid: this.favorite.metadata.guid + }); + } + } public setConfirmation(prettyName: string, favorite: UserFavorite) { this.confirmation = new ConfirmationDialogConfig( diff --git a/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.html b/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.html index 4ad4077847..972ac85319 100644 --- a/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.html +++ b/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.html @@ -1,6 +1,3 @@ -
-
-

{{ title }}

-
+
-
\ No newline at end of file + \ No newline at end of file diff --git a/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.scss b/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.scss index 2a7eaa882f..e69de29bb2 100644 --- a/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.scss +++ b/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.scss @@ -1,21 +0,0 @@ -.markdown-preview { - display: flex; - flex-direction: column; - height: 100vh; - &__header { - display: flex; - flex: 0 0 56px; - height: 56px; - - h1 { - align-self: center; - font-size: 20px; - margin-left: 24px; - } - } - &__content { - flex: 1; - overflow: auto; - padding: 10px 24px; - } -} diff --git a/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.spec.ts b/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.spec.ts index 5e67976b94..d4fa91a6be 100644 --- a/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.spec.ts +++ b/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.spec.ts @@ -5,6 +5,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { CoreTestingModule } from '../../../../test-framework/core-test.modules'; import { createBasicStoreModule } from '@stratos/store/testing'; import { LoggerService } from '../../../core/logger.service'; +import { SidepanelPreviewComponent } from '../sidepanel-preview/sidepanel-preview.component'; import { MarkdownPreviewComponent } from './markdown-preview.component'; describe('MarkdownPreviewComponent', () => { @@ -13,7 +14,7 @@ describe('MarkdownPreviewComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [MarkdownPreviewComponent], + declarations: [MarkdownPreviewComponent, SidepanelPreviewComponent], providers: [LoggerService, HttpClient, HttpHandler], imports: [ HttpClientModule, diff --git a/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.ts b/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.ts index 97384669d0..9dba74b1d5 100644 --- a/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.ts +++ b/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.ts @@ -4,13 +4,14 @@ import { DomSanitizer } from '@angular/platform-browser'; import * as markdown from 'marked'; import { LoggerService } from '../../../core/logger.service'; +import { PreviewableComponent } from '../../previewable-component'; @Component({ selector: 'app-markdown-preview', templateUrl: './markdown-preview.component.html', styleUrls: ['./markdown-preview.component.scss'] }) -export class MarkdownPreviewComponent { +export class MarkdownPreviewComponent implements PreviewableComponent { markdownHtml: string; documentUrl: string; @@ -27,7 +28,15 @@ export class MarkdownPreviewComponent { @ViewChild('markdown', { static: true }) public markdown: ElementRef; - constructor(private httpClient: HttpClient, private logger: LoggerService, private domSanitizer: DomSanitizer) { } + constructor( + private httpClient: HttpClient, + private logger: LoggerService, + private domSanitizer: DomSanitizer + ) { } + + setProps(props: { [key: string]: any }) { + this.setDocumentUrl = props.documentUrl; + } private loadDocument() { this.httpClient.get(this.documentUrl, { responseType: 'text' }).subscribe( diff --git a/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.html b/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.html new file mode 100644 index 0000000000..9e95cac0d8 --- /dev/null +++ b/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.html @@ -0,0 +1,8 @@ +
+
+

{{ title }}

+
+
+ +
+
\ No newline at end of file diff --git a/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.scss b/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.scss new file mode 100644 index 0000000000..c3afff37d8 --- /dev/null +++ b/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.scss @@ -0,0 +1,23 @@ +.sidepanel-preview { + display: flex; + flex-direction: column; + height: 100vh; + &__header { + display: flex; + flex: 0 0 56px; + height: 56px; + + h1 { + align-self: center; + font-size: 20px; + margin-left: 24px; + } + } + &__content { + flex: 1; + overflow-x: hidden; + overflow-y: auto; + padding: 12px 15px; + position: relative; + } +} diff --git a/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.spec.ts b/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.spec.ts new file mode 100644 index 0000000000..2eb1505ae2 --- /dev/null +++ b/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.spec.ts @@ -0,0 +1,37 @@ +import { HttpClient, HttpClientModule, HttpHandler } from '@angular/common/http'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CoreTestingModule } from '../../../../test-framework/core-test.modules'; +import { createBasicStoreModule } from '../../../../test-framework/store-test-helper'; +import { LoggerService } from '../../../core/logger.service'; +import { SidepanelPreviewComponent } from './sidepanel-preview.component'; + +describe('SidepanelPreviewComponent', () => { + let component: SidepanelPreviewComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [SidepanelPreviewComponent], + providers: [LoggerService, HttpClient, HttpHandler], + imports: [ + HttpClientModule, + HttpClientTestingModule, + CoreTestingModule, + createBasicStoreModule() + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SidepanelPreviewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.theme.scss b/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.theme.scss similarity index 67% rename from src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.theme.scss rename to src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.theme.scss index 926eb8127e..c84bed25dc 100644 --- a/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.theme.scss +++ b/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.theme.scss @@ -1,12 +1,14 @@ -@mixin app-markdown-preview-theme($theme, $app-theme) { +@mixin app-sidepanel-preview-theme($theme, $app-theme) { $primary: map-get($theme, primary); - .markdown-preview__header { + .sidepanel-preview__header { background-color: mat-color($primary); color: mat-contrast($primary, 500); } - .markdown-preview__content { + .sidepanel-preview__content { + background-color: map-get($app-theme, app-background-color); + > h1:first-child { height: 0; margin: 0; diff --git a/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.ts b/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.ts new file mode 100644 index 0000000000..315d11c160 --- /dev/null +++ b/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.ts @@ -0,0 +1,14 @@ +import { Component, Input } from '@angular/core'; + +@Component({ + selector: 'app-sidepanel-preview', + templateUrl: './sidepanel-preview.component.html', + styleUrls: ['./sidepanel-preview.component.scss'] +}) +export class SidepanelPreviewComponent { + + @Input() + title: string; + + constructor() { } +} diff --git a/src/frontend/packages/core/src/shared/previewable-component.ts b/src/frontend/packages/core/src/shared/previewable-component.ts new file mode 100644 index 0000000000..40410d6f07 --- /dev/null +++ b/src/frontend/packages/core/src/shared/previewable-component.ts @@ -0,0 +1,3 @@ +export interface PreviewableComponent { + setProps(props: { [key: string]: any }): void; +} diff --git a/src/frontend/packages/core/src/shared/services/panel-preview.service.ts b/src/frontend/packages/core/src/shared/services/panel-preview.service.ts new file mode 100644 index 0000000000..dd31ddcbc2 --- /dev/null +++ b/src/frontend/packages/core/src/shared/services/panel-preview.service.ts @@ -0,0 +1,84 @@ +import { ComponentFactory, ComponentFactoryResolver, ComponentRef, Injectable, ViewContainerRef } from '@angular/core'; +import { Router } from '@angular/router'; +import { asapScheduler, BehaviorSubject, Observable, Subject } from 'rxjs'; +import { filter, observeOn, publishReplay, refCount, tap } from 'rxjs/operators'; + +@Injectable() +export class PanelPreviewService { + private openedSubject: BehaviorSubject; + public opened$: Observable; + + private container: ViewContainerRef; + + constructor( + private componentFactoryResolver: ComponentFactoryResolver, + private router: Router, + ) { + this.openedSubject = new BehaviorSubject(false); + this.opened$ = this.observeSubject(this.openedSubject); + + this.setupRouterListener(); + } + + public setContainer(container: ViewContainerRef) { + if (this.container) { + throw new Error('PanelPreviewService: container already set'); + } + + this.container = container; + } + + public show(component: object, props?: { [key: string]: any }, componentFactoryResolver?: ComponentFactoryResolver) { + if (!this.container) { + throw new Error('PanelPreviewService: container must be set'); + } + + this.render(component, props, componentFactoryResolver); + this.openedSubject.next(true); + } + + public hide() { + if (!this.container) { + throw new Error('PanelPreviewService: container must be set'); + } + + this.openedSubject.next(false); + } + + render( + component: object, + props: { [key: string]: any }, + componentFactoryResolver: ComponentFactoryResolver = this.componentFactoryResolver + ) { + if (this.container.length) { + this.container.remove(0); + } + + const factory: ComponentFactory = componentFactoryResolver.resolveComponentFactory(component as any); + const componentRef: ComponentRef = this.container.createComponent(factory); + + if (props) { + componentRef.instance.setProps(props); + } + } + + public clear() { + this.container.clear(); + this.openedSubject.next(false); + } + + private setupRouterListener() { + this.router.events.pipe( + filter(() => !!this.container), + tap((e) => this.hide())) + .subscribe(); + } + + private observeSubject(subject: Subject) { + return subject.asObservable().pipe( + publishReplay(1), + refCount(), + observeOn(asapScheduler) + ); + } +} diff --git a/src/frontend/packages/core/src/shared/shared.module.ts b/src/frontend/packages/core/src/shared/shared.module.ts index 4eafd277fd..ca1ce6252e 100644 --- a/src/frontend/packages/core/src/shared/shared.module.ts +++ b/src/frontend/packages/core/src/shared/shared.module.ts @@ -83,6 +83,7 @@ import { PageSubNavComponent } from './components/page-sub-nav/page-sub-nav.comp import { PollingIndicatorComponent } from './components/polling-indicator/polling-indicator.component'; import { RingChartComponent } from './components/ring-chart/ring-chart.component'; import { RoutingIndicatorComponent } from './components/routing-indicator/routing-indicator.component'; +import { SidepanelPreviewComponent } from './components/sidepanel-preview/sidepanel-preview.component'; import { SimpleUsageChartComponent } from './components/simple-usage-chart/simple-usage-chart.component'; import { SnackBarReturnComponent } from './components/snackbar-return/snackbar-return.component'; import { SshViewerComponent } from './components/ssh-viewer/ssh-viewer.component'; @@ -220,6 +221,7 @@ import { UserPermissionDirective } from './user-permission.directive'; UnlimitedInputComponent, SimpleListComponent, ListHostDirective, + SidepanelPreviewComponent, CopyToClipboardComponent, TileSelectorTileComponent, ], @@ -311,13 +313,15 @@ import { UserPermissionDirective } from './user-permission.directive'; UnlimitedInputComponent, SimpleListComponent, ListHostDirective, + SidepanelPreviewComponent, CopyToClipboardComponent, TileSelectorTileComponent, ], entryComponents: [ DialogConfirmComponent, EnvVarViewComponent, - SnackBarReturnComponent + SnackBarReturnComponent, + MarkdownPreviewComponent, ], providers: [ ListConfig, diff --git a/src/frontend/packages/core/test-framework/core-test.helper.ts b/src/frontend/packages/core/test-framework/core-test.helper.ts index fc03a7a74e..4568d62a9b 100644 --- a/src/frontend/packages/core/test-framework/core-test.helper.ts +++ b/src/frontend/packages/core/test-framework/core-test.helper.ts @@ -1,3 +1,4 @@ +import { HttpClientModule } from '@angular/common/http'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { RouterTestingModule } from '@angular/router/testing'; import { StoreModule } from '@ngrx/store'; @@ -28,7 +29,6 @@ import { MultilineTitleComponent } from '../src/shared/components/multiline-titl import { SharedModule } from '../src/shared/shared.module'; import { CoreTestingModule } from './core-test.modules'; import { createBasicStoreModule } from '@stratos/store/testing'; -import { HttpClientModule } from '@angular/common/http'; export function generateBaseTestStoreModules() { return [ diff --git a/src/frontend/packages/store/src/actions/dashboard-actions.ts b/src/frontend/packages/store/src/actions/dashboard-actions.ts index 3918585f85..b06a5d043a 100644 --- a/src/frontend/packages/store/src/actions/dashboard-actions.ts +++ b/src/frontend/packages/store/src/actions/dashboard-actions.ts @@ -11,10 +11,6 @@ export const SET_HEADER_EVENT = '[Dashboard] Set header event'; export const ENABLE_SIDE_NAV_MOBILE_MODE = '[Dashboard] Enable mobile nav'; export const DISABLE_SIDE_NAV_MOBILE_MODE = '[Dashboard] Disable mobile nav'; -export const SHOW_SIDE_HELP = '[Dashboard] Show side help'; -export const CLOSE_SIDE_HELP = '[Dashboard] Close side help'; - - export const TIMEOUT_SESSION = '[Dashboard] Timeout Session'; export const ENABLE_POLLING = '[Dashboard] Enable Polling'; export const SET_STRATOS_THEME = '[Dashboard] Set Theme'; @@ -38,15 +34,6 @@ export class ToggleSideNav implements Action { type = TOGGLE_SIDE_NAV; } -export class ShowSideHelp implements Action { - constructor(public document: string) { } - type = SHOW_SIDE_HELP; -} - -export class CloseSideHelp implements Action { - type = CLOSE_SIDE_HELP; -} - export class SetHeaderEvent implements Action { constructor(public minimised = false) { } type = SET_HEADER_EVENT; diff --git a/src/frontend/packages/store/src/entity-catalog/entity-catalog-entity.ts b/src/frontend/packages/store/src/entity-catalog/entity-catalog-entity.ts index 901d9b4245..0cbbfd3cd4 100644 --- a/src/frontend/packages/store/src/entity-catalog/entity-catalog-entity.ts +++ b/src/frontend/packages/store/src/entity-catalog/entity-catalog-entity.ts @@ -254,7 +254,9 @@ export class StratosCatalogEndpointEntity extends StratosBaseCatalogEntity; constructor( entity: StratosEndpointExtensionDefinition | IStratosEndpointDefinition, - getLink?: (metadata: IEndpointFavMetadata) => string + getLink?: (metadata: IEndpointFavMetadata) => string, + // TODO find a way to attach this to PreviewableComponent + getPreviewableComponent?: () => object ) { const fullEntity = { ...entity, @@ -266,6 +268,7 @@ export class StratosCatalogEndpointEntity extends StratosBaseCatalogEntity { getStatusObservable?(entity: Y): Observable; // TODO This should be used in the entities schema. getGuid(entityMetadata: T): string; + // TODO find a way to attach this to PreviewableComponent + getPreviewableComponent?(): object; getLink?(entityMetadata: T): string; getLines?(): EntityRowBuilder[]; getSubTypeLabels?(entityMetadata: T): { diff --git a/src/frontend/packages/store/src/reducers/dashboard-reducer.ts b/src/frontend/packages/store/src/reducers/dashboard-reducer.ts index b5f606d521..9b8baa772a 100644 --- a/src/frontend/packages/store/src/reducers/dashboard-reducer.ts +++ b/src/frontend/packages/store/src/reducers/dashboard-reducer.ts @@ -1,5 +1,4 @@ import { - CLOSE_SIDE_HELP, CLOSE_SIDE_NAV, DISABLE_SIDE_NAV_MOBILE_MODE, ENABLE_POLLING, @@ -7,11 +6,12 @@ import { HYDRATE_DASHBOARD_STATE, HydrateDashboardStateAction, OPEN_SIDE_NAV, + SET_HEADER_EVENT, SET_STRATOS_THEME, + SetHeaderEvent, SetPollingEnabledAction, SetSessionTimeoutAction, SetThemeAction, - SHOW_SIDE_HELP, TIMEOUT_SESSION, TOGGLE_SIDE_NAV, } from '../actions/dashboard-actions'; @@ -23,9 +23,8 @@ export interface DashboardState { isMobile: boolean; isMobileNavOpen: boolean; sideNavPinned: boolean; - sideHelpOpen: boolean; - sideHelpDocument: string; themeKey: string; + headerEventMinimized: boolean; } export const defaultDashboardState: DashboardState = { @@ -35,9 +34,8 @@ export const defaultDashboardState: DashboardState = { isMobile: false, isMobileNavOpen: false, sideNavPinned: true, - sideHelpOpen: false, - sideHelpDocument: null, - themeKey: null + themeKey: null, + headerEventMinimized: false, }; export function dashboardReducer(state: DashboardState = defaultDashboardState, action): DashboardState { @@ -61,10 +59,11 @@ export function dashboardReducer(state: DashboardState = defaultDashboardState, return { ...state, isMobile: true, isMobileNavOpen: false }; case DISABLE_SIDE_NAV_MOBILE_MODE: return { ...state, isMobile: false, isMobileNavOpen: false }; - case SHOW_SIDE_HELP: - return { ...state, sideHelpOpen: true, sideHelpDocument: action.document }; - case CLOSE_SIDE_HELP: - return { ...state, sideHelpOpen: false, sideHelpDocument: '' }; + case SET_HEADER_EVENT: + const setHeaderEvent = action as SetHeaderEvent; + return { + ...state, headerEventMinimized: setHeaderEvent.minimised + }; case TIMEOUT_SESSION: const timeoutSessionAction = action as SetSessionTimeoutAction; return { diff --git a/src/frontend/packages/store/testing/src/store-test-helper.ts b/src/frontend/packages/store/testing/src/store-test-helper.ts index 8f11dbc856..14f555f881 100644 --- a/src/frontend/packages/store/testing/src/store-test-helper.ts +++ b/src/frontend/packages/store/testing/src/store-test-helper.ts @@ -165,7 +165,8 @@ function getDefaultInitialTestStratosStoreState() { isMobileNavOpen: false, sideNavPinned: false, pollingEnabled: true, - themeKey: null + themeKey: null, + headerEventMinimized: true, }, actionHistory: [], lists: {},