mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-10-24 16:32:31 +00:00
feat: add sortable Scope column to Packets table
Region-scope was only visible as a small inline pill inside the Type column (transportBadge). Split it into its own sortable "Scope" column between Type and Observer, matching the same three states (region name / unknown / not applicable for non-transport packets). Wired into the existing column visibility toggle and mobile narrow-width hide list. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
18bc6fb79e
commit
e4593e98d4
3 files changed
+34
-3
No files matched your search
@@ -42,6 +42,18 @@ function transportBadge(rt, scopeName) {
|
||||
return ' <span class="badge ' + cls + '" title="' + escapeHtml(title) + '">' + escapeHtml(label) + '</span>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Dedicated Scope column cell (as opposed to transportBadge's inline pill
|
||||
* on the Type column) — same three-state semantics: non-transport routes
|
||||
* show a dash, transport routes with no resolved region show a muted
|
||||
* "unknown", and a resolved match shows the region name.
|
||||
*/
|
||||
function scopeCellHtml(rt, scopeName) {
|
||||
if (!isTransportRoute(rt)) return '—';
|
||||
if (scopeName) return '<span class="badge-scope" title="Scope: ' + escapeHtml(scopeName) + '">' + escapeHtml(scopeName) + '</span>';
|
||||
return '<span class="badge-scope badge-scope-unknown" title="Transport packet, matching region unknown">unknown</span>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute breakdown byte ranges from raw_hex on the client.
|
||||
* Mirrors cmd/server/decoder.go BuildBreakdown(). Used so per-observation raw_hex
|
||||
|
||||
+7
-2
@@ -1626,7 +1626,7 @@
|
||||
<thead><tr>
|
||||
<th scope="col" class="col-expand" data-priority="1"></th><th scope="col" class="col-region" data-sort-key="region" data-priority="3">Region</th><th scope="col" class="col-time" data-sort-key="time" data-type="date" data-priority="1">Time</th><th scope="col" class="col-hash" data-sort-key="hash" data-priority="3">Hash</th><th scope="col" class="col-size" data-sort-key="size" data-type="numeric" data-priority="4">Size</th>
|
||||
<th scope="col" class="col-hashsize" data-sort-key="hb" data-type="numeric" data-priority="5">HB</th>
|
||||
<th scope="col" class="col-type" data-sort-key="type" data-priority="1">Type</th><th scope="col" class="col-observer" data-sort-key="observer" data-priority="3">Observer</th><th scope="col" class="col-path" data-sort-key="path" data-priority="5">Path</th><th scope="col" class="col-rpt" data-sort-key="rpt" data-type="numeric" data-priority="3">Rpt</th><th scope="col" class="col-details" data-priority="1">Details</th>
|
||||
<th scope="col" class="col-type" data-sort-key="type" data-priority="1">Type</th><th scope="col" class="col-scope" data-sort-key="scope" data-priority="4">Scope</th><th scope="col" class="col-observer" data-sort-key="observer" data-priority="3">Observer</th><th scope="col" class="col-path" data-sort-key="path" data-priority="5">Path</th><th scope="col" class="col-rpt" data-sort-key="rpt" data-type="numeric" data-priority="3">Rpt</th><th scope="col" class="col-details" data-priority="1">Details</th>
|
||||
</tr></thead>
|
||||
<tbody id="pktBody"></tbody>
|
||||
</table></div>
|
||||
@@ -2010,6 +2010,7 @@
|
||||
{ key: 'hash', label: 'Hash' },
|
||||
{ key: 'size', label: 'Size' },
|
||||
{ key: 'type', label: 'Type' },
|
||||
{ key: 'scope', label: 'Scope' },
|
||||
{ key: 'observer', label: 'Observer' },
|
||||
{ key: 'path', label: 'Path' },
|
||||
{ key: 'rpt', label: 'Rpt' },
|
||||
@@ -2019,7 +2020,7 @@
|
||||
// #1249: observer column must stay visible at narrow widths so the IATA
|
||||
// badge (#1188) renders on mobile. Without observer in scope the user
|
||||
// can't see who heard the packet at all.
|
||||
const defaultHidden = isNarrow ? ['region', 'hash', 'path', 'rpt', 'size'] : ['region'];
|
||||
const defaultHidden = isNarrow ? ['region', 'hash', 'path', 'rpt', 'size', 'scope'] : ['region'];
|
||||
let visibleCols;
|
||||
try {
|
||||
visibleCols = JSON.parse(localStorage.getItem('packets-visible-cols'));
|
||||
@@ -2264,6 +2265,7 @@
|
||||
<td class="col-size" data-filter-field="size" data-filter-value="${groupSize || ''}">${groupSize ? groupSize + 'B' : '—'}</td>
|
||||
<td class="col-hashsize mono"${_grpHashSizeTitle}>${groupHashBytes}</td>
|
||||
<td class="col-type" data-filter-field="type" data-filter-value="${escapeHtml(groupTypeName || '')}">${p.payload_type != null ? `<span class="badge badge-${groupTypeClass}">${groupTypeName}</span>${transportBadge(p.route_type, p.scope_name)}` : '—'}</td>
|
||||
<td class="col-scope">${scopeCellHtml(p.route_type, p.scope_name)}</td>
|
||||
<td class="col-observer" data-filter-field="observer" data-filter-value="${escapeHtml(obsNameOnly(headerObserverId) || '')}">${isSingle ? escapeHtml(truncate(obsNameOnly(headerObserverId), 16)) + obsIataBadge(p) : escapeHtml(truncate(obsNameOnly(headerObserverId), 10)) + groupedObserverIataBadgesHtml(p)}</td>
|
||||
<td class="col-path"><span class="path-hops">${groupPathStr}</span></td>
|
||||
<td class="col-rpt">${p.observation_count > 1 ? '<span class="badge badge-obs" title="Seen ' + p.observation_count + ' times"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor-sprite.svg#ph-eye"/></svg> ' + p.observation_count + '</span>' : (isSingle ? '' : p.count)}</td>
|
||||
@@ -2298,6 +2300,7 @@
|
||||
<td class="col-size" data-filter-field="size" data-filter-value="${size || ''}">${size}B</td>
|
||||
<td class="col-hashsize mono"${_cHashSizeTitle}>${childHashBytes}</td>
|
||||
<td class="col-type" data-filter-field="type" data-filter-value="${escapeHtml(typeName || '')}"><span class="badge badge-${typeClass}">${typeName}</span>${transportBadge(c.route_type, c.scope_name)}</td>
|
||||
<td class="col-scope">${scopeCellHtml(c.route_type, c.scope_name)}</td>
|
||||
<td class="col-observer" data-filter-field="observer" data-filter-value="${escapeHtml(obsNameOnly(c.observer_id) || '')}">${escapeHtml(truncate(obsNameOnly(c.observer_id), 16))}${obsIataBadge(c)}</td>
|
||||
<td class="col-path"><span class="path-hops">${childPathStr}</span></td>
|
||||
<td class="col-rpt"></td>
|
||||
@@ -2334,6 +2337,7 @@
|
||||
<td class="col-size" data-filter-field="size" data-filter-value="${size || ''}">${size}B</td>
|
||||
<td class="col-hashsize mono"${_flatHashSizeTitle}>${hashBytes}</td>
|
||||
<td class="col-type" data-filter-field="type" data-filter-value="${escapeHtml(typeName || '')}"><span class="badge badge-${typeClass}">${typeName}</span>${transportBadge(p.route_type, p.scope_name)}</td>
|
||||
<td class="col-scope">${scopeCellHtml(p.route_type, p.scope_name)}</td>
|
||||
<td class="col-observer" data-filter-field="observer" data-filter-value="${escapeHtml(obsNameOnly(p.observer_id) || '')}">${escapeHtml(truncate(obsNameOnly(p.observer_id), 16))}${obsIataBadge(p)}</td>
|
||||
<td class="col-path"><span class="path-hops">${pathStr}</span></td>
|
||||
<td class="col-rpt"></td>
|
||||
@@ -2695,6 +2699,7 @@
|
||||
case 'type': accessor = function(p) { return typeName(p.payload_type); }; break;
|
||||
case 'hash': accessor = function(p) { return p.hash || ''; }; break;
|
||||
case 'observer': accessor = function(p) { return obsName(p.observer_id); }; break;
|
||||
case 'scope': accessor = function(p) { return p.scope_name || ''; }; break;
|
||||
case 'size': accessor = function(p) { return p.packet_size || 0; }; break;
|
||||
case 'hb': accessor = function(p) { return p.hash_byte_count != null ? p.hash_byte_count : (p.hash_size || 0); }; break;
|
||||
case 'rpt': accessor = function(p) {
|
||||
|
||||
+15
-1
@@ -1436,6 +1436,18 @@ body.scroll-locked { overflow: hidden; }
|
||||
.badge-transport-unknown {
|
||||
background: var(--text-muted-bg, #6b728020); color: var(--text-muted, #6b7280);
|
||||
}
|
||||
/* Dedicated Scope column pill (#71 Packets column). Reuses the
|
||||
badge-transport-unknown muted look for the "no match" state so it reads
|
||||
consistently with the inline T? pill on the Type column. */
|
||||
.badge-scope {
|
||||
display: inline-block; padding: 1px 6px; border-radius: 4px;
|
||||
font-size: 10px; font-weight: 700; font-family: var(--mono);
|
||||
background: var(--transport-badge-bg, #f59e0b20); color: var(--transport-badge-fg, #d97706);
|
||||
letter-spacing: .3px;
|
||||
}
|
||||
.badge-scope-unknown {
|
||||
background: var(--text-muted-bg, #6b728020); color: var(--text-muted, #6b7280);
|
||||
}
|
||||
.badge-obs {
|
||||
display: inline-block; padding: 1px 6px; border-radius: 10px;
|
||||
font-size: 10px; font-weight: 600;
|
||||
@@ -2137,6 +2149,7 @@ button.ch-item:hover .ch-icon-btn { opacity: 1; }
|
||||
.obs-table td:first-child { white-space: nowrap; }
|
||||
.obs-table td:nth-child(6) { max-width: none; overflow: visible; }
|
||||
.col-observer { min-width: 70px; max-width: none; }
|
||||
.col-scope { min-width: 60px; max-width: 110px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.spark-bar { position: relative; min-width: 60px; max-width: 100px; flex: 1; height: 18px; background: var(--border); border-radius: 4px; overflow: hidden; display: inline-block; vertical-align: middle; }
|
||||
@media (max-width: 640px) { .spark-bar { max-width: 60px; } }
|
||||
.spark-fill { height: 100%; background: linear-gradient(90deg, var(--accent), var(--accent-hover, #60a5fa)); border-radius: 4px; transition: width 0.3s; }
|
||||
@@ -2993,7 +3006,7 @@ button.ch-item.ch-item-encrypted .ch-badge { filter: grayscale(0.6); }
|
||||
|
||||
/* Hide low-value columns on mobile */
|
||||
@media (max-width: 640px) {
|
||||
.col-region, .col-rpt, .col-size, .col-hashsize, .col-pubkey { display: none; }
|
||||
.col-region, .col-rpt, .col-size, .col-hashsize, .col-pubkey, .col-scope { display: none; }
|
||||
}
|
||||
|
||||
/* Clickable hop links */
|
||||
@@ -3185,6 +3198,7 @@ tr[data-hops]:hover { background: rgba(59,130,246,0.1); }
|
||||
.hide-col-hash .col-hash,
|
||||
.hide-col-size .col-size,
|
||||
.hide-col-type .col-type,
|
||||
.hide-col-scope .col-scope,
|
||||
.hide-col-observer .col-observer,
|
||||
.hide-col-path .col-path,
|
||||
.hide-col-rpt .col-rpt,
|
||||
|
||||
Reference in new issue
Block a user