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