Complete user interface rewrite

This commit introduces the first iteration of a vast web UI
refactor.

The home page now highlights the data recorded by ARA and
the core of the UI now revolves around the one and single
playbook reports page.

There were three main objectives with this UI work:
- Improve UX (ex: being able to search, find & sort things easily
  * Everything is now searchable and sortable
  * Browsing tips have been added to help users get the most out
    of the interface features

- Improve scalability and performance: the interface should be
  fast and easy to browse whether you have dozens or thousands
  of hosts and tasks
  * Every result list or table are now paginated
  * You can customize pagination preferences with the
    ARA_PLAYBOOK_PER_PAGE and ARA_RESULT_PER_PAGE
    configuration parameters.

- Improve static generation time and weight
  Examples of the same data sets before and after:
  * ARA integration tests (5 playbooks, 59 tasks, 69 results):
    * Before: 5.4 seconds, 1.6MB (gzipped), 217 files
    * After: 2 seconds, 1.2MB (gzipped), 119 files
  * OpenStack-Ansible (1 playbook, 1547 tasks, 1667 results):
    * Before: 6m21 seconds, 31MB (gzipped), 3710 files
    * After: 20 seconds, 8.9MB (gzipped), 1916 files

Misc:
- Jinja HTML templates are now fully indented with no regards
  to line length or PEP8 to privilege readability over long and
  nested content.
- Added some missing web application unit tests
- Various javascript and css optimizations
- The web application backend in itself was significantly
  simplified: less routes, less templates, less code
- Added a configuration parameter ARA_PLAYBOOK_PER_PAGE which
  controls the amount of playbooks per page in the playbook
  report list.
- Added a configuration parameter ARA_RESULT_PER_PAGE which
  controls the amount of results per page in the data results
  table (such as hosts, plays and tasks).

Known issues:
- The file list table in the file panel will eventually
  be replaced by a folder/file hierarchy tree

Change-Id: I3802562ae1593a9dbb549d2654d63d00fd0c8310
This commit is contained in:
David Moreau-Simard
2017-02-24 18:27:11 -05:00
parent 1d07049235
commit 20c672cb76
37 changed files with 1483 additions and 1176 deletions

View File

@@ -23,6 +23,8 @@ DEFAULT_ARA_LOG_FORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
DEFAULT_ARA_SQL_DEBUG = False
DEFAULT_ARA_PATH_MAX = 40
DEFAULT_ARA_IGNORE_MIMETYPE_WARNINGS = True
DEFAULT_ARA_PLAYBOOK_PER_PAGE = 10
DEFAULT_ARA_RESULT_PER_PAGE = 25
config, path = load_config_file()
@@ -42,6 +44,14 @@ try:
ARA_PLAYBOOK_OVERRIDE = get_config(
config, 'ara', 'playbook_override', 'ARA_PLAYBOOK_OVERRIDE',
None, islist=True)
ARA_PLAYBOOK_PER_PAGE = get_config(
config, 'ara', 'playbook_per_page', 'ARA_PLAYBOOK_PER_PAGE',
DEFAULT_ARA_PLAYBOOK_PER_PAGE, integer=True
)
ARA_RESULT_PER_PAGE = get_config(
config, 'ara', 'result_per_page', 'ARA_RESULT_PER_PAGE',
DEFAULT_ARA_RESULT_PER_PAGE, integer=True
)
except TypeError:
ARA_TMP_DIR = get_config(
config, 'defaults', 'local_tmp', 'ANSIBLE_LOCAL_TEMP',
@@ -49,6 +59,14 @@ except TypeError:
ARA_PLAYBOOK_OVERRIDE = get_config(
config, 'ara', 'playbook_override', 'ARA_PLAYBOOK_OVERRIDE',
None, value_type='list')
ARA_PLAYBOOK_PER_PAGE = get_config(
config, 'ara', 'playbook_per_page', 'ARA_PLAYBOOK_PER_PAGE',
DEFAULT_ARA_PLAYBOOK_PER_PAGE, value_type='integer'
)
ARA_RESULT_PER_PAGE = get_config(
config, 'ara', 'result_per_page', 'ARA_RESULT_PER_PAGE',
DEFAULT_ARA_RESULT_PER_PAGE, value_type='integer'
)
ARA_LOG_FILE = get_config(
config, 'ara', 'logfile', 'ARA_LOG_FILE',
DEFAULT_ARA_LOG_FILE)

View File

@@ -1,43 +1,23 @@
/* Color for highlighted line */
.hll {
background-color: #39a5dc;
color: white;
/* Main container layout size and offset */
.col-md-custom {
-webkit-box-flex: 0;
-webkit-flex: 0 0 95%;
-ms-flex: 0 0 95%;
flex: 0 0 95%;
max-width: 95%;
}
table.table.table-striped tr.active {
/*
This doesn't actually work as intended, it doesn't set the actual color...
But it still changes the row's color to highlight it, let's fix it later.
*/
background-color: blue;
.col-md-offset-custom {
margin-left: 2.5%;
}
.action-icon {
padding-top: 4px;
padding-bottom: 4px;
padding-left: 6px;
padding-right: 12px;
}
.action-available {
color: #008000;
}
.action-unavailable {
color: #000000;
}
/* Wrap pre tags so they don't print endlessly horizontally */
pre {
white-space: pre-wrap;
white-space: -moz-pre-wrap;
white-space: -pre-wrap;
white-space: -o-pre-wrap;
word-wrap: break-word;
/* Home stat highlight badges */
.stat-highlight {
text-align: left;
font-weight: bold;
}
/* Status styling */
/* Status text styling */
.status-label {
padding-top: 5px;
padding-bottom: 5px;
@@ -61,6 +41,17 @@ pre {
}
/* Table styling */
/* Wrap pre tags so they don't print endlessly horizontally */
.table pre, .results pre {
white-space: pre-wrap;
white-space: -moz-pre-wrap;
white-space: -pre-wrap;
white-space: -o-pre-wrap;
overflow-wrap: break-word;
word-wrap: break-word;
text-align: left;
}
.table th {
font-size: 120%;
}
@@ -69,15 +60,36 @@ pre {
text-align: center;
}
td a {
width:100%;;
.table td a {
width: 100%;
font-weight: bold;
}
.date td {
white-space: nowrap;
}
.table tbody>tr>td.vert-align{
vertical-align: middle;
}
/* Tooltips */
.tooltip-inner {
max-width: 100%;
}
/* Status icon on left side of report list */
.list-view-pf-left {
max-width: 36px;
}
/* Width of date on left side of report list */
.timestamp-heading {
max-width: 125px;
}
/* Combined width of date and playbook path in report list */
.list-view-pf-description {
max-width: 40%;
}
/* Combined width of playbook highlights/panel items in report list */
.list-view-pf-additional-info {
max-width: 70%;
}

BIN
ara/static/images/ara.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.9 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.0 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 7.9 KiB

View File

@@ -1,76 +1,65 @@
/* Taken from http://stackoverflow.com/a/13067009 */
(function(document, history, location) {
var HISTORY_SUPPORT = !!(history && history.pushState);
$(document).ready(function () {
// Toggle dropdown menu
$(".list-view-pf-actions").on("show.bs.dropdown", function () {
var $this = $(this);
var $dropdown = $this.find(".dropdown");
var space = $(window).height() - $dropdown[0].getBoundingClientRect().top - $this.find(".dropdown-menu").outerHeight(true);
$dropdown.toggleClass("dropup", space < 10);
});
var anchorScrolls = {
ANCHOR_REGEX: /^#[^ ]+$/,
OFFSET_HEIGHT_PX: 12,
// Compound expansion
$(".list-view-pf-expand").on("click", function () {
var $this = $(this);
var $heading = $(this).parents(".list-group-item");
//var $row = $heading.parent();
var $subPanels = $heading.find(".list-group-item-container");
var index = $heading.find(".list-view-pf-expand").index(this);
/**
* Establish events, and fix initial scroll position if a hash is provided.
*/
init: function() {
this.scrollToCurrent();
$(window).on('hashchange', $.proxy(this, 'scrollToCurrent'));
$('body').on('click', 'a', $.proxy(this, 'delegateAnchors'));
},
// Remove all active status
$heading.find(".list-view-pf-expand.active").find(".fa-angle-right").removeClass("fa-angle-down")
.end().removeClass("active")
.end().removeClass("list-view-pf-expand-active");
/**
* Return the offset amount to deduct from the normal scroll position.
* Modify as appropriate to allow for dynamic calculations
*/
getFixedOffset: function() {
return this.OFFSET_HEIGHT_PX;
},
// Add active to the clicked item
$(this).addClass("active")
.parents(".list-group-item").addClass("list-view-pf-expand-active")
.end().find(".fa-angle-right").addClass("fa-angle-down");
/**
* If the provided href is an anchor which resolves to an element on the
* page, scroll to it.
* @param {String} href
* @return {Boolean} - Was the href an anchor.
*/
scrollIfAnchor: function(href, pushToHistory) {
var match, anchorOffset;
if(!this.ANCHOR_REGEX.test(href)) {
return false;
}
match = document.getElementById(href.slice(1));
if(match) {
anchorOffset = $(match).offset().top - this.getFixedOffset();
$('html, body').animate({ scrollTop: anchorOffset});
// Add the state to history as-per normal anchor links
if(HISTORY_SUPPORT && pushToHistory) {
history.pushState({}, document.title, location.pathname + href);
// Check if it needs to hide
if($subPanels.eq(index).hasClass("hidden")) {
$heading.find(".list-group-item-container:visible").addClass("hidden");
$subPanels.eq(index).removeClass("hidden");
} else {
$subPanels.eq(index).addClass("hidden");
$heading.find(".list-view-pf-expand.active").find(".fa-angle-right").removeClass("fa-angle-down")
.end().removeClass("active")
.end().removeClass("list-view-pf-expand-active");
}
}
});
return !!match;
},
// Click close button to close the panel
$(".list-group-item-container .close").on("click", function () {
var $this = $(this);
var $panel = $this.parent();
/**
* Attempt to scroll to the current location's hash.
*/
scrollToCurrent: function(e) {
if(this.scrollIfAnchor(window.location.hash) && e) {
e.preventDefault();
}
},
// Close the container and remove the active status
$panel.addClass("hidden")
.parent().removeClass("list-view-pf-expand-active")
.find(".list-view-pf-expand.active").removeClass("active")
.find(".fa-angle-right").removeClass("fa-angle-down")
});
/**
* If the click event's target was an anchor, fix the scroll position.
*/
delegateAnchors: function(e) {
var elem = e.target;
// Initialize tooltips
$('[data-toggle="tooltip"]').tooltip();
if(this.scrollIfAnchor(elem.getAttribute('href'), true)) {
e.preventDefault();
}
}
};
// Highlight the anchor line in file views
var hash = $(location).attr('hash');
$(hash).closest('span').addClass('hll');
$(document).ready($.proxy(anchorScrolls, 'init'));
})(window.document, window.history, window.location);
// Refresh the highlighted line when clicking on a new line in file views
$('a').click(function(){
$("span.hll").removeClass('hll');
var hash = $(this).attr('href');
$(hash).closest('span').addClass('hll');
});
});

167
ara/static/js/jquery.dataTables.min.js vendored Normal file
View File

@@ -0,0 +1,167 @@
/*!
DataTables 1.10.13
©2008-2016 SpryMedia Ltd - datatables.net/license
*/
(function(h){"function"===typeof define&&define.amd?define(["jquery"],function(E){return h(E,window,document)}):"object"===typeof exports?module.exports=function(E,H){E||(E=window);H||(H="undefined"!==typeof window?require("jquery"):require("jquery")(E));return h(H,E,E.document)}:h(jQuery,window,document)})(function(h,E,H,k){function Y(a){var b,c,d={};h.each(a,function(e){if((b=e.match(/^([^A-Z]+?)([A-Z])/))&&-1!=="a aa ai ao as b fn i m o s ".indexOf(b[1]+" "))c=e.replace(b[0],b[2].toLowerCase()),
d[c]=e,"o"===b[1]&&Y(a[e])});a._hungarianMap=d}function J(a,b,c){a._hungarianMap||Y(a);var d;h.each(b,function(e){d=a._hungarianMap[e];if(d!==k&&(c||b[d]===k))"o"===d.charAt(0)?(b[d]||(b[d]={}),h.extend(!0,b[d],b[e]),J(a[d],b[d],c)):b[d]=b[e]})}function Fa(a){var b=m.defaults.oLanguage,c=a.sZeroRecords;!a.sEmptyTable&&(c&&"No data available in table"===b.sEmptyTable)&&F(a,a,"sZeroRecords","sEmptyTable");!a.sLoadingRecords&&(c&&"Loading..."===b.sLoadingRecords)&&F(a,a,"sZeroRecords","sLoadingRecords");
a.sInfoThousands&&(a.sThousands=a.sInfoThousands);(a=a.sDecimal)&&fb(a)}function gb(a){A(a,"ordering","bSort");A(a,"orderMulti","bSortMulti");A(a,"orderClasses","bSortClasses");A(a,"orderCellsTop","bSortCellsTop");A(a,"order","aaSorting");A(a,"orderFixed","aaSortingFixed");A(a,"paging","bPaginate");A(a,"pagingType","sPaginationType");A(a,"pageLength","iDisplayLength");A(a,"searching","bFilter");"boolean"===typeof a.sScrollX&&(a.sScrollX=a.sScrollX?"100%":"");"boolean"===typeof a.scrollX&&(a.scrollX=
a.scrollX?"100%":"");if(a=a.aoSearchCols)for(var b=0,c=a.length;b<c;b++)a[b]&&J(m.models.oSearch,a[b])}function hb(a){A(a,"orderable","bSortable");A(a,"orderData","aDataSort");A(a,"orderSequence","asSorting");A(a,"orderDataType","sortDataType");var b=a.aDataSort;b&&!h.isArray(b)&&(a.aDataSort=[b])}function ib(a){if(!m.__browser){var b={};m.__browser=b;var c=h("<div/>").css({position:"fixed",top:0,left:-1*h(E).scrollLeft(),height:1,width:1,overflow:"hidden"}).append(h("<div/>").css({position:"absolute",
top:1,left:1,width:100,overflow:"scroll"}).append(h("<div/>").css({width:"100%",height:10}))).appendTo("body"),d=c.children(),e=d.children();b.barWidth=d[0].offsetWidth-d[0].clientWidth;b.bScrollOversize=100===e[0].offsetWidth&&100!==d[0].clientWidth;b.bScrollbarLeft=1!==Math.round(e.offset().left);b.bBounding=c[0].getBoundingClientRect().width?!0:!1;c.remove()}h.extend(a.oBrowser,m.__browser);a.oScroll.iBarWidth=m.__browser.barWidth}function jb(a,b,c,d,e,f){var g,j=!1;c!==k&&(g=c,j=!0);for(;d!==
e;)a.hasOwnProperty(d)&&(g=j?b(g,a[d],d,a):a[d],j=!0,d+=f);return g}function Ga(a,b){var c=m.defaults.column,d=a.aoColumns.length,c=h.extend({},m.models.oColumn,c,{nTh:b?b:H.createElement("th"),sTitle:c.sTitle?c.sTitle:b?b.innerHTML:"",aDataSort:c.aDataSort?c.aDataSort:[d],mData:c.mData?c.mData:d,idx:d});a.aoColumns.push(c);c=a.aoPreSearchCols;c[d]=h.extend({},m.models.oSearch,c[d]);la(a,d,h(b).data())}function la(a,b,c){var b=a.aoColumns[b],d=a.oClasses,e=h(b.nTh);if(!b.sWidthOrig){b.sWidthOrig=
e.attr("width")||null;var f=(e.attr("style")||"").match(/width:\s*(\d+[pxem%]+)/);f&&(b.sWidthOrig=f[1])}c!==k&&null!==c&&(hb(c),J(m.defaults.column,c),c.mDataProp!==k&&!c.mData&&(c.mData=c.mDataProp),c.sType&&(b._sManualType=c.sType),c.className&&!c.sClass&&(c.sClass=c.className),h.extend(b,c),F(b,c,"sWidth","sWidthOrig"),c.iDataSort!==k&&(b.aDataSort=[c.iDataSort]),F(b,c,"aDataSort"));var g=b.mData,j=R(g),i=b.mRender?R(b.mRender):null,c=function(a){return"string"===typeof a&&-1!==a.indexOf("@")};
b._bAttrSrc=h.isPlainObject(g)&&(c(g.sort)||c(g.type)||c(g.filter));b._setter=null;b.fnGetData=function(a,b,c){var d=j(a,b,k,c);return i&&b?i(d,b,a,c):d};b.fnSetData=function(a,b,c){return S(g)(a,b,c)};"number"!==typeof g&&(a._rowReadObject=!0);a.oFeatures.bSort||(b.bSortable=!1,e.addClass(d.sSortableNone));a=-1!==h.inArray("asc",b.asSorting);c=-1!==h.inArray("desc",b.asSorting);!b.bSortable||!a&&!c?(b.sSortingClass=d.sSortableNone,b.sSortingClassJUI=""):a&&!c?(b.sSortingClass=d.sSortableAsc,b.sSortingClassJUI=
d.sSortJUIAscAllowed):!a&&c?(b.sSortingClass=d.sSortableDesc,b.sSortingClassJUI=d.sSortJUIDescAllowed):(b.sSortingClass=d.sSortable,b.sSortingClassJUI=d.sSortJUI)}function Z(a){if(!1!==a.oFeatures.bAutoWidth){var b=a.aoColumns;Ha(a);for(var c=0,d=b.length;c<d;c++)b[c].nTh.style.width=b[c].sWidth}b=a.oScroll;(""!==b.sY||""!==b.sX)&&ma(a);s(a,null,"column-sizing",[a])}function $(a,b){var c=na(a,"bVisible");return"number"===typeof c[b]?c[b]:null}function aa(a,b){var c=na(a,"bVisible"),c=h.inArray(b,
c);return-1!==c?c:null}function ba(a){var b=0;h.each(a.aoColumns,function(a,d){d.bVisible&&"none"!==h(d.nTh).css("display")&&b++});return b}function na(a,b){var c=[];h.map(a.aoColumns,function(a,e){a[b]&&c.push(e)});return c}function Ia(a){var b=a.aoColumns,c=a.aoData,d=m.ext.type.detect,e,f,g,j,i,h,l,q,r;e=0;for(f=b.length;e<f;e++)if(l=b[e],r=[],!l.sType&&l._sManualType)l.sType=l._sManualType;else if(!l.sType){g=0;for(j=d.length;g<j;g++){i=0;for(h=c.length;i<h;i++){r[i]===k&&(r[i]=B(a,i,e,"type"));
q=d[g](r[i],a);if(!q&&g!==d.length-1)break;if("html"===q)break}if(q){l.sType=q;break}}l.sType||(l.sType="string")}}function kb(a,b,c,d){var e,f,g,j,i,n,l=a.aoColumns;if(b)for(e=b.length-1;0<=e;e--){n=b[e];var q=n.targets!==k?n.targets:n.aTargets;h.isArray(q)||(q=[q]);f=0;for(g=q.length;f<g;f++)if("number"===typeof q[f]&&0<=q[f]){for(;l.length<=q[f];)Ga(a);d(q[f],n)}else if("number"===typeof q[f]&&0>q[f])d(l.length+q[f],n);else if("string"===typeof q[f]){j=0;for(i=l.length;j<i;j++)("_all"==q[f]||h(l[j].nTh).hasClass(q[f]))&&
d(j,n)}}if(c){e=0;for(a=c.length;e<a;e++)d(e,c[e])}}function N(a,b,c,d){var e=a.aoData.length,f=h.extend(!0,{},m.models.oRow,{src:c?"dom":"data",idx:e});f._aData=b;a.aoData.push(f);for(var g=a.aoColumns,j=0,i=g.length;j<i;j++)g[j].sType=null;a.aiDisplayMaster.push(e);b=a.rowIdFn(b);b!==k&&(a.aIds[b]=f);(c||!a.oFeatures.bDeferRender)&&Ja(a,e,c,d);return e}function oa(a,b){var c;b instanceof h||(b=h(b));return b.map(function(b,e){c=Ka(a,e);return N(a,c.data,e,c.cells)})}function B(a,b,c,d){var e=a.iDraw,
f=a.aoColumns[c],g=a.aoData[b]._aData,j=f.sDefaultContent,i=f.fnGetData(g,d,{settings:a,row:b,col:c});if(i===k)return a.iDrawError!=e&&null===j&&(K(a,0,"Requested unknown parameter "+("function"==typeof f.mData?"{function}":"'"+f.mData+"'")+" for row "+b+", column "+c,4),a.iDrawError=e),j;if((i===g||null===i)&&null!==j&&d!==k)i=j;else if("function"===typeof i)return i.call(g);return null===i&&"display"==d?"":i}function lb(a,b,c,d){a.aoColumns[c].fnSetData(a.aoData[b]._aData,d,{settings:a,row:b,col:c})}
function La(a){return h.map(a.match(/(\\.|[^\.])+/g)||[""],function(a){return a.replace(/\\\./g,".")})}function R(a){if(h.isPlainObject(a)){var b={};h.each(a,function(a,c){c&&(b[a]=R(c))});return function(a,c,f,g){var j=b[c]||b._;return j!==k?j(a,c,f,g):a}}if(null===a)return function(a){return a};if("function"===typeof a)return function(b,c,f,g){return a(b,c,f,g)};if("string"===typeof a&&(-1!==a.indexOf(".")||-1!==a.indexOf("[")||-1!==a.indexOf("("))){var c=function(a,b,f){var g,j;if(""!==f){j=La(f);
for(var i=0,n=j.length;i<n;i++){f=j[i].match(ca);g=j[i].match(V);if(f){j[i]=j[i].replace(ca,"");""!==j[i]&&(a=a[j[i]]);g=[];j.splice(0,i+1);j=j.join(".");if(h.isArray(a)){i=0;for(n=a.length;i<n;i++)g.push(c(a[i],b,j))}a=f[0].substring(1,f[0].length-1);a=""===a?g:g.join(a);break}else if(g){j[i]=j[i].replace(V,"");a=a[j[i]]();continue}if(null===a||a[j[i]]===k)return k;a=a[j[i]]}}return a};return function(b,e){return c(b,e,a)}}return function(b){return b[a]}}function S(a){if(h.isPlainObject(a))return S(a._);
if(null===a)return function(){};if("function"===typeof a)return function(b,d,e){a(b,"set",d,e)};if("string"===typeof a&&(-1!==a.indexOf(".")||-1!==a.indexOf("[")||-1!==a.indexOf("("))){var b=function(a,d,e){var e=La(e),f;f=e[e.length-1];for(var g,j,i=0,n=e.length-1;i<n;i++){g=e[i].match(ca);j=e[i].match(V);if(g){e[i]=e[i].replace(ca,"");a[e[i]]=[];f=e.slice();f.splice(0,i+1);g=f.join(".");if(h.isArray(d)){j=0;for(n=d.length;j<n;j++)f={},b(f,d[j],g),a[e[i]].push(f)}else a[e[i]]=d;return}j&&(e[i]=e[i].replace(V,
""),a=a[e[i]](d));if(null===a[e[i]]||a[e[i]]===k)a[e[i]]={};a=a[e[i]]}if(f.match(V))a[f.replace(V,"")](d);else a[f.replace(ca,"")]=d};return function(c,d){return b(c,d,a)}}return function(b,d){b[a]=d}}function Ma(a){return D(a.aoData,"_aData")}function pa(a){a.aoData.length=0;a.aiDisplayMaster.length=0;a.aiDisplay.length=0;a.aIds={}}function qa(a,b,c){for(var d=-1,e=0,f=a.length;e<f;e++)a[e]==b?d=e:a[e]>b&&a[e]--; -1!=d&&c===k&&a.splice(d,1)}function da(a,b,c,d){var e=a.aoData[b],f,g=function(c,d){for(;c.childNodes.length;)c.removeChild(c.firstChild);
c.innerHTML=B(a,b,d,"display")};if("dom"===c||(!c||"auto"===c)&&"dom"===e.src)e._aData=Ka(a,e,d,d===k?k:e._aData).data;else{var j=e.anCells;if(j)if(d!==k)g(j[d],d);else{c=0;for(f=j.length;c<f;c++)g(j[c],c)}}e._aSortData=null;e._aFilterData=null;g=a.aoColumns;if(d!==k)g[d].sType=null;else{c=0;for(f=g.length;c<f;c++)g[c].sType=null;Na(a,e)}}function Ka(a,b,c,d){var e=[],f=b.firstChild,g,j,i=0,n,l=a.aoColumns,q=a._rowReadObject,d=d!==k?d:q?{}:[],r=function(a,b){if("string"===typeof a){var c=a.indexOf("@");
-1!==c&&(c=a.substring(c+1),S(a)(d,b.getAttribute(c)))}},m=function(a){if(c===k||c===i)j=l[i],n=h.trim(a.innerHTML),j&&j._bAttrSrc?(S(j.mData._)(d,n),r(j.mData.sort,a),r(j.mData.type,a),r(j.mData.filter,a)):q?(j._setter||(j._setter=S(j.mData)),j._setter(d,n)):d[i]=n;i++};if(f)for(;f;){g=f.nodeName.toUpperCase();if("TD"==g||"TH"==g)m(f),e.push(f);f=f.nextSibling}else{e=b.anCells;f=0;for(g=e.length;f<g;f++)m(e[f])}if(b=b.firstChild?b:b.nTr)(b=b.getAttribute("id"))&&S(a.rowId)(d,b);return{data:d,cells:e}}
function Ja(a,b,c,d){var e=a.aoData[b],f=e._aData,g=[],j,i,n,l,q;if(null===e.nTr){j=c||H.createElement("tr");e.nTr=j;e.anCells=g;j._DT_RowIndex=b;Na(a,e);l=0;for(q=a.aoColumns.length;l<q;l++){n=a.aoColumns[l];i=c?d[l]:H.createElement(n.sCellType);i._DT_CellIndex={row:b,column:l};g.push(i);if((!c||n.mRender||n.mData!==l)&&(!h.isPlainObject(n.mData)||n.mData._!==l+".display"))i.innerHTML=B(a,b,l,"display");n.sClass&&(i.className+=" "+n.sClass);n.bVisible&&!c?j.appendChild(i):!n.bVisible&&c&&i.parentNode.removeChild(i);
n.fnCreatedCell&&n.fnCreatedCell.call(a.oInstance,i,B(a,b,l),f,b,l)}s(a,"aoRowCreatedCallback",null,[j,f,b])}e.nTr.setAttribute("role","row")}function Na(a,b){var c=b.nTr,d=b._aData;if(c){var e=a.rowIdFn(d);e&&(c.id=e);d.DT_RowClass&&(e=d.DT_RowClass.split(" "),b.__rowc=b.__rowc?sa(b.__rowc.concat(e)):e,h(c).removeClass(b.__rowc.join(" ")).addClass(d.DT_RowClass));d.DT_RowAttr&&h(c).attr(d.DT_RowAttr);d.DT_RowData&&h(c).data(d.DT_RowData)}}function mb(a){var b,c,d,e,f,g=a.nTHead,j=a.nTFoot,i=0===
h("th, td",g).length,n=a.oClasses,l=a.aoColumns;i&&(e=h("<tr/>").appendTo(g));b=0;for(c=l.length;b<c;b++)f=l[b],d=h(f.nTh).addClass(f.sClass),i&&d.appendTo(e),a.oFeatures.bSort&&(d.addClass(f.sSortingClass),!1!==f.bSortable&&(d.attr("tabindex",a.iTabIndex).attr("aria-controls",a.sTableId),Oa(a,f.nTh,b))),f.sTitle!=d[0].innerHTML&&d.html(f.sTitle),Pa(a,"header")(a,d,f,n);i&&ea(a.aoHeader,g);h(g).find(">tr").attr("role","row");h(g).find(">tr>th, >tr>td").addClass(n.sHeaderTH);h(j).find(">tr>th, >tr>td").addClass(n.sFooterTH);
if(null!==j){a=a.aoFooter[0];b=0;for(c=a.length;b<c;b++)f=l[b],f.nTf=a[b].cell,f.sClass&&h(f.nTf).addClass(f.sClass)}}function fa(a,b,c){var d,e,f,g=[],j=[],i=a.aoColumns.length,n;if(b){c===k&&(c=!1);d=0;for(e=b.length;d<e;d++){g[d]=b[d].slice();g[d].nTr=b[d].nTr;for(f=i-1;0<=f;f--)!a.aoColumns[f].bVisible&&!c&&g[d].splice(f,1);j.push([])}d=0;for(e=g.length;d<e;d++){if(a=g[d].nTr)for(;f=a.firstChild;)a.removeChild(f);f=0;for(b=g[d].length;f<b;f++)if(n=i=1,j[d][f]===k){a.appendChild(g[d][f].cell);
for(j[d][f]=1;g[d+i]!==k&&g[d][f].cell==g[d+i][f].cell;)j[d+i][f]=1,i++;for(;g[d][f+n]!==k&&g[d][f].cell==g[d][f+n].cell;){for(c=0;c<i;c++)j[d+c][f+n]=1;n++}h(g[d][f].cell).attr("rowspan",i).attr("colspan",n)}}}}function O(a){var b=s(a,"aoPreDrawCallback","preDraw",[a]);if(-1!==h.inArray(!1,b))C(a,!1);else{var b=[],c=0,d=a.asStripeClasses,e=d.length,f=a.oLanguage,g=a.iInitDisplayStart,j="ssp"==y(a),i=a.aiDisplay;a.bDrawing=!0;g!==k&&-1!==g&&(a._iDisplayStart=j?g:g>=a.fnRecordsDisplay()?0:g,a.iInitDisplayStart=
-1);var g=a._iDisplayStart,n=a.fnDisplayEnd();if(a.bDeferLoading)a.bDeferLoading=!1,a.iDraw++,C(a,!1);else if(j){if(!a.bDestroying&&!nb(a))return}else a.iDraw++;if(0!==i.length){f=j?a.aoData.length:n;for(j=j?0:g;j<f;j++){var l=i[j],q=a.aoData[l];null===q.nTr&&Ja(a,l);l=q.nTr;if(0!==e){var r=d[c%e];q._sRowStripe!=r&&(h(l).removeClass(q._sRowStripe).addClass(r),q._sRowStripe=r)}s(a,"aoRowCallback",null,[l,q._aData,c,j]);b.push(l);c++}}else c=f.sZeroRecords,1==a.iDraw&&"ajax"==y(a)?c=f.sLoadingRecords:
f.sEmptyTable&&0===a.fnRecordsTotal()&&(c=f.sEmptyTable),b[0]=h("<tr/>",{"class":e?d[0]:""}).append(h("<td />",{valign:"top",colSpan:ba(a),"class":a.oClasses.sRowEmpty}).html(c))[0];s(a,"aoHeaderCallback","header",[h(a.nTHead).children("tr")[0],Ma(a),g,n,i]);s(a,"aoFooterCallback","footer",[h(a.nTFoot).children("tr")[0],Ma(a),g,n,i]);d=h(a.nTBody);d.children().detach();d.append(h(b));s(a,"aoDrawCallback","draw",[a]);a.bSorted=!1;a.bFiltered=!1;a.bDrawing=!1}}function T(a,b){var c=a.oFeatures,d=c.bFilter;
c.bSort&&ob(a);d?ga(a,a.oPreviousSearch):a.aiDisplay=a.aiDisplayMaster.slice();!0!==b&&(a._iDisplayStart=0);a._drawHold=b;O(a);a._drawHold=!1}function pb(a){var b=a.oClasses,c=h(a.nTable),c=h("<div/>").insertBefore(c),d=a.oFeatures,e=h("<div/>",{id:a.sTableId+"_wrapper","class":b.sWrapper+(a.nTFoot?"":" "+b.sNoFooter)});a.nHolding=c[0];a.nTableWrapper=e[0];a.nTableReinsertBefore=a.nTable.nextSibling;for(var f=a.sDom.split(""),g,j,i,n,l,q,k=0;k<f.length;k++){g=null;j=f[k];if("<"==j){i=h("<div/>")[0];
n=f[k+1];if("'"==n||'"'==n){l="";for(q=2;f[k+q]!=n;)l+=f[k+q],q++;"H"==l?l=b.sJUIHeader:"F"==l&&(l=b.sJUIFooter);-1!=l.indexOf(".")?(n=l.split("."),i.id=n[0].substr(1,n[0].length-1),i.className=n[1]):"#"==l.charAt(0)?i.id=l.substr(1,l.length-1):i.className=l;k+=q}e.append(i);e=h(i)}else if(">"==j)e=e.parent();else if("l"==j&&d.bPaginate&&d.bLengthChange)g=qb(a);else if("f"==j&&d.bFilter)g=rb(a);else if("r"==j&&d.bProcessing)g=sb(a);else if("t"==j)g=tb(a);else if("i"==j&&d.bInfo)g=ub(a);else if("p"==
j&&d.bPaginate)g=vb(a);else if(0!==m.ext.feature.length){i=m.ext.feature;q=0;for(n=i.length;q<n;q++)if(j==i[q].cFeature){g=i[q].fnInit(a);break}}g&&(i=a.aanFeatures,i[j]||(i[j]=[]),i[j].push(g),e.append(g))}c.replaceWith(e);a.nHolding=null}function ea(a,b){var c=h(b).children("tr"),d,e,f,g,j,i,n,l,q,k;a.splice(0,a.length);f=0;for(i=c.length;f<i;f++)a.push([]);f=0;for(i=c.length;f<i;f++){d=c[f];for(e=d.firstChild;e;){if("TD"==e.nodeName.toUpperCase()||"TH"==e.nodeName.toUpperCase()){l=1*e.getAttribute("colspan");
q=1*e.getAttribute("rowspan");l=!l||0===l||1===l?1:l;q=!q||0===q||1===q?1:q;g=0;for(j=a[f];j[g];)g++;n=g;k=1===l?!0:!1;for(j=0;j<l;j++)for(g=0;g<q;g++)a[f+g][n+j]={cell:e,unique:k},a[f+g].nTr=d}e=e.nextSibling}}}function ta(a,b,c){var d=[];c||(c=a.aoHeader,b&&(c=[],ea(c,b)));for(var b=0,e=c.length;b<e;b++)for(var f=0,g=c[b].length;f<g;f++)if(c[b][f].unique&&(!d[f]||!a.bSortCellsTop))d[f]=c[b][f].cell;return d}function ua(a,b,c){s(a,"aoServerParams","serverParams",[b]);if(b&&h.isArray(b)){var d={},
e=/(.*?)\[\]$/;h.each(b,function(a,b){var c=b.name.match(e);c?(c=c[0],d[c]||(d[c]=[]),d[c].push(b.value)):d[b.name]=b.value});b=d}var f,g=a.ajax,j=a.oInstance,i=function(b){s(a,null,"xhr",[a,b,a.jqXHR]);c(b)};if(h.isPlainObject(g)&&g.data){f=g.data;var n=h.isFunction(f)?f(b,a):f,b=h.isFunction(f)&&n?n:h.extend(!0,b,n);delete g.data}n={data:b,success:function(b){var c=b.error||b.sError;c&&K(a,0,c);a.json=b;i(b)},dataType:"json",cache:!1,type:a.sServerMethod,error:function(b,c){var d=s(a,null,"xhr",
[a,null,a.jqXHR]);-1===h.inArray(!0,d)&&("parsererror"==c?K(a,0,"Invalid JSON response",1):4===b.readyState&&K(a,0,"Ajax error",7));C(a,!1)}};a.oAjaxData=b;s(a,null,"preXhr",[a,b]);a.fnServerData?a.fnServerData.call(j,a.sAjaxSource,h.map(b,function(a,b){return{name:b,value:a}}),i,a):a.sAjaxSource||"string"===typeof g?a.jqXHR=h.ajax(h.extend(n,{url:g||a.sAjaxSource})):h.isFunction(g)?a.jqXHR=g.call(j,b,i,a):(a.jqXHR=h.ajax(h.extend(n,g)),g.data=f)}function nb(a){return a.bAjaxDataGet?(a.iDraw++,C(a,
!0),ua(a,wb(a),function(b){xb(a,b)}),!1):!0}function wb(a){var b=a.aoColumns,c=b.length,d=a.oFeatures,e=a.oPreviousSearch,f=a.aoPreSearchCols,g,j=[],i,n,l,k=W(a);g=a._iDisplayStart;i=!1!==d.bPaginate?a._iDisplayLength:-1;var r=function(a,b){j.push({name:a,value:b})};r("sEcho",a.iDraw);r("iColumns",c);r("sColumns",D(b,"sName").join(","));r("iDisplayStart",g);r("iDisplayLength",i);var ra={draw:a.iDraw,columns:[],order:[],start:g,length:i,search:{value:e.sSearch,regex:e.bRegex}};for(g=0;g<c;g++)n=b[g],
l=f[g],i="function"==typeof n.mData?"function":n.mData,ra.columns.push({data:i,name:n.sName,searchable:n.bSearchable,orderable:n.bSortable,search:{value:l.sSearch,regex:l.bRegex}}),r("mDataProp_"+g,i),d.bFilter&&(r("sSearch_"+g,l.sSearch),r("bRegex_"+g,l.bRegex),r("bSearchable_"+g,n.bSearchable)),d.bSort&&r("bSortable_"+g,n.bSortable);d.bFilter&&(r("sSearch",e.sSearch),r("bRegex",e.bRegex));d.bSort&&(h.each(k,function(a,b){ra.order.push({column:b.col,dir:b.dir});r("iSortCol_"+a,b.col);r("sSortDir_"+
a,b.dir)}),r("iSortingCols",k.length));b=m.ext.legacy.ajax;return null===b?a.sAjaxSource?j:ra:b?j:ra}function xb(a,b){var c=va(a,b),d=b.sEcho!==k?b.sEcho:b.draw,e=b.iTotalRecords!==k?b.iTotalRecords:b.recordsTotal,f=b.iTotalDisplayRecords!==k?b.iTotalDisplayRecords:b.recordsFiltered;if(d){if(1*d<a.iDraw)return;a.iDraw=1*d}pa(a);a._iRecordsTotal=parseInt(e,10);a._iRecordsDisplay=parseInt(f,10);d=0;for(e=c.length;d<e;d++)N(a,c[d]);a.aiDisplay=a.aiDisplayMaster.slice();a.bAjaxDataGet=!1;O(a);a._bInitComplete||
wa(a,b);a.bAjaxDataGet=!0;C(a,!1)}function va(a,b){var c=h.isPlainObject(a.ajax)&&a.ajax.dataSrc!==k?a.ajax.dataSrc:a.sAjaxDataProp;return"data"===c?b.aaData||b[c]:""!==c?R(c)(b):b}function rb(a){var b=a.oClasses,c=a.sTableId,d=a.oLanguage,e=a.oPreviousSearch,f=a.aanFeatures,g='<input type="search" class="'+b.sFilterInput+'"/>',j=d.sSearch,j=j.match(/_INPUT_/)?j.replace("_INPUT_",g):j+g,b=h("<div/>",{id:!f.f?c+"_filter":null,"class":b.sFilter}).append(h("<label/>").append(j)),f=function(){var b=!this.value?
"":this.value;b!=e.sSearch&&(ga(a,{sSearch:b,bRegex:e.bRegex,bSmart:e.bSmart,bCaseInsensitive:e.bCaseInsensitive}),a._iDisplayStart=0,O(a))},g=null!==a.searchDelay?a.searchDelay:"ssp"===y(a)?400:0,i=h("input",b).val(e.sSearch).attr("placeholder",d.sSearchPlaceholder).on("keyup.DT search.DT input.DT paste.DT cut.DT",g?Qa(f,g):f).on("keypress.DT",function(a){if(13==a.keyCode)return!1}).attr("aria-controls",c);h(a.nTable).on("search.dt.DT",function(b,c){if(a===c)try{i[0]!==H.activeElement&&i.val(e.sSearch)}catch(d){}});
return b[0]}function ga(a,b,c){var d=a.oPreviousSearch,e=a.aoPreSearchCols,f=function(a){d.sSearch=a.sSearch;d.bRegex=a.bRegex;d.bSmart=a.bSmart;d.bCaseInsensitive=a.bCaseInsensitive};Ia(a);if("ssp"!=y(a)){yb(a,b.sSearch,c,b.bEscapeRegex!==k?!b.bEscapeRegex:b.bRegex,b.bSmart,b.bCaseInsensitive);f(b);for(b=0;b<e.length;b++)zb(a,e[b].sSearch,b,e[b].bEscapeRegex!==k?!e[b].bEscapeRegex:e[b].bRegex,e[b].bSmart,e[b].bCaseInsensitive);Ab(a)}else f(b);a.bFiltered=!0;s(a,null,"search",[a])}function Ab(a){for(var b=
m.ext.search,c=a.aiDisplay,d,e,f=0,g=b.length;f<g;f++){for(var j=[],i=0,n=c.length;i<n;i++)e=c[i],d=a.aoData[e],b[f](a,d._aFilterData,e,d._aData,i)&&j.push(e);c.length=0;h.merge(c,j)}}function zb(a,b,c,d,e,f){if(""!==b){for(var g=[],j=a.aiDisplay,d=Ra(b,d,e,f),e=0;e<j.length;e++)b=a.aoData[j[e]]._aFilterData[c],d.test(b)&&g.push(j[e]);a.aiDisplay=g}}function yb(a,b,c,d,e,f){var d=Ra(b,d,e,f),f=a.oPreviousSearch.sSearch,g=a.aiDisplayMaster,j,e=[];0!==m.ext.search.length&&(c=!0);j=Bb(a);if(0>=b.length)a.aiDisplay=
g.slice();else{if(j||c||f.length>b.length||0!==b.indexOf(f)||a.bSorted)a.aiDisplay=g.slice();b=a.aiDisplay;for(c=0;c<b.length;c++)d.test(a.aoData[b[c]]._sFilterRow)&&e.push(b[c]);a.aiDisplay=e}}function Ra(a,b,c,d){a=b?a:Sa(a);c&&(a="^(?=.*?"+h.map(a.match(/"[^"]+"|[^ ]+/g)||[""],function(a){if('"'===a.charAt(0))var b=a.match(/^"(.*)"$/),a=b?b[1]:a;return a.replace('"',"")}).join(")(?=.*?")+").*$");return RegExp(a,d?"i":"")}function Bb(a){var b=a.aoColumns,c,d,e,f,g,j,i,h,l=m.ext.type.search;c=!1;
d=0;for(f=a.aoData.length;d<f;d++)if(h=a.aoData[d],!h._aFilterData){j=[];e=0;for(g=b.length;e<g;e++)c=b[e],c.bSearchable?(i=B(a,d,e,"filter"),l[c.sType]&&(i=l[c.sType](i)),null===i&&(i=""),"string"!==typeof i&&i.toString&&(i=i.toString())):i="",i.indexOf&&-1!==i.indexOf("&")&&(xa.innerHTML=i,i=$b?xa.textContent:xa.innerText),i.replace&&(i=i.replace(/[\r\n]/g,"")),j.push(i);h._aFilterData=j;h._sFilterRow=j.join(" ");c=!0}return c}function Cb(a){return{search:a.sSearch,smart:a.bSmart,regex:a.bRegex,
caseInsensitive:a.bCaseInsensitive}}function Db(a){return{sSearch:a.search,bSmart:a.smart,bRegex:a.regex,bCaseInsensitive:a.caseInsensitive}}function ub(a){var b=a.sTableId,c=a.aanFeatures.i,d=h("<div/>",{"class":a.oClasses.sInfo,id:!c?b+"_info":null});c||(a.aoDrawCallback.push({fn:Eb,sName:"information"}),d.attr("role","status").attr("aria-live","polite"),h(a.nTable).attr("aria-describedby",b+"_info"));return d[0]}function Eb(a){var b=a.aanFeatures.i;if(0!==b.length){var c=a.oLanguage,d=a._iDisplayStart+
1,e=a.fnDisplayEnd(),f=a.fnRecordsTotal(),g=a.fnRecordsDisplay(),j=g?c.sInfo:c.sInfoEmpty;g!==f&&(j+=" "+c.sInfoFiltered);j+=c.sInfoPostFix;j=Fb(a,j);c=c.fnInfoCallback;null!==c&&(j=c.call(a.oInstance,a,d,e,f,g,j));h(b).html(j)}}function Fb(a,b){var c=a.fnFormatNumber,d=a._iDisplayStart+1,e=a._iDisplayLength,f=a.fnRecordsDisplay(),g=-1===e;return b.replace(/_START_/g,c.call(a,d)).replace(/_END_/g,c.call(a,a.fnDisplayEnd())).replace(/_MAX_/g,c.call(a,a.fnRecordsTotal())).replace(/_TOTAL_/g,c.call(a,
f)).replace(/_PAGE_/g,c.call(a,g?1:Math.ceil(d/e))).replace(/_PAGES_/g,c.call(a,g?1:Math.ceil(f/e)))}function ha(a){var b,c,d=a.iInitDisplayStart,e=a.aoColumns,f;c=a.oFeatures;var g=a.bDeferLoading;if(a.bInitialised){pb(a);mb(a);fa(a,a.aoHeader);fa(a,a.aoFooter);C(a,!0);c.bAutoWidth&&Ha(a);b=0;for(c=e.length;b<c;b++)f=e[b],f.sWidth&&(f.nTh.style.width=v(f.sWidth));s(a,null,"preInit",[a]);T(a);e=y(a);if("ssp"!=e||g)"ajax"==e?ua(a,[],function(c){var f=va(a,c);for(b=0;b<f.length;b++)N(a,f[b]);a.iInitDisplayStart=
d;T(a);C(a,!1);wa(a,c)},a):(C(a,!1),wa(a))}else setTimeout(function(){ha(a)},200)}function wa(a,b){a._bInitComplete=!0;(b||a.oInit.aaData)&&Z(a);s(a,null,"plugin-init",[a,b]);s(a,"aoInitComplete","init",[a,b])}function Ta(a,b){var c=parseInt(b,10);a._iDisplayLength=c;Ua(a);s(a,null,"length",[a,c])}function qb(a){for(var b=a.oClasses,c=a.sTableId,d=a.aLengthMenu,e=h.isArray(d[0]),f=e?d[0]:d,d=e?d[1]:d,e=h("<select/>",{name:c+"_length","aria-controls":c,"class":b.sLengthSelect}),g=0,j=f.length;g<j;g++)e[0][g]=
new Option(d[g],f[g]);var i=h("<div><label/></div>").addClass(b.sLength);a.aanFeatures.l||(i[0].id=c+"_length");i.children().append(a.oLanguage.sLengthMenu.replace("_MENU_",e[0].outerHTML));h("select",i).val(a._iDisplayLength).on("change.DT",function(){Ta(a,h(this).val());O(a)});h(a.nTable).on("length.dt.DT",function(b,c,d){a===c&&h("select",i).val(d)});return i[0]}function vb(a){var b=a.sPaginationType,c=m.ext.pager[b],d="function"===typeof c,e=function(a){O(a)},b=h("<div/>").addClass(a.oClasses.sPaging+
b)[0],f=a.aanFeatures;d||c.fnInit(a,b,e);f.p||(b.id=a.sTableId+"_paginate",a.aoDrawCallback.push({fn:function(a){if(d){var b=a._iDisplayStart,i=a._iDisplayLength,h=a.fnRecordsDisplay(),l=-1===i,b=l?0:Math.ceil(b/i),i=l?1:Math.ceil(h/i),h=c(b,i),k,l=0;for(k=f.p.length;l<k;l++)Pa(a,"pageButton")(a,f.p[l],l,h,b,i)}else c.fnUpdate(a,e)},sName:"pagination"}));return b}function Va(a,b,c){var d=a._iDisplayStart,e=a._iDisplayLength,f=a.fnRecordsDisplay();0===f||-1===e?d=0:"number"===typeof b?(d=b*e,d>f&&
(d=0)):"first"==b?d=0:"previous"==b?(d=0<=e?d-e:0,0>d&&(d=0)):"next"==b?d+e<f&&(d+=e):"last"==b?d=Math.floor((f-1)/e)*e:K(a,0,"Unknown paging action: "+b,5);b=a._iDisplayStart!==d;a._iDisplayStart=d;b&&(s(a,null,"page",[a]),c&&O(a));return b}function sb(a){return h("<div/>",{id:!a.aanFeatures.r?a.sTableId+"_processing":null,"class":a.oClasses.sProcessing}).html(a.oLanguage.sProcessing).insertBefore(a.nTable)[0]}function C(a,b){a.oFeatures.bProcessing&&h(a.aanFeatures.r).css("display",b?"block":"none");
s(a,null,"processing",[a,b])}function tb(a){var b=h(a.nTable);b.attr("role","grid");var c=a.oScroll;if(""===c.sX&&""===c.sY)return a.nTable;var d=c.sX,e=c.sY,f=a.oClasses,g=b.children("caption"),j=g.length?g[0]._captionSide:null,i=h(b[0].cloneNode(!1)),n=h(b[0].cloneNode(!1)),l=b.children("tfoot");l.length||(l=null);i=h("<div/>",{"class":f.sScrollWrapper}).append(h("<div/>",{"class":f.sScrollHead}).css({overflow:"hidden",position:"relative",border:0,width:d?!d?null:v(d):"100%"}).append(h("<div/>",
{"class":f.sScrollHeadInner}).css({"box-sizing":"content-box",width:c.sXInner||"100%"}).append(i.removeAttr("id").css("margin-left",0).append("top"===j?g:null).append(b.children("thead"))))).append(h("<div/>",{"class":f.sScrollBody}).css({position:"relative",overflow:"auto",width:!d?null:v(d)}).append(b));l&&i.append(h("<div/>",{"class":f.sScrollFoot}).css({overflow:"hidden",border:0,width:d?!d?null:v(d):"100%"}).append(h("<div/>",{"class":f.sScrollFootInner}).append(n.removeAttr("id").css("margin-left",
0).append("bottom"===j?g:null).append(b.children("tfoot")))));var b=i.children(),k=b[0],f=b[1],r=l?b[2]:null;if(d)h(f).on("scroll.DT",function(){var a=this.scrollLeft;k.scrollLeft=a;l&&(r.scrollLeft=a)});h(f).css(e&&c.bCollapse?"max-height":"height",e);a.nScrollHead=k;a.nScrollBody=f;a.nScrollFoot=r;a.aoDrawCallback.push({fn:ma,sName:"scrolling"});return i[0]}function ma(a){var b=a.oScroll,c=b.sX,d=b.sXInner,e=b.sY,b=b.iBarWidth,f=h(a.nScrollHead),g=f[0].style,j=f.children("div"),i=j[0].style,n=j.children("table"),
j=a.nScrollBody,l=h(j),q=j.style,r=h(a.nScrollFoot).children("div"),m=r.children("table"),p=h(a.nTHead),o=h(a.nTable),u=o[0],s=u.style,t=a.nTFoot?h(a.nTFoot):null,x=a.oBrowser,U=x.bScrollOversize,ac=D(a.aoColumns,"nTh"),P,L,Q,w,Wa=[],y=[],z=[],A=[],B,C=function(a){a=a.style;a.paddingTop="0";a.paddingBottom="0";a.borderTopWidth="0";a.borderBottomWidth="0";a.height=0};L=j.scrollHeight>j.clientHeight;if(a.scrollBarVis!==L&&a.scrollBarVis!==k)a.scrollBarVis=L,Z(a);else{a.scrollBarVis=L;o.children("thead, tfoot").remove();
t&&(Q=t.clone().prependTo(o),P=t.find("tr"),Q=Q.find("tr"));w=p.clone().prependTo(o);p=p.find("tr");L=w.find("tr");w.find("th, td").removeAttr("tabindex");c||(q.width="100%",f[0].style.width="100%");h.each(ta(a,w),function(b,c){B=$(a,b);c.style.width=a.aoColumns[B].sWidth});t&&I(function(a){a.style.width=""},Q);f=o.outerWidth();if(""===c){s.width="100%";if(U&&(o.find("tbody").height()>j.offsetHeight||"scroll"==l.css("overflow-y")))s.width=v(o.outerWidth()-b);f=o.outerWidth()}else""!==d&&(s.width=
v(d),f=o.outerWidth());I(C,L);I(function(a){z.push(a.innerHTML);Wa.push(v(h(a).css("width")))},L);I(function(a,b){if(h.inArray(a,ac)!==-1)a.style.width=Wa[b]},p);h(L).height(0);t&&(I(C,Q),I(function(a){A.push(a.innerHTML);y.push(v(h(a).css("width")))},Q),I(function(a,b){a.style.width=y[b]},P),h(Q).height(0));I(function(a,b){a.innerHTML='<div class="dataTables_sizing" style="height:0;overflow:hidden;">'+z[b]+"</div>";a.style.width=Wa[b]},L);t&&I(function(a,b){a.innerHTML='<div class="dataTables_sizing" style="height:0;overflow:hidden;">'+
A[b]+"</div>";a.style.width=y[b]},Q);if(o.outerWidth()<f){P=j.scrollHeight>j.offsetHeight||"scroll"==l.css("overflow-y")?f+b:f;if(U&&(j.scrollHeight>j.offsetHeight||"scroll"==l.css("overflow-y")))s.width=v(P-b);(""===c||""!==d)&&K(a,1,"Possible column misalignment",6)}else P="100%";q.width=v(P);g.width=v(P);t&&(a.nScrollFoot.style.width=v(P));!e&&U&&(q.height=v(u.offsetHeight+b));c=o.outerWidth();n[0].style.width=v(c);i.width=v(c);d=o.height()>j.clientHeight||"scroll"==l.css("overflow-y");e="padding"+
(x.bScrollbarLeft?"Left":"Right");i[e]=d?b+"px":"0px";t&&(m[0].style.width=v(c),r[0].style.width=v(c),r[0].style[e]=d?b+"px":"0px");o.children("colgroup").insertBefore(o.children("thead"));l.scroll();if((a.bSorted||a.bFiltered)&&!a._drawHold)j.scrollTop=0}}function I(a,b,c){for(var d=0,e=0,f=b.length,g,j;e<f;){g=b[e].firstChild;for(j=c?c[e].firstChild:null;g;)1===g.nodeType&&(c?a(g,j,d):a(g,d),d++),g=g.nextSibling,j=c?j.nextSibling:null;e++}}function Ha(a){var b=a.nTable,c=a.aoColumns,d=a.oScroll,
e=d.sY,f=d.sX,g=d.sXInner,j=c.length,i=na(a,"bVisible"),n=h("th",a.nTHead),l=b.getAttribute("width"),k=b.parentNode,r=!1,m,p,o=a.oBrowser,d=o.bScrollOversize;(m=b.style.width)&&-1!==m.indexOf("%")&&(l=m);for(m=0;m<i.length;m++)p=c[i[m]],null!==p.sWidth&&(p.sWidth=Gb(p.sWidthOrig,k),r=!0);if(d||!r&&!f&&!e&&j==ba(a)&&j==n.length)for(m=0;m<j;m++)i=$(a,m),null!==i&&(c[i].sWidth=v(n.eq(m).width()));else{j=h(b).clone().css("visibility","hidden").removeAttr("id");j.find("tbody tr").remove();var u=h("<tr/>").appendTo(j.find("tbody"));
j.find("thead, tfoot").remove();j.append(h(a.nTHead).clone()).append(h(a.nTFoot).clone());j.find("tfoot th, tfoot td").css("width","");n=ta(a,j.find("thead")[0]);for(m=0;m<i.length;m++)p=c[i[m]],n[m].style.width=null!==p.sWidthOrig&&""!==p.sWidthOrig?v(p.sWidthOrig):"",p.sWidthOrig&&f&&h(n[m]).append(h("<div/>").css({width:p.sWidthOrig,margin:0,padding:0,border:0,height:1}));if(a.aoData.length)for(m=0;m<i.length;m++)r=i[m],p=c[r],h(Hb(a,r)).clone(!1).append(p.sContentPadding).appendTo(u);h("[name]",
j).removeAttr("name");p=h("<div/>").css(f||e?{position:"absolute",top:0,left:0,height:1,right:0,overflow:"hidden"}:{}).append(j).appendTo(k);f&&g?j.width(g):f?(j.css("width","auto"),j.removeAttr("width"),j.width()<k.clientWidth&&l&&j.width(k.clientWidth)):e?j.width(k.clientWidth):l&&j.width(l);for(m=e=0;m<i.length;m++)k=h(n[m]),g=k.outerWidth()-k.width(),k=o.bBounding?Math.ceil(n[m].getBoundingClientRect().width):k.outerWidth(),e+=k,c[i[m]].sWidth=v(k-g);b.style.width=v(e);p.remove()}l&&(b.style.width=
v(l));if((l||f)&&!a._reszEvt)b=function(){h(E).on("resize.DT-"+a.sInstance,Qa(function(){Z(a)}))},d?setTimeout(b,1E3):b(),a._reszEvt=!0}function Gb(a,b){if(!a)return 0;var c=h("<div/>").css("width",v(a)).appendTo(b||H.body),d=c[0].offsetWidth;c.remove();return d}function Hb(a,b){var c=Ib(a,b);if(0>c)return null;var d=a.aoData[c];return!d.nTr?h("<td/>").html(B(a,c,b,"display"))[0]:d.anCells[b]}function Ib(a,b){for(var c,d=-1,e=-1,f=0,g=a.aoData.length;f<g;f++)c=B(a,f,b,"display")+"",c=c.replace(bc,
""),c=c.replace(/&nbsp;/g," "),c.length>d&&(d=c.length,e=f);return e}function v(a){return null===a?"0px":"number"==typeof a?0>a?"0px":a+"px":a.match(/\d$/)?a+"px":a}function W(a){var b,c,d=[],e=a.aoColumns,f,g,j,i;b=a.aaSortingFixed;c=h.isPlainObject(b);var n=[];f=function(a){a.length&&!h.isArray(a[0])?n.push(a):h.merge(n,a)};h.isArray(b)&&f(b);c&&b.pre&&f(b.pre);f(a.aaSorting);c&&b.post&&f(b.post);for(a=0;a<n.length;a++){i=n[a][0];f=e[i].aDataSort;b=0;for(c=f.length;b<c;b++)g=f[b],j=e[g].sType||
"string",n[a]._idx===k&&(n[a]._idx=h.inArray(n[a][1],e[g].asSorting)),d.push({src:i,col:g,dir:n[a][1],index:n[a]._idx,type:j,formatter:m.ext.type.order[j+"-pre"]})}return d}function ob(a){var b,c,d=[],e=m.ext.type.order,f=a.aoData,g=0,j,i=a.aiDisplayMaster,h;Ia(a);h=W(a);b=0;for(c=h.length;b<c;b++)j=h[b],j.formatter&&g++,Jb(a,j.col);if("ssp"!=y(a)&&0!==h.length){b=0;for(c=i.length;b<c;b++)d[i[b]]=b;g===h.length?i.sort(function(a,b){var c,e,g,j,i=h.length,k=f[a]._aSortData,m=f[b]._aSortData;for(g=
0;g<i;g++)if(j=h[g],c=k[j.col],e=m[j.col],c=c<e?-1:c>e?1:0,0!==c)return"asc"===j.dir?c:-c;c=d[a];e=d[b];return c<e?-1:c>e?1:0}):i.sort(function(a,b){var c,g,j,i,k=h.length,m=f[a]._aSortData,p=f[b]._aSortData;for(j=0;j<k;j++)if(i=h[j],c=m[i.col],g=p[i.col],i=e[i.type+"-"+i.dir]||e["string-"+i.dir],c=i(c,g),0!==c)return c;c=d[a];g=d[b];return c<g?-1:c>g?1:0})}a.bSorted=!0}function Kb(a){for(var b,c,d=a.aoColumns,e=W(a),a=a.oLanguage.oAria,f=0,g=d.length;f<g;f++){c=d[f];var j=c.asSorting;b=c.sTitle.replace(/<.*?>/g,
"");var i=c.nTh;i.removeAttribute("aria-sort");c.bSortable&&(0<e.length&&e[0].col==f?(i.setAttribute("aria-sort","asc"==e[0].dir?"ascending":"descending"),c=j[e[0].index+1]||j[0]):c=j[0],b+="asc"===c?a.sSortAscending:a.sSortDescending);i.setAttribute("aria-label",b)}}function Xa(a,b,c,d){var e=a.aaSorting,f=a.aoColumns[b].asSorting,g=function(a,b){var c=a._idx;c===k&&(c=h.inArray(a[1],f));return c+1<f.length?c+1:b?null:0};"number"===typeof e[0]&&(e=a.aaSorting=[e]);c&&a.oFeatures.bSortMulti?(c=h.inArray(b,
D(e,"0")),-1!==c?(b=g(e[c],!0),null===b&&1===e.length&&(b=0),null===b?e.splice(c,1):(e[c][1]=f[b],e[c]._idx=b)):(e.push([b,f[0],0]),e[e.length-1]._idx=0)):e.length&&e[0][0]==b?(b=g(e[0]),e.length=1,e[0][1]=f[b],e[0]._idx=b):(e.length=0,e.push([b,f[0]]),e[0]._idx=0);T(a);"function"==typeof d&&d(a)}function Oa(a,b,c,d){var e=a.aoColumns[c];Ya(b,{},function(b){!1!==e.bSortable&&(a.oFeatures.bProcessing?(C(a,!0),setTimeout(function(){Xa(a,c,b.shiftKey,d);"ssp"!==y(a)&&C(a,!1)},0)):Xa(a,c,b.shiftKey,d))})}
function ya(a){var b=a.aLastSort,c=a.oClasses.sSortColumn,d=W(a),e=a.oFeatures,f,g;if(e.bSort&&e.bSortClasses){e=0;for(f=b.length;e<f;e++)g=b[e].src,h(D(a.aoData,"anCells",g)).removeClass(c+(2>e?e+1:3));e=0;for(f=d.length;e<f;e++)g=d[e].src,h(D(a.aoData,"anCells",g)).addClass(c+(2>e?e+1:3))}a.aLastSort=d}function Jb(a,b){var c=a.aoColumns[b],d=m.ext.order[c.sSortDataType],e;d&&(e=d.call(a.oInstance,a,b,aa(a,b)));for(var f,g=m.ext.type.order[c.sType+"-pre"],j=0,i=a.aoData.length;j<i;j++)if(c=a.aoData[j],
c._aSortData||(c._aSortData=[]),!c._aSortData[b]||d)f=d?e[j]:B(a,j,b,"sort"),c._aSortData[b]=g?g(f):f}function za(a){if(a.oFeatures.bStateSave&&!a.bDestroying){var b={time:+new Date,start:a._iDisplayStart,length:a._iDisplayLength,order:h.extend(!0,[],a.aaSorting),search:Cb(a.oPreviousSearch),columns:h.map(a.aoColumns,function(b,d){return{visible:b.bVisible,search:Cb(a.aoPreSearchCols[d])}})};s(a,"aoStateSaveParams","stateSaveParams",[a,b]);a.oSavedState=b;a.fnStateSaveCallback.call(a.oInstance,a,
b)}}function Lb(a,b,c){var d,e,f=a.aoColumns,b=function(b){if(b&&b.time){var i=s(a,"aoStateLoadParams","stateLoadParams",[a,g]);if(-1===h.inArray(!1,i)&&(i=a.iStateDuration,!(0<i&&b.time<+new Date-1E3*i)&&!(b.columns&&f.length!==b.columns.length))){a.oLoadedState=h.extend(!0,{},g);b.start!==k&&(a._iDisplayStart=b.start,a.iInitDisplayStart=b.start);b.length!==k&&(a._iDisplayLength=b.length);b.order!==k&&(a.aaSorting=[],h.each(b.order,function(b,c){a.aaSorting.push(c[0]>=f.length?[0,c[1]]:c)}));b.search!==
k&&h.extend(a.oPreviousSearch,Db(b.search));if(b.columns){d=0;for(e=b.columns.length;d<e;d++)i=b.columns[d],i.visible!==k&&(f[d].bVisible=i.visible),i.search!==k&&h.extend(a.aoPreSearchCols[d],Db(i.search))}s(a,"aoStateLoaded","stateLoaded",[a,g])}}c()};if(a.oFeatures.bStateSave){var g=a.fnStateLoadCallback.call(a.oInstance,a,b);g!==k&&b(g)}else c()}function Aa(a){var b=m.settings,a=h.inArray(a,D(b,"nTable"));return-1!==a?b[a]:null}function K(a,b,c,d){c="DataTables warning: "+(a?"table id="+a.sTableId+
" - ":"")+c;d&&(c+=". For more information about this error, please see http://datatables.net/tn/"+d);if(b)E.console&&console.log&&console.log(c);else if(b=m.ext,b=b.sErrMode||b.errMode,a&&s(a,null,"error",[a,d,c]),"alert"==b)alert(c);else{if("throw"==b)throw Error(c);"function"==typeof b&&b(a,d,c)}}function F(a,b,c,d){h.isArray(c)?h.each(c,function(c,d){h.isArray(d)?F(a,b,d[0],d[1]):F(a,b,d)}):(d===k&&(d=c),b[c]!==k&&(a[d]=b[c]))}function Mb(a,b,c){var d,e;for(e in b)b.hasOwnProperty(e)&&(d=b[e],
h.isPlainObject(d)?(h.isPlainObject(a[e])||(a[e]={}),h.extend(!0,a[e],d)):a[e]=c&&"data"!==e&&"aaData"!==e&&h.isArray(d)?d.slice():d);return a}function Ya(a,b,c){h(a).on("click.DT",b,function(b){a.blur();c(b)}).on("keypress.DT",b,function(a){13===a.which&&(a.preventDefault(),c(a))}).on("selectstart.DT",function(){return!1})}function z(a,b,c,d){c&&a[b].push({fn:c,sName:d})}function s(a,b,c,d){var e=[];b&&(e=h.map(a[b].slice().reverse(),function(b){return b.fn.apply(a.oInstance,d)}));null!==c&&(b=h.Event(c+
".dt"),h(a.nTable).trigger(b,d),e.push(b.result));return e}function Ua(a){var b=a._iDisplayStart,c=a.fnDisplayEnd(),d=a._iDisplayLength;b>=c&&(b=c-d);b-=b%d;if(-1===d||0>b)b=0;a._iDisplayStart=b}function Pa(a,b){var c=a.renderer,d=m.ext.renderer[b];return h.isPlainObject(c)&&c[b]?d[c[b]]||d._:"string"===typeof c?d[c]||d._:d._}function y(a){return a.oFeatures.bServerSide?"ssp":a.ajax||a.sAjaxSource?"ajax":"dom"}function ia(a,b){var c=[],c=Nb.numbers_length,d=Math.floor(c/2);b<=c?c=X(0,b):a<=d?(c=X(0,
c-2),c.push("ellipsis"),c.push(b-1)):(a>=b-1-d?c=X(b-(c-2),b):(c=X(a-d+2,a+d-1),c.push("ellipsis"),c.push(b-1)),c.splice(0,0,"ellipsis"),c.splice(0,0,0));c.DT_el="span";return c}function fb(a){h.each({num:function(b){return Ba(b,a)},"num-fmt":function(b){return Ba(b,a,Za)},"html-num":function(b){return Ba(b,a,Ca)},"html-num-fmt":function(b){return Ba(b,a,Ca,Za)}},function(b,c){x.type.order[b+a+"-pre"]=c;b.match(/^html\-/)&&(x.type.search[b+a]=x.type.search.html)})}function Ob(a){return function(){var b=
[Aa(this[m.ext.iApiIndex])].concat(Array.prototype.slice.call(arguments));return m.ext.internal[a].apply(this,b)}}var m=function(a){this.$=function(a,b){return this.api(!0).$(a,b)};this._=function(a,b){return this.api(!0).rows(a,b).data()};this.api=function(a){return a?new u(Aa(this[x.iApiIndex])):new u(this)};this.fnAddData=function(a,b){var c=this.api(!0),d=h.isArray(a)&&(h.isArray(a[0])||h.isPlainObject(a[0]))?c.rows.add(a):c.row.add(a);(b===k||b)&&c.draw();return d.flatten().toArray()};this.fnAdjustColumnSizing=
function(a){var b=this.api(!0).columns.adjust(),c=b.settings()[0],d=c.oScroll;a===k||a?b.draw(!1):(""!==d.sX||""!==d.sY)&&ma(c)};this.fnClearTable=function(a){var b=this.api(!0).clear();(a===k||a)&&b.draw()};this.fnClose=function(a){this.api(!0).row(a).child.hide()};this.fnDeleteRow=function(a,b,c){var d=this.api(!0),a=d.rows(a),e=a.settings()[0],h=e.aoData[a[0][0]];a.remove();b&&b.call(this,e,h);(c===k||c)&&d.draw();return h};this.fnDestroy=function(a){this.api(!0).destroy(a)};this.fnDraw=function(a){this.api(!0).draw(a)};
this.fnFilter=function(a,b,c,d,e,h){e=this.api(!0);null===b||b===k?e.search(a,c,d,h):e.column(b).search(a,c,d,h);e.draw()};this.fnGetData=function(a,b){var c=this.api(!0);if(a!==k){var d=a.nodeName?a.nodeName.toLowerCase():"";return b!==k||"td"==d||"th"==d?c.cell(a,b).data():c.row(a).data()||null}return c.data().toArray()};this.fnGetNodes=function(a){var b=this.api(!0);return a!==k?b.row(a).node():b.rows().nodes().flatten().toArray()};this.fnGetPosition=function(a){var b=this.api(!0),c=a.nodeName.toUpperCase();
return"TR"==c?b.row(a).index():"TD"==c||"TH"==c?(a=b.cell(a).index(),[a.row,a.columnVisible,a.column]):null};this.fnIsOpen=function(a){return this.api(!0).row(a).child.isShown()};this.fnOpen=function(a,b,c){return this.api(!0).row(a).child(b,c).show().child()[0]};this.fnPageChange=function(a,b){var c=this.api(!0).page(a);(b===k||b)&&c.draw(!1)};this.fnSetColumnVis=function(a,b,c){a=this.api(!0).column(a).visible(b);(c===k||c)&&a.columns.adjust().draw()};this.fnSettings=function(){return Aa(this[x.iApiIndex])};
this.fnSort=function(a){this.api(!0).order(a).draw()};this.fnSortListener=function(a,b,c){this.api(!0).order.listener(a,b,c)};this.fnUpdate=function(a,b,c,d,e){var h=this.api(!0);c===k||null===c?h.row(b).data(a):h.cell(b,c).data(a);(e===k||e)&&h.columns.adjust();(d===k||d)&&h.draw();return 0};this.fnVersionCheck=x.fnVersionCheck;var b=this,c=a===k,d=this.length;c&&(a={});this.oApi=this.internal=x.internal;for(var e in m.ext.internal)e&&(this[e]=Ob(e));this.each(function(){var e={},g=1<d?Mb(e,a,!0):
a,j=0,i,e=this.getAttribute("id"),n=!1,l=m.defaults,q=h(this);if("table"!=this.nodeName.toLowerCase())K(null,0,"Non-table node initialisation ("+this.nodeName+")",2);else{gb(l);hb(l.column);J(l,l,!0);J(l.column,l.column,!0);J(l,h.extend(g,q.data()));var r=m.settings,j=0;for(i=r.length;j<i;j++){var p=r[j];if(p.nTable==this||p.nTHead.parentNode==this||p.nTFoot&&p.nTFoot.parentNode==this){var u=g.bRetrieve!==k?g.bRetrieve:l.bRetrieve;if(c||u)return p.oInstance;if(g.bDestroy!==k?g.bDestroy:l.bDestroy){p.oInstance.fnDestroy();
break}else{K(p,0,"Cannot reinitialise DataTable",3);return}}if(p.sTableId==this.id){r.splice(j,1);break}}if(null===e||""===e)this.id=e="DataTables_Table_"+m.ext._unique++;var o=h.extend(!0,{},m.models.oSettings,{sDestroyWidth:q[0].style.width,sInstance:e,sTableId:e});o.nTable=this;o.oApi=b.internal;o.oInit=g;r.push(o);o.oInstance=1===b.length?b:q.dataTable();gb(g);g.oLanguage&&Fa(g.oLanguage);g.aLengthMenu&&!g.iDisplayLength&&(g.iDisplayLength=h.isArray(g.aLengthMenu[0])?g.aLengthMenu[0][0]:g.aLengthMenu[0]);
g=Mb(h.extend(!0,{},l),g);F(o.oFeatures,g,"bPaginate bLengthChange bFilter bSort bSortMulti bInfo bProcessing bAutoWidth bSortClasses bServerSide bDeferRender".split(" "));F(o,g,["asStripeClasses","ajax","fnServerData","fnFormatNumber","sServerMethod","aaSorting","aaSortingFixed","aLengthMenu","sPaginationType","sAjaxSource","sAjaxDataProp","iStateDuration","sDom","bSortCellsTop","iTabIndex","fnStateLoadCallback","fnStateSaveCallback","renderer","searchDelay","rowId",["iCookieDuration","iStateDuration"],
["oSearch","oPreviousSearch"],["aoSearchCols","aoPreSearchCols"],["iDisplayLength","_iDisplayLength"],["bJQueryUI","bJUI"]]);F(o.oScroll,g,[["sScrollX","sX"],["sScrollXInner","sXInner"],["sScrollY","sY"],["bScrollCollapse","bCollapse"]]);F(o.oLanguage,g,"fnInfoCallback");z(o,"aoDrawCallback",g.fnDrawCallback,"user");z(o,"aoServerParams",g.fnServerParams,"user");z(o,"aoStateSaveParams",g.fnStateSaveParams,"user");z(o,"aoStateLoadParams",g.fnStateLoadParams,"user");z(o,"aoStateLoaded",g.fnStateLoaded,
"user");z(o,"aoRowCallback",g.fnRowCallback,"user");z(o,"aoRowCreatedCallback",g.fnCreatedRow,"user");z(o,"aoHeaderCallback",g.fnHeaderCallback,"user");z(o,"aoFooterCallback",g.fnFooterCallback,"user");z(o,"aoInitComplete",g.fnInitComplete,"user");z(o,"aoPreDrawCallback",g.fnPreDrawCallback,"user");o.rowIdFn=R(g.rowId);ib(o);var t=o.oClasses;g.bJQueryUI?(h.extend(t,m.ext.oJUIClasses,g.oClasses),g.sDom===l.sDom&&"lfrtip"===l.sDom&&(o.sDom='<"H"lfr>t<"F"ip>'),o.renderer)?h.isPlainObject(o.renderer)&&
!o.renderer.header&&(o.renderer.header="jqueryui"):o.renderer="jqueryui":h.extend(t,m.ext.classes,g.oClasses);q.addClass(t.sTable);o.iInitDisplayStart===k&&(o.iInitDisplayStart=g.iDisplayStart,o._iDisplayStart=g.iDisplayStart);null!==g.iDeferLoading&&(o.bDeferLoading=!0,e=h.isArray(g.iDeferLoading),o._iRecordsDisplay=e?g.iDeferLoading[0]:g.iDeferLoading,o._iRecordsTotal=e?g.iDeferLoading[1]:g.iDeferLoading);var v=o.oLanguage;h.extend(!0,v,g.oLanguage);v.sUrl&&(h.ajax({dataType:"json",url:v.sUrl,success:function(a){Fa(a);
J(l.oLanguage,a);h.extend(true,v,a);ha(o)},error:function(){ha(o)}}),n=!0);null===g.asStripeClasses&&(o.asStripeClasses=[t.sStripeOdd,t.sStripeEven]);var e=o.asStripeClasses,x=q.children("tbody").find("tr").eq(0);-1!==h.inArray(!0,h.map(e,function(a){return x.hasClass(a)}))&&(h("tbody tr",this).removeClass(e.join(" ")),o.asDestroyStripes=e.slice());e=[];r=this.getElementsByTagName("thead");0!==r.length&&(ea(o.aoHeader,r[0]),e=ta(o));if(null===g.aoColumns){r=[];j=0;for(i=e.length;j<i;j++)r.push(null)}else r=
g.aoColumns;j=0;for(i=r.length;j<i;j++)Ga(o,e?e[j]:null);kb(o,g.aoColumnDefs,r,function(a,b){la(o,a,b)});if(x.length){var w=function(a,b){return a.getAttribute("data-"+b)!==null?b:null};h(x[0]).children("th, td").each(function(a,b){var c=o.aoColumns[a];if(c.mData===a){var d=w(b,"sort")||w(b,"order"),e=w(b,"filter")||w(b,"search");if(d!==null||e!==null){c.mData={_:a+".display",sort:d!==null?a+".@data-"+d:k,type:d!==null?a+".@data-"+d:k,filter:e!==null?a+".@data-"+e:k};la(o,a)}}})}var U=o.oFeatures,
e=function(){if(g.aaSorting===k){var a=o.aaSorting;j=0;for(i=a.length;j<i;j++)a[j][1]=o.aoColumns[j].asSorting[0]}ya(o);U.bSort&&z(o,"aoDrawCallback",function(){if(o.bSorted){var a=W(o),b={};h.each(a,function(a,c){b[c.src]=c.dir});s(o,null,"order",[o,a,b]);Kb(o)}});z(o,"aoDrawCallback",function(){(o.bSorted||y(o)==="ssp"||U.bDeferRender)&&ya(o)},"sc");var a=q.children("caption").each(function(){this._captionSide=h(this).css("caption-side")}),b=q.children("thead");b.length===0&&(b=h("<thead/>").appendTo(q));
o.nTHead=b[0];b=q.children("tbody");b.length===0&&(b=h("<tbody/>").appendTo(q));o.nTBody=b[0];b=q.children("tfoot");if(b.length===0&&a.length>0&&(o.oScroll.sX!==""||o.oScroll.sY!==""))b=h("<tfoot/>").appendTo(q);if(b.length===0||b.children().length===0)q.addClass(t.sNoFooter);else if(b.length>0){o.nTFoot=b[0];ea(o.aoFooter,o.nTFoot)}if(g.aaData)for(j=0;j<g.aaData.length;j++)N(o,g.aaData[j]);else(o.bDeferLoading||y(o)=="dom")&&oa(o,h(o.nTBody).children("tr"));o.aiDisplay=o.aiDisplayMaster.slice();
o.bInitialised=true;n===false&&ha(o)};g.bStateSave?(U.bStateSave=!0,z(o,"aoDrawCallback",za,"state_save"),Lb(o,g,e)):e()}});b=null;return this},x,u,p,t,$a={},Pb=/[\r\n]/g,Ca=/<.*?>/g,cc=/^\d{2,4}[\.\/\-]\d{1,2}[\.\/\-]\d{1,2}([T ]{1}\d{1,2}[:\.]\d{2}([\.:]\d{2})?)?$/,dc=RegExp("(\\/|\\.|\\*|\\+|\\?|\\||\\(|\\)|\\[|\\]|\\{|\\}|\\\\|\\$|\\^|\\-)","g"),Za=/[',$£€¥%\u2009\u202F\u20BD\u20a9\u20BArfk]/gi,M=function(a){return!a||!0===a||"-"===a?!0:!1},Qb=function(a){var b=parseInt(a,10);return!isNaN(b)&&
isFinite(a)?b:null},Rb=function(a,b){$a[b]||($a[b]=RegExp(Sa(b),"g"));return"string"===typeof a&&"."!==b?a.replace(/\./g,"").replace($a[b],"."):a},ab=function(a,b,c){var d="string"===typeof a;if(M(a))return!0;b&&d&&(a=Rb(a,b));c&&d&&(a=a.replace(Za,""));return!isNaN(parseFloat(a))&&isFinite(a)},Sb=function(a,b,c){return M(a)?!0:!(M(a)||"string"===typeof a)?null:ab(a.replace(Ca,""),b,c)?!0:null},D=function(a,b,c){var d=[],e=0,f=a.length;if(c!==k)for(;e<f;e++)a[e]&&a[e][b]&&d.push(a[e][b][c]);else for(;e<
f;e++)a[e]&&d.push(a[e][b]);return d},ja=function(a,b,c,d){var e=[],f=0,g=b.length;if(d!==k)for(;f<g;f++)a[b[f]][c]&&e.push(a[b[f]][c][d]);else for(;f<g;f++)e.push(a[b[f]][c]);return e},X=function(a,b){var c=[],d;b===k?(b=0,d=a):(d=b,b=a);for(var e=b;e<d;e++)c.push(e);return c},Tb=function(a){for(var b=[],c=0,d=a.length;c<d;c++)a[c]&&b.push(a[c]);return b},sa=function(a){var b=[],c,d,e=a.length,f,g=0;d=0;a:for(;d<e;d++){c=a[d];for(f=0;f<g;f++)if(b[f]===c)continue a;b.push(c);g++}return b};m.util=
{throttle:function(a,b){var c=b!==k?b:200,d,e;return function(){var b=this,g=+new Date,h=arguments;d&&g<d+c?(clearTimeout(e),e=setTimeout(function(){d=k;a.apply(b,h)},c)):(d=g,a.apply(b,h))}},escapeRegex:function(a){return a.replace(dc,"\\$1")}};var A=function(a,b,c){a[b]!==k&&(a[c]=a[b])},ca=/\[.*?\]$/,V=/\(\)$/,Sa=m.util.escapeRegex,xa=h("<div>")[0],$b=xa.textContent!==k,bc=/<.*?>/g,Qa=m.util.throttle,Ub=[],w=Array.prototype,ec=function(a){var b,c,d=m.settings,e=h.map(d,function(a){return a.nTable});
if(a){if(a.nTable&&a.oApi)return[a];if(a.nodeName&&"table"===a.nodeName.toLowerCase())return b=h.inArray(a,e),-1!==b?[d[b]]:null;if(a&&"function"===typeof a.settings)return a.settings().toArray();"string"===typeof a?c=h(a):a instanceof h&&(c=a)}else return[];if(c)return c.map(function(){b=h.inArray(this,e);return-1!==b?d[b]:null}).toArray()};u=function(a,b){if(!(this instanceof u))return new u(a,b);var c=[],d=function(a){(a=ec(a))&&(c=c.concat(a))};if(h.isArray(a))for(var e=0,f=a.length;e<f;e++)d(a[e]);
else d(a);this.context=sa(c);b&&h.merge(this,b);this.selector={rows:null,cols:null,opts:null};u.extend(this,this,Ub)};m.Api=u;h.extend(u.prototype,{any:function(){return 0!==this.count()},concat:w.concat,context:[],count:function(){return this.flatten().length},each:function(a){for(var b=0,c=this.length;b<c;b++)a.call(this,this[b],b,this);return this},eq:function(a){var b=this.context;return b.length>a?new u(b[a],this[a]):null},filter:function(a){var b=[];if(w.filter)b=w.filter.call(this,a,this);
else for(var c=0,d=this.length;c<d;c++)a.call(this,this[c],c,this)&&b.push(this[c]);return new u(this.context,b)},flatten:function(){var a=[];return new u(this.context,a.concat.apply(a,this.toArray()))},join:w.join,indexOf:w.indexOf||function(a,b){for(var c=b||0,d=this.length;c<d;c++)if(this[c]===a)return c;return-1},iterator:function(a,b,c,d){var e=[],f,g,h,i,n,l=this.context,m,p,t=this.selector;"string"===typeof a&&(d=c,c=b,b=a,a=!1);g=0;for(h=l.length;g<h;g++){var s=new u(l[g]);if("table"===b)f=
c.call(s,l[g],g),f!==k&&e.push(f);else if("columns"===b||"rows"===b)f=c.call(s,l[g],this[g],g),f!==k&&e.push(f);else if("column"===b||"column-rows"===b||"row"===b||"cell"===b){p=this[g];"column-rows"===b&&(m=Da(l[g],t.opts));i=0;for(n=p.length;i<n;i++)f=p[i],f="cell"===b?c.call(s,l[g],f.row,f.column,g,i):c.call(s,l[g],f,g,i,m),f!==k&&e.push(f)}}return e.length||d?(a=new u(l,a?e.concat.apply([],e):e),b=a.selector,b.rows=t.rows,b.cols=t.cols,b.opts=t.opts,a):this},lastIndexOf:w.lastIndexOf||function(a,
b){return this.indexOf.apply(this.toArray.reverse(),arguments)},length:0,map:function(a){var b=[];if(w.map)b=w.map.call(this,a,this);else for(var c=0,d=this.length;c<d;c++)b.push(a.call(this,this[c],c));return new u(this.context,b)},pluck:function(a){return this.map(function(b){return b[a]})},pop:w.pop,push:w.push,reduce:w.reduce||function(a,b){return jb(this,a,b,0,this.length,1)},reduceRight:w.reduceRight||function(a,b){return jb(this,a,b,this.length-1,-1,-1)},reverse:w.reverse,selector:null,shift:w.shift,
sort:w.sort,splice:w.splice,toArray:function(){return w.slice.call(this)},to$:function(){return h(this)},toJQuery:function(){return h(this)},unique:function(){return new u(this.context,sa(this))},unshift:w.unshift});u.extend=function(a,b,c){if(c.length&&b&&(b instanceof u||b.__dt_wrapper)){var d,e,f,g=function(a,b,c){return function(){var d=b.apply(a,arguments);u.extend(d,d,c.methodExt);return d}};d=0;for(e=c.length;d<e;d++)f=c[d],b[f.name]="function"===typeof f.val?g(a,f.val,f):h.isPlainObject(f.val)?
{}:f.val,b[f.name].__dt_wrapper=!0,u.extend(a,b[f.name],f.propExt)}};u.register=p=function(a,b){if(h.isArray(a))for(var c=0,d=a.length;c<d;c++)u.register(a[c],b);else for(var e=a.split("."),f=Ub,g,j,c=0,d=e.length;c<d;c++){g=(j=-1!==e[c].indexOf("()"))?e[c].replace("()",""):e[c];var i;a:{i=0;for(var n=f.length;i<n;i++)if(f[i].name===g){i=f[i];break a}i=null}i||(i={name:g,val:{},methodExt:[],propExt:[]},f.push(i));c===d-1?i.val=b:f=j?i.methodExt:i.propExt}};u.registerPlural=t=function(a,b,c){u.register(a,
c);u.register(b,function(){var a=c.apply(this,arguments);return a===this?this:a instanceof u?a.length?h.isArray(a[0])?new u(a.context,a[0]):a[0]:k:a})};p("tables()",function(a){var b;if(a){b=u;var c=this.context;if("number"===typeof a)a=[c[a]];else var d=h.map(c,function(a){return a.nTable}),a=h(d).filter(a).map(function(){var a=h.inArray(this,d);return c[a]}).toArray();b=new b(a)}else b=this;return b});p("table()",function(a){var a=this.tables(a),b=a.context;return b.length?new u(b[0]):a});t("tables().nodes()",
"table().node()",function(){return this.iterator("table",function(a){return a.nTable},1)});t("tables().body()","table().body()",function(){return this.iterator("table",function(a){return a.nTBody},1)});t("tables().header()","table().header()",function(){return this.iterator("table",function(a){return a.nTHead},1)});t("tables().footer()","table().footer()",function(){return this.iterator("table",function(a){return a.nTFoot},1)});t("tables().containers()","table().container()",function(){return this.iterator("table",
function(a){return a.nTableWrapper},1)});p("draw()",function(a){return this.iterator("table",function(b){"page"===a?O(b):("string"===typeof a&&(a="full-hold"===a?!1:!0),T(b,!1===a))})});p("page()",function(a){return a===k?this.page.info().page:this.iterator("table",function(b){Va(b,a)})});p("page.info()",function(){if(0===this.context.length)return k;var a=this.context[0],b=a._iDisplayStart,c=a.oFeatures.bPaginate?a._iDisplayLength:-1,d=a.fnRecordsDisplay(),e=-1===c;return{page:e?0:Math.floor(b/c),
pages:e?1:Math.ceil(d/c),start:b,end:a.fnDisplayEnd(),length:c,recordsTotal:a.fnRecordsTotal(),recordsDisplay:d,serverSide:"ssp"===y(a)}});p("page.len()",function(a){return a===k?0!==this.context.length?this.context[0]._iDisplayLength:k:this.iterator("table",function(b){Ta(b,a)})});var Vb=function(a,b,c){if(c){var d=new u(a);d.one("draw",function(){c(d.ajax.json())})}if("ssp"==y(a))T(a,b);else{C(a,!0);var e=a.jqXHR;e&&4!==e.readyState&&e.abort();ua(a,[],function(c){pa(a);for(var c=va(a,c),d=0,e=c.length;d<
e;d++)N(a,c[d]);T(a,b);C(a,!1)})}};p("ajax.json()",function(){var a=this.context;if(0<a.length)return a[0].json});p("ajax.params()",function(){var a=this.context;if(0<a.length)return a[0].oAjaxData});p("ajax.reload()",function(a,b){return this.iterator("table",function(c){Vb(c,!1===b,a)})});p("ajax.url()",function(a){var b=this.context;if(a===k){if(0===b.length)return k;b=b[0];return b.ajax?h.isPlainObject(b.ajax)?b.ajax.url:b.ajax:b.sAjaxSource}return this.iterator("table",function(b){h.isPlainObject(b.ajax)?
b.ajax.url=a:b.ajax=a})});p("ajax.url().load()",function(a,b){return this.iterator("table",function(c){Vb(c,!1===b,a)})});var bb=function(a,b,c,d,e){var f=[],g,j,i,n,l,m;i=typeof b;if(!b||"string"===i||"function"===i||b.length===k)b=[b];i=0;for(n=b.length;i<n;i++){j=b[i]&&b[i].split&&!b[i].match(/[\[\(:]/)?b[i].split(","):[b[i]];l=0;for(m=j.length;l<m;l++)(g=c("string"===typeof j[l]?h.trim(j[l]):j[l]))&&g.length&&(f=f.concat(g))}a=x.selector[a];if(a.length){i=0;for(n=a.length;i<n;i++)f=a[i](d,e,f)}return sa(f)},
cb=function(a){a||(a={});a.filter&&a.search===k&&(a.search=a.filter);return h.extend({search:"none",order:"current",page:"all"},a)},db=function(a){for(var b=0,c=a.length;b<c;b++)if(0<a[b].length)return a[0]=a[b],a[0].length=1,a.length=1,a.context=[a.context[b]],a;a.length=0;return a},Da=function(a,b){var c,d,e,f=[],g=a.aiDisplay;c=a.aiDisplayMaster;var j=b.search;d=b.order;e=b.page;if("ssp"==y(a))return"removed"===j?[]:X(0,c.length);if("current"==e){c=a._iDisplayStart;for(d=a.fnDisplayEnd();c<d;c++)f.push(g[c])}else if("current"==
d||"applied"==d)f="none"==j?c.slice():"applied"==j?g.slice():h.map(c,function(a){return-1===h.inArray(a,g)?a:null});else if("index"==d||"original"==d){c=0;for(d=a.aoData.length;c<d;c++)"none"==j?f.push(c):(e=h.inArray(c,g),(-1===e&&"removed"==j||0<=e&&"applied"==j)&&f.push(c))}return f};p("rows()",function(a,b){a===k?a="":h.isPlainObject(a)&&(b=a,a="");var b=cb(b),c=this.iterator("table",function(c){var e=b,f;return bb("row",a,function(a){var b=Qb(a);if(b!==null&&!e)return[b];f||(f=Da(c,e));if(b!==
null&&h.inArray(b,f)!==-1)return[b];if(a===null||a===k||a==="")return f;if(typeof a==="function")return h.map(f,function(b){var e=c.aoData[b];return a(b,e._aData,e.nTr)?b:null});b=Tb(ja(c.aoData,f,"nTr"));if(a.nodeName){if(a._DT_RowIndex!==k)return[a._DT_RowIndex];if(a._DT_CellIndex)return[a._DT_CellIndex.row];b=h(a).closest("*[data-dt-row]");return b.length?[b.data("dt-row")]:[]}if(typeof a==="string"&&a.charAt(0)==="#"){var i=c.aIds[a.replace(/^#/,"")];if(i!==k)return[i.idx]}return h(b).filter(a).map(function(){return this._DT_RowIndex}).toArray()},
c,e)},1);c.selector.rows=a;c.selector.opts=b;return c});p("rows().nodes()",function(){return this.iterator("row",function(a,b){return a.aoData[b].nTr||k},1)});p("rows().data()",function(){return this.iterator(!0,"rows",function(a,b){return ja(a.aoData,b,"_aData")},1)});t("rows().cache()","row().cache()",function(a){return this.iterator("row",function(b,c){var d=b.aoData[c];return"search"===a?d._aFilterData:d._aSortData},1)});t("rows().invalidate()","row().invalidate()",function(a){return this.iterator("row",
function(b,c){da(b,c,a)})});t("rows().indexes()","row().index()",function(){return this.iterator("row",function(a,b){return b},1)});t("rows().ids()","row().id()",function(a){for(var b=[],c=this.context,d=0,e=c.length;d<e;d++)for(var f=0,g=this[d].length;f<g;f++){var h=c[d].rowIdFn(c[d].aoData[this[d][f]]._aData);b.push((!0===a?"#":"")+h)}return new u(c,b)});t("rows().remove()","row().remove()",function(){var a=this;this.iterator("row",function(b,c,d){var e=b.aoData,f=e[c],g,h,i,n,l;e.splice(c,1);
g=0;for(h=e.length;g<h;g++)if(i=e[g],l=i.anCells,null!==i.nTr&&(i.nTr._DT_RowIndex=g),null!==l){i=0;for(n=l.length;i<n;i++)l[i]._DT_CellIndex.row=g}qa(b.aiDisplayMaster,c);qa(b.aiDisplay,c);qa(a[d],c,!1);Ua(b);c=b.rowIdFn(f._aData);c!==k&&delete b.aIds[c]});this.iterator("table",function(a){for(var c=0,d=a.aoData.length;c<d;c++)a.aoData[c].idx=c});return this});p("rows.add()",function(a){var b=this.iterator("table",function(b){var c,f,g,h=[];f=0;for(g=a.length;f<g;f++)c=a[f],c.nodeName&&"TR"===c.nodeName.toUpperCase()?
h.push(oa(b,c)[0]):h.push(N(b,c));return h},1),c=this.rows(-1);c.pop();h.merge(c,b);return c});p("row()",function(a,b){return db(this.rows(a,b))});p("row().data()",function(a){var b=this.context;if(a===k)return b.length&&this.length?b[0].aoData[this[0]]._aData:k;b[0].aoData[this[0]]._aData=a;da(b[0],this[0],"data");return this});p("row().node()",function(){var a=this.context;return a.length&&this.length?a[0].aoData[this[0]].nTr||null:null});p("row.add()",function(a){a instanceof h&&a.length&&(a=a[0]);
var b=this.iterator("table",function(b){return a.nodeName&&"TR"===a.nodeName.toUpperCase()?oa(b,a)[0]:N(b,a)});return this.row(b[0])});var eb=function(a,b){var c=a.context;if(c.length&&(c=c[0].aoData[b!==k?b:a[0]])&&c._details)c._details.remove(),c._detailsShow=k,c._details=k},Wb=function(a,b){var c=a.context;if(c.length&&a.length){var d=c[0].aoData[a[0]];if(d._details){(d._detailsShow=b)?d._details.insertAfter(d.nTr):d._details.detach();var e=c[0],f=new u(e),g=e.aoData;f.off("draw.dt.DT_details column-visibility.dt.DT_details destroy.dt.DT_details");
0<D(g,"_details").length&&(f.on("draw.dt.DT_details",function(a,b){e===b&&f.rows({page:"current"}).eq(0).each(function(a){a=g[a];a._detailsShow&&a._details.insertAfter(a.nTr)})}),f.on("column-visibility.dt.DT_details",function(a,b){if(e===b)for(var c,d=ba(b),f=0,h=g.length;f<h;f++)c=g[f],c._details&&c._details.children("td[colspan]").attr("colspan",d)}),f.on("destroy.dt.DT_details",function(a,b){if(e===b)for(var c=0,d=g.length;c<d;c++)g[c]._details&&eb(f,c)}))}}};p("row().child()",function(a,b){var c=
this.context;if(a===k)return c.length&&this.length?c[0].aoData[this[0]]._details:k;if(!0===a)this.child.show();else if(!1===a)eb(this);else if(c.length&&this.length){var d=c[0],c=c[0].aoData[this[0]],e=[],f=function(a,b){if(h.isArray(a)||a instanceof h)for(var c=0,k=a.length;c<k;c++)f(a[c],b);else a.nodeName&&"tr"===a.nodeName.toLowerCase()?e.push(a):(c=h("<tr><td/></tr>").addClass(b),h("td",c).addClass(b).html(a)[0].colSpan=ba(d),e.push(c[0]))};f(a,b);c._details&&c._details.detach();c._details=h(e);
c._detailsShow&&c._details.insertAfter(c.nTr)}return this});p(["row().child.show()","row().child().show()"],function(){Wb(this,!0);return this});p(["row().child.hide()","row().child().hide()"],function(){Wb(this,!1);return this});p(["row().child.remove()","row().child().remove()"],function(){eb(this);return this});p("row().child.isShown()",function(){var a=this.context;return a.length&&this.length?a[0].aoData[this[0]]._detailsShow||!1:!1});var fc=/^([^:]+):(name|visIdx|visible)$/,Xb=function(a,b,
c,d,e){for(var c=[],d=0,f=e.length;d<f;d++)c.push(B(a,e[d],b));return c};p("columns()",function(a,b){a===k?a="":h.isPlainObject(a)&&(b=a,a="");var b=cb(b),c=this.iterator("table",function(c){var e=a,f=b,g=c.aoColumns,j=D(g,"sName"),i=D(g,"nTh");return bb("column",e,function(a){var b=Qb(a);if(a==="")return X(g.length);if(b!==null)return[b>=0?b:g.length+b];if(typeof a==="function"){var e=Da(c,f);return h.map(g,function(b,f){return a(f,Xb(c,f,0,0,e),i[f])?f:null})}var k=typeof a==="string"?a.match(fc):
"";if(k)switch(k[2]){case "visIdx":case "visible":b=parseInt(k[1],10);if(b<0){var m=h.map(g,function(a,b){return a.bVisible?b:null});return[m[m.length+b]]}return[$(c,b)];case "name":return h.map(j,function(a,b){return a===k[1]?b:null});default:return[]}if(a.nodeName&&a._DT_CellIndex)return[a._DT_CellIndex.column];b=h(i).filter(a).map(function(){return h.inArray(this,i)}).toArray();if(b.length||!a.nodeName)return b;b=h(a).closest("*[data-dt-column]");return b.length?[b.data("dt-column")]:[]},c,f)},
1);c.selector.cols=a;c.selector.opts=b;return c});t("columns().header()","column().header()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].nTh},1)});t("columns().footer()","column().footer()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].nTf},1)});t("columns().data()","column().data()",function(){return this.iterator("column-rows",Xb,1)});t("columns().dataSrc()","column().dataSrc()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].mData},
1)});t("columns().cache()","column().cache()",function(a){return this.iterator("column-rows",function(b,c,d,e,f){return ja(b.aoData,f,"search"===a?"_aFilterData":"_aSortData",c)},1)});t("columns().nodes()","column().nodes()",function(){return this.iterator("column-rows",function(a,b,c,d,e){return ja(a.aoData,e,"anCells",b)},1)});t("columns().visible()","column().visible()",function(a,b){var c=this.iterator("column",function(b,c){if(a===k)return b.aoColumns[c].bVisible;var f=b.aoColumns,g=f[c],j=b.aoData,
i,n,l;if(a!==k&&g.bVisible!==a){if(a){var m=h.inArray(!0,D(f,"bVisible"),c+1);i=0;for(n=j.length;i<n;i++)l=j[i].nTr,f=j[i].anCells,l&&l.insertBefore(f[c],f[m]||null)}else h(D(b.aoData,"anCells",c)).detach();g.bVisible=a;fa(b,b.aoHeader);fa(b,b.aoFooter);za(b)}});a!==k&&(this.iterator("column",function(c,e){s(c,null,"column-visibility",[c,e,a,b])}),(b===k||b)&&this.columns.adjust());return c});t("columns().indexes()","column().index()",function(a){return this.iterator("column",function(b,c){return"visible"===
a?aa(b,c):c},1)});p("columns.adjust()",function(){return this.iterator("table",function(a){Z(a)},1)});p("column.index()",function(a,b){if(0!==this.context.length){var c=this.context[0];if("fromVisible"===a||"toData"===a)return $(c,b);if("fromData"===a||"toVisible"===a)return aa(c,b)}});p("column()",function(a,b){return db(this.columns(a,b))});p("cells()",function(a,b,c){h.isPlainObject(a)&&(a.row===k?(c=a,a=null):(c=b,b=null));h.isPlainObject(b)&&(c=b,b=null);if(null===b||b===k)return this.iterator("table",
function(b){var d=a,e=cb(c),f=b.aoData,g=Da(b,e),i=Tb(ja(f,g,"anCells")),j=h([].concat.apply([],i)),l,n=b.aoColumns.length,m,p,t,u,s,v;return bb("cell",d,function(a){var c=typeof a==="function";if(a===null||a===k||c){m=[];p=0;for(t=g.length;p<t;p++){l=g[p];for(u=0;u<n;u++){s={row:l,column:u};if(c){v=f[l];a(s,B(b,l,u),v.anCells?v.anCells[u]:null)&&m.push(s)}else m.push(s)}}return m}if(h.isPlainObject(a))return[a];c=j.filter(a).map(function(a,b){return{row:b._DT_CellIndex.row,column:b._DT_CellIndex.column}}).toArray();
if(c.length||!a.nodeName)return c;v=h(a).closest("*[data-dt-row]");return v.length?[{row:v.data("dt-row"),column:v.data("dt-column")}]:[]},b,e)});var d=this.columns(b,c),e=this.rows(a,c),f,g,j,i,n,l=this.iterator("table",function(a,b){f=[];g=0;for(j=e[b].length;g<j;g++){i=0;for(n=d[b].length;i<n;i++)f.push({row:e[b][g],column:d[b][i]})}return f},1);h.extend(l.selector,{cols:b,rows:a,opts:c});return l});t("cells().nodes()","cell().node()",function(){return this.iterator("cell",function(a,b,c){return(a=
a.aoData[b])&&a.anCells?a.anCells[c]:k},1)});p("cells().data()",function(){return this.iterator("cell",function(a,b,c){return B(a,b,c)},1)});t("cells().cache()","cell().cache()",function(a){a="search"===a?"_aFilterData":"_aSortData";return this.iterator("cell",function(b,c,d){return b.aoData[c][a][d]},1)});t("cells().render()","cell().render()",function(a){return this.iterator("cell",function(b,c,d){return B(b,c,d,a)},1)});t("cells().indexes()","cell().index()",function(){return this.iterator("cell",
function(a,b,c){return{row:b,column:c,columnVisible:aa(a,c)}},1)});t("cells().invalidate()","cell().invalidate()",function(a){return this.iterator("cell",function(b,c,d){da(b,c,a,d)})});p("cell()",function(a,b,c){return db(this.cells(a,b,c))});p("cell().data()",function(a){var b=this.context,c=this[0];if(a===k)return b.length&&c.length?B(b[0],c[0].row,c[0].column):k;lb(b[0],c[0].row,c[0].column,a);da(b[0],c[0].row,"data",c[0].column);return this});p("order()",function(a,b){var c=this.context;if(a===
k)return 0!==c.length?c[0].aaSorting:k;"number"===typeof a?a=[[a,b]]:a.length&&!h.isArray(a[0])&&(a=Array.prototype.slice.call(arguments));return this.iterator("table",function(b){b.aaSorting=a.slice()})});p("order.listener()",function(a,b,c){return this.iterator("table",function(d){Oa(d,a,b,c)})});p("order.fixed()",function(a){if(!a){var b=this.context,b=b.length?b[0].aaSortingFixed:k;return h.isArray(b)?{pre:b}:b}return this.iterator("table",function(b){b.aaSortingFixed=h.extend(!0,{},a)})});p(["columns().order()",
"column().order()"],function(a){var b=this;return this.iterator("table",function(c,d){var e=[];h.each(b[d],function(b,c){e.push([c,a])});c.aaSorting=e})});p("search()",function(a,b,c,d){var e=this.context;return a===k?0!==e.length?e[0].oPreviousSearch.sSearch:k:this.iterator("table",function(e){e.oFeatures.bFilter&&ga(e,h.extend({},e.oPreviousSearch,{sSearch:a+"",bRegex:null===b?!1:b,bSmart:null===c?!0:c,bCaseInsensitive:null===d?!0:d}),1)})});t("columns().search()","column().search()",function(a,
b,c,d){return this.iterator("column",function(e,f){var g=e.aoPreSearchCols;if(a===k)return g[f].sSearch;e.oFeatures.bFilter&&(h.extend(g[f],{sSearch:a+"",bRegex:null===b?!1:b,bSmart:null===c?!0:c,bCaseInsensitive:null===d?!0:d}),ga(e,e.oPreviousSearch,1))})});p("state()",function(){return this.context.length?this.context[0].oSavedState:null});p("state.clear()",function(){return this.iterator("table",function(a){a.fnStateSaveCallback.call(a.oInstance,a,{})})});p("state.loaded()",function(){return this.context.length?
this.context[0].oLoadedState:null});p("state.save()",function(){return this.iterator("table",function(a){za(a)})});m.versionCheck=m.fnVersionCheck=function(a){for(var b=m.version.split("."),a=a.split("."),c,d,e=0,f=a.length;e<f;e++)if(c=parseInt(b[e],10)||0,d=parseInt(a[e],10)||0,c!==d)return c>d;return!0};m.isDataTable=m.fnIsDataTable=function(a){var b=h(a).get(0),c=!1;if(a instanceof m.Api)return!0;h.each(m.settings,function(a,e){var f=e.nScrollHead?h("table",e.nScrollHead)[0]:null,g=e.nScrollFoot?
h("table",e.nScrollFoot)[0]:null;if(e.nTable===b||f===b||g===b)c=!0});return c};m.tables=m.fnTables=function(a){var b=!1;h.isPlainObject(a)&&(b=a.api,a=a.visible);var c=h.map(m.settings,function(b){if(!a||a&&h(b.nTable).is(":visible"))return b.nTable});return b?new u(c):c};m.camelToHungarian=J;p("$()",function(a,b){var c=this.rows(b).nodes(),c=h(c);return h([].concat(c.filter(a).toArray(),c.find(a).toArray()))});h.each(["on","one","off"],function(a,b){p(b+"()",function(){var a=Array.prototype.slice.call(arguments);
a[0]=h.map(a[0].split(/\s/),function(a){return!a.match(/\.dt\b/)?a+".dt":a}).join(" ");var d=h(this.tables().nodes());d[b].apply(d,a);return this})});p("clear()",function(){return this.iterator("table",function(a){pa(a)})});p("settings()",function(){return new u(this.context,this.context)});p("init()",function(){var a=this.context;return a.length?a[0].oInit:null});p("data()",function(){return this.iterator("table",function(a){return D(a.aoData,"_aData")}).flatten()});p("destroy()",function(a){a=a||
!1;return this.iterator("table",function(b){var c=b.nTableWrapper.parentNode,d=b.oClasses,e=b.nTable,f=b.nTBody,g=b.nTHead,j=b.nTFoot,i=h(e),f=h(f),k=h(b.nTableWrapper),l=h.map(b.aoData,function(a){return a.nTr}),p;b.bDestroying=!0;s(b,"aoDestroyCallback","destroy",[b]);a||(new u(b)).columns().visible(!0);k.off(".DT").find(":not(tbody *)").off(".DT");h(E).off(".DT-"+b.sInstance);e!=g.parentNode&&(i.children("thead").detach(),i.append(g));j&&e!=j.parentNode&&(i.children("tfoot").detach(),i.append(j));
b.aaSorting=[];b.aaSortingFixed=[];ya(b);h(l).removeClass(b.asStripeClasses.join(" "));h("th, td",g).removeClass(d.sSortable+" "+d.sSortableAsc+" "+d.sSortableDesc+" "+d.sSortableNone);b.bJUI&&(h("th span."+d.sSortIcon+", td span."+d.sSortIcon,g).detach(),h("th, td",g).each(function(){var a=h("div."+d.sSortJUIWrapper,this);h(this).append(a.contents());a.detach()}));f.children().detach();f.append(l);g=a?"remove":"detach";i[g]();k[g]();!a&&c&&(c.insertBefore(e,b.nTableReinsertBefore),i.css("width",
b.sDestroyWidth).removeClass(d.sTable),(p=b.asDestroyStripes.length)&&f.children().each(function(a){h(this).addClass(b.asDestroyStripes[a%p])}));c=h.inArray(b,m.settings);-1!==c&&m.settings.splice(c,1)})});h.each(["column","row","cell"],function(a,b){p(b+"s().every()",function(a){var d=this.selector.opts,e=this;return this.iterator(b,function(f,g,h,i,m){a.call(e[b](g,"cell"===b?h:d,"cell"===b?d:k),g,h,i,m)})})});p("i18n()",function(a,b,c){var d=this.context[0],a=R(a)(d.oLanguage);a===k&&(a=b);c!==
k&&h.isPlainObject(a)&&(a=a[c]!==k?a[c]:a._);return a.replace("%d",c)});m.version="1.10.13";m.settings=[];m.models={};m.models.oSearch={bCaseInsensitive:!0,sSearch:"",bRegex:!1,bSmart:!0};m.models.oRow={nTr:null,anCells:null,_aData:[],_aSortData:null,_aFilterData:null,_sFilterRow:null,_sRowStripe:"",src:null,idx:-1};m.models.oColumn={idx:null,aDataSort:null,asSorting:null,bSearchable:null,bSortable:null,bVisible:null,_sManualType:null,_bAttrSrc:!1,fnCreatedCell:null,fnGetData:null,fnSetData:null,
mData:null,mRender:null,nTh:null,nTf:null,sClass:null,sContentPadding:null,sDefaultContent:null,sName:null,sSortDataType:"std",sSortingClass:null,sSortingClassJUI:null,sTitle:null,sType:null,sWidth:null,sWidthOrig:null};m.defaults={aaData:null,aaSorting:[[0,"asc"]],aaSortingFixed:[],ajax:null,aLengthMenu:[10,25,50,100],aoColumns:null,aoColumnDefs:null,aoSearchCols:[],asStripeClasses:null,bAutoWidth:!0,bDeferRender:!1,bDestroy:!1,bFilter:!0,bInfo:!0,bJQueryUI:!1,bLengthChange:!0,bPaginate:!0,bProcessing:!1,
bRetrieve:!1,bScrollCollapse:!1,bServerSide:!1,bSort:!0,bSortMulti:!0,bSortCellsTop:!1,bSortClasses:!0,bStateSave:!1,fnCreatedRow:null,fnDrawCallback:null,fnFooterCallback:null,fnFormatNumber:function(a){return a.toString().replace(/\B(?=(\d{3})+(?!\d))/g,this.oLanguage.sThousands)},fnHeaderCallback:null,fnInfoCallback:null,fnInitComplete:null,fnPreDrawCallback:null,fnRowCallback:null,fnServerData:null,fnServerParams:null,fnStateLoadCallback:function(a){try{return JSON.parse((-1===a.iStateDuration?
sessionStorage:localStorage).getItem("DataTables_"+a.sInstance+"_"+location.pathname))}catch(b){}},fnStateLoadParams:null,fnStateLoaded:null,fnStateSaveCallback:function(a,b){try{(-1===a.iStateDuration?sessionStorage:localStorage).setItem("DataTables_"+a.sInstance+"_"+location.pathname,JSON.stringify(b))}catch(c){}},fnStateSaveParams:null,iStateDuration:7200,iDeferLoading:null,iDisplayLength:10,iDisplayStart:0,iTabIndex:0,oClasses:{},oLanguage:{oAria:{sSortAscending:": activate to sort column ascending",
sSortDescending:": activate to sort column descending"},oPaginate:{sFirst:"First",sLast:"Last",sNext:"Next",sPrevious:"Previous"},sEmptyTable:"No data available in table",sInfo:"Showing _START_ to _END_ of _TOTAL_ entries",sInfoEmpty:"Showing 0 to 0 of 0 entries",sInfoFiltered:"(filtered from _MAX_ total entries)",sInfoPostFix:"",sDecimal:"",sThousands:",",sLengthMenu:"Show _MENU_ entries",sLoadingRecords:"Loading...",sProcessing:"Processing...",sSearch:"Search:",sSearchPlaceholder:"",sUrl:"",sZeroRecords:"No matching records found"},
oSearch:h.extend({},m.models.oSearch),sAjaxDataProp:"data",sAjaxSource:null,sDom:"lfrtip",searchDelay:null,sPaginationType:"simple_numbers",sScrollX:"",sScrollXInner:"",sScrollY:"",sServerMethod:"GET",renderer:null,rowId:"DT_RowId"};Y(m.defaults);m.defaults.column={aDataSort:null,iDataSort:-1,asSorting:["asc","desc"],bSearchable:!0,bSortable:!0,bVisible:!0,fnCreatedCell:null,mData:null,mRender:null,sCellType:"td",sClass:"",sContentPadding:"",sDefaultContent:null,sName:"",sSortDataType:"std",sTitle:null,
sType:null,sWidth:null};Y(m.defaults.column);m.models.oSettings={oFeatures:{bAutoWidth:null,bDeferRender:null,bFilter:null,bInfo:null,bLengthChange:null,bPaginate:null,bProcessing:null,bServerSide:null,bSort:null,bSortMulti:null,bSortClasses:null,bStateSave:null},oScroll:{bCollapse:null,iBarWidth:0,sX:null,sXInner:null,sY:null},oLanguage:{fnInfoCallback:null},oBrowser:{bScrollOversize:!1,bScrollbarLeft:!1,bBounding:!1,barWidth:0},ajax:null,aanFeatures:[],aoData:[],aiDisplay:[],aiDisplayMaster:[],
aIds:{},aoColumns:[],aoHeader:[],aoFooter:[],oPreviousSearch:{},aoPreSearchCols:[],aaSorting:null,aaSortingFixed:[],asStripeClasses:null,asDestroyStripes:[],sDestroyWidth:0,aoRowCallback:[],aoHeaderCallback:[],aoFooterCallback:[],aoDrawCallback:[],aoRowCreatedCallback:[],aoPreDrawCallback:[],aoInitComplete:[],aoStateSaveParams:[],aoStateLoadParams:[],aoStateLoaded:[],sTableId:"",nTable:null,nTHead:null,nTFoot:null,nTBody:null,nTableWrapper:null,bDeferLoading:!1,bInitialised:!1,aoOpenRows:[],sDom:null,
searchDelay:null,sPaginationType:"two_button",iStateDuration:0,aoStateSave:[],aoStateLoad:[],oSavedState:null,oLoadedState:null,sAjaxSource:null,sAjaxDataProp:null,bAjaxDataGet:!0,jqXHR:null,json:k,oAjaxData:k,fnServerData:null,aoServerParams:[],sServerMethod:null,fnFormatNumber:null,aLengthMenu:null,iDraw:0,bDrawing:!1,iDrawError:-1,_iDisplayLength:10,_iDisplayStart:0,_iRecordsTotal:0,_iRecordsDisplay:0,bJUI:null,oClasses:{},bFiltered:!1,bSorted:!1,bSortCellsTop:null,oInit:null,aoDestroyCallback:[],
fnRecordsTotal:function(){return"ssp"==y(this)?1*this._iRecordsTotal:this.aiDisplayMaster.length},fnRecordsDisplay:function(){return"ssp"==y(this)?1*this._iRecordsDisplay:this.aiDisplay.length},fnDisplayEnd:function(){var a=this._iDisplayLength,b=this._iDisplayStart,c=b+a,d=this.aiDisplay.length,e=this.oFeatures,f=e.bPaginate;return e.bServerSide?!1===f||-1===a?b+d:Math.min(b+a,this._iRecordsDisplay):!f||c>d||-1===a?d:c},oInstance:null,sInstance:null,iTabIndex:0,nScrollHead:null,nScrollFoot:null,
aLastSort:[],oPlugins:{},rowIdFn:null,rowId:null};m.ext=x={buttons:{},classes:{},builder:"-source-",errMode:"alert",feature:[],search:[],selector:{cell:[],column:[],row:[]},internal:{},legacy:{ajax:null},pager:{},renderer:{pageButton:{},header:{}},order:{},type:{detect:[],search:{},order:{}},_unique:0,fnVersionCheck:m.fnVersionCheck,iApiIndex:0,oJUIClasses:{},sVersion:m.version};h.extend(x,{afnFiltering:x.search,aTypes:x.type.detect,ofnSearch:x.type.search,oSort:x.type.order,afnSortData:x.order,aoFeatures:x.feature,
oApi:x.internal,oStdClasses:x.classes,oPagination:x.pager});h.extend(m.ext.classes,{sTable:"dataTable",sNoFooter:"no-footer",sPageButton:"paginate_button",sPageButtonActive:"current",sPageButtonDisabled:"disabled",sStripeOdd:"odd",sStripeEven:"even",sRowEmpty:"dataTables_empty",sWrapper:"dataTables_wrapper",sFilter:"dataTables_filter",sInfo:"dataTables_info",sPaging:"dataTables_paginate paging_",sLength:"dataTables_length",sProcessing:"dataTables_processing",sSortAsc:"sorting_asc",sSortDesc:"sorting_desc",
sSortable:"sorting",sSortableAsc:"sorting_asc_disabled",sSortableDesc:"sorting_desc_disabled",sSortableNone:"sorting_disabled",sSortColumn:"sorting_",sFilterInput:"",sLengthSelect:"",sScrollWrapper:"dataTables_scroll",sScrollHead:"dataTables_scrollHead",sScrollHeadInner:"dataTables_scrollHeadInner",sScrollBody:"dataTables_scrollBody",sScrollFoot:"dataTables_scrollFoot",sScrollFootInner:"dataTables_scrollFootInner",sHeaderTH:"",sFooterTH:"",sSortJUIAsc:"",sSortJUIDesc:"",sSortJUI:"",sSortJUIAscAllowed:"",
sSortJUIDescAllowed:"",sSortJUIWrapper:"",sSortIcon:"",sJUIHeader:"",sJUIFooter:""});var Ea="",Ea="",G=Ea+"ui-state-default",ka=Ea+"css_right ui-icon ui-icon-",Yb=Ea+"fg-toolbar ui-toolbar ui-widget-header ui-helper-clearfix";h.extend(m.ext.oJUIClasses,m.ext.classes,{sPageButton:"fg-button ui-button "+G,sPageButtonActive:"ui-state-disabled",sPageButtonDisabled:"ui-state-disabled",sPaging:"dataTables_paginate fg-buttonset ui-buttonset fg-buttonset-multi ui-buttonset-multi paging_",sSortAsc:G+" sorting_asc",
sSortDesc:G+" sorting_desc",sSortable:G+" sorting",sSortableAsc:G+" sorting_asc_disabled",sSortableDesc:G+" sorting_desc_disabled",sSortableNone:G+" sorting_disabled",sSortJUIAsc:ka+"triangle-1-n",sSortJUIDesc:ka+"triangle-1-s",sSortJUI:ka+"carat-2-n-s",sSortJUIAscAllowed:ka+"carat-1-n",sSortJUIDescAllowed:ka+"carat-1-s",sSortJUIWrapper:"DataTables_sort_wrapper",sSortIcon:"DataTables_sort_icon",sScrollHead:"dataTables_scrollHead "+G,sScrollFoot:"dataTables_scrollFoot "+G,sHeaderTH:G,sFooterTH:G,sJUIHeader:Yb+
" ui-corner-tl ui-corner-tr",sJUIFooter:Yb+" ui-corner-bl ui-corner-br"});var Nb=m.ext.pager;h.extend(Nb,{simple:function(){return["previous","next"]},full:function(){return["first","previous","next","last"]},numbers:function(a,b){return[ia(a,b)]},simple_numbers:function(a,b){return["previous",ia(a,b),"next"]},full_numbers:function(a,b){return["first","previous",ia(a,b),"next","last"]},first_last_numbers:function(a,b){return["first",ia(a,b),"last"]},_numbers:ia,numbers_length:7});h.extend(!0,m.ext.renderer,
{pageButton:{_:function(a,b,c,d,e,f){var g=a.oClasses,j=a.oLanguage.oPaginate,i=a.oLanguage.oAria.paginate||{},m,l,p=0,r=function(b,d){var k,t,u,s,v=function(b){Va(a,b.data.action,true)};k=0;for(t=d.length;k<t;k++){s=d[k];if(h.isArray(s)){u=h("<"+(s.DT_el||"div")+"/>").appendTo(b);r(u,s)}else{m=null;l="";switch(s){case "ellipsis":b.append('<span class="ellipsis">&#x2026;</span>');break;case "first":m=j.sFirst;l=s+(e>0?"":" "+g.sPageButtonDisabled);break;case "previous":m=j.sPrevious;l=s+(e>0?"":" "+
g.sPageButtonDisabled);break;case "next":m=j.sNext;l=s+(e<f-1?"":" "+g.sPageButtonDisabled);break;case "last":m=j.sLast;l=s+(e<f-1?"":" "+g.sPageButtonDisabled);break;default:m=s+1;l=e===s?g.sPageButtonActive:""}if(m!==null){u=h("<a>",{"class":g.sPageButton+" "+l,"aria-controls":a.sTableId,"aria-label":i[s],"data-dt-idx":p,tabindex:a.iTabIndex,id:c===0&&typeof s==="string"?a.sTableId+"_"+s:null}).html(m).appendTo(b);Ya(u,{action:s},v);p++}}}},t;try{t=h(b).find(H.activeElement).data("dt-idx")}catch(u){}r(h(b).empty(),
d);t!==k&&h(b).find("[data-dt-idx="+t+"]").focus()}}});h.extend(m.ext.type.detect,[function(a,b){var c=b.oLanguage.sDecimal;return ab(a,c)?"num"+c:null},function(a){if(a&&!(a instanceof Date)&&!cc.test(a))return null;var b=Date.parse(a);return null!==b&&!isNaN(b)||M(a)?"date":null},function(a,b){var c=b.oLanguage.sDecimal;return ab(a,c,!0)?"num-fmt"+c:null},function(a,b){var c=b.oLanguage.sDecimal;return Sb(a,c)?"html-num"+c:null},function(a,b){var c=b.oLanguage.sDecimal;return Sb(a,c,!0)?"html-num-fmt"+
c:null},function(a){return M(a)||"string"===typeof a&&-1!==a.indexOf("<")?"html":null}]);h.extend(m.ext.type.search,{html:function(a){return M(a)?a:"string"===typeof a?a.replace(Pb," ").replace(Ca,""):""},string:function(a){return M(a)?a:"string"===typeof a?a.replace(Pb," "):a}});var Ba=function(a,b,c,d){if(0!==a&&(!a||"-"===a))return-Infinity;b&&(a=Rb(a,b));a.replace&&(c&&(a=a.replace(c,"")),d&&(a=a.replace(d,"")));return 1*a};h.extend(x.type.order,{"date-pre":function(a){return Date.parse(a)||-Infinity},
"html-pre":function(a){return M(a)?"":a.replace?a.replace(/<.*?>/g,"").toLowerCase():a+""},"string-pre":function(a){return M(a)?"":"string"===typeof a?a.toLowerCase():!a.toString?"":a.toString()},"string-asc":function(a,b){return a<b?-1:a>b?1:0},"string-desc":function(a,b){return a<b?1:a>b?-1:0}});fb("");h.extend(!0,m.ext.renderer,{header:{_:function(a,b,c,d){h(a.nTable).on("order.dt.DT",function(e,f,g,h){if(a===f){e=c.idx;b.removeClass(c.sSortingClass+" "+d.sSortAsc+" "+d.sSortDesc).addClass(h[e]==
"asc"?d.sSortAsc:h[e]=="desc"?d.sSortDesc:c.sSortingClass)}})},jqueryui:function(a,b,c,d){h("<div/>").addClass(d.sSortJUIWrapper).append(b.contents()).append(h("<span/>").addClass(d.sSortIcon+" "+c.sSortingClassJUI)).appendTo(b);h(a.nTable).on("order.dt.DT",function(e,f,g,h){if(a===f){e=c.idx;b.removeClass(d.sSortAsc+" "+d.sSortDesc).addClass(h[e]=="asc"?d.sSortAsc:h[e]=="desc"?d.sSortDesc:c.sSortingClass);b.find("span."+d.sSortIcon).removeClass(d.sSortJUIAsc+" "+d.sSortJUIDesc+" "+d.sSortJUI+" "+
d.sSortJUIAscAllowed+" "+d.sSortJUIDescAllowed).addClass(h[e]=="asc"?d.sSortJUIAsc:h[e]=="desc"?d.sSortJUIDesc:c.sSortingClassJUI)}})}}});var Zb=function(a){return"string"===typeof a?a.replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;"):a};m.render={number:function(a,b,c,d,e){return{display:function(f){if("number"!==typeof f&&"string"!==typeof f)return f;var g=0>f?"-":"",h=parseFloat(f);if(isNaN(h))return Zb(f);h=h.toFixed(c);f=Math.abs(h);h=parseInt(f,10);f=c?b+(f-h).toFixed(c).substring(2):
"";return g+(d||"")+h.toString().replace(/\B(?=(\d{3})+(?!\d))/g,a)+f+(e||"")}}},text:function(){return{display:Zb}}};h.extend(m.ext.internal,{_fnExternApiFunc:Ob,_fnBuildAjax:ua,_fnAjaxUpdate:nb,_fnAjaxParameters:wb,_fnAjaxUpdateDraw:xb,_fnAjaxDataSrc:va,_fnAddColumn:Ga,_fnColumnOptions:la,_fnAdjustColumnSizing:Z,_fnVisibleToColumnIndex:$,_fnColumnIndexToVisible:aa,_fnVisbleColumns:ba,_fnGetColumns:na,_fnColumnTypes:Ia,_fnApplyColumnDefs:kb,_fnHungarianMap:Y,_fnCamelToHungarian:J,_fnLanguageCompat:Fa,
_fnBrowserDetect:ib,_fnAddData:N,_fnAddTr:oa,_fnNodeToDataIndex:function(a,b){return b._DT_RowIndex!==k?b._DT_RowIndex:null},_fnNodeToColumnIndex:function(a,b,c){return h.inArray(c,a.aoData[b].anCells)},_fnGetCellData:B,_fnSetCellData:lb,_fnSplitObjNotation:La,_fnGetObjectDataFn:R,_fnSetObjectDataFn:S,_fnGetDataMaster:Ma,_fnClearTable:pa,_fnDeleteIndex:qa,_fnInvalidate:da,_fnGetRowElements:Ka,_fnCreateTr:Ja,_fnBuildHead:mb,_fnDrawHead:fa,_fnDraw:O,_fnReDraw:T,_fnAddOptionsHtml:pb,_fnDetectHeader:ea,
_fnGetUniqueThs:ta,_fnFeatureHtmlFilter:rb,_fnFilterComplete:ga,_fnFilterCustom:Ab,_fnFilterColumn:zb,_fnFilter:yb,_fnFilterCreateSearch:Ra,_fnEscapeRegex:Sa,_fnFilterData:Bb,_fnFeatureHtmlInfo:ub,_fnUpdateInfo:Eb,_fnInfoMacros:Fb,_fnInitialise:ha,_fnInitComplete:wa,_fnLengthChange:Ta,_fnFeatureHtmlLength:qb,_fnFeatureHtmlPaginate:vb,_fnPageChange:Va,_fnFeatureHtmlProcessing:sb,_fnProcessingDisplay:C,_fnFeatureHtmlTable:tb,_fnScrollDraw:ma,_fnApplyToChildren:I,_fnCalculateColumnWidths:Ha,_fnThrottle:Qa,
_fnConvertToWidth:Gb,_fnGetWidestNode:Hb,_fnGetMaxLenString:Ib,_fnStringToCss:v,_fnSortFlatten:W,_fnSort:ob,_fnSortAria:Kb,_fnSortListener:Xa,_fnSortAttachListener:Oa,_fnSortingClasses:ya,_fnSortData:Jb,_fnSaveState:za,_fnLoadState:Lb,_fnSettingsFromNode:Aa,_fnLog:K,_fnMap:F,_fnBindAction:Ya,_fnCallbackReg:z,_fnCallbackFire:s,_fnLengthOverflow:Ua,_fnRenderer:Pa,_fnDataSource:y,_fnRowAttributes:Na,_fnCalculateEnd:function(){}});h.fn.dataTable=m;m.$=h;h.fn.dataTableSettings=m.settings;h.fn.dataTableExt=
m.ext;h.fn.DataTable=function(a){return h(this).dataTable(a).api()};h.each(m,function(a,b){h.fn.DataTable[a]=b});return h.fn.dataTable});

View File

@@ -1,12 +0,0 @@
/*
* jquery-match-height 0.7.0 by @liabru
* http://brm.io/jquery-match-height/
* License MIT
*/
!function(t){"use strict";"function"==typeof define&&define.amd?define(["jquery"],t):"undefined"!=typeof module&&module.exports?module.exports=t(require("jquery")):t(jQuery)}(function(t){var e=-1,o=-1,i=function(t){return parseFloat(t)||0},a=function(e){var o=1,a=t(e),n=null,r=[];return a.each(function(){var e=t(this),a=e.offset().top-i(e.css("margin-top")),s=r.length>0?r[r.length-1]:null;null===s?r.push(e):Math.floor(Math.abs(n-a))<=o?r[r.length-1]=s.add(e):r.push(e),n=a}),r},n=function(e){var o={
byRow:!0,property:"height",target:null,remove:!1};return"object"==typeof e?t.extend(o,e):("boolean"==typeof e?o.byRow=e:"remove"===e&&(o.remove=!0),o)},r=t.fn.matchHeight=function(e){var o=n(e);if(o.remove){var i=this;return this.css(o.property,""),t.each(r._groups,function(t,e){e.elements=e.elements.not(i)}),this}return this.length<=1&&!o.target?this:(r._groups.push({elements:this,options:o}),r._apply(this,o),this)};r.version="0.7.0",r._groups=[],r._throttle=80,r._maintainScroll=!1,r._beforeUpdate=null,
r._afterUpdate=null,r._rows=a,r._parse=i,r._parseOptions=n,r._apply=function(e,o){var s=n(o),h=t(e),l=[h],c=t(window).scrollTop(),p=t("html").outerHeight(!0),d=h.parents().filter(":hidden");return d.each(function(){var e=t(this);e.data("style-cache",e.attr("style"))}),d.css("display","block"),s.byRow&&!s.target&&(h.each(function(){var e=t(this),o=e.css("display");"inline-block"!==o&&"flex"!==o&&"inline-flex"!==o&&(o="block"),e.data("style-cache",e.attr("style")),e.css({display:o,"padding-top":"0",
"padding-bottom":"0","margin-top":"0","margin-bottom":"0","border-top-width":"0","border-bottom-width":"0",height:"100px",overflow:"hidden"})}),l=a(h),h.each(function(){var e=t(this);e.attr("style",e.data("style-cache")||"")})),t.each(l,function(e,o){var a=t(o),n=0;if(s.target)n=s.target.outerHeight(!1);else{if(s.byRow&&a.length<=1)return void a.css(s.property,"");a.each(function(){var e=t(this),o=e.attr("style"),i=e.css("display");"inline-block"!==i&&"flex"!==i&&"inline-flex"!==i&&(i="block");var a={
display:i};a[s.property]="",e.css(a),e.outerHeight(!1)>n&&(n=e.outerHeight(!1)),o?e.attr("style",o):e.css("display","")})}a.each(function(){var e=t(this),o=0;s.target&&e.is(s.target)||("border-box"!==e.css("box-sizing")&&(o+=i(e.css("border-top-width"))+i(e.css("border-bottom-width")),o+=i(e.css("padding-top"))+i(e.css("padding-bottom"))),e.css(s.property,n-o+"px"))})}),d.each(function(){var e=t(this);e.attr("style",e.data("style-cache")||null)}),r._maintainScroll&&t(window).scrollTop(c/p*t("html").outerHeight(!0)),
this},r._applyDataApi=function(){var e={};t("[data-match-height], [data-mh]").each(function(){var o=t(this),i=o.attr("data-mh")||o.attr("data-match-height");i in e?e[i]=e[i].add(o):e[i]=o}),t.each(e,function(){this.matchHeight(!0)})};var s=function(e){r._beforeUpdate&&r._beforeUpdate(e,r._groups),t.each(r._groups,function(){r._apply(this.elements,this.options)}),r._afterUpdate&&r._afterUpdate(e,r._groups)};r._update=function(i,a){if(a&&"resize"===a.type){var n=t(window).width();if(n===e)return;e=n;
}i?-1===o&&(o=setTimeout(function(){s(a),o=-1},r._throttle)):s(a)},t(r._applyDataApi),t(window).bind("load",function(t){r._update(!1,t)}),t(window).bind("resize orientationchange",function(t){r._update(!0,t)})});

View File

@@ -1,37 +1,15 @@
{% extends "layout.html" %}
{% include "head.html" %}
{% block content %}
<!-- Header container -->
<body>
<div class="container-fluid">
<div class="row">
<div class="col-md-10 col-md-offset-1">
<h1><strong>Playbook</strong>: {{ macros.make_link('playbook.show_playbook', file_.playbook.path, playbook=file_.playbook.id) }}{% if file_.playbook.ansible_version %} with Ansible v{{ file_.playbook.ansible_version }}{% endif %}</h1>
<h2>{{ file_.playbook.time_start |datefmt }} <span class="fa fa-angle-double-right"></span> {{ file_.playbook.time_end |datefmt }}</h2>
<h1><strong>File</strong>: {{ file_.path }}</h1>
<div class="row">
<div class="col-md-12">
<h2>File: <strong>{{ file_.path }}</strong></h2>
<p>Last updated: {{ file_.playbook.time_end |datefmt }}</p>
<p>Ansible version: <strong>{{ file_.playbook.ansible_version }}</strong></p>
{{ file_.content.content | yamlhighlight | safe }}
</div>
</div>
</div>
</div>
<!-- Detail container -->
<div class="container-fluid">
<div class="row">
<div class="col-md-11 col-md-offset-1">
<h1><strong>Contents</strong></h1>
{{ file_.content.content | yamlhighlight | safe }}
</div>
</div>
</div>
<script>
$(document).ready(function(){
/* Highlight the anchor line */
var hash = $(location).attr('hash');
$(hash).closest('span').addClass('hll');
/* Refresh the highlighted line when clicking on a new line */
$("a").click(function(){
$("span.hll").removeClass('hll');
var hash = $(this).attr('href');
$(hash).closest('span').addClass('hll');
});
});
</script>
{% endblock %}
</body>
</html>

View File

@@ -0,0 +1,27 @@
{% include "head.html" %}
<!--
This template is not actually served or linked from within the web application.
-->
<body>
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<table class="table table-striped table-bordered table-hover table-condensed">
<thead>
<tr>
<th>File</th>
</tr>
</thead>
<tbody>
{% for file in files %}
<tr>
<td><a href="{{ url_for('file.show_file', file_=file.id) }}">{{ file.id }}</a></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</body>
</html>

26
ara/templates/head.html Normal file
View File

@@ -0,0 +1,26 @@
<!DOCTYPE html>
<html class="layout-pf">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="ARA | Ansible Run Analysis records Ansible playbook runs seamlessly to make them easier to visualize, understand and troubleshoot">
<title>ARA | Ansible Run Analysis</title>
<link rel="shortcut icon" href="{{ url_for('static', filename='images/favicon.ico') }}">
{% block scripts %}
<script src="{{ url_for('static', filename='js/jquery.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/jquery.dataTables.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/bootstrap.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/ara.js') }}"></script>
<script src="{{ url_for('static', filename='js/patternfly.min.js') }}"></script>
{% endblock %}
{% block stylesheets %}
<link href="{{ url_for('static', filename='css/patternfly.min.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='css/patternfly-additions.min.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='css/pygments.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='css/ara.css') }}" rel="stylesheet">
{% endblock %}
</head>

View File

@@ -1,49 +1,61 @@
{% extends "layout.html" %}
{% block content %}
<!-- Main container -->
<!-- Begin home -->
<div class="container-fluid">
<div class="row text-center">
<h1>ARA - Ansible Run Analysis</h1>
<h2><a href="https://github.com/openstack/ara">ARA</a> records <a href="https://www.ansible.com/">Ansible</a> Playbook runs seamlessly to make them easier to visualize, understand and troubleshoot.</h2>
</div>
<div class="row">
<div class="col-md-12">
<h2><strong>Latest playbook runs</strong></h2>
<table class="table table-striped table-bordered table-hover table-condensed">
<thead>
<tr>
<th></th>
<th>Playbook</th>
<th class="col-md-1">Hosts</th>
<th class="date col-md-2">Date</th>
<th class="date col-md-1">Duration</th>
<th class="col-md-1"><span class="ok label status-label">OK</span></th>
<th class="col-md-1"><span class="changed label status-label">CHANGED</span></th>
<th class="col-md-1"><span class="failed label status-label">FAILED</span></th>
<th class="col-md-1"><span class="skipped label status-label">SKIPPED</span></th>
<th class="col-md-1"><span class="unreachable label status-label">UNREACHABLE</span></th>
</tr>
</thead>
<tbody>
{% for playbook in playbooks %}
<tr>
<td class="vert-align">{{ macros.render_status(stats[playbook.id].status) }}</td>
<td>{{ macros.make_link('playbook.show_playbook', playbook.path|pathtruncate,
playbook=playbook.id) }}</td>
<td>{{ playbook.hosts|list|length }}</td>
<td>{{ playbook.time_start |datefmt }}</td>
<td>{{ playbook.duration |timefmt }}</td>
<td>{{ stats[playbook.id].ok }}</td>
<td>{{ stats[playbook.id].changed }}</td>
<td>{{ stats[playbook.id].failed }}</td>
<td>{{ stats[playbook.id].skipped }}</td>
<td>{{ stats[playbook.id].unreachable }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="col-md-custom col-md-offset-custom">
<div class="row text-center">
<br>
<img src="{{ url_for('static', filename='images/logo-main.svg') }}" width="287" height="226" alt="ARA: Ansible Run Analysis" />
<h1><strong>ARA - Ansible Run Analysis</strong></h1>
<h2><a href="https://github.com/openstack/ara" target="_blank">ARA</a> records <a href="https://www.ansible.com/" target="_blank">Ansible</a> playbook runs seamlessly to make them easier to visualize, understand and troubleshoot.</h2>
{% if playbooks and playbooks > 0 %}
<h2>Here's the data that ARA is making available to help you:</h2>
<br>
<ul class="list-group stat-highlight col-md-2 col-md-offset-5">
<li class="list-group-item">
<span class="badge">{{ playbooks }}</span>
Playbook runs
</li>
<li class="list-group-item">
<span class="badge">{{ tasks }}</span>
Tasks
</li>
<li class="list-group-item">
<span class="badge">{{ task_results }}</span>
Task results
</li>
<li class="list-group-item">
<span class="badge">{{ hosts }}</span>
Hosts
</li>
<li class="list-group-item">
<span class="badge">{{ host_facts }}</span>
Hosts with gathered facts
</li>
<li class="list-group-item">
<span class="badge">{{ files }}</span>
Playbook, role and task files
</li>
<li class="list-group-item">
<span class="badge">{{ records }}</span>
Records
</li>
</ul>
</div>
<div class="row text-center">
<h2>Get started by looking at your <a href="{{ url_for('reports.report_list') }}">playbook reports</a>.</h2>
{% else %}
<h2><strong>The ARA database is empty or the web application is not configured properly to find your playbook data.</strong></h2>
<h2>Get started by <a href="https://ara.readthedocs.io/en/latest/configuration.html">configuring Ansible to use ARA</a>.</h2>
<h2>Your data will be available in the interface as soon as you've ran an <code>ansible-playbook</code> at least once after configuring Ansible to use ARA.</h2>
<br>
<br>
<h3>Need help ? Look at the <a href="https://ara.readthedocs.io/en/latest/">documentation</a> or come chat with users and developers on IRC in #ara on the freenode network.</h3>
{% endif %}
</div>
</div>
</div>
</div>
{% endblock %}
<!-- End home -->
{% endblock %}

View File

@@ -1,60 +1,30 @@
{% extends "layout.html" %}
{% include "head.html" %}
{% block content %}
<!-- Header container -->
<body>
<div class="container-fluid">
<div class="row">
<div class="col-md-10 col-md-offset-1">
<h1><strong>Playbook</strong>: {{ macros.make_link('playbook.show_playbook', host.playbook.path, playbook=host.playbook.id) }}{% if host.playbook.ansible_version %} with Ansible v{{ host.playbook.ansible_version }}{% endif %}</h1>
<h2>{{ host.playbook.time_start |datefmt }} <span class="fa fa-angle-double-right"></span> {{ host.playbook.time_end |datefmt }}</h2>
<h1><strong>Host</strong>: {{ host.name }}</h1>
<div class="row">
<div class="col-md-12">
<h2>Host facts: <strong>{{ host.name }}</strong></h2>
<p>Last updated: {{ host.facts.timestamp |datefmt }}</p>
<p>Ansible version: <strong>{{ host.playbook.ansible_version }}</strong></p>
<table class="table table-striped table-bordered table-hover table-condensed">
<thead>
<tr>
<th>Fact</th>
<th>Value</th>
</tr>
</thead>
<tbody>
{% for fact, value in facts %}
<tr>
<td id="{{ fact }}"><a href="#{{ fact }}">{{ fact }}</a></td>
<td><pre>{{ value | to_nice_json }}</pre></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="container-fluid">
<div class="row">
<div class="col-md-10 col-md-offset-1">
<h2><strong>Stats</strong></h2>
<table class="table table-striped table-bordered table-hover table-condensed">
<thead>
<tr>
<th><span class="ok label status-label">OK</span></th>
<th><span class="changed label status-label">CHANGED</span></th>
<th><span class="failed label status-label">FAILED</span></th>
<th><span class="skipped label status-label">SKIPPED</span></th>
<th><span class="unreachable label status-label">UNREACHABLE</span></th>
</tr>
</thead>
<tbody>
<tr>
{{ macros.statslink(host.stats, 'ok', host.playbook, host) }}
{{ macros.statslink(host.stats, 'changed', host.playbook, host) }}
{{ macros.statslink(host.stats, 'failed', host.playbook, host) }}
{{ macros.statslink(host.stats, 'skipped', host.playbook, host) }}
{{ macros.statslink(host.stats, 'unreachable', host.playbook, host) }}
</tr>
</tbody>
</table>
<h2><strong>Facts</strong></h2>
<p>Last updated at {{ host.facts.timestamp |datefmt }}</p>
<table class="table table-striped table-bordered table-hover table-condensed">
<thead>
<tr>
<th>Fact</th>
<th>Value</th>
</tr>
</thead>
<tbody>
{% for fact, value in facts %}
<tr>
<td id="{{ fact }}"><a href="#{{ fact }}">{{ fact }}</a></td>
<td><pre>{{ value | to_nice_json }}</pre></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endblock %}
</body>
</html>

View File

@@ -0,0 +1,29 @@
{% include "head.html" %}
<!--
This template is not actually served or linked from within the web application.
-->
<body>
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<table class="table table-striped table-bordered table-hover table-condensed">
<thead>
<tr>
<th>Host</th>
</tr>
</thead>
<tbody>
{% for host in hosts %}
{% if host.facts %}
<tr>
<td><a href="{{ url_for('host.show_host', id=host.id) }}">{{ host.id }}</a></td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</body>
</html>

View File

@@ -1,44 +1,10 @@
<html class="layout-pf">
{% include "head.html" %}
{% import "macros.html" as macros %}
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ARA | Ansible Run Analysis</title>
<meta name="description" content="ARA: Ansible Run Analysis records Ansible Playbook runs and provides intuitive interfaces to browse them.">
<link rel="shortcut icon" href="{{ url_for('static', filename='images/favicon.ico') }}">
{% block scripts %}
<script src="{{ url_for('static', filename='js/jquery.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/bootstrap.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/ara.js') }}"></script>
<script src="{{ url_for('static', filename='js/jquery.matchHeight-min.js') }}"></script>
<script src="{{ url_for('static', filename='js/patternfly.min.js') }}"></script>
{% endblock %}
{% block stylesheets %}
<link href="{{ url_for('static', filename='css/patternfly.min.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='css/patternfly-additions.min.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='css/pygments.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='css/ara.css') }}" rel="stylesheet">
{% endblock %}
</head>
<body>
{% include "navbar.html" %}
{% block content %}
{% endblock %}
<script>
$(document).ready(function() {
// matchHeight the contents of each .card-pf and then the .card-pf itself
$(".row-cards-pf > [class*='col'] > .card-pf .card-pf-title").matchHeight();
$(".row-cards-pf > [class*='col'] > .card-pf > .card-pf-body").matchHeight();
$(".row-cards-pf > [class*='col'] > .card-pf > .card-pf-footer").matchHeight();
$(".row-cards-pf > [class*='col'] > .card-pf").matchHeight();
// Initialize the vertical navigation
$().setupVerticalNavigation(true);
});
</script>
</body>
</html>
</html>

View File

@@ -1,45 +1,86 @@
<div id="macros">
{% macro make_link(view, label) -%}
<a href="{{ url_for(view, **kwargs) }}">{{label}}</a>
{%- endmacro %}
{% macro statslink(stats, stat, playbook, host) %}
<td>
{% if stats[stat] and stats[stat] >= 1 -%}
{{ make_link('playbook.playbook_results', stats[stat],
playbook=playbook.id, host=host.name, status=stat) }}
{% else -%}
0
{% endif %}
</td>
{% macro render_status(status) %}
{% if status == 'success' %}
<div data-toggle="tooltip" data-placement="bottom" title="Playbook completed successfully">
<span class="pficon pficon-ok list-view-pf-icon-sm list-view-pf-icon-success"></span>
</div>
{% elif status == 'failed' %}
<div data-toggle="tooltip" data-placement="bottom" title="Playbook completed with errors">
<span class="pficon pficon-error-circle-o list-view-pf-icon-sm list-view-pf-icon-danger"></span>
</div>
{% elif status == 'incomplete' %}
<div data-toggle="tooltip" data-placement="bottom" title="Playbook execution was interrupted: data will be incomplete and inconsistent">
<span class="pficon pficon-info list-view-pf-icon-sm list-view-pf-icon-info"></span>
</div>
{% else %}
<div data-toggle="tooltip" data-placement="bottom" title="The status of this playbook is unknown">
<span class="fa fa-question-circle"></span>
</div>
{% endif %}
{% endmacro %}
{% macro render_status(status) %}
{% if status == 'success' %}
<span class="pficon pficon-ok list-view-pf-icon-md list-view-pf-icon-success" title="Playbook finished successfully"></span>
{% elif status == 'failed' %}
<span class="pficon pficon-error-circle-o list-view-pf-icon-md list-view-pf-icon-danger" title="Playbook finished with errors"></span>
{% elif status == 'incomplete' %}
<span class="pficon pficon-info list-view-pf-icon-md list-view-pf-icon-info" title="Playbook was interrupted: data will be incomplete."></span>
{% else %}
<span class="fa fa-question-circle" title="The status of this playbook is unknown."></span>
{% endif %}
{% macro render_playbook_summary(playbook) %}
<dl class='dl-horizontal'>
<dt>Ansible version</dt>
<dd>{{ playbook.ansible_version }}</dd>
<dt>Path</dt>
<dd>{{ playbook.path }}</dd>
</dl>
{% endmacro %}
{% macro render_host_facts(host) %}
{% if host.facts.values %}
{% set facts = host.facts.values|from_json %}
{% set items = ['ansible_fqdn',
'ansible_processor_cores',
'ansible_processor_vcpus',
'ansible_memtotal_mb',
'ansible_swaptotal_mb',
'ansible_distribution',
'ansible_distribution_version'] %}
{% set lists = ['ansible_all_ipv4_addresses', 'ansible_all_ipv6_addresses'] %}
<dl class='dl-horizontal'>
{% for value in items %}
{% if facts[value] %}
<dt>{{ value }}</dt>
<dd>{{ facts[value] }}</dd>
{% endif %}
{% endfor %}
{% for value in lists %}
{% if facts[value] %}
<dt>{{ value }}</dt>
{% for item in facts[value] %}
<dd>{{ item }}</dd>
{% endfor %}
{% endif %}
{% endfor %}
</dl>
{% else %}
No host facts recorded for this host.
{% endif %}
{% endmacro %}
{% macro render_value_type(value, type) %}
{% if type == 'json' or type == 'dict' %}
<pre>{{ value |to_nice_json |safe }}</pre>
{% elif type == 'list' %}
<ul class="text-left">
{% for item in value %}
<li>{{ item }}</li>
{% endfor %}
</ul>
{% elif type == 'url' %}
<a href="{{ value }}" target="_blank">{{ value }}</a>
{% else %}
<pre>{{ value }}</pre>
{% endif %}
{% if type == 'json' or type == 'dict' %}
<pre>{{ value |to_nice_json |safe }}</pre>
{% elif type == 'list' %}
<ul class="text-left">
{% for item in value %}
<li>{{ item }}</li>
{% endfor %}
</ul>
{% elif type == 'url' %}
<a href="{{ value }}" target="_blank">{{ value }}</a>
{% else %}
<pre>{{ value }}</pre>
{% endif %}
{% endmacro %}
{% macro result_page_length(length) %}
{% if length == 0 %}
"paging": false,
{% else %}
"pageLength": {{ length }}
{% endif %}
{% endmacro %}
</div>

View File

@@ -1,24 +1,38 @@
<nav class="navbar navbar-default navbar-pf" role="navigation">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse-1">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="{{ url_for('home.main') }}">
<img src="{{ url_for('static', filename='images/logo.svg') }}" alt="ARA: Ansible Run Analysis" />
</a>
</div>
<div class="collapse navbar-collapse navbar-collapse-1">
<ul class="nav navbar-nav navbar-utility">
<li><a href="https://ara.readthedocs.io/en/latest/" target="_blank">Documentation</a></li>
<li><a href="https://github.com/openstack/ara" target="_blank"><strong>ARA</strong> {{ ara_version }}</a></li>
<li><a href="https://www.ansible.com/" target="_blank"><strong>Ansible</strong> {{ ansible_version }}</a></li>
</ul>
<ul class="nav navbar-nav navbar-primary persistent-secondary">
<li><a href="{{ url_for('home.main') }}">Home</a></li>
<li><a href="{{ url_for('playbook.playbook_summary') }}">All Playbooks</a></li>
</ul>
</div>
</nav>
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse-1">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="{{ url_for('home.main') }}">
<img src="{{ url_for('static', filename='images/logo-header.svg') }}" width="81" height="32" alt="ARA: Ansible Run Analysis" />
</a>
</div>
<div class="collapse navbar-collapse navbar-collapse-1">
<ul class="nav navbar-nav navbar-utility">
<li><a href="https://ara.readthedocs.io/en/latest/" target="_blank">Documentation</a></li>
<li><a href="https://github.com/openstack/ara" target="_blank"><strong>ARA</strong> {{ ara_version }}</a></li>
<li><a href="https://www.ansible.com/" target="_blank"><strong>Ansible</strong> {{ ansible_version }}</a></li>
</ul>
<ul class="nav navbar-nav navbar-primary">
{% if active and active == 'home' %}
<li class="active">
{% else %}
<li>
{% endif %}
<a href="{{ url_for('home.main') }}">Home</a>
</li>
{% if playbooks and playbooks > 0 %}
{% if active and active == 'reports' %}
<li class="active">
{% else %}
<li>
{% endif %}
<a href="{{ url_for('reports.report_list') }}">Playbook reports</a>
</li>
{% endif %}
</ul>
</div>
</nav>

View File

@@ -1,212 +0,0 @@
{% extends "layout.html" %}
{% block content %}
<!-- Header container -->
<div class="container-fluid">
<div class="row">
<div class="col-md-10 col-md-offset-1">
<h1><strong>Playbook</strong>: {{ macros.make_link('playbook.show_playbook', playbook.path, playbook=playbook.id) }}{% if playbook.ansible_version %} with Ansible v{{ playbook.ansible_version }}{% endif %}</h1>
<h2>{{ playbook.time_start |datefmt }} <span class="fa fa-angle-double-right"></span> {{ playbook.time_end |datefmt }}</h2>
</div>
</div>
</div>
<!-- Dashboard container -->
<div class="container-fluid container-cards-pf">
<div class="row row-cards-pf">
<div class="col-md-2 col-md-offset-1">
<div class="card-pf card-pf-accented card-pf-aggregate-status card-pf-aggregate-status-mini" style="height: 59px;">
<h2 class="card-pf-title" style="height: 42px;">
<a href="#hosts">
<span class="pficon pficon-server"></span>
<span class="card-pf-aggregate-status-count">{{ playbook.hosts.count() }}</span> <strong>Hosts</strong>
</a>
</h2>
</div>
</div>
<div class="col-md-2">
<div class="card-pf card-pf-accented card-pf-aggregate-status card-pf-aggregate-status-mini" style="height: 59px;">
<h2 class="card-pf-title" style="height: 42px;">
<a href="#plays">
<span class="pficon pficon-flag"></span>
<span class="card-pf-aggregate-status-count">{{ playbook.plays.count() }}</span> <strong>Plays</strong>
</a>
</h2>
</div>
</div>
<div class="col-md-2">
<div class="card-pf card-pf-accented card-pf-aggregate-status card-pf-aggregate-status-mini" style="height: 59px;">
<h2 class="card-pf-title" style="height: 42px;">
<a href="#files">
<span class="pficon pficon-folder-open"></span>
<span class="card-pf-aggregate-status-count">{{ playbook.files.count() }}</span> <strong>Files</strong>
</a>
</h2>
</div>
</div>
<div class="col-md-2">
<div class="card-pf card-pf-accented card-pf-aggregate-status card-pf-aggregate-status-mini" style="height: 59px;">
<h2 class="card-pf-title" style="height: 42px;">
<a href="#tasks">
<span class="fa fa-check"></span>
<span class="card-pf-aggregate-status-count">{{ playbook.tasks.count() }}</span> <strong>Tasks</strong>
</a>
</h2>
</div>
</div>
<div class="col-md-2">
<div class="card-pf card-pf-accented card-pf-aggregate-status card-pf-aggregate-status-mini" style="height: 59px;">
<h2 class="card-pf-title" style="height: 42px;">
<span class="fa fa-clock-o"></span>
<span class="card-pf-aggregate-status-count">{{ playbook.duration |timefmt }}</span> <strong>Duration</strong>
</h2>
</div>
</div>
</div>
</div>
<!-- Detail container -->
<div class="container-fluid container-pf-nav-pf-vertical container-pf-nav-pf-vertical-with-secondary">
<div class="row">
<div class="col-md-10 col-md-offset-1">
<a href="#hosts"><h2 id="hosts"><strong>Hosts</strong></h2></a>
<table class="table table-striped table-bordered table-hover table-condensed">
<thead>
<tr>
<th><span class="pull-left">Host</span></th>
<th class="col-md-1"><span class="ok label status-label">OK</span></th>
<th class="col-md-1"><span class="changed label status-label">CHANGED</span></th>
<th class="col-md-1"><span class="failed label status-label">FAILED</span></th>
<th class="col-md-1"><span class="skipped label status-label">SKIPPED</span></th>
<th class="col-md-1"><span class="unreachable label status-label">UNREACHABLE</span></th>
</tr>
</thead>
<tbody>
{% for stat in playbook.stats %}
<tr>
<td>
{% if stat.host.facts %}
<a href="{{ url_for('host.show_host', id=stat.host.id) }}">
<span class="pf pficon-info action-icon action-available pull-left" title="Host facts available"></span>
</a>
{% else %}
<span class="pf pficon-info action-icon action-unavailable pull-left" title="Host facts unavailable"></span>
{% endif %}
<span class="pull-left">{{ macros.make_link('playbook.playbook_results', stat.host.name, playbook=playbook.id, host=stat.host.name) }}</span>
</td>
{{ macros.statslink(stat, 'ok', playbook, stat.host) }}
{{ macros.statslink(stat, 'changed', playbook, stat.host) }}
{{ macros.statslink(stat, 'failed', playbook, stat.host) }}
{{ macros.statslink(stat, 'skipped', playbook, stat.host) }}
{{ macros.statslink(stat, 'unreachable', playbook, stat.host) }}
</tr>
{% endfor %}
</tbody>
</table>
<a href="#plays"><h2 id="plays"><strong>Plays</strong></h2></a>
<table class="table table-striped table-bordered table-hover table-condensed">
<thead>
<tr>
<th><span class="pull-left">Play</span></th>
<th class="date col-md-2">Start</th>
<th class="date col-md-2">End</th>
<th class="date col-md-1">Duration</th>
</tr>
</thead>
<tbody>
{% for play in plays %}
<tr>
<td><span class="pull-left">{{ macros.make_link('playbook.playbook_results', play.name, playbook=playbook.id, play=play.id) }}</span></td>
<td>{{ play.time_start |datefmt }}</td>
<td>{{ play.time_end |datefmt }}</td>
<td>{{ play.duration |timefmt }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<a href="#files"><h2 id="files"><strong>Files</strong></h2></a>
<table class="table table-striped table-bordered table-hover table-condensed">
<thead>
<tr>
<th><span class="pull-left">File</span></th>
<th class="date col-md-1"></th>
<th class="date col-md-1"></th>
</tr>
</thead>
<tbody>
<tr>
<td><span class="pull-left">{{ playbook_file.path |pathtruncate(75) }} <strong>(Playbook)</strong></span></td>
<td colspan="2">{{ macros.make_link('file.show_file', 'View', file_=playbook_file.id) }}</td>
</tr>
{% for file in files %}
<tr>
<td><span class="pull-left">{{ file.path |pathtruncate(75) }}</span></td>
<td>{{ macros.make_link('file.show_file', 'View', file_=file.id) }}</td>
<td>{{ macros.make_link('playbook.show_playbook', 'Filter', playbook=playbook.id, file_=file.id) }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<a href="#tasks"><h2 id="tasks"><strong>Tasks</strong></h2></a>
<table class="table table-striped table-bordered table-hover table-condensed">
<thead>
<tr>
<th><span class="pull-left">Task</span></th>
<th>Action</th>
<th class="col-md-3">File</th>
<th class="date col-md-1">Elapsed</th>
<th class="date col-md-1">Duration</th>
</tr>
</thead>
<tbody>
{% for task in tasks %}
<tr>
<td><span class="pull-left">{{ macros.make_link('playbook.playbook_results', task.name, playbook=playbook.id, task=task.id) }}</span></td>
<td>{{ task.action }}</td>
<td>
{% if task.file %}
{{ macros.make_link('file.show_file', task.file.path |pathtruncate(25) + "#" + task.lineno|string, file_=task.file.id, _anchor="line-" ~ task.lineno) }}
<a href="{{ url_for('playbook.show_playbook', playbook=playbook.id, file_=task.file.id) }}"><span class="fa fa-filter pull-right" title="Filter tasks from this file"></span></a>
{% endif %}
</td>
<td>{{ task.offset_from_playbook |timefmt }}</td>
<td>{{ task.duration |timefmt }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<h2><strong>Recorded data</strong></h2>
{% if data %}
<table class="table table-striped table-bordered table-hover table-condensed">
<thead>
<tr>
<th><span class="pull-right">Key</span></th>
<th><span class="pull-left">Value</span></th>
</tr>
</thead>
<tbody>
{% for item in data %}
<tr>
<td class="vert-align" id="{{ item.id }}"><span class="pull-right"><a href="#{{ item.id }}">{{ item.key }}</a></span></td>
<td><span class="pull-left">{{ macros.render_value_type(item.value, item.type) }}</span></td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<h3>There is no recorded data for this playbook. Get started with <a href="http://ara.readthedocs.io/en/latest/usage.html#using-the-ara-record-module" target="_blank">ara_record</a>.</h3>
{% endif %}
</div>
</div>
</div>
{% endblock %}

View File

@@ -1,45 +1,16 @@
{% extends "layout.html" %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<h1><strong>All Playbooks</strong></h1>
<table class="table table-striped table-bordered table-hover table-condensed">
<thead>
<tr>
<th></th>
<th>Playbook</th>
<th class="col-md-1">Hosts</th>
<th class="date col-md-2">Date</th>
<th class="date col-md-1">Duration</th>
<th class="col-md-1"><span class="ok label status-label">OK</span></th>
<th class="col-md-1"><span class="changed label status-label">CHANGED</span></th>
<th class="col-md-1"><span class="failed label status-label">FAILED</span></th>
<th class="col-md-1"><span class="skipped label status-label">SKIPPED</span></th>
<th class="col-md-1"><span class="unreachable label status-label">UNREACHABLE</span></th>
</tr>
</thead>
<tbody>
{% for playbook in playbooks %}
<tr>
<td class="vert-align">{{ macros.render_status(stats[playbook.id].status) }}</td>
<td>
<span class="pull-left">{{ macros.make_link('playbook.show_playbook', playbook.path|pathtruncate(35), playbook=playbook.id) }}</span>
</td>
<td>{{ playbook.hosts|list|length }}</td>
<td>{{ playbook.time_start |datefmt }}</td>
<td>{{ playbook.duration |timefmt }}</td>
<td>{{ stats[playbook.id].ok }}</td>
<td>{{ stats[playbook.id].changed }}</td>
<td>{{ stats[playbook.id].failed }}</td>
<td>{{ stats[playbook.id].skipped }}</td>
<td>{{ stats[playbook.id].unreachable }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="row text-center">
<h1>This URL has moved to the <a href="{{ url_for('reports.report_list') }}">Playbook reports</a> page.</h1>
<h2>It is deprecated and will be removed in a future version.</h2>
<h2>Click <a href="{{ url_for('reports.report_list') }}">here</a> to access the new page if you are not redirected automatically.</h2>
</div>
<script>
window.setTimeout(function(){
window.location.href = "{{ url_for('reports.report_list') }}";
}, 10000);
</script>
{% endblock %}

View File

@@ -1,221 +0,0 @@
{% extends "layout.html" %}
{% block content %}
<!-- Header container -->
<div class="container-fluid">
<div class="row">
<div class="col-xs-12 col-sm-6 col-md-10 col-md-offset-1">
<h1><strong>Playbook</strong>: {{ macros.make_link('playbook.show_playbook', playbook.path, playbook=playbook.id) }}{% if playbook.ansible_version %} with Ansible v{{ playbook.ansible_version }}{% endif %}</h1>
<h2>{{ playbook.time_start |datefmt }} <span class="fa fa-angle-double-right"></span> {{ playbook.time_end |datefmt }}</h2>
</div>
</div>
</div>
<!-- Dashboard container -->
<div class="container-fluid container-cards-pf">
<div class="row row-cards-pf">
<div class="col-md-2 col-md-offset-1">
<div class="card-pf card-pf-accented card-pf-aggregate-status card-pf-aggregate-status-mini" style="height: 59px;">
<h2 class="card-pf-title" style="height: 42px;">
<a href="#hosts">
<span class="pficon pficon-server"></span>
<span class="card-pf-aggregate-status-count">{{ playbook.hosts.count() }}</span> <strong>Hosts</strong>
</a>
</h2>
</div>
</div>
<div class="col-md-2">
<div class="card-pf card-pf-accented card-pf-aggregate-status card-pf-aggregate-status-mini" style="height: 59px;">
<h2 class="card-pf-title" style="height: 42px;">
<a href="#plays">
<span class="pficon pficon-flag"></span>
<span class="card-pf-aggregate-status-count">{{ playbook.plays.count() }}</span> <strong>Plays</strong>
</a>
</h2>
</div>
</div>
<div class="col-md-2">
<div class="card-pf card-pf-accented card-pf-aggregate-status card-pf-aggregate-status-mini" style="height: 59px;">
<h2 class="card-pf-title" style="height: 42px;">
<a href="#files">
<span class="pficon pficon-folder-open"></span>
<span class="card-pf-aggregate-status-count">{{ playbook.files.count() }}</span> <strong>Files</strong>
</a>
</h2>
</div>
</div>
<div class="col-md-2">
<div class="card-pf card-pf-accented card-pf-aggregate-status card-pf-aggregate-status-mini" style="height: 59px;">
<h2 class="card-pf-title" style="height: 42px;">
<a href="#tasks">
<span class="fa fa-check"></span>
<span class="card-pf-aggregate-status-count">{{ playbook.tasks.count() }}</span> <strong>Tasks</strong>
</a>
</h2>
</div>
</div>
<div class="col-md-2">
<div class="card-pf card-pf-accented card-pf-aggregate-status card-pf-aggregate-status-mini" style="height: 59px;">
<h2 class="card-pf-title" style="height: 42px;">
<span class="fa fa-clock-o"></span>
<span class="card-pf-aggregate-status-count">{{ playbook.duration |timefmt }}</span> <strong>Duration</strong>
</h2>
</div>
</div>
</div>
</div>
<!-- Detail container -->
<div class="container-fluid container-pf-nav-pf-vertical container-pf-nav-pf-vertical-with-secondary">
<div class="row">
<div class="col-md-10 col-md-offset-1">
<a href="#hosts"><h2 id="hosts"><strong>Hosts</strong></h2></a>
<table class="table table-striped table-bordered table-hover table-condensed">
<thead>
<tr>
<th><span class="pull-left">Host</span></th>
<th class="col-md-1"><span class="ok label status-label">OK</span></th>
<th class="col-md-1"><span class="changed label status-label">CHANGED</span></th>
<th class="col-md-1"><span class="failed label status-label">FAILED</span></th>
<th class="col-md-1"><span class="skipped label status-label">SKIPPED</span></th>
<th class="col-md-1"><span class="unreachable label status-label">UNREACHABLE</span></th>
</tr>
</thead>
<tbody>
{% for stat in playbook.stats %}
{% if hosts and stat.host.name in hosts %}
<tr class="active">
{% else %}
<tr>
{% endif %}
<td>
{% if stat.host.facts %}
<a href="{{ url_for('host.show_host', id=stat.host.id) }}">
<span class="pf pficon-info action-icon action-available pull-left" title="Host facts available"></span>
</a>
{% else %}
<span class="pf pficon-info action-icon action-unavailable pull-left" title="Host facts unavailable"></span>
{% endif %}
<span class="pull-left">{{ macros.make_link('playbook.playbook_results', stat.host.name, playbook=playbook.id, host=stat.host.name) }}</span>
</td>
{{ macros.statslink(stat, 'ok', playbook, stat.host) }}
{{ macros.statslink(stat, 'changed', playbook, stat.host) }}
{{ macros.statslink(stat, 'failed', playbook, stat.host) }}
{{ macros.statslink(stat, 'skipped', playbook, stat.host) }}
{{ macros.statslink(stat, 'unreachable', playbook, stat.host) }}
</tr>
{% endfor %}
</tbody>
</table>
<a href="#plays"><h2 id="plays"><strong>Plays</strong></h2></a>
<table class="table table-striped table-bordered table-hover table-condensed">
<thead>
<tr>
<th><span class="pull-left">Play</span></th>
<th class="date col-md-2">Start</th>
<th class="date col-md-2">End</th>
<th class="date col-md-1">Duration</th>
</tr>
</thead>
<tbody>
{% for play in playbook.plays %}
{% if plays and play.id in plays %}
<tr class="active">
{% else %}
<tr>
{% endif %}
<td><span class="pull-left">{{ macros.make_link('playbook.playbook_results', play.name, playbook=playbook.id, play=play.id) }}</span></td>
<td class="date">{{ play.time_start |datefmt }}</td>
<td class="date">{{ play.time_end |datefmt }}</td>
<td>{{ play.duration |timefmt }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<a href="#files"><h2 id="files"><strong>Files</strong></h2></a>
<table class="table table-striped table-bordered table-hover table-condensed">
<thead>
<tr>
<th><span class="pull-left">File</span></th>
<th class="date col-md-1"></th>
<th class="date col-md-1"></th>
</tr>
</thead>
<tbody>
<tr>
<td><span class="pull-left">{{ playbook_file.path |pathtruncate(75) }} <strong>(Playbook)</strong></span></td>
<td colspan="2">{{ macros.make_link('file.show_file', 'View', file_=playbook_file.id) }}</td>
</tr>
{% for file in files %}
<tr>
<td><span class="pull-left">{{ file.path |pathtruncate(75) }}</span></td>
<td>{{ macros.make_link('file.show_file', 'View', file_=file.id) }}</td>
<td>{{ macros.make_link('playbook.show_playbook', 'Filter', playbook=playbook.id, file_=file.id) }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<a href="#tasks"><h2 id="tasks"><strong>Task Results</strong></h2></a>
<table class="table table-striped table-bordered table-hover table-condensed">
<thead>
<tr>
<th><span class="pull-left">Task</span></th>
<th>Host</th>
<th>Action</th>
<th class="date col-md-1">Elapsed</th>
<th class="date col-md-1">Duration</th>
<th class="col-md-1">Status</th>
<th></th>
</tr>
</thead>
<tbody>
{% for result in task_results %}
{% set status = result.derived_status %}
<tr>
<td><span class="pull-left">{{ result.task.name }}</span></td>
<td>{{ result.host.name }}</td>
<td>{{ result.task.action }}</td>
<td>{{ result.task.offset_from_playbook|timefmt }}</td>
<td>{{ result.duration |timefmt }}</td>
<td><span class="{{ status }} label status-label">{{ status |upper }}</span></td>
<td>
{{ macros.make_link('result.show_result', 'details', task_result=result.id) }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
<h2><strong>Recorded data</strong></h2>
{% if data %}
<table class="table table-striped table-bordered table-hover table-condensed">
<thead>
<tr>
<th><span class="pull-right">Key</span></th>
<th><span class="pull-left">Value</span></th>
</tr>
</thead>
<tbody>
{% for item in data %}
<tr>
<td class="vert-align" id="{{ item.id }}"><span class="pull-right"><a href="#{{ item.id }}">{{ item.key }}</a></span></td>
<td><span class="pull-left">{{ macros.render_value_type(item.value, item.type) }}</span></td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<h3>There is no recorded data for this playbook. Get started with <a href="http://ara.readthedocs.io/en/latest/usage.html#using-the-ara-record-module" target="_blank">ara_record</a>.</h3>
{% endif %}
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,510 @@
{% extends "layout.html" %}
{% block content %}
<!-- Begin report list -->
<div class="col-md-custom col-md-offset-custom">
<div class="row">
<br>
<div class="alert alert-info">
<span class="pficon pficon-info"></span>
Browse your playbook reports here: Get started by expanding the "<strong>Hosts</strong>", "<strong>Plays</strong>", "<strong>Files</strong>" or "<strong>Tasks</strong>" panels below.
You can mouse over most elements to have more details about them.
</div>
<div class="list-group list-view-pf list-view-pf-view">
{% for playbook in playbooks.items %}
<!-- Begin playbook iteration -->
<div class="list-group-item">
<div class="list-view-pf-left">
{{ macros.render_status(stats[playbook.id].status) }}
</div>
<!-- Begin playbook entry in the list -->
<div class="list-view-pf-main-info">
<div class="list-view-pf-body">
<div class="list-view-pf-description">
<div class="list-group-item-heading timestamp-heading">
<span title="Date at which the playbook started">{{ playbook.time_start |datefmt }}</span>
</div>
<div class="list-group-item-heading" data-toggle="tooltip" data-placement="bottom" data-html="true" title="{{ macros.render_playbook_summary(playbook) }}">
<a href="#" data-toggle="modal" data-target="#file_modal" data-load="{{ playbook.file.id }}">{{ playbook.path }}</a>
</div>
</div>
<div class="list-view-pf-additional-info">
<div class="list-view-pf-additional-info-item" title="Duration of the entire playbook">
<span class="fa fa-clock-o"></span>
<strong>{{ playbook.duration |timefmt }}</strong>
</div>
<br>
<div class="list-view-pf-additional-info-item" title="Hosts involved in the playbook">
<div class="list-view-pf-expand">
<span class="fa fa-angle-right"></span>
<span class="pficon pficon-server"></span>
<strong>{{ playbook.hosts|list|length }}</strong> Hosts
</div>
</div>
<div class="list-view-pf-additional-info-item" title="Plays involved in the playbook">
<div class="list-view-pf-expand">
<span class="fa fa-angle-right"></span>
<span class="pficon pficon-flag"></span>
<strong>{{ playbook.plays|list|length }}</strong> Plays
</div>
</div>
<div class="list-view-pf-additional-info-item" title="Role and task files involved in the playbook">
<div class="list-view-pf-expand">
<span class="fa fa-angle-right"></span>
<span class="fa fa-folder-open"></span>
<strong>{{ playbook.files|list|length }}</strong> Files
</div>
</div>
<div class="list-view-pf-additional-info-item" title="Tasks involved in the playbook">
<div class="list-view-pf-expand">
<span class="fa fa-angle-right"></span>
<span class="fa fa-check"></span>
<strong>{{ playbook.tasks|list|length }}</strong> Tasks
</div>
</div>
<div class="list-view-pf-additional-info-item" title="Records saved with ara_record throughout the playbook">
<div class="list-view-pf-expand">
<span class="fa fa-angle-right"></span>
<span class="fa pficon-save"></span>
<strong>{{ playbook.data|list|length }}</strong> Records
</div>
</div>
</div>
</div>
</div>
<!-- End playbook entry in the list -->
<!-- Begin playbook details -->
<!-- Begin playbook host details -->
<div class="list-group-item-container container-fluid hidden">
<div class="close">
<span class="pficon pficon-close"></span>
</div>
<div class="row">
<div class="col-md-4">
<h3>
<strong>Hosts</strong>
</h3>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><span class="pficon pficon-info"></span> Browsing tips</h3>
</div>
<div class="panel-body">
<ul>
<li>All table columns are sortable</li>
<li>If facts have been gathered:
<ul>
<li>Hover hosts to see a quick highlight summary of the recorded host facts</li>
<li>Click on a host to browse the full list of recorded host facts</li>
</ul>
</li>
<li>Use the search box to find specific hosts based on their properties, for example:
<ul>
<li><strong>localhost</strong> would match only 'localhost' (provided by default: the Ansible inventory hostname)</li>
<li><strong>fedora</strong> would match all 'fedora' hosts (provided by the <i>ansible_distribution</i> fact)</li>
<li><strong>192.168.1</strong> would match all hosts with an IP in the '192.168.1' range (provided by the <i>ansible_all_ipv4_addresses</i> fact)</li>
</ul>
</li>
</ul>
</div>
</div>
</div>
<div class="col-md-8">
<table class="table table-striped table-bordered table-hover table-condensed" id="hosts_{{ playbook.id }}">
<thead>
<tr>
<th><span class="pull-left">Host</span></th>
<th class="col-md-2"><span class="ok label status-label">OK</span></th>
<th class="col-md-2"><span class="changed label status-label">CHANGED</span></th>
<th class="col-md-2"><span class="failed label status-label">FAILED</span></th>
<th class="col-md-2"><span class="skipped label status-label">SKIPPED</span></th>
<th class="col-md-2"><span class="unreachable label status-label">UNREACHABLE</span></th>
</tr>
</thead>
<tbody>
{% for stat in playbook.stats %}
<tr>
<td>
<span class="pull-left">
<div data-toggle="tooltip" data-placement="bottom" data-html="true" title="{{ macros.render_host_facts(stat.host) }}">
{% if stat.host.facts %}
<a href="#" data-toggle="modal" data-target="#host_modal" data-load="{{ stat.host.id }}">{{ stat.host.name }}</a>
{% else %}
{{ stat.host.name }}
{% endif %}
</div>
</span>
</td>
<td>{{ stat['ok'] if stat['ok'] >= 1 else '0' }}</td>
<td>{{ stat['changed'] if stat['changed'] >= 1 else '0' }}</td>
<td>{{ stat['failed'] if stat['failed'] >= 1 else '0' }}</td>
<td>{{ stat['skipped'] if stat['skipped'] >= 1 else '0' }}</td>
<td>{{ stat['unreachable'] if stat['unreachable'] >= 1 else '0' }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<!-- End playbook host details -->
<!-- Begin playbook play details -->
<div class="list-group-item-container container-fluid hidden">
<div class="close">
<span class="pficon pficon-close"></span>
</div>
<div class="row">
<div class="col-md-4">
<h3>
<strong>Plays</strong>
</h3>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><span class="pficon pficon-info"></span> Browsing tips</h3>
</div>
<div class="panel-body">
<ul>
<li>All table columns are sortable</li>
<li>Use the search box to find a specific play based on it's name</li>
<li>To see your playbook file, click the playbook file path in the playbook report list</li>
</ul>
</div>
</div>
</div>
<div class="col-md-8">
<table class="table table-striped table-bordered table-hover table-condensed" id="plays_{{ playbook.id }}">
<thead>
<tr>
<th><span class="pull-left">Play</span></th>
<th class="col-md-2">Start</th>
<th class="col-md-2">End</th>
<th class="col-md-2">Duration</th>
</tr>
</thead>
<tbody>
{% for play in playbook.plays %}
<tr>
<td><span class="pull-left">{{ play.name }}</span></td>
<td>{{ play.time_start |datefmt }}</td>
<td>{{ play.time_end |datefmt }}</td>
<td>{{ play.duration |timefmt }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<!-- End playbook play details -->
<!-- Begin playbook file details -->
<div class="list-group-item-container container-fluid hidden">
<div class="close">
<span class="pficon pficon-close"></span>
</div>
<div class="row">
<div class="col-md-4">
<h3>
<strong>Files</strong>
</h3>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><span class="pficon pficon-info"></span> Browsing tips</h3>
</div>
<div class="panel-body">
<ul>
<li>The table column is sortable</li>
<li>Use the search box to find a specific file based on it's name or path</li>
<li>Click on any file to see it's contents</li>
<li><strong>Note:</strong> This panel is temporary and will be improved in a future version.</li>
</ul>
</div>
</div>
</div>
<div class="col-md-8">
<table class="table table-striped table-bordered table-hover table-condensed" id="files_{{ playbook.id }}">
<thead>
<tr>
<th><span class="pull-left">File</span></th>
</tr>
</thead>
<tbody>
{% for file in playbook.files %}
<tr>
<td><span class="pull-left"><a href="#" data-toggle="modal" data-target="#file_modal" data-load="{{ file.id }}">{{ file.path }}</a></span></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<!-- End playbook file details -->
<!-- Begin playbook task details -->
<div class="list-group-item-container container-fluid hidden">
<div class="close">
<span class="pficon pficon-close"></span>
</div>
<div class="row">
<div class="col-md-4">
<h3>
<strong>Tasks</strong>
</h3>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><span class="pficon pficon-info"></span> Browsing tips</h3>
</div>
<div class="panel-body">
<ul>
<li>All table columns are sortable</li>
<li>Click on a task status (ex: 'failed') to see the detailed task result</li>
<li>Use the search box to find specific tasks based on their properties, for example:
<ul>
<li><strong>do something</strong> would only match tasks with 'do something' in their name</li>
<li><strong>localhost</strong> would only match tasks on the host 'localhost'</li>
<li><strong>include</strong> would match tasks with 'include' in their task name or as the task action
<li><strong>failed</strong> would only match tasks with the 'failed' status</li>
</ul>
</li>
</ul>
</div>
</div>
</div>
<div class="col-md-8">
<table class="table table-striped table-bordered table-hover table-condensed" id="tasks_{{ playbook.id }}">
<thead>
<tr>
<th><span class="pull-left">Task</span></th>
<th>Host</th>
<th>Action</th>
<th class="col-md-1">Elapsed</th>
<th class="col-md-1">Duration</th>
<th class="col-md-1">Status</th>
</tr>
</thead>
<tbody>
{% if playbook.tasks %}
{% for task in playbook.tasks %}
{% if task.task_results %}
{% for result in task.task_results %}
{% set status = result.derived_status %}
<tr>
<td><span class="pull-left">{{ result.task.name }}</span></td>
<td>{{ result.host.name }}</td>
{% if result.task.file %}
<td>
<a href="#" data-toggle="modal" data-target="#file_modal" data-load="{{ result.task.file.id }}#line-{{ result.task.lineno }}">{{ result.task.action }}</a
</td>
{% else %}
<td>{{ result.task.action }}</td>
{% endif %}
<td>{{ result.task.offset_from_playbook|timefmt }}</td>
<td>{{ result.duration |timefmt }}</td>
<td>
<a href="#" data-toggle="modal" data-target="#task_result_modal" data-load="{{ result.id }}"><span class="{{ status }} label status-label">{{ status |upper }}</span></a>
</td>
</tr>
{% endfor %}
{% endif %}
{% endfor %}
{% endif %}
</tbody>
</table>
</div>
</div>
</div>
<!-- End playbook task details -->
<!-- Begin playbook data details -->
<div class="list-group-item-container container-fluid hidden">
<div class="close">
<span class="pficon pficon-close"></span>
</div>
<div class="row">
<div class="col-md-4">
<h3>
<strong>Records</strong>
</h3>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><span class="pficon pficon-info"></span> Browsing tips</h3>
</div>
<div class="panel-body">
<ul>
<li>Data recorded with the <a href="http://ara.readthedocs.io/en/latest/usage.html#ara-record" target="_blank">ara_record</a> Ansible module will be displayed here</li>
<li>Use the search box to find a specific key based on it's name or expected contents.</li>
</ul>
</div>
</div>
</div>
<div class="col-md-8">
{% if playbook.data and playbook.data|list|length > 0 %}
<table class="table table-striped table-bordered table-hover table-condensed" id="data_{{ playbook.id }}">
<thead>
<tr>
<th><span class="pull-right">Key</span></th>
<th><span class="pull-left">Value</span></th>
</tr>
</thead>
<tbody>
{% for item in playbook.data %}
<tr>
<td class="vert-align"><span class="pull-right" title="Type: {{ item.type }}">{{ item.key }}</span></td>
<td><span class="pull-left">{{ macros.render_value_type(item.value, item.type) }}</span></td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<div class="text-center">
<h2>There is no recorded data for this playbook.</h2>
<h3>The <a href="http://ara.readthedocs.io/en/latest/usage.html#ara-record" target="_blank">ara_record</a> Ansible module helps you record arbitrary persistent data that is displayed here.</h3>
<h3>It provides the ability to save strings, lists, dicts, json or urls and each will be rendered accordingly. Try it out!</h3>
</div>
{% endif %}
</div>
</div>
</div>
<!-- End playbook data details -->
<!-- End playbook details -->
</div>
<!-- Begin modals -->
<div class="modal fade" id="host_modal" tabindex="-1" role="dialog" aria-labelledby="host_modal" aria-hidden="true">
<div class="modal-lg modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">
<span class="pficon pficon-close"></span>
</button>
</div>
<div class="modal-body embed-responsive embed-responsive-item">
<iframe id="host_iframe" class="iframe" data-src="{{ url_for('host.index') }}"></iframe>
</div>
</div>
</div>
</div>
<div class="modal fade" id="file_modal" tabindex="-1" role="dialog" aria-labelledby="file_modal" aria-hidden="true">
<div class="modal-lg modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">
<span class="pficon pficon-close"></span>
</button>
</div>
<div class="modal-body embed-responsive embed-responsive-item">
<iframe id="file_iframe" class="iframe" data-src="{{ url_for('file.index') }}"></iframe>
</div>
</div>
</div>
</div>
<div class="modal fade" id="task_result_modal" tabindex="-1" role="dialog" aria-labelledby="task_result_modal" aria-hidden="true">
<div class="modal-lg modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">
<span class="pficon pficon-close"></span>
</button>
</div>
<div class="modal-body embed-responsive embed-responsive-item">
<iframe id="result_iframe" class="iframe" data-src="{{ url_for('result.index') }}"></iframe>
</div>
</div>
</div>
</div>
<!-- End modals -->
<!-- End playbook iteration -->
{% endfor %}
</div>
</div>
</div>
<!-- Begin pagination -->
<div class="col-md-11 col-md-offset-custom">
<div class="row text-center">
Displaying <strong>{{ playbooks.items |length }}</strong> playbook reports out of a total of <strong>{{ playbooks.total }}</strong>.
</div>
{% if playbooks.pages > 1 %}
<div class="row text-center">
<ul class="pagination">
{% if playbooks.has_prev %}
<li>
<a href="{{ url_for('reports.report_list', page=playbooks.prev_num) }}"><span class="i fa fa-angle-left"></span></a>
</li>
{% endif %}
{% for page in playbooks.iter_pages() %}
{% if page == playbooks.page %}
<li class="active">
{% else %}
<li>
{% endif %}
<a href="{{ url_for('reports.report_list', page=page) }}">{{ page }}</a>
</li>
{% endfor %}
{% if playbooks.has_next %}
<li>
<a href="{{ url_for('reports.report_list', page=playbooks.next_num) }}"><span class="i fa fa-angle-right"></span></a>
</li>
{% endif %}
</ul>
</div>
{% endif %}
</div>
<!-- End pagination -->
<script>
{% for playbook in playbooks.items %}
$("#hosts_{{ playbook.id }}").DataTable({
"order": [[ 0, "asc" ]],
"oLanguage": {
"sSearch": "Search: "
},
{{ macros.result_page_length(result_per_page) }}
});
$("#plays_{{ playbook.id }}").DataTable({
"order": [[ 1, "asc" ]],
"oLanguage": {
"sSearch": "Search: "
},
{{ macros.result_page_length(result_per_page) }}
});
$("#files_{{ playbook.id }}").DataTable({
"order": [[ 0, "asc" ]],
"oLanguage": {
"sSearch": "Search: "
},
{{ macros.result_page_length(result_per_page) }}
});
$("#tasks_{{ playbook.id }}").DataTable({
"order": [[ 3, "asc" ]],
"oLanguage": {
"sSearch": "Search: "
},
{{ macros.result_page_length(result_per_page) }}
});
$("#data_{{ playbook.id }}").DataTable({
"ordering": false,
"oLanguage": {
"sSearch": "Search: "
},
{{ macros.result_page_length(result_per_page) }}
});
{% endfor %}
$('.modal').on('show.bs.modal', function(e) {
/* Adjust modal and iframe height based on window size */
$('.modal .modal-body').css('max-height', $(window).height() * 0.7);
$('.embed-responsive-item').css('height', $(window).height() * 0.7);
/* Only load the iframe content when the modal is opened */
var load = $(e.relatedTarget).data('load');
$(this).find('iframe').prop('src', function() {
// Set their src attribute to the value of data-src
return $(this).data('src').replace('index.html','').concat(load);
});
});
</script>
<!-- End report list -->
{% endblock %}

View File

@@ -1,105 +1,48 @@
{% extends "layout.html" %}
{% include "head.html" %}
{% macro display_result(result, hdr) %}
{% if result is mapping %}
{% if 'item' in result %}
<{{ hdr }}>Item</{{ hdr }}>
<pre>{{ result.item |to_nice_json |safe }}</pre>
{% endif %}
{% for attr in result.keys()|sort
if attr not in ['item', 'changed', 'stdout_lines'] %}
{% if result[attr]|default(False) %}
<{{ hdr }}>{{ attr|title }}</{{ hdr }}>
{% if result[attr] is string %}
<pre>{{ result[attr] }}</pre>
{% elif result[attr] is mapping or result[attr] is iterable%}
<pre>{{ result[attr] |to_nice_json |safe }}</pre>
{% else %}
<pre>{{ result |to_nice_json |safe }}</pre>
{% if result is mapping %}
{% if 'item' in result %}
<{{ hdr }}>Item</{{ hdr }}>
<pre>{{ result.item |to_nice_json |safe }}</pre>
{% endif %}
{% endif %}
{% endfor %}
{% else %}
<pre>{{ result |to_nice_json |safe }}</pre>
{% endif %}
{% endmacro %}
<!-- Header container -->
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-md-10 col-md-offset-1">
<h1><strong>Playbook</strong>: {{ macros.make_link('playbook.show_playbook', task_result.task.playbook.path, playbook=task_result.task.playbook.id) }}{% if task_result.task.playbook.ansible_version %} with Ansible v{{ task_result.task.playbook.ansible_version }}{% endif %}</h1>
<h2>{{ task_result.time_start |datefmt }} <span class="fa fa-angle-double-right"></span> {{ task_result.time_end |datefmt }}</h2>
<h1><strong>Task</strong>: {{ task_result.task.name }}</h1>
<h1><strong>Host</strong>: {{ task_result.host.name }}</h1>
</div>
</div>
</div>
<!-- Dashboard container -->
<div class="container-fluid container-cards-pf">
<div class="row row-cards-pf">
<div class="col-md-1"></div>
{% if task_result.task.file %}
<div class="col-md-2">
<div class="card-pf card-pf-accented card-pf-aggregate-status card-pf-aggregate-status-mini" style="height: 59px;">
<h2 class="card-pf-title" style="height: 42px;">
<span class="pficon pficon-folder-open"></span>
<span class="card-pf-aggregate-status-count">
{{ macros.make_link('file.show_file', task_result.task.file.path |pathtruncate(15), file_=task_result.task.file.id) }}
</span> <strong>File</strong>
</h2>
</div>
</div>
<div class="col-md-2">
<div class="card-pf card-pf-accented card-pf-aggregate-status card-pf-aggregate-status-mini" style="height: 59px;">
<h2 class="card-pf-title" style="height: 42px;">
<span class="fa fa-search"></span>
<span class="card-pf-aggregate-status-count">
{{ macros.make_link('file.show_file', task_result.task.lineno, file_=task_result.task.file.id, _anchor="line-" ~ task_result.task.lineno) }}
</span> <strong>File line</strong>
</h2>
</div>
</div>
{% endif %}
<div class="col-md-2">
<div class="card-pf card-pf-accented card-pf-aggregate-status card-pf-aggregate-status-mini" style="height: 59px;">
<h2 class="card-pf-title" style="height: 42px;">
<span class="fa pficon-info"></span>
<span class="card-pf-aggregate-status-count">{{ task_result.task.action }}</span> <strong>Action</strong>
</h2>
</div>
</div>
<div class="col-md-2">
<div class="card-pf card-pf-accented card-pf-aggregate-status card-pf-aggregate-status-mini" style="height: 59px;">
<h2 class="card-pf-title" style="height: 42px;">
<span class="fa fa-clock-o"></span>
<span class="card-pf-aggregate-status-count">{{ task_result.duration |timefmt }}</span> <strong>Duration</strong>
</h2>
</div>
</div>
</div>
</div>
<!-- Detail container -->
<div class="container-fluid">
<div class="row">
<div class="col-md-10 col-md-offset-1">
<h2><strong>Task result information</strong></h2>
{% set result = task_result.result|from_json %}
{% if 'results' in result %}
{% for item in result.results %}
<h2>Result {{loop.index}}</h2>
{{ display_result(item, 'h3') }}
{% for attr in result.keys()|sort if attr not in ['item', 'changed', 'stdout_lines'] %}
{% if result[attr]|default(False) %}
<{{ hdr }}>{{ attr|title }}</{{ hdr }}>
{% if result[attr] is string %}
<pre>{{ result[attr] }}</pre>
{% elif result[attr] is mapping or result[attr] is iterable%}
<pre>{{ result[attr] |to_nice_json |safe }}</pre>
{% else %}
<pre>{{ result |to_nice_json |safe }}</pre>
{% endif %}
{% endif %}
{% endfor %}
{% else %}
{{ display_result(result, 'h2') }}
{% endif %}
{% else %}
<pre>{{ result |to_nice_json |safe }}</pre>
{% endif %}
{% endmacro %}
<div class="container-fluid">
<div class="row">
<div class="col-md-12 results">
<h2>Task: <strong>{{ task_result.task.name }}</strong></h2>
<h2>Host: <strong>{{ task_result.host.name }}</strong></h2>
<p>Time: <strong>{{ task_result.time_start |datefmt }}</strong></p>
<p>Ansible version: <strong>{{ task_result.task.playbook.ansible_version }}</strong></p>
{% set result = task_result.result|from_json %}
{% if 'results' in result %}
{% for item in result.results %}
<h2>Result {{loop.index}}</h2>
{{ display_result(item, 'h3') }}
{% endfor %}
{% else %}
{{ display_result(result, 'h2') }}
{% endif %}
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,27 @@
{% include "head.html" %}
<!--
This template is not actually served or linked from within the web application.
-->
<body>
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<table class="table table-striped table-bordered table-hover table-condensed">
<thead>
<tr>
<th>Result</th>
</tr>
</thead>
<tbody>
{% for result in results %}
<tr>
<td><a href="{{ url_for('result.show_result', task_result=result.id) }}">{{ result.id }}</a></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</body>
</html>

View File

@@ -25,23 +25,117 @@ class TestApp(TestAra):
def tearDown(self):
super(TestApp, self).tearDown()
# Reset app config which might have been altered to defaults
self.app.config['ARA_PLAYBOOK_OVERRIDE'] = None
self.app.config['ARA_PLAYBOOK_PER_PAGE'] = 10
def test_home(self):
def test_home_with_data(self):
ansible_run()
res = self.client.get('/')
self.assertEqual(res.status_code, 200)
def test_home_with_override(self):
def test_home_without_data(self):
res = self.client.get('/')
self.assertEqual(res.status_code, 200)
def test_reports_without_data(self):
res = self.client.get('/reports/')
self.assertEqual(res.status_code, 302)
def test_reports(self):
ansible_run()
res = self.client.get('/reports/')
self.assertEqual(res.status_code, 200)
def test_reports_with_incomplete(self):
ansible_run(complete=False)
res = self.client.get('/reports/')
self.assertEqual(res.status_code, 200)
def test_reports_with_override(self):
ctx = ansible_run()
self.app.config['ARA_PLAYBOOK_OVERRIDE'] = [ctx['playbook'].id]
res = self.client.get('/')
res = self.client.get('/reports/')
self.assertEqual(res.status_code, 200)
def test_home_with_bad_override(self):
def test_reports_with_bad_override(self):
ansible_run()
self.app.config['ARA_PLAYBOOK_OVERRIDE'] = ['uuuu-iiii-dddd-0000']
res = self.client.get('/')
res = self.client.get('/reports/')
self.assertEqual(res.status_code, 302)
def test_reports_with_pagination(self):
ansible_run()
ansible_run()
self.app.config['ARA_PLAYBOOK_PER_PAGE'] = 1
res = self.client.get('/reports/')
self.assertEqual(res.status_code, 200)
res = self.client.get('/reports/1')
self.assertEqual(res.status_code, 200)
res = self.client.get('/reports/2')
self.assertEqual(res.status_code, 200)
def test_show_file(self):
ctx = ansible_run()
res = self.client.get('/file/{0}/'.format(ctx['pb_file'].id))
self.assertEqual(res.status_code, 200)
def test_show_file_index(self):
ansible_run()
res = self.client.get('/file/')
self.assertEqual(res.status_code, 200)
def test_show_host(self):
ctx = ansible_run()
res = self.client.get('/host/{}/'.format(ctx['host'].id))
self.assertEqual(res.status_code, 200)
def test_show_host_index(self):
ansible_run()
res = self.client.get('/host/')
self.assertEqual(res.status_code, 200)
def test_show_host_missing(self):
ansible_run()
res = self.client.get('/host/foo/')
self.assertEqual(res.status_code, 404)
def test_show_host_exists_facts_missing(self):
ctx = ansible_run(gather_facts=False)
res = self.client.get('/host/{}/'.format(ctx['host'].id))
self.assertEqual(res.status_code, 404)
def test_show_host_missing_facts_missing(self):
ansible_run()
res = self.client.get('/host/foo/')
self.assertEqual(res.status_code, 404)
def test_show_result(self):
ctx = ansible_run()
res = self.client.get('/result/{}/'.format(ctx['result'].id))
self.assertEqual(res.status_code, 200)
def test_show_result_index(self):
ansible_run()
res = self.client.get('/result/')
self.assertEqual(res.status_code, 200)
def test_show_result_missing(self):
ansible_run()
res = self.client.get('/result/foo/')
self.assertEqual(res.status_code, 404)
@pytest.mark.incomplete
def test_show_result_incomplete(self):
ctx = ansible_run(complete=False)
res = self.client.get('/result/{}/'.format(ctx['result'].id))
self.assertEqual(res.status_code, 200)
#####
# Tests on deprecated /playbook/, to be removed
#####
def test_list_playbook(self):
ansible_run()
res = self.client.get('/playbook/')
@@ -52,29 +146,12 @@ class TestApp(TestAra):
res = self.client.get('/playbook/')
self.assertEqual(res.status_code, 200)
def test_list_playbook_with_override(self):
ctx = ansible_run()
self.app.config['ARA_PLAYBOOK_OVERRIDE'] = [ctx['playbook'].id]
res = self.client.get('/playbook/')
self.assertEqual(res.status_code, 200)
def test_list_playbook_with_bad_override(self):
ansible_run()
self.app.config['ARA_PLAYBOOK_OVERRIDE'] = ['uuuu-iiii-dddd-0000']
res = self.client.get('/playbook/')
self.assertEqual(res.status_code, 200)
def test_show_playbook(self):
ctx = ansible_run()
res = self.client.get('/playbook/{}/'.format(
ctx['playbook'].id))
self.assertEqual(res.status_code, 200)
def test_show_playbook_missing(self):
ansible_run()
res = self.client.get('/playbook/foo/')
self.assertEqual(res.status_code, 404)
@pytest.mark.incomplete
def test_show_playbook_incomplete(self):
ctx = ansible_run(complete=False)
@@ -115,52 +192,3 @@ class TestApp(TestAra):
ctx['playbook'].id,
ctx['task'].id))
self.assertEqual(res.status_code, 200)
def test_show_playbook_results_missing(self):
ansible_run()
res = self.client.get('/playbook/foo/results/')
self.assertEqual(res.status_code, 404)
def test_show_host(self):
ctx = ansible_run()
res = self.client.get('/host/{}/'.format(ctx['host'].id))
self.assertEqual(res.status_code, 200)
def test_show_host_missing(self):
ansible_run()
res = self.client.get('/host/foo/')
self.assertEqual(res.status_code, 404)
def test_show_host_exists_facts_missing(self):
ctx = ansible_run(gather_facts=False)
res = self.client.get('/host/{}/'.format(ctx['host'].id))
self.assertEqual(res.status_code, 404)
def test_show_host_missing_facts_missing(self):
ansible_run()
res = self.client.get('/host/foo/')
self.assertEqual(res.status_code, 404)
@pytest.mark.incomplete
def test_show_host_incomplete(self):
ctx = ansible_run(complete=False)
res = self.client.get('/host/{}/'.format(ctx['host'].id))
self.assertEqual(res.status_code, 200)
def test_show_result(self):
ctx = ansible_run()
res = self.client.get('/result/{}/'.format(
ctx['result'].id))
self.assertEqual(res.status_code, 200)
def test_show_result_missing(self):
ansible_run()
res = self.client.get('/result/foo/')
self.assertEqual(res.status_code, 404)
@pytest.mark.incomplete
def test_show_result_incomplete(self):
ctx = ansible_run(complete=False)
res = self.client.get('/result/{}/'.format(
ctx['result'].id))
self.assertEqual(res.status_code, 200)

View File

@@ -628,25 +628,19 @@ class TestCLIGenerate(TestAra):
args = parser.parse_args([dir])
cmd.take_action(args)
host_id = ctx['host'].id
file_id = ctx['task'].file_id
play_id = ctx['play'].id
playbook_id = ctx['playbook'].id
host_id = ctx['host'].id
result_id = ctx['result'].id
task_id = ctx['task'].id
file_filter_path = 'playbook/{0}/file/{1}'.format(playbook_id, file_id)
play_path = 'playbook/{0}/play/{1}'.format(playbook_id, play_id)
task_path = 'playbook/{0}/task/{1}'.format(playbook_id, task_id)
paths = [
os.path.join(dir, 'index.html'),
os.path.join(dir, 'static'),
os.path.join(dir, 'file/index.html'),
os.path.join(dir, 'file/{0}'.format(file_id)),
os.path.join(dir, 'host/index.html'),
os.path.join(dir, 'host/{0}'.format(host_id)),
os.path.join(dir, 'playbook/{0}'.format(playbook_id)),
os.path.join(dir, file_filter_path),
os.path.join(dir, play_path),
os.path.join(dir, task_path),
os.path.join(dir, 'reports/index.html'),
os.path.join(dir, 'playbook/index.html'),
os.path.join(dir, 'result/index.html'),
os.path.join(dir, 'result/{0}'.format(result_id))
]
@@ -666,25 +660,19 @@ class TestCLIGenerate(TestAra):
args = parser.parse_args([dir, '--playbook', ctx['playbook'].id])
cmd.take_action(args)
host_id = ctx['host'].id
file_id = ctx['task'].file_id
play_id = ctx['play'].id
playbook_id = ctx['playbook'].id
host_id = ctx['host'].id
result_id = ctx['result'].id
task_id = ctx['task'].id
file_filter_path = 'playbook/{0}/file/{1}'.format(playbook_id, file_id)
play_path = 'playbook/{0}/play/{1}'.format(playbook_id, play_id)
task_path = 'playbook/{0}/task/{1}'.format(playbook_id, task_id)
paths = [
os.path.join(dir, 'index.html'),
os.path.join(dir, 'static'),
os.path.join(dir, 'file/index.html'),
os.path.join(dir, 'file/{0}'.format(file_id)),
os.path.join(dir, 'host/index.html'),
os.path.join(dir, 'host/{0}'.format(host_id)),
os.path.join(dir, 'playbook/{0}'.format(playbook_id)),
os.path.join(dir, file_filter_path),
os.path.join(dir, play_path),
os.path.join(dir, task_path),
os.path.join(dir, 'reports/index.html'),
os.path.join(dir, 'playbook/index.html'),
os.path.join(dir, 'result/index.html'),
os.path.join(dir, 'result/{0}'.format(result_id))
]

View File

@@ -1,8 +1,9 @@
from .playbook import playbook
from .debug import debug
from .host import host
from .result import result
from .home import home
from .file import file
from .home import home
from .host import host
from .playbook import playbook
from .reports import reports
from .result import result
# flake8: noqa

View File

@@ -12,12 +12,31 @@
# License for the specific language governing permissions and limitations
# under the License.
from flask import render_template, abort, Blueprint
from flask import render_template, abort, Blueprint, current_app
from ara import models
file = Blueprint('file', __name__)
@file.route('/')
def index():
"""
This is not served anywhere in the web application.
It is used explicitly in the context of generating static files since
flask-frozen requires url_for's to crawl content.
url_for's are not used with file.show_file directly and are instead
dynamically generated through javascript for performance purposes.
"""
if current_app.config['ARA_PLAYBOOK_OVERRIDE'] is not None:
override = current_app.config['ARA_PLAYBOOK_OVERRIDE']
files = (models.File.query
.filter(models.File.playbook_id.in_(override)))
else:
files = models.File.query.all()
return render_template('file_index.html', files=files)
@file.route('/<file_>/')
def show_file(file_):
""" Returns details of a file """

View File

@@ -13,24 +13,47 @@
# under the License.
from flask import render_template, Blueprint, current_app
from ara import models, utils
from ara import models
home = Blueprint('home', __name__)
@home.route('/')
def main():
""" Returns the dashboard """
""" Returns the home page """
if current_app.config['ARA_PLAYBOOK_OVERRIDE'] is not None:
override = current_app.config['ARA_PLAYBOOK_OVERRIDE']
files = (models.File.query
.filter(models.File.playbook_id.in_(override)))
host_facts = (models.HostFacts.query
.join(models.Host)
.filter(models.Host.playbook_id.in_(override)))
hosts = (models.Host.query
.filter(models.Host.playbook_id.in_(override)))
playbooks = (models.Playbook.query
.filter(models.Playbook.id.in_(override))
.order_by(models.Playbook.time_start.desc()))
.filter(models.Playbook.id.in_(override)))
records = (models.Data.query
.filter(models.Data.playbook_id.in_(override)))
tasks = (models.Task.query
.filter(models.Task.playbook_id.in_(override)))
task_results = (models.TaskResult.query
.join(models.Task)
.filter(models.Task.playbook_id.in_(override)))
else:
playbooks = (models.Playbook.query
.order_by(models.Playbook.time_start.desc())
.limit(10))
files = models.File.query
host_facts = models.HostFacts.query
hosts = models.Host.query
playbooks = models.Playbook.query
records = models.Data.query
tasks = models.Task.query
task_results = models.TaskResult.query
stats = utils.get_summary_stats(playbooks, 'playbook_id')
return render_template('home.html', playbooks=playbooks, stats=stats)
return render_template('home.html',
active='home',
files=files.count(),
host_facts=host_facts.count(),
hosts=hosts.count(),
playbooks=playbooks.count(),
records=records.count(),
tasks=tasks.count(),
task_results=task_results.count())

View File

@@ -15,12 +15,31 @@
import json
import six
from flask import render_template, abort, Blueprint
from flask import render_template, abort, Blueprint, current_app
from ara import models
host = Blueprint('host', __name__)
@host.route('/')
def index():
"""
This is not served anywhere in the web application.
It is used explicitly in the context of generating static files since
flask-frozen requires url_for's to crawl content.
url_for's are not used with host.show_host directly and are instead
dynamically generated through javascript for performance purposes.
"""
if current_app.config['ARA_PLAYBOOK_OVERRIDE'] is not None:
override = current_app.config['ARA_PLAYBOOK_OVERRIDE']
hosts = (models.Host.query
.filter(models.Host.playbook_id.in_(override)))
else:
hosts = models.Host.query.all()
return render_template('host_index.html', hosts=hosts)
@host.route('/<id>/')
def show_host(id):
try:

View File

@@ -12,153 +12,20 @@
# License for the specific language governing permissions and limitations
# under the License.
from flask import render_template, abort, Blueprint, request, current_app
from ara import models, utils
from flask import render_template, Blueprint
playbook = Blueprint('playbook', __name__)
@playbook.route('/')
def playbook_summary():
if current_app.config['ARA_PLAYBOOK_OVERRIDE'] is not None:
override = current_app.config['ARA_PLAYBOOK_OVERRIDE']
playbooks = (models.Playbook.query
.filter(models.Playbook.id.in_(override))
.order_by(models.Playbook.time_start.desc()))
else:
playbooks = (models.Playbook.query
.order_by(models.Playbook.time_start.desc()))
stats = utils.get_summary_stats(playbooks, 'playbook_id')
return render_template('playbook_list.html',
playbooks=playbooks,
stats=stats)
@playbook.route('/<playbook>/')
@playbook.route('/<playbook>/file/<file_>/')
def show_playbook(playbook, file_=None):
playbook = models.Playbook.query.get(playbook)
if playbook is None:
abort(404)
plays = (models.Play.query
.filter(models.Play.playbook_id == playbook.id)
.order_by(models.Play.sortkey))
playbook_file = (models.File.query
.filter(models.File.playbook_id == playbook.id)
.filter(models.File.is_playbook.is_(True))).one()
files = (models.File.query
.filter(models.File.playbook_id == playbook.id)
.filter(models.File.is_playbook.is_(False))
.order_by(models.File.path))
tasks = (models.Task.query
.filter(models.Task.playbook_id == playbook.id)
.order_by(models.Task.sortkey))
try:
data = (models.Data.query
.filter(models.Data.playbook_id == playbook.id)
.order_by(models.Data.key))
# If there are no results, don't return an empty query
if not data.count():
data = None
except models.NoResultFound:
data = None
if file_:
file_ = (models.File.query.get(file_))
if file_ is None:
abort(404)
tasks = (tasks
.join(models.File)
.filter(models.File.id == file_.id))
return render_template('playbook.html',
playbook=playbook,
plays=plays,
playbook_file=playbook_file,
files=files,
tasks=tasks,
data=data,
file_=file_)
@playbook.route('/<playbook>/results/')
@playbook.route('/<playbook>/host/<host>/')
@playbook.route('/<playbook>/host/<host>/<status>/')
@playbook.route('/<playbook>/play/<play>/')
@playbook.route('/<playbook>/task/<task>/')
def playbook_results(playbook, host=None, play=None, task=None, status=None):
playbook = models.Playbook.query.get(playbook)
if playbook is None:
abort(404)
def playbook_summary(playbook=None, file_=None, host=None, status=None,
play=None, task=None):
task_results = (models.TaskResult.query
.join(models.Task)
.join(models.Play)
.join(models.Host)
.join(models.Playbook)
.filter(models.Playbook.id == playbook.id)
.order_by(models.TaskResult.time_start))
hosts = None
host = host or request.args.get('host')
if host is not None:
hosts = [str(h) for h in host.split(',')]
task_results = (task_results
.filter(models.Host.name.in_(hosts)))
plays = None
play = play or request.args.get('play')
if play is not None:
plays = [str(h) for h in play.split(',')]
task_results = (task_results
.filter(models.Play.id.in_(plays)))
playbook_file = (models.File.query
.filter(models.File.playbook_id == playbook.id)
.filter(models.File.is_playbook.is_(True))).one()
files = (models.File.query
.filter(models.File.playbook_id == playbook.id)
.filter(models.File.is_playbook.is_(False))
.order_by(models.File.path))
task = task or request.args.get('task')
if task is not None:
tasks = [str(h) for h in task.split(',')]
task_results = (task_results
.filter(models.Task.id.in_(tasks)))
try:
data = (models.Data.query
.filter(models.Data.playbook_id == playbook.id)
.order_by(models.Data.key))
# If there are no results, don't return an empty query
if not data.count():
data = None
except models.NoResultFound:
data = None
# LKS: We're filtering this with Python rather than SQL. This
# may become relevant if we implement result paging.
status = status or request.args.get('status')
if status is not None:
status = status.split(',')
task_results = (res for res in task_results
if res.derived_status in status)
return render_template('playbook_results.html',
hosts=hosts,
plays=plays,
files=files,
data=data,
playbook=playbook,
playbook_file=playbook_file,
task_results=task_results)
return render_template('playbook_list.html')

51
ara/views/reports.py Normal file
View File

@@ -0,0 +1,51 @@
# Copyright 2017 Red Hat, Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from flask import render_template, Blueprint, current_app, redirect, url_for
from ara import models, utils
reports = Blueprint('reports', __name__)
@reports.route('/')
@reports.route('/<int:page>')
def report_list(page=1):
if current_app.config['ARA_PLAYBOOK_OVERRIDE'] is not None:
override = current_app.config['ARA_PLAYBOOK_OVERRIDE']
playbooks = (models.Playbook.query
.filter(models.Playbook.id.in_(override))
.order_by(models.Playbook.time_start.desc()))
else:
playbooks = (models.Playbook.query
.order_by(models.Playbook.time_start.desc()))
if not playbooks.count():
return redirect(url_for('home.main'))
playbook_per_page = current_app.config['ARA_PLAYBOOK_PER_PAGE']
# Paginate unless playbook_per_page is set to 0
if playbook_per_page >= 1:
playbooks = playbooks.paginate(page, playbook_per_page, False)
else:
playbooks = playbooks.paginate(page, None, False)
stats = utils.get_summary_stats(playbooks.items, 'playbook_id')
result_per_page = current_app.config['ARA_RESULT_PER_PAGE']
return render_template('report_list.html',
active='reports',
result_per_page=result_per_page,
playbooks=playbooks,
stats=stats)

View File

@@ -12,15 +12,36 @@
# License for the specific language governing permissions and limitations
# under the License.
from flask import render_template, abort, Blueprint
from flask import render_template, abort, Blueprint, current_app
from ara import models
result = Blueprint('result', __name__)
@result.route('/')
def index():
"""
This is not served anywhere in the web application.
It is used explicitly in the context of generating static files since
flask-frozen requires url_for's to crawl content.
url_for's are not used with result.show_result directly and are instead
dynamically generated through javascript for performance purposes.
"""
if current_app.config['ARA_PLAYBOOK_OVERRIDE'] is not None:
override = current_app.config['ARA_PLAYBOOK_OVERRIDE']
results = (models.TaskResult.query
.join(models.Task)
.filter(models.Task.playbook_id.in_(override)))
else:
results = models.TaskResult.query.all()
return render_template('task_result_index.html', results=results)
@result.route('/<task_result>/')
def show_result(task_result):
task_result = models.TaskResult.query.get(task_result)
if task_result is None:
abort(404)
return render_template('task_result.html', task_result=task_result)

View File

@@ -33,10 +33,11 @@ import ara.config
DEFAULT_APP_NAME = 'ara'
views = (
(ara.views.home, ''),
(ara.views.file, '/file'),
(ara.views.playbook, '/playbook'),
(ara.views.home, ''),
(ara.views.host, '/host'),
(ara.views.playbook, '/playbook'),
(ara.views.reports, '/reports'),
(ara.views.result, '/result'),
)

View File

@@ -113,6 +113,10 @@ Parameters and their defaults
+-------------------------------+--------------------------+-------------------------------------------+
| ARA_PLAYBOOK_OVERRIDE_ | playbook_override | None |
+-------------------------------+--------------------------+-------------------------------------------+
| ARA_PLAYBOOK_PER_PAGE_ | playbook_per_page | 10  |
+-------------------------------+--------------------------+-------------------------------------------+
| ARA_RESULT_PER_PAGE_ | result_per_page | 25  |
+-------------------------------+--------------------------+-------------------------------------------+
ARA_DIR
~~~~~~~
@@ -193,6 +197,30 @@ to the list of playbook IDs specified.
This is expected to be playbook IDs (ex: retrieved through
``ara playbook list``) in a comma-separated list.
ARA_PLAYBOOK_PER_PAGE
~~~~~~~~~~~~~~~~~~~~~
This is the amount of playbooks runs shown in a single page in the ARA web
interface. The default is ``10`` but you might want to tweak this number up
or down depending on the amount of hosts, tasks and task results contained in
your playbooks.
This directly influences the weight of the pages that will end up being
displayed. Setting this value too high might yield very heavy pages.
Set this parameter to ``0`` to disable playbook listing pagination entirely.
ARA_RESULT_PER_PAGE
~~~~~~~~~~~~~~~~~~~
This is the amount of results shown in a single page in the different data
tables such as hosts, plays and tasks of the ARA web interface.
The default is ``25`` but you might want to tweak this number up or down
depending on your preference.
This has no direct impact on the weight of the page being sent for the reports
as these data tables are rendered on the client side.
Set this parameter to ``0`` to disable pagination for results entirely.
The CLI client and the web application
--------------------------------------

View File

@@ -19,17 +19,18 @@ The Patternfly assets are imported directly from
.. _3.21.0: https://github.com/patternfly/patternfly/releases/tag/v3.21.0
.. _Patternfly's compiled source: https://github.com/patternfly/patternfly/tree/v3.21
jquery.matchHeight
~~~~~~~~~~~~~~~~~~
jquery.datatables
~~~~~~~~~~~~~~~~~
Patternfly's vertical navigation needs the `jquery.matchHeight`_ library.
The version of jquery.matchHeight is currently `0.7.0`_.
`jquery.datatables`_ is what provides the ability for the different tables
in the web interface to be searchable and sortable.
This is imported directly from the `jquery.matchHeight source`_.
The current version of jquery.datatables is 1.10.13.
.. _jquery.matchHeight: https://github.com/liabru/jquery-match-height
.. _0.7.0: https://github.com/liabru/jquery-match-height/releases/tag/0.7.0
.. _jquery.matchHeight source: https://github.com/liabru/jquery-match-height/tree/0.7.0
This is imported directly from the `jquery.datatables source`_.
.. _jquery.datatables: https://datatables.net/
.. _jquery.datatables source: https://cdn.datatables.net/
Bootstrap JS
~~~~~~~~~~~~

View File

@@ -107,6 +107,10 @@ ara stats show $(ara stats list -c ID -f value |head -n1)
ara task show $(ara task list -a -c ID -f value |head -n1)
ara file list -b $pbid
ara file show $(ara file list -b $pbid -c ID -f value|head -n1)
# We want to test pagination in html generation
export ARA_PLAYBOOK_PER_PAGE=3
export ARA_RESULT_PER_PAGE=20
ara generate html ${LOGDIR}/build
ara generate html ${LOGDIR}/build-playbook --playbook $pbid
ara generate junit ${LOGDIR}/junit.xml