roundabout,
created on Sunday, 7 April 2024, 04:40:24 (1712464824),
received on Sunday, 7 April 2024, 12:44:08 (1712493848)
Author identity: vlad <vlad.muntoiu@gmail.com>
96c9bc1fc10c421a0983dff39a702d0fc3b42ba4
.idea/workspace.xml
@@ -4,7 +4,7 @@
<option name="autoReloadType" value="SELECTIVE" /> </component> <component name="ChangeListManager"> <list default="true" id="411335b4-e813-41ad-9046-18b77b97ee46" name="Changes" comment="Endpoint management"><list default="true" id="411335b4-e813-41ad-9046-18b77b97ee46" name="Changes" comment="Save ping results"><change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" /> <change beforePath="$PROJECT_DIR$/app.py" beforeDir="false" afterPath="$PROJECT_DIR$/app.py" afterDir="false" /> <change beforePath="$PROJECT_DIR$/static/style.css" beforeDir="false" afterPath="$PROJECT_DIR$/static/style.css" afterDir="false" />
@@ -91,7 +91,15 @@
<option name="project" value="LOCAL" /> <updated>1712411232132</updated> </task> <option name="localTasksCounter" value="4" /><task id="LOCAL-00004" summary="Save ping results"> <option name="closed" value="true" /> <created>1712420788013</created> <option name="number" value="00004" /> <option name="presentableId" value="LOCAL-00004" /> <option name="project" value="LOCAL" /> <updated>1712420788013</updated> </task> <option name="localTasksCounter" value="5" /><servers /> </component> <component name="Vcs.Log.Tabs.Properties">
@@ -109,6 +117,7 @@
<MESSAGE value="Initial commit" /> <MESSAGE value="More" /> <MESSAGE value="Endpoint management" /> <option name="LAST_COMMIT_MESSAGE" value="Endpoint management" /><MESSAGE value="Save ping results" /> <option name="LAST_COMMIT_MESSAGE" value="Save ping results" /></component> </project>
app.py
@@ -2,6 +2,7 @@ import datetime
import celery import flask import sqlalchemyfrom flask_sqlalchemy import SQLAlchemy from flask_bcrypt import Bcrypt from flask_migrate import Migrate
@@ -37,6 +38,11 @@ celery_app = celery_init_app(app)
app.config["SQLALCHEMY_DATABASE_URI"] = \ "postgresql://echo:1234@localhost:5432/echo" app.config["SQLALCHEMY_ENGINE_OPTIONS"] = { "connect_args": { "options": "-c timezone=utc" } }db = SQLAlchemy(app) bcrypt = Bcrypt(app) migrate = Migrate(app, db)
@@ -76,8 +82,10 @@ with app.app_context():
name = db.Column(db.String(64), nullable=False) comment = db.Column(db.String(2048), nullable=True) ping_interval = db.Column(db.Integer, default=300, nullable=False) buggy = db.Column(db.Boolean, default=False)application = db.relationship("Application", back_populates="endpoints") statuses = db.relationship("Status", back_populates="endpoint", lazy="dynamic")def __init__(self, application, name, address, ping_interval, comment=""): self.application_id = application.id
@@ -88,11 +96,13 @@ with app.app_context():
class Status(db.Model): id = db.Column(db.Integer, unique=True, nullable=False, autoincrement=True, primary_key=True, default=0)endpoint_id = db.Column(db.Integer, nullable=False)id = db.Column(db.Integer, nullable=False, autoincrement=True, primary_key=True) endpoint_id = db.Column(db.Integer, db.ForeignKey("endpoint.id"), nullable=False)time = db.Column(db.DateTime, default=datetime.datetime.utcnow) status = db.Column(db.SmallInteger, nullable=False) buggy = db.Column(db.Boolean, default=False) endpoint = db.relationship("Endpoint", back_populates="statuses")def __init__(self, endpoint_id, status): self.endpoint_id = endpoint_id
@@ -218,10 +228,37 @@ def signup_post():
return flask.redirect("/", code=303) # UTC filter @app.template_filter("utc") def utc_filter(timestamp): return datetime.datetime.utcfromtimestamp(timestamp) @app.route("/app/<int:app_id>/") def app_info(app_id): app_ = db.session.get(Application, app_id) return flask.render_template("app.html", app=app_)time_slices = [(datetime.datetime.utcnow() - datetime.timedelta(seconds=int(flask.request.args.get("bar_duration", 30)) * 60 * (i+1)), datetime.datetime.utcnow() - datetime.timedelta(seconds=int(flask.request.args.get("bar_duration", 30)) * i)) for i in range(int(flask.request.args.get("time_period", 30)) // int(flask.request.args.get("bar_duration", 1)))] slice_results = [] for slice_ in time_slices: slice_results.append(db.session.query(Status).filter( sqlalchemy.and_(Status.endpoint.has(application_id=app_id), Status.time >= slice_[0], Status.time < slice_[1])).all()) return flask.render_template("app.html", app=app_, sorted=sorted, list=list, sorting=lambda x: x.time, reverse=True, is_ok=lambda x: all(status.status in (200, 201, 202, 203, 204, 205, 206, 207, 208, 226, 302, 304, 307) for status in x), and_=sqlalchemy.and_, bar_duration=int(flask.request.args.get("bar_duration", 30)), int=int, Status=Status, time_period=int(flask.request.args.get("time_period", 1440)), now=round(datetime.datetime.utcnow().timestamp()), func=sqlalchemy.func, reversed=reversed, fromtimestamp=datetime.datetime.utcfromtimestamp, slices=slice_results)@app.route("/app/<int:app_id>/edit/")
@@ -238,11 +275,15 @@ def endpoint_edit(app_id, endpoint_id):
flask.abort(403) endpoint = db.session.get(Endpoint, endpoint_id) if flask.request.form.get("delete") == "delete": statuses = db.session.query(Status).filter_by(endpoint_id=endpoint_id).all() for status in statuses: db.session.delete(status)db.session.delete(endpoint) db.session.commit() else: endpoint.name = flask.request.form["name"] endpoint.address = flask.request.form["url"] endpoint.ping_interval = max(15, int(flask.request.form["ping_interval"]))endpoint.comment = flask.request.form["comment"] db.session.commit() return flask.redirect(f"/app/{app_id}/edit", code=303)
@@ -256,9 +297,12 @@ def app_add_endpoint(app_id):
endpoint = Endpoint(app_, flask.request.form["name"], flask.request.form["url"], 300,max(15, int(flask.request.form["ping_interval"])),flask.request.form["comment"]) db.session.add(endpoint) db.session.commit() ping.delay(endpoint.id, endpoint.address, endpoint.ping_interval)return flask.redirect(f"/app/{app_id}/edit", code=303)
static/style.css
@@ -191,6 +191,7 @@ input[type="password"]:not(:placeholder-shown) {
.app-uptime { display: flex; flex-flow: row-reverse nowrap;overflow: hidden; border-radius: calc(24px - 0.75rem); }
@@ -216,6 +217,7 @@ input[type="password"]:not(:placeholder-shown) {
background: #546E7A; height: 2rem; flex: 1 0 auto; outline: 1px solid #ffffff;} .uptime-bar-ok {
@@ -275,11 +277,11 @@ textarea {
.side-by-side { display: flex; gap: 1rem; align-items: stretch;align-items: center;justify-content: space-around; } .side-by-side > button.extend {.extend {flex: 1 1 100%; }
@@ -296,3 +298,18 @@ nav a:focus {
box-shadow: none; text-decoration: underline; } .horizontal-form { display: flex; align-items: center; gap: 1rem; } label { color: #00796B; display: block; } label > input { width: 100%; }
templates/app-editor.html
@@ -8,6 +8,10 @@
<form class="stacked-form" method="post" action="/app/{{ app.id }}/edit/{{ endpoint.id }}"> <input type="text" name="name" placeholder="Name" value="{{ endpoint.name }}"> <input type="url" name="url" placeholder="Ping address" value="{{ endpoint.address }}"> <div class="side-by-side"> <input class="extend" type="number" name="ping_interval" placeholder="Interval (seconds)" value="{{ endpoint.ping_interval }}" step="1" min="15"> seconds </div><textarea name="comment" placeholder="Comment" rows="4">{{ endpoint.comment }}</textarea> <div class="side-by-side"> <button type="submit" class="extend">Apply changes</button>
@@ -22,6 +26,10 @@
<form class="stacked-form" method="post" action="/app/{{ app.id }}/add-endpoint"> <input type="text" name="name" placeholder="Name"> <input type="url" name="url" placeholder="Ping address"> <div class="side-by-side"> <input class="extend" type="number" name="ping_interval" placeholder="Interval (seconds)" value="300" step="1" min="15"> seconds </div><textarea name="comment" placeholder="Comment" rows="4"></textarea> <button type="submit">Add</button> </form>
templates/app.html
@@ -9,12 +9,47 @@
Manage endpoints </a> {% endif %} <form style="margin-bottom: 1em;" class="horizontal-form"> <label class="extend"> Interval duration (minutes) <input type="number" name="bar_duration" step="1" value="{{ bar_duration }}"> </label> <label class="extend"> Time period (minutes) <input type="number" name="time_period" step="1" value="{{ time_period }}"> </label> <button type="submit">Change</button> </form><div id="endpoint-list"> {% for endpoint in app.endpoints %} <div class="endpoint-card"> <h2>{{ endpoint.name }}</h2> <p>{{ endpoint.comment }}</p> <div class="app-uptime"> <!-- {% set slice_size = bar_duration * 60 %}--> <!-- {% set shown_seconds = 3600 %}--> <!-- {% set num_slices = int(shown_seconds // slice_size) %}--> <!-- {% for i in range(num_slices) %}--> <!-- {% set slice_statuses = endpoint.statuses.filter(Status.time.between(fromtimestamp(now - i*slice_size), fromtimestamp(now - i*slice_size + slice_size))) %}--> <!-- {% if not slice_statuses.count() %}--> <!-- <div class="uptime-bar"></div>--> <!-- {% elif is_ok(slice_statuses) %}--> <!-- <div class="uptime-bar uptime-bar-ok"></div>--> <!-- {% else %}--> <!-- <div class="uptime-bar uptime-bar-down"></div>--> <!-- {% endif %}--> <!-- {{ slice_statuses.all() }}<br>--> <!-- {% endfor %}--> {% for slice in slices %} {% if not slice %} <div class="uptime-bar"></div> {% elif is_ok(slice) %} <div class="uptime-bar uptime-bar-ok"></div> {% else %} <div class="uptime-bar uptime-bar-down"></div> {% endif %} {% endfor %} </div></div> {% endfor %} </div>