<!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: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; } 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>