roundabout,
created on Saturday, 6 April 2024, 16:26:27 (1712420787),
received on Sunday, 7 April 2024, 12:44:08 (1712493848)
Author identity: vlad <vlad.muntoiu@gmail.com>
da0632aae170ad798b2c4128c686c4eddfdded4c
.idea/workspace.xml
@@ -4,11 +4,12 @@
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="411335b4-e813-41ad-9046-18b77b97ee46" name="Changes" comment="More">
<list default="true" id="411335b4-e813-41ad-9046-18b77b97ee46" name="Changes" comment="Endpoint management">
<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" />
<change beforePath="$PROJECT_DIR$/templates/app-editor.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/app-editor.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/app.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/app.html" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -82,7 +83,15 @@
<option name="project" value="LOCAL" />
<updated>1712409360299</updated>
</task>
<option name="localTasksCounter" value="3" />
<task id="LOCAL-00003" summary="Endpoint management">
<option name="closed" value="true" />
<created>1712411232132</created>
<option name="number" value="00003" />
<option name="presentableId" value="LOCAL-00003" />
<option name="project" value="LOCAL" />
<updated>1712411232132</updated>
</task>
<option name="localTasksCounter" value="4" />
<servers />
</component>
<component name="Vcs.Log.Tabs.Properties">
@@ -99,6 +108,7 @@
<component name="VcsManagerConfiguration">
<MESSAGE value="Initial commit" />
<MESSAGE value="More" />
<option name="LAST_COMMIT_MESSAGE" value="More" />
<MESSAGE value="Endpoint management" />
<option name="LAST_COMMIT_MESSAGE" value="Endpoint management" />
</component>
</project>
app.py
@@ -1,15 +1,40 @@
import datetime
import celery
import flask
from flask_sqlalchemy import SQLAlchemy
from flask_bcrypt import Bcrypt
from flask_migrate import Migrate
from sqlalchemy.orm import declarative_base
from celery import shared_task
import httpx
app = flask.Flask(__name__)
from celery import Celery, Task
def celery_init_app(app_) -> Celery:
class FlaskTask(Task):
def __call__(self, *args: object, **kwargs: object) -> object:
with app_.app_context():
return self.run(*args, **kwargs)
celery_app = Celery(app_.name, task_cls=FlaskTask)
celery_app.config_from_object(app_.config["CELERY"])
celery_app.set_default()
app_.extensions["celery"] = celery_app
return celery_app
app.config.from_mapping(
CELERY=dict(
broker_url="redis://localhost:6379",
result_backend="redis://localhost:6379"
),
)
celery_app = celery_init_app(app)
app.config["SQLALCHEMY_DATABASE_URI"] = \
"postgresql://echo:1234@localhost:5432/echo"
db = SQLAlchemy(app)
@@ -50,43 +75,56 @@ with app.app_context():
address = db.Column(db.String(2048), nullable=False)
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)
application = db.relationship("Application", back_populates="endpoints")
def __init__(self, application, name, address, comment=""):
def __init__(self, application, name, address, ping_interval, comment=""):
self.application_id = application.id
self.name = name
self.address = address
self.comment = comment
self.ping_interval = ping_interval
Base = declarative_base()
class Status(Base):
__table_args = (
{
"timescaledb_hypertable": {
"time_column_name": "time",
},
}
)
__tablename__ = "status"
id = db.Column(db.Integer, unique=True, nullable=False, autoincrement=True)
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)
time = db.Column(db.DateTime, index=True, default=datetime.datetime.utcnow, primary_key=True)
time = db.Column(db.DateTime, default=datetime.datetime.utcnow)
status = db.Column(db.SmallInteger, nullable=False)
endpoint = db.relationship("Endpoint", back_populates="statuses")
def __init__(self, endpoint, status):
self.endpoint_id = endpoint.id
def __init__(self, endpoint_id, status):
self.endpoint_id = endpoint_id
self.status = status
def ping(endpoint):
url = endpoint.address
response = httpx.get(url)
return response.status_code
@celery.shared_task(name="ping")
def ping(id, address, next_ping):
url = address
print(f"Pinging {url}")
response = httpx.get(url, verify=False)
reading = Status(id, response.status_code)
db.session.add(reading)
db.session.commit()
# Schedule the next ping
ping.apply_async(args=(id, address, next_ping), countdown=next_ping)
@celery.shared_task(name="ping_all")
def ping_all():
endpoints = Endpoint.query.all()
for endpoint in endpoints:
ping.delay(endpoint.id, endpoint.address, endpoint.ping_interval)
task = ping_all.delay()
print()
print()
print(task)
print()
print()
@app.context_processor
@@ -180,11 +218,6 @@ def signup_post():
return flask.redirect("/", code=303)
@app.route("/timeline/<endpoint_id>")
def info(endpoint_id):
return flask.render_template("timeline.html", endpoint=endpoint_id)
@app.route("/app/<int:app_id>/")
def app_info(app_id):
app_ = db.session.get(Application, app_id)
@@ -223,7 +256,9 @@ def app_add_endpoint(app_id):
endpoint = Endpoint(app_,
flask.request.form["name"],
flask.request.form["url"],
300,
flask.request.form["comment"])
db.session.add(endpoint)
db.session.commit()
return flask.redirect(f"/app/{app_id}/edit", code=303)
static/style.css
@@ -176,14 +176,14 @@ input[type="password"]:not(:placeholder-shown) {
font-family: "Lexend Exa", "Lexend", sans-serif;
}
.app-card {
.app-card, .endpoint-card {
width: 100%;
border: 2px solid #000000aa;
padding: 0.75rem;
border-radius: 24px;
}
.app-card h2 {
:is(.app-card, .endpoint-card) h2 {
margin-top: 0;
font-size: 1.45rem;
font-weight: 750;
@@ -249,7 +249,7 @@ iconify-icon {
display: inline-block;
}
.quiet-link:has(.app-card) {
.quiet-link:has(:is(.app-card, .endpoint-card)) {
display: block;
border-radius: 24px;
}
@@ -264,7 +264,7 @@ textarea {
appearance: none;
}
#endpoint-editor {
#endpoint-editor, #endpoint-list {
display: flex;
gap: 2rem;
width: 100%;
@@ -279,8 +279,8 @@ textarea {
justify-content: space-around;
}
.side-by-side > button {
flex: 1 1 25%;
.side-by-side > button.extend {
flex: 1 1 100%;
}
.danger-button {
@@ -291,3 +291,8 @@ textarea {
.danger-button:hover {
background: #D32F2F;
}
nav a:focus {
box-shadow: none;
text-decoration: underline;
}
templates/app-editor.html
@@ -10,8 +10,10 @@
<input type="url" name="url" placeholder="Ping address" value="{{ endpoint.address }}">
<textarea name="comment" placeholder="Comment" rows="4">{{ endpoint.comment }}</textarea>
<div class="side-by-side">
<button type="submit">Apply changes</button>
<button type="submit" name="delete" value="delete" class="danger-button">Delete</button>
<button type="submit" class="extend">Apply changes</button>
<button type="submit" name="delete" value="delete" class="danger-button">
<iconify-icon icon="mdi:trash-can">Delete</iconify-icon>
</button>
</div>
</form>
{% endfor %}
templates/app.html
@@ -9,5 +9,14 @@
Manage endpoints
</a>
{% endif %}
<div id="endpoint-list">
{% for endpoint in app.endpoints %}
<div class="endpoint-card">
<h2>{{ endpoint.name }}</h2>
<p>{{ endpoint.comment }}</p>
</div>
{% endfor %}
</div>
</main>
{% endblock %}