implement greenzone feature, step 1

This commit is contained in:
Joerg Lehmann 2021-06-05 10:04:27 +02:00
parent 5e3f60d560
commit 84b48b50c6
7 changed files with 277 additions and 5 deletions

View File

@ -74,6 +74,9 @@ func serveTemplate(w http.ResponseWriter, r *http.Request) {
query_values, 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 { if err := tmpl.ExecuteTemplate(w, "layout", &data); err != nil {
logit(err.Error()) logit(err.Error())
http.Error(w, http.StatusText(500), 500) http.Error(w, http.StatusText(500), 500)
@ -98,6 +101,7 @@ func main() {
http.HandleFunc("/downloadmetrics", downloadmetricsHandler) http.HandleFunc("/downloadmetrics", downloadmetricsHandler)
http.HandleFunc("/order", orderHandler) http.HandleFunc("/order", orderHandler)
http.HandleFunc("/save_tracker_settings", save_tracker_settingsHandler) http.HandleFunc("/save_tracker_settings", save_tracker_settingsHandler)
http.HandleFunc("/save_tracker_greenzone", save_tracker_greenzoneHandler)
http.HandleFunc("/getstripepaymentintent", getstripepaymentintentHandler) http.HandleFunc("/getstripepaymentintent", getstripepaymentintentHandler)
http.HandleFunc("/stripewebhook", stripeWebhookHandler) http.HandleFunc("/stripewebhook", stripeWebhookHandler)

View File

@ -123,6 +123,21 @@ func updateTrackerSettings(trackerSettings Dev) error {
return nil 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 { func checkUserAvailable(username string) bool {
logit("checkUserAvailable: User: " + username) logit("checkUserAvailable: User: " + username)
conn := globalPool.Get() conn := globalPool.Get()

View File

@ -5,6 +5,7 @@
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js" <script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"
integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA==" integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA=="
crossorigin=""></script> crossorigin=""></script>
<script type="text/javascript" src="/static/js/Map.SelectArea.min.js"></script>
{{end}} {{end}}
{{define "body_content"}} {{define "body_content"}}
{{ if ne .UserName "" }} {{ if ne .UserName "" }}
@ -18,6 +19,22 @@
<script src="static/bulma-calendar/js/bulma-calendar.min.js"></script> <script src="static/bulma-calendar/js/bulma-calendar.min.js"></script>
<script src="static/js/map.js"></script> <script src="static/js/map.js"></script>
<div id="yesnomodal" class="modal">
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">Koordinaten als gr&uuml;ne Zone &uuml;bernehmen?</p>
</header>
<div class="modal-card-body">
<!-- Content ... -->
</div>
<footer class="modal-card-foot">
<button id="modal-yes" class="button is-success">Ja</button>
<button id="modal-no" class="button">Nein</button>
</footer>
</div>
</div>
<div class="columns is-mobile" id="myselectors"> <div class="columns is-mobile" id="myselectors">
<div class="column is-narrow py-1"> <div class="column is-narrow py-1">
<div id="reportrange" class="datepicker" style="background: #fff; cursor: pointer; padding: 5px 10px; border: 1px solid #ccc; width: 100%"> <div id="reportrange" class="datepicker" style="background: #fff; cursor: pointer; padding: 5px 10px; border: 1px solid #ccc; width: 100%">

142
static/js/Map.SelectArea.min.js vendored Normal file
View File

@ -0,0 +1,142 @@
/**
* L.Map.SelectArea - Area selection tool for leaflet
*
* @author Alexander Milevski <info@w8r.name>
* @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)});

View File

@ -138,6 +138,33 @@ function refreshDatapoints(deveui, start, stop) {
} }
$(document).ready(function () { $(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; params = "deveui=" + deveui + "&alias=" + alias + "&start=" + s_start + "&stop=" + s_stop;
$("#btn_download").attr("href", "/downloadmetrics?" + params); $("#btn_download").attr("href", "/downloadmetrics?" + params);
$("#btn_download").attr("download", getDownloadFilename(deveui, s_start, s_stop)); $("#btn_download").attr("download", getDownloadFilename(deveui, s_start, s_stop));
@ -145,10 +172,16 @@ $(document).ready(function () {
crs: L.CRS.EPSG3857, crs: L.CRS.EPSG3857,
zoomControl: true, zoomControl: true,
maxBounds: boundsSwitzerland, maxBounds: boundsSwitzerland,
doubleClickZoom: false doubleClickZoom: false,
selectArea: true
}); });
map.setMinZoom(map.getBoundsZoom(map.options.maxBounds)); 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.on('dblclick', function (e) {
map.fitBounds(bounds, { padding: [20, 20] }); map.fitBounds(bounds, { padding: [20, 20] });
}); });

View File

@ -10,7 +10,7 @@ function validate(what, text) {
} else if (what == 'email') { } 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,}))$/; 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') { } 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); return re.test(text);

View File

@ -126,10 +126,10 @@ func save_tracker_settingsHandler(response http.ResponseWriter, request *http.Re
} }
mygreenzone := greenzone[0] 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) { if !(match3) {
log.Println("Url Param 'email' is not valid") log.Println("Url Param 'greenzone' is not valid")
fmt.Fprintf(response, "{ \"rc\": 17, \"msg\": \"email is not valid, must be in in format max.mustermann@example.com\" }") fmt.Fprintf(response, "{ \"rc\": 17, \"msg\": \"greenzone is not valid, must be in in 123.45,123.45,123.45,123.45\" }")
return 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\" }") 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\" }")
}
}