From 209518819360fcc760f46ac8ff5d89f14b844992 Mon Sep 17 00:00:00 2001 From: wg102 Date: Thu, 17 Sep 2020 15:22:57 -0400 Subject: [PATCH 1/4] fix: delete old notebooks (pvc) --- frontend/src/app/app.module.ts | 4 +- .../confirm-dialog.component.html | 0 .../confirm-dialog.component.scss | 0 .../confirm-dialog.component.spec.ts | 0 .../confirm-dialog.component.ts | 0 .../app/main-table/main-table.component.html | 17 ++- .../app/main-table/main-table.component.scss | 91 ++++++++++++ .../app/main-table/main-table.component.ts | 133 +++++++++++++++++- .../resource-table.component.html | 2 +- .../resource-table.component.scss | 86 ----------- .../resource-table.component.ts | 111 ++------------- .../volumes-table/volume-table.component.html | 41 ++++++ .../volumes-table/volume-table.component.scss | 0 .../volume-table.component.spec.ts | 24 ++++ .../volumes-table/volume-table.component.ts | 33 +++++ .../src/app/services/kubernetes.service.ts | 21 ++- frontend/src/app/utils/types.ts | 7 +- main.go | 11 ++ persistentvolumeclaims.go | 24 ++++ 19 files changed, 407 insertions(+), 198 deletions(-) rename frontend/src/app/main-table/{resource-table => }/confirm-dialog/confirm-dialog.component.html (100%) rename frontend/src/app/main-table/{resource-table => }/confirm-dialog/confirm-dialog.component.scss (100%) rename frontend/src/app/main-table/{resource-table => }/confirm-dialog/confirm-dialog.component.spec.ts (100%) rename frontend/src/app/main-table/{resource-table => }/confirm-dialog/confirm-dialog.component.ts (100%) create mode 100644 frontend/src/app/main-table/volumes-table/volume-table.component.html create mode 100644 frontend/src/app/main-table/volumes-table/volume-table.component.scss create mode 100644 frontend/src/app/main-table/volumes-table/volume-table.component.spec.ts create mode 100644 frontend/src/app/main-table/volumes-table/volume-table.component.ts diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index 1ddc2b80..d5a30b5b 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -29,7 +29,7 @@ import { NamespaceSelectComponent } from "./main-table/namespace-select/namespac import { ResourceTableComponent } from "./main-table/resource-table/resource-table.component"; import { SnackBarComponent } from "./services/snack-bar/snack-bar.component"; import { ResourceFormComponent } from "./resource-form/resource-form.component"; -import { ConfirmDialogComponent } from "./main-table/resource-table/confirm-dialog/confirm-dialog.component"; +import { ConfirmDialogComponent } from "./main-table/confirm-dialog/confirm-dialog.component"; import { VolumeComponent } from "./resource-form/volume/volume.component"; import { FormNameComponent } from "./resource-form/form-name/form-name.component"; import { FormImageComponent } from "./resource-form/form-image/form-image.component"; @@ -48,6 +48,7 @@ import { RokFormDataVolumesComponent } from "./uis/rok/rok-resource-form/rok-for import { RokErrorMsgComponent } from "./uis/rok/rok-error-msg/rok-error-msg.component"; import { FormConfigurationsComponent } from "./resource-form/form-configurations/form-configurations.component"; import { FormGpusComponent } from "./resource-form/form-gpus/form-gpus.component"; +import { VolumeTableComponent } from "./main-table/volumes-table/volume-table.component"; @NgModule({ @@ -77,6 +78,7 @@ import { FormGpusComponent } from "./resource-form/form-gpus/form-gpus.component RokErrorMsgComponent, FormConfigurationsComponent, FormGpusComponent, + VolumeTableComponent, ], imports: [ BrowserModule, diff --git a/frontend/src/app/main-table/resource-table/confirm-dialog/confirm-dialog.component.html b/frontend/src/app/main-table/confirm-dialog/confirm-dialog.component.html similarity index 100% rename from frontend/src/app/main-table/resource-table/confirm-dialog/confirm-dialog.component.html rename to frontend/src/app/main-table/confirm-dialog/confirm-dialog.component.html diff --git a/frontend/src/app/main-table/resource-table/confirm-dialog/confirm-dialog.component.scss b/frontend/src/app/main-table/confirm-dialog/confirm-dialog.component.scss similarity index 100% rename from frontend/src/app/main-table/resource-table/confirm-dialog/confirm-dialog.component.scss rename to frontend/src/app/main-table/confirm-dialog/confirm-dialog.component.scss diff --git a/frontend/src/app/main-table/resource-table/confirm-dialog/confirm-dialog.component.spec.ts b/frontend/src/app/main-table/confirm-dialog/confirm-dialog.component.spec.ts similarity index 100% rename from frontend/src/app/main-table/resource-table/confirm-dialog/confirm-dialog.component.spec.ts rename to frontend/src/app/main-table/confirm-dialog/confirm-dialog.component.spec.ts diff --git a/frontend/src/app/main-table/resource-table/confirm-dialog/confirm-dialog.component.ts b/frontend/src/app/main-table/confirm-dialog/confirm-dialog.component.ts similarity index 100% rename from frontend/src/app/main-table/resource-table/confirm-dialog/confirm-dialog.component.ts rename to frontend/src/app/main-table/confirm-dialog/confirm-dialog.component.ts diff --git a/frontend/src/app/main-table/main-table.component.html b/frontend/src/app/main-table/main-table.component.html index 52f76dba..4773baec 100644 --- a/frontend/src/app/main-table/main-table.component.html +++ b/frontend/src/app/main-table/main-table.component.html @@ -8,6 +8,21 @@
- +
+ + +
+
+ + +
+
+ diff --git a/frontend/src/app/main-table/main-table.component.scss b/frontend/src/app/main-table/main-table.component.scss index c4dcc23d..f5222c41 100644 --- a/frontend/src/app/main-table/main-table.component.scss +++ b/frontend/src/app/main-table/main-table.component.scss @@ -9,3 +9,94 @@ .spacing { padding-top: 1.5rem; } + +.card { + width: 900px; + padding: 0px; + border-radius: 5px; + background: white; +} + +table { + width: 100%; +} + +.header { + display: flex; + align-items: center; + padding: 0px 16px 0px 16px; + height: 64px; +} + +.header p { + font-weight: 400; + font-size: 20px; +} + +.cdk-column-actions { + text-align: center; +} + +.mat-icon { + line-height: 0.85; +} + +td.mat-cell:last-of-type, +td.mat-footer-cell:last-of-type, +th.mat-header-cell:last-of-type { + padding-right: 0px; +} + +.inline { + display: inline-block; +} + +// Status Icons +.running { + color: green; +} + +.warning { + color: orange; +} + +.error { + color: red; +} + +.status { + display: inline-flex; + vertical-align: middle; +} + +.delete { + color: red; +} + +// Flex +.parent { + display: flex; +} + +.spacer { + flex-grow: 1; +} + +th, +td { + overflow: hidden; + text-overflow: ellipsis; +} + +td.mat-column-image, +td.mat-column-name { + max-width: 200px; +} + +td.mat-column-cpu { + width: 40px; +} + +td.mat-column-actions{ + text-align: center; +} diff --git a/frontend/src/app/main-table/main-table.component.ts b/frontend/src/app/main-table/main-table.component.ts index a681d481..170309c8 100644 --- a/frontend/src/app/main-table/main-table.component.ts +++ b/frontend/src/app/main-table/main-table.component.ts @@ -1,5 +1,16 @@ import { Component, OnInit } from "@angular/core"; import { NamespaceService } from "../services/namespace.service"; +import { KubernetesService } from "src/app/services/kubernetes.service"; + +import { Subscription } from "rxjs"; +import { isEqual } from "lodash"; +import { first } from "rxjs/operators"; + +import { ExponentialBackoff } from "src/app/utils/polling"; +import { MatDialog } from "@angular/material/dialog"; +import { ConfirmDialogComponent } from "./confirm-dialog/confirm-dialog.component"; +import { Pvc, Volume, Resource } from "../utils/types"; + @Component({ selector: "app-main-table", @@ -7,10 +18,126 @@ import { NamespaceService } from "../services/namespace.service"; styleUrls: ["./main-table.component.scss"] }) export class MainTableComponent implements OnInit { + currNamespace = ""; namespaces = []; - currNamespace: string; + resources = []; + usedPVCs: Set = new Set(); + pvcs: Volume[] = []; + customPvcs: Pvc[] = []; + + subscriptions = new Subscription(); + poller: ExponentialBackoff; + + constructor( + public ns: NamespaceService, + private k8s: KubernetesService, + private dialog: MatDialog + ) {} + + ngOnInit() { + this.poller = new ExponentialBackoff({ interval: 2000, retries: 3 }); + const resourcesSub = this.poller.start().subscribe(() => { + if (!this.currNamespace) { + return; + } + + let getVolumes = this.k8s.getVolumes(this.currNamespace).toPromise().then( + pvcs => { + if(isEqual(this.pvcs, pvcs)){ + return; + } + this.pvcs = pvcs; + } + ); + + let getResource = this.k8s.getResource(this.currNamespace).toPromise().then( + resources => { + if (isEqual(this.resources, resources)){ + return; + } + this.resources = resources; + this.usedPVCs.clear(); + this.resources.forEach(res => {this.usedPVCs.add(res.volumes.forEach(element => {this.usedPVCs.add(element);}))}) + }); + + Promise.all([getVolumes, getResource]) + .then(val => { + this.customPvcs = []; + this.pvcs.forEach(vol => { + this.customPvcs.push({pvc:vol, ismounted:this.usedPVCs.has(vol.name)}); + }); + }); + }); + + // Keep track of the selected namespace + const namespaceSub = this.ns + .getSelectedNamespace() + .subscribe(namespace => { + this.currNamespace = namespace; + this.poller.reset(); + }); + + this.subscriptions.add(resourcesSub); + this.subscriptions.add(namespaceSub); + } + + deleteResource(rsrc: Resource): void { + const dialogRef = this.dialog.open(ConfirmDialogComponent, { + width: "fit-content", + data: { + title: "You are about to delete Notebook Server: " + rsrc.name, + message: + "Are you sure you want to delete this Notebook Server? " + + "Your data might be lost if the Server is not backed by persistent storage.", + yes: "delete", + no: "cancel" + } + }); + + dialogRef + .afterClosed() + .pipe(first()) + .subscribe(result => { + if (!result || result !== "delete") { + return; + } + + this.k8s + .deleteResource(rsrc.namespace, rsrc.name) + .pipe(first()) + .subscribe(r => { + this.poller.reset(); + }); + }); + } + + deletePvc(p: Pvc): void { + const dialogRef = this.dialog.open(ConfirmDialogComponent, { + width: "fit-content", + data: { + title: "You are about to delete the PVC: " + p.pvc.name, + message: + "Are you sure you want to delete this Persistent Volume Claim? ", + yes: "delete", + no: "cancel" + } + }); + + dialogRef + .afterClosed() + .pipe(first()) + .subscribe(result => { + if (result !== "delete") { + return; + } - constructor(public ns: NamespaceService) {} + this.k8s + .deletePersistentStorageClaim(p.pvc.namespace, p.pvc.name) + .pipe(first()) + .subscribe(r => { + this.poller.reset(); + }); + }); + } - ngOnInit() {} } diff --git a/frontend/src/app/main-table/resource-table/resource-table.component.html b/frontend/src/app/main-table/resource-table/resource-table.component.html index 298fd87d..d42b16bf 100644 --- a/frontend/src/app/main-table/resource-table/resource-table.component.html +++ b/frontend/src/app/main-table/resource-table/resource-table.component.html @@ -13,7 +13,7 @@ - +
diff --git a/frontend/src/app/main-table/resource-table/resource-table.component.scss b/frontend/src/app/main-table/resource-table/resource-table.component.scss index b5b32094..e69de29b 100644 --- a/frontend/src/app/main-table/resource-table/resource-table.component.scss +++ b/frontend/src/app/main-table/resource-table/resource-table.component.scss @@ -1,86 +0,0 @@ -.card { - width: 900px; - padding: 0px; - border-radius: 5px; - background: white; -} - -table { - width: 100%; -} - -.header { - display: flex; - align-items: center; - padding: 0px 16px 0px 16px; - height: 64px; -} - -.header p { - font-weight: 400; - font-size: 20px; -} - -.cdk-column-actions { - text-align: center; -} - -.mat-icon { - line-height: 0.85; -} - -td.mat-cell:last-of-type, -td.mat-footer-cell:last-of-type, -th.mat-header-cell:last-of-type { - padding-right: 0px; -} - -.inline { - display: inline-block; -} - -// Status Icons -.running { - color: green; -} - -.warning { - color: orange; -} - -.error { - color: red; -} - -.status { - display: inline-flex; - vertical-align: middle; -} - -.delete { - color: red; -} - -// Flex -.parent { - display: flex; -} - -.spacer { - flex-grow: 1; -} - -th, -td { - overflow: hidden; - text-overflow: ellipsis; -} - -td.mat-column-image, -td.mat-column-name { - max-width: 200px; -} - -td.mat-column-cpu { - width: 40px; -} diff --git a/frontend/src/app/main-table/resource-table/resource-table.component.ts b/frontend/src/app/main-table/resource-table/resource-table.component.ts index 899d60f4..557a30a1 100644 --- a/frontend/src/app/main-table/resource-table/resource-table.component.ts +++ b/frontend/src/app/main-table/resource-table/resource-table.component.ts @@ -1,33 +1,16 @@ -import { Component, OnInit, ViewChild } from "@angular/core"; -import { MatSort } from "@angular/material/sort"; +import { Component, OnInit, ViewChild, Input, Output, EventEmitter } from "@angular/core"; import { MatTableDataSource } from "@angular/material/table"; -import { MatDialog } from "@angular/material/dialog"; -import { Subscription } from "rxjs"; -import { first } from "rxjs/operators"; -import { isEqual } from "lodash"; - -import { NamespaceService } from "src/app/services/namespace.service"; -import { KubernetesService } from "src/app/services/kubernetes.service"; import { Resource } from "src/app/utils/types"; -import { ExponentialBackoff } from "src/app/utils/polling"; -import { ConfirmDialogComponent } from "./confirm-dialog/confirm-dialog.component"; @Component({ selector: "app-resource-table", templateUrl: "./resource-table.component.html", - styleUrls: ["./resource-table.component.scss"] + styleUrls: ["./resource-table.component.scss", "../main-table.component.scss"] }) export class ResourceTableComponent implements OnInit { - @ViewChild(MatSort) sort: MatSort; - - // Logic data - resources = []; - currNamespace = ""; - - subscriptions = new Subscription(); - poller: ExponentialBackoff; + @Input() notebooks: Resource[]; + @Output() deleteNotebookEvent = new EventEmitter(); - // Table data displayedColumns: string[] = [ "status", "name", @@ -39,94 +22,18 @@ export class ResourceTableComponent implements OnInit { "actions" ]; dataSource = new MatTableDataSource(); + constructor() {} - showNameFilter = false; - - constructor( - private namespaceService: NamespaceService, - private k8s: KubernetesService, - private dialog: MatDialog - ) {} - - ngOnInit() { - this.dataSource.sort = this.sort; + ngOnInit() { } - // Create the exponential backoff poller - this.poller = new ExponentialBackoff({ interval: 2000, retries: 3 }); - const resourcesSub = this.poller.start().subscribe(() => { - // NOTE: We are using both the 'trackBy' feature in the Table for performance - // and also detecting with lodash if the new data is different from the old - // one. This is because, if the data changes we want to reset the poller - if (!this.currNamespace) { - return; - } - - this.k8s.getResource(this.currNamespace).subscribe(resources => { - if (!isEqual(this.resources, resources)) { - this.resources = resources; - this.dataSource.data = this.resources; - this.poller.reset(); - } - }); - }); - - // Keep track of the selected namespace - const namespaceSub = this.namespaceService - .getSelectedNamespace() - .subscribe(namespace => { - this.currNamespace = namespace; - this.poller.reset(); - }); - - this.subscriptions.add(resourcesSub); - this.subscriptions.add(namespaceSub); - } - - ngOnDestroy() { - this.subscriptions.unsubscribe(); - } + ngOnDestroy() { } // Resource (Notebook) Actions connectResource(rsrc: Resource): void { window.open(`/notebook/${rsrc.namespace}/${rsrc.name}/`); } - deleteResource(rsrc: Resource): void { - const dialogRef = this.dialog.open(ConfirmDialogComponent, { - width: "fit-content", - data: { - title: "You are about to delete Notebook Server: " + rsrc.name, - message: - "Are you sure you want to delete this Notebook Server? " + - "Your data might be lost if the Server is not backed by persistent storage.", - yes: "delete", - no: "cancel" - } - }); - - dialogRef - .afterClosed() - .pipe(first()) - .subscribe(result => { - if (!result || result !== "delete") { - return; - } - - this.k8s - .deleteResource(rsrc.namespace, rsrc.name) - .pipe(first()) - .subscribe(r => { - this.poller.reset(); - }); - }); - } - - // Misc - trackByFn(index: number, r: Resource) { - return `${r.name}/${r.namespace}/${r.age}/${r.status}/${r.reason}`; - } - - toggleFilter() { - this.showNameFilter = !this.showNameFilter; + deleteResource(rsrc:Resource){ + this.deleteNotebookEvent.emit(rsrc); } } diff --git a/frontend/src/app/main-table/volumes-table/volume-table.component.html b/frontend/src/app/main-table/volumes-table/volume-table.component.html new file mode 100644 index 00000000..d5155ced --- /dev/null +++ b/frontend/src/app/main-table/volumes-table/volume-table.component.html @@ -0,0 +1,41 @@ +
+
+

Notebook Volumes

+
+
Status
+ + + + + + + + + + + + + + + + + + + + + + + + +
Name{{ elem.pvc.name }}Namespace{{ elem.pvc.namespace }}Is Mounted? + {{ elem.ismounted ? 'Yes' : 'No' }} + + +
+ diff --git a/frontend/src/app/main-table/volumes-table/volume-table.component.scss b/frontend/src/app/main-table/volumes-table/volume-table.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/frontend/src/app/main-table/volumes-table/volume-table.component.spec.ts b/frontend/src/app/main-table/volumes-table/volume-table.component.spec.ts new file mode 100644 index 00000000..3ea890d0 --- /dev/null +++ b/frontend/src/app/main-table/volumes-table/volume-table.component.spec.ts @@ -0,0 +1,24 @@ +import { async, ComponentFixture, TestBed } from "@angular/core/testing"; + +import { VolumeTableComponent } from "./volume-table.component"; + +describe("VolumeTableComponent", () => { + let component: VolumeTableComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [VolumeTableComponent] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(VolumeTableComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it("should create", () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/main-table/volumes-table/volume-table.component.ts b/frontend/src/app/main-table/volumes-table/volume-table.component.ts new file mode 100644 index 00000000..37d6177d --- /dev/null +++ b/frontend/src/app/main-table/volumes-table/volume-table.component.ts @@ -0,0 +1,33 @@ +import { Component, OnInit, Input, Output, EventEmitter } from "@angular/core"; +import { MatTableDataSource } from "@angular/material/table"; +import { Pvc } from "../../utils/types"; + +@Component({ + selector: "app-volume-table", + templateUrl: "./volume-table.component.html", + styleUrls: ["./volume-table.component.scss","../main-table.component.scss"] +}) + +export class VolumeTableComponent implements OnInit { + @Input() custompvcs: Pvc[]; + @Output() deletePvcEvent = new EventEmitter(); + + // Table data + displayedColumns: string[] = [ + "name", + "namespace", + "isMounted", + "actions" + ]; + dataSource = new MatTableDataSource(); + + constructor() { } + + ngOnInit() { } + + ngOnDestroy() { } + + deletePvc(pvc: Pvc){ + this.deletePvcEvent.emit(pvc); + } +} diff --git a/frontend/src/app/services/kubernetes.service.ts b/frontend/src/app/services/kubernetes.service.ts index 16ec7247..1540f0af 100644 --- a/frontend/src/app/services/kubernetes.service.ts +++ b/frontend/src/app/services/kubernetes.service.ts @@ -109,11 +109,26 @@ export class KubernetesService { ); } - // Delete functions - deleteResource(ns: string, nm: string): Observable { + //Delete functions + deleteResource(ns: string, name: string): Observable { const url = environment.apiUrl + - `/api/namespaces/${ns}/${environment.resource}/${nm}`; + `/api/namespaces/${ns}/${environment.resource}/${name}`; + + return this.http.delete(url).pipe( + tap(data => this.handleBackendError(data)), + catchError(error => this.handleError(error)), + map(_ => { + return "deleted"; + }) + ); + } + + // Delete pvc + deletePersistentStorageClaim(ns: string, name: string): Observable { + const url = + environment.apiUrl + + `/api/namespaces/${ns}/pvcs/${name}`; return this.http.delete(url).pipe( tap(data => this.handleBackendError(data)), diff --git a/frontend/src/app/utils/types.ts b/frontend/src/app/utils/types.ts index d1bb1ecf..4391658b 100644 --- a/frontend/src/app/utils/types.ts +++ b/frontend/src/app/utils/types.ts @@ -1,7 +1,7 @@ export interface Volume { name: string; size: string; - namepsace?: string; + namespace?: string; class?: string; mode: string; type?: string; @@ -138,3 +138,8 @@ export enum SnackType { Warning, Info } + +export interface Pvc { + pvc: Volume; + ismounted: boolean +} diff --git a/main.go b/main.go index 498436b0..a672d84d 100644 --- a/main.go +++ b/main.go @@ -177,6 +177,17 @@ func main() { }, }, }, s.GetPersistentVolumeClaims)).Methods("GET") + + router.HandleFunc("/api/namespaces/{namespace}/pvcs/{pvc}", s.checkAccess(authorizationv1.SubjectAccessReview{ + Spec: authorizationv1.SubjectAccessReviewSpec{ + ResourceAttributes: &authorizationv1.ResourceAttributes{ + Group: corev1.SchemeGroupVersion.Group, + Verb: "delete", + Resource: "persistentvolumeclaims", + Version: corev1.SchemeGroupVersion.Version, + }, + }, + }, s.DeletePvc)).Methods("DELETE") router.HandleFunc("/api/namespaces/{namespace}/poddefaults", s.checkAccess(authorizationv1.SubjectAccessReview{ Spec: authorizationv1.SubjectAccessReviewSpec{ ResourceAttributes: &authorizationv1.ResourceAttributes{ diff --git a/persistentvolumeclaims.go b/persistentvolumeclaims.go index 73601507..cfba08f5 100644 --- a/persistentvolumeclaims.go +++ b/persistentvolumeclaims.go @@ -9,6 +9,7 @@ import ( corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/labels" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) type pvcresponse struct { @@ -60,3 +61,26 @@ func (s *server) GetPersistentVolumeClaims(w http.ResponseWriter, r *http.Reques s.respond(w, r, resp) } + +//TODO: Delete pvc +func (s *server) DeletePvc(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + namespace := vars["namespace"] + pvc := vars["pvc"] + + log.Printf("deleting pvc %q for %q", pvc, namespace) + + propagation := v1.DeletePropagationForeground + err := s.clientsets.kubernetes.CoreV1().PersistentVolumeClaims(namespace).Delete(r.Context(), pvc, v1.DeleteOptions{ + PropagationPolicy: &propagation, + }) + if err != nil { + s.error(w, r, err) + return + } + + s.respond(w, r, APIResponse{ + Success: true, + }) +} + From fdb507f647288e6781cac7d68274acb5a7711bec Mon Sep 17 00:00:00 2001 From: Brendan Gadd Date: Wed, 23 Sep 2020 17:37:50 -0400 Subject: [PATCH 2/4] feat: Add icons to main tables + refactor * Add icons to headers of the two tables under the main-table component * Refactor code for cleaner use of promises * Show user size of volumes in table * Show user what notebooks volumes are mounted to --- .../app/main-table/main-table.component.scss | 4 + .../app/main-table/main-table.component.ts | 106 ++++++++---------- .../resource-table.component.html | 1 + .../volumes-table/volume-table.component.html | 17 +-- .../volumes-table/volume-table.component.ts | 26 ++--- frontend/src/app/utils/types.ts | 4 +- frontend/tsconfig.json | 2 +- 7 files changed, 72 insertions(+), 88 deletions(-) diff --git a/frontend/src/app/main-table/main-table.component.scss b/frontend/src/app/main-table/main-table.component.scss index f5222c41..1d7cda90 100644 --- a/frontend/src/app/main-table/main-table.component.scss +++ b/frontend/src/app/main-table/main-table.component.scss @@ -28,6 +28,10 @@ table { height: 64px; } +.header mat-icon { + margin: 3px 10px 0 0; +} + .header p { font-weight: 400; font-size: 20px; diff --git a/frontend/src/app/main-table/main-table.component.ts b/frontend/src/app/main-table/main-table.component.ts index 170309c8..2eac4c5b 100644 --- a/frontend/src/app/main-table/main-table.component.ts +++ b/frontend/src/app/main-table/main-table.component.ts @@ -1,16 +1,15 @@ -import { Component, OnInit } from "@angular/core"; -import { NamespaceService } from "../services/namespace.service"; -import { KubernetesService } from "src/app/services/kubernetes.service"; +import {Component, OnInit} from "@angular/core"; +import {NamespaceService} from "../services/namespace.service"; +import {KubernetesService} from "src/app/services/kubernetes.service"; -import { Subscription } from "rxjs"; -import { isEqual } from "lodash"; -import { first } from "rxjs/operators"; - -import { ExponentialBackoff } from "src/app/utils/polling"; -import { MatDialog } from "@angular/material/dialog"; -import { ConfirmDialogComponent } from "./confirm-dialog/confirm-dialog.component"; -import { Pvc, Volume, Resource } from "../utils/types"; +import {Subscription} from "rxjs"; +import {isEqual} from "lodash"; +import {first} from "rxjs/operators"; +import {ExponentialBackoff} from "src/app/utils/polling"; +import {MatDialog} from "@angular/material/dialog"; +import {ConfirmDialogComponent} from "./confirm-dialog/confirm-dialog.component"; +import {Pvc, Volume, Resource} from "../utils/types"; @Component({ selector: "app-main-table", @@ -35,47 +34,36 @@ export class MainTableComponent implements OnInit { ) {} ngOnInit() { - this.poller = new ExponentialBackoff({ interval: 2000, retries: 3 }); + this.poller = new ExponentialBackoff({interval: 2000, retries: 3}); const resourcesSub = this.poller.start().subscribe(() => { if (!this.currNamespace) { return; - } + } - let getVolumes = this.k8s.getVolumes(this.currNamespace).toPromise().then( - pvcs => { - if(isEqual(this.pvcs, pvcs)){ - return; - } - this.pvcs = pvcs; - } - ); - - let getResource = this.k8s.getResource(this.currNamespace).toPromise().then( - resources => { - if (isEqual(this.resources, resources)){ - return; + Promise.all([ + this.k8s.getResource(this.currNamespace).toPromise(), + this.k8s.getVolumes(this.currNamespace).toPromise() + ]).then(([notebooks, volumes]) => { + if (!isEqual(notebooks, this.resources) || !isEqual(volumes, this.pvcs)) { + this.poller.reset(); } - this.resources = resources; - this.usedPVCs.clear(); - this.resources.forEach(res => {this.usedPVCs.add(res.volumes.forEach(element => {this.usedPVCs.add(element);}))}) - }); - - Promise.all([getVolumes, getResource]) - .then(val => { - this.customPvcs = []; - this.pvcs.forEach(vol => { - this.customPvcs.push({pvc:vol, ismounted:this.usedPVCs.has(vol.name)}); - }); + this.resources = notebooks; + this.pvcs = volumes; + let mounts = Object.fromEntries( + notebooks.flatMap(nb => nb.volumes.map(v => [v, nb])) + ); + this.customPvcs = volumes.map(v => ({ + pvc: v, + mountedBy: mounts[v.name]?.name + })); }); }); // Keep track of the selected namespace - const namespaceSub = this.ns - .getSelectedNamespace() - .subscribe(namespace => { - this.currNamespace = namespace; - this.poller.reset(); - }); + const namespaceSub = this.ns.getSelectedNamespace().subscribe(namespace => { + this.currNamespace = namespace; + this.poller.reset(); + }); this.subscriptions.add(resourcesSub); this.subscriptions.add(namespaceSub); @@ -115,29 +103,29 @@ export class MainTableComponent implements OnInit { const dialogRef = this.dialog.open(ConfirmDialogComponent, { width: "fit-content", data: { - title: "You are about to delete the PVC: " + p.pvc.name, + title: "You are about to delete the volume: " + p.pvc.name, message: - "Are you sure you want to delete this Persistent Volume Claim? ", + "Are you sure you want to delete this volume? " + + "This action can't be undone.", yes: "delete", no: "cancel" } }); dialogRef - .afterClosed() - .pipe(first()) - .subscribe(result => { - if (result !== "delete") { - return; - } + .afterClosed() + .pipe(first()) + .subscribe(result => { + if (result !== "delete") { + return; + } - this.k8s - .deletePersistentStorageClaim(p.pvc.namespace, p.pvc.name) - .pipe(first()) - .subscribe(r => { - this.poller.reset(); - }); - }); + this.k8s + .deletePersistentStorageClaim(p.pvc.namespace, p.pvc.name) + .pipe(first()) + .subscribe(_ => { + this.poller.reset(); + }); + }); } - } diff --git a/frontend/src/app/main-table/resource-table/resource-table.component.html b/frontend/src/app/main-table/resource-table/resource-table.component.html index d42b16bf..46d55784 100644 --- a/frontend/src/app/main-table/resource-table/resource-table.component.html +++ b/frontend/src/app/main-table/resource-table/resource-table.component.html @@ -1,5 +1,6 @@
+ computer

Notebook Servers

diff --git a/frontend/src/app/main-table/volumes-table/volume-table.component.html b/frontend/src/app/main-table/volumes-table/volume-table.component.html index d5155ced..9b30e66f 100644 --- a/frontend/src/app/main-table/volumes-table/volume-table.component.html +++ b/frontend/src/app/main-table/volumes-table/volume-table.component.html @@ -1,5 +1,6 @@
+ storage

Notebook Volumes

@@ -9,15 +10,17 @@ - - - + + + - - + + @@ -27,7 +30,7 @@
{{ elem.pvc.name }} Namespace{{ elem.pvc.namespace }}Size + {{ elem.pvc.size }} + Is Mounted?Used By - {{ elem.ismounted ? 'Yes' : 'No' }} + {{ elem.mountedBy || '(None)' }}