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
-------------
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).
<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>
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>
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);
});
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);
});
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
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);
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):
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):
"""
"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
},
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'])
{
"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']
]
}
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),
"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),
}
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),
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),
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),
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),
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),
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:
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),
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
}
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()
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);
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);
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);
});
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);
});
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);
});
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);
});