diff options
8 files changed, 268 insertions, 234 deletions
diff --git a/modules-available/statistics_reporting/page.inc.php b/modules-available/statistics_reporting/page.inc.php index 81d44e15..235cebb7 100644 --- a/modules-available/statistics_reporting/page.inc.php +++ b/modules-available/statistics_reporting/page.inc.php @@ -4,7 +4,22 @@ class Page_Statistics_Reporting extends Page { - private $action = false; + private $action; + private $type; + + // "Constants" + private $days; + + /** + * @var array Names of columns that are being used by the various tables + */ + private $COLUMNS = array('col_lastlogout', 'col_laststart', 'col_location', 'col_longsessions', 'col_mediantime', + 'col_sessions', 'col_shortsessions', 'col_timeoffline', 'col_totaltime'); + + /** + * @var array Names of the tables we can display + */ + private $TABLES = array('total', 'location', 'client', 'user', 'vm'); /** * Called before any page rendering happens - early hook to check parameters etc. @@ -19,6 +34,15 @@ class Page_Statistics_Reporting extends Page } $this->action = Request::any('action', 'show', 'string'); + $this->type = Request::get('type', 'total', 'string'); + $this->days = Request::get('cutoff', 7, 'int'); + $this->lower = Request::get('lower', 8, 'int'); + $this->upper = Request::get('upper', 20, 'int'); + + if (!in_array($this->type, $this->TABLES)) { + Message::addError('invalid-table-type', $this->type); + $this->type = 'total'; + } } /** @@ -27,20 +51,67 @@ class Page_Statistics_Reporting extends Page protected function doRender() { if ($this->action === 'show') { + + /* + * Leave these here for the translate module + * Dictionary::translate('col_lastlogout'); + * Dictionary::translate('col_laststart'); + * Dictionary::translate('col_location'); + * Dictionary::translate('col_longsessions'); + * Dictionary::translate('col_mediantime'); + * Dictionary::translate('col_sessions'); + * Dictionary::translate('col_shortsessions'); + * Dictionary::translate('col_timeoffline'); + * Dictionary::translate('col_totaltime'); + * Dictionary::translate('table_total'); + * Dictionary::translate('table_location'); + * Dictionary::translate('table_client'); + * Dictionary::translate('table_user'); + * Dictionary::translate('table_vm'); + */ + + $data = array( + 'columns' => array(), + 'tables' => array(), + 'days' => array() + ); + + foreach ($this->COLUMNS as $column) { + $data['columns'][] = array( + 'id' => $column, + 'name' => Dictionary::translate($column, true), + 'checked' => Request::get($column, 'on', 'string') === 'on' ? 'checked' : '', + ); + } + + foreach ($this->TABLES as $table) { + $data['tables'][] = array( + 'name' => Dictionary::translate('table_' . $table, true), + 'value' => $table, + 'selected' => ($this->type === $table) ? 'selected' : '', + ); + } + + foreach (array(1,2,5,7,14,30,90) as $day) { + $data['days'][] = array( + 'days' => $day, + 'selected' => ($day === $this->days) ? 'selected' : '', + ); + } + + $data['lower'] = $this->lower; + $data['upper'] = $this->upper; + + Render::addTemplate('columnChooser', $data); + // timespan you want to see. default = last 7 days - GetData::$from = strtotime("- " . (Request::get('cutoff', 7, 'int') - 1) . " days 00:00:00"); + GetData::$from = strtotime("- " . ($this->days - 1) . " days 00:00:00"); GetData::$to = time(); - GetData::$lowerTimeBound = Request::get('lower', 0, 'int'); - GetData::$upperTimeBound = Request::get('upper', 24, 'int'); - - $data = GetData::total(GETDATA_PRINTABLE); - $data['perLocation'] = GetData::perLocation(GETDATA_PRINTABLE); - $data['perClient'] = GetData::perClient(GETDATA_PRINTABLE); - $data['perUser'] = GetData::perUser(GETDATA_PRINTABLE); - $data['perVM'] = GetData::perVM(GETDATA_PRINTABLE); + GetData::$lowerTimeBound = $this->lower; + GetData::$upperTimeBound = $this->upper; - Render::addTemplate('columnChooser'); - Render::addTemplate('_page', $data); + $data['data'] = $this->fetchData(GETDATA_PRINTABLE); + Render::addTemplate('table-' . $this->type, $data); } } @@ -63,4 +134,20 @@ class Page_Statistics_Reporting extends Page } } + private function fetchData($flags) + { + switch ($this->type) { + case 'total': + return GetData::total($flags); + case 'location': + return GetData::perLocation($flags); + case 'client': + return GetData::perClient($flags); + case 'user': + return GetData::perUser($flags); + case 'vm': + return GetData::perVM($flags); + } + } + } diff --git a/modules-available/statistics_reporting/templates/_page.html b/modules-available/statistics_reporting/templates/_page.html deleted file mode 100644 index 1f4ac52c..00000000 --- a/modules-available/statistics_reporting/templates/_page.html +++ /dev/null @@ -1,111 +0,0 @@ -<table id="table-total" class="table table-condensed table-striped"> - <thead> - <tr> - <th class="text-left col-md-2"></th> - <th class="text-left column-totaltime">{{lang_totalTime}}</th> - <th class="text-left column-mediantime">{{lang_medianSessionLength}}</th> - <th class="text-left column-longsessions">{{lang_longSessions}}</th> - <th class="text-left column-shortsessions">{{lang_shortSessions}}</th> - <th class="text-left column-timeoffline">{{lang_overallOfftime}}</th> - </tr> - </thead> - <tbody> - <tr> - <th class="text-left">{{lang_total}}</th> - <td class="text-left column-totaltime">{{time_s}}</td> - <td class="text-left column-mediantime">{{medianTime_s}}</td> - <td class="text-left column-longsessions">{{sessions}}</td> - <td class="text-left column-shortsessions">{{shortSessions}}</td> - <td class="text-left column-timeoffline">{{totalOfftime_s}}</td> - </tr> - </tbody> -</table> -<table id="table-perlocation" class="table table-condensed table-striped"> - <thead> - <tr> - <th data-sort="string" class="text-left col-md-2">{{lang_location}}</th> - <th data-sort="int" data-sort-default="desc" class="text-left column-totaltime">{{lang_totalTime}}</th> - <th data-sort="int" data-sort-default="desc" class="text-left column-mediantime">{{lang_medianSessionLength}}</th> - <th data-sort="int" data-sort-default="desc" class="text-left column-longsessions">{{lang_longSessions}}</th> - <th data-sort="int" data-sort-default="desc" class="text-left column-shortsessions">{{lang_shortSessions}}</th> - <th data-sort="int" data-sort-default="desc" class="text-left column-timeoffline">{{lang_totalOffTime}}</th> - </tr> - </thead> - <tbody> - {{#perLocation}} - <tr> - <td class="locationName text-left">{{location}}</td> - <td data-sort-value="{{time}}" class="text-left column-totaltime">{{time_s}}</td> - <td data-sort-value="{{medianTime}}" class="text-left column-mediantime">{{medianTime_s}}</td> - <td class="text-left column-longsessions">{{sessions}}</td> - <td class="text-left column-shortsessions">{{shortSessions}}</td> - <td data-sort-value="{{offTime}}" class="text-left column-timeoffline">{{offTime_s}}</td> - </tr> - {{/perLocation}} - </tbody> -</table> -<table id="table-perclient" class="table table-condensed table-striped"> - <thead> - <tr> - <th data-sort="string" class="text-left col-md-4">{{lang_hostname}}</th> - <th data-sort="string" class="text-left column-location">{{lang_location}}</th> - <th data-sort="int" data-sort-default="desc" class="text-left column-totaltime">{{lang_totalTime}}</th> - <th data-sort="int" data-sort-default="desc" class="text-left column-mediantime">{{lang_medianSessionLength}}</th> - <th data-sort="int" data-sort-default="desc" class="text-left column-longsessions">{{lang_longSessions}}</th> - <th data-sort="int" data-sort-default="desc" class="text-left column-shortsessions">{{lang_shortSessions}}</th> - <th data-sort="int" data-sort-default="desc" class="text-left column-timeoffline">{{lang_totalOffTime}}</th> - <th data-sort="int" data-sort-default="desc" class="text-left column-lastlogout">{{lang_clientLogout}}</th> - <th data-sort="int" data-sort-default="desc" class="text-left column-laststart">{{lang_clientStart}}</th> - </tr> - </thead> - <tbody> - {{#perClient}} - <tr> - <td class="text-left">{{hostname}}</td> - <td class="text-left column-location"><a class="locationLink" href="#">{{location}}</a></td> - <td data-sort-value="{{time}}" class="text-left column-totaltime">{{time_s}}</td> - <td data-sort-value="{{medianTime}}" class="text-left column-mediantime">{{medianTime_s}}</td> - <td class="text-left column-longsessions">{{sessions}}</td> - <td class="text-left column-shortsessions">{{shortSessions}}</td> - <td data-sort-value="{{offTime}}" class="text-left column-timeoffline">{{offTime_s}}</td> - <td data-sort-value="{{lastLogout}}" class="text-left column-lastlogout">{{lastLogout_s}}</td> - <td data-sort-value="{{lastStart}}" class="text-left column-laststart">{{lastStart_s}}</td> - </tr> - {{/perClient}} - </tbody> -</table> -<table id="table-peruser" class="table table-condensed table-striped"> - <thead> - <tr> - <th data-sort="string" class="text-left col-md-4">{{lang_user}}</th> - <th data-sort="int" data-sort-default="desc" class="text-left column-sessions">{{lang_sessions}}</th> - </tr> - </thead> - <tbody> - {{#perUser}} - <tr> - <td class="text-left">{{user}}</td> - <td class="text-left column-sessions">{{sessions}}</td> - </tr> - {{/perUser}} - </tbody> -</table> -<table id="table-pervm" class="table table-condensed table-striped"> - <thead> - <tr> - <th data-sort="string" class="text-left col-md-4">{{lang_vm}}</th> - <th data-sort="int" data-sort-default="desc" class="text-left column-sessions">{{lang_sessions}}</th> - </tr> - </thead> - <tbody> - {{#perVM}} - <tr> - <td class="text-left">{{vm}}</td> - <td class="text-left column-sessions">{{sessions}}</td> - </tr> - {{/perVM}} - </tbody> -</table> - - - diff --git a/modules-available/statistics_reporting/templates/columnChooser.html b/modules-available/statistics_reporting/templates/columnChooser.html index c51250bd..b5cf4ec8 100644 --- a/modules-available/statistics_reporting/templates/columnChooser.html +++ b/modules-available/statistics_reporting/templates/columnChooser.html @@ -1,41 +1,39 @@ <div class="container-fluid"> - <div class="row form-inline"> - <div class="col-md-12 top-row"> - <select id="select-table" onchange="chooseTable(this.value)" class="form-control"> - <option value="total">{{lang_total}}</option> - <option value="perlocation">{{lang_perlocation}}</option> - <option value="perclient">{{lang_perclient}}</option> - <option value="peruser">{{lang_peruser}}</option> - <option value="pervm">{{lang_pervm}}</option> - </select> - <select id="select-cutoff" onchange="reloadPage()" class="form-control"> - <option value="1">{{lang_last1}}</option> - <option value="2">{{lang_last2}}</option> - <option value="7">{{lang_last7}}</option> - <option value="14">{{lang_last14}}</option> - <option value="30">{{lang_last30}}</option> - <option value="90">{{lang_last90}}</option> - </select> - - <div id="slider"> - <div id="lower-handle" class="ui-slider-handle"></div> - <div id="upper-handle" class="ui-slider-handle"></div> + <form method="get"> + <input type="hidden" name="do" value="statistics_reporting"> + <div class="row form-inline"> + <div class="col-md-12 top-row"> + <select name="type" id="select-table" class="form-control"> + {{#tables}} + <option value="{{value}}" {{selected}}>{{name}}</option> + {{/tables}} + </select> + <select name="cutoff" id="select-cutoff" class="form-control"> + {{#days}} + <option value="{{days}}" {{selected}}>{{lang_days}}: {{days}}</option> + {{/days}} + </select> + + <div id="slider"> + <div id="lower-handle" class="ui-slider-handle"></div> + <div id="upper-handle" class="ui-slider-handle"></div> + <input type="hidden" id="lower-field" name="lower" value="{{lower}}"> + <input type="hidden" id="upper-field" name="upper" value="{{upper}}"> + </div> + <button type="submit" class="btn btn-sm btn-primary">{{lang_apply}}</button> + <button id="button-settings" type="button" class="pull-right btn btn-default" data-toggle="modal" data-target="#modal-settings" onclick="loadSettings()"><span class="glyphicon glyphicon-cog"></span></button> + </div> + <div class="col-md-12"> + {{#columns}} + <input type="hidden" name="{{id}}" value="off"> + <div class="checkbox"> + <input id="{{id}}" name="{{id}}" type="checkbox" class="column-toggle form-control" {{checked}}> + <label for="{{id}}">{{name}}</label> + </div> + {{/columns}} </div> - <button id="applybound" type="button" class="btn btn-sm btn-primary collapse" onclick="reloadPage()">{{lang_apply}}</button> - <button id="button-settings" type="button" class="pull-right btn btn-default" data-toggle="modal" data-target="#modal-settings" onclick="loadSettings()"><span class="glyphicon glyphicon-cog"></span></button> - </div> - <div class="col-md-12 buttonbar"> - <button id="button-location" type="button" class="column-toggle btn btn-primary" onclick="toggleButton('location')">{{lang_location}}</button> - <button id="button-totaltime" type="button" class="column-toggle btn btn-primary" onclick="toggleButton('totaltime')">{{lang_totalTime}}</button> - <button id="button-mediantime" type="button" class="column-toggle btn btn-primary" onclick="toggleButton('mediantime')">{{lang_medianSessionLength}}</button> - <button id="button-sessions" type="button" class="column-toggle btn btn-primary" onclick="toggleButton('sessions')">{{lang_sessions}}</button> - <button id="button-longsessions" type="button" class="column-toggle btn btn-primary" onclick="toggleButton('longsessions')">{{lang_longSessions}}</button> - <button id="button-shortsessions" type="button" class="column-toggle btn btn-primary" onclick="toggleButton('shortsessions')">{{lang_shortSessions}}</button> - <button id="button-timeoffline" type="button" class="column-toggle btn btn-primary" onclick="toggleButton('timeoffline')">{{lang_totalOffTime}}</button> - <button id="button-lastlogout" type="button" class="column-toggle btn btn-primary" onclick="toggleButton('lastlogout')">{{lang_clientLogout}}</button> - <button id="button-laststart" type="button" class="column-toggle btn btn-primary" onclick="toggleButton('laststart')">{{lang_clientStart}}</button> </div> - </div> + </form> </div> <div id="modal-settings" class="modal fade" role="dialog"> @@ -62,31 +60,29 @@ </div> <script type="application/javascript"> + document.addEventListener("DOMContentLoaded", function () { var lowerHandle = $("#lower-handle"); var upperHandle = $("#upper-handle"); - var lower = getQueryVariable("lower"); - var upper = getQueryVariable("upper"); + var lower = $('#lower-field').val(); + var upper = $('#upper-field').val(); $( "#slider" ).slider({ range: true, min: 0, max: 24, - values: [ lower ? lower : 8, upper ? upper : 22 ], + values: [ lower, upper ], create: function() { - lowerHandle.text( $(this).slider("values")[0]+":00" ); - upperHandle.text( $(this).slider("values")[1]+":00" ); + lowerHandle.text( lower+":00" ); + upperHandle.text( upper+":00" ); }, slide: function(event, ui) { lowerHandle.text(ui.values[0]+":00"); upperHandle.text(ui.values[1]+":00"); + $('#lower-field').val(ui.values[0]); + $('#upper-field').val(ui.values[1]); }, - change: function(event, ui) { - $("#applybound").show() - } }); - loadForm(); - var table = $("table").stupidtable(); table.on("aftertablesort", function (event, data) { var th = $(this).find("th"); @@ -105,76 +101,27 @@ target.parent().css( "background-color", "#f8ff99" ); return false; }); - }); - - function chooseTable(v) { - $("tr").removeAttr('style'); - $("[id^=table-]").hide(); - $('#table-'+v).show(); - $('.buttonbar').find("[id^=button-]").hide(); - var re = /column-(\w+)/g; - var match; - while (match = re.exec($('thead', '#table-'+v).html())) { - $('#button-'+match[1]).show(); - } - } - - function reloadPage() { - saveForm(); - var cutoff = $("#select-cutoff").val(); - var lower = $("#lower-handle").text().split(":")[0]; - var upper = $("#upper-handle").text().split(":")[0]; - window.location.replace("?do=statistics_reporting&cutoff="+cutoff+"&lower="+lower+"&upper="+upper); - } - - function toggleButton(v) { - var button = $('#button-'+v); - if(button.hasClass('btn-default')) { - button.removeClass('btn-default'); - button.addClass('btn-primary'); - $('.column-'+v).show() - } else { - button.removeClass('btn-primary'); - button.addClass('btn-default'); - $('.column-'+v).hide() - } - } - function saveForm() { - sessionStorage.setItem("select-table", $("#select-table").val()); - sessionStorage.setItem("buttons", $('.buttonbar').find(".btn-default").map(function() { return this.id; }).get().join(" ")); - } + $('.column-toggle').change(function () { + updateColumn($(this)); + }); + $('.column-toggle').each(function () { + var box = $(this); + if ($('.' + box.attr('name')).length === 0) { + box.parent().hide(); + } else { + updateColumn(box); + } + }); + }); - function loadForm() { - var selectTable = sessionStorage.getItem("select-table"); - if (selectTable) { - $("#select-table").val(selectTable); - chooseTable(selectTable); + function updateColumn(checkbox) { + var cols = $('.' + checkbox.attr('name')); + if (checkbox.is(':checked')) { + cols.show(); } else { - $("#select-table").val("total"); - chooseTable("total"); - } - - var cutoff = getQueryVariable("cutoff"); - $('#select-cutoff').val(cutoff ? cutoff : 7); - - var buttons = sessionStorage.getItem("buttons"); - if (buttons) { - buttons.split(" ").forEach(function (button) { - toggleButton(button.substr(7)); - }); - } - } - - function getQueryVariable(variable) - { - var query = window.location.search.substring(1); - var vars = query.split("&"); - for (var i=0;i<vars.length;i++) { - var pair = vars[i].split("="); - if(pair[0] == variable){return pair[1];} + cols.hide(); } - return(false); } function loadSettings() { diff --git a/modules-available/statistics_reporting/templates/table-client.html b/modules-available/statistics_reporting/templates/table-client.html new file mode 100644 index 00000000..e4538da6 --- /dev/null +++ b/modules-available/statistics_reporting/templates/table-client.html @@ -0,0 +1,30 @@ +<table id="table-perclient" class="table table-condensed table-striped"> + <thead> + <tr> + <th data-sort="string" class="text-left col-md-4">{{lang_hostname}}</th> + <th data-sort="string" class="text-left col_location">{{lang_location}}</th> + <th data-sort="int" data-sort-default="desc" class="text-left col_totaltime">{{lang_totalTime}}</th> + <th data-sort="int" data-sort-default="desc" class="text-left col_mediantime">{{lang_medianSessionLength}}</th> + <th data-sort="int" data-sort-default="desc" class="text-left col_longsessions">{{lang_longSessions}}</th> + <th data-sort="int" data-sort-default="desc" class="text-left col_shortsessions">{{lang_shortSessions}}</th> + <th data-sort="int" data-sort-default="desc" class="text-left col_timeoffline">{{lang_totalOffTime}}</th> + <th data-sort="int" data-sort-default="desc" class="text-left col_lastlogout">{{lang_clientLogout}}</th> + <th data-sort="int" data-sort-default="desc" class="text-left col_laststart">{{lang_clientStart}}</th> + </tr> + </thead> + <tbody> + {{#data}} + <tr> + <td class="text-left">{{hostname}}</td> + <td class="text-left col_location"><a class="locationLink" href="#">{{location}}</a></td> + <td data-sort-value="{{time}}" class="text-left col_totaltime">{{time_s}}</td> + <td data-sort-value="{{medianTime}}" class="text-left col_mediantime">{{medianTime_s}}</td> + <td class="text-left col_longsessions">{{sessions}}</td> + <td class="text-left col_shortsessions">{{shortSessions}}</td> + <td data-sort-value="{{offTime}}" class="text-left col_timeoffline">{{offTime_s}}</td> + <td data-sort-value="{{lastLogout}}" class="text-left col_lastlogout">{{lastLogout_s}}</td> + <td data-sort-value="{{lastStart}}" class="text-left col_laststart">{{lastStart_s}}</td> + </tr> + {{/data}} + </tbody> +</table> diff --git a/modules-available/statistics_reporting/templates/table-location.html b/modules-available/statistics_reporting/templates/table-location.html new file mode 100644 index 00000000..55fa8e6f --- /dev/null +++ b/modules-available/statistics_reporting/templates/table-location.html @@ -0,0 +1,24 @@ +<table id="table-perlocation" class="table table-condensed table-striped"> + <thead> + <tr> + <th data-sort="string" class="text-left col-md-2">{{lang_location}}</th> + <th data-sort="int" data-sort-default="desc" class="text-left col_totaltime">{{lang_totalTime}}</th> + <th data-sort="int" data-sort-default="desc" class="text-left col_mediantime">{{lang_medianSessionLength}}</th> + <th data-sort="int" data-sort-default="desc" class="text-left col_longsessions">{{lang_longSessions}}</th> + <th data-sort="int" data-sort-default="desc" class="text-left col_shortsessions">{{lang_shortSessions}}</th> + <th data-sort="int" data-sort-default="desc" class="text-left col_timeoffline">{{lang_totalOffTime}}</th> + </tr> + </thead> + <tbody> + {{#data}} + <tr> + <td class="locationName text-left">{{location}}</td> + <td data-sort-value="{{time}}" class="text-left col_totaltime">{{time_s}}</td> + <td data-sort-value="{{medianTime}}" class="text-left col_mediantime">{{medianTime_s}}</td> + <td class="text-left col_longsessions">{{sessions}}</td> + <td class="text-left col_shortsessions">{{shortSessions}}</td> + <td data-sort-value="{{offTime}}" class="text-left col_timeoffline">{{offTime_s}}</td> + </tr> + {{/data}} + </tbody> +</table> diff --git a/modules-available/statistics_reporting/templates/table-total.html b/modules-available/statistics_reporting/templates/table-total.html new file mode 100644 index 00000000..8e24dc01 --- /dev/null +++ b/modules-available/statistics_reporting/templates/table-total.html @@ -0,0 +1,22 @@ +<table id="table-total" class="table table-condensed table-striped"> + <thead> + <tr> + <th class="text-left col-md-2"></th> + <th class="text-left col_totaltime">{{lang_totalTime}}</th> + <th class="text-left col_mediantime">{{lang_medianSessionLength}}</th> + <th class="text-left col_longsessions">{{lang_longSessions}}</th> + <th class="text-left col_shortsessions">{{lang_shortSessions}}</th> + <th class="text-left col_timeoffline">{{lang_overallOfftime}}</th> + </tr> + </thead> + <tbody> + <tr> + <th class="text-left">{{lang_total}}</th> + <td class="text-left col_totaltime">{{data.time_s}}</td> + <td class="text-left col_mediantime">{{data.medianTime_s}}</td> + <td class="text-left col_longsessions">{{data.sessions}}</td> + <td class="text-left col_shortsessions">{{data.shortSessions}}</td> + <td class="text-left col_timeoffline">{{data.totalOfftime_s}}</td> + </tr> + </tbody> +</table> diff --git a/modules-available/statistics_reporting/templates/table-user.html b/modules-available/statistics_reporting/templates/table-user.html new file mode 100644 index 00000000..5c2ba56f --- /dev/null +++ b/modules-available/statistics_reporting/templates/table-user.html @@ -0,0 +1,16 @@ +<table id="table-peruser" class="table table-condensed table-striped"> + <thead> + <tr> + <th data-sort="string" class="text-left col-md-4">{{lang_user}}</th> + <th data-sort="int" data-sort-default="desc" class="text-left col_sessions">{{lang_sessions}}</th> + </tr> + </thead> + <tbody> + {{#data}} + <tr> + <td class="text-left">{{user}}</td> + <td class="text-left col_sessions">{{sessions}}</td> + </tr> + {{/data}} + </tbody> +</table> diff --git a/modules-available/statistics_reporting/templates/table-vm.html b/modules-available/statistics_reporting/templates/table-vm.html new file mode 100644 index 00000000..9a775709 --- /dev/null +++ b/modules-available/statistics_reporting/templates/table-vm.html @@ -0,0 +1,19 @@ +<table id="table-pervm" class="table table-condensed table-striped"> + <thead> + <tr> + <th data-sort="string" class="text-left col-md-4">{{lang_vm}}</th> + <th data-sort="int" data-sort-default="desc" class="text-left col_sessions">{{lang_sessions}}</th> + </tr> + </thead> + <tbody> + {{#data}} + <tr> + <td class="text-left">{{vm}}</td> + <td class="text-left col_sessions">{{sessions}}</td> + </tr> + {{/data}} + </tbody> +</table> + + + |