diff --git a/contrib/msi/build-msi.sh b/contrib/msi/build-msi.sh
index c913c9c4..84ef8164 100644
--- a/contrib/msi/build-msi.sh
+++ b/contrib/msi/build-msi.sh
@@ -98,6 +98,18 @@ else
PKGDISPLAYNAME="RiV-mesh Network"
fi
+cat > mesh-ui-ie.js << EOF
+var ie = new ActiveXObject("InternetExplorer.Application");
+ie.AddressBar = false;
+ie.MenuBar = false;
+ie.ToolBar = false;
+ie.height = 960
+ie.width = 706
+ie.resizable = false
+ie.Visible = true;
+ie.Navigate("http://localhost:19019");
+EOF
+
# Generate the wix.xml file
cat > wix.xml << EOF
diff --git a/contrib/ui/mesh-ui/ui/assets/mesh-ui-es5.js b/contrib/ui/mesh-ui/ui/assets/mesh-ui-es5.js
new file mode 100644
index 00000000..f931f0d7
--- /dev/null
+++ b/contrib/ui/mesh-ui/ui/assets/mesh-ui-es5.js
@@ -0,0 +1,420 @@
+"use strict";
+
+var $ = function $(id) {
+ return document.getElementById(id);
+};
+var $$ = function $$(clazz) {
+ return document.getElementsByClassName(clazz);
+};
+
+function setPingValue(peer, value) {
+ var cellText;
+ var peerCell = $(peer);
+ if (!peerCell) return;
+ var peerTable = $("peer_list");
+ if (value === "-1") {
+ var peerAddress = $("label_" + peer);
+ peerAddress.style.color = "rgba(250,250,250,.5)";
+ } else {
+
+ cellText = document.createTextNode(value);
+ peerCell.appendChild(cellText);
+
+ var peerCellTime = $("time_" + peer);
+ var cellTextTime = document.createTextNode("ms");
+ peerCellTime.appendChild(cellTextTime);
+ }
+ peerCell.parentNode.classList.remove("is-hidden");
+ //sort table
+ moveRowToOrderPos(peerTable, 2, peerCell.parentNode);
+}
+
+function cmpTime(a, b) {
+ return a.textContent.trim() === "" ? 1 : a.textContent.trim() // using `.textContent.trim()` for test
+ .localeCompare(b.textContent.trim(), 'en', { numeric: true });
+}
+
+function moveRowToOrderPos(table, col, row) {
+ var tb = table.tBodies[0],
+ tr = tb.rows;
+ var i = 0;
+ for (; i < tr.length && cmpTime(row.cells[col], tr[i].cells[col]) >= 0; ++i) {}
+ if (i < tr.length && i != row.rowIndex) {
+ tb.deleteRow(row.rowIndex);
+ tb.insertBefore(row, tr[i]);
+ }
+}
+
+function openTab(element, tabName) {
+ // Declare all variables
+ var i, tabContent, tabLinks;
+
+ // Get all elements with class="content" and hide them
+ tabContent = $$("tab here");
+ for (i = 0; i < tabContent.length; i++) {
+ tabContent[i].className = "tab here is-hidden";
+ }
+
+ // Get all elements with class="tab" and remove the class "is-active"
+ tabLinks = $$("tab is-active");
+ for (i = 0; i < tabLinks.length; i++) {
+ tabLinks[i].className = "tab";
+ }
+
+ // Show the current tab, and add an "is-active" class to the button that opened the tab
+ $(tabName).className = "tab here";
+ element.parentElement.className = "tab is-active";
+ //refreshRecordsList();
+}
+
+function copy2clipboard(text) {
+ var textArea = document.createElement("textarea");
+ textArea.style.position = 'fixed';
+ textArea.style.top = 0;
+ textArea.style.left = 0;
+
+ // Ensure it has a small width and height. Setting to 1px / 1em
+ // doesn't work as this gives a negative w/h on some browsers.
+ textArea.style.width = '2em';
+ textArea.style.height = '2em';
+
+ // We don't need padding, reducing the size if it does flash render.
+ textArea.style.padding = 0;
+
+ // Clean up any borders.
+ textArea.style.border = 'none';
+ textArea.style.outline = 'none';
+ textArea.style.boxShadow = 'none';
+
+ // Avoid flash of the white box if rendered for any reason.
+ textArea.style.background = 'transparent';
+ textArea.value = text;
+
+ document.body.appendChild(textArea);
+ textArea.focus();
+ textArea.select();
+ try {
+ var successful = document.execCommand('copy');
+ var msg = successful ? 'successful' : 'unsuccessful';
+ console.log('Copying text command was ' + msg);
+ } catch (err) {
+ console.log('Oops, unable to copy');
+ }
+ document.body.removeChild(textArea);
+ showInfo('value copied successfully!');
+}
+
+function showInfo(text) {
+ var info = $("notification_info");
+ var message = $("info_text");
+ message.innerHTML = text;
+
+ info.className = "notification is-primary";
+ var button = $("info_close");
+ button.onclick = function () {
+ message.value = "";
+ info.className = "notification is-primary is-hidden";
+ };
+ setTimeout(button.onclick, 2000);
+}
+
+function showWindow(text) {
+ var info = $("notification_window");
+ var message = $("info_window");
+ message.innerHTML = text;
+
+ info.classList.remove("is-hidden");
+ var button_info_close = $("info_win_close");
+ button_info_close.onclick = function () {
+ message.value = "";
+ info.classList.add("is-hidden");
+ $("peer_list").remove();
+ };
+ var button_window_close = $("window_close");
+ button_window_close.onclick = function () {
+ message.value = "";
+ info.classList.add("is-hidden");
+ $("peer_list").remove();
+ };
+ var button_window_save = $("window_save");
+ button_window_save.onclick = function () {
+ message.value = "";
+ info.classList.add("is-hidden");
+ //todo save peers
+ var peers = document.querySelectorAll('*[id^="peer-"]');
+ var peer_list = [];
+ for (i = 0; i < peers.length; ++i) {
+ var p = peers[i];
+ if (p.checked) {
+ var peerURL = p.parentElement.parentElement.children[1].innerText;
+ peer_list.push(peerURL);
+ }
+ }
+ fetch('api/peers', {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Riv-Save-Config': 'true'
+ },
+ body: JSON.stringify(peer_list)
+ }).catch(function (error) {
+ console.error('Error:', error);
+ });
+ $("peer_list").remove();
+ };
+}
+
+function add_table(peerList) {
+
+ var peers = [];
+ //const countries = Object.keys(peerList);
+ // get the reference for the body
+ var body = document.createElement("div");
+ // creates a
element and a element
+ var tbl = document.createElement("table");
+ tbl.setAttribute('id', "peer_list");
+ //tbl.setAttribute('cellpadding', '10');
+ var tblBody = document.createElement("tbody");
+
+ // creating all cells
+ for (var c in peerList) {
+ var counter = 1;
+ for (var peer in peerList[c]) {
+ peers.push(peer);
+ // creates a table row
+ var row = document.createElement("tr");
+ row.className = "is-hidden";
+ var imgElement = document.createElement("td");
+ imgElement.className = "big-flag fi fi-" + ui.lookupCountryCodeByAddress(peer);
+ var peerAddress = document.createElement("td");
+ var cellText = document.createTextNode(peer);
+ peerAddress.appendChild(cellText);
+ peerAddress.setAttribute('id', "label_" + peer);
+ var peerPing = document.createElement("td");
+ peerPing.setAttribute('id', peer);
+ var peerPingTime = document.createElement("td");
+ peerPingTime.setAttribute('id', "time_" + peer);
+ var peerSelect = document.createElement("td");
+ var chk = document.createElement('input');
+ chk.setAttribute('type', 'checkbox');
+ chk.setAttribute('id', "peer-" + counter);
+ peerSelect.appendChild(chk);
+
+ row.appendChild(imgElement);
+ row.appendChild(peerAddress);
+ row.appendChild(peerPing);
+ row.appendChild(peerPingTime);
+ row.appendChild(peerSelect);
+ tblBody.appendChild(row);
+ }
+ }
+ // put the in the
+ tbl.appendChild(tblBody);
+ // appends into
+ body.appendChild(tbl);
+ // sets the border attribute of tbl to 2;
+ //tbl.setAttribute("border", "0");
+ showWindow(body.innerHTML);
+ return peers;
+}
+
+function togglePrivKeyVisibility() {
+ if (this.classList.contains("fa-eye")) {
+ this.classList.remove("fa-eye");
+ this.classList.add("fa-eye-slash");
+ $("priv_key_visible").innerHTML = $("priv_key").innerHTML;
+ } else {
+ this.classList.remove("fa-eye-slash");
+ this.classList.add("fa-eye");
+ $("priv_key_visible").innerHTML = "••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••";
+ }
+}
+
+function humanReadableSpeed(speed) {
+ var i = speed == 0 ? 0 : Math.floor(Math.log(speed) / Math.log(1024));
+ return (speed / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + ['Bps', 'kBps', 'MBps', 'GBps', 'TBps'][i];
+}
+
+var ui = ui || {
+ countries: []
+};
+
+ui.getAllPeers = function () {
+ if (!("_allPeersPromise" in ui)) {
+ ui._allPeersPromise = new Promise(function (resolve, reject) {
+ if ("_allPeers" in ui) resolve(ui._allPeers);else fetch('https://map.rivchain.org/rest/peers.json').then(function (response) {
+ return response.json();
+ }).then(function (data) {
+ var _loop = function _loop() {
+ var country = c.slice(0, -3);
+ var filtered = ui.countries.find(function (entry) {
+ return entry.name.toLowerCase() == country;
+ });
+ //let flagFile = filtered ? filtered["flag_4x3"] : "";
+ var flagCode = filtered ? filtered["code"] : "";
+ for (var peer in data[c]) {
+ data[c][peer].countryCode = flagCode;
+ }
+ };
+
+ // add country code to each peer
+ for (var c in data) {
+ _loop();
+ }
+ ui._allPeers = data;
+ resolve(ui._allPeers);
+ }).catch(reject);
+ }).finally(function () {
+ return delete ui._allPeersPromise;
+ });
+ }
+ return ui._allPeersPromise;
+};
+
+ui.showAllPeers = function () {
+ return ui.getAllPeers().then(function (peerList) {
+ var peers = add_table(peerList);
+ //start peers test
+ fetch('api/ping', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify(peers)
+ }).catch(function (error) {
+ console.error('Error:', error);
+ });
+ }).catch(function (error) {
+ console.error(error);
+ });
+};
+
+ui.getConnectedPeers = function () {
+ return fetch('api/peers').then(function (response) {
+ return response.json();
+ });
+};
+
+var regexMulticast = /:\/\/\[fe80::/;
+ui.updateConnectedPeersHandler = function (peers) {
+ $("peers").innerText = "";
+ var regexStrip = /%[^\]]*/gm;
+ var sorted = peers.map(function (peer) {
+ return { "url": peer["remote"], "isMulticast": peer["remote"].match(regexMulticast) };
+ }).sort(function (a, b) {
+ return a.isMulticast > b.isMulticast;
+ });
+ sorted.forEach(function (peer) {
+ var row = $("peers").appendChild(document.createElement('div'));
+ row.className = "overflow-ellipsis";
+ var flag = row.appendChild(document.createElement("span"));
+ if (peer.isMulticast) flag.className = "fa fa-thin fa-share-nodes peer-connected-fl";else flag.className = "fi fi-" + ui.lookupCountryCodeByAddress(peer.url) + " peer-connected-fl";
+ row.append(peer.url.replace(regexStrip, ""));
+ });
+};
+
+ui.updateStatus = function (peers) {
+ var status = "st-error";
+ if (peers) {
+ if (peers.length) {
+ var isNonMulticastExists = peers.filter(function (peer) {
+ return !peer["remote"].match(regexMulticast);
+ }).length;
+ status = isNonMulticastExists ? "st-multicast" : "st-connected";
+ } else {
+ status = "st-connecting";
+ }
+ }
+ Array.from($$("status")).forEach(function (node) {
+ return node.classList.add("is-hidden");
+ });
+ $(status).classList.remove("is-hidden");
+};
+
+ui.updateSpeed = function (peers) {
+ if (peers) {
+ var rsbytes = { "bytes_recvd": peers.reduce(function (acc, peer) {
+ return acc + peer.bytes_recvd;
+ }, 0),
+ "bytes_sent": peers.reduce(function (acc, peer) {
+ return acc + peer.bytes_sent;
+ }, 0),
+ "timestamp": Date.now() };
+ if ("_rsbytes" in ui) {
+ $("dn_speed").innerText = humanReadableSpeed((rsbytes.bytes_recvd - ui._rsbytes.bytes_recvd) * 1000 / (rsbytes.timestamp - ui._rsbytes.timestamp));
+ $("up_speed").innerText = humanReadableSpeed((rsbytes.bytes_sent - ui._rsbytes.bytes_sent) * 1000 / (rsbytes.timestamp - ui._rsbytes.timestamp));
+ }
+ ui._rsbytes = rsbytes;
+ } else {
+ delete ui._rsbytes;
+ $("dn_speed").innerText = "? Bs";
+ $("up_speed").innerText = "? Bs";
+ }
+};
+
+ui.updateConnectedPeers = function () {
+ return ui.getConnectedPeers().then(function (peers) {
+ ui.updateConnectedPeersHandler(peers);
+ ui.updateStatus(peers);
+ ui.updateSpeed(peers);
+ }).catch(function (error) {
+ $("peers").innerText = error.message;
+ ui.updateStatus();
+ ui.updateSpeed();
+ });
+};
+
+ui.lookupCountryCodeByAddress = function (address) {
+ for (var c in ui._allPeers) {
+ for (var peer in ui._allPeers[c]) {
+ if (peer == address) return ui._allPeers[c][peer].countryCode;
+ }
+ }
+};
+
+ui.getSelfInfo = function () {
+ return fetch('api/self').then(function (response) {
+ return response.json();
+ });
+};
+
+ui.updateSelfInfo = function () {
+ return ui.getSelfInfo().then(function (info) {
+ $("ipv6").innerText = info.address;
+ $("subnet").innerText = info.subnet;
+ $("pub_key").innerText = info.key;
+ $("priv_key").innerText = info.private_key;
+ $("ipv6").innerText = info.address;
+ $("version").innerText = info.build_version;
+ }).catch(function (error) {
+ $("ipv6").innerText = error.message;
+ });
+};
+
+ui.sse = new EventSource('/api/sse');
+
+function main() {
+
+ window.addEventListener("load", function () {
+ $("showAllPeersBtn").addEventListener("click", ui.showAllPeers);
+
+ ui.getAllPeers().then(function () {
+ return ui.updateConnectedPeers();
+ });
+ setInterval(ui.updateConnectedPeers, 5000);
+
+ ui.updateSelfInfo();
+ //setInterval(ui.updateSelfInfo, 5000);
+
+ ui.sse.addEventListener("ping", function (e) {
+ var data = JSON.parse(e.data);
+ setPingValue(data.peer, data.value);
+ });
+
+ ui.sse.addEventListener("peers", function (e) {
+ ui.updateConnectedPeersHandler(JSON.parse(e.data));
+ });
+ });
+}
+
+main();
diff --git a/contrib/ui/mesh-ui/ui/assets/mesh-ui.css b/contrib/ui/mesh-ui/ui/assets/mesh-ui.css
index 056251a6..2d981ddc 100644
--- a/contrib/ui/mesh-ui/ui/assets/mesh-ui.css
+++ b/contrib/ui/mesh-ui/ui/assets/mesh-ui.css
@@ -68,12 +68,9 @@ img {
display: block;
}
-html, body {
- height: 100vh;
-}
-
body {
max-width: 690px;
+ min-width: 690px;
margin: auto;
}
@@ -86,6 +83,13 @@ td.fi {
background-position: 0;
}
+footer {
+ position:fixed;
+ bottom:0;
+ max-width: 690px;
+ margin-top: auto;
+}
+
#footer-row td {
padding: 10px;
white-space: nowrap;
diff --git a/contrib/ui/mesh-ui/ui/assets/mesh-ui.js b/contrib/ui/mesh-ui/ui/assets/mesh-ui.js
index cc836c49..294b4f3d 100644
--- a/contrib/ui/mesh-ui/ui/assets/mesh-ui.js
+++ b/contrib/ui/mesh-ui/ui/assets/mesh-ui.js
@@ -229,7 +229,7 @@ function humanReadableSpeed(speed) {
return (speed / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + ['Bps', 'kBps', 'MBps', 'GBps', 'TBps'][i];
}
-var ui = {
+var ui = ui || {
countries: []
};
@@ -372,10 +372,6 @@ ui.sse = new EventSource('/api/sse');
function main() {
- fetch('country.json')
- .then((response) => response.json())
- .then((data) => { ui.countries = data });
-
window.addEventListener("load", () => {
$("showAllPeersBtn").addEventListener("click", ui.showAllPeers);
diff --git a/contrib/ui/mesh-ui/ui/assets/polyfills.js b/contrib/ui/mesh-ui/ui/assets/polyfills.js
new file mode 100644
index 00000000..54c6c2b2
--- /dev/null
+++ b/contrib/ui/mesh-ui/ui/assets/polyfills.js
@@ -0,0 +1,798 @@
+/**
+ * https://github.com/taylorhakes/promise-polyfill
+ */
+!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t():"function"==typeof define&&define.amd?define(t):t()}(0,function(){"use strict";function e(e){var t=this.constructor;return this.then(function(n){return t.resolve(e()).then(function(){return n})},function(n){return t.resolve(e()).then(function(){return t.reject(n)})})}function t(e){return new this(function(t,n){function o(e,n){if(n&&("object"==typeof n||"function"==typeof n)){var f=n.then;if("function"==typeof f)return void f.call(n,function(t){o(e,t)},function(n){r[e]={status:"rejected",reason:n},0==--i&&t(r)})}r[e]={status:"fulfilled",value:n},0==--i&&t(r)}if(!e||"undefined"==typeof e.length)return n(new TypeError(typeof e+" "+e+" is not iterable(cannot read property Symbol(Symbol.iterator))"));var r=Array.prototype.slice.call(e);if(0===r.length)return t([]);for(var i=r.length,f=0;r.length>f;f++)o(f,r[f])})}function n(e){return!(!e||"undefined"==typeof e.length)}function o(){}function r(e){if(!(this instanceof r))throw new TypeError("Promises must be constructed via new");if("function"!=typeof e)throw new TypeError("not a function");this._state=0,this._handled=!1,this._value=undefined,this._deferreds=[],l(e,this)}function i(e,t){for(;3===e._state;)e=e._value;0!==e._state?(e._handled=!0,r._immediateFn(function(){var n=1===e._state?t.onFulfilled:t.onRejected;if(null!==n){var o;try{o=n(e._value)}catch(r){return void u(t.promise,r)}f(t.promise,o)}else(1===e._state?f:u)(t.promise,e._value)})):e._deferreds.push(t)}function f(e,t){try{if(t===e)throw new TypeError("A promise cannot be resolved with itself.");if(t&&("object"==typeof t||"function"==typeof t)){var n=t.then;if(t instanceof r)return e._state=3,e._value=t,void c(e);if("function"==typeof n)return void l(function(e,t){return function(){e.apply(t,arguments)}}(n,t),e)}e._state=1,e._value=t,c(e)}catch(o){u(e,o)}}function u(e,t){e._state=2,e._value=t,c(e)}function c(e){2===e._state&&0===e._deferreds.length&&r._immediateFn(function(){e._handled||r._unhandledRejectionFn(e._value)});for(var t=0,n=e._deferreds.length;n>t;t++)i(e,e._deferreds[t]);e._deferreds=null}function l(e,t){var n=!1;try{e(function(e){n||(n=!0,f(t,e))},function(e){n||(n=!0,u(t,e))})}catch(o){if(n)return;n=!0,u(t,o)}}var a=setTimeout;r.prototype["catch"]=function(e){return this.then(null,e)},r.prototype.then=function(e,t){var n=new this.constructor(o);return i(this,new function(e,t,n){this.onFulfilled="function"==typeof e?e:null,this.onRejected="function"==typeof t?t:null,this.promise=n}(e,t,n)),n},r.prototype["finally"]=e,r.all=function(e){return new r(function(t,o){function r(e,n){try{if(n&&("object"==typeof n||"function"==typeof n)){var u=n.then;if("function"==typeof u)return void u.call(n,function(t){r(e,t)},o)}i[e]=n,0==--f&&t(i)}catch(c){o(c)}}if(!n(e))return o(new TypeError("Promise.all accepts an array"));var i=Array.prototype.slice.call(e);if(0===i.length)return t([]);for(var f=i.length,u=0;i.length>u;u++)r(u,i[u])})},r.allSettled=t,r.resolve=function(e){return e&&"object"==typeof e&&e.constructor===r?e:new r(function(t){t(e)})},r.reject=function(e){return new r(function(t,n){n(e)})},r.race=function(e){return new r(function(t,o){if(!n(e))return o(new TypeError("Promise.race accepts an array"));for(var i=0,f=e.length;f>i;i++)r.resolve(e[i]).then(t,o)})},r._immediateFn="function"==typeof setImmediate&&function(e){setImmediate(e)}||function(e){a(e,0)},r._unhandledRejectionFn=function(e){void 0!==console&&console&&console.warn("Possible Unhandled Promise Rejection:",e)};var s=function(){if("undefined"!=typeof self)return self;if("undefined"!=typeof window)return window;if("undefined"!=typeof global)return global;throw Error("unable to locate global object")}();"function"!=typeof s.Promise?s.Promise=r:(s.Promise.prototype["finally"]||(s.Promise.prototype["finally"]=e),s.Promise.allSettled||(s.Promise.allSettled=t))});
+/** @license
+ * eventsource.js
+ * Available under MIT License (MIT)
+ * https://github.com/Yaffle/EventSource/
+ */
+!function(e){"use strict";var r,H=e.setTimeout,N=e.clearTimeout,j=e.XMLHttpRequest,o=e.XDomainRequest,t=e.ActiveXObject,n=e.EventSource,i=e.document,w=e.Promise,d=e.fetch,a=e.Response,h=e.TextDecoder,s=e.TextEncoder,p=e.AbortController;function c(){this.bitsNeeded=0,this.codePoint=0}"undefined"==typeof window||void 0===i||"readyState"in i||null!=i.body||(i.readyState="loading",window.addEventListener("load",function(e){i.readyState="complete"},!1)),null==j&&null!=t&&(j=function(){return new t("Microsoft.XMLHTTP")}),null==Object.create&&(Object.create=function(e){function t(){}return t.prototype=e,new t}),Date.now||(Date.now=function(){return(new Date).getTime()}),null==p&&(r=d,d=function(e,t){var n=t.signal;return r(e,{headers:t.headers,credentials:t.credentials,cache:t.cache}).then(function(e){var t=e.body.getReader();return n._reader=t,n._aborted&&n._reader.cancel(),{status:e.status,statusText:e.statusText,headers:e.headers,body:{getReader:function(){return t}}}})},p=function(){this.signal={_reader:null,_aborted:!1},this.abort=function(){null!=this.signal._reader&&this.signal._reader.cancel(),this.signal._aborted=!0}}),c.prototype.decode=function(e){function t(e,t,n){if(1===n)return 128>>t<=e&&e<>t<=e&&e<>t<=e&&e<>t<=e&&e<>6?3:31>10)))+String.fromCharCode(56320+(i-65535-1&1023)))}return this.bitsNeeded=o,this.codePoint=i,r};function u(){}null!=h&&null!=s&&function(){try{return"test"===(new h).decode((new s).encode("test"),{stream:!0})}catch(e){}return!1}()||(h=c);function I(e){this.withCredentials=!1,this.readyState=0,this.status=0,this.statusText="",this.responseText="",this.onprogress=u,this.onload=u,this.onerror=u,this.onreadystatechange=u,this._contentType="",this._xhr=e,this._sendTimeout=0,this._abort=u}function l(e){return e.replace(/[A-Z]/g,function(e){return String.fromCharCode(e.charCodeAt(0)+32)})}function f(e){for(var t=Object.create(null),n=e.split("\r\n"),r=0;r -1
+ };
+ }
+
+ function normalizeName(name) {
+ if (typeof name !== 'string') {
+ name = String(name);
+ }
+ if (/[^a-z0-9\-#$%&'*+.^_`|~!]/i.test(name) || name === '') {
+ throw new TypeError('Invalid character in header field name')
+ }
+ return name.toLowerCase()
+ }
+
+ function normalizeValue(value) {
+ if (typeof value !== 'string') {
+ value = String(value);
+ }
+ return value
+ }
+
+ // Build a destructive iterator for the value list
+ function iteratorFor(items) {
+ var iterator = {
+ next: function() {
+ var value = items.shift();
+ return {done: value === undefined, value: value}
+ }
+ };
+
+ if (support.iterable) {
+ iterator[Symbol.iterator] = function() {
+ return iterator
+ };
+ }
+
+ return iterator
+ }
+
+ function Headers(headers) {
+ this.map = {};
+
+ if (headers instanceof Headers) {
+ headers.forEach(function(value, name) {
+ this.append(name, value);
+ }, this);
+ } else if (Array.isArray(headers)) {
+ headers.forEach(function(header) {
+ this.append(header[0], header[1]);
+ }, this);
+ } else if (headers) {
+ Object.getOwnPropertyNames(headers).forEach(function(name) {
+ this.append(name, headers[name]);
+ }, this);
+ }
+ }
+
+ Headers.prototype.append = function(name, value) {
+ name = normalizeName(name);
+ value = normalizeValue(value);
+ var oldValue = this.map[name];
+ this.map[name] = oldValue ? oldValue + ', ' + value : value;
+ };
+
+ Headers.prototype['delete'] = function(name) {
+ delete this.map[normalizeName(name)];
+ };
+
+ Headers.prototype.get = function(name) {
+ name = normalizeName(name);
+ return this.has(name) ? this.map[name] : null
+ };
+
+ Headers.prototype.has = function(name) {
+ return this.map.hasOwnProperty(normalizeName(name))
+ };
+
+ Headers.prototype.set = function(name, value) {
+ this.map[normalizeName(name)] = normalizeValue(value);
+ };
+
+ Headers.prototype.forEach = function(callback, thisArg) {
+ for (var name in this.map) {
+ if (this.map.hasOwnProperty(name)) {
+ callback.call(thisArg, this.map[name], name, this);
+ }
+ }
+ };
+
+ Headers.prototype.keys = function() {
+ var items = [];
+ this.forEach(function(value, name) {
+ items.push(name);
+ });
+ return iteratorFor(items)
+ };
+
+ Headers.prototype.values = function() {
+ var items = [];
+ this.forEach(function(value) {
+ items.push(value);
+ });
+ return iteratorFor(items)
+ };
+
+ Headers.prototype.entries = function() {
+ var items = [];
+ this.forEach(function(value, name) {
+ items.push([name, value]);
+ });
+ return iteratorFor(items)
+ };
+
+ if (support.iterable) {
+ Headers.prototype[Symbol.iterator] = Headers.prototype.entries;
+ }
+
+ function consumed(body) {
+ if (body.bodyUsed) {
+ return Promise.reject(new TypeError('Already read'))
+ }
+ body.bodyUsed = true;
+ }
+
+ function fileReaderReady(reader) {
+ return new Promise(function(resolve, reject) {
+ reader.onload = function() {
+ resolve(reader.result);
+ };
+ reader.onerror = function() {
+ reject(reader.error);
+ };
+ })
+ }
+
+ function readBlobAsArrayBuffer(blob) {
+ var reader = new FileReader();
+ var promise = fileReaderReady(reader);
+ reader.readAsArrayBuffer(blob);
+ return promise
+ }
+
+ function readBlobAsText(blob) {
+ var reader = new FileReader();
+ var promise = fileReaderReady(reader);
+ reader.readAsText(blob);
+ return promise
+ }
+
+ function readArrayBufferAsText(buf) {
+ var view = new Uint8Array(buf);
+ var chars = new Array(view.length);
+
+ for (var i = 0; i < view.length; i++) {
+ chars[i] = String.fromCharCode(view[i]);
+ }
+ return chars.join('')
+ }
+
+ function bufferClone(buf) {
+ if (buf.slice) {
+ return buf.slice(0)
+ } else {
+ var view = new Uint8Array(buf.byteLength);
+ view.set(new Uint8Array(buf));
+ return view.buffer
+ }
+ }
+
+ function Body() {
+ this.bodyUsed = false;
+
+ this._initBody = function(body) {
+ /*
+ fetch-mock wraps the Response object in an ES6 Proxy to
+ provide useful test harness features such as flush. However, on
+ ES5 browsers without fetch or Proxy support pollyfills must be used;
+ the proxy-pollyfill is unable to proxy an attribute unless it exists
+ on the object before the Proxy is created. This change ensures
+ Response.bodyUsed exists on the instance, while maintaining the
+ semantic of setting Request.bodyUsed in the constructor before
+ _initBody is called.
+ */
+ this.bodyUsed = this.bodyUsed;
+ this._bodyInit = body;
+ if (!body) {
+ this._bodyText = '';
+ } else if (typeof body === 'string') {
+ this._bodyText = body;
+ } else if (support.blob && Blob.prototype.isPrototypeOf(body)) {
+ this._bodyBlob = body;
+ } else if (support.formData && FormData.prototype.isPrototypeOf(body)) {
+ this._bodyFormData = body;
+ } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {
+ this._bodyText = body.toString();
+ } else if (support.arrayBuffer && support.blob && isDataView(body)) {
+ this._bodyArrayBuffer = bufferClone(body.buffer);
+ // IE 10-11 can't handle a DataView body.
+ this._bodyInit = new Blob([this._bodyArrayBuffer]);
+ } else if (support.arrayBuffer && (ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))) {
+ this._bodyArrayBuffer = bufferClone(body);
+ } else {
+ this._bodyText = body = Object.prototype.toString.call(body);
+ }
+
+ if (!this.headers.get('content-type')) {
+ if (typeof body === 'string') {
+ this.headers.set('content-type', 'text/plain;charset=UTF-8');
+ } else if (this._bodyBlob && this._bodyBlob.type) {
+ this.headers.set('content-type', this._bodyBlob.type);
+ } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {
+ this.headers.set('content-type', 'application/x-www-form-urlencoded;charset=UTF-8');
+ }
+ }
+ };
+
+ if (support.blob) {
+ this.blob = function() {
+ var rejected = consumed(this);
+ if (rejected) {
+ return rejected
+ }
+
+ if (this._bodyBlob) {
+ return Promise.resolve(this._bodyBlob)
+ } else if (this._bodyArrayBuffer) {
+ return Promise.resolve(new Blob([this._bodyArrayBuffer]))
+ } else if (this._bodyFormData) {
+ throw new Error('could not read FormData body as blob')
+ } else {
+ return Promise.resolve(new Blob([this._bodyText]))
+ }
+ };
+
+ this.arrayBuffer = function() {
+ if (this._bodyArrayBuffer) {
+ var isConsumed = consumed(this);
+ if (isConsumed) {
+ return isConsumed
+ }
+ if (ArrayBuffer.isView(this._bodyArrayBuffer)) {
+ return Promise.resolve(
+ this._bodyArrayBuffer.buffer.slice(
+ this._bodyArrayBuffer.byteOffset,
+ this._bodyArrayBuffer.byteOffset + this._bodyArrayBuffer.byteLength
+ )
+ )
+ } else {
+ return Promise.resolve(this._bodyArrayBuffer)
+ }
+ } else {
+ return this.blob().then(readBlobAsArrayBuffer)
+ }
+ };
+ }
+
+ this.text = function() {
+ var rejected = consumed(this);
+ if (rejected) {
+ return rejected
+ }
+
+ if (this._bodyBlob) {
+ return readBlobAsText(this._bodyBlob)
+ } else if (this._bodyArrayBuffer) {
+ return Promise.resolve(readArrayBufferAsText(this._bodyArrayBuffer))
+ } else if (this._bodyFormData) {
+ throw new Error('could not read FormData body as text')
+ } else {
+ return Promise.resolve(this._bodyText)
+ }
+ };
+
+ if (support.formData) {
+ this.formData = function() {
+ return this.text().then(decode)
+ };
+ }
+
+ this.json = function() {
+ return this.text().then(JSON.parse)
+ };
+
+ return this
+ }
+
+ // HTTP methods whose capitalization should be normalized
+ var methods = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT'];
+
+ function normalizeMethod(method) {
+ var upcased = method.toUpperCase();
+ return methods.indexOf(upcased) > -1 ? upcased : method
+ }
+
+ function Request(input, options) {
+ if (!(this instanceof Request)) {
+ throw new TypeError('Please use the "new" operator, this DOM object constructor cannot be called as a function.')
+ }
+
+ options = options || {};
+ var body = options.body;
+
+ if (input instanceof Request) {
+ if (input.bodyUsed) {
+ throw new TypeError('Already read')
+ }
+ this.url = input.url;
+ this.credentials = input.credentials;
+ if (!options.headers) {
+ this.headers = new Headers(input.headers);
+ }
+ this.method = input.method;
+ this.mode = input.mode;
+ this.signal = input.signal;
+ if (!body && input._bodyInit != null) {
+ body = input._bodyInit;
+ input.bodyUsed = true;
+ }
+ } else {
+ this.url = String(input);
+ }
+
+ this.credentials = options.credentials || this.credentials || 'same-origin';
+ if (options.headers || !this.headers) {
+ this.headers = new Headers(options.headers);
+ }
+ this.method = normalizeMethod(options.method || this.method || 'GET');
+ this.mode = options.mode || this.mode || null;
+ this.signal = options.signal || this.signal;
+ this.referrer = null;
+
+ if ((this.method === 'GET' || this.method === 'HEAD') && body) {
+ throw new TypeError('Body not allowed for GET or HEAD requests')
+ }
+ this._initBody(body);
+
+ if (this.method === 'GET' || this.method === 'HEAD') {
+ if (options.cache === 'no-store' || options.cache === 'no-cache') {
+ // Search for a '_' parameter in the query string
+ var reParamSearch = /([?&])_=[^&]*/;
+ if (reParamSearch.test(this.url)) {
+ // If it already exists then set the value with the current time
+ this.url = this.url.replace(reParamSearch, '$1_=' + new Date().getTime());
+ } else {
+ // Otherwise add a new '_' parameter to the end with the current time
+ var reQueryString = /\?/;
+ this.url += (reQueryString.test(this.url) ? '&' : '?') + '_=' + new Date().getTime();
+ }
+ }
+ }
+ }
+
+ Request.prototype.clone = function() {
+ return new Request(this, {body: this._bodyInit})
+ };
+
+ function decode(body) {
+ var form = new FormData();
+ body
+ .trim()
+ .split('&')
+ .forEach(function(bytes) {
+ if (bytes) {
+ var split = bytes.split('=');
+ var name = split.shift().replace(/\+/g, ' ');
+ var value = split.join('=').replace(/\+/g, ' ');
+ form.append(decodeURIComponent(name), decodeURIComponent(value));
+ }
+ });
+ return form
+ }
+
+ function parseHeaders(rawHeaders) {
+ var headers = new Headers();
+ // Replace instances of \r\n and \n followed by at least one space or horizontal tab with a space
+ // https://tools.ietf.org/html/rfc7230#section-3.2
+ var preProcessedHeaders = rawHeaders.replace(/\r?\n[\t ]+/g, ' ');
+ preProcessedHeaders.split(/\r?\n/).forEach(function(line) {
+ var parts = line.split(':');
+ var key = parts.shift().trim();
+ if (key) {
+ var value = parts.join(':').trim();
+ headers.append(key, value);
+ }
+ });
+ return headers
+ }
+
+ Body.call(Request.prototype);
+
+ function Response(bodyInit, options) {
+ if (!(this instanceof Response)) {
+ throw new TypeError('Please use the "new" operator, this DOM object constructor cannot be called as a function.')
+ }
+ if (!options) {
+ options = {};
+ }
+
+ this.type = 'default';
+ this.status = options.status === undefined ? 200 : options.status;
+ this.ok = this.status >= 200 && this.status < 300;
+ this.statusText = 'statusText' in options ? options.statusText : '';
+ this.headers = new Headers(options.headers);
+ this.url = options.url || '';
+ this._initBody(bodyInit);
+ }
+
+ Body.call(Response.prototype);
+
+ Response.prototype.clone = function() {
+ return new Response(this._bodyInit, {
+ status: this.status,
+ statusText: this.statusText,
+ headers: new Headers(this.headers),
+ url: this.url
+ })
+ };
+
+ Response.error = function() {
+ var response = new Response(null, {status: 0, statusText: ''});
+ response.type = 'error';
+ return response
+ };
+
+ var redirectStatuses = [301, 302, 303, 307, 308];
+
+ Response.redirect = function(url, status) {
+ if (redirectStatuses.indexOf(status) === -1) {
+ throw new RangeError('Invalid status code')
+ }
+
+ return new Response(null, {status: status, headers: {location: url}})
+ };
+
+ exports.DOMException = global.DOMException;
+ try {
+ new exports.DOMException();
+ } catch (err) {
+ exports.DOMException = function(message, name) {
+ this.message = message;
+ this.name = name;
+ var error = Error(message);
+ this.stack = error.stack;
+ };
+ exports.DOMException.prototype = Object.create(Error.prototype);
+ exports.DOMException.prototype.constructor = exports.DOMException;
+ }
+
+ function fetch(input, init) {
+ return new Promise(function(resolve, reject) {
+ var request = new Request(input, init);
+
+ if (request.signal && request.signal.aborted) {
+ return reject(new exports.DOMException('Aborted', 'AbortError'))
+ }
+
+ var xhr = new XMLHttpRequest();
+
+ function abortXhr() {
+ xhr.abort();
+ }
+
+ xhr.onload = function() {
+ var options = {
+ status: xhr.status,
+ statusText: xhr.statusText,
+ headers: parseHeaders(xhr.getAllResponseHeaders() || '')
+ };
+ options.url = 'responseURL' in xhr ? xhr.responseURL : options.headers.get('X-Request-URL');
+ var body = 'response' in xhr ? xhr.response : xhr.responseText;
+ setTimeout(function() {
+ resolve(new Response(body, options));
+ }, 0);
+ };
+
+ xhr.onerror = function() {
+ setTimeout(function() {
+ reject(new TypeError('Network request failed'));
+ }, 0);
+ };
+
+ xhr.ontimeout = function() {
+ setTimeout(function() {
+ reject(new TypeError('Network request failed'));
+ }, 0);
+ };
+
+ xhr.onabort = function() {
+ setTimeout(function() {
+ reject(new exports.DOMException('Aborted', 'AbortError'));
+ }, 0);
+ };
+
+ function fixUrl(url) {
+ try {
+ return url === '' && global.location.href ? global.location.href : url
+ } catch (e) {
+ return url
+ }
+ }
+
+ xhr.open(request.method, fixUrl(request.url), true);
+
+ if (request.credentials === 'include') {
+ xhr.withCredentials = true;
+ } else if (request.credentials === 'omit') {
+ xhr.withCredentials = false;
+ }
+
+ if ('responseType' in xhr) {
+ if (support.blob) {
+ xhr.responseType = 'blob';
+ } else if (
+ support.arrayBuffer &&
+ request.headers.get('Content-Type') &&
+ request.headers.get('Content-Type').indexOf('application/octet-stream') !== -1
+ ) {
+ xhr.responseType = 'arraybuffer';
+ }
+ }
+
+ if (init && typeof init.headers === 'object' && !(init.headers instanceof Headers)) {
+ Object.getOwnPropertyNames(init.headers).forEach(function(name) {
+ xhr.setRequestHeader(name, normalizeValue(init.headers[name]));
+ });
+ } else {
+ request.headers.forEach(function(value, name) {
+ xhr.setRequestHeader(name, value);
+ });
+ }
+
+ if (request.signal) {
+ request.signal.addEventListener('abort', abortXhr);
+
+ xhr.onreadystatechange = function() {
+ // DONE (success or failure)
+ if (xhr.readyState === 4) {
+ request.signal.removeEventListener('abort', abortXhr);
+ }
+ };
+ }
+
+ xhr.send(typeof request._bodyInit === 'undefined' ? null : request._bodyInit);
+ })
+ }
+
+ fetch.polyfill = true;
+
+ if (!global.fetch) {
+ global.fetch = fetch;
+ global.Headers = Headers;
+ global.Request = Request;
+ global.Response = Response;
+ }
+
+ exports.Headers = Headers;
+ exports.Request = Request;
+ exports.Response = Response;
+ exports.fetch = fetch;
+
+ Object.defineProperty(exports, '__esModule', { value: true });
+
+})));
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+/**
+ * ChildNode.append() polyfill
+ * https://gomakethings.com/adding-an-element-to-the-end-of-a-set-of-elements-with-vanilla-javascript/
+ * @author Chris Ferdinandi
+ * @license MIT
+ */
+(function (elem) {
+
+ // Check if element is a node
+ // https://github.com/Financial-Times/polyfill-service
+ var isNode = function (object) {
+
+ // DOM, Level2
+ if (typeof Node === 'function') {
+ return object instanceof Node;
+ }
+
+ // Older browsers, check if it looks like a Node instance)
+ return object &&
+ typeof object === "object" &&
+ object.nodeName &&
+ object.nodeType >= 1 &&
+ object.nodeType <= 12;
+
+ };
+
+ // Add append() method to prototype
+ for (var i = 0; i < elem.length; i++) {
+ if (!window[elem[i]] || 'append' in window[elem[i]].prototype) continue;
+ window[elem[i]].prototype.append = function () {
+ var argArr = Array.prototype.slice.call(arguments);
+ var docFrag = document.createDocumentFragment();
+
+ for (var n = 0; n < argArr.length; n++) {
+ docFrag.appendChild(isNode(argArr[n]) ? argArr[n] : document.createTextNode(String(argArr[n])));
+ }
+
+ this.appendChild(docFrag);
+ };
+ }
+
+})(['Element', 'CharacterData', 'DocumentType']);
+
+
+
+
+
+
+
+
+
+/**
+ * https://github.com/jsPolyfill/Array.prototype.find/blob/master/find.js
+ */
+
+'use strict';
+
+Array.prototype.find = Array.prototype.find || function(callback) {
+ if (this === null) {
+ throw new TypeError('Array.prototype.find called on null or undefined');
+ } else if (typeof callback !== 'function') {
+ throw new TypeError('callback must be a function');
+ }
+ var list = Object(this);
+ // Makes sures is always has an positive integer as length.
+ var length = list.length >>> 0;
+ var thisArg = arguments[1];
+ for (var i = 0; i < length; i++) {
+ var element = list[i];
+ if ( callback.call(thisArg, element, i, list) ) {
+ return element;
+ }
+ }
+};
+
+
+
+
+/**
+ * Array.from() polyfill
+ */
+// From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from
+// Production steps of ECMA-262, Edition 6, 22.1.2.1
+if (!Array.from) {
+ Array.from = (function () {
+ var toStr = Object.prototype.toString;
+ var isCallable = function (fn) {
+ return typeof fn === 'function' || toStr.call(fn) === '[object Function]';
+ };
+ var toInteger = function (value) {
+ var number = Number(value);
+ if (isNaN(number)) { return 0; }
+ if (number === 0 || !isFinite(number)) { return number; }
+ return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number));
+ };
+ var maxSafeInteger = Math.pow(2, 53) - 1;
+ var toLength = function (value) {
+ var len = toInteger(value);
+ return Math.min(Math.max(len, 0), maxSafeInteger);
+ };
+
+ // The length property of the from method is 1.
+ return function from(arrayLike/*, mapFn, thisArg */) {
+ // 1. Let C be the this value.
+ var C = this;
+
+ // 2. Let items be ToObject(arrayLike).
+ var items = Object(arrayLike);
+
+ // 3. ReturnIfAbrupt(items).
+ if (arrayLike == null) {
+ throw new TypeError('Array.from requires an array-like object - not null or undefined');
+ }
+
+ // 4. If mapfn is undefined, then let mapping be false.
+ var mapFn = arguments.length > 1 ? arguments[1] : void undefined;
+ var T;
+ if (typeof mapFn !== 'undefined') {
+ // 5. else
+ // 5. a If IsCallable(mapfn) is false, throw a TypeError exception.
+ if (!isCallable(mapFn)) {
+ throw new TypeError('Array.from: when provided, the second argument must be a function');
+ }
+
+ // 5. b. If thisArg was supplied, let T be thisArg; else let T be undefined.
+ if (arguments.length > 2) {
+ T = arguments[2];
+ }
+ }
+
+ // 10. Let lenValue be Get(items, "length").
+ // 11. Let len be ToLength(lenValue).
+ var len = toLength(items.length);
+
+ // 13. If IsConstructor(C) is true, then
+ // 13. a. Let A be the result of calling the [[Construct]] internal method
+ // of C with an argument list containing the single item len.
+ // 14. a. Else, Let A be ArrayCreate(len).
+ var A = isCallable(C) ? Object(new C(len)) : new Array(len);
+
+ // 16. Let k be 0.
+ var k = 0;
+ // 17. Repeat, while k < len… (also steps a - h)
+ var kValue;
+ while (k < len) {
+ kValue = items[k];
+ if (mapFn) {
+ A[k] = typeof T === 'undefined' ? mapFn(kValue, k) : mapFn.call(T, kValue, k);
+ } else {
+ A[k] = kValue;
+ }
+ k += 1;
+ }
+ // 18. Let putStatus be Put(A, "length", len, true).
+ A.length = len;
+ // 20. Return A.
+ return A;
+ };
+ }());
+}
\ No newline at end of file
diff --git a/contrib/ui/mesh-ui/ui/country.json b/contrib/ui/mesh-ui/ui/country.json
index c528d21d..d7cfc574 100644
--- a/contrib/ui/mesh-ui/ui/country.json
+++ b/contrib/ui/mesh-ui/ui/country.json
@@ -1,4 +1,6 @@
-[
+var ui = ui || {};
+
+ui.countries = [
{
"capital": "Georgetown",
"code": "ac",
diff --git a/contrib/ui/mesh-ui/ui/index.html b/contrib/ui/mesh-ui/ui/index.html
index c222530b..95983b3b 100755
--- a/contrib/ui/mesh-ui/ui/index.html
+++ b/contrib/ui/mesh-ui/ui/index.html
@@ -11,11 +11,27 @@
-
+
+
+
-
-
+
+
@@ -53,7 +69,7 @@
preserveAspectRatio="xMidYMid meet" viewBox="0 0 42 42">
+ fill="white" >
My Node
@@ -67,8 +83,8 @@
style="-ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg);"
preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32">
-
-
+
+
@@ -82,10 +98,10 @@
focusable="false" width="1em" height="1em"
style="-ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg);"
preserveAspectRatio="xMidYMid meet" viewBox="0 0 48 48">
-
+
-
-
+
+
@@ -165,7 +181,8 @@
-
+
-
-
diff --git a/src/admin/admin.go b/src/admin/admin.go
index 7db6f4a6..bf895993 100644
--- a/src/admin/admin.go
+++ b/src/admin/admin.go
@@ -254,10 +254,16 @@ func (a *AdminSocket) StartHttpServer(configFn string, nc *config.NodeConfig) {
a.log.Errorln("An error occurred parsing http address:", err)
return
}
+ addNoCacheHeaders := func(w http.ResponseWriter) {
+ w.Header().Add("Cache-Control", "no-cache, no-store, must-revalidate")
+ w.Header().Add("Pragma", "no-cache")
+ w.Header().Add("Expires", "0")
+ }
http.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Following methods are allowed: getself, getpeers. litening"+u.Host)
})
http.HandleFunc("/api/self", func(w http.ResponseWriter, r *http.Request) {
+ addNoCacheHeaders(w)
switch r.Method {
case "GET":
w.Header().Add("Content-Type", "application/json")
@@ -316,6 +322,7 @@ func (a *AdminSocket) StartHttpServer(configFn string, nc *config.NodeConfig) {
return nil
}
+ addNoCacheHeaders(w)
switch r.Method {
case "GET":
w.Header().Add("Content-Type", "application/json")
@@ -365,6 +372,7 @@ func (a *AdminSocket) StartHttpServer(configFn string, nc *config.NodeConfig) {
})
http.HandleFunc("/api/sse", func(w http.ResponseWriter, r *http.Request) {
+ addNoCacheHeaders(w)
switch r.Method {
case "GET":
w.Header().Add("Content-Type", "text/event-stream")
@@ -399,9 +407,7 @@ func (a *AdminSocket) StartHttpServer(configFn string, nc *config.NodeConfig) {
if docFs == "" {
var nocache = func(fs http.Handler) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
- w.Header().Add("Cache-Control", "no-cache, no-store, must-revalidate")
- w.Header().Add("Pragma", "no-cache")
- w.Header().Add("Expires", "0")
+ addNoCacheHeaders(w)
fs.ServeHTTP(w, r)
}
}