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/04/23 16:19:40 UTC
[incubator-dlab] branch bucket-browser-gcp updated: [DLAB-1551]:
Merge UI in bucket browser (#701)
This is an automated email from the ASF dual-hosted git repository.
dgnatyshyn pushed a commit to branch bucket-browser-gcp
in repository https://gitbox.apache.org/repos/asf/incubator-dlab.git
The following commit(s) were added to refs/heads/bucket-browser-gcp by this push:
new f45a0d7 [DLAB-1551]: Merge UI in bucket browser (#701)
f45a0d7 is described below
commit f45a0d78fdc78d573d626d72192d6d7f22f28b87
Author: Dmytro Gnatyshyn <42...@users.noreply.github.com>
AuthorDate: Thu Apr 23 19:19:33 2020 +0300
[DLAB-1551]: Merge UI in bucket browser (#701)
[DLAB-1551]: Bucket browser UI
---
.../core/interceptors/http.token.interceptor.ts | 2 +-
.../services/applicationServiceFacade.service.ts | 29 +++
.../app/core/services/bucket-browser.service.ts | 153 +++++++++++++
.../bucket-browser/bucket-browser.component.html | 90 ++++++++
.../bucket-browser/bucket-browser.component.scss | 243 +++++++++++++++++++++
.../bucket-browser/bucket-browser.component.ts | 205 +++++++++++++++++
.../folder-tree/folder-tree.component.html | 32 +++
.../folder-tree/folder-tree.component.scss | 38 ++++
.../folder-tree/folder-tree.component.ts | 191 ++++++++++++++++
.../detail-dialog/detail-dialog.component.html | 4 +-
.../detail-dialog/detail-dialog.component.scss | 1 +
.../detail-dialog/detail-dialog.component.ts | 11 +-
.../src/app/resources/resources.component.html | 3 +
.../src/app/resources/resources.component.ts | 6 +
.../webapp/src/app/resources/resources.module.ts | 12 +-
.../webapp/src/assets/styles/_dialogs.scss | 1 +
16 files changed, 1013 insertions(+), 8 deletions(-)
diff --git a/services/self-service/src/main/resources/webapp/src/app/core/interceptors/http.token.interceptor.ts b/services/self-service/src/main/resources/webapp/src/app/core/interceptors/http.token.interceptor.ts
index ce476b4..29aa010 100644
--- a/services/self-service/src/main/resources/webapp/src/app/core/interceptors/http.token.interceptor.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/core/interceptors/http.token.interceptor.ts
@@ -33,7 +33,7 @@ import { Observable } from 'rxjs';
if (token)
headersConfig['Authorization'] = `Bearer ${token}`;
- if (!request.headers.has('Content-Type') && !request.headers.has('Upload'))
+ if (!request.headers.has('Content-Type') && !request.headers.has('Upload') && request.url.indexOf('upload') === -1)
headersConfig['Content-Type'] = 'application/json; charset=UTF-8';
const header = request.clone({ setHeaders: headersConfig });
diff --git a/services/self-service/src/main/resources/webapp/src/app/core/services/applicationServiceFacade.service.ts b/services/self-service/src/main/resources/webapp/src/app/core/services/applicationServiceFacade.service.ts
index 121188e..ebaec6d 100644
--- a/services/self-service/src/main/resources/webapp/src/app/core/services/applicationServiceFacade.service.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/core/services/applicationServiceFacade.service.ts
@@ -52,6 +52,7 @@ export class ApplicationServiceFacade {
private static readonly COMPUTATIONAL_RESOURCES = 'computational_resources';
private static readonly COMPUTATIONAL_RESOURCES_DATAENGINE = 'computational_resources_dataengine';
private static readonly COMPUTATIONAL_RESOURCES_DATAENGINESERVICE = 'computational_resources_dataengineservice';
+ private static readonly BUCKET = 'bucket';
private static readonly USER_PREFERENCES = 'user_preferences';
private static readonly BUDGET = 'budget';
private static readonly ENVIRONMENT_HEALTH_STATUS = 'environment_health_status';
@@ -253,6 +254,31 @@ export class ApplicationServiceFacade {
null);
}
+ public buildGetBucketData(): Observable<any> {
+ return this.buildRequest(HTTPMethod.GET,
+ this.requestRegistry.Item(ApplicationServiceFacade.BUCKET) + '/ofuks-1304-prj1-local-bucket/endpoint/local',
+ null);
+ }
+
+ public buildUploadFileToBucket(data): Observable<any> {
+ return this.buildRequest(HTTPMethod.POST,
+ this.requestRegistry.Item(ApplicationServiceFacade.BUCKET) + '/upload',
+ data);
+ }
+
+ public buildDownloadFileFromBucket(data): Observable<any> {
+ return this.buildRequest(HTTPMethod.GET,
+ this.requestRegistry.Item(ApplicationServiceFacade.BUCKET),
+ data, { observe: 'response', responseType: 'text' } );
+ }
+
+ public buildDeleteFileFromBucket(data): Observable<any> {
+ return this.buildRequest(HTTPMethod.DELETE,
+ this.requestRegistry.Item(ApplicationServiceFacade.BUCKET),
+ data );
+ }
+
+
public buildUpdateUserPreferences(data): Observable<any> {
return this.buildRequest(HTTPMethod.POST,
this.requestRegistry.Item(ApplicationServiceFacade.USER_PREFERENCES),
@@ -651,6 +677,9 @@ export class ApplicationServiceFacade {
this.requestRegistry.Add(ApplicationServiceFacade.COMPUTATIONAL_RESOURCES_TEMLATES,
'/api/infrastructure_templates/computational_templates');
+ // Bucket browser
+ this.requestRegistry.Add(ApplicationServiceFacade.BUCKET, '/api/bucket');
+
// Filtering Configuration
this.requestRegistry.Add(ApplicationServiceFacade.USER_PREFERENCES, '/api/user/settings');
this.requestRegistry.Add(ApplicationServiceFacade.BUDGET, '/api/user/settings/budget');
diff --git a/services/self-service/src/main/resources/webapp/src/app/core/services/bucket-browser.service.ts b/services/self-service/src/main/resources/webapp/src/app/core/services/bucket-browser.service.ts
new file mode 100644
index 0000000..ff27ef5
--- /dev/null
+++ b/services/self-service/src/main/resources/webapp/src/app/core/services/bucket-browser.service.ts
@@ -0,0 +1,153 @@
+import { Injectable } from '@angular/core';
+import {BehaviorSubject, Observable} from 'rxjs';
+import {catchError, map} from 'rxjs/operators';
+import {ErrorUtils} from '../util';
+import {ApplicationServiceFacade} from './applicationServiceFacade.service';
+
+export class TodoItemNode {
+ children: TodoItemNode[];
+ item: string;
+ id: string;
+ size: number;
+}
+
+/** Flat to-do item node with expandable and level information */
+export class TodoItemFlatNode {
+ item: string;
+ level: number;
+ expandable: boolean;
+}
+
+/**
+ * The Json object for to-do list data.
+ */
+
+
+const array = [{'bucket': 'ofuks-1304-prj1-local-bucket', 'object': '4.txt', 'size': '18 bytes', 'creationDate': '21-4-2020 11:36:36'}, {'bucket': 'ofuks-1304-prj1-local-bucket', 'object': '5.txt', 'size': '18 bytes', 'creationDate': '21-4-2020 11:56:46'}, {'bucket': 'ofuks-1304-prj1-local-bucket', 'object': 'Untitled', 'size': '5 bytes', 'creationDate': '13-4-2020 03:39:11'}, {'bucket': 'ofuks-1304-prj1-local-bucket', 'object': 'adasdas', 'size': '1 KB', 'creationDate': '15-4-2020 02:17 [...]
+
+const processFiles = (files, target) => {
+ let pointer = target;
+ files.forEach((file, index) => {
+ if (!pointer[file]) {
+ pointer[file] = {};
+ }
+ pointer = pointer[file];
+ });
+
+};
+
+const processFolderArray = (acc, curr) => {
+ const files = curr.object.split('/').filter(x => x.length > 0);
+ processFiles(files, acc);
+ return acc;
+};
+
+const convertToFolderTree = (data) => data
+ .reduce(
+ processFolderArray,
+ {}
+ );
+
+const TREE_DATA = convertToFolderTree(array);
+
+
+@Injectable({
+ providedIn: 'root'
+})
+export class BucketBrowserService {
+ public dataChange = new BehaviorSubject<TodoItemNode[]>([]);
+ public serverData: any = [];
+ get data(): TodoItemNode[] { return this.dataChange.value; }
+
+ constructor(private applicationServiceFacade: ApplicationServiceFacade) {
+ this.initialize();
+ }
+
+ public getBacketData(): Observable<{}> {
+ return this.applicationServiceFacade
+ .buildGetBucketData()
+ .pipe(
+ map(response => response),
+ catchError(ErrorUtils.handleServiceError));
+ }
+
+ public initialize() {
+ let backetData = [];
+ this.getBacketData().subscribe(v => {
+ this.serverData = v;
+ backetData = convertToFolderTree(v);
+ const data = this.buildFileTree({'ofuks-1304-prj1-local-bucket': backetData}, 0);
+ this.dataChange.next(data);
+ });
+ // this.serverData = array;
+ // backetData = convertToFolderTree(this.serverData);
+ // const data = this.buildFileTree({'ofuks-1304-prj1-local-bucket': backetData}, 0);
+ // this.dataChange.next(data);
+ }
+
+ /**
+ * Build the file structure tree. The `value` is the Json object, or a sub-tree of a Json object.
+ * The return value is the list of `TodoItemNode`.
+ */
+ public buildFileTree(obj: {[key: string]: any}, level: number): TodoItemNode[] {
+ return Object.keys(obj).reduce<TodoItemNode[]>((accumulator, key) => {
+ const value = obj[key];
+ const node = new TodoItemNode();
+ node.item = key;
+ if (Object.keys(value).length) {
+ if (typeof value === 'object') {
+ node.children = this.buildFileTree(value, level + 1);
+ } else {
+ node.item = value;
+ }
+ } else {
+ node.size = this.serverData.filter(v => v.object.indexOf(node.item) !== -1)[0];
+ }
+ return accumulator.concat(node);
+ }, []);
+ }
+
+ public insertItem(parent: TodoItemNode, name, isFile) {
+ if (parent.children) {
+ if (isFile) {
+ parent.children.push(name as TodoItemNode);
+ } else {
+ parent.children.unshift({item: name, children: []} as TodoItemNode);
+ this.dataChange.next(this.data);
+ }
+ }
+ }
+
+ public updateItem(node: TodoItemNode, file) {
+ node.item = file;
+ this.dataChange.next(this.data);
+ }
+
+ public downloadFile(data) {
+ return this.applicationServiceFacade
+ .buildDownloadFileFromBucket(data)
+ .pipe(
+ map(response => response),
+ catchError(ErrorUtils.handleServiceError));
+ }
+
+ public uploadFile(data) {
+ return this.applicationServiceFacade
+ .buildUploadFileToBucket(data)
+ .pipe(
+ map(response => response),
+ catchError(ErrorUtils.handleServiceError));
+ }
+
+ public deleteFile(data) {
+ const url = JSON.stringify(data)
+ return this.applicationServiceFacade
+ .buildDeleteFileFromBucket(url)
+ .pipe(
+ map(response => response),
+ catchError(ErrorUtils.handleServiceError));
+ }
+ // initBucket(bucketType) {
+ // bucketType !== 'project' ? TREE_DATA = local : TREE_DATA = projecta;
+ // }
+}
diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/bucket-browser.component.html b/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/bucket-browser.component.html
new file mode 100644
index 0000000..8671562
--- /dev/null
+++ b/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/bucket-browser.component.html
@@ -0,0 +1,90 @@
+<!--
+ ~ 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="bucket-browser" id="dialog-box">
+ <header class="dialog-header">
+ <h4 class="modal-title">Bucket browser</h4>
+ <button type="button" class="close" (click)="dialogRef.close()">×</button>
+ </header>
+ <div class="dialog-content tabs">
+ <div class="submit m-bott-10 m-top-10">
+ <button mat-raised-button type="button" class="butt action"><input type="file" (change)="handleFileInput($event)">Add file</button>
+ <button mat-raised-button type="button" class="butt action" (click)="folderTreeComponent.addNewItem(selectedFolder, '', false)">Create folder</button>
+ </div>
+ <p class="path"><span>Bucket path:</span><span class="url"> {{path}}</span></p>
+ <div class="backet-wrapper" id="scrolling">
+ <div class="navigation">
+ <dlab-folder-tree (showFolderContent)=onFolderClick($event)> </dlab-folder-tree>
+ </div>
+ <div class="directory">
+ <ul class="folder-tree" *ngIf="!addedFiles.length">
+ <li *ngFor="let file of folderItems" class="folder-item" >
+
+ <div class="folder-item-wrapper" *ngIf="file.children" (click)="showItem(file)">
+ <div class="name name-folder"><i class="material-icons folder-icon" >folder</i><span>{{file.item}}</span></div>
+ <div class="size"></div>
+ <div class="progress-wrapper">
+ <div class="progres" *ngIf="file.isSelected"><div class="bar"></div></div>
+ </div>
+ </div>
+
+ <div class="folder-item-wrapper" (click)="toggleSelectedFile(file)" *ngIf="!file.children">
+ <div class="name name-file">
+ <span class="empty-checkbox" [ngClass]="{'checked': file.isSelected}" (click)="toggleSelectedFile(file);$event.stopPropagation()" >
+ <span class="checked-checkbox" *ngIf="file.isSelected"></span>
+ </span>
+ <span class="item-name">{{file.item}}</span>
+ </div>
+ <div class="size">{{file.size.size}}</div>
+ <div class="size">{{file.size.creationDate }}</div>
+<!-- <div class="progress-wrapper">-->
+<!--<!– <div class="progres" *ngIf="file.isSelected"><div class="bar"></div></div>–>-->
+<!-- </div>-->
+ </div>
+
+ </li>
+ </ul>
+ <ul class="folder-tree">
+ <li *ngFor="let file of addedFiles" class="folder-item">
+ <div class="folder-item-wrapper">
+ <div class="name">{{file.item}}</div>
+ <div class="size">{{file.size}} MB</div>
+ <div class="progress-wrapper">
+<!-- <div class="progres">-->
+<!-- <div class="bar" [ngClass]="{'full': isUploading}">-->
+<!-- -->
+<!-- </div>-->
+
+<!-- </div>-->
+ <mat-progress-bar mode="indeterminate" *ngIf="isUploading"></mat-progress-bar>
+ </div>
+ <div (click)="deleteAddedFile(file)"><i class="material-icons close">close</i></div>
+ </div>
+ </li>
+ </ul>
+ </div>
+ </div>
+ <div class="text-center m-top-30 m-bott-30">
+ <button type="button" class="butt" mat-raised-button (click)="this.dialogRef.close()">Close</button>
+ <button *ngIf="!this.addedFiles.length" type="button" class="butt butt-success" mat-raised-button (click)="fileAction('download')" [disabled]="!selected?.length">Download</button>
+ <button *ngIf="!this.addedFiles.length" type="button" class="butt butt-success" mat-raised-button (click)="fileAction('delete')" [disabled]="!selected?.length">Delete</button>
+ <button *ngIf="this.addedFiles.length !== 0" type="button" class="butt butt-success" mat-raised-button (click)="uploadNewFile()">Upload</button>
+ </div>
+ </div>
+</div>
diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/bucket-browser.component.scss b/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/bucket-browser.component.scss
new file mode 100644
index 0000000..b9033da
--- /dev/null
+++ b/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/bucket-browser.component.scss
@@ -0,0 +1,243 @@
+/*!
+ * 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.
+ */
+
+.bucket-browser {
+ .path{
+ margin: 0 4px 10px 4px;
+ padding: 4px 4px 4px 20px;
+ color: rgba(0,0,0,.87);
+ .url{
+ font-weight: 600;
+ }
+ }
+ bottom: 0;
+ .dialog-content{
+ padding: 0 35px;
+ }
+ .content-box {
+ height: 500px;
+
+ }
+
+ .submit{
+ display: flex;
+ .butt{
+ position: relative;
+ overflow: hidden;
+ margin: 10px;
+ input[type="file"] {
+ position: absolute;
+ left: 0;
+ right: 0;
+ top: 0;
+ bottom: 0;
+ opacity: 0;
+ }
+ }
+ }
+
+ .text-center{
+ position: absolute;
+ bottom: 3vh;
+ left: 0;
+ right: 0;
+ }
+}
+
+.backet-wrapper{
+ height: 50vh;
+ border: 2px solid rgba(0,0,0,.12);
+ border-radius: 5px;
+ display: flex;
+
+
+
+ .navigation{
+ flex: 1;
+ border-right: 2px solid rgba(0,0,0,.12);
+ max-height: 500px;
+ overflow: auto;
+ .folder-tree{
+
+ .folder{
+ line-height: 30px;
+ }
+ }
+
+ }
+
+ .directory{
+ max-height: 500px;
+ overflow: auto;
+ flex: 2;
+ .folder-tree{
+ .name{
+ flex:2;
+ display: flex;
+ align-items: center;
+ &-folder{
+ span{
+ padding-left: 10px;
+ }
+ }
+ &-file{
+ padding-left: 4px;
+ span.item-name{
+ padding-left: 14px;
+ }
+ }
+ }
+ .size{
+ flex:1;
+ }
+ .progress-wrapper{
+ flex:1;
+ .progres{
+ border: 1px solid rgba(0,0,0,.12);
+ height: 15px;
+ position: relative;
+ .bar{
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ width: 0;
+ background-color: #00bcd4;
+ &.full{
+ width: 100%;
+ transition: 5s ease-in-out;
+ }
+ }
+ }
+ }
+ .material-icons.close{
+ font-size: 15px;
+ margin: 0 10px;
+ cursor: pointer;
+ }
+ .action {
+ display: flex;
+ justify-content: space-around;
+ width: 100%;
+ .add-file {
+ overflow: hidden;
+ min-width: 100px ;
+ height: 30px;
+ position: relative;
+ input{
+ position: absolute;
+ left: 0;
+ right: 0;
+ top: 0;
+ bottom: 0;
+ opacity: 0;
+ }
+ }
+ }
+
+ }
+ }
+
+ .folder-tree {
+ padding: 20px 30px;
+ }
+
+ .folder-item{
+ display: flex;
+ align-items: center;
+ .folder-item-wrapper{
+ width: 100%;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ cursor: pointer;
+ color: rgba(0,0,0,.87);
+ i{
+ color: rgb(232, 232, 232);
+ }
+ &:hover{
+ color: #00bcd4;
+ transition: .3s ease-in-out;
+ i{
+ color: #00bcd4;
+ transition: .3s ease-in-out;
+ }
+ .empty-checkbox{
+ border-color: #00bcd4
+ }
+ .progress-wrapper{
+ .progres{
+ border-color: #00bcd4 !important;
+ }
+ }
+ }
+ }
+ }
+}
+
+.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;
+ }
+ .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);
+ }
+}
+
+.folder-item-name{
+ span{
+ color: #000;
+ }
+
+ .check-box {
+ background-color: red;
+ }
+
+}
+
+@media only screen and (max-height: 840px) {
+ .backet-wrapper {
+ height: 45vh;
+ }
+}
+
+@media only screen and (max-height: 690px) {
+ .backet-wrapper {
+ height: 40vh;
+ }
+}
+
+
+
+
+
diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/bucket-browser.component.ts b/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/bucket-browser.component.ts
new file mode 100644
index 0000000..fef7d0d
--- /dev/null
+++ b/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/bucket-browser.component.ts
@@ -0,0 +1,205 @@
+/*
+ * 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, ViewChild, Inject } from '@angular/core';
+import { FormGroup, FormBuilder, Validators } from '@angular/forms';
+import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
+import { ToastrService } from 'ngx-toastr';
+import { ManageUngitService } from '../../core/services';
+
+import {FolderTreeComponent} from './folder-tree/folder-tree.component';
+import {BucketBrowserService, TodoItemNode} from '../../core/services/bucket-browser.service';
+import {FileUtils} from '../../core/util';
+
+@Component({
+ selector: 'dlab-bucket-browser',
+ templateUrl: './bucket-browser.component.html',
+ styleUrls: ['./bucket-browser.component.scss']
+})
+export class BucketBrowserComponent implements OnInit {
+ public filenames: Array<any> = [];
+ public addedFiles = [];
+ public folderItems = [];
+ public path = '';
+ public pathInsideBucket = '';
+ public bucketName = '';
+ public endpoint = '';
+
+ @ViewChild(FolderTreeComponent, {static: true}) folderTreeComponent;
+ public selectedFolder: any;
+ private isUploading: boolean;
+ private selected: any[];
+ private uploadForm: FormGroup;
+
+ constructor(
+ @Inject(MAT_DIALOG_DATA) public data: any,
+ public toastr: ToastrService,
+ public dialog: MatDialog,
+ public dialogRef: MatDialogRef<BucketBrowserComponent>,
+ private manageUngitService: ManageUngitService,
+ private _fb: FormBuilder,
+ private bucketBrowserService: BucketBrowserService,
+ private formBuilder: FormBuilder
+ ) {
+
+ }
+
+ ngOnInit() {
+ // this.bucketBrowserService.getBacketData();
+ this.endpoint = this.data.endpoint;
+ this.uploadForm = this.formBuilder.group({
+ file: ['']
+ });
+ }
+
+ public showItem(item) {
+ const flatItem = this.folderTreeComponent.nestedNodeMap.get(item);
+ this.folderTreeComponent.showItem(flatItem);
+ }
+
+ public handleFileInput(event) {
+ // for (let i = 0; i < files.length; i++) {
+ // const file = files[i];
+ // const path = file.webkitRelativePath.split('/');
+ // }
+ // }
+ if (event.target.files.length > 0) {
+ const file = event.target.files[0];
+ this.uploadForm.get('file').setValue(file);
+ this.filenames = Object['values'](event.target.files).map(v => ({item: v.name, 'size': (v.size / 1048576).toFixed(2)} as unknown as TodoItemNode));
+ this.addedFiles = [...this.addedFiles, ...this.filenames];
+ }
+
+ }
+
+ public toggleSelectedFile(file) {
+ file.isSelected = !file.isSelected;
+ this.selected = this.folderItems.filter(item => item.isSelected);
+ }
+
+ filesPicked(files) {
+ // console.log(files);
+
+ Array.prototype.forEach.call(files, file => {
+ this.addedFiles.push(file.webkitRelativePath);
+ });
+ // console.log(this.addedFiles);
+ }
+
+ public onFolderClick(event) {
+ this.selectedFolder = event.flatNode;
+ this.folderItems = event.element ? event.element.children : event.children;
+ // this.folderItems = this.folderItems.sort((a, b) => (a.children > b.children) ? 1 : -1)
+ // console.log(this.folderItems);
+ this.path = event.path;
+ this.pathInsideBucket = this.path.indexOf('/') !== -1 ? this.path.slice(this.path.indexOf('/') + 1) + '/' : '';
+ this.bucketName = this.path.substring(0, this.path.indexOf('/')) || this.path;
+ this.folderItems.forEach(item => item.isSelected = false);
+ }
+
+ private upload(tree, path) {
+ tree.files.forEach(file => {
+ this.addedFiles.push(path + file.name);
+ });
+ tree.directories.forEach(directory => {
+ const newPath = path + directory.name + '/';
+ this.addedFiles.push(newPath);
+ this.upload(directory, newPath);
+ });
+ }
+
+ public deleteAddedFile(file) {
+ this.addedFiles.splice(this.addedFiles.indexOf(file), 1);
+ }
+
+ private uploadNewFile() {
+ const path = `${this.pathInsideBucket}${this.uploadForm.get('file').value.name}`;
+
+ const formData = new FormData();
+ formData.append('file', this.uploadForm.get('file').value);
+ formData.append('object', path);
+ formData.append('bucket', 'ofuks-1304-prj1-local-bucket');
+ formData.append('endpoint', this.endpoint);
+ // file.inProgress = true;
+ this.isUploading = true;
+ this.bucketBrowserService.uploadFile(formData)
+ // .pipe(
+ // map(event => {
+ // switch (event.type) {
+ // case HttpEventType.UploadProgress:
+ // file.progress = Math.round(event.loaded * 100 / event.total);
+ // break;
+ // case HttpEventType.Response:
+ // return event;
+ // }
+ // }),
+ // catchError((error: HttpErrorResponse) => {
+ // file.inProgress = false;
+ // return of(`${file.name} upload failed.`);
+ // }))
+ .subscribe((event: any) => {
+ // if (typeof (event) === 'object') {
+ // console.log(event.body);
+ // }
+ // this.isUploading = false;
+ this.bucketBrowserService.initialize();
+ this.addedFiles = [];
+ this.isUploading = false;
+ this.toastr.success('File successfully uploaded!', 'Success!');
+ }, error => this.toastr.error(error.message || 'File uploading error!', 'Oops!')
+ );
+ }
+
+ public fileAction(action) {
+ this.selected = this.folderItems.filter(item => item.isSelected);
+ const path = encodeURIComponent(`${this.pathInsideBucket}${this.selected[0].item}`);
+ if (action === 'download') {
+ this.bucketBrowserService.downloadFile(`/${this.bucketName}/object/${path}/endpoint/${this.endpoint}/download`
+ ).subscribe(data => {
+ FileUtils.downloadFile(data);
+ // this.downLoadFile(response, 'aplication/octet-stream');
+ this.toastr.success('File downloading started!', 'Success!');
+ }, error => this.toastr.error(error.message || 'File downloading error!', 'Oops!')
+ );
+ }
+
+ if (action === 'delete') {
+ this.bucketBrowserService.deleteFile(`/${this.bucketName}/object/${path}/endpoint/${this.endpoint}`).subscribe(() => {
+ this.bucketBrowserService.initialize();
+ this.toastr.success('File successfully deleted!', 'Success!');
+ }, error => this.toastr.error(error.message || 'File deleting error!', 'Oops!')
+ );
+ }
+
+ this.folderItems.forEach(item => item.isSelected = false);
+ this.selected = this.folderItems.filter(item => item.isSelected);
+ }
+
+ // downLoadFile(data: any, type: string) {
+ // const blob = new Blob([data], { type: type});
+ // const url = window.URL.createObjectURL(blob);
+ // const pwa = window.open(url);
+ // if (!pwa || pwa.closed || typeof pwa.closed === 'undefined') {
+ // alert( 'Please disable your Pop-up blocker and try again.');
+ // }
+ // }
+}
+
+
+
diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/folder-tree/folder-tree.component.html b/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/folder-tree/folder-tree.component.html
new file mode 100644
index 0000000..b663e78
--- /dev/null
+++ b/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/folder-tree/folder-tree.component.html
@@ -0,0 +1,32 @@
+<mat-tree [dataSource]="dataSource" [treeControl]="treeControl">
+ <mat-tree-node *matTreeNodeDef="let node" matTreeNodeToggle matTreeNodePadding [ngStyle]="{'display': 'none'}">
+ <button mat-icon-button disabled></button>
+ <!-- <mat-checkbox class="checklist-leaf-node"-->
+ <!-- [checked]="checklistSelection.isSelected(node)"-->
+ <!-- (change)="todoLeafItemSelectionToggle(node)"></mat-checkbox>-->
+ {{node.item}}
+</mat-tree-node>
+
+ <mat-tree-node *matTreeNodeDef="let node; when: hasNoContent" matTreeNodePadding>
+ <button mat-icon-button disabled></button>
+ <mat-form-field>
+ <mat-label>New folder</mat-label>
+ <input matInput #itemValue>
+ </mat-form-field>
+ <button mat-button (click)="saveNode(node, itemValue.value)">Save</button>
+ </mat-tree-node>
+
+ <mat-tree-node *matTreeNodeDef="let node; when: hasChild" matTreeNodePadding>
+ <button mat-icon-button matTreeNodeToggle
+ [attr.aria-label]="'toggle ' + node.filename">
+ <mat-icon class="mat-icon-rtl-mirror" [ngClass]="{'active-item': selectedFolder === node}">
+ {{treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right'}}
+ </mat-icon>
+ </button>
+<!-- <mat-checkbox [checked]="descendantsAllSelected(node)"-->
+<!-- [indeterminate]="descendantsPartiallySelected(node)"-->
+<!-- (change)="todoItemSelectionToggle(node)"></mat-checkbox>-->
+ <div (click)="showItem(node)" class="folder-item-line" [ngClass]="{'active-item': selectedFolder === node}"><i class="material-icons folder-icon">folder</i> <span class="folder">{{node.item}}</span></div>
+<!-- <button mat-icon-button (click)="addNewItem(node)"><mat-icon>add</mat-icon></button>-->
+ </mat-tree-node>
+</mat-tree>
diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/folder-tree/folder-tree.component.scss b/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/folder-tree/folder-tree.component.scss
new file mode 100644
index 0000000..e9902af
--- /dev/null
+++ b/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/folder-tree/folder-tree.component.scss
@@ -0,0 +1,38 @@
+
+.folder{
+ padding-left: 5px;
+}
+
+.folder-icon{
+ color: rgb(232, 232, 232);
+}
+
+.folder-item-line{
+ display: flex;
+ align-items: center;
+ cursor: pointer;
+
+}
+
+.active-item {
+ //border-bottom: 1px solid #00bcd4;
+ color: #00bcd4;
+ i{
+ color: #00bcd4;
+ }
+
+
+}
+
+.mat-tree-node{
+ cursor: pointer;
+ transition: .3s;
+ overflow: unset;
+ &:hover{
+ color: #00bcd4;
+ i{
+ color: #00bcd4;
+ }
+ }
+}
+
diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/folder-tree/folder-tree.component.ts b/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/folder-tree/folder-tree.component.ts
new file mode 100644
index 0000000..0b367c5
--- /dev/null
+++ b/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/folder-tree/folder-tree.component.ts
@@ -0,0 +1,191 @@
+import {Component, OnInit, AfterViewInit, Output, EventEmitter} from '@angular/core';
+import {SelectionModel} from '@angular/cdk/collections';
+import {FlatTreeControl} from '@angular/cdk/tree';
+import {MatTreeFlatDataSource, MatTreeFlattener} from '@angular/material/tree';
+import {BucketBrowserService, TodoItemFlatNode, TodoItemNode} from '../../../core/services/bucket-browser.service';
+
+
+@Component({
+ selector: 'dlab-folder-tree',
+ templateUrl: './folder-tree.component.html',
+ styleUrls: ['./folder-tree.component.scss']
+})
+export class FolderTreeComponent implements OnInit {
+
+ @Output() showFolderContent: EventEmitter<any> = new EventEmitter();
+
+ path = [];
+ selectedFolder: TodoItemFlatNode;
+ flatNodeMap = new Map<TodoItemFlatNode, TodoItemNode>();
+ nestedNodeMap = new Map<TodoItemNode, TodoItemFlatNode>();
+ selectedParent: TodoItemFlatNode | null = null;
+ newItemName = '';
+ treeControl: FlatTreeControl<TodoItemFlatNode>;
+ treeFlattener: MatTreeFlattener<TodoItemNode, TodoItemFlatNode>;
+ dataSource: MatTreeFlatDataSource<TodoItemNode, TodoItemFlatNode>;
+
+ checklistSelection = new SelectionModel<TodoItemFlatNode>(true /* multiple */);
+
+ constructor(private bucketBrowserService: BucketBrowserService) {
+ this.treeFlattener = new MatTreeFlattener(this.transformer, this.getLevel,
+ this.isExpandable, this.getChildren);
+
+ this.treeControl = new FlatTreeControl<TodoItemFlatNode>(this.getLevel, this.isExpandable);
+ this.dataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener);
+
+ bucketBrowserService.dataChange.subscribe(data => {
+ this.dataSource.data = data;
+ if (!this.selectedFolder) {
+ const subject = this.dataSource._flattenedData;
+ subject.subscribe((subjectData) => {
+ this.treeControl.expand(subjectData[0]);
+ this.showItem(subjectData[0]);
+ });
+ }
+ });
+ }
+
+ getLevel = (node: TodoItemFlatNode) => node.level;
+
+ isExpandable = (node: TodoItemFlatNode) => node.expandable;
+
+ getChildren = (node: TodoItemNode): TodoItemNode[] => node.children;
+
+ hasChild = (_: number, _nodeData: TodoItemFlatNode) => _nodeData.expandable;
+
+ hasNoContent = (_: number, _nodeData: TodoItemFlatNode) => _nodeData.item === '';
+
+ transformer = (node: TodoItemNode, level: number) => {
+ const existingNode = this.nestedNodeMap.get(node);
+ const flatNode = existingNode && existingNode.item === node.item
+ ? existingNode
+ : new TodoItemFlatNode();
+ flatNode.item = node.item;
+ flatNode.level = level;
+ flatNode.expandable = !!node.children;
+ this.flatNodeMap.set(flatNode, node);
+ this.nestedNodeMap.set(node, flatNode);
+ return flatNode;
+ }
+
+
+ ngOnInit() {
+ // const subject = this.dataSource._flattenedData;
+ // subject.subscribe((data) => {
+ // this.treeControl.expand(data[0]);
+ // this.showItem(data[0]);
+ // });
+ }
+
+ showItem(el) {
+ if (el) {
+ this.treeControl.expand(el);
+ this.selectedFolder = el;
+ const path = this.getPath(el);
+ this.path = [];
+ const data = {
+ flatNode: el,
+ element: this.flatNodeMap.get(el),
+ path: path.join('/')
+ };
+ this.showFolderContent.emit(data);
+ }
+ }
+
+ getPath(el) {
+ if (el) {
+ if (this.path.length === 0) {
+ this.path.unshift(el.item);
+ }
+ if (this.getParentNode(el) !== null) {
+ this.path.unshift(this.getParentNode(el).item);
+ this.getPath(this.getParentNode(el));
+ }
+ return this.path;
+ }
+ }
+
+ descendantsAllSelected(node: TodoItemFlatNode): boolean {
+ const descendants = this.treeControl.getDescendants(node);
+ const descAllSelected = descendants.every(child =>
+ this.checklistSelection.isSelected(child)
+ );
+ return descAllSelected;
+ }
+
+ descendantsPartiallySelected(node: TodoItemFlatNode): boolean {
+ const descendants = this.treeControl.getDescendants(node);
+ const result = descendants.some(child => this.checklistSelection.isSelected(child));
+ return result && !this.descendantsAllSelected(node);
+ }
+
+ todoItemSelectionToggle(node: TodoItemFlatNode): void {
+ this.checklistSelection.toggle(node);
+ const descendants = this.treeControl.getDescendants(node);
+ this.checklistSelection.isSelected(node)
+? this.checklistSelection.select(...descendants)
+ : this.checklistSelection.deselect(...descendants);
+
+ // Force update for the parent
+ descendants.every(child =>
+ this.checklistSelection.isSelected(child)
+);
+ this.checkAllParentsSelection(node);
+}
+
+ todoLeafItemSelectionToggle(node: TodoItemFlatNode): void {
+ this.checklistSelection.toggle(node);
+ this.checkAllParentsSelection(node);
+ }
+
+ checkAllParentsSelection(node: TodoItemFlatNode): void {
+ let parent: TodoItemFlatNode | null = this.getParentNode(node);
+ while (parent) {
+ this.checkRootNodeSelection(parent);
+ parent = this.getParentNode(parent);
+ }
+ }
+
+ checkRootNodeSelection(node: TodoItemFlatNode): void {
+ const nodeSelected = this.checklistSelection.isSelected(node);
+ const descendants = this.treeControl.getDescendants(node);
+ const descAllSelected = descendants.every(child =>
+ this.checklistSelection.isSelected(child)
+ );
+ if (nodeSelected && !descAllSelected) {
+ this.checklistSelection.deselect(node);
+ } else if (!nodeSelected && descAllSelected) {
+ this.checklistSelection.select(node);
+ }
+ }
+
+ getParentNode(node: TodoItemFlatNode): TodoItemFlatNode | null {
+ const currentLevel = this.getLevel(node);
+
+ if (currentLevel < 1) {
+ return null;
+ }
+
+ const startIndex = this.treeControl.dataNodes.indexOf(node) - 1;
+
+ for (let i = startIndex; i >= 0; i--) {
+ const currentNode = this.treeControl.dataNodes[i];
+
+ if (this.getLevel(currentNode) < currentLevel) {
+ return currentNode;
+ }
+ }
+ return null;
+ }
+
+ addNewItem(node: TodoItemFlatNode, file, isFile, path) {
+ const parentNode = this.flatNodeMap.get(node);
+ this.bucketBrowserService.insertItem(parentNode!, file, isFile);
+ this.treeControl.expand(node);
+ }
+
+ saveNode(node: TodoItemFlatNode, itemValue: string) {
+ const nestedNode = this.flatNodeMap.get(node);
+ this.bucketBrowserService.updateItem(nestedNode!, itemValue);
+ }
+}
diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/detail-dialog/detail-dialog.component.html b/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/detail-dialog/detail-dialog.component.html
index 4977b22..817ca22 100644
--- a/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/detail-dialog/detail-dialog.component.html
+++ b/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/detail-dialog/detail-dialog.component.html
@@ -60,14 +60,14 @@
class="strong">{{ notebook.password }}</span></p>
<p class="m-top-30">{{ DICTIONARY[PROVIDER].personal_storage }}: </p>
- <div class="links_block">
+ <div class="links_block" (click)="bucketBrowser('project', notebook.endpoint)">
<p *ngIf="DICTIONARY[PROVIDER].cloud_provider === 'azure' && notebook.account_name">{{ DICTIONARY[PROVIDER].account }}
<span class="bucket-info">{{ notebook.account_name}}</span></p>
<p *ngIf="notebook.bucket_name">{{ DICTIONARY[PROVIDER].container }} <span
class="bucket-info">{{ notebook.bucket_name }}</span></p>
</div>
<p>Shared endpoint bucket: </p>
- <div class="links_block">
+ <div class="links_block" (click)="bucketBrowser('endpoint', notebook.endpoint)">
<p *ngIf="DICTIONARY[PROVIDER].cloud_provider === 'azure' && notebook.shared_account_name">{{ DICTIONARY[PROVIDER].account }}
<span class="bucket-info">{{ notebook.shared_account_name}}</span></p>
<p *ngIf="notebook.shared_bucket_name">{{ DICTIONARY[PROVIDER].container }}
diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/detail-dialog/detail-dialog.component.scss b/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/detail-dialog/detail-dialog.component.scss
index 2849ce0..12be81b 100644
--- a/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/detail-dialog/detail-dialog.component.scss
+++ b/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/detail-dialog/detail-dialog.component.scss
@@ -86,4 +86,5 @@
padding-left: 7px;
font-weight: 600;
color: $blue-grey-color;
+ cursor: pointer;
}
diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/detail-dialog/detail-dialog.component.ts b/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/detail-dialog/detail-dialog.component.ts
index db097f3..ca264b9 100644
--- a/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/detail-dialog/detail-dialog.component.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/detail-dialog/detail-dialog.component.ts
@@ -20,12 +20,13 @@
import { Component, ViewChild, OnInit, Inject } from '@angular/core';
import { FormGroup, FormBuilder } from '@angular/forms';
import { ToastrService } from 'ngx-toastr';
-import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
+import {MatDialogRef, MAT_DIALOG_DATA, MatDialog} from '@angular/material/dialog';
import { DateUtils, CheckUtils } from '../../../core/util';
import { DICTIONARY } from '../../../../dictionary/global.dictionary';
import { DataengineConfigurationService } from '../../../core/services';
import { CLUSTER_CONFIGURATION } from '../../computational/computational-resource-create-dialog/cluster-configuration-templates';
+import {BucketBrowserComponent} from '../../bucket-browser/bucket-browser.component';
@Component({
selector: 'detail-dialog',
@@ -51,7 +52,8 @@ export class DetailDialogComponent implements OnInit {
private dataengineConfigurationService: DataengineConfigurationService,
private _fb: FormBuilder,
public dialogRef: MatDialogRef<DetailDialogComponent>,
- public toastr: ToastrService
+ public toastr: ToastrService,
+ private dialog: MatDialog,
) {
this.notebook = data;
}
@@ -120,4 +122,9 @@ export class DetailDialogComponent implements OnInit {
? (control.value && control.value !== null && CheckUtils.isJSON(control.value) ? null : { valid: false })
: null;
}
+
+ public bucketBrowser(type, endpoint): void {
+ this.dialog.open(BucketBrowserComponent, { data: {type: type, endpoint: endpoint}, panelClass: 'modal-fullscreen' })
+ .afterClosed().subscribe();
+ }
}
diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/resources.component.html b/services/self-service/src/main/resources/webapp/src/app/resources/resources.component.html
index 091ccb7..38e388e 100644
--- a/services/self-service/src/main/resources/webapp/src/app/resources/resources.component.html
+++ b/services/self-service/src/main/resources/webapp/src/app/resources/resources.component.html
@@ -46,6 +46,9 @@
</div>
<div>
+<!-- <button mat-raised-button class="butt butt-tool" (click)="bucketBrowser()">-->
+<!-- <i class="material-icons"></i>Bucket browser-->
+<!-- </button>-->
<button mat-raised-button class="butt butt-tool" (click)="manageUngit()">
<i class="material-icons"></i>Git credentials
</button>
diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/resources.component.ts b/services/self-service/src/main/resources/webapp/src/app/resources/resources.component.ts
index bab05a7..6394d89 100644
--- a/services/self-service/src/main/resources/webapp/src/app/resources/resources.component.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/resources/resources.component.ts
@@ -27,6 +27,7 @@ import { Exploratory } from './resources-grid/resources-grid.model';
import { HealthStatusService, ProjectService } from '../core/services';
import { ManageUngitComponent } from './manage-ungit/manage-ungit.component';
import { Project } from './../administration/project/project.component';
+import {BucketBrowserComponent} from './bucket-browser/bucket-browser.component';
@Component({
selector: 'dlab-resources',
@@ -79,6 +80,11 @@ export class ResourcesComponent implements OnInit {
.afterClosed().subscribe(() => this.refreshGrid());
}
+ public bucketBrowser(): void {
+ this.dialog.open(BucketBrowserComponent, { panelClass: 'modal-fullscreen' })
+ .afterClosed().subscribe(() => this.refreshGrid());
+ }
+
public setActiveProject(project): void {
this.resourcesGrid.selectActiveProject(project);
}
diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/resources.module.ts b/services/self-service/src/main/resources/webapp/src/app/resources/resources.module.ts
index 7ce335b..d2d5f02 100644
--- a/services/self-service/src/main/resources/webapp/src/app/resources/resources.module.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/resources/resources.module.ts
@@ -27,6 +27,9 @@ import { ResourcesGridModule } from './resources-grid';
import { ExploratoryEnvironmentCreateModule } from './exploratory/create-environment';
import { ManageUngitComponent } from './manage-ungit/manage-ungit.component';
import { ConfirmDeleteAccountDialog } from './manage-ungit/manage-ungit.component';
+import {BucketBrowserComponent} from './bucket-browser/bucket-browser.component';
+import {FolderTreeComponent} from './bucket-browser/folder-tree/folder-tree.component';
+import {MatTreeModule} from '@angular/material/tree';
@NgModule({
imports: [
@@ -35,14 +38,17 @@ import { ConfirmDeleteAccountDialog } from './manage-ungit/manage-ungit.componen
ReactiveFormsModule,
ResourcesGridModule,
ExploratoryEnvironmentCreateModule,
- MaterialModule
+ MaterialModule,
+ MatTreeModule
],
declarations: [
ResourcesComponent,
ManageUngitComponent,
- ConfirmDeleteAccountDialog
+ ConfirmDeleteAccountDialog,
+ BucketBrowserComponent,
+ FolderTreeComponent
],
- entryComponents: [ManageUngitComponent, ConfirmDeleteAccountDialog],
+ entryComponents: [ManageUngitComponent, ConfirmDeleteAccountDialog, BucketBrowserComponent, FolderTreeComponent],
exports: [ResourcesComponent]
})
export class ResourcesModule { }
diff --git a/services/self-service/src/main/resources/webapp/src/assets/styles/_dialogs.scss b/services/self-service/src/main/resources/webapp/src/assets/styles/_dialogs.scss
index c6f8fe8..c5ac335 100644
--- a/services/self-service/src/main/resources/webapp/src/assets/styles/_dialogs.scss
+++ b/services/self-service/src/main/resources/webapp/src/assets/styles/_dialogs.scss
@@ -59,6 +59,7 @@ mat-dialog-container {
padding: 0;
border-radius: 0;
overflow-x: hidden;
+ position: relative;
#dialog-box {
color: $modal-text-color;
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@dlab.apache.org
For additional commands, e-mail: commits-help@dlab.apache.org