404 lines
11 KiB
HTML
404 lines
11 KiB
HTML
<!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: 0.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, 0.25);
|
|
}
|
|
50% {
|
|
box-shadow: 0 0 30px rgba(255, 0, 0, 0.7);
|
|
}
|
|
}
|
|
|
|
/* ── 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: 0.04em;
|
|
text-align: center;
|
|
text-shadow: 0 0 24px rgba(255, 0, 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: 0.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, 0.8vw, 12px);
|
|
color: #664444;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.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 0.8s ease;
|
|
}
|
|
.progress-label {
|
|
text-align: right;
|
|
font-size: clamp(9px, 0.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, 0.6);
|
|
padding: 3px 10px;
|
|
border-radius: 4px;
|
|
pointer-events: none;
|
|
z-index: 9999;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<iframe
|
|
id="normal-view"
|
|
src="http://192.168.40.102/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://192.168.40.127/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.type === "machine_update" && 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";
|
|
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>
|