feat(analyzer): add configurable analyzer services in Settings

Add a Settings > Analyzer tab letting users CRUD custom MeshCore Analyzer
services with a star-toggle default and inline disabled switch. The chart
icon under each group-chat message now resolves at click time: built-in
Letsmesh when no enabled customs, the default when set, or a chooser
modal otherwise. Backend stops shipping the prebuilt analyzer_url and
emits packet_hash instead — the frontend substitutes {packetHash} in the
chosen URL template.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
MarekWo
2026-06-05 15:34:45 +02:00
parent e06e7b06dd
commit 10792b8566
6 changed files with 672 additions and 23 deletions
+75
View File
@@ -456,6 +456,9 @@
<li class="nav-item" role="presentation">
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#tabSettingsRegions" type="button">Regions</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#tabSettingsAnalyzer" type="button">Analyzer</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#tabSettingsNotifications" type="button">Notifications</button>
</li>
@@ -986,6 +989,26 @@
Tip: pick the default region via the radio button, or select <em>None</em> to fall back to the firmware default. The chosen region is also pushed to the firmware so any untagged channel uses it.
</div>
</div>
<div class="tab-pane fade" id="tabSettingsAnalyzer">
<div class="alert alert-info py-2 small mb-3 mt-2">
<i class="bi bi-info-circle"></i> Add MeshCore Analyzer services to choose from when clicking
<i class="bi bi-clipboard-data"></i> under a group chat message. The URL must contain the
<code>{packetHash}</code> placeholder &mdash; it is replaced with the message's packet hash.
</div>
<h6 class="mb-2">Analyzer Services</h6>
<div id="analyzersList" class="list-group mb-3">
<div class="text-center text-muted py-3 small">
<div class="spinner-border spinner-border-sm"></div> Loading...
</div>
</div>
<button type="button" class="btn btn-primary btn-sm" id="addAnalyzerBtn">
<i class="bi bi-plus-circle"></i> Add analyzer
</button>
<div class="form-text small mt-2">
Tip: star one analyzer to use it without being asked. Clear the star to be prompted on every click.
Disabled analyzers stay in the list but are hidden from the chooser.
</div>
</div>
<div class="tab-pane fade" id="tabSettingsNotifications">
<p class="text-muted small mb-3">Browser notifications appear when the app is hidden or in the background.</p>
<button id="notificationsToggle" class="list-group-item list-group-item-action d-flex align-items-center gap-3 border rounded" type="button" style="width: 100%;">
@@ -1010,6 +1033,58 @@
</div>
</div>
<!-- Analyzer Edit Modal (create + edit) -->
<div class="modal fade" id="analyzerEditModal" tabindex="-1" style="z-index: 1080;">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header py-2">
<h6 class="modal-title"><i class="bi bi-clipboard-data"></i> <span id="analyzerEditModalTitle">Add analyzer</span></h6>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form id="analyzerEditForm">
<div class="modal-body">
<input type="hidden" id="analyzerEditId" value="">
<div class="mb-3">
<label class="form-label small mb-1" for="analyzerEditName">Name</label>
<input type="text" class="form-control form-control-sm" id="analyzerEditName"
placeholder="e.g. MarWoj Analyzer" required maxlength="60" autocomplete="off">
</div>
<div class="mb-3">
<label class="form-label small mb-1" for="analyzerEditUrl">URL template</label>
<input type="text" class="form-control form-control-sm" id="analyzerEditUrl"
placeholder="https://analyzer.example.com/#/packets/{packetHash}" required autocomplete="off">
<div class="form-text small">Must include <code>{packetHash}</code>.</div>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="analyzerEditDisabled">
<label class="form-check-label small" for="analyzerEditDisabled">Disabled</label>
</div>
<div id="analyzerEditError" class="alert alert-danger py-1 small mt-3 d-none"></div>
</div>
<div class="modal-footer py-2">
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary btn-sm">Save</button>
</div>
</form>
</div>
</div>
</div>
<!-- Analyzer Chooser Modal -->
<div class="modal fade" id="analyzerChooserModal" tabindex="-1" style="z-index: 1080;">
<div class="modal-dialog modal-dialog-centered modal-sm">
<div class="modal-content">
<div class="modal-header py-2">
<h6 class="modal-title"><i class="bi bi-clipboard-data"></i> Choose analyzer</h6>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body p-2">
<div id="analyzerChooserList" class="list-group"></div>
</div>
</div>
</div>
</div>
<!-- Coordinate Picker Map Modal -->
<div class="modal fade" id="coordPickerModal" tabindex="-1" style="z-index: 1080;">
<div class="modal-dialog modal-lg modal-dialog-centered">