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