This is an automated email from the ASF dual-hosted git repository. jongyoul pushed a commit to branch branch-0.12 in repository https://gitbox.apache.org/repos/asf/zeppelin.git
The following commit(s) were added to refs/heads/branch-0.12 by this push: new b05f247dd6 [ZEPPELIN-6085] Add configuration option for setting the default UI b05f247dd6 is described below commit b05f247dd6c017d2aa8fa0887a1073d6ec40d3c1 Author: ChanHo Lee <chanho0...@gmail.com> AuthorDate: Wed Oct 9 15:50:16 2024 +0900 [ZEPPELIN-6085] Add configuration option for setting the default UI ### What is this PR for? This PR introduces a new configuration option `zeppelin.default.ui` to set the default UI for Zeppelin. The available options for this setting are `new` and `classic`. By default, the value is set to `new`, meaning the new UI will be served at the root path (`"/"`), while the classic UI will be available at `"/classic"`. If the configuration is set to `classic`, the classic UI will be served at `"/"`, and the new UI at `"/new"`. ### What type of PR is it? Improvement ### Todos * [ ] - Task ### What is the Jira issue? * Open an issue on Jira https://issues.apache.org/jira/browse/ZEPPELIN/6085 * Put link here, and add [ZEPPELIN-*Jira number*] in PR title, eg. [ZEPPELIN-533] ### How should this be tested? - Testing the `new` option : - Set the `zeppelin.default.ui` config to `new` (the default). - Verify that the new UI is served at the root (`"/"`) and the classic UI is available at `"/classic"`. - Verify that navigating between pages works without any issue. - Verify that the UI switching button works properly in both UIs. - Testing `classic` option : - Similar steps as the above, except the new UI is served at `"/classic"` and the classic UI is available at the root (`"/"`) ### Screenshots (if appropriate) ### Questions: * Does the license files need to update? No * Is there breaking changes for older versions? No * Does this needs documentation? Yes, documentation for the new configuration option has been added. Closes #4824 from tbonelee/add-default-ui-config. Signed-off-by: Jongyoul Lee <jongy...@gmail.com> (cherry picked from commit 00b7c553694050d6d2ee4824a236bd76a7135646) Signed-off-by: Jongyoul Lee <jongy...@gmail.com> --- conf/zeppelin-site.xml.template | 6 +++++ docs/quickstart/explore_ui.md | 15 +++++++++++ docs/setup/operation/configuration.md | 6 +++++ .../zeppelin/conf/ZeppelinConfiguration.java | 11 ++++++++ .../apache/zeppelin/server/IndexHtmlServlet.java | 29 +++++++++++++++++++++- .../org/apache/zeppelin/server/ZeppelinServer.java | 29 +++++++++++++++++----- .../zeppelin/server/IndexHtmlServletTest.java | 4 +-- zeppelin-web-angular/package.json | 2 +- .../src/app/share/header/header.component.html | 2 +- .../src/app/share/header/header.component.ts | 10 ++++++++ zeppelin-web-angular/src/index.html | 9 ++++++- .../src/components/navbar/navbar.controller.js | 8 ++++++ zeppelin-web/src/components/navbar/navbar.html | 2 +- zeppelin-web/src/index.html | 9 ++++++- 14 files changed, 128 insertions(+), 14 deletions(-) diff --git a/conf/zeppelin-site.xml.template b/conf/zeppelin-site.xml.template index 9c719920ce..91b7a15029 100755 --- a/conf/zeppelin-site.xml.template +++ b/conf/zeppelin-site.xml.template @@ -49,6 +49,12 @@ <description>Location of jetty temporary directory</description> </property> +<property> + <name>zeppelin.default.ui</name> + <value>new</value> + <description>Default UI for Zeppelin. Options: classic or new</description> +</property> + <property> <name>zeppelin.notebook.dir</name> <value>notebook</value> diff --git a/docs/quickstart/explore_ui.md b/docs/quickstart/explore_ui.md index 73156f39e7..5a466385e8 100644 --- a/docs/quickstart/explore_ui.md +++ b/docs/quickstart/explore_ui.md @@ -35,6 +35,21 @@ Afterward, you can switch to the classic UI via the `Swtich to Classic UI` butto <img src="{{BASE_PATH}}/assets/themes/zeppelin/img/ui-img/switch_to_classic_ui.png" width="130" /> + +### Configuring the default UI + +Zeppelin allows you to configure the default, especially for users who prefer the classic UI. + +To set the default UI to classic, add the following property to the `zeppelin-site.xml` file: + +```xml +<property> + <name>zeppelin.default.ui</name> + <value>classic</value> + <description>Default UI for Zeppelin. Options: classic or new. Default configuration is 'new'</description> +</property> +``` + ## Main home The first time you connect to Zeppelin ([default installations start on http://localhost:8080](http://localhost:8080/)), you'll land at the main page similar to the below screen capture. diff --git a/docs/setup/operation/configuration.md b/docs/setup/operation/configuration.md index e0c769202d..a2184314d2 100644 --- a/docs/setup/operation/configuration.md +++ b/docs/setup/operation/configuration.md @@ -217,6 +217,12 @@ Sources descending by priority: <td>webapps</td> <td>Location of the jetty temporary directory</td> </tr> + <tr> + <td><h6 class="properties">ZEPPELIN_DEFAULT_UI</h6></td> + <td><h6 class="properties">zeppelin.default.ui</h6></td> + <td>new</td> + <td>Default UI for Zeppelin. <br />Options: <code>classic</code> or <code>new</code></td> + </tr> <tr> <td><h6 class="properties">ZEPPELIN_NOTEBOOK_DIR</h6></td> <td><h6 class="properties">zeppelin.notebook.dir</h6></td> diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java index 5ac7587f29..b590fa8dda 100644 --- a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java +++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java @@ -79,6 +79,11 @@ public class ZeppelinConfiguration { DOCKER } + public enum DEFAULT_UI { + NEW, + CLASSIC + } + // private constructor, so that it is singleton. private ZeppelinConfiguration(@Nullable String filename) { try { @@ -882,6 +887,10 @@ public class ZeppelinConfiguration { return getBoolean(ConfVars.ZEPPELIN_METRIC_ENABLE_PROMETHEUS); } + public DEFAULT_UI getDefaultUi() { + return DEFAULT_UI.valueOf(getString(ConfVars.ZEPPELIN_DEFAULT_UI).toUpperCase()); + } + /** * This method return the complete configuration map * @return @@ -942,6 +951,8 @@ public class ZeppelinConfiguration { ZEPPELIN_WAR("zeppelin.war", "zeppelin-web/dist"), ZEPPELIN_ANGULAR_WAR("zeppelin.angular.war", "zeppelin-web-angular/dist/zeppelin"), ZEPPELIN_WAR_TEMPDIR("zeppelin.war.tempdir", "webapps"), + // "new" or "classic" + ZEPPELIN_DEFAULT_UI("zeppelin.default.ui", "new"), ZEPPELIN_JMX_ENABLE("zeppelin.jmx.enable", false), ZEPPELIN_JMX_PORT("zeppelin.jmx.port", 9996), diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/server/IndexHtmlServlet.java b/zeppelin-server/src/main/java/org/apache/zeppelin/server/IndexHtmlServlet.java index bf32063a5d..b79149a2ba 100644 --- a/zeppelin-server/src/main/java/org/apache/zeppelin/server/IndexHtmlServlet.java +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/server/IndexHtmlServlet.java @@ -46,10 +46,12 @@ public class IndexHtmlServlet extends HttpServlet { final String bodyAddon; final String headAddon; + final String baseHref; - public IndexHtmlServlet(ZeppelinConfiguration zConf) { + public IndexHtmlServlet(ZeppelinConfiguration zConf, String contextPath) { this.bodyAddon = zConf.getHtmlBodyAddon(); this.headAddon = zConf.getHtmlHeadAddon(); + this.baseHref = getBaseHref(contextPath); } @Override @@ -79,6 +81,21 @@ public class IndexHtmlServlet extends HttpServlet { "Unable to process Head html addon. Could not find proper anchor in index.html."); } } + // process base href + if (baseHref != null) { + String baseTag = "<base href=\".*\">"; + if (content.matches(baseTag)) { + content = content.replaceAll(baseTag, baseHref); + } else if (content.contains(TAG_HEAD_CLOSING)) { + content = content.replace(TAG_HEAD_CLOSING, baseHref + TAG_HEAD_CLOSING); + } else if (content.contains(TAG_BODY_OPENING)) { + content = content.replace(TAG_BODY_OPENING, baseHref + TAG_BODY_OPENING); + } else { + LOGGER.error( + "Unable to process base href. Could not find proper anchor in index.html."); + } + } + resp.setContentType("text/html"); resp.setStatus(HttpServletResponse.SC_OK); resp.getWriter().append(content); @@ -86,4 +103,14 @@ public class IndexHtmlServlet extends HttpServlet { LOGGER.error("Error rendering index.html.", e); } } + + private String getBaseHref(String contextPath) { + if (contextPath == null) { + return null; + } + if (!contextPath.endsWith("/")) { + contextPath = contextPath + "/"; + } + return "<base href=\"" + contextPath + "\">"; + } } 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 e41b9b6f94..3a5ef4bc31 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 @@ -62,6 +62,7 @@ import org.apache.shiro.web.env.EnvironmentLoaderListener; import org.apache.shiro.web.servlet.ShiroFilter; import org.apache.zeppelin.conf.ZeppelinConfiguration; import org.apache.zeppelin.conf.ZeppelinConfiguration.ConfVars; +import org.apache.zeppelin.conf.ZeppelinConfiguration.DEFAULT_UI; import org.apache.zeppelin.display.AngularObjectRegistryListener; import org.apache.zeppelin.healthcheck.HealthChecks; import org.apache.zeppelin.helium.ApplicationEventListener; @@ -127,7 +128,8 @@ import org.slf4j.LoggerFactory; /** Main class of Zeppelin. */ public class ZeppelinServer implements AutoCloseable { private static final Logger LOGGER = LoggerFactory.getLogger(ZeppelinServer.class); - private static final String WEB_APP_CONTEXT_CLASSIC = "/classic"; + private static final String NON_DEFAULT_NEW_UI_WEB_APP_CONTEXT_PATH = "/new"; + private static final String NON_DEFAULT_CLASSIC_UI_WEB_APP_CONTEXT_PATH = "/classic"; public static final String DEFAULT_SERVICE_LOCATOR_NAME = "shared-locator"; private final AtomicBoolean duringShutdown = new AtomicBoolean(false); @@ -221,11 +223,22 @@ public class ZeppelinServer implements AutoCloseable { }); // Multiple Web UI - final WebAppContext defaultWebApp = setupWebAppContext(contexts, zConf, zConf.getString(ConfVars.ZEPPELIN_ANGULAR_WAR), zConf.getServerContextPath()); - final WebAppContext classicWebApp = setupWebAppContext(contexts, zConf, zConf.getString(ConfVars.ZEPPELIN_WAR), WEB_APP_CONTEXT_CLASSIC); + String classicUiWebAppContextPath; + String newUiWebAppContextPath; + if (isNewUiDefault(zConf)) { + classicUiWebAppContextPath = NON_DEFAULT_CLASSIC_UI_WEB_APP_CONTEXT_PATH; + newUiWebAppContextPath = zConf.getServerContextPath(); + } else { + classicUiWebAppContextPath = zConf.getServerContextPath(); + newUiWebAppContextPath = NON_DEFAULT_NEW_UI_WEB_APP_CONTEXT_PATH; + } + final WebAppContext newUiWebApp = setupWebAppContext(contexts, zConf, zConf.getString(ConfVars.ZEPPELIN_ANGULAR_WAR), + newUiWebAppContextPath); + final WebAppContext classicUiWebApp = setupWebAppContext(contexts, zConf, zConf.getString(ConfVars.ZEPPELIN_WAR), + classicUiWebAppContextPath); - initWebApp(defaultWebApp); - initWebApp(classicWebApp); + initWebApp(newUiWebApp); + initWebApp(classicUiWebApp); NotebookRepo repo = ServiceLocatorUtilities.getService(sharedServiceLocator, NotebookRepo.class.getName()); @@ -592,7 +605,7 @@ public class ZeppelinServer implements AutoCloseable { webApp.setTempDirectory(warTempDirectory); } // Explicit bind to root - webApp.addServlet(new ServletHolder(new IndexHtmlServlet(zConf)), "/index.html"); + webApp.addServlet(new ServletHolder(new IndexHtmlServlet(zConf, contextPath)), "/index.html"); contexts.addHandler(webApp); webApp.addFilter(new FilterHolder(new CorsFilter(zConf)), "/*", EnumSet.allOf(DispatcherType.class)); @@ -629,6 +642,10 @@ public class ZeppelinServer implements AutoCloseable { setupNotebookServer(webApp); } + private static boolean isNewUiDefault(ZeppelinConfiguration zConf) { + return zConf.getDefaultUi() == DEFAULT_UI.NEW; + } + @Override public void close() throws Exception { shutdown(); diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/server/IndexHtmlServletTest.java b/zeppelin-server/src/test/java/org/apache/zeppelin/server/IndexHtmlServletTest.java index 76d8b8d711..3bab703eda 100644 --- a/zeppelin-server/src/test/java/org/apache/zeppelin/server/IndexHtmlServletTest.java +++ b/zeppelin-server/src/test/java/org/apache/zeppelin/server/IndexHtmlServletTest.java @@ -56,7 +56,7 @@ class IndexHtmlServletTest { .thenReturn(new URL("file:" + FILE_PATH_INDEX_HTML_ZEPPELIN_WEB)); when(sc.getServletContext()).thenReturn(ctx); - IndexHtmlServlet servlet = new IndexHtmlServlet(zConf); + IndexHtmlServlet servlet = new IndexHtmlServlet(zConf, null); servlet.init(sc); HttpServletResponse mockResponse = mock(HttpServletResponse.class); @@ -90,7 +90,7 @@ class IndexHtmlServletTest { .thenReturn(new URL("file:" + FILE_PATH_INDEX_HTML_ZEPPELIN_WEB_ANGULAR)); when(sc.getServletContext()).thenReturn(ctx); - IndexHtmlServlet servlet = new IndexHtmlServlet(zConf); + IndexHtmlServlet servlet = new IndexHtmlServlet(zConf, null); servlet.init(sc); HttpServletResponse mockResponse = mock(HttpServletResponse.class); diff --git a/zeppelin-web-angular/package.json b/zeppelin-web-angular/package.json index a9dc4f4a53..383fe3d3ce 100644 --- a/zeppelin-web-angular/package.json +++ b/zeppelin-web-angular/package.json @@ -5,7 +5,7 @@ "postinstall": "npm run build:projects", "ng": "./node_modules/.bin/ng", "start": "ng serve --proxy-config proxy.conf.js --extra-webpack-config webpack.partial.js", - "build": "ng build --prod --extra-webpack-config webpack.partial.js --base-href /", + "build": "ng build --prod --extra-webpack-config webpack.partial.js", "build:projects": "npm run build-project:sdk && npm run build-project:vis && npm run build-project:helium", "build-helium-vis-example": " ng build --project helium-vis-example", "build-project:sdk": " ng build --project zeppelin-sdk", diff --git a/zeppelin-web-angular/src/app/share/header/header.component.html b/zeppelin-web-angular/src/app/share/header/header.component.html index 55b659f1f0..65376b617d 100644 --- a/zeppelin-web-angular/src/app/share/header/header.component.html +++ b/zeppelin-web-angular/src/app/share/header/header.component.html @@ -67,7 +67,7 @@ <li nz-menu-item (click)="logout()">Logout</li> </ng-container> <li nz-menu-divider></li> - <li nz-menu-item><a href="/classic">Switch to Classic UI</a></li> + <li nz-menu-item><a [href]="classicUiHref">Switch to Classic UI</a></li> </ul> </nz-dropdown-menu> </div> diff --git a/zeppelin-web-angular/src/app/share/header/header.component.ts b/zeppelin-web-angular/src/app/share/header/header.component.ts index b4c20c242c..ce0bc901b7 100644 --- a/zeppelin-web-angular/src/app/share/header/header.component.ts +++ b/zeppelin-web-angular/src/app/share/header/header.component.ts @@ -34,6 +34,7 @@ export class HeaderComponent extends MessageListenersManager implements OnInit, connectStatus = 'error'; noteListVisible = false; queryStr: string | null = null; + classicUiHref: string; about() { this.nzModalService.create({ @@ -69,6 +70,7 @@ export class HeaderComponent extends MessageListenersManager implements OnInit, private cdr: ChangeDetectorRef ) { super(messageService); + this.classicUiHref = this.resolveClassicUiHref(); } ngOnInit() { @@ -98,4 +100,12 @@ export class HeaderComponent extends MessageListenersManager implements OnInit, this.destroy$.complete(); super.ngOnDestroy(); } + + private resolveClassicUiHref() { + if (location.pathname === '/') { + return '/classic'; + } else { + return '/'; + } + } } diff --git a/zeppelin-web-angular/src/index.html b/zeppelin-web-angular/src/index.html index 9ad5caa094..f0c70594df 100644 --- a/zeppelin-web-angular/src/index.html +++ b/zeppelin-web-angular/src/index.html @@ -17,7 +17,6 @@ <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="icon" href="./assets/images/zeppelin.png" type="image/x-icon"> <title>Zeppelin</title> - <base href="/"> </head> <body> <zeppelin-root> @@ -44,6 +43,14 @@ "HTML-CSS": { availableFonts: ["TeX"] }, messageStyle: "none" } + const origin = window.location.origin; + const pathname = window.location.pathname; + config.root = window.location.origin + ( + // remove trailing slash + pathname.charAt(pathname.length - 1) === '/' ? + pathname.slice(0, -1) : + pathname + ); MathJax.Hub.Config(config); </script> </body> diff --git a/zeppelin-web/src/components/navbar/navbar.controller.js b/zeppelin-web/src/components/navbar/navbar.controller.js index 9713e50207..503b9cd9aa 100644 --- a/zeppelin-web/src/components/navbar/navbar.controller.js +++ b/zeppelin-web/src/components/navbar/navbar.controller.js @@ -270,6 +270,14 @@ function NavCtrl($scope, $rootScope, $http, $routeParams, $location, } }); + $scope.resolveNewUiHref = function() { + if (location.pathname === '/') { + return '/new'; + } else { + return '/'; + } + }; + $rootScope.isRevisionSupported = function() { return revisionSupported; }; diff --git a/zeppelin-web/src/components/navbar/navbar.html b/zeppelin-web/src/components/navbar/navbar.html index 990da03cd5..cfda971f71 100644 --- a/zeppelin-web/src/components/navbar/navbar.html +++ b/zeppelin-web/src/components/navbar/navbar.html @@ -106,7 +106,7 @@ limitations under the License. <li ng-if="ticket.principal && ticket.principal !== 'anonymous'" role="separator" style="margin: 5px 0;" class="divider"></li> <li ng-if="ticket.principal && ticket.principal !== 'anonymous'"><a ng-click="navbar.logout()">Logout</a></li> <li role="separator" style="margin: 5px 0;" class="divider"></li> - <li><a href="/">Switch to Default UI</a></li> + <li><a ng-href="{{ resolveNewUiHref() }}">Switch to Default UI</a></li> </ul> </div> </li> diff --git a/zeppelin-web/src/index.html b/zeppelin-web/src/index.html index a149af0db6..9cb60c3f82 100644 --- a/zeppelin-web/src/index.html +++ b/zeppelin-web/src/index.html @@ -159,7 +159,14 @@ limitations under the License. } // add root only if it's not dev mode if (Number(location.port) !== WEB_PORT) { - config.root = '.'; + const origin = window.location.origin; + const pathname = window.location.pathname; + config.root = window.location.origin + ( + // remove trailing slash + pathname.charAt(pathname.length - 1) === '/' ? + pathname.slice(0, -1) : + pathname + ); } MathJax.Hub.Config(config); </script>