Merge pull request #176 from corey-hammerton/0.1.0

puppetboard: Adding PuppetDB 3.x support

Some of the implementations here is:

- Environment Awareness with a dropdown menu to switch environments
- First implementation of pagination in the form of a macro for flexibility
- Use of the PuppetDB 3.x APIs, therefore PuppetDB 2.x compatibility is broken
This commit is contained in:
Corey Hammerton
2015-11-09 20:42:27 -05:00
21 changed files with 716 additions and 246 deletions

View File

@@ -1,4 +1,4 @@
{% macro facts_table(facts, autofocus=False, condensed=False, show_node=False, show_value=True, link_facts=False, margin_top=20, margin_bottom=20) -%}
{% 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>
@@ -19,16 +19,24 @@
{% for fact in facts %}
<tr>
{% if show_node %}
<td><a href="{{url_for('node', node_name=fact.node)}}">{{fact.node}}</a></td>
<td><a href="{{url_for('node', env=current_env, node_name=fact.node)}}">{{fact.node}}</a></td>
{% else %}
<td><a href="{{url_for('fact', fact=fact.name)}}">{{fact.name}}</a></td>
<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 %}
<a href="{{url_for('fact_value', fact=fact.name, value=fact.value)}}">{{fact.value}}</a>
{% 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 %}
{{fact.value}}
{% if fact.value is mapping %}
<pre>{{fact.value|jsonprint}}</pre>
{% else %}
{{fact.value}}
{% endif %}
{% endif %}
</td>
{% endif %}
@@ -70,30 +78,40 @@
</script>
{%- endmacro %}
{% macro reports_table(reports, nodename, reports_count, condensed=False, hash_truncate=False, show_conf_col=True, show_agent_col=True, show_host_col=True) -%}
{% 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 the last {{reports_count}} reports.
Only showing {{reports_count}} reports sorted by Start Time.
</div>
<table class='ui table basic {% if condensed %}compact{% endif %}'>
<table class='ui table basic {% if condensed %}compact{% endif %} report'>
<thead>
<tr>
<th>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 %}
{% if show_host_col %}
<th>Hostname</th>
{% endif %}
<tr>
</thead>
<tbody>
<tbody {% if searchable %}class="searchable" {% endif %}>
{% for report in reports %}
{% if hash_truncate %}
{% set rep_hash = "%s&hellip;"|format(report.hash_[0:10])|safe %}
@@ -105,21 +123,78 @@
{% else %}
<tr>
{% endif %}
<td rel="utctimestamp">{{report.start}}</td>
<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>
<td><a href="{{url_for('report', node_name=nodename, report_id=report.hash_)}}">{{rep_hash}}</a></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 %}
{% if show_host_col %}
<td><a href="{{url_for('node', node_name=report.node)}}">{{ report.node }}</a></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 small status label
{% if status == 'failed' -%}
red
{% elif status == 'changed' -%}
green
{% elif status == 'unreported' -%}
black
{% elif status == 'noop' -%}
blue
{% endif -%}
" href="
{% if report_hash -%}
{{url_for('report', env=current_env, node_name=node_name, report_id=report_hash)}}
{% else -%}
{{url_for('report_latest', env=current_env, node_name=node_name)}}
{% endif -%}
">
{{status}}
</a>
{% if status == 'unreported' %}
<span class="ui small label status"> {{ unreported_time }} </span>
{% else %}
{% if events['failures'] %}<span class="ui small count label red">{{events['failures']}}</span>{% else %}<span class="ui small count label">0</span>{% endif%}
{% if events['successes'] %}<span class="ui small count label green">{{events['successes']}}</span>{% else %}<span class="ui small count label">0</span>{% endif%}
{% if events['skips'] %}<span class="ui small count label orange">{{events['skips']}}</span>{% else %}<span class="ui small count label">0</span>{% endif%}
{% endif %}
{%- endmacro %}
{% macro render_pagination(pagination) -%}
<div class="pagination">
{% if pagination.has_prev %}
<a href="{{url_for_pagination(1)}}">&laquo; First</a>
<a href="{{url_for_pagination(pagination.page - 1)}}">Prev</a>
{% endif %}
{% for page in pagination.iter_pages() %}
{% if page %}
{% if page != pagination.page %}
<a href="{{url_for_pagination(page)}}">{{page}}</a>
{% else %}
<span style="font-weight:bold;">{{page}}</span>
{% endif %}
{% else %}
<span class="ellipsis">...</span>
{% endif %}
{% endfor %}
{% if pagination.has_next %}
<a href="{{url_for_pagination(pagination.page + 1)}}">Next</a>
<a href="{{url_for_pagination(pagination.pages)}}">Last &raquo;</a>
{% endif %}
</div>
{% endmacro %}

View File

@@ -14,7 +14,7 @@
</thead>
<tbody>
<tr>
<td><a href="{{url_for('node', node_name=catalog.node)}}">{{catalog.node}}</a></td>
<td><a href="{{url_for('node', env=current_env, node_name=catalog.node)}}">{{catalog.node}}</a></td>
<td>{{catalog.version}}</td>
<td>{{catalog.transaction_uuid}}</td>
</tr>

View File

@@ -17,12 +17,12 @@
{% for node in nodes %}
<tr>
<td></td>
<td><a href="{{url_for('node', node_name=node.name)}}">{{node.name}}</a></td>
<td><a rel="utctimestamp" href="{{url_for('catalog_node', node_name=node.name)}}">{{node.catalog_timestamp}}</a></td>
<td><a href="{{url_for('node', env=current_env, node_name=node.name)}}">{{node.name}}</a></td>
<td><a rel="utctimestamp" href="{{url_for('catalog_node', env=current_env, node_name=node.name)}}">{{node.catalog_timestamp}}</a></td>
<td>
{% if node.form %}
<div class="ui form">
<form method="POST" action="{{url_for('catalog_submit')}}">
<form method="POST" action="{{url_for('catalog_submit', env=current_env)}}">
{{node.form.csrf_token}}
<div class="field inline">
{{node.form.compare}}

View File

@@ -6,8 +6,8 @@
{{macros.facts_graph(facts, autofocus=True, show_node=True, margin_bottom=10)}}
{% endif %}
{% if value %}
{{macros.facts_table(facts, autofocus=True, show_node=True, show_value=False, margin_bottom=10)}}
{{macros.facts_table(facts, current_env=current_env, autofocus=True, show_node=True, show_value=False, margin_bottom=10)}}
{% else %}
{{macros.facts_table(facts, autofocus=True, show_node=True, link_facts=True, margin_bottom=10)}}
{{macros.facts_table(facts, current_env=current_env, autofocus=True, show_node=True, link_facts=True, margin_bottom=10)}}
{% endif %}
{% endblock content %}

View File

@@ -8,7 +8,7 @@
<span class='ui label darkblue'>{{key}}</span>
<ul class="searchable">
{%- for fact in facts_list %}
<li><a href="{{url_for('fact', fact=fact)}}">{{fact}}</a></li>
<li><a href="{{url_for('fact', env=current_env, fact=fact)}}">{{fact}}</a></li>
{%- endfor %}
</ul>
{% endfor %}

View File

@@ -1,9 +1,10 @@
{% extends 'layout.html' %}
{% import '_macros.html' as macros %}
{% block content %}
<div class="ui vertical grid">
<div class="four column row">
<div class="column">
<a href="nodes?status=failed">
<a href="{{url_for('nodes', env=current_env, status='failed')}}">
<h1 class="ui red header no-margin-bottom">
{{stats['failed']}}
<small>{% if stats['failed']== 1 %} node {% else %} nodes {% endif %}</small>
@@ -12,7 +13,7 @@
<span>with status failed</span>
</div>
<div class="column">
<a href="nodes?status=noop">
<a href="{{url_for('nodes', env=current_env, status='noop')}}">
<h1 class="ui header purple no-margin-bottom">
{{stats['noop']}}
<small>{% if stats['noop']== 1 %} node {% else %} nodes {% endif %}</small>
@@ -21,7 +22,7 @@
<span>with status pending</span>
</div>
<div class="column">
<a href="nodes?status=changed">
<a href="{{url_for('nodes', env=current_env, status='changed')}}">
<h1 class="ui header green no-margin-bottom">
{{stats['changed']}}
<small>{% if stats['changed']== 1 %} node {% else %} nodes {% endif %}</small>
@@ -30,7 +31,7 @@
<span>with status changed</span>
</div>
<div class="column">
<a href="nodes?status=unreported">
<a href="{{url_for('nodes', env=current_env, status='unreported')}}">
<h1 class="ui header black no-margin-bottom">
{{ stats['unreported'] }}
<small>{% if stats['unreported']== 1 %} node {% else %} nodes {% endif %}</small>
@@ -75,40 +76,21 @@
{% if node.status != 'unchanged' %}
<tr>
<td>
<a class="ui small status label
{% if node.status == 'failed' %}
red
{% elif node.status == 'changed' %}
green
{% elif node.status == 'unreported' %}
black
{% elif node.status == 'noop' %}
blue
{% endif %}
" href="{{url_for('report_latest', node_name=node.name)}}">
{{node.status}}
</a>
{% if node.status=='unreported'%}
<span class="ui small label status"> {{ node.unreported_time }} </span>
{% else %}
{% if node.events['failures'] %}<span class="ui small count label red">{{node.events['failures']}}</span>{% else %}<span class="ui small count label">0</span>{% endif%}
{% if node.events['successes'] %}<span class="ui small count label green">{{node.events['successes']}}</span>{% else %}<span class="ui small count label">0</span>{% endif%}
{% if node.events['skips'] %}<span class="ui small count label yellow">{{node.events['skips']}}</span>{% else %}<span class="ui small count label">0</span>{% endif%}
{% endif %}
{{macros.status_counts(status=node.status, node_name=node.name, events=node.events, unreported_time=node.unreported_time, current_env=current_env)}}
</td>
<td>
<a href="{{url_for('node', node_name=node.name)}}">{{ node.name }}</a>
<a href="{{url_for('node', env=current_env, node_name=node.name)}}">{{ node.name }}</a>
</td>
<td>
{% if node.report_timestamp %}
<a href="{{url_for('report_latest', node_name=node.name)}}" rel='utctimestamp'>{{ node.report_timestamp }}</a>
<a href="{{url_for('report_latest', env=current_env, node_name=node.name)}}" rel='utctimestamp'>{{ node.report_timestamp }}</a>
{% else %}
<i class="large ban circle icon"></i>
{% endif %}
</td>
<td>
{% if node.report_timestamp %}
<a title='Reports' href="{{url_for('reports_node', node_name=node.name)}}"><i class='large darkblue book icon'></i></a>
<a title='Reports' href="{{url_for('reports_node', env=current_env, node_name=node.name)}}"><i class='large darkblue book icon'></i></a>
{% endif %}
</td>
</tr>

View File

@@ -23,7 +23,7 @@
</head>
<body>
<nav class="ui fixed darkblue inverted menu">
<div class="ui fixed darkblue inverted menu">
<div class="title item">
Puppetboard
</div>
@@ -38,10 +38,19 @@
('query', 'Query'),
] %}
<a {% if endpoint == request.endpoint %} class="active item" {% else %} class="item" {% endif %}
href="{{ url_for(endpoint) }}">{{ caption }}</a>
href="{{ url_for(endpoint, env=current_env) }}">{{ caption }}</a>
{%- endfor %}
<div class="item" style="float:right"><a href="https://github.com/puppet-community/puppetboard" target="_blank">v0.0.5</a></div>
</nav>
<div class="ui item dropdown">
Environments
<i class="dropdown icon"></i>
<div class="menu">
{% for env in envs %}
<a class="item {% if env == current_env %}active{% endif %}" href="{{url_for_environments(env)}}">{{env}}</a>
{% endfor %}
</div>
</div>
<div class="item right"><a href="https://github.com/puppet-community/puppetboard" target="_blank">v0.1.0</a></div>
</div>
<div class="ui grid padding-bottom">
<div class="one wide column"></div>
<div class="fourteen wide column">
@@ -81,6 +90,9 @@
<script src="{{ url_for('static', filename='js/lists.js') }}"></script>
<script src="{{ url_for('static', filename='js/tables.js') }}"></script>
<script src="{{ url_for('static', filename='js/scroll.top.js') }}"></script>
<script type="text/javascript">
$(".ui.dropdown").dropdown();
</script>
{% block script %} {% endblock script %}
</body>

View File

@@ -3,7 +3,7 @@
<h1>Metrics</h1>
<ul>
{% for key,value in metrics %}
<li><a href="{{url_for('metric', metric=value)}}">{{key}}</li>
<li><a href="{{url_for('metric', env=current_env, metric=value)}}">{{key}}</li>
{% endfor %}
</ul>
{% endblock content %}

View File

@@ -28,12 +28,12 @@
</div>
<div class='row'>
<h1>Reports</h1>
{{ macros.reports_table(reports, node.name, reports_count, condensed=True, hash_truncate=True, show_conf_col=False, show_agent_col=False, show_host_col=False)}}
{{ macros.reports_table(reports, reports_count, report_event_counts, condensed=True, hash_truncate=True, show_conf_col=False, show_agent_col=False, show_host_col=False, current_env=current_env)}}
</div>
</div>
<div class='column'>
<h1>Facts</h1>
{{macros.facts_table(facts, link_facts=True, condensed=True)}}
{{macros.facts_table(facts, link_facts=True, condensed=True, current_env=current_env)}}
</div>
</div>
{% endblock content %}

View File

@@ -1,4 +1,5 @@
{% extends 'layout.html' %}
{% import '_macros.html' as macros %}
{% block content %}
<div class="ui fluid icon input hide" style="margin-bottom:20px">
<input autofocus="autofocus" class="filter-table" placeholder="Type here to filter...">
@@ -17,40 +18,20 @@
{% for node in nodes %}
<tr>
<td>
<a class="ui small status label
{% if node.status == 'failed' %}
red
{% elif node.status == 'changed' %}
green
{% elif node.status == 'unreported' %}
black
{% elif node.status == 'noop' %}
blue
{% endif %}
" href="{{url_for('report_latest', node_name=node.name)}}">
{{node.status}}
</a>
{% if node.status=='unreported'%}
<span class="ui small label status"> {{ node.unreported_time }} </label>
{% else %}
<span>{% if node.events['failures'] %}<span class="ui small count label red">{{node.events['failures']}}</span>{% else %}<span class="ui small count label">0</span>{% endif%}
{% if node.events['successes'] %}<span class="ui small count label green">{{node.events['successes']}}</span>{% else %}<span class="ui small count label">0</span>{% endif%}</span>
{% if node.events['skips'] %}<span class="ui small count label yellow">{{node.events['skips']}}</span>{% else %}<span class="ui small count label">0</span>{% endif%}
{% endif %}
{{macros.status_counts(status=node.status, node_name=node.name, events=node.events, unreported_time=node.unreported_time, current_env=current_env)}}
</td>
<td><a href="{{url_for('node', node_name=node.name)}}">{{node.name}}</a></td>
<td><a rel="utctimestamp" href="{{url_for('catalog_node', node_name=node.name)}}">{{node.catalog_timestamp}}</a></td>
<td><a href="{{url_for('node', env=current_env, node_name=node.name)}}">{{node.name}}</a></td>
<td><a rel="utctimestamp" href="{{url_for('catalog_node', env=current_env, node_name=node.name)}}">{{node.catalog_timestamp}}</a></td>
<td>
{% if node.report_timestamp %}
<a href="{{url_for('report_latest', node_name=node.name)}}" rel='utctimestamp'>{{ node.report_timestamp }}</a>
<a href="{{url_for('report_latest', env=current_env, node_name=node.name)}}" rel='utctimestamp'>{{ node.report_timestamp }}</a>
{% else %}
<i class="large ban circle icon"></i>
{% endif %}
</td>
<td>
{% if node.report_timestamp %}
<a title='Reports' href="{{url_for('reports_node', node_name=node.name)}}"><i class='large darkblue book icon'></i></a>
<i class='large darkblue trash icon'></i>
<a title='Reports' href="{{url_for('reports_node', env=current_env, node_name=node.name, page=1)}}"><i class='large darkblue book icon'></i></a>
{% endif %}
</td>
</tr>

View File

@@ -11,7 +11,7 @@
{% endif %}
{% endwith %}
<div class="ui form">
<form method="POST" action="{{ url_for('query') }}">
<form method="POST" action="{{ url_for('query', env=current_env) }}">
{{ form.csrf_token }}
<div class="field {% if form.query.errors %} error {% endif %}">
{{ form.query(autofocus="autofocus", rows=5, placeholder="Enter your query: [\"=\", \"certname\", \"hostname\"]. You may omit the opening and closing bracket.") }}

View File

@@ -12,7 +12,7 @@
</thead>
<tbody>
<tr>
<td><a href="{{url_for('node', node_name=report.node)}}">{{ report.node }}</a></td>
<td><a href="{{url_for('node', env=current_env, node_name=report.node)}}">{{ report.node }}</a></td>
<td>
{{report.version}}
</td>
@@ -48,26 +48,62 @@
<td>{{event.item['old']}}</td>
<td>{{event.item['new']}}</td>
</tr>
{# <tr>
<td class='message' colspan='4'>
<div id='message-event-{{loop.index}}'>
{{event.item['message']}}
</div>
</td>
</tr>#}
{% endfor %}
</tbody>
</table>
<h1>Logs</h1>
<table class="ui basic table compact">
<thead>
<tr>
<th>Timestamp</th>
<th>Source</th>
<th>Tags</th>
<th>Message</th>
<th>Location</th>
<tr>
</thead>
<tbody>
{% for log in logs %}
{% if log.level == 'info' or log.level == 'notice' %}
<tr class='positive'>
{% elif log.level == 'warning' %}
<tr class='warning'>
{% else %}
<tr class='error'>
{% endif %}
<td rel="utctimestamp">{{log.time}}</td>
<td>{{log.source}}</td>
<td>{{log.tags|join(', ')}}</td>
<td>{{log.message}}</td>
{% if log.file != None and log.line != None %}
<td>{{log.file}}:{{log.line}}</td>
{% else %}
<td></td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
<h1>Metrics</h1>
<table class="ui basic table compact">
<thead>
<tr>
<th>Category</th>
<th>Name</th>
<th>Value</th>
</tr>
</thead>
<tbody>
{% for metric in metrics %}
<tr>
<td>{{metric.category}}</td>
<td>{{metric.name}}</td>
<td>{{metric.value|round(2)}}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock content %}
{% block script %}
<script type='text/javascript'>
jQuery(function ($) {
$("[rel=tooltip]").tooltip();
$(".event").click(function() {
$("#message-" + this.id).slideToggle(200);
return false;
});
});
</script>
{% endblock script %}

View File

@@ -1,6 +1,6 @@
{% extends 'layout.html' %}
{% import '_macros.html' as macros %}
{% block content %}
<div class="ui warning message">
Pending <a href="https://tickets.puppetlabs.com/browse/PDB-201">#PDB-201</a>. You can access reports for a node or individual reports through the <a href="{{url_for('nodes')}}">Nodes</a> tab.
</div>
{{ macros.reports_table(reports, reports_count, report_event_counts, condensed=False, hash_truncate=False, show_conf_col=True, show_agent_col=True, show_host_col=True, show_search_bar=True, searchable=True)}}
{{ macros.render_pagination(pagination)}}
{% endblock content %}

View File

@@ -1,5 +0,0 @@
{% extends 'layout.html' %}
{% import '_macros.html' as macros %}
{% block content %}
{{ macros.reports_table(reports, nodename, reports_count, condensed=False, hash_truncate=False, show_conf_col=True, show_agent_col=True, show_host_col=True)}}
{% endblock content %}