From 68fb49737e31039b68618220744b7d099a3f783c Mon Sep 17 00:00:00 2001 From: e3klund Date: Sun, 11 Oct 2020 17:42:58 -0400 Subject: [PATCH 1/4] mvp of new site --- site/hazebotdev/haze.py | 31 ++++++ site/hazebotdev/requirements.txt | 34 ++++++ site/hazebotdev/static/map.js | 83 +++++++++++++++ site/hazebotdev/static/mapbox-icon.png | Bin 0 -> 3091 bytes site/hazebotdev/static/style.css | 142 +++++++++++++++++++++++++ site/hazebotdev/templates/base.html | 50 +++++++++ site/hazebotdev/templates/index.html | 75 +++++++++++++ 7 files changed, 415 insertions(+) create mode 100644 site/hazebotdev/haze.py create mode 100644 site/hazebotdev/requirements.txt create mode 100644 site/hazebotdev/static/map.js create mode 100644 site/hazebotdev/static/mapbox-icon.png create mode 100644 site/hazebotdev/static/style.css create mode 100644 site/hazebotdev/templates/base.html create mode 100644 site/hazebotdev/templates/index.html diff --git a/site/hazebotdev/haze.py b/site/hazebotdev/haze.py new file mode 100644 index 0000000..bb8afca --- /dev/null +++ b/site/hazebotdev/haze.py @@ -0,0 +1,31 @@ +import os + +from flask import Flask, render_template + +app = Flask(__name__) + +# autoreload of changes to template files +from os import path, walk +extra_dirs = ['templates','static'] +extra_files = extra_dirs[:] +for extra_dir in extra_dirs: + for dirname, dirs, files in walk(extra_dir): + for filename in files: + filename = path.join(dirname, filename) + if path.isfile(filename): + extra_files.append(filename) + +@app.route('/hello') +def hello(): + return 'Hello, World!' + +@app.route('/') +def index(): + return render_template('/index.html') + +@app.route('/base') +def base(): + return render_template('/base.html') + +if __name__ == "__main__": + app.run(debug=True, extra_files=extra_files) \ No newline at end of file diff --git a/site/hazebotdev/requirements.txt b/site/hazebotdev/requirements.txt new file mode 100644 index 0000000..5391739 --- /dev/null +++ b/site/hazebotdev/requirements.txt @@ -0,0 +1,34 @@ +appdirs==1.4.4 +attrs==20.2.0 +black==19.10b0 +cachetools==4.1.1 +certifi==2020.6.20 +chardet==3.0.4 +click==7.1.2 +Flask==1.1.2 +google-api-core==1.22.2 +google-auth==1.21.2 +google-cloud-core==1.4.1 +googleapis-common-protos==1.52.0 +grpcio==1.32.0 +idna==2.10 +itsdangerous==1.1.0 +Jinja2==2.11.2 +MarkupSafe==1.1.1 +mypy==0.782 +mypy-extensions==0.4.3 +pathspec==0.8.0 +protobuf==3.13.0 +pyasn1==0.4.8 +pyasn1-modules==0.2.8 +pytz==2020.1 +regex==2020.7.14 +requests==2.24.0 +rsa==4.6 +six==1.15.0 +toml==0.10.1 +typed-ast==1.4.1 +typing-extensions==3.7.4.3 +urllib3==1.25.10 +websocket-client==0.57.0 +Werkzeug==1.0.1 diff --git a/site/hazebotdev/static/map.js b/site/hazebotdev/static/map.js new file mode 100644 index 0000000..0188504 --- /dev/null +++ b/site/hazebotdev/static/map.js @@ -0,0 +1,83 @@ +mapboxgl.accessToken = 'pk.eyJ1IjoiZTNrbHVuZCIsImEiOiJja2c1bDJ5ZW8wYnp4MnNuenRwc3diZ2w4In0.7pXGkCpKkhFHDm3TMwjgAg'; +var map = new mapboxgl.Map({ + container: 'map', + style: 'mapbox://styles/mapbox/streets-v11', + center: [-122.25948, 37.87221], + zoom: 10 +}); + +map.addControl( + new MapboxGeocoder({ + accessToken: mapboxgl.accessToken, + render: function (item) { + return ( + "
" + "" + item.place_name + '
' + ); + }, + mapboxgl: mapboxgl, + placeholder: 'Enter a zipcode' + }) +); + +// After the map style has loaded on the page, +// add a source layer and default styling for a single point +map.on('load', function() { +map.addSource('single-point', { + type: 'geojson', + data: { + type: 'FeatureCollection', + features: [] + } +}); + +map.addLayer({ + id: 'point', + source: 'single-point', + type: 'circle', + paint: { + 'circle-radius': 9, + 'circle-color': '#448ee4' + } +}); + +}); + +// static lat/long for the markers in development +var geojson = { + type: 'FeatureCollection', + features: [{ + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [-77.032, 38.913] + }, + properties: { + title: 'Mapbox', + description: 'Washington, D.C.' + } + }, + { + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [-122.414, 37.776] + }, + properties: { + title: 'Mapbox', + description: 'San Francisco, California' + } + }] + }; + +// add markers to map +geojson.features.forEach(function(marker) { + + // create a HTML element for each feature + var el = document.createElement('div'); + el.className = 'marker'; + + // make a marker for each feature and add to the map + new mapboxgl.Marker(el) + .setLngLat(marker.geometry.coordinates) + .addTo(map); + }); \ No newline at end of file diff --git a/site/hazebotdev/static/mapbox-icon.png b/site/hazebotdev/static/mapbox-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..46a61a3796a0e9d61e8c44ff8f3e46d88e664447 GIT binary patch literal 3091 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 literal 0 HcmV?d00001 diff --git a/site/hazebotdev/static/style.css b/site/hazebotdev/static/style.css new file mode 100644 index 0000000..55da5fc --- /dev/null +++ b/site/hazebotdev/static/style.css @@ -0,0 +1,142 @@ +html * { + font-family: 'Montserrat', sans-serif; + font-size: 62.5%; +} + +/* Navbar */ +.navbar { + background-color: white; + border-bottom: solid; +} + +.navbar-brand { + font-size: 2rem !important; +} + +.nav-item { + padding-left: 0.5rem; + font-size: 1.6rem; +} + +/* Main page */ +.section { + font-size: 1.6rem; + text-align: center; + margin: 1rem; + padding-bottom: 1rem; +} + +.section-title { + font-size: 2rem; +} + +a.section-title { + text-decoration: none !important; + color: inherit !important; +} + +.section-text { + font-size: 1rem; +} + +.section-list { + text-align: left; + font-size: 1rem; +} + +.intro-list { + padding-inline-start: 0px; +} + +.intro-text { + display: block; + list-style: none; + font-size: 2rem; +} + +.sms-button { + margin-top: 1rem; + margin-bottom: 2rem; + font-size: 1.6rem; +} + +/* how-to map */ +#map { + width: 400px; + height: 300px; + margin: 0.5rem; +} + +.geocoder-dropdown-text { + font-size: 1rem; +} + +.marker { + background-image: url('mapbox-icon.png'); + background-size: cover; + width: 50px; + height: 50px; + border-radius: 50%; + cursor: pointer; + } + +#footer { + position: fixed; + left: 0; + bottom: 0; + width: 100%; +} + +@media (min-width: 576px) { + html { + font-size: 62.5%; + } + + .nav-item { + visibility: hidden; + } + + .section { + border-bottom: none; + } + + .section-title { + font-size: 3rem; + } + + .section-text { + font-size: 1.6rem; + } + + .section-list { + font-size: 1.6rem; + } + + .intro-text { + font-size: 3rem; + } + + .sms-button { + font-size: 2rem !important; + } + + #map { + width: 500px; + height: 400px; + } +} + +/* Medium devices (tablets, 768px and up) */ +@media (min-width: 768px) { + +} + +/* Large devices (desktops, 992px and up) */ +@media (min-width: 992px) { + +} + +/* Extra large devices (large desktops, 1200px and up) */ +@media (min-width: 1200px) { + +} \ No newline at end of file diff --git a/site/hazebotdev/templates/base.html b/site/hazebotdev/templates/base.html new file mode 100644 index 0000000..5b0c4d0 --- /dev/null +++ b/site/hazebotdev/templates/base.html @@ -0,0 +1,50 @@ + + + + {% block head %} + {% block title %}{% endblock %}Hazebot + + + + + + + + {% endblock %} + + + +
+ {% block content %}{% endblock %} +
+ + + \ No newline at end of file diff --git a/site/hazebotdev/templates/index.html b/site/hazebotdev/templates/index.html new file mode 100644 index 0000000..3c97aaa --- /dev/null +++ b/site/hazebotdev/templates/index.html @@ -0,0 +1,75 @@ +{% extends 'base.html' %} +{% block header %} +

{% block title %}{% endblock %}

+{% endblock %} + +{% block content %} + +
+
+
+
+
    +
  • Actionable and
  • +
  • Accessible
  • +
  • Local Air
  • +
  • Quality
  • +
  • Alerts
  • +
+ Sign up for some fresh air +
+ Text your zipcode to recieve actionable updates about the air quality near you +
+
+ +
+
+

How it works

+
+
+
+
+
+
+
+

Mission

+
+ We believe there should be a 211 for environmental events. + This is Hazebot's purpose. To be your text-based companion + for air quality and other environmental disasters.

+ + Our goal is to make this service available to all + regardless of phone type, internet access, or ability. + Just text a zip code to (262) 747-2332 to receive: Local AQI Details; Safe places nearby; Alerting when conditions change
+ +
+
+
+

Who we are

+
+ Ian Hoffman and Will Danforth launched hazebot during the country's worst wildfire season on record. They both live in Oakland and believe that everyone, especially those most vulnerable and under-resourced, should have access to information about their health and how to protect themselves.

+ Please email us at info@hazebot.org or check out our github repo if you'd like to get involved. +
+
+
+
+ + + + + + + + + + + + + + + + {% endblock %} \ No newline at end of file From 87b1eff3b9fc5b4999bcc978770872f1770aee2b Mon Sep 17 00:00:00 2001 From: e3klund Date: Sat, 17 Oct 2020 15:45:49 -0400 Subject: [PATCH 2/4] routing and template changes complete --- site/hazebotdev/static/style.css | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/site/hazebotdev/static/style.css b/site/hazebotdev/static/style.css index 55da5fc..89a8ce5 100644 --- a/site/hazebotdev/static/style.css +++ b/site/hazebotdev/static/style.css @@ -126,17 +126,4 @@ a.section-title { } } -/* Medium devices (tablets, 768px and up) */ -@media (min-width: 768px) { - -} - -/* Large devices (desktops, 992px and up) */ -@media (min-width: 992px) { - -} - -/* Extra large devices (large desktops, 1200px and up) */ -@media (min-width: 1200px) { - } \ No newline at end of file From db9df85f8d3c11c23f13b4489118e362baeb6c64 Mon Sep 17 00:00:00 2001 From: e3klund Date: Sat, 17 Oct 2020 15:56:39 -0400 Subject: [PATCH 3/4] more updates to templates and routes --- app/airq/__init__.py | 2 + app/airq/controllers/admin.py | 10 +- app/airq/controllers/home.py | 14 ++ app/airq/static/home.css | 127 ++++++++++++++ app/airq/static/map.js | 77 ++++++++ app/airq/static/mapbox-icon.png | Bin 0 -> 3091 bytes app/airq/templates/{ => admin}/admin.html | 2 +- app/airq/templates/{ => admin}/base.html | 0 app/airq/templates/{ => admin}/bulk_sms.html | 2 +- .../templates/{ => admin}/bulk_upload.html | 2 +- app/airq/templates/{ => admin}/login.html | 2 +- app/airq/templates/{ => admin}/sms.html | 2 +- app/airq/templates/home.html | 165 ++++++++++++++++++ app/airq/templates/home_base.html | 50 ++++++ 14 files changed, 445 insertions(+), 10 deletions(-) create mode 100644 app/airq/controllers/home.py create mode 100644 app/airq/static/home.css create mode 100644 app/airq/static/map.js create mode 100644 app/airq/static/mapbox-icon.png rename app/airq/templates/{ => admin}/admin.html (98%) rename app/airq/templates/{ => admin}/base.html (100%) rename app/airq/templates/{ => admin}/bulk_sms.html (99%) rename app/airq/templates/{ => admin}/bulk_upload.html (87%) rename app/airq/templates/{ => admin}/login.html (83%) rename app/airq/templates/{ => admin}/sms.html (83%) create mode 100644 app/airq/templates/home.html create mode 100644 app/airq/templates/home_base.html diff --git a/app/airq/__init__.py b/app/airq/__init__.py index 8ed28d9..4ffdf6a 100644 --- a/app/airq/__init__.py +++ b/app/airq/__init__.py @@ -3,6 +3,7 @@ from airq.controllers import admin from airq.controllers import api +from airq.controllers import home from airq import management @@ -16,6 +17,7 @@ # TODO: Change '/' to be the admin page, # and repoint healthcheck /healthcheck app.route("/", methods=["GET"])(api.healthcheck) +app.route("/home", methods=["GET"])(home.home) app.route("/healthcheck", methods=["GET"])(api.healthcheck) app.route("/test/", methods=["GET"])(api.test_command) app.route("/sms/", methods=["POST"])(api.sms_reply) diff --git a/app/airq/controllers/admin.py b/app/airq/controllers/admin.py index a3d9e60..f079c25 100644 --- a/app/airq/controllers/admin.py +++ b/app/airq/controllers/admin.py @@ -42,7 +42,7 @@ def login() -> typing.Union[Response, str]: return redirect(url_for("login")) login_user(user, remember=True) return redirect(url_for("admin_summary")) - return render_template("login.html", title="Sign In", form=form) + return render_template("admin/login.html", title="Sign In", form=form) @login_required @@ -54,7 +54,7 @@ def logout() -> Response: @admin_required def admin_summary() -> str: return render_template( - "admin.html", + "admin/admin.html", title="Admin", summary={ "Total Alerts Sent": Client.query.get_total_num_sends(), @@ -86,7 +86,7 @@ def admin_bulk_sms(): flash("Sent!") return redirect(url_for("admin_summary")) return render_template( - "bulk_sms.html", + "admin/bulk_sms.html", form=form, num_inactive=Client.query.filter_inactive_since(timestamp()).count(), ) @@ -102,7 +102,7 @@ def admin_sms(): client.send_message(form.data["message"]) flash("Sent!") return redirect(url_for("admin_summary")) - return render_template("sms.html", form=form) + return render_template("admin/sms.html", form=form) @admin_required @@ -156,4 +156,4 @@ def upload_users(): return redirect(url_for("upload_users")) - return render_template("bulk_upload.html", form=form) + return render_template("admin/bulk_upload.html", form=form) diff --git a/app/airq/controllers/home.py b/app/airq/controllers/home.py new file mode 100644 index 0000000..3c42eb6 --- /dev/null +++ b/app/airq/controllers/home.py @@ -0,0 +1,14 @@ +import json +from flask import render_template + +from airq.models.sensors import Sensor + +def home() -> str: + points = { + sensor_id: { + "latitude": latitude, + "longitude": longitude, + } for sensor_id, latitude, longitude + in Sensor.query.with_entities(Sensor.id, Sensor.latitude, Sensor.longitude).all() + } + return render_template("home.html", points=json.dumps(points)) \ No newline at end of file diff --git a/app/airq/static/home.css b/app/airq/static/home.css new file mode 100644 index 0000000..ec5ef73 --- /dev/null +++ b/app/airq/static/home.css @@ -0,0 +1,127 @@ +html * { + font-family: 'Montserrat', sans-serif; + font-size: 62.5%; +} + +/* Navbar */ +.navbar { + background-color: white; + border-bottom: solid; +} + +.navbar-brand { + font-size: 2rem !important; +} + +.nav-item { + padding-left: 0.5rem; + font-size: 1.6rem; +} + +/* Main page */ +.section { + font-size: 1.6rem; + text-align: center; + margin: 1rem; + padding-bottom: 1rem; +} + +.section-title { + font-size: 2rem; +} + +a.section-title { + text-decoration: none !important; + color: inherit !important; +} + +.section-text { + font-size: 1rem; +} + +.section-list { + text-align: left; + font-size: 1rem; +} + +.intro-list { + padding-inline-start: 0px; +} + +.intro-text { + display: block; + list-style: none; + font-size: 2rem; +} + +.sms-button { + margin-top: 1rem; + margin-bottom: 2rem; + font-size: 1.6rem; +} + +/* how-to map */ +#map { + width: 400px; + height: 300px; + margin: 0.5rem; +} + +.geocoder-dropdown-text { + font-size: 1rem; +} + +.marker { + background-image: url('mapbox-icon.png'); + background-size: cover; + width: 50px; + height: 50px; + border-radius: 50%; + cursor: pointer; + } + +#footer { + position: fixed; + left: 0; + bottom: 0; + width: 100%; +} + +@media (min-width: 576px) { + html { + font-size: 62.5%; + } + + .nav-item { + visibility: hidden; + } + + .section { + border-bottom: none; + } + + .section-title { + font-size: 3rem; + } + + .section-text { + font-size: 1.6rem; + } + + .section-list { + font-size: 1.6rem; + } + + .intro-text { + font-size: 3rem; + } + + .sms-button { + font-size: 2rem !important; + } + + #map { + width: 500px; + height: 400px; + } +} \ No newline at end of file diff --git a/app/airq/static/map.js b/app/airq/static/map.js new file mode 100644 index 0000000..95681a4 --- /dev/null +++ b/app/airq/static/map.js @@ -0,0 +1,77 @@ +mapboxgl.accessToken = 'pk.eyJ1IjoiZTNrbHVuZCIsImEiOiJja2c1bDJ5ZW8wYnp4MnNuenRwc3diZ2w4In0.7pXGkCpKkhFHDm3TMwjgAg'; +var map = new mapboxgl.Map({ + container: 'map', + style: 'mapbox://styles/mapbox/streets-v11', + center: [-122.25948, 37.87221], + zoom: 10 +}); + +map.addControl( + new MapboxGeocoder({ + accessToken: mapboxgl.accessToken, + render: function (item) { + return ( + "
" + "" + item.place_name + '
' + ); + }, + mapboxgl: mapboxgl, + placeholder: 'Enter a zipcode' + }) +); + +// After the map style has loaded on the page, +// add a source layer and default styling for a single point +map.on('load', function() { + map.addSource('single-point', { + type: 'geojson', + data: { + type: 'FeatureCollection', + features: [] + } + }); + + map.addLayer({ + id: 'point', + source: 'single-point', + type: 'circle', + paint: { + 'circle-radius': 9, + 'circle-color': '#448ee4' + } + }); + +}); +// const sensors = {{points|tojson}}; +// console.log(sensors); + +// static lat/long for the markers in development +var geojson = { + type: 'FeatureCollection', + features: [{ + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [-77.032, 38.913] + }, + }, + { + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [-122.414, 37.776] + }, + }] +}; + +// add markers to map +geojson.features.forEach(function(marker) { + + // create a HTML element for each feature + var el = document.createElement('div'); + el.className = 'marker'; + + // make a marker for each feature and add to the map + new mapboxgl.Marker(el) + .setLngLat(marker.geometry.coordinates) + .addTo(map); +}); diff --git a/app/airq/static/mapbox-icon.png b/app/airq/static/mapbox-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..46a61a3796a0e9d61e8c44ff8f3e46d88e664447 GIT binary patch literal 3091 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 literal 0 HcmV?d00001 diff --git a/app/airq/templates/admin.html b/app/airq/templates/admin/admin.html similarity index 98% rename from app/airq/templates/admin.html rename to app/airq/templates/admin/admin.html index b12b0d8..c4483e7 100644 --- a/app/airq/templates/admin.html +++ b/app/airq/templates/admin/admin.html @@ -1,4 +1,4 @@ -{% extends 'base.html' %} +{% extends 'admin/base.html' %} {% block header %}

{% block title %}Stats{% endblock %}

diff --git a/app/airq/templates/base.html b/app/airq/templates/admin/base.html similarity index 100% rename from app/airq/templates/base.html rename to app/airq/templates/admin/base.html diff --git a/app/airq/templates/bulk_sms.html b/app/airq/templates/admin/bulk_sms.html similarity index 99% rename from app/airq/templates/bulk_sms.html rename to app/airq/templates/admin/bulk_sms.html index d1facb2..9a33fc6 100644 --- a/app/airq/templates/bulk_sms.html +++ b/app/airq/templates/admin/bulk_sms.html @@ -1,4 +1,4 @@ -{% extends 'base.html' %} +{% extends 'admin/base.html' %} {% block head %}