function startSenshamartDemoUI() { //shared const clearTable = (obj) => { while (obj.rows.length !== 0) { obj.deleteRow(-1); } }; //init const publicKeySpan = document.getElementById("publicKey"); const coinCountSpan = document.getElementById("coinCount"); const status = document.getElementById("status"); let currentTab = document.getElementById("sensorsTab"); const initTab = function (baseName, style) { const buttonName = baseName + "Button"; const button = document.getElementById(buttonName); if (button === null) { console.log("Couldn't find: " + buttonName); return; } const tabName = baseName + "Tab"; const tab = document.getElementById(tabName); if (tab === null) { console.log("Couldn't find: " + tabName); return; } tab.style.display = "none"; button.onclick = function (_) { currentTab.style.display = "none"; tab.style.display = style; currentTab = tab; }; }; initTab("sensors", "grid"); initTab("registerSensor", "block"); initTab("integrate", "block"); initTab("freeformQuery", "block"); currentTab.style.display = "grid"; const refreshInfo = { balance: { onNew: [], onDel: [], onChange: [], vals: {} }, sensor: { onNew: [], onDel: [], onChange: [], vals: {} }, broker: { onNew: [], onDel: [], onChange: [], vals: {} }, integration: { onNew: [], onDel: [], onChange: [], vals: {} } }; let ourPubKey = null; const refreshButton = document.getElementById("refresh"); const chainDepth = document.getElementById("chainDepth"); let refreshCounter = 0; let refreshFailed = false; let loaded = false; const statusOK = function (str) { status.innerHTML = str; status.style.backgroundColor = 'lightgreen'; }; const statusWorking = function (str) { status.innerHTML = str; status.style.backgroundColor = 'yellow'; }; const statusError = function (str) { status.innerHTML = str; status.style.backgroundColor = 'red'; }; const refresh = function () { refreshButton.disabled = true; if (loaded !== true) { return; } if (refreshCounter !== 0) { status.innerHTML = "Couldn't refresh, already currently refreshing"; return; } const updateInfo = (type, newData) => { const oldData = type.vals; type.vals = newData; for (const [key, value] of Object.entries(newData)) { if (!(key in oldData)) { for (const handler of type.onNew) { handler(key, value); } } else { for (const handler of type.onChange) { handler(key, value); } } } for (const [key, value] of Object.entries(oldData)) { if (!(key in newData)) { for (const handler of type.onDel) { handler(key); } } } }; const fetchFinal = () => { refreshCounter--; if (refreshCounter === 0) { refreshButton.disabled = false; if (!refreshFailed) { statusOK("Refresh finished"); } } }; const refreshFetch = function (type, path) { fetch(path).then((res) => { return res.json(); }).then((data) => { updateInfo(type, data); }).catch((err) => { console.log(err); statusError("Error: " + err.message); refreshFailed = true; }).finally(fetchFinal); }; refreshCounter = 4; refreshFailed = false; statusWorking("Refreshing"); refreshFetch(refreshInfo.sensor, "/Sensors"); refreshFetch(refreshInfo.broker, "/Brokers"); refreshFetch(refreshInfo.balance, "/Balances"); fetch('/chain-length').then((res) => { return res.json(); }).then((data) => { chainDepth.innerHTML = data; }).catch((err) => { console.log(err); statusError("Error: " + err.message); refreshFailed = true; }).finally(fetchFinal); }; refreshButton.onclick = function (_) { refresh(); }; fetch("/public-key").then(function (res) { return res.json(); }).then(function (pubKey) { ourPubKey = pubKey; publicKeySpan.innerHTML = pubKey; loaded = true; refresh(); }).catch(function (err) { console.log(err); }); //our balance header refreshInfo.balance.onNew.push(function (key, data) { if (key === ourPubKey) { coinCountSpan.innerHTML = data.balance; } }); refreshInfo.balance.onChange.push(function (key, data) { if (key === ourPubKey) { coinCountSpan.innerHTML = data.balance; } }); //sensors const sensorSensors = document.getElementById("sensorSensors"); const sensorInfo = document.getElementById("sensorInfo"); const sensorInfoName = document.getElementById("sensorInfoName"); const sensorInfoCPM = document.getElementById("sensorInfoCPM"); const sensorInfoCPKB = document.getElementById("sensorInfoCPKB"); const sensorInfoBroker = document.getElementById("sensorInfoBroker"); const sensorInfoRDFBody = document.getElementById("sensorInfoRDFBody"); const sensorSetInfo = function (sensor) { sensorInfo.style.display = "block"; sensorInfoName.value = sensor.metadata.name; sensorInfoCPM.value = sensor.metadata.costPerMinute; sensorInfoCPKB.value = sensor.metadata.costPerKB; sensorInfoBroker.value = sensor.metadata.integrationBroker; clearTable(sensorInfoRDFBody); if ("extraNodes" in sensor.metadata) { for (const tuple of sensor.metadata.extraNodes) { const dataRow = sensorInfoRDFBody.insertRow(); const sCell = dataRow.insertCell(); sCell.style.border = "1px solid black"; sCell.innerHTML = tuple.s; const pCell = dataRow.insertCell(); pCell.style.border = "1px solid black"; pCell.innerHTML = tuple.p; const oCell = dataRow.insertCell(); oCell.style.border = "1px solid black"; oCell.innerHTML = tuple.o; } } if ("extraLiterals" in sensor.metadata) { for (const tuple of sensor.metadata.extraLiterals) { const dataRow = sensorInfoRDFBody.insertRow(); const sCell = dataRow.insertCell(); sCell.style.border = "1px solid black"; sCell.innerHTML = tuple.s; const pCell = dataRow.insertCell(); pCell.style.border = "1px solid black"; pCell.innerHTML = tuple.p; const oCell = dataRow.insertCell(); oCell.style.border = "1px solid black"; oCell.innerHTML = tuple.o; } } } refreshInfo.sensor.onNew.push(function (key, data) { sensorSensors.append(new Option(key, key)); }); refreshInfo.sensor.onDel.push(function (key, data) { const child = sensorSensors.namedItem(key); if (child !== null) { sensorSensors.removeChild(child); } }); refreshInfo.sensor.onChange.push(function (key, data) { const child = sensorSensors.namedItem(key); if (child === null) { return; } if (child.selected) { sensorSetInfo(data); } }); sensorSensors.oninput = function (_) { if (sensorSensors.selectedIndex === -1) { brokerInfo.style.display = "none"; return; } const selectedIndex = sensorSensors.selectedIndex; const selectedOption = sensorSensors.item(selectedIndex); const selectedSensor = refreshInfo.sensor.vals[selectedOption.value]; sensorSetInfo(selectedSensor); }; //register sensor const registerName = document.getElementById("registerName"); const registerCPM = document.getElementById("registerCPM"); const registerCPKB = document.getElementById("registerCPKB"); const registerBroker = document.getElementById("registerBroker"); const registerClearMetadata = document.getElementById("registerClearMetadata"); const registerMetadata = document.getElementById("registerMetadata"); registerMetadata.value = ""; const registerReward = document.getElementById("registerReward"); const registerGo = document.getElementById("registerGo"); const registerResult = document.getElementById("registerResult"); let registerParsedNodeMetadata = []; let registerParsedLiteralMetadata = []; registerCPM.addEventListener("change", () => { const parsed = Number.parseInt(registerCPM.value, 10); if (Number.isNaN(parsed) || parsed < 0) { registerCPM.value = 1; } }); registerCPKB.addEventListener("change", () => { const parsed = Number.parseInt(registerCPKB.value, 10); if (Number.isNaN(parsed) || parsed < 0) { registerCPKB.value = 1; } }); registerReward.addEventListener("change", () => { const parsed = Number.parseInt(registerReward.value, 10); if (Number.isNaN(parsed) || parsed < 0) { registerReward.value = 0; } }); refreshInfo.broker.onNew.push(function (key, data) { registerBroker.append(new Option(key, key)); }); refreshInfo.broker.onDel.push(function (key, data) { const child = registerBroker.namedItem(key); if (child !== null) { registerBroker.removeChild(child); } }); registerClearMetadata.addEventListener("click", (_) => { registerParsedNodeMetadata = []; registerParsedLiteralMetadata = []; registerMetadata.value = ""; }); registerMetadata.addEventListener('change', (event) => { if (event.target.files.length !== 1) { statusError("No file was selected"); return; } registerMetadata.disabled = true; registerClearMetadata.disabled = true; const reader = new FileReader(); reader.onload = (_) => { const parser = new N3.Parser(); try { const tuples = parser.parse(reader.result); registerParsedLiteralMetadata = []; registerParsedNodeMetadata = []; for (const tuple of tuples) { const adding = { s: tuple._subject.value, p: tuple._predicate.value, o: tuple._object.value }; if (tuple._object.termType === "Literal") { registerParsedLiteralMetadata.push(adding); } else { registerParsedNodeMetadata.push(adding); } } statusOK(`File was read sucessfully for ${registerParsedLiteralMetadata.length + registerParsedNodeMetadata.length} tuples`); registerMetadata.disabled = false; registerClearMetadata.disabled = false; } catch (ex) { statusError("Couldn't read file: " + ex.message); console.log(ex); registerMetadata.value = ""; registerMetadata.disabled = false; registerClearMetadata.disabled = false; } }; reader.readAsText(event.target.files[0]); }); registerGo.addEventListener("click", (_) => { if (registerBroker.selectedIndex === -1) { statusError("No broker selected"); return; } registerGo.disabled = true; const input = { sensorName: registerName.value, costPerMinute: Number.parseInt(registerCPM.value), costPerKB: Number.parseInt(registerCPKB.value), integrationBroker: registerBroker.item(registerBroker.selectedIndex).value, rewardAmount: Number.parseInt(registerReward.value), }; if (registerParsedLiteralMetadata.length !== 0) { input.extraLiteralMetadata = registerParsedLiteralMetadata; } if (registerParsedNodeMetadata.length !== 0) { input.extraNodeMetadata = registerParsedNodeMetadata; } fetch("/sensorregistration", { method: 'POST', headers: { "Content-Type": "application/json" }, body: JSON.stringify(input) }).then((res) => { return res.json(); }).then((res) => { if (!res.result) { statusError("Error while creating register sensor transaction: " + res.reason); return; } registerResult.innerHTML = JSON.stringify(res.tx,null,2); }).finally(() => { registerGo.disabled = false; }) }); //integrate const integrateAdd = document.getElementById("integrateAdd"); const integrateAddSensors = document.getElementById("integrateAddSensors"); const integrateRemove = document.getElementById("integrateRemove"); const integrateRemoveSensors = document.getElementById("integrateRemoveSensors"); const integrateInfo = document.getElementById("integrateInfo"); const integrateInfoName = document.getElementById("integrateInfoName"); const integrateInfoCPM = document.getElementById("integrateInfoCPM"); const integrateInfoCPKB = document.getElementById("integrateInfoCPKB"); const integrateInfoBroker = document.getElementById("integrateInfoBroker"); const integrateInfoAmountLabel = document.getElementById("integrateInfoAmountLabel"); const integrateInfoAmount = document.getElementById("integrateInfoAmount"); const integrateAcceptModifications = document.getElementById("integrateAcceptModifications"); const integrateReward = document.getElementById("integrateReward"); const integrateGo = document.getElementById("integrateGo"); const integrateResult = document.getElementById("integrateResult"); const integrateConnectInfoBody = document.getElementById("integrateConnectInfoBody"); let integrateSelectedSensor = null; let integrateModifiedCount = 0; const integrateCreatedOutputs = new Map(); const integrateSetInfo = (sensorInfo) => { const sensor = refreshInfo.sensor.vals[sensorInfo.sensor]; integrateSelectedSensor = sensor.metadata.name; integrateInfoName.value = sensor.metadata.name; integrateInfoCPM.value = sensor.metadata.costPerMinute; integrateInfoCPKB.value = sensor.metadata.costPerKB; integrateInfoBroker.value = sensor.metadata.integrationBroker; if ("amount" in sensorInfo) { integrateInfoAmountLabel.style.display = "block"; integrateInfoAmount.style.display = "block"; integrateInfoAmount.value = sensorInfo.amount; } else { integrateInfoAmountLabel.style.display = "none"; integrateInfoAmount.style.display = "none"; } if ("modified" in sensorInfo && sensorInfo.modified) { integrateAcceptModifications.style.display = "block"; } else { integrateAcceptModifications.style.display = "none"; } integrateInfo.style.display = "block"; }; refreshInfo.sensor.onNew.push((key, data) => { if (!integrateCreatedOutputs.has(key)) { integrateAddSensors.append(new Option(key, key)); } }); refreshInfo.sensor.onDel.push((key, data) => { const child = integrateAddSensors.namedItem(key); if (child !== null) { integrateAddSensors.removeChild(child); } if (integrateCreatedOutputs.has(key)) { const found = integrateCreatedOutputs.get(key); found.option.style.color = "red"; if (!found.modified) { integrateModifiedCount++; integrateGo.disabled = true; found.modified = true; } if (integrateSelectedSensor === key) { integrateAcceptModifications.style.display = "block"; } } }); refreshInfo.sensor.onChange.push((key, data) => { if (integrateCreatedOutputs.has(key)) { const found = integrateCreatedOutputs.get(key); if (found.hash !== data.hash) { found.hash = data.hash; if (!found.modified) { found.option.style.color = "orange"; integrateModifiedCount++; integrateGo.disabled = true; found.modified = true; } if (integrateSelectedSensor === key) { integrateSetInfo(found); } } } else { if (integrateSelectedSensor === key) { integrateSetInfo({ name: key }); } } }); integrateAdd.addEventListener("click", (_) => { if (integrateAddSensors.selectedIndex === -1) { statusError("No sensor selected"); return -1; } const sensorName = integrateAddSensors.item(integrateAddSensors.selectedIndex).value; if (integrateCreatedOutputs.has(sensorName)) { return; } const created = { option: new Option(sensorName, sensorName), amount: 1, sensor: sensorName, hash: refreshInfo.sensor.vals[sensorName].hash }; integrateCreatedOutputs.set(sensorName, created); integrateAddSensors.remove(integrateAddSensors.selectedIndex); integrateRemoveSensors.append(created.option); integrateRemoveSensors.selectedIndex = integrateRemoveSensors.options.length - 1; integrateRemoveSensors.focus(); integrateSetInfo(created); }); integrateAddSensors.addEventListener("input", (_) => { if (integrateAddSensors.selectedIndex === -1) { integrateInfo.style.display = "none"; integrateSelectedSensor = null; return; } integrateSetInfo({ sensor: integrateAddSensors.item(integrateAddSensors.selectedIndex).value }); }); integrateAddSensors.addEventListener("focus", (_) => { if (integrateAddSensors.selectedIndex !== -1) { integrateSetInfo({ sensor: integrateAddSensors.item(integrateAddSensors.selectedIndex).value }); } }); integrateRemove.addEventListener("click", (_) => { if (integrateRemoveSensors.selectedIndex === -1) { statusError("No sensor selected"); return -1; } const sensorName = integrateRemoveSensors.item(integrateRemoveSensors.selectedIndex).value; const found = integrateCreatedOutputs.get(sensorName); integrateRemoveSensors.removeChild(found.option); integrateAddSensors.append(new Option(sensorName, sensorName)); integrateAddSensors.selectedIndex = integrateAddSensors.options.length - 1; integrateAddSensors.focus(); integrateCreatedOutputs.delete(sensorName); integrateSetInfo({ sensor: sensorName }); }); integrateRemoveSensors.addEventListener("input", (_) => { if (integrateRemoveSensors.selectedIndex === -1) { integrateInfo.style.display = "none"; integrateSelectedSensor = null; return; } const found = integrateCreatedOutputs.get(integrateRemoveSensors.item(integrateRemoveSensors.selectedIndex).value); integrateSetInfo(found); }); integrateRemoveSensors.addEventListener("focus", (_) => { if (integrateRemoveSensors.selectedIndex !== -1) { const found = integrateCreatedOutputs.get(integrateRemoveSensors.item(integrateRemoveSensors.selectedIndex).value); integrateSetInfo(found); } }); integrateInfoAmount.addEventListener("change", (_) => { const parsed = Number.parseInt(integrateInfoAmount.value, 10); const found = integrateCreatedOutputs.get(integrateSelectedSensor); if (Number.isNaN(parsed) || parsed < 1) { integrateInfoAmount.value = 1; found.amount = 1; } else { found.amount = parsed; } }); integrateAcceptModifications.addEventListener("click", (_) => { if (!integrateCreatedOutputs.has(integrateSelectedSensor)) { return; } const found = integrateCreatedOutputs.get(integrateSelectedSensor); found.modified = false; found.option.style.color = "black"; integrateModifiedCount--; integrateAcceptModifications.style.display = "none"; if (integrateModifiedCount === 0) { integrateGo.disabled = false; } }); integrateReward.addEventListener("change", () => { const parsed = Number.parseInt(integrateReward.value, 10); if (Number.isNaN(parsed) || parsed < 0) { integrateReward.value = 0; } }); integrateGo.addEventListener("click", (_) => { if (integrateModifiedCount !== 0) { return; } const input = { rewardAmount: Number.parseInt(integrateReward.value), witnessCount: 0, outputs: [] }; for (const [name, output] of integrateCreatedOutputs.entries()) { const sensor = refreshInfo.sensor.vals[name]; input.outputs.push({ amount: output.amount, sensorName: name, sensorHash: sensor.hash, brokerHash: refreshInfo.broker.vals[sensor.metadata.integrationBroker].hash }); } const forDisplayLater = []; for (let i = 0; i < input.outputs.length; i++) { const sensor = refreshInfo.sensor.vals[input.outputs[i].sensorName]; forDisplayLater.push({ sensor: sensor.metadata.name, brokerIp: refreshInfo.broker.vals[sensor.metadata.integrationBroker].metadata.endpoint, index: i }); } integrateGo.disabled = true; fetch("/integration", { method: 'POST', headers: { "Content-Type": "application/json" }, body: JSON.stringify(input) }).then((res) => { return res.json(); }).then((res) => { if (!res.result) { statusError("Error while creating integration transaction: " + res.reason); return; } clearTable(integrateConnectInfoBody); for (const display of forDisplayLater) { const dataRow = integrateConnectInfoBody.insertRow(); const sensorNameCell = dataRow.insertCell(); sensorNameCell.border = "1px solid black"; sensorNameCell.innerHTML = display.sensor; const brokerIpCell = dataRow.insertCell(); brokerIpCell.border = "1px solid black"; brokerIpCell.innerHTML = display.brokerIp; const topicCell = dataRow.insertCell(); topicCell.border = "1px solid black"; topicCell.innerHTML = 'out/' + res.hash + '/' +display.index; } integrateResult.innerHTML = JSON.stringify(res.tx, null, 2); }).finally(() => { integrateGo.disabled = false; }) }); //freeform query const freeformSelect = document.getElementById("freeformSelect"); const freeformQuery = document.getElementById("freeformQuery"); const freeformGo = document.getElementById("freeformGo"); const freeformHead = document.getElementById("freeformHead"); const freeformBody = document.getElementById("freeformBody"); const freeformEscaper = document.createElement('textarea'); const freeformEscape = (html) => { freeformEscaper.textContent = html; return freeformEscaper.innerHTML; } const freeformQueries = { "Get all camera sensors": "SELECT ?sensor ?lat ?long ?measures WHERE { ?sensor ?observes. ?sensor ?location. ?observes ?measures . ?location ?lat . ?location ?long . ?observes \"video\"}", "Get all milk pressure sensors": "SELECT ?sensor ?lat ?long ?measures WHERE { ?sensor ?observes. ?sensor ?location. ?observes ?measures . ?location ?lat . ?location ?long . ?observes \"Milk Pressure\"}", "Get all air temperature sensors": "SELECT ?sensor ?lat ?long ?measures WHERE { ?sensor ?observes. ?sensor ?location. ?observes ?measures . ?location ?lat . ?location ?long . ?observes \"Air Temperature\"}", "Get all air humidity sensors": "SELECT ?sensor ?lat ?long ?measures WHERE { ?sensor ?observes. ?sensor ?location. ?observes ?measures . ?location ?lat . ?location ?long . ?observes \"Relative air Humidity\"}", "Get all milk temperature sensors": "SELECT ?sensor ?lat ?long ?measures WHERE { ?sensor ?observes. ?sensor ?location. ?observes ?measures . ?location ?lat . ?location ?long . ?observes \"Milk Temperature\"}", "Get all sensors in Australia": "SELECT ?sensor ?lat ?long ?measures WHERE { ?sensor ?observes. ?sensor ?location. ?observes ?measures . ?location ?lat . ?location ?long . FILTER(xsd:decimal(?long) > 113.338953078 && xsd:decimal(?long) < 153.569469029 && xsd:decimal(?lat) > -43.6345972634 && xsd:decimal(?lat) < -10.6681857235)}" }; const freeformOnInput = () => { if (freeformSelect.selectedIndex === -1) { return; } const selected = freeformSelect.item(freeformSelect.selectedIndex); freeformQuery.innerHTML = freeformEscape(freeformQueries[selected.value]); }; freeformSelect.addEventListener("input", freeformOnInput); for (const [key, value] of Object.entries(freeformQueries)) { freeformSelect.append(new Option(key, key)); } freeformOnInput(); freeformGo.onclick = (_) => { if (freeformSelect.selectedIndex === -1) { statusError("No query selected"); return; } const input = freeformQueries[freeformSelect.item(freeformSelect.selectedIndex).value]; freeformGo.disabled = true; clearTable(freeformHead); clearTable(freeformBody); fetch("/sparql", { method: 'POST', headers: { "Content-Type": "application/json" }, body: JSON.stringify({ query: input }) }).then((res) => { return res.json(); }).then((res) => { if (!res.result) { statusError("Error when querying: " + res.reason); return; } const headersSet = new Set(); for (const obj of res.values) { for (const [key, value] of Object.entries(obj)) { if (!headersSet.has(key)) { headersSet.add(key); } } } const headers = new Map(); for (const header of [...headersSet].sort()) { headers.set(header, headers.size); } const headerRow = freeformHead.insertRow(-1); const headerCells = []; for (var i = 0; i < headers.size; ++i) { const created = document.createElement('th'); headerRow.appendChild(created); headerCells.push(created); } for (const [key, value] of headers) { headerCells[value].innerHTML = key; } for (const obj of res.values) { const dataRow = freeformBody.insertRow(); const cells = []; for (var i = 0; i < headers.size; ++i) { const newCell = dataRow.insertCell(); newCell.style.border = "1px solid black"; cells.push(newCell); } for (const [key, value] of Object.entries(obj)) { cells[headers.get(key)].innerHTML = value.value; } statusOK("Finished query"); } }).finally(() => { freeformGo.disabled = false; }); }; }