fix(ucepsa): auto-refresh operator console safely
This commit is contained in:
parent
d525443de9
commit
69f8416808
@ -1,10 +1,10 @@
|
||||
import json
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
import psycopg2
|
||||
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):
|
||||
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_ASSET = getenv("DEFAULT_ASSET", "revpi_oee_node_01")
|
||||
AUTO_REFRESH_SECONDS = int(getenv("AUTO_REFRESH_SECONDS", "5"))
|
||||
|
||||
ALLOWED_MACHINES = [
|
||||
item.strip()
|
||||
@ -150,7 +151,12 @@ HTML = """
|
||||
<meta charset="utf-8">
|
||||
<title>{{ app_title }}</title>
|
||||
<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>
|
||||
:root {
|
||||
--bg: #0f172a;
|
||||
@ -512,6 +518,54 @@ HTML = """
|
||||
</div>
|
||||
|
||||
</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>
|
||||
</html>
|
||||
"""
|
||||
@ -534,6 +588,7 @@ def index():
|
||||
allowed_machines=ALLOWED_MACHINES,
|
||||
default_machine=selected_machine,
|
||||
default_asset=DEFAULT_ASSET,
|
||||
auto_refresh_seconds=AUTO_REFRESH_SECONDS,
|
||||
loaded_at=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||
fmt_dt=fmt_dt,
|
||||
fmt_num=fmt_num,
|
||||
@ -574,6 +629,73 @@ def health():
|
||||
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__":
|
||||
port = int(getenv("APP_PORT", "8080"))
|
||||
app.run(host="0.0.0.0", port=port)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user