from datetime import datetime, timezone from typing import Optional from flask import Blueprint, Response, current_app, flash, render_template from flask.typing import ResponseReturnValue from flask_wtf import FlaskForm from sqlalchemy import desc, exc from wtforms import BooleanField, SubmitField from app.extensions import db from app.models.automation import Automation, AutomationLogs from app.models.tfstate import TerraformState from app.portal.util import response_404, view_lifecycle bp = Blueprint("automation", __name__) _SECTION_TEMPLATE_VARS = { "section": "automation", "help_url": "https://bypass.censorship.guide/user/automation.html", } class EditAutomationForm(FlaskForm): # type: ignore enabled = BooleanField("Enabled") submit = SubmitField("Save Changes") @bp.route("/list") def automation_list() -> ResponseReturnValue: automations = list( filter( lambda a: a.short_name not in current_app.config.get("HIDDEN_AUTOMATIONS", []), Automation.query.filter(Automation.destroyed.is_(None)) .order_by(Automation.description) .all(), ) ) states = {tfs.key: tfs for tfs in TerraformState.query.all()} return render_template( "list.html.j2", title="Automation Jobs", item="automation", items=automations, states=states, **_SECTION_TEMPLATE_VARS ) @bp.route("/edit/", methods=["GET", "POST"]) def automation_edit(automation_id: int) -> ResponseReturnValue: automation: Optional[Automation] = Automation.query.filter( Automation.id == automation_id ).first() if automation is None: return Response( render_template( "error.html.j2", header="404 Automation Job Not Found", message="The requested automation job could not be found.", **_SECTION_TEMPLATE_VARS ), status=404, ) form = EditAutomationForm(enabled=automation.enabled) if form.validate_on_submit(): automation.enabled = form.enabled.data automation.updated = datetime.now(tz=timezone.utc) 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", ) logs = ( AutomationLogs.query.filter(AutomationLogs.automation_id == automation.id) .order_by(desc(AutomationLogs.added)) .limit(5) .all() ) return render_template( "automation.html.j2", automation=automation, logs=logs, form=form, **_SECTION_TEMPLATE_VARS ) @bp.route("/kick/", methods=["GET", "POST"]) def automation_kick(automation_id: int) -> ResponseReturnValue: automation = Automation.query.filter( Automation.id == automation_id, Automation.destroyed.is_(None) ).first() if automation is None: return response_404("The requested bridge configuration could not be found.") return view_lifecycle( header="Kick automation timer?", message=automation.description, section="automation", success_view="portal.automation.automation_list", success_message="This automation job will next run within 1 minute.", resource=automation, action="kick", )