zeppelin-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From anthonycorba...@apache.org
Subject zeppelin git commit: [ZEPPELIN-1575] Notebook Repo settings UI
Date Thu, 27 Oct 2016 08:42:54 GMT
Repository: zeppelin
Updated Branches:
  refs/heads/master 0af86e393 -> 824fd955f


[ZEPPELIN-1575] Notebook Repo settings UI

### What is this PR for?
The idea behind this feature is to bring flexibility to the user to let him configure his repo via UI without restarting Apache Zeppelin.
This is flexible enough to handle most of the basic needs, but more complex case can be added in the future. I will not implement all NotebookRepo, this should be done in different PR since it will depend on special needs. I will just provide a simple example with VFS notebook repo.

***NB***: this scope of the PR doesn't include save change in the configuration file of zeppelin as well as implementation of every notebook repo, git, s3 etcetc, this will be added later if needed.

### What type of PR is it?
 * **Improvement**

### Todos
* [x] - Implement backend.
* [x] - Implement frontend.
* [x] - Implement LocalVFS directory change as exemple.

### What is the Jira issue?
 * [ZEPPELIN-1575](https://issues.apache.org/jira/browse/ZEPPELIN-1575)

### How should this be tested?
Go to notebook repo setting page, edit the notebook path and save.

### Screenshots (if appropriate)
![notebook_repo_settings](https://cloud.githubusercontent.com/assets/3139557/19643248/583dbc2a-9a24-11e6-8f14-7deda9c443f2.gif)

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

Author: Anthony Corbacho <corbacho.anthony@gmail.com>
Author: Damien CORNEAU <corneadoug@gmail.com>

Closes #1553 from anthonycorbacho/feat/NotebookRepoSettings and squashes the following commits:

6d63832 [Anthony Corbacho] Fix style and rebase mistake...
6f17b18 [Anthony Corbacho] Add tests :: API and REST API
23ad18e [Anthony Corbacho] Rename NotebookRepoSettings to NotebookRepoSettingsInfo
42fd3b5 [Anthony Corbacho] Apply broadcasting note list after sucessful update for notebook repo
7fc1f37 [Anthony Corbacho] Change textarea size to 100 row
357b659 [Anthony Corbacho] Fix ASF header
d49bfe1 [Anthony Corbacho] Fix style
517ebc8 [Anthony Corbacho] Fix style
dc7fc50 [Anthony Corbacho] Fix indentation
0b4b74a [Anthony Corbacho] Add exemple of how to change local notebook repo FS
2d67bc8 [Anthony Corbacho] Added method from notebook repo interface
5a13e62 [Anthony Corbacho] Added 2 new methos in notebookRepoSync, exposed get and update notebook repo
8e890d3 [Anthony Corbacho] Add new wrapper for notebook repo PLUS settings
53346d7 [Anthony Corbacho] Add new methods in notebookRepo :: get and set settings in notebook
23912ce [Anthony Corbacho] Added new wrapper class
4d745ab [Damien CORNEAU] Improve ux
05a64de [Damien CORNEAU] Improve edit repo
9c1f999 [Damien CORNEAU] Implement edit
6a97512 [Damien CORNEAU] Get Repos from api
76479b3 [Damien CORNEAU] Change presentation of notebook settings
8075b04 [Damien CORNEAU] Render mocked list of notebook repo settings
054d2aa [Damien CORNEAU] Base structure for notebook repo ui
30d29cd [Anthony Corbacho] Update zeppelin server and add NotebookRepo rest api to the singleton
f7c7bf2 [Anthony Corbacho] Update method :: updateRepoSettings to create NotebookRepoSettingsRequest with Empty static object instead of null
a4d9749 [Anthony Corbacho] Added utility class for notebook re po payload deserialization
efe4c34 [Anthony Corbacho] Add new Rest api endpoint for notebook Repo settings


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

Branch: refs/heads/master
Commit: 824fd955fb60b75a28db4b4ec9fe6c64cca60638
Parents: 0af86e3
Author: Anthony Corbacho <corbacho.anthony@gmail.com>
Authored: Tue Oct 25 18:13:42 2016 +0900
Committer: Anthony Corbacho <corbacho.anthony@gmail.com>
Committed: Thu Oct 27 17:42:41 2016 +0900

----------------------------------------------------------------------
 .../zeppelin/rest/NotebookRepoRestApi.java      | 115 ++++++++++++++++++
 .../message/NotebookRepoSettingsRequest.java    |  49 ++++++++
 .../apache/zeppelin/server/ZeppelinServer.java  |  25 +++-
 .../zeppelin/rest/NotebookRepoRestApiTest.java  | 120 +++++++++++++++++++
 zeppelin-web/src/app/app.js                     |   5 +
 .../notebookRepos/notebookRepos.controller.js   |  91 ++++++++++++++
 .../src/app/notebookRepos/notebookRepos.html    |  98 +++++++++++++++
 zeppelin-web/src/components/navbar/navbar.html  |   1 +
 zeppelin-web/src/index.html                     |   1 +
 .../notebook/repo/AzureNotebookRepo.java        |  45 +++++--
 .../zeppelin/notebook/repo/NotebookRepo.java    |  17 +++
 .../notebook/repo/NotebookRepoSettingsInfo.java |  44 +++++++
 .../notebook/repo/NotebookRepoSync.java         |  56 ++++++++-
 .../notebook/repo/NotebookRepoWithSettings.java |  79 ++++++++++++
 .../zeppelin/notebook/repo/S3NotebookRepo.java  |  21 +++-
 .../zeppelin/notebook/repo/VFSNotebookRepo.java |  65 ++++++++--
 .../repo/zeppelinhub/ZeppelinHubRepo.java       |  13 ++
 .../notebook/repo/VFSNotebookRepoTest.java      |  30 ++++-
 18 files changed, 841 insertions(+), 34 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/zeppelin/blob/824fd955/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRepoRestApi.java
----------------------------------------------------------------------
diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRepoRestApi.java b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRepoRestApi.java
new file mode 100644
index 0000000..89cb47e
--- /dev/null
+++ b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRepoRestApi.java
@@ -0,0 +1,115 @@
+/*
+ * 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.
+ */
+package org.apache.zeppelin.rest;
+
+import java.util.Collections;
+import java.util.List;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.zeppelin.annotation.ZeppelinApi;
+import org.apache.zeppelin.notebook.repo.NotebookRepoSync;
+import org.apache.zeppelin.notebook.repo.NotebookRepoWithSettings;
+import org.apache.zeppelin.rest.message.NotebookRepoSettingsRequest;
+import org.apache.zeppelin.server.JsonResponse;
+import org.apache.zeppelin.socket.NotebookServer;
+import org.apache.zeppelin.user.AuthenticationInfo;
+import org.apache.zeppelin.utils.SecurityUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.gson.Gson;
+import com.google.gson.JsonSyntaxException;
+
+/**
+ * NoteRepo rest API endpoint.
+ *
+ */
+@Path("/notebook-repositories")
+@Produces("application/json")
+public class NotebookRepoRestApi {
+
+  private static final Logger LOG = LoggerFactory.getLogger(NotebookRepoRestApi.class);
+
+  private Gson gson = new Gson();
+  private NotebookRepoSync noteRepos;
+  private NotebookServer notebookWsServer;
+
+  public NotebookRepoRestApi() {}
+
+  public NotebookRepoRestApi(NotebookRepoSync noteRepos, NotebookServer notebookWsServer) {
+    this.noteRepos = noteRepos;
+    this.notebookWsServer = notebookWsServer;
+  }
+
+  /**
+   * List all notebook repository
+   */
+  @GET
+  @ZeppelinApi
+  public Response listRepoSettings() {
+    AuthenticationInfo subject = new AuthenticationInfo(SecurityUtils.getPrincipal());
+    LOG.info("Getting list of NoteRepo with Settings for user {}", subject.getUser());
+    List<NotebookRepoWithSettings> settings = noteRepos.getNotebookRepos(subject);
+    return new JsonResponse<>(Status.OK, "", settings).build();
+  }
+
+  /**
+   * Update a specific note repo.
+   *
+   * @param message
+   * @param settingId
+   * @return
+   */
+  @PUT
+  @ZeppelinApi
+  public Response updateRepoSetting(String payload) {
+    if (StringUtils.isBlank(payload)) {
+      return new JsonResponse<>(Status.NOT_FOUND, "", Collections.emptyMap()).build();
+    }
+    AuthenticationInfo subject = new AuthenticationInfo(SecurityUtils.getPrincipal());
+    NotebookRepoSettingsRequest newSettings = NotebookRepoSettingsRequest.EMPTY;
+    try {
+      newSettings = gson.fromJson(payload, NotebookRepoSettingsRequest.class);
+    } catch (JsonSyntaxException e) {
+      LOG.error("Cannot update notebook repo settings", e);
+      return new JsonResponse<>(Status.NOT_ACCEPTABLE, "",
+          ImmutableMap.of("error", "Invalid payload structure")).build();
+    }
+
+    if (NotebookRepoSettingsRequest.isEmpty(newSettings)) {
+      LOG.error("Invalid property");
+      return new JsonResponse<>(Status.NOT_ACCEPTABLE, "",
+          ImmutableMap.of("error", "Invalid payload")).build();
+    }
+    LOG.info("User {} is going to change repo setting", subject.getUser());
+    NotebookRepoWithSettings updatedSettings =
+        noteRepos.updateNotebookRepo(newSettings.name, newSettings.settings, subject);
+    if (!updatedSettings.isEmpty()) {
+      LOG.info("Broadcasting note list to user {}", subject.getUser());
+      notebookWsServer.broadcastReloadedNoteList(subject, null);
+    }
+    return new JsonResponse<>(Status.OK, "", updatedSettings).build();
+  }
+}

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/824fd955/zeppelin-server/src/main/java/org/apache/zeppelin/rest/message/NotebookRepoSettingsRequest.java
----------------------------------------------------------------------
diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/message/NotebookRepoSettingsRequest.java b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/message/NotebookRepoSettingsRequest.java
new file mode 100644
index 0000000..9884476
--- /dev/null
+++ b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/message/NotebookRepoSettingsRequest.java
@@ -0,0 +1,49 @@
+/*
+ * 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.
+ */
+package org.apache.zeppelin.rest.message;
+
+import java.util.Collections;
+import java.util.Map;
+
+import org.apache.commons.lang.StringUtils;
+
+/**
+ * Represent payload of a notebook repo settings.
+ */
+public class NotebookRepoSettingsRequest {
+
+  public static final NotebookRepoSettingsRequest EMPTY = new NotebookRepoSettingsRequest();
+
+  public String name;
+  public Map<String, String> settings;
+
+  public NotebookRepoSettingsRequest() {
+    name = StringUtils.EMPTY;
+    settings = Collections.emptyMap();
+  }
+
+  public boolean isEmpty() {
+    return this == EMPTY;
+  }
+
+  public static boolean isEmpty(NotebookRepoSettingsRequest repoSetting) {
+    if (repoSetting == null) {
+      return true;
+    }
+    return repoSetting.isEmpty();
+  }
+}

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/824fd955/zeppelin-server/src/main/java/org/apache/zeppelin/server/ZeppelinServer.java
----------------------------------------------------------------------
diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/server/ZeppelinServer.java b/zeppelin-server/src/main/java/org/apache/zeppelin/server/ZeppelinServer.java
index 162094e..5e8e3d5 100644
--- a/zeppelin-server/src/main/java/org/apache/zeppelin/server/ZeppelinServer.java
+++ b/zeppelin-server/src/main/java/org/apache/zeppelin/server/ZeppelinServer.java
@@ -22,9 +22,7 @@ import java.io.IOException;
 import java.util.EnumSet;
 import java.util.HashSet;
 import java.util.Set;
-import java.util.concurrent.ExecutorService;
 
-import javax.net.ssl.SSLContext;
 import javax.servlet.DispatcherType;
 import javax.ws.rs.core.Application;
 
@@ -37,9 +35,16 @@ import org.apache.zeppelin.helium.HeliumApplicationFactory;
 import org.apache.zeppelin.interpreter.InterpreterFactory;
 import org.apache.zeppelin.notebook.Notebook;
 import org.apache.zeppelin.notebook.NotebookAuthorization;
-import org.apache.zeppelin.notebook.repo.NotebookRepo;
 import org.apache.zeppelin.notebook.repo.NotebookRepoSync;
-import org.apache.zeppelin.rest.*;
+import org.apache.zeppelin.rest.ConfigurationsRestApi;
+import org.apache.zeppelin.rest.CredentialRestApi;
+import org.apache.zeppelin.rest.HeliumRestApi;
+import org.apache.zeppelin.rest.InterpreterRestApi;
+import org.apache.zeppelin.rest.LoginRestApi;
+import org.apache.zeppelin.rest.NotebookRepoRestApi;
+import org.apache.zeppelin.rest.NotebookRestApi;
+import org.apache.zeppelin.rest.SecurityRestApi;
+import org.apache.zeppelin.rest.ZeppelinRestApi;
 import org.apache.zeppelin.scheduler.SchedulerFactory;
 import org.apache.zeppelin.search.LuceneSearch;
 import org.apache.zeppelin.search.SearchService;
@@ -47,7 +52,12 @@ import org.apache.zeppelin.socket.NotebookServer;
 import org.apache.zeppelin.user.Credentials;
 import org.apache.zeppelin.utils.SecurityUtils;
 import org.eclipse.jetty.http.HttpVersion;
-import org.eclipse.jetty.server.*;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.SecureRequestCustomizer;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.SslConnectionFactory;
 import org.eclipse.jetty.server.handler.ContextHandlerCollection;
 import org.eclipse.jetty.server.session.SessionHandler;
 import org.eclipse.jetty.servlet.DefaultServlet;
@@ -73,8 +83,8 @@ public class ZeppelinServer extends Application {
 
   private SchedulerFactory schedulerFactory;
   private InterpreterFactory replFactory;
-  private NotebookRepo notebookRepo;
   private SearchService noteSearchService;
+  private NotebookRepoSync notebookRepo;
   private NotebookAuthorization notebookAuthorization;
   private Credentials credentials;
   private DependencyResolver depResolver;
@@ -308,6 +318,9 @@ public class ZeppelinServer extends Application {
       = new NotebookRestApi(notebook, notebookWsServer, noteSearchService);
     singletons.add(notebookApi);
 
+    NotebookRepoRestApi notebookRepoApi = new NotebookRepoRestApi(notebookRepo, notebookWsServer);
+    singletons.add(notebookRepoApi);
+
     HeliumRestApi heliumApi = new HeliumRestApi(helium, heliumApplicationFactory, notebook);
     singletons.add(heliumApi);
 

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/824fd955/zeppelin-server/src/test/java/org/apache/zeppelin/rest/NotebookRepoRestApiTest.java
----------------------------------------------------------------------
diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/NotebookRepoRestApiTest.java b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/NotebookRepoRestApiTest.java
new file mode 100644
index 0000000..d221792
--- /dev/null
+++ b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/NotebookRepoRestApiTest.java
@@ -0,0 +1,120 @@
+/*
+ * 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.
+ */
+package org.apache.zeppelin.rest;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertThat;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.httpclient.methods.GetMethod;
+import org.apache.commons.httpclient.methods.PutMethod;
+import org.apache.commons.lang.StringUtils;
+import org.apache.zeppelin.user.AuthenticationInfo;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+
+/**
+ * NotebookRepo rest api test.
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class NotebookRepoRestApiTest extends AbstractTestRestApi {
+
+  Gson gson = new Gson();
+  AuthenticationInfo anonymous;
+
+  @BeforeClass
+  public static void init() throws Exception {
+    AbstractTestRestApi.startUp();
+  }
+
+  @AfterClass
+  public static void destroy() throws Exception {
+    AbstractTestRestApi.shutDown();
+  }
+  
+  @Before
+  public void setUp() {
+    anonymous = new AuthenticationInfo("anonymous");
+  }
+  
+  private List<Map<String, Object>> getListOfReposotiry() throws IOException {
+    GetMethod get = httpGet("/notebook-repositories");
+    Map<String, Object> responce = gson.fromJson(get.getResponseBodyAsString(), new TypeToken<Map<String, Object>>() {}.getType());
+    get.releaseConnection();
+    return (List<Map<String, Object>>) responce.get("body");
+  }
+  
+  private void updateNotebookRepoWithNewSetting(String payload) throws IOException {
+    PutMethod put = httpPut("/notebook-repositories", payload);
+    int status = put.getStatusCode();
+    put.releaseConnection();
+    assertThat(status, is(200));
+  }
+  
+  @Test public void ThatCanGetNotebookRepositoiesSettings() throws IOException {
+    List<Map<String, Object>> listOfRepositories = getListOfReposotiry();
+    assertThat(listOfRepositories.size(), is(not(0)));
+  }
+  
+  @Test public void setNewDirectoryForLocalDirectory() throws IOException {
+    List<Map<String, Object>> listOfRepositories = getListOfReposotiry();
+    String localVfs = StringUtils.EMPTY;
+    String className = StringUtils.EMPTY;
+
+    for (int i = 0; i < listOfRepositories.size(); i++) {
+      if (listOfRepositories.get(i).get("name").equals("VFSNotebookRepo")) {
+        localVfs = (String) ((List<Map<String, Object>>)listOfRepositories.get(i).get("settings")).get(0).get("selected");
+        className = (String) listOfRepositories.get(i).get("className");
+        break;
+      }
+    }
+
+    if (StringUtils.isBlank(localVfs)) {
+      // no loval VFS set...
+      return;
+    }
+
+    String payload = "{ \"name\": \"" + className + "\", \"settings\" : { \"Notebook Path\" : \"/tmp/newDir\" } }";
+    updateNotebookRepoWithNewSetting(payload);
+    
+    // Verify
+    listOfRepositories = getListOfReposotiry();
+    String updatedPath = StringUtils.EMPTY;
+    for (int i = 0; i < listOfRepositories.size(); i++) {
+      if (listOfRepositories.get(i).get("name").equals("VFSNotebookRepo")) {
+        updatedPath = (String) ((List<Map<String, Object>>)listOfRepositories.get(i).get("settings")).get(0).get("selected");
+        break;
+      }
+    }
+    assertThat(updatedPath, is("/tmp/newDir"));
+    
+    // go back to normal
+    payload = "{ \"name\": \"" + className + "\", \"settings\" : { \"Notebook Path\" : \"" + localVfs + "\" } }";
+    updateNotebookRepoWithNewSetting(payload);
+  }
+}

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/824fd955/zeppelin-web/src/app/app.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/app.js b/zeppelin-web/src/app/app.js
index 30d24c2..93f6a99 100644
--- a/zeppelin-web/src/app/app.js
+++ b/zeppelin-web/src/app/app.js
@@ -70,6 +70,11 @@
               templateUrl: 'app/interpreter/interpreter.html',
               controller: 'InterpreterCtrl'
             })
+            .when('/notebookRepos', {
+              templateUrl: 'app/notebookRepos/notebookRepos.html',
+              controller: 'NotebookReposCtrl',
+              controllerAs: 'noterepo'
+            })
             .when('/credential', {
               templateUrl: 'app/credential/credential.html',
               controller: 'CredentialCtrl'

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/824fd955/zeppelin-web/src/app/notebookRepos/notebookRepos.controller.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/notebookRepos/notebookRepos.controller.js b/zeppelin-web/src/app/notebookRepos/notebookRepos.controller.js
new file mode 100644
index 0000000..0d8e0e6
--- /dev/null
+++ b/zeppelin-web/src/app/notebookRepos/notebookRepos.controller.js
@@ -0,0 +1,91 @@
+/*
+ * 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.
+ */
+'use strict';
+(function() {
+
+  angular.module('zeppelinWebApp').controller('NotebookReposCtrl', NotebookReposCtrl);
+
+  NotebookReposCtrl.$inject = ['$http', 'baseUrlSrv', 'ngToast'];
+
+  function NotebookReposCtrl($http, baseUrlSrv, ngToast) {
+    var vm = this;
+    vm.notebookRepos = [];
+    vm.showDropdownSelected = showDropdownSelected;
+    vm.saveNotebookRepo = saveNotebookRepo;
+
+    _init();
+
+    // Public functions
+
+    function saveNotebookRepo(valueform, repo, data) {
+      console.log('data %o', data);
+      $http.put(baseUrlSrv.getRestApiBase() + '/notebook-repositories', {
+        'name': repo.className,
+        'settings': data
+      }).success(function(data) {
+        var index = _.findIndex(vm.notebookRepos, {'className': repo.className});
+        if (index >= 0) {
+          vm.notebookRepos[index] = data.body;
+          console.log('repos %o, data %o', vm.notebookRepos, data.body);
+        }
+        valueform.$show();
+      }).error(function() {
+        ngToast.danger({
+          content: 'We couldn\'t save that NotebookRepo\'s settings',
+          verticalPosition: 'bottom',
+          timeout: '3000'
+        });
+        valueform.$show();
+      });
+
+      return 'manual';
+    }
+
+    function showDropdownSelected(setting) {
+      var index = _.findIndex(setting.value, {'value': setting.selected});
+      if (index < 0) {
+        return 'No value';
+      } else {
+        return setting.value[index].name;
+      }
+    }
+
+    // Private functions
+
+    function _getInterpreterSettings() {
+      $http.get(baseUrlSrv.getRestApiBase() + '/notebook-repositories')
+      .success(function(data, status, headers, config) {
+        vm.notebookRepos = data.body;
+        console.log('ya notebookRepos %o', vm.notebookRepos);
+      }).error(function(data, status, headers, config) {
+        if (status === 401) {
+          ngToast.danger({
+            content: 'You don\'t have permission on this page',
+            verticalPosition: 'bottom',
+            timeout: '3000'
+          });
+          setTimeout(function() {
+            window.location.replace('/');
+          }, 3000);
+        }
+        console.log('Error %o %o', status, data.message);
+      });
+    }
+
+    function _init() {
+      _getInterpreterSettings();
+    };
+  }
+
+})();

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/824fd955/zeppelin-web/src/app/notebookRepos/notebookRepos.html
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/notebookRepos/notebookRepos.html b/zeppelin-web/src/app/notebookRepos/notebookRepos.html
new file mode 100644
index 0000000..65ca372
--- /dev/null
+++ b/zeppelin-web/src/app/notebookRepos/notebookRepos.html
@@ -0,0 +1,98 @@
+<!--
+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 class="interpreterHead">
+  <div class="header">
+    <div class="row">
+      <div class="col-md-12">
+        <h3 class="new_h3">
+          Notebook Repos
+        </h3>
+      </div>
+    </div>
+    <div class="row">
+      <div class="col-md-12">
+        Manage your Notebook Repositories' settings.
+      </div>
+    </div>
+  </div>
+</div>
+
+<div class="box width-full"
+     ng-repeat="repo in noterepo.notebookRepos | orderBy: 'name'">
+  <div id="{{repo.name | lowercase}}">
+    <div class="row interpreter">
+
+      <div class="col-md-12">
+        <h3 class="interpreter-title">{{repo.name}}</h3>
+        <span style="float:right" ng-show="repo.settings.length > 0">
+          <button class="btn btn-default btn-xs"
+                  ng-click="valueform.$show();">
+            <span class="fa fa-pencil"></span> edit</button>
+        </span>
+      </div>
+    </div>
+    <div class="row interpreter">
+      <div class="col-md-12" ng-show="repo.settings.length > 0">
+        <h5>Settings</h5>
+        <table class="table table-striped">
+          <thead>
+            <tr>
+              <th style="width:40%">name</th>
+              <th style="width:60%">value</th>
+            </tr>
+          </thead>
+          <tr ng-repeat="setting in repo.settings" >
+            <td ng-bind="setting.name"></td>
+            <td>
+              <span class="btn-group">
+                <span ng-show="setting.type === 'DROPDOWN'">
+                  <span editable-select="setting.selected"
+                          e-name="{{setting.name}}"
+                          e-ng-options="s.value as s.name for s in setting.value"
+                          class="selectpicker" ng-disabled="!valueform.$visible" e-form="valueform">
+                          {{noterepo.showDropdownSelected(setting)}}
+                  </span>
+                </span>
+                <span ng-show="setting.type === 'INPUT'">
+                  <span editable-textarea="setting.selected" e-name="{{setting.name}}" e-form="valueform" e-msd-elastic e-cols="100">
+                    {{setting.selected | breakFilter}}
+                  </span>
+                </span>
+              </span>
+            </td>
+          </tr>
+        </table>
+      </div>
+    </div>
+    <span style="float:right" ng-show="valueform.$visible">
+      <form editable-form name="valueform"
+            onbeforesave="noterepo.saveNotebookRepo(valueform, repo, $data)"
+            ng-show="valueform.$visible">
+        <button type="submit" class="btn btn-primary btn-xs">
+          <span class="fa fa-check"></span> Save
+        </button>
+        <button type="button" class="btn btn-default btn-xs"
+                ng-disabled="valueform.$waiting"
+                ng-click="valueform.$cancel();">
+          <span class="fa fa-remove"></span> Cancel
+        </button>
+      </form>
+    </span>
+    <div class="row interpreter">
+      <div ng-show="repo.settings.length === 0 || valueform.$hidden" class="col-md-12 gray40-message">
+        <em>Currently there are no settings for this Notebook Repository</em>
+      </div>
+    </div>
+  </div>
+</div>

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/824fd955/zeppelin-web/src/components/navbar/navbar.html
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/components/navbar/navbar.html b/zeppelin-web/src/components/navbar/navbar.html
index d2ec728..8249391 100644
--- a/zeppelin-web/src/components/navbar/navbar.html
+++ b/zeppelin-web/src/components/navbar/navbar.html
@@ -86,6 +86,7 @@ limitations under the License.
               <li><a href="" data-toggle="modal" data-target="#aboutModal">About Zeppelin</a></li>
               <li role="separator" style="margin: 5px 0;" class="divider"></li>
               <li><a href="#/interpreter">Interpreter</a></li>
+              <li><a href="#/notebookRepos">Notebook Repos</a></li>
               <li><a href="#/credential">Credential</a></li>
               <li><a href="#/configuration">Configuration</a></li>
               <li ng-if="ticket.principal && ticket.principal !== 'anonymous'" role="separator" style="margin: 5px 0;" class="divider"></li>

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/824fd955/zeppelin-web/src/index.html
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/index.html b/zeppelin-web/src/index.html
index 05a349f..4ffec03 100644
--- a/zeppelin-web/src/index.html
+++ b/zeppelin-web/src/index.html
@@ -162,6 +162,7 @@ limitations under the License.
     <script src="app/configuration/configuration.controller.js"></script>
     <script src="app/notebook/paragraph/paragraph.controller.js"></script>
     <script src="app/search/result-list.controller.js"></script>
+    <script src="app/notebookRepos/notebookRepos.controller.js"></script>
     <script src="components/arrayOrderingSrv/arrayOrdering.service.js"></script>
     <script src="components/navbar/navbar.controller.js"></script>
     <script src="components/ngescape/ngescape.directive.js"></script>

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/824fd955/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/AzureNotebookRepo.java
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/AzureNotebookRepo.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/AzureNotebookRepo.java
index ca72fc1..70bec4f 100644
--- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/AzureNotebookRepo.java
+++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/AzureNotebookRepo.java
@@ -17,11 +17,19 @@
 
 package org.apache.zeppelin.notebook.repo;
 
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-import com.microsoft.azure.storage.CloudStorageAccount;
-import com.microsoft.azure.storage.StorageException;
-import com.microsoft.azure.storage.file.*;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.net.URISyntaxException;
+import java.security.InvalidKeyException;
+import java.util.Collections;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.lang.StringUtils;
 import org.apache.zeppelin.conf.ZeppelinConfiguration;
@@ -33,12 +41,16 @@ import org.apache.zeppelin.scheduler.Job;
 import org.apache.zeppelin.user.AuthenticationInfo;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import java.io.*;
-import java.net.URISyntaxException;
-import java.security.InvalidKeyException;
-import java.util.Date;
-import java.util.LinkedList;
-import java.util.List;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.microsoft.azure.storage.CloudStorageAccount;
+import com.microsoft.azure.storage.StorageException;
+import com.microsoft.azure.storage.file.CloudFile;
+import com.microsoft.azure.storage.file.CloudFileClient;
+import com.microsoft.azure.storage.file.CloudFileDirectory;
+import com.microsoft.azure.storage.file.CloudFileShare;
+import com.microsoft.azure.storage.file.ListFileItem;
 
 /**
  * Azure storage backend for notebooks
@@ -227,4 +239,15 @@ public class AzureNotebookRepo implements NotebookRepo {
     // Auto-generated method stub
     return null;
   }
+
+  @Override
+  public List<NotebookRepoSettingsInfo> getSettings(AuthenticationInfo subject) {
+    LOG.warn("Method not implemented");
+    return Collections.emptyList();
+  }
+
+  @Override
+  public void updateSettings(Map<String, String> settings, AuthenticationInfo subject) {
+    LOG.warn("Method not implemented");
+  }
 }

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/824fd955/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepo.java
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepo.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepo.java
index d1bf1d5..8016217 100644
--- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepo.java
+++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepo.java
@@ -19,6 +19,7 @@ package org.apache.zeppelin.notebook.repo;
 
 import java.io.IOException;
 import java.util.List;
+import java.util.Map;
 
 import org.apache.zeppelin.annotation.ZeppelinApi;
 import org.apache.zeppelin.notebook.Note;
@@ -101,6 +102,22 @@ public interface NotebookRepo {
   @ZeppelinApi public List<Revision> revisionHistory(String noteId, AuthenticationInfo subject);
 
   /**
+   * Get NotebookRepo settings got the given user.
+   *
+   * @param subject
+   * @return
+   */
+  @ZeppelinApi public List<NotebookRepoSettingsInfo> getSettings(AuthenticationInfo subject);
+
+  /**
+   * update notebook repo settings.
+   *
+   * @param settings
+   * @param subject
+   */
+  @ZeppelinApi public void updateSettings(Map<String, String> settings, AuthenticationInfo subject);
+
+  /**
    * Represents the 'Revision' a point in life of the notebook
    */
   static class Revision {

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/824fd955/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepoSettingsInfo.java
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepoSettingsInfo.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepoSettingsInfo.java
new file mode 100644
index 0000000..0525502
--- /dev/null
+++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepoSettingsInfo.java
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+package org.apache.zeppelin.notebook.repo;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Notebook repo settings. This represent a structure of a notebook repo settings that will mostly
+ * used in the frontend.
+ *
+ */
+public class NotebookRepoSettingsInfo {
+
+  /**
+   * Type of value, It can be text or list.
+   */
+  public enum Type {
+    INPUT, DROPDOWN
+  }
+
+  public static NotebookRepoSettingsInfo newInstance() {
+    return new NotebookRepoSettingsInfo();
+  }
+
+  public Type type;
+  public List<Map<String, String>> value;
+  public String selected;
+  public String name;
+}

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/824fd955/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepoSync.java
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepoSync.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepoSync.java
index 0265eff..fcc5045 100644
--- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepoSync.java
+++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepoSync.java
@@ -24,7 +24,6 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Date;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -39,6 +38,8 @@ import org.apache.zeppelin.user.AuthenticationInfo;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.collect.Lists;
+
 /**
  * Notebook repository sync with remote storage
  */
@@ -109,6 +110,39 @@ public class NotebookRepoSync implements NotebookRepo {
     }
   }
 
+  public List<NotebookRepoWithSettings> getNotebookRepos(AuthenticationInfo subject) {
+    List<NotebookRepoWithSettings> reposSetting = Lists.newArrayList();
+
+    NotebookRepoWithSettings repoWithSettings;
+    for (NotebookRepo repo : repos) {
+      repoWithSettings = NotebookRepoWithSettings
+                           .builder(repo.getClass().getSimpleName())
+                           .className(repo.getClass().getName())
+                           .settings(repo.getSettings(subject))
+                           .build();
+      reposSetting.add(repoWithSettings);
+    }
+
+    return reposSetting;
+  }
+
+  public NotebookRepoWithSettings updateNotebookRepo(String name, Map<String, String> settings,
+                                                     AuthenticationInfo subject) {
+    NotebookRepoWithSettings updatedSettings = NotebookRepoWithSettings.EMPTY;
+    for (NotebookRepo repo : repos) {
+      if (repo.getClass().getName().equals(name)) {
+        repo.updateSettings(settings, subject);
+        updatedSettings = NotebookRepoWithSettings
+                            .builder(repo.getClass().getSimpleName())
+                            .className(repo.getClass().getName())
+                            .settings(repo.getSettings(subject))
+                            .build();
+        break;
+      }
+    }
+    return updatedSettings;
+  }
+
   /**
    *  Lists Notebooks from the first repository
    */
@@ -444,4 +478,24 @@ public class NotebookRepoSync implements NotebookRepo {
     }
     return revisions;
   }
+
+  @Override
+  public List<NotebookRepoSettingsInfo> getSettings(AuthenticationInfo subject) {
+    List<NotebookRepoSettingsInfo> repoSettings = Collections.emptyList();
+    try {
+      repoSettings =  getRepo(0).getSettings(subject);
+    } catch (IOException e) {
+      LOG.error("Cannot get notebook repo settings", e);
+    }
+    return repoSettings;
+  }
+
+  @Override
+  public void updateSettings(Map<String, String> settings, AuthenticationInfo subject) {
+    try {
+      getRepo(0).updateSettings(settings, subject);
+    } catch (IOException e) {
+      LOG.error("Cannot update notebook repo settings", e);
+    }
+  }
 }

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/824fd955/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepoWithSettings.java
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepoWithSettings.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepoWithSettings.java
new file mode 100644
index 0000000..e5f59da
--- /dev/null
+++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/NotebookRepoWithSettings.java
@@ -0,0 +1,79 @@
+/*
+ * 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.
+ */
+package org.apache.zeppelin.notebook.repo;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.commons.lang.StringUtils;
+
+/**
+ * Representation of a notebook repo with settings. This is mostly a Wrapper around notebook repo
+ * information plus settings.
+ */
+public class NotebookRepoWithSettings {
+
+  public static final NotebookRepoWithSettings EMPTY =
+      NotebookRepoWithSettings.builder(StringUtils.EMPTY).build();
+
+  public String name;
+  public String className;
+  public List<NotebookRepoSettingsInfo> settings;
+
+  private NotebookRepoWithSettings() {}
+
+  public static Builder builder(String name) {
+    return new Builder(name);
+  }
+
+  private NotebookRepoWithSettings(Builder builder) {
+    name = builder.name;
+    className = builder.className;
+    settings = builder.settings;
+  }
+
+  public boolean isEmpty() {
+    return this.equals(EMPTY);
+  }
+
+  /**
+   * Simple builder :).
+   */
+  public static class Builder {
+    private final String name;
+    private String className = StringUtils.EMPTY;
+    private List<NotebookRepoSettingsInfo> settings = Collections.emptyList();
+
+    public Builder(String name) {
+      this.name = name;
+    }
+
+    public NotebookRepoWithSettings build() {
+      return new NotebookRepoWithSettings(this);
+    }
+
+    public Builder className(String className) {
+      this.className = className;
+      return this;
+    }
+
+    public Builder settings(List<NotebookRepoSettingsInfo> settings) {
+      this.settings = settings;
+      return this;
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/824fd955/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/S3NotebookRepo.java
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/S3NotebookRepo.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/S3NotebookRepo.java
index 0163fc4..889fd03 100644
--- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/S3NotebookRepo.java
+++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/S3NotebookRepo.java
@@ -23,14 +23,12 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStreamWriter;
 import java.io.Writer;
+import java.util.Collections;
 import java.util.Date;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 
-import com.amazonaws.auth.AWSCredentialsProvider;
-import com.amazonaws.services.s3.AmazonS3EncryptionClient;
-import com.amazonaws.services.s3.model.EncryptionMaterialsProvider;
-import com.amazonaws.services.s3.model.KMSEncryptionMaterialsProvider;
 import org.apache.commons.io.FileUtils;
 import org.apache.commons.io.IOUtils;
 import org.apache.zeppelin.conf.ZeppelinConfiguration;
@@ -45,10 +43,14 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.amazonaws.AmazonClientException;
+import com.amazonaws.auth.AWSCredentialsProvider;
 import com.amazonaws.auth.DefaultAWSCredentialsProviderChain;
 import com.amazonaws.services.s3.AmazonS3;
 import com.amazonaws.services.s3.AmazonS3Client;
+import com.amazonaws.services.s3.AmazonS3EncryptionClient;
+import com.amazonaws.services.s3.model.EncryptionMaterialsProvider;
 import com.amazonaws.services.s3.model.GetObjectRequest;
+import com.amazonaws.services.s3.model.KMSEncryptionMaterialsProvider;
 import com.amazonaws.services.s3.model.ListObjectsRequest;
 import com.amazonaws.services.s3.model.ObjectListing;
 import com.amazonaws.services.s3.model.PutObjectRequest;
@@ -270,4 +272,15 @@ public class S3NotebookRepo implements NotebookRepo {
     // Auto-generated method stub
     return null;
   }
+
+  @Override
+  public List<NotebookRepoSettingsInfo> getSettings(AuthenticationInfo subject) {
+    LOG.warn("Method not implemented");
+    return Collections.emptyList();
+  }
+
+  @Override
+  public void updateSettings(Map<String, String> settings, AuthenticationInfo subject) {
+    LOG.warn("Method not implemented");
+  }
 }

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/824fd955/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/VFSNotebookRepo.java
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/VFSNotebookRepo.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/VFSNotebookRepo.java
index 213fdf8..5f3ad96 100644
--- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/VFSNotebookRepo.java
+++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/VFSNotebookRepo.java
@@ -23,11 +23,14 @@ import java.io.InputStream;
 import java.io.OutputStream;
 import java.net.URI;
 import java.net.URISyntaxException;
+import java.util.Collections;
 import java.util.Date;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 
 import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang.StringUtils;
 import org.apache.commons.vfs2.FileContent;
 import org.apache.commons.vfs2.FileObject;
 import org.apache.commons.vfs2.FileSystemManager;
@@ -40,13 +43,14 @@ import org.apache.zeppelin.conf.ZeppelinConfiguration.ConfVars;
 import org.apache.zeppelin.notebook.ApplicationState;
 import org.apache.zeppelin.notebook.Note;
 import org.apache.zeppelin.notebook.NoteInfo;
-import org.apache.zeppelin.notebook.Paragraph;
 import org.apache.zeppelin.notebook.NotebookImportDeserializer;
+import org.apache.zeppelin.notebook.Paragraph;
 import org.apache.zeppelin.scheduler.Job.Status;
 import org.apache.zeppelin.user.AuthenticationInfo;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.collect.Lists;
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
 
@@ -54,7 +58,7 @@ import com.google.gson.GsonBuilder;
 *
 */
 public class VFSNotebookRepo implements NotebookRepo {
-  Logger logger = LoggerFactory.getLogger(VFSNotebookRepo.class);
+  private static final Logger LOG = LoggerFactory.getLogger(VFSNotebookRepo.class);
 
   private FileSystemManager fsManager;
   private URI filesystemRoot;
@@ -62,12 +66,15 @@ public class VFSNotebookRepo implements NotebookRepo {
 
   public VFSNotebookRepo(ZeppelinConfiguration conf) throws IOException {
     this.conf = conf;
+    setNotebookDirectory(conf.getNotebookDir());
+  }
 
+  private void setNotebookDirectory(String notebookDirPath) throws IOException {
     try {
-      if (conf.isWindowsPath(conf.getNotebookDir())) {
-        filesystemRoot = new File(conf.getNotebookDir()).toURI();
+      if (conf.isWindowsPath(notebookDirPath)) {
+        filesystemRoot = new File(notebookDirPath).toURI();
       } else {
-        filesystemRoot = new URI(conf.getNotebookDir());
+        filesystemRoot = new URI(notebookDirPath);
       }
     } catch (URISyntaxException e1) {
       throw new IOException(e1);
@@ -76,7 +83,7 @@ public class VFSNotebookRepo implements NotebookRepo {
     if (filesystemRoot.getScheme() == null) { // it is local path
       try {
         this.filesystemRoot = new URI(new File(
-            conf.getRelativeDir(filesystemRoot.getPath())).getAbsolutePath());
+                conf.getRelativeDir(filesystemRoot.getPath())).getAbsolutePath());
       } catch (URISyntaxException e) {
         throw new IOException(e);
       }
@@ -85,11 +92,15 @@ public class VFSNotebookRepo implements NotebookRepo {
     fsManager = VFS.getManager();
     FileObject file = fsManager.resolveFile(filesystemRoot.getPath());
     if (!file.exists()) {
-      logger.info("Notebook dir doesn't exist, create.");
+      LOG.info("Notebook dir doesn't exist, create on is {}.", file.getName());
       file.createFolder();
     }
   }
 
+  private String getNotebookDirPath() {
+    return filesystemRoot.getPath().toString();
+  }
+
   private String getPath(String path) {
     if (path == null || path.trim().length() == 0) {
       return filesystemRoot.toString();
@@ -141,7 +152,7 @@ public class VFSNotebookRepo implements NotebookRepo {
           infos.add(info);
         }
       } catch (Exception e) {
-        logger.error("Can't read note " + f.getName().toString(), e);
+        LOG.error("Can't read note " + f.getName().toString(), e);
       }
     }
 
@@ -285,4 +296,42 @@ public class VFSNotebookRepo implements NotebookRepo {
     return null;
   }
 
+  @Override
+  public List<NotebookRepoSettingsInfo> getSettings(AuthenticationInfo subject) {
+    NotebookRepoSettingsInfo repoSetting = NotebookRepoSettingsInfo.newInstance();
+    List<NotebookRepoSettingsInfo> settings = Lists.newArrayList();
+
+    repoSetting.name = "Notebook Path";
+    repoSetting.type = NotebookRepoSettingsInfo.Type.INPUT;
+    repoSetting.value = Collections.emptyList();
+    repoSetting.selected = getNotebookDirPath();
+
+    settings.add(repoSetting);
+    return settings;
+  }
+
+  @Override
+  public void updateSettings(Map<String, String> settings, AuthenticationInfo subject) {
+    if (settings == null || settings.isEmpty()) {
+      LOG.error("Cannot update {} with empty settings", this.getClass().getName());
+      return;
+    }
+    String newNotebookDirectotyPath = StringUtils.EMPTY;
+    if (settings.containsKey("Notebook Path")) {
+      newNotebookDirectotyPath = settings.get("Notebook Path");
+    }
+
+    if (StringUtils.isBlank(newNotebookDirectotyPath)) {
+      LOG.error("Notebook path is invalid");
+      return;
+    }
+    LOG.warn("{} will change notebook dir from {} to {}",
+        subject.getUser(), getNotebookDirPath(), newNotebookDirectotyPath);
+    try {
+      setNotebookDirectory(newNotebookDirectotyPath);
+    } catch (IOException e) {
+      LOG.error("Cannot update notebook directory", e);
+    }
+  }
+
 }

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/824fd955/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/zeppelinhub/ZeppelinHubRepo.java
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/zeppelinhub/ZeppelinHubRepo.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/zeppelinhub/ZeppelinHubRepo.java
index d1864c5..a4b6202 100644
--- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/zeppelinhub/ZeppelinHubRepo.java
+++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/repo/zeppelinhub/ZeppelinHubRepo.java
@@ -21,12 +21,14 @@ import java.net.URI;
 import java.net.URISyntaxException;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 
 import org.apache.commons.lang.StringUtils;
 import org.apache.zeppelin.conf.ZeppelinConfiguration;
 import org.apache.zeppelin.notebook.Note;
 import org.apache.zeppelin.notebook.NoteInfo;
 import org.apache.zeppelin.notebook.repo.NotebookRepo;
+import org.apache.zeppelin.notebook.repo.NotebookRepoSettingsInfo;
 import org.apache.zeppelin.notebook.repo.zeppelinhub.rest.ZeppelinhubRestApiHandler;
 import org.apache.zeppelin.notebook.repo.zeppelinhub.websocket.Client;
 import org.apache.zeppelin.user.AuthenticationInfo;
@@ -234,4 +236,15 @@ public class ZeppelinHubRepo implements NotebookRepo {
     return history;
   }
 
+  @Override
+  public List<NotebookRepoSettingsInfo> getSettings(AuthenticationInfo subject) {
+    LOG.warn("Method not implemented");
+    return Collections.emptyList();
+  }
+
+  @Override
+  public void updateSettings(Map<String, String> settings, AuthenticationInfo subject) {
+    LOG.warn("Method not implemented");
+  }
+
 }

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/824fd955/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/repo/VFSNotebookRepoTest.java
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/repo/VFSNotebookRepoTest.java b/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/repo/VFSNotebookRepoTest.java
index e6236c8..f40d5f8 100644
--- a/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/repo/VFSNotebookRepoTest.java
+++ b/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/repo/VFSNotebookRepoTest.java
@@ -22,6 +22,7 @@ import static org.mockito.Mockito.mock;
 
 import java.io.File;
 import java.io.IOException;
+import java.util.List;
 import java.util.Map;
 
 import org.apache.commons.io.FileUtils;
@@ -31,12 +32,13 @@ import org.apache.zeppelin.dep.DependencyResolver;
 import org.apache.zeppelin.interpreter.InterpreterFactory;
 import org.apache.zeppelin.interpreter.InterpreterOption;
 import org.apache.zeppelin.interpreter.mock.MockInterpreter1;
-import org.apache.zeppelin.notebook.*;
-import org.apache.zeppelin.notebook.repo.zeppelinhub.security.Authentication;
-import org.apache.zeppelin.scheduler.JobListener;
+import org.apache.zeppelin.notebook.JobListenerFactory;
+import org.apache.zeppelin.notebook.Note;
+import org.apache.zeppelin.notebook.Notebook;
+import org.apache.zeppelin.notebook.Paragraph;
+import org.apache.zeppelin.notebook.ParagraphJobListener;
 import org.apache.zeppelin.scheduler.SchedulerFactory;
 import org.apache.zeppelin.search.SearchService;
-import org.apache.zeppelin.search.LuceneSearch;
 import org.apache.zeppelin.user.AuthenticationInfo;
 import org.junit.After;
 import org.junit.Before;
@@ -44,6 +46,8 @@ import org.junit.Test;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.collect.ImmutableMap;
+
 public class VFSNotebookRepoTest implements JobListenerFactory {
   private static final Logger LOG = LoggerFactory.getLogger(VFSNotebookRepoTest.class);
   private ZeppelinConfiguration conf;
@@ -141,6 +145,24 @@ public class VFSNotebookRepoTest implements JobListenerFactory {
     assertEquals(note.getName(), "SaveTest");
     notebookRepo.remove(note.getId(), null);
   }
+  
+  @Test
+  public void testUpdateSettings() throws IOException {
+    AuthenticationInfo subject = new AuthenticationInfo("anonymous");
+    File tmpDir = File.createTempFile("temp", Long.toString(System.nanoTime()));
+    Map<String, String> settings = ImmutableMap.of("Notebook Path", tmpDir.getAbsolutePath());
+    
+    List<NotebookRepoSettingsInfo> repoSettings = notebookRepo.getSettings(subject);
+    String originalDir = repoSettings.get(0).selected;
+    
+    notebookRepo.updateSettings(settings, subject);
+    repoSettings = notebookRepo.getSettings(subject);
+    assertEquals(repoSettings.get(0).selected, tmpDir.getAbsolutePath());
+    
+    // restaure
+    notebookRepo.updateSettings(ImmutableMap.of("Notebook Path", originalDir), subject);
+    FileUtils.deleteQuietly(tmpDir);
+  }
 
   class NotebookWriter implements Runnable {
     Note note;


Mime
View raw message