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 b0ea958 SUBMARINE-722. [WEB] Add template page on workbench
b0ea958 is described below
commit b0ea9583d24418653ef341681d68fedb40219dc6
Author: kobe860219 <[email protected]>
AuthorDate: Tue Apr 13 00:12:50 2021 +0800
SUBMARINE-722. [WEB] Add template page on workbench
### What is this PR for?
Complete template page on workbench. This pr is a UI for users to register
new pre-defined experiment template. Now user could register a pre-defined
experiment template, then run experiments quickly with different
hyper-parameters.
### What type of PR is it?
[Feature]
### Todos
* [ ] - templateIT
### What is the Jira issue?
https://issues.apache.org/jira/browse/SUBMARINE-722
### How should this be tested?
https://github.com/kobe860219/submarine/runs/2325676029
### Screenshots (if appropriate)
<img width="1440" alt="截圖 2021-04-13 上午12 18 14"
src="https://user-images.githubusercontent.com/48027290/114427696-eebf4900-9bed-11eb-8790-0ec75a0e347f.png">
<img width="1440" alt="截圖 2021-04-13 上午12 18 23"
src="https://user-images.githubusercontent.com/48027290/114427715-f252d000-9bed-11eb-9fec-51b5ffaf0d4d.png">
<img width="1440" alt="截圖 2021-04-13 上午12 18 38"
src="https://user-images.githubusercontent.com/48027290/114427720-f383fd00-9bed-11eb-83af-e144914674da.png">
<img width="1440" alt="截圖 2021-04-13 上午12 18 47"
src="https://user-images.githubusercontent.com/48027290/114427728-f41c9380-9bed-11eb-9d4d-0d9b82da4bc0.png">
<img width="1440" alt="截圖 2021-04-13 上午12 19 03"
src="https://user-images.githubusercontent.com/48027290/114427732-f54dc080-9bed-11eb-82bc-09e7d24f02e3.png">
<img width="1440" alt="截圖 2021-04-13 上午12 19 13"
src="https://user-images.githubusercontent.com/48027290/114427736-f5e65700-9bed-11eb-80c2-8700aa0458bb.png">
### 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: Liu Xun <[email protected]>
Closes #555 from kobe860219/SUBMARINE-722 and squashes the following
commits:
48da7b6 [kobe860219] Add image column
fc9e695 [kobe860219] Template page done
20569a4 [kobe860219] Init form
6536789 [kobe860219] Delete done
a4cf16d [kobe860219] Implement template info page.
ce0b758 [kobe860219] Init template page
---
.../src/app/interfaces/experiment-template.ts | 4 +-
.../environment-form/environment-form.component.ts | 5 +-
.../template-form/template-form.component.html | 300 +++++++++++++++++++++
.../template-form/template-form.component.scss} | 56 ++--
.../template-form/template-form.component.ts | 290 ++++++++++++++++++++
.../template-home/template-home.component.html | 35 +++
.../template-home/template-home.component.scss} | 25 +-
.../template-home/template-home.component.ts | 50 ++++
.../template-list/template-list.component.html | 41 +++
.../template-list/template-list.component.scss} | 25 +-
.../template-list/template-list.component.ts} | 28 +-
.../template-info/template-info.component.html | 96 +++++++
.../template-info/template-info.component.scss} | 25 +-
.../template-info/template-info.component.ts | 81 ++++++
.../workbench/template/template-routing.module.ts} | 46 ++--
.../workbench/template/template.component.html | 41 +++
.../workbench/template/template.component.scss} | 25 +-
.../workbench/template/template.component.ts} | 31 +--
.../template.module.ts} | 45 ++--
.../pages/workbench/workbench-routing.module.ts | 5 +
.../src/app/pages/workbench/workbench.component.ts | 36 +--
.../src/app/pages/workbench/workbench.module.ts | 2 +
.../src/app/services/experiment.service.ts | 41 ++-
.../app/services/experiment.validator.service.ts | 10 +-
24 files changed, 1133 insertions(+), 210 deletions(-)
diff --git
a/submarine-workbench/workbench-web/src/app/interfaces/experiment-template.ts
b/submarine-workbench/workbench-web/src/app/interfaces/experiment-template.ts
index d88cbf2..498536f 100644
---
a/submarine-workbench/workbench-web/src/app/interfaces/experiment-template.ts
+++
b/submarine-workbench/workbench-web/src/app/interfaces/experiment-template.ts
@@ -19,14 +19,14 @@
import { ExperimentSpec } from '@submarine/interfaces/experiment-spec';
-interface ExperimentTemplateParamSpec {
+export interface ExperimentTemplateParamSpec {
name: string;
required: string;
description: string;
value: string;
}
-interface ExperimentTemplateSpec {
+export interface ExperimentTemplateSpec {
name: string;
author: string;
description: string;
diff --git
a/submarine-workbench/workbench-web/src/app/pages/workbench/environment/environment-home/environment-form/environment-form.component.ts
b/submarine-workbench/workbench-web/src/app/pages/workbench/environment/environment-home/environment-form/environment-form.component.ts
index ac0ed04..91a408d 100644
---
a/submarine-workbench/workbench-web/src/app/pages/workbench/environment/environment-home/environment-form/environment-form.component.ts
+++
b/submarine-workbench/workbench-web/src/app/pages/workbench/environment/environment-home/environment-form/environment-form.component.ts
@@ -20,10 +20,8 @@
import { Component, EventEmitter, OnInit, Output } from '@angular/core';
import { FormArray, FormBuilder, Validators } from '@angular/forms';
import { EnvironmentService } from
'@submarine/services/environment-services/environment.service';
-import { ExperimentValidatorService } from
'@submarine/services/experiment.validator.service';
import { NzMessageService } from 'ng-zorro-antd';
-import { UploadChangeParam, UploadFile, UploadListType } from
'ng-zorro-antd/upload';
-import { BaseApiService } from '@submarine/services/base-api.service';
+import { UploadChangeParam, UploadFile } from 'ng-zorro-antd/upload';
@Component({
selector: 'submarine-environment-form',
@@ -41,7 +39,6 @@ export class EnvironmentFormComponent implements OnInit {
constructor(
private fb: FormBuilder,
- private experimentValidatorService: ExperimentValidatorService,
private environmentService: EnvironmentService,
private nzMessageService: NzMessageService
) {}
diff --git
a/submarine-workbench/workbench-web/src/app/pages/workbench/template/template-home/template-form/template-form.component.html
b/submarine-workbench/workbench-web/src/app/pages/workbench/template/template-home/template-form/template-form.component.html
new file mode 100644
index 0000000..c954dfd
--- /dev/null
+++
b/submarine-workbench/workbench-web/src/app/pages/workbench/template/template-home/template-form/template-form.component.html
@@ -0,0 +1,300 @@
+<!--
+ ~ 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 Template" [nzWidth]="1000">
+ <div [ngSwitch]="step">
+ <div *nzModalFooter>
+ <button
+ nz-button
+ id="btn-tp-form-preStep"
+ style="float: left"
+ nzType="defult"
+ (click)="step = step - 1"
+ *ngIf="step !== 0"
+ >
+ Pre Step
+ </button>
+ <button nz-button id="btn-tp-form-cancel" nzType="default"
(click)="onCancel()">Cancel</button>
+ <button
+ nz-button
+ id="btn-tp-form-page0"
+ nzType="primary"
+ [disabled]="checkTemplateInfo()"
+ (click)="step = step + 1"
+ *ngIf="step === 0"
+ >
+ Next to experiment spec
+ </button>
+ <button
+ nz-button
+ id="btn-tp-form-page1"
+ nzType="primary"
+ [disabled]="checkExperimentInfo()"
+ (click)="step = step + 1"
+ *ngIf="step === 1"
+ >
+ Next to resource spec
+ </button>
+ <button
+ nz-button
+ id="btn-tp-form-creat"
+ nzType="primary"
+ [disabled]="checkResourceSpec()"
+ (click)="createTemplate()"
+ *ngIf="step === 2"
+ >
+ Creat
+ </button>
+ </div>
+ <form nz-form [formGroup]="templateForm" nzLayout="horizontal">
+ <div *ngSwitchCase="0" style="margin-top: 10px">
+ <nz-form-item>
+ <nz-form-label [nzSm]="6" [nzXs]="24" nzRequired
nzFor="templateName">Template Name</nz-form-label>
+ <nz-form-control [nzSm]="14" [nzXs]="24" nzErrorTip="Please input
template name!">
+ <input
+ required
+ nz-input
+ type="text"
+ name="templateName"
+ id="templateName"
+ formControlName="templateName"
+ placeholder="Name of template."
+ />
+ </nz-form-control>
+ </nz-form-item>
+ <nz-form-item>
+ <nz-form-label [nzSm]="6" [nzXs]="24" nzRequired
nzFor="description">Description</nz-form-label>
+ <nz-form-control [nzSm]="14" [nzXs]="24" nzErrorTip="Please input
description for this template!">
+ <textarea
+ nz-input
+ [nzAutosize]="{ minRows: 1, maxRows: 4 }"
+ name="description"
+ formControlName="description"
+ id="description"
+ placeholder="Description for this template."
+ ></textarea>
+ </nz-form-control>
+ </nz-form-item>
+ <div formArrayName="parameters">
+ <ng-container *ngFor="let param of parameters.controls; index as i">
+ <nz-form-item>
+ <nz-form-label nzRequired [nzSm]="6" [nzXs]="24">Param{{ i + 1
}}</nz-form-label>
+ <div [formGroupName]="i">
+ <div nz-col nzSpan="12">
+ <input
+ style="width: 50%"
+ nz-input
+ required
+ id="name{{ i }}"
+ name="name{{ i }}"
+ placeholder="Name"
+ formControlName="name"
+ />
+ <input
+ style="width: 40%; 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(parameters, i)"
+ ></i>
+ <br />
+ <input
+ style="margin-top: 5px"
+ nz-input
+ required
+ id="description{{ i }}"
+ name="description{{ i }}"
+ placeholder="Description"
+ formControlName="description"
+ />
+ </div>
+ </div>
+ </nz-form-item>
+ </ng-container>
+ </div>
+ <button
+ nz-button
+ style="display: block; margin: auto"
+ id="btn-addParam"
+ type="default"
+ (click)="onCreateParam()"
+ >
+ Add Param
+ </button>
+ </div>
+ <div *ngSwitchCase="1" style="margin-top: 10px">
+ <nz-form-item>
+ <nz-form-label [nzSm]="6" [nzXs]="24" nzRequired
nzFor="image">Image</nz-form-label>
+ <nz-form-control [nzSm]="14" [nzXs]="24" nzErrorTip="Please input
image!">
+ <input
+ required
+ nz-input
+ type="text"
+ name="image"
+ id="image"
+ formControlName="image"
+ placeholder="Image for experiment."
+ />
+ </nz-form-control>
+ </nz-form-item>
+ <nz-form-item>
+ <nz-form-label [nzSm]="6" [nzXs]="24" nzRequired
nzFor="cmd">Command</nz-form-label>
+ <nz-form-control [nzSm]="14" [nzXs]="24" nzErrorTip="Please input
command for this template!">
+ <textarea
+ nz-input
+ [nzAutosize]="{ minRows: 1, maxRows: 4 }"
+ name="cmd"
+ formControlName="cmd"
+ id="cmd"
+ placeholder="Command for this template."
+ ></textarea>
+ </nz-form-control>
+ </nz-form-item>
+ <div formArrayName="envVars">
+ <ng-container *ngFor="let env of envVars.controls; index as i">
+ <nz-form-item>
+ <nz-form-label nzRequired [nzSm]="6" [nzXs]="24">Env Var{{ 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="btn-addEnv"
type="default" (click)="onCreateEnv()">
+ Add New Environment Variable
+ </button>
+ </div>
+ <div *ngSwitchCase="2" style="margin-top: 10px">
+ <nz-radio-group [(ngModel)]="framework" [ngModelOptions]="{
standalone: true }">
+ <label nz-radio nzValue="Tensorflow" (click)="deleteAllItem(specs);
jobTypes = 'Distributed Tensorflow'">
+ Distributed Tensorflow
+ </label>
+ <label nz-radio nzValue="Pytorch" (click)="deleteAllItem(specs);
jobType = 'Distributed Pytorch'">
+ Distributed PyTorch
+ </label>
+ <label
+ nz-radio
+ nzValue="Standalone"
+ (click)="deleteAllItem(specs); onCreateSpec(); jobType =
'Standalone Script'"
+ >
+ Standalone Script
+ </label>
+ </nz-radio-group>
+ <br />
+ <button
+ nz-button
+ *ngIf="framework !== 'Standalone'"
+ id="spec-btn"
+ nzType="default"
+ style="margin-top: 10px"
+ (click)="onCreateSpec()"
+ >
+ Add new spec
+ </button>
+ <ul formArrayName="specs" class="list-container">
+ <ng-container *ngFor="let spec of specs.controls; index as i">
+ <li *ngIf="i | indexInRange: currentSpecPage:PAGESIZE"
[formGroupName]="i" class="input-group">
+ <div id="spec{{ i }}" *ngIf="framework !== 'Standalone'">
+ <label>Spec name</label>
+ <nz-select formControlName="name" nzPlaceHolder="Spec name"
[ngSwitch]="framework">
+ <div *ngSwitchCase="'Tensorflow'">
+ <nz-option *ngFor="let spec of TF_SPECNAMES"
[nzValue]="spec" [nzLabel]="spec"></nz-option>
+ </div>
+ <div *ngSwitchCase="'Pytorch'">
+ <nz-option *ngFor="let spec of PYTORCH_SPECNAMES"
[nzValue]="spec" [nzLabel]="spec"></nz-option>
+ </div>
+ </nz-select>
+ </div>
+ <div *ngIf="framework !== 'Standalone'">
+ <label>Number of Replica</label>
+ <input
+ nz-input
+ name="replica{{ i }}"
+ type="number"
+ placeholder="number of replica"
+ formControlName="replicas"
+ />
+ </div>
+ <div>
+ <label>Number of cpu</label>
+ <input nz-input name="cpu{{ i }}" type="number"
placeholder="number of cpu" formControlName="cpus" />
+ </div>
+ <div>
+ <label>Number of gpu</label>
+ <input nz-input name="gpu{{ i }}" type="number"
placeholder="number of gpu" formControlName="gpus" />
+ </div>
+ <div id="memory{{ i }}">
+ <label>Memory</label>
+ <div formGroupName="memory" class="memory-input-group">
+ <input
+ nz-input
+ name="memory{{ i }}"
+ type="number"
+ step="1024"
+ placeholder="Enter number"
+ formControlName="num"
+ />
+ <nz-select formControlName="unit">
+ <nz-option *ngFor="let unit of MEMORY_UNITS"
[nzValue]="unit" [nzLabel]="unit"></nz-option>
+ </nz-select>
+ </div>
+ </div>
+ <i nz-icon nzType="close-circle" nzTheme="fill"
class="delete-icon" (click)="deleteItem(specs, i)"></i>
+ </li>
+ </ng-container>
+ </ul>
+ </div>
+ </form>
+ </div>
+</nz-modal>
diff --git
a/submarine-workbench/workbench-web/src/app/interfaces/experiment-template.ts
b/submarine-workbench/workbench-web/src/app/pages/workbench/template/template-home/template-form/template-form.component.scss
similarity index 60%
copy from
submarine-workbench/workbench-web/src/app/interfaces/experiment-template.ts
copy to
submarine-workbench/workbench-web/src/app/pages/workbench/template/template-home/template-form/template-form.component.scss
index d88cbf2..5e9e45b 100644
---
a/submarine-workbench/workbench-web/src/app/interfaces/experiment-template.ts
+++
b/submarine-workbench/workbench-web/src/app/pages/workbench/template/template-home/template-form/template-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
@@ -16,25 +16,47 @@
* specific language governing permissions and limitations
* under the License.
*/
+
+.list-container {
+ padding: 0;
+ margin: 1rem 0;
+}
-import { ExperimentSpec } from '@submarine/interfaces/experiment-spec';
+.input-group {
+ display: flex;
+ align-items: center;
+ font-size: .8rem;
+ &:not(:first-child) {
+ margin-top: 1rem;
+ }
-interface ExperimentTemplateParamSpec {
- name: string;
- required: string;
- description: string;
- value: string;
-}
+ & > *:not(:last-child) {
+ margin-right: .8rem;
+ }
+
+ & > div:nth-child(2), & > div:nth-child(3) {
+ flex: 0 1 20%;
+ }
-interface ExperimentTemplateSpec {
- name: string;
- author: string;
- description: string;
- parameters: ExperimentTemplateParamSpec[];
- experimentSpec: ExperimentSpec;
+ & > div:nth-child(1), & > div:nth-child(4) {
+ flex: 0 1 20%;
+ }
+
+ & i {
+ cursor: pointer;
+ font-size: 20px;
+ margin-top: 20px;
+ }
}
-export interface ExperimentTemplate {
- experimentTemplateId: string;
- experimentTemplateSpec: ExperimentTemplateSpec;
+.memory-input-group {
+ display: flex;
+ & input {
+ width: 70%;
+ }
+
+ & > * {
+ width: 30%;
+ }
}
+
diff --git
a/submarine-workbench/workbench-web/src/app/pages/workbench/template/template-home/template-form/template-form.component.ts
b/submarine-workbench/workbench-web/src/app/pages/workbench/template/template-home/template-form/template-form.component.ts
new file mode 100644
index 0000000..da0585b
--- /dev/null
+++
b/submarine-workbench/workbench-web/src/app/pages/workbench/template/template-home/template-form/template-form.component.ts
@@ -0,0 +1,290 @@
+/*
+ * 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, Output, EventEmitter } from '@angular/core';
+import { FormArray, FormControl, FormGroup, Validators, FormBuilder } from
'@angular/forms';
+import { ExperimentService } from '@submarine/services/experiment.service';
+import { ExperimentValidatorService } from
'@submarine/services/experiment.validator.service';
+import { Specs } from '@submarine/interfaces/experiment-spec';
+import { ExperimentTemplateSpec } from
'@submarine/interfaces/experiment-template';
+import { NzMessageService } from 'ng-zorro-antd';
+
+@Component({
+ selector: 'submarine-template-form',
+ templateUrl: './template-form.component.html',
+ styleUrls: ['./template-form.component.scss'],
+})
+export class TemplateFormComponent implements OnInit {
+ @Output() private updater = new EventEmitter<string>();
+
+ step: number = 0;
+
+ defaultExperimentName = '{{experiment_name}}';
+
+ isVisible: boolean;
+
+ templateForm: FormGroup;
+ finaleTemplate;
+
+ jobType = 'Distributed Tensorflow';
+ framework = 'Tensorflow';
+ currentSpecPage = 1;
+ PAGESIZE = 5;
+
+ // Constants
+ TF_SPECNAMES = ['Master', 'Worker', 'Ps'];
+ PYTORCH_SPECNAMES = ['Master', 'Worker'];
+ defaultSpecName = 'worker';
+ MEMORY_UNITS = ['M', 'G'];
+
+ AUTHOR = 'admin';
+ NAMESPACE = 'default';
+
+ constructor(
+ private experimentValidatorService: ExperimentValidatorService,
+ private fb: FormBuilder,
+ private experimentService: ExperimentService,
+ private nzMessageService: NzMessageService
+ ) {}
+
+ ngOnInit() {
+ this.templateForm = this.fb.group({
+ templateName: [null, Validators.required],
+ description: [null, Validators.required],
+ parameters: this.fb.array([],
[this.experimentValidatorService.nameValidatorFactory('name')]),
+ code: [null],
+ specs: this.fb.array([],
[this.experimentValidatorService.nameValidatorFactory('name')]),
+ cmd: [null, Validators.required],
+ envVars: this.fb.array([],
[this.experimentValidatorService.nameValidatorFactory('key')]),
+ image: [null, Validators.required],
+ });
+ }
+
+ get templateName() {
+ return this.templateForm.get('templateName');
+ }
+
+ get description() {
+ return this.templateForm.get('description');
+ }
+
+ get parameters() {
+ return this.templateForm.get('parameters') as FormArray;
+ }
+
+ get code() {
+ return this.templateForm.get('code');
+ }
+
+ get specs() {
+ return this.templateForm.get('specs') as FormArray;
+ }
+
+ get cmd() {
+ return this.templateForm.get('cmd');
+ }
+
+ get envVars() {
+ return this.templateForm.get('envVars') as FormArray;
+ }
+
+ get image() {
+ return this.templateForm.get('image');
+ }
+
+ initModal() {
+ this.isVisible = true;
+ this.initForm();
+ }
+
+ initForm() {
+ this.templateName.reset();
+ this.description.reset();
+ this.parameters.clear();
+ this.specs.clear();
+ this.cmd.reset();
+ this.envVars.clear();
+ this.image.reset();
+ }
+
+ checkTemplateInfo() {
+ return this.templateName.invalid || this.description.invalid ||
this.parameters.invalid;
+ }
+
+ checkExperimentInfo() {
+ return this.image.invalid || this.cmd.invalid || this.envVars.invalid;
+ }
+
+ checkResourceSpec() {
+ return this.specs.invalid || this.specs.length < 1;
+ }
+
+ onCancel() {
+ this.isVisible = false;
+ this.step = 0;
+ }
+
+ createParam(defaultName: string = '', defaultValue: string = '') {
+ return new FormGroup(
+ {
+ name: new FormControl(defaultName, [Validators.required]),
+ value: new FormControl(defaultValue, [Validators.required]),
+ required: new FormControl(true, [Validators.required]),
+ description: new FormControl('', [Validators.required]),
+ },
+ [this.experimentValidatorService.paramValidator]
+ );
+ }
+
+ onCreateParam() {
+ const param = this.createParam();
+ this.parameters.push(param);
+ }
+
+ createEnv(defaultKey: string = '', defaultValue: string = '') {
+ return new FormGroup(
+ {
+ key: new FormControl(defaultKey, [Validators.required]),
+ value: new FormControl(defaultValue, [Validators.required]),
+ },
+ [this.experimentValidatorService.envValidator]
+ );
+ }
+
+ onCreateEnv() {
+ const env = this.createEnv();
+ this.envVars.push(env);
+ }
+
+ createSpec(
+ defaultName: string = 'Worker',
+ defaultReplica: number = 1,
+ defaultCpu: number = 1,
+ defaultGpu: number = 0,
+ defaultMemory: number = 1024,
+ defaultUnit: string = 'M'
+ ): FormGroup {
+ return new FormGroup(
+ {
+ name: new FormControl(defaultName, [Validators.required]),
+ replicas: new FormControl(defaultReplica, [Validators.min(1),
Validators.required]),
+ cpus: new FormControl(defaultCpu, [Validators.min(1),
Validators.required]),
+ gpus: new FormControl(defaultGpu, [Validators.min(0),
Validators.required]),
+ memory: new FormGroup(
+ {
+ num: new FormControl(defaultMemory, [Validators.required]),
+ unit: new FormControl(defaultUnit, [Validators.required]),
+ },
+ [this.experimentValidatorService.memoryValidator]
+ ),
+ },
+ [this.experimentValidatorService.specValidator]
+ );
+ }
+
+ onCreateSpec() {
+ const spec = this.createSpec();
+ this.specs.push(spec);
+ }
+
+ deleteItem(arr: FormArray, index: number) {
+ arr.removeAt(index);
+ }
+
+ deleteAllItem(arr: FormArray) {
+ arr.clear();
+ }
+
+ createDefaultParameter() {
+ return new FormGroup({
+ name: new FormControl('experiment_name'),
+ value: new FormControl(null),
+ required: new FormControl(true),
+ description: new FormControl('The name of experiment.'),
+ });
+ }
+
+ constructTemplateSpec() {
+ const defaultParameter = this.createDefaultParameter();
+ this.parameters.push(defaultParameter);
+
+ const specs: Specs = {};
+ for (const spec of this.specs.controls) {
+ if (spec.get('name').value) {
+ specs[spec.get('name').value] = {
+ replicas: spec.get('replicas').value,
+ resources:
`cpu=${spec.get('cpus').value},nvidia.com/gpu=${spec.get('gpus').value},memory=${
+ spec.get('memory').get('num').value
+ }${spec.get('memory').get('unit').value}`,
+ };
+ }
+ }
+
+ const envVars = {};
+ for (const envVar of this.envVars.controls) {
+ if (envVar.get('key').value) {
+ envVars[envVar.get('key').value] = envVar.get('value').value;
+ }
+ }
+
+ const newTemplateSpec: ExperimentTemplateSpec = {
+ name: this.templateForm.get('templateName').value,
+ author: this.AUTHOR,
+ description: this.templateForm.get('description').value,
+ parameters: this.templateForm.get('parameters').value,
+ experimentSpec: {
+ meta: {
+ cmd: this.templateForm.get('cmd').value,
+ name: this.defaultExperimentName,
+ envVars: envVars,
+ framework: this.framework,
+ namespace: this.NAMESPACE,
+ },
+ spec: specs,
+ environment: {
+ image: this.templateForm.get('image').value,
+ },
+ },
+ };
+
+ console.log(newTemplateSpec);
+ return newTemplateSpec;
+ }
+
+ createTemplate() {
+ const templateSpec = this.constructTemplateSpec();
+ this.experimentService.createTemplate(templateSpec).subscribe({
+ next: () => {},
+ error: (msg) => {
+ this.nzMessageService.error(`${msg}, please try again`, {
+ nzPauseOnHover: true,
+ });
+ },
+ complete: () => {
+ this.nzMessageService.success('Template creation succeeds');
+ this.isVisible = false;
+ this.sendUpdate('Update Template List');
+ },
+ });
+ }
+
+ sendUpdate(updateInfo: string) {
+ this.updater.emit(updateInfo);
+ }
+}
diff --git
a/submarine-workbench/workbench-web/src/app/pages/workbench/template/template-home/template-home.component.html
b/submarine-workbench/workbench-web/src/app/pages/workbench/template/template-home/template-home.component.html
new file mode 100644
index 0000000..6d46957
--- /dev/null
+++
b/submarine-workbench/workbench-web/src/app/pages/workbench/template/template-home/template-home.component.html
@@ -0,0 +1,35 @@
+<!--
+ ~ 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-newTemplate"
+ nzType="primary"
+ style="margin: 10px 4px 10px 4px"
+ (click)="form.initModal()"
+ >
+ <i nz-icon nzType="plus"></i>
+ New Template
+ </button>
+ </div>
+ <submarine-template-list
[templateList]="templateList"></submarine-template-list>
+ <submarine-template-form #form
(updater)="updateTemplateList($event)"></submarine-template-form>
+</div>
diff --git
a/submarine-workbench/workbench-web/src/app/interfaces/experiment-template.ts
b/submarine-workbench/workbench-web/src/app/pages/workbench/template/template-home/template-home.component.scss
similarity index 62%
copy from
submarine-workbench/workbench-web/src/app/interfaces/experiment-template.ts
copy to
submarine-workbench/workbench-web/src/app/pages/workbench/template/template-home/template-home.component.scss
index d88cbf2..61d2115 100644
---
a/submarine-workbench/workbench-web/src/app/interfaces/experiment-template.ts
+++
b/submarine-workbench/workbench-web/src/app/pages/workbench/template/template-home/template-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,25 +16,4 @@
* specific language governing permissions and limitations
* under the License.
*/
-
-import { ExperimentSpec } from '@submarine/interfaces/experiment-spec';
-
-interface ExperimentTemplateParamSpec {
- name: string;
- required: string;
- description: string;
- value: string;
-}
-
-interface ExperimentTemplateSpec {
- name: string;
- author: string;
- description: string;
- parameters: ExperimentTemplateParamSpec[];
- experimentSpec: ExperimentSpec;
-}
-
-export interface ExperimentTemplate {
- experimentTemplateId: string;
- experimentTemplateSpec: ExperimentTemplateSpec;
-}
+
\ No newline at end of file
diff --git
a/submarine-workbench/workbench-web/src/app/pages/workbench/template/template-home/template-home.component.ts
b/submarine-workbench/workbench-web/src/app/pages/workbench/template/template-home/template-home.component.ts
new file mode 100644
index 0000000..694f2e3
--- /dev/null
+++
b/submarine-workbench/workbench-web/src/app/pages/workbench/template/template-home/template-home.component.ts
@@ -0,0 +1,50 @@
+/*
+ * 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 } from '@angular/core';
+import { ExperimentTemplate } from '@submarine/interfaces/experiment-template';
+import { ExperimentService } from '@submarine/services/experiment.service';
+import { TemplateFormComponent } from
'./template-form/template-form.component';
+
+@Component({
+ selector: 'submarine-template-home',
+ templateUrl: './template-home.component.html',
+ styleUrls: ['./template-home.component.scss'],
+})
+export class TemplateHomeComponent implements OnInit {
+ constructor(private experimentService: ExperimentService) {}
+
+ templateList: ExperimentTemplate[];
+
+ @ViewChild('form', { static: true }) form: TemplateFormComponent;
+
+ ngOnInit() {
+ this.fetchTemplateList();
+ }
+
+ fetchTemplateList() {
+ this.experimentService.fetchExperimentTemplateList().subscribe((res) => {
+ this.templateList = res;
+ });
+ }
+
+ updateTemplateList(msg: string) {
+ this.fetchTemplateList();
+ }
+}
diff --git
a/submarine-workbench/workbench-web/src/app/pages/workbench/template/template-home/template-list/template-list.component.html
b/submarine-workbench/workbench-web/src/app/pages/workbench/template/template-home/template-list/template-list.component.html
new file mode 100644
index 0000000..d20d8cc
--- /dev/null
+++
b/submarine-workbench/workbench-web/src/app/pages/workbench/template/template-home/template-list/template-list.component.html
@@ -0,0 +1,41 @@
+<!--
+ ~ 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="templateTable" nzBordered #basicTable [nzData]="templateList"
[nzNoResult]="'No data'">
+ <thead>
+ <tr>
+ <th>Template Name</th>
+ <th>Framework</th>
+ <th>Description</th>
+ <th>Image</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr *ngFor="let data of basicTable.data; let i = index">
+ <td>
+ <a [routerLink]="['info', data.experimentTemplateSpec.name]">
+ {{ data.experimentTemplateSpec.name }}
+ </a>
+ </td>
+ <td>{{ data.experimentTemplateSpec.experimentSpec.meta.framework }}</td>
+ <td>{{ data.experimentTemplateSpec.description }}</td>
+ <td>{{ data.experimentTemplateSpec.experimentSpec.environment.image
}}</td>
+ </tr>
+ </tbody>
+</nz-table>
diff --git
a/submarine-workbench/workbench-web/src/app/interfaces/experiment-template.ts
b/submarine-workbench/workbench-web/src/app/pages/workbench/template/template-home/template-list/template-list.component.scss
similarity index 62%
copy from
submarine-workbench/workbench-web/src/app/interfaces/experiment-template.ts
copy to
submarine-workbench/workbench-web/src/app/pages/workbench/template/template-home/template-list/template-list.component.scss
index d88cbf2..61d2115 100644
---
a/submarine-workbench/workbench-web/src/app/interfaces/experiment-template.ts
+++
b/submarine-workbench/workbench-web/src/app/pages/workbench/template/template-home/template-list/template-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,25 +16,4 @@
* specific language governing permissions and limitations
* under the License.
*/
-
-import { ExperimentSpec } from '@submarine/interfaces/experiment-spec';
-
-interface ExperimentTemplateParamSpec {
- name: string;
- required: string;
- description: string;
- value: string;
-}
-
-interface ExperimentTemplateSpec {
- name: string;
- author: string;
- description: string;
- parameters: ExperimentTemplateParamSpec[];
- experimentSpec: ExperimentSpec;
-}
-
-export interface ExperimentTemplate {
- experimentTemplateId: string;
- experimentTemplateSpec: ExperimentTemplateSpec;
-}
+
\ No newline at end of file
diff --git
a/submarine-workbench/workbench-web/src/app/interfaces/experiment-template.ts
b/submarine-workbench/workbench-web/src/app/pages/workbench/template/template-home/template-list/template-list.component.ts
similarity index 62%
copy from
submarine-workbench/workbench-web/src/app/interfaces/experiment-template.ts
copy to
submarine-workbench/workbench-web/src/app/pages/workbench/template/template-home/template-list/template-list.component.ts
index d88cbf2..e3393e1 100644
---
a/submarine-workbench/workbench-web/src/app/interfaces/experiment-template.ts
+++
b/submarine-workbench/workbench-web/src/app/pages/workbench/template/template-home/template-list/template-list.component.ts
@@ -17,24 +17,18 @@
* under the License.
*/
-import { ExperimentSpec } from '@submarine/interfaces/experiment-spec';
+import { Component, Input, OnInit } from '@angular/core';
+import { ExperimentTemplate } from '@submarine/interfaces/experiment-template';
-interface ExperimentTemplateParamSpec {
- name: string;
- required: string;
- description: string;
- value: string;
-}
+@Component({
+ selector: 'submarine-template-list',
+ templateUrl: './template-list.component.html',
+ styleUrls: ['./template-list.component.scss'],
+})
+export class TemplateListComponent implements OnInit {
+ @Input() templateList: ExperimentTemplate[];
-interface ExperimentTemplateSpec {
- name: string;
- author: string;
- description: string;
- parameters: ExperimentTemplateParamSpec[];
- experimentSpec: ExperimentSpec;
-}
+ constructor() {}
-export interface ExperimentTemplate {
- experimentTemplateId: string;
- experimentTemplateSpec: ExperimentTemplateSpec;
+ ngOnInit() {}
}
diff --git
a/submarine-workbench/workbench-web/src/app/pages/workbench/template/template-info/template-info.component.html
b/submarine-workbench/workbench-web/src/app/pages/workbench/template/template-info/template-info.component.html
new file mode 100644
index 0000000..6ecfb50
--- /dev/null
+++
b/submarine-workbench/workbench-web/src/app/pages/workbench/template/template-info/template-info.component.html
@@ -0,0 +1,96 @@
+<!--
+ ~ 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 *ngIf="isLoading == false" style="margin: 15px; padding: 15px;
background-color: white">
+ <div align="right">
+ <button nz-button id="btn-backHome" nzType="primary" style="margin: 10px
4px 10px 4px" (click)="backHome()">
+ <i nz-icon nzType="caret-left"></i>
+ Back
+ </button>
+ <button
+ nz-button
+ id="btn-delTemplate"
+ nzType="primary"
+ style="margin: 10px 4px 10px 4px"
+ nz-popconfirm
+ nzPlacement="left"
+ nzTitle="Are you sure you want to delete?"
+ nzCancelText="Cancel"
+ nzOkText="Ok"
+ (nzOnConfirm)="deleteTemplate()"
+ >
+ <i nz-icon nzType="delete"></i>
+ Delete Template
+ </button>
+ </div>
+ <nz-descriptions nzTitle="Template Info" nzBordered [nzColumn]="{ xxl: 4,
xl: 3, lg: 3, md: 3, sm: 2, xs: 1 }">
+ <nz-descriptions-item nzTitle="Template Name">
+ {{ templateInfo.experimentTemplateSpec.name }}
+ </nz-descriptions-item>
+ <nz-descriptions-item nzTitle="Framework">
+ {{ templateInfo.experimentTemplateSpec.experimentSpec.meta.framework }}
+ </nz-descriptions-item>
+ <nz-descriptions-item nzTitle="Author">
+ {{ templateInfo.experimentTemplateSpec.author }}
+ </nz-descriptions-item>
+ <nz-descriptions-item nzTitle="Description">
+ {{ templateInfo.experimentTemplateSpec.description }}
+ </nz-descriptions-item>
+ </nz-descriptions>
+ <nz-tabset>
+ <nz-tab nzTitle="ExperimentSpec">
+ <nz-descriptions nzTitle="Experiment Spec" nzBordered [nzColumn]="{ xxl:
4, xl: 3, lg: 3, md: 3, sm: 2, xs: 1 }">
+ <nz-descriptions-item nzTitle="Namespace">
+ {{ templateInfo.experimentTemplateSpec.experimentSpec.meta.namespace
}}
+ </nz-descriptions-item>
+ <nz-descriptions-item nzTitle="Image">
+ {{
templateInfo.experimentTemplateSpec.experimentSpec.environment.image }}
+ </nz-descriptions-item>
+ <nz-descriptions-item nzTitle="Command">
+ {{ templateInfo.experimentTemplateSpec.experimentSpec.meta.cmd }}
+ </nz-descriptions-item>
+ <nz-descriptions-item nzTitle="Environment Varibles">
+ {{ templateVars }}
+ </nz-descriptions-item>
+ </nz-descriptions>
+ </nz-tab>
+ <nz-tab nzTitle="Parameters">
+ <nz-table #basicTable
[nzData]="templateInfo.experimentTemplateSpec.parameters">
+ <thead>
+ <tr>
+ <th>Name</th>
+ <th>Value</th>
+ <th>Description</th>
+ <th>Required</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr *ngFor="let data of basicTable.data">
+ <td>{{ data.name }}</td>
+ <td>{{ data.value }}</td>
+ <td>{{ data.description }}</td>
+ <td>
+ {{ data.required }}
+ </td>
+ </tr>
+ </tbody>
+ </nz-table>
+ </nz-tab>
+ </nz-tabset>
+</div>
diff --git
a/submarine-workbench/workbench-web/src/app/interfaces/experiment-template.ts
b/submarine-workbench/workbench-web/src/app/pages/workbench/template/template-info/template-info.component.scss
similarity index 62%
copy from
submarine-workbench/workbench-web/src/app/interfaces/experiment-template.ts
copy to
submarine-workbench/workbench-web/src/app/pages/workbench/template/template-info/template-info.component.scss
index d88cbf2..61d2115 100644
---
a/submarine-workbench/workbench-web/src/app/interfaces/experiment-template.ts
+++
b/submarine-workbench/workbench-web/src/app/pages/workbench/template/template-info/template-info.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,25 +16,4 @@
* specific language governing permissions and limitations
* under the License.
*/
-
-import { ExperimentSpec } from '@submarine/interfaces/experiment-spec';
-
-interface ExperimentTemplateParamSpec {
- name: string;
- required: string;
- description: string;
- value: string;
-}
-
-interface ExperimentTemplateSpec {
- name: string;
- author: string;
- description: string;
- parameters: ExperimentTemplateParamSpec[];
- experimentSpec: ExperimentSpec;
-}
-
-export interface ExperimentTemplate {
- experimentTemplateId: string;
- experimentTemplateSpec: ExperimentTemplateSpec;
-}
+
\ No newline at end of file
diff --git
a/submarine-workbench/workbench-web/src/app/pages/workbench/template/template-info/template-info.component.ts
b/submarine-workbench/workbench-web/src/app/pages/workbench/template/template-info/template-info.component.ts
new file mode 100644
index 0000000..b4da987
--- /dev/null
+++
b/submarine-workbench/workbench-web/src/app/pages/workbench/template/template-info/template-info.component.ts
@@ -0,0 +1,81 @@
+/*
+ * 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 } from '@angular/core';
+import { ActivatedRoute, Router } from '@angular/router';
+import { ExperimentTemplate } from '@submarine/interfaces/experiment-template';
+import { ExperimentService } from '@submarine/services/experiment.service';
+import { NzMessageService } from 'ng-zorro-antd';
+
+@Component({
+ selector: 'submarine-template-info',
+ templateUrl: './template-info.component.html',
+ styleUrls: ['./template-info.component.scss'],
+})
+export class TemplateInfoComponent implements OnInit {
+ isLoading = true;
+ templateName;
+ templateInfo: ExperimentTemplate;
+ templateVars: string;
+
+ constructor(
+ private router: Router,
+ private route: ActivatedRoute,
+ private experimentService: ExperimentService,
+ private nzMessageService: NzMessageService
+ ) {}
+
+ ngOnInit() {
+ this.templateName = this.route.snapshot.params.name;
+ this.getTemplateInfo(this.templateName);
+ this.experimentService.emitInfo(this.templateName);
+ }
+
+ getTemplateInfo(name: string) {
+ this.experimentService.querySpecificTemplate(name).subscribe(
+ (item) => {
+ this.templateInfo = item;
+ this.templateVars =
JSON.stringify(this.templateInfo.experimentTemplateSpec.experimentSpec.meta.envVars);
+ console.log(this.templateInfo.experimentTemplateSpec);
+ this.isLoading = false;
+ },
+ (err) => {
+ this.nzMessageService.error('Cannot load ' + name);
+ this.router.navigate(['/workbench/template']);
+ }
+ );
+ }
+
+ deleteTemplate() {
+ this.experimentService.deleteTemplate(this.templateName).subscribe(
+ () => {
+ this.router.navigate(['/workbench/template']);
+ },
+ (err) => {
+ this.nzMessageService.error(err);
+ }
+ );
+ }
+
+ backHome() {
+ this.router.navigate(['/workbench/template']);
+ this.templateName = null;
+ this.experimentService.emitInfo(this.templateName);
+ }
+}
diff --git
a/submarine-workbench/workbench-web/src/app/interfaces/experiment-template.ts
b/submarine-workbench/workbench-web/src/app/pages/workbench/template/template-routing.module.ts
similarity index 52%
copy from
submarine-workbench/workbench-web/src/app/interfaces/experiment-template.ts
copy to
submarine-workbench/workbench-web/src/app/pages/workbench/template/template-routing.module.ts
index d88cbf2..59b240e 100644
---
a/submarine-workbench/workbench-web/src/app/interfaces/experiment-template.ts
+++
b/submarine-workbench/workbench-web/src/app/pages/workbench/template/template-routing.module.ts
@@ -17,24 +17,32 @@
* under the License.
*/
-import { ExperimentSpec } from '@submarine/interfaces/experiment-spec';
+import { NgModule } from '@angular/core';
+import { RouterModule, Routes } from '@angular/router';
+import { TemplateComponent } from './template.component';
+import { TemplateHomeComponent } from
'./template-home/template-home.component';
+import { TemplateInfoComponent } from
'./template-info/template-info.component';
-interface ExperimentTemplateParamSpec {
- name: string;
- required: string;
- description: string;
- value: string;
-}
+const routes: Routes = [
+ {
+ path: '',
+ component: TemplateComponent,
+ children: [
+ {
+ path: '',
+ pathMatch: 'full',
+ component: TemplateHomeComponent,
+ },
+ {
+ path: 'info/:name',
+ component: TemplateInfoComponent,
+ },
+ ],
+ },
+];
-interface ExperimentTemplateSpec {
- name: string;
- author: string;
- description: string;
- parameters: ExperimentTemplateParamSpec[];
- experimentSpec: ExperimentSpec;
-}
-
-export interface ExperimentTemplate {
- experimentTemplateId: string;
- experimentTemplateSpec: ExperimentTemplateSpec;
-}
+@NgModule({
+ imports: [RouterModule.forChild(routes)],
+ exports: [RouterModule],
+})
+export class TemplateRoutingModule {}
diff --git
a/submarine-workbench/workbench-web/src/app/pages/workbench/template/template.component.html
b/submarine-workbench/workbench-web/src/app/pages/workbench/template/template.component.html
new file mode 100644
index 0000000..6c6011e
--- /dev/null
+++
b/submarine-workbench/workbench-web/src/app/pages/workbench/template/template.component.html
@@ -0,0 +1,41 @@
+<!--
+ ~ 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-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', 'template']" (click)="templateName
= null">template</a>
+ </nz-breadcrumb-item>
+ <nz-breadcrumb-item *ngIf="templateName != null">
+ {{ templateName }}
+ </nz-breadcrumb-item>
+ </nz-breadcrumb>
+ <div *ngIf="templateName == null">
+ <br />
+ <h2>Template</h2>
+ <nz-content>Apache submarine support predefined-template for
experiment.</nz-content>
+ </div>
+ <br />
+ </div>
+ <router-outlet></router-outlet>
+</nz-layout>
diff --git
a/submarine-workbench/workbench-web/src/app/interfaces/experiment-template.ts
b/submarine-workbench/workbench-web/src/app/pages/workbench/template/template.component.scss
similarity index 62%
copy from
submarine-workbench/workbench-web/src/app/interfaces/experiment-template.ts
copy to
submarine-workbench/workbench-web/src/app/pages/workbench/template/template.component.scss
index d88cbf2..61d2115 100644
---
a/submarine-workbench/workbench-web/src/app/interfaces/experiment-template.ts
+++
b/submarine-workbench/workbench-web/src/app/pages/workbench/template/template.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,25 +16,4 @@
* specific language governing permissions and limitations
* under the License.
*/
-
-import { ExperimentSpec } from '@submarine/interfaces/experiment-spec';
-
-interface ExperimentTemplateParamSpec {
- name: string;
- required: string;
- description: string;
- value: string;
-}
-
-interface ExperimentTemplateSpec {
- name: string;
- author: string;
- description: string;
- parameters: ExperimentTemplateParamSpec[];
- experimentSpec: ExperimentSpec;
-}
-
-export interface ExperimentTemplate {
- experimentTemplateId: string;
- experimentTemplateSpec: ExperimentTemplateSpec;
-}
+
\ No newline at end of file
diff --git
a/submarine-workbench/workbench-web/src/app/interfaces/experiment-template.ts
b/submarine-workbench/workbench-web/src/app/pages/workbench/template/template.component.ts
similarity index 58%
copy from
submarine-workbench/workbench-web/src/app/interfaces/experiment-template.ts
copy to
submarine-workbench/workbench-web/src/app/pages/workbench/template/template.component.ts
index d88cbf2..9eea347 100644
---
a/submarine-workbench/workbench-web/src/app/interfaces/experiment-template.ts
+++
b/submarine-workbench/workbench-web/src/app/pages/workbench/template/template.component.ts
@@ -17,24 +17,21 @@
* under the License.
*/
-import { ExperimentSpec } from '@submarine/interfaces/experiment-spec';
+import { Component, OnInit } from '@angular/core';
+import { ExperimentService } from '@submarine/services/experiment.service';
+import { delay } from 'rxjs/operators';
-interface ExperimentTemplateParamSpec {
- name: string;
- required: string;
- description: string;
- value: string;
-}
+@Component({
+ selector: 'submarine-template',
+ templateUrl: './template.component.html',
+ styleUrls: ['./template.component.scss'],
+})
+export class TemplateComponent implements OnInit {
+ templateName: string = null;
-interface ExperimentTemplateSpec {
- name: string;
- author: string;
- description: string;
- parameters: ExperimentTemplateParamSpec[];
- experimentSpec: ExperimentSpec;
-}
+ constructor(private experimentService: ExperimentService) {}
-export interface ExperimentTemplate {
- experimentTemplateId: string;
- experimentTemplateSpec: ExperimentTemplateSpec;
+ ngOnInit() {
+ this.experimentService.infoEmitted$.pipe(delay(0)).subscribe((name) =>
(this.templateName = name));
+ }
}
diff --git
a/submarine-workbench/workbench-web/src/app/pages/workbench/workbench.module.ts
b/submarine-workbench/workbench-web/src/app/pages/workbench/template/template.module.ts
similarity index 56%
copy from
submarine-workbench/workbench-web/src/app/pages/workbench/workbench.module.ts
copy to
submarine-workbench/workbench-web/src/app/pages/workbench/template/template.module.ts
index b08bdc7..e47d696 100644
---
a/submarine-workbench/workbench-web/src/app/pages/workbench/workbench.module.ts
+++
b/submarine-workbench/workbench-web/src/app/pages/workbench/template/template.module.ts
@@ -17,40 +17,37 @@
* under the License.
*/
-import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { NgZorroAntdModule } from 'ng-zorro-antd';
+import { TemplateRoutingModule } from './template-routing.module';
import { RouterModule } from '@angular/router';
-import { WorkbenchRoutingModule } from
'@submarine/pages/workbench/workbench-routing.module';
+import { TemplateHomeComponent } from
'./template-home/template-home.component';
+import { TemplateFormComponent } from
'./template-home/template-form/template-form.component';
+import { TemplateListComponent } from
'./template-home/template-list/template-list.component';
+import { TemplateComponent } from './template.component';
+import { TemplateInfoComponent } from
'./template-info/template-info.component';
import { PipeSharedModule } from '@submarine/pipe/pipe-shared.module';
-import { NgZorroAntdModule } from 'ng-zorro-antd';
-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 { ModelComponent } from './model/model.component';
-import { WorkbenchComponent } from './workbench.component';
-import { WorkspaceComponent } from './workspace/workspace.component';
-import { DataComponent } from './data/data.component';
-import { EnvironmentModule } from './environment/environment.module';
@NgModule({
- declarations: [WorkbenchComponent, HomeComponent, WorkspaceComponent,
DataComponent, ModelComponent],
+ declarations: [
+ TemplateComponent,
+ TemplateHomeComponent,
+ TemplateFormComponent,
+ TemplateListComponent,
+ TemplateInfoComponent,
+ ],
imports: [
CommonModule,
- WorkbenchRoutingModule,
- NgZorroAntdModule,
- RouterModule,
FormsModule,
ReactiveFormsModule,
- WorkspaceModule,
- ExperimentModule,
- InterpreterModule,
+ NgZorroAntdModule,
+ RouterModule,
+ TemplateRoutingModule,
PipeSharedModule,
- NotebookModule,
- EnvironmentModule,
],
+ providers: [],
+ exports: [TemplateComponent],
})
-export class WorkbenchModule {}
+export class TemplateModule {}
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 8232fed..393f75a 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
@@ -62,6 +62,11 @@ const routes: Routes = [
canActivate: ['canActivatePage'],
},
{
+ path: 'template',
+ loadChildren: () => import('./template/template.module').then((m) =>
m.TemplateModule),
+ canActivate: ['canActivatePage'],
+ },
+ {
path: 'data',
component: DataComponent,
canActivate: ['canActivatePage'],
diff --git
a/submarine-workbench/workbench-web/src/app/pages/workbench/workbench.component.ts
b/submarine-workbench/workbench-web/src/app/pages/workbench/workbench.component.ts
index ec8c801..e778f7f 100644
---
a/submarine-workbench/workbench-web/src/app/pages/workbench/workbench.component.ts
+++
b/submarine-workbench/workbench-web/src/app/pages/workbench/workbench.component.ts
@@ -40,7 +40,7 @@ interface SidebarMenu {
@Component({
selector: 'submarine-workbench',
templateUrl: './workbench.component.html',
- styleUrls: ['./workbench.component.scss']
+ styleUrls: ['./workbench.component.scss'],
})
export class WorkbenchComponent implements OnInit {
isCollapsed: boolean = false;
@@ -50,25 +50,31 @@ export class WorkbenchComponent implements OnInit {
title: 'Home',
iconType: 'home',
routerLink: '/workbench/home',
- disabled: true
+ disabled: true,
},
{
title: 'Notebook',
iconType: 'book',
routerLink: '/workbench/notebook',
- disabled: false
+ disabled: false,
},
{
title: 'Experiment',
iconType: 'cluster',
routerLink: '/workbench/experiment',
- disabled: false
+ disabled: false,
+ },
+ {
+ title: 'Template',
+ iconType: 'file',
+ routerLink: '/workbench/template',
+ disabled: false,
},
{
title: 'Environment',
iconType: 'codepen',
routerLink: '/workbench/environment',
- disabled: false
+ disabled: false,
},
{
title: 'Manager',
@@ -78,44 +84,44 @@ export class WorkbenchComponent implements OnInit {
{
title: 'User',
routerLink: '/workbench/manager/user',
- disabled: false
+ disabled: false,
},
{
title: 'Data dict',
routerLink: '/workbench/manager/dataDict',
- disabled: false
+ disabled: false,
},
{
title: 'Department',
routerLink: '/workbench/manager/department',
- disabled: false
- }
- ]
+ disabled: false,
+ },
+ ],
},
{
title: 'Data',
iconType: 'bar-chart',
routerLink: '/workbench/data',
- disabled: true
+ disabled: true,
},
{
title: 'Model',
iconType: 'experiment',
routerLink: '/workbench/model',
- disabled: true
+ disabled: true,
},
{
title: 'Workspace',
iconType: 'desktop',
routerLink: '/workbench/workspace',
- disabled: true
+ disabled: true,
},
{
title: 'Interpreter',
iconType: 'api',
routerLink: '/workbench/interpreter',
- disabled: true
- }
+ disabled: true,
+ },
];
userInfo$: Observable<UserInfo>;
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 b08bdc7..7ed7e21 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
@@ -35,6 +35,7 @@ import { WorkbenchComponent } from './workbench.component';
import { WorkspaceComponent } from './workspace/workspace.component';
import { DataComponent } from './data/data.component';
import { EnvironmentModule } from './environment/environment.module';
+import { TemplateModule } from './template/template.module';
@NgModule({
declarations: [WorkbenchComponent, HomeComponent, WorkspaceComponent,
DataComponent, ModelComponent],
@@ -51,6 +52,7 @@ import { EnvironmentModule } from
'./environment/environment.module';
PipeSharedModule,
NotebookModule,
EnvironmentModule,
+ TemplateModule,
],
})
export class WorkbenchModule {}
diff --git
a/submarine-workbench/workbench-web/src/app/services/experiment.service.ts
b/submarine-workbench/workbench-web/src/app/services/experiment.service.ts
index bd3eaae..5916052 100644
--- a/submarine-workbench/workbench-web/src/app/services/experiment.service.ts
+++ b/submarine-workbench/workbench-web/src/app/services/experiment.service.ts
@@ -22,7 +22,7 @@ import { Injectable } from '@angular/core';
import { Rest } from '@submarine/interfaces';
import { ExperimentInfo } from '@submarine/interfaces/experiment-info';
import { ExperimentSpec } from '@submarine/interfaces/experiment-spec';
-import { ExperimentTemplate } from '@submarine/interfaces/experiment-template';
+import { ExperimentTemplate, ExperimentTemplateSpec } from
'@submarine/interfaces/experiment-template';
import { ExperimentTemplateSubmit } from
'@submarine/interfaces/experiment-template-submit';
import { TensorboardInfo } from '@submarine/interfaces/tensorboard-info';
import { MlflowInfo } from '@submarine/interfaces/mlflow-info';
@@ -186,6 +186,19 @@ export class ExperimentService {
);
}
+ querySpecificTemplate(name: string): Observable<ExperimentTemplate> {
+ const apiUrl = this.baseApi.getRestApi('/v1/template/' + name);
+ return this.httpClient.get<Rest<ExperimentTemplate>>(apiUrl).pipe(
+ switchMap((res) => {
+ if (res.success) {
+ return of(res.result);
+ } else {
+ throw this.baseApi.createRequestError(res.message, res.code, apiUrl,
'get');
+ }
+ })
+ );
+ }
+
createExperimentfromTemplate(
experimentSpec: ExperimentTemplateSubmit,
templateName: string
@@ -213,6 +226,32 @@ export class ExperimentService {
);
}
+ createTemplate(templateSpec: ExperimentTemplateSpec):
Observable<ExperimentTemplate> {
+ const apiUrl = this.baseApi.getRestApi(`/v1/template`);
+ return this.httpClient.post<Rest<ExperimentTemplate>>(apiUrl,
templateSpec).pipe(
+ switchMap((res) => {
+ if (res.success) {
+ return of(res.result);
+ } else {
+ throw this.baseApi.createRequestError(res.message, res.code, apiUrl,
'post', templateSpec);
+ }
+ })
+ );
+ }
+
+ deleteTemplate(name: string): Observable<ExperimentTemplate> {
+ const apiUrl = this.baseApi.getRestApi(`/v1/template/${name}`);
+ return this.httpClient.delete<Rest<any>>(apiUrl).pipe(
+ switchMap((res) => {
+ if (res.success) {
+ return of(res.result);
+ } else {
+ throw this.baseApi.createRequestError(res.message, res.code, apiUrl,
'delete', name);
+ }
+ })
+ );
+ }
+
getTensorboardInfo(): Observable<TensorboardInfo> {
const apiUrl = this.baseApi.getRestApi('/v1/experiment/tensorboard');
return this.httpClient.get<Rest<TensorboardInfo>>(apiUrl).pipe(
diff --git
a/submarine-workbench/workbench-web/src/app/services/experiment.validator.service.ts
b/submarine-workbench/workbench-web/src/app/services/experiment.validator.service.ts
index a2716fb..659490c 100644
---
a/submarine-workbench/workbench-web/src/app/services/experiment.validator.service.ts
+++
b/submarine-workbench/workbench-web/src/app/services/experiment.validator.service.ts
@@ -21,7 +21,7 @@ import { FormGroup, ValidatorFn, ValidationErrors, FormArray
} from '@angular/fo
import { Injectable } from '@angular/core';
@Injectable({
- providedIn: 'root'
+ providedIn: 'root',
})
export class ExperimentValidatorService {
/**
@@ -34,6 +34,12 @@ export class ExperimentValidatorService {
return !(key.invalid || keyValue.invalid) ? null : { envMissing: 'Missing
key or value' };
};
+ paramValidator: ValidatorFn = (paramGroup: FormGroup): ValidationErrors |
null => {
+ const key = paramGroup.get('name');
+ const keyValue = paramGroup.get('value');
+ return !(key.invalid || keyValue.invalid) ? null : { envMissing: 'Missing
key or value' };
+ };
+
specValidator: ValidatorFn = (specGroup: FormGroup): ValidationErrors | null
=> {
const name = specGroup.get('name');
const replicas = specGroup.get('replicas');
@@ -77,7 +83,7 @@ export class ExperimentValidatorService {
if (duplicateSet.has(nameControl.value)) {
// Found duplicates, manually set errors on FormControl level
nameControl.setErrors({
- duplicateError: 'Duplicate key or name'
+ duplicateError: 'Duplicate key or name',
});
} else {
duplicateSet.add(nameControl.value);
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]