diff --git a/routes/timer.py b/routes/timer.py index b0a5b14..bb6c180 100644 --- a/routes/timer.py +++ b/routes/timer.py @@ -425,8 +425,8 @@ def logs(function_id): def history(function_id): # Fetch the timer function to verify ownership timer_function = db.execute(""" - SELECT id, name, code, version_number - FROM timer_functions + SELECT id, name, code AS script, version_number + FROM timer_functions WHERE id = %s AND user_id = %s """, [function_id, current_user.id], one=True) @@ -450,7 +450,8 @@ def history(function_id): 'user_id': current_user.id, 'function_id': function_id, 'timer_function': timer_function, - 'versions': versions + 'versions': versions, + 'title': timer_function['name'] } if htmx: diff --git a/static/js/mithril/FunctionHistory.js b/static/js/mithril/FunctionHistory.js index 495fef3..ce90148 100644 --- a/static/js/mithril/FunctionHistory.js +++ b/static/js/mithril/FunctionHistory.js @@ -1,96 +1,170 @@ const FunctionHistory = { - oninit: function(vnode) { - // Initialize indices in state - vnode.state.leftIndex = vnode.attrs.versions.length - 1; // Earliest - vnode.state.rightIndex = 0; // Latest - }, - view: function (vnode) { - const versions = vnode.attrs.versions; - return m("div", [ - m("div.flex.gap-4.mb-4", [ - m("div.flex-1", [ - m("label.block.text-sm.font-medium.text-gray-700", "Left Version"), - m("select.mt-1.block.w-full.rounded-md.border-gray-300.shadow-sm", { - onchange: (e) => { - vnode.state.leftIndex = parseInt(e.target.value); - vnode.state.leftVersion = versions[vnode.state.leftIndex]; - vnode.state.updateDiff(); - }, - value: vnode.state.leftIndex - }, versions.map((v, idx) => - m("option", { value: idx }, `Version ${v.version_number} (${new Date(v.versioned_at).toLocaleString()})`) - )) - ]), - m("div.flex-1", [ - m("label.block.text-sm.font-medium.text-gray-700", "Right Version"), - m("select.mt-1.block.w-full.rounded-md.border-gray-300.shadow-sm", { - onchange: (e) => { - vnode.state.rightIndex = parseInt(e.target.value); - vnode.state.rightVersion = versions[vnode.state.rightIndex]; - vnode.state.updateDiff(); - }, - value: vnode.state.rightIndex - }, versions.map((v, idx) => - m("option", { value: idx }, `Version ${v.version_number} (${new Date(v.versioned_at).toLocaleString()})`) - )) - ]) - ]), - m("div", { - id: "diff-container", - style: "height: 500px; position: relative;" - }) - ]); - }, - oncreate: function (vnode) { - const versions = vnode.attrs.versions; - // Initialize with the earliest and most recent versions if available - if (versions.length >= 2) { - vnode.state.leftVersion = versions[versions.length - 1]; // Earliest version - vnode.state.rightVersion = versions[0]; // Latest version - } else if (versions.length === 1) { - vnode.state.leftVersion = versions[0]; - vnode.state.rightVersion = versions[0]; - } + oninit: function (vnode) { + const versions = vnode.attrs.versions; + vnode.state.mode = "view"; // 'view' or 'diff' + vnode.state.selectedVersion = versions[0]; + vnode.state.leftVersion = versions.length > 1 ? versions[1] : versions[0]; + vnode.state.rightVersion = versions[0]; + vnode.state.editor = null; + vnode.state.aceDiffer = null; + }, - vnode.state.updateDiff = function () { - if (vnode.state.aceDiffer) { - // Clean up previous instance - vnode.state.aceDiffer.destroy(); - } + view: function (vnode) { + const { versions } = vnode.attrs; + const { mode, selectedVersion, leftVersion, rightVersion } = vnode.state; - vnode.state.aceDiffer = new AceDiff({ - element: '#diff-container', - mode: "ace/mode/javascript", - left: { - content: vnode.state.leftVersion ? vnode.state.leftVersion.script : "", - editable: false, + return m(".flex.flex-col.md:flex-row.h-full", [ + // Vertical Timeline + m(".w-full.md:w-64.border-b.md:border-r.md:border-b-0.overflow-y-auto", [ + m("div.p-4.border-b.flex.justify-between.items-center", [ + m("h3.text-lg.font-semibold", "History"), + m( + "button.text-sm.bg-gray-200.hover:bg-gray-300.text-gray-800.font-semibold.py-1.px-2.rounded", + { + onclick: () => { + vnode.state.mode = mode === "view" ? "diff" : "view"; + }, + }, + mode === "view" ? "Compare" : "View" + ), + ]), + m( + "ul.p-2", + versions.map((version) => { + const isActive = + selectedVersion && + version.version_number === selectedVersion.version_number; + return m( + "li.p-2.cursor-pointer", + { + class: isActive + ? "bg-gray-200 rounded" + : "hover:bg-gray-100 rounded", + onclick: () => { + vnode.state.selectedVersion = version; + if (mode === "diff") { + vnode.state.rightVersion = version; + } }, - right: { - content: vnode.state.rightVersion ? vnode.state.rightVersion.script : "", - editable: false, - } - }); + }, + [ + m("div.font-bold", `Version ${version.version_number}`), + m( + "div.text-sm.text-gray-600", + new Date(version.versioned_at).toLocaleString() + ), + ] + ); + }) + ), + ]), - // Configure both editors - const editors = vnode.state.aceDiffer.getEditors(); - ['left', 'right'].forEach(side => { - editors[side].setOptions({ - maxLines: 20, - autoScrollEditorIntoView: true, - }); - editors[side].session.setOption("useWorker", false); - }); - }; + // Code Viewer or Differ + m(".flex-1.p-4", [ + mode === "view" + ? m("div", [ + m( + "h3.text-lg.font-semibold.mb-2", + `Version ${selectedVersion.version_number}` + ), + m("div#editor-history", { style: "height: 600px;" }), + ]) + : m("div", [ + m(".flex.flex-col.md:flex-row.gap-4.mb-4", [ + m(".flex-1", [ + m("label.block.text-sm.font-medium.text-gray-700", "Left"), + m( + "select.mt-1.block.w-full.rounded-md.border-gray-300.shadow-sm", + { + onchange: (e) => + (vnode.state.leftVersion = versions.find( + (v) => v.version_number == e.target.value + )), + value: leftVersion.version_number, + }, + versions.map((v) => + m( + "option", + { value: v.version_number }, + `Version ${v.version_number}` + ) + ) + ), + ]), + m(".flex-1", [ + m("label.block.text-sm.font-medium.text-gray-700", "Right"), + m( + "select.mt-1.block.w-full.rounded-md.border-gray-300.shadow-sm", + { + onchange: (e) => + (vnode.state.rightVersion = versions.find( + (v) => v.version_number == e.target.value + )), + value: rightVersion.version_number, + }, + versions.map((v) => + m( + "option", + { value: v.version_number }, + `Version ${v.version_number}` + ) + ) + ), + ]), + ]), + m("#diff-container", { style: "height: 600px;" }), + ]), + ]), + ]); + }, - // Initial diff setup - vnode.state.updateDiff(); - }, - onremove: function (vnode) { - // Clean up AceDiff when component is removed - if (vnode.state.aceDiffer) { - vnode.state.aceDiffer.destroy(); - } + oncreate: function (vnode) { + this.updateEditorOrDiffer(vnode); + }, + + onupdate: function (vnode) { + this.updateEditorOrDiffer(vnode); + }, + + updateEditorOrDiffer: function (vnode) { + const { mode, selectedVersion, leftVersion, rightVersion } = vnode.state; + + if (mode === "view") { + if (vnode.state.aceDiffer) { + vnode.state.aceDiffer.destroy(); + vnode.state.aceDiffer = null; + } + if (!vnode.state.editor) { + vnode.state.editor = ace.edit("editor-history"); + vnode.state.editor.setTheme("ace/theme/monokai"); + vnode.state.editor.session.setMode("ace/mode/javascript"); + vnode.state.editor.setReadOnly(true); + } + vnode.state.editor.setValue(selectedVersion.script, -1); + } else { + // diff mode + if (vnode.state.editor) { + vnode.state.editor.destroy(); + vnode.state.editor = null; + } + if (vnode.state.aceDiffer) { + vnode.state.aceDiffer.destroy(); + } + vnode.state.aceDiffer = new AceDiff({ + element: "#diff-container", + mode: "ace/mode/javascript", + left: { content: leftVersion.script, editable: false }, + right: { content: rightVersion.script, editable: false }, + }); } -}; + }, -export default FunctionHistory; + onremove: function (vnode) { + if (vnode.state.editor) { + vnode.state.editor.destroy(); + } + if (vnode.state.aceDiffer) { + vnode.state.aceDiffer.destroy(); + } + }, +}; diff --git a/static/js/mithril/diffView.js b/static/js/mithril/diffView.js index 2df3c71..efb8335 100644 --- a/static/js/mithril/diffView.js +++ b/static/js/mithril/diffView.js @@ -1,94 +1,118 @@ const DiffView = { - oninit: function(vnode) { - // Initialize indices in state - vnode.state.leftIndex = vnode.attrs.versions.length - 1; // Earliest - vnode.state.rightIndex = 0; // Latest - }, - view: function (vnode) { - const versions = vnode.attrs.versions; - return m("div", [ - m("div.flex.gap-4.mb-4", [ - m("div.flex-1", [ - m("label.block.text-sm.font-medium.text-gray-700", "Left Version"), - m("select.mt-1.block.w-full.rounded-md.border-gray-300.shadow-sm", { - onchange: (e) => { - vnode.state.leftIndex = parseInt(e.target.value); - vnode.state.leftVersion = versions[vnode.state.leftIndex]; - vnode.state.updateDiff(); - }, - value: vnode.state.leftIndex - }, versions.map((v, idx) => - m("option", { value: idx }, `Version ${v.version_number} (${new Date(v.versioned_at).toLocaleString()})`) - )) - ]), - m("div.flex-1", [ - m("label.block.text-sm.font-medium.text-gray-700", "Right Version"), - m("select.mt-1.block.w-full.rounded-md.border-gray-300.shadow-sm", { - onchange: (e) => { - vnode.state.rightIndex = parseInt(e.target.value); - vnode.state.rightVersion = versions[vnode.state.rightIndex]; - vnode.state.updateDiff(); - }, - value: vnode.state.rightIndex - }, versions.map((v, idx) => - m("option", { value: idx }, `Version ${v.version_number} (${new Date(v.versioned_at).toLocaleString()})`) - )) - ]) - ]), - m("div", { - id: "diff-container", - style: "height: 500px; position: relative;" - }) - ]); - }, - oncreate: function (vnode) { - const versions = vnode.attrs.versions; - // Initialize with the earliest and most recent versions if available - if (versions.length >= 2) { - vnode.state.leftVersion = versions[versions.length - 1]; // Earliest version - vnode.state.rightVersion = versions[0]; // Latest version - } else if (versions.length === 1) { - vnode.state.leftVersion = versions[0]; - vnode.state.rightVersion = versions[0]; - } - - vnode.state.updateDiff = function () { - if (vnode.state.aceDiffer) { - // Clean up previous instance - vnode.state.aceDiffer.destroy(); - } - - vnode.state.aceDiffer = new AceDiff({ - element: '#diff-container', - mode: "ace/mode/javascript", - left: { - content: vnode.state.leftVersion ? vnode.state.leftVersion.script : "", - editable: false, - }, - right: { - content: vnode.state.rightVersion ? vnode.state.rightVersion.script : "", - editable: false, - } - }); - - // Configure both editors - const editors = vnode.state.aceDiffer.getEditors(); - ['left', 'right'].forEach(side => { - editors[side].setOptions({ - maxLines: 20, - autoScrollEditorIntoView: true, - }); - editors[side].session.setOption("useWorker", false); - }); - }; - - // Initial diff setup - vnode.state.updateDiff(); - }, - onremove: function (vnode) { - // Clean up AceDiff when component is removed - if (vnode.state.aceDiffer) { - vnode.state.aceDiffer.destroy(); - } + oninit: function (vnode) { + // Initialize indices in state + vnode.state.leftIndex = vnode.attrs.versions.length - 1; // Earliest + vnode.state.rightIndex = 0; // Latest + }, + view: function (vnode) { + const versions = vnode.attrs.versions; + return m("div", [ + m("div.flex.gap-4.mb-4", [ + m("div.flex-1", [ + m("label.block.text-sm.font-medium.text-gray-700", "Left Version"), + m( + "select.mt-1.block.w-full.rounded-md.border-gray-300.shadow-sm", + { + onchange: (e) => { + vnode.state.leftIndex = parseInt(e.target.value); + vnode.state.leftVersion = versions[vnode.state.leftIndex]; + vnode.state.updateDiff(); + }, + value: vnode.state.leftIndex, + }, + versions.map((v, idx) => + m( + "option", + { value: idx }, + `Version ${v.version_number} (${new Date( + v.versioned_at + ).toLocaleString()})` + ) + ) + ), + ]), + m("div.flex-1", [ + m("label.block.text-sm.font-medium.text-gray-700", "Right Version"), + m( + "select.mt-1.block.w-full.rounded-md.border-gray-300.shadow-sm", + { + onchange: (e) => { + vnode.state.rightIndex = parseInt(e.target.value); + vnode.state.rightVersion = versions[vnode.state.rightIndex]; + vnode.state.updateDiff(); + }, + value: vnode.state.rightIndex, + }, + versions.map((v, idx) => + m( + "option", + { value: idx }, + `Version ${v.version_number} (${new Date( + v.versioned_at + ).toLocaleString()})` + ) + ) + ), + ]), + ]), + m("div", { + id: "diff-container", + style: "height: 500px; position: relative;", + }), + ]); + }, + oncreate: function (vnode) { + const versions = vnode.attrs.versions; + // Initialize with the earliest and most recent versions if available + if (versions.length >= 2) { + vnode.state.leftVersion = versions[versions.length - 1]; // Earliest version + vnode.state.rightVersion = versions[0]; // Latest version + } else if (versions.length === 1) { + vnode.state.leftVersion = versions[0]; + vnode.state.rightVersion = versions[0]; } -}; \ No newline at end of file + + vnode.state.updateDiff = function () { + if (vnode.state.aceDiffer) { + // Clean up previous instance + vnode.state.aceDiffer.destroy(); + } + + vnode.state.aceDiffer = new AceDiff({ + element: "#diff-container", + mode: "ace/mode/javascript", + left: { + content: vnode.state.leftVersion + ? vnode.state.leftVersion.script + : "", + editable: false, + }, + right: { + content: vnode.state.rightVersion + ? vnode.state.rightVersion.script + : "", + editable: false, + }, + }); + + // Configure both editors + const editors = vnode.state.aceDiffer.getEditors(); + ["left", "right"].forEach((side) => { + editors[side].setOptions({ + maxLines: 20, + autoScrollEditorIntoView: true, + }); + editors[side].session.setOption("useWorker", false); + }); + }; + + // Initial diff setup + vnode.state.updateDiff(); + }, + onremove: function (vnode) { + // Clean up AceDiff when component is removed + if (vnode.state.aceDiffer) { + vnode.state.aceDiffer.destroy(); + } + }, +}; diff --git a/templates/base.html b/templates/base.html index 6853461..b82ec0f 100644 --- a/templates/base.html +++ b/templates/base.html @@ -27,6 +27,7 @@ + diff --git a/templates/dashboard/http_functions/history.html b/templates/dashboard/http_functions/history.html index e4285d2..85767a8 100644 --- a/templates/dashboard/http_functions/history.html +++ b/templates/dashboard/http_functions/history.html @@ -13,12 +13,12 @@ cancel_url=url_for('http.overview'), logs_url=url_for('http.logs', function_id=function_id), history_url=url_for('http.history', function_id=function_id)) }} -
+ {% endblock %} \ No newline at end of file diff --git a/templates/dashboard/timer_functions/header.html b/templates/dashboard/timer_functions/header.html new file mode 100644 index 0000000..e157373 --- /dev/null +++ b/templates/dashboard/timer_functions/header.html @@ -0,0 +1,88 @@ +