Add community section where public functions can be viewed

This commit is contained in:
Peter Stockings
2025-11-21 10:30:14 +11:00
parent 8eb9b7dceb
commit 213abbfe93
10 changed files with 365 additions and 10 deletions

2
app.py
View File

@@ -17,6 +17,7 @@ from routes.http import http
from routes.llm import llm
from routes.auth import auth
from routes.settings import settings
from routes.community import community
from constants import DEFAULT_FUNCTION_NAME, DEFAULT_SCRIPT, DEFAULT_ENVIRONMENT
from flask_apscheduler import APScheduler
import asyncio
@@ -45,6 +46,7 @@ app.register_blueprint(http, url_prefix='/http')
app.register_blueprint(llm, url_prefix='/llm')
app.register_blueprint(auth, url_prefix='/auth')
app.register_blueprint(settings, url_prefix='/settings')
app.register_blueprint(community, url_prefix='/community')
# Swith to inter app routing, which results in speed up from ~400ms to ~270ms
# https://stackoverflow.com/questions/76886643/linking-two-not-exposed-dokku-apps

68
db.py
View File

@@ -61,37 +61,55 @@ class DataBase():
if search_query:
search_pattern = f"%{search_query}%"
http_functions = self.execute(
'SELECT id, user_id, NAME, path, script_content, invoked_count, environment_info, is_public, log_request, log_response, version_number, runtime FROM http_functions WHERE user_id=%s AND (NAME ILIKE %s OR path ILIKE %s) ORDER by id DESC',
'SELECT id, user_id, NAME, path, script_content, invoked_count, environment_info, is_public, log_request, log_response, version_number, runtime, description FROM http_functions WHERE user_id=%s AND (NAME ILIKE %s OR path ILIKE %s) ORDER by id DESC',
[user_id, search_pattern, search_pattern]
)
else:
http_functions = self.execute(
'SELECT id, user_id, NAME, path, script_content, invoked_count, environment_info, is_public, log_request, log_response, version_number, runtime FROM http_functions WHERE user_id=%s ORDER by id DESC',
'SELECT id, user_id, NAME, path, script_content, invoked_count, environment_info, is_public, log_request, log_response, version_number, runtime, description FROM http_functions WHERE user_id=%s ORDER by id DESC',
[user_id]
)
return http_functions
def get_public_http_functions(self, search_query=None):
if search_query:
search_pattern = f"%{search_query}%"
http_functions = self.execute(
'SELECT h.id, h.user_id, h.NAME, h.path, h.script_content, h.invoked_count, h.environment_info, h.is_public, h.log_request, h.log_response, h.version_number, h.runtime, h.description, h.created_at, u.username FROM http_functions h JOIN users u ON h.user_id = u.id WHERE h.is_public=TRUE AND (h.NAME ILIKE %s OR h.description ILIKE %s) ORDER by h.created_at DESC',
[search_pattern, search_pattern]
)
else:
http_functions = self.execute(
'SELECT h.id, h.user_id, h.NAME, h.path, h.script_content, h.invoked_count, h.environment_info, h.is_public, h.log_request, h.log_response, h.version_number, h.runtime, h.description, h.created_at, u.username FROM http_functions h JOIN users u ON h.user_id = u.id WHERE h.is_public=TRUE ORDER by h.created_at DESC'
)
return http_functions
def get_http_function(self, user_id, name):
http_function = self.execute(
'SELECT id, user_id, NAME, path, script_content, invoked_count, environment_info, is_public, log_request, log_response, version_number, created_at, runtime FROM http_functions WHERE user_id=%s AND NAME=%s', [user_id, name], one=True)
'SELECT id, user_id, NAME, path, script_content, invoked_count, environment_info, is_public, log_request, log_response, version_number, created_at, runtime, description 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, path, script_content, invoked_count, environment_info, is_public, log_request, log_response, version_number, created_at, runtime FROM http_functions WHERE user_id=%s AND id=%s', [user_id, http_function_id], one=True)
'SELECT id, user_id, NAME, path, script_content, invoked_count, environment_info, is_public, log_request, log_response, version_number, created_at, runtime, description 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, path, script_content, environment_info, is_public, log_request, log_response, runtime):
def get_public_http_function_by_id(self, http_function_id):
http_function = self.execute(
'SELECT h.id, h.user_id, h.NAME, h.path, h.script_content, h.invoked_count, h.environment_info, h.is_public, h.log_request, h.log_response, h.version_number, h.created_at, h.runtime, h.description, u.username FROM http_functions h JOIN users u ON h.user_id = u.id WHERE h.id=%s AND h.is_public=TRUE', [http_function_id], one=True)
return http_function
def create_new_http_function(self, user_id, name, path, script_content, environment_info, is_public, log_request, log_response, runtime, description=""):
self.execute(
'INSERT INTO http_functions (user_id, NAME, path, script_content, environment_info, is_public, log_request, log_response, runtime) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)',
[user_id, name, path, script_content, environment_info, is_public, log_request, log_response, runtime],
'INSERT INTO http_functions (user_id, NAME, path, script_content, environment_info, is_public, log_request, log_response, runtime, description) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)',
[user_id, name, path, script_content, environment_info, is_public, log_request, log_response, runtime, description],
commit=True
)
def edit_http_function(self, user_id, function_id, name, path, script_content, environment_info, is_public, log_request, log_response, runtime):
def edit_http_function(self, user_id, function_id, name, path, script_content, environment_info, is_public, log_request, log_response, runtime, description=""):
updated_version = self.execute(
'UPDATE http_functions SET NAME=%s, path=%s, script_content=%s, environment_info=%s, is_public=%s, log_request=%s, log_response=%s, runtime=%s WHERE user_id=%s AND id=%s RETURNING version_number',
[name, path, script_content, environment_info, is_public, log_request, log_response, runtime, user_id, function_id],
'UPDATE http_functions SET NAME=%s, path=%s, script_content=%s, environment_info=%s, is_public=%s, log_request=%s, log_response=%s, runtime=%s, description=%s WHERE user_id=%s AND id=%s RETURNING version_number',
[name, path, script_content, environment_info, is_public, log_request, log_response, runtime, description, user_id, function_id],
commit=True, one=True
)
return updated_version
@@ -116,6 +134,36 @@ WHERE http_function_id=%s
ORDER BY invocation_time DESC""", [http_function_id])
return http_function_invocations
def fork_http_function(self, user_id, function_id):
# Get the original function
original = self.execute(
'SELECT NAME, path, script_content, environment_info, runtime, description FROM http_functions WHERE id=%s',
[function_id],
one=True
)
if not original:
raise Exception("Function not found")
new_name = original['name']
# Check if name exists for this user
exists = self.execute('SELECT 1 FROM http_functions WHERE user_id=%s AND NAME=%s', [user_id, new_name], one=True)
if exists:
new_name = f"{new_name}-fork"
self.create_new_http_function(
user_id,
new_name,
original['path'],
original['script_content'],
original['environment_info'],
False, # is_public
True, # log_request
False, # log_response
original['runtime'],
original['description']
)
return new_name
def get_user(self, user_id):
user = self.execute(
'SELECT id, username, password_hash, created_at FROM users WHERE id=%s', [int(user_id)], one=True)

50
routes/community.py Normal file
View File

@@ -0,0 +1,50 @@
from flask import Blueprint, render_template, request, redirect, url_for, flash, jsonify
from flask_login import login_required, current_user
from extensions import db, environment, htmx
from jinja2_fragments import render_block
import json
community = Blueprint('community', __name__)
@community.route("/", methods=["GET"])
@login_required
def index():
search_query = request.args.get("q", "")
public_functions = db.get_public_http_functions(search_query)
if htmx:
return render_block(
environment,
"community/index.html",
"page",
public_functions=public_functions,
search_query=search_query
)
return render_template("community/index.html", public_functions=public_functions, search_query=search_query)
@community.route("/<int:function_id>", methods=["GET"])
@login_required
def view(function_id):
function = db.get_public_http_function_by_id(function_id)
if not function:
flash("Function not found or not public", "error")
return redirect(url_for("community.index"))
# Format environment info for display
if function.get('environment_info'):
function['environment_info'] = json.dumps(function['environment_info'], indent=2)
if htmx:
return render_block(environment, "community/view.html", "page", function=function)
return render_template("community/view.html", function=function)
@community.route("/fork/<int:function_id>", methods=["POST"])
@login_required
def fork(function_id):
try:
user_id = current_user.id
new_name = db.fork_http_function(user_id, function_id)
flash(f"Function forked as '{new_name}'", "success")
return jsonify({"status": "success", "redirect": url_for("http.overview")})
except Exception as e:
return jsonify({"status": "error", "message": str(e)}), 400

View File

@@ -166,6 +166,7 @@ def new():
"is_public": False,
"log_request": True,
"log_response": False,
"description": "",
}
if htmx:
return render_block(
@@ -181,6 +182,7 @@ def new():
log_request = request.json.get("log_request")
log_response = request.json.get("log_response")
runtime = request.json.get("runtime", "node")
description = request.json.get("description", "")
db.create_new_http_function(
user_id,
@@ -192,6 +194,7 @@ def new():
log_request,
log_response,
runtime,
description
)
return (
@@ -218,6 +221,7 @@ def edit(function_id):
log_request = request.json.get("log_request")
log_response = request.json.get("log_response")
runtime = request.json.get("runtime", "node")
description = request.json.get("description", "")
updated_version = db.edit_http_function(
user_id,
@@ -230,6 +234,7 @@ def edit(function_id):
log_request,
log_response,
runtime,
description
)
return {"status": "success", "message": f"{name} updated"}
@@ -374,6 +379,7 @@ def editor(function_id):
"log_response": http_function["log_response"],
"version_number": http_function["version_number"],
"runtime": http_function.get("runtime", "node"),
"description": http_function.get("description", ""),
"user_id": user_id,
"function_id": function_id,
# Add new URLs for navigation

View File

@@ -4,6 +4,7 @@ def create_http_function_view_model(http_function):
"user_id": http_function['user_id'],
"name": http_function['name'],
"path": http_function['path'],
"description": http_function['description'],
"runtime": http_function['runtime'],
"script_content": http_function['script_content'],
"invoked_count": http_function['invoked_count'],

View File

@@ -16,6 +16,7 @@ const Editor = {
this.name = vnode.attrs.name || "foo";
this.path = vnode.attrs.path || "";
this.versionNumber = vnode.attrs.versionNumber || "1";
this.description = vnode.attrs.description || "";
this.nameEditing = false;
this.pathEditing = false;
this.jsValue = vnode.attrs.jsValue || "";
@@ -131,6 +132,7 @@ const Editor = {
log_request: this.logRequest,
log_response: this.logResponse,
runtime: this.runtime,
description: this.description,
};
payload = this.isTimer
@@ -145,6 +147,7 @@ const Editor = {
: null,
run_date: this.triggerType === "date" ? this.runDate : null,
is_enabled: this.isEnabled,
description: this.description,
}
: {
name: this.name,
@@ -155,6 +158,7 @@ const Editor = {
log_request: this.logRequest,
log_response: this.logResponse,
runtime: this.runtime,
description: this.description,
};
const response = await m.request({
@@ -603,6 +607,16 @@ const Editor = {
this.showFunctionSettings &&
m("div", { class: "bg-gray-100 dark:bg-gray-800 p-4 border-b" }, [
m("div", { class: "flex flex-col space-y-4" }, [
m("div", { class: "flex flex-col space-y-2" }, [
m("label", { class: "text-sm font-medium text-gray-700 dark:text-gray-300" }, "Description"),
m("textarea", {
class: "w-full p-2 border rounded bg-white dark:bg-gray-700 dark:border-gray-600 text-sm",
rows: 2,
placeholder: "Describe what this function does...",
value: this.description,
oninput: (e) => (this.description = e.target.value)
})
]),
m("div", { class: "flex flex-wrap gap-6" }, [
this.showPublicToggle &&
m(

View File

@@ -0,0 +1,70 @@
{% extends 'dashboard.html' %}
{% block page %}
<div class="flex flex-col space-y-6">
<div class="flex items-center justify-between">
<h1 class="text-2xl font-bold tracking-tight text-gray-900 dark:text-white">Community Library</h1>
<div class="w-full max-w-sm">
<form class="relative" hx-get="{{ url_for('community.index') }}" hx-target="#container" hx-push-url="true">
<div class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
<svg aria-hidden="true" class="w-5 h-5 text-gray-500 dark:text-gray-400" fill="none"
stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
</svg>
</div>
<input type="search" name="q"
class="block w-full p-2 pl-10 text-sm text-gray-900 border border-gray-300 rounded-lg bg-gray-50 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
placeholder="Search functions..." value="{{ search_query }}">
</form>
</div>
</div>
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
{% for function in public_functions %}
<div
class="bg-white border border-gray-200 rounded-lg shadow dark:bg-gray-800 dark:border-gray-700 hover:shadow-lg transition-shadow duration-200">
<div class="p-5">
<div class="flex justify-between items-start mb-2">
<h5 class="text-xl font-bold tracking-tight text-gray-900 dark:text-white truncate"
title="{{ function.name }}">{{ function.name }}</h5>
<span
class="bg-blue-100 text-blue-800 text-xs font-medium px-2.5 py-0.5 rounded dark:bg-blue-900 dark:text-blue-300">{{
function.runtime }}</span>
</div>
<p class="mb-3 font-normal text-gray-700 dark:text-gray-400 text-sm line-clamp-2 h-10">
{{ function.description or "No description provided." }}
</p>
<div class="flex items-center justify-between text-xs text-gray-500 dark:text-gray-400 mb-4">
<span class="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="w-4 h-4 mr-1">
<path stroke-linecap="round" stroke-linejoin="round"
d="M15.75 6a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0ZM4.501 20.118a7.5 7.5 0 0 1 14.998 0A17.933 17.933 0 0 1 12 21.75c-2.676 0-5.216-.584-7.499-1.632Z" />
</svg>
{{ function.username }}
</span>
<span>{{ function.created_at.strftime('%Y-%m-%d') }}</span>
</div>
<a href="#" hx-get="{{ url_for('community.view', function_id=function.id) }}" hx-target="#container"
hx-push-url="true"
class="inline-flex items-center px-3 py-2 text-sm font-medium text-center text-white bg-blue-700 rounded-lg hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800 w-full justify-center">
View Details
<svg aria-hidden="true" class="w-4 h-4 ml-2 -mr-1" fill="currentColor" viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd"
d="M10.293 3.293a1 1 0 011.414 0l6 6a1 1 0 010 1.414l-6 6a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-4.293-4.293a1 1 0 010-1.414z"
clip-rule="evenodd"></path>
</svg>
</a>
</div>
</div>
{% else %}
<div class="col-span-full text-center py-10 text-gray-500 dark:text-gray-400">
<p class="text-lg">No public functions found.</p>
<p class="text-sm">Be the first to publish one!</p>
</div>
{% endfor %}
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,152 @@
{% extends 'dashboard.html' %}
{% block page %}
<div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<!-- Back Link -->
<div class="mb-6">
<a href="#" hx-get="{{ url_for('community.index') }}" hx-target="#container" hx-push-url="true"
class="inline-flex items-center text-sm font-medium text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="w-4 h-4 mr-1">
<path stroke-linecap="round" stroke-linejoin="round" d="M10.5 19.5 3 12m0 0 7.5-7.5M3 12h18" />
</svg>
Back to Community Library
</a>
</div>
<!-- Header Section -->
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700 p-6 mb-6">
<div class="flex flex-col md:flex-row md:items-start md:justify-between gap-4">
<div class="flex-1 min-w-0">
<div class="flex items-center gap-3 mb-2">
<h1 class="text-3xl font-bold text-gray-900 dark:text-white tracking-tight">{{ function.name }}</h1>
<span
class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300 border border-blue-200 dark:border-blue-800 uppercase tracking-wide">
{{ function.runtime }}
</span>
</div>
<div class="flex flex-wrap items-center gap-4 text-sm text-gray-500 dark:text-gray-400">
<div class="flex items-center gap-1.5">
<div class="p-1 rounded-full bg-gray-100 dark:bg-gray-700">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"
class="w-4 h-4 text-gray-600 dark:text-gray-300">
<path fill-rule="evenodd"
d="M7.5 6a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM3.751 20.105a8.25 8.25 0 0 1 16.498 0 .75.75 0 0 1-.437.695A18.683 18.683 0 0 1 12 22.5c-2.786 0-5.433-.608-7.812-1.7a.75.75 0 0 1-.437-.695Z"
clip-rule="evenodd" />
</svg>
</div>
<span class="font-medium text-gray-700 dark:text-gray-300">{{ function.username }}</span>
</div>
<span class="hidden sm:inline text-gray-300 dark:text-gray-600"></span>
<div class="flex items-center gap-1.5">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="w-4 h-4">
<path stroke-linecap="round" stroke-linejoin="round"
d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 0 1 2.25-2.25h13.5A2.25 2.25 0 0 1 21 7.5v11.25m-18 0h18M5.25 12h13.5h-13.5Zm0 3.75h13.5h-13.5Z" />
</svg>
<span>Published on {{ function.created_at.strftime('%B %d, %Y') }}</span>
</div>
</div>
{% if function.description %}
<div class="mt-4 prose prose-sm dark:prose-invert max-w-none text-gray-600 dark:text-gray-300">
<p class="whitespace-pre-wrap">{{ function.description }}</p>
</div>
{% endif %}
</div>
<div class="flex-shrink-0">
<button hx-post="{{ url_for('community.fork', function_id=function.id) }}" hx-target="#container"
class="inline-flex items-center justify-center px-5 py-2.5 text-sm font-medium text-white transition-all bg-green-600 border border-transparent rounded-lg shadow-sm hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 dark:focus:ring-offset-gray-900">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="w-5 h-5 mr-2 -ml-1">
<path stroke-linecap="round" stroke-linejoin="round"
d="M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 0 1-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 0 1 1.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 0 0-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 0 1-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 0 0-3.375-3.375h-1.5a1.125 1.125 0 0 1-1.125-1.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H9.75" />
</svg>
Fork to My Library
</button>
</div>
</div>
</div>
<!-- Tabs & Content -->
<div
class="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden">
<div class="border-b border-gray-200 dark:border-gray-700">
<nav class="flex -mb-px" aria-label="Tabs">
<button onclick="switchTab('code')" id="tab-code"
class="w-1/2 py-4 px-1 text-center border-b-2 font-medium text-sm focus:outline-none transition-colors border-blue-500 text-blue-600 dark:text-blue-400">
Function Code
</button>
<button onclick="switchTab('env')" id="tab-env"
class="w-1/2 py-4 px-1 text-center border-b-2 font-medium text-sm focus:outline-none transition-colors border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300">
Environment Config
</button>
</nav>
</div>
<div class="p-0">
<div id="content-code" class="block">
<div id="code-editor" class="h-[500px] w-full"></div>
</div>
<div id="content-env" class="hidden">
<div id="env-editor" class="h-[500px] w-full"></div>
</div>
</div>
</div>
</div>
<script>
// Tab Switching Logic
window.switchTab = function (tabName) {
// Update Tab Styles
const tabs = ['code', 'env'];
tabs.forEach(t => {
const btn = document.getElementById(`tab-${t}`);
const content = document.getElementById(`content-${t}`);
if (t === tabName) {
btn.classList.remove('border-transparent', 'text-gray-500', 'hover:text-gray-700', 'hover:border-gray-300', 'dark:text-gray-400', 'dark:hover:text-gray-300');
btn.classList.add('border-blue-500', 'text-blue-600', 'dark:text-blue-400');
content.classList.remove('hidden');
content.classList.add('block');
} else {
btn.classList.add('border-transparent', 'text-gray-500', 'hover:text-gray-700', 'hover:border-gray-300', 'dark:text-gray-400', 'dark:hover:text-gray-300');
btn.classList.remove('border-blue-500', 'text-blue-600', 'dark:text-blue-400');
content.classList.remove('block');
content.classList.add('hidden');
}
});
// Resize editors to ensure they render correctly after visibility change
if (tabName === 'code') codeEditor.resize();
if (tabName === 'env') envEditor.resize();
}
// Initialize Ace Editors
var codeEditor = ace.edit("code-editor");
codeEditor.setTheme("ace/theme/github_dark");
codeEditor.session.setMode("ace/mode/{{ 'python' if function.runtime == 'python' else 'javascript' }}");
codeEditor.setValue({{ function.script_content | tojson | safe }}, -1);
codeEditor.setReadOnly(true);
codeEditor.setOptions({
fontSize: "14px",
showPrintMargin: false,
highlightActiveLine: false,
highlightGutterLine: false
});
var envEditor = ace.edit("env-editor");
envEditor.setTheme("ace/theme/github_dark");
envEditor.session.setMode("ace/mode/json");
envEditor.setValue({{ function.environment_info | safe }}, -1);
envEditor.setReadOnly(true);
envEditor.setOptions({
fontSize: "14px",
showPrintMargin: false,
highlightActiveLine: false,
highlightGutterLine: false
});
</script>
{% endblock %}

View File

@@ -63,6 +63,17 @@
d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
</svg>
Settings
</a><a
class="flex items-center gap-3 rounded-lg px-3 py-2 text-gray-500 transition-all hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-50 cursor-pointer"
data-id="18" href="{{ url_for('community.index') }}">
<svg data-slot="icon" data-darkreader-inline-stroke="" width="18" height="18" fill="none"
stroke-width="1.5" stroke="currentColor" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round"
d="M18 18.72a9.094 9.094 0 0 0 3.741-.479 3 3 0 0 0-4.682-2.72m.94 3.198.001.031c0 .225-.012.447-.037.666A11.944 11.944 0 0 1 12 21c-2.17 0-4.207-.576-5.963-1.584A6.062 6.062 0 0 1 6 18.719m12 0a5.971 5.971 0 0 0-.941-3.197m0 0A5.995 5.995 0 0 0 12 12.75a5.995 5.995 0 0 0-5.058 2.772m0 0a3 3 0 0 0-4.681 2.72 8.986 8.986 0 0 0 3.74.477m.94-3.197a5.971 5.971 0 0 0-.94 3.197M15 6.75a3 3 0 1 1-6 0 3 3 0 0 1 6 0Zm6 3a2.25 2.25 0 1 1-4.5 0 2.25 2.25 0 0 1 4.5 0Zm-13.5 0a2.25 2.25 0 1 1-4.5 0 2.25 2.25 0 0 1 4.5 0Z">
</path>
</svg>
Community
</a></nav>
</div>

View File

@@ -25,6 +25,7 @@ history_url=url_for('http.history', function_id=function_id)) }}
name: '{{ name }}',
path: '{{ path }}',
functionId: {{ id }},
description: '{{ description }}',
jsValue: {{ script_content | tojson | safe }},
jsonValue: {{ environment_info | tojson | safe }},
isEdit: true,