You are viewing a plain text version of this content. The canonical link for it is here.
Posted to submarine-dev@hadoop.apache.org by zh...@apache.org on 2019/10/22 08:14:51 UTC

[hadoop-submarine] branch master updated: SUBMARINE-241. Workbench web ng

This is an automated email from the ASF dual-hosted git repository.

zhouquan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hadoop-submarine.git


The following commit(s) were added to refs/heads/master by this push:
     new 15c18b0  SUBMARINE-241. Workbench web ng
15c18b0 is described below

commit 15c18b004aab4376107d3830b85f969c91d25ef9
Author: lleohao <ll...@hotmail.com>
AuthorDate: Tue Oct 22 12:24:03 2019 +0800

    SUBMARINE-241. Workbench web ng
    
    ### What is this PR for?
    Develop Web Framework with Angular and Ng-zero
    Init project and add user module ( login and regisiter )
    
    ### What type of PR is it?
    [Feature]
    
    ### What is the Jira issue?
    https://issues.apache.org/jira/browse/SUBMARINE-241
    
    ### How should this be tested?
    No test
    
    ### Screenshots (if appropriate)
    ![image](https://user-images.githubusercontent.com/12764126/67159475-c0a1d580-f377-11e9-865e-d934a6bf9be3.png)
    
    ### Questions:
    * Does the licenses files need update? No
    * Is there breaking changes for older versions? No
    * Does this needs documentation? No
    
    Author: lleohao <ll...@hotmail.com>
    
    Closes #58 from lleohao/workbench-web-ng and squashes the following commits:
    
    52b819a [lleohao] [SUBMARINE-241] refactor: translate to english
    7b6c726 [lleohao] [SUBMARINE-241] fix: fix package error
    c529061 [lleohao] [SUBMARINE-241] feat: LocalStorageService add error log
    bda54d7 [lleohao] [SUBMARINE-241] refactor: change register title
    b5228a8 [lleohao] [SUBMARINE-241] refactor: translate to english
    884c46e [lleohao] [SUBMARINE-241] refactor: remove unused pom config
    0cb5548 [lleohao] [SUBMARINE-241] refactor: update copyright
    db779a4 [lleohao] SUBMARINE-241. Fix workbench-web-ng pom error
    d7edee3 [lleohao] SUBMARINE-241. Fix rat check
    49dd692 [lleohao] SUBMARINE-241. Add workbench-web-ng LICENSE
    3908d06 [lleohao] SUBMARINE-241. Remove commit lint
    fa4041d [lleohao] SUBMARINE-241. Add login page and auth guard
    5a0d1b5 [lleohao] feat: init angular project
---
 submarine-workbench/pom.xml                        |   1 +
 submarine-workbench/workbench-web-ng/.editorconfig |  14 +
 submarine-workbench/workbench-web-ng/.gitignore    |  46 ++
 .../workbench-web-ng/.prettierignore               |   4 +
 submarine-workbench/workbench-web-ng/.prettierrc   |  15 +
 submarine-workbench/workbench-web-ng/LICENSE       |  16 +
 submarine-workbench/workbench-web-ng/README.md     |  29 ++
 submarine-workbench/workbench-web-ng/angular.json  | 136 +++++
 submarine-workbench/workbench-web-ng/browserslist  |  12 +
 .../workbench-web-ng/e2e/protractor.conf.js        |  51 ++
 .../workbench-web-ng/e2e/src/app.e2e-spec.ts       |  42 ++
 .../workbench-web-ng/e2e/src/app.po.ts             |  30 ++
 .../workbench-web-ng/e2e/tsconfig.json             |  13 +
 submarine-workbench/workbench-web-ng/karma.conf.js |  51 ++
 submarine-workbench/workbench-web-ng/package.json  |  52 ++
 submarine-workbench/workbench-web-ng/pom.xml       |  62 +++
 submarine-workbench/workbench-web-ng/proxy.conf.js |  66 +++
 .../workbench-web-ng/src/WEB-INF/web.xml           |  47 ++
 .../workbench-web-ng/src/app/app-routing.module.ts |  45 ++
 .../workbench-web-ng/src/app/app.component.html    |  74 +++
 .../workbench-web-ng/src/app/app.component.scss    |  99 ++++
 .../workbench-web-ng/src/app/app.component.spec.ts |  50 ++
 .../workbench-web-ng/src/app/app.component.ts      |  40 ++
 .../workbench-web-ng/src/app/app.module.ts         |  50 ++
 .../src/app/core/auth/auth.guard.ts                |  48 ++
 .../workbench-web-ng/src/app/core/index.ts         |  20 +
 .../workbench-web-ng/src/app/core/publick-api.ts   |  20 +
 .../src/app/icons-provider.module.ts               |  30 ++
 .../workbench-web-ng/src/app/interfaces/index.ts   |  20 +
 .../src/app/interfaces/permission.ts               |  35 ++
 .../src/app/interfaces/public-api.ts               |  23 +
 .../workbench-web-ng/src/app/interfaces/rest.ts    |  26 +
 .../workbench-web-ng/src/app/interfaces/role.ts    |  31 ++
 .../workbench-web-ng/src/app/interfaces/user.ts    |  31 ++
 .../app/pages/manager/manager-routing.module.ts    |  47 ++
 .../src/app/pages/manager/manager.component.html   |  21 +
 .../src/app/pages/manager/manager.component.scss   |  19 +
 .../src/app/pages/manager/manager.component.ts     |  31 ++
 .../src/app/pages/manager/manager.module.ts        |  31 ++
 .../src/app/pages/manager/user/user.component.html |  20 +
 .../src/app/pages/manager/user/user.component.scss |  19 +
 .../src/app/pages/manager/user/user.component.ts   |  31 ++
 .../app/pages/user/login/login-routing.module.ts   |  30 ++
 .../src/app/pages/user/login/login.component.html  |  46 ++
 .../src/app/pages/user/login/login.component.scss  |  31 ++
 .../src/app/pages/user/login/login.component.ts    |  86 ++++
 .../pages/user/register/register.component.html    |  20 +
 .../pages/user/register/register.component.scss    |  19 +
 .../app/pages/user/register/register.component.ts  |  31 ++
 .../src/app/pages/user/user-routing.module.ts      |  52 ++
 .../src/app/pages/user/user.component.html         |  31 ++
 .../src/app/pages/user/user.component.scss         |  55 ++
 .../src/app/pages/user/user.component.ts           |  31 ++
 .../src/app/pages/user/user.module.ts              |  33 ++
 .../src/app/services/auth.service.ts               |  70 +++
 .../src/app/services/base-api.service.ts           |  58 +++
 .../workbench-web-ng/src/app/services/index.ts     |  20 +
 .../src/app/services/local-storage.service.ts      |  53 ++
 .../src/app/services/public-api.ts                 |  20 +
 .../workbench-web-ng/src/assets/.gitkeep           |   0
 .../workbench-web-ng/src/assets/background.svg     |  69 +++
 .../workbench-web-ng/src/assets/logo-128.png       | Bin 0 -> 6098 bytes
 .../workbench-web-ng/src/assets/submarine-logo.svg | 133 +++++
 .../src/environments/environment.prod.ts           |  22 +
 .../src/environments/environment.ts                |  35 ++
 .../workbench-web-ng/src/favicon.ico               | Bin 0 -> 113805 bytes
 .../workbench-web-ng/src/index.html                |  32 ++
 submarine-workbench/workbench-web-ng/src/main.ts   |  31 ++
 .../workbench-web-ng/src/polyfills.ts              |  82 +++
 .../workbench-web-ng/src/styles.scss               |  20 +
 submarine-workbench/workbench-web-ng/src/test.ts   |  39 ++
 .../workbench-web-ng/src/theme.less                | 561 +++++++++++++++++++++
 .../workbench-web-ng/tsconfig.app.json             |  18 +
 submarine-workbench/workbench-web-ng/tsconfig.json |  32 ++
 .../workbench-web-ng/tsconfig.spec.json            |  18 +
 submarine-workbench/workbench-web-ng/tslint.json   | 142 ++++++
 76 files changed, 3468 insertions(+)

diff --git a/submarine-workbench/pom.xml b/submarine-workbench/pom.xml
index 7238f42..0fc9ae9 100644
--- a/submarine-workbench/pom.xml
+++ b/submarine-workbench/pom.xml
@@ -39,6 +39,7 @@
   <modules>
     <module>interpreter</module>
     <module>workbench-web</module>
+    <module>workbench-web-ng</module>
     <module>workbench-server</module>
   </modules>
 
diff --git a/submarine-workbench/workbench-web-ng/.editorconfig b/submarine-workbench/workbench-web-ng/.editorconfig
new file mode 100644
index 0000000..c97d1a4
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/.editorconfig
@@ -0,0 +1,14 @@
+# Editor configuration, see https://editorconfig.org
+root = true
+
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 2
+insert_final_newline = true
+trim_trailing_whitespace = true
+end_of_line=lf
+
+[*.md]
+max_line_length = off
+trim_trailing_whitespace = false
diff --git a/submarine-workbench/workbench-web-ng/.gitignore b/submarine-workbench/workbench-web-ng/.gitignore
new file mode 100644
index 0000000..86d943a
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/.gitignore
@@ -0,0 +1,46 @@
+# See http://help.github.com/ignore-files/ for more about ignoring files.
+
+# compiled output
+/dist
+/tmp
+/out-tsc
+# Only exists if Bazel was run
+/bazel-out
+
+# dependencies
+/node_modules
+
+# profiling files
+chrome-profiler-events*.json
+speed-measure-plugin*.json
+
+# IDEs and editors
+/.idea
+.project
+.classpath
+.c9/
+*.launch
+.settings/
+*.sublime-workspace
+
+# IDE - VSCode
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+.history/*
+
+# misc
+/.sass-cache
+/connect.lock
+/coverage
+/libpeerconnection.log
+npm-debug.log
+yarn-error.log
+testem.log
+/typings
+
+# System Files
+.DS_Store
+Thumbs.db
diff --git a/submarine-workbench/workbench-web-ng/.prettierignore b/submarine-workbench/workbench-web-ng/.prettierignore
new file mode 100644
index 0000000..02a039a
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/.prettierignore
@@ -0,0 +1,4 @@
+**/*.md
+**/*.less
+**/*.scss
+**/*.svg
diff --git a/submarine-workbench/workbench-web-ng/.prettierrc b/submarine-workbench/workbench-web-ng/.prettierrc
new file mode 100644
index 0000000..4b9bb8a
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/.prettierrc
@@ -0,0 +1,15 @@
+{
+  "singleQuote": true,
+  "printWidth": 120,
+  "tabWidth": 2,
+  "useTabs": false,
+  "htmlWhitespaceSensitivity": "ignore",
+  "overrides": [
+    {
+      "files": ".prettierrc",
+      "options": {
+        "parser": "json"
+      }
+    }
+  ]
+}
diff --git a/submarine-workbench/workbench-web-ng/LICENSE b/submarine-workbench/workbench-web-ng/LICENSE
new file mode 100644
index 0000000..60b675e
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/LICENSE
@@ -0,0 +1,16 @@
+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.
diff --git a/submarine-workbench/workbench-web-ng/README.md b/submarine-workbench/workbench-web-ng/README.md
new file mode 100644
index 0000000..e6019dc
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/README.md
@@ -0,0 +1,29 @@
+# Submarine Web
+
+This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 8.3.9.
+
+## Development server
+
+> Start the Submarine server first
+
+Run `npm start` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
+
+## Code scaffolding
+
+Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
+
+## Build
+
+Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.
+
+## Running unit tests
+
+Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
+
+## Running end-to-end tests
+
+Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
+
+## Further help
+
+To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
diff --git a/submarine-workbench/workbench-web-ng/angular.json b/submarine-workbench/workbench-web-ng/angular.json
new file mode 100644
index 0000000..c47d29d
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/angular.json
@@ -0,0 +1,136 @@
+{
+  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
+  "version": 1,
+  "newProjectRoot": "projects",
+  "projects": {
+    "workbench-web-ng": {
+      "projectType": "application",
+      "schematics": {
+        "@schematics/angular:component": {
+          "style": "scss"
+        }
+      },
+      "root": "",
+      "sourceRoot": "src",
+      "prefix": "app",
+      "architect": {
+        "build": {
+          "builder": "@angular-devkit/build-angular:browser",
+          "options": {
+            "outputPath": "dist/workbench-web-ng",
+            "index": "src/index.html",
+            "main": "src/main.ts",
+            "polyfills": "src/polyfills.ts",
+            "tsConfig": "tsconfig.app.json",
+            "aot": false,
+            "assets": [
+              "src/favicon.ico",
+              "src/assets",
+              {
+                "glob": "**/*",
+                "input": "./node_modules/@ant-design/icons-angular/src/inline-svg/",
+                "output": "/assets/"
+              }
+            ],
+            "styles": [
+              "src/theme.less",
+              "src/styles.scss"
+            ],
+            "scripts": []
+          },
+          "configurations": {
+            "production": {
+              "fileReplacements": [
+                {
+                  "replace": "src/environments/environment.ts",
+                  "with": "src/environments/environment.prod.ts"
+                }
+              ],
+              "optimization": true,
+              "outputHashing": "all",
+              "sourceMap": false,
+              "extractCss": true,
+              "namedChunks": false,
+              "aot": true,
+              "extractLicenses": true,
+              "vendorChunk": false,
+              "buildOptimizer": true,
+              "budgets": [
+                {
+                  "type": "initial",
+                  "maximumWarning": "2mb",
+                  "maximumError": "5mb"
+                },
+                {
+                  "type": "anyComponentStyle",
+                  "maximumWarning": "6kb",
+                  "maximumError": "10kb"
+                }
+              ]
+            }
+          }
+        },
+        "serve": {
+          "builder": "@angular-devkit/build-angular:dev-server",
+          "options": {
+            "browserTarget": "workbench-web-ng:build"
+          },
+          "configurations": {
+            "production": {
+              "browserTarget": "workbench-web-ng:build:production"
+            }
+          }
+        },
+        "extract-i18n": {
+          "builder": "@angular-devkit/build-angular:extract-i18n",
+          "options": {
+            "browserTarget": "workbench-web-ng:build"
+          }
+        },
+        "test": {
+          "builder": "@angular-devkit/build-angular:karma",
+          "options": {
+            "main": "src/test.ts",
+            "polyfills": "src/polyfills.ts",
+            "tsConfig": "tsconfig.spec.json",
+            "karmaConfig": "karma.conf.js",
+            "assets": [
+              "src/favicon.ico",
+              "src/assets"
+            ],
+            "styles": [
+              "src/styles.scss"
+            ],
+            "scripts": []
+          }
+        },
+        "lint": {
+          "builder": "@angular-devkit/build-angular:tslint",
+          "options": {
+            "tsConfig": [
+              "tsconfig.app.json",
+              "tsconfig.spec.json",
+              "e2e/tsconfig.json"
+            ],
+            "exclude": [
+              "**/node_modules/**"
+            ]
+          }
+        },
+        "e2e": {
+          "builder": "@angular-devkit/build-angular:protractor",
+          "options": {
+            "protractorConfig": "e2e/protractor.conf.js",
+            "devServerTarget": "workbench-web-ng:serve"
+          },
+          "configurations": {
+            "production": {
+              "devServerTarget": "workbench-web-ng:serve:production"
+            }
+          }
+        }
+      }
+    }
+  },
+  "defaultProject": "workbench-web-ng"
+}
\ No newline at end of file
diff --git a/submarine-workbench/workbench-web-ng/browserslist b/submarine-workbench/workbench-web-ng/browserslist
new file mode 100644
index 0000000..8084853
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/browserslist
@@ -0,0 +1,12 @@
+# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
+# For additional information regarding the format and rule options, please see:
+# https://github.com/browserslist/browserslist#queries
+
+# You can see what browsers were selected by your queries by running:
+#   npx browserslist
+
+> 0.5%
+last 2 versions
+Firefox ESR
+not dead
+not IE 9-11 # For IE 9-11 support, remove 'not'.
\ No newline at end of file
diff --git a/submarine-workbench/workbench-web-ng/e2e/protractor.conf.js b/submarine-workbench/workbench-web-ng/e2e/protractor.conf.js
new file mode 100644
index 0000000..77cf687
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/e2e/protractor.conf.js
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+
+// @ts-check
+// Protractor configuration file, see link for more information
+// https://github.com/angular/protractor/blob/master/lib/config.ts
+
+const { SpecReporter } = require('jasmine-spec-reporter');
+
+/**
+ * @type { import("protractor").Config }
+ */
+exports.config = {
+  allScriptsTimeout: 11000,
+  specs: [
+    './src/**/*.e2e-spec.ts'
+  ],
+  capabilities: {
+    'browserName': 'chrome'
+  },
+  directConnect: true,
+  baseUrl: 'http://localhost:4200/',
+  framework: 'jasmine',
+  jasmineNodeOpts: {
+    showColors: true,
+    defaultTimeoutInterval: 30000,
+    print: function() {}
+  },
+  onPrepare() {
+    require('ts-node').register({
+      project: require('path').join(__dirname, './tsconfig.json')
+    });
+    jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
+  }
+};
diff --git a/submarine-workbench/workbench-web-ng/e2e/src/app.e2e-spec.ts b/submarine-workbench/workbench-web-ng/e2e/src/app.e2e-spec.ts
new file mode 100644
index 0000000..5b0d8f6
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/e2e/src/app.e2e-spec.ts
@@ -0,0 +1,42 @@
+/*
+ * 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 { AppPage } from './app.po';
+import { browser, logging } from 'protractor';
+
+describe('workspace-project App', () => {
+  let page: AppPage;
+
+  beforeEach(() => {
+    page = new AppPage();
+  });
+
+  it('should display welcome message', () => {
+    page.navigateTo();
+    expect(page.getTitleText()).toEqual('workbench-web-ng app is running!');
+  });
+
+  afterEach(async () => {
+    // Assert that there are no errors emitted from the browser
+    const logs = await browser.manage().logs().get(logging.Type.BROWSER);
+    expect(logs).not.toContain(jasmine.objectContaining({
+      level: logging.Level.SEVERE,
+    } as logging.Entry));
+  });
+});
diff --git a/submarine-workbench/workbench-web-ng/e2e/src/app.po.ts b/submarine-workbench/workbench-web-ng/e2e/src/app.po.ts
new file mode 100644
index 0000000..493318b
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/e2e/src/app.po.ts
@@ -0,0 +1,30 @@
+/*
+ * 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 { browser, by, element } from 'protractor';
+
+export class AppPage {
+  navigateTo() {
+    return browser.get(browser.baseUrl) as Promise<any>;
+  }
+
+  getTitleText() {
+    return element(by.css('app-root .content span')).getText() as Promise<string>;
+  }
+}
diff --git a/submarine-workbench/workbench-web-ng/e2e/tsconfig.json b/submarine-workbench/workbench-web-ng/e2e/tsconfig.json
new file mode 100644
index 0000000..39b800f
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/e2e/tsconfig.json
@@ -0,0 +1,13 @@
+{
+  "extends": "../tsconfig.json",
+  "compilerOptions": {
+    "outDir": "../out-tsc/e2e",
+    "module": "commonjs",
+    "target": "es5",
+    "types": [
+      "jasmine",
+      "jasminewd2",
+      "node"
+    ]
+  }
+}
diff --git a/submarine-workbench/workbench-web-ng/karma.conf.js b/submarine-workbench/workbench-web-ng/karma.conf.js
new file mode 100644
index 0000000..00f2741
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/karma.conf.js
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+
+// Karma configuration file, see link for more information
+// https://karma-runner.github.io/1.0/config/configuration-file.html
+
+module.exports = function (config) {
+  config.set({
+    basePath: '',
+    frameworks: ['jasmine', '@angular-devkit/build-angular'],
+    plugins: [
+      require('karma-jasmine'),
+      require('karma-chrome-launcher'),
+      require('karma-jasmine-html-reporter'),
+      require('karma-coverage-istanbul-reporter'),
+      require('@angular-devkit/build-angular/plugins/karma')
+    ],
+    client: {
+      clearContext: false // leave Jasmine Spec Runner output visible in browser
+    },
+    coverageIstanbulReporter: {
+      dir: require('path').join(__dirname, './coverage/workbench-web-ng'),
+      reports: ['html', 'lcovonly', 'text-summary'],
+      fixWebpackSourcePaths: true
+    },
+    reporters: ['progress', 'kjhtml'],
+    port: 9876,
+    colors: true,
+    logLevel: config.LOG_INFO,
+    autoWatch: true,
+    browsers: ['Chrome'],
+    singleRun: false,
+    restartOnFileChange: true
+  });
+};
diff --git a/submarine-workbench/workbench-web-ng/package.json b/submarine-workbench/workbench-web-ng/package.json
new file mode 100644
index 0000000..c2deab2
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/package.json
@@ -0,0 +1,52 @@
+{
+  "name": "workbench-web-ng",
+  "version": "0.0.0",
+  "scripts": {
+    "ng": "ng",
+    "start": "ng serve --proxy-config proxy.conf.js",
+    "build": "ng build",
+    "test": "ng test",
+    "lint": "ng lint",
+    "e2e": "ng e2e"
+  },
+  "private": true,
+  "dependencies": {
+    "@angular/animations": "~8.2.9",
+    "@angular/common": "~8.2.9",
+    "@angular/compiler": "~8.2.9",
+    "@angular/core": "~8.2.9",
+    "@angular/forms": "~8.2.9",
+    "@angular/platform-browser": "~8.2.9",
+    "@angular/platform-browser-dynamic": "~8.2.9",
+    "@angular/router": "~8.2.9",
+    "md5": "^2.2.1",
+    "ng-zorro-antd": "8.1.2",
+    "rxjs": "~6.4.0",
+    "tslib": "^1.10.0",
+    "zone.js": "~0.9.1"
+  },
+  "devDependencies": {
+    "@angular-devkit/build-angular": "~0.803.9",
+    "@angular/cli": "~8.3.9",
+    "@angular/compiler-cli": "~8.2.9",
+    "@angular/language-service": "~8.2.9",
+    "@types/jasmine": "~3.3.8",
+    "@types/jasminewd2": "~2.0.3",
+    "@types/node": "~8.9.4",
+    "codelyzer": "^5.1.2",
+    "dotenv": "^8.0.0",
+    "https-proxy-agent": "^2.2.1",
+    "jasmine-core": "~3.4.0",
+    "jasmine-spec-reporter": "~4.2.1",
+    "karma": "~4.1.0",
+    "karma-chrome-launcher": "~2.2.0",
+    "karma-coverage-istanbul-reporter": "~2.0.1",
+    "karma-jasmine": "~2.0.1",
+    "karma-jasmine-html-reporter": "^1.4.0",
+    "prettier": "^1.17.0",
+    "protractor": "~5.4.0",
+    "ts-node": "~7.0.0",
+    "tslint": "~5.15.0",
+    "typescript": "~3.5.3"
+  }
+}
diff --git a/submarine-workbench/workbench-web-ng/pom.xml b/submarine-workbench/workbench-web-ng/pom.xml
new file mode 100644
index 0000000..9b4fca3
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/pom.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.submarine</groupId>
+    <artifactId>submarine-workbench</artifactId>
+    <version>0.3.0-SNAPSHOT</version>
+    <relativePath>../pom.xml</relativePath>
+  </parent>
+
+  <groupId>org.apache.submarine</groupId>
+  <artifactId>workbench-web-ng</artifactId>
+  <!--  <packaging>war</packaging>-->
+  <!--  <version>0.3.0-SNAPSHOT</version>-->
+  <!--  <name>Submarine: Workbench Web Angular</name>-->
+
+  <!-- See https://github.com/eirslett/frontend-maven-plugin/issues/229 -->
+  <prerequisites>
+    <maven>3.1.0</maven>
+  </prerequisites>
+
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+
+    <!--plugin versions-->
+    <plugin.frontend.nodeDownloadRoot>https://nodejs.org/dist/</plugin.frontend.nodeDownloadRoot>
+  </properties>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.rat</groupId>
+        <artifactId>apache-rat-plugin</artifactId>
+        <configuration>
+          <skip>true</skip>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+
+</project>
diff --git a/submarine-workbench/workbench-web-ng/proxy.conf.js b/submarine-workbench/workbench-web-ng/proxy.conf.js
new file mode 100644
index 0000000..3f07d6b
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/proxy.conf.js
@@ -0,0 +1,66 @@
+/*
+ * 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.
+ */
+
+const dotenv = require('dotenv');
+const HttpsProxyAgent = require('https-proxy-agent');
+dotenv.config();
+
+const proxyConfig = [
+  {
+    context: ['/api'],
+    target: 'http://localhost:8080',
+    secure: false,
+    changeOrigin: true
+  },
+  {
+    context: '/ws',
+    target: 'ws://localhost:8080',
+    secure: false,
+    ws:true,
+    changeOrigin: true
+  }
+];
+
+function httpUrlToWSUrl(url) {
+  return url.replace(/(http)(s)?\/\//, "ws$2://");
+}
+
+function setupForCorporateProxy(proxyConfig) {
+  const proxyServer = process.env.SERVER_PROXY;
+  const httpProxy = process.env.HTTP_PROXY;
+  if (proxyServer) {
+    let agent = null;
+    if (httpProxy) {
+      agent = new HttpsProxyAgent(httpProxy);
+    }
+    proxyConfig.forEach(function(entry) {
+      if (entry.context === '/ws') {
+        entry.target = httpUrlToWSUrl(proxyServer)
+      } else {
+        entry.target = proxyServer;
+      }
+      if (agent) {
+        entry.agent = agent;
+      }
+    });
+  }
+  return proxyConfig;
+}
+
+module.exports = setupForCorporateProxy(proxyConfig);
diff --git a/submarine-workbench/workbench-web-ng/src/WEB-INF/web.xml b/submarine-workbench/workbench-web-ng/src/WEB-INF/web.xml
new file mode 100644
index 0000000..2374dec
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/src/WEB-INF/web.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+  ~ 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.
+  -->
+
+<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+         version="3.0">
+
+ <display-name>workbench-web</display-name>
+  <servlet>
+    <servlet-name>default</servlet-name>
+    <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
+    <init-param>
+      <param-name>jersey.config.server.provider.packages</param-name>
+      <param-value>org.apache.submarine.rest</param-value>
+    </init-param>
+    <load-on-startup>1</load-on-startup>
+  </servlet>
+
+  <context-param>
+    <param-name>configuration</param-name>
+    <param-value>deployment</param-value>
+  </context-param>
+
+  <session-config>
+    <cookie-config>
+      <http-only>true</http-only>
+      <secure>true</secure>
+    </cookie-config>
+  </session-config>
+</web-app>
diff --git a/submarine-workbench/workbench-web-ng/src/app/app-routing.module.ts b/submarine-workbench/workbench-web-ng/src/app/app-routing.module.ts
new file mode 100644
index 0000000..6cb4725
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/src/app/app-routing.module.ts
@@ -0,0 +1,45 @@
+/*
+ * 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 { RouterModule, Routes } from '@angular/router';
+import { AuthGuard } from '@submarine/core';
+
+const routes: Routes = [
+  {
+    path: '',
+    pathMatch: 'full',
+    redirectTo: '/manager/user'
+  },
+  {
+    path: 'manager',
+    canActivate: [AuthGuard],
+    loadChildren: () => import('./pages/manager/manager.module').then(m => m.ManagerModule)
+  },
+  {
+    path: 'user',
+    loadChildren: () => import('./pages/user/user.module').then(m => m.UserModule)
+  }
+];
+
+@NgModule({
+  imports: [RouterModule.forRoot(routes)],
+  exports: [RouterModule]
+})
+export class AppRoutingModule {}
diff --git a/submarine-workbench/workbench-web-ng/src/app/app.component.html b/submarine-workbench/workbench-web-ng/src/app/app.component.html
new file mode 100644
index 0000000..6c447c0
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/src/app/app.component.html
@@ -0,0 +1,74 @@
+<!--
+  ~ 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 class="app-layout">
+  <router-outlet></router-outlet>
+<!--  <nz-sider class="menu-sidebar"-->
+<!--            nzCollapsible-->
+<!--            nzWidth="256px"-->
+<!--            nzBreakpoint="md"-->
+<!--            [(nzCollapsed)]="isCollapsed"-->
+<!--            [nzTrigger]="null">-->
+<!--    <div class="sidebar-logo">-->
+<!--      <a href="https://ng.ant.design/" target="_blank">-->
+<!--        <img src="https://ng.ant.design/assets/img/logo.svg" alt="logo">-->
+<!--        <h1>Ant Design Of Angular</h1>-->
+<!--      </a>-->
+<!--    </div>-->
+<!--    <ul nz-menu nzTheme="dark" nzMode="inline" [nzInlineCollapsed]="isCollapsed">-->
+<!--      <li nz-submenu nzOpen nzTitle="Dashboard" nzIcon="dashboard">-->
+<!--        <ul>-->
+<!--          <li nz-menu-item nzMatchRouter>-->
+<!--            <a routerLink="/welcome">Welcome</a>-->
+<!--          </li>-->
+<!--          <li nz-menu-item nzMatchRouter>-->
+<!--            <a>Monitor</a>-->
+<!--          </li>-->
+<!--          <li nz-menu-item nzMatchRouter>-->
+<!--            <a>Workplace</a>-->
+<!--          </li>-->
+<!--        </ul>-->
+<!--      </li>-->
+<!--      <li nz-submenu nzOpen nzTitle="Form" nzIcon="form">-->
+<!--        <ul>-->
+<!--          <li nz-menu-item nzMatchRouter>-->
+<!--            <a>Basic Form</a>-->
+<!--          </li>-->
+<!--        </ul>-->
+<!--      </li>-->
+<!--    </ul>-->
+<!--  </nz-sider>-->
+<!--  <nz-layout>-->
+<!--    <nz-header>-->
+<!--      <div class="app-header">-->
+<!--        <span class="header-trigger" (click)="isCollapsed = !isCollapsed">-->
+<!--            <i class="trigger"-->
+<!--               nz-icon-->
+<!--               [nzType]="isCollapsed ? 'menu-unfold' : 'menu-fold'"-->
+<!--            ></i>-->
+<!--        </span>-->
+<!--      </div>-->
+<!--    </nz-header>-->
+<!--    <nz-content>-->
+<!--      <div class="inner-content">-->
+<!--        <router-outlet></router-outlet>-->
+<!--      </div>-->
+<!--    </nz-content>-->
+<!--  </nz-layout>-->
+</nz-layout>
diff --git a/submarine-workbench/workbench-web-ng/src/app/app.component.scss b/submarine-workbench/workbench-web-ng/src/app/app.component.scss
new file mode 100644
index 0000000..67bafe6
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/src/app/app.component.scss
@@ -0,0 +1,99 @@
+/*!
+ * 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.
+ */
+
+:host {
+  display: flex;
+  text-rendering: optimizeLegibility;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+.app-layout {
+  height: 100vh;
+}
+
+.menu-sidebar {
+  position: relative;
+  z-index: 10;
+  min-height: 100vh;
+  box-shadow: 2px 0 6px rgba(0,21,41,.35);
+}
+
+.header-trigger {
+  height: 64px;
+  padding: 20px 24px;
+  font-size: 20px;
+  cursor: pointer;
+  transition: all .3s,padding 0s;
+}
+
+.trigger:hover {
+  color: #1890ff;
+}
+
+.sidebar-logo {
+  position: relative;
+  height: 64px;
+  padding-left: 24px;
+  overflow: hidden;
+  line-height: 64px;
+  background: #001529;
+  transition: all .3s;
+}
+
+.sidebar-logo img {
+  display: inline-block;
+  height: 32px;
+  width: 32px;
+  vertical-align: middle;
+}
+
+.sidebar-logo h1 {
+  display: inline-block;
+  margin: 0 0 0 20px;
+  color: #fff;
+  font-weight: 600;
+  font-size: 14px;
+  font-family: Avenir,Helvetica Neue,Arial,Helvetica,sans-serif;
+  vertical-align: middle;
+}
+
+nz-header {
+  padding: 0;
+  width: 100%;
+  z-index: 2;
+}
+
+.app-header {
+  position: relative;
+  height: 64px;
+  padding: 0;
+  background: #fff;
+  box-shadow: 0 1px 4px rgba(0,21,41,.08);
+}
+
+nz-content {
+  margin: 24px;
+}
+
+.inner-content {
+  padding: 24px;
+  background: #fff;
+  height: 100%;
+}
diff --git a/submarine-workbench/workbench-web-ng/src/app/app.component.spec.ts b/submarine-workbench/workbench-web-ng/src/app/app.component.spec.ts
new file mode 100644
index 0000000..e59a500
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/src/app/app.component.spec.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 { async, TestBed } from '@angular/core/testing';
+import { RouterTestingModule } from '@angular/router/testing';
+import { AppComponent } from './app.component';
+
+describe('AppComponent', () => {
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      imports: [RouterTestingModule],
+      declarations: [AppComponent]
+    }).compileComponents();
+  }));
+
+  it('should create the app', () => {
+    const fixture = TestBed.createComponent(AppComponent);
+    const app = fixture.debugElement.componentInstance;
+    expect(app).toBeTruthy();
+  });
+
+  it(`should have as title 'workbench-web-ng'`, () => {
+    const fixture = TestBed.createComponent(AppComponent);
+    const app = fixture.debugElement.componentInstance;
+    expect(app.title).toEqual('workbench-web-ng');
+  });
+
+  it('should render title', () => {
+    const fixture = TestBed.createComponent(AppComponent);
+    fixture.detectChanges();
+    const compiled = fixture.debugElement.nativeElement;
+    expect(compiled.querySelector('.content span').textContent).toContain('workbench-web-ng app is running!');
+  });
+});
diff --git a/submarine-workbench/workbench-web-ng/src/app/app.component.ts b/submarine-workbench/workbench-web-ng/src/app/app.component.ts
new file mode 100644
index 0000000..51425c4
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/src/app/app.component.ts
@@ -0,0 +1,40 @@
+/*
+ * 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 { Title } from '@angular/platform-browser';
+import { NavigationEnd, Router } from '@angular/router';
+import { filter } from 'rxjs/operators';
+
+@Component({
+  selector: 'submarine-root',
+  templateUrl: './app.component.html',
+  styleUrls: ['./app.component.scss']
+})
+export class AppComponent implements OnInit {
+  constructor(private router: Router, private title: Title) {}
+
+  ngOnInit(): void {
+    this.router.events.pipe(filter(event => event instanceof NavigationEnd)).subscribe(() => {
+      const paths = this.router.url.split('/');
+
+      this.title.setTitle(`Submarine - ${paths[paths.length - 1]}`);
+    });
+  }
+}
diff --git a/submarine-workbench/workbench-web-ng/src/app/app.module.ts b/submarine-workbench/workbench-web-ng/src/app/app.module.ts
new file mode 100644
index 0000000..59c9cfd
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/src/app/app.module.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 { NgModule } from '@angular/core';
+import { BrowserModule } from '@angular/platform-browser';
+
+import { registerLocaleData } from '@angular/common';
+import { HttpClientModule } from '@angular/common/http';
+import zh from '@angular/common/locales/zh';
+import { FormsModule } from '@angular/forms';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+import { LocalStorageService } from '@submarine/services/local-storage.service';
+import { zh_CN, NgZorroAntdModule, NZ_I18N } from 'ng-zorro-antd';
+import { AppRoutingModule } from './app-routing.module';
+import { AppComponent } from './app.component';
+import { IconsProviderModule } from './icons-provider.module';
+
+registerLocaleData(zh);
+
+@NgModule({
+  declarations: [AppComponent],
+  imports: [
+    BrowserModule,
+    AppRoutingModule,
+    IconsProviderModule,
+    NgZorroAntdModule,
+    FormsModule,
+    HttpClientModule,
+    BrowserAnimationsModule
+  ],
+  providers: [{ provide: NZ_I18N, useValue: zh_CN }, LocalStorageService],
+  bootstrap: [AppComponent]
+})
+export class AppModule {}
diff --git a/submarine-workbench/workbench-web-ng/src/app/core/auth/auth.guard.ts b/submarine-workbench/workbench-web-ng/src/app/core/auth/auth.guard.ts
new file mode 100644
index 0000000..63a9a8b
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/src/app/core/auth/auth.guard.ts
@@ -0,0 +1,48 @@
+/*
+ * 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 { Injectable } from '@angular/core';
+import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
+import { AuthService } from '@submarine/services';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class AuthGuard implements CanActivate {
+  constructor(private authService: AuthService, private router: Router) {}
+
+  canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
+    const url: string = state.url;
+
+    return this.checkLogin(url);
+  }
+
+  checkLogin(url: string): boolean {
+    if (this.authService.isLoggedIn) {
+      return true;
+    }
+
+    // Store the attempted URL for redirecting
+    this.authService.redirectUrl = url;
+
+    // Navigate to the login page with extras
+    this.router.navigate(['/user/login']);
+    return false;
+  }
+}
diff --git a/submarine-workbench/workbench-web-ng/src/app/core/index.ts b/submarine-workbench/workbench-web-ng/src/app/core/index.ts
new file mode 100644
index 0000000..30631d0
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/src/app/core/index.ts
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+export * from './publick-api';
diff --git a/submarine-workbench/workbench-web-ng/src/app/core/publick-api.ts b/submarine-workbench/workbench-web-ng/src/app/core/publick-api.ts
new file mode 100644
index 0000000..498014e
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/src/app/core/publick-api.ts
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+export * from './auth/auth.guard';
diff --git a/submarine-workbench/workbench-web-ng/src/app/icons-provider.module.ts b/submarine-workbench/workbench-web-ng/src/app/icons-provider.module.ts
new file mode 100644
index 0000000..8f51cf1
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/src/app/icons-provider.module.ts
@@ -0,0 +1,30 @@
+/*
+ * 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 { NZ_ICONS } from 'ng-zorro-antd';
+
+import { DashboardOutline, FormOutline, MenuFoldOutline, MenuUnfoldOutline } from '@ant-design/icons-angular/icons';
+
+const icons = [MenuFoldOutline, MenuUnfoldOutline, DashboardOutline, FormOutline];
+
+@NgModule({
+  providers: [{ provide: NZ_ICONS, useValue: icons }]
+})
+export class IconsProviderModule {}
diff --git a/submarine-workbench/workbench-web-ng/src/app/interfaces/index.ts b/submarine-workbench/workbench-web-ng/src/app/interfaces/index.ts
new file mode 100644
index 0000000..630627e
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/src/app/interfaces/index.ts
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+export * from './public-api';
diff --git a/submarine-workbench/workbench-web-ng/src/app/interfaces/permission.ts b/submarine-workbench/workbench-web-ng/src/app/interfaces/permission.ts
new file mode 100644
index 0000000..fffef9e
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/src/app/interfaces/permission.ts
@@ -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.
+ */
+
+export interface PermissionActionEntitySet {
+  action: string;
+  defaultChecked: boolean;
+  describe: string;
+}
+
+export type PermissionAction = PermissionActionEntitySet;
+
+export class Permission {
+  permissionId = '';
+  permissionName = '';
+  roleId = '';
+  actionList = null;
+  actionEntitySet: PermissionActionEntitySet[] = [];
+  actions: PermissionAction[] = [];
+}
diff --git a/submarine-workbench/workbench-web-ng/src/app/interfaces/public-api.ts b/submarine-workbench/workbench-web-ng/src/app/interfaces/public-api.ts
new file mode 100644
index 0000000..8958522
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/src/app/interfaces/public-api.ts
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+
+export * from './permission';
+export * from './role';
+export * from './user';
+export * from './rest';
diff --git a/submarine-workbench/workbench-web-ng/src/app/interfaces/rest.ts b/submarine-workbench/workbench-web-ng/src/app/interfaces/rest.ts
new file mode 100644
index 0000000..dcf39fc
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/src/app/interfaces/rest.ts
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+
+export interface Rest<T> {
+  result: T;
+  code: number;
+  message: string;
+  status: string;
+  success: boolean;
+}
diff --git a/submarine-workbench/workbench-web-ng/src/app/interfaces/role.ts b/submarine-workbench/workbench-web-ng/src/app/interfaces/role.ts
new file mode 100644
index 0000000..f3f8669
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/src/app/interfaces/role.ts
@@ -0,0 +1,31 @@
+/*
+ * 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 { Permission } from './permission';
+
+export type RoleId = string;
+
+export class Role {
+  createTime: number = -1;
+  creatorId = '';
+  describe = '';
+  id = '';
+  name = '';
+  permissions: Permission[];
+}
diff --git a/submarine-workbench/workbench-web-ng/src/app/interfaces/user.ts b/submarine-workbench/workbench-web-ng/src/app/interfaces/user.ts
new file mode 100644
index 0000000..c68c424
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/src/app/interfaces/user.ts
@@ -0,0 +1,31 @@
+/*
+ * 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 { Role } from './role';
+
+export class User {
+  avatar = '';
+  id = '';
+  name = '';
+  telephone = '';
+  username = '';
+  role: Role;
+  roleId: number;
+  token: string;
+}
diff --git a/submarine-workbench/workbench-web-ng/src/app/pages/manager/manager-routing.module.ts b/submarine-workbench/workbench-web-ng/src/app/pages/manager/manager-routing.module.ts
new file mode 100644
index 0000000..97c775f
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/src/app/pages/manager/manager-routing.module.ts
@@ -0,0 +1,47 @@
+/*
+ * 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 { RouterModule, Routes } from '@angular/router';
+import { ManagerComponent } from './manager.component';
+import { UserComponent } from './user/user.component';
+
+const routes: Routes = [
+  {
+    path: '',
+    component: ManagerComponent,
+    children: [
+      {
+        path: '',
+        pathMatch: 'full',
+        redirectTo: '/manager/user'
+      },
+      {
+        path: 'user',
+        component: UserComponent
+      }
+    ]
+  }
+];
+
+@NgModule({
+  imports: [RouterModule.forChild(routes)],
+  exports: [RouterModule]
+})
+export class ManagerRoutingModule {}
diff --git a/submarine-workbench/workbench-web-ng/src/app/pages/manager/manager.component.html b/submarine-workbench/workbench-web-ng/src/app/pages/manager/manager.component.html
new file mode 100644
index 0000000..4f806ff
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/src/app/pages/manager/manager.component.html
@@ -0,0 +1,21 @@
+<!--
+  ~ 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.
+  -->
+
+<p>manager works!</p>
+<router-outlet></router-outlet>
diff --git a/submarine-workbench/workbench-web-ng/src/app/pages/manager/manager.component.scss b/submarine-workbench/workbench-web-ng/src/app/pages/manager/manager.component.scss
new file mode 100644
index 0000000..510f082
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/src/app/pages/manager/manager.component.scss
@@ -0,0 +1,19 @@
+/*!
+ * 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.
+ */
+
diff --git a/submarine-workbench/workbench-web-ng/src/app/pages/manager/manager.component.ts b/submarine-workbench/workbench-web-ng/src/app/pages/manager/manager.component.ts
new file mode 100644
index 0000000..efa6503
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/src/app/pages/manager/manager.component.ts
@@ -0,0 +1,31 @@
+/*
+ * 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';
+
+@Component({
+  selector: 'submarine-manager',
+  templateUrl: './manager.component.html',
+  styleUrls: ['./manager.component.scss']
+})
+export class ManagerComponent implements OnInit {
+  constructor() {}
+
+  ngOnInit() {}
+}
diff --git a/submarine-workbench/workbench-web-ng/src/app/pages/manager/manager.module.ts b/submarine-workbench/workbench-web-ng/src/app/pages/manager/manager.module.ts
new file mode 100644
index 0000000..cfabafe
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/src/app/pages/manager/manager.module.ts
@@ -0,0 +1,31 @@
+/*
+ * 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 { CommonModule } from '@angular/common';
+import { NgModule } from '@angular/core';
+import { NgZorroAntdModule } from 'ng-zorro-antd';
+import { ManagerRoutingModule } from './manager-routing.module';
+import { ManagerComponent } from './manager.component';
+import { UserComponent } from './user/user.component';
+
+@NgModule({
+  declarations: [UserComponent, ManagerComponent],
+  imports: [CommonModule, ManagerRoutingModule, NgZorroAntdModule]
+})
+export class ManagerModule {}
diff --git a/submarine-workbench/workbench-web-ng/src/app/pages/manager/user/user.component.html b/submarine-workbench/workbench-web-ng/src/app/pages/manager/user/user.component.html
new file mode 100644
index 0000000..c5412cf
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/src/app/pages/manager/user/user.component.html
@@ -0,0 +1,20 @@
+<!--
+  ~ 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.
+  -->
+
+<p>user works!</p>
diff --git a/submarine-workbench/workbench-web-ng/src/app/pages/manager/user/user.component.scss b/submarine-workbench/workbench-web-ng/src/app/pages/manager/user/user.component.scss
new file mode 100644
index 0000000..510f082
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/src/app/pages/manager/user/user.component.scss
@@ -0,0 +1,19 @@
+/*!
+ * 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.
+ */
+
diff --git a/submarine-workbench/workbench-web-ng/src/app/pages/manager/user/user.component.ts b/submarine-workbench/workbench-web-ng/src/app/pages/manager/user/user.component.ts
new file mode 100644
index 0000000..8c3d582
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/src/app/pages/manager/user/user.component.ts
@@ -0,0 +1,31 @@
+/*
+ * 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';
+
+@Component({
+  selector: 'submarine-manager-user',
+  templateUrl: './user.component.html',
+  styleUrls: ['./user.component.scss']
+})
+export class UserComponent implements OnInit {
+  constructor() {}
+
+  ngOnInit() {}
+}
diff --git a/submarine-workbench/workbench-web-ng/src/app/pages/user/login/login-routing.module.ts b/submarine-workbench/workbench-web-ng/src/app/pages/user/login/login-routing.module.ts
new file mode 100644
index 0000000..b9c11e5
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/src/app/pages/user/login/login-routing.module.ts
@@ -0,0 +1,30 @@
+/*
+ * 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 { RouterModule, Routes } from '@angular/router';
+import { LoginComponent } from './login.component';
+
+const routes: Routes = [{ path: '', component: LoginComponent }];
+
+@NgModule({
+  imports: [RouterModule.forChild(routes)],
+  exports: [RouterModule]
+})
+export class LoginRoutingModule {}
diff --git a/submarine-workbench/workbench-web-ng/src/app/pages/user/login/login.component.html b/submarine-workbench/workbench-web-ng/src/app/pages/user/login/login.component.html
new file mode 100644
index 0000000..294cc71
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/src/app/pages/user/login/login.component.html
@@ -0,0 +1,46 @@
+<!--
+  ~ 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.
+  -->
+
+<form nz-form [formGroup]="validateForm" class="login-form" (ngSubmit)="submitForm()">
+  <nz-form-item>
+    <nz-form-control nzErrorTip="Please input your username!">
+      <nz-input-group nzPrefixIcon="user">
+        <input type="text" nz-input formControlName="userName" placeholder="Username" />
+      </nz-input-group>
+    </nz-form-control>
+  </nz-form-item>
+  <nz-form-item>
+    <nz-form-control nzErrorTip="Please input your Password!">
+      <nz-input-group nzPrefixIcon="lock">
+        <input type="password" nz-input formControlName="password" placeholder="Password" />
+      </nz-input-group>
+    </nz-form-control>
+  </nz-form-item>
+  <nz-form-item>
+    <nz-form-control>
+      <label nz-checkbox formControlName="remember">
+        <span>Remember me</span>
+      </label>
+      <a class="login-form-forgot" [routerLink]="['/user/register']">Forgot password</a>
+      <button nz-button class="login-form-button" [nzType]="'primary'">Log in</button>
+      Or
+      <a [routerLink]="['/user/register']">register account!</a>
+    </nz-form-control>
+  </nz-form-item>
+</form>
diff --git a/submarine-workbench/workbench-web-ng/src/app/pages/user/login/login.component.scss b/submarine-workbench/workbench-web-ng/src/app/pages/user/login/login.component.scss
new file mode 100644
index 0000000..f0c18d5
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/src/app/pages/user/login/login.component.scss
@@ -0,0 +1,31 @@
+/*!
+ * 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.
+ */
+
+.login-form {
+  max-width: 320px;
+}
+
+.login-form-forgot {
+  float: right;
+}
+
+.login-form-button {
+  width: 100%;
+}
+
diff --git a/submarine-workbench/workbench-web-ng/src/app/pages/user/login/login.component.ts b/submarine-workbench/workbench-web-ng/src/app/pages/user/login/login.component.ts
new file mode 100644
index 0000000..9190cea
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/src/app/pages/user/login/login.component.ts
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { Component, OnInit } from '@angular/core';
+import { FormBuilder, FormGroup, Validators } from '@angular/forms';
+import { Router } from '@angular/router';
+import { NzNotificationService } from 'ng-zorro-antd';
+import { AuthService } from '../../../services';
+
+@Component({
+  selector: 'submarine-login',
+  templateUrl: './login.component.html',
+  styleUrls: ['./login.component.scss']
+})
+export class LoginComponent implements OnInit {
+  validateForm: FormGroup;
+
+  constructor(
+    private fb: FormBuilder,
+    private authService: AuthService,
+    private nzNotificationService: NzNotificationService,
+    private router: Router
+  ) {
+    if (this.authService.isLoggedIn) {
+      this.router.navigate(['/manager/user']);
+    }
+  }
+
+  submitForm(): void {
+    for (const i in this.validateForm.controls) {
+      this.validateForm.controls[i].markAsDirty();
+      this.validateForm.controls[i].updateValueAndValidity();
+    }
+
+    if (this.validateForm.status === 'VALID') {
+      const { value } = this.validateForm;
+      this.authService.login(value).subscribe(
+        () => {
+          this.loginSuccess();
+        },
+        error => {
+          console.log(error);
+          this.requestFailed(error);
+        }
+      );
+    }
+  }
+
+  ngOnInit(): void {
+    this.validateForm = this.fb.group({
+      userName: [null, [Validators.required]],
+      password: [null, [Validators.required]],
+      remember: [true]
+    });
+  }
+
+  loginSuccess() {
+    this.router.navigate(['/manager/user']);
+
+    setTimeout(() => {
+      this.nzNotificationService.success('Welcome', 'Welcome back');
+    }, 1000);
+  }
+
+  requestFailed(error: Error) {
+    this.nzNotificationService.error('Request error', error.message || 'Request error, please try again', {
+      nzDuration: 4000
+    });
+  }
+}
diff --git a/submarine-workbench/workbench-web-ng/src/app/pages/user/register/register.component.html b/submarine-workbench/workbench-web-ng/src/app/pages/user/register/register.component.html
new file mode 100644
index 0000000..8955bef
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/src/app/pages/user/register/register.component.html
@@ -0,0 +1,20 @@
+<!--
+  ~ 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.
+  -->
+
+<p>register works!</p>
diff --git a/submarine-workbench/workbench-web-ng/src/app/pages/user/register/register.component.scss b/submarine-workbench/workbench-web-ng/src/app/pages/user/register/register.component.scss
new file mode 100644
index 0000000..510f082
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/src/app/pages/user/register/register.component.scss
@@ -0,0 +1,19 @@
+/*!
+ * 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.
+ */
+
diff --git a/submarine-workbench/workbench-web-ng/src/app/pages/user/register/register.component.ts b/submarine-workbench/workbench-web-ng/src/app/pages/user/register/register.component.ts
new file mode 100644
index 0000000..c8e9c62
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/src/app/pages/user/register/register.component.ts
@@ -0,0 +1,31 @@
+/*
+ * 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';
+
+@Component({
+  selector: 'submarine-register',
+  templateUrl: './register.component.html',
+  styleUrls: ['./register.component.scss']
+})
+export class RegisterComponent implements OnInit {
+  constructor() {}
+
+  ngOnInit() {}
+}
diff --git a/submarine-workbench/workbench-web-ng/src/app/pages/user/user-routing.module.ts b/submarine-workbench/workbench-web-ng/src/app/pages/user/user-routing.module.ts
new file mode 100644
index 0000000..9098a99
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/src/app/pages/user/user-routing.module.ts
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { NgModule } from '@angular/core';
+import { RouterModule, Routes } from '@angular/router';
+import { LoginComponent } from './login/login.component';
+import { RegisterComponent } from './register/register.component';
+import { UserComponent } from './user.component';
+
+const routes: Routes = [
+  {
+    path: '',
+    component: UserComponent,
+    children: [
+      {
+        path: '',
+        redirectTo: '/user/login',
+        pathMatch: 'full'
+      },
+      {
+        path: 'login',
+        component: LoginComponent
+      },
+      {
+        path: 'register',
+        component: RegisterComponent
+      }
+    ]
+  }
+];
+
+@NgModule({
+  imports: [RouterModule.forChild(routes)],
+  exports: [RouterModule]
+})
+export class UserRoutingModule {}
diff --git a/submarine-workbench/workbench-web-ng/src/app/pages/user/user.component.html b/submarine-workbench/workbench-web-ng/src/app/pages/user/user.component.html
new file mode 100644
index 0000000..7469e9d
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/src/app/pages/user/user.component.html
@@ -0,0 +1,31 @@
+<!--
+  ~ 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-content class="content">
+  <div class="inner">
+    <div class="logo">
+      <img src="/assets/logo-128.png" alt="submarine-logo" />
+      <span class="title">Submarine</span>
+    </div>
+    <div class="desc">
+      Apache Submarine Machine Learning Workbench
+    </div>
+    <router-outlet></router-outlet>
+  </div>
+</nz-content>
diff --git a/submarine-workbench/workbench-web-ng/src/app/pages/user/user.component.scss b/submarine-workbench/workbench-web-ng/src/app/pages/user/user.component.scss
new file mode 100644
index 0000000..34990ff
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/src/app/pages/user/user.component.scss
@@ -0,0 +1,55 @@
+/*!
+ * 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.
+ */
+
+.content {
+  height: 100vh;
+  padding-top: 110px;
+  background: url("/assets/background.svg") no-repeat 50%;
+  background-size: 100%;
+}
+
+.inner {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+}
+
+.logo {
+  height: 44px;
+  display: flex;
+
+  img {
+    height: inherit;
+    margin-right: 16px;
+  }
+}
+
+.title {
+  font-size: 33px;
+  color: rgba(0,0,0,.85);
+  font-family: Avenir,Helvetica Neue,Arial,Helvetica,sans-serif;
+  font-weight: 600;
+}
+
+.desc {
+  font-size: 14px;
+  color: rgba(0,0,0,.45);
+  margin-top: 12px;
+  margin-bottom: 40px
+}
diff --git a/submarine-workbench/workbench-web-ng/src/app/pages/user/user.component.ts b/submarine-workbench/workbench-web-ng/src/app/pages/user/user.component.ts
new file mode 100644
index 0000000..b38a643
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/src/app/pages/user/user.component.ts
@@ -0,0 +1,31 @@
+/*
+ * 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';
+
+@Component({
+  selector: 'submarine-user',
+  templateUrl: './user.component.html',
+  styleUrls: ['./user.component.scss']
+})
+export class UserComponent implements OnInit {
+  constructor() {}
+
+  ngOnInit() {}
+}
diff --git a/submarine-workbench/workbench-web-ng/src/app/pages/user/user.module.ts b/submarine-workbench/workbench-web-ng/src/app/pages/user/user.module.ts
new file mode 100644
index 0000000..9d6d0ca
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/src/app/pages/user/user.module.ts
@@ -0,0 +1,33 @@
+/*
+ * 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 { CommonModule } from '@angular/common';
+import { NgModule } from '@angular/core';
+import { ReactiveFormsModule } from '@angular/forms';
+import { NgZorroAntdModule } from 'ng-zorro-antd';
+import { LoginComponent } from './login/login.component';
+import { RegisterComponent } from './register/register.component';
+import { UserRoutingModule } from './user-routing.module';
+import { UserComponent } from './user.component';
+
+@NgModule({
+  declarations: [RegisterComponent, UserComponent, LoginComponent],
+  imports: [CommonModule, UserRoutingModule, ReactiveFormsModule, NgZorroAntdModule]
+})
+export class UserModule {}
diff --git a/submarine-workbench/workbench-web-ng/src/app/services/auth.service.ts b/submarine-workbench/workbench-web-ng/src/app/services/auth.service.ts
new file mode 100644
index 0000000..1f3865a
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/src/app/services/auth.service.ts
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { HttpClient } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { Rest, User } from '@submarine/interfaces';
+import { BaseApiService } from '@submarine/services/base-api.service';
+import { LocalStorageService } from '@submarine/services/local-storage.service';
+import * as md5 from 'md5';
+import { Observable } from 'rxjs';
+import { map } from 'rxjs/operators';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class AuthService {
+  isLoggedIn = false;
+  authTokenKey = 'auth_token';
+
+  // store the URL so we can redirect after logging in
+  redirectUrl: string;
+
+  constructor(
+    private localStorageService: LocalStorageService,
+    private baseApi: BaseApiService,
+    private httpClient: HttpClient
+  ) {
+    const authToken = this.localStorageService.get<string>(this.authTokenKey);
+    this.isLoggedIn = !!authToken;
+  }
+
+  login(userForm: { userName: string; password: string }): Observable<boolean> {
+    return this.httpClient
+      .post<Rest<User>>(this.baseApi.getRestApi('/auth/login'), {
+        username: userForm.userName,
+        password: md5(userForm.password)
+      })
+      .pipe(
+        map(res => {
+          if (res.success) {
+            this.isLoggedIn = true;
+            this.localStorageService.set(this.authTokenKey, res.result.token);
+          }
+
+          return res.success;
+        })
+      );
+  }
+
+  logout(): void {
+    this.isLoggedIn = false;
+    this.localStorageService.remove(this.authTokenKey);
+  }
+}
diff --git a/submarine-workbench/workbench-web-ng/src/app/services/base-api.service.ts b/submarine-workbench/workbench-web-ng/src/app/services/base-api.service.ts
new file mode 100644
index 0000000..076ab1f
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/src/app/services/base-api.service.ts
@@ -0,0 +1,58 @@
+/*
+ * 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 { Injectable } from '@angular/core';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class BaseApiService {
+  baseApi: string;
+
+  getPort() {
+    let port = Number(location.port);
+    if (!port) {
+      port = 80;
+      if (location.protocol === 'https:') {
+        port = 443;
+      }
+    }
+    return port;
+  }
+
+  getBase() {
+    return `${location.protocol}//${location.hostname}:${this.getPort()}`;
+  }
+
+  getRestApiBase() {
+    if (!this.baseApi) {
+      this.baseApi = this.skipTrailingSlash(this.getBase()) + '/api';
+    }
+
+    return this.baseApi;
+  }
+
+  getRestApi(str: string): string {
+    return `${this.getRestApiBase()}${str}`;
+  }
+
+  private skipTrailingSlash(path) {
+    return path.replace(/\/$/, '');
+  }
+}
diff --git a/submarine-workbench/workbench-web-ng/src/app/services/index.ts b/submarine-workbench/workbench-web-ng/src/app/services/index.ts
new file mode 100644
index 0000000..630627e
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/src/app/services/index.ts
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+export * from './public-api';
diff --git a/submarine-workbench/workbench-web-ng/src/app/services/local-storage.service.ts b/submarine-workbench/workbench-web-ng/src/app/services/local-storage.service.ts
new file mode 100644
index 0000000..e9ea620
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/src/app/services/local-storage.service.ts
@@ -0,0 +1,53 @@
+/*
+ * 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 { Injectable } from '@angular/core';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class LocalStorageService {
+  prefix = 'submarine';
+
+  generateKey(key: string) {
+    return `${this.prefix}_${key.toLowerCase()}`;
+  }
+
+  get<T>(key: string): T {
+    try {
+      return JSON.parse(localStorage.getItem(this.generateKey(key)));
+    } catch (e) {
+      return null;
+    }
+  }
+
+  set(key: string, value: string | number | object | any[]) {
+    try {
+      const saveValue = JSON.stringify(value);
+
+      window.localStorage.setItem(this.generateKey(key), saveValue);
+    } catch (e) {
+      console.error(`key: ${key}, value: ${value} save error`, e);
+    }
+  }
+
+  remove(key: string) {
+    window.localStorage.removeItem(this.generateKey(key));
+  }
+}
diff --git a/submarine-workbench/workbench-web-ng/src/app/services/public-api.ts b/submarine-workbench/workbench-web-ng/src/app/services/public-api.ts
new file mode 100644
index 0000000..a853cff
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/src/app/services/public-api.ts
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+export * from './auth.service';
diff --git a/submarine-workbench/workbench-web-ng/src/assets/.gitkeep b/submarine-workbench/workbench-web-ng/src/assets/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/submarine-workbench/workbench-web-ng/src/assets/background.svg b/submarine-workbench/workbench-web-ng/src/assets/background.svg
new file mode 100644
index 0000000..ce9aa89
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/src/assets/background.svg
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="1361px" height="609px" viewBox="0 0 1361 609" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 46.2 (44496) - http://www.bohemiancoding.com/sketch -->
+    <title>Group 21</title>
+    <desc>Created with Sketch.</desc>
+    <defs></defs>
+    <g id="Ant-Design-Pro-3.0" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g transform="translate(-79.000000, -82.000000)">
+            <g id="Group-21" transform="translate(77.000000, 73.000000)">
+                <g id="Group-18" opacity="0.8" transform="translate(74.901416, 569.699158) rotate(-7.000000) translate(-74.901416, -569.699158) translate(4.901416, 525.199158)">
+                    <ellipse id="Oval-11" fill="#CFDAE6" opacity="0.25" cx="63.5748792" cy="32.468367" rx="21.7830479" ry="21.766008"></ellipse>
+                    <ellipse id="Oval-3" fill="#CFDAE6" opacity="0.599999964" cx="5.98746479" cy="13.8668601" rx="5.2173913" ry="5.21330997"></ellipse>
+                    <path d="M38.1354514,88.3520215 C43.8984227,88.3520215 48.570234,83.6838647 48.570234,77.9254015 C48.570234,72.1669383 43.8984227,67.4987816 38.1354514,67.4987816 C32.3724801,67.4987816 27.7006688,72.1669383 27.7006688,77.9254015 C27.7006688,83.6838647 32.3724801,88.3520215 38.1354514,88.3520215 Z" id="Oval-3-Copy" fill="#CFDAE6" opacity="0.45"></path>
+                    <path d="M64.2775582,33.1704963 L119.185836,16.5654915" id="Path-12" stroke="#CFDAE6" stroke-width="1.73913043" stroke-linecap="round" stroke-linejoin="round"></path>
+                    <path d="M42.1431708,26.5002681 L7.71190162,14.5640702" id="Path-16" stroke="#E0B4B7" stroke-width="0.702678964" opacity="0.7" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="1.405357899873153,2.108036953469981"></path>
+                    <path d="M63.9262187,33.521561 L43.6721326,69.3250951" id="Path-15" stroke="#BACAD9" stroke-width="0.702678964" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="1.405357899873153,2.108036953469981"></path>
+                    <g id="Group-17" transform="translate(126.850922, 13.543654) rotate(30.000000) translate(-126.850922, -13.543654) translate(117.285705, 4.381889)" fill="#CFDAE6">
+                        <ellipse id="Oval-4" opacity="0.45" cx="9.13482653" cy="9.12768076" rx="9.13482653" ry="9.12768076"></ellipse>
+                        <path d="M18.2696531,18.2553615 C18.2696531,13.2142826 14.1798519,9.12768076 9.13482653,9.12768076 C4.08980114,9.12768076 0,13.2142826 0,18.2553615 L18.2696531,18.2553615 Z" id="Oval-4" transform="translate(9.134827, 13.691521) scale(-1, -1) translate(-9.134827, -13.691521) "></path>
+                    </g>
+                </g>
+                <g id="Group-14" transform="translate(216.294700, 123.725600) rotate(-5.000000) translate(-216.294700, -123.725600) translate(106.294700, 35.225600)">
+                    <ellipse id="Oval-2" fill="#CFDAE6" opacity="0.25" cx="29.1176471" cy="29.1402439" rx="29.1176471" ry="29.1402439"></ellipse>
+                    <ellipse id="Oval-2" fill="#CFDAE6" opacity="0.3" cx="29.1176471" cy="29.1402439" rx="21.5686275" ry="21.5853659"></ellipse>
+                    <ellipse id="Oval-2-Copy" stroke="#CFDAE6" opacity="0.4" cx="179.019608" cy="138.146341" rx="23.7254902" ry="23.7439024"></ellipse>
+                    <ellipse id="Oval-2" fill="#BACAD9" opacity="0.5" cx="29.1176471" cy="29.1402439" rx="10.7843137" ry="10.7926829"></ellipse>
+                    <path d="M29.1176471,39.9329268 L29.1176471,18.347561 C23.1616351,18.347561 18.3333333,23.1796097 18.3333333,29.1402439 C18.3333333,35.1008781 23.1616351,39.9329268 29.1176471,39.9329268 Z" id="Oval-2" fill="#BACAD9"></path>
+                    <g id="Group-9" opacity="0.45" transform="translate(172.000000, 131.000000)" fill="#E6A1A6">
+                        <ellipse id="Oval-2-Copy-2" cx="7.01960784" cy="7.14634146" rx="6.47058824" ry="6.47560976"></ellipse>
+                        <path d="M0.549019608,13.6219512 C4.12262681,13.6219512 7.01960784,10.722722 7.01960784,7.14634146 C7.01960784,3.56996095 4.12262681,0.670731707 0.549019608,0.670731707 L0.549019608,13.6219512 Z" id="Oval-2-Copy-2" transform="translate(3.784314, 7.146341) scale(-1, 1) translate(-3.784314, -7.146341) "></path>
+                    </g>
+                    <ellipse id="Oval-10" fill="#CFDAE6" cx="218.382353" cy="138.685976" rx="1.61764706" ry="1.61890244"></ellipse>
+                    <ellipse id="Oval-10-Copy-2" fill="#E0B4B7" opacity="0.35" cx="179.558824" cy="175.381098" rx="1.61764706" ry="1.61890244"></ellipse>
+                    <ellipse id="Oval-10-Copy" fill="#E0B4B7" opacity="0.35" cx="180.098039" cy="102.530488" rx="2.15686275" ry="2.15853659"></ellipse>
+                    <path d="M28.9985381,29.9671598 L171.151018,132.876024" id="Path-11" stroke="#CFDAE6" opacity="0.8"></path>
+                </g>
+                <g id="Group-10" opacity="0.799999952" transform="translate(1054.100635, 36.659317) rotate(-11.000000) translate(-1054.100635, -36.659317) translate(1026.600635, 4.659317)">
+                    <ellipse id="Oval-7" stroke="#CFDAE6" stroke-width="0.941176471" cx="43.8135593" cy="32" rx="11.1864407" ry="11.2941176"></ellipse>
+                    <g id="Group-12" transform="translate(34.596774, 23.111111)" fill="#BACAD9">
+                        <ellipse id="Oval-7" opacity="0.45" cx="9.18534718" cy="8.88888889" rx="8.47457627" ry="8.55614973"></ellipse>
+                        <path d="M9.18534718,17.4450386 C13.8657264,17.4450386 17.6599235,13.6143199 17.6599235,8.88888889 C17.6599235,4.16345787 13.8657264,0.332739156 9.18534718,0.332739156 L9.18534718,17.4450386 Z" id="Oval-7"></path>
+                    </g>
+                    <path d="M34.6597385,24.809694 L5.71666084,4.76878945" id="Path-2" stroke="#CFDAE6" stroke-width="0.941176471"></path>
+                    <ellipse id="Oval" stroke="#CFDAE6" stroke-width="0.941176471" cx="3.26271186" cy="3.29411765" rx="3.26271186" ry="3.29411765"></ellipse>
+                    <ellipse id="Oval-Copy" fill="#F7E1AD" cx="2.79661017" cy="61.1764706" rx="2.79661017" ry="2.82352941"></ellipse>
+                    <path d="M34.6312443,39.2922712 L5.06366663,59.785082" id="Path-10" stroke="#CFDAE6" stroke-width="0.941176471"></path>
+                </g>
+                <g id="Group-19" opacity="0.33" transform="translate(1282.537219, 446.502867) rotate(-10.000000) translate(-1282.537219, -446.502867) translate(1142.537219, 327.502867)">
+                    <g id="Group-17" transform="translate(141.333539, 104.502742) rotate(275.000000) translate(-141.333539, -104.502742) translate(129.333539, 92.502742)" fill="#BACAD9">
+                        <circle id="Oval-4" opacity="0.45" cx="11.6666667" cy="11.6666667" r="11.6666667"></circle>
+                        <path d="M23.3333333,23.3333333 C23.3333333,16.8900113 18.1099887,11.6666667 11.6666667,11.6666667 C5.22334459,11.6666667 0,16.8900113 0,23.3333333 L23.3333333,23.3333333 Z" id="Oval-4" transform="translate(11.666667, 17.500000) scale(-1, -1) translate(-11.666667, -17.500000) "></path>
+                    </g>
+                    <circle id="Oval-5-Copy-6" fill="#CFDAE6" cx="201.833333" cy="87.5" r="5.83333333"></circle>
+                    <path d="M143.5,88.8126685 L155.070501,17.6038544" id="Path-17" stroke="#BACAD9" stroke-width="1.16666667"></path>
+                    <path d="M17.5,37.3333333 L127.466252,97.6449735" id="Path-18" stroke="#BACAD9" stroke-width="1.16666667"></path>
+                    <polyline id="Path-19" stroke="#CFDAE6" stroke-width="1.16666667" points="143.902597 120.302281 174.935455 231.571342 38.5 147.510847 126.366941 110.833333"></polyline>
+                    <path d="M159.833333,99.7453842 L195.416667,89.25" id="Path-20" stroke="#E0B4B7" stroke-width="1.16666667" opacity="0.6"></path>
+                    <path d="M205.333333,82.1372105 L238.719406,36.1666667" id="Path-24" stroke="#BACAD9" stroke-width="1.16666667"></path>
+                    <path d="M266.723424,132.231988 L207.083333,90.4166667" id="Path-25" stroke="#CFDAE6" stroke-width="1.16666667"></path>
+                    <circle id="Oval-5" fill="#C1D1E0" cx="156.916667" cy="8.75" r="8.75"></circle>
+                    <circle id="Oval-5-Copy-3" fill="#C1D1E0" cx="39.0833333" cy="148.75" r="5.25"></circle>
+                    <circle id="Oval-5-Copy-2" fill-opacity="0.6" fill="#D1DEED" cx="8.75" cy="33.25" r="8.75"></circle>
+                    <circle id="Oval-5-Copy-4" fill-opacity="0.6" fill="#D1DEED" cx="243.833333" cy="30.3333333" r="5.83333333"></circle>
+                    <circle id="Oval-5-Copy-5" fill="#E0B4B7" cx="175.583333" cy="232.75" r="5.25"></circle>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>
diff --git a/submarine-workbench/workbench-web-ng/src/assets/logo-128.png b/submarine-workbench/workbench-web-ng/src/assets/logo-128.png
new file mode 100644
index 0000000..47d1be9
Binary files /dev/null and b/submarine-workbench/workbench-web-ng/src/assets/logo-128.png differ
diff --git a/submarine-workbench/workbench-web-ng/src/assets/submarine-logo.svg b/submarine-workbench/workbench-web-ng/src/assets/submarine-logo.svg
new file mode 100644
index 0000000..8575466
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/src/assets/submarine-logo.svg
@@ -0,0 +1,133 @@
+<svg id="logo-sprint" data-name="logo-sprint" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
+     viewBox="0 0 638.26 796.53">
+  <defs>
+    <style>
+      .cls-1{fill:none;}.cls-2{fill:#2e51bf;}.cls-3{fill:#3cddc9;}.cls-4{clip-path:url(#clip-path);}.cls-5{fill:#3090d8;}.cls-6{fill:#3fbfde;}.cls-7{fill:#1d1d1d;}.cls-8{clip-path:url(#clip-path-2);}.cls-9{fill:#313131;}.cls-10{fill:#454545;}.cls-11{fill:#686868;}.cls-12{fill:#fff;}.cls-13{clip-path:url(#clip-path-3);}.cls-14{fill:#cecece;}.cls-15{fill:#bababa;}.cls-16{fill:#979797;}
+    </style>
+    <clipPath id="clip-path">
+      <path class="cls-1"
+            d="M413.18,66.76H354a121.42,121.42,0,0,0-47,9.58h0L218,115a2,2,0,0,0,0,3.53l103.26,44.79a52,52,0,0,0,18.15,3.44H366.7a20,20,0,0,0,19.67-16.44h0a31.32,31.32,0,0,1,17.58-22.91h0l5.24-2c12.41-4.58,22.37-11.82,27.22-23.25l.4-1a25.31,25.31,0,0,0-23.63-34.38Z"/>
+    </clipPath>
+    <clipPath id="clip-path-2">
+      <path class="cls-1"
+            d="M403.33,305.77H344.16a121.42,121.42,0,0,0-47,9.58h0L208.13,354a2,2,0,0,0,0,3.53l103.26,44.79a52,52,0,0,0,18.15,3.44h27.32a20,20,0,0,0,19.67-16.44h0a31.32,31.32,0,0,1,17.58-22.91h0l5.24-2c12.41-4.58,22.37-11.82,27.22-23.25l.4-1a25.31,25.31,0,0,0-23.63-34.38Z"/>
+    </clipPath>
+    <clipPath id="clip-path-3">
+      <path class="cls-1"
+            d="M403.33,583.75H344.16a121.42,121.42,0,0,0-47,9.58h0L208.13,632a2,2,0,0,0,0,3.53l103.26,44.79a52,52,0,0,0,18.15,3.44h27.32a20,20,0,0,0,19.67-16.44h0a31.32,31.32,0,0,1,17.58-22.91h0l5.24-2c12.41-4.58,22.37-11.82,27.22-23.25l.4-1a25.31,25.31,0,0,0-23.63-34.38Z"/>
+    </clipPath>
+  </defs>
+  <title>logo</title>
+  <rect x="-1.28" y="520.68" width="638.26" height="275.85"/>
+  <path class="cls-2"
+        d="M243.33,138.29l-21.09-9.17a1,1,0,0,0-1.32.52l-9.53,21.92a2,2,0,0,0,1.83,2.8h20.13a6.33,6.33,0,0,0,5.77-3.88l4.73-10.87A1,1,0,0,0,243.33,138.29Z"/>
+  <path class="cls-2"
+        d="M220.92,103.89a1,1,0,0,0,1.32.52l21.09-9.17a1,1,0,0,0,.52-1.32L239.12,83a6.33,6.33,0,0,0-5.77-3.88H213.22a2,2,0,0,0-1.83,2.8Z"/>
+  <path class="cls-3"
+        d="M235.94,196.28h4v18.29c0,4.57,2,6.78,6.11,6.78,4,0,6.94-2.31,7-6.73V196.28h4v28H254l-.77-2.93H253c-.77,1.08-3.29,3.49-8,3.49-5.76,0-8.94-3-9.1-9.4Z"/>
+  <path class="cls-3"
+        d="M265.65,185.24h4v12.95h.21a11.06,11.06,0,0,1,6.78-2.16c5.86,0,10.64,3.8,10.64,13.36,0,9.09-4.47,15.36-13.72,15.36a17.71,17.71,0,0,1-7.86-1.49ZM283.22,210c0-8.12-3.8-10.43-7.71-10.43a7.46,7.46,0,0,0-5.86,2.77v18.19a12,12,0,0,0,4.47.77C279.57,221.26,283.22,217.51,283.22,210Z"/>
+  <path class="cls-3"
+        d="M356.55,204.35V217c0,2.52.77,4.21,2.72,4.21H360v2.93a3.13,3.13,0,0,1-1.44.31,5,5,0,0,1-5.09-3.39h-.21a10.09,10.09,0,0,1-8.32,3.75c-5.19,0-8.07-3.65-8.07-7.45,0-6.17,6.06-9.2,16-9v-3c0-4.57-2.21-5.91-5.91-5.91a7,7,0,0,0-6.37,3.39l-2.93-2.31a10.91,10.91,0,0,1,9.4-4.57C353,195.87,356.55,198.49,356.55,204.35Zm-3.7,7.09c-6.73-.26-12,1.39-12,5.6,0,2.47,1.8,4.47,5,4.47a8.63,8.63,0,0,0,7-3.8Z"/>
+  <path class="cls-3" d="M388.05,185.19a2.9,2.9,0,1,1,0,5.81,2.9,2.9,0,0,1,0-5.81Zm-1.75,11.1h4v28h-4Z"/>
+  <path class="cls-3"
+        d="M448.89,222.08a14.54,14.54,0,0,1-8.79,2.52c-8,0-12.28-4.06-12.28-14.49,0-10.12,5.65-14.28,11.87-14.28,6.63,0,10.17,4.32,10.17,10.94a43.49,43.49,0,0,1-.31,4.83H431.78c.36,6.89,3.24,9.51,8.84,9.51a10.61,10.61,0,0,0,6.47-2.11Zm-17.06-13.77h14c.05-.62.05-1.23.05-1.59,0-4.78-2.05-7.45-6.58-7.45C435.69,199.26,432.19,201.58,431.83,208.31Z"/>
+  <path class="cls-3"
+        d="M321.4,195.72c-4.37,0-7.09,2.16-8.17,4.42H313c-1-2.52-3.24-4.42-7.4-4.42a8.25,8.25,0,0,0-7.25,3.55h-.1v-3h-4v28h4v-20a6.49,6.49,0,0,1,6.32-5c3.6,0,5.55,2.42,5.55,6.83v18.14h4v-20.3a6.69,6.69,0,0,1,6.42-4.68c3.44,0,5.5,2.21,5.5,6.53v18.45h4V205C330,198.85,326.95,195.72,321.4,195.72Z"/>
+  <path class="cls-3"
+        d="M411.84,195.72a10.6,10.6,0,0,0-8,3.49h-.05v-2.93h-4v28h4v-20a7.54,7.54,0,0,1,7.09-5c3.9,0,6,2.36,6,6.63v18.34h4V205C420.78,198.75,417.44,195.72,411.84,195.72Z"/>
+  <path class="cls-3"
+        d="M366.4,196.63h3.88l.4,5h.17c1.88-3.48,4.74-5.71,7.88-5.71a6.88,6.88,0,0,1,3,.57l-.91,4.11a7.76,7.76,0,0,0-2.68-.46c-2.34,0-5.14,1.71-7,6.39v17.81H366.4Z"/>
+  <path class="cls-3"
+        d="M212.15,197.11c0-3.29,2.52-5.65,6.73-5.65a10.79,10.79,0,0,1,7,2.47l2.52-3.19a14.55,14.55,0,0,0-9.3-3c-6.47,0-11.15,4.21-11.15,9.66,0,11.36,17.16,9.51,17.16,17.68,0,3.33-2.61,6-7.48,6h-21.2a20,20,0,0,0-2.34,3.69h23.16c7.71,0,12-4.52,12-10C229.31,203.68,212.15,205.43,212.15,197.11Z"/>
+  <path class="cls-2"
+        d="M413.18,66.76H354a121.42,121.42,0,0,0-47,9.58h0L218,115a2,2,0,0,0,0,3.53l103.26,44.79a52,52,0,0,0,18.15,3.44H366.7a20,20,0,0,0,19.67-16.44h0a31.32,31.32,0,0,1,17.58-22.91h0l5.24-2c12.41-4.58,22.37-11.82,27.22-23.25l.4-1a25.31,25.31,0,0,0-23.63-34.38Z"/>
+  <path class="cls-1"
+        d="M413.18,66.76H354a121.42,121.42,0,0,0-47,9.58h0L218,115a2,2,0,0,0,0,3.53l100.59,43.65,2.67,1.14a52,52,0,0,0,18.15,3.44H366.7a20,20,0,0,0,19.67-16.44h0a31.32,31.32,0,0,1,17.58-22.91h0l5.24-2c12.41-4.58,22.37-11.82,27.22-23.25l.4-1a25.31,25.31,0,0,0-23.63-34.38Z"/>
+  <path class="cls-1"
+        d="M413.18,66.76H354a121.42,121.42,0,0,0-47,9.58h0L218,115a2,2,0,0,0,0,3.53l100.59,43.65,2.67,1.14a52,52,0,0,0,18.15,3.44H366.7a20,20,0,0,0,19.67-16.44h0a31.32,31.32,0,0,1,17.58-22.91h0l5.24-2c12.41-4.58,22.37-11.82,27.22-23.25l.4-1a25.31,25.31,0,0,0-23.63-34.38Z"/>
+  <g class="cls-4">
+    <path class="cls-5" d="M474.56,65.74H352.22v0a119.86,119.86,0,1,0,118,88.08h4.28Z"/>
+    <path class="cls-6" d="M454.52,86.09V66.76H400.07a86.42,86.42,0,1,0,54.45,19.33Z"/>
+    <path class="cls-1"
+          d="M413.18,66.76H354a121.42,121.42,0,0,0-47,9.58h0L218,115a2,2,0,0,0,0,3.53l100.59,43.65,2.67,1.14a52,52,0,0,0,18.15,3.44H366.7a20,20,0,0,0,19.67-16.44h0a31.32,31.32,0,0,1,17.58-22.91h0l5.24-2c12.41-4.58,22.37-11.82,27.22-23.25l.4-1a25.31,25.31,0,0,0-23.63-34.38Z"/>
+    <path class="cls-3"
+          d="M419,120.85a28.73,28.73,0,0,1-12.9,3.61h0a62.88,62.88,0,0,0-55.84,42.3H352A63.3,63.3,0,1,0,475,187.64C475,152.68,416.64,122.3,419,120.85Z"/>
+    <path class="cls-1"
+          d="M413.18,66.76H354a121.42,121.42,0,0,0-47,9.58h0L218,115a2,2,0,0,0,0,3.53l100.59,43.65,2.67,1.14a52,52,0,0,0,18.15,3.44H366.7a20,20,0,0,0,19.67-16.44h0a31.32,31.32,0,0,1,17.58-22.91h0l5.24-2c12.41-4.58,22.37-11.82,27.22-23.25l.4-1a25.31,25.31,0,0,0-23.63-34.38Z"/>
+  </g>
+  <path class="cls-7"
+        d="M233.48,377.29l-21.09-9.17a1,1,0,0,0-1.32.52l-9.53,21.92a2,2,0,0,0,1.83,2.8H223.5a6.33,6.33,0,0,0,5.77-3.88L234,378.61A1,1,0,0,0,233.48,377.29Z"/>
+  <path class="cls-7"
+        d="M211.07,342.89a1,1,0,0,0,1.32.52l21.09-9.17a1,1,0,0,0,.52-1.32l-4.73-10.87a6.33,6.33,0,0,0-5.77-3.88H203.37a2,2,0,0,0-1.83,2.8Z"/>
+  <path class="cls-7"
+        d="M226.09,435.29h4v18.29c0,4.57,2,6.78,6.11,6.78,4,0,6.94-2.31,7-6.73V435.29h4v28h-3.13l-.77-2.93h-.21c-.77,1.08-3.29,3.49-8,3.49-5.76,0-8.94-3-9.1-9.4Z"/>
+  <path class="cls-7"
+        d="M255.8,424.24h4v12.95H260a11.06,11.06,0,0,1,6.78-2.16c5.86,0,10.64,3.8,10.64,13.36,0,9.09-4.47,15.36-13.72,15.36a17.71,17.71,0,0,1-7.86-1.49ZM273.37,449c0-8.12-3.8-10.43-7.71-10.43a7.46,7.46,0,0,0-5.86,2.77v18.19a12,12,0,0,0,4.47.77C269.72,460.26,273.37,456.51,273.37,449Z"/>
+  <path class="cls-7"
+        d="M346.7,443.36V456c0,2.52.77,4.21,2.72,4.21h.72v2.93a3.13,3.13,0,0,1-1.44.31,5,5,0,0,1-5.09-3.39h-.21a10.09,10.09,0,0,1-8.32,3.75c-5.19,0-8.07-3.65-8.07-7.45,0-6.17,6.06-9.2,16-9v-3c0-4.57-2.21-5.91-5.91-5.91a7,7,0,0,0-6.37,3.39l-2.93-2.31a10.91,10.91,0,0,1,9.4-4.57C343.15,434.88,346.7,437.5,346.7,443.36Zm-3.7,7.09c-6.73-.26-12,1.39-12,5.6,0,2.47,1.8,4.47,5,4.47a8.63,8.63,0,0,0,7-3.8Z"/>
+  <path class="cls-7" d="M378.2,424.19a2.9,2.9,0,1,1,0,5.81,2.9,2.9,0,0,1,0-5.81Zm-1.75,11.1h4v28h-4Z"/>
+  <path class="cls-7"
+        d="M439,461.08a14.54,14.54,0,0,1-8.79,2.52c-8,0-12.28-4.06-12.28-14.49,0-10.12,5.65-14.28,11.87-14.28,6.63,0,10.17,4.32,10.17,10.94a43.49,43.49,0,0,1-.31,4.83H421.93c.36,6.89,3.24,9.51,8.84,9.51a10.61,10.61,0,0,0,6.47-2.11ZM422,447.31h14c.05-.62.05-1.23.05-1.59,0-4.78-2.05-7.45-6.58-7.45C425.84,438.27,422.34,440.58,422,447.31Z"/>
+  <path class="cls-7"
+        d="M311.55,434.73c-4.37,0-7.09,2.16-8.17,4.42h-.21c-1-2.52-3.24-4.42-7.4-4.42a8.25,8.25,0,0,0-7.25,3.55h-.1v-3h-4v28h4v-20a6.49,6.49,0,0,1,6.32-5c3.6,0,5.55,2.42,5.55,6.83v18.14h4v-20.3a6.69,6.69,0,0,1,6.42-4.68c3.44,0,5.5,2.21,5.5,6.53v18.45h4V444C320.18,437.86,317.1,434.73,311.55,434.73Z"/>
+  <path class="cls-7"
+        d="M402,434.73a10.6,10.6,0,0,0-8,3.49H394v-2.93h-4v28h4v-20a7.54,7.54,0,0,1,7.09-5c3.9,0,6,2.36,6,6.63v18.34h4V444C410.93,437.76,407.59,434.73,402,434.73Z"/>
+  <path class="cls-7"
+        d="M356.55,435.63h3.88l.4,5H361c1.88-3.48,4.74-5.71,7.88-5.71a6.88,6.88,0,0,1,3,.57l-.91,4.11a7.76,7.76,0,0,0-2.68-.46c-2.34,0-5.14,1.71-7,6.39v17.81h-4.68Z"/>
+  <path class="cls-7"
+        d="M202.3,436.11c0-3.29,2.52-5.65,6.73-5.65a10.79,10.79,0,0,1,7,2.47l2.52-3.19a14.55,14.55,0,0,0-9.3-3c-6.47,0-11.15,4.21-11.15,9.66,0,11.36,17.16,9.51,17.16,17.68,0,3.33-2.61,6-7.48,6h-21.2a20,20,0,0,0-2.34,3.69h23.16c7.71,0,12-4.52,12-10C219.46,442.69,202.3,444.44,202.3,436.11Z"/>
+  <path class="cls-7"
+        d="M403.33,305.77H344.16a121.42,121.42,0,0,0-47,9.58h0L208.13,354a2,2,0,0,0,0,3.53l103.26,44.79a52,52,0,0,0,18.15,3.44h27.32a20,20,0,0,0,19.67-16.44h0a31.32,31.32,0,0,1,17.58-22.91h0l5.24-2c12.41-4.58,22.37-11.82,27.22-23.25l.4-1a25.31,25.31,0,0,0-23.63-34.38Z"/>
+  <path class="cls-1"
+        d="M403.33,305.77H344.16a121.42,121.42,0,0,0-47,9.58h0L208.13,354a2,2,0,0,0,0,3.53l100.59,43.65,2.67,1.14a52,52,0,0,0,18.15,3.44h27.32a20,20,0,0,0,19.67-16.44h0a31.32,31.32,0,0,1,17.58-22.91h0l5.24-2c12.41-4.58,22.37-11.82,27.22-23.25l.4-1a25.31,25.31,0,0,0-23.63-34.38Z"/>
+  <path class="cls-1"
+        d="M403.33,305.77H344.16a121.42,121.42,0,0,0-47,9.58h0L208.13,354a2,2,0,0,0,0,3.53l100.59,43.65,2.67,1.14a52,52,0,0,0,18.15,3.44h27.32a20,20,0,0,0,19.67-16.44h0a31.32,31.32,0,0,1,17.58-22.91h0l5.24-2c12.41-4.58,22.37-11.82,27.22-23.25l.4-1a25.31,25.31,0,0,0-23.63-34.38Z"/>
+  <g class="cls-8">
+    <path class="cls-9" d="M464.7,304.74H342.37v0a119.86,119.86,0,1,0,118,88.08h4.28Z"/>
+    <path class="cls-10" d="M444.67,325.1V305.77H390.22a86.42,86.42,0,1,0,54.45,19.33Z"/>
+    <path class="cls-1"
+          d="M403.33,305.77H344.16a121.42,121.42,0,0,0-47,9.58h0L208.13,354a2,2,0,0,0,0,3.53l100.59,43.65,2.67,1.14a52,52,0,0,0,18.15,3.44h27.32a20,20,0,0,0,19.67-16.44h0a31.32,31.32,0,0,1,17.58-22.91h0l5.24-2c12.41-4.58,22.37-11.82,27.22-23.25l.4-1a25.31,25.31,0,0,0-23.63-34.38Z"/>
+    <path class="cls-11"
+          d="M409.18,359.86a28.73,28.73,0,0,1-12.9,3.61h0a62.88,62.88,0,0,0-55.84,42.3h1.69a63.3,63.3,0,1,0,123.06,20.88C465.18,391.69,406.79,361.3,409.18,359.86Z"/>
+    <path class="cls-1"
+          d="M403.33,305.77H344.16a121.42,121.42,0,0,0-47,9.58h0L208.13,354a2,2,0,0,0,0,3.53l100.59,43.65,2.67,1.14a52,52,0,0,0,18.15,3.44h27.32a20,20,0,0,0,19.67-16.44h0a31.32,31.32,0,0,1,17.58-22.91h0l5.24-2c12.41-4.58,22.37-11.82,27.22-23.25l.4-1a25.31,25.31,0,0,0-23.63-34.38Z"/>
+  </g>
+  <path class="cls-12"
+        d="M233.48,655.28l-21.09-9.17a1,1,0,0,0-1.32.52l-9.53,21.92a2,2,0,0,0,1.83,2.8H223.5a6.33,6.33,0,0,0,5.77-3.88L234,656.59A1,1,0,0,0,233.48,655.28Z"/>
+  <path class="cls-12"
+        d="M211.07,620.88a1,1,0,0,0,1.32.52l21.09-9.17a1,1,0,0,0,.52-1.32L229.27,600a6.33,6.33,0,0,0-5.77-3.88H203.37a2,2,0,0,0-1.83,2.8Z"/>
+  <path class="cls-12"
+        d="M226.09,713.28h4v18.29c0,4.57,2,6.78,6.11,6.78,4,0,6.94-2.31,7-6.73V713.28h4v28h-3.13l-.77-2.93h-.21c-.77,1.08-3.29,3.49-8,3.49-5.76,0-8.94-3-9.1-9.4Z"/>
+  <path class="cls-12"
+        d="M255.8,702.23h4v12.95H260a11.06,11.06,0,0,1,6.78-2.16c5.86,0,10.64,3.8,10.64,13.36,0,9.09-4.47,15.36-13.72,15.36a17.71,17.71,0,0,1-7.86-1.49Zm17.57,24.72c0-8.12-3.8-10.43-7.71-10.43a7.46,7.46,0,0,0-5.86,2.77v18.19a12,12,0,0,0,4.47.77C269.72,738.25,273.37,734.5,273.37,726.94Z"/>
+  <path class="cls-12"
+        d="M346.7,721.34V734c0,2.52.77,4.21,2.72,4.21h.72v2.93a3.13,3.13,0,0,1-1.44.31,5,5,0,0,1-5.09-3.39h-.21a10.09,10.09,0,0,1-8.32,3.75c-5.19,0-8.07-3.65-8.07-7.45,0-6.17,6.06-9.2,16-9v-3c0-4.57-2.21-5.91-5.91-5.91a7,7,0,0,0-6.37,3.39l-2.93-2.31a10.91,10.91,0,0,1,9.4-4.57C343.15,712.86,346.7,715.49,346.7,721.34Zm-3.7,7.09c-6.73-.26-12,1.39-12,5.6,0,2.47,1.8,4.47,5,4.47a8.63,8.63,0,0,0,7-3.8Z"/>
+  <path class="cls-12" d="M378.2,702.18a2.9,2.9,0,1,1,0,5.81,2.9,2.9,0,0,1,0-5.81Zm-1.75,11.1h4v28h-4Z"/>
+  <path class="cls-12"
+        d="M439,739.07a14.54,14.54,0,0,1-8.79,2.52c-8,0-12.28-4.06-12.28-14.49,0-10.12,5.65-14.28,11.87-14.28,6.63,0,10.17,4.32,10.17,10.94a43.49,43.49,0,0,1-.31,4.83H421.93c.36,6.89,3.24,9.51,8.84,9.51a10.61,10.61,0,0,0,6.47-2.11ZM422,725.3h14c.05-.62.05-1.23.05-1.59,0-4.78-2.05-7.45-6.58-7.45C425.84,716.26,422.34,718.57,422,725.3Z"/>
+  <path class="cls-12"
+        d="M311.55,712.71c-4.37,0-7.09,2.16-8.17,4.42h-.21c-1-2.52-3.24-4.42-7.4-4.42a8.25,8.25,0,0,0-7.25,3.55h-.1v-3h-4v28h4v-20a6.49,6.49,0,0,1,6.32-5c3.6,0,5.55,2.42,5.55,6.83v18.14h4v-20.3a6.69,6.69,0,0,1,6.42-4.68c3.44,0,5.5,2.21,5.5,6.53v18.45h4V722C320.18,715.84,317.1,712.71,311.55,712.71Z"/>
+  <path class="cls-12"
+        d="M402,712.71a10.6,10.6,0,0,0-8,3.49H394v-2.93h-4v28h4v-20a7.54,7.54,0,0,1,7.09-5c3.9,0,6,2.36,6,6.63v18.34h4V722C410.93,715.74,407.59,712.71,402,712.71Z"/>
+  <path class="cls-12"
+        d="M356.55,713.62h3.88l.4,5H361c1.88-3.48,4.74-5.71,7.88-5.71a6.88,6.88,0,0,1,3,.57l-.91,4.11a7.76,7.76,0,0,0-2.68-.46c-2.34,0-5.14,1.71-7,6.39v17.81h-4.68Z"/>
+  <path class="cls-12"
+        d="M202.3,714.1c0-3.29,2.52-5.65,6.73-5.65a10.79,10.79,0,0,1,7,2.47l2.52-3.19a14.55,14.55,0,0,0-9.3-3c-6.47,0-11.15,4.21-11.15,9.66,0,11.36,17.16,9.51,17.16,17.68,0,3.33-2.61,6-7.48,6h-21.2a20,20,0,0,0-2.34,3.69h23.16c7.71,0,12-4.52,12-10C219.46,720.68,202.3,722.42,202.3,714.1Z"/>
+  <path class="cls-12"
+        d="M403.33,583.75H344.16a121.42,121.42,0,0,0-47,9.58h0L208.13,632a2,2,0,0,0,0,3.53l103.26,44.79a52,52,0,0,0,18.15,3.44h27.32a20,20,0,0,0,19.67-16.44h0a31.32,31.32,0,0,1,17.58-22.91h0l5.24-2c12.41-4.58,22.37-11.82,27.22-23.25l.4-1a25.31,25.31,0,0,0-23.63-34.38Z"/>
+  <path class="cls-1"
+        d="M403.33,583.75H344.16a121.42,121.42,0,0,0-47,9.58h0L208.13,632a2,2,0,0,0,0,3.53l100.59,43.65,2.67,1.14a52,52,0,0,0,18.15,3.44h27.32a20,20,0,0,0,19.67-16.44h0a31.32,31.32,0,0,1,17.58-22.91h0l5.24-2c12.41-4.58,22.37-11.82,27.22-23.25l.4-1a25.31,25.31,0,0,0-23.63-34.38Z"/>
+  <path class="cls-1"
+        d="M403.33,583.75H344.16a121.42,121.42,0,0,0-47,9.58h0L208.13,632a2,2,0,0,0,0,3.53l100.59,43.65,2.67,1.14a52,52,0,0,0,18.15,3.44h27.32a20,20,0,0,0,19.67-16.44h0a31.32,31.32,0,0,1,17.58-22.91h0l5.24-2c12.41-4.58,22.37-11.82,27.22-23.25l.4-1a25.31,25.31,0,0,0-23.63-34.38Z"/>
+  <g class="cls-13">
+    <path class="cls-14" d="M464.7,582.73H342.37v0a119.86,119.86,0,1,0,118,88.08h4.28Z"/>
+    <path class="cls-15" d="M444.67,603.08V583.75H390.22a86.42,86.42,0,1,0,54.45,19.33Z"/>
+    <path class="cls-1"
+          d="M403.33,583.75H344.16a121.42,121.42,0,0,0-47,9.58h0L208.13,632a2,2,0,0,0,0,3.53l100.59,43.65,2.67,1.14a52,52,0,0,0,18.15,3.44h27.32a20,20,0,0,0,19.67-16.44h0a31.32,31.32,0,0,1,17.58-22.91h0l5.24-2c12.41-4.58,22.37-11.82,27.22-23.25l.4-1a25.31,25.31,0,0,0-23.63-34.38Z"/>
+    <path class="cls-16"
+          d="M409.18,637.84a28.73,28.73,0,0,1-12.9,3.61h0a62.88,62.88,0,0,0-55.84,42.3h1.69a63.3,63.3,0,1,0,123.06,20.88C465.18,669.68,406.79,639.29,409.18,637.84Z"/>
+    <path class="cls-1"
+          d="M403.33,583.75H344.16a121.42,121.42,0,0,0-47,9.58h0L208.13,632a2,2,0,0,0,0,3.53l100.59,43.65,2.67,1.14a52,52,0,0,0,18.15,3.44h27.32a20,20,0,0,0,19.67-16.44h0a31.32,31.32,0,0,1,17.58-22.91h0l5.24-2c12.41-4.58,22.37-11.82,27.22-23.25l.4-1a25.31,25.31,0,0,0-23.63-34.38Z"/>
+  </g>
+</svg>
diff --git a/submarine-workbench/workbench-web-ng/src/environments/environment.prod.ts b/submarine-workbench/workbench-web-ng/src/environments/environment.prod.ts
new file mode 100644
index 0000000..4982e3c
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/src/environments/environment.prod.ts
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+
+export const environment = {
+  production: true
+};
diff --git a/submarine-workbench/workbench-web-ng/src/environments/environment.ts b/submarine-workbench/workbench-web-ng/src/environments/environment.ts
new file mode 100644
index 0000000..1241921
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/src/environments/environment.ts
@@ -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.
+ */
+
+// This file can be replaced during build by using the `fileReplacements` array.
+// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
+// The list of file replacements can be found in `angular.json`.
+
+export const environment = {
+  production: false
+};
+
+/*
+ * For easier debugging in development mode, you can import the following file
+ * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
+ *
+ * This import should be commented out in production mode because it will have a negative impact
+ * on performance if an error is thrown.
+ */
+// import 'zone.js/dist/zone-error';  // Included with Angular CLI.
diff --git a/submarine-workbench/workbench-web-ng/src/favicon.ico b/submarine-workbench/workbench-web-ng/src/favicon.ico
new file mode 100644
index 0000000..4465ca9
Binary files /dev/null and b/submarine-workbench/workbench-web-ng/src/favicon.ico differ
diff --git a/submarine-workbench/workbench-web-ng/src/index.html b/submarine-workbench/workbench-web-ng/src/index.html
new file mode 100644
index 0000000..2134e57
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/src/index.html
@@ -0,0 +1,32 @@
+<!--
+  ~ 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.
+  -->
+
+<!doctype html>
+<html lang="en">
+<head>
+  <meta charset="utf-8">
+  <title>Submarine</title>
+  <base href="/">
+  <meta name="viewport" content="width=device-width, initial-scale=1">
+  <link rel="icon" type="image/x-icon" href="favicon.ico">
+</head>
+<body>
+  <submarine-root></submarine-root>
+</body>
+</html>
diff --git a/submarine-workbench/workbench-web-ng/src/main.ts b/submarine-workbench/workbench-web-ng/src/main.ts
new file mode 100644
index 0000000..6274974
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/src/main.ts
@@ -0,0 +1,31 @@
+/*
+ * 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 { enableProdMode } from '@angular/core';
+import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
+
+import { AppModule } from './app/app.module';
+import { environment } from '@submarine/environment';
+
+if (environment.production) {
+  enableProdMode();
+}
+
+platformBrowserDynamic().bootstrapModule(AppModule)
+  .catch(err => console.error(err));
diff --git a/submarine-workbench/workbench-web-ng/src/polyfills.ts b/submarine-workbench/workbench-web-ng/src/polyfills.ts
new file mode 100644
index 0000000..15f6f9c
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/src/polyfills.ts
@@ -0,0 +1,82 @@
+/*
+ * 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.
+ */
+
+/**
+ * This file includes polyfills needed by Angular and is loaded before the app.
+ * You can add your own extra polyfills to this file.
+ *
+ * This file is divided into 2 sections:
+ *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
+ *   2. Application imports. Files imported after ZoneJS that should be loaded before your main
+ *      file.
+ *
+ * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
+ * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
+ * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
+ *
+ * Learn more in https://angular.io/guide/browser-support
+ */
+
+/***************************************************************************************************
+ * BROWSER POLYFILLS
+ */
+
+/** IE10 and IE11 requires the following for NgClass support on SVG elements */
+// import 'classlist.js';  // Run `npm install --save classlist.js`.
+
+/**
+ * Web Animations `@angular/platform-browser/animations`
+ * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
+ * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
+ */
+// import 'web-animations-js';  // Run `npm install --save web-animations-js`.
+
+/**
+ * By default, zone.js will patch all possible macroTask and DomEvents
+ * user can disable parts of macroTask/DomEvents patch by setting following flags
+ * because those flags need to be set before `zone.js` being loaded, and webpack
+ * will put import in the top of bundle, so user need to create a separate file
+ * in this directory (for example: zone-flags.ts), and put the following flags
+ * into that file, and then add the following code before importing zone.js.
+ * import './zone-flags.ts';
+ *
+ * The flags allowed in zone-flags.ts are listed here.
+ *
+ * The following flags will work for all browsers.
+ *
+ * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
+ * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
+ * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
+ *
+ *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
+ *  with the following flag, it will bypass `zone.js` patch for IE/Edge
+ *
+ *  (window as any).__Zone_enable_cross_context_check = true;
+ *
+ */
+
+/***************************************************************************************************
+ * Zone JS is required by default for Angular itself.
+ */
+import 'zone.js/dist/zone';  // Included with Angular CLI.
+
+
+/***************************************************************************************************
+ * APPLICATION IMPORTS
+ */
diff --git a/submarine-workbench/workbench-web-ng/src/styles.scss b/submarine-workbench/workbench-web-ng/src/styles.scss
new file mode 100644
index 0000000..6d93bff
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/src/styles.scss
@@ -0,0 +1,20 @@
+/*!
+ * 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.
+ */
+
+/* You can add global styles to this file, and also import other style files */
diff --git a/submarine-workbench/workbench-web-ng/src/test.ts b/submarine-workbench/workbench-web-ng/src/test.ts
new file mode 100644
index 0000000..b90b1d8
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/src/test.ts
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+// This file is required by karma.conf.js and loads recursively all the .spec and framework files
+
+import 'zone.js/dist/zone-testing';
+import { getTestBed } from '@angular/core/testing';
+import {
+  BrowserDynamicTestingModule,
+  platformBrowserDynamicTesting
+} from '@angular/platform-browser-dynamic/testing';
+
+declare const require: any;
+
+// First, initialize the Angular testing environment.
+getTestBed().initTestEnvironment(
+  BrowserDynamicTestingModule,
+  platformBrowserDynamicTesting()
+);
+// Then we find all the tests.
+const context = require.context('./', true, /\.spec\.ts$/);
+// And load the modules.
+context.keys().map(context);
diff --git a/submarine-workbench/workbench-web-ng/src/theme.less b/submarine-workbench/workbench-web-ng/src/theme.less
new file mode 100644
index 0000000..ab88f5d
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/src/theme.less
@@ -0,0 +1,561 @@
+/*
+ * 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 "../node_modules/ng-zorro-antd/ng-zorro-antd.less";
+// The prefix to use on all css classes from ant.
+@ant-prefix : ant;
+
+// -------- Colors -----------
+@primary-color : @blue-6;
+@info-color : @blue-6;
+@success-color : @green-6;
+@processing-color : @blue-6;
+@error-color : @red-6;
+@highlight-color : @red-6;
+@warning-color : @gold-6;
+@normal-color : #d9d9d9;
+
+// Color used by default to control hover and active backgrounds and for
+// alert info backgrounds.
+@primary-1: color(~`colorPalette('@{primary-color}', 1) `); // replace tint(@primary-color, 90%)
+@primary-2: color(~`colorPalette('@{primary-color}', 2) `); // replace tint(@primary-color, 80%)
+@primary-3: color(~`colorPalette('@{primary-color}', 3) `); // unused
+@primary-4: color(~`colorPalette('@{primary-color}', 4) `); // unused
+@primary-5: color(
+  ~`colorPalette('@{primary-color}', 5) `
+); // color used to control the text color in many active and hover states, replace tint(@primary-color, 20%)
+@primary-6: @primary-color; // color used to control the text color of active buttons, don't use, use @primary-color
+@primary-7: color(~`colorPalette('@{primary-color}', 7) `); // replace shade(@primary-color, 5%)
+@primary-8: color(~`colorPalette('@{primary-color}', 8) `); // unused
+@primary-9: color(~`colorPalette('@{primary-color}', 9) `); // unused
+@primary-10: color(~`colorPalette('@{primary-color}', 10) `); // unused
+
+// Base Scaffolding Variables
+// ---
+
+// Background color for `<body>`
+@body-background : #fff;
+// Base background color for most components
+@component-background : #fff;
+@font-family : "Chinese Quote", -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif,
+"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
+@code-family : "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
+@heading-color : fade(#000, 85%);
+@text-color : fade(#000, 65%);
+@text-color-secondary : fade(#000, 45%);
+@heading-color-dark : fade(#fff, 100%);
+@text-color-dark : fade(#fff, 85%);
+@text-color-secondary-dark: fade(#fff, 65%);
+@font-variant-base : tabular-nums;
+@font-size-base : 14px;
+@font-size-lg : @font-size-base + 2px;
+@font-size-sm : 12px;
+@line-height-base : 1.5;
+@border-radius-base : 4px;
+@border-radius-sm : 2px;
+
+// vertical paddings
+@padding-lg : 24px; // containers
+@padding-md : 16px; // small containers and buttons
+@padding-sm : 12px; // Form controls and items
+@padding-xs : 8px; // small items
+
+// vertical padding for all form controls
+@control-padding-horizontal: @padding-sm;
+@control-padding-horizontal-sm: @padding-xs;
+
+// The background colors for active and hover states for things like
+// list items or table cells.
+@item-active-bg : @primary-1;
+@item-hover-bg : @primary-1;
+
+// ICONFONT
+@iconfont-css-prefix : anticon;
+
+// LINK
+@link-color : @primary-color;
+@link-hover-color : color(~`colorPalette("@{link-color}", 5)`);
+@link-active-color : color(~`colorPalette("@{link-color}", 7)`);
+@link-decoration : none;
+@link-hover-decoration : none;
+
+// Animation
+@ease-base-out : cubic-bezier(0.7, 0.3, 0.1, 1);
+@ease-base-in : cubic-bezier(0.9, 0, 0.3, 0.7);
+@ease-out : cubic-bezier(0.215, 0.61, 0.355, 1);
+@ease-in : cubic-bezier(0.55, 0.055, 0.675, 0.19);
+@ease-in-out : cubic-bezier(0.645, 0.045, 0.355, 1);
+@ease-out-back : cubic-bezier(0.12, 0.4, 0.29, 1.46);
+@ease-in-back : cubic-bezier(0.71, -0.46, 0.88, 0.6);
+@ease-in-out-back : cubic-bezier(0.71, -0.46, 0.29, 1.46);
+@ease-out-circ : cubic-bezier(0.08, 0.82, 0.17, 1);
+@ease-in-circ : cubic-bezier(0.6, 0.04, 0.98, 0.34);
+@ease-in-out-circ : cubic-bezier(0.78, 0.14, 0.15, 0.86);
+@ease-out-quint : cubic-bezier(0.23, 1, 0.32, 1);
+@ease-in-quint : cubic-bezier(0.755, 0.05, 0.855, 0.06);
+@ease-in-out-quint : cubic-bezier(0.86, 0, 0.07, 1);
+
+// Border color
+@border-color-base : hsv(0, 0, 85%); // base border outline a component
+@border-color-split : hsv(0, 0, 91%); // split border inside a component
+@border-width-base : 1px; // width of the border for a component
+@border-style-base : solid; // style of a components border
+
+// Outline
+@outline-blur-size : 0;
+@outline-width : 2px;
+@outline-color : @primary-color;
+
+@background-color-light : hsv(0, 0, 98%); // background of header and selected item
+@background-color-base : hsv(0, 0, 96%); // Default grey background color
+
+// Disabled states
+@disabled-color : fade(#000, 25%);
+@disabled-bg : @background-color-base;
+@disabled-color-dark : fade(#fff, 35%);
+
+// Shadow
+@shadow-color : rgba(0, 0, 0, .15);
+@box-shadow-base : @shadow-1-down;
+@shadow-1-up : 0 -2px 8px @shadow-color;
+@shadow-1-down : 0 2px 8px @shadow-color;
+@shadow-1-left : -2px 0 8px @shadow-color;
+@shadow-1-right : 2px 0 8px @shadow-color;
+@shadow-2 : 0 4px 12px @shadow-color;
+
+// Buttons
+@btn-font-weight : 400;
+@btn-border-radius-base : @border-radius-base;
+@btn-border-radius-sm : @border-radius-base;
+
+@btn-primary-color : #fff;
+@btn-primary-bg : @primary-color;
+
+@btn-default-color : @text-color;
+@btn-default-bg : #fff;
+@btn-default-border : @border-color-base;
+
+@btn-danger-color : @error-color;
+@btn-danger-bg : @background-color-base;
+@btn-danger-border : @border-color-base;
+
+@btn-disable-color : @disabled-color;
+@btn-disable-bg : @disabled-bg;
+@btn-disable-border : @border-color-base;
+
+@btn-padding-base : 0 @padding-md - 1px;
+@btn-font-size-lg : @font-size-lg;
+@btn-font-size-sm : @font-size-base;
+@btn-padding-lg : @btn-padding-base;
+@btn-padding-sm : 0 @padding-xs - 1px;
+
+@btn-height-base : 32px;
+@btn-height-lg : 40px;
+@btn-height-sm : 24px;
+
+@btn-circle-size : @btn-height-base;
+@btn-circle-size-lg : @btn-height-lg;
+@btn-circle-size-sm : @btn-height-sm;
+
+@btn-group-border : @primary-5;
+
+// Checkbox
+@checkbox-size : 16px;
+@checkbox-color : @primary-color;
+@checkbox-check-color : #fff;
+@checkbox-border-width : @border-width-base;
+
+// Radio
+@radio-size : 16px;
+@radio-dot-color : @primary-color;
+
+// Radio buttons
+@radio-button-bg : @btn-default-bg;
+@radio-button-color : @btn-default-color;
+@radio-button-hover-color : @primary-5;
+@radio-button-active-color : @primary-7;
+
+// Media queries breakpoints
+// Extra small screen / phone
+@screen-xs : 480px;
+@screen-xs-min : @screen-xs;
+
+// Small screen / tablet
+@screen-sm : 576px;
+@screen-sm-min : @screen-sm;
+
+// Medium screen / desktop
+@screen-md : 768px;
+@screen-md-min : @screen-md;
+
+// Large screen / wide desktop
+@screen-lg : 992px;
+@screen-lg-min : @screen-lg;
+
+// Extra large screen / full hd
+@screen-xl : 1200px;
+@screen-xl-min : @screen-xl;
+
+// Extra extra large screen / large descktop
+@screen-xxl : 1600px;
+@screen-xxl-min : @screen-xxl;
+
+// provide a maximum
+@screen-xs-max : (@screen-sm-min - 1px);
+@screen-sm-max : (@screen-md-min - 1px);
+@screen-md-max : (@screen-lg-min - 1px);
+@screen-lg-max : (@screen-xl-min - 1px);
+@screen-xl-max : (@screen-xxl-min - 1px);
+
+// Grid system
+@grid-columns : 24;
+@grid-gutter-width : 0;
+
+// Layout
+@layout-body-background : #f0f2f5;
+@layout-header-background : #001529;
+@layout-footer-background : @layout-body-background;
+@layout-header-height : 64px;
+@layout-header-padding : 0 50px;
+@layout-footer-padding : 24px 50px;
+@layout-sider-background : @layout-header-background;
+@layout-trigger-height : 48px;
+@layout-trigger-background : #002140;
+@layout-trigger-color : #fff;
+@layout-zero-trigger-width : 36px;
+@layout-zero-trigger-height : 42px;
+// Layout light theme
+@layout-sider-background-light : #fff;
+@layout-trigger-background-light: #fff;
+@layout-trigger-color-light : @text-color;
+
+// z-index list
+@zindex-affix : 10;
+@zindex-back-top : 10;
+@zindex-modal-mask : 1000;
+@zindex-modal : 1000;
+@zindex-notification : 1010;
+@zindex-message : 1010;
+@zindex-popover : 1030;
+@zindex-picker : 1050;
+@zindex-dropdown : 1050;
+@zindex-tooltip : 1060;
+
+// Animation
+@animation-duration-slow: 0.3s; // Modal
+@animation-duration-base: 0.2s;
+@animation-duration-fast: 0.1s; // Tooltip
+
+// Form
+// ---
+@label-required-color : @highlight-color;
+@label-color : @heading-color;
+@form-item-margin-bottom : 24px;
+@form-item-trailing-colon : true;
+@form-vertical-label-padding : 0 0 8px;
+@form-vertical-label-margin : 0;
+
+// Input
+// ---
+@input-height-base : 32px;
+@input-height-lg : 40px;
+@input-height-sm : 24px;
+@input-padding-horizontal : @control-padding-horizontal - 1px;
+@input-padding-horizontal-base: @input-padding-horizontal;
+@input-padding-horizontal-sm : @control-padding-horizontal-sm - 1px;
+@input-padding-horizontal-lg : @input-padding-horizontal;
+@input-padding-vertical-base : 4px;
+@input-padding-vertical-sm : 1px;
+@input-padding-vertical-lg : 6px;
+@input-placeholder-color : hsv(0, 0, 75%);
+@input-color : @text-color;
+@input-border-color : @border-color-base;
+@input-bg : #fff;
+@input-addon-bg : @background-color-light;
+@input-hover-border-color : @primary-color;
+@input-disabled-bg : @disabled-bg;
+@input-outline-offset : 0 0;
+
+// Tooltip
+// ---
+//* Tooltip max width
+@tooltip-max-width: 250px;
+//** Tooltip text color
+@tooltip-color: #fff;
+//** Tooltip background color
+@tooltip-bg: rgba(0, 0, 0, 0.75);
+//** Tooltip arrow width
+@tooltip-arrow-width: 5px;
+//** Tooltip distance with trigger
+@tooltip-distance: @tooltip-arrow-width - 1px + 4px;
+//** Tooltip arrow color
+@tooltip-arrow-color: @tooltip-bg;
+
+// Popover
+// ---
+//** Popover body background color
+@popover-bg: #fff;
+//** Popover text color
+@popover-color: @text-color;
+//** Popover maximum width
+@popover-min-width: 177px;
+//** Popover arrow width
+@popover-arrow-width: 6px;
+//** Popover arrow color
+@popover-arrow-color: @popover-bg;
+//** Popover outer arrow width
+//** Popover outer arrow color
+@popover-arrow-outer-color: @popover-bg;
+//** Popover distance with trigger
+@popover-distance: @popover-arrow-width + 4px;
+
+// Modal
+// --
+@modal-header-bg: @component-background;
+@modal-mask-bg: rgba(0, 0, 0, 0.65);
+
+// Progress
+// --
+@progress-default-color: @processing-color;
+@progress-remaining-color: @background-color-base;
+@progress-text-color: @text-color;
+
+// Menu
+// ---
+@menu-inline-toplevel-item-height: 40px;
+@menu-item-height: 40px;
+@menu-collapsed-width: 80px;
+@menu-bg: @component-background;
+@menu-popup-bg: @component-background;
+@menu-item-color: @text-color;
+@menu-highlight-color: @primary-color;
+@menu-item-active-bg: @item-active-bg;
+@menu-item-active-border-width: 3px;
+@menu-item-group-title-color: @text-color-secondary;
+// dark theme
+@menu-dark-color: @text-color-secondary-dark;
+@menu-dark-bg: @layout-header-background;
+@menu-dark-arrow-color: #fff;
+@menu-dark-submenu-bg: #000c17;
+@menu-dark-highlight-color: #fff;
+@menu-dark-item-active-bg: @primary-color;
+
+// Spin
+// ---
+@spin-dot-size-sm: 14px;
+@spin-dot-size: 20px;
+@spin-dot-size-lg: 32px;
+
+// Table
+// --
+@table-header-bg: @background-color-light;
+@table-header-color: @heading-color;
+@table-header-sort-bg: @background-color-base;
+@table-body-sort-bg: rgba(0, 0, 0, 0.01);
+@table-row-hover-bg: @primary-1;
+@table-selected-row-bg: #fafafa;
+@table-expanded-row-bg: #fbfbfb;
+@table-padding-vertical: 16px;
+@table-padding-horizontal: 16px;
+
+// Tag
+// --
+@tag-default-bg: @background-color-light;
+@tag-default-color: @text-color;
+@tag-font-size: @font-size-sm;
+
+// TimePicker
+// ---
+@time-picker-panel-column-width: 56px;
+@time-picker-panel-width: @time-picker-panel-column-width * 3;
+@time-picker-selected-bg: @background-color-base;
+
+// Carousel
+// ---
+@carousel-dot-width: 16px;
+@carousel-dot-height: 3px;
+@carousel-dot-active-width: 24px;
+
+// Badge
+// ---
+@badge-height: 20px;
+@badge-dot-size: 6px;
+@badge-font-size: @font-size-sm;
+@badge-font-weight: normal;
+@badge-status-size: 6px;
+
+// Rate
+// ---
+@rate-star-color: @yellow-6;
+@rate-star-bg: @border-color-split;
+
+// Card
+// ---
+@card-head-color: @heading-color;
+@card-head-background: transparent;
+@card-head-padding: 16px;
+@card-inner-head-padding: 12px;
+@card-padding-base: 24px;
+@card-padding-wider: 32px;
+@card-actions-background: @background-color-light;
+@card-shadow: 0 2px 8px rgba(0, 0, 0, 0.09);
+
+// Comment
+// ---
+@comment-padding-base: 16px 0;
+@comment-nest-indent: 44px;
+@comment-author-name-color: @text-color-secondary;
+@comment-author-time-color: #ccc;
+@comment-action-color: @text-color-secondary;
+@comment-action-hover-color: #595959;
+
+// Tabs
+// ---
+@tabs-card-head-background: @background-color-light;
+@tabs-card-height: 40px;
+@tabs-card-active-color: @primary-color;
+@tabs-title-font-size: @font-size-base;
+@tabs-title-font-size-lg: @font-size-lg;
+@tabs-title-font-size-sm: @font-size-base;
+@tabs-ink-bar-color: @primary-color;
+@tabs-bar-margin: 0 0 16px 0;
+@tabs-horizontal-margin: 0 32px 0 0;
+@tabs-horizontal-padding: 12px 16px;
+@tabs-vertical-padding: 8px 24px;
+@tabs-vertical-margin: 0 0 16px 0;
+@tabs-scrolling-size: 32px;
+@tabs-highlight-color: @primary-color;
+@tabs-hover-color: @primary-5;
+@tabs-active-color: @primary-7;
+
+// BackTop
+// ---
+@back-top-color: #fff;
+@back-top-bg: @text-color-secondary;
+@back-top-hover-bg: @text-color;
+
+// Avatar
+// ---
+@avatar-size-base: 32px;
+@avatar-size-lg: 40px;
+@avatar-size-sm: 24px;
+@avatar-font-size-base: 18px;
+@avatar-font-size-lg: 24px;
+@avatar-font-size-sm: 14px;
+@avatar-bg: #ccc;
+@avatar-color: #fff;
+@avatar-border-radius: @border-radius-base;
+
+// Switch
+// ---
+@switch-height: 22px;
+@switch-sm-height: 16px;
+@switch-sm-checked-margin-left: -(@switch-sm-height - 3px);
+@switch-disabled-opacity: 0.4;
+@switch-color: @primary-color;
+
+// Pagination
+// ---
+@pagination-item-size: 32px;
+@pagination-item-size-sm: 24px;
+@pagination-font-family: Arial;
+@pagination-font-weight-active: 500;
+
+// Breadcrumb
+// ---
+@breadcrumb-base-color: @text-color-secondary;
+@breadcrumb-last-item-color: @text-color;
+@breadcrumb-font-size: @font-size-base;
+@breadcrumb-icon-font-size: @font-size-base;
+@breadcrumb-link-color: @text-color-secondary;
+@breadcrumb-link-color-hover: @primary-5;
+@breadcrumb-separator-color: @text-color-secondary;
+@breadcrumb-separator-margin: 0 @padding-xs;
+
+// Slider
+// ---
+@slider-margin: 14px 6px 10px;
+@slider-rail-background-color: @background-color-base;
+@slider-rail-background-color-hover: #e1e1e1;
+@slider-track-background-color: @primary-5;
+@slider-track-background-color-hover: @primary-6;
+@slider-handle-color: @primary-5;
+@slider-handle-color-hover: @primary-6;
+@slider-handle-color-focus: tint(@primary-color, 20%);
+@slider-handle-color-focus-shadow: fade(@primary-color, 20%);
+@slider-handle-color-tooltip-open: @primary-color;
+@slider-dot-border-color: @border-color-split;
+@slider-dot-border-color-active: @primary-5;
+@slider-disabled-color: @disabled-color;
+@slider-disabled-background-color: @component-background;
+
+// Tree
+// ---
+@tree-title-height: 24px;
+@tree-child-padding: 18px;
+@tree-directory-selected-color: #fff;
+@tree-directory-selected-bg: @primary-color;
+
+// Collapse
+// ---
+@collapse-header-padding: 12px 16px 12px 40px;
+@collapse-header-bg: @background-color-light;
+@collapse-content-padding: @padding-md;
+@collapse-content-bg: @component-background;
+
+// Skeleton
+// ---
+@skeleton-color: #f2f2f2;
+
+// Transfer
+// ---
+@transfer-disabled-bg: @disabled-bg;
+
+// Message
+// ---
+@message-notice-content-padding: 10px 16px;
+
+// Motion
+// ---
+@wave-animation-width: 6px;
+
+// Alert
+// ---
+@alert-success-border-color: ~`colorPalette('@{success-color}', 3) `;
+@alert-success-bg-color: ~`colorPalette('@{success-color}', 1) `;
+@alert-success-icon-color: @success-color;
+@alert-info-border-color: ~`colorPalette('@{info-color}', 3) `;
+@alert-info-bg-color: ~`colorPalette('@{info-color}', 1) `;
+@alert-info-icon-color: @info-color;
+@alert-warning-border-color: ~`colorPalette('@{warning-color}', 3) `;
+@alert-warning-bg-color: ~`colorPalette('@{warning-color}', 1) `;
+@alert-warning-icon-color: @warning-color;
+@alert-error-border-color: ~`colorPalette('@{error-color}', 3) `;
+@alert-error-bg-color: ~`colorPalette('@{error-color}', 1) `;
+@alert-error-icon-color: @error-color;
+
+// List
+// ---
+@list-empty-text-padding: @padding-md;
+@list-item-padding: @padding-sm 0;
+@list-item-content-margin: 0 0 @padding-md 0;
+@list-item-meta-margin-bottom: @padding-md;
+@list-item-meta-avatar-margin-right: @padding-md;
+@list-item-meta-title-margin-bottom: @padding-sm;
+
diff --git a/submarine-workbench/workbench-web-ng/tsconfig.app.json b/submarine-workbench/workbench-web-ng/tsconfig.app.json
new file mode 100644
index 0000000..565a11a
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/tsconfig.app.json
@@ -0,0 +1,18 @@
+{
+  "extends": "./tsconfig.json",
+  "compilerOptions": {
+    "outDir": "./out-tsc/app",
+    "types": []
+  },
+  "files": [
+    "src/main.ts",
+    "src/polyfills.ts"
+  ],
+  "include": [
+    "src/**/*.ts"
+  ],
+  "exclude": [
+    "src/test.ts",
+    "src/**/*.spec.ts"
+  ]
+}
diff --git a/submarine-workbench/workbench-web-ng/tsconfig.json b/submarine-workbench/workbench-web-ng/tsconfig.json
new file mode 100644
index 0000000..a9a1c46
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/tsconfig.json
@@ -0,0 +1,32 @@
+{
+  "compileOnSave": false,
+  "compilerOptions": {
+    "baseUrl": "./",
+    "paths": {
+      "@submarine/*": [
+        "./src/app/*",
+        "./src/environments/*"
+      ]
+    },
+    "outDir": "./dist/out-tsc",
+    "sourceMap": true,
+    "declaration": false,
+    "downlevelIteration": true,
+    "experimentalDecorators": true,
+    "module": "esnext",
+    "moduleResolution": "node",
+    "importHelpers": true,
+    "target": "es2015",
+    "typeRoots": [
+      "node_modules/@types"
+    ],
+    "lib": [
+      "es2018",
+      "dom"
+    ]
+  },
+  "angularCompilerOptions": {
+    "fullTemplateTypeCheck": true,
+    "strictInjectionParameters": true
+  }
+}
diff --git a/submarine-workbench/workbench-web-ng/tsconfig.spec.json b/submarine-workbench/workbench-web-ng/tsconfig.spec.json
new file mode 100644
index 0000000..6400fde
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/tsconfig.spec.json
@@ -0,0 +1,18 @@
+{
+  "extends": "./tsconfig.json",
+  "compilerOptions": {
+    "outDir": "./out-tsc/spec",
+    "types": [
+      "jasmine",
+      "node"
+    ]
+  },
+  "files": [
+    "src/test.ts",
+    "src/polyfills.ts"
+  ],
+  "include": [
+    "src/**/*.spec.ts",
+    "src/**/*.d.ts"
+  ]
+}
diff --git a/submarine-workbench/workbench-web-ng/tslint.json b/submarine-workbench/workbench-web-ng/tslint.json
new file mode 100644
index 0000000..ae8d3ae
--- /dev/null
+++ b/submarine-workbench/workbench-web-ng/tslint.json
@@ -0,0 +1,142 @@
+{
+  "rulesDirectory": ["node_modules/codelyzer"],
+  "linterOptions": {
+    "exclude": [
+      "e2e/**",
+      "src/main.ts",
+      "src/polyfills.ts",
+      "src/test.ts"
+    ]
+  },
+  "rules": {
+    "no-life-cycle-call": false,
+    "prefer-output-readonly": true,
+    "no-conflicting-life-cycle-hooks": false,
+    "enforce-component-selector": false,
+    "use-host-property-decorator": false,
+    "use-view-encapsulation": false,
+    "no-output-rename": true,
+    "no-output-on-prefix": true,
+    "no-forward-ref": false,
+    "use-life-cycle-interface": true,
+    "contextual-life-cycle": true,
+    "trackBy-function": false,
+    "use-pipe-transform-interface": true,
+    "component-class-suffix": true,
+    "directive-class-suffix": true,
+    "angular-whitespace": [false, "check-interpolation"],
+    "directive-selector": [true, "attribute", ["submarine"], ["camelCase", "kebab-case"]],
+    "component-selector": [true, ["element", "attribute"], ["submarine"], "kebab-case"],
+    "callable-types": true,
+    "class-name": true,
+    "comment-format": [true, "check-space"],
+    "align": [true, "parameters", "statements"],
+    "array-type": [true, "array-simple"],
+    "arrow-return-shorthand": true,
+    "ban-types": [
+      true,
+      ["Object", "Use {} instead."],
+      ["String", "Use string instead."],
+      ["Number", "Use number instead."],
+      ["Boolean", "Use boolean instead."],
+      ["Function", "Use specific callable interface instead."]
+    ],
+    "binary-expression-operand-order": true,
+    "curly": true,
+    "encoding": true,
+    "eofline": true,
+    "deprecation": {
+      "severity": "warn"
+    },
+    "import-spacing": true,
+    "indent": [true, "spaces"],
+    "interface-name": [true, "never-prefix"],
+    "interface-over-type-literal": true,
+    "label-position": true,
+    "new-parens": true,
+    "no-angle-bracket-type-assertion": true,
+    "member-access": false,
+    "member-ordering": [
+      true,
+      {
+        "order": ["static-field", "instance-field", "static-method", "instance-method"]
+      }
+    ],
+    "no-any": false,
+    "no-arg": true,
+    "no-bitwise": false,
+    "no-consecutive-blank-lines": [true],
+    "no-console": [true, "debug", "info", "time", "timeEnd", "trace"],
+    "no-duplicate-variable": true,
+    "no-conditional-assignment": true,
+    "no-construct": true,
+    "no-debugger": true,
+    "no-duplicate-imports": true,
+    "no-duplicate-super": true,
+    "no-empty": false,
+    "no-empty-interface": true,
+    "no-eval": true,
+    "no-floating-promises": false,
+    "no-for-in-array": true,
+    "no-import-side-effect": true,
+    "no-inferrable-types": [true, "ignore-params", "ignore-properties"],
+    "no-invalid-template-strings": true,
+    "no-invalid-this": true,
+    "no-irregular-whitespace": true,
+    "no-magic-numbers": false,
+    "no-misused-new": true,
+    "no-namespace": [true, "allow-declarations"],
+    "no-non-null-assertion": false,
+    "no-shadowed-variable": true,
+    "no-sparse-arrays": true,
+    "no-string-literal": true,
+    "no-string-throw": true,
+    "no-switch-case-fall-through": true,
+    "no-this-assignment": true,
+    "no-trailing-whitespace": true,
+    "no-parameter-reassignment": true,
+    "no-unnecessary-initializer": true,
+    "no-unused-expression": true,
+    "no-use-before-declare": true,
+    "no-var-keyword": true,
+    "number-literal-format": true,
+    "one-line": [true, "check-open-brace", "check-catch", "check-else", "check-whitespace"],
+    "one-variable-per-declaration": [true, "ignore-for-loop"],
+    "ordered-imports": [
+      true,
+      {
+        "import-sources-order": "lowercase-last",
+        "named-imports-order": "lowercase-first"
+      }
+    ],
+    "prefer-conditional-expression": false,
+    "prefer-const": true,
+    "prefer-method-signature": true,
+    "prefer-object-spread": true,
+    "prefer-template": [true, "allow-single-concat"],
+    "radix": true,
+    "trailing-comma": [
+      true,
+      {
+        "multiline": "never",
+        "singleline": "never"
+      }
+    ],
+    "triple-equals": [true, "allow-null-check"],
+    "typedef-whitespace": [
+      true,
+      {
+        "call-signature": "nospace",
+        "index-signature": "nospace",
+        "parameter": "nospace",
+        "property-declaration": "nospace",
+        "variable-declaration": "nospace"
+      }
+    ],
+    "unified-signatures": true,
+    "use-isnan": true,
+    "variable-name": [true, "ban-keywords", "allow-leading-underscore"],
+    "whitespace": [true, "check-branch", "check-decl", "check-operator", "check-separator", "check-type"],
+    "no-input-rename": true
+  }
+}