<i class="fa fa-heartbeat" rv-style="health_status | health_color"></i>
<span>Cluster health</span></a>
</li>
- <li class="treeview{%if path_info.startswith(('/server', '/osd'))%} active{%endif%}">
+ <li class="treeview{%if path_info.startswith(('/server', '/osd', '/monitor'))%} active{%endif%}">
<a href="#"><i class="fa fa-server"></i> <span>Cluster</span>
<span class="pull-right-container">
<i class="fa fa-angle-left pull-right"></i>
<li>
<a href="{{ url_prefix }}/osd">OSDs</a>
</li>
+ <li>
+ <a href="{{ url_prefix }}/monitors">Monitors</a>
+ </li>
</ul>
</li>
<li class="treeview{%if path_info.startswith('/rbd')%} active{%endif%}">
return global_instance().url_prefix + url
-
class StandbyModule(MgrStandbyModule):
def serve(self):
server_addr = self.get_localized_config('server_addr', '::')
content_data=json.dumps(self._servers(), indent=2)
)
+ @cherrypy.expose
+ def monitors(self):
+ template = env.get_template("monitors.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),
+ content_data=json.dumps(self._monitors(), indent=2)
+ )
+
+ @cherrypy.expose
+ @cherrypy.tools.json_out()
+ def monitors_data(self):
+ return self._monitors
+
+ def _monitors(self):
+ in_quorum, out_quorum = [], []
+
+ counters = [ 'mon.num_sessions'
+ ]
+
+ mon_status = global_instance().get_sync_object(MonStatus).data
+ for mon in mon_status["monmap"]["mons"]:
+ mon["stats"] = {}
+ for counter in counters:
+ data = global_instance().get_counter("mon", mon["name"], counter)
+ if data is not None:
+ mon["stats"][counter.split(".")[1]] = data[counter]
+ else:
+ mon["stats"][counter.split(".")[1]] = []
+ if mon["rank"] in mon_status["quorum"]:
+ in_quorum.append(mon)
+ else:
+ out_quorum.append(mon)
+
+ return {
+ 'mon_status': mon_status,
+ 'in_quorum' : in_quorum,
+ 'out_quorum': out_quorum,
+ }
+
def _servers(self):
return {
'servers': global_instance().list_servers()
--- /dev/null
+
+{% extends "base.html" %}
+
+{% block content %}
+
+
+<script>
+ $(document).ready(function(){
+ // Pre-populated initial data at page load
+ var content_data = {{ content_data }};
+ var refresh = function() {
+ $.get("{{ url_prefix }}/monitors_data", function(data) {
+ _.extend(content_data, data);
+ $('.inlinesparkline').sparkline();
+ setTimeout(refresh, 5000);
+ });
+ };
+ setTimeout(refresh, 5000);
+
+ rivets.formatters.sparkline_data = function(time_series) {
+ result = "";
+ for (var i = 1; i < time_series.length; ++i) {
+ var y = time_series[i][1];
+ result += (y + ",");
+ }
+ return result;
+
+ };
+
+ rivets.bind($("#content"), content_data);
+ $(".inlinesparkline").sparkline();
+ });
+</script>
+
+<!-- Page Header -->
+<section class="content-header">
+ <h1 style="font-weight:bold">
+ Monitors
+ </h1>
+</section>
+
+<!-- Main content -->
+<section class="content text-center">
+ <div class="row">
+ <div class="col-md-4">
+ <div class="box text-left" style="font-size:13.4px">
+ <table class="table table-bordered">
+ <tr><td><p><span style="color:yellow; font-weight:bold">Cluster ID: </span></td><td>{mon_status.monmap.fsid}</p></td></tr>
+ <tr><td><p><span style="color:yellow; font-weight:bold">monmap modified: </span></td><td> {mon_status.monmap.modified}</p></td></tr>
+ <tr><td><p><span style="color:yellow; font-weight:bold">monmap epoch: </span></td><td> {mon_status.monmap.epoch}</p></td></tr>
+ <tr><td><p><span style="color:yellow; font-weight:bold">quorum con: </span></td><td> {mon_status.features.quorum_con}</p></td></tr>
+ <tr><td><p><span style="color:yellow; font-weight:bold">quorum mon: </span></td><td> {mon_status.features.quorum_mon}</p></td></tr>
+ <tr><td><p><span style="color:yellow; font-weight:bold">required con: </span></td><td> {mon_status.features.required_con}</p></td></tr>
+ <tr><td><p><span style="color:yellow; font-weight:bold">required mon: </span></td><td> {mon_status.features.required_mon}</p></td></tr>
+ </table>
+ </div>
+ </div>
+ <div class="col-md-8">
+ <div class="box">
+ <h3 style="color:#00bb00;font-weight:bold">IN QUORUM</h3>
+ <div class="box-body">
+ <table class="table table-bordered text-center">
+ <thead>
+ <th>Name</th>
+ <th>Rank</th>
+ <th>Public Addr</th>
+ <th>Open Sessions</th>
+ </thead>
+ <tr rv-each-mon="in_quorum" style="font-size:15px;height:80px">
+ <td style="font-weight:bold">{mon.name}</td>
+ <td>{mon.rank}</td>
+ <td>{mon.public_addr}</td>
+ <td><span class="inlinesparkline" style="font-size:30px">{ mon.stats.num_sessions | sparkline_data }</span></td>
+ </tr>
+ </table>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="box">
+ <h3 style="color:#00bb00; font-weight:bold">NOT IN QUORUM</h3>
+ <div class="box-body">
+ <table class="table table-bordered text-center">
+ <thead>
+ <th>Name</th>
+ <th>Rank</th>
+ <th>Public Addr</th>
+ </thead>
+ <tr rv-each-mon="out_quorum" class="font-size:15px;height:80px">
+ <td style="font-weight:bold">{mon.name}</td>
+ <td>{mon.rank}</td>
+ <td>{mon.public_addr}</td>
+ </tr>
+ </table>
+ </div>
+ </div>
+</section>
+
+
+{% endblock %}