This is an automated email from the ASF dual-hosted git repository.
pingsutw 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 ade9311 SUBMARINE-700. [WEB] Refactor notebook page of Workbench
ade9311 is described below
commit ade9311f74abf41ed8867e1e76e4ee76ca24c0e1
Author: kobe860219 <[email protected]>
AuthorDate: Fri Jan 29 17:55:25 2021 +0800
SUBMARINE-700. [WEB] Refactor notebook page of Workbench
### What is this PR for?
Refactor the architecture of notebook component in workbench. The new
architecture is as below :

### What type of PR is it?
[Refactoring]
### Todos
### What is the Jira issue?
https://issues.apache.org/jira/browse/SUBMARINE-700
### How should this be tested?
https://travis-ci.org/github/kobe860219/submarine/builds/755191807
### 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: kobe860219 <[email protected]>
Signed-off-by: Kevin <[email protected]>
Closes #494 from kobe860219/SUBMARINE-700 and squashes the following
commits:
24e25a8 [kobe860219] Fix test failure and update image
981224f [kobe860219] Prettier code
8189a95 [kobe860219] Fix interval and prettier code
d50ae4a [kobe860219] Add popconfirm
5fd746f [kobe860219] Fix architecture and form
a648e6a [kobe860219] Remove unuse css
d4e4aa3 [kobe860219] SUBMARINE-700. [WEB] Refactor notebook page of
Workbench
---
docs/assets/created-notebook.png | Bin 71556 -> 219016 bytes
docs/assets/notebook-form.png | Bin 68260 -> 241529 bytes
docs/assets/notebook-list.png | Bin 65219 -> 189814 bytes
.../apache/submarine/integration/notebookIT.java | 6 +-
.../{ => notebook-interfaces}/notebook-info.ts | 5 +-
.../{ => notebook-interfaces}/notebook-spec.ts | 0
.../notebook-form/notebook-form.component.html | 112 ++++++++
.../notebook-form/notebook-form.component.scss} | 20 +-
.../notebook-form/notebook-form.component.ts} | 245 +++++------------
.../notebook-home/notebook-home.component.html | 38 +++
.../notebook-home/notebook-home.component.scss} | 16 +-
.../notebook-home/notebook-home.component.ts | 86 ++++++
.../notebook-list/notebook-list.component.html | 77 ++++++
.../notebook-list/notebook-list.component.scss} | 16 +-
.../notebook-list/notebook-list.component.ts | 52 ++++
...tebook.module.ts => notebook-routing.module.ts} | 27 +-
.../workbench/notebook/notebook.component.html | 226 +--------------
.../workbench/notebook/notebook.component.scss | 49 ----
.../pages/workbench/notebook/notebook.component.ts | 304 +--------------------
.../pages/workbench/notebook/notebook.module.ts | 24 +-
.../pages/workbench/workbench-routing.module.ts | 3 +-
.../src/app/pages/workbench/workbench.module.ts | 14 +-
.../{ => notebook-services}/notebook.service.ts | 17 +-
.../workbench-web/src/app/services/polling.ts | 105 -------
24 files changed, 520 insertions(+), 922 deletions(-)
diff --git a/docs/assets/created-notebook.png b/docs/assets/created-notebook.png
index 6d08f3f..d903361 100644
Binary files a/docs/assets/created-notebook.png and
b/docs/assets/created-notebook.png differ
diff --git a/docs/assets/notebook-form.png b/docs/assets/notebook-form.png
index 04208ad..a95310f 100644
Binary files a/docs/assets/notebook-form.png and
b/docs/assets/notebook-form.png differ
diff --git a/docs/assets/notebook-list.png b/docs/assets/notebook-list.png
index 6a90526..aaceded 100644
Binary files a/docs/assets/notebook-list.png and
b/docs/assets/notebook-list.png differ
diff --git
a/submarine-test/test-e2e/src/test/java/org/apache/submarine/integration/notebookIT.java
b/submarine-test/test-e2e/src/test/java/org/apache/submarine/integration/notebookIT.java
index ff5290a..2bb1537 100644
---
a/submarine-test/test-e2e/src/test/java/org/apache/submarine/integration/notebookIT.java
+++
b/submarine-test/test-e2e/src/test/java/org/apache/submarine/integration/notebookIT.java
@@ -58,7 +58,7 @@ public class notebookIT extends AbstractSubmarineIT {
// Test for creating new notebook
LOG.info("Create Notebook Test");
- pollingWait(By.xpath("//button[@id='btnNewNotebook']"),
MAX_BROWSER_TIMEOUT_SEC).click();
+ pollingWait(By.xpath("//button[@id='btn-newNotebook']"),
MAX_BROWSER_TIMEOUT_SEC).click();
pollingWait(By.cssSelector("input[ng-reflect-name='notebookName']"),
MAX_BROWSER_TIMEOUT_SEC).sendKeys("test-nb");
pollingWait(By.cssSelector("input[ng-reflect-name='cpus']"),
MAX_BROWSER_TIMEOUT_SEC).clear();
pollingWait(By.cssSelector("input[ng-reflect-name='cpus']"),
MAX_BROWSER_TIMEOUT_SEC).sendKeys("2");
@@ -68,12 +68,12 @@ public class notebookIT extends AbstractSubmarineIT {
pollingWait(By.xpath("//button[@id='envVar-btn']"),
MAX_BROWSER_TIMEOUT_SEC).click();
pollingWait(By.xpath("//input[@name='key0']"),
MAX_BROWSER_TIMEOUT_SEC).sendKeys("testKey0");
pollingWait(By.xpath("//input[@name='value0']"),
MAX_BROWSER_TIMEOUT_SEC).sendKeys("testValue0");
- pollingWait(By.xpath("//button[@id='create-btn']"),
MAX_BROWSER_TIMEOUT_SEC).click();
+ pollingWait(By.xpath("//button[@id='nb-form-btn-create']"),
MAX_BROWSER_TIMEOUT_SEC).click();
/*
Future add k8s test.
Assert.assertEquals(pollingWait(By.xpath("//td[contains(., 'test-nb')]"),
MAX_BROWSER_TIMEOUT_SEC).isDisplayed(), true);
*/
-
Assert.assertEquals(pollingWait(By.xpath("//button[@id='btnNewNotebook']"),
MAX_BROWSER_TIMEOUT_SEC).isDisplayed(), true);
+
Assert.assertEquals(pollingWait(By.xpath("//button[@id='btn-newNotebook']"),
MAX_BROWSER_TIMEOUT_SEC).isDisplayed(), true);
LOG.info("Test Success!");
}
diff --git
a/submarine-workbench/workbench-web/src/app/interfaces/notebook-info.ts
b/submarine-workbench/workbench-web/src/app/interfaces/notebook-interfaces/notebook-info.ts
similarity index 87%
copy from submarine-workbench/workbench-web/src/app/interfaces/notebook-info.ts
copy to
submarine-workbench/workbench-web/src/app/interfaces/notebook-interfaces/notebook-info.ts
index 91a8d21..128f7a4 100644
--- a/submarine-workbench/workbench-web/src/app/interfaces/notebook-info.ts
+++
b/submarine-workbench/workbench-web/src/app/interfaces/notebook-interfaces/notebook-info.ts
@@ -18,14 +18,15 @@
*/
import { Url } from 'url';
-import { NotebookSpec } from '@submarine/interfaces/notebook-spec';
+import { NotebookSpec } from
'@submarine/interfaces/notebook-interfaces/notebook-spec';
-export interface Notebook {
+export interface NotebookInfo {
notebookId: string;
name: string;
uid: string;
url: Url;
status: string;
+ reason: string;
createdTime: string;
deletedTime: string;
spec: NotebookSpec;
diff --git
a/submarine-workbench/workbench-web/src/app/interfaces/notebook-spec.ts
b/submarine-workbench/workbench-web/src/app/interfaces/notebook-interfaces/notebook-spec.ts
similarity index 100%
rename from
submarine-workbench/workbench-web/src/app/interfaces/notebook-spec.ts
rename to
submarine-workbench/workbench-web/src/app/interfaces/notebook-interfaces/notebook-spec.ts
diff --git
a/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook-home/notebook-form/notebook-form.component.html
b/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook-home/notebook-form/notebook-form.component.html
new file mode 100644
index 0000000..141200a
--- /dev/null
+++
b/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook-home/notebook-form/notebook-form.component.html
@@ -0,0 +1,112 @@
+<!--
+ ~ 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.
+ -->
+
+<nz-modal [(nzVisible)]="isVisible" (nzOnCancel)="isVisible = false"
nzTitle="Create Notebook" [nzWidth]="700">
+ <div *nzModalFooter>
+ <button nz-button id="nb-form-btn-cancel" nzType="default"
(click)="isVisible = false">Cancel</button>
+ <button nz-button id="nb-form-btn-create" nzType="primary"
[disabled]="checkStatus()" (click)="submitForm()">
+ Create
+ </button>
+ </div>
+ <form [formGroup]="notebookForm">
+ <nz-form-item>
+ <nz-form-label [nzSm]="6" [nzXs]="24" nzRequired
nzFor="notebookName">Notebook Name</nz-form-label>
+ <nz-form-control [nzSm]="14" [nzXs]="24">
+ <input nz-input required type="text" name="notebookName"
id="notebookName" formControlName="notebookName" />
+ </nz-form-control>
+ </nz-form-item>
+ <nz-form-item>
+ <nz-form-label [nzSm]="6" [nzXs]="24" nzRequired
nzFor="environment">Environment</nz-form-label>
+ <nz-form-control [nzSm]="14" [nzXs]="24">
+ <nz-select required name="select-envName" formControlName="envName">
+ <nz-option
+ *ngFor="let env of envNameList; let i; of: index"
+ id="env{{ i }}"
+ [nzValue]="env"
+ [nzLabel]="env"
+ ></nz-option>
+ </nz-select>
+ </nz-form-control>
+ </nz-form-item>
+ <nz-form-item>
+ <nz-form-label [nzSm]="6" [nzXs]="24" nzRequired
nzFor="cpus">CPU</nz-form-label>
+ <nz-form-control [nzSm]="14" [nzXs]="24">
+ <input nz-input min="0" required type="number" name="cpus" id="cpus"
formControlName="cpus" />
+ </nz-form-control>
+ </nz-form-item>
+ <nz-form-item>
+ <nz-form-label [nzSm]="6" [nzXs]="24" nzRequired
nzFor="gpus">GPU</nz-form-label>
+ <nz-form-control [nzSm]="14" [nzXs]="24">
+ <input nz-input min="0" type="number" name="gpus" id="gpus"
formControlName="gpus" />
+ </nz-form-control>
+ </nz-form-item>
+ <nz-form-item>
+ <nz-form-label [nzSm]="6" [nzXs]="24" nzRequired
nzFor="memoryNum">Memory</nz-form-label>
+ <nz-form-control [nzSm]="14" [nzXs]="24">
+ <div nz-col nzSpan="6">
+ <input nz-input required name="memoryNum" placeholder="EX:1024"
formControlName="memoryNum" />
+ </div>
+ <div nz-col nzSpan="6" style="margin-left: 5px">
+ <nz-select formControlName="unit">
+ <nz-option *ngFor="let unit of MEMORY_UNITS" [nzValue]="unit"
[nzLabel]="unit"></nz-option>
+ </nz-select>
+ </div>
+ </nz-form-control>
+ </nz-form-item>
+ <div formArrayName="envVars">
+ <ng-container *ngFor="let envVar of envVars.controls; index as i">
+ <nz-form-item>
+ <nz-form-label nzRequired [nzSm]="6" [nzXs]="24">EnvVar{{ i + 1
}}</nz-form-label>
+ <div [formGroupName]="i">
+ <div nz-col nzSpan="12">
+ <input
+ style="width: 30%"
+ nz-input
+ required
+ id="key{{ i }}"
+ name="key{{ i }}"
+ placeholder="Key"
+ formControlName="key"
+ />
+ <input
+ style="width: 60%; margin-left: 10px"
+ nz-input
+ required
+ id="value{{ i }}"
+ name="value{{ i }}"
+ placeholder="Value"
+ formControlName="value"
+ />
+ <i
+ nz-icon
+ style="margin-left: 5px"
+ nzType="close-circle"
+ nzTheme="fill"
+ (click)="deleteItem(envVars, i)"
+ ></i>
+ </div>
+ </div>
+ </nz-form-item>
+ </ng-container>
+ </div>
+ <button nz-button style="display: block; margin: auto" id="envVar-btn"
type="default" (click)="onCreateEnvVar()">
+ New EnvVar
+ </button>
+ </form>
+</nz-modal>
diff --git
a/submarine-workbench/workbench-web/src/app/interfaces/notebook-info.ts
b/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook-home/notebook-form/notebook-form.component.scss
similarity index 74%
copy from submarine-workbench/workbench-web/src/app/interfaces/notebook-info.ts
copy to
submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook-home/notebook-form/notebook-form.component.scss
index 91a8d21..9daa273 100644
--- a/submarine-workbench/workbench-web/src/app/interfaces/notebook-info.ts
+++
b/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook-home/notebook-form/notebook-form.component.scss
@@ -1,4 +1,4 @@
-/*
+/*!
* 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
@@ -17,16 +17,8 @@
* under the License.
*/
-import { Url } from 'url';
-import { NotebookSpec } from '@submarine/interfaces/notebook-spec';
-
-export interface Notebook {
- notebookId: string;
- name: string;
- uid: string;
- url: Url;
- status: string;
- createdTime: string;
- deletedTime: string;
- spec: NotebookSpec;
-}
+.form-btn {
+ margin: 10px;
+ width : 140px;
+ height: 50px
+}
\ No newline at end of file
diff --git
a/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook.component.ts
b/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook-home/notebook-form/notebook-form.component.ts
similarity index 54%
copy from
submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook.component.ts
copy to
submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook-home/notebook-form/notebook-form.component.ts
index e0d1533..ef230ed 100644
---
a/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook.component.ts
+++
b/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook-home/notebook-form/notebook-form.component.ts
@@ -19,93 +19,67 @@
import { Component, OnInit } from '@angular/core';
import { FormArray, FormControl, FormGroup, Validators, FormBuilder } from
'@angular/forms';
-import { NotebookService } from '@submarine/services/notebook.service';
-import { NzMessageService } from 'ng-zorro-antd/message';
-import { EnvironmentService } from '@submarine/services/environment.service';
import { ExperimentValidatorService } from
'@submarine/services/experiment.validator.service';
+import { EnvironmentService } from '@submarine/services/environment.service';
+import { NotebookService } from
'@submarine/services/notebook-services/notebook.service';
import { UserService } from '@submarine/services/user.service';
-import { nullSafeIsEquivalent } from '@angular/compiler/src/output/output_ast';
-import { Subscription } from 'rxjs';
-import { ExponentialBackoff } from '@submarine/services/polling';
-import { isEqual } from "lodash";
-import { NzNotificationService } from 'ng-zorro-antd/notification';
+import { NzMessageService } from 'ng-zorro-antd/message';
@Component({
- selector: 'submarine-notebook',
- templateUrl: './notebook.component.html',
- styleUrls: ['./notebook.component.scss']
+ selector: 'submarine-notebook-form',
+ templateUrl: './notebook-form.component.html',
+ styleUrls: ['./notebook-form.component.scss'],
})
-export class NotebookComponent implements OnInit {
+export class NotebookFormComponent implements OnInit {
+ isVisible: boolean;
+
+ // User Information
+ userId;
+
// Environment
envList;
envNameList = [];
indexOfDeaultEnv;
- // Namesapces
- allNamespaceList = [];
- currentNamespace;
-
- // Notebook list
- allNotebookList;
- notebookTable;
-
- // New Notebook Form
+ // Form
notebookForm: FormGroup;
- isVisible = false;
MEMORY_UNITS = ['M', 'Gi'];
- // User Information
- userId;
-
- // Sync //
- // Subscription
- subscriptions = new Subscription();
- // Poller
- poller: ExponentialBackoff;
-
constructor(
- private notebookService: NotebookService,
- private nzMessageService: NzMessageService,
- private environmentService: EnvironmentService,
+ private fb: FormBuilder,
private experimentValidatorService: ExperimentValidatorService,
+ private environmentService: EnvironmentService,
+ private notebookService: NotebookService,
private userService: UserService,
- private fb: FormBuilder,
- private nzNotificationService: NzNotificationService
+ private nzMessageService: NzMessageService
) {}
ngOnInit() {
- this.poller = new ExponentialBackoff({ interval: 1000, retries: 3 });
- const resourcesSub = this.poller.start().subscribe(() => {
- this.userService.fetchUserInfo().subscribe((res) => {
- this.userId = res.id;
-
this.notebookService.fetchNotebookList(this.userId).subscribe(resources => {
- if (!isEqual(this.allNotebookList, resources)) {
- this.allNotebookList = resources;
- this.poller.reset();
- }
- });
- });
+ this.userService.fetchUserInfo().subscribe((res) => {
+ this.userId = res.id;
});
-
- this.subscriptions.add(resourcesSub);
this.notebookForm = this.fb.group({
- notebookName: [null, [
- Validators.maxLength(63),
- Validators.pattern('^([a-z]|[a-z][-a-z0-9]*[a-z0-9])$'),
- Validators.required]],
+ notebookName: [
+ null,
+ [Validators.maxLength(63),
Validators.pattern('^([a-z]|[a-z][-a-z0-9]*[a-z0-9])$'), Validators.required],
+ ],
envName: [null, Validators.required], // Environment
envVars: this.fb.array([],
[this.experimentValidatorService.nameValidatorFactory('key')]),
cpus: [null, [Validators.min(1), Validators.required]],
gpus: [null],
memoryNum: [null, [Validators.required]],
- unit: [this.MEMORY_UNITS[0], [Validators.required]]
+ unit: [this.MEMORY_UNITS[0], [Validators.required]],
});
+
this.fetchEnvList();
+
+ this.initFormStatus();
}
- ngOnDestroy() {
- this.subscriptions.unsubscribe();
+ initModal() {
+ this.isVisible = true;
+ this.initFormStatus();
}
// Get all environment
@@ -121,70 +95,15 @@ export class NotebookComponent implements OnInit {
});
}
- // Get all notebooks, then set default namespace.
- fetchNotebookList(id: string) {
- this.notebookService.fetchNotebookList(id).subscribe((list) => {
- this.allNotebookList = list;
- console.log(this.allNotebookList);
- });
- }
-
- /* (Future work. If we need a api for get all namespaces.)
- getAllNamespaces() {
- this.allNotebookList.forEach((element) => {
- if (this.allNamespaceList.indexOf(element.spec.meta.namespace) < 0) {
- this.allNamespaceList.push(element.spec.meta.namespace);
- }
- });
- }
- */
-
- // Future work. If we have a api for get all namespaces.
- /*
- setDefaultTable() {
- this.currentNamespace = this.allNamespaceList[0];
- this.notebookTable = [];
- this.allNotebookList.forEach((item) => {
- if (item.spec.meta.namespace == this.currentNamespace) {
- this.notebookTable.push(item);
- }
- });
- }
- */
-
- // Future work. If we have a api for get all namespaces.
- switchNamespace(namespace: string) {
- this.notebookTable = [];
- this.allNotebookList.forEach((item) => {
- if (item.spec.meta.namespace == namespace) {
- this.notebookTable.push(item);
- }
- });
- console.log(this.notebookTable);
- }
-
- deleteNotebook(id: string) {
- this.notebookService.deleteNotebook(id).subscribe(
- () => {
- this.updateNotebookTable(this.userId);
- },
- (err) => {
- this.nzMessageService.error(err.message);
- }
- );
- }
-
- // Create or Delete, then update Notebook Table
- updateNotebookTable(id: string) {
- this.notebookService.fetchNotebookList(id).subscribe((list) => {
- this.allNotebookList = list;
- this.notebookTable = [];
- this.allNotebookList.forEach((item) => {
- if (item.spec.meta.namespace == this.currentNamespace) {
- this.notebookTable.push(item);
- }
- });
- });
+ // Init Form
+ initFormStatus() {
+ this.notebookName.reset();
+ this.envName.reset(this.envNameList[this.indexOfDeaultEnv]);
+ this.envVars.clear();
+ this.cpus.reset(1);
+ this.gpus.reset(0);
+ this.memoryNum.reset();
+ this.unit.reset(this.MEMORY_UNITS[0]);
}
get notebookName() {
@@ -209,42 +128,13 @@ export class NotebookComponent implements OnInit {
return this.notebookForm.get('unit');
}
- // Init form when click create-btn
- initNotebookStatus() {
- this.isVisible = true;
- this.notebookName.reset();
- this.envName.reset(this.envNameList[this.indexOfDeaultEnv]);
- this.envVars.clear();
- this.cpus.reset(1);
- this.gpus.reset(0);
- this.memoryNum.reset();
- this.unit.reset(this.MEMORY_UNITS[0]);
- }
-
- // Check form
- checkStatus() {
- return (
- this.notebookName.invalid ||
- this.envName.invalid ||
- this.cpus.invalid ||
- this.gpus.invalid ||
- this.memoryNum.invalid ||
- this.envVars.invalid
- );
- }
-
- // Submmit
- handleOk() {
- this.createNotebookSpec();
- }
-
// EnvVars Form
createEnvVar(defaultKey: string = '', defaultValue: string = '') {
// Create a new FormGroup
return new FormGroup(
{
key: new FormControl(defaultKey, [Validators.required]),
- value: new FormControl(defaultValue, [Validators.required])
+ value: new FormControl(defaultValue, [Validators.required]),
},
[this.experimentValidatorService.envValidator]
);
@@ -261,8 +151,20 @@ export class NotebookComponent implements OnInit {
arr.removeAt(index);
}
+ // Check form
+ checkStatus() {
+ return (
+ this.notebookName.invalid ||
+ this.envName.invalid ||
+ this.cpus.invalid ||
+ this.gpus.invalid ||
+ this.memoryNum.invalid ||
+ this.envVars.invalid
+ );
+ }
+
// Develope submmit spec
- createNotebookSpec() {
+ submitForm() {
// Check GPU, then develope resources spec
let resourceSpec;
if (this.notebookForm.get('gpus').value === 0 ||
this.notebookForm.get('gpus').value == null) {
@@ -270,24 +172,25 @@ export class NotebookComponent implements OnInit {
this.notebookForm.get('unit').value
}`;
} else {
- resourceSpec =
`cpu=${this.notebookForm.get('cpus').value},nvidia.com/gpu=${this.notebookForm.get('gpus').value},memory=${
- this.notebookForm.get('memoryNum').value
- }${this.notebookForm.get('unit').value}`;
+ resourceSpec =
`cpu=${this.notebookForm.get('cpus').value},nvidia.com/gpu=${
+ this.notebookForm.get('gpus').value
+
},memory=${this.notebookForm.get('memoryNum').value}${this.notebookForm.get('unit').value}`;
}
+
// Develope submmit spec
const newNotebookSpec = {
meta: {
name: this.notebookForm.get('notebookName').value,
namespace: 'default',
- ownerId: this.userId
+ ownerId: this.userId,
},
environment: {
- name: this.notebookForm.get('envName').value
+ name: this.notebookForm.get('envName').value,
},
spec: {
envVars: {},
- resources: resourceSpec
- }
+ resources: resourceSpec,
+ },
};
for (const envVar of this.envVars.controls) {
@@ -296,34 +199,20 @@ export class NotebookComponent implements OnInit {
}
}
- //console.log(newNotebookSpec);
-
// Post
this.notebookService.createNotebook(newNotebookSpec).subscribe({
- next: (result) => {
- this.fetchNotebookList(this.userId);
- },
+ next: (result) => {},
error: (msg) => {
this.nzMessageService.error(`${msg}, please try again`, {
- nzPauseOnHover: true
+ nzPauseOnHover: true,
});
},
complete: () => {
+ this.nzMessageService.info(`Notebook Creating`, {
+ nzPauseOnHover: true,
+ });
this.isVisible = false;
- }
+ },
});
}
-
- showReason(reason: string) {
- this.nzNotificationService.blank(
- 'Notebook Status',
- reason
- );
- }
-
- // TODO(kobe860219): Make a notebook run
- runNotebook() {}
-
- // TODO(kobe860219): Stop a running notebook
- stopNotebook() {}
}
diff --git
a/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook-home/notebook-home.component.html
b/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook-home/notebook-home.component.html
new file mode 100644
index 0000000..e895d64
--- /dev/null
+++
b/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook-home/notebook-home.component.html
@@ -0,0 +1,38 @@
+<!--
+ ~ 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 style="margin: 15px; padding: 15px; background-color: white">
+ <div align="right">
+ <button
+ nz-button
+ id="btn-newNotebook"
+ nzType="primary"
+ style="margin: 10px 4px 10px 4px"
+ (click)="form.initModal()"
+ >
+ <i nz-icon nzType="plus"></i>
+ New Notebook
+ </button>
+ </div>
+ <submarine-notebook-list
+ [notebookList]="notebookList"
+ (deleteNotebook)="onDeleteNotebook($event)"
+ ></submarine-notebook-list>
+ <submarine-notebook-form #form></submarine-notebook-form>
+</div>
diff --git
a/submarine-workbench/workbench-web/src/app/interfaces/notebook-info.ts
b/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook-home/notebook-home.component.scss
similarity index 74%
copy from submarine-workbench/workbench-web/src/app/interfaces/notebook-info.ts
copy to
submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook-home/notebook-home.component.scss
index 91a8d21..3d56d22 100644
--- a/submarine-workbench/workbench-web/src/app/interfaces/notebook-info.ts
+++
b/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook-home/notebook-home.component.scss
@@ -1,4 +1,4 @@
-/*
+/*!
* 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
@@ -16,17 +16,3 @@
* specific language governing permissions and limitations
* under the License.
*/
-
-import { Url } from 'url';
-import { NotebookSpec } from '@submarine/interfaces/notebook-spec';
-
-export interface Notebook {
- notebookId: string;
- name: string;
- uid: string;
- url: Url;
- status: string;
- createdTime: string;
- deletedTime: string;
- spec: NotebookSpec;
-}
diff --git
a/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook-home/notebook-home.component.ts
b/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook-home/notebook-home.component.ts
new file mode 100644
index 0000000..a19f74c
--- /dev/null
+++
b/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook-home/notebook-home.component.ts
@@ -0,0 +1,86 @@
+/*
+ * 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, OnDestroy } from '@angular/core';
+import { NotebookService } from
'@submarine/services/notebook-services/notebook.service';
+import { NzMessageService } from 'ng-zorro-antd/message';
+import { UserService } from '@submarine/services/user.service';
+import { isEqual } from 'lodash';
+import { NotebookFormComponent } from
'./notebook-form/notebook-form.component';
+import { interval, Subscription } from 'rxjs';
+import { mergeMap, timeout } from 'rxjs/operators';
+
+@Component({
+ selector: 'submarine-notebook-home',
+ templateUrl: './notebook-home.component.html',
+ styleUrls: ['./notebook-home.component.scss'],
+})
+export class NotebookHomeComponent implements OnInit, OnDestroy {
+ // User Information
+ userId;
+
+ // Notebook list
+ notebookList;
+
+ subscribtions = new Subscription();
+
+ @ViewChild('form', { static: true }) form: NotebookFormComponent;
+
+ constructor(
+ private notebookService: NotebookService,
+ private nzMessageService: NzMessageService,
+ private userService: UserService
+ ) {}
+
+ ngOnInit() {
+ this.userService.fetchUserInfo().subscribe((res) => {
+ this.userId = res.id;
+ });
+
+ const resourceSub = interval(1000).subscribe(() => {
+ this.notebookService.fetchNotebookList(this.userId).subscribe((res) => {
+ if (!isEqual(this.notebookList, res)) {
+ this.notebookList = res;
+ }
+ });
+ });
+
+ this.subscribtions.add(resourceSub);
+ }
+
+ ngOnDestroy() {
+ this.subscribtions.unsubscribe();
+ }
+
+ onDeleteNotebook(id: string) {
+ this.notebookService.deleteNotebook(id).subscribe({
+ next: (result) => {},
+ error: (msg) => {
+ this.nzMessageService.error(`${msg}, please try again`, {
+ nzPauseOnHover: true,
+ });
+ },
+ complete: () => {
+ this.nzMessageService.info(`Delete Notebook...`, {
+ nzPauseOnHover: true,
+ });
+ },
+ });
+ }
+}
diff --git
a/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook-home/notebook-list/notebook-list.component.html
b/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook-home/notebook-list/notebook-list.component.html
new file mode 100644
index 0000000..5a84233
--- /dev/null
+++
b/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook-home/notebook-list/notebook-list.component.html
@@ -0,0 +1,77 @@
+<!--
+ ~ 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.
+ -->
+
+<nz-table id="notebookListTable" style="padding-top: 5px" #basicTable
[nzData]="notebookList" [nzNoResult]="'No data'">
+ <thead>
+ <tr>
+ <th></th>
+ <th>Name</th>
+ <th>Environment</th>
+ <th>Docker Image</th>
+ <th>Resources</th>
+ <th>Status</th>
+ <th>Action</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr *ngFor="let data of basicTable.data; let i = index">
+ <td *ngIf="data.status === 'running'">
+ <i
+ nz-icon
+ [nzType]="'check-circle'"
+ [nzTheme]="'twotone'"
+ [nzTwotoneColor]="'#52c41a'"
+ style="color: #08c; font-size: 24px"
+ ></i>
+ </td>
+ <td *ngIf="data.status !== 'running'">
+ <i nz-icon nzType="loading" nzTheme="outline" style="color: #08c;
font-size: 24px"></i>
+ </td>
+ <td *ngIf="data.status === 'running'">
+ <a href="{{ data.url }}" target="_blank">{{ data.name }}</a>
+ </td>
+ <td *ngIf="data.status !== 'running'">
+ {{ data.name }}
+ </td>
+ <td>{{ data.spec.environment.name }}</td>
+ <td>{{ data.spec.environment.dockerImage }}</td>
+ <td>
+ {{ data.spec.spec.resources }}
+ </td>
+ <td>
+ <a (click)="showReason(data.reason)">
+ <nz-tag [nzColor]="statusColor[data.status]">{{ data.status
}}</nz-tag>
+ </a>
+ </td>
+ <td>
+ <a
+ id="btn-deleteNotebook{{ i }}"
+ nz-popconfirm
+ nzPlacement="left"
+ nzTitle="Are you sure you want to delete?"
+ nzCancelText="Cancel"
+ nzOkText="Ok"
+ (nzOnConfirm)="onDeleteNotebook(data.notebookId)"
+ >
+ Delete
+ </a>
+ </td>
+ </tr>
+ </tbody>
+</nz-table>
diff --git
a/submarine-workbench/workbench-web/src/app/interfaces/notebook-info.ts
b/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook-home/notebook-list/notebook-list.component.scss
similarity index 74%
rename from
submarine-workbench/workbench-web/src/app/interfaces/notebook-info.ts
rename to
submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook-home/notebook-list/notebook-list.component.scss
index 91a8d21..3d56d22 100644
--- a/submarine-workbench/workbench-web/src/app/interfaces/notebook-info.ts
+++
b/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook-home/notebook-list/notebook-list.component.scss
@@ -1,4 +1,4 @@
-/*
+/*!
* 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
@@ -16,17 +16,3 @@
* specific language governing permissions and limitations
* under the License.
*/
-
-import { Url } from 'url';
-import { NotebookSpec } from '@submarine/interfaces/notebook-spec';
-
-export interface Notebook {
- notebookId: string;
- name: string;
- uid: string;
- url: Url;
- status: string;
- createdTime: string;
- deletedTime: string;
- spec: NotebookSpec;
-}
diff --git
a/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook-home/notebook-list/notebook-list.component.ts
b/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook-home/notebook-list/notebook-list.component.ts
new file mode 100644
index 0000000..4cb5617
--- /dev/null
+++
b/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook-home/notebook-list/notebook-list.component.ts
@@ -0,0 +1,52 @@
+/*
+ * 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, EventEmitter, Input, OnInit, Output } from '@angular/core';
+import { NotebookInfo } from
'@submarine/interfaces/notebook-interfaces/notebook-info';
+import { NzNotificationService } from 'ng-zorro-antd/notification';
+
+@Component({
+ selector: 'submarine-notebook-list',
+ templateUrl: './notebook-list.component.html',
+ styleUrls: ['./notebook-list.component.scss'],
+})
+export class NotebookListComponent implements OnInit {
+ constructor(private nzNotificationService: NzNotificationService) {}
+
+ @Input() notebookList: NotebookInfo[];
+
+ @Output() deleteNotebook = new EventEmitter<string>();
+
+ statusColor: { [key: string]: string } = {
+ creating: 'gold',
+ waiting: 'gold',
+ running: 'green',
+ terminating: 'blue',
+ };
+
+ ngOnInit() {}
+
+ showReason(reason: string) {
+ this.nzNotificationService.blank('Notebook Status', reason);
+ }
+
+ onDeleteNotebook(id: string) {
+ this.deleteNotebook.emit(id);
+ }
+}
diff --git
a/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook.module.ts
b/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook-routing.module.ts
similarity index 66%
copy from
submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook.module.ts
copy to
submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook-routing.module.ts
index 06996b9..6ad2d7d 100644
---
a/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook.module.ts
+++
b/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook-routing.module.ts
@@ -17,16 +17,27 @@
* under the License.
*/
-import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
+import { RouterModule, Routes } from '@angular/router';
+import { NotebookHomeComponent } from
'./notebook-home/notebook-home.component';
import { NotebookComponent } from './notebook.component';
-import { FormsModule, ReactiveFormsModule } from '@angular/forms';
-import { NgZorroAntdModule } from 'ng-zorro-antd';
-import { PipeSharedModule } from '@submarine/pipe/pipe-shared.module';
+
+const routes: Routes = [
+ {
+ path: '',
+ component: NotebookComponent,
+ children: [
+ {
+ path: '',
+ pathMatch: 'full',
+ component: NotebookHomeComponent,
+ },
+ ],
+ },
+];
@NgModule({
- declarations: [NotebookComponent],
- exports: [NotebookComponent],
- imports: [CommonModule, FormsModule, ReactiveFormsModule, NgZorroAntdModule,
PipeSharedModule]
+ imports: [RouterModule.forChild(routes)],
+ exports: [RouterModule],
})
-export class NotebookModule {}
+export class NotebookRoutingModule {}
diff --git
a/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook.component.html
b/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook.component.html
index 9e7d814..f430035 100644
---
a/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook.component.html
+++
b/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook.component.html
@@ -16,218 +16,22 @@
~ specific language governing permissions and limitations
~ under the License.
-->
-
-<nz-layout style="margin: -24px -24px 16px;">
- <nz-layout class="inner-layout">
- <div id="notebookOuter">
- <nz-breadcrumb>
- <nz-breadcrumb-item>
- <!--<a [routerLink]="['/', 'workbench', 'home']">Home</a>-->
- <a>Home</a>
- </nz-breadcrumb-item>
- <nz-breadcrumb-item>
- <a [routerLink]="['/', 'workbench', 'notebook']">Notebook</a>
- </nz-breadcrumb-item>
- </nz-breadcrumb>
- <div>
- <br />
- <h2>Notebook</h2>
- <nz-content>Apache submarine support jupyter notebook.</nz-content>
- <br />
- </div>
- </div>
- </nz-layout>
- <div id="notebookDiv">
- <div nz-row>
- <div nz-col nzSpan="12">
- <h2>Notebook List</h2>
- <!--
- <label style="font-size: large; color: black;">Namespaces :</label>
- <nz-select disabled="disabled" style="margin-left: 5px; width: 240px;"
[(ngModel)]="currentNamespace">
- <nz-option [nzValue]="currentNamespace"
[nzLabel]="currentNamespace"></nz-option>
- </nz-select>
- -->
- </div>
- <div nz-col nzSpan="12" align="right">
- <nz-input-group nzSearch style="width: 300px; margin-right: 5px;"
[nzAddOnAfter]="suffixIconButton">
- <input type="text" nz-input id="searchInput" placeholder="input
search text" />
- </nz-input-group>
- <ng-template #suffixIconButton>
- <button nz-button nzType="primary" nzSearch><i nz-icon
nzType="search"></i></button>
- </ng-template>
- <button
- id="btnNewNotebook"
- nz-button
- nzType="primary"
- style="margin-right: 5px;"
- (click)="initNotebookStatus()"
- >
- <i nz-icon nzType="plus"></i>
- New Notebook
- </button>
- </div>
- </div>
+<nz-layout style="margin: -24px -24px 16px">
+ <div style="background-color: white; padding-left: 30px; padding-top: 20px">
+ <nz-breadcrumb>
+ <nz-breadcrumb-item>
+ <a>Home</a>
+ </nz-breadcrumb-item>
+ <nz-breadcrumb-item>
+ <a [routerLink]="['/', 'workbench', 'notebook']">notebook</a>
+ </nz-breadcrumb-item>
+ </nz-breadcrumb>
<div>
- <nz-table id="notebookListTable" style="padding-top: 5px;" #basicTable
[nzData]="allNotebookList">
- <thead>
- <tr>
- <th></th>
- <th>Name</th>
- <th>Environment</th>
- <th>Docker Image</th>
- <th>Resources</th>
- <th>Status</th>
- <th>Action</th>
- </tr>
- </thead>
- <tbody>
- <tr *ngFor="let data of basicTable.data; let i = index">
- <td *ngIf="data.status === 'running'">
- <i
- nz-icon
- [nzType]="'check-circle'"
- [nzTheme]="'twotone'"
- [nzTwotoneColor]="'#52c41a'"
- style="color: #08c; font-size: 24px;">
- </i>
- </td>
- <td *ngIf="data.status !== 'running'">
- <i
- nz-icon
- nzType="loading"
- nzTheme="outline"
- style="color: #08c; font-size: 24px;">
- </i>
- </td>
- <td *ngIf="data.status === 'running'">
- <a href="{{ data.url }}" target="_blank">{{ data.name }}</a>
- </td>
- <td *ngIf="data.status !== 'running'">
- {{ data.name }}
- </td>
- <td>{{ data.spec.environment.name }}</td>
- <td>{{ data.spec.environment.dockerImage }}</td>
- <td>
- {{ data.spec.spec.resources }}
- </td>
- <td>
- <a (click)=showReason(data.reason)>{{ data.status }}</a>
- </td>
- <td>
- <a id="delete{{ i }}"
(click)="deleteNotebook(data.notebookId)">Delete</a></td>
- </tr>
- </tbody>
- </nz-table>
+ <br />
+ <h2>Notebook</h2>
+ <nz-content>Apache submarine support jupyter notebook.</nz-content>
</div>
+ <br />
</div>
+ <router-outlet></router-outlet>
</nz-layout>
-
-<nz-modal
- [(nzVisible)]="isVisible"
- nzTitle="Create Notebook"
- [(nzOkText)]="okText"
- (nzOnCancel)="isVisible = false"
- [nzWidth]="700"
->
- <form nz-form [formGroup]="notebookForm" nzLayout="horizontal">
- <div *nzModalFooter>
- <button nz-button nzType="default" (click)="isVisible =
false">Cancel</button>
- <button id="create-btn" nz-button nzType="primary"
[disabled]="checkStatus()" (click)="handleOk()">
- Create
- </button>
- </div>
- <nz-form-item>
- <nz-form-label [nzSm]="6" [nzXs]="24" nzRequired nzFor="notebookName">
- Notebook Name
- </nz-form-label>
- <nz-form-control [nzSm]="14" [nzXs]="24">
- <input nz-input required type="text" name="notebookName"
id="notebookName" formControlName="notebookName" />
- </nz-form-control>
- </nz-form-item>
- <nz-form-item>
- <nz-form-label [nzSm]="6" [nzXs]="24" nzRequired nzFor="environment">
- Environment
- </nz-form-label>
- <nz-form-control [nzSm]="14" [nzXs]="24">
- <nz-select required name="select-envName" formControlName="envName">
- <nz-option
- *ngFor="let env of envNameList; let i; of: index"
- id="env{{ i }}"
- [nzValue]="env"
- [nzLabel]="env"
- ></nz-option>
- </nz-select>
- </nz-form-control>
- </nz-form-item>
- <nz-form-item>
- <nz-form-label [nzSm]="6" [nzXs]="24" nzRequired nzFor="cpus">
- CPU
- </nz-form-label>
- <nz-form-control [nzSm]="14" [nzXs]="24">
- <input nz-input min="0" required type="number" name="cpus" id="cpus"
formControlName="cpus" />
- </nz-form-control>
- </nz-form-item>
- <nz-form-item>
- <nz-form-label [nzSm]="6" [nzXs]="24" nzRequired nzFor="gpus">
- GPU
- </nz-form-label>
- <nz-form-control [nzSm]="14" [nzXs]="24">
- <input nz-input min="0" type="number" name="gpus" id="gpus"
formControlName="gpus" />
- </nz-form-control>
- </nz-form-item>
- <nz-form-item>
- <nz-form-label [nzSm]="6" [nzXs]="24" nzRequired nzFor="memoryNum">
- Memory
- </nz-form-label>
- <nz-form-control [nzSm]="14" [nzXs]="24">
- <div nz-col nzSpan="6">
- <input nz-input required name="memoryNum" placeholder="EX:1024"
formControlName="memoryNum" />
- </div>
- <div nz-col nzSpan="6" style="margin-left: 5px;">
- <nz-select formControlName="unit">
- <nz-option *ngFor="let unit of MEMORY_UNITS" [nzValue]="unit"
[nzLabel]="unit"></nz-option>
- </nz-select>
- </div>
- </nz-form-control>
- </nz-form-item>
- <div formArrayName="envVars">
- <ng-container *ngFor="let envVar of envVars.controls; index as i">
- <nz-form-item>
- <nz-form-label nzRequired [nzSm]="6" [nzXs]="24">EnvVar{{ i+1
}}</nz-form-label>
- <div [formGroupName]="i">
- <div nz-col nzSpan="12">
- <input
- style="width: 30%;"
- nz-input
- required
- id="key{{ i }}"
- name="key{{ i }}"
- placeholder="Key"
- formControlName="key"
- />
- <input
- style="width: 60%; margin-left: 10px;"
- nz-input
- required
- id="value{{ i }}"
- name="value{{ i }}"
- placeholder="Value"
- formControlName="value"
- />
- <i
- nz-icon
- style="margin-left: 5px;"
- nzType="close-circle"
- nzTheme="fill"
- (click)="deleteItem(envVars, i)"
- ></i>
- </div>
- </div>
- </nz-form-item>
- </ng-container>
- </div>
- <button nz-button style="display: block; margin: auto;" id="envVar-btn"
type="default" (click)="onCreateEnvVar()">
- New EnvVar
- </button>
- </form>
-</nz-modal>
diff --git
a/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook.component.scss
b/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook.component.scss
index 68e727f..3d56d22 100644
---
a/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook.component.scss
+++
b/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook.component.scss
@@ -16,52 +16,3 @@
* specific language governing permissions and limitations
* under the License.
*/
-
-#notebookOuter{
- background-color: white;
- padding-left: 30px;
- padding-top: 20px;
- }
-
- #notebookDiv{
- margin: 10px;
- background-color: white;
- padding: 10px;
- overflow: auto;
- }
-
- .red-star {
- color: red;
- }
-
- .newNotebookForm {
- display: flex;
- align-items: center;
- margin-bottom: 1.5rem;
- & label {
- flex: 0 0 25%;
- text-align: right;
- font-weight: 500;
- &:not(:last-child) {
- margin-right: 1.5rem;
- }
- }
- & input {
- flex: 0 0 48%;
- }
-
- & nz-select {
- flex: 0 0 48%;
- }
- }
-
- .memory-input-group {
- display: flex;
- & input {
- width: 70%;
- }
-
- & > * {
- width: 30%;
- }
-}
diff --git
a/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook.component.ts
b/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook.component.ts
index e0d1533..6e3a11e 100644
---
a/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook.component.ts
+++
b/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook.component.ts
@@ -18,312 +18,14 @@
*/
import { Component, OnInit } from '@angular/core';
-import { FormArray, FormControl, FormGroup, Validators, FormBuilder } from
'@angular/forms';
-import { NotebookService } from '@submarine/services/notebook.service';
-import { NzMessageService } from 'ng-zorro-antd/message';
-import { EnvironmentService } from '@submarine/services/environment.service';
-import { ExperimentValidatorService } from
'@submarine/services/experiment.validator.service';
-import { UserService } from '@submarine/services/user.service';
-import { nullSafeIsEquivalent } from '@angular/compiler/src/output/output_ast';
-import { Subscription } from 'rxjs';
-import { ExponentialBackoff } from '@submarine/services/polling';
-import { isEqual } from "lodash";
-import { NzNotificationService } from 'ng-zorro-antd/notification';
@Component({
selector: 'submarine-notebook',
templateUrl: './notebook.component.html',
- styleUrls: ['./notebook.component.scss']
+ styleUrls: ['./notebook.component.scss'],
})
export class NotebookComponent implements OnInit {
- // Environment
- envList;
- envNameList = [];
- indexOfDeaultEnv;
+ constructor() {}
- // Namesapces
- allNamespaceList = [];
- currentNamespace;
-
- // Notebook list
- allNotebookList;
- notebookTable;
-
- // New Notebook Form
- notebookForm: FormGroup;
- isVisible = false;
- MEMORY_UNITS = ['M', 'Gi'];
-
- // User Information
- userId;
-
- // Sync //
- // Subscription
- subscriptions = new Subscription();
- // Poller
- poller: ExponentialBackoff;
-
- constructor(
- private notebookService: NotebookService,
- private nzMessageService: NzMessageService,
- private environmentService: EnvironmentService,
- private experimentValidatorService: ExperimentValidatorService,
- private userService: UserService,
- private fb: FormBuilder,
- private nzNotificationService: NzNotificationService
- ) {}
-
- ngOnInit() {
- this.poller = new ExponentialBackoff({ interval: 1000, retries: 3 });
- const resourcesSub = this.poller.start().subscribe(() => {
- this.userService.fetchUserInfo().subscribe((res) => {
- this.userId = res.id;
-
this.notebookService.fetchNotebookList(this.userId).subscribe(resources => {
- if (!isEqual(this.allNotebookList, resources)) {
- this.allNotebookList = resources;
- this.poller.reset();
- }
- });
- });
- });
-
- this.subscriptions.add(resourcesSub);
-
- this.notebookForm = this.fb.group({
- notebookName: [null, [
- Validators.maxLength(63),
- Validators.pattern('^([a-z]|[a-z][-a-z0-9]*[a-z0-9])$'),
- Validators.required]],
- envName: [null, Validators.required], // Environment
- envVars: this.fb.array([],
[this.experimentValidatorService.nameValidatorFactory('key')]),
- cpus: [null, [Validators.min(1), Validators.required]],
- gpus: [null],
- memoryNum: [null, [Validators.required]],
- unit: [this.MEMORY_UNITS[0], [Validators.required]]
- });
- this.fetchEnvList();
- }
-
- ngOnDestroy() {
- this.subscriptions.unsubscribe();
- }
-
- // Get all environment
- fetchEnvList() {
- this.environmentService.fetchEnvironmentList().subscribe((list) => {
- this.envList = list;
- this.envList.forEach((env) => {
- if (this.envNameList.indexOf(env.environmentSpec.name) < 0) {
- this.envNameList.push(env.environmentSpec.name);
- }
- });
- this.indexOfDeaultEnv = this.envNameList.indexOf('notebook-env');
- });
- }
-
- // Get all notebooks, then set default namespace.
- fetchNotebookList(id: string) {
- this.notebookService.fetchNotebookList(id).subscribe((list) => {
- this.allNotebookList = list;
- console.log(this.allNotebookList);
- });
- }
-
- /* (Future work. If we need a api for get all namespaces.)
- getAllNamespaces() {
- this.allNotebookList.forEach((element) => {
- if (this.allNamespaceList.indexOf(element.spec.meta.namespace) < 0) {
- this.allNamespaceList.push(element.spec.meta.namespace);
- }
- });
- }
- */
-
- // Future work. If we have a api for get all namespaces.
- /*
- setDefaultTable() {
- this.currentNamespace = this.allNamespaceList[0];
- this.notebookTable = [];
- this.allNotebookList.forEach((item) => {
- if (item.spec.meta.namespace == this.currentNamespace) {
- this.notebookTable.push(item);
- }
- });
- }
- */
-
- // Future work. If we have a api for get all namespaces.
- switchNamespace(namespace: string) {
- this.notebookTable = [];
- this.allNotebookList.forEach((item) => {
- if (item.spec.meta.namespace == namespace) {
- this.notebookTable.push(item);
- }
- });
- console.log(this.notebookTable);
- }
-
- deleteNotebook(id: string) {
- this.notebookService.deleteNotebook(id).subscribe(
- () => {
- this.updateNotebookTable(this.userId);
- },
- (err) => {
- this.nzMessageService.error(err.message);
- }
- );
- }
-
- // Create or Delete, then update Notebook Table
- updateNotebookTable(id: string) {
- this.notebookService.fetchNotebookList(id).subscribe((list) => {
- this.allNotebookList = list;
- this.notebookTable = [];
- this.allNotebookList.forEach((item) => {
- if (item.spec.meta.namespace == this.currentNamespace) {
- this.notebookTable.push(item);
- }
- });
- });
- }
-
- get notebookName() {
- return this.notebookForm.get('notebookName');
- }
- get envName() {
- return this.notebookForm.get('envName');
- }
- get envVars() {
- return this.notebookForm.get('envVars') as FormArray;
- }
- get cpus() {
- return this.notebookForm.get('cpus');
- }
- get gpus() {
- return this.notebookForm.get('gpus');
- }
- get memoryNum() {
- return this.notebookForm.get('memoryNum');
- }
- get unit() {
- return this.notebookForm.get('unit');
- }
-
- // Init form when click create-btn
- initNotebookStatus() {
- this.isVisible = true;
- this.notebookName.reset();
- this.envName.reset(this.envNameList[this.indexOfDeaultEnv]);
- this.envVars.clear();
- this.cpus.reset(1);
- this.gpus.reset(0);
- this.memoryNum.reset();
- this.unit.reset(this.MEMORY_UNITS[0]);
- }
-
- // Check form
- checkStatus() {
- return (
- this.notebookName.invalid ||
- this.envName.invalid ||
- this.cpus.invalid ||
- this.gpus.invalid ||
- this.memoryNum.invalid ||
- this.envVars.invalid
- );
- }
-
- // Submmit
- handleOk() {
- this.createNotebookSpec();
- }
-
- // EnvVars Form
- createEnvVar(defaultKey: string = '', defaultValue: string = '') {
- // Create a new FormGroup
- return new FormGroup(
- {
- key: new FormControl(defaultKey, [Validators.required]),
- value: new FormControl(defaultValue, [Validators.required])
- },
- [this.experimentValidatorService.envValidator]
- );
- }
-
- // EnvVars Form
- onCreateEnvVar() {
- const env = this.createEnvVar();
- this.envVars.push(env);
- }
-
- // Delete item in EnvVars Form
- deleteItem(arr: FormArray, index: number) {
- arr.removeAt(index);
- }
-
- // Develope submmit spec
- createNotebookSpec() {
- // Check GPU, then develope resources spec
- let resourceSpec;
- if (this.notebookForm.get('gpus').value === 0 ||
this.notebookForm.get('gpus').value == null) {
- resourceSpec =
`cpu=${this.notebookForm.get('cpus').value},memory=${this.notebookForm.get('memoryNum').value}${
- this.notebookForm.get('unit').value
- }`;
- } else {
- resourceSpec =
`cpu=${this.notebookForm.get('cpus').value},nvidia.com/gpu=${this.notebookForm.get('gpus').value},memory=${
- this.notebookForm.get('memoryNum').value
- }${this.notebookForm.get('unit').value}`;
- }
- // Develope submmit spec
- const newNotebookSpec = {
- meta: {
- name: this.notebookForm.get('notebookName').value,
- namespace: 'default',
- ownerId: this.userId
- },
- environment: {
- name: this.notebookForm.get('envName').value
- },
- spec: {
- envVars: {},
- resources: resourceSpec
- }
- };
-
- for (const envVar of this.envVars.controls) {
- if (envVar.get('key').value) {
- newNotebookSpec.spec.envVars[envVar.get('key').value] =
envVar.get('value').value;
- }
- }
-
- //console.log(newNotebookSpec);
-
- // Post
- this.notebookService.createNotebook(newNotebookSpec).subscribe({
- next: (result) => {
- this.fetchNotebookList(this.userId);
- },
- error: (msg) => {
- this.nzMessageService.error(`${msg}, please try again`, {
- nzPauseOnHover: true
- });
- },
- complete: () => {
- this.isVisible = false;
- }
- });
- }
-
- showReason(reason: string) {
- this.nzNotificationService.blank(
- 'Notebook Status',
- reason
- );
- }
-
- // TODO(kobe860219): Make a notebook run
- runNotebook() {}
-
- // TODO(kobe860219): Stop a running notebook
- stopNotebook() {}
+ ngOnInit() {}
}
diff --git
a/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook.module.ts
b/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook.module.ts
index 06996b9..9985e96 100644
---
a/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook.module.ts
+++
b/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook.module.ts
@@ -19,14 +19,30 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
-import { NotebookComponent } from './notebook.component';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
-import { NgZorroAntdModule } from 'ng-zorro-antd';
+import { RouterModule } from '@angular/router';
import { PipeSharedModule } from '@submarine/pipe/pipe-shared.module';
+import { NotebookService } from
'@submarine/services/notebook-services/notebook.service';
+import { NgZorroAntdModule } from 'ng-zorro-antd';
+import { NotebookRoutingModule } from './notebook-routing.module';
+
+import { NotebookComponent } from './notebook.component';
+import { NotebookHomeComponent } from
'./notebook-home/notebook-home.component';
+import { NotebookListComponent } from
'./notebook-home/notebook-list/notebook-list.component';
+import { NotebookFormComponent } from
'./notebook-home/notebook-form/notebook-form.component';
@NgModule({
- declarations: [NotebookComponent],
+ imports: [
+ CommonModule,
+ FormsModule,
+ ReactiveFormsModule,
+ RouterModule,
+ PipeSharedModule,
+ NgZorroAntdModule,
+ NotebookRoutingModule,
+ ],
+ providers: [NotebookService],
+ declarations: [NotebookComponent, NotebookHomeComponent,
NotebookListComponent, NotebookFormComponent],
exports: [NotebookComponent],
- imports: [CommonModule, FormsModule, ReactiveFormsModule, NgZorroAntdModule,
PipeSharedModule]
})
export class NotebookModule {}
diff --git
a/submarine-workbench/workbench-web/src/app/pages/workbench/workbench-routing.module.ts
b/submarine-workbench/workbench-web/src/app/pages/workbench/workbench-routing.module.ts
index bc838b1..a0aa0cd 100644
---
a/submarine-workbench/workbench-web/src/app/pages/workbench/workbench-routing.module.ts
+++
b/submarine-workbench/workbench-web/src/app/pages/workbench/workbench-routing.module.ts
@@ -25,7 +25,6 @@ import { DataComponent } from './data/data.component';
import { HomeComponent } from './home/home.component';
import { InterpreterComponent } from './interpreter/interpreter.component';
import { ModelComponent } from './model/model.component';
-import { NotebookComponent } from './notebook/notebook.component';
import { WorkspaceComponent } from './workspace/workspace.component';
const routes: Routes = [
@@ -75,7 +74,7 @@ const routes: Routes = [
},
{
path: 'notebook',
- component: NotebookComponent,
+ loadChildren: () => import('./notebook/notebook.module').then((m) =>
m.NotebookModule),
canActivate: ['canActivatePage'],
},
{
diff --git
a/submarine-workbench/workbench-web/src/app/pages/workbench/workbench.module.ts
b/submarine-workbench/workbench-web/src/app/pages/workbench/workbench.module.ts
index 908b48d..248eaef 100644
---
a/submarine-workbench/workbench-web/src/app/pages/workbench/workbench.module.ts
+++
b/submarine-workbench/workbench-web/src/app/pages/workbench/workbench.module.ts
@@ -24,17 +24,17 @@ import { RouterModule } from '@angular/router';
import { WorkbenchRoutingModule } from
'@submarine/pages/workbench/workbench-routing.module';
import { PipeSharedModule } from '@submarine/pipe/pipe-shared.module';
import { NgZorroAntdModule } from 'ng-zorro-antd';
-import { DataComponent } from './data/data.component';
+import { WorkspaceModule } from './workspace/workspace.module';
import { ExperimentModule } from './experiment/experiment.module';
+import { InterpreterModule } from './interpreter/interpreter.module';
+import { NotebookModule } from './notebook/notebook.module';
import { HomeComponent } from './home/home.component';
-import { InterpreterModule } from './interpreter/interpreter.module';
import { ModelComponent } from './model/model.component';
import { WorkbenchComponent } from './workbench.component';
import { WorkspaceComponent } from './workspace/workspace.component';
-import { WorkspaceModule } from './workspace/workspace.module';
import { EnvironmentComponent } from './environment/environment.component';
-import { NotebookComponent } from './notebook/notebook.component';
+import { DataComponent } from './data/data.component';
@NgModule({
declarations: [
@@ -43,8 +43,7 @@ import { NotebookComponent } from
'./notebook/notebook.component';
WorkspaceComponent,
DataComponent,
ModelComponent,
- EnvironmentComponent,
- NotebookComponent
+ EnvironmentComponent
],
imports: [
CommonModule,
@@ -56,7 +55,8 @@ import { NotebookComponent } from
'./notebook/notebook.component';
WorkspaceModule,
ExperimentModule,
InterpreterModule,
- PipeSharedModule
+ PipeSharedModule,
+ NotebookModule
]
})
export class WorkbenchModule { }
diff --git
a/submarine-workbench/workbench-web/src/app/services/notebook.service.ts
b/submarine-workbench/workbench-web/src/app/services/notebook-services/notebook.service.ts
similarity index 80%
rename from
submarine-workbench/workbench-web/src/app/services/notebook.service.ts
rename to
submarine-workbench/workbench-web/src/app/services/notebook-services/notebook.service.ts
index 880b538..c273718 100644
--- a/submarine-workbench/workbench-web/src/app/services/notebook.service.ts
+++
b/submarine-workbench/workbench-web/src/app/services/notebook-services/notebook.service.ts
@@ -23,17 +23,18 @@ import { Rest } from '@submarine/interfaces';
import { BaseApiService } from '@submarine/services/base-api.service';
import { of, throwError, Observable } from 'rxjs';
import { catchError, map, switchMap } from 'rxjs/operators';
-import { Notebook } from '@submarine/interfaces/notebook-info';
+import { NotebookInfo } from
'@submarine/interfaces/notebook-interfaces/notebook-info';
+import { NotebookSpec } from
'@submarine/interfaces/notebook-interfaces/notebook-spec';
@Injectable({
- providedIn: 'root'
+ providedIn: 'root',
})
export class NotebookService {
constructor(private baseApi: BaseApiService, private httpClient: HttpClient)
{}
- fetchNotebookList(id: string) {
+ fetchNotebookList(id: string): Observable<NotebookInfo[]> {
const apiUrl = this.baseApi.getRestApi('/v1/notebook?id=' + id);
- return this.httpClient.get<Rest<Notebook>>(apiUrl).pipe(
+ return this.httpClient.get<Rest<NotebookInfo[]>>(apiUrl).pipe(
switchMap((res) => {
if (res.success) {
return of(res.result);
@@ -44,9 +45,9 @@ export class NotebookService {
);
}
- createNotebook(newNotebook: object): Observable<Notebook> {
+ createNotebook(notebookSpec: object): Observable<NotebookSpec> {
const apiUrl = this.baseApi.getRestApi('/v1/notebook');
- return this.httpClient.post<Rest<Notebook>>(apiUrl, newNotebook).pipe(
+ return this.httpClient.post<Rest<NotebookSpec>>(apiUrl, notebookSpec).pipe(
map((res) => res.result), // return result directly if succeeding
catchError((e) => {
let message: string;
@@ -68,9 +69,9 @@ export class NotebookService {
);
}
- deleteNotebook(id: string): Observable<Notebook> {
+ deleteNotebook(id: string): Observable<NotebookInfo> {
const apiUrl = this.baseApi.getRestApi(`/v1/notebook/${id}`);
- return this.httpClient.delete<Rest<Notebook>>(apiUrl).pipe(
+ return this.httpClient.delete<Rest<NotebookInfo>>(apiUrl).pipe(
switchMap((res) => {
if (res.success) {
return of(res.result);
diff --git a/submarine-workbench/workbench-web/src/app/services/polling.ts
b/submarine-workbench/workbench-web/src/app/services/polling.ts
deleted file mode 100644
index 1a9a4a4..0000000
--- a/submarine-workbench/workbench-web/src/app/services/polling.ts
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * 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 { Subscription, Subject, interval, timer } from "rxjs";
-
- export interface BackoffConfig {
- retries?: number;
- interval?: number;
- maxInterval?: number;
- }
-
- const defaultConfig: BackoffConfig = {
- retries: 1,
- interval: 1000,
- maxInterval: 16000
- }
-
- export class ExponentialBackoff {
- private retries: number;
- private interval: number;
- private maxInterval: number;
-
- private scheduler: Subscription;
- private poller: Subject<number>;
- private n: number;
-
- private remainingTries: number;
- private currInterval: number;
-
- constructor(config: BackoffConfig = defaultConfig) {
- const conf = { ...defaultConfig, ...config };
-
- this.retries = conf.retries;
- this.interval = conf.interval;
- this.maxInterval = conf.maxInterval;
-
- this.poller = new Subject<number>();
-
- this.n = 0;
- this.remainingTries = this.retries + 1;
- this.currInterval = this.interval;
- }
-
- public start() {
- if(this.scheduler) {
- this.scheduler.unsubscribe();
- }
-
- this.scheduler = timer(0, this.interval).subscribe(() => {
- this.iterate();
- });
-
- return this.poller;
- }
-
- private iterate() {
- this.n++;
- this.poller.next(this.n);
-
- this.scheduler.unsubscribe();
- this.remainingTries--;
- if (this.remainingTries === 0) {
- this.remainingTries = this.retries;
- this.currInterval = Math.min(this.currInterval * 2,
this.maxInterval);
- }
-
- this.scheduler = interval(this.currInterval).subscribe(() => {
- this.iterate();
- });
- }
-
- public reset() {
- this.n = 0;
- this.currInterval = this.interval;
- this.remainingTries = this.retries + 1;
-
- this.start();
- }
-
- public stop() {
- if (this.scheduler) {
- this.scheduler.unsubscribe();
- }
- }
-
- public getPoller() {
- return this.poller;
- }
-}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]