mirror of
https://github.com/MarekWo/mc-webui.git
synced 2026-07-26 19:42:43 +02:00
feat(pathanalyzer): path hash size (HB) column and filter
- HB column in the message table: distinct hash sizes (bytes per hop) across the message's routed echoes, e.g. '1' or '1,2'; em dash when no routed echo - HB filter (Any/1-byte/2-byte/3-byte): matches when any routed echo uses that hash size; 0-hop echoes are excluded since their stored hash_size is meaningless; combines with the other filters and is reset by Clear Verified live via Playwright: 1B/2B/3B filters each return only messages with a matching routed echo (183/166/9 of 606, zero false positives), combined hops+HB filtering works, Clear resets. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -83,7 +83,7 @@ function showNotification(message, type = 'info') {
|
||||
// ================================================================
|
||||
|
||||
let paMessages = [];
|
||||
let paFilters = { hops: 'any', token: '', sender: '' };
|
||||
let paFilters = { hops: 'any', hashSize: 'any', token: '', sender: '' };
|
||||
let paCurrentView = 'messages'; // 'messages' | 'stats'
|
||||
let paContacts = []; // /api/contacts/cached?format=full
|
||||
let paStatsSort = { key: 'relayed', dir: -1 };
|
||||
@@ -126,6 +126,12 @@ function paMessageMatchesFilters(msg) {
|
||||
want === '4+' ? e.hops >= 4 : e.hops === parseInt(want, 10));
|
||||
if (!ok) return false;
|
||||
}
|
||||
if (paFilters.hashSize !== 'any') {
|
||||
const want = parseInt(paFilters.hashSize, 10);
|
||||
// Only routed echoes carry a meaningful hash size
|
||||
const ok = msg.echoView.some(e => e.hops > 0 && (e.hash_size || 1) === want);
|
||||
if (!ok) return false;
|
||||
}
|
||||
if (paFilters.token) {
|
||||
const t = paFilters.token;
|
||||
const ok = msg.echoView.some(e => e.tokens.some(tok => tok.startsWith(t)));
|
||||
@@ -138,7 +144,8 @@ function paMessageMatchesFilters(msg) {
|
||||
}
|
||||
|
||||
function paFiltersActive() {
|
||||
return paFilters.hops !== 'any' || paFilters.token !== '' || paFilters.sender !== '';
|
||||
return paFilters.hops !== 'any' || paFilters.hashSize !== 'any'
|
||||
|| paFilters.token !== '' || paFilters.sender !== '';
|
||||
}
|
||||
|
||||
function paFormatTime(msg) {
|
||||
@@ -258,6 +265,12 @@ function paRenderTable() {
|
||||
tdHops.textContent = (msg.hop_count === null || msg.hop_count === undefined) ? '—' : msg.hop_count;
|
||||
tr.appendChild(tdHops);
|
||||
|
||||
const tdHb = document.createElement('td');
|
||||
tdHb.className = 'text-end';
|
||||
const hbSizes = [...new Set(msg.echoView.filter(e => e.hops > 0).map(e => e.hash_size || 1))].sort();
|
||||
tdHb.textContent = hbSizes.length > 0 ? hbSizes.join(',') : '—';
|
||||
tr.appendChild(tdHb);
|
||||
|
||||
const tdEchoes = document.createElement('td');
|
||||
tdEchoes.className = 'text-end';
|
||||
tdEchoes.textContent = msg.echoView.length;
|
||||
@@ -270,7 +283,7 @@ function paRenderTable() {
|
||||
detailTr.className = 'd-none';
|
||||
const detailTd = document.createElement('td');
|
||||
detailTd.className = 'pa-echo-cell';
|
||||
detailTd.colSpan = 8;
|
||||
detailTd.colSpan = 9;
|
||||
for (const echo of msg.echoView) {
|
||||
detailTd.appendChild(paBuildEchoLine(echo));
|
||||
}
|
||||
@@ -699,6 +712,7 @@ async function paLoadMessages() {
|
||||
|
||||
function paReadFilters() {
|
||||
paFilters.hops = document.getElementById('paHopsFilter').value;
|
||||
paFilters.hashSize = document.getElementById('paHashSizeFilter').value;
|
||||
// Normalize token input to hex characters only (matches chip display casing)
|
||||
paFilters.token = document.getElementById('paTokenFilter').value
|
||||
.replace(/[^0-9a-fA-F]/g, '').toUpperCase();
|
||||
@@ -715,6 +729,7 @@ function paApplyFilters() {
|
||||
|
||||
function paClearFilters() {
|
||||
document.getElementById('paHopsFilter').value = 'any';
|
||||
document.getElementById('paHashSizeFilter').value = 'any';
|
||||
document.getElementById('paTokenFilter').value = '';
|
||||
document.getElementById('paSenderFilter').value = '';
|
||||
paApplyFilters();
|
||||
@@ -735,6 +750,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
filterDebounce = setTimeout(paApplyFilters, 150);
|
||||
};
|
||||
document.getElementById('paHopsFilter').addEventListener('change', paApplyFilters);
|
||||
document.getElementById('paHashSizeFilter').addEventListener('change', paApplyFilters);
|
||||
document.getElementById('paTokenFilter').addEventListener('input', debouncedApply);
|
||||
document.getElementById('paSenderFilter').addEventListener('input', debouncedApply);
|
||||
document.getElementById('paClearFiltersBtn').addEventListener('click', paClearFilters);
|
||||
|
||||
@@ -344,6 +344,13 @@
|
||||
<option value="3">3 hops</option>
|
||||
<option value="4+">4+ hops</option>
|
||||
</select>
|
||||
<select id="paHashSizeFilter" class="form-select form-select-sm" style="width: auto;"
|
||||
title="Filter by path hash size (bytes per hop, any routed echo)">
|
||||
<option value="any" selected>Any HB</option>
|
||||
<option value="1">1-byte</option>
|
||||
<option value="2">2-byte</option>
|
||||
<option value="3">3-byte</option>
|
||||
</select>
|
||||
<input id="paTokenFilter" type="text" class="form-control form-control-sm" style="width: 9.5rem;"
|
||||
placeholder="Repeater hash (3B)" title="Match a repeater hash anywhere in a path (prefix match)">
|
||||
<input id="paSenderFilter" type="text" class="form-control form-control-sm" style="width: 9.5rem;"
|
||||
@@ -375,6 +382,7 @@
|
||||
<th>Message</th>
|
||||
<th>Hash</th>
|
||||
<th class="text-end">Hops</th>
|
||||
<th class="text-end" title="Path hash size in bytes per hop (from routed echoes)">HB</th>
|
||||
<th class="text-end">Echoes</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
Reference in New Issue
Block a user