diff --git a/app.py b/app.py index 0541fe8..5216b08 100644 --- a/app.py +++ b/app.py @@ -13,6 +13,7 @@ from dotenv import load_dotenv from routes.timer import timer from routes.test import test from routes.home import home +from routes.http import http from flask_apscheduler import APScheduler import asyncio import aiohttp @@ -37,6 +38,7 @@ init_app(app) app.register_blueprint(timer, url_prefix='/timer') app.register_blueprint(test, url_prefix='/test') app.register_blueprint(home, url_prefix='/home') +app.register_blueprint(http, url_prefix='/dashboard/http_functions') class User(UserMixin): def __init__(self, id, username, password_hash, created_at): @@ -126,140 +128,6 @@ def dashboard(): http_functions = create_http_functions_view_model(http_functions) return render_template("dashboard/http_functions/overview.html", http_functions=http_functions) -@ app.route("/dashboard/http_functions", methods=["GET"]) -@login_required -def dashboard_http_functions(): - user_id = current_user.id - http_functions = db.get_http_functions_for_user(user_id) - http_functions = create_http_functions_view_model(http_functions) - if htmx: - return render_block(app.jinja_env, "dashboard/http_functions/overview.html", "page", http_functions=http_functions) - return render_template("dashboard/http_functions/overview.html", http_functions=http_functions) - - -@ app.route("/dashboard/http_functions/add_form", methods=["GET"]) -@login_required -def get_http_function_add_form(): - user_id = current_user.id - if htmx: - return render_block(app.jinja_env, 'dashboard/http_functions/new.html', 'page', user_id=user_id, name=DEFAULT_FUNCTION_NAME, script=DEFAULT_SCRIPT, environment_info=DEFAULT_ENVIRONMENT, is_public=False, log_request=True, log_response=False) - return render_template("dashboard/http_functions/new.html", user_id=user_id, name=DEFAULT_FUNCTION_NAME, script=DEFAULT_SCRIPT, environment_info=DEFAULT_ENVIRONMENT, is_public=False, log_request=True, log_response=False) - - -@ app.route("/dashboard/http_functions/create", methods=["POST"]) -@login_required -def create_http_function(): - try: - user_id = current_user.id - name = request.form.get('name') - script_content = request.form.get('script_content') - environment_info = request.form.get('environment_info') - is_public = request.form.get('is_public') - log_request = request.form.get('log_request') - log_response = request.form.get('log_response') - - db.create_new_http_function(user_id, name, script_content, environment_info, is_public, log_request, log_response) - - http_functions = db.get_http_functions_for_user(user_id) - http_functions = create_http_functions_view_model(http_functions) - return render_block(app.jinja_env, "dashboard/http_functions/overview.html", "page", http_functions=http_functions), 200, {"HX-Push-Url": url_for('dashboard_http_functions')} - except Exception as e: - print(e) - return { "status": "error", "message": str(e) } - - -@ app.route("/dashboard/http_functions//edit", methods=["POST"]) -@login_required -def edit_http_function(function_id): - try: - user_id = current_user.id - name = request.json.get('name') - script_content = request.json.get('script_content') - environment_info = request.json.get('environment_info') - is_public = request.json.get('is_public') - log_request = request.json.get('log_request') - log_response = request.json.get('log_response') - - updated_version = db.edit_http_function(user_id, function_id, name, script_content, environment_info, is_public, log_request, log_response) - - return { "status": "success", "message": f'{name} updated' } - except Exception as e: - print(e) - return { "status": "error", "message": str(e) } - -@ app.route("/dashboard/http_functions//delete", methods=["DELETE"]) -@login_required -def delete_http_function(function_id): - try: - user_id = current_user.id - db.delete_http_function(user_id, function_id) - - http_functions = db.get_http_functions_for_user(user_id) - http_functions = create_http_functions_view_model(http_functions) - return render_block(app.jinja_env, "dashboard/http_functions/overview.html", "page", http_functions=http_functions), 200, {"HX-Push-Url": url_for('dashboard_http_functions')} - except Exception as e: - return jsonify({"status": 'error', "message": str(e)}), 500 - -@ app.route("/dashboard/http_functions//logs", methods=["GET"]) -@login_required -def get_http_function_logs(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'] - http_function_invocations = db.get_http_function_invocations(function_id) - if htmx: - return render_block(app.jinja_env, 'dashboard/http_functions/logs.html', 'page', user_id=user_id, function_id=function_id, name=name, http_function_invocations=http_function_invocations) - return render_template("dashboard/http_functions/logs.html", user_id=user_id, name=name, function_id=function_id, http_function_invocations=http_function_invocations) - -@ app.route("/dashboard/http_functions//client", methods=["GET"]) -@login_required -def client(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 - - http_function = create_http_function_view_model(http_function) - if htmx: - 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//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'] - original_script = http_function['script_content'] if version_number == 1 else None - - 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 raw_history: - original_script = raw_history[-1]['script_content'] - - 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, original_script=original_script) - 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, original_script=original_script) def _generate_script_from_natural_language(natural_query): """Generates a Javascript function from natural language using Gemini REST API.""" @@ -275,33 +143,68 @@ def _generate_script_from_natural_language(natural_query): prompt = f""" You are an expert Javascript developer. Your task is to write a complete, production-ready Javascript async function based on the user's request. +**Function Signature:** +Your function MUST have the following signature: `async (req, environment) => {{ ... }}` +- `req`: An object containing details about the incoming HTTP request (e.g., `req.method`, `req.headers`, `req.body`, `req.query`). +- `environment`: A mutable JSON object that persists across executions. You can read and write to it to maintain state. + **Environment & Constraints:** - The function will be executed in a simple, sandboxed Javascript environment. -- **DO NOT** use `require()`, `import`, or any other module loading system. The environment does not support it. +- **CRITICAL**: ALL helper functions or variables MUST be defined *inside* the main `async` function. Do not define anything in the global scope. +- **DO NOT** use `require()`, `import`, or any other module loading system. - **DO NOT** access the file system (`fs` module) or make network requests. -- The function must be a single `async` arrow function. -- You have access to a persistent JSON object called `environment`. You can read from and write to it. For example: `environment.my_variable = 'hello'`. -- You also have access to a `console.log()` function for logging. +- You have access to a `console.log()` function for logging. -**Input:** -The function receives one argument: `req`, an object containing details about the incoming HTTP request. It has properties like `req.method`, `req.headers`, `req.body`, `req.query`, etc. - -**Output:** -You must use one of the following helper functions to return a response: +**Response Helpers:** +You must use one of the following functions to return a response: - `HtmlResponse(body)`: Returns an HTML response. -- `JsonResponse(body)`: Returns a JSON response. The `body` will be automatically stringified. +- `JsonResponse(body)`: Returns a JSON response. - `TextResponse(body)`: Returns a plain text response. -- `RedirectResponse(url)`: Redirects the user to a different URL. +- `RedirectResponse(url)`: Redirects the user. -**Example:** +**Complex Example (Tic-Tac-Toe):** ```javascript -async (req) => {{ - if (!environment.counter) {{ - environment.counter = 0; +async (req, environment) => {{ + // Helper function defined INSIDE the main function + function checkWinner(board) {{ + const winConditions = [ + [0, 1, 2], [3, 4, 5], [6, 7, 8], + [0, 3, 6], [1, 4, 7], [2, 5, 8], + [0, 4, 8], [2, 4, 6] + ]; + for (const condition of winConditions) {{ + const [a, b, c] = condition; + if (board[a] && board[a] === board[b] && board[a] === board[c]) {{ + return board[a]; + }} + }} + return null; }} - environment.counter++; - console.log("The counter is now " + environment.counter); - return HtmlResponse(`

Counter: ${{environment.counter}}

`); + + if (!environment.board) {{ + environment.board = Array(9).fill(""); + environment.turn = "X"; + environment.winner = null; + }} + + if (req.method === "POST" && req.json && req.json.move !== undefined) {{ + const move = parseInt(req.json.move); + if (environment.board[move] === "" && !environment.winner) {{ + environment.board[move] = environment.turn; + environment.turn = environment.turn === "X" ? "O" : "X"; + environment.winner = checkWinner(environment.board); + }} + }} + + const boardHTML = environment.board.map((cell, index) => ``).join(""); + const message = environment.winner ? `Player ${{environment.winner}} wins!` : `Turn: ${{environment.turn}}`; + + return HtmlResponse(` + + Tic-Tac-Toe +

${{message}}

${{boardHTML}}
+ + `); }} ``` @@ -414,7 +317,18 @@ def execute_http_function(user_id, function): return jsonify({'error': 'Function not found'}), 404 code = http_function['script_content'] - environment = http_function['environment_info'] + environment_info = http_function['environment_info'] + + # Ensure environment is a dictionary + if isinstance(environment_info, str) and environment_info: + try: + environment = json.loads(environment_info) + except json.JSONDecodeError: + environment = {} + elif isinstance(environment_info, dict): + environment = environment_info + else: + environment = {} is_public = http_function['is_public'] log_request = http_function['log_request'] log_response = http_function['log_response'] @@ -538,129 +452,7 @@ def logout(): logout_user() return redirect(url_for('home')) -@app.route("/http_function_editor/", methods=["GET"]) -@login_required -def http_function_editor(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 - - # Create a view model with all necessary data for the editor - editor_data = { - 'id': http_function['id'], - 'name': http_function['name'], - 'script_content': http_function['script_content'], - 'environment_info': json.dumps(http_function['environment_info'], indent=2), - 'is_public': http_function['is_public'], - 'log_request': http_function['log_request'], - 'log_response': http_function['log_response'], - 'version_number': http_function['version_number'], - 'user_id': user_id, - 'function_id': function_id, - # Add new URLs for navigation - 'cancel_url': url_for('dashboard_http_functions'), - 'edit_url': url_for('http_function_editor', function_id=function_id), - } - if htmx: - return render_block(app.jinja_env, "dashboard/http_functions/editor.html", "page", **editor_data) - - return render_template("dashboard/http_functions/editor.html", **editor_data) - -@app.route("/api/http_functions", methods=["POST"]) -@login_required -def api_create_http_function(): - try: - user_id = current_user.id - data = request.get_json() - name = data.get('name') - script_content = data.get('script_content') - environment_info = data.get('environment_info') - is_public = data.get('is_public') - log_request = data.get('log_request') - log_response = data.get('log_response') - - # Check if function with same name already exists for this user - existing_function = db.get_http_function(user_id, name) - if existing_function: - return jsonify({ - "status": "error", - "message": f"A function with the name '{name}' already exists" - }), 400 - - http_function = db.create_new_http_function( - user_id, - name, - script_content, - environment_info, - is_public, - log_request, - log_response - ) - - return jsonify({ - "status": "success", - "message": f'{name} created', - "function": http_function - }) - except Exception as e: - return jsonify({ - "status": "error", - "message": str(e) - }), 400 - -@app.route("/api/http_functions/", methods=["POST"]) -@login_required -def api_update_http_function(function_id): - try: - user_id = current_user.id - data = request.get_json() - name = data.get('name') - script_content = data.get('script_content') - environment_info = data.get('environment_info') - is_public = data.get('is_public') - log_request = data.get('log_request') - log_response = data.get('log_response') - - updated_function = db.edit_http_function( - user_id, - function_id, - name, - script_content, - environment_info, - is_public, - log_request, - log_response - ) - - return jsonify({ - "status": "success", - "message": f'{name} updated', - "function": updated_function - }) - except Exception as e: - return jsonify({ - "status": "error", - "message": str(e) - }), 400 - -@app.route("/api/http_functions/", methods=["DELETE"]) -@login_required -def api_delete_http_function(function_id): - try: - user_id = current_user.id - db.delete_http_function(user_id, function_id) - - return jsonify({ - "status": "success", - "message": "Function deleted successfully" - }) - except Exception as e: - return jsonify({ - "status": "error", - "message": str(e) - }), 400 @login_manager.user_loader def load_user(user_id): diff --git a/routes/http.py b/routes/http.py new file mode 100644 index 0000000..ea9e5ca --- /dev/null +++ b/routes/http.py @@ -0,0 +1,360 @@ +from flask import Blueprint, render_template, redirect, url_for, flash, request, jsonify +from jinja2_fragments import render_block +from werkzeug.security import generate_password_hash, check_password_hash +from flask_login import current_user, login_required +from extensions import db, htmx, environment +from datetime import datetime, timezone, timedelta +import json +from services import create_http_function_view_model, create_http_functions_view_model + +''' +CREATE TABLE http_function_versions ( + id SERIAL PRIMARY KEY, + http_function_id INT NOT NULL, + script_content TEXT NOT NULL, + version_number INT NOT NULL, + versioned_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + + CONSTRAINT fk_http_function_versions + FOREIGN KEY (http_function_id) + REFERENCES http_functions (id) + ON DELETE CASCADE + ON UPDATE CASCADE +); + +CREATE OR REPLACE FUNCTION fn_http_functions_versioning() +RETURNS TRIGGER +LANGUAGE plpgsql +AS $$ +DECLARE + next_version INT; +BEGIN + IF TG_OP = 'INSERT' THEN + INSERT INTO http_function_versions (http_function_id, script_content, version_number) + VALUES (NEW.id, NEW.script_content, 1); + + UPDATE http_functions + SET version_number = 1 + WHERE id = NEW.id; + + RETURN NEW; + + ELSIF TG_OP = 'UPDATE' THEN + IF NEW.script_content IS DISTINCT FROM OLD.script_content THEN + SELECT COALESCE(MAX(version_number), 0) + 1 + INTO next_version + FROM http_function_versions + WHERE http_function_id = NEW.id; + + INSERT INTO http_function_versions (http_function_id, script_content, version_number) + VALUES (NEW.id, NEW.script_content, next_version); + + UPDATE http_functions + SET version_number = next_version + WHERE id = NEW.id; + END IF; + + RETURN NEW; + END IF; + + RETURN NEW; +END; +$$; + +CREATE TRIGGER tr_http_functions_versioning +AFTER INSERT OR UPDATE +ON http_functions +FOR EACH ROW +EXECUTE PROCEDURE fn_http_functions_versioning(); +''' + +DEFAULT_SCRIPT = """async (req) => { + console.log(`Method:${req.method}`) + console.log(`Generating ${environment.lines} random lines...`) + let svgContent = ""; + + for (let i = 0; i < environment.lines; i++) { + console.log(i) + let pathD = "M2 " + Math.random() * 79; + let circles = ""; + + for (let x = 12; x <= 202; x += 10) { + let y = Math.random() * 79 + pathD += ` L${x} ${y}`; + circles += ``; + } + + let pathColor = `rgb(${Math.random() * 255}, ${Math.random() * 255}, ${Math.random() * 255})`; + svgContent += ` + + + ${circles} + `; + } + + return HtmlResponse(`${svgContent}`); +}""" + +DEFAULT_ENVIRONMENT = """{ + "lines": 3 +}""" + +http = Blueprint('http', __name__) + +@http.route("/", methods=["GET"]) +@login_required +def dashboard_http_functions(): + user_id = current_user.id + http_functions = db.get_http_functions_for_user(user_id) + http_functions = create_http_functions_view_model(http_functions) + if htmx: + return render_block(environment, "dashboard/http_functions/overview.html", "page", http_functions=http_functions) + return render_template("dashboard/http_functions/overview.html", http_functions=http_functions) + + +@http.route("/add_form", methods=["GET"]) +@login_required +def get_http_function_add_form(): + user_id = current_user.id + if htmx: + return render_block(environment, 'dashboard/http_functions/new.html', 'page', user_id=user_id, name='foo', script=DEFAULT_SCRIPT, environment_info=DEFAULT_ENVIRONMENT, is_public=False, log_request=True, log_response=False) + return render_template("dashboard/http_functions/new.html", user_id=user_id, name='foo', script=DEFAULT_SCRIPT, environment_info=DEFAULT_ENVIRONMENT, is_public=False, log_request=True, log_response=False) + + +@http.route("/create", methods=["POST"]) +@login_required +def create_http_function(): + try: + user_id = current_user.id + name = request.form.get('name') + script_content = request.form.get('script_content') + environment_info = request.form.get('environment_info') + is_public = request.form.get('is_public') + log_request = request.form.get('log_request') + log_response = request.form.get('log_response') + + db.create_new_http_function(user_id, name, script_content, environment_info, is_public, log_request, log_response) + + http_functions = db.get_http_functions_for_user(user_id) + http_functions = create_http_functions_view_model(http_functions) + return render_block(environment, "dashboard/http_functions/overview.html", "page", http_functions=http_functions), 200, {"HX-Push-Url": url_for('http.dashboard_http_functions')} + except Exception as e: + print(e) + return { "status": "error", "message": str(e) } + + +@http.route("//edit", methods=["POST"]) +@login_required +def edit_http_function(function_id): + try: + user_id = current_user.id + name = request.json.get('name') + script_content = request.json.get('script_content') + environment_info = request.json.get('environment_info') + is_public = request.json.get('is_public') + log_request = request.json.get('log_request') + log_response = request.json.get('log_response') + + updated_version = db.edit_http_function(user_id, function_id, name, script_content, environment_info, is_public, log_request, log_response) + + return { "status": "success", "message": f'{name} updated' } + except Exception as e: + print(e) + return { "status": "error", "message": str(e) } + +@http.route("//delete", methods=["DELETE"]) +@login_required +def delete_http_function(function_id): + try: + user_id = current_user.id + db.delete_http_function(user_id, function_id) + + http_functions = db.get_http_functions_for_user(user_id) + http_functions = create_http_functions_view_model(http_functions) + return render_block(environment, "dashboard/http_functions/overview.html", "page", http_functions=http_functions), 200, {"HX-Push-Url": url_for('http.dashboard_http_functions')} + except Exception as e: + return jsonify({"status": 'error', "message": str(e)}), 500 + +@http.route("//logs", methods=["GET"]) +@login_required +def get_http_function_logs(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'] + http_function_invocations = db.get_http_function_invocations(function_id) + if htmx: + return render_block(environment, 'dashboard/http_functions/logs.html', 'page', user_id=user_id, function_id=function_id, name=name, http_function_invocations=http_function_invocations) + return render_template("dashboard/http_functions/logs.html", user_id=user_id, name=name, function_id=function_id, http_function_invocations=http_function_invocations) + +@http.route("//client", methods=["GET"]) +@login_required +def client(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 + + http_function = create_http_function_view_model(http_function) + if htmx: + return render_block(environment, '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) + +@http.route("//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'] + original_script = http_function['script_content'] if version_number == 1 else None + + 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 raw_history: + original_script = raw_history[-1]['script_content'] + + if htmx: + return render_block(environment, '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, original_script=original_script) + 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, original_script=original_script) + +@http.route("/editor/", methods=["GET"]) +@login_required +def http_function_editor(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 + + # Create a view model with all necessary data for the editor + editor_data = { + 'id': http_function['id'], + 'name': http_function['name'], + 'script_content': http_function['script_content'], + 'environment_info': json.dumps(http_function['environment_info'], indent=2), + 'is_public': http_function['is_public'], + 'log_request': http_function['log_request'], + 'log_response': http_function['log_response'], + 'version_number': http_function['version_number'], + 'user_id': user_id, + 'function_id': function_id, + # Add new URLs for navigation + 'cancel_url': url_for('http.dashboard_http_functions'), + 'edit_url': url_for('http.http_function_editor', function_id=function_id), + } + + if htmx: + return render_block(environment, "dashboard/http_functions/editor.html", "page", **editor_data) + + return render_template("dashboard/http_functions/editor.html", **editor_data) +@http.route("/api", methods=["POST"]) +@login_required +def api_create_http_function(): + try: + user_id = current_user.id + data = request.get_json() + name = data.get('name') + script_content = data.get('script_content') + environment_info = data.get('environment_info') + is_public = data.get('is_public') + log_request = data.get('log_request') + log_response = data.get('log_response') + + # Check if function with same name already exists for this user + existing_function = db.get_http_function(user_id, name) + if existing_function: + return jsonify({ + "status": "error", + "message": f"A function with the name '{name}' already exists" + }), 400 + + http_function = db.create_new_http_function( + user_id, + name, + script_content, + environment_info, + is_public, + log_request, + log_response + ) + + return jsonify({ + "status": "success", + "message": f'{name} created', + "function": http_function + }) + except Exception as e: + return jsonify({ + "status": "error", + "message": str(e) + }), 400 + +@http.route("/api/", methods=["POST"]) +@login_required +def api_update_http_function(function_id): + try: + user_id = current_user.id + data = request.get_json() + name = data.get('name') + script_content = data.get('script_content') + environment_info = data.get('environment_info') + is_public = data.get('is_public') + log_request = data.get('log_request') + log_response = data.get('log_response') + + updated_function = db.edit_http_function( + user_id, + function_id, + name, + script_content, + environment_info, + is_public, + log_request, + log_response + ) + + return jsonify({ + "status": "success", + "message": f'{name} updated', + "function": updated_function + }) + except Exception as e: + return jsonify({ + "status": "error", + "message": str(e) + }), 400 + +@http.route("/api/", methods=["DELETE"]) +@login_required +def api_delete_http_function(function_id): + try: + user_id = current_user.id + db.delete_http_function(user_id, function_id) + + return jsonify({ + "status": "success", + "message": "Function deleted successfully" + }) + except Exception as e: + return jsonify({ + "status": "error", + "message": str(e) + }), 400 \ No newline at end of file diff --git a/static/js/mithril/FunctionHistory.js b/static/js/mithril/FunctionHistory.js new file mode 100644 index 0000000..495fef3 --- /dev/null +++ b/static/js/mithril/FunctionHistory.js @@ -0,0 +1,96 @@ +const FunctionHistory = { + oninit: function(vnode) { + // Initialize indices in state + vnode.state.leftIndex = vnode.attrs.versions.length - 1; // Earliest + vnode.state.rightIndex = 0; // Latest + }, + view: function (vnode) { + const versions = vnode.attrs.versions; + return m("div", [ + m("div.flex.gap-4.mb-4", [ + m("div.flex-1", [ + m("label.block.text-sm.font-medium.text-gray-700", "Left Version"), + m("select.mt-1.block.w-full.rounded-md.border-gray-300.shadow-sm", { + onchange: (e) => { + vnode.state.leftIndex = parseInt(e.target.value); + vnode.state.leftVersion = versions[vnode.state.leftIndex]; + vnode.state.updateDiff(); + }, + value: vnode.state.leftIndex + }, versions.map((v, idx) => + m("option", { value: idx }, `Version ${v.version_number} (${new Date(v.versioned_at).toLocaleString()})`) + )) + ]), + m("div.flex-1", [ + m("label.block.text-sm.font-medium.text-gray-700", "Right Version"), + m("select.mt-1.block.w-full.rounded-md.border-gray-300.shadow-sm", { + onchange: (e) => { + vnode.state.rightIndex = parseInt(e.target.value); + vnode.state.rightVersion = versions[vnode.state.rightIndex]; + vnode.state.updateDiff(); + }, + value: vnode.state.rightIndex + }, versions.map((v, idx) => + m("option", { value: idx }, `Version ${v.version_number} (${new Date(v.versioned_at).toLocaleString()})`) + )) + ]) + ]), + m("div", { + id: "diff-container", + style: "height: 500px; position: relative;" + }) + ]); + }, + oncreate: function (vnode) { + const versions = vnode.attrs.versions; + // Initialize with the earliest and most recent versions if available + if (versions.length >= 2) { + vnode.state.leftVersion = versions[versions.length - 1]; // Earliest version + vnode.state.rightVersion = versions[0]; // Latest version + } else if (versions.length === 1) { + vnode.state.leftVersion = versions[0]; + vnode.state.rightVersion = versions[0]; + } + + vnode.state.updateDiff = function () { + if (vnode.state.aceDiffer) { + // Clean up previous instance + vnode.state.aceDiffer.destroy(); + } + + vnode.state.aceDiffer = new AceDiff({ + element: '#diff-container', + mode: "ace/mode/javascript", + left: { + content: vnode.state.leftVersion ? vnode.state.leftVersion.script : "", + editable: false, + }, + right: { + content: vnode.state.rightVersion ? vnode.state.rightVersion.script : "", + editable: false, + } + }); + + // Configure both editors + const editors = vnode.state.aceDiffer.getEditors(); + ['left', 'right'].forEach(side => { + editors[side].setOptions({ + maxLines: 20, + autoScrollEditorIntoView: true, + }); + editors[side].session.setOption("useWorker", false); + }); + }; + + // Initial diff setup + vnode.state.updateDiff(); + }, + onremove: function (vnode) { + // Clean up AceDiff when component is removed + if (vnode.state.aceDiffer) { + vnode.state.aceDiffer.destroy(); + } + } +}; + +export default FunctionHistory; diff --git a/templates/dashboard.html b/templates/dashboard.html index 09d3165..d321315 100644 --- a/templates/dashboard.html +++ b/templates/dashboard.html @@ -33,7 +33,7 @@ Home diff --git a/templates/dashboard/home.html b/templates/dashboard/home.html index c8681fb..5fa06c7 100644 --- a/templates/dashboard/home.html +++ b/templates/dashboard/home.html @@ -270,7 +270,7 @@ class="inline-flex items-center justify-center px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-lg transition-colors duration-200"> View Timer Functions - View HTTP Functions diff --git a/templates/dashboard/http_functions/client.html b/templates/dashboard/http_functions/client.html index d3a7594..d6f1f1f 100644 --- a/templates/dashboard/http_functions/client.html +++ b/templates/dashboard/http_functions/client.html @@ -8,10 +8,10 @@ show_edit_form=True, show_logs=True, show_client=True, show_history=True, -edit_url=url_for('http_function_editor', function_id=function_id), -cancel_url=url_for('dashboard_http_functions'), -logs_url=url_for('get_http_function_logs', function_id=function_id), -history_url=url_for('get_http_function_history', function_id=function_id)) }} +edit_url=url_for('http.http_function_editor', function_id=function_id), +cancel_url=url_for('http.dashboard_http_functions'), +logs_url=url_for('http.get_http_function_logs', function_id=function_id), +history_url=url_for('http.get_http_function_history', function_id=function_id)) }}
diff --git a/templates/dashboard/http_functions/editor.html b/templates/dashboard/http_functions/editor.html index 9a09b16..c1f536e 100644 --- a/templates/dashboard/http_functions/editor.html +++ b/templates/dashboard/http_functions/editor.html @@ -10,8 +10,8 @@ show_client=True, show_history=True, edit_url=edit_url, cancel_url=cancel_url, -logs_url=url_for('get_http_function_logs', function_id=function_id), -history_url=url_for('get_http_function_history', function_id=function_id)) }} +logs_url=url_for('http.get_http_function_logs', function_id=function_id), +history_url=url_for('http.get_http_function_history', function_id=function_id)) }}
@@ -33,8 +33,8 @@ history_url=url_for('get_http_function_history', function_id=function_id)) }} logResponse: {{ log_response | tojson }}, versionNumber: {{ version_number }}, executeUrl: "{{ url_for('execute_code', playground='true') }}", - saveUrl: "{{ url_for('api_update_http_function', function_id=id) if id else url_for('api_create_http_function') }}", - deleteUrl: "{{ url_for('api_delete_http_function', function_id=id) if id else '' }}", + saveUrl: "{{ url_for('http.api_update_http_function', function_id=id) if id else url_for('http.api_create_http_function') }}", + deleteUrl: "{{ url_for('http.api_delete_http_function', function_id=id) if id else '' }}", showDeleteButton: true }) }) diff --git a/templates/dashboard/http_functions/header.html b/templates/dashboard/http_functions/header.html index 6ad139d..2b45706 100644 --- a/templates/dashboard/http_functions/header.html +++ b/templates/dashboard/http_functions/header.html @@ -42,8 +42,8 @@ {% if show_client|default(false, true) %}