diff --git a/djangorota/djangorota/templates/base.html b/djangorota/djangorota/templates/base.html index a1a02bd..c6f9fd0 100644 --- a/djangorota/djangorota/templates/base.html +++ b/djangorota/djangorota/templates/base.html @@ -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); } }); diff --git a/djangorota/djangorota/templates/rota/rota_detail.html b/djangorota/djangorota/templates/rota/rota_detail.html index 778b503..9d93070 100644 --- a/djangorota/djangorota/templates/rota/rota_detail.html +++ b/djangorota/djangorota/templates/rota/rota_detail.html @@ -140,8 +140,8 @@
{% csrf_token %}
-
-
+
+
diff --git a/djangorota/djangorota/templates/rota/rota_run_detail.html b/djangorota/djangorota/templates/rota/rota_run_detail.html index 5f4c3a8..3a76d96 100644 --- a/djangorota/djangorota/templates/rota/rota_run_detail.html +++ b/djangorota/djangorota/templates/rota/rota_run_detail.html @@ -5,6 +5,7 @@ Rota run {{ run.id }} + @@ -21,13 +22,14 @@

Back to rota Open export +

Log
-
{{ run.log }}
+
{{ run.log }}
@@ -36,7 +38,7 @@
Result (raw)
-
{{ result_pretty|default:run.result }}
+
{{ result_pretty|default:run.result }}
diff --git a/djangorota/rota/views.py b/djangorota/rota/views.py index 0cffaae..d91c5c2 100644 --- a/djangorota/rota/views.py +++ b/djangorota/rota/views.py @@ -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'Status: {run.get_status_display()}' f' started: {run.started_at}' + f' server_time: {timezone.now().isoformat()}' "" ) - 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'
Status: {run.get_status_display()}' - f' finished: {run.finished_at}
' - "" + # 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'
Status: {escape(run.get_status_display())}' + f' finished: {escape(str(run.finished_at))}
' ) - return HttpResponse(final_html) + + # OOB swap for the log block + log_fragment = f'
{escape(run.log or "")}
' + + # OOB swap for the result block + result_fragment = f'
{escape(result_pretty or "")}
' + + # OOB swap for a 'View final output' button + view_button = ( + f'
' + f'View final output' + "
" + ) + + 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):