Merge branch 'master' into facts
This commit is contained in:
11
hooks/pre_build
Executable file
11
hooks/pre_build
Executable file
@@ -0,0 +1,11 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
version=$(git describe HEAD --abbrev=4)
|
||||||
|
|
||||||
|
cat << EOF > puppetboard/version.py
|
||||||
|
#
|
||||||
|
# Puppetboard version module
|
||||||
|
#
|
||||||
|
__version__ = '${version}'
|
||||||
|
EOF
|
||||||
|
|
||||||
@@ -27,6 +27,7 @@ from puppetboard.utils import (
|
|||||||
from puppetboard.dailychart import get_daily_reports_chart
|
from puppetboard.dailychart import get_daily_reports_chart
|
||||||
|
|
||||||
import werkzeug.exceptions as ex
|
import werkzeug.exceptions as ex
|
||||||
|
import CommonMark
|
||||||
|
|
||||||
from . import __version__
|
from . import __version__
|
||||||
|
|
||||||
@@ -322,29 +323,11 @@ def nodes(env):
|
|||||||
current_env=env)))
|
current_env=env)))
|
||||||
|
|
||||||
|
|
||||||
@app.route('/inventory', defaults={'env': app.config['DEFAULT_ENVIRONMENT']})
|
def inventory_facts():
|
||||||
@app.route('/<env>/inventory')
|
# a list of facts descriptions to go in table header
|
||||||
def inventory(env):
|
headers = []
|
||||||
"""Fetch all (active) nodes from PuppetDB and stream a table displaying
|
# a list of inventory fact names
|
||||||
those nodes along with a set of facts about them.
|
fact_names = []
|
||||||
|
|
||||||
Downside of the streaming aproach is that since we've already sent our
|
|
||||||
headers we can't abort the request if we detect an error. Because of this
|
|
||||||
we'll end up with an empty table instead because of how yield_or_stop
|
|
||||||
works. Once pagination is in place we can change this but we'll need to
|
|
||||||
provide a search feature instead.
|
|
||||||
|
|
||||||
:param env: Search for facts in this environment
|
|
||||||
:type env: :obj:`string`
|
|
||||||
"""
|
|
||||||
envs = environments()
|
|
||||||
check_env(env, envs)
|
|
||||||
|
|
||||||
headers = [] # a list of fact descriptions to go
|
|
||||||
# in the table header
|
|
||||||
fact_names = [] # a list of inventory fact names
|
|
||||||
fact_data = {} # a multidimensional dict for node and
|
|
||||||
# fact data
|
|
||||||
|
|
||||||
# load the list of items/facts we want in our inventory
|
# load the list of items/facts we want in our inventory
|
||||||
try:
|
try:
|
||||||
@@ -362,33 +345,65 @@ def inventory(env):
|
|||||||
headers.append(desc)
|
headers.append(desc)
|
||||||
fact_names.append(name)
|
fact_names.append(name)
|
||||||
|
|
||||||
|
return headers, fact_names
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/inventory', defaults={'env': app.config['DEFAULT_ENVIRONMENT']})
|
||||||
|
@app.route('/<env>/inventory')
|
||||||
|
def inventory(env):
|
||||||
|
"""Fetch all (active) nodes from PuppetDB and stream a table displaying
|
||||||
|
those nodes along with a set of facts about them.
|
||||||
|
|
||||||
|
:param env: Search for facts in this environment
|
||||||
|
:type env: :obj:`string`
|
||||||
|
"""
|
||||||
|
envs = environments()
|
||||||
|
check_env(env, envs)
|
||||||
|
headers, fact_names = inventory_facts()
|
||||||
|
|
||||||
|
return render_template(
|
||||||
|
'inventory.html',
|
||||||
|
envs=envs,
|
||||||
|
current_env=env,
|
||||||
|
fact_headers=headers)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/inventory/json',
|
||||||
|
defaults={'env': app.config['DEFAULT_ENVIRONMENT']})
|
||||||
|
@app.route('/<env>/inventory/json')
|
||||||
|
def inventory_ajax(env):
|
||||||
|
"""Backend endpoint for inventory table"""
|
||||||
|
draw = int(request.args.get('draw', 0))
|
||||||
|
|
||||||
|
envs = environments()
|
||||||
|
check_env(env, envs)
|
||||||
|
headers, fact_names = inventory_facts()
|
||||||
|
|
||||||
query = AndOperator()
|
query = AndOperator()
|
||||||
fact_query = OrOperator()
|
fact_query = OrOperator()
|
||||||
fact_query.add([EqualsOperator("name", name) for name in fact_names])
|
fact_query.add([EqualsOperator("name", name) for name in fact_names])
|
||||||
|
query.add(fact_query)
|
||||||
|
|
||||||
if env != '*':
|
if env != '*':
|
||||||
query.add(EqualsOperator("environment", env))
|
query.add(EqualsOperator("environment", env))
|
||||||
|
|
||||||
query.add(fact_query)
|
|
||||||
|
|
||||||
# get all the facts from PuppetDB
|
|
||||||
facts = puppetdb.facts(query=query)
|
facts = puppetdb.facts(query=query)
|
||||||
|
|
||||||
|
fact_data = {}
|
||||||
for fact in facts:
|
for fact in facts:
|
||||||
if fact.node not in fact_data:
|
if fact.node not in fact_data:
|
||||||
fact_data[fact.node] = {}
|
fact_data[fact.node] = {}
|
||||||
|
|
||||||
fact_data[fact.node][fact.name] = fact.value
|
fact_data[fact.node][fact.name] = fact.value
|
||||||
|
|
||||||
return Response(stream_with_context(
|
total = len(fact_data)
|
||||||
stream_template(
|
|
||||||
'inventory.html',
|
return render_template(
|
||||||
headers=headers,
|
'inventory.json.tpl',
|
||||||
fact_names=fact_names,
|
draw=draw,
|
||||||
|
total=total,
|
||||||
|
total_filtered=total,
|
||||||
fact_data=fact_data,
|
fact_data=fact_data,
|
||||||
envs=envs,
|
columns=fact_names)
|
||||||
current_env=env
|
|
||||||
)))
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/node/<node_name>',
|
@app.route('/node/<node_name>',
|
||||||
@@ -529,26 +544,17 @@ def reports_ajax(env, node_name):
|
|||||||
reports_events = []
|
reports_events = []
|
||||||
total = 0
|
total = 0
|
||||||
|
|
||||||
report_event_counts = {}
|
# Convert metrics to relational dict
|
||||||
# Create a map from the metrics data to what the templates
|
metrics = {}
|
||||||
# use to express the data.
|
|
||||||
report_map = {
|
|
||||||
'success': 'successes',
|
|
||||||
'failure': 'failures',
|
|
||||||
'skipped': 'skips',
|
|
||||||
'noops': 'noop'
|
|
||||||
}
|
|
||||||
for report in reports_events:
|
for report in reports_events:
|
||||||
if total is None:
|
if total is None:
|
||||||
total = puppetdb.total
|
total = puppetdb.total
|
||||||
|
|
||||||
report_counts = {'successes': 0, 'failures': 0, 'skips': 0}
|
metrics[report.hash_] = {}
|
||||||
for metrics in report.metrics:
|
for m in report.metrics:
|
||||||
if 'name' in metrics and metrics['name'] in report_map:
|
if m['category'] not in metrics[report.hash_]:
|
||||||
key_name = report_map[metrics['name']]
|
metrics[report.hash_][m['category']] = {}
|
||||||
report_counts[key_name] = metrics['value']
|
metrics[report.hash_][m['category']][m['name']] = m['value']
|
||||||
|
|
||||||
report_event_counts[report.hash_] = report_counts
|
|
||||||
|
|
||||||
if total is None:
|
if total is None:
|
||||||
total = 0
|
total = 0
|
||||||
@@ -559,7 +565,7 @@ def reports_ajax(env, node_name):
|
|||||||
total=total,
|
total=total,
|
||||||
total_filtered=total,
|
total_filtered=total,
|
||||||
reports=reports,
|
reports=reports,
|
||||||
report_event_counts=report_event_counts,
|
metrics=metrics,
|
||||||
envs=envs,
|
envs=envs,
|
||||||
current_env=env,
|
current_env=env,
|
||||||
columns=REPORTS_COLUMNS[:max_col])
|
columns=REPORTS_COLUMNS[:max_col])
|
||||||
@@ -605,6 +611,8 @@ def report(env, node_name, report_id):
|
|||||||
except StopIteration:
|
except StopIteration:
|
||||||
abort(404)
|
abort(404)
|
||||||
|
|
||||||
|
report.version = CommonMark.commonmark(report.version)
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
'report.html',
|
'report.html',
|
||||||
report=report,
|
report=report,
|
||||||
|
|||||||
@@ -18,6 +18,11 @@ LOGLEVEL = 'info'
|
|||||||
NORMAL_TABLE_COUNT = 100
|
NORMAL_TABLE_COUNT = 100
|
||||||
LITTLE_TABLE_COUNT = 10
|
LITTLE_TABLE_COUNT = 10
|
||||||
TABLE_COUNT_SELECTOR = [10, 20, 50, 100, 500]
|
TABLE_COUNT_SELECTOR = [10, 20, 50, 100, 500]
|
||||||
|
DISPLAYED_METRICS = ['resources.total',
|
||||||
|
'events.failure',
|
||||||
|
'events.success',
|
||||||
|
'resources.skipped',
|
||||||
|
'events.noop']
|
||||||
OFFLINE_MODE = False
|
OFFLINE_MODE = False
|
||||||
ENABLE_CATALOG = False
|
ENABLE_CATALOG = False
|
||||||
OVERVIEW_FILTER = None
|
OVERVIEW_FILTER = None
|
||||||
|
|||||||
@@ -33,6 +33,13 @@ TABLE_COUNT_DEF = "10,20,50,100,500"
|
|||||||
TABLE_COUNT_SELECTOR = [int(x) for x in os.getenv('TABLE_COUNT_SELECTOR',
|
TABLE_COUNT_SELECTOR = [int(x) for x in os.getenv('TABLE_COUNT_SELECTOR',
|
||||||
TABLE_COUNT_DEF).split(',')]
|
TABLE_COUNT_DEF).split(',')]
|
||||||
|
|
||||||
|
DISP_METR_DEF = ','.join(['resources.total', 'events.failure',
|
||||||
|
'events.success', 'resources.skipped',
|
||||||
|
'events.noop'])
|
||||||
|
|
||||||
|
DISPLAYED_METRICS = [x.strip() for x in os.getenv('DISPLAYED_METRICS',
|
||||||
|
DISP_METR_DEF).split(',')]
|
||||||
|
|
||||||
OFFLINE_MODE = bool(os.getenv('OFFLINE_MODE', 'False').upper() == 'TRUE')
|
OFFLINE_MODE = bool(os.getenv('OFFLINE_MODE', 'False').upper() == 'TRUE')
|
||||||
ENABLE_CATALOG = bool(os.getenv('ENABLE_CATALOG', 'False').upper() == 'TRUE')
|
ENABLE_CATALOG = bool(os.getenv('ENABLE_CATALOG', 'False').upper() == 'TRUE')
|
||||||
OVERVIEW_FILTER = os.getenv('OVERVIEW_FILTER', None)
|
OVERVIEW_FILTER = os.getenv('OVERVIEW_FILTER', None)
|
||||||
@@ -46,7 +53,6 @@ GRAPH_FACTS_DEFAULT = ','.join(['architecture', 'clientversion', 'domain',
|
|||||||
GRAPH_FACTS = [x.strip() for x in os.getenv('GRAPH_FACTS',
|
GRAPH_FACTS = [x.strip() for x in os.getenv('GRAPH_FACTS',
|
||||||
GRAPH_FACTS_DEFAULT).split(',')]
|
GRAPH_FACTS_DEFAULT).split(',')]
|
||||||
|
|
||||||
|
|
||||||
GRAPH_TYPE = os.getenv('GRAPH_TYPE', 'pie')
|
GRAPH_TYPE = os.getenv('GRAPH_TYPE', 'pie')
|
||||||
|
|
||||||
# Tuples are hard to express as an environment variable, so here
|
# Tuples are hard to express as an environment variable, so here
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ h1.ui.header.no-margin-bottom {
|
|||||||
color: #AA4643;
|
color: #AA4643;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui.label.failed {
|
.ui.label.failed, .ui.label.events.failure {
|
||||||
background-color: #AA4643;
|
background-color: #AA4643;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ h1.ui.header.no-margin-bottom {
|
|||||||
color: #4572A7;
|
color: #4572A7;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui.label.changed {
|
.ui.label.changed, .ui.label.events.success {
|
||||||
background-color: #4572A7;
|
background-color: #4572A7;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,10 +68,14 @@ h1.ui.header.no-margin-bottom {
|
|||||||
color: #DB843D;
|
color: #DB843D;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui.label.noop {
|
.ui.label.noop, .ui.label.events.noop {
|
||||||
background-color: #DB843D;
|
background-color: #DB843D;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ui.label.resources.total {
|
||||||
|
background-color: #989898;
|
||||||
|
}
|
||||||
|
|
||||||
.ui.label.unchanged {
|
.ui.label.unchanged {
|
||||||
background-color: #89A54E;
|
background-color: #89A54E;
|
||||||
}
|
}
|
||||||
@@ -80,7 +84,7 @@ h1.ui.header.no-margin-bottom {
|
|||||||
color: orange;
|
color: orange;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui.label.skipped {
|
.ui.label.skipped, .ui.label.resources.skipped {
|
||||||
background-color: orange;
|
background-color: orange;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
jQuery(function ($) {
|
jQuery(function ($) {
|
||||||
function generateChart(el) {
|
function generateChart(el) {
|
||||||
var url = "/daily_reports_chart.json";
|
var url = "daily_reports_chart.json";
|
||||||
var certname = $(el).attr('data-certname');
|
var certname = $(el).attr('data-certname');
|
||||||
if (typeof certname !== typeof undefined && certname !== false) {
|
if (typeof certname !== typeof undefined && certname !== false) {
|
||||||
url = url + "?certname=" + certname;
|
url = url + "?certname=" + certname;
|
||||||
|
|||||||
@@ -9,6 +9,29 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{%- endmacro %}
|
{%- endmacro %}
|
||||||
|
|
||||||
|
{% macro report_status(caller, status, node_name, metrics, current_env, unreported_time=False, report_hash=False) -%}
|
||||||
|
<a class="ui {{status}} 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 %}
|
||||||
|
{% for metric in config.DISPLAYED_METRICS %}
|
||||||
|
{% set path = metric.split('.') %}
|
||||||
|
{% set title = ' '.join(path) %}
|
||||||
|
{% if metrics[path[0]] and metrics[path[0]][path[1]] %}
|
||||||
|
{% set value = metrics[path[0]][path[1]] %}
|
||||||
|
{% if value != 0 and value|int != value %}
|
||||||
|
{% set format_str = '%.2f' %}
|
||||||
|
{% else %}
|
||||||
|
{% set format_str = '%s' %}
|
||||||
|
{% endif %}
|
||||||
|
<span title="{{ title }}" class="ui small count label {{ title }}">{{ format_str|format(value) }}</span>
|
||||||
|
{% else %}
|
||||||
|
<span title="{{ title }}" class="ui small count label">0</span>
|
||||||
|
{% endif%}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{%- endmacro %}
|
||||||
|
|
||||||
{% macro datatable_init(table_html_id, ajax_url, default_length, length_selector, extra_options=None) -%}
|
{% macro datatable_init(table_html_id, ajax_url, default_length, length_selector, extra_options=None) -%}
|
||||||
// Init datatable
|
// Init datatable
|
||||||
$.fn.dataTable.ext.errMode = 'throw';
|
$.fn.dataTable.ext.errMode = 'throw';
|
||||||
|
|||||||
@@ -1,24 +1,21 @@
|
|||||||
{% extends 'layout.html' %}
|
{% extends 'layout.html' %}
|
||||||
|
{% import '_macros.html' as macros %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="ui fluid icon input hide" style="margin-bottom:20px">
|
<table id="inventory_table" class='ui fixed compact very basic sortable table'>
|
||||||
<input autofocus="autofocus" class="filter-table" placeholder="Type here to filter...">
|
|
||||||
</div>
|
|
||||||
<table class='ui compact very basic sortable table'>
|
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
{% for head in headers %}
|
{% for head in fact_headers %}
|
||||||
<th{% if loop.index == 1 %} class="default-sort"{% endif %}>{{head}}</th>
|
<th>{{head}}</th>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="searchable">
|
<tbody class="searchable">
|
||||||
{% for node, facts in fact_data.iteritems() %}
|
|
||||||
<tr>
|
|
||||||
{% for name in fact_names %}
|
|
||||||
<td><a href="{{url_for('node', env=current_env, node_name=node)}}">{{facts.get(name, 'undef')}}</a></td>
|
|
||||||
{% endfor %}
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
{% block onload_script %}
|
||||||
|
{% macro extra_options(caller) %}
|
||||||
|
'serverSide': false,
|
||||||
|
{% endmacro %}
|
||||||
|
{{ macros.datatable_init(table_html_id="inventory_table", ajax_url=url_for('inventory_ajax', env=current_env), default_length=config.NORMAL_TABLE_COUNT, length_selector=config.TABLE_COUNT_SELECTOR, extra_options=extra_options) }}
|
||||||
|
{% endblock onload_script %}
|
||||||
|
|||||||
23
puppetboard/templates/inventory.json.tpl
Normal file
23
puppetboard/templates/inventory.json.tpl
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{%- import '_macros.html' as macros -%}
|
||||||
|
{
|
||||||
|
"draw": {{draw}},
|
||||||
|
"recordsTotal": {{total}},
|
||||||
|
"recordsFiltered": {{total_filtered}},
|
||||||
|
"data": [
|
||||||
|
{% for node in fact_data -%}
|
||||||
|
{%- if not loop.first %},{%- endif -%}
|
||||||
|
[
|
||||||
|
{%- for column in columns -%}
|
||||||
|
{%- if not loop.first %},{%- endif -%}
|
||||||
|
{%- if column in ['fqdn', 'hostname'] -%}
|
||||||
|
{% filter jsonprint %}<a href="{{ url_for('node', env=current_env, node_name=node) }}">{{ node }}</a>{% endfilter %}
|
||||||
|
{%- elif fact_data[node][column] -%}
|
||||||
|
{{ fact_data[node][column] | jsonprint }}
|
||||||
|
{%- else -%}
|
||||||
|
""
|
||||||
|
{%- endif -%}
|
||||||
|
{%- endfor -%}
|
||||||
|
]
|
||||||
|
{% endfor -%}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td><a href="{{url_for('node', env=current_env, 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>
|
<td>
|
||||||
{{report.version}}
|
{{report.version|safe}}
|
||||||
</td>
|
</td>
|
||||||
<td rel="utctimestamp">
|
<td rel="utctimestamp">
|
||||||
{{report.start}}
|
{{report.start}}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
"<span rel=\"utctimestamp\">{{ report[column.attr] }}</span>"
|
"<span rel=\"utctimestamp\">{{ report[column.attr] }}</span>"
|
||||||
{%- elif column.type == 'status' -%}
|
{%- elif column.type == 'status' -%}
|
||||||
{% filter jsonprint -%}
|
{% filter jsonprint -%}
|
||||||
{{ macros.status_counts(status=report.status, node_name=report.node, events=report_event_counts[report.hash_], report_hash=report.hash_, current_env=current_env) }}
|
{{ macros.report_status(status=report.status, node_name=report.node, metrics=metrics[report.hash_], report_hash=report.hash_, current_env=current_env) }}
|
||||||
{%- endfilter %}
|
{%- endfilter %}
|
||||||
{%- elif column.type == 'node' -%}
|
{%- elif column.type == 'node' -%}
|
||||||
{% filter jsonprint %}<a href="{{url_for('node', env=current_env, node_name=report.node)}}">{{ report.node }}</a>{% endfilter %}
|
{% filter jsonprint %}<a href="{{url_for('node', env=current_env, node_name=report.node)}}">{{ report.node }}</a>{% endfilter %}
|
||||||
|
|||||||
@@ -7,3 +7,4 @@ Werkzeug >=0.12.1
|
|||||||
itsdangerous >=0.23
|
itsdangerous >=0.23
|
||||||
pypuppetdb >=0.3.2
|
pypuppetdb >=0.3.2
|
||||||
requests >=2.13.0
|
requests >=2.13.0
|
||||||
|
CommonMark==0.7.2
|
||||||
|
|||||||
@@ -116,3 +116,11 @@ def test_env_table_selector(cleanUpEnv):
|
|||||||
os.environ['TABLE_COUNT_SELECTOR'] = '5,15,25'
|
os.environ['TABLE_COUNT_SELECTOR'] = '5,15,25'
|
||||||
reload(docker_settings)
|
reload(docker_settings)
|
||||||
assert [5, 15, 25] == docker_settings.TABLE_COUNT_SELECTOR
|
assert [5, 15, 25] == docker_settings.TABLE_COUNT_SELECTOR
|
||||||
|
|
||||||
|
|
||||||
|
def test_env_column_options(cleanUpEnv):
|
||||||
|
os.environ['DISPLAYED_METRICS'] = 'resources.total, events.failure'
|
||||||
|
|
||||||
|
reload(docker_settings)
|
||||||
|
assert ['resources.total',
|
||||||
|
'events.failure'] == docker_settings.DISPLAYED_METRICS
|
||||||
|
|||||||
Reference in New Issue
Block a user