Skip to content

Commit 2e9daf6

Browse files
authored
Merge pull request #20 from StatCan/18-destroy-old-notebooks
Support notebook volume (PVC) management
2 parents 1a50add + 4fabd14 commit 2e9daf6

20 files changed

+446
-178
lines changed

frontend/src/app/app.module.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import { NamespaceSelectComponent } from "./main-table/namespace-select/namespac
2929
import { ResourceTableComponent } from "./main-table/resource-table/resource-table.component";
3030
import { SnackBarComponent } from "./services/snack-bar/snack-bar.component";
3131
import { ResourceFormComponent } from "./resource-form/resource-form.component";
32-
import { ConfirmDialogComponent } from "./main-table/resource-table/confirm-dialog/confirm-dialog.component";
32+
import { ConfirmDialogComponent } from "./main-table/confirm-dialog/confirm-dialog.component";
3333
import { VolumeComponent } from "./resource-form/volume/volume.component";
3434
import { FormNameComponent } from "./resource-form/form-name/form-name.component";
3535
import { FormImageComponent } from "./resource-form/form-image/form-image.component";
@@ -48,6 +48,7 @@ import { RokFormDataVolumesComponent } from "./uis/rok/rok-resource-form/rok-for
4848
import { RokErrorMsgComponent } from "./uis/rok/rok-error-msg/rok-error-msg.component";
4949
import { FormConfigurationsComponent } from "./resource-form/form-configurations/form-configurations.component";
5050
import { FormGpusComponent } from "./resource-form/form-gpus/form-gpus.component";
51+
import { VolumeTableComponent } from "./main-table/volumes-table/volume-table.component";
5152

5253

5354
@NgModule({
@@ -77,6 +78,7 @@ import { FormGpusComponent } from "./resource-form/form-gpus/form-gpus.component
7778
RokErrorMsgComponent,
7879
FormConfigurationsComponent,
7980
FormGpusComponent,
81+
VolumeTableComponent,
8082
],
8183
imports: [
8284
BrowserModule,

frontend/src/app/main-table/main-table.component.html

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,21 @@
88
<!-- The Table showing our Resource -->
99
<div class="parent" [class.spacing]="ns.dashboardConnected()">
1010
<div class="spacer"></div>
11-
<app-resource-table></app-resource-table>
11+
<app-resource-table
12+
[notebooks]="resources"
13+
(deleteNotebookEvent)="deleteResource($event)"
14+
></app-resource-table>
1215
<div class="spacer"></div>
1316
</div>
17+
18+
<!-- The Table showing the persistent volume claims -->
19+
<div class="parent spacing">
20+
<div class="spacer"></div>
21+
<app-volume-table
22+
[pvcProperties]="pvcProperties"
23+
(deletePvcEvent)="deletePvc($event)"
24+
>
25+
</app-volume-table>
26+
<div class="spacer"></div>
27+
</div>
28+

frontend/src/app/main-table/main-table.component.scss

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,98 @@
99
.spacing {
1010
padding-top: 1.5rem;
1111
}
12+
13+
.card {
14+
width: 900px;
15+
padding: 0px;
16+
border-radius: 5px;
17+
background: white;
18+
}
19+
20+
table {
21+
width: 100%;
22+
}
23+
24+
.header {
25+
display: flex;
26+
align-items: center;
27+
padding: 0px 16px 0px 16px;
28+
height: 64px;
29+
}
30+
31+
.header mat-icon {
32+
margin: 3px 10px 0 0;
33+
}
34+
35+
.header p {
36+
font-weight: 400;
37+
font-size: 20px;
38+
}
39+
40+
.cdk-column-actions {
41+
text-align: center;
42+
}
43+
44+
.mat-icon {
45+
line-height: 0.85;
46+
}
47+
48+
td.mat-cell:last-of-type,
49+
td.mat-footer-cell:last-of-type,
50+
th.mat-header-cell:last-of-type {
51+
padding-right: 0px;
52+
}
53+
54+
.inline {
55+
display: inline-block;
56+
}
57+
58+
// Status Icons
59+
.running {
60+
color: green;
61+
}
62+
63+
.warning {
64+
color: orange;
65+
}
66+
67+
.error {
68+
color: red;
69+
}
70+
71+
.status {
72+
display: inline-flex;
73+
vertical-align: middle;
74+
}
75+
76+
.delete {
77+
color: red;
78+
}
79+
80+
// Flex
81+
.parent {
82+
display: flex;
83+
}
84+
85+
.spacer {
86+
flex-grow: 1;
87+
}
88+
89+
th,
90+
td {
91+
overflow: hidden;
92+
text-overflow: ellipsis;
93+
}
94+
95+
td.mat-column-image,
96+
td.mat-column-name {
97+
max-width: 200px;
98+
}
99+
100+
td.mat-column-cpu {
101+
width: 40px;
102+
}
103+
104+
td.mat-column-actions{
105+
text-align: center;
106+
}
Lines changed: 76 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,87 @@
1-
import { Component, OnInit } from "@angular/core";
2-
import { NamespaceService } from "../services/namespace.service";
1+
import {Component, OnInit} from "@angular/core";
2+
import {NamespaceService} from "../services/namespace.service";
3+
import {KubernetesService} from "src/app/services/kubernetes.service";
4+
5+
import {Subscription} from "rxjs";
6+
import {isEqual} from "lodash";
7+
import {first} from "rxjs/operators";
8+
9+
import {ExponentialBackoff} from "src/app/utils/polling";
10+
import {Volume, Resource} from "../utils/types";
11+
import {PvcWithStatus} from "./volumes-table/volume-table.component"
312

413
@Component({
514
selector: "app-main-table",
615
templateUrl: "./main-table.component.html",
716
styleUrls: ["./main-table.component.scss"]
817
})
918
export class MainTableComponent implements OnInit {
19+
currNamespace = "";
1020
namespaces = [];
11-
currNamespace: string;
21+
resources = [];
22+
23+
pvcs: Volume[] = [];
24+
pvcProperties: PvcWithStatus[] = [];
25+
26+
subscriptions = new Subscription();
27+
poller: ExponentialBackoff;
28+
29+
constructor(
30+
public ns: NamespaceService,
31+
private k8s: KubernetesService,
32+
) {}
33+
34+
ngOnInit() {
35+
this.poller = new ExponentialBackoff({interval: 2000, retries: 3});
36+
const resourcesSub = this.poller.start().subscribe(() => {
37+
if (!this.currNamespace) {
38+
return;
39+
}
40+
41+
Promise.all([
42+
this.k8s.getResource(this.currNamespace).toPromise(),
43+
this.k8s.getVolumes(this.currNamespace).toPromise()
44+
]).then(([notebooks, volumes]) => {
45+
if (!isEqual(notebooks, this.resources) || !isEqual(volumes, this.pvcs)) {
46+
this.poller.reset();
47+
}
48+
this.resources = notebooks;
49+
this.pvcs = volumes;
50+
let mounts = Object.fromEntries(
51+
notebooks.flatMap(nb => nb.volumes.map(v => [v, nb]))
52+
);
53+
this.pvcProperties = volumes.map(v => ({
54+
pvc: v,
55+
mountedBy: mounts[v.name]?.name
56+
}));
57+
});
58+
});
59+
60+
// Keep track of the selected namespace
61+
const namespaceSub = this.ns.getSelectedNamespace().subscribe(namespace => {
62+
this.currNamespace = namespace;
63+
this.poller.reset();
64+
});
65+
66+
this.subscriptions.add(resourcesSub);
67+
this.subscriptions.add(namespaceSub);
68+
}
1269

13-
constructor(public ns: NamespaceService) {}
70+
deleteResource(rsrc: Resource): void {
71+
this.k8s
72+
.deleteResource(rsrc.namespace, rsrc.name)
73+
.pipe(first())
74+
.subscribe(r => {
75+
this.poller.reset();
76+
});
77+
}
1478

15-
ngOnInit() {}
79+
deletePvc(p: PvcWithStatus): void {
80+
this.k8s
81+
.deletePersistentVolumeClaim(p.pvc.namespace, p.pvc.name)
82+
.pipe(first())
83+
.subscribe(_ => {
84+
this.poller.reset();
85+
});
86+
}
1687
}

frontend/src/app/main-table/resource-table/resource-table.component.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<div class="card mat-elevation-z2">
22
<div class="header">
3+
<mat-icon>computer</mat-icon>
34
<p>Notebook Servers</p>
45

56
<div class="spacer"></div>
@@ -13,7 +14,7 @@
1314

1415
<mat-divider></mat-divider>
1516

16-
<table mat-table [dataSource]="dataSource" [trackBy]="trackByFn" matSort>
17+
<table mat-table [dataSource]="notebooks" matSort>
1718
<!-- Status Column -->
1819
<ng-container matColumnDef="status">
1920
<th mat-header-cell *matHeaderCellDef>Status</th>
Lines changed: 0 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,86 +0,0 @@
1-
.card {
2-
width: 900px;
3-
padding: 0px;
4-
border-radius: 5px;
5-
background: white;
6-
}
7-
8-
table {
9-
width: 100%;
10-
}
11-
12-
.header {
13-
display: flex;
14-
align-items: center;
15-
padding: 0px 16px 0px 16px;
16-
height: 64px;
17-
}
18-
19-
.header p {
20-
font-weight: 400;
21-
font-size: 20px;
22-
}
23-
24-
.cdk-column-actions {
25-
text-align: center;
26-
}
27-
28-
.mat-icon {
29-
line-height: 0.85;
30-
}
31-
32-
td.mat-cell:last-of-type,
33-
td.mat-footer-cell:last-of-type,
34-
th.mat-header-cell:last-of-type {
35-
padding-right: 0px;
36-
}
37-
38-
.inline {
39-
display: inline-block;
40-
}
41-
42-
// Status Icons
43-
.running {
44-
color: green;
45-
}
46-
47-
.warning {
48-
color: orange;
49-
}
50-
51-
.error {
52-
color: red;
53-
}
54-
55-
.status {
56-
display: inline-flex;
57-
vertical-align: middle;
58-
}
59-
60-
.delete {
61-
color: red;
62-
}
63-
64-
// Flex
65-
.parent {
66-
display: flex;
67-
}
68-
69-
.spacer {
70-
flex-grow: 1;
71-
}
72-
73-
th,
74-
td {
75-
overflow: hidden;
76-
text-overflow: ellipsis;
77-
}
78-
79-
td.mat-column-image,
80-
td.mat-column-name {
81-
max-width: 200px;
82-
}
83-
84-
td.mat-column-cpu {
85-
width: 40px;
86-
}

0 commit comments

Comments
 (0)