Compare commits
6 Commits
0641b8bf20
...
a1aeb97e41
Author | SHA1 | Date |
---|---|---|
Simon | a1aeb97e41 | |
Simon | b0e21de7ad | |
Simon | 1040e38b22 | |
Simon | 15a1340e15 | |
Simon | 6fa62cb79e | |
Simon | cebb5a3bcd |
|
@ -1,6 +1,7 @@
|
||||||
# python stuff
|
# python stuff
|
||||||
__pycache__
|
__pycache__
|
||||||
.vscode
|
.vscode
|
||||||
|
.venv
|
||||||
|
|
||||||
# protect real config files
|
# protect real config files
|
||||||
config.json
|
config.json
|
||||||
|
@ -10,8 +11,5 @@ config.h
|
||||||
postgres.env
|
postgres.env
|
||||||
umami.env
|
umami.env
|
||||||
|
|
||||||
# frontend font files
|
|
||||||
*.ttf
|
|
||||||
|
|
||||||
# dynamic files
|
# dynamic files
|
||||||
**/dyn/
|
**/dyn/
|
||||||
|
|
17
deploy.sh
17
deploy.sh
|
@ -39,27 +39,12 @@ function sync_test {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# sync prod db to testing
|
|
||||||
function sync_db {
|
|
||||||
|
|
||||||
newest_backup=$(ssh $remote_host 'find backup -type f -name "pg_*.gz" | sort | tail -n 1')
|
|
||||||
rsync --progress -e ssh $remote_host:"$newest_backup" .
|
|
||||||
rsync --progress -e ssh "$(basename "$newest_backup")" $local_host:backup/backup.gz
|
|
||||||
|
|
||||||
ssh $local_host "gzip -d backup/backup.gz"
|
|
||||||
ssh $local_host "docker exec -i postgres psql -U aqi -d aqi < backup/backup"
|
|
||||||
ssh $local_host "docker compose -f docker/docker-compose.yml restart"
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if [[ $1 == "test" ]]; then
|
if [[ $1 == "test" ]]; then
|
||||||
sync_test "$2"
|
sync_test "$2"
|
||||||
elif [[ $1 == "docker" ]]; then
|
elif [[ $1 == "docker" ]]; then
|
||||||
sync_docker
|
sync_docker
|
||||||
elif [[ $1 == "db" ]]; then
|
|
||||||
sync_db
|
|
||||||
else
|
else
|
||||||
echo "valid options are: test | docker | db"
|
echo "valid options are: test | docker"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
##
|
##
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
APScheduler==3.10.1
|
APScheduler==3.10.4
|
||||||
Flask==2.3.2
|
Flask==3.0.0
|
||||||
Flask_HTTPAuth==4.8.0
|
Flask_HTTPAuth==4.8.0
|
||||||
Flask_Table==0.5.0
|
ipython==8.19.0
|
||||||
ipython==8.13.2
|
matplotlib==3.8.2
|
||||||
matplotlib==3.7.1
|
numpy==1.26.3
|
||||||
numpy==1.24.3
|
pandas==2.1.4
|
||||||
pandas==2.0.1
|
psycopg2-binary==2.9.9
|
||||||
psycopg2-binary==2.9.6
|
requests==2.31.0
|
||||||
requests==2.30.0
|
scipy==1.11.4
|
||||||
scipy==1.10.1
|
uWSGI==2.0.23
|
||||||
uWSGI==2.0.21
|
|
||||||
|
|
|
@ -89,7 +89,7 @@ class CurrentPlot:
|
||||||
# calc ticks
|
# calc ticks
|
||||||
y_max = np.ceil(y.max()/50)*50 + 50
|
y_max = np.ceil(y.max()/50)*50 + 50
|
||||||
# setup plot
|
# setup plot
|
||||||
plt.style.use('seaborn')
|
plt.style.use('seaborn-v0_8')
|
||||||
plt.plot(x, y, color='#313131',)
|
plt.plot(x, y, color='#313131',)
|
||||||
# fill colors
|
# fill colors
|
||||||
plt_fill(plt, x, y)
|
plt_fill(plt, x, y)
|
||||||
|
|
|
@ -183,7 +183,7 @@ class MonthGenerator():
|
||||||
x_dates = [f'{str(i).zfill(2)} {month_short}' for i in x_numbers]
|
x_dates = [f'{str(i).zfill(2)} {month_short}' for i in x_numbers]
|
||||||
x_ticks = x_range, x_dates
|
x_ticks = x_range, x_dates
|
||||||
# plot
|
# plot
|
||||||
plt.style.use('seaborn')
|
plt.style.use('seaborn-v0_8')
|
||||||
plt.plot(x, y_1, color='#313131', label='this year')
|
plt.plot(x, y_1, color='#313131', label='this year')
|
||||||
plt.plot(
|
plt.plot(
|
||||||
x, y_2, color='#666666', linestyle='dashed', label='last year'
|
x, y_2, color='#666666', linestyle='dashed', label='last year'
|
||||||
|
|
|
@ -192,7 +192,7 @@ class LastSevenDays:
|
||||||
x_ticks = self.axis['x_ticks']
|
x_ticks = self.axis['x_ticks']
|
||||||
y_max = np.ceil(max(pd.concat([y_1, y_2]))/50)*50 + 50
|
y_max = np.ceil(max(pd.concat([y_1, y_2]))/50)*50 + 50
|
||||||
# plot
|
# plot
|
||||||
plt.style.use('seaborn')
|
plt.style.use('seaborn-v0_8')
|
||||||
plt.plot(x, y_1, color='#313131', label='2hour avg')
|
plt.plot(x, y_1, color='#313131', label='2hour avg')
|
||||||
plt.plot(x, y_2, color='#cc0000', label='daily avg')
|
plt.plot(x, y_2, color='#cc0000', label='daily avg')
|
||||||
# fill colors
|
# fill colors
|
||||||
|
@ -284,7 +284,7 @@ class LastThreeDays:
|
||||||
x = axis['x']
|
x = axis['x']
|
||||||
y = axis['y'].replace(0, 1)
|
y = axis['y'].replace(0, 1)
|
||||||
x_ticks = np.arange(0, 97, step=8)
|
x_ticks = np.arange(0, 97, step=8)
|
||||||
plt.style.use('seaborn')
|
plt.style.use('seaborn-v0_8')
|
||||||
plt.plot(x, y, color='#313131',)
|
plt.plot(x, y, color='#313131',)
|
||||||
# fill colors
|
# fill colors
|
||||||
plt_fill(plt, x, y)
|
plt_fill(plt, x, y)
|
||||||
|
@ -363,7 +363,7 @@ class PmGraphs:
|
||||||
else:
|
else:
|
||||||
col.append('#ff4d4d')
|
col.append('#ff4d4d')
|
||||||
# plot
|
# plot
|
||||||
plt.style.use('seaborn')
|
plt.style.use('seaborn-v0_8')
|
||||||
plt.bar(x_dates, y, color=col, width=0.5)
|
plt.bar(x_dates, y, color=col, width=0.5)
|
||||||
plt.axhline(y=thresh, color='#6ecd65', linestyle=':')
|
plt.axhline(y=thresh, color='#6ecd65', linestyle=':')
|
||||||
plt.xticks(ticks=x_range, labels=x_dates)
|
plt.xticks(ticks=x_range, labels=x_dates)
|
||||||
|
@ -426,7 +426,7 @@ class HourBar:
|
||||||
# color columns
|
# color columns
|
||||||
col = NightlyPlots.color_colums(y)
|
col = NightlyPlots.color_colums(y)
|
||||||
# create plot
|
# create plot
|
||||||
plt.style.use('seaborn')
|
plt.style.use('seaborn-v0_8')
|
||||||
plt.bar(x, y, color=col, width=0.5)
|
plt.bar(x, y, color=col, width=0.5)
|
||||||
plt.yticks(np.arange(0, y_max, step=50))
|
plt.yticks(np.arange(0, y_max, step=50))
|
||||||
plt.xticks(ticks=x_range, labels=x_hours)
|
plt.xticks(ticks=x_range, labels=x_hours)
|
||||||
|
@ -549,7 +549,7 @@ class YearComparison:
|
||||||
# build plot
|
# build plot
|
||||||
plt.title("Daily avg AQI values compared to last year", fontsize=15)
|
plt.title("Daily avg AQI values compared to last year", fontsize=15)
|
||||||
chart_fill(plt, y_ticks)
|
chart_fill(plt, y_ticks)
|
||||||
plt.style.use("seaborn")
|
plt.style.use("seaborn-v0_8")
|
||||||
plt.plot(x, y_1, color="#313131", label="this year")
|
plt.plot(x, y_1, color="#313131", label="this year")
|
||||||
plt.plot(
|
plt.plot(
|
||||||
x, y_2, color="#666666", linestyle="dashed", label="last year"
|
x, y_2, color="#666666", linestyle="dashed", label="last year"
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from flask_table import create_table, Col
|
# from flask_table import create_table, Col
|
||||||
|
|
||||||
|
|
||||||
def get_config():
|
def get_config():
|
||||||
|
@ -102,37 +102,9 @@ def chart_fill(plt, y_ticks):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class Table:
|
def get_table(path):
|
||||||
""" create html table from filename to pass to template """
|
"""read json file from path"""
|
||||||
|
with open(path, "r") as f:
|
||||||
|
table_data = json.loads(f.read()).get("data")
|
||||||
|
|
||||||
COLUMNS = [' ', 'this year', 'last year', 'change']
|
return table_data
|
||||||
|
|
||||||
def __init__(self, filename):
|
|
||||||
self.filename = filename
|
|
||||||
self.rows = self.get_rows()
|
|
||||||
|
|
||||||
def get_rows(self):
|
|
||||||
""" read filename to build rows dict """
|
|
||||||
|
|
||||||
with open(self.filename, 'r') as json_file:
|
|
||||||
json_raw = json_file.read()
|
|
||||||
|
|
||||||
table_json = json.loads(json_raw)
|
|
||||||
|
|
||||||
rows = []
|
|
||||||
for i in table_json['data']:
|
|
||||||
row = dict(zip(self.COLUMNS, i))
|
|
||||||
rows.append(row)
|
|
||||||
|
|
||||||
return rows
|
|
||||||
|
|
||||||
def create_table(self):
|
|
||||||
""" create the table with rows and columns """
|
|
||||||
|
|
||||||
blank_table = create_table(options={'classes': ['comp-table']})
|
|
||||||
|
|
||||||
for i in self.COLUMNS:
|
|
||||||
blank_table.add_column(i, Col(i))
|
|
||||||
|
|
||||||
table_obj = blank_table(self.rows)
|
|
||||||
return table_obj
|
|
||||||
|
|
|
@ -0,0 +1,93 @@
|
||||||
|
Copyright 2015 The Rubik Project Authors (https://github.com/googlefonts/rubik)
|
||||||
|
|
||||||
|
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||||
|
This license is copied below, and is also available with a FAQ at:
|
||||||
|
http://scripts.sil.org/OFL
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
PREAMBLE
|
||||||
|
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||||
|
development of collaborative font projects, to support the font creation
|
||||||
|
efforts of academic and linguistic communities, and to provide a free and
|
||||||
|
open framework in which fonts may be shared and improved in partnership
|
||||||
|
with others.
|
||||||
|
|
||||||
|
The OFL allows the licensed fonts to be used, studied, modified and
|
||||||
|
redistributed freely as long as they are not sold by themselves. The
|
||||||
|
fonts, including any derivative works, can be bundled, embedded,
|
||||||
|
redistributed and/or sold with any software provided that any reserved
|
||||||
|
names are not used by derivative works. The fonts and derivatives,
|
||||||
|
however, cannot be released under any other type of license. The
|
||||||
|
requirement for fonts to remain under this license does not apply
|
||||||
|
to any document created using the fonts or their derivatives.
|
||||||
|
|
||||||
|
DEFINITIONS
|
||||||
|
"Font Software" refers to the set of files released by the Copyright
|
||||||
|
Holder(s) under this license and clearly marked as such. This may
|
||||||
|
include source files, build scripts and documentation.
|
||||||
|
|
||||||
|
"Reserved Font Name" refers to any names specified as such after the
|
||||||
|
copyright statement(s).
|
||||||
|
|
||||||
|
"Original Version" refers to the collection of Font Software components as
|
||||||
|
distributed by the Copyright Holder(s).
|
||||||
|
|
||||||
|
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||||
|
or substituting -- in part or in whole -- any of the components of the
|
||||||
|
Original Version, by changing formats or by porting the Font Software to a
|
||||||
|
new environment.
|
||||||
|
|
||||||
|
"Author" refers to any designer, engineer, programmer, technical
|
||||||
|
writer or other person who contributed to the Font Software.
|
||||||
|
|
||||||
|
PERMISSION & CONDITIONS
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||||
|
redistribute, and sell modified and unmodified copies of the Font
|
||||||
|
Software, subject to the following conditions:
|
||||||
|
|
||||||
|
1) Neither the Font Software nor any of its individual components,
|
||||||
|
in Original or Modified Versions, may be sold by itself.
|
||||||
|
|
||||||
|
2) Original or Modified Versions of the Font Software may be bundled,
|
||||||
|
redistributed and/or sold with any software, provided that each copy
|
||||||
|
contains the above copyright notice and this license. These can be
|
||||||
|
included either as stand-alone text files, human-readable headers or
|
||||||
|
in the appropriate machine-readable metadata fields within text or
|
||||||
|
binary files as long as those fields can be easily viewed by the user.
|
||||||
|
|
||||||
|
3) No Modified Version of the Font Software may use the Reserved Font
|
||||||
|
Name(s) unless explicit written permission is granted by the corresponding
|
||||||
|
Copyright Holder. This restriction only applies to the primary font name as
|
||||||
|
presented to the users.
|
||||||
|
|
||||||
|
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||||
|
Software shall not be used to promote, endorse or advertise any
|
||||||
|
Modified Version, except to acknowledge the contribution(s) of the
|
||||||
|
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
5) The Font Software, modified or unmodified, in part or in whole,
|
||||||
|
must be distributed entirely under this license, and must not be
|
||||||
|
distributed under any other license. The requirement for fonts to
|
||||||
|
remain under this license does not apply to any document created
|
||||||
|
using the Font Software.
|
||||||
|
|
||||||
|
TERMINATION
|
||||||
|
This license becomes null and void if any of the above conditions are
|
||||||
|
not met.
|
||||||
|
|
||||||
|
DISCLAIMER
|
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||||
|
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||||
|
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||||
|
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||||
|
OTHER DEALINGS IN THE FONT SOFTWARE.
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -246,7 +246,7 @@ function colorTables() {
|
||||||
cell.textContent = '\u25B2';
|
cell.textContent = '\u25B2';
|
||||||
cell.style.backgroundColor = '#ff4d4d';
|
cell.style.backgroundColor = '#ff4d4d';
|
||||||
} else if (cellContent == 'same') {
|
} else if (cellContent == 'same') {
|
||||||
cell.textContent = '\u301C';
|
cell.textContent = '\uFF5E';
|
||||||
cell.style.backgroundColor = '#bdbdbd';
|
cell.style.backgroundColor = '#bdbdbd';
|
||||||
} else if (cellContent == 'nan') {
|
} else if (cellContent == 'nan') {
|
||||||
cell.style.backgroundColor = '#eeeeee';
|
cell.style.backgroundColor = '#eeeeee';
|
||||||
|
|
|
@ -91,7 +91,26 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="content graph">
|
<div class="content graph">
|
||||||
<div class="graph-item">
|
<div class="graph-item">
|
||||||
{{ table }}
|
<table class="comp-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<th>this year</th>
|
||||||
|
<th>last year</th>
|
||||||
|
<th>change</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for row in table %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ row.0 }}</td>
|
||||||
|
<td>{{ row.1 }}</td>
|
||||||
|
<td>{{ row.2 }}</td>
|
||||||
|
<td>{{ row.3 }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="graph-item">
|
<div class="graph-item">
|
||||||
<a href="/static/dyn/year-graph.png" data-lightbox="year">
|
<a href="/static/dyn/year-graph.png" data-lightbox="year">
|
||||||
|
|
|
@ -47,7 +47,7 @@
|
||||||
{% block content %}{% endblock %}
|
{% block content %}{% endblock %}
|
||||||
<div class="full-width col_bg">
|
<div class="full-width col_bg">
|
||||||
<div class="col-box">
|
<div class="col-box">
|
||||||
<p>© 2023 | <a href="https://github.com/bbilly1/aqi_monitor" target="_blank">Documentation</a></p>
|
<p>© <script type="text/javascript">document.write(new Date().getFullYear());</script> | <a href="https://github.com/bbilly1/aqi_monitor" target="_blank">Documentation</a></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -16,7 +16,26 @@
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="graph-item">
|
<div class="graph-item">
|
||||||
{{month.table}}
|
<table class="comp-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<th>this year</th>
|
||||||
|
<th>last year</th>
|
||||||
|
<th>change</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for row in month.table %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ row.0 }}</td>
|
||||||
|
<td>{{ row.1 }}</td>
|
||||||
|
<td>{{ row.2 }}</td>
|
||||||
|
<td>{{ row.3 }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
|
@ -9,7 +9,7 @@ from flask_httpauth import HTTPBasicAuth
|
||||||
from apscheduler.schedulers.background import BackgroundScheduler
|
from apscheduler.schedulers.background import BackgroundScheduler
|
||||||
|
|
||||||
|
|
||||||
from src.helper import Table, get_config
|
from src.helper import get_config, get_table
|
||||||
from src.db import get_current, insert_data
|
from src.db import get_current, insert_data
|
||||||
from src.graph_current import main as current_graph
|
from src.graph_current import main as current_graph
|
||||||
from src.graph_nightly import main as nightly_graph
|
from src.graph_nightly import main as nightly_graph
|
||||||
|
@ -92,7 +92,7 @@ def about():
|
||||||
@app.route("/graphs")
|
@app.route("/graphs")
|
||||||
def graphs():
|
def graphs():
|
||||||
""" graphs page """
|
""" graphs page """
|
||||||
table = Table('static/dyn/year-table.json').create_table()
|
table = get_table("static/dyn/year-table.json")
|
||||||
return render_template('graphs.html', title='Graphs', table=table)
|
return render_template('graphs.html', title='Graphs', table=table)
|
||||||
|
|
||||||
|
|
||||||
|
@ -108,7 +108,7 @@ def monthly():
|
||||||
month_graph = os.path.join('static/dyn/monthly', month_clean + '.png')
|
month_graph = os.path.join('static/dyn/monthly', month_clean + '.png')
|
||||||
month_name = datetime.strptime(month_clean, "%Y-%m").strftime('%B %Y')
|
month_name = datetime.strptime(month_clean, "%Y-%m").strftime('%B %Y')
|
||||||
month_json = os.path.join('static/dyn/monthly', month)
|
month_json = os.path.join('static/dyn/monthly', month)
|
||||||
table = Table(month_json).create_table()
|
table = get_table(month_json)
|
||||||
month_dict = {
|
month_dict = {
|
||||||
'month_graph': month_graph,
|
'month_graph': month_graph,
|
||||||
'month_name': month_name,
|
'month_name': month_name,
|
||||||
|
|
Loading…
Reference in New Issue