You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@submarine.apache.org by li...@apache.org on 2021/02/09 04:50:11 UTC

[submarine] branch master updated: SUBMARINE-712. [WEB] Refactor environment page of Workbench

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 b4b1381  SUBMARINE-712. [WEB] Refactor environment page of Workbench
b4b1381 is described below

commit b4b1381a14c63e4fc2800f48d983317bfd875411
Author: kobe860219 <ko...@gmail.com>
AuthorDate: Sat Feb 6 23:26:31 2021 +0800

    SUBMARINE-712. [WEB] Refactor environment page of Workbench
    
    ### What is this PR for?
    Refactor environment page as below:
    ![image-2021-01-19-23-49-11-883](https://user-images.githubusercontent.com/48027290/106375938-94269700-63cb-11eb-9129-3ad4ee87208d.png)
    
    ### What type of PR is it?
    [Refactoring]
    
    ### Todos
    
    ### What is the Jira issue?
    https://issues.apache.org/jira/browse/SUBMARINE-712
    
    ### How should this be tested?
    https://travis-ci.org/github/kobe860219/submarine/builds/756909341
    
    ### Screenshots (if appropriate)
    ![environmentDemo 20210131](https://user-images.githubusercontent.com/48027290/106375980-e10a6d80-63cb-11eb-9742-1308e3028f85.gif)
    
    ### Questions:
    * Does the licenses files need update? No
    * Is there breaking changes for older versions? No
    * Does this needs documentation? No
    
    Author: kobe860219 <ko...@gmail.com>
    
    Signed-off-by: Liu Xun <li...@apache.org>
    
    Closes #503 from kobe860219/SUBMARINE-712 and squashes the following commits:
    
    3ffcae0 [kobe860219] Remove ngIf condition about submarine's default environment.
    92e92d7 [kobe860219] Fix e2e
    5e0a1fd [kobe860219] Fix test-e2e and proxy target
    02153a3 [kobe860219] SUBMARINE-712. [WEB] Refactor environment page of Workbench
    4d4fc0a [kobe860219] Add environment module and routing-module
---
 .../submarine/integration/environmentIT.java       |   6 +-
 .../environment-info.ts                            |   4 +-
 .../environment-spec.ts                            |  11 +-
 .../environment-form.component.html                | 142 ++++++++++++
 .../environment-form.component.scss}               |   9 +-
 .../environment-form.component.ts}                 | 104 +++------
 .../environment-home.component.html                |  38 +++
 .../environment-home.component.scss}               |   9 +-
 .../environment-home/environment-home.component.ts |  63 +++++
 .../environment-list.component.html                |  61 +++++
 .../environment-list.component.scss}               |   9 +-
 .../environment-list.component.ts}                 |  24 +-
 .../environment/environment-routing.module.ts}     |  28 ++-
 .../environment/environment.component.html         | 254 ++-------------------
 .../environment/environment.component.scss         |  80 -------
 .../workbench/environment/environment.component.ts | 172 +-------------
 .../workbench/environment/environment.module.ts    |  38 +++
 .../notebook-form/notebook-form.component.ts       |   2 +-
 .../notebook-home/notebook-home.component.ts       |   1 -
 .../pages/workbench/workbench-routing.module.ts    |   3 +-
 .../src/app/pages/workbench/workbench.module.ts    |  18 +-
 .../environment.service.ts                         |  23 +-
 22 files changed, 465 insertions(+), 634 deletions(-)

diff --git a/submarine-test/test-e2e/src/test/java/org/apache/submarine/integration/environmentIT.java b/submarine-test/test-e2e/src/test/java/org/apache/submarine/integration/environmentIT.java
index 84f78e0..906a0c1 100644
--- a/submarine-test/test-e2e/src/test/java/org/apache/submarine/integration/environmentIT.java
+++ b/submarine-test/test-e2e/src/test/java/org/apache/submarine/integration/environmentIT.java
@@ -59,7 +59,9 @@ public class environmentIT extends AbstractSubmarineIT {
 
     // Test create new environment
     LOG.info("Create new environment");
-    pollingWait(By.xpath("//button[@id='createEnvironment']"), MAX_BROWSER_TIMEOUT_SEC).click();
+    pollingWait(By.xpath("//button[@id='btn-newEnvironment']"), MAX_BROWSER_TIMEOUT_SEC).click();
+    pollingWait(By.xpath("//button[@id='btn-cancel']"), MAX_BROWSER_TIMEOUT_SEC).click();
+    pollingWait(By.xpath("//button[@id='btn-newEnvironment']"), MAX_BROWSER_TIMEOUT_SEC).click();
     pollingWait(By.cssSelector("input[ng-reflect-name='environmentName']"), MAX_BROWSER_TIMEOUT_SEC).sendKeys("testEnvName");
     pollingWait(By.cssSelector("input[ng-reflect-name='dockerImage']"), MAX_BROWSER_TIMEOUT_SEC).sendKeys("testDockerImage");
     pollingWait(By.cssSelector("input[ng-reflect-name='name']"), MAX_BROWSER_TIMEOUT_SEC).sendKeys("testName");
@@ -67,7 +69,7 @@ public class environmentIT extends AbstractSubmarineIT {
     pollingWait(By.xpath("//input[@id='channel0']"), MAX_BROWSER_TIMEOUT_SEC).sendKeys("testChannel");
     pollingWait(By.xpath("//button[@id='addDep-btn']"), MAX_BROWSER_TIMEOUT_SEC).click();
     pollingWait(By.xpath("//input[@id='dependencies0']"), MAX_BROWSER_TIMEOUT_SEC).sendKeys("testDep");
-    pollingWait(By.xpath("//button[@id='go']"), MAX_BROWSER_TIMEOUT_SEC).click();
+    pollingWait(By.xpath("//button[@id='btn-submit']"), MAX_BROWSER_TIMEOUT_SEC).click();
     Assert.assertEquals(pollingWait(By.xpath("//td[contains(., 'testEnvName')]"), MAX_BROWSER_TIMEOUT_SEC).isDisplayed(), true);
     Thread.sleep(2000);
   }
diff --git a/submarine-workbench/workbench-web/src/app/interfaces/environment-info.ts b/submarine-workbench/workbench-web/src/app/interfaces/environment-interfaces/environment-info.ts
similarity index 93%
copy from submarine-workbench/workbench-web/src/app/interfaces/environment-info.ts
copy to submarine-workbench/workbench-web/src/app/interfaces/environment-interfaces/environment-info.ts
index 7ff369c..523578a 100644
--- a/submarine-workbench/workbench-web/src/app/interfaces/environment-info.ts
+++ b/submarine-workbench/workbench-web/src/app/interfaces/environment-interfaces/environment-info.ts
@@ -17,9 +17,9 @@
  * under the License.
  */
 
-import { EnvironmentSpec } from '@submarine/interfaces/environment-spec';
+import { EnvironmentSpec } from '@submarine/interfaces/environment-interfaces/environment-spec';
 
-export interface Environment {
+export interface EnvironmentInfo {
   environmentId: string;
   environmentSpec: EnvironmentSpec;
 }
diff --git a/submarine-workbench/workbench-web/src/app/interfaces/environment-spec.ts b/submarine-workbench/workbench-web/src/app/interfaces/environment-interfaces/environment-spec.ts
similarity index 99%
rename from submarine-workbench/workbench-web/src/app/interfaces/environment-spec.ts
rename to submarine-workbench/workbench-web/src/app/interfaces/environment-interfaces/environment-spec.ts
index 90cd3b6..41634f7 100644
--- a/submarine-workbench/workbench-web/src/app/interfaces/environment-spec.ts
+++ b/submarine-workbench/workbench-web/src/app/interfaces/environment-interfaces/environment-spec.ts
@@ -16,14 +16,15 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-export interface KernelSpec {
-  name: string;
-  channels: string[];
-  dependencies: string[];
-}
 
 export interface EnvironmentSpec {
   name: string;
   dockerImage: string;
   kernelSpec: KernelSpec;
 }
+
+export interface KernelSpec {
+  name: string;
+  channels: string[];
+  dependencies: string[];
+}
diff --git a/submarine-workbench/workbench-web/src/app/pages/workbench/environment/environment-home/environment-form/environment-form.component.html b/submarine-workbench/workbench-web/src/app/pages/workbench/environment/environment-home/environment-form/environment-form.component.html
new file mode 100644
index 0000000..fe42505
--- /dev/null
+++ b/submarine-workbench/workbench-web/src/app/pages/workbench/environment/environment-home/environment-form/environment-form.component.html
@@ -0,0 +1,142 @@
+<!--
+  ~ 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" nzTitle="Create Environment" [nzWidth]="700" (nzOnCancel)="isVisible = false">
+  <form nz-form [formGroup]="environmentForm" nzLayout="horizontal">
+    <div *nzModalFooter>
+      <button id="btn-cancel" nz-button nzType="default" (click)="closeModal()">Cancel</button>
+      <button id="btn-submit" nz-button nzType="primary" [disabled]="checkStatus()" (click)="createEnvironment()">
+        Create
+      </button>
+    </div>
+    <h2>Meta</h2>
+    <nz-form-item>
+      <nz-form-label [nzSm]="6" [nzXs]="24" nzRequired nzFor="environmentName">Environment Name</nz-form-label>
+      <div nz-col nzSpan="16">
+        <nz-form-control nzErrorTip="Please input environment name!">
+          <input
+            required
+            nz-input
+            style="width: 80%"
+            type="text"
+            name="environmentName"
+            id="environmentName"
+            formControlName="environmentName"
+          />
+        </nz-form-control>
+      </div>
+    </nz-form-item>
+    <nz-form-item>
+      <nz-form-label [nzSm]="6" [nzXs]="24" nzRequired nzFor="dockerImage">Docker Image</nz-form-label>
+      <div nz-col nzSpan="16">
+        <nz-form-control nzErrorTip="Please input docker image!">
+          <input
+            required
+            nz-input
+            style="width: 80%"
+            type="text"
+            name="dockerImage"
+            id="dockerImage"
+            formControlName="dockerImage"
+          />
+        </nz-form-control>
+      </div>
+    </nz-form-item>
+    <h2>Kernel Spec</h2>
+    <nz-form-item>
+      <nz-form-label [nzSm]="6" [nzXs]="24" nzRequired nzFor="name">Name</nz-form-label>
+      <div nz-col nzSpan="16">
+        <nz-form-control nzErrorTip="Please input name!">
+          <input required nz-input style="width: 80%" type="text" name="name" id="name" formControlName="name" />
+        </nz-form-control>
+      </div>
+    </nz-form-item>
+    <div formArrayName="channels">
+      <div *ngFor="let channel of channels.controls; let i = index">
+        <nz-form-item>
+          <nz-form-label [nzSm]="6" [nzXs]="24" nzRequired nzFor="channel">Channel {{ i + 1 }}</nz-form-label>
+          <div nz-col nzSpan="16">
+            <nz-form-control nzErrorTip="Please input channel!">
+              <input
+                required
+                nz-input
+                style="width: 80%"
+                type="text"
+                name="channel{{ i }}"
+                id="channel{{ i }}"
+                [formControlName]="i"
+              />
+              <i
+                nz-icon
+                style="margin-left: 5px"
+                nzType="close-circle"
+                nzTheme="fill"
+                (click)="deleteItem(channels, i)"
+              ></i>
+            </nz-form-control>
+          </div>
+        </nz-form-item>
+      </div>
+    </div>
+    <div style="margin: 10px">
+      <button nz-button style="display: block; margin: auto" id="addChannel-btn" type="default" (click)="addChannel()">
+        New Channel
+      </button>
+    </div>
+    <div formArrayName="dependencies">
+      <div *ngFor="let channel of dependencies.controls; let i = index">
+        <nz-form-item>
+          <nz-form-label [nzSm]="6" [nzXs]="24" nzRequired nzFor="dependency">Dependency {{ i + 1 }}</nz-form-label>
+          <div nz-col nzSpan="16">
+            <nz-form-control nzErrorTip="Please input dependency!">
+              <input
+                required
+                nz-input
+                style="width: 80%"
+                type="text"
+                name="dependencies{{ i }}"
+                id="dependencies{{ i }}"
+                [formControlName]="i"
+              />
+              <i
+                nz-icon
+                style="margin-left: 5px"
+                nzType="close-circle"
+                nzTheme="fill"
+                (click)="deleteItem(dependencies, i)"
+              ></i>
+            </nz-form-control>
+          </div>
+        </nz-form-item>
+      </div>
+    </div>
+    <div style="margin: 10px">
+      <button
+        style="margin-top: 10px"
+        nz-button
+        style="display: block; margin: auto"
+        id="addDep-btn"
+        type="default"
+        (click)="addDependencies()"
+      >
+        New Dependency
+      </button>
+    </div>
+  </form>
+</nz-modal>
diff --git a/submarine-workbench/workbench-web/src/app/interfaces/environment-info.ts b/submarine-workbench/workbench-web/src/app/pages/workbench/environment/environment-home/environment-form/environment-form.component.scss
similarity index 82%
copy from submarine-workbench/workbench-web/src/app/interfaces/environment-info.ts
copy to submarine-workbench/workbench-web/src/app/pages/workbench/environment/environment-home/environment-form/environment-form.component.scss
index 7ff369c..3d56d22 100644
--- a/submarine-workbench/workbench-web/src/app/interfaces/environment-info.ts
+++ b/submarine-workbench/workbench-web/src/app/pages/workbench/environment/environment-home/environment-form/environment-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,10 +16,3 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-
-import { EnvironmentSpec } from '@submarine/interfaces/environment-spec';
-
-export interface Environment {
-  environmentId: string;
-  environmentSpec: EnvironmentSpec;
-}
diff --git a/submarine-workbench/workbench-web/src/app/pages/workbench/environment/environment.component.ts b/submarine-workbench/workbench-web/src/app/pages/workbench/environment/environment-home/environment-form/environment-form.component.ts
similarity index 61%
copy from submarine-workbench/workbench-web/src/app/pages/workbench/environment/environment.component.ts
copy to submarine-workbench/workbench-web/src/app/pages/workbench/environment/environment-home/environment-form/environment-form.component.ts
index d936ad9..bec1fd8 100644
--- a/submarine-workbench/workbench-web/src/app/pages/workbench/environment/environment.component.ts
+++ b/submarine-workbench/workbench-web/src/app/pages/workbench/environment/environment-home/environment-form/environment-form.component.ts
@@ -17,52 +17,47 @@
  * under the License.
  */
 
-import { Component, OnInit } from '@angular/core';
-import { FormBuilder, FormArray, FormControl, FormGroup, Validators } from '@angular/forms';
-import { Environment } from '@submarine/interfaces/environment-info';
-import { EnvironmentService } from '@submarine/services/environment.service';
-import { NzMessageService } from 'ng-zorro-antd';
+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';
 
 @Component({
-  selector: 'submarine-environment',
-  templateUrl: './environment.component.html',
-  styleUrls: ['./environment.component.scss']
+  selector: 'submarine-environment-form',
+  templateUrl: './environment-form.component.html',
+  styleUrls: ['./environment-form.component.scss'],
 })
-export class EnvironmentComponent implements OnInit {
-  constructor(
-    private environmentService: EnvironmentService, 
-    private nzMessageService: NzMessageService,
-    private fb: FormBuilder,
-    private experimentValidatorService: ExperimentValidatorService
-    ) {}
+export class EnvironmentFormComponent implements OnInit {
+  @Output() private updater = new EventEmitter<string>();
 
-  environmentList: Environment[] = [];
-  checkedList: boolean[] = [];
-  selectAllChecked: boolean = false;
-
-  isVisible = false;
+  isVisible: boolean;
   environmentForm;
 
+  constructor(
+    private fb: FormBuilder,
+    private experimentValidatorService: ExperimentValidatorService,
+    private environmentService: EnvironmentService,
+    private nzMessageService: NzMessageService
+  ) {}
+
   ngOnInit() {
     this.environmentForm = this.fb.group({
       environmentName: [null, Validators.required],
       dockerImage: [null, Validators.required],
       name: [null, Validators.required],
       channels: this.fb.array([]),
-      dependencies: this.fb.array([])
-    })
-    this.fetchEnvironmentList();
+      dependencies: this.fb.array([]),
+    });
   }
 
-  fetchEnvironmentList() {
-    this.environmentService.fetchEnvironmentList().subscribe((list) => {
-      this.environmentList = list;
-      this.checkedList = [];
-      for (let i = 0; i < this.environmentList.length; i++) {
-        this.checkedList.push(false);
-      }
-    });
+  initModal() {
+    this.isVisible = true;
+    this.initFormStatus();
+  }
+
+  sendUpdate() {
+    this.updater.emit('Update List');
   }
 
   get environmentName() {
@@ -85,7 +80,7 @@ export class EnvironmentComponent implements OnInit {
     return this.environmentForm.get('dependencies') as FormArray;
   }
 
-  initEnvForm() {
+  initFormStatus() {
     this.isVisible = true;
     this.environmentName.reset();
     this.dockerImage.reset();
@@ -123,15 +118,14 @@ export class EnvironmentComponent implements OnInit {
   createEnvironment() {
     this.isVisible = false;
     const newEnvironmentSpec = this.createEnvironmentSpec();
-    console.log(newEnvironmentSpec);
     this.environmentService.createEnvironment(newEnvironmentSpec).subscribe(
       () => {
         this.nzMessageService.success('Create Environment Success!');
-        this.fetchEnvironmentList();
+        this.sendUpdate();
       },
       (err) => {
         this.nzMessageService.error(`${err}, please try again`, {
-          nzPauseOnHover: true
+          nzPauseOnHover: true,
         });
       }
     );
@@ -144,8 +138,8 @@ export class EnvironmentComponent implements OnInit {
       kernelSpec: {
         name: this.environmentForm.get('name').value,
         channels: [],
-        dependencies: []
-      }
+        dependencies: [],
+      },
     };
 
     for (const channel of this.channels.controls) {
@@ -158,40 +152,4 @@ export class EnvironmentComponent implements OnInit {
 
     return environmentSpec;
   }
-
-  // TODO(kobe860219): Update an environment
-  updateEnvironment(id: string, data) {}
-
-  onDeleteEnvironment(name: string, onMessage: boolean) {
-    this.environmentService.deleteEnvironment(name).subscribe(
-      () => {
-        if (onMessage === true) {
-          this.nzMessageService.success('Delete Experiment Successfully!');
-        }
-        this.fetchEnvironmentList();
-      },
-      (err) => {
-        if (onMessage === true) {
-          this.nzMessageService.error(err.message);
-        }
-      }
-    );
-  }
-
-  deleteEnvironments() {
-    for (let i = this.checkedList.length - 1; i >= 0; i--) {
-      console.log(this.environmentList[i].environmentSpec.name);
-      if (this.checkedList[i] === true && this.environmentList[i].environmentSpec.name != 'notebook-env') {
-        this.onDeleteEnvironment(this.environmentList[i].environmentSpec.name, false);
-      }
-    }
-
-    this.selectAllChecked = false;
-  }
-
-  selectAllEnv() {
-    for (let i = 0; i < this.checkedList.length; i++) {
-      this.checkedList[i] = this.selectAllChecked;
-    }
-  }
 }
diff --git a/submarine-workbench/workbench-web/src/app/pages/workbench/environment/environment-home/environment-home.component.html b/submarine-workbench/workbench-web/src/app/pages/workbench/environment/environment-home/environment-home.component.html
new file mode 100644
index 0000000..8223f9d
--- /dev/null
+++ b/submarine-workbench/workbench-web/src/app/pages/workbench/environment/environment-home/environment-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-newEnvironment"
+      nzType="primary"
+      style="margin: 10px 4px 10px 4px"
+      (click)="form.initModal()"
+    >
+      <i nz-icon nzType="plus"></i>
+      New Environment
+    </button>
+  </div>
+  <submarine-environment-list
+    [environmentList]="environmentList"
+    (deleteEnvironment)="onDeleteEnvironment($event)"
+  ></submarine-environment-list>
+  <submarine-environment-form #form (updater)="updateEnvironmentList($event)"></submarine-environment-form>
+</div>
diff --git a/submarine-workbench/workbench-web/src/app/interfaces/environment-info.ts b/submarine-workbench/workbench-web/src/app/pages/workbench/environment/environment-home/environment-home.component.scss
similarity index 82%
copy from submarine-workbench/workbench-web/src/app/interfaces/environment-info.ts
copy to submarine-workbench/workbench-web/src/app/pages/workbench/environment/environment-home/environment-home.component.scss
index 7ff369c..3d56d22 100644
--- a/submarine-workbench/workbench-web/src/app/interfaces/environment-info.ts
+++ b/submarine-workbench/workbench-web/src/app/pages/workbench/environment/environment-home/environment-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,10 +16,3 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-
-import { EnvironmentSpec } from '@submarine/interfaces/environment-spec';
-
-export interface Environment {
-  environmentId: string;
-  environmentSpec: EnvironmentSpec;
-}
diff --git a/submarine-workbench/workbench-web/src/app/pages/workbench/environment/environment-home/environment-home.component.ts b/submarine-workbench/workbench-web/src/app/pages/workbench/environment/environment-home/environment-home.component.ts
new file mode 100644
index 0000000..747bb8c
--- /dev/null
+++ b/submarine-workbench/workbench-web/src/app/pages/workbench/environment/environment-home/environment-home.component.ts
@@ -0,0 +1,63 @@
+/*!
+ * 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 { EnvironmentInfo } from '@submarine/interfaces/environment-interfaces/environment-info';
+import { EnvironmentService } from '@submarine/services/environment-services/environment.service';
+import { NzMessageService } from 'ng-zorro-antd';
+import { EnvironmentFormComponent } from './environment-form/environment-form.component';
+
+@Component({
+  selector: 'submarine-environment-home',
+  templateUrl: './environment-home.component.html',
+  styleUrls: ['./environment-home.component.scss'],
+})
+export class EnvironmentHomeComponent implements OnInit {
+  environmentList: EnvironmentInfo[];
+
+  @ViewChild('form', { static: true }) form: EnvironmentFormComponent;
+
+  constructor(private environmentService: EnvironmentService, private nzMessageService: NzMessageService) {}
+
+  ngOnInit() {
+    this.fetchEnvironmentList();
+  }
+
+  fetchEnvironmentList() {
+    this.environmentService.fetchEnvironmentList().subscribe((res) => {
+      this.environmentList = res;
+    });
+  }
+
+  updateEnvironmentList(msg: string) {
+    this.fetchEnvironmentList();
+  }
+
+  onDeleteEnvironment(name: string) {
+    this.environmentService.deleteEnvironment(name).subscribe(
+      () => {
+        this.fetchEnvironmentList();
+        this.nzMessageService.success(`Delete ${name} Success!`);
+      },
+      (err) => {
+        this.nzMessageService.error(err);
+      }
+    );
+  }
+}
diff --git a/submarine-workbench/workbench-web/src/app/pages/workbench/environment/environment-home/environment-list/environment-list.component.html b/submarine-workbench/workbench-web/src/app/pages/workbench/environment/environment-home/environment-list/environment-list.component.html
new file mode 100644
index 0000000..81b3af7
--- /dev/null
+++ b/submarine-workbench/workbench-web/src/app/pages/workbench/environment/environment-home/environment-list/environment-list.component.html
@@ -0,0 +1,61 @@
+<!--
+  ~ 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="environmentTable" nzBordered #basicTable [nzData]="environmentList" [nzNoResult]="'No data'">
+  <thead>
+    <tr>
+      <th>Environment Name</th>
+      <th>Docker image</th>
+      <th>Kernel name</th>
+      <th>Kernel channel</th>
+      <th>Kernel dependencies</th>
+      <th>Action</th>
+    </tr>
+  </thead>
+  <tbody>
+    <tr *ngFor="let data of basicTable.data; let i = index">
+      <td>{{ data.environmentSpec.name }}</td>
+      <td>{{ data.environmentSpec.dockerImage }}</td>
+      <td>{{ data.environmentSpec.kernelSpec.name }}</td>
+      <td>
+        <dl *ngFor="let channel of data.environmentSpec.kernelSpec.channels">
+          <dt>{{ channel }}</dt>
+        </dl>
+      </td>
+      <td>
+        <dl *ngFor="let dependency of data.environmentSpec.kernelSpec.dependencies">
+          <dt>{{ dependency }}</dt>
+        </dl>
+      </td>
+      <td>
+        <a
+          id="btn-deleteEnvironment{{ i }}"
+          nz-popconfirm
+          nzPlacement="left"
+          nzTitle="Are you sure you want to delete?"
+          nzCancelText="Cancel"
+          nzOkText="Ok"
+          (nzOnConfirm)="onDeleteEnvironment(data.environmentSpec.name)"
+        >
+          Delete
+        </a>
+      </td>
+    </tr>
+  </tbody>
+</nz-table>
diff --git a/submarine-workbench/workbench-web/src/app/interfaces/environment-info.ts b/submarine-workbench/workbench-web/src/app/pages/workbench/environment/environment-home/environment-list/environment-list.component.scss
similarity index 82%
copy from submarine-workbench/workbench-web/src/app/interfaces/environment-info.ts
copy to submarine-workbench/workbench-web/src/app/pages/workbench/environment/environment-home/environment-list/environment-list.component.scss
index 7ff369c..3d56d22 100644
--- a/submarine-workbench/workbench-web/src/app/interfaces/environment-info.ts
+++ b/submarine-workbench/workbench-web/src/app/pages/workbench/environment/environment-home/environment-list/environment-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,10 +16,3 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-
-import { EnvironmentSpec } from '@submarine/interfaces/environment-spec';
-
-export interface Environment {
-  environmentId: string;
-  environmentSpec: EnvironmentSpec;
-}
diff --git a/submarine-workbench/workbench-web/src/app/interfaces/environment-info.ts b/submarine-workbench/workbench-web/src/app/pages/workbench/environment/environment-home/environment-list/environment-list.component.ts
similarity index 57%
copy from submarine-workbench/workbench-web/src/app/interfaces/environment-info.ts
copy to submarine-workbench/workbench-web/src/app/pages/workbench/environment/environment-home/environment-list/environment-list.component.ts
index 7ff369c..ad68320 100644
--- a/submarine-workbench/workbench-web/src/app/interfaces/environment-info.ts
+++ b/submarine-workbench/workbench-web/src/app/pages/workbench/environment/environment-home/environment-list/environment-list.component.ts
@@ -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,9 +17,23 @@
  * under the License.
  */
 
-import { EnvironmentSpec } from '@submarine/interfaces/environment-spec';
+import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
+import { ExperimentInfo } from '@submarine/interfaces/experiment-info';
 
-export interface Environment {
-  environmentId: string;
-  environmentSpec: EnvironmentSpec;
+@Component({
+  selector: 'submarine-environment-list',
+  templateUrl: './environment-list.component.html',
+  styleUrls: ['./environment-list.component.scss'],
+})
+export class EnvironmentListComponent implements OnInit {
+  @Input() environmentList: ExperimentInfo[];
+  @Output() deleteEnvironment = new EventEmitter<string>();
+
+  constructor() {}
+
+  ngOnInit() {}
+
+  onDeleteEnvironment(name: string) {
+    this.deleteEnvironment.emit(name);
+  }
 }
diff --git a/submarine-workbench/workbench-web/src/app/interfaces/environment-info.ts b/submarine-workbench/workbench-web/src/app/pages/workbench/environment/environment-routing.module.ts
similarity index 57%
rename from submarine-workbench/workbench-web/src/app/interfaces/environment-info.ts
rename to submarine-workbench/workbench-web/src/app/pages/workbench/environment/environment-routing.module.ts
index 7ff369c..a0ef7be 100644
--- a/submarine-workbench/workbench-web/src/app/interfaces/environment-info.ts
+++ b/submarine-workbench/workbench-web/src/app/pages/workbench/environment/environment-routing.module.ts
@@ -17,9 +17,27 @@
  * under the License.
  */
 
-import { EnvironmentSpec } from '@submarine/interfaces/environment-spec';
+import { NgModule } from '@angular/core';
+import { RouterModule, Routes } from '@angular/router';
+import { EnvironmentComponent } from './environment.component';
+import { EnvironmentHomeComponent } from './environment-home/environment-home.component';
 
-export interface Environment {
-  environmentId: string;
-  environmentSpec: EnvironmentSpec;
-}
+const routes: Routes = [
+  {
+    path: '',
+    component: EnvironmentComponent,
+    children: [
+      {
+        path: '',
+        pathMatch: 'full',
+        component: EnvironmentHomeComponent,
+      },
+    ],
+  },
+];
+
+@NgModule({
+  imports: [RouterModule.forChild(routes)],
+  exports: [RouterModule],
+})
+export class EnvironmentRoutingModule {}
diff --git a/submarine-workbench/workbench-web/src/app/pages/workbench/environment/environment.component.html b/submarine-workbench/workbench-web/src/app/pages/workbench/environment/environment.component.html
index d8bef06..c266640 100644
--- a/submarine-workbench/workbench-web/src/app/pages/workbench/environment/environment.component.html
+++ b/submarine-workbench/workbench-web/src/app/pages/workbench/environment/environment.component.html
@@ -17,246 +17,22 @@
   ~ under the License.
   -->
 
-<nz-layout style="margin: -24px -24px 16px;">
-  <nz-layout class="inner-layout">
-    <div id="environmentOuter">
-      <nz-breadcrumb>
-        <nz-breadcrumb-item>
-          <!--<a [routerLink]="['/', 'workbench', 'home']">Home</a>-->
-          <a>Home</a>
-        </nz-breadcrumb-item>
-        <nz-breadcrumb-item>
-          <a [routerLink]="['/', 'workbench', 'environment']">environment</a>
-        </nz-breadcrumb-item>
-      </nz-breadcrumb>
-      <div>
-        <br />
-        <h2>Environment</h2>
-        <nz-content>
-          Environment profiles defines a set of libraries and when Docker is being used, a Docker image in order to run
-          an experiment or a notebook.
-        </nz-content>
-      </div>
-      <br />
-    </div>
-  </nz-layout>
-  <div id="environmentData">
+<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', 'environment']">environment</a>
+      </nz-breadcrumb-item>
+    </nz-breadcrumb>
     <div>
-      <div align="right">
-        <nz-input-group
-          nzSearch
-          style="width: 300px; margin-top: 15px; margin-left: 10px; margin-right: 5px;"
-          [nzAddOnAfter]="suffixIconButton"
-        >
-          <input type="text" nz-input 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
-          nz-button
-          id="createEnvironment"
-          nzType="primary"
-          style="margin-right: 5px; margin-bottom: 15px; margin-top: 15px;"
-          (click)="initEnvForm()"
-        >
-          <i nz-icon nzType="plus"></i>
-          New Environment
-        </button>
-        <button
-          nz-button
-          id="deleteEnvironment"
-          nzType="primary"
-          style="margin-bottom: 15px; margin-top: 15px;"
-          nz-popconfirm
-          nzTitle="Confirm to delete?"
-          nzCancelText="Cancel"
-          nzOkText="Ok"
-          (nzOnConfirm)="deleteEnvironments()"
-        >
-          <i nz-icon nzType="delete" nzTheme="outline"></i>
-          Delete
-        </button>
-      </div>
-      <nz-table id="releaseTable" nzBordered #basicTable [nzData]="environmentList" [nzNoResult]="'No data'">
-        <thead>
-          <tr>
-            <th>
-              <label nz-checkbox [(ngModel)]="selectAllChecked" (ngModelChange)="selectAllEnv()"></label>
-            </th>
-            <th>Environment Name</th>
-            <th>Docker image</th>
-            <th>Kernel name</th>
-            <th>Kernel channel</th>
-            <th>Kernel dependencies</th>
-          </tr>
-        </thead>
-        <tbody>
-          <tr *ngFor="let data of basicTable.data; let i = index">
-            <td>
-              <label *ngIf="data.environmentSpec.name != 'notebook-env'" nz-checkbox [(ngModel)]="checkedList[i]"></label>
-            </td>
-            <td>{{ data.environmentSpec.name }}</td>
-            <td>{{ data.environmentSpec.dockerImage }}</td>
-            <td>{{ data.environmentSpec.kernelSpec.name }}</td>
-            <td>
-              <dl *ngFor="let channel of data.environmentSpec.kernelSpec.channels">
-                <dt>{{ channel }}</dt>
-              </dl>
-            </td>
-            <td>
-              <dl *ngFor="let dependency of data.environmentSpec.kernelSpec.dependencies">
-                <dt>{{ dependency }}</dt>
-              </dl>
-            </td>
-          </tr>
-        </tbody>
-      </nz-table>
+      <br />
+      <h2>Environment</h2>
+      <nz-content>Apache submarine support environment management for experiments and notebooks.</nz-content>
     </div>
-    <router-outlet></router-outlet>
+    <br />
   </div>
+  <router-outlet></router-outlet>
 </nz-layout>
-
-<nz-modal [(nzVisible)]="isVisible" nzTitle="Create Environment" [nzWidth]="700" (nzOnCancel)="isVisible = false">
-  <form nz-form [formGroup]="environmentForm" nzLayout="horizontal">
-    <div *nzModalFooter>
-      <button nz-button nzType="default" (click)="closeModal()">Cancel</button>
-      <button id="go" nz-button nzType="primary" [disabled]="checkStatus()" (click)="createEnvironment()">Create</button>
-    </div>
-    <h2>Meta</h2>
-    <nz-form-item>
-      <nz-form-label [nzSm]="6" [nzXs]="24" nzRequired nzFor="environmentName">
-        Environment Name
-      </nz-form-label>
-      <div nz-col nzSpan="16">
-        <nz-form-control nzErrorTip="Please input environment name!">
-          <input
-            required
-            nz-input
-            style="width: 80%;"
-            type="text"
-            name="environmentName"
-            id="environmentName"
-            formControlName="environmentName"
-          />
-        </nz-form-control>
-      </div>
-    </nz-form-item>
-    <nz-form-item>
-      <nz-form-label [nzSm]="6" [nzXs]="24" nzRequired nzFor="dockerImage">
-        Docker Image
-      </nz-form-label>
-      <div nz-col nzSpan="16">
-        <nz-form-control nzErrorTip="Please input docker image!">
-          <input 
-            required 
-            nz-input 
-            style="width: 80%;"
-            type="text" 
-            name="dockerImage" 
-            id="dockerImage" 
-            formControlName="dockerImage" />
-        </nz-form-control>
-      </div>
-    </nz-form-item>
-    <h2>Kernel Spec</h2>
-    <nz-form-item>
-      <nz-form-label [nzSm]="6" [nzXs]="24" nzRequired nzFor="name">
-        Name
-      </nz-form-label>
-      <div nz-col nzSpan="16">
-        <nz-form-control nzErrorTip="Please input name!">
-          <input 
-            required 
-            nz-input 
-            style="width: 80%;"
-            type="text" 
-            name="name" 
-            id="name" 
-            formControlName="name" />
-        </nz-form-control>
-      </div>
-    </nz-form-item>
-    <div formArrayName="channels">
-      <div *ngFor="let channel of channels.controls; let i = index">
-        <nz-form-item>
-          <nz-form-label [nzSm]="6" [nzXs]="24" nzRequired nzFor="channel">
-            Channel {{ i + 1 }}
-          </nz-form-label>
-          <div nz-col nzSpan="16">
-            <nz-form-control nzErrorTip="Please input channel!">
-              <input 
-                required 
-                nz-input 
-                style="width: 80%;" 
-                type="text" 
-                name="channel{{ i }}" 
-                id="channel{{ i }}" 
-                [formControlName]="i" />
-              <i
-                nz-icon
-                style="margin-left: 5px;"
-                nzType="close-circle"
-                nzTheme="fill"
-                (click)="deleteItem(channels, i)"
-              ></i>
-            </nz-form-control>
-          </div>
-        </nz-form-item>
-      </div>
-    </div>
-    <div style="margin: 10px;">
-      <button
-        nz-button
-        style="display: block; margin: auto;"
-        id="addChannel-btn"
-        type="default"
-        (click)="addChannel()"
-      >
-        New Channel
-      </button>
-    </div>
-    <div formArrayName="dependencies">
-      <div *ngFor="let channel of dependencies.controls; let i = index">
-        <nz-form-item>
-          <nz-form-label [nzSm]="6" [nzXs]="24" nzRequired nzFor="dependency">
-            Dependency {{ i + 1 }}
-          </nz-form-label>
-          <div nz-col nzSpan="16">
-            <nz-form-control nzErrorTip="Please input dependency!">
-              <input
-                required
-                nz-input
-                style="width: 80%;"
-                type="text"
-                name="dependencies{{ i }}"
-                id="dependencies{{ i }}"
-                [formControlName]="i"
-              />
-              <i
-                nz-icon
-                style="margin-left: 5px;"
-                nzType="close-circle"
-                nzTheme="fill"
-                (click)="deleteItem(dependencies, i)"
-              ></i>
-            </nz-form-control>
-          </div>
-        </nz-form-item>
-      </div>
-    </div>
-    <div style="margin: 10px;">
-      <button
-        style="margin-top: 10px;"
-        nz-button
-        style="display: block; margin: auto;"
-        id="addDep-btn"
-        type="default"
-        (click)="addDependencies()"
-      >
-        New Dependency
-      </button>
-    </div>
-  </form>
-</nz-modal>
diff --git a/submarine-workbench/workbench-web/src/app/pages/workbench/environment/environment.component.scss b/submarine-workbench/workbench-web/src/app/pages/workbench/environment/environment.component.scss
index 4972c5c..3d56d22 100644
--- a/submarine-workbench/workbench-web/src/app/pages/workbench/environment/environment.component.scss
+++ b/submarine-workbench/workbench-web/src/app/pages/workbench/environment/environment.component.scss
@@ -16,83 +16,3 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-
-#environmentOuter{
-  background-color: white;
-  padding-left: 30px;
-  padding-top: 20px;
-}
-
-#environmentData{
-  margin-top: 16px;
-  margin-left: 25px;
-  margin-right: 25px;
-  background-color:white;
-  padding-left: 10px;
-  padding-right: 10px;
-}
-
-input.ng-invalid.ng-touched {
-  border: 1px solid red;
-}
-
-textarea.ng-invalid.ng-touched {
-  border: 1px solid red;
-}
-
-.red-star {
-  margin-top: 20px;
-  color: red;
-}
-
-.list-container {
-  padding: 0;
-  margin: 1rem 0;
-}
-
-.input-group {
-  display: flex;
-  align-items: center;
-  &:not(:first-child) {
-    margin-top: 1rem;
-  }
-
-  & input {
-    flex: 0 2 20%;
-    &:not(:last-child) {
-      margin-right: .5rem;
-    }
-  }
-
-  & i {
-    cursor: pointer;
-    font-size: 1.2rem;
-  }
-}
-
-/* utility */
-.single-field-group {
-  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%;
-  }
-  & textarea {
-    flex: 0 0 55%;
-  }
-}
-
-.alert-message {
-  color: red;
-  margin-top: .3rem;
-  margin-left: .5rem;
-}
diff --git a/submarine-workbench/workbench-web/src/app/pages/workbench/environment/environment.component.ts b/submarine-workbench/workbench-web/src/app/pages/workbench/environment/environment.component.ts
index d936ad9..9322539 100644
--- a/submarine-workbench/workbench-web/src/app/pages/workbench/environment/environment.component.ts
+++ b/submarine-workbench/workbench-web/src/app/pages/workbench/environment/environment.component.ts
@@ -18,180 +18,14 @@
  */
 
 import { Component, OnInit } from '@angular/core';
-import { FormBuilder, FormArray, FormControl, FormGroup, Validators } from '@angular/forms';
-import { Environment } from '@submarine/interfaces/environment-info';
-import { EnvironmentService } from '@submarine/services/environment.service';
-import { NzMessageService } from 'ng-zorro-antd';
-import { ExperimentValidatorService } from '@submarine/services/experiment.validator.service';
 
 @Component({
   selector: 'submarine-environment',
   templateUrl: './environment.component.html',
-  styleUrls: ['./environment.component.scss']
+  styleUrls: ['./environment.component.scss'],
 })
 export class EnvironmentComponent implements OnInit {
-  constructor(
-    private environmentService: EnvironmentService, 
-    private nzMessageService: NzMessageService,
-    private fb: FormBuilder,
-    private experimentValidatorService: ExperimentValidatorService
-    ) {}
+  constructor() {}
 
-  environmentList: Environment[] = [];
-  checkedList: boolean[] = [];
-  selectAllChecked: boolean = false;
-
-  isVisible = false;
-  environmentForm;
-
-  ngOnInit() {
-    this.environmentForm = this.fb.group({
-      environmentName: [null, Validators.required],
-      dockerImage: [null, Validators.required],
-      name: [null, Validators.required],
-      channels: this.fb.array([]),
-      dependencies: this.fb.array([])
-    })
-    this.fetchEnvironmentList();
-  }
-
-  fetchEnvironmentList() {
-    this.environmentService.fetchEnvironmentList().subscribe((list) => {
-      this.environmentList = list;
-      this.checkedList = [];
-      for (let i = 0; i < this.environmentList.length; i++) {
-        this.checkedList.push(false);
-      }
-    });
-  }
-
-  get environmentName() {
-    return this.environmentForm.get('environmentName');
-  }
-
-  get dockerImage() {
-    return this.environmentForm.get('dockerImage');
-  }
-
-  get name() {
-    return this.environmentForm.get('name');
-  }
-
-  get channels() {
-    return this.environmentForm.get('channels') as FormArray;
-  }
-
-  get dependencies() {
-    return this.environmentForm.get('dependencies') as FormArray;
-  }
-
-  initEnvForm() {
-    this.isVisible = true;
-    this.environmentName.reset();
-    this.dockerImage.reset();
-    this.name.reset();
-    this.channels.clear();
-    this.dependencies.clear();
-  }
-
-  checkStatus() {
-    return (
-      this.environmentName.invalid ||
-      this.dockerImage.invalid ||
-      this.name.invalid ||
-      this.channels.invalid ||
-      this.dependencies.invalid
-    );
-  }
-
-  closeModal() {
-    this.isVisible = false;
-  }
-
-  addChannel() {
-    this.channels.push(this.fb.control(null, Validators.required));
-  }
-
-  addDependencies() {
-    this.dependencies.push(this.fb.control(null, Validators.required));
-  }
-
-  deleteItem(arr: FormArray, index: number) {
-    arr.removeAt(index);
-  }
-
-  createEnvironment() {
-    this.isVisible = false;
-    const newEnvironmentSpec = this.createEnvironmentSpec();
-    console.log(newEnvironmentSpec);
-    this.environmentService.createEnvironment(newEnvironmentSpec).subscribe(
-      () => {
-        this.nzMessageService.success('Create Environment Success!');
-        this.fetchEnvironmentList();
-      },
-      (err) => {
-        this.nzMessageService.error(`${err}, please try again`, {
-          nzPauseOnHover: true
-        });
-      }
-    );
-  }
-
-  createEnvironmentSpec() {
-    const environmentSpec = {
-      name: this.environmentForm.get('environmentName').value,
-      dockerImage: this.environmentForm.get('dockerImage').value,
-      kernelSpec: {
-        name: this.environmentForm.get('name').value,
-        channels: [],
-        dependencies: []
-      }
-    };
-
-    for (const channel of this.channels.controls) {
-      environmentSpec.kernelSpec.channels.push(channel.value);
-    }
-
-    for (const dependency of this.dependencies.controls) {
-      environmentSpec.kernelSpec.dependencies.push(dependency.value);
-    }
-
-    return environmentSpec;
-  }
-
-  // TODO(kobe860219): Update an environment
-  updateEnvironment(id: string, data) {}
-
-  onDeleteEnvironment(name: string, onMessage: boolean) {
-    this.environmentService.deleteEnvironment(name).subscribe(
-      () => {
-        if (onMessage === true) {
-          this.nzMessageService.success('Delete Experiment Successfully!');
-        }
-        this.fetchEnvironmentList();
-      },
-      (err) => {
-        if (onMessage === true) {
-          this.nzMessageService.error(err.message);
-        }
-      }
-    );
-  }
-
-  deleteEnvironments() {
-    for (let i = this.checkedList.length - 1; i >= 0; i--) {
-      console.log(this.environmentList[i].environmentSpec.name);
-      if (this.checkedList[i] === true && this.environmentList[i].environmentSpec.name != 'notebook-env') {
-        this.onDeleteEnvironment(this.environmentList[i].environmentSpec.name, false);
-      }
-    }
-
-    this.selectAllChecked = false;
-  }
-
-  selectAllEnv() {
-    for (let i = 0; i < this.checkedList.length; i++) {
-      this.checkedList[i] = this.selectAllChecked;
-    }
-  }
+  ngOnInit() {}
 }
diff --git a/submarine-workbench/workbench-web/src/app/pages/workbench/environment/environment.module.ts b/submarine-workbench/workbench-web/src/app/pages/workbench/environment/environment.module.ts
new file mode 100644
index 0000000..a611205
--- /dev/null
+++ b/submarine-workbench/workbench-web/src/app/pages/workbench/environment/environment.module.ts
@@ -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.
+ */
+
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { NgZorroAntdModule } from 'ng-zorro-antd';
+import { EnvironmentRoutingModule } from './environment-routing.module';
+import { RouterModule } from '@angular/router';
+import { EnvironmentHomeComponent } from './environment-home/environment-home.component';
+import { EnvironmentService } from '@submarine/services/environment-services/environment.service';
+import { EnvironmentComponent } from './environment.component';
+import { EnvironmentListComponent } from './environment-home/environment-list/environment-list.component';
+import { EnvironmentFormComponent } from './environment-home/environment-form/environment-form.component';
+
+@NgModule({
+  declarations: [EnvironmentComponent, EnvironmentHomeComponent, EnvironmentListComponent, EnvironmentFormComponent],
+  imports: [CommonModule, ReactiveFormsModule, FormsModule, RouterModule, NgZorroAntdModule, EnvironmentRoutingModule],
+  providers: [EnvironmentService],
+  exports: [EnvironmentComponent],
+})
+export class EnvironmentModule {}
diff --git a/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook-home/notebook-form/notebook-form.component.ts b/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook-home/notebook-form/notebook-form.component.ts
index ef230ed..c821500 100644
--- a/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook-home/notebook-form/notebook-form.component.ts
+++ b/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook-home/notebook-form/notebook-form.component.ts
@@ -20,7 +20,7 @@
 import { Component, OnInit } from '@angular/core';
 import { FormArray, FormControl, FormGroup, Validators, FormBuilder } from '@angular/forms';
 import { ExperimentValidatorService } from '@submarine/services/experiment.validator.service';
-import { EnvironmentService } from '@submarine/services/environment.service';
+import { EnvironmentService } from '@submarine/services/environment-services/environment.service';
 import { NotebookService } from '@submarine/services/notebook-services/notebook.service';
 import { UserService } from '@submarine/services/user.service';
 import { NzMessageService } from 'ng-zorro-antd/message';
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
index a19f74c..33e6835 100644
--- 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
@@ -24,7 +24,6 @@ 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',
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 a0aa0cd..8232fed 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
@@ -19,7 +19,6 @@
 
 import { NgModule } from '@angular/core';
 import { ActivatedRouteSnapshot, RouterModule, RouterStateSnapshot, Routes } from '@angular/router';
-import { EnvironmentComponent } from '@submarine/pages/workbench/environment/environment.component';
 import { WorkbenchComponent } from '@submarine/pages/workbench/workbench.component';
 import { DataComponent } from './data/data.component';
 import { HomeComponent } from './home/home.component';
@@ -59,7 +58,7 @@ const routes: Routes = [
       },
       {
         path: 'environment',
-        component: EnvironmentComponent,
+        loadChildren: () => import('./environment/environment.module').then((m) => m.EnvironmentModule),
         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 248eaef..b08bdc7 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
@@ -33,18 +33,11 @@ import { HomeComponent } from './home/home.component';
 import { ModelComponent } from './model/model.component';
 import { WorkbenchComponent } from './workbench.component';
 import { WorkspaceComponent } from './workspace/workspace.component';
-import { EnvironmentComponent } from './environment/environment.component';
 import { DataComponent } from './data/data.component';
+import { EnvironmentModule } from './environment/environment.module';
 
 @NgModule({
-  declarations: [
-    WorkbenchComponent,
-    HomeComponent,
-    WorkspaceComponent,
-    DataComponent,
-    ModelComponent,
-    EnvironmentComponent
-  ],
+  declarations: [WorkbenchComponent, HomeComponent, WorkspaceComponent, DataComponent, ModelComponent],
   imports: [
     CommonModule,
     WorkbenchRoutingModule,
@@ -56,7 +49,8 @@ import { DataComponent } from './data/data.component';
     ExperimentModule,
     InterpreterModule,
     PipeSharedModule,
-    NotebookModule
-  ]
+    NotebookModule,
+    EnvironmentModule,
+  ],
 })
-export class WorkbenchModule { }
+export class WorkbenchModule {}
diff --git a/submarine-workbench/workbench-web/src/app/services/environment.service.ts b/submarine-workbench/workbench-web/src/app/services/environment-services/environment.service.ts
similarity index 77%
rename from submarine-workbench/workbench-web/src/app/services/environment.service.ts
rename to submarine-workbench/workbench-web/src/app/services/environment-services/environment.service.ts
index da5126c..e91d119 100644
--- a/submarine-workbench/workbench-web/src/app/services/environment.service.ts
+++ b/submarine-workbench/workbench-web/src/app/services/environment-services/environment.service.ts
@@ -20,20 +20,21 @@
 import { HttpClient } from '@angular/common/http';
 import { Injectable } from '@angular/core';
 import { Rest } from '@submarine/interfaces';
-import { Environment } from '@submarine/interfaces/environment-info';
+import { EnvironmentInfo } from '@submarine/interfaces/environment-interfaces/environment-info';
+import { EnvironmentSpec } from '@submarine/interfaces/environment-interfaces/environment-spec';
 import { BaseApiService } from '@submarine/services/base-api.service';
 import { of, Observable } from 'rxjs';
 import { switchMap } from 'rxjs/operators';
 
 @Injectable({
-  providedIn: 'root'
+  providedIn: 'root',
 })
 export class EnvironmentService {
   constructor(private baseApi: BaseApiService, private httpClient: HttpClient) {}
 
-  fetchEnvironmentList(): Observable<Environment[]> {
+  fetchEnvironmentList(): Observable<EnvironmentInfo[]> {
     const apiUrl = this.baseApi.getRestApi('/v1/environment');
-    return this.httpClient.get<Rest<Environment[]>>(apiUrl).pipe(
+    return this.httpClient.get<Rest<EnvironmentInfo[]>>(apiUrl).pipe(
       switchMap((res) => {
         if (res.success) {
           return of(res.result);
@@ -44,12 +45,9 @@ export class EnvironmentService {
     );
   }
 
-  // TODO(kobe860219): Query environment
-  querySpecificEnvironment(id: string) {}
-
-  createEnvironment(spec: object): Observable<Environment> {
+  createEnvironment(spec: object): Observable<EnvironmentSpec> {
     const apiUrl = this.baseApi.getRestApi(`/v1/environment/`);
-    return this.httpClient.post<Rest<Environment>>(apiUrl, spec).pipe(
+    return this.httpClient.post<Rest<EnvironmentSpec>>(apiUrl, spec).pipe(
       switchMap((res) => {
         if (res.success) {
           return of(res.result);
@@ -60,12 +58,9 @@ export class EnvironmentService {
     );
   }
 
-  // TODO(kobe860219): Update an environment
-  updateEnvironment(updateData) {}
-
-  deleteEnvironment(name: string): Observable<Environment> {
+  deleteEnvironment(name: string): Observable<EnvironmentInfo> {
     const apiUrl = this.baseApi.getRestApi(`/v1/environment/${name}`);
-    return this.httpClient.delete<Rest<Environment>>(apiUrl).pipe(
+    return this.httpClient.delete<Rest<EnvironmentInfo>>(apiUrl).pipe(
       switchMap((res) => {
         if (res.success) {
           return of(res.result);


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@submarine.apache.org
For additional commands, e-mail: dev-help@submarine.apache.org