u
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
* Devices API - List unique device/channel pairs
|
||||
*/
|
||||
|
||||
module.exports = function setupDevicesApi(app, { db, OUTPUT_CHANNELS }) {
|
||||
module.exports = function setupDevicesApi(app, { db, getOutputChannels }) {
|
||||
// GET /api/devices - Returns list of unique device/channel pairs (sensors + outputs)
|
||||
app.get('/api/devices', (req, res) => {
|
||||
try {
|
||||
@@ -12,7 +12,8 @@ module.exports = function setupDevicesApi(app, { db, OUTPUT_CHANNELS }) {
|
||||
const sensorRows = sensorStmt.all();
|
||||
|
||||
// Add output channels with 'output' as device
|
||||
const outputRows = OUTPUT_CHANNELS.map(ch => ({
|
||||
const outputChannels = getOutputChannels();
|
||||
const outputRows = outputChannels.map(ch => ({
|
||||
device: 'output',
|
||||
channel: ch.channel
|
||||
}));
|
||||
|
||||
@@ -6,11 +6,12 @@ const setupAuthApi = require('./auth');
|
||||
const setupViewsApi = require('./views');
|
||||
const setupRulesApi = require('./rules');
|
||||
const setupOutputsApi = require('./outputs');
|
||||
const setupOutputConfigApi = require('./output-config');
|
||||
const setupDevicesApi = require('./devices');
|
||||
const setupReadingsApi = require('./readings');
|
||||
|
||||
module.exports = function setupAllApis(app, context) {
|
||||
const { db, bcrypt, jwt, JWT_SECRET, OUTPUT_CHANNELS, OUTPUT_BINDINGS, runRules, activeRuleIds } = context;
|
||||
const { db, bcrypt, jwt, JWT_SECRET, getOutputChannels, getOutputBindings, runRules, activeRuleIds } = context;
|
||||
|
||||
// Auth middleware helpers
|
||||
const checkAuth = (req, res, next) => {
|
||||
@@ -37,7 +38,8 @@ module.exports = function setupAllApis(app, context) {
|
||||
setupAuthApi(app, { db, bcrypt, jwt, JWT_SECRET });
|
||||
setupViewsApi(app, { db, checkAuth, requireAdmin });
|
||||
setupRulesApi(app, { db, checkAuth, requireAdmin, runRules, activeRuleIds });
|
||||
setupOutputsApi(app, { db, OUTPUT_CHANNELS, OUTPUT_BINDINGS });
|
||||
setupDevicesApi(app, { db, OUTPUT_CHANNELS });
|
||||
setupOutputConfigApi(app, { db, checkAuth, requireAdmin });
|
||||
setupOutputsApi(app, { db, getOutputChannels, getOutputBindings });
|
||||
setupDevicesApi(app, { db, getOutputChannels });
|
||||
setupReadingsApi(app, { db });
|
||||
};
|
||||
|
||||
162
uiserver/api/output-config.js
Normal file
162
uiserver/api/output-config.js
Normal file
@@ -0,0 +1,162 @@
|
||||
/**
|
||||
* Output Config API - CRUD for output channel configurations
|
||||
*/
|
||||
|
||||
module.exports = function setupOutputConfigApi(app, { db, checkAuth, requireAdmin }) {
|
||||
// Apply checkAuth middleware to output config routes
|
||||
app.use('/api/output-configs', checkAuth);
|
||||
|
||||
// GET /api/output-configs - List all output configs
|
||||
app.get('/api/output-configs', (req, res) => {
|
||||
try {
|
||||
if (!db) throw new Error('Database not connected');
|
||||
const stmt = db.prepare('SELECT * FROM output_configs ORDER BY position ASC');
|
||||
const rows = stmt.all();
|
||||
res.json(rows);
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// POST /api/output-configs - Create new output config (admin only)
|
||||
app.post('/api/output-configs', requireAdmin, (req, res) => {
|
||||
const { channel, description, value_type, min_value, max_value, device, device_channel } = req.body;
|
||||
|
||||
if (!channel || !value_type) {
|
||||
return res.status(400).json({ error: 'Missing required fields: channel, value_type' });
|
||||
}
|
||||
|
||||
try {
|
||||
// Get max position
|
||||
const maxPos = db.prepare('SELECT MAX(position) as max FROM output_configs').get();
|
||||
const position = (maxPos.max ?? -1) + 1;
|
||||
|
||||
const stmt = db.prepare(`
|
||||
INSERT INTO output_configs (channel, description, value_type, min_value, max_value, device, device_channel, position)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`);
|
||||
const info = stmt.run(
|
||||
channel,
|
||||
description || '',
|
||||
value_type,
|
||||
min_value ?? 0,
|
||||
max_value ?? 1,
|
||||
device || null,
|
||||
device_channel || null,
|
||||
position
|
||||
);
|
||||
|
||||
global.insertChangelog(req.user?.username || 'admin', `Created output config "${channel}"`);
|
||||
|
||||
res.json({
|
||||
id: info.lastInsertRowid,
|
||||
channel,
|
||||
description,
|
||||
value_type,
|
||||
min_value: min_value ?? 0,
|
||||
max_value: max_value ?? 1,
|
||||
device,
|
||||
device_channel,
|
||||
position
|
||||
});
|
||||
} catch (err) {
|
||||
if (err.message.includes('UNIQUE constraint')) {
|
||||
return res.status(400).json({ error: 'Channel name already exists' });
|
||||
}
|
||||
res.status(500).json({ error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// PUT /api/output-configs/:id - Update output config (admin only)
|
||||
app.put('/api/output-configs/:id', requireAdmin, (req, res) => {
|
||||
const { channel, description, value_type, min_value, max_value, device, device_channel } = req.body;
|
||||
|
||||
try {
|
||||
const oldConfig = db.prepare('SELECT * FROM output_configs WHERE id = ?').get(req.params.id);
|
||||
if (!oldConfig) {
|
||||
return res.status(404).json({ error: 'Output config not found' });
|
||||
}
|
||||
|
||||
const stmt = db.prepare(`
|
||||
UPDATE output_configs
|
||||
SET channel = ?, description = ?, value_type = ?, min_value = ?, max_value = ?, device = ?, device_channel = ?
|
||||
WHERE id = ?
|
||||
`);
|
||||
const info = stmt.run(
|
||||
channel ?? oldConfig.channel,
|
||||
description ?? oldConfig.description,
|
||||
value_type ?? oldConfig.value_type,
|
||||
min_value ?? oldConfig.min_value,
|
||||
max_value ?? oldConfig.max_value,
|
||||
device ?? oldConfig.device,
|
||||
device_channel ?? oldConfig.device_channel,
|
||||
req.params.id
|
||||
);
|
||||
|
||||
if (info.changes > 0) {
|
||||
const changes = [];
|
||||
if (oldConfig.channel !== channel) changes.push(`channel: ${oldConfig.channel} → ${channel}`);
|
||||
if (oldConfig.device !== device) changes.push(`device: ${oldConfig.device || 'none'} → ${device || 'none'}`);
|
||||
if (oldConfig.device_channel !== device_channel) changes.push(`device_channel: ${oldConfig.device_channel || 'none'} → ${device_channel || 'none'}`);
|
||||
|
||||
const changeText = changes.length > 0
|
||||
? `Updated output config "${channel}": ${changes.join(', ')}`
|
||||
: `Updated output config "${channel}"`;
|
||||
global.insertChangelog(req.user?.username || 'admin', changeText);
|
||||
|
||||
res.json({ success: true, id: req.params.id });
|
||||
} else {
|
||||
res.status(404).json({ error: 'Output config not found' });
|
||||
}
|
||||
} catch (err) {
|
||||
if (err.message.includes('UNIQUE constraint')) {
|
||||
return res.status(400).json({ error: 'Channel name already exists' });
|
||||
}
|
||||
res.status(500).json({ error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// DELETE /api/output-configs/:id - Delete output config (admin only)
|
||||
app.delete('/api/output-configs/:id', requireAdmin, (req, res) => {
|
||||
try {
|
||||
const config = db.prepare('SELECT channel FROM output_configs WHERE id = ?').get(req.params.id);
|
||||
if (!config) {
|
||||
return res.status(404).json({ error: 'Output config not found' });
|
||||
}
|
||||
|
||||
const stmt = db.prepare('DELETE FROM output_configs WHERE id = ?');
|
||||
const info = stmt.run(req.params.id);
|
||||
|
||||
if (info.changes > 0) {
|
||||
global.insertChangelog(req.user?.username || 'admin', `Deleted output config "${config.channel}"`);
|
||||
res.json({ success: true });
|
||||
} else {
|
||||
res.status(404).json({ error: 'Output config not found' });
|
||||
}
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// POST /api/output-configs/reorder - Reorder output configs (admin only)
|
||||
app.post('/api/output-configs/reorder', requireAdmin, (req, res) => {
|
||||
const { order } = req.body;
|
||||
if (!Array.isArray(order)) {
|
||||
return res.status(400).json({ error: 'Invalid format' });
|
||||
}
|
||||
|
||||
const updateStmt = db.prepare('UPDATE output_configs SET position = ? WHERE id = ?');
|
||||
const updateMany = db.transaction((items) => {
|
||||
for (const item of items) {
|
||||
updateStmt.run(item.position, item.id);
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
updateMany(order);
|
||||
res.json({ success: true });
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message });
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -2,10 +2,10 @@
|
||||
* Outputs API - Output channel definitions and values
|
||||
*/
|
||||
|
||||
module.exports = function setupOutputsApi(app, { db, OUTPUT_CHANNELS, OUTPUT_BINDINGS }) {
|
||||
module.exports = function setupOutputsApi(app, { db, getOutputChannels, getOutputBindings }) {
|
||||
// GET /api/outputs - List output channel definitions
|
||||
app.get('/api/outputs', (req, res) => {
|
||||
res.json(OUTPUT_CHANNELS);
|
||||
res.json(getOutputChannels());
|
||||
});
|
||||
|
||||
// GET /api/outputs/values - Get current output values
|
||||
@@ -24,7 +24,8 @@ module.exports = function setupOutputsApi(app, { db, OUTPUT_CHANNELS, OUTPUT_BIN
|
||||
result[row.channel] = row.value;
|
||||
});
|
||||
// Fill in defaults for missing channels
|
||||
OUTPUT_CHANNELS.forEach(ch => {
|
||||
const outputChannels = getOutputChannels();
|
||||
outputChannels.forEach(ch => {
|
||||
if (result[ch.channel] === undefined) {
|
||||
result[ch.channel] = 0;
|
||||
}
|
||||
@@ -55,8 +56,9 @@ module.exports = function setupOutputsApi(app, { db, OUTPUT_CHANNELS, OUTPUT_BIN
|
||||
});
|
||||
|
||||
// Map to device commands
|
||||
const bindings = getOutputBindings();
|
||||
const commands = {};
|
||||
for (const [outputChannel, binding] of Object.entries(OUTPUT_BINDINGS)) {
|
||||
for (const [outputChannel, binding] of Object.entries(bindings)) {
|
||||
const value = outputValues[outputChannel] ?? 0;
|
||||
const deviceKey = `${binding.device}:${binding.channel}`;
|
||||
commands[deviceKey] = {
|
||||
|
||||
Reference in New Issue
Block a user