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/03/10 12:38:42 UTC
[incubator-dlab] branch develop updated: [DLAB-384]: Grouped roles
(#642)
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 e2aa6f9 [DLAB-384]: Grouped roles (#642)
e2aa6f9 is described below
commit e2aa6f9351ecc0c18ba5f9077831cacc1728be1b
Author: Dmytro Gnatyshyn <42...@users.noreply.github.com>
AuthorDate: Tue Mar 10 14:38:35 2020 +0200
[DLAB-384]: Grouped roles (#642)
[DLAB-384]: Grouped roles
---
.../epam/dlab/backendapi/dao/UserRoleDaoImpl.java | 2 +
.../dlab/backendapi/resources/dto/UserRoleDto.java | 10 +-
.../src/main/resources/mongo/gcp/mongo_roles.json | 26 ++
.../app/administration/roles/roles.component.html | 64 ++---
.../app/administration/roles/roles.component.scss | 15 +-
.../app/administration/roles/roles.component.ts | 29 +-
.../webapp/src/app/shared/form-controls/index.ts | 5 +-
.../multi-level-select-dropdown.component.html | 70 +++++
.../multi-level-select-dropdown.component.scss | 319 +++++++++++++++++++++
.../multi-level-select-dropdown.component.ts | 101 +++++++
.../resources/webapp/src/assets/styles/_theme.scss | 6 +
11 files changed, 582 insertions(+), 65 deletions(-)
diff --git a/services/self-service/src/main/java/com/epam/dlab/backendapi/dao/UserRoleDaoImpl.java b/services/self-service/src/main/java/com/epam/dlab/backendapi/dao/UserRoleDaoImpl.java
index 0767be4..f4deb0a 100644
--- a/services/self-service/src/main/java/com/epam/dlab/backendapi/dao/UserRoleDaoImpl.java
+++ b/services/self-service/src/main/java/com/epam/dlab/backendapi/dao/UserRoleDaoImpl.java
@@ -55,6 +55,7 @@ public class UserRoleDaoImpl extends BaseDAO implements UserRoleDao {
private static final String USERS_FIELD = "users";
private static final String GROUPS_FIELD = "groups";
private static final String DESCRIPTION = "description";
+ private static final String TYPE = "type";
private static final String ROLES = "roles";
private static final String GROUPS = "$groups";
private static final String GROUP = "group";
@@ -169,6 +170,7 @@ public class UserRoleDaoImpl extends BaseDAO implements UserRoleDao {
private Document roleDocument() {
return new Document().append(ID, "$" + ID)
.append(DESCRIPTION, "$" + DESCRIPTION)
+ .append(TYPE, "$" + TYPE)
.append(USERS_FIELD, "$" + USERS_FIELD)
.append(EXPLORATORY_SHAPES_FIELD, "$" + EXPLORATORY_SHAPES_FIELD)
.append(PAGES_FIELD, "$" + PAGES_FIELD)
diff --git a/services/self-service/src/main/java/com/epam/dlab/backendapi/resources/dto/UserRoleDto.java b/services/self-service/src/main/java/com/epam/dlab/backendapi/resources/dto/UserRoleDto.java
index 21ce26d..b1d3337 100644
--- a/services/self-service/src/main/java/com/epam/dlab/backendapi/resources/dto/UserRoleDto.java
+++ b/services/self-service/src/main/java/com/epam/dlab/backendapi/resources/dto/UserRoleDto.java
@@ -31,10 +31,10 @@ import java.util.Set;
@ToString
@JsonIgnoreProperties(ignoreUnknown = true)
public class UserRoleDto {
-
@JsonProperty("_id")
private String id;
private String description;
+ private Type type;
private Set<String> pages;
private Set<String> computationals;
private Set<String> exploratories;
@@ -42,4 +42,12 @@ public class UserRoleDto {
private Set<String> exploratoryShapes;
private Set<String> groups;
+ private enum Type {
+ NOTEBOOK,
+ COMPUTATIONAL,
+ NOTEBOOK_SHAPE,
+ COMPUTATIONAL_SHAPE,
+ BILLING,
+ ADMINISTRATION
+ }
}
diff --git a/services/self-service/src/main/resources/mongo/gcp/mongo_roles.json b/services/self-service/src/main/resources/mongo/gcp/mongo_roles.json
index 43d12e3..d3c22ba 100644
--- a/services/self-service/src/main/resources/mongo/gcp/mongo_roles.json
+++ b/services/self-service/src/main/resources/mongo/gcp/mongo_roles.json
@@ -2,6 +2,7 @@
{
"_id": "nbShapes_n1-highcpu-2_fetching",
"description": "Use n1-highcpu-2 instance shape for notebook",
+ "type": "NOTEBOOK_SHAPE",
"exploratory_shapes": [
"n1-highcpu-2"
],
@@ -12,6 +13,7 @@
{
"_id": "nbShapes_n1-highcpu-8_fetching",
"description": "Use n1-highcpu-8 instance shape for notebook",
+ "type": "NOTEBOOK_SHAPE",
"exploratory_shapes": [
"n1-highcpu-8"
],
@@ -22,6 +24,7 @@
{
"_id": "nbShapes_n1-highcpu-32_fetching",
"description": "Use n1-highcpu-32 instance shape for notebook",
+ "type": "NOTEBOOK_SHAPE",
"exploratory_shapes": [
"n1-highcpu-32"
],
@@ -32,6 +35,7 @@
{
"_id": "nbShapes_n1-highmem-4_fetching",
"description": "Use n1-highmem-4 instance shape for notebook",
+ "type": "NOTEBOOK_SHAPE",
"exploratory_shapes": [
"n1-highmem-4"
],
@@ -42,6 +46,7 @@
{
"_id": "nbShapes_n1-highmem-16_fetching",
"description": "Use n1-highmem-16 instance shape for notebook",
+ "type": "NOTEBOOK_SHAPE",
"exploratory_shapes": [
"n1-highmem-16"
],
@@ -52,6 +57,7 @@
{
"_id": "nbShapes_n1-highmem-32_fetching",
"description": "Use n1-highmem-32 instance shape for notebook",
+ "type": "NOTEBOOK_SHAPE",
"exploratory_shapes": [
"n1-highmem-32"
],
@@ -62,6 +68,7 @@
{
"_id": "nbShapes_n1-standard-2_fetching",
"description": "Use n1-standard-2 instance shape for notebook",
+ "type": "NOTEBOOK_SHAPE",
"exploratory_shapes": [
"n1-standard-2"
],
@@ -72,6 +79,7 @@
{
"_id": "nbCreateDeeplearning",
"description": "Create Notebook Deep Learning",
+ "type": "NOTEBOOK",
"exploratories": [
"docker.dlab-deeplearning"
],
@@ -82,6 +90,7 @@
{
"_id": "nbCreateJupyter",
"description": "Create Notebook Jupyter",
+ "type": "NOTEBOOK",
"exploratories": [
"docker.dlab-jupyter"
],
@@ -92,6 +101,7 @@
{
"_id": "nbCreateJupyterLab",
"description": "Create Notebook JupyterLab",
+ "type": "NOTEBOOK",
"exploratories": [
"docker.dlab-jupyterlab"
],
@@ -102,6 +112,7 @@
{
"_id": "nbCreateSuperset",
"description": "Create Notebook Superset",
+ "type": "NOTEBOOK",
"exploratories": [
"docker.dlab-superset"
],
@@ -112,6 +123,7 @@
{
"_id": "nbCreateRstudio",
"description": "Create Notebook RStudio",
+ "type": "NOTEBOOK",
"exploratories": [
"docker.dlab-rstudio"
],
@@ -122,6 +134,7 @@
{
"_id": "nbCreateTensor",
"description": "Create Notebook Jupyter with TensorFlow",
+ "type": "NOTEBOOK",
"exploratories": [
"docker.dlab-tensor"
],
@@ -132,6 +145,7 @@
{
"_id": "nbCreateTensorRstudio",
"description": "Create Notebook RStudio with TensorFlow",
+ "type": "NOTEBOOK",
"exploratories": [
"docker.dlab-tensor-rstudio"
],
@@ -142,6 +156,7 @@
{
"_id": "nbCreateZeppelin",
"description": "Create Notebook Apache Zeppelin",
+ "type": "NOTEBOOK",
"exploratories": [
"docker.dlab-zeppelin"
],
@@ -152,6 +167,7 @@
{
"_id": "nbCreateDataEngine",
"description": "Create Data Engine",
+ "type": "COMPUTATIONAL",
"computationals": [
"docker.dlab-dataengine"
],
@@ -162,6 +178,7 @@
{
"_id": "nbCreateDataEngineService",
"description": "Create Data Engine Service",
+ "type": "COMPUTATIONAL",
"computationals": [
"docker.dlab-dataengine-service"
],
@@ -172,6 +189,7 @@
{
"_id": "compShapes_n1-standard-2_fetching",
"description": "Use n1-standard-2 instance shape for cluster",
+ "type": "COMPUTATIONAL_SHAPE",
"computational_shapes": [
"n1-standard-2"
],
@@ -182,6 +200,7 @@
{
"_id": "compShapes_n1-highmem-4_fetching",
"description": "Use n1-highmem-4 instance shape for cluster",
+ "type": "COMPUTATIONAL_SHAPE",
"computational_shapes": [
"n1-highmem-4"
],
@@ -192,6 +211,7 @@
{
"_id": "compShapes_n1-highmem-16_fetching",
"description": "Use n1-highmem-16 instance shape for cluster",
+ "type": "COMPUTATIONAL_SHAPE",
"computational_shapes": [
"n1-highmem-16"
],
@@ -202,6 +222,7 @@
{
"_id": "compShapes_n1-highmem-32_fetching",
"description": "Use n1-highmem-32 instance shape for cluster",
+ "type": "COMPUTATIONAL_SHAPE",
"computational_shapes": [
"n1-highmem-32"
],
@@ -212,6 +233,7 @@
{
"_id": "compShapes_n1-highcpu-8_fetching",
"description": "Use n1-highcpu-8 instance shape for cluster",
+ "type": "COMPUTATIONAL_SHAPE",
"computational_shapes": [
"n1-highcpu-8"
],
@@ -222,6 +244,7 @@
{
"_id": "compShapes_n1-highcpu-2_fetching",
"description": "Use n1-highcpu-2 instance shape for cluster",
+ "type": "COMPUTATIONAL_SHAPE",
"computational_shapes": [
"n1-highcpu-2"
],
@@ -232,6 +255,7 @@
{
"_id": "compShapes_n1-highcpu-32_fetching",
"description": "Use n1-highcpu-32 instance shape for cluster",
+ "type": "COMPUTATIONAL_SHAPE",
"computational_shapes": [
"n1-highcpu-32"
],
@@ -242,6 +266,7 @@
{
"_id": "nbBillingReportFull",
"description": "View full billing report for all users",
+ "type": "BILLING",
"pages": [
"/api/infrastructure_provision/billing"
],
@@ -252,6 +277,7 @@
{
"_id": "admin",
"description": "Allow to execute administration operation",
+ "type": "ADMINISTRATION",
"pages": [
"environment/*",
"/api/infrastructure/backup",
diff --git a/services/self-service/src/main/resources/webapp/src/app/administration/roles/roles.component.html b/services/self-service/src/main/resources/webapp/src/app/administration/roles/roles.component.html
index af111c2..0a7c8e2 100644
--- a/services/self-service/src/main/resources/webapp/src/app/administration/roles/roles.component.html
+++ b/services/self-service/src/main/resources/webapp/src/app/administration/roles/roles.component.html
@@ -45,6 +45,7 @@
class="material-icons">keyboard_arrow_right</i></button>
</div>
</mat-step>
+
<mat-step [completed]='false'>
<ng-template matStepLabel>Users</ng-template>
<div class="inner-step mat-reset">
@@ -58,35 +59,17 @@
class="material-icons">keyboard_arrow_right</i></button>
</div>
</mat-step>
+
<mat-step [completed]='false'>
<ng-template matStepLabel>Roles</ng-template>
<div class="inner-step mat-reset roles">
<div class="selector-wrapper">
- <mat-form-field>
- <mat-select
- multiple [compareWith]="compareObjects"
- name="roles"
- [(value)]="setupRoles"
- disableOptionCentering
- placeholder="Select roles"
- panelClass="select-role"
- >
- <mat-option class="multiple-select" disabled>
- <a class="select ani" (click)="selectAllOptions(setupRoles, rolesList)">
- <i class="material-icons">playlist_add_check</i> All
- </a>
- <a class="deselect ani" (click)="selectAllOptions(setupRoles)">
- <i class="material-icons">clear</i> None
- </a>
- </mat-option>
- <mat-option *ngFor="let role of rolesList" [value]="role">
- {{ role }}
- </mat-option>
- </mat-select>
- <button class="caret">
- <i class="material-icons">keyboard_arrow_down</i>
- </button>
- </mat-form-field>
+ <multi-level-select-dropdown
+ (selectionChange)="onUpdate($event)"
+ name="roles"
+ [items]="rolesList"
+ [model]="setupRoles">
+ </multi-level-select-dropdown>
</div>
</div>
<div class="text-center m-bott-10">
@@ -94,9 +77,10 @@
class="material-icons">keyboard_arrow_left</i>Back</button>
<button mat-raised-button (click)="resetDialog()" class="butt">Cancel</button>
<button mat-raised-button (click)="manageAction('create', 'group')" class="butt butt-success"
- [disabled]="!setupGroup || setupGroupName.errors?.patterns || setupGroupName.errors?.duplicate || !setupRoles.length > 0">Create</button>
+ [disabled]="!setupGroup || setupGroupName.errors?.patterns || setupGroupName.errors?.duplicate || !setupRoles.length">Create</button>
</div>
</mat-step>
+
</mat-horizontal-stepper>
</mat-card>
<mat-divider></mat-divider>
@@ -112,27 +96,13 @@
<th mat-header-cell *matHeaderCellDef class="roles"> Roles </th>
<td mat-cell *matCellDef="let element" class="roles">
<div class="inner-step mat-reset">
- <div class="selector-wrapper-edit">
- <mat-form-field class="select">
- <mat-select multiple [compareWith]="compareObjects" name="selected_roles" disableOptionCentering
- [(value)]="element.selected_roles" placeholder="Select roles" class="roles-select" panelClass="select-role">
- <mat-option class="multiple-select" disabled>
- <a class="select ani" (click)="selectAllOptions(element, rolesList, 'selected_roles')">
- <i class="material-icons">playlist_add_check</i> All
- </a>
- <a class="deselect ani" (click)="selectAllOptions(element, null, 'selected_roles')">
- <i class="material-icons">clear</i> None
- </a>
- </mat-option>
- <mat-option *ngFor="let role of rolesList" [value]="role">
- {{ role }}
- </mat-option>
- </mat-select>
- <button class="caret">
- <i class="material-icons">keyboard_arrow_down</i>
- </button>
- </mat-form-field>
- </div>
+ <multi-level-select-dropdown
+ (selectionChange)="onUpdate($event)"
+ [type]="element.group"
+ [items]="rolesList"
+ [model]="element.selected_roles">
+
+ </multi-level-select-dropdown>
</div>
</td>
</ng-container>
diff --git a/services/self-service/src/main/resources/webapp/src/app/administration/roles/roles.component.scss b/services/self-service/src/main/resources/webapp/src/app/administration/roles/roles.component.scss
index dd14655..f2c10d8 100644
--- a/services/self-service/src/main/resources/webapp/src/app/administration/roles/roles.component.scss
+++ b/services/self-service/src/main/resources/webapp/src/app/administration/roles/roles.component.scss
@@ -88,17 +88,23 @@
}
}
+.mat-horizontal-content-container{
+ overflow: visible !important;
+}
+
.selector-wrapper {
display: flex;
align-self: center;
width: 490px;
height: 36px;
- padding-left: 10px;
+ padding-left: 0;
font-family: 'Open Sans', sans-serif;
font-size: 15px;
font-weight: 300;
box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12);
-
+ multi-level-select-dropdown{
+ width: 100%;
+ }
mat-form-field {
width: 100%;
@@ -137,7 +143,6 @@
}
.roles {
- // width: 30%;
.selector-wrapper-edit {
position: relative;
@@ -343,11 +348,11 @@ table {
}
.roles {
- width: 30%;
+ width: 35%;
}
.users {
- width: 40%;
+ width: 35%;
}
.actions {
diff --git a/services/self-service/src/main/resources/webapp/src/app/administration/roles/roles.component.ts b/services/self-service/src/main/resources/webapp/src/app/administration/roles/roles.component.ts
index 8afec35..01ff281 100644
--- a/services/self-service/src/main/resources/webapp/src/app/administration/roles/roles.component.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/administration/roles/roles.component.ts
@@ -28,6 +28,7 @@ import { DICTIONARY } from '../../../dictionary/global.dictionary';
import {ProgressBarService} from '../../core/services/progress-bar.service';
import {ConfirmationDialogComponent, ConfirmationDialogType} from '../../shared/modal-dialog/confirmation-dialog';
+
@Component({
selector: 'dlab-roles',
templateUrl: './roles.component.html',
@@ -38,11 +39,11 @@ export class RolesComponent implements OnInit {
public groupsData: Array<any> = [];
public roles: Array<any> = [];
- public rolesList: Array<string> = [];
+ public rolesList: Array<any> = [];
public setupGroup: string = '';
public setupUser: string = '';
public manageUser: string = '';
- public setupRoles: Array<string> = [];
+ public setupRoles: Array<any> = [];
public updatedRoles: Array<string> = [];
public healthStatus: any;
public delimitersRegex = /[-_]?/g;
@@ -72,7 +73,10 @@ export class RolesComponent implements OnInit {
this.rolesService.getRolesData().subscribe(
(roles: any) => {
this.roles = roles;
- this.rolesList = roles.map(role => role.description);
+ this.rolesList = roles.map((role, index) => {
+ return {role: role.description, type: role.type};
+ });
+
this.updateGroupData(groups);
this.getGroupsListCopy();
this.stepperView = false;
@@ -103,7 +107,7 @@ export class RolesComponent implements OnInit {
action, type, value: {
name: this.setupGroup,
users: this.setupUser ? this.setupUser.split(',').map(elem => elem.trim()) : [],
- roleIds: this.extractIds(this.roles, this.setupRoles)
+ roleIds: this.extractIds(this.roles, this.setupRoles.map(v => v.role))
}
});
this.stepperView = false;
@@ -126,7 +130,7 @@ export class RolesComponent implements OnInit {
this.manageRolesGroups({
action, type, value: {
name: item.group,
- roleIds: this.extractIds(this.roles, item.selected_roles),
+ roleIds: this.extractIds(this.roles, item.selected_roles.map(v => v.role)),
users: item.users || []
}
});
@@ -202,7 +206,7 @@ export class RolesComponent implements OnInit {
public updateGroupData(groups) {
this.groupsData = groups.map(v => v).sort((a, b) => (a.group > b.group) ? 1 : ((b.group > a.group) ? -1 : 0));
this.groupsData.forEach(item => {
- item.selected_roles = item.roles.map(role => role.description);
+ item.selected_roles = item.roles.map(role => ({role: role.description, type: role.type}));
});
}
@@ -224,10 +228,6 @@ export class RolesComponent implements OnInit {
});
}
- public compareObjects(o1: any, o2: any): boolean {
- return o1.toLowerCase() === o2.toLowerCase();
- }
-
public resetDialog() {
this.stepperView = false;
this.setupGroup = '';
@@ -251,6 +251,14 @@ export class RolesComponent implements OnInit {
this.healthStatusService.getEnvironmentHealthStatus()
.subscribe((result: any) => this.healthStatus = result);
}
+
+ public onUpdate($event): void {
+ if ($event.type) {
+ this.groupsData.filter(group => group.group === $event.type)[0].selected_roles = $event.model;
+ } else {
+ this.setupRoles = $event.model;
+ }
+ }
}
@@ -273,6 +281,7 @@ export class RolesComponent implements OnInit {
`,
styles: [`.group-name { max-width: 96%; display: inline-block; vertical-align: bottom; }`]
})
+
export class ConfirmDeleteUserAccountDialogComponent {
constructor(
public dialogRef: MatDialogRef<ConfirmDeleteUserAccountDialogComponent>,
diff --git a/services/self-service/src/main/resources/webapp/src/app/shared/form-controls/index.ts b/services/self-service/src/main/resources/webapp/src/app/shared/form-controls/index.ts
index bac0dd6..4ea5a14 100644
--- a/services/self-service/src/main/resources/webapp/src/app/shared/form-controls/index.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/shared/form-controls/index.ts
@@ -25,6 +25,7 @@ import { MultiSelectDropdownComponent } from './multi-select-dropdown/multi-sele
import { DirectivesModule } from '../../core/directives';
import { KeysPipeModule, UnderscorelessPipeModule } from '../../core/pipes';
import { BubbleModule } from '..';
+import {MultiLevelSelectDropdownComponent} from './multi-level-select-dropdown/multi-level-select-dropdown.component';
export * from './multi-select-dropdown/multi-select-dropdown.component';
export * from './dropdown-list/dropdown-list.component';
@@ -37,7 +38,7 @@ export * from './dropdown-list/dropdown-list.component';
UnderscorelessPipeModule,
BubbleModule
],
- declarations: [DropdownListComponent, MultiSelectDropdownComponent],
- exports: [DropdownListComponent, MultiSelectDropdownComponent]
+ declarations: [DropdownListComponent, MultiSelectDropdownComponent, MultiLevelSelectDropdownComponent],
+ exports: [DropdownListComponent, MultiSelectDropdownComponent, MultiLevelSelectDropdownComponent]
})
export class FormControlsModule {}
diff --git a/services/self-service/src/main/resources/webapp/src/app/shared/form-controls/multi-level-select-dropdown/multi-level-select-dropdown.component.html b/services/self-service/src/main/resources/webapp/src/app/shared/form-controls/multi-level-select-dropdown/multi-level-select-dropdown.component.html
new file mode 100644
index 0000000..f6b52da
--- /dev/null
+++ b/services/self-service/src/main/resources/webapp/src/app/shared/form-controls/multi-level-select-dropdown/multi-level-select-dropdown.component.html
@@ -0,0 +1,70 @@
+<!--
+ ~ 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="dropdown-multiselect btn-group" ngClass="{{type || ''}}">
+ <button type="button" #list (click)="multiactions.toggle($event, list)">
+ <span class="ellipsis" *ngIf="model.length === 0">Select roles</span>
+ <span class="selected-items ellipsis" *ngIf="model.length !== 0">
+ {{selectedRolesList()}}
+ </span>
+ <span class="caret-btn"><i class="material-icons">keyboard_arrow_down</i></span>
+ </button>
+
+ <bubble-up #multiactions position="bottom" [keep-open]="true" class="mt-5">
+ <ul class="list-menu" id="scrolling">
+ <li class="filter-actions">
+ <a class="select_all" (click)="selectAllOptions($event)">
+ <i class="material-icons">playlist_add_check</i> All
+ </a>
+ <a class="deselect_all" (click)="deselectAllOptions($event)">
+ <i class="material-icons">clear</i> None
+ </a>
+ </li>
+
+ <ng-template ngFor let-item [ngForOf]="items" let-i="index">
+ <li class="role-label" role="presentation" *ngIf="i === 0 || model && item.type !== items[i - 1].type" (click)="toggleItemsForLable(item.type, $event)">
+ <a href="#" class="list-item" role="menuitem">
+ <span class="arrow" [ngClass]="{'rotate-arrow': isOpenCategory[item.type], 'arrow-checked': selectedAllInCattegory(item.type) || selectedSomeInCattegory(item.type)}"></span>
+ <span class="empty-checkbox" [ngClass]="{'checked': selectedAllInCattegory(item.type) || selectedSomeInCattegory(item.type)}" (click)="toggleselectedCategory($event, model, item.type);$event.stopPropagation()" >
+ <span class="checked-checkbox" *ngIf="selectedAllInCattegory(item.type)"></span>
+ <span class="line-checkbox" *ngIf="selectedSomeInCattegory(item.type)"></span>
+ </span>
+ {{labels[item.type] || item.type | titlecase}}
+ </a>
+ </li>
+
+ <li class="role-item" role="presentation" *ngIf="model && isOpenCategory[item.type]" >
+ <a href="#" class="list-item" role="menuitem" (click)="toggleSelectedOptions($event, model, item)">
+ <span class="empty-checkbox" [ngClass]="{'checked': checkInModel(item.role)}">
+ <span class="checked-checkbox" *ngIf="checkInModel(item.role)"></span>
+ </span>
+ {{item.role}}
+ </a>
+ </li>
+ </ng-template>
+
+ <li *ngIf="items?.length == 0">
+ <a role="menuitem" class="list-item">
+ <span class="material-icons">visibility_off</span>
+ No {{type}}
+ </a>
+ </li>
+ </ul>
+ </bubble-up>
+</div>
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
new file mode 100644
index 0000000..00d9a80
--- /dev/null
+++ b/services/self-service/src/main/resources/webapp/src/app/shared/form-controls/multi-level-select-dropdown/multi-level-select-dropdown.component.scss
@@ -0,0 +1,319 @@
+/*!
+ * 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.
+ */
+
+.dropdown-list,
+.dropdown-multiselect {
+ width: 100%;
+ position: relative;
+}
+
+.dropdown-list button,
+.dropdown-multiselect button {
+ height: 38px;
+ width: 100%;
+ background: #fff;
+ padding-left: 15px;
+ font-size: 14px;
+ // height: 34px;
+ text-align: left;
+ white-space: nowrap;
+ cursor: pointer;
+ border-radius: 0;
+ border: none;
+ outline: none;
+ box-shadow: 0 3px 1px -2px rgba(0, 0, 0, .2), 0 2px 2px 0 rgba(0, 0, 0, .14), 0 1px 5px 0 rgba(0, 0, 0, .12);
+}
+
+.dropdown-list {
+ button {
+ line-height: 38px;
+
+ span {
+ color: #4a5c89;
+
+ em {
+ font-size: 13px;
+ color: #35afd5;
+ margin-right: 0px;
+ font-style: normal;
+ }
+ }
+ }
+}
+
+.dropdown-list button:active,
+.dropdown-list button:focus,
+.dropdown-multiselect button:active,
+.dropdown-multiselect button:focus {
+ box-shadow: 0 5px 5px -3px rgba(0, 0, 0, .2), 0 8px 10px 1px rgba(0, 0, 0, .14), 0 3px 14px 2px rgba(0, 0, 0, .12);
+}
+
+.dropdown-multiselect {
+ button {
+ span {
+ color: #999;
+ font-weight: 300;
+ display: inline-block;
+ max-width: 80%;
+ }
+
+ .selected-items {
+ color: #4a5c89;
+ max-width: 477px;
+ }
+ }
+}
+
+.selected-items strong {
+ font-weight: 300;
+}
+
+.dropdown-list,
+.dropdown-multiselect {
+ .caret-btn {
+ position: absolute;
+ top: 0;
+ right: 0;
+ width: 40px;
+ height: 100%;
+ text-align: center;
+ padding: 7px;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ border-left: 1px solid #ececec;
+ background: #fff;
+ color: #36afd5 !important;
+ }
+
+ .list-menu {
+ width: 100%;
+ max-height: 450px;
+ left: 0;
+ padding: 0;
+ margin: 0;
+ overflow-y: auto;
+ overflow-x: hidden;
+
+ li {
+ padding: 0;
+ margin: 0;
+ }
+ .role-item{
+ padding-left: 30px;
+ }
+
+
+ }
+
+ &.statuses {
+ .list-menu {
+ .list-item {
+ text-transform: capitalize;
+ }
+ }
+ }
+
+ &.resources {
+ .list-menu {
+ .list-item {
+ text-transform: capitalize;
+ }
+ }
+ }
+}
+
+.dropdown-list .list-menu a,
+.dropdown-multiselect .list-menu li a {
+ display: block;
+ padding: 10px;
+ padding-left: 15px;
+ position: relative;
+ font-weight: 300;
+ cursor: pointer;
+ color: #4a5c89;
+ text-decoration: none;
+}
+
+.dropdown-multiselect .list-menu li a {
+ padding-left: 45px;
+ transition: all .45s ease-in-out;
+}
+
+.dropdown-list .list-menu a:hover,
+.dropdown-multiselect .list-menu a:hover {
+ background: #f7f7f7;
+ color: #35afd5;
+}
+
+.dropdown-multiselect .list-menu .filter-actions {
+ display: flex;
+ cursor: pointer;
+ border-bottom: 1px solid #ececec;
+}
+
+.dropdown-multiselect .list-menu .filter-actions a {
+ width: 50%;
+ color: #35afd5;
+ display: block;
+ padding: 0;
+ line-height: 40px !important;
+ text-align: center;
+}
+
+.dropdown-list {
+
+ .list-menu,
+ .title {
+ span {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ font-weight: 300;
+ }
+ }
+}
+
+.dropdown-list .list-menu li span.caption {
+ display: block;
+ padding: 10px 15px;
+ cursor: default;
+}
+
+.dropdown-list .list-menu li i,
+.dropdown-list .list-menu li strong {
+ display: inline-block;
+ width: 30px;
+ text-align: center;
+ vertical-align: middle;
+ color: #35afd5;
+ line-height: 26px;
+}
+
+.dropdown-list .list-menu li i {
+ vertical-align: sub;
+ font-size: 18px;
+}
+
+.dropdown-list .list-menu a {
+ padding: 12px;
+ padding-left: 15px;
+ position: relative;
+ font-weight: 300;
+ cursor: pointer;
+
+ em {
+ font-size: 13px;
+ color: #35afd5;
+ margin-right: 0px;
+ font-style: normal;
+ }
+}
+
+.dropdown-list .list-menu a.empty {
+ height: 36px;
+}
+
+.dropdown-multiselect .list-menu .filter-actions i {
+ vertical-align: sub;
+ color: #35afd5;
+ font-size: 18px;
+ line-height: 26px;
+ transition: all .45s ease-in-out;
+}
+
+.dropdown-multiselect .list-menu .select_all:hover,
+.dropdown-multiselect .list-menu .select_all:hover i {
+ color: #4eaf3e !important;
+ background: #f9fafb;
+}
+
+.dropdown-multiselect .list-menu .deselect_all:hover,
+.dropdown-multiselect .list-menu .deselect_all:hover i {
+ color: #f1696e !important;
+ background: #f9fafb;
+}
+
+.dropdown-multiselect .list-menu a {
+ span {
+ position: absolute;
+ top: 10px;
+ left: 25px;
+ color: #35afd5;
+
+ &.checked-checkbox {
+ top: 0px;
+ left: 4px;
+ width: 5px;
+ height: 10px;
+ border-bottom: 2px solid white;
+ border-right: 2px solid white;
+ position: absolute;
+ transform: rotate(45deg);
+ }
+
+ &.line-checkbox {
+ top: 0px;
+ left: 2px;
+ width: 8px;
+ height: 7px;
+ border-bottom: 2px solid white;
+ }
+
+ &.arrow{
+ width: 16px;
+ height: 14px;
+ border: 8px solid transparent;
+ border-left: 8px solid lightgrey;
+ left: 10px;
+ top: 12px;
+ border-radius: 3px;
+
+ &.rotate-arrow{
+ transform: rotate(90deg);
+ transition: .1s ease-in-out;
+ top: 15px;
+ left: 6px;
+ }
+
+ &.arrow-checked{
+ border-left: 8px solid #35afd5;
+ }
+ }
+ }
+
+
+}
+
+.dropdown-multiselect.btn-group.open .dropdown-toggle {
+ box-shadow: none;
+}
+
+.empty-checkbox {
+ width: 16px;
+ height: 16px;
+ border-radius: 2px;
+ border: 2px solid lightgrey;
+ margin-top: 2px;
+ position: relative;
+ &.checked {
+ border-color: #35afd5;
+ background-color: #35afd5;
+ }
+}
+
diff --git a/services/self-service/src/main/resources/webapp/src/app/shared/form-controls/multi-level-select-dropdown/multi-level-select-dropdown.component.ts b/services/self-service/src/main/resources/webapp/src/app/shared/form-controls/multi-level-select-dropdown/multi-level-select-dropdown.component.ts
new file mode 100644
index 0000000..fe8b36a
--- /dev/null
+++ b/services/self-service/src/main/resources/webapp/src/app/shared/form-controls/multi-level-select-dropdown/multi-level-select-dropdown.component.ts
@@ -0,0 +1,101 @@
+/*
+ * 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 { Input, Output, Component, EventEmitter } from '@angular/core';
+
+@Component({
+ selector: 'multi-level-select-dropdown',
+ templateUrl: 'multi-level-select-dropdown.component.html',
+ styleUrls: ['multi-level-select-dropdown.component.scss']
+})
+
+export class MultiLevelSelectDropdownComponent {
+
+ @Input() items: Array<any>;
+ @Input() model: Array<any>;
+ @Input() type: string;
+ @Output() selectionChange: EventEmitter<{}> = new EventEmitter();
+
+ public isOpenCategory = {
+ };
+
+ public labels = {
+ COMPUTATIONAL_SHAPE: 'Compute shapes',
+ NOTEBOOK_SHAPE: 'Notebook shape'
+ };
+
+ toggleSelectedOptions($event, model, value) {
+ $event.preventDefault();
+ const currRole = model.filter(v => v.role === value.role).length;
+ currRole ? this.model = model.filter(v => v.role !== value.role) : model.push(value);
+ this.onUpdate($event);
+ }
+
+ toggleselectedCategory($event, model, value) {
+ $event.preventDefault();
+ const categoryItems = this.items.filter(role => role.type === value);
+ this.selectedAllInCattegory(value) ? this.model = this.model.filter(role => role.type !== value) : categoryItems.forEach(role => {
+ if (!model.filter(mod => mod.role === role.role).length) {this.model.push(role); }
+ });
+ this.onUpdate($event);
+ }
+
+ selectAllOptions($event) {
+
+ $event.preventDefault();
+ this.model = [...this.items];
+ this.onUpdate($event);
+ $event.preventDefault();
+ }
+
+ deselectAllOptions($event) {
+ this.model = [];
+ this.onUpdate($event);
+ $event.preventDefault();
+ }
+
+ onUpdate($event): void {
+ this.selectionChange.emit({ model: this.model, type: this.type, $event });
+ }
+
+ public toggleItemsForLable(label, $event) {
+ this.isOpenCategory[label] = !this.isOpenCategory[label];
+ $event.preventDefault();
+ }
+
+ public selectedAllInCattegory(category) {
+ const selected = this.model.filter(role => role.type === category);
+ const categoryItems = this.items.filter(role => role.type === category);
+ return selected.length === categoryItems.length;
+ }
+
+ public selectedSomeInCattegory(category) {
+ const selected = this.model.filter(role => role.type === category);
+ const categoryItems = this.items.filter(role => role.type === category);
+ return selected.length && selected.length !== categoryItems.length;
+ }
+
+ public checkInModel(item) {
+ return this.model.filter(v => v.role === item).length;
+ }
+
+ public selectedRolesList() {
+ return this.model.map(role => role.role).join(',');
+ }
+}
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 0353978..7b48bba 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
@@ -656,3 +656,9 @@ mat-progress-bar {
background-color: #baf0f7;
}
}
+.manage-roles{
+ .mat-horizontal-content-container{
+ overflow: visible !important;
+ }
+}
+
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@dlab.apache.org
For additional commands, e-mail: commits-help@dlab.apache.org