Skip to content

Commit 8641aab

Browse files
authored
Merge pull request #51 from StatCan/50-add-validation-mounted-pvc
feat: Add validation for mounted pvc
2 parents 0e116fd + 22805a3 commit 8641aab

File tree

4 files changed

+54
-8
lines changed

4 files changed

+54
-8
lines changed

frontend/src/app/resource-form/volume/volume.component.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,10 @@
1818
matInput
1919
formControlName="name"
2020
*ngIf="volume.value.type == 'New'; else existingPvcControl"
21+
[errorStateMatcher]="matcher"
2122
/>
2223
<ng-template #existingPvcControl>
23-
<mat-select formControlName="name">
24+
<mat-select formControlName="name" [errorStateMatcher]="matcher">
2425
<mat-option *ngFor="let pvc of existingPVCs" [value]="pvc">{{
2526
pvc
2627
}}</mat-option>

frontend/src/app/resource-form/volume/volume.component.ts

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import { Component, OnInit, Input, OnDestroy } from "@angular/core";
2-
import { FormGroup, Validators } from "@angular/forms";
2+
import { FormGroup, Validators, ValidatorFn, AbstractControl, FormControl, FormGroupDirective, NgForm } from "@angular/forms";
33
import { Volume } from "src/app/utils/types";
44
import { Subscription } from "rxjs";
5-
import {TranslateService} from "@ngx-translate/core";
5+
import { TranslateService } from "@ngx-translate/core";
6+
import { NamespaceService } from "src/app/services/namespace.service";
7+
import { KubernetesService } from "src/app/services/kubernetes.service";
8+
import {ErrorStateMatcher} from '@angular/material/core';
69

710
@Component({
811
selector: "app-volume",
@@ -12,9 +15,12 @@ import {TranslateService} from "@ngx-translate/core";
1215
export class VolumeComponent implements OnInit, OnDestroy {
1316
private _notebookName = "";
1417
private _defaultStorageClass: boolean;
18+
private mountedVolumes: Set<string> = new Set<string>();
1519

1620
currentPVC: Volume;
1721
existingPVCs: Set<string> = new Set();
22+
// Specific error matcher for volume name field
23+
matcher = new PvcErrorStateMatcher();
1824

1925
subscriptions = new Subscription();
2026

@@ -101,14 +107,21 @@ export class VolumeComponent implements OnInit, OnDestroy {
101107
}
102108

103109
// ----- Component Functions -----
104-
constructor(private translate: TranslateService) {}
110+
constructor(
111+
private translate: TranslateService,
112+
private k8s: KubernetesService,
113+
private ns: NamespaceService) { }
105114

106115
ngOnInit() {
107116
this.volume
108117
.get("name")
109-
.setValidators([Validators.required, Validators.pattern(/^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$/)]);
110-
111-
this.subscriptions.add(
118+
.setValidators([
119+
Validators.required,
120+
this.isMountedValidator(),
121+
Validators.pattern(/^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$/)
122+
]);
123+
124+
this.subscriptions.add(
112125
this.volume.get("type").valueChanges.subscribe((type: string) => {
113126
this.setVolumeType(type);
114127
})
@@ -122,6 +135,18 @@ export class VolumeComponent implements OnInit, OnDestroy {
122135
this.updateVolInputFields();
123136
})
124137
);
138+
139+
// Get the list of mounted volumes of the existing Notebooks in the selected Namespace
140+
this.subscriptions.add(
141+
this.ns.getSelectedNamespace().subscribe(ns => {
142+
this.k8s.getResource(ns).subscribe(notebooks => {
143+
this.mountedVolumes.clear();
144+
notebooks.map(nb => nb.volumes.map(v => {
145+
this.mountedVolumes.add(v)
146+
}));
147+
});
148+
})
149+
);
125150
}
126151

127152
ngOnDestroy() {
@@ -166,12 +191,30 @@ export class VolumeComponent implements OnInit, OnDestroy {
166191

167192
showNameError() {
168193
const volumeName = this.volume.get("name");
169-
170194
if (volumeName.hasError("required")) {
171195
return this.translate.instant("volume.errorNameRequired");
172196
}
173197
if (volumeName.hasError("pattern")) {
174198
return this.translate.instant("volume.errorNamePattern");
175199
}
200+
if (volumeName.hasError("isMounted")) {
201+
return this.translate.instant("volume.errorMountedVolume");
202+
}
203+
}
204+
205+
//Method that disables selecting a mounted pvc
206+
private isMountedValidator(): ValidatorFn {
207+
return (control: AbstractControl): { [key: string]: any } => {
208+
const exists = this.mountedVolumes.has(control.value);
209+
return exists ? { isMounted: true } : null;
210+
};
211+
}
212+
}
213+
// Error when invalid control is dirty, touched, or submitted
214+
export class PvcErrorStateMatcher implements ErrorStateMatcher {
215+
isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
216+
const isSubmitted = form && form.submitted;
217+
//Allows to control when volume is untouched but already assigned
218+
return !!(control && control.invalid && (control.dirty || control.touched || isSubmitted || !control.hasError("pattern")));
176219
}
177220
}

frontend/src/assets/i18n/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@
126126
"lblName": "Name",
127127
"errorNameRequired": "The volume name can't be empty",
128128
"errorNamePattern": "The volume name can only contain lowercase alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character",
129+
"errorMountedVolume":"The volume is already mounted to another notebook and cannot be currently selected",
129130
"lblSize": "Size",
130131
"lblMode": "Mode",
131132
"optReadWriteOnce": "ReadWriteOnce",

frontend/src/assets/i18n/fr.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@
127127
"lblName": "Nom",
128128
"errorNameRequired": "Le nom du volume ne peut pas être vide",
129129
"errorNamePattern": "Le nom du volume ne peut contenir que des caractères alphanumériques en minuscule, '-' ou '.', et doit commencer et se terminer par un caractères alphanumériques",
130+
"errorMountedVolume":"Le volume est déjà monté sur un autre bloc-notes et ne peut être sélectionné actuellement",
130131
"lblSize": "Taille",
131132
"lblMode": "Mode",
132133
"optReadWriteOnce": "ReadWriteOnce",

0 commit comments

Comments
 (0)