feat: path_hash_mode selector in Settings + global Close button

Adds a Path hash mode dropdown (1B/2B/3B) to Settings → Device → Public
Info, so the mode can be switched from the UI instead of the meshcli
console. The Settings modal now has a persistent Close button in the
footer, visible on every tab.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
MarekWo
2026-04-22 07:32:50 +02:00
parent cfdb68390f
commit 3b4ed26c50
3 changed files with 58 additions and 6 deletions
+22
View File
@@ -1170,6 +1170,14 @@ def get_device_config():
if not info:
return jsonify({'success': False, 'error': 'No device info available'}), 503
path_hash_mode = None
try:
phm_result = dm.get_param('path_hash_mode')
if phm_result.get('success'):
path_hash_mode = phm_result.get('data', {}).get('path_hash_mode')
except Exception as e:
logger.warning(f"Could not read path_hash_mode: {e}")
return jsonify({
'success': True,
'config': {
@@ -1177,6 +1185,7 @@ def get_device_config():
'lat': info.get('adv_lat', 0),
'lon': info.get('adv_lon', 0),
'advert_loc_policy': info.get('adv_loc_policy', 0),
'path_hash_mode': path_hash_mode,
'radio_freq': info.get('radio_freq', 0),
'radio_bw': info.get('radio_bw', 0),
'radio_sf': info.get('radio_sf', 0),
@@ -1224,6 +1233,19 @@ def update_device_config():
if not result.get('success'):
errors.append(f"advert_loc_policy: {result.get('error')}")
# Path hash mode (0=1B, 1=2B, 2=3B)
if 'path_hash_mode' in data and data['path_hash_mode'] is not None:
try:
phm = int(data['path_hash_mode'])
if phm not in (0, 1, 2):
errors.append(f"path_hash_mode: must be 0, 1, or 2")
else:
result = dm.set_param('path_hash_mode', str(phm))
if not result.get('success'):
errors.append(f"path_hash_mode: {result.get('error')}")
except (TypeError, ValueError):
errors.append("path_hash_mode: invalid value")
# Radio params (all 4 together)
if 'radio_freq' in data:
freq = float(data['radio_freq'])
+20 -6
View File
@@ -1872,6 +1872,13 @@ async function loadDeviceConfig() {
document.getElementById('settDeviceLat').value = c.lat || '';
document.getElementById('settDeviceLon').value = c.lon || '';
document.getElementById('settDeviceAdvertLoc').checked = !!c.advert_loc_policy;
const phmSel = document.getElementById('settDevicePathHashMode');
if (phmSel) {
const phm = (c.path_hash_mode === 0 || c.path_hash_mode === 1 || c.path_hash_mode === 2)
? String(c.path_hash_mode) : '0';
phmSel.value = phm;
phmSel.dataset.initial = phm;
}
// Radio
document.getElementById('settRadioFreq').value = c.radio_freq || '';
@@ -1910,21 +1917,28 @@ async function saveDevicePublicInfo() {
const lon = parseFloat(document.getElementById('settDeviceLon').value) || 0;
const advertLoc = document.getElementById('settDeviceAdvertLoc').checked;
const phmSel = document.getElementById('settDevicePathHashMode');
const payload = {
name: name,
lat: lat,
lon: lon,
advert_loc_policy: advertLoc
};
if (phmSel && phmSel.value !== phmSel.dataset.initial) {
payload.path_hash_mode = parseInt(phmSel.value, 10);
}
try {
const resp = await fetch('/api/device/config', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: name,
lat: lat,
lon: lon,
advert_loc_policy: advertLoc
})
body: JSON.stringify(payload)
});
const data = await resp.json();
if (data.success) {
showNotification('Public info saved', 'success');
_selfInfo = null;
if (phmSel) phmSel.dataset.initial = phmSel.value;
} else {
showNotification(data.error || 'Failed to save', 'danger');
}
+16
View File
@@ -440,6 +440,19 @@
</div>
</td>
</tr>
<tr>
<td class="ps-0">Path hash mode
<span class="badge rounded-pill text-muted" data-bs-toggle="tooltip"
title="Bytes per hop in routing paths. 1B = shortest path, more collisions; 3B = longest, fewest collisions."><i class="bi bi-info-circle"></i></span>
</td>
<td class="pe-0">
<select class="form-select form-select-sm" id="settDevicePathHashMode">
<option value="0">1 byte (default)</option>
<option value="1">2 bytes</option>
<option value="2">3 bytes</option>
</select>
</td>
</tr>
</tbody>
</table>
<div class="d-flex gap-2">
@@ -723,6 +736,9 @@
</div>
</div>
</div>
<div class="modal-footer py-2">
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>