]> 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)
committerNick Erdmann <n@nirf.de>
Tue, 22 Aug 2017 16:14:42 +0000 (16:14 +0000)
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>
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 1d4947c26de3f5b22ce982777603196f47ea9895..f21145ac814fd2c304d78c2157560313a92d4e43 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 c58f1e18fd57c967444e5b84e6a65188d9d49b67..d6246f0c3e8c43ac9569e957f902f0e7551a1f4a 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 59dc312d9fa11ec7bfa9dd64f94018345316f4ea..b46eef245d5c99090d6899d76ed9b540c190ecd9 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 ea1e8a7e88bd0c6103468989d4c40f28b60a3da8..ccff4e552eac358d588210a670dc7861971f44ab 100644 (file)
@@ -61,6 +61,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 Module(MgrModule):
     def __init__(self, *args, **kwargs):
@@ -98,6 +102,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):
         """
@@ -374,7 +381,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
             },
@@ -440,7 +447,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'])
@@ -455,7 +462,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']
                 ]
@@ -479,6 +486,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),
@@ -542,11 +550,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),
@@ -591,6 +600,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),
@@ -617,6 +627,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),
@@ -643,6 +654,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),
@@ -658,6 +670,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),
@@ -668,6 +681,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),
@@ -821,6 +835,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:
@@ -882,6 +907,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),
@@ -923,7 +949,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
 
@@ -976,14 +1002,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...")
         cherrypy.engine.start()
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);
                 });