]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
pybind/mgr/dashboard: add url_prefix
authorNick Erdmann <n@nirf.de>
Mon, 21 Aug 2017 17:21:10 +0000 (17:21 +0000)
committerShinobu Kinjo <shinobu@redhat.com>
Sat, 4 Nov 2017 23:15:51 +0000 (08:15 +0900)
This adds a configuration variable url_prefix to the dashboard that
that is prepended to all URLs so you can access the dashboard at
http://$IP:$PORT/$PREFIX/. This is necessary if you wish to use a
reverse http proxy that forwards to the dashboard under a sub-path.

Fixes: http://tracker.ceph.com/issues/20568
Signed-off-by: Nick Erdmann <n@nirf.de>
(cherry picked from commit 6d285fff0df598d66f46d38bf0a6a2cc9c0dc62f)

12 files changed:
doc/mgr/dashboard.rst
src/pybind/mgr/dashboard/base.html
src/pybind/mgr/dashboard/clients.html
src/pybind/mgr/dashboard/filesystem.html
src/pybind/mgr/dashboard/health.html
src/pybind/mgr/dashboard/module.py
src/pybind/mgr/dashboard/osd_perf.html
src/pybind/mgr/dashboard/osds.html
src/pybind/mgr/dashboard/rbd_iscsi.html
src/pybind/mgr/dashboard/rbd_mirroring.html
src/pybind/mgr/dashboard/rbd_pool.html
src/pybind/mgr/dashboard/servers.html

index 8908497003dae367fe2244e879135e1a3998d214..4c2116b133ec7cba825fec311c8cd06ae94e1c6e 100644 (file)
@@ -39,6 +39,13 @@ If the port is not configured, the web app will bind to port ``7000``.
 If the address it not configured, the web app will bind to ``::``,
 which corresponds to all available IPv4 and IPv6 addresses.
 
+You can configure a prefix for all URLs::
+
+  ceph config-key set mgr/dashboard/url_prefix $PREFIX
+
+so you can access the dashboard at ``http://$IP:$PORT/$PREFIX/``.
+
+
 Load balancer
 -------------
 
@@ -48,4 +55,5 @@ manager is active (e.g., ``ceph mgr dump``).  In order to make the
 dashboard available via a consistent URL regardless of which manager
 daemon is currently active, you may want to set up a load balancer
 front-end to direct traffic to whichever manager endpoint is
-available.
+available. If you use a reverse http proxy that forwards a subpath to
+the dashboard, you need to configure ``url_prefix`` (see above).
index 146bd92d0c807a2d7aaa7fd06b60ac42288b3e02..f85efd7d0925618a6093cdc992cdd8b3819c6d3d 100644 (file)
@@ -9,27 +9,27 @@
     <meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"
           name="viewport">
     <link rel="stylesheet"
-          href="/static/AdminLTE-2.3.7/bootstrap/css/bootstrap.min.css">
+          href="{{ url_prefix }}/static/AdminLTE-2.3.7/bootstrap/css/bootstrap.min.css">
     <link rel="stylesheet"
           href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.5.0/css/font-awesome.min.css">
     <link rel="stylesheet"
           href="https://cdnjs.cloudflare.com/ajax/libs/ionicons/2.0.1/css/ionicons.min.css">
     <link rel="stylesheet"
-          href="/static/AdminLTE-2.3.7/dist/css/AdminLTE.min.css">
+          href="{{ url_prefix }}/static/AdminLTE-2.3.7/dist/css/AdminLTE.min.css">
     <link rel="stylesheet"
-          href="/static/AdminLTE-2.3.7/dist/css/skins/skin-blue.min.css">
+          href="{{ url_prefix }}/static/AdminLTE-2.3.7/dist/css/skins/skin-blue.min.css">
     <link rel="stylesheet"
-          href="/static/AdminLTE-2.3.7/plugins/datatables/jquery.dataTables.css">
+          href="{{ url_prefix }}/static/AdminLTE-2.3.7/plugins/datatables/jquery.dataTables.css">
 
-    <script src="/static/AdminLTE-2.3.7/plugins/jQuery/jquery-2.2.3.min.js"></script>
-    <script src="/static/AdminLTE-2.3.7/plugins/sparkline/jquery.sparkline.min.js"></script>
+    <script src="{{ url_prefix }}/static/AdminLTE-2.3.7/plugins/jQuery/jquery-2.2.3.min.js"></script>
+    <script src="{{ url_prefix }}/static/AdminLTE-2.3.7/plugins/sparkline/jquery.sparkline.min.js"></script>
 
-    <script src="/static/rivets.bundled.min.js"></script>
-    <script src="/static/underscore-min.js"></script>
+    <script src="{{ url_prefix }}/static/rivets.bundled.min.js"></script>
+    <script src="{{ url_prefix }}/static/underscore-min.js"></script>
 
-    <script src="/static/AdminLTE-2.3.7/bootstrap/js/bootstrap.min.js"></script>
-    <script src="/static/AdminLTE-2.3.7/dist/js/app.min.js"></script>
-    <script src="/static/AdminLTE-2.3.7/plugins/datatables/jquery.dataTables.min.js"></script>
+    <script src="{{ url_prefix }}/static/AdminLTE-2.3.7/bootstrap/js/bootstrap.min.js"></script>
+    <script src="{{ url_prefix }}/static/AdminLTE-2.3.7/dist/js/app.min.js"></script>
+    <script src="{{ url_prefix }}/static/AdminLTE-2.3.7/plugins/datatables/jquery.dataTables.min.js"></script>
 
     <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.17.1/moment.min.js"></script>
     <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.4.0/Chart.min.js"></script>
@@ -42,7 +42,7 @@
             var refresh_interval = 5000;
 
             var refresh = function() {
-                $.get("/toplevel_data", function(data) {
+                $.get("{{ url_prefix }}/toplevel_data", function(data) {
                     _.extend(toplevel_data, data);
                     setTimeout(refresh, refresh_interval);
                 });
       </script>
 
       <link rel="shortcut icon" href="http://ceph.com/wp-content/themes/ceph/favicon.ico">
-      <link rel="shortcut icon" href="/static/favicon.ico">
+      <link rel="shortcut icon" href="{{ url_prefix }}/static/favicon.ico">
 
     <style>
         div.box {
     <!-- Main Header -->
     <header class="main-header">
         <!-- Logo -->
-        <a href="/" class="logo">
+        <a href="{{ url_prefix }}/" class="logo">
       <span class="logo-lg">
-        <img src="/static/Ceph_Logo_Standard_RGB_White_120411_fa.png"
+        <img src="{{ url_prefix }}/static/Ceph_Logo_Standard_RGB_White_120411_fa.png"
              width="123px" height="34px"/>
           </span>
       <span class="logo-mini">
-        <img src="/static/logo-mini.png"
+        <img src="{{ url_prefix }}/static/logo-mini.png"
              width="34px" height="34px"/>
           </span>
         </a>
       <ul class="sidebar-menu">
         <!-- Optionally, you can add icons to the links -->
         <li class="{%if path_info=='/' or path_info.startswith('/health')%}active{%endif%}">
-            <a href="/health">
+            <a href="{{ url_prefix }}/health">
             <i class="fa fa-heartbeat" rv-style="health_status | health_color"></i>
             <span>Cluster health</span></a>
         </li>
           </a>
           <ul class="treeview-menu menu-open">
             <li>
-                <a href="/servers">Servers</a>
+                <a href="{{ url_prefix }}/servers">Servers</a>
             </li>
             <li>
-                <a href="/osd">OSDs</a>
+                <a href="{{ url_prefix }}/osd">OSDs</a>
             </li>
           </ul>
         </li>
           </a>
           <ul class="treeview-menu menu-open">
             <li>
-              <a href="/rbd_mirroring">
+              <a href="{{ url_prefix }}/rbd_mirroring">
                 <i class="fa fa-exchange"></i> Mirroring
                 <span class="pull-right-container">
                     <small rv-hide="rbd_mirroring.warnings | hide_count_box" class="label pull-right bg-yellow">{rbd_mirroring.warnings}</small>
               </a>
             </li>
             <li>
-              <a href="/rbd_iscsi">
+              <a href="{{ url_prefix }}/rbd_iscsi">
                 <i class="fa fa-upload"></i> iSCSI
                 <span class="pull-right-container" />
               </a>
index bd5a557210bca21fc92c69806a03bec57445aaa0..ab99bf764e768037bd40ebcdabf4e17dded8bee1 100644 (file)
@@ -8,7 +8,7 @@
             var content_data = {{ content_data }};
 
             var refresh = function() {
-                $.get("/clients_data/" + content_data.fscid  + "/", function(data) {
+                $.get("{{ url_prefix }}/clients_data/" + content_data.fscid  + "/", function(data) {
                     content_data.clients = data;
                     setTimeout(refresh, 5000);
                 });
index ae3116c53a5015bee1958ee69ada7f15063da1a4..60a97a007d7628967174261be927d8637a77cc2e 100644 (file)
@@ -8,7 +8,7 @@
             var content_data = {{ content_data }};
 
             var refresh = function() {
-                $.get("/filesystem_data/" + content_data.fs_status.filesystem.id  + "/", function(data) {
+                $.get("{{ url_prefix }}/filesystem_data/" + content_data.fs_status.filesystem.id  + "/", function(data) {
                     _.extend(content_data.fs_status, data);
                     setTimeout(refresh, 5000);
                 });
@@ -71,7 +71,7 @@
             var rhs_transform = delta_timeseries;
 
             var draw_chart = function() {
-                $.get("/mds_counters/" + content_data.fs_status.filesystem.id  + "/", function(data) {
+                $.get("{{ url_prefix }}/mds_counters/" + content_data.fs_status.filesystem.id  + "/", function(data) {
                     var top_chart = true;
 
                     // Cull any chart elements that correspond to MDSs no
index 7b3e6f52787eb38c27c86995702490dc2d422932..e076d4bb0ad528232176c6b4fec19d7d3c4ce95c 100644 (file)
             rivets.bind($("#content"), content_data);
 
             var refresh = function() {
-                $.get("/health_data", function(data) {
+                $.get("{{ url_prefix }}/health_data", function(data) {
                     _.extend(content_data, data);
                     draw_usage_charts();
                     setTimeout(refresh, 5000);
index 526007e00f8d0f5cfc7a13508309f8f16b5728d8..252d06c6e0565c5ef74708737becc244fa6ffd09 100644 (file)
@@ -62,6 +62,10 @@ def recurse_refs(root, path):
 
     log.info("%s %d (%s)" % (path, sys.getrefcount(root), root.__class__))
 
+def get_prefixed_url(url):
+    return global_instance().url_prefix + url
+
+
 
 class StandbyModule(MgrStandbyModule):
     def serve(self):
@@ -143,6 +147,9 @@ class Module(MgrModule):
         self.pool_stats = defaultdict(lambda: defaultdict(
             lambda: collections.deque(maxlen=10)))
 
+        # A prefix for all URLs to use the dashboard with a reverse http proxy
+        self.url_prefix = ''
+
     @property
     def rados(self):
         """
@@ -418,7 +425,7 @@ class Module(MgrModule):
                 "id": fs_id,
                 "name": mdsmap['fs_name'],
                 "client_count": client_count,
-                "clients_url": "/clients/{0}/".format(fs_id),
+                "clients_url": get_prefixed_url("/clients/{0}/".format(fs_id)),
                 "ranks": rank_table,
                 "pools": pools_table
             },
@@ -484,7 +491,7 @@ class Module(MgrModule):
                 rbd_pools = sorted([
                     {
                         "name": name,
-                        "url": "/rbd_pool/{0}/".format(name)
+                        "url": get_prefixed_url("/rbd_pool/{0}/".format(name))
                     }
                     for name in data
                 ], key=lambda k: k['name'])
@@ -499,7 +506,7 @@ class Module(MgrModule):
                     {
                         "id": f['id'],
                         "name": f['mdsmap']['fs_name'],
-                        "url": "/filesystem/{0}/".format(f['id'])
+                        "url": get_prefixed_url("/filesystem/{0}/".format(f['id']))
                     }
                     for f in fsmap.data['filesystems']
                 ]
@@ -523,6 +530,7 @@ class Module(MgrModule):
                 }
 
                 return template.render(
+                    url_prefix = global_instance().url_prefix,
                     ceph_version=global_instance().version,
                     path_info=cherrypy.request.path_info,
                     toplevel_data=json.dumps(toplevel_data, indent=2),
@@ -586,11 +594,12 @@ class Module(MgrModule):
                     "clients": clients,
                     "fs_name": fs_name,
                     "fscid": fscid,
-                    "fs_url": "/filesystem/" + fscid_str + "/"
+                    "fs_url": get_prefixed_url("/filesystem/" + fscid_str + "/")
                 }
 
                 template = env.get_template("clients.html")
                 return template.render(
+                    url_prefix = global_instance().url_prefix,
                     ceph_version=global_instance().version,
                     path_info=cherrypy.request.path_info,
                     toplevel_data=json.dumps(self._toplevel_data(), indent=2),
@@ -635,6 +644,7 @@ class Module(MgrModule):
                 }
 
                 return template.render(
+                    url_prefix = global_instance().url_prefix,
                     ceph_version=global_instance().version,
                     path_info=cherrypy.request.path_info,
                     toplevel_data=json.dumps(toplevel_data, indent=2),
@@ -661,6 +671,7 @@ class Module(MgrModule):
                 content_data = self._rbd_mirroring()
 
                 return template.render(
+                    url_prefix = global_instance().url_prefix,
                     ceph_version=global_instance().version,
                     path_info=cherrypy.request.path_info,
                     toplevel_data=json.dumps(toplevel_data, indent=2),
@@ -687,6 +698,7 @@ class Module(MgrModule):
                 content_data = self._rbd_iscsi()
 
                 return template.render(
+                    url_prefix = global_instance().url_prefix,
                     ceph_version=global_instance().version,
                     path_info=cherrypy.request.path_info,
                     toplevel_data=json.dumps(toplevel_data, indent=2),
@@ -702,6 +714,7 @@ class Module(MgrModule):
             def health(self):
                 template = env.get_template("health.html")
                 return template.render(
+                    url_prefix = global_instance().url_prefix,
                     ceph_version=global_instance().version,
                     path_info=cherrypy.request.path_info,
                     toplevel_data=json.dumps(self._toplevel_data(), indent=2),
@@ -712,6 +725,7 @@ class Module(MgrModule):
             def servers(self):
                 template = env.get_template("servers.html")
                 return template.render(
+                    url_prefix = global_instance().url_prefix,
                     ceph_version=global_instance().version,
                     path_info=cherrypy.request.path_info,
                     toplevel_data=json.dumps(self._toplevel_data(), indent=2),
@@ -865,6 +879,17 @@ class Module(MgrModule):
                         ret[k1][k2] = sorted_dict
                 return ret
 
+        url_prefix = self.get_config('url_prefix')
+        if url_prefix == None:
+            url_prefix = ''
+        else:
+            if len(url_prefix) != 0:
+                if url_prefix[0] != '/':
+                    url_prefix = '/'+url_prefix
+                if url_prefix[-1] == '/':
+                    url_prefix = url_prefix[:-1]
+        self.url_prefix = url_prefix
+
         server_addr = self.get_localized_config('server_addr', '::')
         server_port = self.get_localized_config('server_port', '7000')
         if server_addr is None:
@@ -936,6 +961,7 @@ class Module(MgrModule):
                 toplevel_data = self._toplevel_data()
 
                 return template.render(
+                    url_prefix = global_instance().url_prefix,
                     ceph_version=global_instance().version,
                     path_info='/osd' + cherrypy.request.path_info,
                     toplevel_data=json.dumps(toplevel_data, indent=2),
@@ -977,7 +1003,7 @@ class Module(MgrModule):
                 result['up'] = osd_info['up']
                 result['in'] = osd_info['in']
 
-                result['url'] = "/osd/perf/{0}".format(osd_id)
+                result['url'] = get_prefixed_url("/osd/perf/{0}".format(osd_id))
 
                 return result
 
@@ -1030,14 +1056,15 @@ class Module(MgrModule):
                 }
 
                 return template.render(
+                    url_prefix = global_instance().url_prefix,
                     ceph_version=global_instance().version,
                     path_info='/osd' + cherrypy.request.path_info,
                     toplevel_data=json.dumps(toplevel_data, indent=2),
                     content_data=json.dumps(content_data, indent=2)
                 )
 
-        cherrypy.tree.mount(Root(), "/", conf)
-        cherrypy.tree.mount(OSDEndpoint(), "/osd", conf)
+        cherrypy.tree.mount(Root(), get_prefixed_url("/"), conf)
+        cherrypy.tree.mount(OSDEndpoint(), get_prefixed_url("/osd"), conf)
 
         log.info("Starting engine on {0}:{1}...".format(
             server_addr, server_port))
index 7ab958effb56d3bb6b0fd06dcfbc4e9324af2786..b13ad17e1fd7ba0791ca12afa59f8ce1d11b940a 100644 (file)
@@ -96,7 +96,7 @@
             post_load();
 
             var refresh = function() {
-                $.get("/osd/perf_data/" + content_data.osd.osd  + "/", function(data) {
+                $.get("{{ url_prefix }}/osd/perf_data/" + content_data.osd.osd  + "/", function(data) {
                     _.extend(content_data.osd_histogram, data.osd_histogram);
                     _.extend(content_data.osd, data.osd);
                     _.extend(content_data.osd_metadata, data.osd_metadata);
index ddcb8577d9526ba5f72fcdd40c5675dca8342447..b55b3df594c281acd72a41ae1452c9487775c1b3 100644 (file)
@@ -8,7 +8,7 @@
             var content_data = {{ content_data }};
 
             var refresh = function() {
-                $.get("/osd/list_data/", function(data) {
+                $.get("{{ url_prefix }}/osd/list_data/", function(data) {
                     content_data.osds_by_server = data;
                     $('.inlinesparkline').sparkline();
                     setTimeout(refresh, 5000);
index 105f5dec8aa5600878f5c47edf77af9e4d324647..b8e47fdb23488b4316c41c1debc982a857de5ac2 100644 (file)
@@ -8,7 +8,7 @@
             var content_data = {{ content_data }};
 
             var refresh = function() {
-                $.get("/rbd_iscsi_data", function(data) {
+                $.get("{{ url_prefix }}/rbd_iscsi_data", function(data) {
                     _.extend(content_data, data);
                     setTimeout(refresh, 30000);
                 });
index b83aadd08f40b1ff7c7d842da3e9e66450f25d63..2720685474a04f85f946d11cce6ea3615dc0d287 100644 (file)
@@ -8,7 +8,7 @@
             var content_data = {{ content_data }};
 
             var refresh = function() {
-                $.get("/rbd_mirroring_data", function(data) {
+                $.get("{{ url_prefix }}/rbd_mirroring_data", function(data) {
                     _.extend(content_data, data);
                     setTimeout(refresh, 30000);
                 });
index 973bc3717d89e74ea290b3811a61194e6a0d8db5..0d0e54fdc6f3153f5eb012a3144e5102bfea917d 100644 (file)
@@ -8,7 +8,7 @@
             var content_data = {{ content_data }};
 
             var refresh = function() {
-                $.get("/rbd_pool_data/" + content_data.pool_name  + "/", function(data) {
+                $.get("{{ url_prefix }}/rbd_pool_data/" + content_data.pool_name  + "/", function(data) {
                     content_data.images = data;
                     setTimeout(refresh, 10000);
                 });
index e6c8b3cf603336ea9d55181b6209520ab463433d..421d3389c3585edb09d8845208d6bda5f7ce2380 100644 (file)
@@ -8,7 +8,7 @@
             var content_data = {{ content_data }};
 
             var refresh = function() {
-                $.get("/servers_data", function(data) {
+                $.get("{{ url_prefix }}/servers_data", function(data) {
                     _.extend(content_data, data);
                     setTimeout(refresh, 5000);
                 });