automation: establish an automation framework
This commit is contained in:
parent
1b53bf451c
commit
8abe5d60fa
31 changed files with 586 additions and 274 deletions
|
@ -8,6 +8,7 @@ from app.models.alarms import Alarm
|
|||
from app import Origin, Proxy
|
||||
from app.models.base import Group, MirrorList
|
||||
from app.portal.forms import LifecycleForm, NewMirrorListForm
|
||||
from app.portal.automation import bp as automation
|
||||
from app.portal.bridgeconf import bp as bridgeconf
|
||||
from app.portal.bridge import bp as bridge
|
||||
from app.portal.group import bp as group
|
||||
|
@ -17,7 +18,7 @@ from app.portal.proxy import bp as proxy
|
|||
from app.portal.util import response_404, view_lifecycle
|
||||
|
||||
portal = Blueprint("portal", __name__, template_folder="templates", static_folder="static")
|
||||
|
||||
portal.register_blueprint(automation, url_prefix="/automation")
|
||||
portal.register_blueprint(bridgeconf, url_prefix="/bridgeconf")
|
||||
portal.register_blueprint(bridge, url_prefix="/bridge")
|
||||
portal.register_blueprint(group, url_prefix="/group")
|
||||
|
|
70
app/portal/automation.py
Normal file
70
app/portal/automation.py
Normal file
|
@ -0,0 +1,70 @@
|
|||
from datetime import datetime
|
||||
|
||||
from flask import render_template, flash, Response, Blueprint
|
||||
from flask_wtf import FlaskForm
|
||||
from sqlalchemy import exc
|
||||
from wtforms import SubmitField, BooleanField
|
||||
from wtforms.validators import DataRequired
|
||||
|
||||
from app.extensions import db
|
||||
from app.models.automation import Automation
|
||||
from app.portal.util import view_lifecycle, response_404
|
||||
|
||||
bp = Blueprint("automation", __name__)
|
||||
|
||||
|
||||
class EditAutomationForm(FlaskForm):
|
||||
enabled = BooleanField('Enabled')
|
||||
submit = SubmitField('Save Changes')
|
||||
|
||||
|
||||
@bp.route("/list")
|
||||
def automation_list():
|
||||
automations = Automation.query.filter(
|
||||
Automation.destroyed == None).order_by(Automation.description).all()
|
||||
return render_template("list.html.j2",
|
||||
section="automation",
|
||||
title="Automation Jobs",
|
||||
item="automation",
|
||||
items=automations)
|
||||
|
||||
|
||||
@bp.route('/edit/<automation_id>', methods=['GET', 'POST'])
|
||||
def automation_edit(automation_id):
|
||||
automation = Automation.query.filter(Automation.id == automation_id).first()
|
||||
if automation is None:
|
||||
return Response(render_template("error.html.j2",
|
||||
section="automation",
|
||||
header="404 Automation Job Not Found",
|
||||
message="The requested automation job could not be found."),
|
||||
status=404)
|
||||
form = EditAutomationForm(enabled=automation.enabled)
|
||||
if form.validate_on_submit():
|
||||
automation.enabled = form.enabled.data
|
||||
automation.updated = datetime.utcnow()
|
||||
try:
|
||||
db.session.commit()
|
||||
flash("Saved changes to bridge configuration.", "success")
|
||||
except exc.SQLAlchemyError:
|
||||
flash("An error occurred saving the changes to the bridge configuration.", "danger")
|
||||
return render_template("automation.html.j2",
|
||||
section="automation",
|
||||
automation=automation, form=form)
|
||||
|
||||
|
||||
@bp.route("/kick/<automation_id>", methods=['GET', 'POST'])
|
||||
def automation_kick(automation_id: int):
|
||||
automation = Automation.query.filter(
|
||||
Automation.id == automation_id,
|
||||
Automation.destroyed == None).first()
|
||||
if automation is None:
|
||||
return response_404("The requested bridge configuration could not be found.")
|
||||
return view_lifecycle(
|
||||
header=f"Kick automation timer?",
|
||||
message=automation.description,
|
||||
success_view="portal.automation.automation_list",
|
||||
success_message="This automation job will next run within 1 minute.",
|
||||
section="automation",
|
||||
resource=automation,
|
||||
action="kick"
|
||||
)
|
16
app/portal/templates/automation.html.j2
Normal file
16
app/portal/templates/automation.html.j2
Normal file
|
@ -0,0 +1,16 @@
|
|||
{% extends "base.html.j2" %}
|
||||
{% from 'bootstrap5/form.html' import render_form %}
|
||||
{% from "tables.html.j2" import automation_logs_table %}
|
||||
|
||||
{% block content %}
|
||||
<h1 class="h2 mt-3">Automation Job</h1>
|
||||
<h2 class="h3">{{ automation.description }} ({{ automation.short_name }})</h2>
|
||||
|
||||
<div style="border: 1px solid #666;" class="p-3">
|
||||
{{ render_form(form) }}
|
||||
</div>
|
||||
|
||||
<h3>Logs</h3>
|
||||
{{ automation_logs_table(automation.logs) }}
|
||||
|
||||
{% endblock %}
|
|
@ -128,6 +128,12 @@
|
|||
<span>Monitoring</span>
|
||||
</h6>
|
||||
<ul class="nav flex-column">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link{% if section == "automation" %} active{% endif %}"
|
||||
href="{{ url_for("portal.automation.automation_list") }}">
|
||||
Automation
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link{% if section == "alarm" %} active{% endif %}"
|
||||
href="{{ url_for("portal.view_alarms") }}">
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{% extends "base.html.j2" %}
|
||||
{% from "tables.html.j2" import bridgeconfs_table, bridges_table,
|
||||
{% from "tables.html.j2" import automations_table, bridgeconfs_table, bridges_table,
|
||||
groups_table, mirrorlists_table, origins_table, origin_onion_table,
|
||||
onions_table, proxies_table %}
|
||||
|
||||
|
@ -8,7 +8,9 @@
|
|||
{% if new_link %}
|
||||
<a href="{{ new_link }}" class="btn btn-success">Create new {{ item }}</a>
|
||||
{% endif %}
|
||||
{% if item == "bridge configuration" %}
|
||||
{% if item == "automation" %}
|
||||
{{ automations_table(items) }}
|
||||
{% elif item == "bridge configuration" %}
|
||||
{{ bridgeconfs_table(items) }}
|
||||
{% elif item == "bridge" %}
|
||||
{{ bridges_table(items) }}
|
||||
|
|
|
@ -26,6 +26,67 @@
|
|||
<div class="alert alert-danger">Not implemented yet.</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro automations_table(automations) %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Description</th>
|
||||
<th scope="col">Status</th>
|
||||
<th scope="col">Enabled</th>
|
||||
<th scope="col">Last Run</th>
|
||||
<th scope="col">Next Run</th>
|
||||
<th scope="col">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for automation in automations %}
|
||||
<tr>
|
||||
<td title="{{ automation.short_name }}">{{ automation.description }}</td>
|
||||
<td title="{{ automation.state.name }}">
|
||||
{% if automation.state.name == "IDLE" %}
|
||||
🕰️
|
||||
{% elif automation.state.name == "RUNNING" %}
|
||||
🏃
|
||||
{% else %}
|
||||
💥
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{% if automation.enabled %}✅{% else %}❌{% endif %}</td>
|
||||
<td>{{ automation.last_run | format_datetime }}</td>
|
||||
<td>{{ automation.next_run | format_datetime }}</td>
|
||||
<td>
|
||||
<a href="{{ url_for("portal.automation.automation_edit", automation_id=automation.id) }}" class="btn btn-primary btn-sm">View/Edit</a>
|
||||
<a href="{{ url_for("portal.automation.automation_kick", automation_id=automation.id) }}" class="btn btn-success btn-sm">Kick Timer</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro automation_logs_table(logs) %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Log Created</th>
|
||||
<th scope="col">Log</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for log in logs %}
|
||||
<tr>
|
||||
<td>{{ log.added | format_datetime }}</td>
|
||||
<td>{{ log.logs }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro groups_table(groups) %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-sm">
|
||||
|
@ -84,6 +145,8 @@
|
|||
<td>
|
||||
<a href="{{ url_for("portal.origin.origin_edit", origin_id=origin.id) }}"
|
||||
class="btn btn-primary btn-sm">View/Edit</a>
|
||||
<a href="{{ url_for("portal.origin.origin_destroy", origin_id=origin.id) }}"
|
||||
class="btn btn-danger btn-sm">Destroy</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
|
|
|
@ -20,11 +20,19 @@ def view_lifecycle(*,
|
|||
resource: AbstractResource,
|
||||
action: str):
|
||||
form = LifecycleForm()
|
||||
if action == "destroy":
|
||||
form.submit.render_kw = {"class": "btn btn-danger"}
|
||||
elif action == "deprecate":
|
||||
form.submit.render_kw = {"class": "btn btn-warning"}
|
||||
elif action == "kick":
|
||||
form.submit.render_kw = {"class": "btn btn-success"}
|
||||
if form.validate_on_submit():
|
||||
if action == "destroy":
|
||||
resource.destroy()
|
||||
elif action == "deprecate":
|
||||
resource.deprecate(reason="manual")
|
||||
elif action == "kick":
|
||||
resource.kick()
|
||||
else:
|
||||
flash("Unknown action")
|
||||
return redirect(url_for("portal.portal_home"))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue