Add in version history for http functions

and also load .env in dev mode
This commit is contained in:
Peter Stockings
2024-05-18 21:55:11 +10:00
parent b4c67d8346
commit 2b4ca571ca
8 changed files with 239 additions and 7 deletions

40
app.py
View File

@@ -1,6 +1,6 @@
import json
import os
from flask import Flask, Response, jsonify, redirect, render_template, request, url_for
from flask import Flask, Response, jsonify, redirect, render_template, render_template_string, request, url_for
import jinja_partials
from jinja2_fragments import render_block
from flask_htmx import HTMX
@@ -9,6 +9,12 @@ from db import DataBase
from services import create_http_function_view_model, create_http_functions_view_model
from flask_login import LoginManager, UserMixin, current_user, login_required, login_user, logout_user
from werkzeug.security import check_password_hash, generate_password_hash
import os
from dotenv import load_dotenv
# Load environment variables from .env file in non-production environments
if os.environ.get('FLASK_ENV') != 'production':
load_dotenv()
login_manager = LoginManager()
app = Flask(__name__)
@@ -222,6 +228,38 @@ def client(function_id):
return render_block(app.jinja_env, 'dashboard/http_functions/client.html', 'page', function_id=function_id, **http_function)
return render_template("dashboard/http_functions/client.html", function_id=function_id, **http_function)
@app.route("/dashboard/http_functions/<int:function_id>/history", methods=["GET"])
@login_required
def get_http_function_history(function_id):
user_id = current_user.id
http_function = db.get_http_function_by_id(user_id, function_id)
if not http_function:
return jsonify({'error': 'Function not found'}), 404
name = http_function['name']
version_number = http_function['version_number']
http_function_history = []
if version_number > 1:
raw_history = db.get_http_function_history(function_id)
for i in range(len(raw_history) - 1):
pre_version = raw_history[i + 1]
post_version = raw_history[i]
http_function_history.append({
'pre': pre_version['script_content'],
'post': post_version['script_content'],
'version_id': post_version['version_id'],
'version_number': post_version['version_number'],
'updated_at': post_version['updated_at']
})
if htmx:
return render_block(app.jinja_env, 'dashboard/http_functions/history.html', 'page', user_id=user_id, function_id=function_id, name=name, http_function=http_function, http_function_history=http_function_history)
return render_template("dashboard/http_functions/history.html", user_id=user_id, name=name, function_id=function_id, http_function=http_function, http_function_history=http_function_history)
@ app.route("/dashboard/timer_functions", methods=["GET"])
@login_required
def dashboard_timer_functions():

9
db.py
View File

@@ -54,12 +54,12 @@ class DataBase():
def get_http_function(self, user_id, name):
http_function = self.execute(
'SELECT id, user_id, NAME, script_content, invoked_count, environment_info, is_public, log_request, log_response, version_number FROM http_functions WHERE user_id=%s AND NAME=%s', [user_id, name], one=True)
'SELECT id, user_id, NAME, script_content, invoked_count, environment_info, is_public, log_request, log_response, version_number, created_at FROM http_functions WHERE user_id=%s AND NAME=%s', [user_id, name], one=True)
return http_function
def get_http_function_by_id(self, user_id, http_function_id):
http_function = self.execute(
'SELECT id, user_id, NAME, script_content, invoked_count, environment_info, is_public, log_request, log_response, version_number FROM http_functions WHERE user_id=%s AND id=%s', [user_id, http_function_id], one=True)
'SELECT id, user_id, NAME, script_content, invoked_count, environment_info, is_public, log_request, log_response, version_number, created_at FROM http_functions WHERE user_id=%s AND id=%s', [user_id, http_function_id], one=True)
return http_function
def create_new_http_function(self, user_id, name, script_content, environment_info, is_public, log_request, log_response):
@@ -111,3 +111,8 @@ ORDER BY invocation_time DESC""", [http_function_id])
new_user = self.execute(
'INSERT INTO users (username, password_hash) VALUES (%s, %s) RETURNING id, username, password_hash, created_at', [username, password_hash], commit=True, one=True)
return new_user
def get_http_function_history(self, function_id):
http_function_history = self.execute(
'SELECT version_id, http_function_id, script_content, version_number, updated_at FROM http_functions_versions WHERE http_function_id=%s AND version_number > 1 ORDER BY version_number DESC', [function_id])
return http_function_history

View File

@@ -9,3 +9,4 @@ jinja2-fragments==0.3.0
Werkzeug==2.2.2
requests==2.26.0
Flask-Login==0.6.3
python-dotenv==1.0.1

View File

@@ -24,6 +24,9 @@
<script src="https://unpkg.com/mithril/mithril.js"></script>
<script src="https://unpkg.com/ace-diff@^2"></script>
<link href="https://unpkg.com/ace-diff@^2/dist/ace-diff.min.css" rel="stylesheet">
<style>
@import url("https://rsms.me/inter/inter.css");

View File

@@ -4,7 +4,8 @@
{{ render_partial('dashboard/http_functions/header.html', title='Update', user_id=user_id, function_id=function_id,
name=name,
refresh_url=url_for('get_http_function_edit_form', function_id=function_id), show_logs=True, show_client=True) }}
refresh_url=url_for('get_http_function_edit_form', function_id=function_id), show_logs=True, show_client=True,
show_history=True) }}
{{ render_partial('function_editor.html', function_id=function_id, name=name, script=script,
environment_info=environment_info,

View File

@@ -54,6 +54,19 @@
</div>
{% endif %}
{% if show_history|default(false, true) %}
<div class="ml-2 cursor-pointer text-gray-500"
hx-get="{{ url_for('get_http_function_history', function_id=function_id) }}" hx-target="#container"
hx-swap="innerHTML" hx-push-url="true">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 512 512" stroke-width="25"
stroke="currentColor" data-slot="icon" class="w-6 h-6">
<path
d="M416,160a64,64,0,1,0-96.27,55.24c-2.29,29.08-20.08,37-75,48.42-17.76,3.68-35.93,7.45-52.71,13.93V151.39a64,64,0,1,0-64,0V360.61a64,64,0,1,0,64.42.24c2.39-18,16-24.33,65.26-34.52,27.43-5.67,55.78-11.54,79.78-26.95,29-18.58,44.53-46.78,46.36-83.89A64,64,0,0,0,416,160ZM160,64a32,32,0,1,1-32,32A32,32,0,0,1,160,64Zm0,384a32,32,0,1,1,32-32A32,32,0,0,1,160,448ZM352,192a32,32,0,1,1,32-32A32,32,0,0,1,352,192Z">
</path>
</svg>
</div>
{% endif %}
</div>
<button
class="bg-transparent hover:bg-blue-500 text-blue-700 font-semibold hover:text-white py-2 px-4 border border-blue-500 hover:border-transparent rounded flex mr-2 items-center ml-auto"

View File

@@ -0,0 +1,170 @@
{% extends 'dashboard.html' %}
{% block page %}
{{ render_partial('dashboard/http_functions/header.html', title='History', user_id=user_id, function_id=function_id,
name=name,
refresh_url=url_for('get_http_function_history', function_id=function_id), show_logs=True, show_client=True,
show_edit_form=True) }}
<!-- Timeline -->
<div>
{% if http_function.version_number > 1 %}
{% for entry in http_function_history %}
<!-- Item -->
<div class="flex gap-x-3">
<!-- Left Content -->
<div class="w-24 text-end">
<span class="text-xs text-gray-500">{{ entry.updated_at.strftime('%b %d %Y %I:%M %p').lstrip("0").replace("
0", " ") }}
</span>
</div>
<!-- End Left Content -->
<!-- Icon -->
<div
class="relative last:after:hidden after:absolute after:top-7 after:bottom-0 after:start-3.5 after:w-px after:-translate-x-[0.5px] after:bg-gray-200">
<div class="relative z-10 size-7 flex justify-center items-center">
<div class="size-2 rounded-full bg-gray-400"></div>
</div>
</div>
<!-- End Icon -->
<!-- Right Content -->
<div class="grow pt-0.5 pb-4 cursor-pointer">
<h3 class="flex gap-x-1.5 font-semibold text-gray-800">
v{{ entry.version_number }}
</h3>
<p class="mt-1 text-sm text-gray-600">
</p>
<button type="button"
class="mt-1 -ms-1 p-1 inline-flex items-center gap-x-2 text-xs rounded-lg border border-transparent text-gray-500 hover:bg-gray-100 disabled:opacity-50 disabled:pointer-events-none">
Revert
</button>
</div>
<!-- End Right Content -->
</div>
<div class="block">
<div id="diff_{{ entry.version_id }}" class="relative h-80"></div>
</div>
<script>
(function () {
// Assigning JSON content to a JavaScript variable
var preContent = '{{ entry.pre | tojson | safe }}';
var postContent = '{{ entry.post | tojson | safe }}';
var aceDiffer = new AceDiff({
element: '#diff_{{ entry.version_id }}',
mode: "ace/mode/javascript",
left: {
content: preContent,
editable: false,
},
right: {
content: postContent,
editable: false,
},
});
// Retrieve the individual editors
var editors = aceDiffer.getEditors();
// Set the maxLines property on each editor
editors.left.setOptions({
maxLines: 20, // Set the maximum number of lines
autoScrollEditorIntoView: true,
});
editors.left.session.setOption("useWorker", false);
editors.right.setOptions({
maxLines: 20, // Set the maximum number of lines
autoScrollEditorIntoView: true,
});
editors.right.session.setOption("useWorker", false);
})();
</script>
<!-- End Item -->
{% endfor %}
<!-- Item -->
<div class="flex gap-x-3">
<!-- Left Content -->
<div class="w-24 text-end">
<span class="text-xs text-gray-500">{{ http_function.created_at.strftime('%b %d %Y %I:%M
%p').lstrip("0").replace("
0", " ") }}
</span>
</div>
<!-- End Left Content -->
<!-- Icon -->
<div
class="relative last:after:hidden after:absolute after:top-7 after:bottom-0 after:start-3.5 after:w-px after:-translate-x-[0.5px] after:bg-gray-200">
<div class="relative z-10 size-7 flex justify-center items-center">
<div class="size-2 rounded-full bg-gray-400"></div>
</div>
</div>
<!-- End Icon -->
<!-- Right Content -->
<div class="grow pt-0.5 pb-4 cursor-pointer">
<h3 class="flex gap-x-1.5 font-semibold text-gray-800">
v1
</h3>
<p class="mt-1 text-sm text-gray-600">
</p>
<button type="button"
class="mt-1 -ms-1 p-1 inline-flex items-center gap-x-2 text-xs rounded-lg border border-transparent text-gray-500 hover:bg-gray-100 disabled:opacity-50 disabled:pointer-events-none">
Revert
</button>
</div>
<!-- End Right Content -->
</div>
<!-- End Item -->
{% else %}
<!-- Item -->
<div class="flex gap-x-3">
<!-- Left Content -->
<div class="w-24 text-end">
<span class="text-xs text-gray-500">{{ http_function.created_at.strftime('%b %d %Y %I:%M
%p').lstrip("0").replace("
0", " ") }}
</span>
</div>
<!-- End Left Content -->
<!-- Icon -->
<div
class="relative last:after:hidden after:absolute after:top-7 after:bottom-0 after:start-3.5 after:w-px after:-translate-x-[0.5px] after:bg-gray-200">
<div class="relative z-10 size-7 flex justify-center items-center">
<div class="size-2 rounded-full bg-gray-400"></div>
</div>
</div>
<!-- End Icon -->
<!-- Right Content -->
<div class="grow pt-0.5 pb-4 cursor-pointer">
<h3 class="flex gap-x-1.5 font-semibold text-gray-800">
v1
</h3>
<p class="mt-1 text-sm text-gray-600">
</p>
<button type="button"
class="mt-1 -ms-1 p-1 inline-flex items-center gap-x-2 text-xs rounded-lg border border-transparent text-gray-500 hover:bg-gray-100 disabled:opacity-50 disabled:pointer-events-none">
Revert
</button>
</div>
<!-- End Right Content -->
</div>
<!-- End Item -->
{% endif %}
</div>
<!-- End Timeline -->
{% endblock %}

View File

@@ -4,7 +4,8 @@
{{ render_partial('dashboard/http_functions/header.html', title='Logs', user_id=user_id, function_id=function_id,
name=name,
refresh_url=url_for('get_http_function_logs', function_id=function_id), show_edit_form=True, show_client=True) }}
refresh_url=url_for('get_http_function_logs', function_id=function_id), show_edit_form=True, show_client=True,
show_history=True) }}
<div class="block md:grid md:grid-cols-4 md:gap-4 p-4">
<!-- Headers -->