From 84b48b50c6da63df6903dbb4c4dac782f2396199 Mon Sep 17 00:00:00 2001 From: Joerg Lehmann Date: Sat, 5 Jun 2021 10:04:27 +0200 Subject: [PATCH] implement greenzone feature, step 1 --- main.go | 4 + persistence.go | 15 ++++ snippets/map.html | 17 ++++ static/js/Map.SelectArea.min.js | 142 ++++++++++++++++++++++++++++++++ static/js/map.js | 35 +++++++- static/js/tracker.js | 2 +- tracker.go | 67 ++++++++++++++- 7 files changed, 277 insertions(+), 5 deletions(-) create mode 100644 static/js/Map.SelectArea.min.js diff --git a/main.go b/main.go index d0d45e7..e2fb7d5 100644 --- a/main.go +++ b/main.go @@ -74,6 +74,9 @@ func serveTemplate(w http.ResponseWriter, r *http.Request) { query_values, } + // because of caching, we need to set the Last-Modified header + w.Header().Set("Last-Modified", time.Now().Format(http.TimeFormat)) + if err := tmpl.ExecuteTemplate(w, "layout", &data); err != nil { logit(err.Error()) http.Error(w, http.StatusText(500), 500) @@ -98,6 +101,7 @@ func main() { http.HandleFunc("/downloadmetrics", downloadmetricsHandler) http.HandleFunc("/order", orderHandler) http.HandleFunc("/save_tracker_settings", save_tracker_settingsHandler) + http.HandleFunc("/save_tracker_greenzone", save_tracker_greenzoneHandler) http.HandleFunc("/getstripepaymentintent", getstripepaymentintentHandler) http.HandleFunc("/stripewebhook", stripeWebhookHandler) diff --git a/persistence.go b/persistence.go index a4cd43b..80ec444 100644 --- a/persistence.go +++ b/persistence.go @@ -123,6 +123,21 @@ func updateTrackerSettings(trackerSettings Dev) error { return nil } +func updateTrackerGreenzone(deveui string, greenzone string) error { + conn := globalPool.Get() + defer conn.Close() + + // SET object + _, err := conn.Do("HMSET", devPrefix+deveui, + "greenzone", greenzone) + + if err != nil { + return err + } + + return nil +} + func checkUserAvailable(username string) bool { logit("checkUserAvailable: User: " + username) conn := globalPool.Get() diff --git a/snippets/map.html b/snippets/map.html index bd76808..a0d005f 100644 --- a/snippets/map.html +++ b/snippets/map.html @@ -5,6 +5,7 @@ + {{end}} {{define "body_content"}} {{ if ne .UserName "" }} @@ -18,6 +19,22 @@ + +
diff --git a/static/js/Map.SelectArea.min.js b/static/js/Map.SelectArea.min.js new file mode 100644 index 0000000..60484e5 --- /dev/null +++ b/static/js/Map.SelectArea.min.js @@ -0,0 +1,142 @@ +/** + * L.Map.SelectArea - Area selection tool for leaflet + * + * @author Alexander Milevski + * @see https://github.com/w8r/leaflet-area-select + * @license MIT + * @preserve + */ +// UMD +!function(t){var o;if("function"==typeof define&&define.amd) +// AMD +define(["leaflet"],t);else if("undefined"!=typeof module) +// Node/CommonJS +o=require("leaflet"),module.exports=t(o);else{ +// Browser globals +if(void 0===window.L)throw new Error("Leaflet must be loaded first");t(window.L)}}(function(a){function t(){return!0} +/** + * @class L.Map.SelectArea + * @extends {L.Map.BoxZoom} + */ +a.Map.SelectArea=a.Map.BoxZoom.extend({statics:{ +/** + * @static + * @type {String} + */ +AREA_SELECTED:"areaselected", +/** + * @static + * @type {String} + */ +AREA_SELECT_START:"areaselectstart", +/** + * @static + * @type {String} + */ +AREA_SELECTION_TOGGLED:"areaselecttoggled"},options:{shiftKey:!1,ctrlKey:!0,validate:t,autoDisable:!1,cursor:"crosshair"}, +/** + * @param {L.Map} map + * @constructor + */ +initialize:function(t,o){a.Util.setOptions(this,o||{}),a.Map.BoxZoom.prototype.initialize.call(this,t), +/** + * @type {Function} + */ +this._validate=null, +/** + * @type {Boolean} + */ +this._moved=!1, +/** + * @type {Boolean} + */ +this._autoDisable=!this.options.ctrlKey&&this.options.autoDisable, +/** + * @type {L.Point} + */ +this._lastLayerPoint=null, +/** + * @type {String|Null} + */ +this._beforeCursor=null,this.setValidate(this.options.validate),this.setAutoDisable(this.options.autoDisable)}, +/** + * @param {Function=} validate + * @return {SelectArea} + */ +setValidate:function(o){var e=this;return"function"!=typeof o&&(o=t),this._validate=function(t){return o.call(e,t)},this}, +/** + * @param {Boolean} autoDisable + */ +setAutoDisable:function(t){this._autoDisable=!!t}, +/** + * @param {Boolean} on + */ +setControlKey:function(t){var o=this._enabled;o&&this.disable(),this.options.ctrlKey=!!t,t&&(this.options.shiftKey=!1),o&&this.enable()}, +/** + * @param {Boolean} on + */ +setShiftKey:function(t){var o=this._enabled;o&&this.disable(),this.options.shiftKey=!!t,t&&(this.options.ctrlKey=!1),o&&this.enable()}, +/** + * Disable dragging or zoombox + * @param {Function=} validate + * @param {Boolean=} autoDisable + */ +enable:function(t,o){this.options.shiftKey?this._map.boxZoom&&this._map.boxZoom.disable():this.options.ctrlKey||this._map.dragging.disable(),a.Map.BoxZoom.prototype.enable.call(this),this.options.ctrlKey||this._setCursor(),t&&this.setValidate(t),this.setAutoDisable(o),this._map.fire(a.Map.SelectArea.AREA_SELECTION_TOGGLED)}, +/** + * Re-enable box zoom or dragging + */ +disable:function(){a.Map.BoxZoom.prototype.disable.call(this),this.options.ctrlKey||this._restoreCursor(),this.options.shiftKey?this._map.boxZoom&&this._map.boxZoom.enable():this._map.dragging.enable(),this._map.fire(a.Map.SelectArea.AREA_SELECTION_TOGGLED)}, +/** + * Also listen to ESC to cancel interaction + * @override + */ +addHooks:function(){a.Map.BoxZoom.prototype.addHooks.call(this),a.DomEvent.on(document,"keyup",this._onKeyUp,this).on(document,"keydown",this._onKeyPress,this).on(document,"contextmenu",this._onMouseDown,this).on(window,"blur",this._onBlur,this),this._map.on("dragstart",this._onMouseDown,this)}, +/** + * @override + */ +removeHooks:function(){a.Map.BoxZoom.prototype.removeHooks.call(this),a.DomEvent.off(document,"keyup",this._onKeyUp,this).off(document,"keydown",this._onKeyPress,this).off(document,"contextmenu",this._onMouseDown,this).off(window,"blur",this._onBlur,this),this._map.off("dragstart",this._onMouseDown,this)}, +/** + * @override + */ +_onMouseDown:function(t){if(this._moved=!1,this._lastLayerPoint=null,this.options.shiftKey&&!t.shiftKey||this.options.ctrlKey&&!t.ctrlKey||1!==t.which&&1!==t.button)return!1;a.DomEvent.stop(t);var o=this._map.mouseEventToLayerPoint(t);if(!this._validate(o))return!1;a.DomUtil.disableTextSelection(),a.DomUtil.disableImageDrag(),this._startLayerPoint=o,a.DomEvent.on(document,"mousemove",this._onMouseMove,this).on(document,"mouseup",this._onMouseUp,this).on(document,"keydown",this._onKeyDown,this)}, +/** + * @override + */ +_onMouseMove:function(t){this._moved||(this._box=a.DomUtil.create("div","leaflet-zoom-box",this._pane),a.DomUtil.setPosition(this._box,this._startLayerPoint),this._map.fire(a.Map.SelectArea.AREA_SELECT_START));var o,e=this._startLayerPoint,i=this._box,s=this._map.mouseEventToLayerPoint(t),n=s.subtract(e);this._validate(s)&&(this._lastLayerPoint=s,o=new a.Point(Math.min(s.x,e.x),Math.min(s.y,e.y)),a.DomUtil.setPosition(i,o),this._moved=!0, +// TODO refactor: remove hardcoded 4 pixels +i.style.width=Math.max(0,Math.abs(n.x)-4)+"px",i.style.height=Math.max(0,Math.abs(n.y)-4)+"px")}, +/** + * General on/off toggle + * @param {KeyboardEvent} e + */ +_onKeyUp:function(t){27===t.keyCode?this._moved&&this._box&&this._finish():this.options.ctrlKey&&(this._restoreCursor(),this._map.dragging.enable())}, +/** + * Key down listener to enable on ctrl-press + * @param {KeyboardEvent} e + */ +_onKeyPress:function(t){this.options.ctrlKey&&(t.ctrlKey||"dragstart"===t.type)&&null===this._beforeCursor&&(this._setCursor(),this._map.dragging._draggable._onUp(t),// hardcore +this._map.dragging.disable())}, +/** + * Window blur listener to restore state + * @param {Event} e + */ +_onBlur:function(t){this._restoreCursor(),this._map.dragging.enable()}, +/** + * Set crosshair cursor + */ +_setCursor:function(){this._beforeCursor=this._container.style.cursor,this._container.style.cursor=this.options.cursor}, +/** + * Restore status quo cursor + */ +_restoreCursor:function(){this._container.style.cursor=this._beforeCursor,this._beforeCursor=null}, +/** + * @override + */ +_onMouseUp:function(t){this._finish();var o,e=this._map,i=this._lastLayerPoint;// map.mouseEventToLayerPoint(e); +i&&!this._startLayerPoint.equals(i)&&(a.DomEvent.stop(t),o=new a.LatLngBounds(e.layerPointToLatLng(this._startLayerPoint),e.layerPointToLatLng(i)), +//map.fitBounds(bounds); +this._autoDisable?this.disable():this._restoreCursor(),this._moved=!1,a.Util.requestAnimFrame(function(){e.fire(a.Map.SelectArea.AREA_SELECTED,{bounds:o})}))}}), +// expose setting +a.Map.mergeOptions({selectArea:!1}), +// register hook +a.Map.addInitHook("addHandler","selectArea",a.Map.SelectArea)}); diff --git a/static/js/map.js b/static/js/map.js index dfea31a..ea69879 100644 --- a/static/js/map.js +++ b/static/js/map.js @@ -138,6 +138,33 @@ function refreshDatapoints(deveui, start, stop) { } $(document).ready(function () { + var greenzone = ""; + $("#modal-yes").click(function () { + $("#yesnomodal").removeClass("is-active"); + $.ajax({ + url: "save_tracker_greenzone", + type: "get", //send it through get method + dataType: "json", + data: { + deveui: deveui, + greenzone: greenzone + }, + success: function (response) { + if (response.rc == 0) { + $('#greenzone_'+$('#deveui').html()).html($('#greenzone').val()); + } + }, + error: function (xhr) { + //Do Something to handle error + console.log("Somethin went wrong in ajax save_tracker_greenzone..."); + } + }); + }); + + $("#modal-no").click(function () { + $("#yesnomodal").removeClass("is-active"); + }); + params = "deveui=" + deveui + "&alias=" + alias + "&start=" + s_start + "&stop=" + s_stop; $("#btn_download").attr("href", "/downloadmetrics?" + params); $("#btn_download").attr("download", getDownloadFilename(deveui, s_start, s_stop)); @@ -145,10 +172,16 @@ $(document).ready(function () { crs: L.CRS.EPSG3857, zoomControl: true, maxBounds: boundsSwitzerland, - doubleClickZoom: false + doubleClickZoom: false, + selectArea: true }); map.setMinZoom(map.getBoundsZoom(map.options.maxBounds)); + map.on('areaselected', (e) => { + greenzone = (e.bounds.toBBoxString()); // lon, lat, lon, lat + $("#yesnomodal").addClass("is-active"); + }); + map.on('dblclick', function (e) { map.fitBounds(bounds, { padding: [20, 20] }); }); diff --git a/static/js/tracker.js b/static/js/tracker.js index 02f3c28..2ba69ee 100644 --- a/static/js/tracker.js +++ b/static/js/tracker.js @@ -10,7 +10,7 @@ function validate(what, text) { } else if (what == 'email') { var re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; } else if (what == 'greenzone') { - var re = /^$|^[0-9]+\.[0-9]+,[0-9]+\.[0-9],[0-9]+\.[0-9],[0-9]+\.[0-9]$/; + var re = /^$|^[0-9]+\.[0-9]+,[0-9]+\.[0-9]+,[0-9]+\.[0-9]+,[0-9]+\.[0-9]+$/; } return re.test(text); diff --git a/tracker.go b/tracker.go index 57e0186..0ef9727 100644 --- a/tracker.go +++ b/tracker.go @@ -126,10 +126,10 @@ func save_tracker_settingsHandler(response http.ResponseWriter, request *http.Re } mygreenzone := greenzone[0] - match3, _ := regexp.MatchString(`^$|^[0-9]+\.[0-9]+,[0-9]+\.[0-9],[0-9]+\.[0-9],[0-9]+\.[0-9]$`, mygreenzone) + match3, _ := regexp.MatchString(`^$|^[0-9]+\.[0-9]+,[0-9]+\.[0-9]+,[0-9]+\.[0-9]+,[0-9]+\.[0-9]+$`, mygreenzone) if !(match3) { - log.Println("Url Param 'email' is not valid") - fmt.Fprintf(response, "{ \"rc\": 17, \"msg\": \"email is not valid, must be in in format max.mustermann@example.com\" }") + log.Println("Url Param 'greenzone' is not valid") + fmt.Fprintf(response, "{ \"rc\": 17, \"msg\": \"greenzone is not valid, must be in in 123.45,123.45,123.45,123.45\" }") return } @@ -157,3 +157,64 @@ func save_tracker_settingsHandler(response http.ResponseWriter, request *http.Re fmt.Fprintf(response, "{ \"rc\": 7, \"msg\": \"Only available for logged in users\" }") } } + +func save_tracker_greenzoneHandler(response http.ResponseWriter, request *http.Request) { + name := getUserName(request) + if name != "" { + + deveui, ok := request.URL.Query()["deveui"] + + if !ok || len(deveui[0]) < 1 { + log.Println("Url Param 'deveui' is missing") + fmt.Fprintf(response, "{ \"rc\": 1, \"msg\": \"deveui must be specified in URL\" }") + return + } + // Query()["deveui"] will return an array of items, + // we only want the single item. + mydeveui := deveui[0] + + if len(mydeveui) != 16 { + log.Println("specified 'deveui' has invalid length") + fmt.Fprintf(response, "{ \"rc\": 8, \"msg\": \"specified deveui has invalid length\" }") + return + } + + if !(Contains(getMyDevs(name), mydeveui)) { + log.Println("specified 'deveui' does not belong to this user") + fmt.Fprintf(response, "{ \"rc\": 2, \"msg\": \"specified deveui does not belong to this user\" }") + return + } + + log.Println("Url Param 'deveui' is: " + string(mydeveui)) + + greenzone, ok1 := request.URL.Query()["greenzone"] + + if !ok1 { + log.Println("Url Param 'greenzone' is missing") + fmt.Fprintf(response, "{ \"rc\": 3, \"msg\": \"greenzone must be specified in URL\" }") + return + } + mygreenzone := greenzone[0] + + match, _ := regexp.MatchString(`^$|^[0-9]+\.[0-9]+,[0-9]+\.[0-9]+,[0-9]+\.[0-9]+,[0-9]+\.[0-9]+$`, mygreenzone) + if !(match) { + log.Println("Url Param 'greenzone' is not valid") + fmt.Fprintf(response, "{ \"rc\": 17, \"msg\": \"greenzone is not valid, must be in in 123.45,123.45,123.45,123.45\" }") + return + } + + // now we try to save the settings + err := updateTrackerGreenzone(mydeveui, mygreenzone) + + if err != nil { + log.Println("Error to Update Greenzone") + fmt.Fprintf(response, "{ \"rc\": 6, \"msg\": \"error with updating greenzone\" }") + return + } else { + fmt.Fprintf(response, "{ \"rc\": 0, \"msg\": \"SUCCESS\" }") + } + + } else { + fmt.Fprintf(response, "{ \"rc\": 7, \"msg\": \"Only available for logged in users\" }") + } +}