Add version history view for timer functions
- Implement `/history/<function_id>` route in timer routes to fetch and display function versions - Create new Mithril `DiffView` component for comparing script versions - Add new history template for timer functions with version comparison - Include version diff functionality using AceDiff library - Update header and edit templates to include history URL for timer functions
This commit is contained in:
@@ -402,4 +402,41 @@ def logs(function_id):
|
|||||||
return render_block(environment, 'dashboard/timer_functions/logs.html', 'page', **args)
|
return render_block(environment, 'dashboard/timer_functions/logs.html', 'page', **args)
|
||||||
return render_template('dashboard/timer_functions/logs.html', **args)
|
return render_template('dashboard/timer_functions/logs.html', **args)
|
||||||
|
|
||||||
|
@timer.route('/history/<int:function_id>')
|
||||||
|
@login_required
|
||||||
|
def history(function_id):
|
||||||
|
# Fetch the timer function to verify ownership
|
||||||
|
timer_function = db.execute("""
|
||||||
|
SELECT id, name, code, version_number
|
||||||
|
FROM timer_functions
|
||||||
|
WHERE id = %s AND user_id = %s
|
||||||
|
""", [function_id, current_user.id], one=True)
|
||||||
|
|
||||||
|
if not timer_function:
|
||||||
|
flash('Timer function not found', 'error')
|
||||||
|
return redirect(url_for('timer.overview'))
|
||||||
|
|
||||||
|
# Fetch all versions
|
||||||
|
versions = db.execute("""
|
||||||
|
SELECT version_number, script, versioned_at
|
||||||
|
FROM timer_function_versions
|
||||||
|
WHERE timer_function_id = %s
|
||||||
|
ORDER BY version_number DESC
|
||||||
|
""", [function_id])
|
||||||
|
|
||||||
|
# Convert datetime objects to ISO format strings
|
||||||
|
for version in versions:
|
||||||
|
version['versioned_at'] = version['versioned_at'].isoformat() if version['versioned_at'] else None
|
||||||
|
|
||||||
|
args = {
|
||||||
|
'user_id': current_user.id,
|
||||||
|
'function_id': function_id,
|
||||||
|
'timer_function': timer_function,
|
||||||
|
'versions': versions
|
||||||
|
}
|
||||||
|
|
||||||
|
if htmx:
|
||||||
|
return render_block(environment, 'dashboard/timer_functions/history.html', 'page', **args)
|
||||||
|
return render_template('dashboard/timer_functions/history.html', **args)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
94
static/js/mithril/diffView.js
Normal file
94
static/js/mithril/diffView.js
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
<script src="/static/js/mithril/editor.js"></script>
|
<script src="/static/js/mithril/editor.js"></script>
|
||||||
<script src="/static/js/mithril/responseView.js"></script>
|
<script src="/static/js/mithril/responseView.js"></script>
|
||||||
<script src="/static/js/mithril/alert.js"></script>
|
<script src="/static/js/mithril/alert.js"></script>
|
||||||
|
<script src="/static/js/mithril/diffView.js"></script>
|
||||||
|
|
||||||
<script src="https://unpkg.com/ace-diff@^2"></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">
|
<link href="https://unpkg.com/ace-diff@^2/dist/ace-diff.min.css" rel="stylesheet">
|
||||||
|
|||||||
@@ -9,8 +9,9 @@ show_logs=True,
|
|||||||
show_client=True,
|
show_client=True,
|
||||||
show_history=True,
|
show_history=True,
|
||||||
edit_url=url_for('http_function_editor', function_id=function_id),
|
edit_url=url_for('http_function_editor', function_id=function_id),
|
||||||
cancel_url=url_for('dashboard_http_functions',
|
cancel_url=url_for('dashboard_http_functions'),
|
||||||
logs_url=url_for('get_http_function_logs', function_id=function_id))) }}
|
logs_url=url_for('get_http_function_logs', function_id=function_id),
|
||||||
|
history_url=url_for('get_http_function_history', function_id=function_id)) }}
|
||||||
|
|
||||||
<div class="mx-auto w-full pt-4" id="client-u{{ user_id }}-f{{ function_id }}">
|
<div class="mx-auto w-full pt-4" id="client-u{{ user_id }}-f{{ function_id }}">
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ show_client=True,
|
|||||||
show_history=True,
|
show_history=True,
|
||||||
edit_url=edit_url,
|
edit_url=edit_url,
|
||||||
cancel_url=cancel_url,
|
cancel_url=cancel_url,
|
||||||
logs_url=url_for('get_http_function_logs', function_id=function_id)) }}
|
logs_url=url_for('get_http_function_logs', function_id=function_id),
|
||||||
|
history_url=url_for('get_http_function_history', function_id=function_id)) }}
|
||||||
|
|
||||||
|
|
||||||
<div id="app" class="p-1">
|
<div id="app" class="p-1">
|
||||||
|
|||||||
@@ -59,8 +59,7 @@
|
|||||||
{% if show_history|default(false, true) %}
|
{% if show_history|default(false, true) %}
|
||||||
<button
|
<button
|
||||||
class="group flex flex-col items-center {% if active_tab == 'history' %}text-blue-600{% else %}text-gray-500 hover:text-blue-600{% endif %}"
|
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-get="{{ history_url }}" hx-target="#container" hx-swap="innerHTML" hx-push-url="true">
|
||||||
hx-swap="innerHTML" hx-push-url="true">
|
|
||||||
<div
|
<div
|
||||||
class="p-2 rounded-lg {% if active_tab == 'history' %}bg-blue-50{% else %}group-hover:bg-blue-50{% endif %}">
|
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"
|
<svg class="w-6 h-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 512 512"
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ show_client=True,
|
|||||||
show_history=True,
|
show_history=True,
|
||||||
edit_url=url_for('http_function_editor', function_id=function_id),
|
edit_url=url_for('http_function_editor', function_id=function_id),
|
||||||
cancel_url=url_for('dashboard_http_functions'),
|
cancel_url=url_for('dashboard_http_functions'),
|
||||||
logs_url=url_for('get_http_function_logs', function_id=function_id)) }}
|
logs_url=url_for('get_http_function_logs', function_id=function_id),
|
||||||
|
history_url=url_for('get_http_function_history', function_id=function_id)) }}
|
||||||
|
|
||||||
<!-- Timeline -->
|
<!-- Timeline -->
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -9,8 +9,9 @@ show_logs=True,
|
|||||||
show_client=True,
|
show_client=True,
|
||||||
show_history=True,
|
show_history=True,
|
||||||
edit_url=url_for('http_function_editor', function_id=function_id),
|
edit_url=url_for('http_function_editor', function_id=function_id),
|
||||||
cancel_url=url_for('dashboard_http_functions',
|
cancel_url=url_for('dashboard_http_functions'),
|
||||||
logs_url=url_for('get_http_function_logs', function_id=function_id))) }}
|
logs_url=url_for('get_http_function_logs', function_id=function_id),
|
||||||
|
history_url=url_for('get_http_function_history', function_id=function_id)) }}
|
||||||
|
|
||||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||||
<div class="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden">
|
<div class="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden">
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ show_client=False,
|
|||||||
show_history=True,
|
show_history=True,
|
||||||
edit_url=url_for('timer.edit', function_id=function_id),
|
edit_url=url_for('timer.edit', function_id=function_id),
|
||||||
cancel_url=url_for('timer.overview'),
|
cancel_url=url_for('timer.overview'),
|
||||||
logs_url=url_for('timer.logs', function_id=function_id)) }}
|
logs_url=url_for('timer.logs', function_id=function_id),
|
||||||
|
history_url=url_for('timer.history', function_id=function_id)) }}
|
||||||
|
|
||||||
|
|
||||||
<div id="app" class="p-1">
|
<div id="app" class="p-1">
|
||||||
|
|||||||
26
templates/dashboard/timer_functions/history.html
Normal file
26
templates/dashboard/timer_functions/history.html
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{% extends 'dashboard.html' %}
|
||||||
|
|
||||||
|
{% block page %}
|
||||||
|
|
||||||
|
{{ 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_history=True,
|
||||||
|
edit_url=url_for('timer.edit', function_id=function_id),
|
||||||
|
cancel_url=url_for('timer.overview'),
|
||||||
|
logs_url=url_for('timer.logs', function_id=function_id),
|
||||||
|
history_url=url_for('timer.history', function_id=function_id))
|
||||||
|
}}
|
||||||
|
|
||||||
|
<div id="version-diff"></div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Mount the Mithril component with versions as an attribute
|
||||||
|
m.mount(document.getElementById("version-diff"), {
|
||||||
|
view: () => m(DiffView, { versions: {{ versions| tojson }} })
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
@@ -9,7 +9,8 @@ show_logs=True,
|
|||||||
show_history=True,
|
show_history=True,
|
||||||
edit_url=url_for('timer.edit', function_id=function_id),
|
edit_url=url_for('timer.edit', function_id=function_id),
|
||||||
cancel_url=url_for('timer.overview'),
|
cancel_url=url_for('timer.overview'),
|
||||||
logs_url=url_for('timer.logs', function_id=function_id)) }}
|
logs_url=url_for('timer.logs', function_id=function_id),
|
||||||
|
history_url=url_for('timer.history', function_id=function_id)) }}
|
||||||
|
|
||||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||||
<div class="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden">
|
<div class="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden">
|
||||||
|
|||||||
Reference in New Issue
Block a user