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; }, }), ]); }, };