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