From cc68fbc644d409ffd96740d4a5e33d4e360a3cb2 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Thu, 25 Jun 2020 14:00:55 +0100 Subject: [PATCH 1/4] Squashed commit of the following: commit 84c75c17f76cff7e576402dd2a77eb52717e3758 Author: Athanasius Date: Wed Jun 24 20:00:58 2020 +0100 Software jsGrid now uses cached data when sorting/drill changed commit a9ed1d902c8301d62348eaa684eda5010a846ca1 Author: Athanasius Date: Wed Jun 24 16:49:47 2020 +0100 Convert Schemas to jsGrid commit 26fe4a7e0c997d71411d5159adafad24cff5dc74 Author: Athanasius Date: Wed Jun 24 16:20:57 2020 +0100 Change schemas HTML to just div.tables commit 45c86a901f8627a23f9b5c573b15c615cff40d8f Author: Athanasius Date: Wed Jun 24 16:19:34 2020 +0100 Remove defunct Uploaders section from index.html commit 3680197f86c805a1472f2538f3135c8698201f4d Author: Athanasius Date: Wed Jun 24 15:43:34 2020 +0100 Style today/yesterday "0" as per boostrap td.warning The elements/classes/IDs are different so can't just have it use the actual bootstrap CSS. commit ef855586305033854a3e74663f607c3ff68d3534 Author: Athanasius Date: Wed Jun 24 15:17:20 2020 +0100 Sanitises producing the Softwares data array We were first making a dictionary only to immediately use it to make an array for jsgrid/highcharts to use, then never using the dictionary again. So, just generate the array to start with, no intermediate step. This also sanitised some of the variable names so it's more obvious what data they hold. commit d5be067bb3bbe8837909e437f6ed099c65f66be7 Author: Athanasius Date: Tue Jun 23 20:10:20 2020 +0100 Set padding on td elements to get size correct No need for that 37px line-height, it was a consequence of this padding on non-jsgrid table cells. commit 109540131361cb6b44f55c506d913da6d43f3af2 Author: Athanasius Date: Tue Jun 23 19:58:32 2020 +0100 Clicking back out of Software drilldown working * Also fixes CSS so all jsgrid rows are that 37px height. commit f4c27fb53b33c3c28b7ad60b8e908a5dc330cce5 Author: Athanasius Date: Tue Jun 23 19:39:02 2020 +0100 'side square' colouring on sorts and drilldown commit 6c79f4b92bf1f9c73f760588e78ff874db2254a6 Author: Athanasius Date: Tue Jun 23 18:43:53 2020 +0100 Drilldown *in* for Software -> Versions now works commit 353b379d5a49aab826908f2c95f758d5fdd0114e Author: Athanasius Date: Tue Jun 23 17:56:54 2020 +0100 Starts work on displaying software -> version table drilldowns if/else in place, and the else handling the software*names*/totals table still works. commit fe2f6f0e73787512c8b5ca511871792a393894ce Author: Athanasius Date: Tue Jun 23 16:58:32 2020 +0100 Sort order on different column is now the same as on previous column If we start on Today/Ascending then: Yesterday -> Yesterday/Asc Total -> Total/Asc Total -> Total/Desc Today -> Today/Desc Today -> Today/Asc Yesterday -> Yesterday/Asc Total -> Total/Asc Total -> Total/Desc i.e. it takes a second click on a column to change to the opposite sort order, not always defaulting to 'Asc' when you change column. commit 9c471d2cebb5e26bca64f968b6846279fb488d84 Author: Athanasius Date: Tue Jun 23 16:37:26 2020 +0100 Now changing the pie chart depending on current sort column commit c1f07a38700860a859647cd563f0133965d9068d Author: Athanasius Date: Tue Jun 23 12:24:33 2020 +0100 Remember prior 'softwares' sort and re-apply for new data This defaults on first load as per: var softwaresSort = ... commit fa560cadd754fd5a934b9c926befe4d40651aa61 Author: Athanasius Date: Tue Jun 23 08:42:44 2020 +0100 Sane initial sorting, and some cleanup. * We can specify a sort column and order, no need for the 'reverse' hack, which is just going to confuse users and doesn't cause initial sorting anyway. * The softwaresTotal list can just be an Array, and then actually matches what we were doing with jsGridSoftwareByName[] anyway, so just use it. commit 05cc80d09b8107b94313481d20fdf3a94bd71b7a Author: Athanasius Date: Mon Jun 22 21:53:59 2020 +0100 Documents the data, and why it's in different formats commit b567a1b452a5685fe296c9d8a3eb7aaae35d291f Author: Athanasius Date: Mon Jun 22 21:43:30 2020 +0100 Remove the long-since defunct dataUploaders bits commit 282dd9df4d5200a3cc59e21005275440e1550464 Author: Athanasius Date: Mon Jun 22 21:41:13 2020 +0100 Moving closer to understanding the data. * We'll now use an id'd div to put in a jsGrid set of divs per: 1) The totals per software 2) Each software's versions counts Currently this is working to display the software name totals, and attempts to add the per-software version-counts tables, but treats the data incorrectly. commit 5bcf4fb0a23315f2d3029f0f328a5c7a4a8bdf1a Author: Athanasius Date: Mon Jun 22 21:40:24 2020 +0100 div for software tables needs an id, not a class. We want to put one div inside it per overal softwares and then one per specific software. commit 90e1a34d4cda8e960dcd13b1f7000af8904a614e Author: Athanasius Date: Mon Jun 22 18:16:56 2020 +0100 Main Software table and pie chart rendering correctly * Having to force the row line-height to what FF ends up with in the old code, which is ~37px, not the ~20px specified. * Applying data-name to the .square elements so as to then later set their background colour. Can't do this as they're created as the data isn't yet in place to lookup the colour. * Removes the commented out old version of all that code, as it's now jsgrid-ised in that rowRenderer. commit dfd2d09513fb4dafe1bc4ec0e3a9fb32ba7518c1 Author: Athanasius Date: Mon Jun 22 17:41:08 2020 +0100 Fix rendering of custom rowRenderer Software rows * As jsGrid adds its own table there's no need to have our own, just use the .table-responsive div * Use a rowRenderer function based on the existing code to make the row * But that means needing to ensure the styling manually, as jsgrid does one table for its header and another for the data. It would apply the configured styling to botj, but with the custom rowRenderer that doesn't happen. commit 70bb2e822e4e5d8253f308b4c862127126731637 Author: Athanasius Date: Mon Jun 22 16:55:20 2020 +0100 Enable the Software main pie chart to actually render * The code to add the data points to it initially had been commented out. * That 'sort the data' also is the only thing populating softwaresTotal. commit 61e1a17a1fba63cdcc7ce016b304c39670d90a11 Author: Athanasius Date: Mon Jun 22 16:04:51 2020 +0100 CSS: Prevent jsGrid tables from showing un-necessary scrollbars commit 1109bf57122428b6603fcea9836f0b95c36ee31b Author: Athanasius Date: Mon Jun 22 15:49:26 2020 +0100 Placeholder to implement clicking into drilldowns For now I want to just get the generation of drilldowns converted to jsGrid. Then I'll look into hooking up clicking the softwareName causing switching to displaying its drilldown. commit 87b78c98a0f59f10010c29abc3ee40f1ca3536e1 Author: Athanasius Date: Mon Jun 22 14:54:30 2020 +0100 Numeric sorting now DESC first-click commit 571a058a3fa3658ef4b7c4f3f40beab37585eb33 Author: Athanasius Date: Mon Jun 22 14:13:56 2020 +0100 More progress. Headings, comma-formatted numbers, widths... * Column added for the high-chart colours, not yet implemented fully. This will rely on implementing a custom jsGrid field. * comma-separated thousands formatting on the numbers. * Some width settings to match the pre-jsGrid layout. commit a73f069d014c9ff5ea98158afe4ea993f2109a2c Author: Athanasius Date: Mon Jun 22 12:52:12 2020 +0100 Replace Software table with sortable jsgrid This is very rough and ready, but works: * Defaults to no sort * Sort will initially be ascending, we want descending * All the old styling is lost * Not yet handling click-through to per-softwareName version tables commit 2afb5f435617ed4fec9771a0f5e95ceef5fdd671 Author: Athanasius Date: Mon Jun 22 12:51:52 2020 +0100 Include jsgrid css and js --- contrib/monitor/css/eddn.css | 21 +- contrib/monitor/index.html | 93 +--- contrib/monitor/js/eddn.js | 872 ++++++++++++++++++++++------------- 3 files changed, 586 insertions(+), 400 deletions(-) diff --git a/contrib/monitor/css/eddn.css b/contrib/monitor/css/eddn.css index a53f512..090041d 100644 --- a/contrib/monitor/css/eddn.css +++ b/contrib/monitor/css/eddn.css @@ -24,4 +24,23 @@ footer .navbar-default { margin-bottom: 0; } -.highcharts-container{width:100% !important; height:100% !important;} \ No newline at end of file +.highcharts-container{width:100% !important; height:100% !important;} + +.jsgrid { + /* Same as bootstrap CSS sets on body */ + font-size: 14px; +} + +.jsgrid-grid-header, +.jsgrid-grid-body{ + overflow: auto; +} + +tr.jsgrid-row > td, tr.jsgrid-alt-row > td { + padding: 8px; +} + +/* Style the zero boxes as per bootstrap td.warning */ +tr.jsgrid-row > td.warning { + background-color:#faf2cc; +} diff --git a/contrib/monitor/index.html b/contrib/monitor/index.html index 5de73bd..34421fc 100644 --- a/contrib/monitor/index.html +++ b/contrib/monitor/index.html @@ -8,6 +8,9 @@ + + + @@ -18,6 +21,7 @@ + @@ -315,21 +319,7 @@
-
- - - - - - - - - - - - - -
Today hitsYesterday hitsTotal hits
+
@@ -354,61 +344,6 @@
- -
@@ -423,22 +358,8 @@
-
- - - - - - - - - - - - - -
Today hitsYesterday hitsTotal hits
-
+
+
diff --git a/contrib/monitor/js/eddn.js b/contrib/monitor/js/eddn.js index 78d07c6..9f2847e 100644 --- a/contrib/monitor/js/eddn.js +++ b/contrib/monitor/js/eddn.js @@ -1,3 +1,5 @@ +/* vim: wrapmargin=0 textwidth=0 tabstop=4 softtabstop=4 expandtab shiftwidth=4 + */ var updateInterval = 60000, monitorEndPoint = 'https://eddn.edcd.io:9091/', @@ -72,9 +74,340 @@ secondsToDurationString = function(seconds) { var drillDownSoftware = false; var currentDrillDown = false; -var softwaresTotal = {}; +var softwaresSort = { field: 'today', order: 'desc' }; // Very first load sort order +var softwaresData = new Array(); // The last data we got from API +var softwaresViewData = new Array(); // The data for the current view var softwaresVersion = {}; +var softwaresJsGridDataController = function () { + console.log('softwares -> jsGrid.controller.loadData() returning %o', softwaresViewData); + return softwaresViewData; +}; + +/* + * Create a new jsGrid and HighChart for Softwares + */ +var softwaresNewJsGrid = function () { + var chart = $('#software .chart').highcharts(), + series = chart.get('softwares'); + var newJsGrid; + if (currentDrillDown) { + newJsGrid = $("#table-softwares").jsGrid({ + width: "100%", + + filtering: false, + inserting: false, + editing: false, + sorting: true, + + controller: { + loadData: softwaresJsGridDataController, + }, + + fields: [ + { + title: "", + width: "30px", + sorting: false, + readOnly: true, + }, + { + title: currentDrillDown, + width: "50%", + name: "name", + type: "text", + align: "left", + readOnly: true, + }, + { + title: "Today hits", + name: "today", + type: "number", + align: "right", + readOnly: true, + css: "stat today", + itemTemplate: formatNumberJsGrid, + }, + { + title: "Yesterday hits", + name: "yesterday", + type: "number", + align: "right", + readOnly: true, + css: "stat yesterday", + itemTemplate: formatNumberJsGrid, + }, + { + title: "Total hits", + name: "total", + type: "number", + align: "right", + readOnly: true, + css: "stat total", + itemTemplate: formatNumberJsGrid, + }, + ], + + rowRenderer: function(item) { + softwareSplit = item.name.split(' | '); + return $('').attr('data-type', 'parent').attr('data-name', item.name).on('mouseover', function(){ + chart.get('software-' + makeSlug(item.name)).setState('hover'); + chart.tooltip.refresh(chart.get('software-' + makeSlug(item.name))); + }).on('mouseout', function(){ + if(chart.get('software-' + makeSlug(item.name))) + chart.get('software-' + makeSlug(item.name)).setState(''); + chart.tooltip.hide(); + }).append( + $('').addClass('square').attr('data-name', item.name).css('width', '30px').css('padding', '8px') + ).append( + $('').html('' + item.name + '').css('cursor','pointer').css('width', '50%') + ) + .append( + $('').addClass(item.today ? 'stat today' : 'warning').html(formatNumber(item.today || 0)) + ) + .append( + $('').addClass(item.yesterday ? 'stat yesterday' : 'warning').html(formatNumber(item.yesterday || 0)) + ) + .append( + $('').addClass('stat total').html('' + formatNumber(item.total) + '') + ); + }, + + onRefreshed: function(grid) { + // Gets fired when sort is changed + //console.log('softwares.onRefreshed(): %o', grid); + if (grid && grid.grid && grid.grid._sortField) { + //console.log(' grid sort is: %o, %o', grid.grid._sortField.name, grid.grid._sortOrder); + //console.log(' saved sort is: %o', softwaresSort); + if (softwaresSort.field != grid.grid._sortField.name) { + softwaresSort.field = grid.grid._sortField.name; + $("#table-softwares").jsGrid("sort", softwaresSort); + return; + } else { + softwaresSort.order = grid.grid._sortOrder; + } + $.each(softwaresViewData, function(key, values) { + + if(!chart.get('software-' + makeSlug(values.name))) + { + //console.log('Adding data point sort is: %o', softwaresSort.field); + // Populates the data into the overall Software pie chart as per current sort column + series.addPoint({id: 'software-' + makeSlug(values.name), name: values.name, y: parseInt(values[grid.grid._sortField.name]), drilldown: true}, false); + } else { + // Populates the data into the overall Software pie chart as per current sort column + chart.get('software-' + makeSlug(values.name)).update(parseInt(values[grid.grid._sortField.name]), false); + } + $(".square[data-name='" + this.name + "']").css('background', chart.get('software-' + makeSlug(values.name)).color); + }); + } + chart.redraw(); + }, + }); + + $("#table-softwares table .jsgrid-header-row th:eq(0)").html('') + .css('cursor','pointer') + .on('click', function(event) { + //console.log('softwares: click! %o', event); + currentDrillDown = false; + /* + * No longer drilling down, so need to reset the data + */ + softwaresViewData = new Array(); + softwaresData.forEach(function(software, s) { + softwareSplit = software.name.split(' | '); + name = softwareSplit[0]; + var sw = softwaresViewData.find(o => o.name === name); + if(!sw) { + softwaresViewData.push({ 'name': name, 'today': software.today, 'yesterday': software.yesterday, 'total': software.total}); + sw = softwaresViewData.find(o => o.name === name); + } else { + sw['today'] += software.today; + sw['yesterday'] += software.yesterday; + sw['total'] += software.total; + } + }); + softwaresNewJsGrid(); + }); + + } else { + // Not drilling down + newJsGrid = $("#table-softwares").jsGrid({ + width: "100%", + + filtering: false, + inserting: false, + editing: false, + sorting: true, + autoload: false, + + controller: { + loadData: softwaresJsGridDataController + }, + + fields: [ + { + title: "", + width: "30px", + sorting: false, + readOnly: true, + }, + { + title: "Software name", + width: "50%", + name: "name", + type: "text", + align: "left", + readOnly: true, + }, + { + title: "Today hits", + name: "today", + type: "number", + align: "right", + readOnly: true, + css: "stat today", + itemTemplate: formatNumberJsGrid, + }, + { + title: "Yesterday hits", + name: "yesterday", + type: "number", + align: "right", + readOnly: true, + css: "stat yesterday", + itemTemplate: formatNumberJsGrid, + }, + { + title: "Total hits", + name: "total", + type: "number", + align: "right", + readOnly: true, + css: "stat total", + itemTemplate: formatNumberJsGrid, + }, + ], + + rowRenderer: function(item) { + return $('').attr('data-type', 'parent').attr('data-name', item.name).on('click', function(event){ + //console.log('softwares: click! %o', event); + currentDrillDown = item.name; + + /* + * The data we need for this drilldown + */ + softwaresViewData = new Array(); + softwaresData.forEach(function(software, s) { + softwareSplit = software.name.split(' | '); + var name = ""; + if (currentDrillDown == softwareSplit[0]) { + name = softwareSplit[1]; + } else { + return true; + } + var sw = softwaresViewData.find(o => o.name === name); + if(!sw) { + softwaresViewData.push({ 'name': name, 'today': software.today, 'yesterday': software.yesterday, 'total': software.total}); + sw = softwaresViewData.find(o => o.name === name); + } else { + sw['today'] += software.today; + sw['yesterday'] += software.yesterday; + sw['total'] += software.total; + } + }); + softwaresNewJsGrid(); + }).on('mouseover', function(){ + chart.get('software-' + makeSlug(item.name)).setState('hover'); + chart.tooltip.refresh(chart.get('software-' + makeSlug(item.name))); + }).on('mouseout', function(){ + if(chart.get('software-' + makeSlug(item.name))) + chart.get('software-' + makeSlug(item.name)).setState(''); + chart.tooltip.hide(); + }).append( + $('').addClass('square').attr('data-name', item.name).css('width', '30px').css('padding', '8px') + ).append( + $('').html('' + item.name + '').css('cursor','pointer').css('width', '50%') + ) + .append( + $('').addClass(item.today ? 'stat today' : 'warning').html(formatNumber(item.today || 0)) + ) + .append( + $('').addClass(item.yesterday ? 'stat yesterday' : 'warning').html(formatNumber(item.yesterday || 0)) + ) + .append( + $('').addClass('stat total').html('' + formatNumber(item.total) + '') + ); + }, + + onRefreshed: function(grid) { + // Gets fired when sort is changed + //console.log('softwares.onRefreshed(): %o', grid); + if (grid && grid.grid && grid.grid._sortField) { + //console.log(' grid sort is: %o, %o', grid.grid._sortField.name, grid.grid._sortOrder); + //console.log(' saved sort is: %o', softwaresSort); + if (softwaresSort.field != grid.grid._sortField.name) { + softwaresSort.field = grid.grid._sortField.name; + $("#table-softwares").jsGrid("sort", softwaresSort); + return; + } else { + softwaresSort.order = grid.grid._sortOrder; + } + $.each(softwaresViewData, function(key, values) { + + if(!chart.get('software-' + makeSlug(values.name))) + { + //console.log('Adding data point sort is: %o', softwaresSort.field); + // Populates the data into the overall Software pie chart as per current sort column + series.addPoint({id: 'software-' + makeSlug(values.name), name: values.name, y: parseInt(values[grid.grid._sortField.name]), drilldown: true}, false); + } else { + // Populates the data into the overall Software pie chart as per current sort column + chart.get('software-' + makeSlug(values.name)).update(parseInt(values[grid.grid._sortField.name]), false); + } + $(".square[data-name='" + this.name + "']").css('background', chart.get('software-' + makeSlug(values.name)).color); + }); + } + chart.redraw(); + }, + }); + } + + // Because we're using a controller for data we need to trigger it + $("#table-softwares").jsGrid("loadData"); + // Re-apply the last stored sor + $("#table-softwares").jsGrid("sort", softwaresSort); + + // Populate the chart with the data + series.remove(false); + series = chart.addSeries({ + id: 'softwares', + name: 'Softwares', + type: 'pie', + data: [] + }); + $.each(softwaresViewData, function(key, values) { + field = $("#table-softwares").jsGrid("getSorting").field; + if(!chart.get('software-' + makeSlug(values.name))) + { + //console.log('Adding data point sort is: %o', softwaresSort.field); + // Populates the data into the overall Software pie chart as per current sort column + series.addPoint({id: 'software-' + makeSlug(values.name), name: values.name, y: parseInt(values[field]), drilldown: true}, false); + } else { + // Populates the data into the overall Software pie chart as per current sort column + chart.get('software-' + makeSlug(values.name)).update(parseInt(values[field]), false); + } + $(".square[data-name='" + this.name + "']").css('background', chart.get('software-' + makeSlug(values.name)).color); + }); + + chart.redraw(); + + $('#software').find(".stat").removeClass("warning").each(function() { + if ($(this).html() == "0") + $(this).addClass("warning"); + }); + + return newJsGrid; +} + var doUpdateSoftwares = function() { var dToday = new Date(), @@ -83,192 +416,92 @@ var doUpdateSoftwares = function() yesterday = dYesterday.getUTCFullYear() + '-' + ("0" + (dYesterday.getUTCMonth() + 1)).slice(-2) + '-' + ("0" + (dYesterday.getUTCDate())).slice(-2), today = dToday.getUTCFullYear() + '-' + ("0" + (dToday.getUTCMonth() + 1)).slice(-2) + '-' + ("0" + (dToday.getUTCDate())).slice(-2); + /* + * Gathering the data per a " | " tuple takes two calls. + * + * 1) First a /getSoftwares/?dateStart=&dateEnd= + * + * This returns an object with two top level keys, one for each date. The value + * for each is another object with " | " as each key, + * and the value as the count for that tuple. + * + * 2) Then the lifetime totals for each " | " tuple, from + * /getTotalSoftwares/ + * + * This returns an object with " | " tuples as keys, + * the values being the lifetime totals for each tuple. + * + * The calls are nested here, so only the inner .ajax() has access to the totality of data. + */ $.ajax({ dataType: "json", url: monitorEndPoint + 'getSoftwares/?dateStart=' + yesterday + '&dateEnd = ' + today, - success: function(softwares){ + success: function(softwaresTodayYesterday){ $.ajax({ dataType: "json", url: monitorEndPoint + 'getTotalSoftwares/', - success: function(softwaresTotalData){ - var chart = $('#software .chart').highcharts(), - series = chart.get('softwares'); + success: function(softwaresTotals){ + // Might happen when nothing is received... + if(softwaresTodayYesterday[yesterday] == undefined) + softwaresTodayYesterday[yesterday] = []; + if(softwaresTodayYesterday[today] == undefined) + softwaresTodayYesterday[today] = []; - // Count total by software, all versions included - var softwareName = {}; - $.each(softwaresTotalData, function(software, hits){ - softwareSplit = software.split(' | '); + /* + * Prepare 'softwaresData' dictionary: + * + * key: software name, including the version + * value: dictionary with counts for: today, yesterday, total (all time) + */ + softwaresData = new Array(); + $.each(softwaresTotals, function(softwareName, total){ + var sw = { 'name': softwareName, 'today': 0, 'yesterday': 0, 'total': parseInt(total)}; - if(!softwareName[softwareSplit[0]]) - softwareName[softwareSplit[0]] = [0,0, parseInt(hits)]; - else - softwareName[softwareSplit[0]][2] += parseInt(hits); - - // Might happen when nothing is received... - if(softwares[yesterday] == undefined) - softwares[yesterday] = []; - if(softwares[today] == undefined) - softwares[today] = []; - - softwareName[softwareSplit[0]][0] += parseInt(softwares[today][software] || 0); - softwareName[softwareSplit[0]][1] += parseInt(softwares[yesterday][software] || 0); + sw['today'] += parseInt(softwaresTodayYesterday[today][softwareName] || 0); + sw['yesterday'] += parseInt(softwaresTodayYesterday[yesterday][softwareName] || 0); + softwaresData.push(sw); }); - // Sort by total DESC - var tmp = new Array(); - $.each(softwareName, function(software, hits){ tmp.push({name: software, today: hits[0], yesterday: hits[1], total: hits[2]}); }); - tmp.sort(function(a,b) { return b.total - a.total; }); - softwaresTotal = tmp; - - $('#software .table tbody').empty(); - - // Prepare drilldowns - $.each(softwaresTotalData, function(software, hits){ - softwareSplit = software.split(' | '); - - $('#software .table tbody').append( - newTr = $('').attr('data-type', 'drilldown').attr('data-parent', softwareSplit[0]).attr('data-name', software).on('mouseover', function(){ - chart.get('software-' + makeSlug(software)).setState('hover'); - chart.tooltip.refresh(chart.get('software-' + makeSlug(software))); - }).on('mouseout', function(){ - chart.get('software-' + makeSlug(software)).setState(''); - chart.tooltip.hide(); - }).append( - $('').addClass('square') - ).append( - $('').html('' + softwareSplit[1] + '') - ) - .append( - $('').addClass('stat today').html(formatNumber(softwares[today][software] || 0)) - ) - .append( - $('').addClass('stat yesterday').html(formatNumber(softwares[yesterday][software] || 0)) - ) - .append( - $('').addClass('stat total').html('' + formatNumber(hits) + '') - ) - ); - - if(!drillDownSoftware) - newTr.hide(); - else - if(softwareSplit[0] != currentDrillDown) - newTr.hide(); - - if(!softwaresVersion[softwareSplit[0]]) - softwaresVersion[softwareSplit[0]] = {}; - if(!softwaresVersion[softwareSplit[0]][software]) - softwaresVersion[softwareSplit[0]][software] = { - today: (softwares[today][software] || 0), yesterday: (softwares[yesterday][software] || 0), total: hits - }; - }); - - // Add main softwares - $.each(softwaresTotal, function(key, values){ - $('#software .table tbody').append( - newTr = $('').attr('data-type', 'parent').attr('data-name', values.name).on('click', function(event){ - event.stopImmediatePropagation(); - currentSoftware = $(this).attr('data-name'); - - if(!drillDownSoftware) - { - currentDrillDown = currentSoftware; - - $('#software .table thead th:eq(0)').html('') - .css('cursor','pointer') - .on('click', function(){ - currentDrillDown = false; - chart.showDrillUpButton(); - $('#software .table thead th:eq(0)').html(''); - $('#software .table thead th:eq(1)').html(''); - $('#software .table tbody tr[data-type=parent]').show(); - $('#software .table tbody tr[data-type=drilldown]').hide(); - drillDownSoftware = !drillDownSoftware; - doUpdateSoftwares(); - chart.drillUp(); - }); - $('#software .table thead th:eq(1)').html(currentSoftware); - $('#software .table tbody tr[data-type=parent]').hide(); - $('#software .table tbody tr[data-type=drilldown][data-parent="' + currentSoftware + '"]').show(); - - var currentData = []; - - $.each(softwaresVersion[currentSoftware], function(key, value){ - currentData.push({ - id: 'software-' + makeSlug(key), - name: key, - y: parseInt(value.total) - }); - }); - - chart.addSeriesAsDrilldown(chart.get('software-' + makeSlug(currentSoftware)), { - id: 'softwareDrilldown-' + makeSlug(currentSoftware), - type: 'pie', - name: currentSoftware, - data: currentData - }); - chart.redraw(); - - if(chart.drillUpButton) - chart.drillUpButton = chart.drillUpButton.destroy(); - - $('#software .table tbody tr[data-type=drilldown][data-parent="' + currentSoftware + '"]').each(function(){ - $(this).find('.square').css('background', chart.get('software-' + makeSlug($(this).attr('data-name'))).color); - }); - } - - drillDownSoftware = !drillDownSoftware; - }).on('mouseover', function(){ - chart.get('software-' + makeSlug(values.name)).setState('hover'); - chart.tooltip.refresh(chart.get('software-' + makeSlug(values.name))); - }).on('mouseout', function(){ - if(chart.get('software-' + makeSlug(values.name))) - chart.get('software-' + makeSlug(values.name)).setState(''); - chart.tooltip.hide(); - }).append( - $('').addClass('square') - ).append( - $('').html('' + values.name + '').css('cursor','pointer') - ) - .append( - $('').addClass('stat today').html(formatNumber(values.today || 0)) - ) - .append( - $('').addClass('stat yesterday').html(formatNumber(values.yesterday || 0)) - ) - .append( - $('').addClass('stat total').html('' + formatNumber(values.total) + '') - ) - ); - - if(!drillDownSoftware) - { - if(!chart.get('software-' + makeSlug(values.name))) - { - series.addPoint({id: 'software-' + makeSlug(values.name), name: values.name, y: parseInt(values.total), drilldown: true}, false); + /* + * Now the data we need for the current view (overall data or a drilldown of a software) + */ + softwaresViewData = new Array(); + softwaresData.forEach(function(software, s) { + softwareSplit = software.name.split(' | '); + var name = ""; + if (currentDrillDown) { + if (currentDrillDown == softwareSplit[0]) { + name = softwareSplit[1]; + } else { + return true; } - else - chart.get('software-' + makeSlug(values.name)).update(parseInt(values.total), false); - - newTr.find('.square').css('background', chart.get('software-' + makeSlug(values.name)).color); + } else { + name = softwareSplit[0]; + } + var sw = softwaresViewData.find(o => o.name === name); + if(!sw) { + softwaresViewData.push({ 'name': name, 'today': software.today, 'yesterday': software.yesterday, 'total': software.total}); + sw = softwaresViewData.find(o => o.name === name); + } else { + sw['today'] += software.today; + sw['yesterday'] += software.yesterday; + sw['total'] += software.total; } - - if(drillDownSoftware) - newTr.hide(); }); - if(drillDownSoftware) - $('#software .table tbody tr[data-type=drilldown][data-parent="' + currentDrillDown + '"]').each(function(){ - $(this).find('.square').css('background', chart.get('software-' + makeSlug($(this).attr('data-name'))).color); - }); + // Ensure we have the jsGrid added + if (! $("#table-softwares").length ) { + // Append a new DIV for this jsGrid to the "#software #tables" div + $('#software #tables').append( + $('
').addClass('jsGridTable').attr('id', 'table-softwares') + ); + } else { + // Store the last selected sort so we can apply it to the new version + softwaresSort = $("#table-softwares").jsGrid("getSorting"); + } - chart.redraw(); - - $('#software').find(".stat").removeClass("warning").each(function() { - if ($(this).html() == "0") - $(this).addClass("warning"); - }); + newJsGrid = softwaresNewJsGrid(); $('#software').find(".update_timestamp").html(d.toString("yyyy-MM-dd HH:mm:ss")); } @@ -277,86 +510,9 @@ var doUpdateSoftwares = function() }); } -/* -var doUpdateUploaders = function() -{ - var dToday = new Date(), - dYesterday = new (function(d){ d.setDate(d.getDate()-1); return d})(new Date), - - yesterday = dYesterday.getUTCFullYear() + '-' + ("0" + (dYesterday.getUTCMonth() + 1)).slice(-2) + '-' + ("0" + (dYesterday.getUTCDate())).slice(-2), - today = dToday.getUTCFullYear() + '-' + ("0" + (dToday.getUTCMonth() + 1)).slice(-2) + '-' + ("0" + (dToday.getUTCDate())).slice(-2); - - $.ajax({ - dataType: "json", - url: monitorEndPoint + 'getUploaders/?dateStart=' + yesterday + '&dateEnd = ' + today, - success: function(uploaders){ - $.ajax({ - dataType: "json", - url: monitorEndPoint + 'getTotalUploaders/', - success: function(uploadersTotal){ - var chart = $('#uploaders .chart').highcharts(), - series = chart.get('uploaders'); - - $('#uploaders .table tbody').empty(); - - $.each(uploadersTotal, function(uploader, hits){ - if(uploader.length > 32) - truncateUploader = jQuery.trim(uploader).substring(0, 32) + "..." - else - truncateUploader = uploader - - // Might happen when nothing is received... - if(uploaders[yesterday] == undefined) - uploaders[yesterday] = []; - if(uploaders[today] == undefined) - uploaders[today] = []; - - $('#uploaders .table tbody').append( - newTr = $('').attr('data-name', uploader).on('mouseover', function(){ - chart.get('uploader-' + makeSlug(uploader)).setState('hover'); - chart.tooltip.refresh(chart.get('uploader-' + makeSlug(uploader))); - }).on('mouseout', function(){ - chart.get('uploader-' + makeSlug(uploader)).setState(''); - chart.tooltip.hide(); - }).append( - $('').addClass('square') - ).append( - $('').html('' + truncateUploader + '') - ) - .append( - $('').addClass('stat today').html(formatNumber(uploaders[today][uploader] || 0)) - ) - .append( - $('').addClass('stat yesterday').html(formatNumber(uploaders[yesterday][uploader] || 0)) - ) - .append( - $('').addClass('stat total').html('' + formatNumber(hits) + '') - ) - ); - - if(!chart.get('uploader-' + makeSlug(uploader))) - series.addPoint({id: 'uploader-' + makeSlug(uploader), name: uploader, y: parseInt(hits)}, false); - else - chart.get('uploader-' + makeSlug(uploader)).update(parseInt(hits), false); - - newTr.find('.square').css('background', chart.get('uploader-' + makeSlug(uploader)).color); - }); - - chart.redraw(); - - $('#uploaders').find(".stat").removeClass("warning").each(function() { - if ($(this).html() == "0") - $(this).addClass("warning"); - }); - - $('#uploaders').find(".update_timestamp").html(d.toString("yyyy-MM-dd HH:mm:ss")); - } - }); - } - }); -} -*/ +var schemasSort = { field: 'today', order: 'desc' }; // Very first load sort order +var schemasData = new Array(); var doUpdateSchemas = function() { @@ -369,88 +525,174 @@ var doUpdateSchemas = function() $.ajax({ dataType: "json", url: monitorEndPoint + 'getSchemas/?dateStart=' + yesterday + '&dateEnd = ' + today, - success: function(schemas){ + success: function(schemasTodayYesterday){ + // Might happen when nothing is received... + if(schemasTodayYesterday[yesterday] == undefined) + schemaTodayYesterday[yesterday] = []; + if(schemasTodayYesterday[today] == undefined) + schemasTodayYesterday[today] = []; + $.ajax({ dataType: "json", url: monitorEndPoint + 'getTotalSchemas/', - success: function(schemasTotalTmp){ - // Convert old schemas and sum them to new schemas - schemasTotal = {}; - $.each(schemasTotalTmp, function(schema, hits){ - schema = schema.replace('http://schemas.elite-markets.net/eddn/', 'https://eddn.edcd.io/schemas/'); - hits = parseInt(hits); - - if(schemasTotal[schema]){ schemasTotal[schema] += hits; } - else{ schemasTotal[schema] = hits; } - }); - + success: function(schemasTotals){ var chart = $('#schemas .chart').highcharts(), series = chart.get('schemas'); - $('#schemas .table tbody').empty(); + /* + * Prepare 'schemasData' dictionary + */ + schemasData = new Array(); + $.each(schemasTotals, function(schema, total) { + schemaName = schema.replace('http://schemas.elite-markets.net/eddn/', 'https://eddn.edcd.io/schemas/'); + // Due to the schema renames and us merging them there could be more than one + // row of data input per schema + var sch = schemasData.find(o => o.name === schemaName); + if (!sch) { + schemasData.push({ 'name': schemaName, 'today': 0, 'yesterday': 0, 'total': parseInt(total)}); + sch = schemasData.find(o => o.name === schemaName); + } else { + sch['total'] += parseInt(total); + } + }); - $.each(schemasTotal, function(schema, hits){ - // Might happen when nothing is received... - if(schemas[yesterday] == undefined) - schemas[yesterday] = []; - if(schemas[today] == undefined) - schemas[today] = []; + // Today + $.each(schemasTodayYesterday[today], function(schema, hits) { + schemaName = schema.replace('http://schemas.elite-markets.net/eddn/', 'https://eddn.edcd.io/schemas/'); + var sch = schemasData.find(o => o.name === schemaName); + sch['today'] += parseInt(hits); + }); + // Yesterday + $.each(schemasTodayYesterday[yesterday], function(schema, hits) { + schemaName = schema.replace('http://schemas.elite-markets.net/eddn/', 'https://eddn.edcd.io/schemas/'); + var sch = schemasData.find(o => o.name === schemaName); + sch['yesterday'] += parseInt(hits); + }); - // Convert old schemas and sum them to new schemas - schemasYesterdayTmp = {}; - $.each(schemas[yesterday], function(schema, hits){ - schema = schema.replace('http://schemas.elite-markets.net/eddn/', 'https://eddn.edcd.io/schemas/'); - hits = parseInt(hits); + // Ensure we have the jsGrid added + if (! $("#table-schemas").length ) { + // Append a new DIV for this jsGrid to the "#schemas #tables" div + $('#schemas #tables').append( + $('
').addClass('jsGridTable').attr('id', 'table-schemas') + ); + } else { + // Store the last selected sort so we can apply it to the new version + schemasSort = $("#table-schemas").jsGrid("getSorting"); + } - if(schemasYesterdayTmp[schema]){ schemasYesterdayTmp[schema] += hits; } - else{ schemasYesterdayTmp[schema] = hits; } - }); - schemas[yesterday] = schemasYesterdayTmp; + newJsGrid = $("#table-schemas").jsGrid({ + width: "100%", - schemasTodayTmp = {}; - $.each(schemas[today], function(schema, hits){ - schema = schema.replace('http://schemas.elite-markets.net/eddn/', 'https://eddn.edcd.io/schemas/'); - hits = parseInt(hits); + filtering: false, + inserting: false, + editing: false, + sorting: true, + autoload: false, - if(schemasTodayTmp[schema]){ schemasYesterdayTmp[schema] += hits; } - else{ schemasTodayTmp[schema] = hits; } - }); - schemas[today] = schemasTodayTmp; + data: schemasData, - var slug = makeSlug(schema); - var name = makeName(schema); + fields: [ + { + title: "", + width: "30px", + name: "chartslug", + sorting: false, + readOnly: true, + }, + { + title: "Schema", + width: "50%", + name: "name", + type: "text", + align: "left", + readOnly: true, + }, + { + title: "Today hits", + name: "today", + type: "number", + align: "right", + readOnly: true, + css: "stat today", + itemTemplate: formatNumberJsGrid, + }, + { + title: "Yesterday hits", + name: "yesterday", + type: "number", + align: "right", + readOnly: true, + css: "stat yesterday", + itemTemplate: formatNumberJsGrid, + }, + { + title: "Total hits", + name: "total", + type: "number", + align: "right", + readOnly: true, + css: "stat total", + itemTemplate: formatNumberJsGrid, + }, + ], - $('#schemas .table tbody').append( - newTr = $('').attr('data-name', schema).on('mouseover', function(){ - chart.get('schema-' + slug).setState('hover'); - chart.tooltip.refresh(chart.get('schema-' +slug)); + rowRenderer: function(item) { + return $('').attr('data-type', 'parent').attr('data-name', item.name).on('mouseover', function(){ + chart.get('schema-' + makeSlug(item.name)).setState('hover'); + chart.tooltip.refresh(chart.get('schema-' + makeSlug(item.name))); }).on('mouseout', function(){ - chart.get('schema-' + slug).setState(''); + if(chart.get('schema-' + makeSlug(item.name))) + chart.get('schema-' + makeSlug(item.name)).setState(''); chart.tooltip.hide(); }).append( - $('').addClass('square') + $('').addClass('square').attr('data-name', item.name).css('width', '30px').css('padding', '8px') ).append( - $('').html('' + name + '') + $('').html('' + makeName(item.name) + '').css('cursor','pointer').css('width', '50%') ) .append( - $('').addClass('stat today').html(formatNumber(schemas[today][schema] || 0)) + $('').addClass(item.today ? 'stat today' : 'warning').html(formatNumber(item.today || 0)) ) .append( - $('').addClass('stat yesterday').html(formatNumber(schemas[yesterday][schema] || 0)) + $('').addClass(item.yesterday ? 'stat yesterday' : 'warning').html(formatNumber(item.yesterday || 0)) ) .append( - $('').addClass('stat total').html('' + formatNumber(hits) + '') - ) - ); + $('').addClass('stat total').html('' + formatNumber(item.total) + '') + ); + }, - if(!chart.get('schema-' + slug)) - series.addPoint({id: 'schema-' + slug, name: name, y: parseInt(hits)}, false); - else - chart.get('schema-' + slug).update(parseInt(hits), false); - - newTr.find('.square').css('background', chart.get('schema-' + slug).color); + onRefreshed: function(grid) { + // Gets fired when sort is changed + //console.log('softwares.onRefreshed(): %o', grid); + if (grid && grid.grid && grid.grid._sortField) { + //console.log(' grid sort is: %o, %o', grid.grid._sortField.name, grid.grid._sortOrder); + //console.log(' saved sort is: %o', schemasSort); + if (schemasSort.field != grid.grid._sortField.name) { + schemasSort.field = grid.grid._sortField.name; + $("#table-schemas").jsGrid("sort", schemasSort); + return; + } else { + schemasSort.order = grid.grid._sortOrder; + } + $.each(schemasData, function(key, values) { + if(!chart.get('schema-' + makeSlug(values.name))) + { + //console.log('Adding data point sort is: %o', schemasSort.field); + // Populates the data into the overall Software pie chart as per current sort column + series.addPoint({id: 'schema-' + makeSlug(values.name), name: values.name, y: parseInt(values[grid.grid._sortField.name]), drilldown: true}, false); + } else { + // Populates the data into the overall Software pie chart as per current sort column + chart.get('schema-' + makeSlug(values.name)).update(parseInt(values[grid.grid._sortField.name]), false); + } + $(".square[data-name='" + this.name + "']").css('background', chart.get('schema-' + makeSlug(values.name)).color); + }); + } + chart.redraw(); + }, }); + // Re-apply the last stored sort + $("#table-schemas").jsGrid("sort", schemasSort); + chart.redraw(); $('#schemas').find(".stat").removeClass("warning").each(function() { @@ -703,13 +945,6 @@ var start = function(){ }] }); - /* - doUpdateUploaders(); - setInterval(function(){ - doUpdateUploaders(); - }, updateInterval); - */ - // Grab schema from monitor $('#schemas .chart').highcharts({ chart: { @@ -741,6 +976,17 @@ var start = function(){ }); } +/* + * JS Grid related functions + */ + +/* + * Nicely format a number for jsGrid + */ +formatNumberJsGrid = function(value, item) { + return value.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, "$1,"); +} + $(document).ready(function(){ start(); }); From 576fe7ec29fc314a9a61fec60c58a8f0293e1a45 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Thu, 25 Jun 2020 14:09:02 +0100 Subject: [PATCH 2/4] Silence console.log on softwaresJsGridDataController --- contrib/monitor/js/eddn.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/monitor/js/eddn.js b/contrib/monitor/js/eddn.js index 9f2847e..feb320e 100644 --- a/contrib/monitor/js/eddn.js +++ b/contrib/monitor/js/eddn.js @@ -80,7 +80,7 @@ var softwaresViewData = new Array(); // The data for the current view var softwaresVersion = {}; var softwaresJsGridDataController = function () { - console.log('softwares -> jsGrid.controller.loadData() returning %o', softwaresViewData); + //console.log('softwares -> jsGrid.controller.loadData() returning %o', softwaresViewData); return softwaresViewData; }; From a2638caed25e9aaea5c6044edc26bd1a1eb92a77 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Fri, 26 Jun 2020 14:41:25 +0100 Subject: [PATCH 3/4] Also apply "it's zero" background to jsgrid-alt-row --- contrib/monitor/css/eddn.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/monitor/css/eddn.css b/contrib/monitor/css/eddn.css index 090041d..d4eb455 100644 --- a/contrib/monitor/css/eddn.css +++ b/contrib/monitor/css/eddn.css @@ -41,6 +41,6 @@ tr.jsgrid-row > td, tr.jsgrid-alt-row > td { } /* Style the zero boxes as per bootstrap td.warning */ -tr.jsgrid-row > td.warning { +tr.jsgrid-row > td.warning, tr.jsgrid-alt-row > td.warning { background-color:#faf2cc; } From d7d441b9e2365b339564fce264328e273a731db5 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Sun, 5 Jul 2020 15:15:04 +0100 Subject: [PATCH 4/4] Escape ' when setting data-name attributes. * "Garud's Test" was tripping this up. * It's just for how the table rows get referred to, no impact on the highlight into the HighChart. --- contrib/monitor/js/eddn.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contrib/monitor/js/eddn.js b/contrib/monitor/js/eddn.js index feb320e..a02619b 100644 --- a/contrib/monitor/js/eddn.js +++ b/contrib/monitor/js/eddn.js @@ -197,7 +197,7 @@ var softwaresNewJsGrid = function () { // Populates the data into the overall Software pie chart as per current sort column chart.get('software-' + makeSlug(values.name)).update(parseInt(values[grid.grid._sortField.name]), false); } - $(".square[data-name='" + this.name + "']").css('background', chart.get('software-' + makeSlug(values.name)).color); + $(".square[data-name='" + this.name.replace("'", "\\'") + "']").css('background', chart.get('software-' + makeSlug(values.name)).color); }); } chart.redraw(); @@ -363,7 +363,7 @@ var softwaresNewJsGrid = function () { // Populates the data into the overall Software pie chart as per current sort column chart.get('software-' + makeSlug(values.name)).update(parseInt(values[grid.grid._sortField.name]), false); } - $(".square[data-name='" + this.name + "']").css('background', chart.get('software-' + makeSlug(values.name)).color); + $(".square[data-name='" + this.name.replace("'", "\\'") + "']").css('background', chart.get('software-' + makeSlug(values.name)).color); }); } chart.redraw(); @@ -395,7 +395,7 @@ var softwaresNewJsGrid = function () { // Populates the data into the overall Software pie chart as per current sort column chart.get('software-' + makeSlug(values.name)).update(parseInt(values[field]), false); } - $(".square[data-name='" + this.name + "']").css('background', chart.get('software-' + makeSlug(values.name)).color); + $(".square[data-name='" + this.name.replace("'", "\\'") + "']").css('background', chart.get('software-' + makeSlug(values.name)).color); }); chart.redraw(); @@ -683,7 +683,7 @@ var doUpdateSchemas = function() // Populates the data into the overall Software pie chart as per current sort column chart.get('schema-' + makeSlug(values.name)).update(parseInt(values[grid.grid._sortField.name]), false); } - $(".square[data-name='" + this.name + "']").css('background', chart.get('schema-' + makeSlug(values.name)).color); + $(".square[data-name='" + this.name.replace("'", "\\'") + "']").css('background', chart.get('schema-' + makeSlug(values.name)).color); }); } chart.redraw();