From: Roman Bazalevsky Date: Wed, 28 Nov 2018 14:17:23 +0000 (+0300) Subject: Полностью новая версия веб-интерфейса на базе chart.js X-Git-Url: https://git.rvb.name/weathermon.git/commitdiff_plain/2094fb89e05795f5daee526dc4617a169faba201?ds=inline;hp=7d6cb9a3d58687e0bf3ca3c30882782fe7ae12b8 Полностью новая версия веб-интерфейса на базе chart.js --- diff --git a/web/archive.html b/web/archive.html new file mode 100644 index 0000000..e402043 --- /dev/null +++ b/web/archive.html @@ -0,0 +1,48 @@ + + + + + + + + + + + + + +
+ +
+ +
+
+
+
+
Год
+
+
Месяц
+
+
День
+
+
Параметр
+
+
+
+ + +
+
+
+
+ + + + + diff --git a/web/archive.js b/web/archive.js new file mode 100644 index 0000000..9420250 --- /dev/null +++ b/web/archive.js @@ -0,0 +1,328 @@ +urlbase="/meteo/cgi/"; + +archive_base="/meteo/archive"; +current_base = "/meteo/graph"; + +var url = window.location.pathname; + +if (url.startsWith(archive_base)) { + url = url.substr(archive_base.length); +} + +var params = url.split('/'); + +var year = params[1]; +var month = params[2]; +var day = params[3]; +var devid = params[4]; +var sensor = params[5]; +var param = params[6]; + +var properties; +var years; +var months; +var days; +var sensors; + +function composeUrl() { + url = window.location.protocol+"//"+window.location.host+archive_base+"/"; + if (year) { + url = url + year; + if (month) { + url = url + "/" + month; + if (day) { + url = url + "/" + day; + if (devid) { + url = url + "/" + devid; + if (sensor) { + url = url + "/" + sensor; + if (param) { + url = url + "/" + param; + } + } + } + } + } + } + history.pushState({}, null, url); +} + +function selectChange() { + new_year = document.getElementById("year").value; + new_month = document.getElementById("month").value; + new_day = document.getElementById("day").value; + sensor_id = document.getElementById("sensor").value.split("."); + new_devid = sensor_id[0]; + new_sensor = sensor_id[1]; + new_param = sensor_id[2]; + if (year != new_year) { + year = new_year; + GetMonths(); + } else if (month != new_month) { + month = new_month; + GetDays(); + } else if (day != new_day) { + day = new_day; + GetSensors(); + } else if ((new_devid != devid) || (new_sensor != sensor) || (new_param != param)) { + devid = new_devid; + sensor = new_sensor; + param = new_param; + RefreshGraph(); + } + composeUrl(); +} + +function processDataset(dataset,devid,sensorname,paramname) { + var scale = properties["scale"][devid+"."+sensorname+"."+paramname] + if (scale) { + var result=[]; + for (idx in dataset) { + newRec = {}; + newRec.t = dataset[idx].t + newRec.y = dataset[idx].y * scale[0]; + result.push(newRec); + } + return result; + } else { + return dataset; + } +} + +function drawGraph(graphData) { + + document.getElementById("current").href = current_base+"/"+devid + "/" + sensor + "/" + param; + + var div = document.getElementById("chartdiv"); + var canvas = document.getElementById("chart"); + + canvas.width = div.style.width; + canvas.height = div.style.height; + + var ctx = canvas.getContext('2d'); + var color = Chart.helpers.color; + + var sensor_path = devid+"."+sensor+"."+param; + + var y_label = properties["names"][sensor_path]; + if (properties["units"][sensor_path]) { + y_label = y_label + ", " + properties["units"][sensor_path]; + } + + var cfg = { + type: 'bar', + data: { + datasets: [ + { + label: properties["places"][sensor_path] + ' - ' + properties["names"][sensor_path], + backgroundColor: color(properties["colors"][sensor_path]).alpha(0.5).rgbString(), + borderColor: properties["colors"][sensor_path], + data: processDataset(graphData,devid,sensor,param), + type: 'line', + pointRadius: 0, + fill: true, + borderWidth: 2 + } + ] + }, + options: { + legend: { + labels: { + fontColor: properties["fonts"]["legend"]["color"], + fontSize: properties["fonts"]["legend"]["size"], + fontStyle: properties["fonts"]["legend"]["style"], + } + }, + scales: { + xAxes: [{ + type: 'time', + distribution: 'series', + scaleLabel: { + fontColor: properties["fonts"]["axes"]["color"], + fontSize: properties["fonts"]["axes"]["size"], + fontStyle: properties["fonts"]["axes"]["style"], + } + }], + yAxes: [{ + scaleLabel: { + display: true, + labelString: y_label, + fontColor: properties["fonts"]["axes"]["color"], + fontSize: properties["fonts"]["axes"]["size"], + fontStyle: properties["fonts"]["axes"]["style"], + } + }] + } + } + + } + var chart = new Chart(ctx, cfg); +} + +function RefreshGraph() { + + var req = new XMLHttpRequest(); + + req.onreadystatechange = function () { + if (this.readyState != 4) return; + if (this.status != 200) { + setTimeout(RefreshGraph,30000); + return; + } + var graphData = JSON.parse(this.responseText); + drawGraph(graphData); + }; + + req.open("GET", urlbase+"get-archive/"+year+"/"+month+"/"+day+"/"+devid+"/"+sensor+"/"+param, true); + req.withCredentials = true; + req.send(); + +} + +function GetProperties() { + var req = new XMLHttpRequest(); + + req.onreadystatechange = function () { + if (this.readyState != 4) return; + if (this.status != 200) { + setTimeout(GetProperties,30000); + return; + } + properties = JSON.parse(this.responseText); + GetYears(); + }; + + req.open("GET", urlbase+"props", true); + req.withCredentials = true; + req.send(); + +} + +function fillSelector(id,data,value,nameFunc) { + + var element = document.getElementById(id); + var html = ""; + var line; + + for (i in data) { + if (nameFunc) { + line = "" + } else { + line = "" + } + html = html + line; + } + + element.innerHTML = html; + if (value) { + element.value = value; + } + return element.value; + +} + +function GetYears() { + var req = new XMLHttpRequest(); + + req.onreadystatechange = function () { + if (this.readyState != 4) return; + if (this.status != 200) { + setTimeout(GetYears,30000); + return; + } + years = JSON.parse(this.responseText); + year = fillSelector("year", years, year); + composeUrl(); + if (year) { + GetMonths(); + } + }; + + req.open("GET", urlbase+"years", true); + req.withCredentials = true; + req.send(); + +} + +function GetMonths() { + var req = new XMLHttpRequest(); + + req.onreadystatechange = function () { + if (this.readyState != 4) return; + if (this.status != 200) { + setTimeout(GetMonths,30000); + return; + } + months = JSON.parse(this.responseText); + month = fillSelector("month", months, month); + composeUrl(); + if (month) { + GetDays(); + } + }; + + req.open("GET", urlbase+"months/"+year, true); + req.withCredentials = true; + req.send(); + +} + +function GetDays() { + var req = new XMLHttpRequest(); + + req.onreadystatechange = function () { + if (this.readyState != 4) return; + if (this.status != 200) { + setTimeout(GetDays,30000); + return; + } + days = JSON.parse(this.responseText); + day = fillSelector("day", days, day); + composeUrl(); + if (day) { + GetSensors(); + } + }; + + req.open("GET", urlbase+"days/"+year+"/"+month, true); + req.withCredentials = true; + req.send(); + +} + +function SensorName(id) { + return properties["places"][id]+" - "+properties["names"][id]; +} + +function GetSensors() { + var req = new XMLHttpRequest(); + + req.onreadystatechange = function () { + if (this.readyState != 4) return; + if (this.status != 200) { + setTimeout(GetSensors,30000); + return; + } + sensors = JSON.parse(this.responseText); + if (devid && sensor && param) { + sensor_id = devid+"."+sensor+"."+param; + } else { + sensor_id = null; + } + sensor_id = fillSelector("sensor", sensors, sensor_id, SensorName).split("."); + devid = sensor_id[0]; + sensor = sensor_id[1]; + param = sensor_id[2]; + composeUrl(); + if (sensor_id) { + RefreshGraph(); + } + }; + + req.open("GET", urlbase+"sensors/"+year+"/"+month+"/"+day, true); + req.withCredentials = true; + req.send(); + +} + +setTimeout(GetProperties,100); diff --git a/web/auth.php b/web/auth.php new file mode 100644 index 0000000..4807f49 --- /dev/null +++ b/web/auth.php @@ -0,0 +1,66 @@ + false)))) { + die($err); +} + +$db -> exec('SET CHARACTER SET utf8'); + +$auth_token = $_COOKIE["auth-token"]; + +$timestamp = 0; + +if ($auth_token) { + + $sql = " + SELECT UNIX_TIMESTAMP(MAX(expires)) timestamp FROM tokens WHERE str=:s and expires>now() + "; + + $q = $db -> prepare( $sql ); + $q -> bindParam(':s',$auth_token,PDO::PARAM_INT); + $q -> execute(); + + $res = []; + + $row = $q -> fetch(PDO::FETCH_ASSOC); + $timestamp = $row['timestamp']; + +} + +if ($timestamp) { + setcookie("auth-token",$auth_token,$timestamp); +} else { + $auth_token = com_create_guid(); + $timestamp = time()+86400*365; + setcookie("auth-token",$auth_token,$timestamp); + $sql = " + INSERT INTO tokens(str,description,expires) VALUES (:token,:descr,FROM_UNIXTIME(:expires)) + "; + $q = $db -> prepare( $sql ); + $q -> bindParam(':token',$auth_token,PDO::PARAM_STR); + $descr = $_SERVER["PHP_AUTH_USER"]." from ".$_SERVER["REMOTE_ADDR"]." at ".date('m/d/Y h:i:s a', time()); + $q -> bindParam(':descr',$descr,PDO::PARAM_STR); + $q -> bindParam(':expires',$timestamp,PDO::PARAM_INT); + $db -> beginTransaction(); + $q -> execute(); + $db -> commit(); + +} + +print $auth_token; + +?> diff --git a/web/cgi.php b/web/cgi.php new file mode 100644 index 0000000..facdc70 --- /dev/null +++ b/web/cgi.php @@ -0,0 +1,467 @@ + false)))) { + die($err); +} + +$db -> exec('SET CHARACTER SET utf8'); + +$auth_token = $_COOKIE["auth-token"]; +$auth = 0; + +if ($auth_token) { + + $sql = " + SELECT COUNT(*) AS auth FROM tokens WHERE str=:s and expires>now() + "; + + $q = $db -> prepare( $sql ); + $q -> bindParam(':s',$auth_token,PDO::PARAM_INT); + $q -> execute(); + + $res = []; + + $row = $q -> fetch(PDO::FETCH_ASSOC); + $auth = $row['auth']; + +} + +if ($auth || (strpos($client_ip, "192.168.") === 0) || (strpos($client_ip, "10.8.") === 0)) { + $filter = ''; +} else { + $filter = ' and s.is_public=1'; +} + +$hash = md5($_SERVER['QUERY_STRING'].":".$filter); +$redis = new Redis(); +$redis->pconnect('127.0.0.1', 6379); +$results = $redis->get('meteo-'.$hash); +if ($results) { + + $results = unserialize($results); + print(json_encode($results)); + return; + +} + +function getYears($db) { + + $sql = " + SELECT DISTINCT DATE_FORMAT(day,'%Y') y FROM sensors_ranges ORDER BY y DESC + "; + + $q = $db -> prepare( $sql ); + $q -> execute(); + + $res = []; + + while ($row = $q -> fetch(PDO::FETCH_ASSOC)) { + array_push($res, $row['y']); + } + + return $res; + +} + +function getMonths($db,$year) { + + $y = intval($year); + + $sql = " + SELECT DISTINCT DATE_FORMAT(day,'%m') m FROM sensors_ranges WHERE day>=STR_TO_DATE('".strval($y)."-01-01','%Y-%m-%d') and day prepare( $sql ); + $q -> execute(); + + $res = []; + + while ($row = $q -> fetch(PDO::FETCH_ASSOC)) { + array_push($res, $row['m']); + } + + return $res; + +} + +function getDays($db,$year,$month) { + + $y = intval($year); + $m = intval($month); + + $sql = " + SELECT DISTINCT DATE_FORMAT(day,'%d') d FROM sensors_ranges WHERE day>=STR_TO_DATE('".strval($y)."-".strval($m)."-01','%Y-%m-%d') and DATE_ADD(STR_TO_DATE('".strval($y)."-".strval($m)."-01','%Y-%m-%d'), INTERVAL 1 MONTH) ORDER BY d DESC + "; + + $q = $db -> prepare( $sql ); + $q -> execute(); + + $res = []; + + while ($row = $q -> fetch(PDO::FETCH_ASSOC)) { + array_push($res, $row['d']); + } + + return $res; + +} + +function getSensors($db,$year,$month,$day) { + + global $filter; + + $y = intval($year); + $m = intval($month); + $d = intval($day); + + $sql = " + SELECT DISTINCT + CONCAT(s_id,'.',t.st_abbr,'.',p.st_name) id + FROM + sensors_ranges r, sensors s, sensor_types t, st_parameters p + WHERE + r.sensor=s.id and r.parameter=p.id and s.st_id=t.id and p.st_id=t.id ".$filter." + and r.day=STR_TO_DATE('".strval($y)."-".strval($m)."-".strval($d)."','%Y-%m-%d') + "; + + $q = $db -> prepare( $sql ); + $q -> execute(); + + $res = []; + + while ($row = $q -> fetch(PDO::FETCH_ASSOC)) { + array_push($res, $row['id']); + } + + return $res; + +} + +function getCurrent($db,$id,$type,$param) { + + global $filter; + + $sql = " + SELECT s.id s_id,p.id p_id + FROM sensors s,sensor_types t, st_parameters p + WHERE s.st_id=t.id and p.st_id=t.id and s_id=:id and t.st_abbr=:type and p.st_name=:param ".$filter." + "; + + $q = $db -> prepare( $sql ); + $q -> bindParam(':id',$id,PDO::PARAM_STR); + $q -> bindParam(':type',$type,PDO::PARAM_STR); + $q -> bindParam(':param',$param,PDO::PARAM_STR); + $q -> execute(); + $sensor = $q -> fetch(PDO::FETCH_ASSOC); + + $sql = " + SELECT + u.id stored_unit,du.id display_unit + FROM + sensors s,sensor_types t,st_parameters p,units u,units du + WHERE s.st_id=t.id and p.st_id=t.id and p.st_unit=u.id and u.unit_group=du.unit_group and du.is_default=1 and s.id=:id and p.id=:param + "; + + $q = $db -> prepare( $sql ); + $q -> bindParam(':id',$sensor['s_id'],PDO::PARAM_INT); + $q -> bindParam(':param',$sensor['p_id'],PDO::PARAM_INT); + $q -> execute(); + $units = $q -> fetch(PDO::FETCH_ASSOC); + + $sql = " + SELECT + CONCAT(subset.t,'5:00') t,UnitConv(avg(subset.y),:stored,:display) y + FROM ( + SELECT + substr(date_format(timestamp,'%Y-%m-%dT%H:%i'),1,15) t,value y + FROM + sensor_values + WHERE + sensor_id = :id and parameter_id=:param + and timestamp>adddate(now(),-1) + ) subset + GROUP BY subset.t + ORDER BY subset.t + "; + + $q = $db -> prepare( $sql ); + $q -> bindParam(':id',$sensor['s_id'],PDO::PARAM_INT); + $q -> bindParam(':param',$sensor['p_id'],PDO::PARAM_INT); + $q -> bindParam(':stored',$units['stored_unit'],PDO::PARAM_INT); + $q -> bindParam(':display',$units['display_unit'],PDO::PARAM_INT); + $q -> execute(); + + return $q -> fetchAll(PDO::FETCH_ASSOC); + +} + +function getArchive($db,$year,$month,$day,$id,$type,$param) { + + global $filter; + + $y = intval($year); + $m = intval($month); + $d = intval($day); + + $date = strval($y).'-'.strval($m).'-'.strval($d); + + $sql = " + SELECT s.id s_id,p.id p_id + FROM sensors s,sensor_types t, st_parameters p + WHERE s.st_id=t.id and p.st_id=t.id and s_id=:id and t.st_abbr=:type and p.st_name=:param ".$filter." + "; + + $q = $db -> prepare( $sql ); + $q -> bindParam(':id',$id,PDO::PARAM_STR); + $q -> bindParam(':type',$type,PDO::PARAM_STR); + $q -> bindParam(':param',$param,PDO::PARAM_STR); + $q -> execute(); + $sensor = $q -> fetch(PDO::FETCH_ASSOC); + + $sql = " + SELECT + u.id stored_unit,du.id display_unit + FROM + sensors s,sensor_types t,st_parameters p,units u,units du + WHERE s.st_id=t.id and p.st_id=t.id and p.st_unit=u.id and u.unit_group=du.unit_group and du.is_default=1 and s.id=:id and p.id=:param + "; + + $q = $db -> prepare( $sql ); + $q -> bindParam(':id',$sensor['s_id'],PDO::PARAM_INT); + $q -> bindParam(':param',$sensor['p_id'],PDO::PARAM_INT); + $q -> execute(); + $units = $q -> fetch(PDO::FETCH_ASSOC); + + $sql = " + SELECT + CONCAT(subset.t,'5:00') t,UnitConv(avg(subset.y),:stored,:display) y + FROM ( + SELECT + substr(date_format(timestamp,'%Y-%m-%dT%H:%i'),1,15) t,value y + FROM + sensor_values + WHERE + sensor_id = :id and parameter_id=:param + and timestamp>=STR_TO_DATE(:d,'%Y-%m-%d') + and timestamp prepare( $sql ); + $q -> bindParam(':id',$sensor['s_id'],PDO::PARAM_INT); + $q -> bindParam(':param',$sensor['p_id'],PDO::PARAM_INT); + $q -> bindParam(':d',$date,PDO::PARAM_STR); + $q -> bindParam(':stored',$units['stored_unit'],PDO::PARAM_INT); + $q -> bindParam(':display',$units['display_unit'],PDO::PARAM_INT); + $q -> execute(); + + return $q -> fetchAll(PDO::FETCH_ASSOC); + +} + +function getProps($db, $localNet) { + + global $filter; + + $sql = " + SELECT + CONCAT(s_id,'.',t.st_abbr,'.',p.st_name) sensor_id, + p.st_description name, + du.name_short unit, + du.prec prec, + pl.place_name, + p.st_line_color color + FROM + sensors s, sensor_types t, st_parameters p,units u,units du,places pl + WHERE + s.st_id=t.id and p.st_id=t.id and p.st_unit=u.id and u.unit_group=du.unit_group and du.is_default=1 and pl.idplaces=s.place_id ".$filter." + "; + + $q = $db -> prepare( $sql ); + $q -> execute(); + $reply = [ + "names" => [], + "colors" => [], + "units" => [], + "scale" => [], + "places" => [], + "fonts" => [ + "axes" => [ "color" => "black", "size" => 16, "style" => "normal" ], + "legend" => [ "color" => "black", "size" => 16, "style" => "normal" + ] ] ]; + + while ($row = $q -> fetch(PDO::FETCH_ASSOC)) { + $reply["names"][$row["sensor_id"]] = $row["name"]; + $reply["colors"][$row["sensor_id"]] = $row["color"]; + $reply["units"][$row["sensor_id"]] = $row["unit"]; + $reply["places"][$row["sensor_id"]] = $row["place_name"]; + $reply["scale"][$row["sensor_id"]] = [ 0 => 1.0, 1 => $row["prec"] ]; + } + + return $reply; + +} + +function getState($db, $localNet) { + + global $filter; + + $sql = " + SELECT + DISTINCT + s.s_id as sensor_id, + st.st_abbr, + v.sensor as sensor_int_id, + pl.place_name s_description, + p.id as param_id, + p.st_name as param_name, + p.st_description, + s.place_id, + u.id stored_unit_id, + du.id unit_id + FROM + sensors_ranges v,st_parameters p,sensors s,places pl,sensor_types st,units u,units du + WHERE + v.timestamp>addtime(now(), -43200) + and s.st_id=st.id + and v.sensor=s.id + and s.st_id=st.id + and v.parameter=p.id + and s.st_id=p.st_id + and p.id>=0 + and s.place_id=pl.idplaces + and p.st_unit=u.id + and u.unit_group=du.unit_group + and du.is_default=1 + ".$filter." + ORDER BY + s_description,sensor_id,param_id + "; + + $q = $db -> prepare( $sql ); + $q -> execute(); + + $reply = []; + + while ($row = $q -> fetch(PDO::FETCH_ASSOC)) { + + $sensor_id = $row['sensor_id']; + $st_id = $row['st_abbr']; + $sensor_int_id = $row['sensor_int_id']; + $param_id = $row['param_name']; + $param_int_id = $row['param_id']; + $unit_id = $row['unit_id']; + $stored_unit_id = $row['stored_unit_id']; + $place_description = $row['s_description']; + + $sql_last_val = " + SELECT UnitConv(value,".$stored_unit_id.",".$unit_id.") as val,timestamp + FROM + sensor_values + WHERE + sensor_id = ".$sensor_int_id." and parameter_id=".$param_int_id." + ORDER BY + timestamp DESC + LIMIT 1 + "; + + $qv = $db -> prepare( $sql_last_val ); + $qv -> execute(); + + $v_row = $qv -> fetch(PDO::FETCH_ASSOC); + + $value = $v_row["val"]; + $timestamp = $v_row["timestamp"]; + + if (! array_key_exists($place_description,$reply)) { + $reply[$place_description] = []; + } + + if (! array_key_exists($sensor_id,$reply[$place_description])) { + $reply[$place_description][$sensor_id] = []; + } + + if (! array_key_exists($st_id,$reply[$place_description][$sensor_id])) { + $reply[$place_description][$sensor_id][$st_id] = []; + } + + $reply[$place_description][$sensor_id][$st_id][$param_id] = $value; + $reply[$place_description][$sensor_id]['timestamp'] = $timestamp; + + } + + return $reply; + +} + +$expire = 60; + +if ($query == 'props') { + + $reply = getProps($db, $local_net); + $expire = 600; + +} elseif ($query == 'state') { + + $reply = getState($db, $local_net); + +} elseif (startsWith($query,'get/')) { + + $sensor = explode('/',substr($query,strlen('get/'))); + $reply = getCurrent($db,$sensor[0],$sensor[1],$sensor[2]); + +} elseif ($query == 'years') { + + $reply = getYears($db); + $expire = 3600; + +} elseif (startsWith($query,'months/')) { + + $date = explode('/',substr($query,strlen('months/'))); + $reply = getMonths($db,$date[0]); + $expire = 3600; + +} elseif (startsWith($query,'days/')) { + + $date = explode('/',substr($query,strlen('days/'))); + $reply = getDays($db,$date[0],$date[1]); + $expire = 3600; + +} elseif (startsWith($query,'sensors/')) { + + $date = explode('/',substr($query,strlen('sensors/'))); + $reply = getSensors($db,$date[0],$date[1],$date[2]); + $expire = 3600; + +} elseif (startsWith($query,'get-archive/')) { + + $path = explode('/',substr($query,strlen('get-archive/'))); + $reply = getArchive($db,$path[0],$path[1],$path[2],$path[3],$path[4],$path[5]); + $expire = 14400; + +} + +if ($reply) { + + $redis->set('meteo-'.$hash, serialize($reply)); + $redis->expire('meteo-'.$hash, $expire); + + print(json_encode($reply)); + +} + +?> diff --git a/web/config_local.php b/web/config_local.php index d6cce97..a4614fd 100644 --- a/web/config_local.php +++ b/web/config_local.php @@ -1,14 +1,13 @@ \ No newline at end of file +?> diff --git a/web/config_local.php~ b/web/config_local.php~ new file mode 100644 index 0000000..1ca0c18 --- /dev/null +++ b/web/config_local.php~ @@ -0,0 +1,13 @@ + diff --git a/web/graph.html b/web/graph.html new file mode 100644 index 0000000..f580ca4 --- /dev/null +++ b/web/graph.html @@ -0,0 +1,38 @@ + + + + + + + + + + + + + +
+ +
+ +
+
+
+
+ + +
+
+
+
+ +
Powered by Ubuntu 18.04
+ + + diff --git a/web/graph.js b/web/graph.js new file mode 100644 index 0000000..9ad8aa4 --- /dev/null +++ b/web/graph.js @@ -0,0 +1,143 @@ +urlbase="/meteo/cgi/" +archive_base="/meteo/archive"; + +var params = window.location.pathname.split('/').slice(-4); +var sensor_id = params[1]; +var sensor = params[2]; +var param = params[3]; + +var sensor_path = sensor_id + "." + sensor + "." + param; + +var properties; + +function processDataset(dataset,sensorid,sensorname,paramname) { + var scale = properties["scale"][sensorid+"."+sensorname+"."+paramname] + if (scale) { + var result=[]; + for (idx in dataset) { + newRec = {}; + newRec.t = dataset[idx].t + newRec.y = dataset[idx].y * scale[0]; + result.push(newRec); + } + return result; + } else { + return dataset; + } +} + +function drawGraph(graphData) { + + document.getElementById("archive").href = archive_base+"////"+sensor_id + "/" + sensor + "/" + param; + + var div = document.getElementById("chartdiv"); + var canvas = document.getElementById("chart"); + + canvas.width = div.style.width; + canvas.height = div.style.height; + + var ctx = canvas.getContext('2d'); + var color = Chart.helpers.color; + + var y_label = properties["names"][sensor_path]; + if (properties["units"][sensor_path]) { + y_label = y_label + ", " + properties["units"][sensor_path]; + } + + var cfg = { + type: 'bar', + data: { + datasets: [ + { + label: properties["places"][sensor_path] + ' - ' + properties["names"][sensor_path], + backgroundColor: color(properties["colors"][sensor_path]).alpha(0.5).rgbString(), + borderColor: properties["colors"][sensor_path], + data: processDataset(graphData,sensor_id,sensor,param), + type: 'line', + pointRadius: 0, + fill: true, + borderWidth: 2 + } + ] + }, + options: { + animation: { + duration: 0, + }, + hover: { + animationDuration: 0, + }, + responsiveAnimationDuration: 0, + legend: { + labels: { + fontColor: properties["fonts"]["legend"]["color"], + fontSize: properties["fonts"]["legend"]["size"], + fontStyle: properties["fonts"]["legend"]["style"], + } + }, + scales: { + xAxes: [{ + type: 'time', + distribution: 'series', + scaleLabel: { + fontColor: properties["fonts"]["axes"]["color"], + fontSize: properties["fonts"]["axes"]["size"], + fontStyle: properties["fonts"]["axes"]["style"], + } + }], + yAxes: [{ + scaleLabel: { + display: true, + labelString: y_label, + fontColor: properties["fonts"]["axes"]["color"], + fontSize: properties["fonts"]["axes"]["size"], + fontStyle: properties["fonts"]["axes"]["style"], + } + }] + } + } + } + var chart = new Chart(ctx, cfg); +} + +function RefreshGraph() { + + var req = new XMLHttpRequest(); + + req.onreadystatechange = function () { + if (this.readyState != 4) return; + if (this.status != 200) { + setTimeout(RefreshGraph,60000); + return; + } + var graphData = JSON.parse(this.responseText); + drawGraph(graphData); + setTimeout(RefreshGraph,60000) + }; + + req.open("GET", urlbase+"get/"+sensor_id+"/"+sensor+"/"+param, true); + req.withCredentials = true; + req.send(); + +} + +function GetProperties() { + var req = new XMLHttpRequest(); + + req.onreadystatechange = function () { + if (this.readyState != 4) return; + if (this.status != 200) { + setTimeout(GetProperties,30000); + return; + } + properties = JSON.parse(this.responseText); + setTimeout(RefreshGraph,100) + }; + + req.open("GET", urlbase+"props", true); + req.withCredentials = true; + req.send(); + +} + +setTimeout(GetProperties,100) diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..0fec812 --- /dev/null +++ b/web/index.html @@ -0,0 +1,53 @@ + + + + + + + + + + + + +
+
+ +
+
+ + + + + + +
+ +
+ +
+ +
+ +
+ +
+ +
Powered by Ubuntu 18.04
+ + + diff --git a/web/meteo.css b/web/meteo.css new file mode 100644 index 0000000..2fcb18b --- /dev/null +++ b/web/meteo.css @@ -0,0 +1,220 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-size: 0.8rem; + background-color: #EEE; +} + +html, body { + margin: 0px; + padding: 0px; + height: 100%; + font-family: Microsoft Yahei, WenQuanYi Micro Hei, sans-serif, "Helvetica Neue", Helvetica, Hiragino Sans GB; +} + +header { + background: darkred; + color: white; +} + +header, .main { + width: 100%; + position: absolute; +} + +header { + height: 5rem; + box-shadow: 0 2px 5px rgba(0, 0, 0, .26); + transition: box-shadow .2s; + float: left; + position: fixed; + top: 0px; + z-index: 2000; +} + +header > .fill > .container { + padding-top: 0.25rem; + padding-right: 1rem; + padding-bottom: 0.25rem; + display: flex; + align-items: center; + height: 5rem; +} + +header > .fill > .container > img{ + max-height: 4.5rem; + margin: 0.5rem; + padding: 0.5rem; +} + +header > .fill > .container > .brand { + font-size: 1.2rem; + color: white; + text-decoration: none; + margin-left: 1rem; +} + +.main { + top: 4rem; + bottom: 0rem; + position: relative; + height: calc(100% - 5rem); +} + +.main > #maincontent { + background-color: #EEE; + height: calc(100% - 5rem); +} + +#maincontent > .container { + margin: 1rem 1rem 1rem 1rem; + padding-top: 1px; + height: 100%; +} + +.section { + margin: 0.5rem; + padding: 1rem; + border: 0; + font-weight: normal; + font-style: normal; + line-height: 1; + font-family: inherit; + float: left; + min-width: inherit; + border-radius: 0; + color: #404040; + background-color: #FFF; + box-shadow: 0 2px 2px 0 rgba(0, 0, 0, .16), 0 0 2px 0 rgba(0, 0, 0, .12); + -webkit-overflow-scrolling: touch; +} + +.wide-section { + margin: 0.5rem; + padding: 1rem; + border: 0; + font-weight: normal; + font-style: normal; + line-height: 1; + font-family: inherit; + float: left; + min-width: calc(100% - 2rem); + border-radius: 0; + color: #404040; + background-color: #FFF; + box-shadow: 0 2px 2px 0 rgba(0, 0, 0, .16), 0 0 2px 0 rgba(0, 0, 0, .12); + -webkit-overflow-scrolling: touch; +} + +.divider { + margin: 0.5rem; + padding: 1rem; + border: 0; + font-weight: normal; + font-style: bold; + line-height: 1; + font-family: inherit; + font-size: 1.5rem; + float: left; + min-width: calc(100% - 2rem); + border-radius: 0; + color: #404040; + -webkit-overflow-scrolling: touch; +} + +.large-section { + margin: 0.5rem; + padding: 1rem; + border: 0; + font-weight: normal; + font-style: normal; + line-height: 1; + font-family: inherit; + min-width: calc(100% - 2rem); + border-radius: 0; + height: 100%; + background-color: #FFF; + box-shadow: 0 2px 2px 0 rgba(0, 0, 0, .16), 0 0 2px 0 rgba(0, 0, 0, .12); + -webkit-overflow-scrolling: touch; +} + +.bottom-section { + margin: 0.5rem; + padding: 2rem; + border: 0; + font-weight: normal; + font-style: normal; + line-height: 1; + font-family: inherit; + float: left; + min-width: calc(100% - 2rem); + border-radius: 0; + height: 80%; + background-color: #FFF; + box-shadow: 0 2px 2px 0 rgba(0, 0, 0, .16), 0 0 2px 0 rgba(0, 0, 0, .12); + -webkit-overflow-scrolling: touch; +} + +footer { + text-align: right; + padding: 1rem; + color: #aaa; + font-size: 0.8rem; + text-shadow: 0px 0px 2px #BBB; + position: fixed; + left: 0px; + bottom: 0px; + width: 100%; +} + +footer > a { + color: #aaa; + text-decoration: none; +} + +.reference { + padding: 1rem 1rem; + text-decoration: bold; + float: left; + font-size: 4rem; +} + +.reference-unit { + padding: 0.5rem 1rem; + text-decoration: bold; + float: left; + font-size: 3rem; +} + +.section:hover { + color: white; + background: darkred; +} + +.reference-header { + padding: 0.5rem 0.2rem; + text-decoration: none; + font-size: 1.5rem; + text-align: left; +} + +.selector-header { + padding: 1rem 1rem; + text-decoration: bold; + float: left; + font-size: 1.5rem; +} + +.selector { + padding: 1rem 1rem; + text-decoration: bold; + float: left; +} + +option { + font-size: 1.2rem; +} diff --git a/web/meteo.js b/web/meteo.js new file mode 100644 index 0000000..ee8947e --- /dev/null +++ b/web/meteo.js @@ -0,0 +1,130 @@ +urlbase="/meteo/cgi/" + +currentState="" + +function getCookie(name) { + var matches = document.cookie.match(new RegExp( + "(?:^|; )" + name.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, '\\$1') + "=([^;]*)" + )); + return matches ? decodeURIComponent(matches[1]) : undefined; +} + +function RefreshPageState() { + + var req = new XMLHttpRequest(); + + req.onreadystatechange = function () { + + if (this.readyState != 4) return; + if (this.status == 200) { + + var returnedData = JSON.parse(this.responseText); + + var template = document.getElementById("template").innerHTML; + var divider_template = document.getElementById("divider-template").innerHTML; + var value_color = document.getElementById("value").style.color; + + var html = ""; + + for (var place in returnedData) { + var divider = divider_template.replace(/\$PLACE/g,place); + html = html + divider; + for (var sensor_id in returnedData[place]) { + var timestamp = returnedData[place][sensor_id]["timestamp"]; + for (var sensor in returnedData[place][sensor_id]) { + if (sensor != "timestamp") { + for (var param in returnedData[place][sensor_id][sensor]) { + sensor_path = sensor_id+"."+sensor+"."+param; + name = properties["names"][sensor_path]; + if (! name.startsWith("-")) { + value = returnedData[place][sensor_id][sensor][param]; + if (! name) { name = sensor_path; } + units = properties["units"][sensor_path]; + scale = properties["scale"][sensor_path]; + color = properties["colors"][sensor_path]; + if (scale) { + value = (scale[0] * value).toFixed(scale[1]); + } + if (! color) { + color = value_color; + } + var section = template.replace(/\$SENSOR_ID/g,sensor_id); + section = section.replace(/\$SENSOR/g,sensor); + section = section.replace(/\$PARAM/g,param); + section = section.replace(/\$NAME/g,name); + section = section.replace(/\$UNITS/g,units); + section = section.replace(/\$VALUE/g,value); + section = section.replace(/\$COLOR/g,color); + section = section.replace(/\$TIMESTAMP/g,timestamp); + html = html + section; + } + } + } + } + } + } + + document.getElementById("meteo").innerHTML = html; + + setTimeout(RefreshPageState,60000) + } else { + setTimeout(RefreshPageState,60000) + } + }; + + req.open("GET", urlbase+"state", true); + req.withCredentials = true; + req.send(); + +} + +function GetProperties() { + var req = new XMLHttpRequest(); + + req.onreadystatechange = function () { + if (this.readyState != 4) return; + if (this.status != 200) { + setTimeout(GetProperties,30000); + return; + } + properties = JSON.parse(this.responseText); + RefreshPageState(); + }; + + req.open("GET", urlbase+"props", true); + req.withCredentials = true; + req.send(); + +} + +function GetAuth() { + var req = new XMLHttpRequest(); + + req.onreadystatechange = function () { + if (this.readyState != 4) return; + if (this.status != 200) { + setTimeout(GetAuth,30000); + return; + } + location.reload(); + }; + + req.open("GET", "auth", true); + req.withCredentials = true; + req.send(); + +} + +function CheckCookie() { + + if (getCookie("auth-token")) { + + authDiv = document.getElementById("auth"); + authDiv.innerHTML = ""; + + } + +} + +setTimeout(GetProperties,100); +setTimeout(CheckCookie,200); diff --git a/web/send.php b/web/send.php index 60d21db..86286ef 100644 --- a/web/send.php +++ b/web/send.php @@ -1,28 +1,57 @@ $valid_ip_end || $valid_ip_start > $client_ip) { + $client_ip = $_SERVER["REMOTE_ADDR"]; + + if ((strpos($client_ip, "192.168.") === 0) || (strpos($client_ip, "10.8.") === 0) || (strpos($client_ip, "2001:470:6f:9d5:") === 0)) { + + + $local_net = True; + + } else { + + $local_net = False; + + } + + + + if (! $local_net) { header('HTTP/1.1 403 Forbidden'); echo "IP not in allowed range"; exit; } + $stype = $_REQUEST['stype']; $sid = $_REQUEST['sid']; $param = $_REQUEST['param']; $value = $_REQUEST['value']; + if (isset($_REQUEST['time'])) { + + $timestamp = "'".$_REQUEST['time']."'"; + + } else { + + $timestamp = 'NULL'; + + } + + + $connection = new mysqli($mysql_host, $mysql_user, $mysql_pwd, $mysql_schema, $mysql_port); if ($connection->connect_errno) { header('HTTP/1.1 500 Internal Server Error'); exit; } - $str = "CALL meteo.submit_value('".$stype."','".$sid."','".$param."',".$value.",NULL)"; + $str = "CALL meteo.submit_value('".$stype."','".$sid."','".$param."',".$value.",".$timestamp.")"; if (!$connection->query($str)) { header('HTTP/1.1 500 Internal Server Error'); + echo "$str\n"; + echo "Call Failed\n"; exit; } else { $connection->commit(); diff --git a/web/weather.svg b/web/weather.svg new file mode 100644 index 0000000..dc49122 --- /dev/null +++ b/web/weather.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +