Refactor HTTP function editor with Mithril components and new API endpoints
- Add new Mithril components for editor (editor.js), response view (responseView.js), and alerts (alert.js) - Create new API endpoints for creating, updating, and deleting HTTP functions - Update templates to use new Mithril editor component - Improve header navigation with more consistent styling and active state indicators - Remove old edit form route and template - Add new dedicated editor route and template cursor.ai
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +1,2 @@
|
||||
/.env
|
||||
__pycache__/
|
||||
|
||||
141
app.py
141
app.py
@@ -151,24 +151,6 @@ def create_http_function():
|
||||
print(e)
|
||||
return { "status": "error", "message": str(e) }
|
||||
|
||||
@ app.route("/dashboard/http_functions/<int:function_id>/edit_form", methods=["GET"])
|
||||
@login_required
|
||||
def get_http_function_edit_form(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']
|
||||
script = 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']
|
||||
|
||||
if htmx:
|
||||
return render_block(app.jinja_env, 'dashboard/http_functions/edit.html', 'page', user_id=user_id, function_id=function_id, name=name, script=script, environment_info=environment_info, is_public=is_public, log_request=log_request, log_response=log_response, version_number=version_number)
|
||||
return render_template("dashboard/http_functions/edit.html", user_id=user_id, name=name, function_id=function_id, script=script, environment_info=environment_info, is_public=is_public, log_request=log_request, log_response=log_response, version_number=version_number)
|
||||
|
||||
@ app.route("/dashboard/http_functions/<int:function_id>/edit", methods=["POST"])
|
||||
@login_required
|
||||
@@ -437,6 +419,129 @@ def logout():
|
||||
logout_user()
|
||||
return redirect(url_for('home'))
|
||||
|
||||
@app.route("/http_function_editor/<int:function_id>", 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/<int:function_id>", 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/<int:function_id>", 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):
|
||||
|
||||
40
static/js/mithril/alert.js
Normal file
40
static/js/mithril/alert.js
Normal file
@@ -0,0 +1,40 @@
|
||||
const Alert = {
|
||||
show(message, type = 'success') {
|
||||
const alert = document.createElement('div');
|
||||
alert.className = `fixed top-4 right-4 p-4 rounded-md text-white ${
|
||||
type === 'success' ? 'bg-green-500' : 'bg-red-500'
|
||||
} transition-opacity duration-500`;
|
||||
|
||||
alert.innerHTML = `
|
||||
<div class="flex items-center">
|
||||
<span class="mr-2">
|
||||
${type === 'success' ? `
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
` : `
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
`}
|
||||
</span>
|
||||
<span>${message}</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(alert);
|
||||
|
||||
// Fade in
|
||||
setTimeout(() => {
|
||||
alert.style.opacity = '1';
|
||||
}, 10);
|
||||
|
||||
// Fade out and remove
|
||||
setTimeout(() => {
|
||||
alert.style.opacity = '0';
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(alert);
|
||||
}, 500);
|
||||
}, 3000);
|
||||
}
|
||||
};
|
||||
496
static/js/mithril/editor.js
Normal file
496
static/js/mithril/editor.js
Normal file
@@ -0,0 +1,496 @@
|
||||
const Editor = {
|
||||
oninit(vnode) {
|
||||
// Props
|
||||
this.isEdit = vnode.attrs.isEdit || false;
|
||||
this.isAdd = vnode.attrs.isAdd || false;
|
||||
this.isPublic = vnode.attrs.isPublic || false;
|
||||
this.logRequest = vnode.attrs.logRequest || false;
|
||||
this.logResponse = vnode.attrs.logResponse || false;
|
||||
|
||||
// Only controls whether the name/version is shown (left side of header),
|
||||
// but we still always show the Execute button on the right side.
|
||||
this.showHeader = vnode.attrs.showHeader !== false; // default true
|
||||
|
||||
// New props for showing/hiding individual settings
|
||||
this.showPublicToggle = vnode.attrs.showPublicToggle !== false; // default true
|
||||
this.showLogRequestToggle = vnode.attrs.showLogRequestToggle !== false; // default true
|
||||
this.showLogResponseToggle = vnode.attrs.showLogResponseToggle !== false; // default true
|
||||
this.showSaveButton = vnode.attrs.showSaveButton !== false; // default true
|
||||
|
||||
// New prop to control entire settings panel visibility
|
||||
this.showFunctionSettings = vnode.attrs.showFunctionSettings !== false; // default true
|
||||
|
||||
// Name + version
|
||||
this.name = vnode.attrs.name || "foo";
|
||||
this.versionNumber = vnode.attrs.versionNumber || "1";
|
||||
this.nameEditing = false;
|
||||
|
||||
// Editor defaults
|
||||
this.jsValue = vnode.attrs.jsValue || "";
|
||||
this.jsonValue = vnode.attrs.jsonValue || "{}";
|
||||
|
||||
// Execute endpoint
|
||||
this.executeUrl = vnode.attrs.executeUrl;
|
||||
|
||||
// State for environment toggle, fetch results, etc.
|
||||
this.showEnvironment = false;
|
||||
this.loading = false;
|
||||
this.error = null;
|
||||
this.response = null; // JSON from server
|
||||
this.responseRaw = ""; // Raw text from server
|
||||
this.responseTime = 0;
|
||||
this.responseSize = 0;
|
||||
|
||||
// URL props
|
||||
this.saveUrl = vnode.attrs.saveUrl;
|
||||
|
||||
// Separate loading states for each button
|
||||
this.executeLoading = false;
|
||||
this.saveLoading = false;
|
||||
|
||||
// New prop for showing/hiding delete button
|
||||
this.showDeleteButton = vnode.attrs.showDeleteButton !== false; // default true
|
||||
|
||||
// Delete endpoint
|
||||
this.deleteUrl = vnode.attrs.deleteUrl;
|
||||
|
||||
this.dashboardUrl = vnode.attrs.dashboardUrl;
|
||||
},
|
||||
|
||||
oncreate() {
|
||||
// Initialize top JS editor
|
||||
this.editorJS = ace.edit("js-editor");
|
||||
this.editorJS.setOptions({ maxLines: 100 });
|
||||
this.editorJS.setTheme("ace/theme/github_dark");
|
||||
this.editorJS.session.setMode("ace/mode/javascript");
|
||||
this.editorJS.setValue(this.jsValue, -1);
|
||||
|
||||
this.editorJS.session.on("change", () => {
|
||||
this.jsValue = this.editorJS.getValue();
|
||||
m.redraw();
|
||||
});
|
||||
|
||||
// Initialize bottom JSON editor
|
||||
this.editorJSON = ace.edit("json-editor");
|
||||
this.editorJSON.setOptions({ maxLines: 100 });
|
||||
this.editorJSON.setTheme("ace/theme/github_dark");
|
||||
this.editorJSON.session.setMode("ace/mode/json");
|
||||
this.editorJSON.setValue(this.jsonValue, -1);
|
||||
|
||||
this.editorJSON.session.on("change", () => {
|
||||
this.jsonValue = this.editorJSON.getValue();
|
||||
m.redraw();
|
||||
});
|
||||
},
|
||||
|
||||
async execute() {
|
||||
this.executeLoading = true;
|
||||
this.error = null;
|
||||
this.response = null;
|
||||
this.responseRaw = "";
|
||||
this.responseSize = 0;
|
||||
this.responseTime = 0;
|
||||
|
||||
const startTime = Date.now();
|
||||
try {
|
||||
const code = this.editorJS.getValue();
|
||||
const environment_info = this.editorJSON.getValue();
|
||||
|
||||
const resp = await fetch(this.executeUrl, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ code, environment_info }),
|
||||
});
|
||||
if (!resp.ok) {
|
||||
throw new Error(`HTTP error! status: ${resp.status}`);
|
||||
}
|
||||
|
||||
this.responseRaw = await resp.text();
|
||||
this.responseSize = new Blob([this.responseRaw]).size;
|
||||
this.response = JSON.parse(this.responseRaw);
|
||||
this.responseTime = Date.now() - startTime;
|
||||
} catch (err) {
|
||||
this.error = err;
|
||||
} finally {
|
||||
this.executeLoading = false;
|
||||
m.redraw();
|
||||
}
|
||||
},
|
||||
|
||||
async save() {
|
||||
this.saveLoading = true;
|
||||
this.error = null;
|
||||
|
||||
try {
|
||||
const payload = {
|
||||
name: this.name,
|
||||
script_content: this.jsValue,
|
||||
environment_info: this.jsonValue,
|
||||
is_public: this.isPublic,
|
||||
log_request: this.logRequest,
|
||||
log_response: this.logResponse
|
||||
};
|
||||
|
||||
const response = await m.request({
|
||||
method: 'POST',
|
||||
url: this.saveUrl,
|
||||
body: payload
|
||||
});
|
||||
|
||||
if (response.status === 'success') {
|
||||
if (this.isAdd) {
|
||||
window.location.href = this.dashboardUrl;
|
||||
} else {
|
||||
// Increment version number after successful save
|
||||
this.versionNumber = (parseInt(this.versionNumber) + 1).toString();
|
||||
}
|
||||
Alert.show(response.message || 'Function saved successfully!', 'success');
|
||||
} else {
|
||||
Alert.show(response.message || 'Error saving function', 'error');
|
||||
this.error = new Error(response.message);
|
||||
}
|
||||
} catch (err) {
|
||||
Alert.show(err?.response.message || 'Error saving function', 'error');
|
||||
this.error = err?.response;
|
||||
} finally {
|
||||
this.saveLoading = false;
|
||||
m.redraw();
|
||||
}
|
||||
},
|
||||
|
||||
async delete() {
|
||||
if (!confirm('Are you sure you want to delete this function?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.deleteLoading = true;
|
||||
this.error = null;
|
||||
|
||||
try {
|
||||
const response = await m.request({
|
||||
method: 'DELETE',
|
||||
url: this.deleteUrl
|
||||
});
|
||||
|
||||
if (response.status === 'success') {
|
||||
Alert.show(response.message || 'Function deleted successfully!', 'success');
|
||||
// Optionally redirect to a different page after deletion
|
||||
window.location.href = '/dashboard/http_functions';
|
||||
} else {
|
||||
Alert.show(response.message || 'Error deleting function', 'error');
|
||||
this.error = new Error(response.message);
|
||||
}
|
||||
} catch (err) {
|
||||
Alert.show(err.message || 'Error deleting function', 'error');
|
||||
this.error = err;
|
||||
} finally {
|
||||
this.deleteLoading = false;
|
||||
m.redraw();
|
||||
}
|
||||
},
|
||||
|
||||
view() {
|
||||
return m("div", { class: "" }, [
|
||||
/* ─────────────────────────────────────────────────────────────────
|
||||
HEADER BAR
|
||||
─────────────────────────────────────────────────────────────────*/
|
||||
m(
|
||||
"div",
|
||||
{
|
||||
class:
|
||||
"flex items-center justify-between p-2 border-b border-gray-200 dark:border-gray-800",
|
||||
},
|
||||
[
|
||||
// Left side: name/version OR add input (shown only if showHeader==true)
|
||||
this.showHeader
|
||||
? m("div", { class: "flex space-x-2" }, [
|
||||
// If editing existing function
|
||||
this.isEdit
|
||||
? m(
|
||||
"div",
|
||||
{
|
||||
class:
|
||||
"inline-flex items-center space-x-1 h-10 py-2 text-gray-600 dark:text-gray-400 text-md font-medium cursor-pointer",
|
||||
},
|
||||
[
|
||||
m("span", { class: "inline-flex items-center" }, [
|
||||
!this.nameEditing
|
||||
? m(
|
||||
"span",
|
||||
{
|
||||
class: "font-mono",
|
||||
onclick: () => (this.nameEditing = true),
|
||||
},
|
||||
this.name
|
||||
)
|
||||
: m("input", {
|
||||
class:
|
||||
"bg-gray-50 border border-gray-300 text-sm rounded-lg p-1.5 dark:bg-gray-700 dark:border-gray-600",
|
||||
value: this.name,
|
||||
oninput: (e) => (this.name = e.target.value),
|
||||
onblur: () => (this.nameEditing = false),
|
||||
autofocus: true,
|
||||
}),
|
||||
|
||||
// Pencil icon
|
||||
m(
|
||||
"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",
|
||||
onclick: () =>
|
||||
(this.nameEditing = !this.nameEditing),
|
||||
},
|
||||
m("path", {
|
||||
"stroke-linecap": "round",
|
||||
"stroke-linejoin": "round",
|
||||
d: "m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10",
|
||||
})
|
||||
),
|
||||
]),
|
||||
// Version
|
||||
m(
|
||||
"span",
|
||||
{
|
||||
class:
|
||||
"bg-blue-500 text-white text-xs font-semibold px-2 py-1 rounded ml-2",
|
||||
},
|
||||
`v${this.versionNumber}`
|
||||
),
|
||||
]
|
||||
)
|
||||
: null,
|
||||
|
||||
// If adding a new function
|
||||
this.isAdd
|
||||
? m("div", { class: "w-full" }, [
|
||||
m(
|
||||
"label",
|
||||
{ class: "block mb-2 text-sm font-medium" },
|
||||
"Function name"
|
||||
),
|
||||
m("input", {
|
||||
type: "text",
|
||||
class: "bg-gray-50 border w-full p-2.5 rounded-lg",
|
||||
placeholder: "foo",
|
||||
required: true,
|
||||
value: this.name,
|
||||
oninput: (e) => (this.name = e.target.value),
|
||||
}),
|
||||
])
|
||||
: null,
|
||||
])
|
||||
: m("div"), // If header is hidden, left side is empty
|
||||
|
||||
// Right side: always show spinner or execute button
|
||||
m("div", { class: "flex items-center space-x-3" }, [
|
||||
this.executeLoading
|
||||
? m("div", {
|
||||
class:
|
||||
"animate-spin h-6 w-6 border-4 border-green-300 border-t-transparent rounded-full",
|
||||
})
|
||||
: m(
|
||||
"button",
|
||||
{
|
||||
class: "p-2 rounded-full hover:bg-gray-200 text-green-700",
|
||||
onclick: () => this.execute(),
|
||||
title: "Execute",
|
||||
},
|
||||
m(
|
||||
"svg",
|
||||
{
|
||||
xmlns: "http://www.w3.org/2000/svg",
|
||||
fill: "none",
|
||||
viewBox: "0 0 24 24",
|
||||
"stroke-width": "1.5",
|
||||
stroke: "currentColor",
|
||||
class: "w-6 h-6",
|
||||
},
|
||||
m("path", {
|
||||
"stroke-linecap": "round",
|
||||
"stroke-linejoin": "round",
|
||||
d: "M5.25 5.25 19.5 12 5.25 18.75 5.25 5.25z",
|
||||
})
|
||||
)
|
||||
),
|
||||
]),
|
||||
]
|
||||
),
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────────
|
||||
JS Editor
|
||||
─────────────────────────────────────────────────────────────────*/
|
||||
m("div", { id: "js-editor", class: "rounded shadow h-64" }),
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────────
|
||||
Environment Toggle
|
||||
─────────────────────────────────────────────────────────────────*/
|
||||
m(
|
||||
"div",
|
||||
{ class: "flex space-x-2 border-b border-gray-200 justify-between" },
|
||||
[
|
||||
m(
|
||||
"button",
|
||||
{
|
||||
class:
|
||||
"inline-flex items-center px-4 py-2 h-10 text-gray-600 hover:bg-accent hover:text-accent-foreground",
|
||||
onclick: () => (this.showEnvironment = !this.showEnvironment),
|
||||
},
|
||||
[
|
||||
m(
|
||||
"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",
|
||||
},
|
||||
m("path", {
|
||||
"stroke-linecap": "round",
|
||||
"stroke-linejoin": "round",
|
||||
d: "M20.25 6.375c0 2.278-3.694 4.125-8.25 4.125S3.75 8.653 3.75 6.375m16.5 0c0-2.278-3.694-4.125-8.25-4.125S3.75 4.097 3.75 6.375m16.5 0v11.25c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125V6.375m16.5 0v3.75m-16.5-3.75v3.75m16.5 0v3.75C20.25 16.153 16.556 18 12 18s-8.25-1.847-8.25-4.125v-3.75m16.5 0c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125",
|
||||
})
|
||||
),
|
||||
m("span", { class: "ml-1" }, "Environment"),
|
||||
]
|
||||
),
|
||||
m("div", { class: "flex-auto" }),
|
||||
]
|
||||
),
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────────
|
||||
JSON Editor
|
||||
─────────────────────────────────────────────────────────────────*/
|
||||
m("div", { id: "json-editor", class: "rounded shadow h-64" }),
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────────
|
||||
Hidden fields (if needed for forms)
|
||||
─────────────────────────────────────────────────────────────────*/
|
||||
m("input", { type: "hidden", name: "script", value: this.jsValue }),
|
||||
m("input", {
|
||||
type: "hidden",
|
||||
name: "environment",
|
||||
value: this.jsonValue,
|
||||
}),
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────────
|
||||
Loading & Error
|
||||
─────────────────────────────────────────────────────────────────*/
|
||||
this.error &&
|
||||
m(
|
||||
"div",
|
||||
{ class: "mt-2 p-2 text-red-600 font-semibold" },
|
||||
`Error: ${this.error.message}`
|
||||
),
|
||||
|
||||
// Function settings panel
|
||||
this.showFunctionSettings && m("div", { class: "bg-gray-100 dark:bg-gray-800 p-4 border-b" }, [
|
||||
// Settings group
|
||||
m("div", { class: "flex flex-col space-y-4" }, [
|
||||
// Toggles group
|
||||
m("div", { class: "flex flex-wrap gap-6" }, [
|
||||
// Public/Private toggle
|
||||
this.showPublicToggle && m("label", {
|
||||
class: "flex items-center space-x-3 text-sm text-gray-600 dark:text-gray-300 cursor-pointer"
|
||||
}, [
|
||||
m("div", { class: "relative" }, [
|
||||
m("input[type=checkbox]", {
|
||||
class: "sr-only peer",
|
||||
checked: this.isPublic,
|
||||
onchange: (e) => this.isPublic = e.target.checked
|
||||
}),
|
||||
m("div", {
|
||||
class: "w-11 h-6 bg-gray-200 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"
|
||||
})
|
||||
]),
|
||||
m("span", "Public Function")
|
||||
]),
|
||||
|
||||
// Log Request toggle
|
||||
this.showLogRequestToggle && m("label", {
|
||||
class: "flex items-center space-x-3 text-sm text-gray-600 dark:text-gray-300 cursor-pointer"
|
||||
}, [
|
||||
m("div", { class: "relative" }, [
|
||||
m("input[type=checkbox]", {
|
||||
class: "sr-only peer",
|
||||
checked: this.logRequest,
|
||||
onchange: (e) => this.logRequest = e.target.checked
|
||||
}),
|
||||
m("div", {
|
||||
class: "w-11 h-6 bg-gray-200 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"
|
||||
})
|
||||
]),
|
||||
m("span", "Log Requests")
|
||||
]),
|
||||
|
||||
// Log Response toggle
|
||||
this.showLogResponseToggle && m("label", {
|
||||
class: "flex items-center space-x-3 text-sm text-gray-600 dark:text-gray-300 cursor-pointer"
|
||||
}, [
|
||||
m("div", { class: "relative" }, [
|
||||
m("input[type=checkbox]", {
|
||||
class: "sr-only peer",
|
||||
checked: this.logResponse,
|
||||
onchange: (e) => this.logResponse = e.target.checked
|
||||
}),
|
||||
m("div", {
|
||||
class: "w-11 h-6 bg-gray-200 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"
|
||||
})
|
||||
]),
|
||||
m("span", "Log Responses")
|
||||
])
|
||||
]),
|
||||
|
||||
// Actions group
|
||||
m("div", { class: "flex items-center justify-end space-x-3 pt-2" }, [
|
||||
// Save button
|
||||
this.showSaveButton && m("button", {
|
||||
class: "px-4 py-2 bg-blue-500 text-white text-sm font-medium rounded-lg hover:bg-blue-600 transition-colors flex items-center space-x-2 disabled:opacity-50",
|
||||
onclick: () => this.save(),
|
||||
disabled: this.saveLoading
|
||||
}, [
|
||||
this.saveLoading && m("div", {
|
||||
class: "animate-spin h-4 w-4 border-2 border-white border-t-transparent rounded-full"
|
||||
}),
|
||||
m("span", this.saveLoading ? "Saving..." : "Save Function")
|
||||
]),
|
||||
|
||||
// Delete button
|
||||
this.showDeleteButton && m("button", {
|
||||
class: "px-4 py-2 bg-white text-red-600 text-sm font-medium border border-red-200 rounded-lg hover:bg-red-50 transition-colors flex items-center space-x-2 disabled:opacity-50",
|
||||
onclick: () => this.delete(),
|
||||
disabled: this.deleteLoading
|
||||
}, [
|
||||
this.deleteLoading && m("div", {
|
||||
class: "animate-spin h-4 w-4 border-2 border-red-600 border-t-transparent rounded-full"
|
||||
}),
|
||||
m("span", this.deleteLoading ? "Deleting..." : "Delete Function")
|
||||
])
|
||||
])
|
||||
])
|
||||
]),
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────────
|
||||
ResponseView (child) if needed
|
||||
─────────────────────────────────────────────────────────────────*/
|
||||
!this.executeLoading &&
|
||||
!this.error &&
|
||||
this.response &&
|
||||
m(ResponseView, {
|
||||
response: this.response,
|
||||
responseTime: this.responseTime,
|
||||
responseSize: this.responseSize,
|
||||
envEditorValue: this.jsonValue,
|
||||
onClose: () => {
|
||||
this.response = null;
|
||||
},
|
||||
}),
|
||||
]);
|
||||
},
|
||||
};
|
||||
358
static/js/mithril/responseView.js
Normal file
358
static/js/mithril/responseView.js
Normal file
@@ -0,0 +1,358 @@
|
||||
const ResponseView = {
|
||||
oninit() {
|
||||
// Which tab is visible? 0=Logs, 1=Preview, 2=Raw, 3=Diff
|
||||
this.tabIndex = 1;
|
||||
},
|
||||
|
||||
view(vnode) {
|
||||
const {
|
||||
response, // Full JSON from server
|
||||
responseTime, // #ms from request start to finish
|
||||
responseSize, // #bytes in the raw response
|
||||
envEditorValue, // The JSON environment from the editor (string)
|
||||
onClose, // Callback to clear or close this view
|
||||
} = vnode.attrs;
|
||||
|
||||
// If there's no response, nothing to show
|
||||
if (!response) return null;
|
||||
|
||||
return m("div", { class: "mt-2 p-1 rounded-md bg-gray-200 min-h-40" }, [
|
||||
/* ─────────────────────────────────────────────────────────────────
|
||||
TAB HEADERS
|
||||
─────────────────────────────────────────────────────────────────*/
|
||||
m("div", { class: "flex justify-between items-center" }, [
|
||||
// Left: 4 tab buttons
|
||||
m(
|
||||
"div",
|
||||
{
|
||||
class: "flex space-x-4 text-sm font-medium text-gray-600 ml-2 py-1",
|
||||
},
|
||||
[
|
||||
// Logs (tabIndex=0)
|
||||
m(
|
||||
"div",
|
||||
{
|
||||
class:
|
||||
"flex items-center space-x-1 cursor-pointer " +
|
||||
(this.tabIndex === 0
|
||||
? "border-b-2 text-blue-400 border-blue-400"
|
||||
: ""),
|
||||
onclick: () => (this.tabIndex = 0),
|
||||
},
|
||||
[
|
||||
m(
|
||||
"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",
|
||||
},
|
||||
m("path", {
|
||||
"stroke-linecap": "round",
|
||||
"stroke-linejoin": "round",
|
||||
d: "M3.75 6.75h16.5M3.75 12H12m-8.25 5.25h16.5",
|
||||
})
|
||||
),
|
||||
m("span", `Logs(${response.logs.length})`),
|
||||
]
|
||||
),
|
||||
|
||||
// Raw (tabIndex=2)
|
||||
m(
|
||||
"div",
|
||||
{
|
||||
class:
|
||||
"flex items-center space-x-1 cursor-pointer " +
|
||||
(this.tabIndex === 2
|
||||
? "border-b-2 text-blue-400 border-blue-400"
|
||||
: ""),
|
||||
onclick: () => (this.tabIndex = 2),
|
||||
},
|
||||
[
|
||||
m(
|
||||
"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",
|
||||
},
|
||||
m("path", {
|
||||
"stroke-linecap": "round",
|
||||
"stroke-linejoin": "round",
|
||||
d: "M19.5 3 4.5 3m15 0v3.75c0 .621-.504 1.125-1.125 1.125h-3.75m4.875-4.875-4.875 4.875m-9.75 0A2.625 2.625 0 0 1 3 5.25v-.75c0-.621.504-1.125 1.125-1.125h.75c.621 0 1.125.504 1.125 1.125v.75c0 .621-.504 1.125-1.125 1.125H4.5Zm0 6.375h15m-15 6h15",
|
||||
})
|
||||
),
|
||||
m("span", "Raw"),
|
||||
]
|
||||
),
|
||||
|
||||
// Diff (tabIndex=3)
|
||||
m(
|
||||
"div",
|
||||
{
|
||||
class:
|
||||
"flex items-center space-x-1 cursor-pointer " +
|
||||
(this.tabIndex === 3
|
||||
? "border-b-2 text-blue-400 border-blue-400"
|
||||
: ""),
|
||||
onclick: () => (this.tabIndex = 3),
|
||||
},
|
||||
[
|
||||
m(
|
||||
"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",
|
||||
},
|
||||
m("path", {
|
||||
"stroke-linecap": "round",
|
||||
"stroke-linejoin": "round",
|
||||
d: "M16.5 3.75 7.5 20.25m9-16.5H9m7.5 0V12m-9 8.25h9m-9 0V12",
|
||||
})
|
||||
),
|
||||
m("span", "Diff"),
|
||||
]
|
||||
),
|
||||
|
||||
// Preview (tabIndex=1)
|
||||
m(
|
||||
"div",
|
||||
{
|
||||
class:
|
||||
"flex items-center space-x-1 cursor-pointer " +
|
||||
(this.tabIndex === 1
|
||||
? "border-b-2 text-blue-400 border-blue-400"
|
||||
: ""),
|
||||
onclick: () => (this.tabIndex = 1),
|
||||
},
|
||||
[
|
||||
m(
|
||||
"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",
|
||||
},
|
||||
[
|
||||
m("path", {
|
||||
"stroke-linecap": "round",
|
||||
"stroke-linejoin": "round",
|
||||
d: "M2.036 12.322a1.012 1.012 0 0 1 0-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178Z",
|
||||
}),
|
||||
m("path", {
|
||||
"stroke-linecap": "round",
|
||||
"stroke-linejoin": "round",
|
||||
d: "M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z",
|
||||
}),
|
||||
]
|
||||
),
|
||||
m("span", "Preview"),
|
||||
]
|
||||
),
|
||||
]
|
||||
),
|
||||
|
||||
// Right: Close icon
|
||||
m(
|
||||
"div",
|
||||
{
|
||||
class: "cursor-pointer text-gray-600 rounded-md hover:bg-gray-300",
|
||||
onclick: onClose,
|
||||
},
|
||||
m(
|
||||
"svg",
|
||||
{
|
||||
xmlns: "http://www.w3.org/2000/svg",
|
||||
fill: "none",
|
||||
viewBox: "0 0 24 24",
|
||||
"stroke-width": "1.5",
|
||||
stroke: "currentColor",
|
||||
class: "w-6 h-6",
|
||||
},
|
||||
m("path", {
|
||||
"stroke-linecap": "round",
|
||||
"stroke-linejoin": "round",
|
||||
d: "M6 18 18 6M6 6l12 12",
|
||||
})
|
||||
)
|
||||
),
|
||||
]),
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────────
|
||||
TAB CONTENT
|
||||
─────────────────────────────────────────────────────────────────*/
|
||||
m("div", { class: "p-2" }, [
|
||||
// (0) LOGS
|
||||
this.tabIndex === 0 &&
|
||||
m(
|
||||
"div",
|
||||
{ class: "text-sm font-mono space-y-1" },
|
||||
(response.logs || []).map((log) =>
|
||||
m("div", [
|
||||
m(
|
||||
"svg",
|
||||
{
|
||||
class: "inline-block w-4 h-4 mr-1",
|
||||
fill: "none",
|
||||
stroke: "currentColor",
|
||||
"stroke-width": "1.5",
|
||||
viewBox: "0 0 24 24",
|
||||
},
|
||||
m("path", {
|
||||
"stroke-linecap": "round",
|
||||
"stroke-linejoin": "round",
|
||||
d: "m8.25 4.5 7.5 7.5-7.5 7.5",
|
||||
})
|
||||
),
|
||||
log,
|
||||
])
|
||||
)
|
||||
),
|
||||
|
||||
// (2) RAW
|
||||
this.tabIndex === 2 &&
|
||||
m("div", { class: "space-y-2" }, [
|
||||
// Show raw body
|
||||
m("div", { class: "bg-white p-2 rounded-md" }, [
|
||||
m("div", { class: "font-bold text-gray-600 mb-1" }, "Raw Body"),
|
||||
m(
|
||||
"pre",
|
||||
{ class: "text-sm overflow-auto" },
|
||||
response.result?.body ?? "No body"
|
||||
),
|
||||
]),
|
||||
|
||||
// Show headers
|
||||
m("div", { class: "bg-white p-2 rounded-md" }, [
|
||||
m("div", { class: "font-bold text-gray-600 mb-1" }, "Headers"),
|
||||
response.result?.headers
|
||||
? m(
|
||||
"table",
|
||||
{ class: "text-sm w-full border-collapse" },
|
||||
Object.entries(response.result.headers).map(([k, v]) =>
|
||||
m("tr", [
|
||||
m("td", { class: "border p-1 font-medium w-1/4" }, k),
|
||||
m("td", { class: "border p-1" }, v),
|
||||
])
|
||||
)
|
||||
)
|
||||
: m("div", "No headers"),
|
||||
]),
|
||||
]),
|
||||
|
||||
// (3) DIFF
|
||||
this.tabIndex === 3 &&
|
||||
m("div", {
|
||||
id: "env-diff-container",
|
||||
style: "position: relative; width: 100%; height: 300px;",
|
||||
oncreate: (vnode) => {
|
||||
// Build "left" from envEditorValue
|
||||
let leftText;
|
||||
try {
|
||||
// Attempt to format nicely
|
||||
const leftObj = JSON.parse(envEditorValue);
|
||||
leftText = JSON.stringify(leftObj, null, 2);
|
||||
} catch (e) {
|
||||
// Fallback to raw string
|
||||
leftText = envEditorValue;
|
||||
}
|
||||
|
||||
// Build "right" from server environment
|
||||
let rightText = "";
|
||||
let serverEnv = response?.environment || {};
|
||||
try {
|
||||
rightText = JSON.stringify(serverEnv, null, 2);
|
||||
} catch (err) {
|
||||
rightText = String(serverEnv);
|
||||
}
|
||||
|
||||
// Initialize AceDiff
|
||||
const aceDiffer = new AceDiff({
|
||||
element: vnode.dom,
|
||||
mode: "ace/mode/json", // Or "ace/mode/javascript"
|
||||
theme: "ace/theme/github_dark",
|
||||
left: {
|
||||
content: leftText,
|
||||
editable: false,
|
||||
},
|
||||
right: {
|
||||
content: rightText,
|
||||
editable: false,
|
||||
},
|
||||
});
|
||||
|
||||
// Optional: set max lines, disable worker
|
||||
const ed = aceDiffer.getEditors();
|
||||
ed.left.setOptions({ maxLines: 20 });
|
||||
ed.left.session.setOption("useWorker", false);
|
||||
ed.right.setOptions({ maxLines: 20 });
|
||||
ed.right.session.setOption("useWorker", false);
|
||||
},
|
||||
}),
|
||||
|
||||
// (1) PREVIEW
|
||||
this.tabIndex === 1 &&
|
||||
m(
|
||||
"div",
|
||||
{
|
||||
class:
|
||||
"min-h-32 rounded-md bg-white p-2 " +
|
||||
(response.status !== "SUCCESS"
|
||||
? "border-red-700 border-2"
|
||||
: ""),
|
||||
},
|
||||
response.status === "SUCCESS"
|
||||
? m.trust(response.result.body)
|
||||
: JSON.stringify(response.result)
|
||||
),
|
||||
]),
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────────
|
||||
FOOTER (Status, Time, Size)
|
||||
─────────────────────────────────────────────────────────────────*/
|
||||
m("div", { class: "flex justify-end p-1" }, [
|
||||
m("div", { class: "text-sm font-medium text-gray-600 space-x-4" }, [
|
||||
// Status
|
||||
m("span", [
|
||||
"Status: ",
|
||||
m(
|
||||
"span",
|
||||
{
|
||||
class:
|
||||
response.status === "SUCCESS"
|
||||
? "text-green-600"
|
||||
: "text-red-700",
|
||||
},
|
||||
response?.result?.status || "Error"
|
||||
),
|
||||
]),
|
||||
// Time
|
||||
responseTime != null &&
|
||||
m("span", [
|
||||
"Time: ",
|
||||
m("span", { class: "text-green-600" }, `${responseTime}ms`),
|
||||
]),
|
||||
// Size
|
||||
responseSize != null &&
|
||||
m("span", [
|
||||
"Size: ",
|
||||
m("span", { class: "text-green-600" }, `${responseSize} bytes`),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
]);
|
||||
},
|
||||
};
|
||||
@@ -23,6 +23,10 @@
|
||||
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
|
||||
<script src="https://unpkg.com/mithril/mithril.js"></script>
|
||||
<script src="/static/js/mithril/editor.js"></script>
|
||||
<script src="/static/js/mithril/responseView.js"></script>
|
||||
<script src="/static/js/mithril/alert.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">
|
||||
|
||||
@@ -2,9 +2,14 @@
|
||||
|
||||
{% block page %}
|
||||
|
||||
{{ render_partial('dashboard/http_functions/header.html', title='Try', user_id=user_id, function_id=function_id,
|
||||
name=name,
|
||||
show_refresh=False, show_link=False, show_edit_form=True, show_client=True, show_logs=True, show_history=True) }}
|
||||
{{ render_partial('dashboard/http_functions/header.html', user_id=user_id, function_id=function_id,
|
||||
active_tab='client',
|
||||
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')) }}
|
||||
|
||||
<div class="mx-auto w-full pt-4" id="client-u{{ user_id }}-f{{ function_id }}">
|
||||
</div>
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
{% extends 'dashboard.html' %}
|
||||
|
||||
{% block page %}
|
||||
|
||||
{{ 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,
|
||||
show_history=True) }}
|
||||
|
||||
{{ render_partial('function_editor.html', function_id=function_id, name=name, script=script,
|
||||
environment_info=environment_info,
|
||||
is_public=is_public, log_request=log_request, log_response=log_response, version_number=version_number, is_edit=True) }}
|
||||
|
||||
{% endblock %}
|
||||
41
templates/dashboard/http_functions/editor.html
Normal file
41
templates/dashboard/http_functions/editor.html
Normal file
@@ -0,0 +1,41 @@
|
||||
{% extends 'dashboard.html' %}
|
||||
|
||||
{% block page %}
|
||||
|
||||
{{ render_partial('dashboard/http_functions/header.html', user_id=user_id, function_id=function_id,
|
||||
active_tab='edit',
|
||||
show_edit_form=True,
|
||||
show_logs=True,
|
||||
show_client=True,
|
||||
show_history=True,
|
||||
edit_url=edit_url,
|
||||
cancel_url=cancel_url) }}
|
||||
|
||||
|
||||
<div id="app" class="p-1">
|
||||
<!-- The Editor component will be mounted here -->
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Mount the component
|
||||
m.mount(document.getElementById("app"), {
|
||||
view: () => m(Editor, {
|
||||
name: '{{ name }}',
|
||||
functionId: {{ id }},
|
||||
jsValue: {{ script_content | tojson | safe }},
|
||||
jsonValue: {{ environment_info | tojson | safe }},
|
||||
isEdit: true,
|
||||
showHeader: true,
|
||||
isPublic: {{ is_public | tojson }},
|
||||
logRequest: {{ log_request | tojson }},
|
||||
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 '' }}",
|
||||
showDeleteButton: true
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
@@ -1,103 +1,108 @@
|
||||
<div>
|
||||
<div class="flex items-center pb-1" data-id="51">
|
||||
<div class="flex items-center">
|
||||
<div class="bg-white dark:bg-gray-800 border-b">
|
||||
|
||||
<!-- Action Buttons Row -->
|
||||
<div class="flex items-center justify-between pb-2">
|
||||
<!-- Action Buttons (left side) -->
|
||||
<div class="flex items-center space-x-4">
|
||||
<h1 class="font-semibold text-lg md:text-2xl" data-id="52">
|
||||
{{ title }}{% if show_name|default(true, false) %}: <span class="font-mono">{{ name }}</span>{% endif %}
|
||||
{{ title }}
|
||||
</h1>
|
||||
|
||||
{% if show_refresh|default(true, false) %}
|
||||
<div class="ml-2 cursor-pointer text-gray-500" hx-get="{{ refresh_url }}" hx-target="#container"
|
||||
hx-swap="innerHTML" hx-push-url="true">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
||||
stroke="currentColor" class="w-6 h-6" data-darkreader-inline-stroke=""
|
||||
style="--darkreader-inline-stroke: currentColor;">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99">
|
||||
</path>
|
||||
</svg>
|
||||
</div>
|
||||
{% if show_edit_form|default(false, true) %}
|
||||
<button
|
||||
class="group flex flex-col items-center {% if active_tab == 'edit' %}text-blue-600{% else %}text-gray-500 hover:text-blue-600{% endif %}"
|
||||
hx-get="{{ edit_url }}" hx-target="#container" hx-swap="innerHTML" hx-push-url="true">
|
||||
<div
|
||||
class="p-2 rounded-lg {% if active_tab == 'edit' %}bg-blue-50{% else %}group-hover:bg-blue-50{% endif %}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
||||
stroke="currentColor" data-slot="icon" class="w-6 h-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10" />
|
||||
</svg>
|
||||
</div>
|
||||
<span class="text-xs font-medium">Edit</span>
|
||||
</button>
|
||||
{% endif %}
|
||||
|
||||
{% if show_logs|default(false, true) %}
|
||||
<div class="ml-2 cursor-pointer text-gray-500"
|
||||
<button
|
||||
class="group flex flex-col items-center {% if active_tab == 'logs' %}text-blue-600{% else %}text-gray-500 hover:text-blue-600{% endif %}"
|
||||
hx-get="{{ url_for('get_http_function_logs', 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 24 24" stroke-width="1.5"
|
||||
stroke="currentColor" data-slot="icon" class="w-6 h-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
class="p-2 rounded-lg {% if active_tab == 'logs' %}bg-blue-50{% else %}group-hover:bg-blue-50{% endif %}">
|
||||
<svg class="w-6 h-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||
stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z" />
|
||||
</svg>
|
||||
</div>
|
||||
<span class="text-xs font-medium">Logs</span>
|
||||
</button>
|
||||
{% endif %}
|
||||
|
||||
{% if show_client|default(false, true) %}
|
||||
<div class="ml-2 cursor-pointer text-gray-500" hx-get="{{ url_for('client', 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 24 24" stroke-width="1.5"
|
||||
stroke="currentColor" class="w-6 h-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="m3.75 13.5 10.5-11.25L12 10.5h8.25L9.75 21.75 12 13.5H3.75Z" />
|
||||
</svg>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if show_edit_form|default(false, true) %}
|
||||
<div class="ml-2 cursor-pointer text-gray-500"
|
||||
hx-get="{{ url_for('get_http_function_edit_form', 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 24 24" stroke-width="1.5"
|
||||
stroke="currentColor" data-slot="icon" class="w-6 h-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10" />
|
||||
</svg>
|
||||
</div>
|
||||
<button
|
||||
class="group flex flex-col items-center {% if active_tab == 'client' %}text-blue-600{% else %}text-gray-500 hover:text-blue-600{% endif %}"
|
||||
hx-get="{{ url_for('client', function_id=function_id) }}" hx-target="#container" hx-swap="innerHTML"
|
||||
hx-push-url="true">
|
||||
<div
|
||||
class="p-2 rounded-lg {% if active_tab == 'client' %}bg-blue-50{% else %}group-hover:bg-blue-50{% endif %}">
|
||||
<svg class="w-6 h-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||
stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="m3.75 13.5 10.5-11.25L12 10.5h8.25L9.75 21.75 12 13.5H3.75Z" />
|
||||
</svg>
|
||||
</div>
|
||||
<span class="text-xs font-medium">Client</span>
|
||||
</button>
|
||||
{% endif %}
|
||||
|
||||
{% if show_history|default(false, true) %}
|
||||
<div class="ml-2 cursor-pointer text-gray-500"
|
||||
<button
|
||||
class="group flex flex-col items-center {% if active_tab == 'history' %}text-blue-600{% else %}text-gray-500 hover:text-blue-600{% endif %}"
|
||||
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>
|
||||
<div
|
||||
class="p-2 rounded-lg {% if active_tab == 'history' %}bg-blue-50{% else %}group-hover:bg-blue-50{% endif %}">
|
||||
<svg class="w-6 h-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 512 512"
|
||||
stroke-width="25" stroke="currentColor">
|
||||
<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>
|
||||
<span class="text-xs font-medium">History</span>
|
||||
</button>
|
||||
{% endif %}
|
||||
|
||||
{% if show_new|default(false, true) %}
|
||||
<button
|
||||
class="group flex flex-col items-center {% if active_tab == 'new' %}text-blue-600{% else %}text-gray-500 hover:text-blue-600{% endif %}"
|
||||
hx-get="{{ new_url }}" hx-target="#container" hx-swap="innerHTML" hx-push-url="true">
|
||||
<div
|
||||
class="p-2 rounded-lg {% if active_tab == 'new' %}bg-blue-50{% else %}group-hover:bg-blue-50{% endif %}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
||||
stroke="currentColor" class="w-6 h-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
|
||||
</svg>
|
||||
</div>
|
||||
<span class="text-xs font-medium">New</span>
|
||||
</button>
|
||||
{% 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"
|
||||
hx-get="{{ url_for('dashboard_http_functions') }}" hx-target="#container" hx-swap="innerHTML"
|
||||
hx-push-url="true">
|
||||
<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" data-darkreader-inline-stroke=""
|
||||
style="--darkreader-inline-stroke: currentColor;">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
<span>Cancel</span>
|
||||
|
||||
<!-- Cancel Button (right side) -->
|
||||
<button class="group flex flex-col items-center text-gray-500 hover:text-blue-600"
|
||||
hx-get="{{ cancel_url|default(url_for('dashboard_http_functions')) }}" hx-target="#container"
|
||||
hx-swap="innerHTML" hx-push-url="true">
|
||||
<div class="p-2 rounded-lg group-hover:bg-blue-50">
|
||||
<svg class="w-6 h-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||
stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<span class="text-xs font-medium">Cancel</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{% if show_link|default(true, false) %}
|
||||
<div class="flex">
|
||||
<a class="text-gray-300" href="{{ url_for('execute_http_function', user_id=user_id, function=name) }}">{{
|
||||
url_for('execute_http_function', user_id=user_id, function=name) }}</a>
|
||||
<button
|
||||
class="inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 hover:bg-accent hover:text-accent-foreground px-2 text-gray-600 dark:text-gray-400"
|
||||
data-id="24">
|
||||
<span class="sr-only" data-id="25">Add Link</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-4 h-4"
|
||||
data-id="26" data-darkreader-inline-stroke="" style="--darkreader-inline-stroke: currentColor;">
|
||||
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71">
|
||||
</path>
|
||||
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71">
|
||||
</path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -2,10 +2,14 @@
|
||||
|
||||
{% 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) }}
|
||||
{{ render_partial('dashboard/http_functions/header.html', user_id=user_id, function_id=function_id,
|
||||
active_tab='history',
|
||||
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')) }}
|
||||
|
||||
<!-- Timeline -->
|
||||
<div>
|
||||
@@ -185,7 +189,6 @@ show_edit_form=True) }}
|
||||
});
|
||||
editor.setTheme("ace/theme/github_dark");
|
||||
editor.session.setMode("ace/mode/javascript");
|
||||
|
||||
</script>
|
||||
|
||||
<!-- End Item -->
|
||||
|
||||
@@ -2,10 +2,14 @@
|
||||
|
||||
{% block page %}
|
||||
|
||||
{{ 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,
|
||||
show_history=True) }}
|
||||
{{ render_partial('dashboard/http_functions/header.html', user_id=user_id, function_id=function_id,
|
||||
active_tab='logs',
|
||||
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')) }}
|
||||
|
||||
<div class="block md:grid md:grid-cols-4 md:gap-4 p-4">
|
||||
<!-- Headers -->
|
||||
|
||||
@@ -2,12 +2,40 @@
|
||||
|
||||
{% block page %}
|
||||
|
||||
{{ render_partial('dashboard/http_functions/header.html', title='New HTTP function', user_id=user_id, show_name=False,
|
||||
show_refresh=False, show_logs=False,
|
||||
show_client=False, show_link=False)
|
||||
{{ render_partial('dashboard/http_functions/header.html',
|
||||
user_id=user_id,
|
||||
show_name=False,
|
||||
show_refresh=False,
|
||||
show_logs=False,
|
||||
show_client=False,
|
||||
show_link=False,
|
||||
dashboardUrl=url_for('dashboard_http_functions'),
|
||||
title='New HTTP Function')
|
||||
}}
|
||||
|
||||
{{ render_partial('function_editor.html', name=name, script=script, environment_info=environment_info,
|
||||
is_public=is_public, log_request=log_request, log_response=log_response, is_add=True) }}
|
||||
<div id="app" class="p-1">
|
||||
<!-- The Editor component will be mounted here -->
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Mount the component
|
||||
m.mount(document.getElementById("app"), {
|
||||
view: () => m(Editor, {
|
||||
name: '{{ name }}',
|
||||
jsValue: {{ script | tojson | safe }},
|
||||
jsonValue: {{ environment_info | tojson | safe }},
|
||||
isEdit: false,
|
||||
isAdd: true,
|
||||
showHeader: true,
|
||||
isPublic: {{ is_public | tojson }},
|
||||
logRequest: {{ log_request | tojson }},
|
||||
logResponse: {{ log_response | tojson }},
|
||||
executeUrl: "{{ url_for('execute_code', playground='true') }}",
|
||||
saveUrl: "{{ url_for('api_create_http_function') }}",
|
||||
showDeleteButton: false,
|
||||
dashboardUrl: "{{ url_for('dashboard_http_functions') }}"
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
@@ -80,7 +80,7 @@
|
||||
</button>
|
||||
<button
|
||||
class="inline-flex items-center justify-center text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground h-9 rounded-md px-3 text-gray-800"
|
||||
hx-get="{{ url_for('get_http_function_edit_form', function_id=function.id) }}"
|
||||
hx-get="{{ url_for('http_function_editor', function_id=function.id) }}"
|
||||
hx-target="#container" hx-swap="innerHTML" hx-push-url="true">
|
||||
Edit
|
||||
</button>
|
||||
|
||||
@@ -21,6 +21,13 @@
|
||||
integrity="sha512-g9yptARGYXbHR9r3kTKIAzF+vvmgEieTxuuUUcHC5tKYFpLR3DR+lsisH2KZJG2Nwaou8jjYVRdbbbBQI3Bo5w=="
|
||||
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
|
||||
<script src="https://unpkg.com/mithril/mithril.js"></script>
|
||||
<script src="/static/js/mithril/editor.js"></script>
|
||||
<script src="/static/js/mithril/responseView.js"></script>
|
||||
<script src="/static/js/mithril/alert.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");
|
||||
|
||||
@@ -109,8 +116,24 @@
|
||||
View documentation</a>
|
||||
</div>
|
||||
|
||||
<div id="app" class="p-1">
|
||||
<!-- The Editor component will be mounted here -->
|
||||
</div>
|
||||
|
||||
{{ render_partial('function_editor.html', name=name, script=script, environment_info=environment_info) }}
|
||||
<script>
|
||||
// Mount the component
|
||||
m.mount(document.getElementById("app"), {
|
||||
view: () => m(Editor, {
|
||||
name: '{{ name }}',
|
||||
jsValue: {{ script | tojson | safe }},
|
||||
jsonValue: {{ environment_info | tojson | safe }},
|
||||
isEdit: true,
|
||||
showHeader: false,
|
||||
showFunctionSettings: false,
|
||||
executeUrl: "{{ url_for('execute_code', playground='true') }}",
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<section class="py-8">
|
||||
<div class="container mx-auto flex flex-wrap pt-4 pb-12">
|
||||
|
||||
Reference in New Issue
Block a user