feat: adapter preferences editing
This commit is contained in:
parent
790719e2a1
commit
6c3c96a70b
@ -75,6 +75,7 @@ The package now installs a lightweight watcher (`/usr/sbin/mqtt-bridge-monitor`)
|
|||||||
- Managed with the standard init script: `service mqtt-bridge start|stop|status`.
|
- Managed with the standard init script: `service mqtt-bridge start|stop|status`.
|
||||||
- Writes state transitions to the system log (`logread -e mqtt-bridge-monitor`).
|
- Writes state transitions to the system log (`logread -e mqtt-bridge-monitor`).
|
||||||
- Updates each adapter section with `detected`, `port`, `bus`, `device`, `health`, and `last_seen`, which the LuCI Devices tab now surfaces.
|
- Updates each adapter section with `detected`, `port`, `bus`, `device`, `health`, and `last_seen`, which the LuCI Devices tab now surfaces.
|
||||||
|
- The MQTT Settings view exposes the same adapter entries so you can enable/disable presets, rename labels, or override `/dev/tty*` assignments without leaving the UI.
|
||||||
|
|
||||||
Use `uci show mqtt-bridge.adapter` to inspect the persisted metadata, or `ubus call luci.mqtt-bridge status` to see the JSON payload consumed by the UI.
|
Use `uci show mqtt-bridge.adapter` to inspect the persisted metadata, or `ubus call luci.mqtt-bridge status` to see the JSON payload consumed by the UI.
|
||||||
|
|
||||||
|
|||||||
@ -9,7 +9,7 @@ USB-aware MQTT orchestrator for SecuBox routers. The application discovers USB s
|
|||||||
|
|
||||||
- `overview.js` – broker status, metrics, quick actions.
|
- `overview.js` – broker status, metrics, quick actions.
|
||||||
- `devices.js` – USB/tasmota sensor list with pairing wizard.
|
- `devices.js` – USB/tasmota sensor list with pairing wizard.
|
||||||
- `settings.js` – broker credentials, topic templates, retention options.
|
- `settings.js` – broker credentials, topic templates, retention options, adapter preferences (enable/label/tty overrides).
|
||||||
|
|
||||||
## RPC Methods
|
## RPC Methods
|
||||||
|
|
||||||
|
|||||||
@ -692,6 +692,43 @@ pre {
|
|||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mb-adapter-grid {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mb-adapter-row {
|
||||||
|
border: 1px solid var(--mb-border);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 16px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
background: var(--mb-card);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mb-adapter-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mb-switch {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--mb-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mb-switch input {
|
||||||
|
accent-color: var(--mb-accent);
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.mqtt-bridge-dashboard {
|
.mqtt-bridge-dashboard {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
|
|||||||
@ -13,17 +13,20 @@ Theme.init({ language: lang });
|
|||||||
|
|
||||||
return view.extend({
|
return view.extend({
|
||||||
load: function() {
|
load: function() {
|
||||||
return API.getStatus().then(function(status) {
|
return API.getStatus();
|
||||||
return status.settings || {};
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function(settings) {
|
render: function(payload) {
|
||||||
|
var settings = (payload && payload.settings) || {};
|
||||||
|
var adapters = (payload && payload.adapters) || [];
|
||||||
|
this.currentAdapters = adapters;
|
||||||
|
|
||||||
var container = E('div', { 'class': 'mqtt-bridge-dashboard' }, [
|
var container = E('div', { 'class': 'mqtt-bridge-dashboard' }, [
|
||||||
E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox-theme/secubox-theme.css') }),
|
E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox-theme/secubox-theme.css') }),
|
||||||
E('link', { 'rel': 'stylesheet', 'href': L.resource('mqtt-bridge/common.css') }),
|
E('link', { 'rel': 'stylesheet', 'href': L.resource('mqtt-bridge/common.css') }),
|
||||||
Nav.renderTabs('settings'),
|
Nav.renderTabs('settings'),
|
||||||
this.renderSettingsCard(settings || {})
|
this.renderSettingsCard(settings || {}),
|
||||||
|
this.renderAdapterCard(adapters || [])
|
||||||
]);
|
]);
|
||||||
return container;
|
return container;
|
||||||
},
|
},
|
||||||
@ -42,11 +45,51 @@ return view.extend({
|
|||||||
this.input('retention', _('Retention (days)'), settings.retention || 7, 'number')
|
this.input('retention', _('Retention (days)'), settings.retention || 7, 'number')
|
||||||
]),
|
]),
|
||||||
E('div', { 'style': 'margin-top:16px;' }, [
|
E('div', { 'style': 'margin-top:16px;' }, [
|
||||||
E('button', { 'class': 'mb-btn mb-btn-primary', 'click': ui.createHandlerFn(this, 'saveSettings') }, ['💾 ', _('Save settings')])
|
E('button', { 'class': 'mb-btn mb-btn-primary', 'click': ui.createHandlerFn(this, 'savePreferences') }, ['💾 ', _('Save preferences')])
|
||||||
])
|
])
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
renderAdapterCard: function(adapters) {
|
||||||
|
var items = adapters && adapters.length ? adapters.map(this.renderAdapterRow.bind(this)) :
|
||||||
|
[E('p', { 'style': 'color:var(--mb-muted);' }, _('No adapters configured yet. UCI sections named `config adapter` will appear here.'))];
|
||||||
|
return E('div', { 'class': 'mb-card' }, [
|
||||||
|
E('div', { 'class': 'mb-card-header' }, [
|
||||||
|
E('div', { 'class': 'mb-card-title' }, [E('span', {}, '🧩'), _('Adapter preferences')])
|
||||||
|
]),
|
||||||
|
E('div', { 'class': 'mb-adapter-grid' }, items)
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
|
||||||
|
renderAdapterRow: function(adapter) {
|
||||||
|
var id = adapter.id || adapter.section || adapter.preset || adapter.vendor + ':' + adapter.product;
|
||||||
|
var inputId = this.makeAdapterInputId(id, 'label');
|
||||||
|
return E('div', { 'class': 'mb-adapter-row' }, [
|
||||||
|
E('div', { 'class': 'mb-adapter-header' }, [
|
||||||
|
E('div', {}, [
|
||||||
|
E('strong', {}, adapter.label || id || _('Adapter')),
|
||||||
|
E('div', { 'class': 'mb-profile-meta' }, [
|
||||||
|
adapter.vendor && adapter.product ? _('VID:PID ') + adapter.vendor + ':' + adapter.product : null,
|
||||||
|
adapter.port ? _('Port ') + adapter.port : null
|
||||||
|
].filter(Boolean).map(function(entry) {
|
||||||
|
return E('span', {}, entry);
|
||||||
|
}))
|
||||||
|
]),
|
||||||
|
E('label', { 'class': 'mb-switch' }, [
|
||||||
|
E('input', {
|
||||||
|
'type': 'checkbox',
|
||||||
|
'id': this.makeAdapterInputId(id, 'enabled'),
|
||||||
|
'checked': adapter.enabled !== false && adapter.enabled !== '0'
|
||||||
|
}),
|
||||||
|
E('span', {}, _('Enabled'))
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
this.input(this.makeAdapterInputId(id, 'custom-label'), _('Display label'), adapter.label || id),
|
||||||
|
this.input(this.makeAdapterInputId(id, 'custom-port'), _('Preferred /dev/tty*'), adapter.port || '', 'text'),
|
||||||
|
adapter.notes ? E('p', { 'class': 'mb-profile-notes' }, adapter.notes) : null
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
|
||||||
input: function(id, label, value, type) {
|
input: function(id, label, value, type) {
|
||||||
return E('div', { 'class': 'mb-input-group' }, [
|
return E('div', { 'class': 'mb-input-group' }, [
|
||||||
E('label', { 'class': 'mb-stat-label', 'for': id }, label),
|
E('label', { 'class': 'mb-stat-label', 'for': id }, label),
|
||||||
@ -59,8 +102,12 @@ return view.extend({
|
|||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
|
|
||||||
saveSettings: function() {
|
makeAdapterInputId: function(id, field) {
|
||||||
var payload = {
|
return 'adapter-' + (id || 'x').replace(/[^a-z0-9_-]/ig, '_') + '-' + field;
|
||||||
|
},
|
||||||
|
|
||||||
|
collectSettings: function() {
|
||||||
|
return {
|
||||||
host: document.getElementById('broker-host').value,
|
host: document.getElementById('broker-host').value,
|
||||||
port: parseInt(document.getElementById('broker-port').value, 10) || 1883,
|
port: parseInt(document.getElementById('broker-port').value, 10) || 1883,
|
||||||
username: document.getElementById('username').value,
|
username: document.getElementById('username').value,
|
||||||
@ -68,6 +115,33 @@ return view.extend({
|
|||||||
base_topic: document.getElementById('base-topic').value,
|
base_topic: document.getElementById('base-topic').value,
|
||||||
retention: parseInt(document.getElementById('retention').value, 10) || 7
|
retention: parseInt(document.getElementById('retention').value, 10) || 7
|
||||||
};
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
collectAdapters: function() {
|
||||||
|
var adapters = {};
|
||||||
|
var list = this.currentAdapters || [];
|
||||||
|
list.forEach(function(adapter) {
|
||||||
|
var id = adapter.id || adapter.section;
|
||||||
|
if (!id)
|
||||||
|
return;
|
||||||
|
var enabledEl = document.getElementById(this.makeAdapterInputId(id, 'enabled'));
|
||||||
|
var labelEl = document.getElementById(this.makeAdapterInputId(id, 'custom-label'));
|
||||||
|
var portEl = document.getElementById(this.makeAdapterInputId(id, 'custom-port'));
|
||||||
|
adapters[id] = {
|
||||||
|
enabled: enabledEl ? (enabledEl.checked ? 1 : 0) : 1,
|
||||||
|
label: labelEl ? labelEl.value : (adapter.label || ''),
|
||||||
|
port: portEl ? portEl.value : (adapter.port || ''),
|
||||||
|
preset: adapter.preset || '',
|
||||||
|
vendor: adapter.vendor || '',
|
||||||
|
product: adapter.product || ''
|
||||||
|
};
|
||||||
|
}, this);
|
||||||
|
return adapters;
|
||||||
|
},
|
||||||
|
|
||||||
|
savePreferences: function() {
|
||||||
|
var payload = this.collectSettings();
|
||||||
|
payload.adapters = this.collectAdapters();
|
||||||
|
|
||||||
ui.showModal(_('Saving MQTT settings'), [
|
ui.showModal(_('Saving MQTT settings'), [
|
||||||
E('p', {}, _('Applying broker configuration…')),
|
E('p', {}, _('Applying broker configuration…')),
|
||||||
|
|||||||
@ -113,6 +113,38 @@ append_configured_adapters() {
|
|||||||
json_close_array
|
json_close_array
|
||||||
}
|
}
|
||||||
|
|
||||||
|
apply_adapter_settings() {
|
||||||
|
local adapter_keys
|
||||||
|
json_get_keys adapter_keys
|
||||||
|
for adapter in $adapter_keys; do
|
||||||
|
json_select "$adapter" || continue
|
||||||
|
local enabled label port vendor product preset
|
||||||
|
json_get_var enabled enabled
|
||||||
|
json_get_var label label
|
||||||
|
json_get_var port port
|
||||||
|
json_get_var vendor vendor
|
||||||
|
json_get_var product product
|
||||||
|
json_get_var preset preset
|
||||||
|
json_select ..
|
||||||
|
[ -n "$adapter" ] || continue
|
||||||
|
|
||||||
|
[ -n "$enabled" ] && uci set mqtt-bridge.adapter."$adapter".enabled="$enabled"
|
||||||
|
if [ -n "$label" ]; then
|
||||||
|
uci set mqtt-bridge.adapter."$adapter".title="$label"
|
||||||
|
else
|
||||||
|
uci delete mqtt-bridge.adapter."$adapter".title >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
if [ -n "$port" ]; then
|
||||||
|
uci set mqtt-bridge.adapter."$adapter".port="$port"
|
||||||
|
else
|
||||||
|
uci delete mqtt-bridge.adapter."$adapter".port >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
[ -n "$vendor" ] && uci set mqtt-bridge.adapter."$adapter".vendor="$vendor"
|
||||||
|
[ -n "$product" ] && uci set mqtt-bridge.adapter."$adapter".product="$product"
|
||||||
|
[ -n "$preset" ] && uci set mqtt-bridge.adapter."$adapter".preset="$preset"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
status() {
|
status() {
|
||||||
json_init
|
json_init
|
||||||
json_add_string "broker" "$(uci -q get mqtt-bridge.broker.host || echo 'localhost')"
|
json_add_string "broker" "$(uci -q get mqtt-bridge.broker.host || echo 'localhost')"
|
||||||
@ -208,6 +240,12 @@ apply_settings() {
|
|||||||
json_get_var password password
|
json_get_var password password
|
||||||
json_get_var base_topic base_topic
|
json_get_var base_topic base_topic
|
||||||
json_get_var retention retention
|
json_get_var retention retention
|
||||||
|
json_get_type adapters_type adapters
|
||||||
|
if [ "$adapters_type" = "object" ]; then
|
||||||
|
json_select adapters
|
||||||
|
apply_adapter_settings
|
||||||
|
json_select ..
|
||||||
|
fi
|
||||||
json_cleanup
|
json_cleanup
|
||||||
|
|
||||||
[ -n "$host" ] && uci set mqtt-bridge.broker.host="$host"
|
[ -n "$host" ] && uci set mqtt-bridge.broker.host="$host"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user