feat: timer intervallo misura su ricetta + auto-logout per inattività
- Recipe: nuovo campo measurement_interval_minutes (migration 003) con UI nel recipe editor - Auto-logout: impostazione di sistema configurabile da admin/settings, timer inattività JS con warning modale 60s prima del logout, tracking eventi utente throttled - Navbar: aggiunto link "Impostazioni" per admin (desktop + mobile) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -115,6 +115,74 @@
|
||||
|
||||
{% block extra_js %}{% endblock %}
|
||||
|
||||
{% if current_user and auto_logout_minutes %}
|
||||
<script>
|
||||
function inactivityTimer() {
|
||||
var timeoutMs = {{ auto_logout_minutes }} * 60 * 1000;
|
||||
var warningMs = 60 * 1000;
|
||||
return {
|
||||
lastActivity: Date.now(),
|
||||
showWarning: false,
|
||||
remainingSeconds: 60,
|
||||
_interval: null,
|
||||
init() {
|
||||
var self = this;
|
||||
this._interval = setInterval(function() { self._check(); }, 5000);
|
||||
var lastUpdate = 0;
|
||||
var handler = function() {
|
||||
var now = Date.now();
|
||||
if (now - lastUpdate > 3000) {
|
||||
lastUpdate = now;
|
||||
self.lastActivity = now;
|
||||
self.showWarning = false;
|
||||
}
|
||||
};
|
||||
['mousemove','mousedown','keypress','touchstart','scroll'].forEach(function(evt) {
|
||||
window.addEventListener(evt, handler, { passive: true });
|
||||
});
|
||||
},
|
||||
resetTimer() {
|
||||
this.lastActivity = Date.now();
|
||||
this.showWarning = false;
|
||||
},
|
||||
_check() {
|
||||
var elapsed = Date.now() - this.lastActivity;
|
||||
var remaining = timeoutMs - elapsed;
|
||||
if (remaining <= 0) {
|
||||
clearInterval(this._interval);
|
||||
window.location.href = '{{ url_for("auth.logout") }}';
|
||||
} else if (remaining <= warningMs) {
|
||||
this.showWarning = true;
|
||||
this.remainingSeconds = Math.ceil(remaining / 1000);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
<div x-data="inactivityTimer()" class="contents">
|
||||
<template x-if="showWarning">
|
||||
<div class="fixed inset-0 z-[100] flex items-center justify-center bg-black/50" @click.self="resetTimer()">
|
||||
<div class="bg-[var(--bg-primary)] rounded-xl p-6 shadow-2xl max-w-sm mx-4 border border-[var(--border-color)]">
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<div class="w-10 h-10 rounded-full bg-amber-100 dark:bg-amber-900/30 flex items-center justify-center">
|
||||
<svg class="w-5 h-5 text-amber-600" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="text-lg font-semibold text-[var(--text-primary)]">{{ _('Sessione in scadenza') }}</h3>
|
||||
</div>
|
||||
<p class="text-sm text-[var(--text-secondary)] mb-5">
|
||||
{{ _('Sarai disconnesso tra') }} <span class="font-mono font-bold text-amber-600" x-text="remainingSeconds"></span> {{ _('secondi per inattività.') }}
|
||||
</p>
|
||||
<button @click="resetTimer()" class="btn btn-primary w-full">
|
||||
{{ _('Continua a lavorare') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Alpine.js CDN (defer) - must load AFTER extra_js so component functions are defined -->
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user