u
This commit is contained in:
@@ -52,6 +52,7 @@ const OUTPUT_BINDINGS = {
|
||||
'CO2Valve': { device: 'tapo', channel: 'c', type: 'switch' },
|
||||
'TentExhaust': { device: 'tapo', channel: 'fantent', type: 'switch' },
|
||||
'CircFanLevel': { device: 'ac', channel: 'tent:fan', type: 'level' },
|
||||
'RoomExhaust': { device: 'ac', channel: 'wall-fan', type: 'level' },
|
||||
};
|
||||
|
||||
// =============================================
|
||||
@@ -359,6 +360,7 @@ const OUTPUT_CHANNELS = [
|
||||
{ channel: 'CO2Valve', type: 'boolean', min: 0, max: 1, description: 'CO2 Valve' },
|
||||
{ channel: 'BigDehumid', type: 'boolean', min: 0, max: 1, description: 'Big Dehumidifier' },
|
||||
{ channel: 'TentExhaust', type: 'boolean', min: 0, max: 1, description: 'Tent Exhaust Fan' },
|
||||
{ channel: 'RoomExhaust', type: 'number', min: 0, max: 10, description: 'Room Exhaust Fan' },
|
||||
];
|
||||
|
||||
// Get current sensor value
|
||||
@@ -740,10 +742,83 @@ module.exports = {
|
||||
app.put('/api/views/:id', requireAdmin, (req, res) => {
|
||||
const { name, config } = req.body;
|
||||
try {
|
||||
// Get old view for comparison
|
||||
const oldView = db.prepare('SELECT * FROM views WHERE id = ?').get(req.params.id);
|
||||
if (!oldView) {
|
||||
return res.status(404).json({ error: 'View not found' });
|
||||
}
|
||||
|
||||
const stmt = db.prepare('UPDATE views SET name = ?, config = ? WHERE id = ?');
|
||||
const info = stmt.run(name, JSON.stringify(config), req.params.id);
|
||||
if (info.changes > 0) {
|
||||
global.insertChangelog(req.user.username, `Updated view "${name}" (ID: ${req.params.id})`);
|
||||
// Build detailed changelog
|
||||
const changes = [];
|
||||
|
||||
// Check name change
|
||||
if (oldView.name !== name) {
|
||||
changes.push(`renamed: "${oldView.name}" → "${name}"`);
|
||||
}
|
||||
|
||||
// Parse configs for comparison
|
||||
let oldConfig = {};
|
||||
try { oldConfig = JSON.parse(oldView.config || '{}'); } catch (e) { }
|
||||
const newConfig = config || {};
|
||||
|
||||
// Compare channels
|
||||
const oldChannels = (oldConfig.channels || []).map(ch =>
|
||||
typeof ch === 'string' ? ch : ch.channel
|
||||
);
|
||||
const newChannels = (newConfig.channels || []).map(ch =>
|
||||
typeof ch === 'string' ? ch : ch.channel
|
||||
);
|
||||
|
||||
const added = newChannels.filter(ch => !oldChannels.includes(ch));
|
||||
const removed = oldChannels.filter(ch => !newChannels.includes(ch));
|
||||
|
||||
if (added.length > 0) {
|
||||
changes.push(`added channels: ${added.join(', ')}`);
|
||||
}
|
||||
if (removed.length > 0) {
|
||||
changes.push(`removed channels: ${removed.join(', ')}`);
|
||||
}
|
||||
|
||||
// Check for color/fill changes
|
||||
const oldChannelConfigs = {};
|
||||
(oldConfig.channels || []).forEach(ch => {
|
||||
if (typeof ch === 'object') {
|
||||
oldChannelConfigs[ch.channel] = ch;
|
||||
}
|
||||
});
|
||||
const newChannelConfigs = {};
|
||||
(newConfig.channels || []).forEach(ch => {
|
||||
if (typeof ch === 'object') {
|
||||
newChannelConfigs[ch.channel] = ch;
|
||||
}
|
||||
});
|
||||
|
||||
const colorChanges = [];
|
||||
for (const ch of newChannels) {
|
||||
const oldCh = oldChannelConfigs[ch] || {};
|
||||
const newCh = newChannelConfigs[ch] || {};
|
||||
if (oldCh.color !== newCh.color || oldCh.fillColor !== newCh.fillColor) {
|
||||
colorChanges.push(ch.split(':').pop()); // Just the channel name
|
||||
}
|
||||
}
|
||||
if (colorChanges.length > 0) {
|
||||
changes.push(`colors changed for: ${colorChanges.join(', ')}`);
|
||||
}
|
||||
|
||||
// Check order change
|
||||
if (added.length === 0 && removed.length === 0 &&
|
||||
JSON.stringify(oldChannels) !== JSON.stringify(newChannels)) {
|
||||
changes.push('channel order changed');
|
||||
}
|
||||
|
||||
const changeText = changes.length > 0
|
||||
? `Updated view "${name}": ${changes.join('; ')}`
|
||||
: `Updated view "${name}" (no significant changes)`;
|
||||
global.insertChangelog(req.user.username, changeText);
|
||||
|
||||
res.json({ id: req.params.id, name, config });
|
||||
} else {
|
||||
res.status(404).json({ error: 'View not found' });
|
||||
@@ -910,6 +985,12 @@ module.exports = {
|
||||
app.put('/api/rules/:id', requireAdmin, (req, res) => {
|
||||
const { name, type, enabled, conditions, action } = req.body;
|
||||
try {
|
||||
// Get old rule for comparison
|
||||
const oldRule = db.prepare('SELECT * FROM rules WHERE id = ?').get(req.params.id);
|
||||
if (!oldRule) {
|
||||
return res.status(404).json({ error: 'Rule not found' });
|
||||
}
|
||||
|
||||
const stmt = db.prepare(`
|
||||
UPDATE rules SET name = ?, type = ?, enabled = ?, conditions = ?, action = ?, updated_at = datetime('now')
|
||||
WHERE id = ?
|
||||
@@ -922,9 +1003,46 @@ module.exports = {
|
||||
JSON.stringify(action),
|
||||
req.params.id
|
||||
);
|
||||
|
||||
if (info.changes > 0) {
|
||||
runRules(); // Trigger rules immediately
|
||||
global.insertChangelog(req.user?.username || 'admin', `Updated rule "${name}" (ID: ${req.params.id})`);
|
||||
|
||||
// Build detailed changelog
|
||||
const changes = [];
|
||||
if (oldRule.name !== name) {
|
||||
changes.push(`name: "${oldRule.name}" → "${name}"`);
|
||||
}
|
||||
if (!!oldRule.enabled !== !!enabled) {
|
||||
changes.push(`enabled: ${oldRule.enabled ? 'on' : 'off'} → ${enabled ? 'on' : 'off'}`);
|
||||
}
|
||||
const oldConditions = oldRule.conditions || '{}';
|
||||
const newConditions = JSON.stringify(conditions);
|
||||
if (oldConditions !== newConditions) {
|
||||
changes.push('conditions changed');
|
||||
}
|
||||
const oldAction = oldRule.action || '{}';
|
||||
const newAction = JSON.stringify(action);
|
||||
if (oldAction !== newAction) {
|
||||
// Parse to show what changed in action
|
||||
try {
|
||||
const oldA = JSON.parse(oldAction);
|
||||
const newA = action;
|
||||
if (oldA.channel !== newA.channel) {
|
||||
changes.push(`action channel: ${oldA.channel} → ${newA.channel}`);
|
||||
}
|
||||
if (JSON.stringify(oldA.value) !== JSON.stringify(newA.value)) {
|
||||
changes.push(`action value: ${JSON.stringify(oldA.value)} → ${JSON.stringify(newA.value)}`);
|
||||
}
|
||||
} catch (e) {
|
||||
changes.push('action changed');
|
||||
}
|
||||
}
|
||||
|
||||
const changeText = changes.length > 0
|
||||
? `Updated rule "${name}": ${changes.join(', ')}`
|
||||
: `Updated rule "${name}" (no changes)`;
|
||||
global.insertChangelog(req.user?.username || 'admin', changeText);
|
||||
|
||||
res.json({ id: req.params.id, name, type, enabled, conditions, action });
|
||||
} else {
|
||||
res.status(404).json({ error: 'Rule not found' });
|
||||
|
||||
Reference in New Issue
Block a user