Merge pull request #55 from nedap/semantic

Transition to Semantic-UI framework
This commit is contained in:
Daniele Sluijters
2014-03-13 13:57:55 +01:00
22 changed files with 368 additions and 432 deletions

View File

@@ -4,8 +4,12 @@ from __future__ import absolute_import
import os
import logging
import collections
import urllib
try:
from urllib import unquote
except ImportError:
from urllib.parse import unquote
from datetime import datetime, timedelta
from multiprocessing.dummy import Pool as ThreadPool
from flask import (
Flask, render_template, abort, url_for,
@@ -88,19 +92,17 @@ def index():
# TODO: Would be great if we could parallelize this somehow, doing these
# requests in sequence is rather pointless.
prefix = 'com.puppetlabs.puppetdb.query.population'
num_nodes = get_or_abort(
puppetdb.metric,
"{0}{1}".format(prefix, ':type=default,name=num-nodes'))
num_resources = get_or_abort(
puppetdb.metric,
"{0}{1}".format(prefix, ':type=default,name=num-resources'))
avg_resources_node = get_or_abort(
puppetdb.metric,
"{0}{1}".format(prefix, ':type=default,name=avg-resources-per-node'))
pool = ThreadPool()
endpoints = [
"{0}{1}".format(prefix, ':type=default,name=num-nodes'),
"{0}{1}".format(prefix, ':type=default,name=num-resources'),
"{0}{1}".format(prefix, ':type=default,name=avg-resources-per-node'),
]
fetched_metrics = pool.map(puppetdb.metric, endpoints)
metrics = {
'num_nodes': num_nodes['Value'],
'num_resources': num_resources['Value'],
'avg_resources_node': "{0:10.0f}".format(avg_resources_node['Value']),
'num_nodes': fetched_metrics[0]['Value'],
'num_resources': fetched_metrics[1]['Value'],
'avg_resources_node': "{0:10.0f}".format(fetched_metrics[2]['Value']),
}
nodes = puppetdb.nodes(
@@ -284,10 +286,14 @@ def query():
if app.config['ENABLE_QUERY']:
form = QueryForm()
if form.validate_on_submit():
if form.query.data[0] == '[':
query = form.query.data
else:
query = '[{0}]'.format(form.query.data)
result = get_or_abort(
puppetdb._query,
form.endpoints.data,
query='[{0}]'.format(form.query.data))
query=query)
return render_template('query.html', form=form, result=result)
return render_template('query.html', form=form)
else:
@@ -298,14 +304,14 @@ def query():
@app.route('/metrics')
def metrics():
metrics = get_or_abort(puppetdb._query, 'metrics', path='mbeans')
for key, value in metrics.iteritems():
for key, value in metrics.items():
metrics[key] = value.split('/')[3]
return render_template('metrics.html', metrics=sorted(metrics.items()))
@app.route('/metric/<metric>')
def metric(metric):
name = urllib.unquote(metric)
name = unquote(metric)
metric = puppetdb.metric(metric)
return render_template(
'metric.html',

View File

@@ -1,104 +1,65 @@
body {
padding-top: 60px;
margin: 0;
font-family: "Open Sans", sans-serif;
}
th.headerSortUp {
position: relative
a {
color: #2C3E50;
text-decoration: none;
}
th.headerSortDown {
position: relative
h1.ui.header.no-margin-bottom {
margin-bottom: 0;
}
th.header {
position: relative
.tablesorter-header-inner {
float: left;
}
th.header:after {
content: "\f0dc";
font-family: FontAwesome;
font-style: normal;
font-weight: normal;
text-decoration: inherit;
color: #000;
font-size: 18px;
padding-right: 0.5em;
float:right;
th.tablesorter-headerAsc::after {
content: '\25b4' !important;
float: right;
}
th.headerSortUp:after {
content: "\f0de";
font-family: FontAwesome;
font-style: normal;
font-weight: normal;
text-decoration: inherit;
color: #000;
font-size: 18px;
padding-right: 0.5em;
float:right;
th.tablesorter-headerDesc::after {
content: '\25be' !important;
float: right;
}
th.headerSortDown:after {
content: "\f0dd";
font-family: FontAwesome;
font-style: normal;
font-weight: normal;
text-decoration: inherit;
color: #000;
font-size: 18px;
padding-right: 0.5em;
float:right;
.ui.grid.padding-bottom {
padding-bottom: 40px !important;
}
.stat {
margin-bottom: 40px;
.status {
width: 47%;
text-align: center;
display: block;
}
.navbar .brand:hover {
color: #fff;
.count {
width: 21%;
text-align: center;
display: block;
}
.table tbody tr.error>td {
background-color: #f2dede;
.no-margin-top {
margin-top: -35px !important;
}
h1.error {
color: rgb(223, 46, 27);
.absolute {
position: fixed;
bottom: 0;
width: 100%;
background: #E8E8E8;
}
h1.success {
color: #18BC9C;
.absolute div {
padding: 1em;
}
h1.noop {
color:#aaa;
.ui.menu.darkblue {
background-color:#2C3E50;
}
tr.event {
cursor: pointer;
}
td.message {
padding: 0;
border: 0;
background-color: #FFFFE9;
}
div[id^='message-event'] {
display: none;
padding: 4px 15px 4px 15px;
}
.label-count {
width:25px;
text-align:center;
}
.label-time {
width:73px;
text-align:center;
}
.label-status {
width:100px;
text-align:center;
}
.label-nothing {
background-color:#ddd;
color:#ddd;
}
.label.label-failed {
background-color: rgb(231, 76, 60);
}
.label.label-changed {
background-color: rgb(24, 188, 156);
}
.label.label-unreported {
background-color: rgb(231, 76, 60);
background-color: rgb(129, 145, 146);
}
.btn-lastreport {
width:100px;
.ui.darkblue.header, i.darkblue {
color:#2C3E50;
}

View File

@@ -0,0 +1,9 @@
/*
A simple, lightweight jQuery plugin for creating sortable tables.
https://github.com/kylefox/jquery-tablesort
Version 0.0.2
*/
$(function(){var a=window.jQuery;a.tablesort=function(d,c){var e=this;this.$table=d;this.$thead=this.$table.find("thead");this.settings=a.extend({},a.tablesort.defaults,c);this.$table.find("th").bind("click.tablesort",function(){e.sort(a(this))});this.direction=this.$th=this.index=null};a.tablesort.prototype={sort:function(d,c){var e=new Date,b=this,g=this.$table,n=0<this.$thead.length?g.find("tbody tr"):g.find("tr").has("td"),m=g.find("tr td:nth-of-type("+(d.index()+1)+")"),h=d.data().sortBy,k=[],
l=m.map(function(c,e){return h?"function"===typeof h?h(a(d),a(e),b):h:null!=a(this).data().sortValue?a(this).data().sortValue:a(this).text()});if(0!==l.length){b.$table.find("th").removeClass(b.settings.asc+" "+b.settings.desc);this.direction="asc"!==c&&"desc"!==c?"asc"===this.direction?"desc":"asc":c;c="asc"==this.direction?1:-1;b.$table.trigger("tablesort:start",[b]);b.log("Sorting by "+this.index+" "+this.direction);for(var f=0,p=l.length;f<p;f++)k.push({index:f,cell:m[f],row:n[f],value:l[f]});
k.sort(function(a,b){return a.value>b.value?1*c:a.value<b.value?-1*c:0});a.each(k,function(a,b){g.append(b.row)});d.addClass(b.settings[b.direction]);b.log("Sort finished in "+((new Date).getTime()-e.getTime())+"ms");b.$table.trigger("tablesort:complete",[b])}},log:function(d){(a.tablesort.DEBUG||this.settings.debug)&&console&&console.log&&console.log("[tablesort] "+d)},destroy:function(){this.$table.find("th").unbind("click.tablesort");this.$table.data("tablesort",null);return null}};a.tablesort.DEBUG=
!1;a.tablesort.defaults={debug:a.tablesort.DEBUG,asc:"sorted ascending",desc:"sorted descending"};a.fn.tablesort=function(d){var c,e;return this.each(function(){c=a(this);(e=c.data("tablesort"))&&e.destroy();c.data("tablesort",new a.tablesort(c,d))})}});

View File

@@ -16,7 +16,7 @@ jQuery(function ($) {
if (result == '') {
tstamp.text('Unknown');
} else {
tstamp.text(localise_timestamp(tstring).format('LLLL'));
tstamp.text(localise_timestamp(tstring).format('MMM DD YYYY - HH:mm:ss'));
};
});

View File

@@ -1,11 +1,5 @@
{% extends 'layout.html' %}
{% block row_fluid %}
<div class="container" style="margin-bottom:55px;">
<div class="row">
<div class="span12">
<h2>Bad Request</h2>
<p>The request sent to PuppetDB was invalid. This is usually caused by using an unsupported operator.</p>
</div>
</div>
</div>
{% block content %}
<h1>Bad Request</h1>
<p>The request sent to PuppetDB was invalid. This is usually caused by using an unsupported operator.</p>
{% endblock %}

View File

@@ -1,11 +1,5 @@
{% extends 'layout.html' %}
{% block row_fluid %}
<div class="container" style="margin-bottom:55px;">
<div class="row">
<div class="span12">
<h2>Permission Denied</h2>
<p>What you were looking for has been disabled by the administrator.</p>
</div>
</div>
</div>
{% block content %}
<h1>Permission Denied</h1>
<p>What you were looking for has been disabled by the administrator.</p>
{% endblock %}

View File

@@ -1,11 +1,5 @@
{% extends 'layout.html' %}
{% block row_fluid %}
<div class="container" style="margin-bottom:55px;">
<div class="row">
<div class="span12">
<h2>Not Found</h2>
<p>What you were looking for could not be found in PuppetDB.</p>
</div>
</div>
</div>
{% block content%}
<h1>Not Found</h1>
<p>What you were looking for could not be found in PuppetDB.</p>
{% endblock %}

View File

@@ -1,16 +1,10 @@
{% extends 'layout.html' %}
{% block row_fluid %}
<div class="container" style="margin-bottom:55px;">
<div class="row">
<div class="span12">
<h2>Internal Server Error</h2>
<p>This error usually occurs because:
<ul>
<li>We were unable to reach PuppetDB;</li>
<Li>The query to be executed was malformed resulting in an incorrectly encoded request.</li>
</ul></p>
<p>Please have a look at the log output for further information.</p>
</div>
</div>
</div>
{% block content %}
<h2>Internal Server Error</h2>
<p>This error usually occurs because:
<ul>
<li>We were unable to reach PuppetDB;</li>
<Li>The query to be executed was malformed resulting in an incorrectly encoded request.</li>
</ul></p>
<p>Please have a look at the log output for further information.</p>
{% endblock %}

View File

@@ -1,8 +1,8 @@
{% macro facts_table(facts, autofocus=False, condensed=False, show_node=False, show_value=True, link_facts=False, margin_top=20, margin_bottom=20) -%}
<div class="filter" style="margin-bottom:{{margin_bottom}}px;margin-top:{{margin_top}}px;">
<input {% if autofocus %} autofocus="autofocus" {% endif %} style="width:100%" type="text" class="filter-table input-medium search-query" placeholder="Type here to filter">
<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="filter-table facts table table-striped {% if condensed %}table-condensed{% endif%}" style="table-layout:fixed">
<table class="filter-table facts ui basic table facts {% if condensed %}compact{% endif%}" style="table-layout: fixed;">
<thead>
<tr>
{% if show_node %}
@@ -96,10 +96,10 @@
{%- endmacro %}
{% macro reports_table(reports, nodename, condensed=False, hash_truncate=False, show_conf_col=True, show_agent_col=True, show_host_col=True) -%}
<div class="alert alert-info">
<div class="ui info message">
Only showing the last ten reports.
</div>
<table class='table table-striped {% if condensed %}table-condensed{% endif %}'>
<table class='ui table basic {% if condensed %}compact{% endif %}'>
<thead>
<tr>
<th>Start time</th>
@@ -119,7 +119,7 @@
<tbody>
{% for report in reports %}
{% if hash_truncate %}
{% set rep_hash = "%s&hellip;"|format(report.hash_[0:6])|safe %}
{% set rep_hash = "%s&hellip;"|format(report.hash_[0:10])|safe %}
{% else %}
{% set rep_hash = report.hash_ %}
{% endif %}
@@ -139,7 +139,7 @@
<td>{{report.agent_version}}</td>
{% endif %}
{% if show_host_col %}
<td>{{nodename}}</td>
<td><a href="{{url_for('node', node_name=report.node)}}">{{ report.node }}</a></td>
{% endif %}
</tr>
{% endfor %}

View File

@@ -2,8 +2,8 @@
{% import '_macros.html' as macros %}
{% block content %}
<h1>{{name}}{% if value %}/{{value}}{% endif %} ({{facts|length}})</h1>
{{macros.facts_graph(facts, autofocus=True, show_node=True, margin_bottom=10)}}
{{macros.facts_graph_value(facts, autofocus=True, show_node=True, margin_bottom=10)}}
{#{{macros.facts_graph(facts, autofocus=True, show_node=True, margin_bottom=10)}}#
{{macros.facts_graph_value(facts, autofocus=True, show_node=True, margin_bottom=10)}}#}
{% if value %}
{{macros.facts_table(facts, autofocus=True, show_node=True, show_value=False, margin_bottom=10)}}
{% else %}

View File

@@ -1,11 +1,11 @@
{% extends 'layout.html' %}
{% block content %}
<div class="hide" style="margin-bottom:20px">
<input autofocus="autofocus" style="width:100%" type="text" class="filter-list input-medium search-query" placeholder="Type here to filter">
<div class="ui fluid icon input hide" style="margin-bottom:20px">
<input autofocus="autofocus" class="filter-list" placeholder="Type here to filter...">
</div>
<div style="-moz-column-count:4; -webkit-column-count:4; column-count:4;">
{%- for key,facts_list in facts_dict %}
<span class='label label-success'>{{key}}</span>
<span class='ui label purple'>{{key}}</span>
<ul class="searchable">
{%- for fact in facts_list %}
<li><a href="{{url_for('fact', fact=fact)}}">{{fact}}</a></li>

View File

@@ -1,102 +1,106 @@
{% extends 'layout.html' %}
{% block row_fluid %}
<div class="container" style="margin-bottom:55px;">
<div class="row">
<div class="span12">
<div class="span4 stat">
<a href="nodes?status=failed">
<h1 class="error">{{stats['failed']}}
<small>{% if stats['failed']== 1 %} node {% else %} nodes {% endif %}</small>
</h1>
</a>
<span>with status failed</span>
</div>
<div class="span4 stat">
<a href="nodes?status=changed">
<h1 class="success">{{stats['changed']}}
<small>{% if stats['changed']== 1 %} node {% else %} nodes {% endif %}</small>
</h1>
</a>
<span>with status changed</span>
</div>
<div class="span4 stat">
<a href="nodes?status=unreported">
<h1 class="noop">{{ stats['unreported'] }}
<small>{% if stats['unreported']== 1 %} node {% else %} nodes {% endif %}</small>
</h1>
</a>
<span>
unreported in the last {{ config.UNRESPONSIVE_HOURS }} hours
</span>
</div>
{% block content %}
<div class="ui vertical grid">
<div class="three column row">
<div class="column">
<a href="nodes?status=failed">
<h1 class="ui red header no-margin-bottom">
{{stats['failed']}}
<small>{% if stats['failed']== 1 %} node {% else %} nodes {% endif %}</small>
</h1>
</a>
<span>with status failed</span>
</div>
<div class="column">
<a href="nodes?status=changed">
<h1 class="ui header green no-margin-bottom">
{{stats['changed']}}
<small>{% if stats['changed']== 1 %} node {% else %} nodes {% endif %}</small>
</h1>
</a>
<span>with status changed</span>
</div>
<div class="column">
<a href="nodes?status=unreported">
<h1 class="ui header black no-margin-bottom">
{{ stats['unreported'] }}
<small>{% if stats['unreported']== 1 %} node {% else %} nodes {% endif %}</small>
</h1>
</a>
<span>unreported in the last {{ config.UNRESPONSIVE_HOURS }} hours</span>
</div>
</div>
<div class="row">
<div class="span12">
<div class="span4 stat">
<h1>{{metrics['num_nodes']}}</h1>
<span>Population</span>
</div>
<div class="span4 stat">
<h1>{{metrics['num_resources']}}</h1>
<span>Resources managed</span>
</div>
<div class="span4 stat">
<h1>{{metrics['avg_resources_node']}}</h1>
<span>Avg. resources/node</span>
</div>
<div class="three column row">
<div class="column">
<h1 class="ui header darkblue no-margin-bottom">{{metrics['num_nodes']}}</h1>
<span>Population</span>
</div>
<div class="column">
<h1 class="ui header darkblue no-margin-bottom">{{metrics['num_resources']}}</h1>
<span>Resources managed</span>
</div>
<div class="column">
<h1 class="ui header darkblue no-margin-bottom">{{metrics['avg_resources_node']}}</h1>
<span>Avg. resources/node</span>
</div>
</div>
<div class="row">
<div class="span12">
<div class="ui divider">
</div>
<div class="one column row no-margin-top">
<div class="column">
{% if nodes %}
<h2>Nodes status detail ({{nodes|length}})</h2>
<table class='dashboard table table-striped table-condensed'>
<thead>
<tr>
<th style="min-width:220px;">Status</th>
<th style="width:600px;">Hostname</th>
<th style="width:120px;"></th>
</tr>
</thead>
<tbody class="searchable">
{% for node in nodes %}
{% if node.status != 'unchanged' %}
<tr>
<td>
<a href="{{url_for('report_latest', node_name=node.name)}}">
<span class="label label-status label-{{node.status}}">{{node.status}}</span>
</a>
{% if node.status=='unreported'%}
<span class="label label-time label-unreported"> {{ node.unreported_time }} </label>
{% else %}
{% if node.events['failures'] %}<span class="label label-important label-count">{{node.events['failures']}}</span>{% else %}<span class="label label-count">0</span>{% endif%}
{% if node.events['successes'] %}<span class="label label-success label-count">{{node.events['successes']}}</span>{% else %}<span class="label label-count">0</span>{% endif%}
{% endif %}
</td>
<td><a href="{{url_for('node', node_name=node.name)}}">{{ node.name }}</a></td>
<td>
{% if node.unreported_time != None or node.status != 'unreported' %}
<a class="btn btn-small btn-primary btn-lastreport" href="{{url_for('report_latest', node_name=node.name)}}">Latest Report</a>
{% else %}
<a class="btn btn-small btn-lastreport"> No Report </a>
<h2>Nodes status detail ({{nodes|length}})</h2>
<table class='ui compact basic table dashboard'>
<thead>
<tr>
<th class="five wide">Status</th>
<th class="ten wide">Hostname</th>
<th class="one wide"></th>
</tr>
</thead>
<tbody class="searchable">
{% for node in nodes %}
{% 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
{% endif %}
</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
" 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 %}
{% 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%}
{% endif %}
</td>
<td>
<a href="{{url_for('node', node_name=node.name)}}">{{ node.name }}</a>
</td>
<td>
<a title="Latest Report" href="{{url_for('report_latest', node_name=node.name)}}">
<i class="large tasks darkblue icon"></i>
</a>
</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
{% else %}
<h2>Nodes status detail</h2>
<div class="alert alert-info">
Nothing seems to be changing.
</div>
<h2>Nodes status detail</h2>
<div class="ui info message">
Nothing seems to be changing.
</div>
{% endif %}
</div>
</div>
</div>
{% endblock row_fluid %}
{% endblock content %}

View File

@@ -2,66 +2,52 @@
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Puppet&#7427;oard</title>
<link href="//netdna.bootstrapcdn.com/bootswatch/2.3.2/flatly/bootstrap.min.css" rel="stylesheet">
<link href="//netdna.bootstrapcdn.com/font-awesome/3.2.1/css/font-awesome.min.css" rel="stylesheet">
<link href="{{url_for('static', filename='css/puppetboard.css')}}" rel="stylesheet">
<title>Puppetboard</title>
<link href="//cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.12.0/css/semantic.min.css" rel="stylesheet">
<link href='//fonts.googleapis.com/css?family=Open+Sans' rel='stylesheet' type='text/css'>
<link href="//cdnjs.cloudflare.com/ajax/libs/jquery.tablesorter/2.13.3/css/filter.formatter.css" rel="stylesheet">
<link href="{{ url_for('static', filename='css/puppetboard.css')}}" rel="stylesheet">
</head>
<body>
<div style="font-variant:small-caps" class="navbar navbar-fixed-top">
<div class="navbar-inner">
<div class="container">
<a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</a>
<span style="margin-top:-2px;" class="brand">Puppetboard</span>
<div class="nav-collapse collapse">
<ul class="nav">
{%- for endpoint, caption in [
('index', 'Overview'),
('nodes', 'Nodes'),
('facts', 'Facts'),
('reports', 'Reports'),
('metrics', 'Metrics'),
('query', 'Query'),
] %}
<li{% if endpoint == request.endpoint %} class=active{% endif
%}><a href="{{ url_for(endpoint) }}">{{ caption }}</a></li>
{%- endfor %}
</ul>
</div>
</div>
<nav class="ui fixed darkblue inverted menu">
<div class="title item">
Puppetboard
</div>
</div>
{% block container %}
<div class="container-fluid" style="margin-bottom:55px;">
<div class="row-fluid">
{% block row_fluid %}
<div class="span12">
{% block content %} {% endblock content %}
</div>
{% endblock row_fluid %}
</div>
</div>
{% endblock container %}
<div class="navbar navbar-fixed-bottom" style="background-color:rgb(249, 249, 249);">
<div class="navbar" style="height:55px;margin-bottom:0;">
<div class="container-fluid" style="line-height:55px;">
Copyright © 2013 <a href="https://github.com/daenney">Daniele Sluijters</a>. <span style="float:right">Live from PuppetDB.</span>
</div>
{%- for endpoint, caption in [
('index', 'Overview'),
('nodes', 'Nodes'),
('facts', 'Facts'),
('reports', 'Reports'),
('metrics', 'Metrics'),
('query', 'Query'),
] %}
<a {% if endpoint == request.endpoint %} class="active item" {% else %} class="item" {% endif %}
href="{{ url_for(endpoint) }}">{{ caption }}</a>
{%- endfor %}
</nav>
<div class="ui grid padding-bottom">
<div class="one wide column"></div>
<div class="fourteen wide column">
{% block content %} {% endblock content %}
</div>
<div class="one wide column"></div>
</div>
<script src="//code.jquery.com/jquery-1.10.0.min.js"></script>
<script src="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/js/bootstrap.min.js"></script>
<script src="//cdn.jsdelivr.net/tablesorter/2.0.3/jquery.tablesorter.min.js"></script>
<script src="{{ url_for('static', filename='js/moment.js')}}"></script>
<footer class="ui absolute fixed bottom">
<div>
Copyright © 2013-2014 <a href="https://github.com/daenney">Daniele Sluijters</a>. <span style="float:right">Live from PuppetDB.</span>
</div>
</footer>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.12.0/javascript/semantic.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.5.1/moment.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery.tablesorter/2.13.3/jquery.tablesorter.min.js"></script>
<script src="{{ url_for('static', filename='js/timestamps.js')}}"></script>
<script src="{{url_for('static', filename='js/tables.js')}}"></script>
<script src="{{url_for('static', filename='js/lists.js')}}"></script>
<script src="{{url_for('static', filename='js/tables.js')}}"></script>
{% block script %} {% endblock script %}
</body>
</html>

View File

@@ -2,11 +2,21 @@
{% block content %}
<div class="page-header">
<h1>Metric
{% set name = "%s&hellip;"|format(name[:75])|safe if name|length > 75%}
{% if name|length > 75 %}
{% set name = "%s&hellip;"|format(name[:75])|safe %}
{% else %}
{% set name = name %}
{% endif %}
<small>{{name}}</small>
</h1>
</div>
<table class="table table-striped">
<table class="ui segment table">
<thead>
<tr>
<th>Option</th>
<th>Value</th>
</tr>
</thead>
<tbody>
{% for key,value in metric %}
<tr>

View File

@@ -1,37 +1,39 @@
{% extends 'layout.html' %}
{% import '_macros.html' as macros %}
{% block content %}
<div class="row-fluid">
<div class="span12">
<h1>Details</h1>
<table class="table table-striped table-condensed" style="table-layout:fixed">
<thead>
<tr>
<th>Hostname</th>
<th>Facts uploaded at</th>
<th>Catalog compiled at</th>
<th>Report uploaded at</th>
</tr>
</thead>
<tbody>
<tr>
<td style="word-wrap:break-word"><b>{{node.name}}</b></td>
<td rel="utctimestamp">{{node.facts_timestamp}}</td>
<td rel="utctimestamp">{{node.catalog_timestamp}}</td>
<td rel="utctimestamp">{{node.report_timestamp}}</td>
</tr>
</tbody>
</table>
<div class='ui two column grid'>
<div class='column'>
<div class='row'>
<h1>Details</h1>
<table class="ui table compact basic">
<tbody>
<tr>
<th>Hostname</th>
<td style="word-wrap:break-word"><b>{{node.name}}</b></td>
</tr>
<tr>
<th>Facts &nbsp;&nbsp;&nbsp;&nbsp;<i title='uploaded at' class=' upload icon'></i></th>
<td rel="utctimestamp">{{node.facts_timestamp}}</td>
</tr>
<tr>
<th>Catalog <i title='uploaded at' class=' upload icon'></i></th>
<td rel="utctimestamp">{{node.catalog_timestamp}}</td>
</tr>
<tr>
<th>Report &nbsp;<i title='uploaded at' class=' upload icon'></i></th>
<td rel="utctimestamp">{{node.report_timestamp}}</td>
</tr>
</tbody>
</table>
</div>
<div class='row'>
<h1>Reports</h1>
{{ macros.reports_table(reports, node.name, condensed=True, hash_truncate=True, show_conf_col=False, show_agent_col=False, show_host_col=False)}}
</div>
</div>
</div>
<div class="row-fluid">
<div class="span6">
<div class='column'>
<h1>Facts</h1>
{{macros.facts_table(facts, link_facts=True, condensed=True, margin_top=10)}}
</div>
<div class="span6">
<h1>Reports</h1>
{{ macros.reports_table(reports, node.name, condensed=True, hash_truncate=True, show_conf_col=False, show_agent_col=False, show_host_col=False)}}
{{macros.facts_table(facts, link_facts=True, condensed=True)}}
</div>
</div>
{% endblock content %}

View File

@@ -1,56 +1,53 @@
{% extends 'layout.html' %}
{% block content %}
<div class="alert alert-info">
PuppetDB currently only returns active nodes.
<div class="ui fluid icon input hide" style="margin-bottom:20px">
<input autofocus="autofocus" class="filter-table" placeholder="Type here to filter...">
</div>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{category}}">
{{message}}
</div>
{% endfor %}
{% endif %}
{% endwith %}
<div class="hide" style="margin-bottom:20px">
<input autofocus="autofocus" style="width:100%" type="text" class="filter-table input-medium search-query" placeholder="Type here to filter">
</div>
<table class='nodes table table-striped table-condensed'>
<table class='ui compact basic table nodes'>
<thead>
<tr>
<th>Status</th>
<th>Hostname</th>
<th>Catalog compiled at</th>
<th>Last report</th>
<th>Catalog</th>
<th>Report</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody class="searchable">
{% for node in nodes %}
<tr>
<td><a href="{{url_for('report_latest', node_name=node.name)}}">
<span class="label label-status label-{{ node.status }}">{{ node.status }}</span>
</a>
{% if node.status=='unreported'%}
<span class="label label-time label-unreported"> {{ node.unreported_time }} </label>
{% else %}
{% if node.events['failures'] %}<span class="label label-count label-important">{{node.events['failures']}}</span>{% else %}<span class="label label-count">0</span>{% endif%}
{% if node.events['successes'] %}<span class="label label-count label-success">{{node.events['successes']}}</span>{% else %}<span class="label label-count">0</span>{% endif%}
{% endif %}
<td>
<a class="ui small status label
{% if node.status == 'failed' %}
red
{% elif node.status == 'changed' %}
green
{% elif node.status == 'unreported' %}
black
{% 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>
{% endif %}
</td>
<td><a href="{{url_for('node', node_name=node.name)}}">{{node.name}}</a></td>
<td rel="utctimestamp">{{node.catalog_timestamp}}</td>
<td>
{% if node.report_timestamp %}
<span rel="utctimestamp">{{ node.report_timestamp }}</span>
<a href="{{url_for('report_latest', node_name=node.name)}}" rel='utctimestamp'>{{ node.report_timestamp }}</a>
{% else %}
<i class="icon icon-ban-circle"></i>
<i class="large ban circle icon"></i>
{% endif %}
</td>
<td>
{% if node.report_timestamp %}
<a class="btn btn-small btn-primary" href="{{url_for('report_latest', node_name=node.name)}}">Latest Report</a>
<a class="btn btn-small btn-primary" href="{{url_for('reports_node', node=node.name)}}">Reports</a>
<a title='Reports' href="{{url_for('reports_node', node=node.name)}}"><i class='large darkblue book icon'></i></a>
<i class='large darkblue trash icon'></i>
{% endif %}
</td>
</tr>

View File

@@ -1,61 +1,43 @@
{% extends 'layout.html' %}
{% block row_fluid %}
<div class="span12">
<div class="alert">
This is highly experimental and will likely set your server on fire.
</div>
{% block content %}
<h2>Compose</h2>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="ui {{category}} message">
{{message}}
</div>
<div class="container" style="margin-bottom:55px;">
<div class="row">
<div class="span12">
<h2>Compose</h2>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{category}}">
{{message}}
{% endfor %}
{% endif %}
{% endwith %}
<div class="ui form">
<form method="POST" action="{{ url_for('query')}}"
{{ form.csrf_token }}
<div class="field {% if form.query.errors %} error {% endif %}">
{{form.query(autofocus="autofocus", rows=5, placeholder="Enter your query: [\"=\", \"name\", \"hostname\"]. You may omit the opening and closing bracket.")}}
</div>
<div class="inline fields">
{% for subfield in form.endpoints %}
<div class="field">
<div class="ui radio checkbox">
{{subfield }}
{{subfield.label}}
</div>
</div>
{% endfor %}
{% endif %}
{% endwith %}
<form class="form-horizontal" method="POST" action="{{ url_for('query')}}">
{{ form.csrf_token }}
<div class="control-group {% if form.query.errors %} error {% endif %}">
{{form.query.label(class_="control-label")}}
<div class="controls">
{{form.query(class_="input-block-level", autofocus="autofocus", rows=5, placeholder="\"=\", \"name\", \"hostname\"")}}
{% if form.query.errors %}
<span class="help-inline">{% for error in form.query.errors %}{{error}}{% endfor %}</span>
{% endif %}
</div>
</div>
<div class="control-group {% if form.endpoints.errors %} error {% endif %}">
{{form.endpoints.label(class_="control-label")}}
<div class="controls">
{% for subfield in form.endpoints %}
{{subfield.label(class_="radio inline")}}
{{subfield }}
{% endfor %}
{% if form.endpoints.errors %}
<span class="help-inline">{% for error in form.endpoints.errors %}{{error}}{% endfor %}</span>
{% endif %}
</div>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">Yes I'm sure</button>
<button type="button" class="btn">No thanks</button>
</div>
{{ form.hidden_tag() }}
</form>
</div>
</div>
{% if result %}
<div class="row">
<div class="span12">
<h2>Result</h2>
<pre><code>{{result|jsonprint}}</code></pre>
</div>
</div>
{% endif %}
{{ form.hidden_tag() }}
<input type=submit class="ui submit button" value='Submit'>
<input type=reset class="ui red submit button" value='Cancel'>
</form>
</div>
{% endblock row_fluid %}
{% if result %}
<div class="row">
<div class="span12">
<h2>Result</h2>
<pre><code>{{result|jsonprint}}</code></pre>
</div>
</div>
{% endif %}
{% endblock content %}

View File

@@ -1,7 +1,7 @@
{% extends 'layout.html' %}
{% block content %}
<h1>Summary</h1>
<table class='table table-striped'>
<table class='ui basic table'>
<thead>
<tr>
<th>Hostname</th>
@@ -27,7 +27,7 @@
</table>
<h1>Events</h1>
<table class='table table-striped table-condensed'>
<table class='ui basic table compact'>
<thead>
<tr>
<th>Resource</th>
@@ -39,22 +39,22 @@
<tbody>
{% for event in events %}
{% if not event.failed and event.item['old'] != event.item['new'] %}
<tr id='event-{{loop.index}}' class='success event'>
<tr id='event-{{loop.index}}' class='positive'>
{% elif event.failed %}
<tr id='event-{{loop.index}}' class='error event'>
<tr id='event-{{loop.index}}' class='error'>
{% endif %}
<td>{{event.item['type']}}[{{event.item['title']}}]</td>
<td>{{event.status}}</td>
<td>{{event.item['old']}}</td>
<td>{{event.item['new']}}</td>
</tr>
<tr>
{# <tr>
<td class='message' colspan='4'>
<div id='message-event-{{loop.index}}'>
{{event.item['message']}}
</div>
</td>
</tr>
</tr>#}
{% endfor %}
</tbody>
</table>

View File

@@ -1,6 +1,6 @@
{% extends 'layout.html' %}
{% block content %}
<div class="alert">
<div class="ui warning message">
Pending <a href="http://projects.puppetlabs.com/issues/21600">#21600</a>. You can access reports for a node or individual reports through the <a href="{{url_for('nodes')}}">Nodes</a> tab.
</div>
{% endblock content %}

View File

@@ -20,7 +20,7 @@ def get_or_abort(func, *args, **kwargs):
"""
try:
return func(*args, **kwargs)
except HTTPError, e:
except HTTPError as e:
abort(e.response.status_code)
except ConnectionError:
abort(500)

View File

@@ -1,9 +1,9 @@
Flask==0.10.1
Flask-WTF==0.8.4
Jinja2==2.7
MarkupSafe==0.18
WTForms==1.0.4
Werkzeug==0.9.3
itsdangerous==0.22
pypuppetdb==0.1.0
requests==1.2.3
Flask-WTF==0.9.4
Jinja2==2.7.2
MarkupSafe==0.19
WTForms==1.0.5
Werkzeug==0.9.4
itsdangerous==0.23
pypuppetdb==0.1.1
requests==2.2.1

View File

@@ -45,5 +45,8 @@ setup(
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.2',
'Programming Language :: Python :: 3.3',
],
)