You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@dlab.apache.org by dg...@apache.org on 2020/06/15 07:04:08 UTC

[incubator-dlab] branch develop updated: [DLAB-1861]: Added checkboxes to environment management (#785)

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

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


The following commit(s) were added to refs/heads/develop by this push:
     new 729bef9  [DLAB-1861]: Added checkboxes to environment management (#785)
729bef9 is described below

commit 729bef999c9c20d9802e40650ae65cbfb6e32940
Author: Dmytro Gnatyshyn <42...@users.noreply.github.com>
AuthorDate: Mon Jun 15 10:04:01 2020 +0300

    [DLAB-1861]: Added checkboxes to environment management (#785)
    
    [DLAB-1861]: Added checkboxes to environment management
---
 .../management-grid/management-grid.component.html |  37 +++++-
 .../management-grid/management-grid.component.scss |  11 +-
 .../management-grid/management-grid.component.ts   | 147 +++++++++++++++------
 .../management/management.component.html           |  35 ++++-
 .../management/management.component.ts             | 103 ++++++++++++++-
 .../resources/webapp/src/assets/styles/_theme.scss |  94 ++++++++++++-
 6 files changed, 379 insertions(+), 48 deletions(-)

diff --git a/services/self-service/src/main/resources/webapp/src/app/administration/management/management-grid/management-grid.component.html b/services/self-service/src/main/resources/webapp/src/app/administration/management/management-grid/management-grid.component.html
index 8b7e459..88b005c 100644
--- a/services/self-service/src/main/resources/webapp/src/app/administration/management/management-grid/management-grid.component.html
+++ b/services/self-service/src/main/resources/webapp/src/app/administration/management/management-grid/management-grid.component.html
@@ -19,6 +19,25 @@
 
 <div class="ani">
   <table mat-table [dataSource]="allFilteredEnvironmentData" class="data-grid management mat-elevation-z6">
+    <ng-container matColumnDef="checkbox">
+      <th mat-header-cell *matHeaderCellDef class="checkbox label-header">
+        <div  class="empty-checkbox header-checkbox" [ngClass]="{'checked': selected?.length === allActiveNotebooks?.length}" (click)="toggleSelectionAll();$event.stopPropagation()" >
+          <span class="checked-checkbox" *ngIf="selected?.length === allActiveNotebooks?.length"></span>
+        </div>
+        <button mat-icon-button aria-label="More" class="ar checkbox-border" (click)="toggleFilterRow()">
+          <i class="material-icons">
+<!--            <span *ngIf="filtering && filterForm.users.length > 0 && !collapsedFilterRow">filter_list</span>-->
+            <span>more_vert</span>
+          </i>
+        </button>
+      </th>
+      <td mat-cell *matCellDef="let element">
+        <div *ngIf="element.type !== 'edge node' && (element.status==='running' || element.status==='stopped')" class="empty-checkbox" [ngClass]="{'checked': element.isSelected}" (click)="toggleActionForAll(element);$event.stopPropagation()" >
+          <span class="checked-checkbox" *ngIf="element.isSelected"></span>
+        </div>
+      </td>
+    </ng-container>
+
     <ng-container matColumnDef="user">
       <th mat-header-cell *matHeaderCellDef class="user label-header">
         <span class="label">User</span>
@@ -137,10 +156,14 @@
         <span class="label"> Actions </span>
       </th>
       <td mat-cell *matCellDef="let element" class="settings actions-col">
-        <span #settings class="actions" (click)="actions.toggle($event, settings)" *ngIf="element.type !== 'edge node'"
-          [ngClass]="{
-            'disabled' : isActiveResources(element),
-            'disabled' : element.status !== 'running' && element.status !== 'stopped' && element.status !== 'stopping' && element.status !== 'failed' }"></span>
+        <span [ngClass]="{'not-allow' : selected?.length}">
+          <span #settings class="actions" (click)="actions.toggle($event, settings)" *ngIf="element.type !== 'edge node'"
+            [ngClass]="{
+              'disabled' : isActiveResources(element),
+              'disabled' : (element.status !== 'running' && element.status !== 'stopped' && element.status !== 'stopping' && element.status !== 'failed') || selected?.length}">
+
+          </span>
+        </span>
         <bubble-up #actions class="list-menu" position="bottom-left" alternative="top-left">
           <ul class="list-unstyled">
             <li
@@ -176,6 +199,12 @@
 
 
     <!-- FILTERING -->
+    <ng-container matColumnDef="checkbox-filter" sticky>
+      <th mat-header-cell *matHeaderCellDef class="filter-row-item">
+
+      </th>
+    </ng-container>
+
     <ng-container matColumnDef="user-filter" sticky>
       <th mat-header-cell *matHeaderCellDef class="filter-row-item">
         <multi-select-dropdown (selectionChange)="onUpdate($event)" [type]="'users'" [items]="filterConfiguration.users"
diff --git a/services/self-service/src/main/resources/webapp/src/app/administration/management/management-grid/management-grid.component.scss b/services/self-service/src/main/resources/webapp/src/app/administration/management/management-grid/management-grid.component.scss
index 5d40c3e..99fde37 100644
--- a/services/self-service/src/main/resources/webapp/src/app/administration/management/management-grid/management-grid.component.scss
+++ b/services/self-service/src/main/resources/webapp/src/app/administration/management/management-grid/management-grid.component.scss
@@ -19,7 +19,16 @@
 
 .data-grid {
   &.management {
+    .mat-column-checkbox{
+      padding-left: 10px;
+      padding-right: 10px;
+      &.label-header{
+        width: 65px;
+        display: flex;
+        align-items: center;
+      }
 
+    }
     .user{
       width: 15%;
     }
@@ -45,7 +54,7 @@
     }
 
     .resources {
-      width: 22%;
+      width: 21%;
       padding: 5px;
     }
 
diff --git a/services/self-service/src/main/resources/webapp/src/app/administration/management/management-grid/management-grid.component.ts b/services/self-service/src/main/resources/webapp/src/app/administration/management/management-grid/management-grid.component.ts
index 796859a..38e4590 100644
--- a/services/self-service/src/main/resources/webapp/src/app/administration/management/management-grid/management-grid.component.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/administration/management/management-grid/management-grid.component.ts
@@ -60,9 +60,12 @@ export class ManagementGridComponent implements OnInit {
   @Input() currentUser: string = '';
   @Output() refreshGrid: EventEmitter<{}> = new EventEmitter();
   @Output() actionToggle: EventEmitter<ManageAction> = new EventEmitter();
+  @Output() emitSelectedList: EventEmitter<ManageAction> = new EventEmitter();
 
-  displayedColumns: string[] = ['user', 'type', 'project', 'shape', 'status', 'resources', 'actions'];
-  displayedFilterColumns: string[] = ['user-filter', 'type-filter', 'project-filter', 'shape-filter', 'status-filter', 'resource-filter', 'actions-filter'];
+  displayedColumns: string[] = [ 'checkbox', 'user', 'type', 'project', 'shape', 'status', 'resources', 'actions'];
+  displayedFilterColumns: string[] = ['checkbox-filter', 'user-filter', 'type-filter', 'project-filter', 'shape-filter', 'status-filter', 'resource-filter', 'actions-filter'];
+  private selected;
+  private allActiveNotebooks: any;
 
   constructor(
     private healthStatusService: HealthStatusService,
@@ -113,7 +116,7 @@ export class ManagementGridComponent implements OnInit {
     let filteredData = this.getEnvironmentDataCopy();
 
     const containsStatus = (list, selectedItems) => {
-      if (list){
+      if (list) {
         return list.filter((item: any) => { if (selectedItems.indexOf(item.status) !== -1) return item; });
       }
     };
@@ -123,8 +126,8 @@ export class ManagementGridComponent implements OnInit {
       filteredData = filteredData.filter(item => {
 
         const isUser = config.users.length > 0 ? (config.users.indexOf(item.user) !== -1) : true;
-        const isTypeName = item.name ?
-          item.name.toLowerCase().indexOf(config.type.toLowerCase()) !== -1 : item.type.toLowerCase().indexOf(config.type.toLowerCase()) !== -1;
+        const isTypeName = item.name ? item.name.toLowerCase()
+          .indexOf(config.type.toLowerCase()) !== -1 : item.type.toLowerCase().indexOf(config.type.toLowerCase()) !== -1;
         const isStatus = config.statuses.length > 0 ? (config.statuses.indexOf(item.status) !== -1) : (config.type !== 'active');
         const isShape = config.shapes.length > 0 ? (config.shapes.indexOf(item.shape) !== -1) : true;
         const isProject = config.projects.length > 0 ? (config.projects.indexOf(item.project) !== -1) : true;
@@ -144,6 +147,7 @@ export class ManagementGridComponent implements OnInit {
       });
     }
     this.allFilteredEnvironmentData = filteredData;
+    this.allActiveNotebooks = this.allFilteredEnvironmentData.filter(v => v.name && (v.status === 'running' || v.status === 'stopped'));
   }
 
   getEnvironmentDataCopy() {
@@ -151,38 +155,7 @@ export class ManagementGridComponent implements OnInit {
   }
 
   toggleResourceAction(environment: any, action: string, resource?): void {
-    if (resource) {
-      const resource_name = resource ? resource.computational_name : environment.name;
-      this.dialog.open(ReconfirmationDialogComponent, {
-        data: { action, resource_name, user: environment.user },
-        width: '550px', panelClass: 'error-modalbox'
-      }).afterClosed().subscribe(result => {
-        result && this.actionToggle.emit({ action, environment, resource });
-      });
-    } else {
-      const type = (environment.type.toLowerCase() === 'edge node')
-        ? ConfirmationDialogType.StopEdgeNode : ConfirmationDialogType.StopExploratory;
-
-      if (action === 'stop') {
-        this.dialog.open(ConfirmationDialogComponent, {
-          data: { notebook: environment, type: type, manageAction: true }, panelClass: 'modal-md'
-        }).afterClosed().subscribe(() => this.buildGrid());
-      } else if (action === 'terminate') {
-        this.dialog.open(ConfirmationDialogComponent, {
-          data: { notebook: environment, type: ConfirmationDialogType.TerminateExploratory, manageAction: true }, panelClass: 'modal-md'
-        }).afterClosed().subscribe(() => this.buildGrid());
-      } else if (action === 'run') {
-        this.healthStatusService.runEdgeNode().subscribe(() => {
-          this.buildGrid();
-          this.toastr.success('Edge node is starting!', 'Processing!');
-        }, () => this.toastr.error('Edge Node running failed!', 'Oops!'));
-      } else if (action === 'recreate') {
-        this.healthStatusService.recreateEdgeNode().subscribe(() => {
-          this.buildGrid();
-          this.toastr.success('Edge Node recreation is processing!', 'Processing!');
-        }, () => this.toastr.error('Edge Node recreation failed!', 'Oops!'));
-      }
-    }
+    this.actionToggle.emit({ environment, action, resource });
   }
 
   isResourcesInProgress(notebook) {
@@ -240,6 +213,22 @@ export class ManagementGridComponent implements OnInit {
     })
       .afterClosed().subscribe(() => {});
   }
+
+  toggleActionForAll(element) {
+    element.isSelected = !element.isSelected;
+    this.selected = this.allFilteredEnvironmentData.filter(item => !!item.isSelected);
+    this.emitSelectedList.emit(this.selected);
+  }
+
+  toggleSelectionAll() {
+    if (this.selected && this.selected.length === this.allActiveNotebooks.length) {
+      this.allActiveNotebooks.forEach(notebook => notebook.isSelected = false);
+    } else {
+      this.allActiveNotebooks.forEach(notebook => notebook.isSelected = true);
+    }
+    this.selected = this.allFilteredEnvironmentData.filter(item => !!item.isSelected);
+    this.emitSelectedList.emit(this.selected);
+  }
 }
 
 
@@ -251,23 +240,101 @@ export class ManagementGridComponent implements OnInit {
     <button type="button" class="close" (click)="dialogRef.close()">&times;</button>
   </div>
   <div mat-dialog-content class="content">
+    <div *ngIf="data.type === 'cluster'">
       <p>Resource <span class="strong"> {{ data.resource_name }}</span> of user <span class="strong"> {{ data.user }} </span> will be
       <span *ngIf="data.action === 'terminate'"> decommissioned.</span>
       <span *ngIf="data.action === 'stop'">stopped.</span>
     </p>
-    <p class="m-top-20"><span class="strong">Do you want to proceed?</span></p>
+    </div>
+    <div class="resource-list" *ngIf="data.type === 'notebook'">
+      <div class="resource-list-header">
+        <div class="resource-name">Notebook</div>
+        <div class="clusters-list">
+          <div class="clusters-list-item">
+            <div class="cluster">Cluster</div>
+            <div class="status">Further status</div>
+          </div>
+        </div>
+
+      </div>
+      <div class="scrolling-content resource-heigth">
+        <div class="resource-list-row sans node" *ngFor="let notebook of notebooks">
+          <div class="resource-name ellipsis">
+            {{notebook.name}}
+          </div>
+
+          <div class="clusters-list">
+            <div class="clusters-list-item">
+              <div class="cluster"></div>
+              <div class="status"
+                   [ngClass]="{
+                   'stopped': data.action==='stop', 'terminated': data.action === 'terminate'
+                    }"
+              >
+                {{data.action  === 'stop' ? 'Stopped' : 'Terminated'}}
+              </div>
+            </div>
+            <div class="clusters-list-item" *ngFor="let cluster of notebook?.resources">
+              <div class="cluster">{{cluster.computational_name}}</div>
+              <div class="status" [ngClass]="{
+              'stopped': (data.action==='stop' && cluster.image==='docker.dlab-dataengine'), 'terminated': data.action === 'terminate' || (data.action==='stop' && cluster.image!=='docker.dlab-dataengine')
+              }">{{data.action  === 'stop' && cluster.image === "docker.dlab-dataengine" ? 'Stopped' : 'Terminated'}}</div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
   </div>
-  <div class="text-center">
+  <div class="text-center ">
+    <p class="strong">Do you want to proceed?</p>
+  </div>
+  <div class="text-center m-top-20">
     <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>
   `,
   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; }
+      .plur { font-style: normal; }
+      .scrolling-content{overflow-y: auto; max-height: 200px; }
+      .cluster { width: 50%; text-align: left; color: #577289;}
+      .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 ReconfirmationDialogComponent {
+  private notebooks;
   constructor(
     public dialogRef: MatDialogRef<ReconfirmationDialogComponent>,
     @Inject(MAT_DIALOG_DATA) public data: any
-  ) { }
+  ) {
+    if (data.notebooks && data.notebooks.length) {
+      this.notebooks = JSON.parse(JSON.stringify(data.notebooks));
+      this.notebooks = this.notebooks.map(notebook => {
+        notebook.resources = notebook.resources.filter(res => res.status !== 'terminated' && res.status.slice(0, 4) !== data.action);
+        return notebook;
+      });
+    }
+  }
 }
diff --git a/services/self-service/src/main/resources/webapp/src/app/administration/management/management.component.html b/services/self-service/src/main/resources/webapp/src/app/administration/management/management.component.html
index 4c4bdae..eeb2d9b 100644
--- a/services/self-service/src/main/resources/webapp/src/app/administration/management/management.component.html
+++ b/services/self-service/src/main/resources/webapp/src/app/administration/management/management.component.html
@@ -20,6 +20,39 @@
 <div class="base-retreat">
   <div class="sub-nav">
     <div *ngIf="healthStatus?.admin" class="admin-group">
+      <div class="action-select-wrapper admin-group" >
+        <span class="action-button-wrapper">
+          <button
+            type="button" class="butt actions-btn"
+            mat-raised-button
+            [disabled]="!selected.length"
+            (click)="toogleActions();$event.stopPropagation()"
+          >
+            Actions
+            <i class="material-icons" >{{ !isActionsOpen ?  'expand_more' : 'expand_less' }}</i>
+          </button>
+          </span>
+        <div class="action-menu" *ngIf="isActionsOpen">
+          <span>
+          <button
+           type="button" class="butt action-menu-item"
+            [ngClass]="{'disabled': selectedRunning.length === 0  || selectedStopped.length !== 0 }"
+            mat-raised-button
+            [disabled]="selectedRunning.length === 0  || selectedStopped.length !== 0"
+            (click)="resourseAction('stop');$event.stopPropagation()"
+          >
+            Stop
+          </button>
+            </span>
+          <button
+            type="button" class="butt action-menu-item"
+            mat-raised-button
+            (click)="resourseAction('terminate');$event.stopPropagation()"
+          >
+            Terminate
+          </button>
+        </div>
+      </div>
       <button mat-raised-button class="butt ssn" (click)="showEndpointsDialog()">
         <i class="material-icons"></i>Endpoints
       </button>
@@ -40,6 +73,6 @@
   <mat-divider></mat-divider>
   <management-grid [currentUser]="user.toLowerCase()" [isAdmin]="healthStatus?.admin"
     [environmentsHealthStatuses]="healthStatus?.list_resources" (refreshGrid)="buildGrid()"
-    (actionToggle)="manageEnvironmentAction($event)">
+    (actionToggle)="toggleResourceAction($event)" (emitSelectedList)="selectedList($event)">
   </management-grid>
 </div>
diff --git a/services/self-service/src/main/resources/webapp/src/app/administration/management/management.component.ts b/services/self-service/src/main/resources/webapp/src/app/administration/management/management.component.ts
index 87e554d..3062a07 100644
--- a/services/self-service/src/main/resources/webapp/src/app/administration/management/management.component.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/administration/management/management.component.ts
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-import { Component, OnInit } from '@angular/core';
+import {Component, OnInit, ViewChild} from '@angular/core';
 import { MatDialog } from '@angular/material/dialog';
 import { ToastrService } from 'ngx-toastr';
 
@@ -39,6 +39,9 @@ import { ExploratoryModel } from '../../resources/resources-grid/resources-grid.
 
 import { EnvironmentsDataService } from './management-data.service';
 import { ProjectService } from '../../core/services';
+import {ConfirmationDialogComponent, ConfirmationDialogType} from '../../shared/modal-dialog/confirmation-dialog';
+import {ManagementGridComponent, ReconfirmationDialogComponent} from './management-grid/management-grid.component';
+import {FolderTreeComponent} from '../../resources/bucket-browser/folder-tree/folder-tree.component';
 
 @Component({
   selector: 'environments-management',
@@ -50,6 +53,13 @@ export class ManagementComponent implements OnInit {
   public healthStatus: GeneralEnvironmentStatus;
   // public anyEnvInProgress: boolean = false;
   public dialogRef: any;
+  private selected: any[] = [];
+  public isActionsOpen: boolean = false;
+  public selectedRunning: any[];
+  public selectedStopped: any[];
+
+  @ViewChild(ManagementGridComponent, {static: true}) managementGrid;
+
 
   constructor(
     public toastr: ToastrService,
@@ -171,4 +181,95 @@ export class ManagementComponent implements OnInit {
   private getTotalBudgetData() {
     return this.healthStatusService.getTotalBudgetData();
   }
+
+  public selectedList($event) {
+    this.selected = $event;
+    if (this.selected.length === 0) {
+      this.isActionsOpen = false;
+    }
+
+    this.selectedRunning = this.selected.filter(item => item.status === 'running');
+    this.selectedStopped = this.selected.filter(item => item.status === 'stopped');
+  }
+
+  public toogleActions() {
+    this.isActionsOpen = !this.isActionsOpen;
+  }
+
+  toggleResourceAction($event): void {
+    const {environment, action, resource} = $event;
+    if (resource) {
+      const resource_name = resource ? resource.computational_name : environment.name;
+      this.dialog.open(ReconfirmationDialogComponent, {
+        data: { action, resource_name, user: environment.user, type: 'cluster'},
+        width: '550px', panelClass: 'error-modalbox'
+      }).afterClosed().subscribe(result => {
+        result && this.manageEnvironmentAction({ action, environment, resource });
+      });
+    } else {
+      const notebooks = this.selected.length ? this.selected : [environment];
+      if (action === 'stop') {
+        this.dialog.open(ReconfirmationDialogComponent, {
+          data: { notebooks: notebooks, type: 'notebook', action },
+          width: '550px', panelClass: 'error-modalbox'
+        }).afterClosed().subscribe((res) => {
+          if (res) {
+            notebooks.forEach((env) => {
+              this.manageEnvironmentsService.environmentManagement(env.user, 'stop', env.project, env.name)
+                .subscribe(
+                  response => {
+                    console.log(response);
+                    this.buildGrid();
+                  },
+                  error => console.log(error)
+                );
+            });
+          }
+          this.clearSelection();
+        });
+      } else if (action === 'terminate') {
+        this.dialog.open(ReconfirmationDialogComponent, {
+          data: { notebooks: notebooks, type: 'notebook', action }, width: '550px', panelClass: 'error-modalbox'
+        }).afterClosed().subscribe((res) => {
+          if (res) {
+            notebooks.forEach((env) => {
+              this.manageEnvironmentsService.environmentManagement(env.user, 'terminate', env.project, env.name)
+                .subscribe(
+                  response => {
+                    this.buildGrid();
+
+                  },
+                  error => console.log(error)
+                );
+            });
+          }
+          this.clearSelection();
+        });
+      // } else if (action === 'run') {
+      //   this.healthStatusService.runEdgeNode().subscribe(() => {
+      //     this.buildGrid();
+      //     this.toastr.success('Edge node is starting!', 'Processing!');
+      //   }, () => this.toastr.error('Edge Node running failed!', 'Oops!'));
+      // } else if (action === 'recreate') {
+      //   this.healthStatusService.recreateEdgeNode().subscribe(() => {
+      //     this.buildGrid();
+      //     this.toastr.success('Edge Node recreation is processing!', 'Processing!');
+      //   }, () => this.toastr.error('Edge Node recreation failed!', 'Oops!'));
+      }
+    }
+  }
+
+  private clearSelection() {
+    this.selected = [];
+    this.isActionsOpen = false;
+    if (this.managementGrid.selected && this.managementGrid.selected.length !== 0) {
+      this.managementGrid.selected.forEach(item => item.isSelected = false);
+      this.managementGrid.selected = [];
+    }
+  }
+
+
+  public resourseAction(action) {
+      this.toggleResourceAction({environment: this.selected, action: action});
+  }
 }
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 43e9c50..bf3529d 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
@@ -433,6 +433,98 @@ span.mat-slide-toggle-content {
   }
 }
 
+.empty-checkbox {
+  min-width: 16px;
+  width: 16px;
+  height: 16px;
+  border-radius: 2px;
+  border: 2px solid lightgrey;
+  margin-top: 2px;
+  position: relative;
+  cursor: pointer;
+  &.checked {
+    border-color: #35afd5;
+    background-color: #35afd5;
+  }
+  .checked-checkbox {
+    top: 0;
+    left: 4px;
+    width: 5px;
+    height: 10px;
+    border-bottom: 2px solid white;
+    border-right: 2px solid white;
+    position: absolute;
+    transform: rotate(45deg);
+  }
+}
+
+.action-select-wrapper{
+  position: relative;
+  display: block !important;
+
+  .action-button-wrapper{
+    position: relative;
+    width: 160px;
+  }
+
+  .mat-raised-button.butt{
+    margin-bottom: 0;
+    padding-left: 45px;
+    text-align: left;
+
+
+    &.actions-btn{
+      padding-right: 38px;
+
+
+      .material-icons{
+        transition: ease-in-out 1s;
+        font-size: 25px;
+        position: absolute;
+        top: 7px;
+        right: 30px;
+      }
+    }
+  }
+
+  .action-menu{
+    position: absolute;
+    text-align: center;
+    display: block;
+    background-color: #fff;
+
+    &-item.mat-raised-button.butt{
+      z-index: 101;
+      margin: 0;
+      box-shadow: 0 2px 1px -1px rgba(0,0,0,.2), 0 0px 0px 0 rgba(0,0,0,.14), 0 1px 5px 0 rgba(0,0,0,.12);
+      width: 160px;
+      padding: 0 20px;
+      border-radius: 0;
+      font-style: normal;
+      font-weight: 600;
+      font-size: 15px;
+      font-family: 'Open Sans', sans-serif;
+      color: #577289;
+      position: relative;
+      overflow: hidden;
+      line-height: 36px;
+      padding-left: 45px;
+      text-align: left;
+      &.action-menu-item{
+        &:hover{
+          color:  #00bcd4;
+          background-color: #fafafa;
+        }
+        &.disabled{
+          &:hover{
+            color: #577289;
+          }
+        }
+      }
+    }
+  }
+}
+
 mat-horizontal-stepper {
   .mat-step-header {
     .mat-step-icon {
@@ -561,7 +653,7 @@ mat-horizontal-stepper {
 
     .content {
       color: #718ba6;
-      padding: 20px 0;
+      padding: 20px 40px;
       font-size: 14px;
       font-weight: 400;
       text-align: center;


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