- 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
359 lines
13 KiB
JavaScript
359 lines
13 KiB
JavaScript
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`),
|
|
]),
|
|
]),
|
|
]),
|
|
]);
|
|
},
|
|
};
|