Skip to content

Commit 416bcb1

Browse files
authored
Merge pull request #61 from StatCan/59-add-custom-image-url-validation
fix: add url validation for custom images
2 parents 37c5e7c + 4e2554f commit 416bcb1

File tree

4 files changed

+66
-20
lines changed

4 files changed

+66
-20
lines changed

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@ <h3>
55
</h3>
66
<p>
77
{{ "formImage.txtImage" | translate }}
8-
<a href="https://statcan.github.io/daaas/en/1-Experiments/Selecting-an-Image/"
9-
target="_blank" style="text-decoration: none">
8+
<a
9+
href="https://statcan.github.io/daaas/en/1-Experiments/Selecting-an-Image/"
10+
target="_blank"
11+
style="text-decoration: none"
12+
>
1013
{{ "formImage.txtImageLink" | translate }}
1114
</a>
1215
</p>
@@ -43,6 +46,6 @@ <h3>
4346
formControlName="customImage"
4447
#cstmimg
4548
/>
46-
<mat-error>{{ "formImage.errorImageRequired" | translate }}</mat-error>
49+
<mat-error>{{ urlValidation() }}</mat-error>
4750
</mat-form-field>
4851
</div>
Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
import { Component, OnInit, Input } from "@angular/core";
2-
import { FormGroup } from "@angular/forms";
1+
import {Component, OnInit, Input} from "@angular/core";
2+
import {FormGroup, ValidatorFn, AbstractControl} from "@angular/forms";
3+
import {TranslateService} from "@ngx-translate/core";
4+
import {Subscription} from "rxjs";
35

46
@Component({
57
selector: "app-form-image",
@@ -12,24 +14,64 @@ export class FormImageComponent implements OnInit {
1214
@Input() readonly: boolean;
1315
@Input() hideRegistry: boolean;
1416
@Input() hideVersion: boolean;
17+
subscriptions = new Subscription();
1518

16-
constructor() {}
19+
constructor(private translate: TranslateService) {}
1720

18-
ngOnInit() {}
21+
ngOnInit() {
22+
//Add validator for custom image urls (no http[s]://)
23+
this.parentForm.get("customImage").setValidators([this.urlValidator()]);
24+
25+
//disable custom image input when not being used, so errors are ignored
26+
this.subscriptions.add(
27+
this.parentForm
28+
.get("customImageCheck")
29+
.valueChanges.subscribe((b: boolean) => {
30+
if (b) {
31+
this.parentForm.controls.customImage.enable();
32+
} else {
33+
this.parentForm.controls.customImage.disable();
34+
}
35+
})
36+
);
37+
}
1938

2039
imageDisplayName(image: string): string {
21-
const [name, version = null] = image.split(':');
22-
let tokens = name.split('/')
40+
const [name, version = null] = image.split(":");
41+
let tokens = name.split("/");
2342

24-
if (this.hideRegistry && tokens.length > 1 && tokens[0].includes('.')) {
43+
if (this.hideRegistry && tokens.length > 1 && tokens[0].includes(".")) {
2544
tokens.shift();
2645
}
27-
let displayName = tokens.join('/');
46+
let displayName = tokens.join("/");
2847

2948
if (!this.hideVersion && version !== null) {
3049
displayName = `${displayName}:${version}`;
3150
}
32-
3351
return displayName;
3452
}
53+
54+
urlValidation(): void {
55+
const url = this.parentForm.get("customImage");
56+
57+
if (url.hasError("invalidUrl")) {
58+
let urlBeginning = "https://";
59+
const schemeReg = /^http:\/\//i;
60+
61+
if (schemeReg.test(url.value)) {
62+
urlBeginning = "http://";
63+
}
64+
65+
return this.translate.instant("formImage.errorHttp", {
66+
scheme: urlBeginning
67+
});
68+
}
69+
}
70+
71+
private urlValidator(): ValidatorFn {
72+
return (control: AbstractControl): {[key: string]: any} => {
73+
const schemeReg = /^http[s]?:\/\//i;
74+
return schemeReg.test(control.value) ? {invalidUrl: true} : null;
75+
};
76+
}
3577
}

frontend/src/assets/i18n/en.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
"formAdvancedOptions": {
5151
"h3AdvancedSettings": "Advanced Settings",
5252
"toggleSharedMemory": "Enable Shared Memory",
53-
"lblSystemLanguage":"System language",
53+
"lblSystemLanguage": "System language",
5454
"lblEnglish": "English",
5555
"lblFrench": "Français"
5656
},
@@ -83,7 +83,8 @@
8383
"plhImage": "Docker Image",
8484
"lblCustomImage": "Custom Image",
8585
"plhCustomImage": "Provide a custom Image",
86-
"errorImageRequired": "Please provide and Image to use"
86+
"errorImageRequired": "Please provide and Image to use",
87+
"errorHttp": "{{scheme}} is not allowed in URLs"
8788
},
8889
"formName": {
8990
"h3Name": "Name",
@@ -129,7 +130,7 @@
129130
"lblName": "Name",
130131
"errorNameRequired": "The volume name can't be empty",
131132
"errorNamePattern": "The volume name can only contain lowercase alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character",
132-
"errorMountedVolume":"The volume is already mounted to another notebook and cannot be currently selected",
133+
"errorMountedVolume": "The volume is already mounted to another notebook and cannot be currently selected",
133134
"lblSize": "Size",
134135
"lblMode": "Mode",
135136
"optReadWriteOnce": "ReadWriteOnce",

frontend/src/assets/i18n/fr.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
"formAdvancedOptions": {
5151
"h3AdvancedSettings": "Paramètres avancés",
5252
"toggleSharedMemory": "Activer la mémoire partagée",
53-
"lblSystemLanguage":"Langue du système",
53+
"lblSystemLanguage": "Langue du système",
5454
"lblEnglish": "English",
5555
"lblFrench": "Français"
5656
},
@@ -73,18 +73,18 @@
7373
"lblGpuVendor": "Vendeur de GPU",
7474
"errorGpuVendorRequired": "Vous devez spécifier le vendeur des GPUs assignés",
7575
"specsWarningMessage": "La sélection de 1 GPU définira automatiquement 4 processeurs et 96Gi de la mémoire."
76-
7776
},
7877
"formImage": {
7978
"h3Image": "Image",
8079
"txtImage": "Sélectionner les logiciels de base désirés sur votre serveur. Si vous choisissez une image personnallisée, elle doit être disponible dans le registre des conteneurs DAaaS.",
81-
"txtImageLink": "Voici plus d'informations pour vous aider à sélectionner une image selon vos besoins.",
80+
"txtImageLink": "Voici plus d'informations pour vous aider à sélectionner une image selon vos besoins.",
8281
"ckbImage": "Image personnalisée",
8382
"lblImage": "Image",
8483
"plhImage": "Image Docker",
8584
"lblCustomImage": "Image personnalisée",
8685
"plhCustomImage": "Fournir une image personnalisée",
87-
"errorImageRequired": "Veuillez fournir une image a utiliser"
86+
"errorImageRequired": "Veuillez fournir une image a utiliser",
87+
"errorHttp": "{{scheme}} n'est pas autorisé dans les URLs"
8888
},
8989
"formName": {
9090
"h3Name": "Nom",
@@ -130,7 +130,7 @@
130130
"lblName": "Nom",
131131
"errorNameRequired": "Le nom du volume ne peut pas être vide",
132132
"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",
133-
"errorMountedVolume":"Le volume est déjà monté sur un autre bloc-notes et ne peut être sélectionné actuellement",
133+
"errorMountedVolume": "Le volume est déjà monté sur un autre bloc-notes et ne peut être sélectionné actuellement",
134134
"lblSize": "Taille",
135135
"lblMode": "Mode",
136136
"optReadWriteOnce": "ReadWriteOnce",

0 commit comments

Comments
 (0)