zeppelin-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From k..@apache.org
Subject [5/5] zeppelin git commit: [ZEPPELIN-2749] Use scalable file structure for zeppelin web
Date Fri, 14 Jul 2017 16:07:10 GMT
[ZEPPELIN-2749] Use scalable file structure for zeppelin web

### What is this PR for?

We have improved zeppelin-web, but some parts are still messy. As part of keeping zeppelin-web module healthy ([ZEPPELIN-2725](https://issues.apache.org/jira/browse/ZEPPELIN-2725)), I suggest having these file structure. (Refer the screenshot section)

Here are few reasons.

- unified directory, file name helps us to recognize, find which part we should modify / fix
  * Let's say we need to modify the resize feature of paragraph, where the developer can find it? currently, it's under `component/resizable` not under `paragraph/resizeable` and also it's not the shareable component. There is no reason to keep that files in `component/**`
- [this structure](https://github.com/toddmotto/angularjs-styleguide#file-naming-conventions) is what the angularjs community has verified for few years. so newly joined developers can feel more comfortable.
- this is necessary for [Modular archiecture](https://issues.apache.org/jira/browse/ZEPPELIN-2750) and it eventually helps us to make a smooth transition toward next technologies (even whatever we will use)

Additionally,

- This is not the meaningless refactoring PR and doesn't block developing new features / fixes (Please refer the `Some Details` section)
- I will handle conflicts for few days would be brought by other WIPs

For your information,

- https://github.com/toddmotto/angularjs-styleguide#file-naming-conventions
- https://github.com/johnpapa/angular-styleguide/blob/master/a1/README.md#naming

#### How to Review This PR?

Please follow the commits. I modified submodules by splitting commits. Thus commit message includes what has been done in that PR. For example,

![image](https://user-images.githubusercontent.com/4968473/27993114-d8ac45e6-64dd-11e7-8130-f3fa887054a1.png)

#### Some Details

- Didn't change the widely used variable names not to make many conflicts. For example, `websocketMsgSrv`, `arrayOrderingSrv`
- Since there are helium packages already published, didn't change the HTML file names like `pivot_setting.html` (it's better to use `pivot-setting.html` if we following the rule)

### What type of PR is it?
[Improvement | Refactoring]

### Todos

Please refer the commit message.

### What is the Jira issue?

[ZEPPELIN-2749](https://issues.apache.org/jira/browse/ZEPPELIN-2749)

### How should this be tested?

**All functionalities must work** as like before, CI will test it.

### Screenshots (if appropriate)

#### Before: messy, mixed directory structure

![image](https://user-images.githubusercontent.com/4968473/27993126-0a94aca6-64de-11e7-93db-548b6fcc6913.png)

#### After: only the shared components will be placed under `components/`

![image](https://user-images.githubusercontent.com/4968473/27993118-ee1bd2d4-64dd-11e7-95b6-f71dc628a94e.png)

### Questions:
* Does the licenses files need update? - NO
* Is there breaking changes for older versions? - NO
* Does this needs documentation? - NO

Author: 1ambda <1amb4a@gmail.com>

Closes #2472 from 1ambda/ZEPPELIN-2749/use-scalable-file-structure-for-zeppelin-web and squashes the following commits:

22e6bf95 [1ambda] fix: AuthenticationIT, InterpreterModeActionsIT
e31f0bc8 [1ambda] fix: AbstractZeppelinIT
102e5443 [1ambda] paragraph-parameterized-query-form: Rename
74e0af41 [1ambda] paragraph-progress-bar: Rename
0029d6b7 [1ambda] job-progress-bar: Rename
af420a41 [1ambda] helium: Move helium related files into app/helium
285a6462 [1ambda] notebook-repository: Rename injected ctrl name
147572f1 [1ambda] notebook-repository: Rename files, funcs
4f66b642 [1ambda] ng-*: Rename files, funcs
f25d981e [1ambda] interpreter: Move interpreter specific directives into interpreter/
4a0602f7 [1ambda] array-ordering: Rename files
1e4ba709 [1ambda] code-editor: Move paragraph specific directive into paragraph/code-editor
31dcf6a3 [1ambda] base-url: Rename file
1afb2d03 [1ambda] note-name-filter: Rename files
ec046683 [1ambda] dropdown-input: Move notebook specific directive into notebook/dropdown-input
81fa2d44 [1ambda] elastic-input: Move notebook specific ctrl into notebook/elastic-input
657d0638 [1ambda] note-rename: Add prefix note-
47ac45d1 [1ambda] expand-collapse: Move navbar specific directive into navbar/
8bc78124 [1ambda] login: Remove invalid attr in login
b2bf91b0 [1ambda] note-import: Rename to noteImportCtrl
07b1f3ff [1ambda] note-create: Rename to noteCreateModal
568149ed [1ambda] note-create: Rename injected controller
c3402449 [1ambda] note-create: Rename files
e17c7ee7 [1ambda] note-create: Remove useless dialog postfix
b0a36a7e [1ambda] note-import: Remove meaningless postfix dialog
abf6869d [1ambda] note-import: Rename files
a40ea23c [1ambda] search: Move search specific service into serach/
7a094841 [1ambda] browser-detect: move save-as specific service into save-as/
40f62e3c [1ambda] rename: Modify injected service name
e993133a [1ambda] rename: Rename funcs
1f950943 [1ambda] note-action: Rename injected service name
d3da2d45 [1ambda] note-action: Rename files, funcs
b9f3c178 [1ambda] note-list-elem: Rename
d3132e9a [1ambda] note-list: Remove useless middle word Data
b5dff142 [1ambda] resizable: Move para specific directive into paragraph/
bfcd6b85 [1ambda] resizable: Rename funcs
40643997 [1ambda] fix: Remove useless postfix and rename files, funcs
0611aff5 [1ambda] websocket: Rename funcs
a2527530 [1ambda] fix: Remove meaningless postfix from searchService
c21a1237 [1ambda] move: paragraph specific ctrl into paragraph/
ad99c04b [1ambda] move: note specific dialog into notebook/
701f4432 [1ambda] move: note specific service into notebook/
3f02c503 [1ambda] fix: Remove unused saveAsService from paragraph ctrl
d7d7434a [1ambda] move: note specific service saveAsService into notebook/
4e7f6d9c [1ambda] fix: Move repository-create into interpreter/
a9a6368a [1ambda] fix: remove useless dir interpreter-create
c1c210de [1ambda] rename: repository-dialog -> repository-create


Project: http://git-wip-us.apache.org/repos/asf/zeppelin/repo
Commit: http://git-wip-us.apache.org/repos/asf/zeppelin/commit/6bd6c708
Tree: http://git-wip-us.apache.org/repos/asf/zeppelin/tree/6bd6c708
Diff: http://git-wip-us.apache.org/repos/asf/zeppelin/diff/6bd6c708

Branch: refs/heads/master
Commit: 6bd6c70881e8515a5b0679e5a8d6c235cfb1adf6
Parents: ebb6591
Author: 1ambda <1amb4a@gmail.com>
Authored: Mon Jul 10 15:52:37 2017 +0900
Committer: 1ambda <1amb4a@gmail.com>
Committed: Sat Jul 15 01:06:54 2017 +0900

----------------------------------------------------------------------
 .../org/apache/zeppelin/AbstractZeppelinIT.java |   2 +-
 .../zeppelin/integration/AuthenticationIT.java  |   2 +-
 .../integration/InterpreterModeActionsIT.java   |   2 +-
 zeppelin-web/src/app/app.js                     |   4 +-
 zeppelin-web/src/app/helium/helium-conf.js      |  99 +++++
 zeppelin-web/src/app/helium/helium-package.js   |  47 +++
 zeppelin-web/src/app/helium/helium-type.js      |  20 +
 zeppelin-web/src/app/helium/helium.config.js    | 101 -----
 .../src/app/helium/helium.controller.js         |   2 +-
 zeppelin-web/src/app/helium/helium.service.js   | 301 +++++++++++++++
 zeppelin-web/src/app/home/home.controller.js    |  25 +-
 zeppelin-web/src/app/home/home.html             |   4 +-
 .../src/app/home/notebook-template.html         |   4 +-
 zeppelin-web/src/app/home/notebook.html         |   4 +-
 .../src/app/interpreter/interpreter-create.html | 383 +++++++++++++++++++
 .../interpreter-create/interpreter-create.html  | 383 -------------------
 .../interpreter/interpreter-item.directive.js   |  31 ++
 .../src/app/interpreter/interpreter.html        |  10 +-
 .../src/app/interpreter/repository-create.html  | 115 ++++++
 .../widget/number-widget.directive.js           |  31 ++
 .../app/jobmanager/jobs/job-progress-bar.html   |  22 ++
 .../app/jobmanager/jobs/job-progressBar.html    |  22 --
 zeppelin-web/src/app/jobmanager/jobs/job.html   |   2 +-
 .../notebook-repository.controller.js           |  87 +++++
 .../notebook-repository.html                    |  98 +++++
 .../dropdown-input/dropdown-input.directive.js  |  26 ++
 .../elastic-input/elastic-input.controller.js   |  21 +
 .../src/app/notebook/note-var-share.service.js  |  39 ++
 .../src/app/notebook/notebook-actionBar.html    |   2 +-
 .../src/app/notebook/notebook.controller.js     |   8 +-
 .../notebook/paragraph/clipboard.controller.js  |  34 ++
 .../code-editor/code-editor.directive.html      |  21 +
 .../code-editor/code-editor.directive.js        |  38 ++
 .../paragraph-parameterized-query-form.html     |  71 ++++
 .../paragraph-parameterizedQueryForm.html       |  71 ----
 .../paragraph/paragraph-progress-bar.html       |  22 ++
 .../paragraph/paragraph-progressBar.html        |  22 --
 .../notebook/paragraph/paragraph.controller.js  |   2 +-
 .../src/app/notebook/paragraph/paragraph.html   |   4 +-
 .../notebook/paragraph/resizable.directive.js   |  69 ++++
 .../notebook/save-as/browser-detect.service.js  |  39 ++
 .../src/app/notebook/save-as/save-as.service.js |  51 +++
 zeppelin-web/src/app/notebook/shortcut.html     | 312 +++++++++++++++
 .../notebookRepos/notebookRepos.controller.js   |  87 -----
 .../src/app/notebookRepos/notebookRepos.html    |  98 -----
 zeppelin-web/src/app/search/search.service.js   |  33 ++
 .../array-ordering/array-ordering.service.js    |  62 +++
 .../arrayOrderingSrv/arrayOrdering.service.js   |  62 ---
 .../src/components/base-url/base-url.service.js |  50 +++
 .../src/components/baseUrl/baseUrl.service.js   |  50 ---
 .../browser-detect/browserDetect.service.js     |  39 --
 .../clipboard/clipboard.controller.js           |  34 --
 .../dropdowninput/dropdowninput.directive.js    |  26 --
 .../components/editor/ace.editor.directive.html |  21 -
 .../components/editor/codeEditor.directive.js   |  38 --
 .../elasticInputCtrl/elasticInput.controller.js |  21 -
 .../expandCollapse/expandCollapse.css           |  17 -
 .../expandCollapse/expandCollapse.directive.js  |  46 ---
 .../filterNoteNames/filter-note-names.html      |  19 -
 .../src/components/helium/helium-conf.js        |  99 -----
 .../src/components/helium/helium-package.js     |  47 ---
 .../src/components/helium/helium-type.js        |  20 -
 .../src/components/helium/helium.service.js     | 301 ---------------
 .../interpreter/interpreter.directive.js        |  31 --
 .../widget/widget.number.directive.js           |  31 --
 zeppelin-web/src/components/login/login.html    |   2 +-
 .../modal-shortcut/modal-shortcut.html          | 312 ---------------
 .../navbar/expand-collapse/expand-collapse.css  |  17 +
 .../expand-collapse.directive.js                |  48 +++
 .../navbar/navbar-note-list-elem.html           |  50 +++
 .../components/navbar/navbar-noteList-elem.html |  50 ---
 .../src/components/navbar/navbar.controller.js  |   6 +-
 zeppelin-web/src/components/navbar/navbar.html  |  10 +-
 .../components/ng-enter/ng-enter.directive.js   |  30 ++
 .../ng-enter/ng-enter.directive.test.js         |  24 ++
 .../components/ng-escape/ng-escape.directive.js |  28 ++
 .../src/components/ngenter/ngenter.directive.js |  30 --
 .../ngenter/ngenter.directive.test.js           |  24 --
 .../components/ngescape/ngescape.directive.js   |  28 --
 .../note-action/note-action.service.js          | 183 +++++++++
 .../note-create/note-create.controller.js       | 106 +++++
 .../note-create/note-create.controller.test.js  |  39 ++
 .../src/components/note-create/note-create.css  |  49 +++
 .../src/components/note-create/note-create.html |  66 ++++
 .../components/note-create/visible.directive.js |  45 +++
 .../note-import/note-import.controller.js       | 138 +++++++
 .../src/components/note-import/note-import.css  |  91 +++++
 .../src/components/note-import/note-import.html |  80 ++++
 .../components/note-list/note-list.factory.js   |  81 ++++
 .../note-list/note-list.factory.test.js         |  75 ++++
 .../note-name-filter/note-name-filter.html      |  19 +
 .../note-rename/note-rename.controller.js       |  48 +++
 .../src/components/note-rename/note-rename.css  |  38 ++
 .../src/components/note-rename/note-rename.html |  43 +++
 .../note-rename/note-rename.service.js          |  32 ++
 .../components/noteAction/noteAction.service.js | 183 ---------
 .../noteListDataFactory/noteList.datafactory.js |  81 ----
 .../noteList.datafactory.test.js                |  75 ----
 .../noteName-create/note-name-dialog.css        |  49 ---
 .../noteName-create/note-name-dialog.html       |  66 ----
 .../noteName-create/notename.controller.js      | 106 -----
 .../noteName-create/notename.controller.test.js |  39 --
 .../noteName-create/visible.directive.js        |  45 ---
 .../noteName-import/note-import-dialog.css      |  91 -----
 .../noteName-import/note-import-dialog.html     |  80 ----
 .../notenameImport.controller.js                | 138 -------
 .../notevarshareService/notevarshare.service.js |  39 --
 .../src/components/rename/rename.controller.js  |  48 ---
 zeppelin-web/src/components/rename/rename.css   |  38 --
 zeppelin-web/src/components/rename/rename.html  |  43 ---
 .../src/components/rename/rename.service.js     |  32 --
 .../repository-create/repository-dialog.html    | 115 ------
 .../components/resizable/resizable.directive.js |  69 ----
 .../src/components/saveAs/saveAs.service.js     |  51 ---
 .../components/searchService/search.service.js  |  33 --
 .../websocket/websocket-event.factory.js        | 196 ++++++++++
 .../websocket/websocket-message.service.js      | 343 +++++++++++++++++
 .../websocketEvents/websocketEvents.factory.js  | 196 ----------
 .../websocketEvents/websocketMsg.service.js     | 343 -----------------
 zeppelin-web/src/index.html                     |  19 +-
 zeppelin-web/src/index.js                       |  54 +--
 121 files changed, 4075 insertions(+), 4174 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/zeppelin/blob/6bd6c708/zeppelin-server/src/test/java/org/apache/zeppelin/AbstractZeppelinIT.java
----------------------------------------------------------------------
diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/AbstractZeppelinIT.java b/zeppelin-server/src/test/java/org/apache/zeppelin/AbstractZeppelinIT.java
index e16bf1a..475be50 100644
--- a/zeppelin-server/src/test/java/org/apache/zeppelin/AbstractZeppelinIT.java
+++ b/zeppelin-server/src/test/java/org/apache/zeppelin/AbstractZeppelinIT.java
@@ -107,7 +107,7 @@ abstract public class AbstractZeppelinIT {
         " note')]"));
 
     WebDriverWait block = new WebDriverWait(driver, MAX_BROWSER_TIMEOUT_SEC);
-    block.until(ExpectedConditions.visibilityOfElementLocated(By.id("noteNameModal")));
+    block.until(ExpectedConditions.visibilityOfElementLocated(By.id("noteCreateModal")));
     clickAndWait(By.id("createNoteButton"));
     block.until(ExpectedConditions.invisibilityOfElementLocated(By.className("pull-right")));
   }

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/6bd6c708/zeppelin-server/src/test/java/org/apache/zeppelin/integration/AuthenticationIT.java
----------------------------------------------------------------------
diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/integration/AuthenticationIT.java b/zeppelin-server/src/test/java/org/apache/zeppelin/integration/AuthenticationIT.java
index 523b8f8..f87bff2 100644
--- a/zeppelin-server/src/test/java/org/apache/zeppelin/integration/AuthenticationIT.java
+++ b/zeppelin-server/src/test/java/org/apache/zeppelin/integration/AuthenticationIT.java
@@ -125,7 +125,7 @@ public class AuthenticationIT extends AbstractZeppelinIT {
     ZeppelinITUtils.sleep(1000, false);
     pollingWait(By.xpath("//*[@id='userName']"), MAX_BROWSER_TIMEOUT_SEC).sendKeys(userName);
     pollingWait(By.xpath("//*[@id='password']"), MAX_BROWSER_TIMEOUT_SEC).sendKeys(password);
-    pollingWait(By.xpath("//*[@id='NoteImportCtrl']//button[contains(.,'Login')]"),
+    pollingWait(By.xpath("//*[@id='loginModalContent']//button[contains(.,'Login')]"),
         MAX_BROWSER_TIMEOUT_SEC).click();
     ZeppelinITUtils.sleep(1000, false);
   }

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/6bd6c708/zeppelin-server/src/test/java/org/apache/zeppelin/integration/InterpreterModeActionsIT.java
----------------------------------------------------------------------
diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/integration/InterpreterModeActionsIT.java b/zeppelin-server/src/test/java/org/apache/zeppelin/integration/InterpreterModeActionsIT.java
index 212b04b..9bfeae0 100644
--- a/zeppelin-server/src/test/java/org/apache/zeppelin/integration/InterpreterModeActionsIT.java
+++ b/zeppelin-server/src/test/java/org/apache/zeppelin/integration/InterpreterModeActionsIT.java
@@ -139,7 +139,7 @@ public class InterpreterModeActionsIT extends AbstractZeppelinIT {
     ZeppelinITUtils.sleep(500, false);
     pollingWait(By.xpath("//*[@id='userName']"), MAX_BROWSER_TIMEOUT_SEC).sendKeys(userName);
     pollingWait(By.xpath("//*[@id='password']"), MAX_BROWSER_TIMEOUT_SEC).sendKeys(password);
-    pollingWait(By.xpath("//*[@id='NoteImportCtrl']//button[contains(.,'Login')]"),
+    pollingWait(By.xpath("//*[@id='loginModalContent']//button[contains(.,'Login')]"),
         MAX_BROWSER_TIMEOUT_SEC).click();
     ZeppelinITUtils.sleep(1000, false);
   }

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/6bd6c708/zeppelin-web/src/app/app.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/app.js b/zeppelin-web/src/app/app.js
index f645d3b..9878d56 100644
--- a/zeppelin-web/src/app/app.js
+++ b/zeppelin-web/src/app/app.js
@@ -113,8 +113,8 @@ let zeppelinWebApp = angular.module('zeppelinWebApp', requiredModules)
         controller: 'InterpreterCtrl'
       })
       .when('/notebookRepos', {
-        templateUrl: 'app/notebookRepos/notebookRepos.html',
-        controller: 'NotebookReposCtrl',
+        templateUrl: 'app/notebook-repository/notebook-repository.html',
+        controller: 'NotebookRepositoryCtrl',
         controllerAs: 'noterepo'
       })
       .when('/credential', {

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/6bd6c708/zeppelin-web/src/app/helium/helium-conf.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/helium/helium-conf.js b/zeppelin-web/src/app/helium/helium-conf.js
new file mode 100644
index 0000000..10ca18a
--- /dev/null
+++ b/zeppelin-web/src/app/helium/helium-conf.js
@@ -0,0 +1,99 @@
+/*
+ * Licensed 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 HeliumConfFieldType = {
+  NUMBER: 'number',
+  JSON: 'json',
+  STRING: 'string',
+}
+
+/**
+ * @param persisted <Object> including `type`, `description`, `defaultValue` for each conf key
+ * @param spec <Object> including `value` for each conf key
+ */
+export function mergePersistedConfWithSpec (persisted, spec) {
+  const confs = []
+
+  for (let name in spec) {
+    const specField = spec[name]
+    const persistedValue = persisted[name]
+
+    const value = (persistedValue) ? persistedValue : specField.defaultValue
+    const merged = {
+      name: name,
+      type: specField.type,
+      description: specField.description,
+      value: value,
+      defaultValue: specField.defaultValue,
+    }
+
+    confs.push(merged)
+  }
+
+  return confs
+}
+
+export function createAllPackageConfigs (defaultPackages, persistedConfs) {
+  let packageConfs = {}
+
+  for (let name in defaultPackages) {
+    const pkgSearchResult = defaultPackages[name]
+
+    const spec = pkgSearchResult.pkg.config
+    if (!spec) { continue }
+
+    const artifact = pkgSearchResult.pkg.artifact
+    if (!artifact) { continue }
+
+    let persistedConf = {}
+    if (persistedConfs[artifact]) {
+      persistedConf = persistedConfs[artifact]
+    }
+
+    const confs = mergePersistedConfWithSpec(persistedConf, spec)
+    packageConfs[name] = confs
+  }
+
+  return packageConfs
+}
+
+export function parseConfigValue (type, stringified) {
+  let value = stringified
+
+  try {
+    if (HeliumConfFieldType.NUMBER === type) {
+      value = parseFloat(stringified)
+    } else if (HeliumConfFieldType.JSON === type) {
+      value = JSON.parse(stringified)
+    }
+  } catch (error) {
+    // return just the stringified one
+    console.error(`Failed to parse conf type ${type}, value ${value}`)
+  }
+
+  return value
+}
+
+/**
+ * persist key-value only
+ * since other info (e.g type, desc) can be provided by default config
+ */
+export function createPersistableConfig (currentConfs) {
+  const filtered = currentConfs.reduce((acc, c) => {
+    acc[c.name] = parseConfigValue(c.type, c.value)
+    return acc
+  }, {})
+
+  return filtered
+}

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/6bd6c708/zeppelin-web/src/app/helium/helium-package.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/helium/helium-package.js b/zeppelin-web/src/app/helium/helium-package.js
new file mode 100644
index 0000000..88d191a
--- /dev/null
+++ b/zeppelin-web/src/app/helium/helium-package.js
@@ -0,0 +1,47 @@
+/*
+ * Licensed 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 function createDefaultPackage (pkgSearchResult, sce) {
+  for (let pkgIdx in pkgSearchResult) {
+    const pkg = pkgSearchResult[pkgIdx]
+    pkg.pkg.icon = sce.trustAsHtml(pkg.pkg.icon)
+    if (pkg.enabled) {
+      pkgSearchResult.splice(pkgIdx, 1)
+      return pkg
+    }
+  }
+
+  // show first available version if package is not enabled
+  const result = pkgSearchResult[0]
+  pkgSearchResult.splice(0, 1)
+  return result
+}
+
+/**
+ * create default packages based on `enabled` field and `latest` version.
+ *
+ * @param pkgSearchResults
+ * @param sce angular `$sce` object
+ * @returns {Object} including {name, pkgInfo}
+ */
+export function createDefaultPackages (pkgSearchResults, sce) {
+  const defaultPackages = {}
+  // show enabled version if any version of package is enabled
+  for (let name in pkgSearchResults) {
+    const pkgSearchResult = pkgSearchResults[name]
+    defaultPackages[name] = createDefaultPackage(pkgSearchResult, sce)
+  }
+
+  return defaultPackages
+}

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/6bd6c708/zeppelin-web/src/app/helium/helium-type.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/helium/helium-type.js b/zeppelin-web/src/app/helium/helium-type.js
new file mode 100644
index 0000000..27b34fa
--- /dev/null
+++ b/zeppelin-web/src/app/helium/helium-type.js
@@ -0,0 +1,20 @@
+/*
+ * Licensed 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 HeliumType = {
+  VISUALIZATION: 'VISUALIZATION',
+  SPELL: 'SPELL',
+  INTERPRETER: 'INTERPRETER',
+  APPLICATION: 'APPLICATION',
+}

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/6bd6c708/zeppelin-web/src/app/helium/helium.config.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/helium/helium.config.js b/zeppelin-web/src/app/helium/helium.config.js
deleted file mode 100644
index ace7136..0000000
--- a/zeppelin-web/src/app/helium/helium.config.js
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Licensed 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 HeliumConfFieldType = {
-  NUMBER: 'number',
-  JSON: 'json',
-  STRING: 'string',
-}
-
-/**
- * @param persisted <Object> including `type`, `description`, `defaultValue` for each conf key
- * @param spec <Object> including `value` for each conf key
- */
-export function mergePersistedConfWithSpec (persisted, spec) {
-  const confs = []
-
-  for (let name in spec) {
-    const specField = spec[name]
-    const persistedValue = persisted[name]
-
-    const value = (persistedValue) ? persistedValue : specField.defaultValue
-    const merged = {
-      name: name,
-      type: specField.type,
-      description: specField.description,
-      value: value,
-      defaultValue: specField.defaultValue,
-    }
-
-    confs.push(merged)
-  }
-
-  return confs
-}
-
-export function createPackageConf (defaultPackages, persistedPackacgeConfs) {
-  let packageConfs = {}
-
-  for (let name in defaultPackages) {
-    const pkgInfo = defaultPackages[name]
-
-    const configSpec = pkgInfo.pkg.config
-    if (!configSpec) { continue }
-
-    const version = pkgInfo.pkg.version
-    if (!version) { continue }
-
-    let config = {}
-    if (persistedPackacgeConfs[name] && persistedPackacgeConfs[name][version]) {
-      config = persistedPackacgeConfs[name][version]
-    }
-
-    const confs = mergePersistedConfWithSpec(config, configSpec)
-    packageConfs[name] = confs
-  }
-
-  return packageConfs
-}
-
-export function parseConfigValue (type, stringified) {
-  let value = stringified
-
-  try {
-    if (HeliumConfFieldType.NUMBER === type) {
-      value = parseFloat(stringified)
-    } else if (HeliumConfFieldType.JSON === type) {
-      value = JSON.parse(stringified)
-    }
-  } catch (error) {
-    // return just the stringified one
-    console.error(`Failed to parse conf type ${type}, value ${value}`)
-  }
-
-  return value
-}
-
-/**
- * create persistable config object
- */
-export function createPersistableConfig (currentConf) {
-  // persist key-value only
-  // since other info (e.g type, desc) can be provided by default config
-  const filtered = currentConf.reduce((acc, c) => {
-    let value = parseConfigValue(c.type, c.value)
-    acc[c.name] = value
-    return acc
-  }, {})
-
-  return filtered
-}

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/6bd6c708/zeppelin-web/src/app/helium/helium.controller.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/helium/helium.controller.js b/zeppelin-web/src/app/helium/helium.controller.js
index a610fb6..a397ace 100644
--- a/zeppelin-web/src/app/helium/helium.controller.js
+++ b/zeppelin-web/src/app/helium/helium.controller.js
@@ -12,7 +12,7 @@
  * limitations under the License.
  */
 
-import { HeliumType, } from '../../components/helium/helium-type'
+import { HeliumType, } from './helium-type'
 
 export default function HeliumCtrl ($scope, $rootScope, $sce,
                                    baseUrlSrv, ngToast, heliumService) {

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/6bd6c708/zeppelin-web/src/app/helium/helium.service.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/helium/helium.service.js b/zeppelin-web/src/app/helium/helium.service.js
new file mode 100644
index 0000000..d2054b3
--- /dev/null
+++ b/zeppelin-web/src/app/helium/helium.service.js
@@ -0,0 +1,301 @@
+/*
+ * Licensed 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 { HeliumType, } from './helium-type'
+import {
+  createAllPackageConfigs,
+  createPersistableConfig,
+  mergePersistedConfWithSpec,
+} from './helium-conf'
+import {
+  createDefaultPackages,
+} from './helium-package'
+
+angular.module('zeppelinWebApp').service('heliumService', HeliumService)
+
+export default function HeliumService($http, $sce, baseUrlSrv) {
+  'ngInject'
+
+  let visualizationBundles = []
+  let visualizationPackageOrder = []
+  // name `heliumBundles` should be same as `HeliumBundleFactory.HELIUM_BUNDLES_VAR`
+  let heliumBundles = []
+  // map for `{ magic: interpreter }`
+  let spellPerMagic = {}
+  // map for `{ magic: package-name }`
+  let pkgNamePerMagic = {}
+
+  /**
+   * @param magic {string} e.g `%flowchart`
+   * @returns {SpellBase} undefined if magic is not registered
+   */
+  this.getSpellByMagic = function (magic) {
+    return spellPerMagic[magic]
+  }
+
+  this.executeSpell = function (magic, textWithoutMagic) {
+    const promisedConf = this.getSinglePackageConfigUsingMagic(magic)
+      .then(confs => createPersistableConfig(confs))
+
+    return promisedConf.then(conf => {
+      const spell = this.getSpellByMagic(magic)
+      const spellResult = spell.interpret(textWithoutMagic, conf)
+      const parsed = spellResult.getAllParsedDataWithTypes(
+        spellPerMagic, magic, textWithoutMagic)
+
+      return parsed
+    })
+  }
+
+  this.executeSpellAsDisplaySystem = function (magic, textWithoutMagic) {
+    const promisedConf = this.getSinglePackageConfigUsingMagic(magic)
+      .then(confs => createPersistableConfig(confs))
+
+    return promisedConf.then(conf => {
+      const spell = this.getSpellByMagic(magic)
+      const spellResult = spell.interpret(textWithoutMagic.trim(), conf)
+      const parsed = spellResult.getAllParsedDataWithTypes(spellPerMagic)
+
+      return parsed
+    })
+  }
+
+  this.getVisualizationCachedPackages = function () {
+    return visualizationBundles
+  }
+
+  this.getVisualizationCachedPackageOrder = function () {
+    return visualizationPackageOrder
+  }
+
+  /**
+   * @returns {Promise} which returns bundleOrder and cache it in `visualizationPackageOrder`
+   */
+  this.getVisualizationPackageOrder = function () {
+    return $http.get(baseUrlSrv.getRestApiBase() + '/helium/order/visualization')
+      .then(function (response, status) {
+        const order = response.data.body
+        visualizationPackageOrder = order
+        return order
+      })
+      .catch(function (error) {
+        console.error('Can not get bundle order', error)
+      })
+  }
+
+  this.setVisualizationPackageOrder = function (list) {
+    return $http.post(baseUrlSrv.getRestApiBase() + '/helium/order/visualization', list)
+  }
+
+  this.enable = function (name, artifact) {
+    return $http.post(baseUrlSrv.getRestApiBase() + '/helium/enable/' + name, artifact)
+  }
+
+  this.disable = function (name) {
+    return $http.post(baseUrlSrv.getRestApiBase() + '/helium/disable/' + name)
+  }
+
+  this.saveConfig = function (pkg, defaultPackageConfig, closeConfigPanelCallback) {
+    // in case of local package, it will include `/`
+    const pkgArtifact = encodeURIComponent(pkg.artifact)
+    const pkgName = pkg.name
+    const filtered = createPersistableConfig(defaultPackageConfig)
+
+    if (!pkgName || !pkgArtifact || !filtered) {
+      console.error(
+        `Can't save config for helium package '${pkgArtifact}'`, filtered)
+      return
+    }
+
+    const url = `${baseUrlSrv.getRestApiBase()}/helium/config/${pkgName}/${pkgArtifact}`
+    return $http.post(url, filtered)
+      .then(() => {
+        if (closeConfigPanelCallback) { closeConfigPanelCallback() }
+      }).catch((error) => {
+        console.error(`Failed to save config for ${pkgArtifact}`, error)
+      })
+  }
+
+  /**
+   * @returns {Promise<Object>} which including {name, Array<package info for artifact>}
+   */
+  this.getAllPackageInfo = function () {
+    return $http.get(`${baseUrlSrv.getRestApiBase()}/helium/package`)
+      .then(function (response, status) {
+        return response.data.body
+      })
+      .catch(function (error) {
+        console.error('Failed to get all package infos', error)
+      })
+  }
+
+  this.getAllEnabledPackages = function () {
+    return $http.get(`${baseUrlSrv.getRestApiBase()}/helium/enabledPackage`)
+      .then(function (response, status) {
+        return response.data.body
+      })
+      .catch(function (error) {
+        console.error('Failed to get all enabled package infos', error)
+      })
+  }
+
+  this.getSingleBundle = function (pkgName) {
+    let url = `${baseUrlSrv.getRestApiBase()}/helium/bundle/load/${pkgName}`
+    if (process.env.HELIUM_BUNDLE_DEV) {
+      url = url + '?refresh=true'
+    }
+
+    return $http.get(url)
+      .then(function (response, status) {
+        const bundle = response.data
+        if (bundle.substring(0, 'ERROR:'.length) === 'ERROR:') {
+          console.error(`Failed to get bundle: ${pkgName}`, bundle)
+          return '' // empty bundle will be filtered later
+        }
+
+        return bundle
+      })
+      .catch(function (error) {
+        console.error(`Failed to get single bundle: ${pkgName}`, error)
+      })
+  }
+
+  this.getDefaultPackages = function () {
+    return this.getAllPackageInfo()
+      .then(pkgSearchResults => {
+        return createDefaultPackages(pkgSearchResults, $sce)
+      })
+  }
+
+  this.getAllPackageInfoAndDefaultPackages = function () {
+    return this.getAllPackageInfo()
+      .then(pkgSearchResults => {
+        return {
+          pkgSearchResults: pkgSearchResults,
+          defaultPackages: createDefaultPackages(pkgSearchResults, $sce),
+        }
+      })
+  }
+
+  /**
+   * get all package configs.
+   * @return { Promise<{name, Array<Object>}> }
+   */
+  this.getAllPackageConfigs = function () {
+    const promisedDefaultPackages = this.getDefaultPackages()
+    const promisedPersistedConfs =
+      $http.get(`${baseUrlSrv.getRestApiBase()}/helium/config`)
+      .then(function (response, status) {
+        return response.data.body
+      })
+
+    return Promise.all([promisedDefaultPackages, promisedPersistedConfs])
+      .then(values => {
+        const defaultPackages = values[0]
+        const persistedConfs = values[1]
+
+        return createAllPackageConfigs(defaultPackages, persistedConfs)
+      })
+      .catch(function (error) {
+        console.error('Failed to get all package configs', error)
+      })
+  }
+
+  /**
+   * get the package config which is persisted in server.
+   * @return { Promise<Array<Object>> }
+   */
+  this.getSinglePackageConfigs = function (pkg) {
+    const pkgName = pkg.name
+    // in case of local package, it will include `/`
+    const pkgArtifact = encodeURIComponent(pkg.artifact)
+
+    if (!pkgName || !pkgArtifact) {
+      console.error('Failed to fetch config for\n', pkg)
+      return Promise.resolve([])
+    }
+
+    const confUrl = `${baseUrlSrv.getRestApiBase()}/helium/config/${pkgName}/${pkgArtifact}`
+    const promisedConf = $http.get(confUrl)
+      .then(function (response, status) {
+        return response.data.body
+      })
+
+    return promisedConf.then(({confSpec, confPersisted}) => {
+      const merged = mergePersistedConfWithSpec(confPersisted, confSpec)
+      return merged
+    })
+  }
+
+  this.getSinglePackageConfigUsingMagic = function (magic) {
+    const pkgName = pkgNamePerMagic[magic]
+
+    const confUrl = `${baseUrlSrv.getRestApiBase()}/helium/spell/config/${pkgName}`
+    const promisedConf = $http.get(confUrl)
+      .then(function (response, status) {
+        return response.data.body
+      })
+
+    return promisedConf.then(({confSpec, confPersisted}) => {
+      const merged = mergePersistedConfWithSpec(confPersisted, confSpec)
+      return merged
+    })
+  }
+
+  const p = this.getAllEnabledPackages()
+    .then(enabledPackageSearchResults => {
+      const promises = enabledPackageSearchResults.map(packageSearchResult => {
+        const pkgName = packageSearchResult.pkg.name
+        return this.getSingleBundle(pkgName)
+      })
+
+      return Promise.all(promises)
+    })
+    .then(bundles => {
+      return bundles.reduce((acc, b) => {
+        // filter out empty bundle
+        if (b === '') { return acc }
+        acc.push(b)
+        return acc
+      }, [])
+    })
+
+  // load should be promise
+  this.load = p.then(availableBundles => {
+    // evaluate bundles
+    availableBundles.map(b => {
+      // eslint-disable-next-line no-eval
+      eval(b)
+    })
+
+    // extract bundles by type
+    heliumBundles.map(b => {
+      if (b.type === HeliumType.SPELL) {
+        const spell = new b.class() // eslint-disable-line new-cap
+        const pkgName = b.id
+        spellPerMagic[spell.getMagic()] = spell
+        pkgNamePerMagic[spell.getMagic()] = pkgName
+      } else if (b.type === HeliumType.VISUALIZATION) {
+        visualizationBundles.push(b)
+      }
+    })
+  })
+
+  this.init = function() {
+    this.getVisualizationPackageOrder()
+  }
+
+  // init
+  this.init()
+}

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/6bd6c708/zeppelin-web/src/app/home/home.controller.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/home/home.controller.js b/zeppelin-web/src/app/home/home.controller.js
index 83f9681..2cf8439 100644
--- a/zeppelin-web/src/app/home/home.controller.js
+++ b/zeppelin-web/src/app/home/home.controller.js
@@ -14,15 +14,16 @@
 
 angular.module('zeppelinWebApp').controller('HomeCtrl', HomeCtrl)
 
-function HomeCtrl ($scope, noteListDataFactory, websocketMsgSrv, $rootScope, arrayOrderingSrv,
-                  ngToast, noteActionSrv, TRASH_FOLDER_ID) {
+function HomeCtrl ($scope, noteListFactory, websocketMsgSrv, $rootScope, arrayOrderingSrv,
+                  ngToast, noteActionService, TRASH_FOLDER_ID) {
   'ngInject'
 
   ngToast.dismiss()
   let vm = this
-  vm.notes = noteListDataFactory
+  vm.notes = noteListFactory
   vm.websocketMsgSrv = websocketMsgSrv
   vm.arrayOrderingSrv = arrayOrderingSrv
+  vm.noteActionService = noteActionService
 
   vm.notebookHome = false
   vm.noteCustomHome = true
@@ -85,15 +86,15 @@ function HomeCtrl ($scope, noteListDataFactory, websocketMsgSrv, $rootScope, arr
   })
 
   $scope.renameNote = function (nodeId, nodePath) {
-    noteActionSrv.renameNote(nodeId, nodePath)
+    vm.noteActionService.renameNote(nodeId, nodePath)
   }
 
   $scope.moveNoteToTrash = function (noteId) {
-    noteActionSrv.moveNoteToTrash(noteId, false)
+    vm.noteActionService.moveNoteToTrash(noteId, false)
   }
 
   $scope.moveFolderToTrash = function (folderId) {
-    noteActionSrv.moveFolderToTrash(folderId)
+    vm.noteActionService.moveFolderToTrash(folderId)
   }
 
   $scope.restoreNote = function (noteId) {
@@ -105,27 +106,27 @@ function HomeCtrl ($scope, noteListDataFactory, websocketMsgSrv, $rootScope, arr
   }
 
   $scope.restoreAll = function () {
-    noteActionSrv.restoreAll()
+    vm.noteActionService.restoreAll()
   }
 
   $scope.renameFolder = function (node) {
-    noteActionSrv.renameFolder(node.id)
+    vm.noteActionService.renameFolder(node.id)
   }
 
   $scope.removeNote = function (noteId) {
-    noteActionSrv.removeNote(noteId, false)
+    vm.noteActionService.removeNote(noteId, false)
   }
 
   $scope.removeFolder = function (folderId) {
-    noteActionSrv.removeFolder(folderId)
+    vm.noteActionService.removeFolder(folderId)
   }
 
   $scope.emptyTrash = function () {
-    noteActionSrv.emptyTrash()
+    vm.noteActionService.emptyTrash()
   }
 
   $scope.clearAllParagraphOutput = function (noteId) {
-    noteActionSrv.clearAllParagraphOutput(noteId)
+    vm.noteActionService.clearAllParagraphOutput(noteId)
   }
 
   $scope.isFilterNote = function (note) {

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/6bd6c708/zeppelin-web/src/app/home/home.html
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/home/home.html b/zeppelin-web/src/app/home/home.html
index 7c1154f..1ab9718 100644
--- a/zeppelin-web/src/app/home/home.html
+++ b/zeppelin-web/src/app/home/home.html
@@ -36,10 +36,10 @@ limitations under the License.
           <div>
             <h5><a href="" data-toggle="modal" data-target="#noteImportModal" style="text-decoration: none;">
               <i style="font-size: 15px;" class="fa fa-upload"></i> Import note</a></h5>
-            <h5 ng-controller="NotenameCtrl as notenamectrl"><a href="" data-toggle="modal" data-target="#noteNameModal" style="text-decoration: none;" ng-click="notenamectrl.getInterpreterSettings()">
+            <h5 ng-controller="NoteCreateCtrl as noteCreateCtrl"><a href="" data-toggle="modal" data-target="#noteCreateModal" style="text-decoration: none;" ng-click="noteCreateCtrl.getInterpreterSettings()">
               <i style="font-size: 15px;" class="icon-notebook"></i> Create new note</a></h5>
             <ul id="notebook-names">
-              <li class="filter-names" ng-include="'components/filterNoteNames/filter-note-names.html'"></li>
+              <li class="filter-names" ng-include="'components/note-name-filter/note-name-filter.html'"></li>
               <li ng-repeat="note in home.notes.list | filter:query.q | orderBy:node:false:home.arrayOrderingSrv.noteComparator track by $index">
                 <i style="font-size: 10px;" class="icon-doc"></i>
                 <a style="text-decoration: none;" href="#/notebook/{{note.id}}">{{noteName(note)}}</a>

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/6bd6c708/zeppelin-web/src/app/home/notebook-template.html
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/home/notebook-template.html b/zeppelin-web/src/app/home/notebook-template.html
index 4c287de..76602aa 100644
--- a/zeppelin-web/src/app/home/notebook-template.html
+++ b/zeppelin-web/src/app/home/notebook-template.html
@@ -58,8 +58,8 @@ limitations under the License.
       <a style="text-decoration: none; cursor: pointer;" ng-click="toggleFolderNode(node)">
         <i style="font-size: 10px;" ng-class="node.hidden ? 'fa fa-folder' : 'fa fa-folder-open'"></i> {{getNoteName(node)}}
       </a>
-      <a ng-if="!node.isTrash" href="" data-toggle="modal" data-target="#noteNameModal" style="text-decoration: none;"
-         ng-controller="NotenameCtrl as notenamectrl" ng-click="notenamectrl.getInterpreterSettings()" data-path="{{node.id}}">
+      <a ng-if="!node.isTrash" href="" data-toggle="modal" data-target="#noteCreateModal" style="text-decoration: none;"
+         ng-controller="NoteCreateCtrl as noteCreateCtrl" ng-click="noteCreateCtrl.getInterpreterSettings()" data-path="{{node.id}}">
         <i style="margin-left: 10px;"
            class="fa fa-plus notebook-list-btn" ng-show="showFolderButton"
            tooltip-placement="bottom" uib-tooltip="Create new note">

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/6bd6c708/zeppelin-web/src/app/home/notebook.html
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/home/notebook.html b/zeppelin-web/src/app/home/notebook.html
index 76214e9..f276a22 100644
--- a/zeppelin-web/src/app/home/notebook.html
+++ b/zeppelin-web/src/app/home/notebook.html
@@ -23,10 +23,10 @@ limitations under the License.
       </h4>
         <h5><a href="" data-toggle="modal" data-target="#noteImportModal" style="text-decoration: none;">
            <i style="font-size: 15px;" class="fa fa-upload"></i> Import note</a></h5>
-         <h5><a href="" data-toggle="modal" data-target="#noteNameModal" style="text-decoration: none;">
+         <h5><a href="" data-toggle="modal" data-target="#noteCreateModal" style="text-decoration: none;">
            <i style="font-size: 15px;" class="icon-notebook"></i> Create new note</a></h5>
        <ul id="notebook-names">
-         <li class="filter-names" ng-include="'components/filterNoteNames/filter-note-names.html'"></li>
+         <li class="filter-names" ng-include="'components/note-name-filter/note-name-filter.html'"></li>
          <div ng-if="!query.q || query.q === ''">
            <li ng-repeat="node in home.notes.root.children | orderBy:node:false:home.arrayOrderingSrv.noteComparator track by $index"
                ng-include src="'app/home/notebook-template.html'" ng-class="note_folder_renderer"></li>

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/6bd6c708/zeppelin-web/src/app/interpreter/interpreter-create.html
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/interpreter/interpreter-create.html b/zeppelin-web/src/app/interpreter/interpreter-create.html
new file mode 100644
index 0000000..b37f160
--- /dev/null
+++ b/zeppelin-web/src/app/interpreter/interpreter-create.html
@@ -0,0 +1,383 @@
+<!--
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<div>
+  <div class="row interpreter">
+    <div class="col-md-12" ng-show="showAddNewSetting">
+      <hr />
+      <div class="interpreterSettingAdd">
+        <h4>Create new interpreter</h4>
+
+        <div class="form-group" style="width:200px">
+          <b>Interpreter Name</b>
+          <input id="newInterpreterSettingName" input pu-elastic-input
+                 pu-elastic-input-minwidth="180px" ng-model="newInterpreterSetting.name" />
+        </div>
+
+        <b>Interpreter group</b>
+        <div class="form-group"
+             style="width:180px">
+          <select class="form-control input-sm" ng-model="newInterpreterSetting.group"
+                  ng-change="newInterpreterGroupChange()">
+            <option ng-repeat="availableInterpreter in availableInterpreters | unique: 'name'| orderBy: 'name'" value="{{availableInterpreter.name}}">
+              {{availableInterpreter.name}}
+            </option>
+          </select>
+        </div>
+
+        <div>
+          <h5>Option</h5>
+          <div class="row interpreter" style="margin-top: 5px;">
+            <div class="col-md-6">
+              The interpreter will be instantiated
+              <span class="btn-group">
+              <button type="button" class="btn btn-default btn-xs dropdown-toggle"
+                      data-toggle="dropdown">
+                {{getInterpreterRunningOption(setting.id)}} <span class="caret"></span>
+              </button>
+              <ul class="dropdown-menu" role="menu">
+                <li>
+                  <a style="cursor:pointer"
+                     ng-click="setInterpreterRunningOption(setting.id, 'shared', 'shared')">
+                    Globally
+                  </a>
+                </li>
+                <li>
+                  <a style="cursor:pointer"
+                     ng-click="setInterpreterRunningOption(setting.id, 'scoped', '')">
+                    Per Note
+                  </a>
+                </li>
+                <li ng-if="ticket.principal !== 'anonymous'">
+                  <a style="cursor:pointer"
+                     ng-click="setInterpreterRunningOption(setting.id, 'shared', 'scoped')">
+                    Per User
+                  </a>
+                </li>
+              </ul>
+            </span>
+              in
+              <span class="btn-group">
+              <button type="button" class="btn btn-default btn-xs dropdown-toggle"
+                      data-toggle="dropdown"
+                      ng-disabled="getInterpreterRunningOption(setting.id) === 'Globally'">
+                <span ng-if="getInterpreterRunningOption(setting.id) !== 'Per User'">
+                  {{getPerNoteOption(setting.id)}}
+                </span>
+                <span ng-if="getInterpreterRunningOption(setting.id) === 'Per User'">
+                  {{getPerUserOption(setting.id)}}
+                </span>
+                  <span class="caret"></span>
+              </button>
+              <ul class="dropdown-menu" role="menu">
+                <li
+                  ng-if="getInterpreterRunningOption(setting.id) === 'Globally'">
+                  <a style="cursor:pointer"
+                     uib-tooltip="Single interpreter instance are shared across notes"
+                     ng-click="setPerNoteOption(setting.id, 'shared')">
+                    shared per note
+                  </a>
+                </li>
+
+                <li>
+                  <a style="cursor:pointer"
+                     ng-if="getInterpreterRunningOption(setting.id) === 'Per Note'"
+                     uib-tooltip="Separate Interpreter instance for each note"
+                     ng-click="setPerNoteOption(setting.id, 'scoped')">
+                    scoped per note
+                  </a>
+                </li>
+                <li>
+                  <a style="cursor:pointer"
+                     ng-if="getInterpreterRunningOption(setting.id) === 'Per User'"
+                     uib-tooltip="Separate Interpreter instance for each note"
+                     ng-click="setPerUserOption(setting.id, 'scoped')">
+                    scoped per user
+                  </a>
+                </li>
+
+                <li>
+                  <a style="cursor:pointer"
+                     ng-if="getInterpreterRunningOption(setting.id) === 'Per Note'"
+                     uib-tooltip="Separate Interpreter process for each note"
+                     ng-click="setPerNoteOption(setting.id, 'isolated')">
+                    isolated per note
+                  </a>
+                </li>
+                <li>
+                  <a style="cursor:pointer"
+                     ng-if="getInterpreterRunningOption(setting.id) === 'Per User'"
+                     uib-tooltip="Separate Interpreter process for each note"
+                     ng-click="setPerUserOption(setting.id, 'isolated')">
+                    isolated per user
+                  </a>
+                </li>
+              </ul>
+            </span>
+              process
+              <a class="fa fa-info-circle interpreter-binding-mode-info-link"
+                 aria-hidden="true"
+                 uib-tooltip="Can manage interpreter sessions differently by setting this option. Click this button to learn more"
+                 ng-href="{{getInterpreterBindingModeDocsLink()}}" target="_blank"></a>
+            <span ng-if="getInterpreterRunningOption(setting.id) === 'Per User' && ticket.principal !== 'anonymous'">
+              <span ng-if="getPerNoteOption(setting.id) === 'shared'">
+                <button type="button" class="btn btn-default btn-xs"
+                        ng-click="setPerNoteOption(setting.id, 'scoped')"
+                        data-toggle="dropdown">
+                  <i class="fa fa-plus"></i>
+                </button>
+              </span>
+            </span>
+            </div>
+          </div>
+          <div class="row interpreter"
+               style="margin-top: 6px;"
+               ng-if="getInterpreterRunningOption(setting.id) === 'Per User'
+                      && ticket.principal !== 'anonymous'
+                      && getPerNoteOption(setting.id) !== 'shared'">
+            <div class="col-md-12">
+              <span>
+                <span class="hidden-xs" style="padding-left: 190px;">And </span>
+                <span class="visible-xs" style="padding-left: 0px;">And </span>
+                <span class="btn-group">
+                  <button type="button" class="btn btn-default btn-xs dropdown-toggle"
+                          data-toggle="dropdown"
+                          ng-disabled="true">
+                    <span>
+                      Per Note
+                    </span>
+                    <span class="caret"></span>
+                  </button>
+                </span>
+                in
+                <span class="btn-group">
+                  <button type="button" class="btn btn-default btn-xs dropdown-toggle"
+                          data-toggle="dropdown">
+                    <span>
+                      {{getPerNoteOption(setting.id)}}
+                    </span>
+                    <span class="caret"></span>
+                  </button>
+                  <ul class="dropdown-menu" role="menu">
+                    <li>
+                      <a style="cursor:pointer"
+                         uib-tooltip="Separate Interpreter instance for each note"
+                         ng-click="setPerNoteOption(setting.id, 'scoped')">
+                        scoped per note
+                      </a>
+                    </li>
+                    <li>
+                      <a style="cursor:pointer"
+                         uib-tooltip="Separate Interpreter process for each note"
+                         ng-click="setPerNoteOption(setting.id, 'isolated')">
+                        isolated per note
+                      </a>
+                    </li>
+                  </ul>
+                </span>
+                process.
+                <button type="button" class="btn btn-default btn-xs"
+                        ng-click="setPerNoteOption(setting.id, 'shared')"
+                        data-toggle="dropdown">
+                  <i class="fa fa-minus"></i>
+                </button>
+              </span>
+            </div>
+          </div>
+        </div>
+        <div class="row interpreter" style="margin-top: 5px;"
+             ng-show="getInterpreterRunningOption(setting.id)=='Per User' && getPerUserOption(setting.id)=='isolated'">
+          <div class="col-md-12">
+            <div class="checkbox remove-margin-top-bottom">
+          <span class="input-group" style="line-height:30px;">
+            <label>
+              <input type="checkbox" style="width:20px" ng-model="newInterpreterSetting.option.isUserImpersonate" />
+                User Impersonate
+            </label>
+          </span>
+            </div>
+          </div>
+        </div>
+        <div class="row interpreter">
+          <div class="col-md-12">
+            <div class="checkbox remove-margin-top-bottom">
+          <span class="input-group" style="line-height:30px;">
+            <label>
+              <input type="checkbox" style="width:20px" id="isExistingProcess" ng-model="newInterpreterSetting.option.isExistingProcess"/>
+              Connect to existing process
+            </label>
+          </span>
+            </div>
+          </div>
+        </div>
+        <div class="row interpreter" ng-show="newInterpreterSetting.option.isExistingProcess" >
+          <div class="col-md-12">
+            <b>Host</b>
+            <input id="newInterpreterSettingHost" input pu-elastic-input
+                   pu-elastic-input-minwidth="180px" ng-model="newInterpreterSetting.option.host"/>
+          </div>
+          <div class="col-md-12">
+            <b>Port</b>
+            <input id="newInterpreterSettingPort" input pu-elastic-input
+                   pu-elastic-input-minwidth="180px" ng-model="newInterpreterSetting.option.port"/>
+          </div>
+        </div>
+        <div class="row interpreter">
+          <div class="col-md-12">
+            <div class="checkbox remove-margin-top-bottom">
+          <span class="input-group" style="line-height:30px;">
+            <label>
+              <input type="checkbox" style="width:20px !important" id="idShowPermission" ng-click="togglePermissions('newInterpreter')" ng-model="newInterpreterSetting.option.setPermission"/>
+               Set permission
+            </label>
+          </span>
+            </div>
+          </div>
+        </div>
+        <br/>
+
+        <div class="row interpreter">
+          <div class="col-md-12">
+            <!-- permissions -->
+            <div ng-show="newInterpreterSetting.option.setPermission" class="permissionsForm">
+              <div>
+                <p>
+                  Enter comma separated users and groups in the fields. <br />
+                  Empty field (*) implies anyone can run this interpreter.
+                </p>
+                <div>
+                  <span class="owners">Owners </span>
+                  <select id="newInterpreterOwners" class="form-control" multiple="multiple">
+                    <option ng-repeat="owner in newInterpreterSetting.option.owners" selected="selected">{{owner}}</option>
+                  </select>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <div>
+          <h5>Properties</h5>
+          <table class="table table-striped properties">
+            <tr>
+              <th>name</th>
+              <th>value</th>
+              <th>action</th>
+              <th>description</th>
+            </tr>
+            <tr ng-repeat="key in newInterpreterSetting.properties | sortByKey">
+              <td>{{key}}</td>
+              <td style="vertical-align: middle;">
+                <textarea ng-if="newInterpreterSetting.properties[key].type === 'textarea'"
+                          msd-elastic ng-model="newInterpreterSetting.properties[key].value"></textarea>
+                <input ng-if="newInterpreterSetting.properties[key].type === 'string'"
+                       type="text" msd-elastic ng-model="newInterpreterSetting.properties[key].value" />
+                <input ng-if="newInterpreterSetting.properties[key].type === 'number'"
+                       type="text" number-widget msd-elastic ng-model="newInterpreterSetting.properties[key].value" />
+                <input ng-if="newInterpreterSetting.properties[key].type === 'url'"
+                       type="text" msd-elastic ng-model="newInterpreterSetting.properties[key].value" />
+                <input ng-if="newInterpreterSetting.properties[key].type === 'password'"
+                       type="password" msd-elastic ng-model="newInterpreterSetting.properties[key].value" />
+                <input ng-if="newInterpreterSetting.properties[key].type === 'boolean'"
+                       type="checkbox" msd-elastic ng-model="newInterpreterSetting.properties[key].value" />
+              </td>
+              <td style="vertical-align: middle;">
+                <button class="btn btn-default btn-sm fa fa-remove" ng-click="removeInterpreterProperty(key)">
+                </button>
+              </td>
+              <td style="vertical-align: middle;">
+                {{newInterpreterSetting.properties[key].description}}
+              </td>
+            </tr>
+
+            <tr>
+              <td style="vertical-align: middle;">
+                <input pu-elastic-input pu-elastic-input-minwidth="180px"
+                       ng-model="newInterpreterSetting.propertyKey" />
+              </td>
+              <td style="vertical-align: middle;" ng-switch on="newInterpreterSetting.propertyType">
+                <textarea ng-switch-default msd-elastic ng-model="newInterpreterSetting.propertyValue"></textarea>
+                <input ng-switch-when="string" type="text" msd-elastic ng-model="newInterpreterSetting.propertyValue" />
+                <input ng-switch-when="number" type="text" number-widget msd-elastic ng-model="newInterpreterSetting.propertyValue" />
+                <input ng-switch-when="url" type="text" msd-elastic ng-model="newInterpreterSetting.propertyValue" />
+                <input ng-switch-when="password" type="password" msd-elastic ng-model="newInterpreterSetting.propertyValue" />
+                <input ng-switch-when="checkbox" type="checkbox" msd-elastic ng-model="newInterpreterSetting.propertyValue" />
+              </td>
+              <td style="vertical-align: middle;">
+                <select msd-elastic ng-model="newInterpreterSetting.propertyType" ng-init="newInterpreterSetting.propertyType=interpreterPropertyTypes[0]"
+                        ng-options="item for item in interpreterPropertyTypes" ng-change="defaultValueByType(newInterpreterSetting)">
+                </select>
+                <button class="btn btn-default btn-sm fa fa-plus" ng-click="addNewInterpreterProperty()">
+                </button>
+              </td>
+              <td></td>
+            </tr>
+          </table>
+        </div>
+
+        <div>
+          <h5>Dependencies</h5>
+          <table class="table table-striped properties">
+            <tr>
+              <th>artifact</th>
+              <th>exclude</th>
+              <th>action</th>
+            </tr>
+
+            <tr ng-repeat="dep in newInterpreterSetting.dependencies">
+              <td>
+                <input ng-model="dep.groupArtifactVersion" style="width:100%" />
+              </td>
+              <td>
+                <textarea msd-elastic ng-model="dep.exclusions"
+                          ng-list
+                          placeholder="(Optional) comma separated groupId:artifactId list">
+                </textarea>
+              </td>
+              <td>
+                <button class="btn btn-default btn-sm fa fa-remove"
+                     ng-click="removeInterpreterDependency(dep.groupArtifactVersion)">
+                </button>
+              </td>
+            </tr>
+
+            <tr>
+              <td>
+                <input ng-model="newInterpreterSetting.depArtifact"
+                       placeholder="groupId:artifactId:version or local file path"
+                       style="width: 100%" />
+              </td>
+              <td>
+                <textarea msd-elastic ng-model="newInterpreterSetting.depExclude"
+                          ng-list
+                          placeholder="(Optional) comma separated groupId:artifactId list">
+                </textarea>
+              </td>
+              <td>
+                <button class="btn btn-default btn-sm fa fa-plus" ng-click="addNewInterpreterDependency()">
+                </button>
+              </td>
+            </tr>
+          </table>
+        </div>
+
+        <span class="btn btn-primary" ng-click="addNewInterpreterSetting()">
+          Save
+        </span>
+        <span class="btn btn-default" ng-click="cancelInterpreterSetting()">
+          Cancel
+        </span>
+      </div>
+    </div>
+  </div>
+</div>

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/6bd6c708/zeppelin-web/src/app/interpreter/interpreter-create/interpreter-create.html
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/interpreter/interpreter-create/interpreter-create.html b/zeppelin-web/src/app/interpreter/interpreter-create/interpreter-create.html
deleted file mode 100644
index 1fa60f8..0000000
--- a/zeppelin-web/src/app/interpreter/interpreter-create/interpreter-create.html
+++ /dev/null
@@ -1,383 +0,0 @@
-<!--
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
--->
-<div>
-  <div class="row interpreter">
-    <div class="col-md-12" ng-show="showAddNewSetting">
-      <hr />
-      <div class="interpreterSettingAdd">
-        <h4>Create new interpreter</h4>
-
-        <div class="form-group" style="width:200px">
-          <b>Interpreter Name</b>
-          <input id="newInterpreterSettingName" input pu-elastic-input
-                 pu-elastic-input-minwidth="180px" ng-model="newInterpreterSetting.name" />
-        </div>
-
-        <b>Interpreter group</b>
-        <div class="form-group"
-             style="width:180px">
-          <select class="form-control input-sm" ng-model="newInterpreterSetting.group"
-                  ng-change="newInterpreterGroupChange()">
-            <option ng-repeat="availableInterpreter in availableInterpreters | unique: 'name'| orderBy: 'name'" value="{{availableInterpreter.name}}">
-              {{availableInterpreter.name}}
-            </option>
-          </select>
-        </div>
-
-        <div>
-          <h5>Option</h5>
-          <div class="row interpreter" style="margin-top: 5px;">
-            <div class="col-md-6">
-              The interpreter will be instantiated
-              <span class="btn-group">
-              <button type="button" class="btn btn-default btn-xs dropdown-toggle"
-                      data-toggle="dropdown">
-                {{getInterpreterRunningOption(setting.id)}} <span class="caret"></span>
-              </button>
-              <ul class="dropdown-menu" role="menu">
-                <li>
-                  <a style="cursor:pointer"
-                     ng-click="setInterpreterRunningOption(setting.id, 'shared', 'shared')">
-                    Globally
-                  </a>
-                </li>
-                <li>
-                  <a style="cursor:pointer"
-                     ng-click="setInterpreterRunningOption(setting.id, 'scoped', '')">
-                    Per Note
-                  </a>
-                </li>
-                <li ng-if="ticket.principal !== 'anonymous'">
-                  <a style="cursor:pointer"
-                     ng-click="setInterpreterRunningOption(setting.id, 'shared', 'scoped')">
-                    Per User
-                  </a>
-                </li>
-              </ul>
-            </span>
-              in
-              <span class="btn-group">
-              <button type="button" class="btn btn-default btn-xs dropdown-toggle"
-                      data-toggle="dropdown"
-                      ng-disabled="getInterpreterRunningOption(setting.id) === 'Globally'">
-                <span ng-if="getInterpreterRunningOption(setting.id) !== 'Per User'">
-                  {{getPerNoteOption(setting.id)}}
-                </span>
-                <span ng-if="getInterpreterRunningOption(setting.id) === 'Per User'">
-                  {{getPerUserOption(setting.id)}}
-                </span>
-                  <span class="caret"></span>
-              </button>
-              <ul class="dropdown-menu" role="menu">
-                <li
-                  ng-if="getInterpreterRunningOption(setting.id) === 'Globally'">
-                  <a style="cursor:pointer"
-                     uib-tooltip="Single interpreter instance are shared across notes"
-                     ng-click="setPerNoteOption(setting.id, 'shared')">
-                    shared per note
-                  </a>
-                </li>
-
-                <li>
-                  <a style="cursor:pointer"
-                     ng-if="getInterpreterRunningOption(setting.id) === 'Per Note'"
-                     uib-tooltip="Separate Interpreter instance for each note"
-                     ng-click="setPerNoteOption(setting.id, 'scoped')">
-                    scoped per note
-                  </a>
-                </li>
-                <li>
-                  <a style="cursor:pointer"
-                     ng-if="getInterpreterRunningOption(setting.id) === 'Per User'"
-                     uib-tooltip="Separate Interpreter instance for each note"
-                     ng-click="setPerUserOption(setting.id, 'scoped')">
-                    scoped per user
-                  </a>
-                </li>
-
-                <li>
-                  <a style="cursor:pointer"
-                     ng-if="getInterpreterRunningOption(setting.id) === 'Per Note'"
-                     uib-tooltip="Separate Interpreter process for each note"
-                     ng-click="setPerNoteOption(setting.id, 'isolated')">
-                    isolated per note
-                  </a>
-                </li>
-                <li>
-                  <a style="cursor:pointer"
-                     ng-if="getInterpreterRunningOption(setting.id) === 'Per User'"
-                     uib-tooltip="Separate Interpreter process for each note"
-                     ng-click="setPerUserOption(setting.id, 'isolated')">
-                    isolated per user
-                  </a>
-                </li>
-              </ul>
-            </span>
-              process
-              <a class="fa fa-info-circle interpreter-binding-mode-info-link"
-                 aria-hidden="true"
-                 uib-tooltip="Can manage interpreter sessions differently by setting this option. Click this button to learn more"
-                 ng-href="{{getInterpreterBindingModeDocsLink()}}" target="_blank"></a>
-            <span ng-if="getInterpreterRunningOption(setting.id) === 'Per User' && ticket.principal !== 'anonymous'">
-              <span ng-if="getPerNoteOption(setting.id) === 'shared'">
-                <button type="button" class="btn btn-default btn-xs"
-                        ng-click="setPerNoteOption(setting.id, 'scoped')"
-                        data-toggle="dropdown">
-                  <i class="fa fa-plus"></i>
-                </button>
-              </span>
-            </span>
-            </div>
-          </div>
-          <div class="row interpreter"
-               style="margin-top: 6px;"
-               ng-if="getInterpreterRunningOption(setting.id) === 'Per User'
-                      && ticket.principal !== 'anonymous'
-                      && getPerNoteOption(setting.id) !== 'shared'">
-            <div class="col-md-12">
-              <span>
-                <span class="hidden-xs" style="padding-left: 190px;">And </span>
-                <span class="visible-xs" style="padding-left: 0px;">And </span>
-                <span class="btn-group">
-                  <button type="button" class="btn btn-default btn-xs dropdown-toggle"
-                          data-toggle="dropdown"
-                          ng-disabled="true">
-                    <span>
-                      Per Note
-                    </span>
-                    <span class="caret"></span>
-                  </button>
-                </span>
-                in
-                <span class="btn-group">
-                  <button type="button" class="btn btn-default btn-xs dropdown-toggle"
-                          data-toggle="dropdown">
-                    <span>
-                      {{getPerNoteOption(setting.id)}}
-                    </span>
-                    <span class="caret"></span>
-                  </button>
-                  <ul class="dropdown-menu" role="menu">
-                    <li>
-                      <a style="cursor:pointer"
-                         uib-tooltip="Separate Interpreter instance for each note"
-                         ng-click="setPerNoteOption(setting.id, 'scoped')">
-                        scoped per note
-                      </a>
-                    </li>
-                    <li>
-                      <a style="cursor:pointer"
-                         uib-tooltip="Separate Interpreter process for each note"
-                         ng-click="setPerNoteOption(setting.id, 'isolated')">
-                        isolated per note
-                      </a>
-                    </li>
-                  </ul>
-                </span>
-                process.
-                <button type="button" class="btn btn-default btn-xs"
-                        ng-click="setPerNoteOption(setting.id, 'shared')"
-                        data-toggle="dropdown">
-                  <i class="fa fa-minus"></i>
-                </button>
-              </span>
-            </div>
-          </div>
-        </div>
-        <div class="row interpreter" style="margin-top: 5px;"
-             ng-show="getInterpreterRunningOption(setting.id)=='Per User' && getPerUserOption(setting.id)=='isolated'">
-          <div class="col-md-12">
-            <div class="checkbox remove-margin-top-bottom">
-          <span class="input-group" style="line-height:30px;">
-            <label>
-              <input type="checkbox" style="width:20px" ng-model="newInterpreterSetting.option.isUserImpersonate" />
-                User Impersonate
-            </label>
-          </span>
-            </div>
-          </div>
-        </div>
-        <div class="row interpreter">
-          <div class="col-md-12">
-            <div class="checkbox remove-margin-top-bottom">
-          <span class="input-group" style="line-height:30px;">
-            <label>
-              <input type="checkbox" style="width:20px" id="isExistingProcess" ng-model="newInterpreterSetting.option.isExistingProcess"/>
-              Connect to existing process
-            </label>
-          </span>
-            </div>
-          </div>
-        </div>
-        <div class="row interpreter" ng-show="newInterpreterSetting.option.isExistingProcess" >
-          <div class="col-md-12">
-            <b>Host</b>
-            <input id="newInterpreterSettingHost" input pu-elastic-input
-                   pu-elastic-input-minwidth="180px" ng-model="newInterpreterSetting.option.host"/>
-          </div>
-          <div class="col-md-12">
-            <b>Port</b>
-            <input id="newInterpreterSettingPort" input pu-elastic-input
-                   pu-elastic-input-minwidth="180px" ng-model="newInterpreterSetting.option.port"/>
-          </div>
-        </div>
-        <div class="row interpreter">
-          <div class="col-md-12">
-            <div class="checkbox remove-margin-top-bottom">
-          <span class="input-group" style="line-height:30px;">
-            <label>
-              <input type="checkbox" style="width:20px !important" id="idShowPermission" ng-click="togglePermissions('newInterpreter')" ng-model="newInterpreterSetting.option.setPermission"/>
-               Set permission
-            </label>
-          </span>
-            </div>
-          </div>
-        </div>
-        <br/>
-
-        <div class="row interpreter">
-          <div class="col-md-12">
-            <!-- permissions -->
-            <div ng-show="newInterpreterSetting.option.setPermission" class="permissionsForm">
-              <div>
-                <p>
-                  Enter comma separated users and groups in the fields. <br />
-                  Empty field (*) implies anyone can run this interpreter.
-                </p>
-                <div>
-                  <span class="owners">Owners </span>
-                  <select id="newInterpreterOwners" class="form-control" multiple="multiple">
-                    <option ng-repeat="owner in newInterpreterSetting.option.owners" selected="selected">{{owner}}</option>
-                  </select>
-                </div>
-              </div>
-            </div>
-          </div>
-        </div>
-
-        <div>
-          <h5>Properties</h5>
-          <table class="table table-striped properties">
-            <tr>
-              <th>name</th>
-              <th>value</th>
-              <th>action</th>
-              <th>description</th>
-            </tr>
-            <tr ng-repeat="key in newInterpreterSetting.properties | sortByKey">
-              <td>{{key}}</td>
-              <td style="vertical-align: middle;">
-                <textarea ng-if="newInterpreterSetting.properties[key].type === 'textarea'"
-                          msd-elastic ng-model="newInterpreterSetting.properties[key].value"></textarea>
-                <input ng-if="newInterpreterSetting.properties[key].type === 'string'"
-                       type="text" msd-elastic ng-model="newInterpreterSetting.properties[key].value" />
-                <input ng-if="newInterpreterSetting.properties[key].type === 'number'"
-                       type="text" widget-number msd-elastic ng-model="newInterpreterSetting.properties[key].value" />
-                <input ng-if="newInterpreterSetting.properties[key].type === 'url'"
-                       type="text" msd-elastic ng-model="newInterpreterSetting.properties[key].value" />
-                <input ng-if="newInterpreterSetting.properties[key].type === 'password'"
-                       type="password" msd-elastic ng-model="newInterpreterSetting.properties[key].value" />
-                <input ng-if="newInterpreterSetting.properties[key].type === 'boolean'"
-                       type="checkbox" msd-elastic ng-model="newInterpreterSetting.properties[key].value" />
-              </td>
-              <td style="vertical-align: middle;">
-                <button class="btn btn-default btn-sm fa fa-remove" ng-click="removeInterpreterProperty(key)">
-                </button>
-              </td>
-              <td style="vertical-align: middle;">
-                {{newInterpreterSetting.properties[key].description}}
-              </td>
-            </tr>
-
-            <tr>
-              <td style="vertical-align: middle;">
-                <input pu-elastic-input pu-elastic-input-minwidth="180px"
-                       ng-model="newInterpreterSetting.propertyKey" />
-              </td>
-              <td style="vertical-align: middle;" ng-switch on="newInterpreterSetting.propertyType">
-                <textarea ng-switch-default msd-elastic ng-model="newInterpreterSetting.propertyValue"></textarea>
-                <input ng-switch-when="string" type="text" msd-elastic ng-model="newInterpreterSetting.propertyValue" />
-                <input ng-switch-when="number" type="text" widget-number msd-elastic ng-model="newInterpreterSetting.propertyValue" />
-                <input ng-switch-when="url" type="text" msd-elastic ng-model="newInterpreterSetting.propertyValue" />
-                <input ng-switch-when="password" type="password" msd-elastic ng-model="newInterpreterSetting.propertyValue" />
-                <input ng-switch-when="checkbox" type="checkbox" msd-elastic ng-model="newInterpreterSetting.propertyValue" />
-              </td>
-              <td style="vertical-align: middle;">
-                <select msd-elastic ng-model="newInterpreterSetting.propertyType" ng-init="newInterpreterSetting.propertyType=interpreterPropertyTypes[0]"
-                        ng-options="item for item in interpreterPropertyTypes" ng-change="defaultValueByType(newInterpreterSetting)">
-                </select>
-                <button class="btn btn-default btn-sm fa fa-plus" ng-click="addNewInterpreterProperty()">
-                </button>
-              </td>
-              <td></td>
-            </tr>
-          </table>
-        </div>
-
-        <div>
-          <h5>Dependencies</h5>
-          <table class="table table-striped properties">
-            <tr>
-              <th>artifact</th>
-              <th>exclude</th>
-              <th>action</th>
-            </tr>
-
-            <tr ng-repeat="dep in newInterpreterSetting.dependencies">
-              <td>
-                <input ng-model="dep.groupArtifactVersion" style="width:100%" />
-              </td>
-              <td>
-                <textarea msd-elastic ng-model="dep.exclusions"
-                          ng-list
-                          placeholder="(Optional) comma separated groupId:artifactId list">
-                </textarea>
-              </td>
-              <td>
-                <button class="btn btn-default btn-sm fa fa-remove"
-                     ng-click="removeInterpreterDependency(dep.groupArtifactVersion)">
-                </button>
-              </td>
-            </tr>
-
-            <tr>
-              <td>
-                <input ng-model="newInterpreterSetting.depArtifact"
-                       placeholder="groupId:artifactId:version or local file path"
-                       style="width: 100%" />
-              </td>
-              <td>
-                <textarea msd-elastic ng-model="newInterpreterSetting.depExclude"
-                          ng-list
-                          placeholder="(Optional) comma separated groupId:artifactId list">
-                </textarea>
-              </td>
-              <td>
-                <button class="btn btn-default btn-sm fa fa-plus" ng-click="addNewInterpreterDependency()">
-                </button>
-              </td>
-            </tr>
-          </table>
-        </div>
-
-        <span class="btn btn-primary" ng-click="addNewInterpreterSetting()">
-          Save
-        </span>
-        <span class="btn btn-default" ng-click="cancelInterpreterSetting()">
-          Cancel
-        </span>
-      </div>
-    </div>
-  </div>
-</div>

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/6bd6c708/zeppelin-web/src/app/interpreter/interpreter-item.directive.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/interpreter/interpreter-item.directive.js b/zeppelin-web/src/app/interpreter/interpreter-item.directive.js
new file mode 100644
index 0000000..4bde44d
--- /dev/null
+++ b/zeppelin-web/src/app/interpreter/interpreter-item.directive.js
@@ -0,0 +1,31 @@
+/*
+ * Licensed 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.
+ */
+
+angular.module('zeppelinWebApp').directive('interpreterItem', InterpreterItemDirective)
+
+function InterpreterItemDirective ($timeout) {
+  'ngInject'
+
+  return {
+    restrict: 'A',
+    link: function (scope, element, attr) {
+      if (scope.$last === true) {
+        $timeout(function () {
+          let id = 'ngRenderFinished'
+          scope.$emit(id)
+        })
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/6bd6c708/zeppelin-web/src/app/interpreter/interpreter.html
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/interpreter/interpreter.html b/zeppelin-web/src/app/interpreter/interpreter.html
index 617ce38..c1d90cc 100644
--- a/zeppelin-web/src/app/interpreter/interpreter.html
+++ b/zeppelin-web/src/app/interpreter/interpreter.html
@@ -72,7 +72,7 @@ limitations under the License.
           </a>
         </li>
         <li class="liVertical">
-          <div ng-include src="'components/repository-create/repository-dialog.html'"></div>
+          <div ng-include src="'app/interpreter/repository-create.html'"></div>
           <div class="btn btn-default"
                data-toggle="modal"
                data-target="#repoModal">
@@ -83,11 +83,11 @@ limitations under the License.
     </div>
   </div>
 
-  <div ng-include src="'app/interpreter/interpreter-create/interpreter-create.html'"></div>
+  <div ng-include src="'app/interpreter/interpreter-create.html'"></div>
 </div>
 
-<div class="box width-full"
-     ng-repeat="setting in interpreterSettings | orderBy: 'name' | filter: {name:searchInterpreter} " interpreter-directive>
+<div interpreter-item class="box width-full"
+     ng-repeat="setting in interpreterSettings | orderBy: 'name' | filter: {name:searchInterpreter}" >
   <div id="{{setting.name | lowercase}}">
     <div class="row interpreter">
 
@@ -443,7 +443,7 @@ limitations under the License.
             <td style="vertical-align: middle;" ng-switch on="setting.propertyType">
               <textarea ng-switch-default msd-elastic ng-model="setting.propertyValue"></textarea>
               <input ng-switch-when="string" type="text" msd-elastic ng-model="setting.propertyValue"/>
-              <input ng-switch-when="number" type="text" msd-elastic ng-model="setting.propertyValue" widget-number />
+              <input ng-switch-when="number" type="text" msd-elastic ng-model="setting.propertyValue" number-widget />
               <input ng-switch-when="url" type="text" msd-elastic ng-model="setting.propertyValue" />
               <input ng-switch-when="password" type="password" msd-elastic ng-model="setting.propertyValue" />
               <input ng-switch-when="checkbox" type="checkbox" msd-elastic ng-model="setting.propertyValue" />


Mime
View raw message