app.py
Python script, ASCII text executable
1
import datetime
2
3
import celery
4
import flask
5
from flask_sqlalchemy import SQLAlchemy
6
from flask_bcrypt import Bcrypt
7
from flask_migrate import Migrate
8
9
from sqlalchemy.orm import declarative_base
10
from celery import shared_task
11
import httpx
12
13
app = flask.Flask(__name__)
14
from celery import Celery, Task
15
16
17
def celery_init_app(app_) -> Celery:
18
class FlaskTask(Task):
19
def __call__(self, *args: object, **kwargs: object) -> object:
20
with app_.app_context():
21
return self.run(*args, **kwargs)
22
23
celery_app = Celery(app_.name, task_cls=FlaskTask)
24
celery_app.config_from_object(app_.config["CELERY"])
25
celery_app.set_default()
26
app_.extensions["celery"] = celery_app
27
return celery_app
28
29
30
app.config.from_mapping(
31
CELERY=dict(
32
broker_url="redis://localhost:6379",
33
result_backend="redis://localhost:6379"
34
),
35
)
36
celery_app = celery_init_app(app)
37
38
app.config["SQLALCHEMY_DATABASE_URI"] = \
39
"postgresql://echo:1234@localhost:5432/echo"
40
db = SQLAlchemy(app)
41
bcrypt = Bcrypt(app)
42
migrate = Migrate(app, db)
43
app.config["SESSION_TYPE"] = "filesystem"
44
app.config["SECRET_KEY"] = "super secret"
45
46
with app.app_context():
47
class User(db.Model):
48
username = db.Column(db.String(64), unique=True, nullable=False, primary_key=True)
49
password = db.Column(db.String(72), nullable=False)
50
admin = db.Column(db.Boolean, nullable=False, default=False)
51
52
applications = db.relationship("Application", back_populates="owner")
53
54
def __init__(self, username, password, admin=False):
55
self.username = username
56
self.password = bcrypt.generate_password_hash(password).decode("utf-8")
57
self.admin = admin
58
59
class Application(db.Model):
60
id = db.Column(db.Integer, primary_key=True, autoincrement=True, unique=True, default=0)
61
name = db.Column(db.String(64), unique=True, nullable=False)
62
owner_name = db.Column(db.String(64), db.ForeignKey("user.username"), nullable=False)
63
64
owner = db.relationship("User", back_populates="applications")
65
66
endpoints = db.relationship("Endpoint", back_populates="application")
67
68
def __init__(self, name, owner):
69
self.name = name
70
self.owner_name = owner.username
71
72
class Endpoint(db.Model):
73
id = db.Column(db.Integer, unique=True, nullable=False, primary_key=True, autoincrement=True)
74
application_id = db.Column(db.Integer, db.ForeignKey("application.id"), nullable=False)
75
address = db.Column(db.String(2048), nullable=False)
76
name = db.Column(db.String(64), nullable=False)
77
comment = db.Column(db.String(2048), nullable=True)
78
ping_interval = db.Column(db.Integer, default=300, nullable=False)
79
80
application = db.relationship("Application", back_populates="endpoints")
81
82
def __init__(self, application, name, address, ping_interval, comment=""):
83
self.application_id = application.id
84
self.name = name
85
self.address = address
86
self.comment = comment
87
self.ping_interval = ping_interval
88
89
90
class Status(db.Model):
91
id = db.Column(db.Integer, unique=True, nullable=False, autoincrement=True, primary_key=True, default=0)
92
endpoint_id = db.Column(db.Integer, nullable=False)
93
time = db.Column(db.DateTime, default=datetime.datetime.utcnow)
94
95
status = db.Column(db.SmallInteger, nullable=False)
96
97
def __init__(self, endpoint_id, status):
98
self.endpoint_id = endpoint_id
99
self.status = status
100
101
102
@celery.shared_task(name="ping")
103
def ping(id, address, next_ping):
104
url = address
105
print(f"Pinging {url}")
106
response = httpx.get(url, verify=False)
107
reading = Status(id, response.status_code)
108
db.session.add(reading)
109
db.session.commit()
110
111
# Schedule the next ping
112
ping.apply_async(args=(id, address, next_ping), countdown=next_ping)
113
114
@celery.shared_task(name="ping_all")
115
def ping_all():
116
endpoints = Endpoint.query.all()
117
for endpoint in endpoints:
118
ping.delay(endpoint.id, endpoint.address, endpoint.ping_interval)
119
120
121
task = ping_all.delay()
122
123
print()
124
print()
125
print(task)
126
print()
127
print()
128
129
130
@app.context_processor
131
def default():
132
return {
133
"session": flask.session,
134
}
135
136
137
@app.route("/")
138
def dashboard():
139
return flask.render_template("dashboard.html", apps=Application.query.all())
140
141
142
@app.route("/login", methods=["GET"])
143
def login():
144
return flask.render_template("login.html")
145
146
147
@app.route("/signup", methods=["GET"])
148
def signup():
149
return flask.render_template("signup.html")
150
151
152
@app.route("/new-app", methods=["GET"])
153
def new_app():
154
if not flask.session.get("username"):
155
return flask.redirect("/login", code=303)
156
return flask.render_template("new-app.html")
157
158
159
@app.route("/new-app", methods=["POST"])
160
def new_app_post():
161
if not flask.session.get("username"):
162
return flask.redirect("/login", code=303)
163
if Application.query.filter_by(name=flask.request.form["name"]).first():
164
flask.flash("Application already exists")
165
return flask.redirect("/new-app", code=303)
166
167
new_app_ = Application(
168
flask.request.form["name"],
169
db.session.get(User, flask.session["username"]),
170
)
171
db.session.add(new_app_)
172
db.session.commit()
173
return flask.redirect("/", code=303)
174
175
176
@app.route("/login", methods=["POST"])
177
def login_post():
178
user = db.session.get(User, flask.request.form["username"])
179
if not user:
180
flask.flash("Username doesn't exist")
181
return flask.redirect("/signup", code=303)
182
if not bcrypt.check_password_hash(user.password, flask.request.form["password"]):
183
flask.flash("Wrong password")
184
return flask.redirect("/signup", code=303)
185
186
flask.session["username"] = user.username
187
return flask.redirect("/", code=303)
188
189
190
@app.route("/logout")
191
def logout():
192
flask.session.pop("username", None)
193
return flask.redirect("/", code=303)
194
195
196
@app.route("/signup", methods=["POST"])
197
def signup_post():
198
if flask.request.form["password"] != flask.request.form["password2"]:
199
flask.flash("Passwords do not match")
200
return flask.redirect("/signup", code=303)
201
if db.session.get(User, flask.request.form["username"]):
202
flask.flash("Username already exists")
203
return flask.redirect("/signup", code=303)
204
if len(flask.request.form["password"]) < 8:
205
flask.flash("Password must be at least 8 characters")
206
return flask.redirect("/signup", code=303)
207
if len(flask.request.form["username"]) < 4:
208
flask.flash("Username must be at least 4 characters")
209
return flask.redirect("/signup", code=303)
210
211
new_user = User(
212
flask.request.form["username"],
213
flask.request.form["password"],
214
)
215
db.session.add(new_user)
216
db.session.commit()
217
flask.session["username"] = new_user.username
218
return flask.redirect("/", code=303)
219
220
221
@app.route("/app/<int:app_id>/")
222
def app_info(app_id):
223
app_ = db.session.get(Application, app_id)
224
return flask.render_template("app.html", app=app_)
225
226
227
@app.route("/app/<int:app_id>/edit/")
228
def app_editor(app_id):
229
if flask.session.get("username") != db.session.get(Application, app_id).owner_name:
230
flask.abort(403)
231
app_ = db.session.get(Application, app_id)
232
return flask.render_template("app-editor.html", app=app_)
233
234
235
@app.route("/app/<int:app_id>/edit/<int:endpoint_id>", methods=["POST"])
236
def endpoint_edit(app_id, endpoint_id):
237
if flask.session.get("username") != db.session.get(Application, app_id).owner_name:
238
flask.abort(403)
239
endpoint = db.session.get(Endpoint, endpoint_id)
240
if flask.request.form.get("delete") == "delete":
241
db.session.delete(endpoint)
242
db.session.commit()
243
else:
244
endpoint.name = flask.request.form["name"]
245
endpoint.address = flask.request.form["url"]
246
endpoint.comment = flask.request.form["comment"]
247
db.session.commit()
248
return flask.redirect(f"/app/{app_id}/edit", code=303)
249
250
251
@app.route("/app/<int:app_id>/add-endpoint", methods=["POST"])
252
def app_add_endpoint(app_id):
253
if flask.session.get("username") != db.session.get(Application, app_id).owner_name:
254
flask.abort(403)
255
app_ = db.session.get(Application, app_id)
256
endpoint = Endpoint(app_,
257
flask.request.form["name"],
258
flask.request.form["url"],
259
300,
260
flask.request.form["comment"])
261
db.session.add(endpoint)
262
db.session.commit()
263
return flask.redirect(f"/app/{app_id}/edit", code=303)
264
265