This commit is contained in:
Mateusz Gruszczyński
2026-09-04 11:00:15 +02:00
parent 8152e63d63
commit 236540e0f0
27 changed files with 557 additions and 102 deletions
+38 -3
View File
@@ -133,9 +133,38 @@ function renderControlPlan() {
host.innerHTML = content;
if (section) section.hidden = !content;
}
function applyControlPlan(plan, revision = null) {
if (!plan || typeof plan !== 'object') return false;
const hasRevision = revision !== null && revision !== undefined && revision !== '';
const hasCurrentRevision = app.controlPlanRevision !== null && app.controlPlanRevision !== undefined && app.controlPlanRevision !== '';
const nextRevision = hasRevision ? Number(revision) : NaN;
const currentRevision = hasCurrentRevision ? Number(app.controlPlanRevision) : NaN;
if (Number.isFinite(nextRevision) && Number.isFinite(currentRevision) && nextRevision < currentRevision) return false;
app.controlPlan = plan;
if (Number.isFinite(nextRevision)) app.controlPlanRevision = nextRevision;
renderControlPlan();
renderSimulationPage();
return true;
}
function controlPlanWebSocketReady() {
return !!app.ws && app.ws.readyState === WebSocket.OPEN && app.controlPlanPushReady;
}
async function loadControlPlan() {
try { app.controlPlan = await api('/api/control-plan'); renderControlPlan(); renderSimulationPage(); }
try {
const plan = await api('/api/control-plan');
// A fallback request may have started while disconnected and finish after WS resync.
// Never let that older unversioned HTTP response overwrite a revisioned pushed plan.
if (!controlPlanWebSocketReady()) applyControlPlan(plan);
}
catch (error) { console.warn('Unable to load control plan:', error); }
finally {
if (!controlPlanWebSocketReady()) {
clearTimeout(app.controlPlanTimer);
app.controlPlanTimer = setTimeout(loadControlPlan, 10000);
}
}
}
function simulationTime(value, options = {}) {
@@ -409,8 +438,14 @@ function renderSimulationPage() {
rulesHost.innerHTML = activeRules.length ? activeRules.map(rule => `<article class="simulation-rule-card"><div class="simulation-rule-head"><div><span class="eyebrow">${esc(tr('common.automation'))}</span><h3>${esc(rule.name)}</h3></div><span class="badge active">${esc(automationTriggerLabel(rule))}</span></div><p>${esc(tr('simulation.ruleOnDevice', { device: rule.action_device_name || tr('common.noDevice') }))}</p><div class="simulation-rule-action">${esc(simulationRuleAction(rule))}</div><div class="simulation-rule-meta"><small>${esc(tr('automations.last'))}: ${esc(dateTime(rule.last_fired_at))}</small><small>${esc(tr('simulation.nextReady'))}: ${esc(dateTime(rule.next_ready_at))}</small></div></article>`).join('') : `<div class="empty">${esc(tr('simulation.noRules'))}</div>`;
}
function scheduleControlPlanLoad() {
function scheduleControlPlanLoad(delay = 180) {
if (controlPlanWebSocketReady()) return;
clearTimeout(app.controlPlanTimer);
app.controlPlanTimer = setTimeout(loadControlPlan, 180);
app.controlPlanTimer = setTimeout(loadControlPlan, delay);
}
function stopControlPlanFallback() {
clearTimeout(app.controlPlanTimer);
app.controlPlanTimer = null;
}