From dcdfb27684418eeedb346f18d1c4ef88a425efdb Mon Sep 17 00:00:00 2001 From: sebseb7 Date: Thu, 25 Dec 2025 06:13:23 +0100 Subject: [PATCH] u --- uiserver/src/components/ViewManager.js | 76 ++++++++++++++++---------- uiserver/webpack.config.js | 16 ++++++ 2 files changed, 64 insertions(+), 28 deletions(-) diff --git a/uiserver/src/components/ViewManager.js b/uiserver/src/components/ViewManager.js index dfd2081..103fad0 100644 --- a/uiserver/src/components/ViewManager.js +++ b/uiserver/src/components/ViewManager.js @@ -43,6 +43,7 @@ class ViewManager extends Component { this.state = { views: [], rules: [], + activeRuleIds: [], outputValues: {}, open: false, colorPickerOpen: false, @@ -77,7 +78,8 @@ class ViewManager extends Component { this.rulesInterval = setInterval(() => { this.loadRules(); this.loadOutputValues(); - }, 30000); + this.loadRuleStatus(); + }, 5000); if (this.isAdmin()) { fetch('/api/devices') .then(res => res.json()) @@ -128,6 +130,13 @@ class ViewManager extends Component { .catch(console.error); }; + loadRuleStatus = () => { + fetch('/api/rules/status') + .then(res => res.json()) + .then(data => this.setState({ activeRuleIds: data.activeIds || [] })) + .catch(console.error); + }; + parseViewData(view) { let channels = []; let axes = { left: { min: '', max: '' }, right: { min: '', max: '' } }; @@ -197,7 +206,10 @@ class ViewManager extends Component { } return `📅 ${operator} ${value}`; case 'sensor': - // Show device:channel for clarity (e.g. "ac:controller:humidity") + // Show device:channel for clarity + if (value && value.type === 'dynamic') { + return `📡 ${channel} ${op} (${value.channel} * ${value.factor} + ${value.offset})`; + } return `📡 ${channel} ${op} ${value}`; case 'output': return `⚙️ ${channel} ${op} ${value}`; @@ -216,6 +228,11 @@ class ViewManager extends Component { 'TentExhaust': '💨 Tent Exhaust Fan' }; const name = channelNames[action.channel] || action.channel; + + if (action.value && action.value.type === 'calculated') { + return `${name} = (${action.value.sensorA} - ${action.value.sensorB || '0'}) * ${action.value.factor} + ${action.value.offset}`; + } + return `${name} = ${action.value}`; }; @@ -508,35 +525,38 @@ class ViewManager extends Component { 🤖 Active Rules - {this.state.rules.filter(r => r.enabled).map((rule, idx) => ( - - - {this.getRuleEmoji(rule)} - - - - {rule.name} + {this.state.rules.filter(r => r.enabled).map((rule, idx) => { + const isActive = this.state.activeRuleIds.includes(rule.id); + return ( + + + {this.getRuleEmoji(rule)} - - {this.formatRuleConditions(rule.conditions)} → {this.formatRuleAction(rule.action)} + + + {rule.name} + + + {this.formatRuleConditions(rule.conditions)} → {this.formatRuleAction(rule.action)} + + + + #{idx + 1} - - #{idx + 1} - - - ))} + ); + })} 📊 Current outputs: {Object.entries(this.state.outputValues).filter(([k, v]) => v > 0).map(([k, v]) => `${k}=${v}`).join(', ') || 'all off'} diff --git a/uiserver/webpack.config.js b/uiserver/webpack.config.js index c51c316..bf6f918 100644 --- a/uiserver/webpack.config.js +++ b/uiserver/webpack.config.js @@ -491,6 +491,9 @@ function evaluateCondition(condition) { } } +// Global set to track currently active rule IDs +const activeRuleIds = new Set(); + // Run all rules function runRules() { if (!db) return; @@ -498,6 +501,9 @@ function runRules() { try { const rules = db.prepare('SELECT * FROM rules WHERE enabled = 1 ORDER BY position ASC').all(); + // Clear active rules list at start of run + activeRuleIds.clear(); + // Default all outputs to OFF (0) - if no rule sets them, they stay off const desiredOutputs = {}; for (const ch of OUTPUT_CHANNELS) { @@ -510,6 +516,9 @@ function runRules() { const action = JSON.parse(rule.action || '{}'); if (evaluateCondition(conditions)) { + // Rule matches - add to active list + activeRuleIds.add(rule.id); + // Rule matches - set output (later rules override) if (action.channel && action.value !== undefined) { let finalValue = action.value; @@ -818,6 +827,11 @@ module.exports = { } }); + // GET /api/rules/status - Get currently active rule IDs + app.get('/api/rules/status', (req, res) => { + res.json({ activeIds: Array.from(activeRuleIds) }); + }); + // GET /api/rules - List all rules app.get('/api/rules', (req, res) => { try { @@ -835,6 +849,8 @@ module.exports = { } }); + + // POST /api/rules - Create rule (admin only) app.post('/api/rules', requireAdmin, (req, res) => { const { name, type = 'static', enabled = 1, conditions, action } = req.body;