From bdf4225dec164644e0ab98b8dc65d73554504d45 Mon Sep 17 00:00:00 2001 From: David Roberts Date: Tue, 29 Aug 2017 13:24:53 -0700 Subject: [PATCH 1/3] Clean up web interface storage tab. - Use diffing algorithm so you can select text and images. - This uses textcontent, plugs a vulnerability where you could inject arbitrary javascript if you could upload a modded item with html in the name, eg, "iron-plate". - Fall back to transparent png if item image not found. - This stops the flashing box when the image does not exist. - (internal) Simplify `sortByKey(array, key)`. --- static/index.html | 1 + static/main.js | 2 +- static/pictures/unknown-item.png | Bin 0 -> 126 bytes static/storage.js | 42 +++++++++++++++++++++---------- 4 files changed, 31 insertions(+), 14 deletions(-) create mode 100644 static/pictures/unknown-item.png diff --git a/static/index.html b/static/index.html index 03a05b22..72c62f14 100644 --- a/static/index.html +++ b/static/index.html @@ -11,6 +11,7 @@

Storage

+
diff --git a/static/main.js b/static/main.js index 6806f13d..7cb7fdad 100644 --- a/static/main.js +++ b/static/main.js @@ -78,7 +78,7 @@ function getImageFromName(name){ return "https://wiki.factorio.com/images/" + capitalizeFirstLetter(replaceAll(factorioLocale["fluid-name"][name], ' ', '_').toLowerCase()) + ".png"; } else if(factorioLocale["equipment-name"] && factorioLocale["equipment-name"][name]){ return "https://wiki.factorio.com/images/" + capitalizeFirstLetter(replaceAll(factorioLocale["equipment-name"][name], ' ', '_').toLowerCase()) + ".png"; - } else return ""; + } else return "/pictures/unknown-item.png"; } window.factorioLocale = {}; $.getJSON("/api/getFactorioLocale", (locale) => { diff --git a/static/pictures/unknown-item.png b/static/pictures/unknown-item.png new file mode 100644 index 0000000000000000000000000000000000000000..59a2372571c606336ad52ed2fb2c7529edfb5921 GIT binary patch literal 126 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx1|;Q0k8}blwj^(N7l!{JxM1({$v_d#0*}aI z1_r)EAj~ML;ne^XWH0gbb!C6ZAuDXI=y;IZ9Vo=@>Eal|aXmSKfr){Em4We*8tZJJ OFoUP7pUXO@geCw*EE@>` literal 0 HcmV?d00001 diff --git a/static/storage.js b/static/storage.js index c68cd982..7506dc0d 100644 --- a/static/storage.js +++ b/static/storage.js @@ -1,16 +1,33 @@ // function to draw data we recieve from ajax requests function drawcontents(data) { - data = sortByKey(data, "count"); - let result = ""; - for(i = 0;i < data.length; i++) { - var img = getImageFromName(data[i].name); - var searchField = document.querySelector("#search"); - if(!searchField.value || data[i].name.includes(searchField.value)) { - result = result + ""; + const table = document.querySelector("#contents tbody"); //tables have tbody inserted automatically + const rows = table.children; + + sortByKey(data, "count"); + data.forEach(function(item, i) { + let row = rows[i]; + if(!row) { + row = document.createElement('tr'); + row.innerHTML = ""; + table.appendChild(row); } - } - result = result + "
" + data[i].name + "" + data[i].count + "
" - document.getElementById("contents").innerHTML = result; + + const img = row.querySelector('img'); + const imgName = getImageFromName(item.name); + if(img.getAttribute('src') !== imgName) { + img.setAttribute('src',imgName); + } + + const name = row.querySelector('.name'); + if(name.textContent !== item.name) { + name.textContent = item.name; + } + + const count = row.querySelector('.count'); + if(count.textContent !== ''+item.count) { + count.textContent = item.count; + } + }) } // get cluster inventory from master @@ -33,8 +50,7 @@ if(JSON.parse(localStorage.settings)["Periodically update storage screen"]) { // function to sort arrays of objects after a keys value function sortByKey(array, key) { - return array.sort(function(a, b) { - var x = Number(a[key]); var y = Number(b[key]); - return ((x < y) ? 1 : ((x > y) ? -1 : 0)); + array.sort(function(a, b) { + return b[key] - a[key]; }); } \ No newline at end of file From 000b2fc147c43e92ebc0848bc42613a675996d30 Mon Sep 17 00:00:00 2001 From: David Roberts Date: Tue, 29 Aug 2017 13:57:13 -0700 Subject: [PATCH 2/3] Added search functionality back in. It's much snappier now. - Is now case-insensitive. - And regex. --- static/index.html | 2 +- static/storage.js | 26 +++++++++++++++++++++++--- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/static/index.html b/static/index.html index 72c62f14..f5a5559e 100644 --- a/static/index.html +++ b/static/index.html @@ -1,7 +1,7 @@ <%- include("head.html") %> - + diff --git a/static/storage.js b/static/storage.js index 7506dc0d..ee15e77d 100644 --- a/static/storage.js +++ b/static/storage.js @@ -1,9 +1,20 @@ // function to draw data we recieve from ajax requests +_lastData = []; function drawcontents(data) { + data = data || _lastData; //Cache data so we can drawcontents without waiting for the server, for the search box. + _lastData = data; + + const search = new RegExp(document.querySelector("#search").value, 'i'); + data = data.filter(function(item) { + return search.test(item.name); + }) + + sortByKey(data, "count"); + const table = document.querySelector("#contents tbody"); //tables have tbody inserted automatically const rows = table.children; - sortByKey(data, "count"); + //update existing rows or create new ones data.forEach(function(item, i) { let row = rows[i]; if(!row) { @@ -27,7 +38,12 @@ function drawcontents(data) { if(count.textContent !== ''+item.count) { count.textContent = item.count; } - }) + }); + + //remove excess rows, for example, after filtering + while (data.length < rows.length) { + table.removeChild(rows[data.length]); + } } // get cluster inventory from master @@ -53,4 +69,8 @@ function sortByKey(array, key) { array.sort(function(a, b) { return b[key] - a[key]; }); -} \ No newline at end of file +} + +document.querySelector("#search").addEventListener('input', function() { + drawcontents(); +}) \ No newline at end of file From 49887012f7af08d379c8f7bdf177103300c103ef Mon Sep 17 00:00:00 2001 From: David Roberts Date: Tue, 29 Aug 2017 16:25:33 -0700 Subject: [PATCH 3/3] Tweaked slaveDisplay.js to use 92% less data to draw a chart. This is projected to speed up chart loading significantly, from 3.4s to about 0.5s. - Added copper-cable to the list of hidables on the chart, and removed copper-wire which isn't a thing. - Don't display items as hidable that aren't being produced. It's confusing. - Added /getTimelineStats, because /getStats is used by another view that I don't feel like converting to not require the 0-production data points. --- master.js | 43 ++++++++++++++++++++++++++++ static/slaveDisplay.js | 65 +++++++++++++++++++++--------------------- 2 files changed, 76 insertions(+), 32 deletions(-) diff --git a/master.js b/master.js index 58e1547f..d5e0f079 100644 --- a/master.js +++ b/master.js @@ -307,6 +307,49 @@ app.post("/api/getStats", function(req,res) { }); } }); + +// Modified version of getStats which feeds the web interface production graphs +// at /nodes. It is like getStats but it does not report items which were not +// produced at the moment of recording. (This is to save space, the 0-items were +// making up about 92% of the response body weight.) +app.post("/api/getTimelineStats", function(req,res) { + console.log(req.body); + if(typeof req.body == "object" && req.body.slaveID) { + // if not specified, get stats for last 24 hours + if(!req.body.fromTime) { + req.body.fromTime = Date.now() - 86400000 // 24 hours in MS + } + if(!req.body.toTime) { + req.body.toTime = Date.now(); + } + console.log("Looking... " + JSON.stringify(req.body)); + db.flows.find({ + slaveID: req.body.slaveID, + }, function(err, docs) { + if(err) { + console.error(err); + return; + } + + //Filter out all the entries outside the time range. + docs = docs.filter(function (el) { + return el.timestamp <= req.body.toTime && el.timestamp >= req.body.fromTime; + }); + + //Filter out all the elements that weren't produced. + docs.forEach(el => { + for(let key in el.data) { + if(el.data[key] === '0') { + delete el.data[key]; + } + } + }); + + res.send(docs); + }); + } +}); + app.get("/api/getFactorioLocale", function(req,res){ getFactorioLocale.asObject(config.factorioDirectory, "en", (err, factorioLocale) => res.send(factorioLocale)); }); diff --git a/static/slaveDisplay.js b/static/slaveDisplay.js index 2add9d04..c1dc2292 100644 --- a/static/slaveDisplay.js +++ b/static/slaveDisplay.js @@ -44,32 +44,34 @@ let chartIgnoreList = [ // ID of slave, ID of canvasjs div without # function makeGraph(slaveID, selector) { - post("api/getStats", {slaveID: slaveID}, function(data){ + post("api/getTimelineStats", {slaveID: slaveID}, function(pointsInTime) { //console.log("Building chart " + slaveID + " with this data:") - //console.log(data) - if(data.length > 0) { - // find keys - let itemNames = []; - for(let key in data[data.length - 1].data) { - itemNames[itemNames.length] = key + + //Find all keys at all points in time. + let itemNames = {}; + pointsInTime.forEach(function(point) { + for(let key in point.data) { + itemNames[key] = true; } - let chartData = []; - for(let o = 0; o < itemNames.length; o++) { - if(!chartIgnoreList.includes(itemNames[o])){ - chartData[chartData.length] = generateLineChartArray(data, itemNames[o]); - } - } - //console.log(chartData) - drawChart(selector, chartData) - } - // callback(data); + }); + itemNames = Object.keys(itemNames); + + //Generate a chart list for each item. + const displayNames = itemNames.filter(function(name) { + return !chartIgnoreList.includes(name); + }); + const chartData = displayNames.map(function(name) { + return generateLineChartArray(pointsInTime, name) + }); + + chartData.length && drawChart(selector, chartData) }) } // production info // string, object, function(object) function post(url, data, callback) { - console.log("POST " + url + JSON.stringify(data)) + console.log("POST " + url, data); var xhr = new XMLHttpRequest(); xhr.open("POST", url, true); xhr.setRequestHeader("Content-type", "application/json"); @@ -83,27 +85,26 @@ function post(url, data, callback) { } function generateLineChartArray(data, nameKey) { + const legendKeys = ["copper-cable", "iron-plate", "copper-plate", "electronic-circuit", "steel-plate", "advanced-circuit", "crude-oil", "petroleum-gas"]; let chartData = []; for(let i = 0; i < data.length; i++) { - // only show recent data - if(data[i].timestamp > Date.now() - (24*60*60*1000)){ - let y = data[i].data[nameKey] - if(!data[i].data[nameKey]) { - y = 0; - } else if(y < 0) { - y = 0; - } - chartData[chartData.length] = { - x: new Date(data[i].timestamp), - y: Number(y) - } + let y = data[i].data[nameKey] + if(!data[i].data[nameKey]) { + y = 0; + } else if(y < 0) { + y = 0; + } + chartData[chartData.length] = { + x: new Date(data[i].timestamp), + y: Number(y) } } chartData = sortByKey(chartData, "x"); let xyz = {}; xyz.name = nameKey; xyz.type = "line"; - if(nameKey == "copper-wire"||nameKey == "iron-plate"||nameKey == "copper-plate"||nameKey == "electronic-circuit"||nameKey == "steel-plate"||nameKey == "advanced-circuit"||nameKey == "crude-oil"||nameKey == "petroleum-gas"){ + + if(legendKeys.indexOf(nameKey) >= 0){ xyz.showInLegend = true; } xyz.dataPoints = chartData; @@ -113,7 +114,7 @@ function generateLineChartArray(data, nameKey) { var chartsByID = {}; function drawChart(selector, chartData) { // selector is ID of element, ex "chartContainer" or "-123199123" - console.log(chartData) + // console.log(chartData) chartsByID[selector] = new CanvasJS.Chart(selector, { title:{ text: "Production graph"