* puppetboard/app.py: Refactoring the report event counts. Writing the report event counts code in the node(), reports() and reports_node() functions to iterate through report.events() instead of querying the event counts endpoint for each report. This solution is heavier than the original because we have to query for all full event objects for each report and iterate through them to interpret their statuses. I originally wanted to replace the report.events() function with an events variable for the Report object but that turned out to be not technically possible presently because the report extended events' timestamps for in a different format which python can't interpret. Specifically the timezone values contain ':', there is no Python 2.x documentation that states timezones containing colons is supported. This does, however, lighten our dependency on the event-counts endpoint which is marked as experimental. Which means that it may change or be removed in a future release. This also resolves a silent bug which may or may not include environment filters on the event-counts queries. report.events() searches the Events endpoint based on the report hash which eliminates the possibility of mistaken relationships. * puppetboard/app.py: Replacing url_for_* functions with a single url_for_field() The url_for_pagination and url_for_environments functions only worked with a single, fixed request argument, 'page' and 'env' respectively. The new url_for_field() excepts 2 arguments, field: the name of the argument to update, and value: its intended value. Should consider adding a url_for_fields() function that accepts a dict argument and updates all the request arguments using a dict.update(). There is currently no requirement for it so it will remain in the backlog. * puppetboard/templates/reports.html: Adding a dropdown menu to limit the report count This fixes https://github.com/voxpupuli/puppetboard/issues/202 This new dropdown allows users to select their desired number of reports on the reports() and reports_node() pages. The available options are app.config['REPORTS_COUNT'], 25, 50, 100 or All. The default value is determined by the REPORTS_COUNT configuration value. Had to modify url_for_field() to merge the request args to the view args in order to generate the links that include the limit query string.
199 lines
7.1 KiB
HTML
199 lines
7.1 KiB
HTML
{% macro facts_table(facts, current_env, autofocus=False, condensed=False, show_node=False, show_value=True, link_facts=False, margin_top=20, margin_bottom=20) -%}
|
|
<div class="ui fluid icon input hide" style="margin-bottom:20px">
|
|
<input {% if autofocus %} autofocus="autofocus" {% endif %} class="filter-table" placeholder="Type here to filter...">
|
|
</div>
|
|
<table class="ui very basic {% if condensed %}very{% endif%} compact sortable table" style="table-layout: fixed;">
|
|
<thead>
|
|
<tr>
|
|
{% if show_node %}
|
|
<th>Node</th>
|
|
{% else %}
|
|
<th class="default-sort">Fact</th>
|
|
{% endif %}
|
|
{% if show_value %}
|
|
<th>Value</th>
|
|
{% endif %}
|
|
</tr>
|
|
</thead>
|
|
<tbody class="searchable">
|
|
{% for fact in facts %}
|
|
<tr>
|
|
{% if show_node %}
|
|
<td><a href="{{url_for('node', env=current_env, node_name=fact.node)}}">{{fact.node}}</a></td>
|
|
{% else %}
|
|
<td><a href="{{url_for('fact', env=current_env, fact=fact.name)}}">{{fact.name}}</a></td>
|
|
{% endif %}
|
|
{% if show_value %}
|
|
<td style="word-wrap:break-word">
|
|
{% if link_facts %}
|
|
{% if fact.value is mapping %}
|
|
<a href="{{url_for('fact_value', env=current_env, fact=fact.name, value=fact.value)}}"><pre>{{fact.value|jsonprint}}</pre></a>
|
|
{% else %}
|
|
<a href="{{url_for('fact_value', env=current_env, fact=fact.name, value=fact.value)}}">{{fact.value}}</a>
|
|
{% endif %}
|
|
{% else %}
|
|
{% if fact.value is mapping %}
|
|
<pre>{{fact.value|jsonprint}}</pre>
|
|
{% else %}
|
|
{{fact.value}}
|
|
{% endif %}
|
|
{% endif %}
|
|
</td>
|
|
{% endif %}
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
{%- endmacro %}
|
|
{% macro facts_graph(facts, autofocus=False, condensed=False, show_node=False, margin_top=20, margin_bottom=20) -%}
|
|
<script src="{{url_for('static', filename='js/d3.min.js')}}"></script>
|
|
<script src="{{url_for('static', filename='js/c3.min.js')}}"></script>
|
|
<div id="factChart" width="300" height="300"></div>
|
|
<script type="text/javascript">
|
|
var data = [
|
|
{% for fact in facts|groupby('value') %}
|
|
{
|
|
label: '{{ fact.grouper.replace("\n", " ") }}',
|
|
value: {{ fact.list|length }}
|
|
},
|
|
{% endfor %}
|
|
{
|
|
value: 0,
|
|
}
|
|
]
|
|
var fact_values = data.map(function(item) { return [item.label, item.value]; }).filter(function(item){return item[0];}).sort(function(a,b){return b[1] - a[1];});
|
|
var realdata = fact_values.slice(0, 15);
|
|
var otherdata = fact_values.slice(15);
|
|
if (otherdata.length > 0) {
|
|
realdata.push(["other", otherdata.reduce(function(a,b){return a + b[1];},0)]);
|
|
}
|
|
var chart = c3.generate({
|
|
bindto: '#factChart',
|
|
data: {
|
|
columns: realdata,
|
|
type : 'pie',
|
|
}
|
|
});
|
|
|
|
</script>
|
|
{%- endmacro %}
|
|
|
|
{% macro reports_table(reports, reports_count, report_event_counts, current_env, condensed=False, hash_truncate=False, show_conf_col=True, show_agent_col=True, show_host_col=True, show_run_col=False, show_full_col=False, show_search_bar=False, searchable=False) -%}
|
|
{% if show_search_bar %}
|
|
<div class="ui fluid icon input hide" style="margin-bottom:20px">
|
|
<input autofocus="autofocus" class="filter-table" placeholder="Type here to filter...">
|
|
</div>
|
|
{% endif %}
|
|
<div class="ui info message">
|
|
|
|
Only showing {{reports_count}} reports sorted by Start Time.
|
|
|
|
</div>
|
|
<table class='ui very basic {% if condensed %}very compact{% endif %} table stackable sortable table'>
|
|
<thead>
|
|
<tr>
|
|
<th class="default-sort">Start time</th>
|
|
<th>Status</th>
|
|
{% if show_host_col %}
|
|
<th>Hostname</th>
|
|
{% endif %}
|
|
{% if show_run_col %}
|
|
<th>Run time</th>
|
|
{% endif %}
|
|
{% if show_full_col %}
|
|
<th>Full report</th>
|
|
{% endif %}
|
|
{% if show_conf_col %}
|
|
<th>Configuration version</th>
|
|
{% endif %}
|
|
{% if show_agent_col %}
|
|
<th>Agent version</th>
|
|
{% endif %}
|
|
<tr>
|
|
</thead>
|
|
<tbody {% if searchable %}class="searchable" {% endif %}>
|
|
{% for report in reports %}
|
|
{% if hash_truncate %}
|
|
{% set rep_hash = "%s…"|format(report.hash_[0:10])|safe %}
|
|
{% else %}
|
|
{% set rep_hash = report.hash_ %}
|
|
{% endif %}
|
|
{% if report.failed %}
|
|
<tr class="error">
|
|
{% else %}
|
|
<tr>
|
|
{% endif %}
|
|
<td rel="utctimestamp">{{report.start}}</td>
|
|
<td>
|
|
{% call status_counts(status=report.status, node_name=report.node, events=report_event_counts[report.hash_], report_hash=report.hash_, current_env=current_env) %}{% endcall %}
|
|
</td>
|
|
{% if show_host_col %}
|
|
<td><a href="{{url_for('node', env=current_env, node_name=report.node)}}">{{ report.node }}</a></td>
|
|
{% endif %}
|
|
{% if show_run_col %}
|
|
<td>{{report.run_time}}</td>
|
|
{% endif %}
|
|
{% if show_full_col %}
|
|
<td><a href="{{url_for('report', env=current_env, node_name=report.node, report_id=report.hash_)}}">{{rep_hash}}</a></td>
|
|
{% endif %}
|
|
{% if show_conf_col %}
|
|
<td>{{report.version}}</td>
|
|
{% endif %}
|
|
{% if show_agent_col %}
|
|
<td>{{report.agent_version}}</td>
|
|
{% endif %}
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
{%- endmacro %}
|
|
{% macro status_counts(caller, status, node_name, events, current_env, unreported_time=False, report_hash=False) -%}
|
|
<a class="ui
|
|
{% if status == 'failed' -%}
|
|
failed
|
|
{% elif status == 'changed' -%}
|
|
changed
|
|
{% elif status == 'unreported' -%}
|
|
unreported
|
|
{% elif status == 'noop' -%}
|
|
noop
|
|
{% elif status == 'unchanged' -%}
|
|
unchanged
|
|
{% endif -%}
|
|
label status" href="
|
|
{{url_for('report', env=current_env, node_name=node_name, report_id=report_hash)}}
|
|
">
|
|
{{ status|upper }}
|
|
</a>
|
|
{% if status == 'unreported' %}
|
|
<span class="ui label status"> {{ unreported_time|upper }} </span>
|
|
{% else %}
|
|
{% if events['failures'] %}<span class="ui small count label failed">{{events['failures']}}</span>{% else %}<span class="ui small count label">0</span>{% endif%}
|
|
{% if events['successes'] %}<span class="ui small count label changed">{{events['successes']}}</span>{% else %}<span class="ui small count label">0</span>{% endif%}
|
|
{% if events['skips'] %}<span class="ui small count label skipped">{{events['skips']}}</span>{% else %}<span class="ui small count label">0</span>{% endif%}
|
|
{% endif %}
|
|
{%- endmacro %}
|
|
{% macro render_pagination(pagination) -%}
|
|
<div class="ui pagination menu">
|
|
{% if pagination.has_prev %}
|
|
<a class="item" href="{{url_for_field('page', 1)}}">« First</a>
|
|
<a class="item" href="{{url_for_field('page', pagination.page - 1)}}">Prev</a>
|
|
{% endif %}
|
|
{% for page in pagination.iter_pages() %}
|
|
{% if page %}
|
|
{% if page != pagination.page %}
|
|
<a class="item" href="{{url_for_field('page', page)}}">{{page}}</a>
|
|
{% else %}
|
|
<a class="active item">{{page}}</a>
|
|
{% endif %}
|
|
{% else %}
|
|
<a class="disabled item">...</a>
|
|
{% endif %}
|
|
{% endfor %}
|
|
{% if pagination.has_next %}
|
|
<a class="item" href="{{url_for_field('page', pagination.page + 1)}}">Next</a>
|
|
<a class="item" href="{{url_for_field('page', pagination.pages)}}">Last »</a>
|
|
{% endif %}
|
|
</div>
|
|
{% endmacro %}
|