=e-1){var s=u[n];return s.x0=i,s.y0=o,s.x1=a,void(s.y1=c)}for(var l=f[n],h=r/2+l,d=n+1,p=e-1;d>>1;f[v]c-o){var _=(i*y+a*g)/r;t(n,d,g,i,o,_,c),t(d,e,y,_,o,a,c)}else{var b=(o*y+c*g)/r;t(n,d,g,i,o,a,b),t(d,e,y,i,b,a,c)}}(0,c,t.value,n,e,r,i)},t.treemapDice=nh,t.treemapResquarify=yh,t.treemapSlice=dh,t.treemapSliceDice=function(t,n,e,r,i){(1&t.depth?dh:nh)(t,n,e,r,i)},t.treemapSquarify=gh,t.tsv=ca,t.tsvFormat=Zo,t.tsvFormatBody=Qo,t.tsvFormatRow=Jo,t.tsvFormatRows=Ko,t.tsvFormatValue=ta,t.tsvParse=$o,t.tsvParseRows=Wo,t.utcDay=$d,t.utcDays=Wd,t.utcFriday=ep,t.utcFridays=fp,t.utcHour=Vd,t.utcHours=Gd,t.utcMillisecond=dd,t.utcMilliseconds=pd,t.utcMinute=jd,t.utcMinutes=Xd,t.utcMonday=Kd,t.utcMondays=op,t.utcMonth=lp,t.utcMonths=hp,t.utcSaturday=rp,t.utcSaturdays=sp,t.utcSecond=yd,t.utcSeconds=_d,t.utcSunday=Qd,t.utcSundays=ip,t.utcThursday=np,t.utcThursdays=cp,t.utcTuesday=Jd,t.utcTuesdays=ap,t.utcWednesday=tp,t.utcWednesdays=up,t.utcWeek=Qd,t.utcWeeks=ip,t.utcYear=dp,t.utcYears=pp,t.values=function(t){var n=[];for(var e in t)n.push(t[e]);return n},t.variance=c,t.version="5.14.2",t.voronoi=function(){var t=fb,n=sb,e=null;function r(r){return new Xb(r.map(function(e,i){var o=[Math.round(t(e,i,r)/Yb)*Yb,Math.round(n(e,i,r)/Yb)*Yb];return o.index=i,o.data=e,o}),e)}return r.polygons=function(t){return r(t).polygons()},r.links=function(t){return r(t).links()},r.triangles=function(t){return r(t).triangles()},r.x=function(n){return arguments.length?(t="function"==typeof n?n:cb(+n),r):t},r.y=function(t){return arguments.length?(n="function"==typeof t?t:cb(+t),r):n},r.extent=function(t){return arguments.length?(e=null==t?null:[[+t[0][0],+t[0][1]],[+t[1][0],+t[1][1]]],r):e&&[[e[0][0],e[0][1]],[e[1][0],e[1][1]]]},r.size=function(t){return arguments.length?(e=null==t?null:[[0,0],[+t[0],+t[1]]],r):e&&[e[1][0]-e[0][0],e[1][1]-e[0][1]]},r},t.window=ct,t.xml=la,t.zip=function(){return k(arguments)},t.zoom=function(){var n,e,r=Jb,i=tm,o=im,a=em,u=rm,c=[0,1/0],f=[[-1/0,-1/0],[1/0,1/0]],s=250,l=Fe,h=I("start","zoom","end"),d=500,p=150,v=0;function g(t){t.property("__zoom",nm).on("wheel.zoom",M).on("mousedown.zoom",N).on("dblclick.zoom",T).filter(u).on("touchstart.zoom",A).on("touchmove.zoom",S).on("touchend.zoom touchcancel.zoom",k).style("touch-action","none").style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}function y(t,n){return(n=Math.max(c[0],Math.min(c[1],n)))===t.k?t:new $b(n,t.x,t.y)}function _(t,n,e){var r=n[0]-e[0]*t.k,i=n[1]-e[1]*t.k;return r===t.x&&i===t.y?t:new $b(t.k,r,i)}function b(t){return[(+t[0][0]+ +t[1][0])/2,(+t[0][1]+ +t[1][1])/2]}function m(t,n,e){t.on("start.zoom",function(){x(this,arguments).start()}).on("interrupt.zoom end.zoom",function(){x(this,arguments).end()}).tween("zoom",function(){var t=this,r=arguments,o=x(t,r),a=i.apply(t,r),u=null==e?b(a):"function"==typeof e?e.apply(t,r):e,c=Math.max(a[1][0]-a[0][0],a[1][1]-a[0][1]),f=t.__zoom,s="function"==typeof n?n.apply(t,r):n,h=l(f.invert(u).concat(c/f.k),s.invert(u).concat(c/s.k));return function(t){if(1===t)t=s;else{var n=h(t),e=c/n[2];t=new $b(e,u[0]-n[0]*e,u[1]-n[1]*e)}o.zoom(null,t)}})}function x(t,n,e){return!e&&t.__zooming||new w(t,n)}function w(t,n){this.that=t,this.args=n,this.active=0,this.extent=i.apply(t,n),this.taps=0}function M(){if(r.apply(this,arguments)){var t=x(this,arguments),n=this.__zoom,e=Math.max(c[0],Math.min(c[1],n.k*Math.pow(2,a.apply(this,arguments)))),i=Bt(this);if(t.wheel)t.mouse[0][0]===i[0]&&t.mouse[0][1]===i[1]||(t.mouse[1]=n.invert(t.mouse[0]=i)),clearTimeout(t.wheel);else{if(n.k===e)return;t.mouse=[i,n.invert(i)],Er(this),t.start()}Kb(),t.wheel=setTimeout(function(){t.wheel=null,t.end()},p),t.zoom("mouse",o(_(y(n,e),t.mouse[0],t.mouse[1]),t.extent,f))}}function N(){if(!e&&r.apply(this,arguments)){var n=x(this,arguments,!0),i=Rt(t.event.view).on("mousemove.zoom",function(){if(Kb(),!n.moved){var e=t.event.clientX-u,r=t.event.clientY-c;n.moved=e*e+r*r>v}n.zoom("mouse",o(_(n.that.__zoom,n.mouse[0]=Bt(n.that),n.mouse[1]),n.extent,f))},!0).on("mouseup.zoom",function(){i.on("mousemove.zoom mouseup.zoom",null),jt(t.event.view,n.moved),Kb(),n.end()},!0),a=Bt(this),u=t.event.clientX,c=t.event.clientY;Ht(t.event.view),Qb(),n.mouse=[a,this.__zoom.invert(a)],Er(this),n.start()}}function T(){if(r.apply(this,arguments)){var n=this.__zoom,e=Bt(this),a=n.invert(e),u=n.k*(t.event.shiftKey?.5:2),c=o(_(y(n,u),e,a),i.apply(this,arguments),f);Kb(),s>0?Rt(this).transition().duration(s).call(m,c,e):Rt(this).call(g.transform,c)}}function A(){if(r.apply(this,arguments)){var e,i,o,a,u=t.event.touches,c=u.length,f=x(this,arguments,t.event.changedTouches.length===c);for(Qb(),i=0;i {
+ for (let key in things){
+ var thing_id = things[key]['href'].substr(things[key]['href'].lastIndexOf('/') + 1);
+ this.thing_title_lookup_table[thing_id] = things[key]['title'];
+ }
+ });
+
+ var latest_property_id = 4;
+
+ fetch(`/extensions/${this.id}/views/content.html`)
+ .then((res) => res.text())
+ .then((text) => {
+ this.content = text;
+ })
+ .catch((e) => console.error('Failed to fetch content:', e));
+ }
+
+ create_thing_list(body){
+ //console.log("Creating main thing list");
+
+ const pre = document.getElementById('extension-privacy-manager-response-data');
+ const thing_list = document.getElementById('extension-privacy-manager-thing-list');
+
+ for (var key in body['data']) {
+
+ var dataline = JSON.parse(body['data'][key]['name']);
+ //console.log(Object.keys(dataline));
+
+ var this_object = this;
+ //console.log(this_object);
+
+ var node = document.createElement("LI"); // Create a node
+ node.setAttribute("data-property-id", body['data'][key]['id']);
+ node.setAttribute("data-data-type", body['data'][key]['data_type']);
+ var human_readable_thing_title = dataline['thing'];
+ if( human_readable_thing_title in this.thing_title_lookup_table ){
+ human_readable_thing_title = this.thing_title_lookup_table[human_readable_thing_title];
+ }
+ var textnode = document.createTextNode(human_readable_thing_title + ' - ' + dataline['property']); // Create a text node
+ node.onclick = function() { this_object.thing_list_click(this) };
+ node.appendChild(textnode);
+ thing_list.appendChild(node);
+ }
+ pre.innerText = "";
+ }
+
+
+
+ display_thing_data(property_id,data_type,raw_data){ // Uses json to generate dataviz
+ const dataviz = document.getElementById('extension-privacy-manager-thing-dataviz');
+ var data = []
+
+ for( var key in raw_data ){
+ data.push({'date': raw_data[key]['date'], 'value': raw_data[key]['value']});
+ }
+
+ var elem = document.getElementById("extension-privacy-manager-thing-dataviz-svg > *");
+ if( elem != null ){
+ elem.parentNode.removeChild(elem);
+ }
+
+
+ //var svg = d3.select("#extension-privacy-manager-thing-dataviz").append("svg"),
+ var svg = d3.select("#extension-privacy-manager-thing-dataviz-svg"),
+ margin = {top: 20, right: 20, bottom: 110, left: 40},
+ margin2 = {top: 430, right: 20, bottom: 30, left: 40},
+ width = +svg.attr("width") - margin.left - margin.right,
+ height = +svg.attr("height") - margin.top - margin.bottom,
+ height2 = +svg.attr("height") - margin2.top - margin2.bottom;
+
+
+ svg.selectAll("*").remove();
+
+ var date_array = [];
+ var value_array = [];
+
+ data.forEach(function (arrayItem) {
+ date_array.push( new Date(arrayItem['date']) );
+ value_array.push( arrayItem['value'] );
+ });
+
+
+ // Dimensions and margins
+ var svg = d3.select("#extension-privacy-manager-thing-dataviz-svg");
+
+ //var width = +svg.attr("width")
+ var width = document.getElementById('extension-privacy-manager-thing-dataviz').offsetWidth;
+ console.log("offsetWidth = " + width);
+ document.getElementById('extension-privacy-manager-thing-dataviz-svg').style.width = width + "px";
+ //console.log();
+
+ var height = +svg.attr("height")
+
+ width = width - 50;
+ height = height - 50;
+
+ //var margin = {top: (0.1*width), right: (0.1*width), bottom: (0.1*width), left: (0.1*width)};
+ //var margin = {top: 0, right: (0.1*width), bottom: (0.1*width), left: (0.1*width)};
+ var margin = {top: 10, right: 0, bottom: 0, left: 50};
+
+ // create a clipping region
+ svg.append("defs").append("clipPath")
+ .attr("id", "clip")
+ .append("rect")
+ .attr("width", width)
+ .attr("height", height);
+
+ // Give the data a bit more space at the top and bottom
+ var extra_low = d3.min(value_array) - 1;
+ var extra_high = d3.max(value_array) + 1;
+
+ // Check if the minimum and maximum date range has changed/expanded. This is used when deleting data.
+ var minimum_time = d3.min(date_array)
+ var maximum_time = d3.max(date_array)
+
+ if(minimum_time < this.min_time){
+ this.min_time = minimum_time;
+ }
+ if(maximum_time > this.max_time){
+ this.max_time = maximum_time;
+ }
+
+ // create scale objects
+ var xScale = d3.scaleTime()
+ //.domain(d3.extent(date_array))
+ .domain([minimum_time,maximum_time])
+ .range([0, width]);
+ var yScale = d3.scaleLinear()
+ //.domain(d3.extent(value_array))
+ .domain([d3.min(value_array) - 1, d3.max(value_array) + 1])
+ .range([height, 0]);
+
+ // create axes
+ var xAxis = d3.axisBottom(xScale);
+ //.ticks(20, "s");
+
+
+ var yAxis = d3.axisLeft(yScale)
+ .ticks(20, "s");
+
+ // Draw Axis
+ var gX = svg.append('g')
+ .attr('transform', 'translate(' + margin.left + ',' + (margin.top + height) + ')')
+ .call(xAxis);
+ var gY = svg.append('g')
+ .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
+ .call(yAxis);
+
+ // Rectangle for the zoom function
+ var rectangle_overlay = svg.append("rect")
+ .attr("width", width)
+ .attr("height", height)
+ .style("fill", "none")
+ .style("pointer-events", "all")
+ .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
+ //rectangle_overlay.call(zoom);
+
+ // Create datapoints holder
+ var points_g = svg.append("g")
+ .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
+ .attr("clip-path", "url(#clip)")
+ .classed("points_g", true);
+
+
+ // Draw Datapoints
+ var points = points_g.selectAll("circle").data(data);
+
+ points = points.enter().append("circle")
+ .attr('cx', function(d) {return xScale(new Date(d.date))})
+ .attr('cy', function(d) {return yScale(d.value)})
+ .attr('r', 5)
+ .style("fill-opacity", .5)
+ .attr('class', 'extension-privacy-manager-svg-circle')
+ .attr("data-value", function(d) { return d.value; })
+ .attr("data-date", function(d) { return d.date; })
+ .attr("data-property-id", property_id )
+ .attr("data-data-type", data_type );
+
+ // Points mouse events
+ points_g.selectAll("circle")
+ .on("mouseover", function(d) {
+ //console.log(this);
+ this.setAttribute('fill-opacity', 1);
+ this.setAttribute('r', 7);
+ })
+ .on("mouseout", function(d) {
+ //console.log(this);
+ this.setAttribute('fill-opacity', .5);
+ this.setAttribute('r', 5);
+ })
+ .on("click", function(d) {
+ //console.log(this);
+ //console.log( this.getAttribute("data-date") );
+ //console.log( this.getAttribute("data-value") );
+ //console.log( this.getAttribute("data-property-id") );
+ //console.log( this.getAttribute("data-data-type") );
+
+ document.getElementById('extension-privacy-manager-thing-options').style.display = 'block';
+
+ // reset all circle to blue
+ d3.selectAll('.extension-privacy-manager-svg-circle')
+ .style('fill', 'black');
+
+ d3.select(this).style("fill", "magenta");
+
+ document.getElementById('extension-privacy-manager-input-change-old-epoch').value = this.getAttribute("data-date");
+
+ var select = new Date(Number(this.getAttribute("data-date")));
+ console.log("selected point as date object = " + select);
+
+ document.getElementById('extension-privacy-manager-input-change-value').value = this.getAttribute("data-value");
+ document.getElementById('extension-privacy-manager-input-change-property-id').value = this.getAttribute("data-property-id");
+
+ document.getElementById('extension-privacy-manager-input-second').value = select.getSeconds();
+ document.getElementById('extension-privacy-manager-input-minute').value = select.getMinutes(); //select.toLocaleDateString("en-UK",{minute: '2-digit'});
+ document.getElementById('extension-privacy-manager-input-hour').value = select.getHours();
+ document.getElementById('extension-privacy-manager-input-day').value = select.getDate();
+ document.getElementById('extension-privacy-manager-input-month').value = select.getMonth();
+ document.getElementById('extension-privacy-manager-input-year').value = select.getFullYear();
+ document.getElementById('extension-privacy-manager-input-millis').value = select.getMilliseconds();
+ })
+
+
+ // Zooming
+ var zoom = d3.zoom()
+ .scaleExtent([.5, 20])
+ .extent([[0, 0], [width, height]])
+ .on("zoom", zoomed);
+
+ rectangle_overlay.call(zoom);
+
+ function zoomed() {
+
+ // Create new scale
+ var new_xScale = d3.event.transform.rescaleX(xScale);
+ var new_yScale = d3.event.transform.rescaleY(yScale);
+
+ // Update axis
+ gX.call(xAxis.scale(new_xScale));
+
+ points.data(data)
+ .attr('cx', function(d) {return new_xScale(d.date)})
+ .attr('cy', function(d) {return yScale(d.value)})
+ }
+
+ }
+
+
+
+
+ // HELPER METHODS
+
+ hasClass(ele,cls) {
+ //console.log(ele);
+ //console.log(cls);
+ return !!ele.className.match(new RegExp('(\\s|^)'+cls+'(\\s|$)'));
+ }
+
+ addClass(ele,cls) {
+ if (!this.hasClass(ele,cls)) ele.className += " "+cls;
+ }
+
+ removeClass(ele,cls) {
+ if (this.hasClass(ele,cls)) {
+ var reg = new RegExp('(\\s|^)'+cls+'(\\s|$)');
+ ele.className=ele.className.replace(reg,' ');
+ }
+ }
+
+
+ thing_list_click(the_target){
+ const pre = document.getElementById('extension-privacy-manager-response-data');
+
+ // Update CSS
+ var remove_click_css_list = document.querySelectorAll('#extension-privacy-manager-thing-list > *');
+ for (var i=0, max=remove_click_css_list.length; i < max; i++) {
+ this.removeClass(remove_click_css_list[i],"clicked");
+ }
+ this.addClass(the_target,"clicked");
+
+ var target_property_id = the_target.getAttribute('data-property-id');
+ var target_data_type = the_target.getAttribute('data-data-type');
+ //console.log(target_data_type);
+ document.getElementById('extension-privacy-manager-input-change-data-type').value = target_data_type; // Make sure this is always populated with the correct data type. Bit of a clumsy use of hidden fields, should improve later.
+ //console.log(target_thing_id);
+
+ // Get data for selected thing
+ window.API.postJson(
+ `/extensions/${this.id}/api/get_property_data`,
+ {'property_id': target_property_id, 'data_type':target_data_type}
+ ).then((body) => {
+ this.display_thing_data(target_property_id, target_data_type, body['data']);
+ //pre.innerText = JSON.stringify(body, null, 2);
+ //pre.innerText = body['state'];
+ }).catch((e) => {
+ console.log("Privacy manager: error getting property data");
+ pre.innerText = e.toString();
+ });
+
+ }
+
+
+ show() {
+ this.view.innerHTML = this.content;
+
+ const pre = document.getElementById('extension-privacy-manager-response-data');
+ const thing_list = document.getElementById('extension-privacy-manager-thing-list');
+ const dataviz = document.getElementById('extension-privacy-manager-thing-dataviz');
+
+ const tab_button_sculptor = document.getElementById('extension-privacy-manager-tab-button-sculptor');
+ const tab_button_internal = document.getElementById('extension-privacy-manager-tab-button-internal');
+
+ const tab_sculptor = document.getElementById('extension-privacy-manager-tab-sculptor');
+ const tab_internal = document.getElementById('extension-privacy-manager-tab-internal');
+
+ const button_change_point = document.getElementById('extension-privacy-manager-button-change-point');
+ const button_create_point = document.getElementById('extension-privacy-manager-button-create-point');
+ const button_delete_point = document.getElementById('extension-privacy-manager-button-delete-point');
+ const button_delete_before = document.getElementById('extension-privacy-manager-button-delete-before');
+ const button_delete_after = document.getElementById('extension-privacy-manager-button-delete-after');
+
+ const input_change_value = document.getElementById('extension-privacy-manager-input-change-value');
+ const input_change_property_id = document.getElementById('extension-privacy-manager-input-change-property-id');
+ const input_change_data_type = document.getElementById('extension-privacy-manager-input-change-data-type');
+
+ pre.innerText = "";
+
+
+
+ // TABS
+
+ // Data sculptor
+ tab_button_sculptor.addEventListener('click', () => {
+ this.addClass(tab_button_sculptor,"extension-privacy-manager-button-active");
+ this.removeClass(tab_button_internal,"extension-privacy-manager-button-active");
+
+ this.addClass(tab_internal,"extension-privacy-manager-hidden");
+ this.removeClass(tab_sculptor,"extension-privacy-manager-hidden");
+ });
+
+ // Internal logs tab
+ tab_button_internal.addEventListener('click', () => {
+ this.addClass(tab_button_internal,"extension-privacy-manager-button-active");
+ this.removeClass(tab_button_sculptor,"extension-privacy-manager-button-active");
+
+ this.addClass(tab_sculptor,"extension-privacy-manager-hidden");
+ this.removeClass(tab_internal,"extension-privacy-manager-hidden");
+
+ window.API.postJson(
+ `/extensions/${this.id}/api/internal_logs`,
+ {'action':'get' ,'filename':'all'}
+
+ ).then((body) => {
+ //thing_list.innerText = body['data'];
+ this.show_internal_logs(body['data']);
+
+ }).catch((e) => {
+ //pre.innerText = e.toString();
+ console.log("Privacy manager: error in show function");
+ console.log(e.toString());
+ });
+
+ });
+
+
+ // CHANGE POINT
+ button_change_point.addEventListener('click', () => {
+ //console.log("Changing point");
+ //console.log(input_change_date.value);
+ this.change_handler("change");
+ });
+
+
+ // CREATE POINT
+ button_create_point.addEventListener('click', () => {
+ //console.log("Creating a new point");
+ this.change_handler("create");
+ });
+
+
+ // DELETE POINT
+ button_delete_point.addEventListener('click', () => {
+ //console.log("clicked delete");
+ this.delete_handler("delete-point");
+ });
+
+ // DELETE ALL BEFORE
+ button_delete_before.addEventListener('click', () => {
+ //console.log("clicked delete before");
+ this.delete_handler("delete-before");
+ });
+
+ // DELETE ALL AFTER
+ button_delete_after.addEventListener('click', () => {
+ //console.log("clicked delete after");
+ this.delete_handler("delete-after");
+ });
+
+
+
+ // Get list of properties for sculptor
+
+ window.API.postJson(
+ `/extensions/${this.id}/api/init` //,{'init':1}
+
+ ).then((body) => {
+ //thing_list.innerText = body['data'];
+ this.create_thing_list(body);
+
+ }).catch((e) => {
+ //pre.innerText = e.toString();
+ console.log("Privacy manager: error in show function");
+ console.log(e.toString());
+ });
+
+
+
+ //
+ // INITIALISE INTERNAL LOGS BUTTONS
+ //
+
+ // DELETE ALL INTERNAL LOGS
+ document.getElementById('extension-privacy-manager-button-delete-all-logs').addEventListener('click', () => {
+ console.log("clicked delete all internal logs");
+ this.delete_internal_logs("all");
+ });
+
+
+ }
+
+
+ get_new_date(){
+ var fresh_date = new Date(0);
+ console.log(fresh_date);
+ fresh_date.setFullYear( document.getElementById('extension-privacy-manager-input-year').value );
+ fresh_date.setMonth( document.getElementById('extension-privacy-manager-input-month').value );
+ fresh_date.setDate( document.getElementById('extension-privacy-manager-input-day').value );
+ fresh_date.setHours( document.getElementById('extension-privacy-manager-input-hour').value );
+ fresh_date.setMinutes( document.getElementById('extension-privacy-manager-input-minute').value );
+ fresh_date.setSeconds( document.getElementById('extension-privacy-manager-input-second').value );
+ fresh_date.setMilliseconds( document.getElementById('extension-privacy-manager-input-millis').value );
+
+ console.log(document.getElementById('extension-privacy-manager-input-year').value);
+ console.log(document.getElementById('extension-privacy-manager-input-month').value);
+ console.log(document.getElementById('extension-privacy-manager-input-day').value);
+ console.log(document.getElementById('extension-privacy-manager-input-hour').value);
+ console.log(document.getElementById('extension-privacy-manager-input-minute').value);
+ console.log(document.getElementById('extension-privacy-manager-input-second').value);
+ console.log(document.getElementById('extension-privacy-manager-input-millis').value);
+
+ console.log("new date stamp: " + fresh_date.valueOf() );
+
+ return fresh_date.valueOf();
+ }
+
+
+ change_handler(action){
+ const pre = document.getElementById('extension-privacy-manager-response-data');
+
+ var input_change_value = document.getElementById('extension-privacy-manager-input-change-value').value;
+ var updating_property_id = document.getElementById('extension-privacy-manager-input-change-property-id').value;
+ var updating_data_type = document.getElementById('extension-privacy-manager-input-change-data-type').value;
+ var old_date_stamp = document.getElementById('extension-privacy-manager-input-change-old-epoch').value;
+ var new_date_stamp = this.get_new_date(); // reconnect all the pieces from the dropdowns (and the hidden milliseconds value) into the new date
+
+ console.log("____action = " + action);
+ console.log("property = " + updating_property_id);
+ console.log("of type = " + updating_data_type);
+ console.log("old_date_stamp = " + old_date_stamp);
+ console.log("new_date_stamp = " + new_date_stamp);
+
+
+ if( action == "create" && old_date_stamp == new_date_stamp ){
+ console.log("Shouldn't make a new point at the same date as the old one.")
+ pre.innerText = "Please change the date of the new point.";
+ return
+ }
+
+ window.API.postJson(
+ `/extensions/${this.id}/api/point_change_value`,
+ {'action':action, 'property_id':updating_property_id, 'data_type': updating_data_type ,'new_value':input_change_value, 'old_date': old_date_stamp, 'new_date': new_date_stamp,}
+ ).then((body) => {
+ //thing_list.innerText = body['data'];
+ //console.log(body);
+ document.getElementById('extension-privacy-manager-input-change-old-epoch').value = new_date_stamp; // Move new timestamp into "old timestamp" role, in case the user wants to change it again immediately.
+ this.display_thing_data(updating_property_id, updating_data_type, body['data']);
+
+ }).catch((e) => {
+ console.log("Privacy manager: error in change handler");
+ pre.innerText = e.toString();
+ });
+ }
+
+
+
+ delete_handler(action){
+ console.log("Deleting point(s). Action:");
+ console.log(action);
+ //console.log(input_change_date.value);
+
+ console.log("min-time: " + this.min_time);
+ console.log("min-time: " + this.max_time);
+
+ const pre = document.getElementById('extension-privacy-manager-response-data');
+ const options_pane = document.getElementById('extension-privacy-manager-thing-options');
+ //const input_change_value = document.getElementById('extension-privacy-manager-input-change-value');
+
+ var updating_data_type = document.getElementById('extension-privacy-manager-input-change-data-type').value;
+ var updating_property_id = document.getElementById('extension-privacy-manager-input-change-property-id').value;
+
+ // In the future users could delete a selection
+
+ const selected_point_date = document.getElementById('extension-privacy-manager-input-change-old-epoch').value;
+ if( action == "delete-point" ){
+ var start_date_stamp = selected_point_date
+ var end_date_stamp = selected_point_date; //this.get_new_date(); // reconnect all the pieces from the dropdowns (and the hidden milliseconds value) into the new date
+ }
+ else if( action == "delete-before" ){
+ var start_date_stamp = this.min_time.getTime(); //toUTCString();
+ var end_date_stamp = selected_point_date;
+ }
+ else if( action == "delete-after" ){
+ var start_date_stamp = selected_point_date
+ var end_date_stamp = this.max_time.getTime(); //.toUTCString();
+ }
+
+ console.log("____action = " + action);
+ console.log("property = " + updating_property_id);
+ console.log("of type = " + updating_data_type);
+ console.log("end_date_stamp = " + end_date_stamp);
+ console.log("start_date_stamp = " + start_date_stamp);
+
+ options_pane.style.opacity = .5;
+
+
+ window.API.postJson(
+ `/extensions/${this.id}/api/point_delete`,
+ {'action':action, 'property_id':updating_property_id, 'data_type':updating_data_type, 'start_date':start_date_stamp, 'end_date':end_date_stamp}
+ ).then((body) => {
+ options_pane.style.opacity = 1;
+ //console.log(body['data']);
+
+ document.getElementById('extension-privacy-manager-input-change-old-epoch').value = "";
+ document.getElementById('extension-privacy-manager-input-change-value').value = "";
+
+ // Update the dataviz
+ this.display_thing_data(updating_property_id, updating_data_type, body['data']);
+
+ }).catch((e) => {
+ console.log("Privacy manager: error in deletion handler");
+ pre.innerText = e.toString();
+ });
+
+ } // End of button delete point add listener
+
+
+
+
+
+ //
+ // INTERNAL LOGS
+ //
+
+
+ show_internal_logs(file_list) {
+
+ const pre = document.getElementById('extension-privacy-manager-response-data');
+ const logs_list = document.getElementById('extension-privacy-manager-logs-list');
+
+ file_list.sort();
+ //console.log(file_list)
+
+ logs_list.innerHTML = "";
+
+ for (var key in file_list) {
+
+ var this_object = this;
+
+ var node = document.createElement("LI"); // Create a node
+ node.setAttribute("class", "extension-privacy-manager-deletable_item" );
+ node.setAttribute("data-filename", file_list[key] );
+
+ var textnode = document.createTextNode( file_list[key] ); // Create a text node
+ node.onclick = function() {
+ this_object.delete_internal_logs( file_list[key] )
+ };
+ node.appendChild(textnode);
+
+ logs_list.appendChild(node);
+ }
+ pre.innerText = "";
+ }
+
+
+
+ delete_internal_logs(filename){
+ //console.log("Deleting log files. filename:");
+ //console.log(filename);
+
+ const pre = document.getElementById('extension-privacy-manager-response-data');
+ const logs_list = document.getElementById('extension-privacy-manager-logs-list');
+
+ window.API.postJson(
+ `/extensions/${this.id}/api/internal_logs`,
+ {'action':'delete', 'filename':filename}
+
+ ).then((body) => {
+ //console.log(body);
+ this.show_internal_logs(body['data']);
+
+ }).catch((e) => {
+ console.log("Privacy manager: error in internal log deletion handler");
+ pre.innerText = e.toString();
+ });
+
+ } // End of button delete point add listener
+
+ }
+
+ new PrivacyManager();
+
+})();
+
+
diff --git a/main.py b/main.py
new file mode 100644
index 0000000..5d104ce
--- /dev/null
+++ b/main.py
@@ -0,0 +1,40 @@
+"""Privacy Manager add-on for Mozilla WebThings Gateway."""
+
+from os import path
+import functools
+import gateway_addon
+import signal
+import sys
+import time
+
+sys.path.append(path.join(path.dirname(path.abspath(__file__)), 'lib'))
+
+#from pkg.power_settings import PowerSettingsAdapter # noqa
+from pkg.privacy_manager import PrivacyManagerAPIHandler # noqa
+
+
+
+_HANDLER = None
+
+print = functools.partial(print, flush=True)
+
+
+def cleanup(signum, frame):
+ """Clean up any resources before exiting."""
+ if _HANDLER is not None:
+ _HANDLER.close_proxy()
+
+ sys.exit(0)
+
+
+if __name__ == '__main__':
+ signal.signal(signal.SIGINT, cleanup)
+ signal.signal(signal.SIGTERM, cleanup)
+ #_HANDLER = PowerSettingsAdapter(verbose=True)
+ _HANDLER = PrivacyManagerAPIHandler(verbose=True)
+
+
+ # Wait until the proxy stops running, indicating that the gateway shut us
+ # down.
+ while _HANDLER.proxy_running():
+ time.sleep(2)
\ No newline at end of file
diff --git a/manifest.json b/manifest.json
new file mode 100644
index 0000000..8523145
--- /dev/null
+++ b/manifest.json
@@ -0,0 +1,54 @@
+{
+ "author": "Candle",
+ "content_scripts": [
+ {
+ "css": [
+ "css/extension.css",
+ "css/tail.datetime.css"
+ ],
+ "js": [
+ "js/extension.js",
+ "js/d3.min.js"
+ ]
+ }
+ ],
+ "description": "Manage and sculpt your data",
+ "gateway_specific_settings": {
+ "webthings": {
+ "exec": "python3 {path}/main.py",
+ "primary_type": "extension",
+ "strict_max_version": "*",
+ "strict_min_version": "0.10.0"
+ }
+ },
+ "homepage_url": "https://github.com/createcandle/privacy-manager",
+ "id": "privacy-manager",
+ "license": "MPL-2.0",
+ "manifest_version": 1,
+ "name": "Privacy Manager",
+ "options": {
+ "default": {
+ "Debugging": false
+ },
+ "schema": {
+ "properties": {
+ "Debugging": {
+ "description": "Debugging allows you to diagnose any issues with the add-on. If enabled it will result in a lot more debug data in the internal log (which can be found under Settings -> Developer -> View internal logs).",
+ "type": "boolean"
+ }
+ },
+ "required": [
+ "Debugging"
+ ],
+ "type": "object"
+ }
+ },
+ "short_name": "Privacy",
+ "version": "0.0.1",
+ "web_accessible_resources": [
+ "css/*.css",
+ "images/*.svg",
+ "js/*.js",
+ "views/*.html"
+ ]
+}
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..5741ee5
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,1122 @@
+{
+ "name": "example-extension",
+ "version": "0.0.3",
+ "lockfileVersion": 1,
+ "requires": true,
+ "dependencies": {
+ "@babel/code-frame": {
+ "version": "7.5.5",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz",
+ "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==",
+ "dev": true,
+ "requires": {
+ "@babel/highlight": "^7.0.0"
+ }
+ },
+ "@babel/generator": {
+ "version": "7.6.0",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.6.0.tgz",
+ "integrity": "sha512-Ms8Mo7YBdMMn1BYuNtKuP/z0TgEIhbcyB8HVR6PPNYp4P61lMsABiS4A3VG1qznjXVCf3r+fVHhm4efTYVsySA==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.6.0",
+ "jsesc": "^2.5.1",
+ "lodash": "^4.17.13",
+ "source-map": "^0.5.0",
+ "trim-right": "^1.0.1"
+ }
+ },
+ "@babel/helper-function-name": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz",
+ "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-get-function-arity": "^7.0.0",
+ "@babel/template": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "@babel/helper-get-function-arity": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz",
+ "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "@babel/helper-split-export-declaration": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz",
+ "integrity": "sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.4.4"
+ }
+ },
+ "@babel/highlight": {
+ "version": "7.5.0",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.5.0.tgz",
+ "integrity": "sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ==",
+ "dev": true,
+ "requires": {
+ "chalk": "^2.0.0",
+ "esutils": "^2.0.2",
+ "js-tokens": "^4.0.0"
+ }
+ },
+ "@babel/parser": {
+ "version": "7.6.0",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.6.0.tgz",
+ "integrity": "sha512-+o2q111WEx4srBs7L9eJmcwi655eD8sXniLqMB93TBK9GrNzGrxDWSjiqz2hLU0Ha8MTXFIP0yd9fNdP+m43ZQ==",
+ "dev": true
+ },
+ "@babel/template": {
+ "version": "7.6.0",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.6.0.tgz",
+ "integrity": "sha512-5AEH2EXD8euCk446b7edmgFdub/qfH1SN6Nii3+fyXP807QRx9Q73A2N5hNwRRslC2H9sNzaFhsPubkS4L8oNQ==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.0.0",
+ "@babel/parser": "^7.6.0",
+ "@babel/types": "^7.6.0"
+ }
+ },
+ "@babel/traverse": {
+ "version": "7.6.0",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.6.0.tgz",
+ "integrity": "sha512-93t52SaOBgml/xY74lsmt7xOR4ufYvhb5c5qiM6lu4J/dWGMAfAh6eKw4PjLes6DI6nQgearoxnFJk60YchpvQ==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.5.5",
+ "@babel/generator": "^7.6.0",
+ "@babel/helper-function-name": "^7.1.0",
+ "@babel/helper-split-export-declaration": "^7.4.4",
+ "@babel/parser": "^7.6.0",
+ "@babel/types": "^7.6.0",
+ "debug": "^4.1.0",
+ "globals": "^11.1.0",
+ "lodash": "^4.17.13"
+ }
+ },
+ "@babel/types": {
+ "version": "7.6.1",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.1.tgz",
+ "integrity": "sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==",
+ "dev": true,
+ "requires": {
+ "esutils": "^2.0.2",
+ "lodash": "^4.17.13",
+ "to-fast-properties": "^2.0.0"
+ }
+ },
+ "acorn": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.0.tgz",
+ "integrity": "sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==",
+ "dev": true
+ },
+ "acorn-jsx": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.2.tgz",
+ "integrity": "sha512-tiNTrP1MP0QrChmD2DdupCr6HWSFeKVw5d/dHTu4Y7rkAkRhU/Dt7dphAfIUyxtHpl/eBVip5uTNSpQJHylpAw==",
+ "dev": true
+ },
+ "ajv": {
+ "version": "6.10.2",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz",
+ "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==",
+ "dev": true,
+ "requires": {
+ "fast-deep-equal": "^2.0.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ }
+ },
+ "ansi-escapes": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz",
+ "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==",
+ "dev": true
+ },
+ "ansi-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
+ "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
+ "dev": true
+ },
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "dev": true,
+ "requires": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
+ "astral-regex": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz",
+ "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==",
+ "dev": true
+ },
+ "babel-eslint": {
+ "version": "10.0.3",
+ "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.0.3.tgz",
+ "integrity": "sha512-z3U7eMY6r/3f3/JB9mTsLjyxrv0Yb1zb8PCWCLpguxfCzBIZUwy23R1t/XKewP+8mEN2Ck8Dtr4q20z6ce6SoA==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.0.0",
+ "@babel/parser": "^7.0.0",
+ "@babel/traverse": "^7.0.0",
+ "@babel/types": "^7.0.0",
+ "eslint-visitor-keys": "^1.0.0",
+ "resolve": "^1.12.0"
+ }
+ },
+ "balanced-match": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
+ "dev": true
+ },
+ "brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "requires": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "chardet": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
+ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
+ "dev": true
+ },
+ "cli-cursor": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz",
+ "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=",
+ "dev": true,
+ "requires": {
+ "restore-cursor": "^2.0.0"
+ }
+ },
+ "cli-width": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz",
+ "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=",
+ "dev": true
+ },
+ "color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "dev": true,
+ "requires": {
+ "color-name": "1.1.3"
+ }
+ },
+ "color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+ "dev": true
+ },
+ "concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+ "dev": true
+ },
+ "cross-spawn": {
+ "version": "6.0.5",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
+ "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
+ "dev": true,
+ "requires": {
+ "nice-try": "^1.0.4",
+ "path-key": "^2.0.1",
+ "semver": "^5.5.0",
+ "shebang-command": "^1.2.0",
+ "which": "^1.2.9"
+ },
+ "dependencies": {
+ "semver": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+ "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+ "dev": true
+ }
+ }
+ },
+ "debug": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+ "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+ "dev": true,
+ "requires": {
+ "ms": "^2.1.1"
+ }
+ },
+ "deep-is": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
+ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
+ "dev": true
+ },
+ "doctrine": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
+ "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+ "dev": true,
+ "requires": {
+ "esutils": "^2.0.2"
+ }
+ },
+ "emoji-regex": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
+ "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
+ "dev": true
+ },
+ "escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+ "dev": true
+ },
+ "eslint": {
+ "version": "6.5.1",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.5.1.tgz",
+ "integrity": "sha512-32h99BoLYStT1iq1v2P9uwpyznQ4M2jRiFB6acitKz52Gqn+vPaMDUTB1bYi1WN4Nquj2w+t+bimYUG83DC55A==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.0.0",
+ "ajv": "^6.10.0",
+ "chalk": "^2.1.0",
+ "cross-spawn": "^6.0.5",
+ "debug": "^4.0.1",
+ "doctrine": "^3.0.0",
+ "eslint-scope": "^5.0.0",
+ "eslint-utils": "^1.4.2",
+ "eslint-visitor-keys": "^1.1.0",
+ "espree": "^6.1.1",
+ "esquery": "^1.0.1",
+ "esutils": "^2.0.2",
+ "file-entry-cache": "^5.0.1",
+ "functional-red-black-tree": "^1.0.1",
+ "glob-parent": "^5.0.0",
+ "globals": "^11.7.0",
+ "ignore": "^4.0.6",
+ "import-fresh": "^3.0.0",
+ "imurmurhash": "^0.1.4",
+ "inquirer": "^6.4.1",
+ "is-glob": "^4.0.0",
+ "js-yaml": "^3.13.1",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "levn": "^0.3.0",
+ "lodash": "^4.17.14",
+ "minimatch": "^3.0.4",
+ "mkdirp": "^0.5.1",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.8.2",
+ "progress": "^2.0.0",
+ "regexpp": "^2.0.1",
+ "semver": "^6.1.2",
+ "strip-ansi": "^5.2.0",
+ "strip-json-comments": "^3.0.1",
+ "table": "^5.2.3",
+ "text-table": "^0.2.0",
+ "v8-compile-cache": "^2.0.3"
+ }
+ },
+ "eslint-scope": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz",
+ "integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==",
+ "dev": true,
+ "requires": {
+ "esrecurse": "^4.1.0",
+ "estraverse": "^4.1.1"
+ }
+ },
+ "eslint-utils": {
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.2.tgz",
+ "integrity": "sha512-eAZS2sEUMlIeCjBeubdj45dmBHQwPHWyBcT1VSYB7o9x9WRRqKxyUoiXlRjyAwzN7YEzHJlYg0NmzDRWx6GP4Q==",
+ "dev": true,
+ "requires": {
+ "eslint-visitor-keys": "^1.0.0"
+ }
+ },
+ "eslint-visitor-keys": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz",
+ "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==",
+ "dev": true
+ },
+ "espree": {
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-6.1.1.tgz",
+ "integrity": "sha512-EYbr8XZUhWbYCqQRW0duU5LxzL5bETN6AjKBGy1302qqzPaCH10QbRg3Wvco79Z8x9WbiE8HYB4e75xl6qUYvQ==",
+ "dev": true,
+ "requires": {
+ "acorn": "^7.0.0",
+ "acorn-jsx": "^5.0.2",
+ "eslint-visitor-keys": "^1.1.0"
+ }
+ },
+ "esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "dev": true
+ },
+ "esquery": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz",
+ "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==",
+ "dev": true,
+ "requires": {
+ "estraverse": "^4.0.0"
+ }
+ },
+ "esrecurse": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz",
+ "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==",
+ "dev": true,
+ "requires": {
+ "estraverse": "^4.1.0"
+ }
+ },
+ "estraverse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
+ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
+ "dev": true
+ },
+ "esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true
+ },
+ "external-editor": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
+ "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==",
+ "dev": true,
+ "requires": {
+ "chardet": "^0.7.0",
+ "iconv-lite": "^0.4.24",
+ "tmp": "^0.0.33"
+ }
+ },
+ "fast-deep-equal": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
+ "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=",
+ "dev": true
+ },
+ "fast-json-stable-stringify": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
+ "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=",
+ "dev": true
+ },
+ "fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
+ "dev": true
+ },
+ "figures": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
+ "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=",
+ "dev": true,
+ "requires": {
+ "escape-string-regexp": "^1.0.5"
+ }
+ },
+ "file-entry-cache": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz",
+ "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==",
+ "dev": true,
+ "requires": {
+ "flat-cache": "^2.0.1"
+ }
+ },
+ "flat-cache": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz",
+ "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==",
+ "dev": true,
+ "requires": {
+ "flatted": "^2.0.0",
+ "rimraf": "2.6.3",
+ "write": "1.0.3"
+ }
+ },
+ "flatted": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz",
+ "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==",
+ "dev": true
+ },
+ "fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
+ "dev": true
+ },
+ "functional-red-black-tree": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
+ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=",
+ "dev": true
+ },
+ "glob": {
+ "version": "7.1.4",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz",
+ "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==",
+ "dev": true,
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
+ "glob-parent": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz",
+ "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==",
+ "dev": true,
+ "requires": {
+ "is-glob": "^4.0.1"
+ }
+ },
+ "globals": {
+ "version": "11.12.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
+ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
+ "dev": true
+ },
+ "has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+ "dev": true
+ },
+ "iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "dev": true,
+ "requires": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ }
+ },
+ "ignore": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
+ "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
+ "dev": true
+ },
+ "import-fresh": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.1.0.tgz",
+ "integrity": "sha512-PpuksHKGt8rXfWEr9m9EHIpgyyaltBy8+eF6GJM0QCAxMgxCfucMF3mjecK2QsJr0amJW7gTqh5/wht0z2UhEQ==",
+ "dev": true,
+ "requires": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ }
+ },
+ "imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
+ "dev": true
+ },
+ "inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+ "dev": true,
+ "requires": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true
+ },
+ "inquirer": {
+ "version": "6.5.2",
+ "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz",
+ "integrity": "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==",
+ "dev": true,
+ "requires": {
+ "ansi-escapes": "^3.2.0",
+ "chalk": "^2.4.2",
+ "cli-cursor": "^2.1.0",
+ "cli-width": "^2.0.0",
+ "external-editor": "^3.0.3",
+ "figures": "^2.0.0",
+ "lodash": "^4.17.12",
+ "mute-stream": "0.0.7",
+ "run-async": "^2.2.0",
+ "rxjs": "^6.4.0",
+ "string-width": "^2.1.0",
+ "strip-ansi": "^5.1.0",
+ "through": "^2.3.6"
+ }
+ },
+ "is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
+ "dev": true
+ },
+ "is-fullwidth-code-point": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+ "dev": true
+ },
+ "is-glob": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
+ "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
+ "dev": true,
+ "requires": {
+ "is-extglob": "^2.1.1"
+ }
+ },
+ "is-promise": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz",
+ "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=",
+ "dev": true
+ },
+ "isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
+ "dev": true
+ },
+ "js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true
+ },
+ "js-yaml": {
+ "version": "3.13.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
+ "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
+ "dev": true,
+ "requires": {
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ }
+ },
+ "jsesc": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
+ "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
+ "dev": true
+ },
+ "json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true
+ },
+ "json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
+ "dev": true
+ },
+ "levn": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
+ "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
+ "dev": true,
+ "requires": {
+ "prelude-ls": "~1.1.2",
+ "type-check": "~0.3.2"
+ }
+ },
+ "lodash": {
+ "version": "4.17.15",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
+ "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
+ "dev": true
+ },
+ "mimic-fn": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz",
+ "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==",
+ "dev": true
+ },
+ "minimatch": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+ "dev": true,
+ "requires": {
+ "brace-expansion": "^1.1.7"
+ }
+ },
+ "minimist": {
+ "version": "0.0.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
+ "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
+ "dev": true
+ },
+ "mkdirp": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+ "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
+ "dev": true,
+ "requires": {
+ "minimist": "0.0.8"
+ }
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
+ "mute-stream": {
+ "version": "0.0.7",
+ "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz",
+ "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=",
+ "dev": true
+ },
+ "natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
+ "dev": true
+ },
+ "nice-try": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
+ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
+ "dev": true
+ },
+ "once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+ "dev": true,
+ "requires": {
+ "wrappy": "1"
+ }
+ },
+ "onetime": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz",
+ "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=",
+ "dev": true,
+ "requires": {
+ "mimic-fn": "^1.0.0"
+ }
+ },
+ "optionator": {
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz",
+ "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=",
+ "dev": true,
+ "requires": {
+ "deep-is": "~0.1.3",
+ "fast-levenshtein": "~2.0.4",
+ "levn": "~0.3.0",
+ "prelude-ls": "~1.1.2",
+ "type-check": "~0.3.2",
+ "wordwrap": "~1.0.0"
+ }
+ },
+ "os-tmpdir": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
+ "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
+ "dev": true
+ },
+ "parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "requires": {
+ "callsites": "^3.0.0"
+ }
+ },
+ "path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
+ "dev": true
+ },
+ "path-key": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
+ "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=",
+ "dev": true
+ },
+ "path-parse": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
+ "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
+ "dev": true
+ },
+ "prelude-ls": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
+ "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=",
+ "dev": true
+ },
+ "progress": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
+ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
+ "dev": true
+ },
+ "punycode": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
+ "dev": true
+ },
+ "regexpp": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz",
+ "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==",
+ "dev": true
+ },
+ "resolve": {
+ "version": "1.12.0",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz",
+ "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==",
+ "dev": true,
+ "requires": {
+ "path-parse": "^1.0.6"
+ }
+ },
+ "resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true
+ },
+ "restore-cursor": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz",
+ "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=",
+ "dev": true,
+ "requires": {
+ "onetime": "^2.0.0",
+ "signal-exit": "^3.0.2"
+ }
+ },
+ "rimraf": {
+ "version": "2.6.3",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
+ "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
+ "dev": true,
+ "requires": {
+ "glob": "^7.1.3"
+ }
+ },
+ "run-async": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz",
+ "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=",
+ "dev": true,
+ "requires": {
+ "is-promise": "^2.1.0"
+ }
+ },
+ "rxjs": {
+ "version": "6.5.3",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.3.tgz",
+ "integrity": "sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA==",
+ "dev": true,
+ "requires": {
+ "tslib": "^1.9.0"
+ }
+ },
+ "safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "dev": true
+ },
+ "semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "dev": true
+ },
+ "shebang-command": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
+ "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
+ "dev": true,
+ "requires": {
+ "shebang-regex": "^1.0.0"
+ }
+ },
+ "shebang-regex": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
+ "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
+ "dev": true
+ },
+ "signal-exit": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
+ "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
+ "dev": true
+ },
+ "slice-ansi": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz",
+ "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.0",
+ "astral-regex": "^1.0.0",
+ "is-fullwidth-code-point": "^2.0.0"
+ }
+ },
+ "source-map": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+ "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+ "dev": true
+ },
+ "sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
+ "dev": true
+ },
+ "string-width": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
+ "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
+ "dev": true,
+ "requires": {
+ "is-fullwidth-code-point": "^2.0.0",
+ "strip-ansi": "^4.0.0"
+ },
+ "dependencies": {
+ "strip-ansi": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^3.0.0"
+ }
+ }
+ }
+ },
+ "strip-ansi": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^4.1.0"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+ "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+ "dev": true
+ }
+ }
+ },
+ "strip-json-comments": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz",
+ "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ },
+ "table": {
+ "version": "5.4.6",
+ "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz",
+ "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==",
+ "dev": true,
+ "requires": {
+ "ajv": "^6.10.2",
+ "lodash": "^4.17.14",
+ "slice-ansi": "^2.1.0",
+ "string-width": "^3.0.0"
+ },
+ "dependencies": {
+ "string-width": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+ "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+ "dev": true,
+ "requires": {
+ "emoji-regex": "^7.0.1",
+ "is-fullwidth-code-point": "^2.0.0",
+ "strip-ansi": "^5.1.0"
+ }
+ }
+ }
+ },
+ "text-table": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
+ "dev": true
+ },
+ "through": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
+ "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
+ "dev": true
+ },
+ "tmp": {
+ "version": "0.0.33",
+ "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
+ "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
+ "dev": true,
+ "requires": {
+ "os-tmpdir": "~1.0.2"
+ }
+ },
+ "to-fast-properties": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
+ "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=",
+ "dev": true
+ },
+ "trim-right": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz",
+ "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=",
+ "dev": true
+ },
+ "tslib": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz",
+ "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==",
+ "dev": true
+ },
+ "type-check": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
+ "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=",
+ "dev": true,
+ "requires": {
+ "prelude-ls": "~1.1.2"
+ }
+ },
+ "uri-js": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
+ "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
+ "dev": true,
+ "requires": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "v8-compile-cache": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz",
+ "integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==",
+ "dev": true
+ },
+ "which": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
+ "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
+ "dev": true,
+ "requires": {
+ "isexe": "^2.0.0"
+ }
+ },
+ "wordwrap": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
+ "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=",
+ "dev": true
+ },
+ "wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+ "dev": true
+ },
+ "write": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz",
+ "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==",
+ "dev": true,
+ "requires": {
+ "mkdirp": "^0.5.1"
+ }
+ }
+ }
+}
diff --git a/package.sh b/package.sh
new file mode 100644
index 0000000..ff4352c
--- /dev/null
+++ b/package.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+version=$(grep '"version":' manifest.json | cut -d: -f2 | cut -d\" -f2)
+
+rm -rf SHA256SUMS package
+rm -rf ._*
+mkdir package
+cp *.py manifest.json LICENSE README.md package/
+cp -r pkg css images js views package/
+cd package
+find . -type f \! -name SHA256SUMS -exec sha256sum {} \; >> SHA256SUMS
+cd ..
+
+tar czf "privacy-manager-${version}.tgz" package
+sha256sum "privacy-manager-${version}.tgz"
diff --git a/pkg/privacy_manager.py b/pkg/privacy_manager.py
new file mode 100644
index 0000000..71d4cd8
--- /dev/null
+++ b/pkg/privacy_manager.py
@@ -0,0 +1,651 @@
+"""Power Settings API handler."""
+
+
+import functools
+import json
+import os
+#from os import listdir
+#from os.path import isfile, join
+from time import sleep
+import datetime
+import subprocess
+import sqlite3
+import requests
+
+try:
+ from gateway_addon import APIHandler, APIResponse
+ #print("succesfully loaded APIHandler and APIResponse from gateway_addon")
+except:
+ print("Import APIHandler and APIResponse from gateway_addon failed. Use at least WebThings Gateway version 0.10")
+
+try:
+ from gateway_addon import Adapter, Device, Database
+except:
+ print("Gateway not loaded?!")
+
+print = functools.partial(print, flush=True)
+
+
+class PrivacyManagerAPIHandler(APIHandler):
+ """Power settings API handler."""
+
+ def __init__(self, verbose=False):
+ """Initialize the object."""
+ #print("INSIDE API HANDLER INIT")
+
+
+
+
+ self.server = 'http://127.0.0.1:8080'
+ self.DEV = True
+ self.DEBUG = False
+
+ self.things = [] # Holds all the things, updated via the API. Used to display a nicer thing name instead of the technical internal ID.
+ self.data_types_lookup_table = {}
+
+ try:
+ manifest_fname = os.path.join(
+ os.path.dirname(__file__),
+ '..',
+ 'manifest.json'
+ )
+
+ with open(manifest_fname, 'rt') as f:
+ manifest = json.load(f)
+
+ APIHandler.__init__(self, manifest['id'])
+ self.manager_proxy.add_api_handler(self)
+
+
+ # LOAD CONFIG
+ try:
+ self.add_from_config()
+ except Exception as ex:
+ print("Error loading config: " + str(ex))
+
+
+ if self.DEBUG:
+ print("self.manager_proxy = " + str(self.manager_proxy))
+ print("Created new API HANDLER: " + str(manifest['id']))
+ except Exception as e:
+ print("Failed to init UX extension API handler: " + str(e))
+
+ try:
+ self.addon_path = os.path.join(os.path.expanduser('~'), '.mozilla-iot', 'addons', 'privacy-manager')
+ self.log_dir_path = os.path.join(os.path.expanduser('~'), '.mozilla-iot', 'log')
+ self.log_db_path = os.path.join(self.log_dir_path, 'logs.sqlite3')
+
+ except Exception as e:
+ print("Failed to further init UX extension API handler: " + str(e))
+
+ # Respond to gateway version
+ try:
+ if self.DEBUG:
+ print(self.gateway_version)
+ except:
+ print("self.gateway_version did not exist")
+
+ while(True):
+ sleep(1)
+
+
+
+
+ # Read the settings from the add-on settings page
+ def add_from_config(self):
+ """Attempt to add all configured devices."""
+ try:
+ database = Database('privacy-manager')
+ if not database.open():
+ print("Could not open settings database")
+ return
+
+ config = database.load_config()
+ database.close()
+
+ except:
+ print("Error! Failed to open settings database.")
+
+ if not config:
+ print("Error loading config from database")
+ return
+
+ if self.DEV:
+ print(str(config))
+
+ if 'Debugging' in config:
+ self.DEBUG = bool(config['Debugging'])
+ if self.DEBUG:
+ print("-Debugging preference was in config: " + str(self.DEBUG))
+
+
+
+ def handle_request(self, request):
+ """
+ Handle a new API request for this handler.
+
+ request -- APIRequest object
+ """
+
+ try:
+
+ if request.method != 'POST':
+ return APIResponse(status=404)
+
+ if request.path == '/init' or request.path == '/get_property_data' or request.path == '/point_change_value' or request.path == '/point_delete' or request.path == '/internal_logs':
+
+ try:
+
+ if request.path == '/init':
+
+ # Get the list of properties that are being logged
+ try:
+ data = self.get_init_data()
+ if isinstance(data, str):
+ state = 'error'
+ else:
+ state = 'ok'
+
+ return APIResponse(
+ status=200,
+ content_type='application/json',
+ content=json.dumps({'state' : state, 'data' : data}),
+ )
+ except Exception as ex:
+ print("Error getting init data: " + str(ex))
+ return APIResponse(
+ status=500,
+ content_type='application/json',
+ content=json.dumps("Error while getting thing data: " + str(ex)),
+ )
+
+
+
+ elif request.path == '/get_property_data':
+ try:
+ target_data_type = self.data_types_lookup_table[int(request.body['property_id'])]
+ print("target data type from internal lookup table: " + str(target_data_type))
+ data = self.get_property_data( str(request.body['property_id']), target_data_type )
+ if isinstance(data, str):
+ state = 'error'
+ else:
+ state = 'ok'
+
+ return APIResponse(
+ status=200,
+ content_type='application/json',
+ content=json.dumps({'state' : state, 'data' : data}),
+ )
+ except Exception as ex:
+ print("Error getting thing data: " + str(ex))
+ return APIResponse(
+ status=500,
+ content_type='application/json',
+ content=json.dumps("Error while getting thing data: " + str(ex)),
+ )
+
+
+ elif request.path == '/point_change_value':
+ try:
+ data = []
+ target_data_type = self.data_types_lookup_table[int(request.body['property_id'])]
+ print("target data type from internal lookup table: " + str(target_data_type))
+ # action, data_type, property_id, new_value, old_date, new_date
+ data = self.point_change_value( str(request.body['action']), target_data_type, str(request.body['property_id']), str(request.body['new_value']), str(request.body['old_date']), str(request.body['new_date']) )
+ if isinstance(data, str):
+ state = 'error'
+ else:
+ state = 'ok'
+
+ return APIResponse(
+ status=200,
+ content_type='application/json',
+ content=json.dumps({'state' : state, 'data' : data}),
+ )
+ except Exception as ex:
+ print("Error getting thing data: " + str(ex))
+ return APIResponse(
+ status=500,
+ content_type='application/json',
+ content=json.dumps("Error while changing point: " + str(ex)),
+ )
+
+
+
+
+ elif request.path == '/point_delete':
+ print("POINT DELETE CALLED")
+ try:
+ data = []
+
+ target_data_type = self.data_types_lookup_table[int(request.body['property_id'])]
+ print("target data type from internal lookup table: " + str(target_data_type))
+
+ data = self.point_delete(str(request.body['property_id']), target_data_type, str(request.body['start_date']), str(request.body['end_date']) ) #new_value,date,property_id
+ if isinstance(data, str):
+ state = 'error'
+ else:
+ state = 'ok'
+
+ return APIResponse(
+ status=200,
+ content_type='application/json',
+ content=json.dumps({'state' : state, 'data' : data}),
+ )
+ except Exception as ex:
+ print("Error deleting point(s): " + str(ex))
+ return APIResponse(
+ status=500,
+ content_type='application/json',
+ content=json.dumps("Error while deleting point(s): " + str(ex)),
+ )
+
+
+
+
+ elif request.path == '/internal_logs':
+ print("INTERNAL LOGS CALLED")
+ try:
+ data = []
+
+ action = "get"
+ filename = "all"
+ try:
+ filename = str(request.body['filename'])
+ except:
+ print("No specific filename provided")
+ try:
+ action = str(request.body['action'])
+ except:
+ print("No specific action provided, will read data.")
+
+ data = self.internal_logs(action, filename)
+ if isinstance(data, str):
+ state = 'error'
+ else:
+ state = 'ok'
+
+ return APIResponse(
+ status=200,
+ content_type='application/json',
+ content=json.dumps({'state' : state, 'data' : data}),
+ )
+ except Exception as ex:
+ print("Error deleting point(s): " + str(ex))
+ return APIResponse(
+ status=500,
+ content_type='application/json',
+ content=json.dumps("Error while getting logs data: " + str(ex)),
+ )
+
+
+ else:
+ return APIResponse(
+ status=500,
+ content_type='application/json',
+ content=json.dumps("API error"),
+ )
+
+
+ except Exception as ex:
+ print(str(ex))
+ return APIResponse(
+ status=500,
+ content_type='application/json',
+ content=json.dumps("Error"),
+ )
+
+ else:
+ return APIResponse(status=404)
+
+ except Exception as e:
+ print("Failed to handle UX extension API request: " + str(e))
+ return APIResponse(
+ status=500,
+ content_type='application/json',
+ content=json.dumps("API Error"),
+ )
+
+
+
+
+ # INIT
+ def get_init_data(self):
+ if self.DEBUG:
+ print("Getting the initialisation data")
+
+ try:
+ db = sqlite3.connect(':memory:')
+ db = sqlite3.connect(self.log_db_path)
+ except Exception as e:
+ print("Error opening log file: " + str(e))
+ return "Error opening log file: " + str(e)
+
+ # Get list of properties that are being logged from database
+ try:
+ result = []
+ cursor = db.cursor()
+ cursor.execute("SELECT id,descr,maxAge FROM metricIds")
+ all_rows = cursor.fetchall()
+ #if self.DEBUG:
+ #print(str(all_rows));
+ for row in all_rows:
+
+ # Get human readable title, if it's available.
+ current_title = str(row[0])
+ data_type = "none"
+ try:
+ cursor.execute("SELECT value FROM metricsNumber WHERE id=?", (row[0],))
+ data_check = cursor.fetchall()
+ if len(data_check) > 0:
+ data_type = "metricsNumber"
+ self.data_types_lookup_table[row[0]] = 'metricsNumber'
+ if self.DEBUG:
+ print("Data type for this log is Number")
+ else:
+ try:
+ cursor.execute("SELECT value FROM metricsBoolean WHERE id=?", (row[0],))
+ data_check = cursor.fetchall()
+ if len(data_check) > 0:
+ data_type = "metricsBoolean"
+ self.data_types_lookup_table[row[0]] = 'metricsBoolean'
+ if self.DEBUG:
+ print("Data type for this log is Boolean")
+ else:
+ print("Datatype also wasn't boolean. Must be other?")
+ # TODO here support for "other" can be added later, if necessary
+ except Exception as ex:
+ print("Error querying if boolean data exists for this item: " + str(ex))
+
+ except Exception as ex:
+ print("Error getting test data to determine data type: " + str(ex))
+
+ #print("data_type = " + str(data_type))
+ result.append( {'id':row[0],'name':row[1], 'data_type':data_type} )
+
+ db.close()
+ return result
+
+ except Exception as e:
+ print("Init: Error reading data: " + str(e))
+ try:
+ db.close()
+ except:
+ pass
+ return "Init: general error reading data: " + str(e)
+
+
+
+ # GET ALL DATA FOR A SINGLE THING
+ def get_property_data(self, property_id, data_type):
+ if self.DEBUG:
+ print("Getting data for thing " + str(property_id) + " of type " + str(data_type))
+ result = []
+
+ if property_id == None or data_type == None:
+ print("No thing ID or data type provided")
+ return result
+
+ if not data_type in ("metricsBoolean", "metricsNumber", "metricsOther"):
+ print("data_type not of allowed type")
+ return "error"
+
+ try:
+ db = sqlite3.connect(':memory:')
+ db = sqlite3.connect(self.log_db_path)
+ except Exception as e:
+ print("Error opening log database: " + str(e))
+ return "Error opening log database: " + str(e)
+
+ try:
+ cursor = db.cursor()
+ cursor.execute("SELECT date, value FROM " + data_type + " WHERE id=?",(property_id,))
+ all_rows = cursor.fetchall()
+
+ for row in all_rows:
+ #print('date: {0}, value: {1}'.format(row[0],row[1]))
+ result.append( {'date':row[0],'value':row[1]} )
+
+ db.close()
+ return result
+
+ except Exception as e:
+ print("Get property data: error reading data: " + str(e))
+ try:
+ db.close()
+ except:
+ pass
+ return "get_property_data: error reading data: " + str(e)
+
+ return "ok"
+
+
+
+
+ # CHANGE VALUE OF A SINGLE POINT
+ def point_change_value(self, action, data_type, property_id, new_value, old_date, new_date):
+ print("Asked to change/create data point for property " + str(property_id) + " of type " + str(data_type) + " in table " + str(action) + " to " + str(new_value))
+ result = "error"
+
+ if property_id == None or action == None:
+ print("No action set or property ID provided")
+ return "error"
+
+ if not data_type in ("metricsBoolean", "metricsNumber", "metricsOther"):
+ print("data_type not of allowed type")
+ return "error"
+
+ if data_type == "metricsBoolean":
+ if float(new_value) >= 1:
+ new_value = 1
+ else:
+ new_value = 0
+ elif data_type == "metricsNumber":
+ try:
+ new_value = int(new_value)
+ except:
+ new_value = float(new_value)
+
+ try:
+ new_date = int(new_date)
+ old_date = int(old_date)
+ property_id = int(property_id)
+ except:
+ print("Error: the date and/or property strings could not be turned into an int")
+ return "error"
+
+ if self.DEBUG:
+ print("action: " + str(action))
+ print("At old date " + str(old_date))
+ print("and new date " + str(new_date))
+ print("changing value to " + str(new_value))
+ print("for property " + str(property_id))
+ print("of type " + str(data_type))
+
+ try:
+ db = sqlite3.connect(':memory:')
+ db = sqlite3.connect(self.log_db_path)
+ except Exception as e:
+ print("Error opening log file: " + str(e))
+ return "Error opening log file: " + str(e)
+
+ try:
+ cursor = db.cursor()
+
+ if action == "change":
+ cursor.execute("UPDATE " + data_type + " SET value=?,date=? WHERE date=? AND id=?", (new_value,new_date,old_date,property_id))
+ if cursor.rowcount == 1:
+ db.commit()
+ #result = "ok"
+ result = []
+
+ else:
+ return "error" #result = "error"
+
+
+ elif action == "create":
+ print("Creating a new data point")
+ #INSERT INTO projects(name,begin_date,end_date) VALUES(?,?,?)
+ #cursor.execute("INSERT INTO employees VALUES(1, 'John', 700, 'HR', 'Manager', '2017-01-04')"
+ command = "INSERT INTO {}(id,date,value) VALUES({},{},{})".format(data_type, property_id, new_date, new_value)
+ print("COMMAND = " + str(command))
+ cursor.execute(command)
+ #cursor.execute("INSERT INTO " + data_type + " VALUES ?,?,?", (property_id, new_date, new_value,))
+ db.commit()
+
+ # update cursor position?
+ cursor.close()
+ cursor = db.cursor()
+ result = []
+
+ # If all went well, get all the data points.
+ cursor.execute("SELECT date, value FROM " + data_type + " WHERE id=?", (property_id,))
+ all_rows = cursor.fetchall()
+ #if self.DEBUG:
+
+ for row in all_rows:
+ #print('date: {0}, value: {1}'.format(row[0],row[1]))
+ result.append( {'date':row[0],'value':row[1]} )
+
+ db.close()
+ return result
+
+ except Exception as e:
+ print("Error changing point data: " + str(e))
+ try:
+ db.close()
+ except:
+ pass
+ return "Error changing point data: " + str(e)
+
+
+
+
+
+ # DELETE A SINGLE POINT
+
+ def point_delete(self,property_id,data_type,start_date,end_date):
+
+ result = "error"
+
+ if property_id == None:
+ print("No property ID provided")
+ return result
+
+ if not data_type in ("metricsBoolean", "metricsNumber", "metricsOther"):
+ print("data_type not of allowed type")
+ return "error"
+
+ if self.DEBUG:
+ print("Delete from " + str(start_date))
+ print("to " + str(end_date))
+ print("for ID " + str(property_id))
+ print("of data_type " + str(data_type))
+
+ try:
+ db = sqlite3.connect(':memory:')
+ db = sqlite3.connect(self.log_db_path)
+ except Exception as e:
+ print("Error opening log file: " + str(e))
+ return "Error opening log file: " + str(e)
+
+ try:
+ cursor = db.cursor()
+
+ cursor.execute("DELETE FROM " + data_type + " WHERE id=? AND date>=? AND date<=?", (property_id,start_date,end_date,))
+ print("cursor.rowcount after deletion = " + str(cursor.rowcount))
+ if cursor.rowcount > 0:
+ db.commit()
+ result = []
+ #cursor.close()
+ #cursor = db.cursor()
+ #Get all the data points.
+ cursor.execute("SELECT date, value FROM " + data_type + " WHERE id=?", (property_id,))
+ all_rows = cursor.fetchall()
+
+ for row in all_rows:
+ print('date: {0}, value: {1}'.format(row[0],row[1]))
+ result.append( {'date':row[0],'value':row[1]} )
+
+ else:
+ result = "error"
+
+ print(str(result))
+ db.close()
+ return result
+
+ except Exception as e:
+ print("Error deleting a point: " + str(e))
+ try:
+ db.close()
+ except:
+ pass
+ return "Error deleting a point: " + str(e)
+
+
+
+
+
+
+ def internal_logs(self,action,filename):
+ print("in internal logs method. Filename: " + str(filename))
+
+ result = []
+
+ try:
+ # First we delete what needs to be deleted.
+ if action == "delete":
+ for fname in os.listdir(self.log_dir_path):
+ if fname.startswith("run-app.log"):
+ if filename == "all":
+ try:
+ os.remove(os.path.join(self.log_dir_path, fname))
+ if self.DEBUG:
+ print("File deleted")
+ except Exception as ex:
+ if self.DEBUG:
+ print("Could not delete file: " + str(ex))
+ elif str(filename) == str(fname):
+ print("WILL DELETE A SINGLE FILE: " + str(filename))
+ try:
+ os.remove(os.path.join(self.log_dir_path, fname))
+ if self.DEBUG:
+ print("File deleted")
+ except Exception as ex:
+ if self.DEBUG:
+ print("Could not delete file: " + str(ex))
+
+ # Secondly, we send a list of (remaining) existing files.
+ for fname in os.listdir(self.log_dir_path):
+ if fname.startswith("run-app.log"):
+ result.append(fname)
+
+
+ except Exception as ex:
+ print("Error in log handler: " + str(ex))
+
+ return result
+
+
+ def unload(self):
+ if self.DEBUG:
+ print("Shutting down")
+
+
+
+
+def run_command(cmd, timeout_seconds=60):
+ try:
+
+ p = subprocess.run(cmd, timeout=timeout_seconds, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, universal_newlines=True)
+
+ if p.returncode == 0:
+ return p.stdout + '\n' + "Command success" #.decode('utf-8')
+ #yield("Command success")
+ else:
+ if p.stderr:
+ return "Error: " + str(p.stderr) + '\n' + "Command failed" #.decode('utf-8'))
+
+ except Exception as e:
+ print("Error running Arduino CLI command: " + str(e))
+
\ No newline at end of file
diff --git a/views/content.html b/views/content.html
new file mode 100644
index 0000000..7c4aeec
--- /dev/null
+++ b/views/content.html
@@ -0,0 +1,301 @@
+
+
+
+
Privacy manager
+
+
+
+
+
+
+
Data sculptor
+
+
+
+
+
+
+
+
+
Selected point
+
+
+
+
+
+
+
+
+ Y
+
+
+ M
+
+
+ D
+
+
+
+
+
+ h
+
+
+ m
+
+
+ s
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Internal logs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+