fix(ucepsa): auto-refresh operator console safely

This commit is contained in:
Victor Fraile Garcia 2026-05-27 14:38:46 +02:00
parent d525443de9
commit 69f8416808

View File

@ -1,10 +1,10 @@
import json
import os import os
from datetime import datetime from datetime import datetime
import psycopg2 import psycopg2
import psycopg2.extras import psycopg2.extras
from flask import Flask, redirect, render_template_string, request, url_for from flask import Flask, Response, redirect, render_template_string, request, url_for
def getenv(name, default=None, required=False): def getenv(name, default=None, required=False):
value = os.getenv(name, default) value = os.getenv(name, default)
@ -26,6 +26,7 @@ APP_TITLE = getenv("APP_TITLE", "MESAVAULT Edge-OEE - Consola de paros UCEPSA")
DEFAULT_MACHINE_ID = getenv("DEFAULT_MACHINE_ID", "CORT-01") DEFAULT_MACHINE_ID = getenv("DEFAULT_MACHINE_ID", "CORT-01")
DEFAULT_ASSET = getenv("DEFAULT_ASSET", "revpi_oee_node_01") DEFAULT_ASSET = getenv("DEFAULT_ASSET", "revpi_oee_node_01")
AUTO_REFRESH_SECONDS = int(getenv("AUTO_REFRESH_SECONDS", "5"))
ALLOWED_MACHINES = [ ALLOWED_MACHINES = [
item.strip() item.strip()
@ -150,7 +151,12 @@ HTML = """
<meta charset="utf-8"> <meta charset="utf-8">
<title>{{ app_title }}</title> <title>{{ app_title }}</title>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="manifest" href="{{ url_for('manifest', machine_id=selected_machine) }}">
<meta name="theme-color" content="#0f172a">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-title" content="MESAVAULT {{ selected_machine }}">
<link rel="icon" href="{{ url_for('pwa_icon') }}" type="image/svg+xml">
<style> <style>
:root { :root {
--bg: #0f172a; --bg: #0f172a;
@ -512,6 +518,54 @@ HTML = """
</div> </div>
</main> </main>
<script>
if ("serviceWorker" in navigator) {
window.addEventListener("load", function () {
navigator.serviceWorker.register("/sw.js").catch(function (err) {
console.log("Service worker no registrado:", err);
});
});
}
</script>
<script>
(function () {
const refreshSeconds = Number("{{ auto_refresh_seconds }}");
const refreshMs = Math.max(refreshSeconds, 2) * 1000;
let formDirty = false;
document.addEventListener("input", function (event) {
if (event.target.closest("form")) {
formDirty = true;
}
});
document.addEventListener("change", function (event) {
if (event.target.closest("form")) {
formDirty = true;
}
});
function userIsEditing() {
const active = document.activeElement;
if (!active) return false;
const tag = active.tagName;
return tag === "INPUT" ||
tag === "SELECT" ||
tag === "TEXTAREA" ||
active.isContentEditable;
}
setInterval(function () {
if (document.hidden) return;
if (formDirty || userIsEditing()) return;
window.location.reload();
}, refreshMs);
})();
</script>
</body> </body>
</html> </html>
""" """
@ -534,6 +588,7 @@ def index():
allowed_machines=ALLOWED_MACHINES, allowed_machines=ALLOWED_MACHINES,
default_machine=selected_machine, default_machine=selected_machine,
default_asset=DEFAULT_ASSET, default_asset=DEFAULT_ASSET,
auto_refresh_seconds=AUTO_REFRESH_SECONDS,
loaded_at=datetime.now().strftime("%Y-%m-%d %H:%M:%S"), loaded_at=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
fmt_dt=fmt_dt, fmt_dt=fmt_dt,
fmt_num=fmt_num, fmt_num=fmt_num,
@ -574,6 +629,73 @@ def health():
return {"status": "error", "error": str(exc)}, 500 return {"status": "error", "error": str(exc)}, 500
@app.route("/manifest.webmanifest", methods=["GET"])
def manifest():
selected_machine = resolve_machine_id(request.args.get("machine_id"))
payload = {
"name": f"MESAVAULT Edge-OEE {selected_machine}",
"short_name": selected_machine,
"description": "Consola de paros MESAVAULT Edge-OEE para UCEPSA",
"start_url": url_for("index", machine_id=selected_machine),
"scope": "/",
"display": "standalone",
"orientation": "landscape",
"background_color": "#0f172a",
"theme_color": "#0f172a",
"icons": [
{
"src": url_for("pwa_icon"),
"sizes": "any",
"type": "image/svg+xml",
"purpose": "any maskable",
}
],
}
return Response(
json.dumps(payload, ensure_ascii=False),
mimetype="application/manifest+json",
)
@app.route("/sw.js", methods=["GET"])
def service_worker():
js = """
self.addEventListener("install", function (event) {
self.skipWaiting();
});
self.addEventListener("activate", function (event) {
event.waitUntil(self.clients.claim());
});
self.addEventListener("fetch", function (event) {
event.respondWith(fetch(event.request));
});
"""
return Response(
js,
mimetype="application/javascript",
headers={"Cache-Control": "no-cache"},
)
@app.route("/pwa-icon.svg", methods=["GET"])
def pwa_icon():
svg = """
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<rect width="512" height="512" rx="96" fill="#0f172a"/>
<path d="M96 328h320v48H96z" fill="#60a5fa"/>
<path d="M136 136h240v128H136z" fill="#2563eb"/>
<path d="M168 176h176v48H168z" fill="#dbeafe"/>
<circle cx="160" cy="392" r="28" fill="#22c55e"/>
<circle cx="352" cy="392" r="28" fill="#22c55e"/>
<text x="256" y="304" text-anchor="middle" font-size="64" font-family="Arial" font-weight="700" fill="#ffffff">OEE</text>
</svg>
"""
return Response(svg, mimetype="image/svg+xml")
if __name__ == "__main__": if __name__ == "__main__":
port = int(getenv("APP_PORT", "8080")) port = int(getenv("APP_PORT", "8080"))
app.run(host="0.0.0.0", port=port) app.run(host="0.0.0.0", port=port)