This is an automated email from the ASF dual-hosted git repository.
liuxun pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/submarine.git
The following commit(s) were added to refs/heads/master by this push:
new e5bcea3 Submarine-463. [WEB]Implement database connection in project
page with Angular
e5bcea3 is described below
commit e5bcea382c74047e2503a4ba891eb19fed813b7c
Author: chiajoukuo <[email protected]>
AuthorDate: Mon May 18 16:03:26 2020 +0800
Submarine-463. [WEB]Implement database connection in project page with
Angular
### What is this PR for?
Implement database connection in project page with Angular
### What type of PR is it?
[Feature]
### Todos
### What is the Jira issue?
https://issues.apache.org/jira/browse/SUBMARINE-463
### How should this be tested?
https://travis-ci.org/github/apache/submarine/builds/688270358
### Screenshots (if appropriate)

### Questions:
* Does the licenses files need update? No
* Is there breaking changes for older versions? No
* Does this needs documentation? No
Author: chiajoukuo <[email protected]>
Author: chiajoukuo <[email protected]>
Closes #289 from chiajoukuo/SUBMARINE-463 and squashes the following
commits:
765005d [chiajoukuo] remove comment
56c2cb8 [chiajoukuo] Update
submarine-workbench/workbench-web-ng/src/app/services/project.service.ts
a1313b6 [chiajoukuo] fix IT
fd803a9 [chiajoukuo] fix IT
4fa2d4a [chiajoukuo] fix IT
c13d532 [chiajoukuo] fix IT
e826824 [chiajoukuo] fix IT
abe4c86 [chiajoukuo] fix IT
a343738 [chiajoukuo] SUBMARINE-463
f319ded [chiajoukuo] SUBMARINE-463
---
.../apache/submarine/integration/workspaceIT.java | 40 +++++-----
.../public-api.ts => interfaces/project.ts} | 25 ++++--
.../src/app/interfaces/public-api.ts | 1 +
.../new-project-page.component.html | 16 ++--
.../new-project-page/new-project-page.component.ts | 42 +++++++++-
.../workspace/project/project.component.html | 7 +-
.../workspace/project/project.component.ts | 85 +++++++++++++++++----
.../src/app/services/project.service.ts | 89 ++++++++++++++++++++++
.../src/app/services/public-api.ts | 1 +
9 files changed, 255 insertions(+), 51 deletions(-)
diff --git
a/submarine-test/test-e2e/src/test/java/org/apache/submarine/integration/workspaceIT.java
b/submarine-test/test-e2e/src/test/java/org/apache/submarine/integration/workspaceIT.java
index 73ce796..d18ad4d 100644
---
a/submarine-test/test-e2e/src/test/java/org/apache/submarine/integration/workspaceIT.java
+++
b/submarine-test/test-e2e/src/test/java/org/apache/submarine/integration/workspaceIT.java
@@ -20,6 +20,8 @@ package org.apache.submarine.integration;
import org.apache.submarine.AbstractSubmarineIT;
import org.apache.submarine.WebDriverManager;
import org.apache.submarine.SubmarineITUtils;
+import org.openqa.selenium.support.ui.ExpectedConditions;
+import org.openqa.selenium.support.ui.WebDriverWait;
import org.openqa.selenium.By;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -56,8 +58,28 @@ public class workspaceIT extends AbstractSubmarineIT {
pollingWait(By.xpath("//span[contains(text(), \"Workspace\")]"),
MAX_BROWSER_TIMEOUT_SEC).click();
Assert.assertEquals(driver.getCurrentUrl(),
"http://localhost:8080/workbench/workspace");
+ WebDriverWait wait = new WebDriverWait( driver, 60);
+
wait.until(ExpectedConditions.visibilityOfElementLocated(By.xpath("//li[contains(text(),
\"Release\")]")));
+
+ //Test release part
+ pollingWait(By.xpath("//li[contains(text(), \"Release\")]"),
MAX_BROWSER_TIMEOUT_SEC).click();
+
Assert.assertEquals(pollingWait(By.xpath("//nz-table[@id='releaseTable']"),
MAX_BROWSER_TIMEOUT_SEC).isDisplayed(), true);
+
+ //Test training part
+ pollingWait(By.xpath("//li[contains(text(), \"Training\")]"),
MAX_BROWSER_TIMEOUT_SEC).click();
+ Assert.assertEquals(pollingWait(By.xpath("//div[@id='trainingDiv']"),
MAX_BROWSER_TIMEOUT_SEC).isDisplayed(), true);
+
+ //Test team part
+ pollingWait(By.xpath("//li[contains(text(), \"Team\")]"),
MAX_BROWSER_TIMEOUT_SEC).click();
+ Assert.assertEquals(pollingWait(By.xpath("//div[@id='teamDiv']"),
MAX_BROWSER_TIMEOUT_SEC).isDisplayed(), true);
+
+ // shared part
+ pollingWait(By.xpath("//li[contains(text(), \"Shared\")]"),
MAX_BROWSER_TIMEOUT_SEC).click();
+ Assert.assertEquals(pollingWait(By.xpath("//nz-table[@id='sharedTable']"),
MAX_BROWSER_TIMEOUT_SEC).isDisplayed(), true);
+
//Test project part
pollingWait(By.xpath("//li[contains(text(), \"Project\")]"),
MAX_BROWSER_TIMEOUT_SEC).click();
+
wait.until(ExpectedConditions.visibilityOfElementLocated(By.xpath("//div[@id='addProjectbtn']")));
Assert.assertEquals(pollingWait(By.xpath("//div[@id='addProjectbtn']"),
MAX_BROWSER_TIMEOUT_SEC).isDisplayed(), true);
pollingWait(By.xpath("//div[@id='addProjectbtn']/button"),
MAX_BROWSER_TIMEOUT_SEC).click();
//step1
@@ -74,23 +96,7 @@ public class workspaceIT extends AbstractSubmarineIT {
//return to project page
Assert.assertEquals(pollingWait(By.xpath("//div[@id='addProjectbtn']"),
MAX_BROWSER_TIMEOUT_SEC).isDisplayed(), true);
-
- //Test release part
- pollingWait(By.xpath("//li[contains(text(), \"Release\")]"),
MAX_BROWSER_TIMEOUT_SEC).click();
-
Assert.assertEquals(pollingWait(By.xpath("//nz-table[@id='releaseTable']"),
MAX_BROWSER_TIMEOUT_SEC).isDisplayed(), true);
-
- //Test training part
- pollingWait(By.xpath("//li[contains(text(), \"Training\")]"),
MAX_BROWSER_TIMEOUT_SEC).click();
- Assert.assertEquals(pollingWait(By.xpath("//div[@id='trainingDiv']"),
MAX_BROWSER_TIMEOUT_SEC).isDisplayed(), true);
-
- //Test team part
- pollingWait(By.xpath("//li[contains(text(), \"Team\")]"),
MAX_BROWSER_TIMEOUT_SEC).click();
- Assert.assertEquals(pollingWait(By.xpath("//div[@id='teamDiv']"),
MAX_BROWSER_TIMEOUT_SEC).isDisplayed(), true);
-
-
- // shared part
- pollingWait(By.xpath("//li[contains(text(), \"Shared\")]"),
MAX_BROWSER_TIMEOUT_SEC).click();
- Assert.assertEquals(pollingWait(By.xpath("//nz-table[@id='sharedTable']"),
MAX_BROWSER_TIMEOUT_SEC).isDisplayed(), true);
+
}
}
diff --git
a/submarine-workbench/workbench-web-ng/src/app/services/public-api.ts
b/submarine-workbench/workbench-web-ng/src/app/interfaces/project.ts
similarity index 65%
copy from submarine-workbench/workbench-web-ng/src/app/services/public-api.ts
copy to submarine-workbench/workbench-web-ng/src/app/interfaces/project.ts
index 3851356..7a1e250 100644
--- a/submarine-workbench/workbench-web-ng/src/app/services/public-api.ts
+++ b/submarine-workbench/workbench-web-ng/src/app/interfaces/project.ts
@@ -17,10 +17,21 @@
* under the License.
*/
-export * from './auth.service';
-export * from './base-api.service';
-export * from './department.service';
-export * from './local-storage.service';
-export * from './system-utils.service';
-export * from './user.service';
-export * from './team.service';
+import { BaseEntity } from './base-entity';
+
+export interface Project extends BaseEntity{
+ name: string;
+ description: string;
+ tags: string[];
+ inputTagVisibility: boolean;
+ projectInputTag: string;
+ starNum: number;
+ likeNum: number;
+ messageNum: number;
+ permission: string;
+ projectFilesList: string[];
+ teamName: string;
+ type: string;
+ userName: string;
+ visibility: string;
+ }
diff --git
a/submarine-workbench/workbench-web-ng/src/app/interfaces/public-api.ts
b/submarine-workbench/workbench-web-ng/src/app/interfaces/public-api.ts
index 9aed87d..5b4738c 100644
--- a/submarine-workbench/workbench-web-ng/src/app/interfaces/public-api.ts
+++ b/submarine-workbench/workbench-web-ng/src/app/interfaces/public-api.ts
@@ -24,3 +24,4 @@ export * from './rest';
export * from './action';
export * from './sys-user';
export * from './sys-team';
+export * from './project';
diff --git
a/submarine-workbench/workbench-web-ng/src/app/pages/workbench/workspace/project/new-project-page/new-project-page.component.html
b/submarine-workbench/workbench-web-ng/src/app/pages/workbench/workspace/project/new-project-page/new-project-page.component.html
index 6a8ffbd..97f04be 100644
---
a/submarine-workbench/workbench-web-ng/src/app/pages/workbench/workspace/project/new-project-page/new-project-page.component.html
+++
b/submarine-workbench/workbench-web-ng/src/app/pages/workbench/workspace/project/new-project-page/new-project-page.component.html
@@ -46,24 +46,24 @@
<div>
<label class="form-label"><span class="red-star">*
</span>Visibility:</label>
<nz-radio-group name="visibility"
[(ngModel)]="newProjectContent.visibility">
- <label class="darkLabel" nz-radio nzValue="Private">Private - Only
project collaborators can view or edit the
+ <label class="darkLabel" nz-radio
nzValue="PROJECT_VISIBILITY_PRIVATE">Private - Only project collaborators can
view or edit the
project.</label>
- <label class="darkLabel" nz-radio nzValue="Team">Team - All members
of the team can view the project.</label>
- <div *ngIf="newProjectContent.visibility==='Team'">
+ <label class="darkLabel" nz-radio
nzValue="PROJECT_VISIBILITY_TEAM">Team - All members of the team can view the
project.</label>
+ <div
*ngIf="newProjectContent.visibility==='PROJECT_VISIBILITY_TEAM'">
<nz-select [(ngModel)]="newProjectContent.team" name="team"
nzAllowClear nzPlaceHolder="Choose" style="margin-top: 10px;">
<nz-option *ngFor="let team of teams" [nzValue]="team"
[nzLabel]="team"></nz-option>
</nz-select>
</div>
- <label class="darkLabel" nz-radio nzValue="Public">Public - All
authenticated users can view the
+ <label class="darkLabel" nz-radio
nzValue="PROJECT_VISIBILITY_PUBLIC">Public - All authenticated users can view
the
project.</label>
</nz-radio-group>
</div>
- <div
*ngIf="newProjectContent.visibility==='Team'||newProjectContent.visibility==='Public'">
+ <div
*ngIf="newProjectContent.visibility==='PROJECT_VISIBILITY_TEAM'||newProjectContent.visibility==='PROJECT_VISIBILITY_PUBLIC'">
<label class="form-label"><span class="red-star">*
</span>Permission:</label>
<nz-radio-group name="permission"
[(ngModel)]="newProjectContent.permission">
- <label class="darkLabel" nz-radio nzValue="View">Can View - All
members can view the project.</label>
- <label class="darkLabel" nz-radio nzValue="Edit">Can Edit - All
members can edit the project.</label>
- <label class="darkLabel" nz-radio nzValue="Execute">Can Execute -
All members can execute the project.</label>
+ <label class="darkLabel" nz-radio
nzValue="PROJECT_PERMISSION_VIEW">Can View - All members can view the
project.</label>
+ <label class="darkLabel" nz-radio
nzValue="PROJECT_PERMISSION_EDIT">Can Edit - All members can edit the
project.</label>
+ <label class="darkLabel" nz-radio
nzValue="PROJECT_PERMISSION_EXECUTE">Can Execute - All members can execute the
project.</label>
</nz-radio-group>
</div>
</form>
diff --git
a/submarine-workbench/workbench-web-ng/src/app/pages/workbench/workspace/project/new-project-page/new-project-page.component.ts
b/submarine-workbench/workbench-web-ng/src/app/pages/workbench/workspace/project/new-project-page/new-project-page.component.ts
index cab477a..39409c9 100644
---
a/submarine-workbench/workbench-web-ng/src/app/pages/workbench/workspace/project/new-project-page/new-project-page.component.ts
+++
b/submarine-workbench/workbench-web-ng/src/app/pages/workbench/workspace/project/new-project-page/new-project-page.component.ts
@@ -20,6 +20,20 @@
import { Component, OnInit, ViewChild, Output, EventEmitter, Input } from
'@angular/core';
import { NgForm } from '@angular/forms';
import { NzMessageService } from 'ng-zorro-antd';
+import { UserService, ProjectService } from '@submarine/services';
+
+interface AddProjectParams {
+ name: string;
+ userName: string;
+ description: string;
+ type: string;
+ teamName: string;
+ visibility: string;
+ permission: string;
+ starNum: number;
+ likeNum: number;
+ messageNum: number;
+}
@Component({
@@ -29,6 +43,7 @@ import { NzMessageService } from 'ng-zorro-antd';
})
export class NewProjectPageComponent implements OnInit {
@Output() closeProjectPage = new EventEmitter<boolean>();
+ @Output() addProject = new EventEmitter<AddProjectParams>();
@ViewChild('f', { static: true }) signupForm: NgForm;
//TODO(jasoonn): get team from API
teams = ['ciil'];
@@ -37,6 +52,7 @@ export class NewProjectPageComponent implements OnInit {
initialState=0;
templateType="Python";
+ username = '';
newProjectContent = { projectName: '', description: '', visibility:
'Private', team: '' ,permission: 'View', files: []};
@@ -49,9 +65,16 @@ export class NewProjectPageComponent implements OnInit {
];
- constructor(private msg: NzMessageService) { }
+ constructor(
+ private msg: NzMessageService,
+ private projectService: ProjectService,
+ private userService: UserService
+ ) { }
ngOnInit() {
+ this.userService.fetchUserInfo().subscribe((data) => {
+ this.username = data.username;
+ })
}
handleChange({ file, fileList }): void {
@@ -84,10 +107,21 @@ export class NewProjectPageComponent implements OnInit {
}
- //TODO(jasoonn): Add the new project
done(): void{
- console.log(this.newProjectContent);
- this.clearProject();
+ var project = {
+ name: this.newProjectContent.projectName,
+ userName: this.username,
+ description: this.newProjectContent.description,
+ type: 'PROJECT_TYPE_NOTEBOOK',
+ teamName: this.newProjectContent.team,
+ visibility: this.newProjectContent.visibility,
+ permission: this.newProjectContent.permission,
+ starNum: 0,
+ likeNum: 0,
+ messageNum: 0
+ }
+ console.log(project)
+ this.addProject.emit(project);
}
//TODO(jasoonn): open in notebook
diff --git
a/submarine-workbench/workbench-web-ng/src/app/pages/workbench/workspace/project/project.component.html
b/submarine-workbench/workbench-web-ng/src/app/pages/workbench/workspace/project/project.component.html
index 1aaa9b7..58b4d88 100644
---
a/submarine-workbench/workbench-web-ng/src/app/pages/workbench/workspace/project/project.component.html
+++
b/submarine-workbench/workbench-web-ng/src/app/pages/workbench/workspace/project/project.component.html
@@ -18,7 +18,9 @@
-->
<br>
-<div *ngIf="!newProject">
+<nz-spin *ngIf="!newProject && isLoading" style="height: 50px;">
+</nz-spin>
+<div *ngIf="!newProject && !isLoading">
<div id="addProjectbtn">
<button nz-button nzBlock (click)="newProject=true">+ Add New
Project</button>
</div>
@@ -72,6 +74,7 @@
</div>
<div *ngIf="newProject" id="projectNewDivOuter">
<div id="projectNewDivInner">
- <app-new-project-page
(closeProjectPage)="newProject=false;"></app-new-project-page>
+ <app-new-project-page
(addProject)="addProject($event)"></app-new-project-page>
+ <!-- <app-new-project-page
(closeProjectPage)="newProject=false;"></app-new-project-page> -->
</div>
</div>
diff --git
a/submarine-workbench/workbench-web-ng/src/app/pages/workbench/workspace/project/project.component.ts
b/submarine-workbench/workbench-web-ng/src/app/pages/workbench/workspace/project/project.component.ts
index 02a6210..6788c29 100644
---
a/submarine-workbench/workbench-web-ng/src/app/pages/workbench/workspace/project/project.component.ts
+++
b/submarine-workbench/workbench-web-ng/src/app/pages/workbench/workspace/project/project.component.ts
@@ -18,6 +18,11 @@
*/
import { Component, OnInit, ViewChild, ElementRef } from '@angular/core';
+import { UserService, ProjectService } from '@submarine/services';
+import { UserInfo } from '@submarine/interfaces';
+import { NzNotificationService } from 'ng-zorro-antd';
+import { tap } from 'rxjs/operators';
+import { Observable } from 'rxjs';
@Component({
selector: 'app-project',
@@ -27,26 +32,78 @@ import { Component, OnInit, ViewChild, ElementRef } from
'@angular/core';
export class ProjectComponent implements OnInit {
newProject = false;
existProjects = [];
-
+ isLoading = false;
+ username = '';
@ViewChild('inputElement', { static: false }) inputElement: ElementRef;
+ userInfo$: Observable<UserInfo>;
+
+ constructor(
+ private projectService: ProjectService,
+ private userService: UserService,
+ private nzNotificationService: NzNotificationService
+ ) {
- constructor() { }
+ }
//TODO(jasoonn): get projects data from server
- ngOnInit() {
- this.existProjects.push({
- projectName: 'projectName0', description: 'description', tags: ['12',
'Tag 2'], inputTagVisibility: false, projectInputTag: '', starNum:0, likeNum:0,
msgNum:0
- });
- this.existProjects.push({
- projectName: 'projectName1', description: 'description', tags:
['Unremovable', 'Tag 2'], inputTagVisibility: false, projectInputTag: '',
starNum:0, likeNum:0, msgNum:0
- });
- this.existProjects.push({
- projectName: 'projectName1', description: 'description', tags:
['Unremovable', 'Tag 2', 'Tag 3'], inputTagVisibility: false, projectInputTag:
'', starNum:0, likeNum:0, msgNum:0
+ async ngOnInit() {
+ await this.userService.fetchUserInfo().toPromise().then(data => {
+ this.username = data.username;
});
+ //TODO(chiajoukuo): add pagination
+ var params = {
+ userName: this.username,
+ column: 'update_time',
+ order: 'desc',
+ pageNo: ''+1,//this.pagination.current,
+ pageSize: ''+99//this.pagination.pageSize
+ }
+ var res;
+ this.projectService.fetchProjectList(params)
+ .subscribe(
+ (data) => {
+ res = data['records']
+ for(var i=0; i<res.length; i++){
+ this.existProjects.push({
+ projectName: res[i].name,
+ description: res[i].description,
+ tags: res[i].tags ===null?[]:res[i].tags, //['12', 'Tag 2']
+ inputTagVisibility: false,
+ projectInputTag: '',
+ starNum: res[i].starNum,
+ likeNum: res[i].likeNum,
+ msgNum: res[i].messageNum
+ })
+ }
+ },
+ error => {
+ console.log("ERROR", error)
+ }
+ );
+
+ }
+
+ addProject(event){
this.existProjects.push({
- projectName: 'projectName1', description: 'description', tags:
['Unremovable', 'Tag 2', 'Tag 3'], inputTagVisibility: false, projectInputTag:
'', starNum:0, likeNum:0, msgNum:0
+ projectName: event.name,
+ description: event.description,
+ tags: [],
+ inputTagVisibility: false,
+ projectInputTag: '',
+ starNum: 0,
+ likeNum: 0,
+ msgNum: 0
})
+
+ this.projectService.addProject(event).subscribe(() => {
+ }, err => {
+ console.log("ERROR", err)
+ });
+ this.newProject = false;
+ console.log("proj", event);
}
+
+
//TODO(jasoonn): Update tag in server
handleCloseTag(project, tag){
project.tags = project.tags.filter(itag => itag!==tag);
@@ -55,7 +112,9 @@ export class ProjectComponent implements OnInit {
}
//TODO(jasoonn): update tag in server
handleInputConfirm(project): void {
- if (project.projectInputTag &&
project.tags.indexOf(project.projectInputTag) === -1) {
+ console.log(project.tags);
+ if (project.projectInputTag && (project.tags == null || (project.tags !=
null && project.tags.indexOf(project.projectInputTag)=== -1))) {
+ console.log(project);
project.tags = [...project.tags, project.projectInputTag];
}
project.inputTagVisibility = false;
diff --git
a/submarine-workbench/workbench-web-ng/src/app/services/project.service.ts
b/submarine-workbench/workbench-web-ng/src/app/services/project.service.ts
new file mode 100644
index 0000000..6ac330c
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/src/app/services/project.service.ts
@@ -0,0 +1,89 @@
+/*
+ * 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 { HttpClient } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { ListResult, Rest, Project } from '@submarine/interfaces';
+import { of, Observable } from 'rxjs';
+import { switchMap } from 'rxjs/operators';
+import { BaseApiService } from './base-api.service';
+
+interface ProjectQueryParams {
+ userName: string;
+ column: string;
+ order: string;
+ pageNo: string;
+ pageSize: string;
+}
+interface AddProjectParams {
+ name: string;
+ userName: string;
+ description: string;
+ type: string;
+ teamName: string;
+ visibility: string;
+ permission: string;
+ starNum: number;
+ likeNum: number;
+ messageNum: number;
+}
+
+@Injectable({
+ providedIn: 'root'
+})
+export class ProjectService {
+
+ constructor(
+ private baseApi: BaseApiService,
+ private httpClient: HttpClient
+ ) {
+
+ }
+
+ fetchProjectList(queryParams: Partial<ProjectQueryParams>):
Observable<ListResult<Project>> {
+ const apiUrl = this.baseApi.getRestApi('/project/list');
+ console.log(apiUrl)
+ return this.httpClient.get<Rest<ListResult<Project>>>(apiUrl, {params:
queryParams})
+ .pipe(
+ switchMap(res => {
+ if (res.success) {
+ return of(res.result);
+ } else {
+ throw this.baseApi.createRequestError(res.message, res.code, apiUrl,
'get', queryParams);
+ }
+ })
+ );;
+
+ }
+
+ addProject(params: Partial<AddProjectParams>): Observable<Project> {
+ console.log("addProject", params)
+ const apiUrl = this.baseApi.getRestApi('/project/add');
+ return this.httpClient.post<Rest<Project>>(apiUrl, params)
+ .pipe(
+ switchMap(res => {
+ if (res.success) {
+ return of(res.result);
+ } else {
+ throw this.baseApi.createRequestError(res.message, res.code, apiUrl,
'post', params);
+ }
+ })
+ );
+ }
+}
diff --git
a/submarine-workbench/workbench-web-ng/src/app/services/public-api.ts
b/submarine-workbench/workbench-web-ng/src/app/services/public-api.ts
index 3851356..5ab721b 100644
--- a/submarine-workbench/workbench-web-ng/src/app/services/public-api.ts
+++ b/submarine-workbench/workbench-web-ng/src/app/services/public-api.ts
@@ -18,6 +18,7 @@
*/
export * from './auth.service';
+export * from './project.service';
export * from './base-api.service';
export * from './department.service';
export * from './local-storage.service';
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]