diff options
Diffstat (limited to 'modules/changes/changes.html')
-rw-r--r-- | modules/changes/changes.html | 257 |
1 files changed, 257 insertions, 0 deletions
diff --git a/modules/changes/changes.html b/modules/changes/changes.html new file mode 100644 index 0000000..cda3da1 --- /dev/null +++ b/modules/changes/changes.html @@ -0,0 +1,257 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +<title>Website change monitor</title> +<script> +"use strict"; + +var urls=[]; + +function pad(s,n,c){ + s=s+""; + if(!c)c="0"; + var fill=c,filllen=n-s.length; + if(filllen<=0)return s; + while(fill.length<filllen)fill+=c; + return fill+s; +} + +function dateformat(date){ + return date.getFullYear()+"/"+pad(date.getMonth(),2)+"/"+pad(date.getDate(),2)+" "+ + pad(date.getHours(),2)+":"+pad(date.getMinutes(),2)+":"+pad(date.getSeconds(),2); +} + +function fetch(method,url,data/*?*/,cb){ + if(!cb){ + cb=data; + data=undefined; + if(!cb)throw new Error("No callback passed to fetch"); + } + var xhr=new XMLHttpRequest(); + xhr.onreadystatechange=function(ev){ + if(xhr.readyState<4)return; + cb(xhr.status,xhr.responseText); + }; + xhr.open(method,url); + xhr.send(data); +} + +function focusData(data){ + document.getElementById("timelinecontainer").classList.add("visible"); + var header=document.getElementById("timelineheader"); + var tbody=document.getElementById("timelinetbody"); + if(!header.firstChild)header.appendChild(document.createTextNode(data.url)); + else header.firstChild.nodeValue=data.url; + + var l=tbody.children,i; + for(i=l.length-1;i>=0;i--)tbody.removeChild(l[i]); + + var tr,td,s; + for(i=data.timeline.length-1;i>=0;i--){ + tr=document.createElement("tr"); + if(i<data.timeline.length-1&&data.timeline[i][1]==data.timeline[i+1][1]){ + tr.classList.add("repeated"); + } + + td=document.createElement("td"); + td.appendChild(document.createTextNode(dateformat(new Date(data.timeline[i][0])))); + tr.appendChild(td); + + td=document.createElement("td"); + s=data.timeline[i][1]; + if(s==null)s="(error while retrieving page)"; + td.appendChild(document.createTextNode(s)); + tr.appendChild(td); + + tbody.appendChild(tr); + } +} + +function makeURLtr(url){ + var tr=document.createElement("tr"); + var td=document.createElement("td"); + + td.appendChild(document.createTextNode(url)); + + var e=document.createElement("input"); + e.type="button"; + e.classList.add("rowdeletebutton"); + e.value="X"; + e.setAttribute("title","Delete URL"); + e.addEventListener("click",function(ev){ + ev.stopPropagation(); + fetch("DELETE","/changes/url",url,function(status,body){ + if(document.getElementById("timelineheader").firstChild.nodeValue==url){ + document.getElementById("timelinecontainer").classList.remove("visible"); + } + if(status!=200){ + alert("Error deleting url: ("+status+") "+body); + return; + } + tr.parentNode.removeChild(tr); + updateURLs(); + }); + }); + td.appendChild(e); + + tr.addEventListener("click",function(ev){ + fetch("GET","/changes/url?url="+encodeURIComponent(url),function(status,body){ + if(status!=200){ + alert("Error getting data: ("+status+") "+body); + return; + } + var data; + try { + data=JSON.parse(body); + } catch(e){ + alert("Server sent invalid data!"); + console.log("Invalid data:",data); + return; + } + focusData(data); + }); + }); + + tr.appendChild(td); + return tr; +} + +function updateURLs(){ + fetch("GET","/changes/urls",function(status,body){ + urls=JSON.parse(body); + if(!urls){ + urls=[]; + alert("Error retrieving URLs!"); + return; + } + var tbody=document.getElementById("urlstable").firstElementChild; + var l=tbody.children,i,tr,td; + for(i=l.length-1;i>=0;i--)tbody.removeChild(l[i]); + for(i=0;i<urls.length;i++){ + tbody.appendChild(makeURLtr(urls[i])); + } + }); +} + +function addURLbutton(){ + var e=document.getElementById("urladdinput"); + var url=e.value; + fetch("POST","/changes/url",url,function(status,body){ + if(status==200){ + e.value=""; + } else { + alert("URL submission error: ("+status+") "+body); + } + updateURLs(); + }); +} + +function addURLkeypress(ev){ + if(ev.keyCode==10||ev.keyCode==13)addURLbutton(); +} + +function sendRefresh(){ + fetch("POST","/changes/refresh",function(status,body){}); +} + +window.addEventListener("load",function(){ + updateURLs(); + //setInterval(updateURLs,10*1000); +}); +</script> +<style> +body{ + font-family:Georgia,Times,serif; + font-size:14px; +} +h1{ + /*font-size:28px;*/ + /*margin:19px 0 19px 0;*/ +} +#urlscontainer{ + width:500px; +} +#urlstablecontainer{ + height:130px; + border:1px #888 solid; + overflow-y:scroll; +} +#urlstable{ + width:100%; + border-collapse:collapse; +} +#urlstable tbody, #urlstable tr, #urlstable td{ + width:100%; +} +#urlstable tr{ + cursor:pointer; +} +#urlstable tr:hover{ + background-color:#ddd; +} +#urlstable tr:nth-child(2n){ + background-color:#eee; +} +#urlstable td{ + height:100%; + vertical-align:middle; +} +#urladdinput{ + width:300px; +} +.rowdeletebutton{ + color:red; + font-family:Monospace; + font-weight:bold; + float:right; + cursor:pointer; + padding:0 3px 0 3px; + background-color:rgba(255,0,0,0.1); + border-radius:2px; + border-width:0; +} + +#timelinetbl{ + border-collapse:collapse; +} +#timelinetbl td, #timelinetbl th{ + border:1px #888 solid; +} + +tr.repeated{ + color:#aaa; + font-size:10px; +} + +#timelinecontainer{ + display:none; +} +#timelinecontainer.visible{ + display:block; +} +</style> +</head> +<body> +<h1>Website change monitor</h1> +<b>URLs:</b> +<input type="button" onclick="sendRefresh();" value="Redownload sites"> +<br> +<div> + <div id="urlscontainer"> + <div id="urlstablecontainer"> + <table id="urlstable"><tbody></tbody></table> + </div> + <input type="text" id="urladdinput" onkeypress="addURLkeypress(event);"> + <input type="button" onclick="addURLbutton();" value="Add URL" title="Add URL to watch list"> + </div> +</div> +<div id="timelinecontainer"> + <h2 id="timelineheader"></h2> + <table id="timelinetbl"> + <thead><tr><th>Date</th><th>Hash</th></tr></thead> + <tbody id="timelinetbody"></tbody> + </table> +</div> +</body> +</html> |