Enhance rota run detail view with real-time log and result updates, and add "View final output" button
This commit is contained in:
@@ -162,6 +162,19 @@
|
||||
if(triggers.includes('leaveUpdated')){
|
||||
try{ console.debug('[DEBUG] htmx:afterRequest -> leaveUpdated'); if(window._calendars){ const cs = Object.values(window._calendars).filter(c=>c.refreshLeaves); console.debug('[DEBUG] afterRequest -> refreshing', cs.length, 'calendars'); cs.forEach(c => c.refreshLeaves && c.refreshLeaves()); } }catch(e){}
|
||||
}
|
||||
if(triggers.includes('runCompleted')){
|
||||
try{
|
||||
// Stop further HTMX polling by removing polling attributes from
|
||||
// the run status element and its container. The server will return
|
||||
// out-of-band (OOB) swaps to update the log/result areas in-place
|
||||
// and to insert a "View final output" button, so a full page
|
||||
// reload is no longer required.
|
||||
const el = document.querySelector('#run-status');
|
||||
if(el){ el.removeAttribute('hx-get'); el.removeAttribute('hx-trigger'); el.removeAttribute('hx-swap'); }
|
||||
const container = document.querySelector('#run-status-container');
|
||||
if(container){ container.removeAttribute('hx-get'); container.removeAttribute('hx-trigger'); }
|
||||
}catch(e){ console.error('runCompleted handler failed', e); }
|
||||
}
|
||||
}catch(e){ console.error('htmx:afterRequest robustness handler', e); }
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -140,8 +140,8 @@
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<div class="field is-grouped is-grouped-multiline">
|
||||
<div class="control"><label class="checkbox"><input type="checkbox" name="generate_builder" value="1"> Generate HTML export</label></div>
|
||||
<div class="control"><label class="checkbox"><input type="checkbox" name="builder_solve" value="1"> Solve when exporting (may be slow)</label></div>
|
||||
<div class="control"><label class="checkbox"><input type="checkbox" name="generate_builder" value="1" checked> Generate HTML export</label></div>
|
||||
<div class="control"><label class="checkbox"><input type="checkbox" name="builder_solve" value="1" checked> Solve when exporting (may be slow)</label></div>
|
||||
<div class="control"><button class="button is-primary" type="submit">Run rota</button></div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<meta charset="utf-8">
|
||||
<title>Rota run {{ run.id }}</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css" rel="stylesheet">
|
||||
<script src="https://unpkg.com/htmx.org@1.9.2"></script>
|
||||
<style>pre { white-space: pre-wrap; }</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -21,13 +22,14 @@
|
||||
<p class="mt-2">
|
||||
<a class="button" href="{% url 'rota:rota_detail' run.rota.id %}">Back to rota</a>
|
||||
<a class="button is-light" href="{% url 'rota:rota_run_export_html' run.id %}" target="_blank">Open export</a>
|
||||
<span id="view-final-output" style="margin-left:0.5rem;"></span>
|
||||
</p>
|
||||
|
||||
<div class="box">
|
||||
<details>
|
||||
<summary><strong>Log</strong></summary>
|
||||
<div style="margin-top:0.5rem; max-height:40vh; overflow:auto; border-top:1px solid #eee; padding-top:0.5rem;">
|
||||
<pre>{{ run.log }}</pre>
|
||||
<div id="run-log"><pre id="run-log-pre">{{ run.log }}</pre></div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
@@ -36,7 +38,7 @@
|
||||
<details>
|
||||
<summary><strong>Result (raw)</strong></summary>
|
||||
<div style="margin-top:0.5rem; max-height:40vh; overflow:auto; border-top:1px solid #eee; padding-top:0.5rem;">
|
||||
<pre>{{ result_pretty|default:run.result }}</pre>
|
||||
<div id="run-result"><pre id="run-result-pre">{{ result_pretty|default:run.result }}</pre></div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from django.shortcuts import render, get_object_or_404, redirect
|
||||
from django.utils import timezone
|
||||
from django.urls import reverse
|
||||
|
||||
from .models import RotaSchedule, Worker
|
||||
@@ -758,18 +759,54 @@ def rota_run_status(request, run_id):
|
||||
'hx-trigger="every 3s" hx-swap="outerHTML">'
|
||||
f'<span class="tag is-info">Status: {run.get_status_display()}</span>'
|
||||
f' <small>started: {run.started_at}</small>'
|
||||
f' <small style="margin-left:0.5rem; color:#666;">server_time: {timezone.now().isoformat()}</small>'
|
||||
"</div>"
|
||||
)
|
||||
return HttpResponse(html)
|
||||
resp = HttpResponse(html)
|
||||
# Prevent aggressive caching so polling always fetches fresh state
|
||||
resp['Cache-Control'] = 'no-cache, no-store, must-revalidate'
|
||||
resp['Pragma'] = 'no-cache'
|
||||
return resp
|
||||
|
||||
# Finished/failed: return final fragment and reload the page so the
|
||||
# user sees final logs/export without further polling.
|
||||
final_html = (
|
||||
f'<div id="run-status-final"><span class="tag is-success">Status: {run.get_status_display()}</span>'
|
||||
f' <small>finished: {run.finished_at}</small></div>'
|
||||
"<script>window.location.reload();</script>"
|
||||
# Build out-of-band swaps to update the log and result sections in-place
|
||||
from django.utils.html import escape
|
||||
|
||||
# Prepare pretty result if present
|
||||
try:
|
||||
if run.result is not None:
|
||||
import json as _json
|
||||
|
||||
result_pretty = _json.dumps(run.result, indent=2, default=str)
|
||||
else:
|
||||
result_pretty = ""
|
||||
except Exception:
|
||||
result_pretty = str(run.result)
|
||||
|
||||
status_fragment = (
|
||||
f'<div id="run-status"><span class="tag is-success">Status: {escape(run.get_status_display())}</span>'
|
||||
f' <small>finished: {escape(str(run.finished_at))}</small></div>'
|
||||
)
|
||||
return HttpResponse(final_html)
|
||||
|
||||
# OOB swap for the log block
|
||||
log_fragment = f'<div id="run-log" hx-swap-oob="true"><pre>{escape(run.log or "")}</pre></div>'
|
||||
|
||||
# OOB swap for the result block
|
||||
result_fragment = f'<div id="run-result" hx-swap-oob="true"><pre>{escape(result_pretty or "")}</pre></div>'
|
||||
|
||||
# OOB swap for a 'View final output' button
|
||||
view_button = (
|
||||
f'<div id="view-final-output" hx-swap-oob="true">'
|
||||
f'<a class="button is-link" href="{reverse("rota:rota_run_export_html", args=[run.id])}" target="_blank">View final output</a>'
|
||||
"</div>"
|
||||
)
|
||||
|
||||
body = status_fragment + log_fragment + result_fragment + view_button
|
||||
resp = HttpResponse(body)
|
||||
resp['Cache-Control'] = 'no-cache, no-store, must-revalidate'
|
||||
resp['Pragma'] = 'no-cache'
|
||||
return resp
|
||||
|
||||
|
||||
def rota_run_export_html(request, run_id):
|
||||
|
||||
Reference in New Issue
Block a user