diff --git a/PersistenceViewer/index.html b/PersistenceViewer/index.html index 218a3db..c34b6c2 100644 --- a/PersistenceViewer/index.html +++ b/PersistenceViewer/index.html @@ -2,115 +2,8 @@ - - + +
@@ -144,313 +37,6 @@
- + diff --git a/PersistenceViewer/script.js b/PersistenceViewer/script.js new file mode 100644 index 0000000..2e7adba --- /dev/null +++ b/PersistenceViewer/script.js @@ -0,0 +1,340 @@ +// Insert the date locale that will be applied at the table format here +// Leave empty for default date display +// Set to 'auto' to let JS decide the value +const DateTimeDisplayLocale = 'auto' + +// The name of the first breadcrum +const FirstCrumbName = "House"; + +var ohURL = window.location.origin; +var serviceId = getQueryVariable("serviceid").toLowerCase(); +var startItem = getQueryVariable("startitem"); +var startWithGroups = true; +if (getQueryVariable("startwithgroups").toLowerCase() == "false") { + startWithGroups = false; +} +var shouldSort = true; +if (getQueryVariable("shouldsort").toLowerCase() == "false") { + shouldSort = false; +} +// console.log("getQueryVariable: serviceId=%s, startItem=%s, startWithGroups=%s, shouldSort=%s",serviceId,startItem,startWithGroups,shouldSort); +document.getElementById("start_tm").defaultValue = "00:00"; +document.getElementById("end_tm").defaultValue = "00:00"; + +function sortTable() { + var body = document.getElementById("ItemsBody"); + var rows = body.getElementsByTagName("TR"); + var switching = true; + var shouldSwitch = false; + var i; + var x; + var y; + while (switching) {// loop until no switching has been done + switching = false; + for (i = 0; i < rows.length - 1; i+=1) {// loop through all table rows + shouldSwitch = false; + // get the two elements you want to compare, one from current row and one from the next + x = rows[i].getElementsByTagName("TD")[0]; + y = rows[i + 1].getElementsByTagName("TD")[0]; + if (x.innerHTML.toLowerCase() > y.innerHTML.toLowerCase()) {// check if the two rows should switch place + //if so, mark as a switch and break the loop + shouldSwitch= true; + break; + } + } + if (shouldSwitch) { + // if a switch has been marked, make the switch and mark that a switch has been done + rows[i].parentNode.insertBefore(rows[i + 1], rows[i]); + switching = true; + } + } + var numGroups = 0; + for (i = 0; i < rows.length; i+=1) { + x = rows[i].getElementsByTagName("TD")[0]; + if (x.classList.contains("groupItem")) { + rows[i].parentNode.insertBefore(rows[i], rows[numGroups++]); + } + } +} + +function getQueryVariable(variable) { + var query = window.location.search.substring(1); + var vars = query.split("&"); + var i; + var pair; + for (i = 0; i < vars.length; i+=1) { + pair = vars[i].split("="); + if (decodeURIComponent(pair[0]).toLowerCase() == variable) { + return decodeURIComponent(pair[1]); + } + } + return ""; +} + +// Fills up the items table with group members +function displayItems(groupMembers) { + //console.log("displayItems: groupMembers=%s",JSON.stringify(groupMembers)); + //console.log("displayItems: groupMembers.length=%s",groupMembers.length); + document.getElementById("ItemsBody").innerHTML = ""; + var tableBody = document.getElementById("ItemsBody"); + var i; + var j; + var row; + var cell; + for (i = 0; i < groupMembers.length; i+=1) { + row = document.createElement("TR"); + cell = document.createElement("TD"); + cell.innerHTML = groupMembers[i].name; + row.appendChild(cell); + if (groupMembers[i].type === "Group") {// it's a group + cell.classList.add("groupItem"); + //console.log("displayItems: group name=%s",groupMembers[i].name); + cell.addEventListener("click",function() { + //console.log("displayItems: cell clicked=%s",this.innerHTML); + setBreadcrumbs(this.innerHTML,true); + }); + } + else {// it's an item + cell.addEventListener("click",function() { + //console.log("displayItems: cell clicked=%s",this.innerHTML); + var activeElements = document.getElementsByClassName("active"); + for (j = 0; j < activeElements.length; j+=1) { + activeElements[j].classList.remove("active"); + } + this.classList.add("active"); + setBreadcrumbs(this.innerHTML,false); + }); } + tableBody.appendChild(row); + } + document.getElementById("Items").appendChild(tableBody); + if (shouldSort === true) { + sortTable(); + } +} + +// Fills up main event table. Uses the item name as an input +function displayHistory(item) { + if (typeof(item) === "undefined" || item === null) { + item = document.getElementById("breadcrumbsUL").lastElementChild.lastElementChild.innerHTML; + } + var tableBody = document.getElementById("HistoryBody"); + tableBody.innerHTML = ""; + var row; + var time; + var state; + if (item === "") { + // If API returned null + row = document.createElement("TR"); + time = document.createElement("TD"); + time.innerHTML = "N/A"; + row.appendChild(time); + state = document.createElement("TD"); + state.innerHTML = "N/A"; + row.appendChild(state); + tableBody.appendChild(row); + } + else { + //console.log("displayHistory: item=%s",item); + // Filling up the selection interval + var tempDate; + if (document.getElementById("start_dt").value === "") { + tempDate = new Date(new Date().setMonth(new Date().getMonth() - 1)); + document.getElementById("start_dt").value = tempDate.toISOString().split("T")[0]; + } + if (document.getElementById("start_tm").value === "") { + document.getElementById("start_tm").value = "00:00"; + } + if (document.getElementById("end_dt").value === "") { + tempDate = new Date(new Date().setDate(new Date().getDate() + 1)); + document.getElementById("end_dt").value = tempDate.toISOString().split("T")[0]; + } + if (document.getElementById("end_tm").value === "") { + document.getElementById("end_tm").value = "00:00"; + } + var urlString = ohURL + "/rest/persistence/items/" + item + "?starttime=" + document.getElementById("start_dt").value + "T" + document.getElementById("start_tm").value + "&endtime=" + document.getElementById("end_dt").value + "T" + document.getElementById("end_tm").value; + if (serviceId !== "") { + urlString = urlString + "&serviceId=" + serviceId; + } + // console.log("displayHistory API call: urlString=%s",urlString); + $.ajax({ + url : urlString, + data : {}, + success : function(data) { + //console.log("displayHistory: data=%s",JSON.stringify(data)); + if (data.datapoints > 0) { + var i; + if (data.data.length === 2 * data.datapoints) { + //this corrects for a bug in the REST API for OnOffType items that was fixed in ESH after 2.3 release (https://github.com/eclipse/smarthome/issues/5628) + for (i = 0; i < 2 * data.datapoints; i+=2) { + row = document.createElement("TR"); + time = document.createElement("TD"); + time.innerHTML = new Date(data.data[i].time); + row.appendChild(time); + state = document.createElement("TD"); + state.innerHTML = data.data[i].state; + row.appendChild(state); + tableBody.insertBefore(row, tableBody.childNodes[0]); + } + } + else { + for (i = 0; i < data.datapoints; i+=1) { + // console.log("Parsing event #%s", i); + row = document.createElement("TR"); + time = document.createElement("TD"); + time.innerHTML = getDateString(new Date(data.data[i].time)); + row.appendChild(time); + state = document.createElement("TD"); + state.innerHTML = data.data[i].state; + row.appendChild(state); + tableBody.insertBefore(row, tableBody.childNodes[0]); + } + } + } + else { + row = document.createElement("TR"); + time = document.createElement("TD"); + time.innerHTML = "N/A"; + row.appendChild(time); + state = document.createElement("TD"); + state.innerHTML = "N/A"; + row.appendChild(state); + tableBody.appendChild(row); + } + document.getElementById("History").appendChild(tableBody); + }, + error: function(error){ + console.log("displayHistory: error=%s",error); + } + }); + } +} + +// Sets the date in the correct given format +function getDateString(date) { + if (DateTimeDisplayLocale == '') { + return date + } + else if (DateTimeDisplayLocale == 'auto') { + return date.toLocaleString(); + } else { + return date.toLocaleString(DateTimeDisplayLocale); + } +} + +function getItems(item) { + //console.log("getItems: %s",item); + var urlString = ohURL + "/rest/items"; + if (item === "") { + urlString = urlString + "?recursive=false"; + } + else { + urlString = urlString + "/" + item; + } + //console.log("getItems: urlString=%s",urlString); + $.ajax({ + url : urlString, + data : {}, + success : function(data) { + //console.log("getItems: data=#s",JSON.stringify(data)); + //console.log("getItems: data: groups=%s",JSON.stringify(data).includes("members")); + if (item === "" && startWithGroups === true) { + //console.log("first load: data.length=%s",data.length); + var i; + for (i = data.length - 1; i >= 0; i-=1) { + if (data[i].groupNames.length > 0) { + data.splice(i,1);// remove everything but groups that are not members of a group + } + } + } + if (data.members) { + displayItems(data.members); + } + else { + if(data.name == item) { + // If we are here then there are no items that we need to display. + // Therefore, we hide the item table + document.getElementById("Items").style.display = "none"; + document.getElementById("History").style.width = "calc(100% - 20px)"; + } + else { + displayItems(data); + } + } + displayHistory(item); + }, + error : function(error){ + console.log("getItems: error=%s",error); + } + }); +} + +function setBreadcrumbs(item,isGroup) { + var breadcrumbsUL = document.getElementById("breadcrumbsUL"); + var oldBreadcrumbs = breadcrumbsUL.children; + var lastBreadcrumb = breadcrumbsUL.lastElementChild; + //console.log("setBreadcrumbs: oldBreadcrumbs=%s",JSON.stringify(oldBreadcrumbs)); + if (lastBreadcrumb.hasChildNodes() && lastBreadcrumb.children[0].href === "") { + breadcrumbsUL.removeChild(lastBreadcrumb); + oldBreadcrumbs = breadcrumbsUL.children; + } + if (isGroup) { + var indexFound = 0; + var i; + for (i = oldBreadcrumbs.length - 1; i >= 0; i-=1) { + //console.log("setBreadcrumbs: text=%s",oldBreadcrumbs[i].children[0].text); + if (oldBreadcrumbs[i].children[0].text === item) { + indexFound = i; + } + } + if (indexFound > 0 || item === startItem) { + for (i = oldBreadcrumbs.length - 1; i >= indexFound; i-=1) { + breadcrumbsUL.removeChild(oldBreadcrumbs[i]); + } + } + getItems(item); + var activeElements = document.getElementsByClassName("active"); + for (i = 0; i < activeElements.length; i+=1) { + activeElements[i].classList.remove("active"); + } + } + else { + displayHistory(item); + } + if (item === startItem) { + createFirstBreadcrumb(startItem); + } + else { + var breadcrumb = document.createElement("LI"); + var breadcrumbAnchor = document.createElement("A"); + var breadcrumbText = document.createTextNode(item); + //breadcrumbAnchor.appendChild(breadcrumbText); + if (isGroup) { + breadcrumbAnchor.setAttribute("href","javascript:setBreadcrumbs('" + item + "',true)"); + } + breadcrumbAnchor.appendChild(breadcrumbText); + breadcrumb.appendChild(breadcrumbAnchor); + breadcrumbsUL.appendChild(breadcrumb); + } +} + +function createFirstBreadcrumb(item) { + var newItem = item; + if (item === startItem) { + newItem = FirstCrumbName; + } + var breadcrumbsUL = document.getElementById("breadcrumbsUL"); + var breadcrumb = document.createElement("LI"); + var breadcrumbAnchor = document.createElement("A"); + var breadcrumbText = document.createTextNode(newItem); + //breadcrumbAnchor.appendChild(breadcrumbText); + breadcrumbAnchor.setAttribute("href","javascript:setBreadcrumbs('" + item + "',true)"); + breadcrumbAnchor.appendChild(breadcrumbText); + breadcrumb.appendChild(breadcrumbAnchor); + breadcrumbsUL.appendChild(breadcrumb); +} + +$( "#history_canvas" ).ready($(function() { + //console.log("start"); + createFirstBreadcrumb(startItem); + getItems(startItem); +})); \ No newline at end of file diff --git a/PersistenceViewer/style.css b/PersistenceViewer/style.css new file mode 100644 index 0000000..8ce022d --- /dev/null +++ b/PersistenceViewer/style.css @@ -0,0 +1,124 @@ +input { + border-radius: 5px; + margin: 5px; + background: rgb(155, 155, 155); + color: rgb(33, 33, 33); +} + +.DatepickerBlock { + float: right; + margin: 10px 40px 10px 0px; +} + +body { + margin: 0px; + background: rgb(33, 33, 33); + color: rgb(155, 155, 155); +} + +#history_canvas { + flex-wrap: nowrap; + display: flex; + flex-direction: row; +} + +#breadcrumbsUL { + margin: 10px 0px; + list-style: none; + float: left; +} + +#breadcrumbsUL li { + display: inline; + font-size: 18px; +} + +#breadcrumbsUL li+li:before { + color: rgb(115, 100, 125); + content: ">\00a0"; +} + +#breadcrumbsUL li a { + color: rgb(155, 155, 155); + text-decoration: none; +} + +#breadcrumbsUL li:last-child a { + color: rgb(225, 225, 250); + font-size: 110%; +} + +#breadcrumbsUL li a:hover { + color: rgb(225, 225, 250); +} + +#history_canvas table { + color: black; + border-spacing: 1px; + border-radius: 5px; + background: rgb(115, 100, 125); +} + +#Items { + width: calc(25% - 40px); + margin-top: 0px; + margin-right: 10px; + margin-bottom: 20px; + margin-left: 20px; +} + +#History { + width: calc(75% - 20px); + margin-top: 0px; + margin-right: 20px; + margin-bottom: 20px; + margin-left: 10px; +} + +#History th:first-child, +#History td:first-child { + width: 52ch; +} + +#History th:nth-child(2), +#History td:nth-child(2) { + width: calc(100% - 52ch); +} + +#history_canvas tbody { + overflow-y: auto; + overflow-x: hidden; + display: block; + max-height: 800px; +} + +#history_canvas tr { + width: 100%; + display: table; + table-layout: fixed; +} + +#history_canvas th { + background: rgb(115, 100, 125); +} + +#history_canvas td { + background: rgb(155, 155, 155); +} + +#history_canvas th, +#history_canvas td { + padding: 5px; +} + +#history_canvas td { + border: thin solid black; +} + +#history_canvas td.active { + outline: thick solid rgb(115, 15, 15); +} + +#history_canvas td.groupItem { + background: rgb(115, 135, 105); +} \ No newline at end of file