diff --git a/tensorboard/plugins/core/core_plugin.py b/tensorboard/plugins/core/core_plugin.py
index 629b6b6467..98837e4f7a 100644
--- a/tensorboard/plugins/core/core_plugin.py
+++ b/tensorboard/plugins/core/core_plugin.py
@@ -45,6 +45,10 @@
# transition to 'text/javascript'.
JS_MIMETYPES = ["text/javascript", "application/javascript"]
JS_CACHE_EXPIRATION_IN_SECS = 86400
+# Th paths that the application should be served at
+# Note that paths other than "/" should NOT end in '/' because trailing '/'
+# characters are removed by the serving logic in tensorboard/backend/application.py
+APP_PATHS = ["/", "/flags"]
class CorePlugin(base_plugin.TBPlugin):
@@ -109,17 +113,34 @@ def get_resource_apps(self):
content = zip_.read(path)
# Opt out of gzipping index.html
if path == "index.html":
- apps["/" + path] = functools.partial(
- self._serve_index, content
- )
+ for app_path in APP_PATHS:
+ path_with_slash = (
+ app_path
+ if app_path.endswith("/")
+ else app_path + "/"
+ )
+ apps[path_with_slash + path] = functools.partial(
+ self._serve_index, content, app_path
+ )
continue
gzipped_asset_bytes = _gzip(content)
wsgi_app = functools.partial(
self._serve_asset, path, gzipped_asset_bytes
)
- apps["/" + path] = wsgi_app
- apps["/"] = apps["/index.html"]
+ for app_path in APP_PATHS:
+ path_with_slash = (
+ app_path
+ if app_path.endswith("/")
+ else app_path + "/"
+ )
+ apps[path_with_slash + path] = wsgi_app
+ for app_path in APP_PATHS:
+ if app_path.endswith("/"):
+ apps[app_path] = apps[app_path + "index.html"]
+ else:
+ apps[app_path] = apps[app_path + "/index.html"]
+ apps[app_path] = apps[app_path]
return apps
@wrappers.Request.application
@@ -151,21 +172,23 @@ def _serve_asset(self, path, gzipped_asset_bytes, request):
)
@wrappers.Request.application
- def _serve_index(self, index_asset_bytes, request):
+ def _serve_index(self, index_asset_bytes, content_path, request):
"""Serves index.html content.
Note that we opt out of gzipping index.html to write preamble before the
resource content. This inflates the resource size from 2x kiB to 1xx
kiB, but we require an ability to flush preamble with the HTML content.
"""
+ if not request.path.endswith("/"):
+ return utils.redirect(request.path + "/")
relpath = (
posixpath.relpath(self._path_prefix, request.script_root)
if self._path_prefix
else "."
)
meta_header = (
- ''
- % relpath
+ ''
+ % (relpath, content_path)
)
content = meta_header.encode("utf-8") + index_asset_bytes
# By passing content_encoding, disallow gzipping. Bloats the content
diff --git a/tensorboard/webapp/app_routing/programmatical_navigation_types.ts b/tensorboard/webapp/app_routing/programmatical_navigation_types.ts
index 0fa4c20875..bc688efd40 100644
--- a/tensorboard/webapp/app_routing/programmatical_navigation_types.ts
+++ b/tensorboard/webapp/app_routing/programmatical_navigation_types.ts
@@ -40,10 +40,17 @@ export interface NavigateToExperiments {
resetNamespacedState?: boolean;
}
+export interface NavigateToFlags {
+ routeKind: RouteKind.FLAGS;
+ routeParams: {},
+ resetNamespacedState?: boolean;
+}
+
export type ProgrammaticalNavigation =
| NavigateToExperiment
| NavigateToCompare
- | NavigateToExperiments;
+ | NavigateToExperiments
+ | NavigateToFlags;
export interface NavigationLambda {
actionCreator: ActionCreator;