Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add validation for mounted pvc #51

Merged
merged 2 commits into from
Jan 7, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion frontend/src/app/resource-form/volume/volume.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@
matInput
formControlName="name"
*ngIf="volume.value.type == 'New'; else existingPvcControl"
[errorStateMatcher]="matcher"
/>
<ng-template #existingPvcControl>
<mat-select formControlName="name">
<mat-select formControlName="name" [errorStateMatcher]="matcher">
<mat-option *ngFor="let pvc of existingPVCs" [value]="pvc">{{
pvc
}}</mat-option>
Expand Down
57 changes: 50 additions & 7 deletions frontend/src/app/resource-form/volume/volume.component.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { Component, OnInit, Input, OnDestroy } from "@angular/core";
import { FormGroup, Validators } from "@angular/forms";
import { FormGroup, Validators, ValidatorFn, AbstractControl, FormControl, FormGroupDirective, NgForm } from "@angular/forms";
import { Volume } from "src/app/utils/types";
import { Subscription } from "rxjs";
import {TranslateService} from "@ngx-translate/core";
import { TranslateService } from "@ngx-translate/core";
import { NamespaceService } from "src/app/services/namespace.service";
import { KubernetesService } from "src/app/services/kubernetes.service";
import {ErrorStateMatcher} from '@angular/material/core';

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

currentPVC: Volume;
existingPVCs: Set<string> = new Set();
// Specific error matcher for volume name field
matcher = new PvcErrorStateMatcher();

subscriptions = new Subscription();

Expand Down Expand Up @@ -101,14 +107,21 @@ export class VolumeComponent implements OnInit, OnDestroy {
}

// ----- Component Functions -----
constructor(private translate: TranslateService) {}
constructor(
private translate: TranslateService,
private k8s: KubernetesService,
private ns: NamespaceService) { }

ngOnInit() {
this.volume
.get("name")
.setValidators([Validators.required, Validators.pattern(/^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$/)]);

this.subscriptions.add(
.setValidators([
Validators.required,
this.isMountedValidator(),
Validators.pattern(/^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$/)
]);

this.subscriptions.add(
this.volume.get("type").valueChanges.subscribe((type: string) => {
this.setVolumeType(type);
})
Expand All @@ -122,6 +135,18 @@ export class VolumeComponent implements OnInit, OnDestroy {
this.updateVolInputFields();
})
);

// Get the list of mounted volumes of the existing Notebooks in the selected Namespace
this.subscriptions.add(
this.ns.getSelectedNamespace().subscribe(ns => {
this.k8s.getResource(ns).subscribe(notebooks => {
this.mountedVolumes.clear();
notebooks.map(nb => nb.volumes.map(v => {
this.mountedVolumes.add(v)
}));
});
})
);
}

ngOnDestroy() {
Expand Down Expand Up @@ -166,12 +191,30 @@ export class VolumeComponent implements OnInit, OnDestroy {

showNameError() {
const volumeName = this.volume.get("name");

if (volumeName.hasError("required")) {
return this.translate.instant("volume.errorNameRequired");
}
if (volumeName.hasError("pattern")) {
return this.translate.instant("volume.errorNamePattern");
}
if (volumeName.hasError("isMounted")) {
return this.translate.instant("volume.errorMountedVolume");
}
}

//Method that disables selecting a mounted pvc
private isMountedValidator(): ValidatorFn {
return (control: AbstractControl): { [key: string]: any } => {
const exists = this.mountedVolumes.has(control.value);
return exists ? { isMounted: true } : null;
};
}
}
// Error when invalid control is dirty, touched, or submitted
export class PvcErrorStateMatcher implements ErrorStateMatcher {
isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
const isSubmitted = form && form.submitted;
//Allows to control when volume is untouched but already assigned
return !!(control && control.invalid && (control.dirty || control.touched || isSubmitted || !control.hasError("pattern")));
}
}
1 change: 1 addition & 0 deletions frontend/src/assets/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@
"lblName": "Name",
"errorNameRequired": "The volume name can't be empty",
"errorNamePattern": "The volume name can only contain lowercase alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character",
"errorMountedVolume":"The volume is already mounted to another notebook and cannot be currently selected",
"lblSize": "Size",
"lblMode": "Mode",
"optReadWriteOnce": "ReadWriteOnce",
Expand Down
1 change: 1 addition & 0 deletions frontend/src/assets/i18n/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@
"lblName": "Nom",
"errorNameRequired": "Le nom du volume ne peut pas être vide",
"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",
"errorMountedVolume":"Le volume est déjà monté sur un autre bloc-notes et ne peut être sélectionné actuellement",
"lblSize": "Taille",
"lblMode": "Mode",
"optReadWriteOnce": "ReadWriteOnce",
Expand Down