Add support to switch between deno and nodejs function executor
This commit is contained in:
14
app.py
14
app.py
@@ -46,7 +46,8 @@ app.register_blueprint(auth, url_prefix='/auth')
|
||||
|
||||
# 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
|
||||
API_URL = os.environ.get('API_URL', 'http://isolator.web:5000/execute') # 'https://isolator.peterstockings.com/execute' 'http://127.0.0.1:5000/execute'
|
||||
NODE_API_URL = os.environ.get('NODE_API_URL', 'http://isolator.web:5000/execute')
|
||||
DENO_API_URL = os.environ.get('DENO_API_URL', 'http://deno-isolator.web:5000/execute')
|
||||
|
||||
|
||||
def map_isolator_response_to_flask_response(response):
|
||||
@@ -82,6 +83,9 @@ async def execute_code():
|
||||
try:
|
||||
# Extract code and convert request to a format acceptable by Node.js app
|
||||
code = request.json.get('code')
|
||||
runtime = request.json.get('runtime', 'node') # Default to node
|
||||
api_url = DENO_API_URL if runtime == 'deno' else NODE_API_URL
|
||||
|
||||
request_obj = {
|
||||
'method': request.method,
|
||||
'headers': dict(request.headers),
|
||||
@@ -92,9 +96,9 @@ async def execute_code():
|
||||
environment = request.json.get('environment_info')
|
||||
environment_json = json.loads(environment)
|
||||
|
||||
# Call the Node.js API asynchronously
|
||||
# Call the selected isolator API asynchronously
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(API_URL, json={'code': code, 'request': request_obj, 'environment': environment_json, 'name': "anonymous"}) as response:
|
||||
async with session.post(api_url, json={'code': code, 'request': request_obj, 'environment': environment_json, 'name': "anonymous"}) as response:
|
||||
response_data = await response.json()
|
||||
|
||||
# check if playground=true is in the query string
|
||||
@@ -122,6 +126,7 @@ async def execute_http_function(user_id, function):
|
||||
|
||||
code = http_function['script_content']
|
||||
environment_info = http_function['environment_info']
|
||||
runtime = http_function.get('runtime', 'node') # Default to node
|
||||
|
||||
# Ensure environment is a dictionary
|
||||
if isinstance(environment_info, str) and environment_info:
|
||||
@@ -176,8 +181,9 @@ async def execute_http_function(user_id, function):
|
||||
request_data['text'] = request.data.decode('utf-8')
|
||||
|
||||
# Call the Node.js API asynchronously
|
||||
api_url = DENO_API_URL if runtime == 'deno' else NODE_API_URL
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(API_URL, json={'code': code, 'request': request_data, 'environment': environment, 'name': function_name}) as response:
|
||||
async with session.post(api_url, json={'code': code, 'request': request_data, 'environment': environment, 'name': function_name}) as response:
|
||||
response_data = await response.json()
|
||||
|
||||
db.update_http_function_environment_info_and_invoked_count(user_id, function_name, response_data['environment'])
|
||||
|
||||
18
db.py
18
db.py
@@ -59,30 +59,30 @@ class DataBase():
|
||||
|
||||
def get_http_functions_for_user(self, user_id):
|
||||
http_functions = 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 ORDER by id DESC', [user_id])
|
||||
'SELECT id, user_id, NAME, 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', [user_id])
|
||||
return http_functions
|
||||
|
||||
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, created_at 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, runtime 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, created_at 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, runtime 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):
|
||||
def create_new_http_function(self, user_id, name, script_content, environment_info, is_public, log_request, log_response, runtime):
|
||||
self.execute(
|
||||
'INSERT INTO http_functions (user_id, NAME, script_content, environment_info, is_public, log_request, log_response) VALUES (%s, %s, %s, %s, %s, %s, %s)',
|
||||
[user_id, name, script_content, environment_info, is_public, log_request, log_response],
|
||||
'INSERT INTO http_functions (user_id, NAME, script_content, environment_info, is_public, log_request, log_response, runtime) VALUES (%s, %s, %s, %s, %s, %s, %s, %s)',
|
||||
[user_id, name, script_content, environment_info, is_public, log_request, log_response, runtime],
|
||||
commit=True
|
||||
)
|
||||
|
||||
def edit_http_function(self, user_id, function_id, name, script_content, environment_info, is_public, log_request, log_response):
|
||||
def edit_http_function(self, user_id, function_id, name, script_content, environment_info, is_public, log_request, log_response, runtime):
|
||||
updated_version = self.execute(
|
||||
'UPDATE http_functions SET NAME=%s, script_content=%s, environment_info=%s, is_public=%s, log_request=%s, log_response=%s WHERE user_id=%s AND id=%s RETURNING version_number',
|
||||
[name, script_content, environment_info, is_public, log_request, log_response, user_id, function_id],
|
||||
'UPDATE http_functions SET NAME=%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, script_content, environment_info, is_public, log_request, log_response, runtime, user_id, function_id],
|
||||
commit=True, one=True
|
||||
)
|
||||
return updated_version
|
||||
|
||||
@@ -105,8 +105,7 @@ http = Blueprint('http', __name__)
|
||||
@login_required
|
||||
def overview():
|
||||
user_id = current_user.id
|
||||
http_functions = db.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 ORDER by id DESC', [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)
|
||||
@@ -128,12 +127,9 @@ def new():
|
||||
is_public = request.json.get('is_public')
|
||||
log_request = request.json.get('log_request')
|
||||
log_response = request.json.get('log_response')
|
||||
runtime = request.json.get('runtime', 'node')
|
||||
|
||||
db.execute(
|
||||
'INSERT INTO http_functions (user_id, NAME, script_content, environment_info, is_public, log_request, log_response) VALUES (%s, %s, %s, %s, %s, %s, %s)',
|
||||
[user_id, name, script_content, environment_info, is_public, log_request, log_response],
|
||||
commit=True
|
||||
)
|
||||
db.create_new_http_function(user_id, name, script_content, environment_info, is_public, log_request, log_response, runtime)
|
||||
|
||||
return jsonify({
|
||||
"status": "success",
|
||||
@@ -155,12 +151,9 @@ def edit(function_id):
|
||||
is_public = request.json.get('is_public')
|
||||
log_request = request.json.get('log_request')
|
||||
log_response = request.json.get('log_response')
|
||||
runtime = request.json.get('runtime', 'node')
|
||||
|
||||
updated_version = db.execute(
|
||||
'UPDATE http_functions SET NAME=%s, script_content=%s, environment_info=%s, is_public=%s, log_request=%s, log_response=%s WHERE user_id=%s AND id=%s RETURNING version_number',
|
||||
[name, script_content, environment_info, is_public, log_request, log_response, user_id, function_id],
|
||||
commit=True, one=True
|
||||
)
|
||||
updated_version = db.edit_http_function(user_id, function_id, name, script_content, environment_info, is_public, log_request, log_response, runtime)
|
||||
|
||||
return { "status": "success", "message": f'{name} updated' }
|
||||
except Exception as e:
|
||||
@@ -184,9 +177,7 @@ def delete(function_id):
|
||||
@login_required
|
||||
def logs(function_id):
|
||||
user_id = current_user.id
|
||||
http_function = db.execute(
|
||||
'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, function_id], one=True)
|
||||
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']
|
||||
@@ -199,9 +190,7 @@ def logs(function_id):
|
||||
@login_required
|
||||
def client(function_id):
|
||||
user_id = current_user.id
|
||||
http_function = db.execute(
|
||||
'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, function_id], one=True)
|
||||
http_function = db.get_http_function_by_id(user_id, function_id)
|
||||
if not http_function:
|
||||
return jsonify({'error': 'Function not found'}), 404
|
||||
|
||||
@@ -265,6 +254,7 @@ def editor(function_id):
|
||||
'log_request': http_function['log_request'],
|
||||
'log_response': http_function['log_response'],
|
||||
'version_number': http_function['version_number'],
|
||||
'runtime': http_function.get('runtime', 'node'),
|
||||
'user_id': user_id,
|
||||
'function_id': function_id,
|
||||
# Add new URLs for navigation
|
||||
|
||||
@@ -152,10 +152,10 @@ timer = Blueprint('timer', __name__)
|
||||
@login_required
|
||||
def overview():
|
||||
timer_functions = db.execute("""
|
||||
SELECT id, name, code, environment, trigger_type,
|
||||
frequency_minutes, run_date, next_run,
|
||||
last_run, enabled, invocation_count
|
||||
FROM timer_functions
|
||||
SELECT id, name, code, environment, trigger_type,
|
||||
frequency_minutes, run_date, next_run,
|
||||
last_run, enabled, invocation_count, runtime
|
||||
FROM timer_functions
|
||||
WHERE user_id = %s
|
||||
ORDER BY id DESC
|
||||
""", [current_user.id])
|
||||
@@ -182,6 +182,7 @@ def new():
|
||||
try:
|
||||
data = request.json
|
||||
trigger_type = data.get('trigger_type')
|
||||
runtime = data.get('runtime', 'node')
|
||||
|
||||
# Validate trigger type
|
||||
if trigger_type not in ('interval', 'date'):
|
||||
@@ -202,9 +203,9 @@ def new():
|
||||
# Insert new timer function
|
||||
db.execute("""
|
||||
INSERT INTO timer_functions
|
||||
(name, code, environment, user_id, trigger_type,
|
||||
frequency_minutes, run_date, next_run, enabled)
|
||||
VALUES (%s, %s, %s::jsonb, %s, %s, %s, %s, %s, %s)
|
||||
(name, code, environment, user_id, trigger_type,
|
||||
frequency_minutes, run_date, next_run, enabled, runtime)
|
||||
VALUES (%s, %s, %s::jsonb, %s, %s, %s, %s, %s, %s, %s)
|
||||
RETURNING id
|
||||
""", [
|
||||
data.get('name'),
|
||||
@@ -215,7 +216,8 @@ def new():
|
||||
frequency_minutes if trigger_type == 'interval' else None,
|
||||
run_date if trigger_type == 'date' else None,
|
||||
next_run,
|
||||
True
|
||||
True,
|
||||
runtime
|
||||
],
|
||||
commit=True)
|
||||
|
||||
@@ -236,10 +238,10 @@ def edit(function_id):
|
||||
if request.method == 'GET':
|
||||
# Fetch the timer function
|
||||
timer_function = db.execute("""
|
||||
SELECT id, name, code, environment, version_number, trigger_type,
|
||||
frequency_minutes, run_date, next_run,
|
||||
last_run, enabled, invocation_count
|
||||
FROM timer_functions
|
||||
SELECT id, name, code, environment, version_number, trigger_type,
|
||||
frequency_minutes, run_date, next_run,
|
||||
last_run, enabled, invocation_count, runtime
|
||||
FROM timer_functions
|
||||
WHERE id = %s AND user_id = %s
|
||||
""", [function_id, current_user.id], one=True)
|
||||
|
||||
@@ -254,7 +256,8 @@ def edit(function_id):
|
||||
|
||||
args = {
|
||||
'function_id': function_id,
|
||||
'timer_function': timer_function
|
||||
'timer_function': timer_function,
|
||||
'runtime': timer_function.get('runtime', 'node')
|
||||
}
|
||||
|
||||
if htmx:
|
||||
@@ -265,6 +268,7 @@ def edit(function_id):
|
||||
try:
|
||||
data = request.json
|
||||
trigger_type = data.get('trigger_type')
|
||||
runtime = data.get('runtime', 'node')
|
||||
|
||||
# Validate trigger type
|
||||
if trigger_type not in ('interval', 'date'):
|
||||
@@ -292,7 +296,8 @@ def edit(function_id):
|
||||
frequency_minutes = %s,
|
||||
run_date = %s,
|
||||
next_run = %s,
|
||||
enabled = %s
|
||||
enabled = %s,
|
||||
runtime = %s
|
||||
WHERE id = %s AND user_id = %s
|
||||
RETURNING id
|
||||
""", [
|
||||
@@ -304,6 +309,7 @@ def edit(function_id):
|
||||
run_date if trigger_type == 'date' else None,
|
||||
next_run,
|
||||
data.get('is_enabled', True), # Default to True if not provided
|
||||
runtime,
|
||||
function_id,
|
||||
current_user.id
|
||||
],
|
||||
|
||||
@@ -6,6 +6,7 @@ const Editor = {
|
||||
this.isPublic = vnode.attrs.isPublic || false;
|
||||
this.logRequest = vnode.attrs.logRequest || false;
|
||||
this.logResponse = vnode.attrs.logResponse || false;
|
||||
this.runtime = vnode.attrs.runtime || "node"; // Add runtime
|
||||
|
||||
// Only controls whether the name/version is shown (left side of header),
|
||||
// but we still always show the Execute button on the right side.
|
||||
@@ -119,7 +120,11 @@ const Editor = {
|
||||
const resp = await fetch(this.executeUrl, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ code, environment_info }),
|
||||
body: JSON.stringify({
|
||||
code,
|
||||
environment_info,
|
||||
runtime: this.runtime,
|
||||
}),
|
||||
});
|
||||
if (!resp.ok) {
|
||||
throw new Error(`HTTP error! status: ${resp.status}`);
|
||||
@@ -149,6 +154,7 @@ const Editor = {
|
||||
is_public: this.isPublic,
|
||||
log_request: this.logRequest,
|
||||
log_response: this.logResponse,
|
||||
runtime: this.runtime,
|
||||
};
|
||||
|
||||
// Create payload based on whether this is a timer function
|
||||
@@ -172,6 +178,7 @@ const Editor = {
|
||||
is_public: this.isPublic,
|
||||
log_request: this.logRequest,
|
||||
log_response: this.logResponse,
|
||||
runtime: this.runtime,
|
||||
};
|
||||
|
||||
const response = await m.request({
|
||||
@@ -513,6 +520,39 @@ const Editor = {
|
||||
m("div", { class: "flex flex-col space-y-4" }, [
|
||||
// Toggles group
|
||||
m("div", { class: "flex flex-wrap gap-6" }, [
|
||||
// Runtime dropdown
|
||||
m("div", { class: "flex flex-col" }, [
|
||||
m(
|
||||
"label",
|
||||
{
|
||||
for: "runtime-select",
|
||||
class: "mb-2 text-sm font-medium",
|
||||
},
|
||||
"Runtime"
|
||||
),
|
||||
m(
|
||||
"select",
|
||||
{
|
||||
id: "runtime-select",
|
||||
class:
|
||||
"bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 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",
|
||||
onchange: (e) => (this.runtime = e.target.value),
|
||||
},
|
||||
[
|
||||
m(
|
||||
"option",
|
||||
{ value: "node", selected: this.runtime === "node" },
|
||||
"Node.js"
|
||||
),
|
||||
m(
|
||||
"option",
|
||||
{ value: "deno", selected: this.runtime === "deno" },
|
||||
"Deno"
|
||||
),
|
||||
]
|
||||
),
|
||||
]),
|
||||
|
||||
// Public/Private toggle
|
||||
this.showPublicToggle &&
|
||||
m(
|
||||
|
||||
@@ -32,6 +32,7 @@ history_url=url_for('http.history', function_id=function_id)) }}
|
||||
logRequest: {{ log_request | tojson }},
|
||||
logResponse: {{ log_response | tojson }},
|
||||
versionNumber: {{ version_number }},
|
||||
runtime: '{{ runtime }}',
|
||||
executeUrl: "{{ url_for('execute_code', playground='true') }}",
|
||||
saveUrl: "{{ url_for('http.edit', function_id=id) if id else url_for('http.new') }}",
|
||||
deleteUrl: "{{ url_for('http.delete', function_id=id) if id else '' }}",
|
||||
|
||||
@@ -30,6 +30,7 @@ history_url=url_for('timer.history', function_id=function_id)) }}
|
||||
isEdit: true,
|
||||
showHeader: true,
|
||||
versionNumber: {{ timer_function.version_number }},
|
||||
runtime: '{{ runtime }}',
|
||||
isEnabled: {{ timer_function.enabled | tojson }},
|
||||
executeUrl: "{{ url_for('execute_code', playground='true') }}",
|
||||
saveUrl: "{{ url_for('timer.edit', function_id=function_id) }}",
|
||||
|
||||
Reference in New Issue
Block a user