<!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()+1,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; header.setAttribute("href",data.url); document.getElementById("diffbox").innerHTML=""; var l=tbody.children,i; for(i=l.length-1;i>=0;i--)tbody.removeChild(l[i]); var tr,td,e,s,date; for(i=data.timeline.length-1;i>=0;i--){ tr=document.createElement("tr"); if(i>0&&data.timeline[i][1]==data.timeline[i-1][1]){ tr.classList.add("repeated"); } td=document.createElement("td"); date=new Date(data.timeline[i][0]); td.appendChild(document.createTextNode(dateformat(date))); 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); td=document.createElement("td"); e=document.createElement("input"); e.setAttribute("type","radio"); e.setAttribute("name","diffold"); e.setAttribute("value",data.timeline[i][0]); td.appendChild(e); tr.appendChild(td); td=document.createElement("td"); e=document.createElement("input"); e.setAttribute("type","radio"); e.setAttribute("name","diffnew"); e.setAttribute("value",data.timeline[i][0]); td.appendChild(e); tr.appendChild(td); td=document.createElement("td"); e=document.createElement("a"); e.href="javascript:void(0)"; e.appendChild(document.createTextNode("Delete till here")); e.addEventListener("click",(function(url,date){return function(ev){ if(!confirm( "This will delete history up to and including the selected date "+ "("+dateformat(date)+"), for the following url:\n"+data.url+ "\nIs this OK?")){ return; } fetch("DELETE","/changes/url/history",JSON.stringify({url:data.url,todate:date}),function(status,body){ if(status!=200){ alert("Error deleting history: ("+status+") "+body); return; } focusURL(data.url); }); };})(data.url,date)); td.appendChild(e); tr.appendChild(td); tbody.appendChild(tr); } } function focusURL(url){ 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); }); } 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(); if(!confirm("Delete the following url?\n"+url))return; fetch("DELETE","/changes/url",url,function(status,body){ if(status!=200){ alert("Error deleting: ("+status+") "+body); return; } var tlh=document.getElementById("timelineheader"); if(tlh.firstChild&&tlh.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){ focusURL(url); }); tr.appendChild(td); return tr; } function updateURLs(){ fetch("GET","/changes/urls",function(status,body){ urls=JSON.parse(body); if(status!=200||!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 doShowDiff(){ var oldradio=document.querySelector("input[name=diffold]:checked"); var newradio=document.querySelector("input[name=diffnew]:checked"); if(!oldradio||!newradio){ alert("Select two different records to perform a diff"); return; } var olddate=oldradio.value; var newdate=newradio.value; var url=document.getElementById("timelineheader").firstChild.nodeValue; fetch("POST","/changes/diff", JSON.stringify({url:url,olddate:olddate,newdate:newdate}), function(status,body){ var diffbox; if(status==200){ diffbox=document.getElementById("diffbox"); if(diffbox.firstChild)diffbox.removeChild(diffbox.firstChild); diffbox.appendChild(document.createTextNode(body)); } else { alert("Error: ("+status+") "+body); } }); } 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{ border:1px #888 solid; } #urlstable{ width:100%; border-collapse:collapse; } #urlstable tbody, #urlstable tr, #urlstable td{ width:100%; } #urlstable tr{ cursor:pointer; } #urlstable tr:nth-child(2n){ background-color:#eee; } #urlstable tr:hover{ background-color:#ddd; } #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; } #timelinetbl td:nth-child(3), #timelinetbl td:nth-child(4){ text-align:center; } #timelinetbl td:nth-child(5){ font-size:10px; padding-left:40px; padding-right:10px; } 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><a id="timelineheader"></a></h2> <table id="timelinetbl"> <thead><tr> <th>Date</th> <th>Hash</th> <th>Old</th> <th>New</th> </tr></thead> <tbody id="timelinetbody"></tbody> </table> <br> <input type="button" value="Show diff" onclick="doShowDiff();"> <br> <pre id="diffbox"></pre> </div> </body> </html>