You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@datalab.apache.org by dg...@apache.org on 2020/11/19 13:07:16 UTC

[incubator-datalab] 01/02: [DATALAB-2138]: Implement administration page

This is an automated email from the ASF dual-hosted git repository.

dgnatyshyn pushed a commit to branch DATALAB-2156
in repository https://gitbox.apache.org/repos/asf/incubator-datalab.git

commit 37fd894117fdb962f3f53beaa53551db701a1a2a
Author: Dmytro_Gnatyshyn <di...@ukr.net>
AuthorDate: Thu Nov 19 12:03:27 2020 +0200

    [DATALAB-2138]: Implement administration page
---
 .../src/main/resources/webapp/package-lock.json    |  19 ++
 .../src/main/resources/webapp/package.json         |   1 +
 .../app/administration/administration.module.ts    |   5 +-
 .../configuration/configuration.component.html     | 125 ++++++++++++
 .../configuration/configuration.component.scss     |  87 ++++++++
 .../configuration/configuration.component.ts       | 215 ++++++++++++++++++++
 .../index.ts}                                      |  27 ++-
 .../main/resources/webapp/src/app/app.module.ts    |   4 +-
 .../resources/webapp/src/app/app.routing.module.ts |   6 +
 .../resources/webapp/src/app/core/core.module.ts   |   3 +-
 .../app/core/services/configutration.service.ts    | 222 +++++++++++++++++++++
 .../multi-level-select-dropdown.component.scss     |   2 +
 .../src/app/shared/navbar/navbar.component.html    |   6 +
 .../webapp/src/assets/styles/_general.scss         |   1 +
 .../resources/webapp/src/assets/styles/_theme.scss |  22 ++
 .../src/main/resources/webapp/src/styles.scss      |   5 +-
 16 files changed, 737 insertions(+), 13 deletions(-)

diff --git a/services/self-service/src/main/resources/webapp/package-lock.json b/services/self-service/src/main/resources/webapp/package-lock.json
index a4d8a2f..89cca19 100644
--- a/services/self-service/src/main/resources/webapp/package-lock.json
+++ b/services/self-service/src/main/resources/webapp/package-lock.json
@@ -2002,6 +2002,11 @@
         "negotiator": "0.6.2"
       }
     },
+    "ace-builds": {
+      "version": "1.4.12",
+      "resolved": "https://registry.npmjs.org/ace-builds/-/ace-builds-1.4.12.tgz",
+      "integrity": "sha512-G+chJctFPiiLGvs3+/Mly3apXTcfgE45dT5yp12BcWZ1kUs+gm0qd3/fv4gsz6fVag4mM0moHVpjHDIgph6Psg=="
+    },
     "acorn": {
       "version": "6.4.2",
       "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz",
@@ -2602,6 +2607,11 @@
       "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=",
       "dev": true
     },
+    "brace": {
+      "version": "0.11.1",
+      "resolved": "https://registry.npmjs.org/brace/-/brace-0.11.1.tgz",
+      "integrity": "sha1-SJb8ydVE7vRfS7dmDbMg07N5/lg="
+    },
     "brace-expansion": {
       "version": "1.1.11",
       "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -7092,6 +7102,15 @@
         "date-fns": "^1.29.0"
       }
     },
+    "ng2-ace-editor": {
+      "version": "0.3.9",
+      "resolved": "https://registry.npmjs.org/ng2-ace-editor/-/ng2-ace-editor-0.3.9.tgz",
+      "integrity": "sha512-e8Q4YCirlL/OEiekewmzupG+zV3prYsiYmQnRzQzd0wNgsPjOLOdb0it7cCbzFfIXKGyIIHKTW5584WxPr2LnQ==",
+      "requires": {
+        "ace-builds": "^1.4.2",
+        "brace": "^0.11.1"
+      }
+    },
     "ngx-toastr": {
       "version": "12.1.0",
       "resolved": "https://registry.npmjs.org/ngx-toastr/-/ngx-toastr-12.1.0.tgz",
diff --git a/services/self-service/src/main/resources/webapp/package.json b/services/self-service/src/main/resources/webapp/package.json
index 6baa7a2..bd320b3 100644
--- a/services/self-service/src/main/resources/webapp/package.json
+++ b/services/self-service/src/main/resources/webapp/package.json
@@ -33,6 +33,7 @@
     "moment": "^2.24.0",
     "moment-timezone": "^0.5.31",
     "ng-daterangepicker": "^1.1.0",
+    "ng2-ace-editor": "^0.3.9",
     "ngx-toastr": "^12.1.0",
     "rxjs": "^6.6.3",
     "rxjs-compat": "6.5.3",
diff --git a/services/self-service/src/main/resources/webapp/src/app/administration/administration.module.ts b/services/self-service/src/main/resources/webapp/src/app/administration/administration.module.ts
index e535bda..5315ff6 100644
--- a/services/self-service/src/main/resources/webapp/src/app/administration/administration.module.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/administration/administration.module.ts
@@ -23,10 +23,11 @@ import { CommonModule } from '@angular/common';
 import { ManagenementModule } from './management';
 import { ProjectModule } from './project';
 import { RolesModule } from './roles';
+import {ConfigurationModule} from './configuration';
 
 @NgModule({
-  imports: [CommonModule, ManagenementModule, ProjectModule, RolesModule],
+  imports: [CommonModule, ManagenementModule, ProjectModule, RolesModule, ConfigurationModule],
   declarations: [],
-  exports: [ManagenementModule, ProjectModule, RolesModule]
+  exports: [ManagenementModule, ProjectModule, RolesModule, ConfigurationModule]
 })
 export class AdministrationModule { }
diff --git a/services/self-service/src/main/resources/webapp/src/app/administration/configuration/configuration.component.html b/services/self-service/src/main/resources/webapp/src/app/administration/configuration/configuration.component.html
new file mode 100644
index 0000000..8877a6c
--- /dev/null
+++ b/services/self-service/src/main/resources/webapp/src/app/administration/configuration/configuration.component.html
@@ -0,0 +1,125 @@
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~   http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+
+<div class="base-retreat">
+  <div class="sub-nav">
+    <button mat-raised-button class="butt"
+            (click)="action('save')"
+            [disabled]="provisioningSource === serverConfigs.provisioningSource
+               && serverConfigs.selvServiceSource === selvServiceSource
+               && serverConfigs.billingSource === billingSource
+               "
+    >
+      Save
+    </button>
+    <button mat-raised-button class="butt"
+            (click)="action('discard')"
+            [disabled]="provisioningSource === serverConfigs.provisioningSource
+               && serverConfigs.selvServiceSource === selvServiceSource
+               && serverConfigs.billingSource === billingSource"
+    >
+      Discard changes
+    </button>
+    <button mat-raised-button class="butt" (click)="refresh()">
+      <i class="material-icons refresh-icon">autorenew</i>Refresh
+    </button>
+  </div>
+  <mat-divider></mat-divider>
+  <div class="configuration-wrapper">
+    <mat-tab-group animationDuration="0.5ms" (selectedTabChange)="tabChanged($event)">
+      <mat-tab label="Main"
+               [disabled]="!(provisioningSource === serverConfigs.provisioningSource
+               && serverConfigs.selvServiceSource === selvServiceSource
+               && serverConfigs.billingSource === billingSource
+               ) && activeTab !== 0
+"
+      >
+        <h4>Main settings</h4>
+        <div class="main-wrapper">
+          <section class="section">
+            <p class="section-title">Restart services</p>
+            <div class="section-content">
+              <ul class="list-menu selection-list">
+                <li>
+                  <p class="list-item" role="menuitem">
+                    <span class="empty-checkbox" [ngClass]="{'checked': true}" (click)="toggleSetings($event);$event.stopPropagation()" >
+                    <span class="checked-checkbox" *ngIf="true"></span>
+                  </span>
+                    Provisioning
+                  </p>
+                </li>
+                <li>
+                  <p class="list-item" role="menuitem">
+                    <span class="empty-checkbox" [ngClass]="{'checked': true}" (click)="toggleSetings($event);$event.stopPropagation()" >
+                    <span class="checked-checkbox" *ngIf="true"></span>
+                  </span>
+                    Selv service
+                  </p>
+                </li>
+                <li>
+                  <p class="list-item" role="menuitem">
+                    <span class="empty-checkbox" [ngClass]="{'checked': true}" (click)="toggleSetings($event);$event.stopPropagation()" >
+                    <span class="checked-checkbox" *ngIf="true"></span>
+                  </span>
+                    Billing
+                  </p>
+                </li>
+              </ul>
+              <button mat-raised-button type="button" class="butt action" (click)="restartServices()">Restart</button>
+            </div>
+          </section>
+        </div>
+      </mat-tab>
+      <mat-tab label="Provisioning"
+               [disabled]="!(provisioningSource === serverConfigs.provisioningSource
+               && serverConfigs.selvServiceSource === selvServiceSource
+               && serverConfigs.billingSource === billingSource
+               ) && activeTab !== 1
+">
+        <h4>Edit provisioning.yml</h4>
+        <div class="editor-wrap">
+          <div ace-editor [(text)]="provisioningSource" ></div>
+        </div>
+      </mat-tab>
+      <mat-tab label="Self service"
+               [disabled]="!(provisioningSource === serverConfigs.provisioningSource
+               && serverConfigs.selvServiceSource === selvServiceSource
+               && serverConfigs.billingSource === billingSource
+               ) && activeTab !== 2
+"
+      >
+         <h4>Edit self-service.yml</h4>
+        <div class="editor-wrap">
+          <div ace-editor [(text)]="selvServiceSource" ></div>
+        </div>
+      </mat-tab>
+      <mat-tab label="Billing"
+               [disabled]="!(provisioningSource === serverConfigs.provisioningSource
+               && serverConfigs.selvServiceSource === selvServiceSource
+               && serverConfigs.billingSource === billingSource
+               ) && activeTab !== 3"
+      >
+        <h4>Edit billing.yml</h4>
+        <div class="editor-wrap">
+          <div ace-editor [(text)]="billingSource" ></div>
+        </div>
+      </mat-tab>
+    </mat-tab-group>
+  </div>
+</div>
diff --git a/services/self-service/src/main/resources/webapp/src/app/administration/configuration/configuration.component.scss b/services/self-service/src/main/resources/webapp/src/app/administration/configuration/configuration.component.scss
new file mode 100644
index 0000000..a6051b1
--- /dev/null
+++ b/services/self-service/src/main/resources/webapp/src/app/administration/configuration/configuration.component.scss
@@ -0,0 +1,87 @@
+/*!
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+.configuration-wrapper{
+  box-shadow: 0px 3px 5px -1px rgba(0, 0, 0, 0.2), 0px 6px 10px 0px rgba(0, 0, 0, 0.14), 0px 1px 18px 0px rgba(0, 0, 0, 0.12);
+  padding-top: 0;
+  height: calc(100vh - 130px);
+
+  .sub-nav{
+    justify-content: flex-end;
+    padding: 0 30px;
+  }
+
+  h4{
+    padding: 10px 30px;
+    color: rgba(0,0,0,.87);
+  }
+
+  .editor-wrap{
+    height: calc(100% - 60px);
+    box-shadow: 0 2px 24px 0 rgba(73,93,112,0.3);
+    margin: 0 30px;
+  }
+
+  .main-wrapper{
+    padding: 10px 30px;
+
+    .section{
+
+      &-title{
+        font-size: 17px;
+      }
+
+      &-content {
+
+        .list-menu {
+          width: 100%;
+          max-height: 450px;
+          left: 0;
+          padding: 10px 0;
+          margin: 0;
+          overflow-y: auto;
+          overflow-x: hidden;
+
+          li {
+            padding: 0;
+            margin: 0;
+          }
+
+          .list-item{
+            padding: 5px 10px;
+            display: flex;
+
+            .empty-checkbox{
+              display: block;
+              margin-right: 5px;
+            }
+          }
+        }
+      }
+    }
+  }
+}
+.sub-nav{
+  justify-content: flex-end;
+
+  .butt{
+    margin-left: 10px;
+  }
+
+}
diff --git a/services/self-service/src/main/resources/webapp/src/app/administration/configuration/configuration.component.ts b/services/self-service/src/main/resources/webapp/src/app/administration/configuration/configuration.component.ts
new file mode 100644
index 0000000..8d9830c
--- /dev/null
+++ b/services/self-service/src/main/resources/webapp/src/app/administration/configuration/configuration.component.ts
@@ -0,0 +1,215 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import {Component, OnInit, Output, EventEmitter, Inject, ViewChild, HostListener} from '@angular/core';
+import { ValidatorFn, FormControl } from '@angular/forms';
+import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
+import { ToastrService } from 'ngx-toastr';
+import {RolesGroupsService, HealthStatusService, ApplicationSecurityService, AppRoutingService} from '../../core/services';
+import {CheckUtils, SortUtils} from '../../core/util';
+import { DICTIONARY } from '../../../dictionary/global.dictionary';
+import {ProgressBarService} from '../../core/services/progress-bar.service';
+import {ConfirmationDialogComponent, ConfirmationDialogType} from '../../shared/modal-dialog/confirmation-dialog';
+import {MatTabChangeEvent} from '@angular/material/tabs';
+import {Router} from '@angular/router';
+import {ConfigurationService} from '../../core/services/configutration.service';
+import {NotificationDialogComponent} from '../../shared/modal-dialog/notification-dialog';
+import {logger} from 'codelyzer/util/logger';
+
+@Component({
+  selector: 'datalab-configuration',
+  templateUrl: './configuration.component.html',
+  styleUrls: ['./configuration.component.scss']
+})
+export class ConfigurationComponent implements OnInit {
+  private healthStatus: any;
+  editorOptions = {theme: 'vs-dark', language: 'javascript'};
+  code: string = 'function x() {console.log("Hello world!");}';
+  text: any;
+  public activeTab: number = 0;
+  selvServiceSource;
+  provisioningSource;
+  billingSource;
+  serverConfigs = {
+    selvServiceSource: {},
+    provisioningSource: {},
+    billingSource: {},
+  };
+  services: [
+    {label: 'Self service'},
+    {label: 'Provisioning'},
+    {label: 'Billing'},
+  ];
+
+  private confirmMessages = {
+    restartService: 'Restarting services will make DataLab unavailable for some time.',
+    discardChanges: 'Discard all unsaved changes.',
+    saveChanges: 'After you save changes you need to restart service.',
+  };
+
+  @HostListener('window:keydown', ['$event'])
+  onKeyDown(event: KeyboardEvent) {
+    if ((event.metaKey || event.ctrlKey) && event.key === 's' && this.activeTab !== 0 && this.router.url === '/configuration') {
+      this.action('save');
+      event.preventDefault();
+    }
+  }
+
+  constructor(
+    private healthStatusService: HealthStatusService,
+    private appRoutingService: AppRoutingService,
+    private configurationService: ConfigurationService,
+    private router: Router,
+    public dialog: MatDialog
+  ) { }
+
+  ngOnInit() {
+    this.getEnvironmentHealthStatus();
+    this.getSettings();
+  }
+  private getEnvironmentHealthStatus() {
+    this.healthStatusService.getEnvironmentHealthStatus()
+      .subscribe((result: any) => {
+          this.healthStatus = result;
+          if (!this.healthStatus.admin && !this.healthStatus.projectAdmin) {
+            this.appRoutingService.redirectToHomePage();
+          } else {
+
+          }
+        }
+      );
+  }
+
+  refresh() {
+    console.log('Refresh');
+  }
+
+  action(action) {
+    this.dialog.open(SettingsConfirmationDialogComponent, { data: {
+        action: action, message: action === 'discard' ? this.confirmMessages.discardChanges : this.confirmMessages.saveChanges
+      }, panelClass: 'modal-sm' })
+      .afterClosed().subscribe(result => {
+      console.log(action, this.activeTab);
+    });
+  }
+
+  getSettings() {
+    this.configurationService.getSelvServiceSettings().subscribe(v => {
+      console.log(v);
+      this.serverConfigs.billingSource = v;
+      this.serverConfigs.selvServiceSource = v;
+      this.serverConfigs.provisioningSource = v;
+      this.selvServiceSource = v;
+      this.provisioningSource = v;
+      this.billingSource = v;
+    }
+    );
+  }
+
+  public tabChanged(tabChangeEvent: MatTabChangeEvent): void {
+    this.activeTab = tabChangeEvent.index;
+    if (this.selvServiceSource !== this.serverConfigs.selvServiceSource) {
+      this.dialog.open(SettingsConfirmationDialogComponent, { data: {
+          action: 'Was changed'
+        }, panelClass: 'modal-sm' })
+        .afterClosed().subscribe(result => {
+        if (result) {
+          this.serverConfigs.selvServiceSource = this.selvServiceSource;
+        } else {
+          this.selvServiceSource = this.serverConfigs.selvServiceSource;
+        }
+        });
+    }
+  }
+
+  toggleSetings($event: MouseEvent) {
+
+  }
+
+  restartServices() {
+    this.dialog.open(SettingsConfirmationDialogComponent, { data: {
+        action: 'Restart services', message: this.confirmMessages.restartService
+      }, panelClass: 'modal-sm' })
+      .afterClosed().subscribe(result => {
+    });
+  }
+}
+
+@Component({
+  selector: 'confirm-dialog',
+  template: `
+  <div id="dialog-box">
+    <div class="dialog-header">
+      <h4 class="modal-title"><span class="capitalize">{{ data.action }}</span> <span *ngIf="data.action === 'save' || data.action === 'discard'"> changes</span></h4>
+      <button type="button" class="close" (click)="dialogRef.close()">&times;</button>
+    </div>
+
+    <div mat-dialog-content class="content">
+      {{data.message}}
+    </div>
+    <div class="text-center ">
+      <p class="strong">Do you want to proceed?</p>
+    </div>
+    <div class="text-center m-top-20 pb-25">
+      <button type="button" class="butt" mat-raised-button (click)="dialogRef.close()">No</button>
+      <button type="button" class="butt butt-success" mat-raised-button (click)="dialogRef.close(true)">Yes</button>
+    </div>
+  </div>
+  `,
+  styles: [
+    `
+      .content { color: #718ba6; padding: 20px 50px; font-size: 14px; font-weight: 400; margin: 0; }
+      .info { color: #35afd5; }
+      .info .confirm-dialog { color: #607D8B; }
+      header { display: flex; justify-content: space-between; color: #607D8B; }
+      header h4 i { vertical-align: bottom; }
+      header a i { font-size: 20px; }
+      header a:hover i { color: #35afd5; cursor: pointer; }
+      .content{padding: 35px 30px 30px 30px;}
+      .plur { font-style: normal; }
+      .scrolling-content{overflow-y: auto; max-height: 200px; }
+      .cluster { width: 50%; text-align: left;}
+      .status { width: 50%;text-align: left;}
+      .label { font-size: 15px; font-weight: 500; font-family: "Open Sans",sans-serif;}
+      .node { font-weight: 300;}
+      .resource-name { width: 40%;text-align: left; padding: 10px 0;line-height: 26px;}
+      .clusters-list { width: 60%;text-align: left; padding: 10px 0;line-height: 26px;}
+      .clusters-list-item { width: 100%;text-align: left;display: flex}
+      .resource-list{max-width: 100%; margin: 0 auto;margin-top: 20px; }
+      .resource-list-header{display: flex; font-weight: 600; font-size: 16px;height: 48px; border-top: 1px solid #edf1f5; border-bottom: 1px solid #edf1f5; padding: 0 20px;}
+      .resource-list-row{display: flex; border-bottom: 1px solid #edf1f5;padding: 0 20px;}
+      .confirm-resource-terminating{text-align: left; padding: 10px 20px;}
+      .confirm-message{color: #ef5c4b;font-size: 13px;min-height: 18px; text-align: center; padding-top: 20px}
+      .checkbox{margin-right: 5px;vertical-align: middle; margin-bottom: 3px;}
+      label{cursor: pointer}
+      .bottom-message{padding-top: 15px;}
+      .table-header{padding-bottom: 10px;}`
+  ]
+})
+
+export class SettingsConfirmationDialogComponent {
+  constructor(
+    public dialogRef: MatDialogRef<SettingsConfirmationDialogComponent>,
+    @Inject(MAT_DIALOG_DATA) public data: any
+  ) {
+
+  }
+}
+
+
diff --git a/services/self-service/src/main/resources/webapp/src/app/administration/administration.module.ts b/services/self-service/src/main/resources/webapp/src/app/administration/configuration/index.ts
similarity index 53%
copy from services/self-service/src/main/resources/webapp/src/app/administration/administration.module.ts
copy to services/self-service/src/main/resources/webapp/src/app/administration/configuration/index.ts
index e535bda..d38f2c5 100644
--- a/services/self-service/src/main/resources/webapp/src/app/administration/administration.module.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/administration/configuration/index.ts
@@ -19,14 +19,25 @@
 
 import { NgModule } from '@angular/core';
 import { CommonModule } from '@angular/common';
-
-import { ManagenementModule } from './management';
-import { ProjectModule } from './project';
-import { RolesModule } from './roles';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { MaterialModule } from '../../shared/material.module';
+import { FormControlsModule } from '../../shared/form-controls';
+import {InformMessageModule} from '../../shared/inform-message';
+import {ConfigurationComponent, SettingsConfirmationDialogComponent} from './configuration.component';
+import {AceEditorModule} from 'ng2-ace-editor';
 
 @NgModule({
-  imports: [CommonModule, ManagenementModule, ProjectModule, RolesModule],
-  declarations: [],
-  exports: [ManagenementModule, ProjectModule, RolesModule]
+  imports: [
+    CommonModule,
+    FormsModule,
+    ReactiveFormsModule,
+    MaterialModule,
+    FormControlsModule,
+    InformMessageModule,
+    AceEditorModule
+  ],
+  declarations: [ConfigurationComponent, SettingsConfirmationDialogComponent],
+  entryComponents: [SettingsConfirmationDialogComponent],
+  exports: [ConfigurationComponent]
 })
-export class AdministrationModule { }
+export class ConfigurationModule { }
diff --git a/services/self-service/src/main/resources/webapp/src/app/app.module.ts b/services/self-service/src/main/resources/webapp/src/app/app.module.ts
index 9559cf1..e6e887c 100644
--- a/services/self-service/src/main/resources/webapp/src/app/app.module.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/app.module.ts
@@ -41,6 +41,7 @@ import { CoreModule } from './core/core.module';
 import { SwaggerAPIModule } from './swagger';
 import {ReportsModule} from './reports/reports.module';
 import {LocalizationService} from './core/services/localization.service';
+import {AceEditorModule} from 'ng2-ace-editor';
 
 LocalizationService.registerCulture(window.navigator.language);
 
@@ -66,7 +67,8 @@ LocalizationService.registerCulture(window.navigator.language);
     RouterModule,
     AppRoutingModule,
     CoreModule.forRoot(),
-    ToastrModule.forRoot({ timeOut: 10000 })
+    ToastrModule.forRoot({ timeOut: 10000 }),
+    AceEditorModule
   ],
   providers: [{
     provide: LocationStrategy,
diff --git a/services/self-service/src/main/resources/webapp/src/app/app.routing.module.ts b/services/self-service/src/main/resources/webapp/src/app/app.routing.module.ts
index 35c9df7..fe28bf8 100644
--- a/services/self-service/src/main/resources/webapp/src/app/app.routing.module.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/app.routing.module.ts
@@ -34,6 +34,7 @@ import { RolesComponent } from './administration/roles/roles.component';
 import { SwaggerComponent } from './swagger/swagger.component';
 import { AuthorizationGuard, CheckParamsGuard, CloudProviderGuard, AdminGuard, AuditGuard } from './core/services';
 import {AuditComponent} from './reports/audit/audit.component';
+import {ConfigurationComponent} from './administration/configuration/configuration.component';
 
 const routes: Routes = [{
   path: 'login',
@@ -68,6 +69,11 @@ const routes: Routes = [{
       component: ManagementComponent,
       canActivate: [AuthorizationGuard, AdminGuard]
     }, {
+      path: 'configuration',
+      component: ConfigurationComponent,
+      canActivate: [AuthorizationGuard, AdminGuard]
+    },
+    {
       path: 'swagger',
       component: SwaggerComponent,
       canActivate: [AuthorizationGuard]
diff --git a/services/self-service/src/main/resources/webapp/src/app/core/core.module.ts b/services/self-service/src/main/resources/webapp/src/app/core/core.module.ts
index 4b1c0f9..9b97b1f 100644
--- a/services/self-service/src/main/resources/webapp/src/app/core/core.module.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/core/core.module.ts
@@ -49,6 +49,7 @@ import { ErrorInterceptor } from './interceptors/error.interceptor';
 
 import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
 import {AuditGuard} from './services';
+import {ConfigurationService} from './services/configutration.service';
 
 @NgModule({
   imports: [CommonModule],
@@ -85,7 +86,7 @@ export class CoreModule {
         ProjectService,
         EndpointService,
         UserAccessKeyService,
-
+        ConfigurationService,
         { provide: MatDialogRef, useValue: {} },
         { provide: MAT_DIALOG_DATA, useValue: [] },
         {
diff --git a/services/self-service/src/main/resources/webapp/src/app/core/services/configutration.service.ts b/services/self-service/src/main/resources/webapp/src/app/core/services/configutration.service.ts
new file mode 100644
index 0000000..8cc8406
--- /dev/null
+++ b/services/self-service/src/main/resources/webapp/src/app/core/services/configutration.service.ts
@@ -0,0 +1,222 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { Injectable } from '@angular/core';
+import {Observable, of} from 'rxjs';
+import { map, catchError } from 'rxjs/operators';
+
+import { ApplicationServiceFacade } from './applicationServiceFacade.service';
+import { ErrorUtils } from '../util';
+
+@Injectable()
+export class ConfigurationService {
+  constructor(private applicationServiceFacade: ApplicationServiceFacade) { }
+
+  public getSelvServiceSettings(): Observable<{}> {
+    return of(`# *****************************************************************************
+    #
+    #  Licensed to the Apache Software Foundation (ASF) under one
+    #  or more contributor license agreements.  See the NOTICE file
+    #  distributed with this work for additional information
+    #  regarding copyright ownership.  The ASF licenses this file
+    #  to you under the Apache License, Version 2.0 (the
+    #  "License"); you may not use this file except in compliance
+    #  with the License.  You may obtain a copy of the License at
+    #
+    #  http://www.apache.org/licenses/LICENSE-2.0
+    #
+    #  Unless required by applicable law or agreed to in writing,
+      #  software distributed under the License is distributed on an
+    #  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    #  KIND, either express or implied.  See the License for the
+      #  specific language governing permissions and limitations
+    #  under the License.
+      #
+    # ******************************************************************************
+
+    <#include "/infrastructure-provisioning/src/ssn/templates/ssn.yml">
+
+    # Minimum and maximum number of slave EMR instances than could be created
+    minEmrInstanceCount: 2
+    maxEmrInstanceCount: 14
+    # Minimum and maximum percentage cost for slave EMR spot instances biding
+    minEmrSpotInstanceBidPct: 20
+    maxEmrSpotInstanceBidPct: 90
+
+    # Maximum length for gcp user name (due to gcp restrictions)
+    maxUserNameLength: 10
+    # Minimum and maximum number of slave Dataproc instances that could be created
+    minInstanceCount: 3
+    maxInstanceCount: 15
+    minDataprocPreemptibleCount: 0
+    gcpOuauth2AuthenticationEnabled: false
+
+    # Boundaries for Spark cluster creation
+    minSparkInstanceCount: 2
+    maxSparkInstanceCount: 14
+
+    # Timeout for check the status of environment via provisioning service
+    checkEnvStatusTimeout: 5m
+
+    # Restrict access to DataLab features using roles policy
+    rolePolicyEnabled: true
+    # Default access to DataLab features using roles policy
+    roleDefaultAccess: false
+
+    # Set to true to enable the scheduler of billing report.
+      billingSchedulerEnabled: true
+    billingPort: 8088
+    # Set to true to enable audit
+    auditEnabled: true
+    # Name of configuration file for billing report.
+    <#if DEV_MODE == "true">
+    billingConfFile: \${sys['user.dir']}/../billing/billing.yml
+    <#else>
+    billingConfFile: \${DATALAB_CONF_DIR}/billing.yml
+    </#if>
+
+    ssnInstanceSize: <SSN_INSTANCE_SIZE>
+
+      serviceBaseName: SERVICE_BASE_NAME
+    os: OPERATION_SYSTEM
+    server:
+      requestLog:
+        appenders:
+          - type: file
+    currentLogFilename: \${LOG_ROOT_DIR}/ssn/request-selfservice.log
+    archive: true
+    archivedLogFilenamePattern: \${LOG_ROOT_DIR}/ssn/request-selfservice-%d{yyyy-MM-dd}.log.gz
+    archivedFileCount: 10
+    rootPath: "/api"
+    applicationConnectors:
+      #    - type: http
+    #      port: 8080
+    - type: https
+    port: 8443
+    certAlias: ssn
+    validateCerts: false
+    keyStorePath: \${KEY_STORE_PATH}
+      keyStorePassword: \${KEY_STORE_PASSWORD}
+        trustStorePath: \${TRUST_STORE_PATH}
+          trustStorePassword: \${TRUST_STORE_PASSWORD}
+            adminConnectors:
+              #    - type: http
+    #      port: 8081
+    - type: https
+    port: 8444
+    certAlias: ssn
+    validateCerts: false
+    keyStorePath: \${KEY_STORE_PATH}
+      keyStorePassword: \${KEY_STORE_PASSWORD}
+        trustStorePath: \${TRUST_STORE_PATH}
+          trustStorePassword: \${TRUST_STORE_PASSWORD}
+
+            mongoMigrationEnabled: false
+
+    logging:
+      level: INFO
+    loggers:
+      com.epam: INFO
+    org.apache.guacamole: DEBUG
+    com.novemberain: ERROR
+    appenders:
+      <#if DEV_MODE == "true">
+    - type: console
+    </#if>
+    - type: file
+    currentLogFilename: \${LOG_ROOT_DIR}/ssn/selfservice.log
+    archive: true
+    archivedLogFilenamePattern: \${LOG_ROOT_DIR}/ssn/selfservice-%d{yyyy-MM-dd}.log.gz
+    archivedFileCount: 10
+
+    mavenSearchService:
+      protocol: http
+    host: search.maven.org
+    port: 80
+    jerseyClient:
+      timeout: 5s
+    connectionTimeout: 5s
+
+    schedulers:
+      inactivity:
+        enabled: false
+    cron: "0 0 0/2 ? * * *"
+    checkInfrastructureStatusScheduler:
+      enabled: true
+    cron: "0 0/15 * ? * *"
+    startComputationalScheduler:
+      enabled: true
+    cron: "*/20 * * ? * * *"
+    stopComputationalScheduler:
+      enabled: true
+    cron: "*/20 * * ? * * *"
+    startExploratoryScheduler:
+      enabled: true
+    cron: "*/20 * * ? * * *"
+    stopExploratoryScheduler:
+      enabled: true
+    cron: "*/20 * * ? * * *"
+    terminateComputationalScheduler:
+      enabled: true
+    cron: "*/20 * * ? * * *"
+    checkQuoteScheduler:
+      enabled: true
+    cron: "0 2/15 * ? * *"
+    checkUserQuoteScheduler:
+      enabled: false
+    cron: "0 0 * ? * * *"
+    checkProjectQuoteScheduler:
+      enabled: true
+    cron: "0 4/15 * ? * *"
+    checkEndpointStatusScheduler:
+      enabled: true
+    cron: "0 6/15 * ? * *"
+    billingScheduler:
+      enabled: true
+    cron: "0 0/15 * ? * *"
+
+
+    guacamole:
+      connectionProtocol: ssh
+    serverPort: 4822
+    port: 22
+    username: datalab-user
+
+    keycloakConfiguration:
+      redirectUri: http://localhost:4200/
+        realm: DLAB_bhliva
+    bearer-only: true
+    auth-server-url: http://52.11.45.11:8080/auth
+      ssl-required: none
+    register-node-at-startup: true
+    register-node-period: 600
+    resource: sss
+    credentials:
+      secret: cf5a484b-039b-4161-8707-ad65c0f25962
+
+    jerseyClient:
+      minThreads: 1
+    maxThreads: 128
+    workQueueSize: 8
+    gzipEnabled: true
+    gzipEnabledForRequests: false
+    chunkedEncodingEnabled: true
+    `)
+  }
+}
diff --git a/services/self-service/src/main/resources/webapp/src/app/shared/form-controls/multi-level-select-dropdown/multi-level-select-dropdown.component.scss b/services/self-service/src/main/resources/webapp/src/app/shared/form-controls/multi-level-select-dropdown/multi-level-select-dropdown.component.scss
index aab2b07..27e1e90 100644
--- a/services/self-service/src/main/resources/webapp/src/app/shared/form-controls/multi-level-select-dropdown/multi-level-select-dropdown.component.scss
+++ b/services/self-service/src/main/resources/webapp/src/app/shared/form-controls/multi-level-select-dropdown/multi-level-select-dropdown.component.scss
@@ -120,9 +120,11 @@
       padding: 0;
       margin: 0;
     }
+
     .role-item{
       padding-left: 30px;
     }
+
     .role-cloud-item{
       padding-left: 60px;
     }
diff --git a/services/self-service/src/main/resources/webapp/src/app/shared/navbar/navbar.component.html b/services/self-service/src/main/resources/webapp/src/app/shared/navbar/navbar.component.html
index 9e73f34..b662258 100644
--- a/services/self-service/src/main/resources/webapp/src/app/shared/navbar/navbar.component.html
+++ b/services/self-service/src/main/resources/webapp/src/app/shared/navbar/navbar.component.html
@@ -94,6 +94,12 @@
               <span *ngIf="isExpanded; else env">Environment Management</span>
               <ng-template #env><i class="material-icons">settings</i></ng-template>
             </a>
+            <a class="sub-nav-item" [style.margin-left.px]="isExpanded ? '30' : '0'"
+               [routerLink]="['/configuration']" [routerLinkActive]="['active']"
+               [routerLinkActiveOptions]="{exact:true}">
+              <span *ngIf="isExpanded; else env">Configuration</span>
+              <ng-template #env><i class="material-icons">settings</i></ng-template>
+            </a>
           </a>
           <a class="nav-item has-children" *ngIf="healthStatus?.billingEnabled || healthStatus?.auditEnabled">
             <span *ngIf="isExpanded">Reports</span>
diff --git a/services/self-service/src/main/resources/webapp/src/assets/styles/_general.scss b/services/self-service/src/main/resources/webapp/src/assets/styles/_general.scss
index 9234c6a..e27d33f 100644
--- a/services/self-service/src/main/resources/webapp/src/assets/styles/_general.scss
+++ b/services/self-service/src/main/resources/webapp/src/assets/styles/_general.scss
@@ -39,6 +39,7 @@ body.modal-open {
 .pr-3{padding-right: 3px}
 
 .pb-50 {padding-bottom: 50px;}
+.pb-25 {padding-bottom: 25px;}
 .pb-10 {padding-bottom: 10px;}
 
 .txt-r {text-align: right }
diff --git a/services/self-service/src/main/resources/webapp/src/assets/styles/_theme.scss b/services/self-service/src/main/resources/webapp/src/assets/styles/_theme.scss
index ec8fee0..6f9086c 100644
--- a/services/self-service/src/main/resources/webapp/src/assets/styles/_theme.scss
+++ b/services/self-service/src/main/resources/webapp/src/assets/styles/_theme.scss
@@ -1021,6 +1021,28 @@ mat-progress-bar {
     }
   }
 }
+.configuration-wrapper{
+  box-shadow: 0px 3px 5px -1px rgba(0, 0, 0, 0.2), 0px 6px 10px 0px rgba(0, 0, 0, 0.14), 0px 1px 18px 0px rgba(0, 0, 0, 0.12);
+  .mat-tab-header{
+    margin: 0 30px;
+  }
+  .mat-tab-label{
+    .mat-tab-label-content{
+      font-family: "Open Sans", sans-serif;
+      font-weight: 600;
+      font-size: 13px;
+    }
+  }
+
+  .ace-monokai{
+    height: calc(100%);
+  }
+
+  .ace_scrollbar{
+
+  }
+}
+
 
 
 
diff --git a/services/self-service/src/main/resources/webapp/src/styles.scss b/services/self-service/src/main/resources/webapp/src/styles.scss
index c09ccd9..3c5d948 100644
--- a/services/self-service/src/main/resources/webapp/src/styles.scss
+++ b/services/self-service/src/main/resources/webapp/src/styles.scss
@@ -382,12 +382,13 @@ input[type='number'] {
     text-align: center;
   }
 }
-#scrolling, .scrolling{
+#scrolling, .scrolling, ace_scrollbar{
   scrollbar-width: thin;
 }
 
 #scrolling::-webkit-scrollbar,
 .scrolling::-webkit-scrollbar,
+.ace_scrollbar::-webkit-scrollbar,
 .list-selected mat-chip-list .mat-chip-list-wrapper::-webkit-scrollbar {
   width: 5px;
   height: 5px;
@@ -395,6 +396,7 @@ input[type='number'] {
 
 #scrolling::-webkit-scrollbar-track,
 .scrolling::-webkit-scrollbar-track,
+.ace_scrollbar::-webkit-scrollbar-track,
 .list-selected mat-chip-list .mat-chip-list-wrapper::-webkit-scrollbar-track {
   box-shadow: none;
   -webkit-box-shadow: none;
@@ -403,6 +405,7 @@ input[type='number'] {
 
 #scrolling::-webkit-scrollbar-thumb,
 .scrolling::-webkit-scrollbar-thumb,
+.ace_scrollbar::-webkit-scrollbar-thumb,
 .list-selected mat-chip-list .mat-chip-list-wrapper::-webkit-scrollbar-thumb {
   background-color: #f6fafe;
   background-color: rgba(0, 0, 0, 0.4);


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@datalab.apache.org
For additional commands, e-mail: commits-help@datalab.apache.org