diff --git a/frontend/src/app/resource-form/form-advanced-options/form-advanced-options.component.html b/frontend/src/app/resource-form/form-advanced-options/form-advanced-options.component.html index 0291a942..5da50bc1 100644 --- a/frontend/src/app/resource-form/form-advanced-options/form-advanced-options.component.html +++ b/frontend/src/app/resource-form/form-advanced-options/form-advanced-options.component.html @@ -1,13 +1,22 @@
-

+ +

-

- {{ "formAdvancedOptions.txtMiscSettings" | translate }} -

- - - {{ "formAdvancedOptions.toggleSharedMemory" | translate }} - + {{ "formAdvancedOptions.h3AdvancedSettings" | translate }} + +
+
+ + {{ "formAdvancedOptions.toggleSharedMemory" | translate }} + + + {{ "formAdvancedOptions.lblSystemLanguage" | translate }} + + + {{ v.label | translate }} + + + +
+
diff --git a/frontend/src/app/resource-form/form-advanced-options/form-advanced-options.component.scss b/frontend/src/app/resource-form/form-advanced-options/form-advanced-options.component.scss index 5453173d..e3da6e76 100644 --- a/frontend/src/app/resource-form/form-advanced-options/form-advanced-options.component.scss +++ b/frontend/src/app/resource-form/form-advanced-options/form-advanced-options.component.scss @@ -1,3 +1,60 @@ mat-slide-toggle { margin-bottom: 0.6rem; } + +//Simulate the h3 heading for the title +.lbl-toggle-advanced-options{ + color: rgba(0, 0, 0, 0.54); + display: block; + font-size: 1.17em; + margin-block-start: 1em; + margin-block-end: 1em; + margin-inline-start: 0px; + margin-inline-end: 0px; + font-weight: bold; + + transition: all 0.25s ease-out; + cursor: pointer; +} +// Have the + sign +.lbl-toggle-advanced-options::after { + content: '\02795'; /* Unicode character for "plus" sign (+) */ + font-size: 0.8em; + float: right; + margin-left: 0.5em; + transition: transform .2s ease-out; +} +// Have the - sign +.toggle-advanced-options:checked + .lbl-toggle-advanced-options::after { + content: "\2796"; /* Unicode character for "minus" sign (-) */ +} +//Hide the checkbox +.toggle-advanced-options[type='checkbox']{ + display: none; +} +.collapsible-content { + max-height: 0px; + overflow: hidden; + transition: max-height .25s ease-in-out; +} + +.toggle-advanced-options:checked + .lbl-toggle-advanced-options + .collapsible-content { + max-height: 100vh; +} + +.toggle-advanced-options:checked + .lbl-toggle-advanced-options { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} + +.collapsible-content .content-inner { + border: 1px solid rgba(224, 224, 224, 0.45); + border-bottom-left-radius: 7px; + border-bottom-right-radius: 7px; + padding: .5rem 1rem; +} + +.matSpacing { + padding: 0.6em 0em; +} + diff --git a/frontend/src/app/resource-form/form-advanced-options/form-advanced-options.component.ts b/frontend/src/app/resource-form/form-advanced-options/form-advanced-options.component.ts index 3251d21e..b3c10093 100644 --- a/frontend/src/app/resource-form/form-advanced-options/form-advanced-options.component.ts +++ b/frontend/src/app/resource-form/form-advanced-options/form-advanced-options.component.ts @@ -1,5 +1,6 @@ import { Component, OnInit, Input } from "@angular/core"; import { FormGroup } from "@angular/forms"; +import {TranslateService} from "@ngx-translate/core"; @Component({ selector: "app-form-advanced-options", @@ -11,8 +12,14 @@ import { FormGroup } from "@angular/forms"; }) export class FormAdvancedOptionsComponent implements OnInit { @Input() parentForm: FormGroup; + languageList = [ + {'id':'en', 'label':'formAdvancedOptions.lblEnglish'}, + {'id':'fr', 'label':'formAdvancedOptions.lblFrench'} + ]; - constructor() {} + constructor(private translate: TranslateService) {} - ngOnInit() {} + ngOnInit() { + this.parentForm.controls.language.setValue(this.translate.defaultLang); + } } diff --git a/frontend/src/app/utils/common.ts b/frontend/src/app/utils/common.ts index fe2e6bf2..54641a25 100644 --- a/frontend/src/app/utils/common.ts +++ b/frontend/src/app/utils/common.ts @@ -30,6 +30,7 @@ export function getFormDefaults(): FormGroup { datavols: fb.array([]), shm: [true, []], configurations: [[], []], + language: ['', [Validators.required]], }); } diff --git a/frontend/src/assets/i18n/en.json b/frontend/src/assets/i18n/en.json index 9895b6ef..1a4986ff 100644 --- a/frontend/src/assets/i18n/en.json +++ b/frontend/src/assets/i18n/en.json @@ -48,9 +48,11 @@ "deleteDialogNo": "Cancel" }, "formAdvancedOptions": { - "h3MiscSettings": "Miscellaneous Settings", - "txtMiscSettings": "Other possible settings to be applied to the Notebook Server.", - "toggleSharedMemory": "Enable Shared Memory" + "h3AdvancedSettings": "Advanced Settings", + "toggleSharedMemory": "Enable Shared Memory", + "lblSystemLanguage":"System language", + "lblEnglish": "English", + "lblFrench": "Français" }, "formConfiguration": { "h3Configuration": "Configurations", diff --git a/frontend/src/assets/i18n/fr.json b/frontend/src/assets/i18n/fr.json index 775c3684..516247a1 100644 --- a/frontend/src/assets/i18n/fr.json +++ b/frontend/src/assets/i18n/fr.json @@ -48,9 +48,11 @@ "deleteDialogNo": "Annuler" }, "formAdvancedOptions": { - "h3MiscSettings": "Paramètres divers", - "txtMiscSettings": "Autres paramètres pouvant être appliqué au serveur bloc-notes.", - "toggleSharedMemory": "Activer la mémoire partagée" + "h3AdvancedSettings": "Paramètres avancés", + "toggleSharedMemory": "Activer la mémoire partagée", + "lblSystemLanguage":"Langue du système", + "lblEnglish": "English", + "lblFrench": "Français" }, "formConfiguration": { "h3Configuration": "Configurations", diff --git a/go.sum b/go.sum index bb67f1ee..bc98fb5e 100644 --- a/go.sum +++ b/go.sum @@ -363,6 +363,7 @@ k8s.io/apimachinery v0.18.6/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCk k8s.io/apimachinery v0.18.8 h1:jimPrycCqgx2QPearX3to1JePz7wSbVLq+7PdBTTwQ0= k8s.io/apimachinery v0.18.8/go.mod h1:6sQd+iHEqmOtALqOFjSWp2KZ9F0wlU/nWm0ZgsYWMig= k8s.io/apimachinery v0.19.2 h1:5Gy9vQpAGTKHPVOh5c4plE274X8D/6cuEiTO2zve7tc= +k8s.io/apimachinery v0.20.1 h1:LAhz8pKbgR8tUwn7boK+b2HZdt7MiTu2mkYtFMUjTRQ= k8s.io/client-go v0.18.6 h1:I+oWqJbibLSGsZj8Xs8F0aWVXJVIoUHWaaJV3kUN/Zw= k8s.io/client-go v0.18.6/go.mod h1:/fwtGLjYMS1MaM5oi+eXhKwG+1UHidUEXRh6cNsdO0Q= k8s.io/code-generator v0.0.0-20200403215918-804a58607501/go.mod h1:UZPlxqFoDEMYYDJksMKLFggA4nK5Y3Nni//sVQggki4= diff --git a/notebooks.go b/notebooks.go index 2ca22d0a..cc73e732 100644 --- a/notebooks.go +++ b/notebooks.go @@ -3,10 +3,12 @@ package main import ( "context" "encoding/json" + "errors" "fmt" "io/ioutil" "log" "net/http" + "regexp" "sort" "strings" @@ -23,6 +25,7 @@ import ( const DefaultServiceAccountName string = "default-editor" const SharedMemoryVolumeName string = "dshm" const SharedMemoryVolumePath string = "/dev/shm" +const EnvKfLanguage string = "KF_LANG" type volumetype string @@ -61,6 +64,7 @@ type newnotebookrequest struct { DataVolumes []volumerequest `json:"datavols"` EnableSharedMemory bool `json:"shm"` Configurations []string `json:"configurations"` + Language string `json:"language"` } type notebookresponse struct { @@ -471,6 +475,19 @@ func (s *server) NewNotebook(w http.ResponseWriter, r *http.Request) { } } + //Add Language + //Validate that the language format is valid (language[_territory]) + match, err := regexp.MatchString("^[[:alpha:]]{2}(_[[:alpha:]]{2})?$", req.Language) + if (err != nil || !match) { + var errLanguageFormat = errors.New("Error: the value of KF_LANG environment variable ('" + req.Language + "') is not a valid format (e.g 'en', 'en_US', ...)") + s.error(w, r, errLanguageFormat) + return + } + notebook.Spec.Template.Spec.Containers[0].Env = append(notebook.Spec.Template.Spec.Containers[0].Env, corev1.EnvVar{ + Name: EnvKfLanguage, + Value: req.Language, + }) + log.Printf("creating notebook %q for %q", notebook.ObjectMeta.Name, namespace) // Submit the notebook to the API server