Files
als-alert-screen/als_alert_screen.html
T
2026-06-08 09:37:18 +02:00

336 lines
11 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ALS Alert Screen</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: Arial, sans-serif;
background: #000;
color: #fff;
height: 100vh;
overflow: hidden;
}
/* ── NORMAL VIEW ──────────────────────────────── */
#normal-view {
width: 100%;
height: 100vh;
border: none;
display: block;
}
/* ── ALARM VIEW ──────────────────────────────── */
#alarm-view {
display: none;
flex-direction: column;
width: 100%;
height: 100vh;
background: #0d0000;
overflow: hidden;
}
#alarm-view.active { display: flex; }
.alarm-header {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
gap: 24px;
padding: 18px 24px;
background: #1e0000;
border-bottom: 3px solid #ff0000;
animation: headerBlink 1s step-start infinite;
}
@keyframes headerBlink {
0%, 100% { background: #1e0000; }
50% { background: #3a0000; }
}
.alarm-icon { font-size: clamp(28px, 4vw, 56px); }
.alarm-title {
font-size: clamp(22px, 3.5vw, 52px);
font-weight: 900;
color: #ff2222;
letter-spacing: .15em;
text-transform: uppercase;
}
.alarm-count {
font-size: clamp(18px, 2.5vw, 38px);
font-weight: 700;
color: #ff8888;
background: #3a0000;
border: 2px solid #ff3333;
border-radius: 8px;
padding: 4px 18px;
}
/* cards area */
#machines-grid {
flex: 1;
display: grid;
/* rows, columns & --cols/--rows set dynamically in JS */
gap: 3px;
background: #000;
overflow: hidden;
}
.machine-card {
background: #1a0000;
border: none;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 0.5em;
overflow: hidden;
container-type: inline-size;
animation: cardGlow 2s ease-in-out infinite;
}
@keyframes cardGlow {
0%, 100% { box-shadow: 0 0 12px rgba(255,0,0,.25); }
50% { box-shadow: 0 0 30px rgba(255,0,0,.70); }
}
/* ── big machine number skala przez --cols / --rows + cqw ── */
.card-machine-id {
font-size: min(calc(55vw / var(--cols, 1)), calc(55vh / var(--rows, 1)), 35cqw);
font-weight: 900;
color: #ff2222;
line-height: 1;
letter-spacing: .04em;
text-align: center;
text-shadow: 0 0 24px rgba(255,0,0,.6);
}
.card-status {
font-size: min(calc(7vw / var(--cols, 1)), calc(7vh / var(--rows, 1)), 5cqw);
font-weight: 700;
color: #ff9999;
text-transform: uppercase;
letter-spacing: .08em;
text-align: center;
}
/* ── detail grid ── */
.card-info {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 6px 14px;
border-top: 1px solid #3a0000;
padding-top: 8px;
}
.info-item { display: flex; flex-direction: column; }
.info-label {
font-size: clamp(9px, .8vw, 12px);
color: #664444;
text-transform: uppercase;
letter-spacing: .05em;
}
.info-value {
font-size: clamp(11px, 1.1vw, 16px);
color: #ddcccc;
font-weight: 700;
}
.progress-row {
grid-column: 1 / -1;
margin-top: 2px;
}
.progress-bar {
height: 12px;
background: #3a0000;
border-radius: 6px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #990000, #ff4444);
border-radius: 6px;
transition: width .8s ease;
}
.progress-label {
text-align: right;
font-size: clamp(9px, .8vw, 11px);
color: #775555;
margin-top: 3px;
}
/* ── STATUS BAR ──────────────────────────────── */
#status-bar {
position: fixed;
bottom: 6px;
right: 10px;
font-size: 11px;
color: #444;
background: rgba(0,0,0,.6);
padding: 3px 10px;
border-radius: 4px;
pointer-events: none;
z-index: 9999;
}
</style>
</head>
<body>
<iframe id="normal-view" src="http://diluals31/terminal/ift/5/PL/index" allowfullscreen></iframe>
<div id="alarm-view">
<div class="alarm-header">
<span class="alarm-icon"></span>
<span class="alarm-title">Alarm</span>
<span class="alarm-count" id="alarm-count">0</span>
<span class="alarm-icon"></span>
</div>
<div id="machines-grid"></div>
</div>
<!-- <div id="status-bar">WS: 0 / 0</div> -->
<script>
const MACHINES = [
"1.64","1.76","2.60","2.63","2.67","2.72","2.74",
"3.01","3.04","3.08","3.22","3.26","3.51","3.55","3.56",
"3.59","3.61","3.66","3.69","3.70","3.71","3.73","3.75",
"4.05","4.09","4.14","4.52","4.53","4.57","4.58","4.62",
"4.65","4.68","5.29","5.54"
];
const WS_BASE = "ws://luport.local/ws/als-data/";
const RECONNECT_MS = 5000;
// ── Debug mode ────────────────────────────────────────────────────────────
// Otwórz stronę z ?debug aby wymusić widok alarmowy z przykładowymi danymi.
// Możesz też wpisać w konsoli: DEBUG_ALARM = true; updateDisplay();
let DEBUG_ALARM = new URLSearchParams(location.search).has("debug");
const DEBUG_MACHINES = ["2.60", "3.22", "4.09"];
const DEBUG_DATA = {
"2.60": { machine:"2.60", status:"Alarm - brak materiału" },
"3.22": { machine:"3.22", status:"Alarm - awaria" },
"4.09": { machine:"4.09", status:"Alarm - przekroczony cykl" },
};
const machineData = {}; // id → last data payload
const wsStatus = {}; // id → 'connected' | 'error' | 'disconnected'
const wsMap = {}; // id → WebSocket instance
// ── WebSocket management ──────────────────────────────────────────────────
function connect(id) {
if (wsMap[id]) {
try { wsMap[id].close(); } catch (_) {}
}
const ws = new WebSocket(WS_BASE + id + "/");
wsMap[id] = ws;
wsStatus[id] = "connecting";
ws.onopen = () => {
wsStatus[id] = "connected";
refreshStatus();
};
ws.onmessage = (ev) => {
try {
const msg = JSON.parse(ev.data);
if (msg.data) {
machineData[id] = { ...msg.data, _ts: msg.timestamp };
updateDisplay();
}
} catch (e) { /* ignore malformed frames */ }
};
ws.onerror = () => {
wsStatus[id] = "error";
refreshStatus();
};
ws.onclose = () => {
wsStatus[id] = "disconnected";
// Usuwamy dane maszyny przy rozłączeniu nie możemy potwierdzić
// aktualnego stanu, więc nie trzymamy przestarzałego alarmu w cache.
// Po ponownym połączeniu serwer wyśle aktualny stan.
delete machineData[id];
updateDisplay();
refreshStatus();
setTimeout(() => connect(id), RECONNECT_MS);
};
}
// ── Display logic ─────────────────────────────────────────────────────────
function isAlarm(data) {
if (DEBUG_ALARM) return true; // w trybie debug wszystkie wstrzyknięte maszyny to alarmy
return data && typeof data.status === "string" &&
data.status.toLowerCase().includes("alarm");
}
function updateDisplay() {
const alarms = Object.entries(machineData)
.filter(([, d]) => isAlarm(d))
.sort(([a], [b]) => a.localeCompare(b, undefined, { numeric: true }));
const normalView = document.getElementById("normal-view");
const alarmView = document.getElementById("alarm-view");
const countEl = document.getElementById("alarm-count");
if (alarms.length > 0) {
normalView.style.display = "none";
alarmView.classList.add("active");
countEl.textContent = alarms.length === 1
? "1 maszyna"
: alarms.length + " maszyny";
renderCards(alarms);
} else {
normalView.style.display = "block";
alarmView.classList.remove("active");
}
}
function renderCards(alarms) {
const grid = document.getElementById("machines-grid");
grid.innerHTML = "";
const n = alarms.length;
const cols = Math.ceil(Math.sqrt(n));
const rows = Math.ceil(n / cols);
grid.style.gridTemplateColumns = `repeat(${cols}, 1fr)`;
grid.style.gridTemplateRows = `repeat(${rows}, 1fr)`;
grid.style.setProperty("--cols", cols);
grid.style.setProperty("--rows", rows);
alarms.forEach(([id, d]) => {
const card = document.createElement("div");
card.className = "machine-card";
card.innerHTML = `
<div class="card-machine-id">${d.machine ?? id}</div>
<!-- <div class="card-status">${d.status ?? "ALARM"}</div> -->
`;
grid.appendChild(card);
});
}
function refreshStatus() {
const connected = Object.values(wsStatus).filter(s => s === "connected").length;
document.getElementById("status-bar").textContent =
`WS: ${connected} / ${MACHINES.length}`;
}
// ── Bootstrap ─────────────────────────────────────────────────────────────
if (DEBUG_ALARM) {
DEBUG_MACHINES.forEach(id => { machineData[id] = DEBUG_DATA[id]; });
document.getElementById("status-bar").textContent = "[DEBUG] widok alarmowy";
} else {
MACHINES.forEach(connect);
}
updateDisplay();
</script>
</body>
</html>