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`.
|
||||
- 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.
|
||||
- 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.
|
||||
|
||||
|
||||
@ -9,7 +9,7 @@ USB-aware MQTT orchestrator for SecuBox routers. The application discovers USB s
|
||||
|
||||
- `overview.js` – broker status, metrics, quick actions.
|
||||
- `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
|
||||
|
||||
|
||||
@ -692,6 +692,43 @@ pre {
|
||||
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) {
|
||||
.mqtt-bridge-dashboard {
|
||||
padding: 16px;
|
||||
|
||||
@ -13,17 +13,20 @@ Theme.init({ language: lang });
|
||||
|
||||
return view.extend({
|
||||
load: function() {
|
||||
return API.getStatus().then(function(status) {
|
||||
return status.settings || {};
|
||||
});
|
||||
return API.getStatus();
|
||||
},
|
||||
|
||||
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' }, [
|
||||
E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox-theme/secubox-theme.css') }),
|
||||
E('link', { 'rel': 'stylesheet', 'href': L.resource('mqtt-bridge/common.css') }),
|
||||
Nav.renderTabs('settings'),
|
||||
this.renderSettingsCard(settings || {})
|
||||
this.renderSettingsCard(settings || {}),
|
||||
this.renderAdapterCard(adapters || [])
|
||||
]);
|
||||
return container;
|
||||
},
|
||||
@ -42,11 +45,51 @@ return view.extend({
|
||||
this.input('retention', _('Retention (days)'), settings.retention || 7, 'number')
|
||||
]),
|
||||
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) {
|
||||
return E('div', { 'class': 'mb-input-group' }, [
|
||||
E('label', { 'class': 'mb-stat-label', 'for': id }, label),
|
||||
@ -59,8 +102,12 @@ return view.extend({
|
||||
]);
|
||||
},
|
||||
|
||||
saveSettings: function() {
|
||||
var payload = {
|
||||
makeAdapterInputId: function(id, field) {
|
||||
return 'adapter-' + (id || 'x').replace(/[^a-z0-9_-]/ig, '_') + '-' + field;
|
||||
},
|
||||
|
||||
collectSettings: function() {
|
||||
return {
|
||||
host: document.getElementById('broker-host').value,
|
||||
port: parseInt(document.getElementById('broker-port').value, 10) || 1883,
|
||||
username: document.getElementById('username').value,
|
||||
@ -68,6 +115,33 @@ return view.extend({
|
||||
base_topic: document.getElementById('base-topic').value,
|
||||
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'), [
|
||||
E('p', {}, _('Applying broker configuration…')),
|
||||
|
||||
@ -113,6 +113,38 @@ append_configured_adapters() {
|
||||
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() {
|
||||
json_init
|
||||
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 base_topic base_topic
|
||||
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
|
||||
|
||||
[ -n "$host" ] && uci set mqtt-bridge.broker.host="$host"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user