diff --git a/deploy/public/index.html b/deploy/public/index.html
index 2ec9e96..80b53a7 100644
--- a/deploy/public/index.html
+++ b/deploy/public/index.html
@@ -225,6 +225,8 @@ a{color:var(--blue);text-decoration:none;transition:color .2s}a:hover{color:var(
.show-more-btn{font-size:.8rem;color:var(--blue);cursor:pointer;padding:.5rem 0;margin-top:.25rem;transition:color .2s;user-select:none}
.show-more-btn:hover{color:var(--cyan);text-decoration:underline}
+.sort-toggle{font-size:.7rem;color:var(--muted);cursor:pointer;padding:.2rem .5rem;border:1px solid var(--border);border-radius:4px;transition:all .2s;user-select:none}
+.sort-toggle:hover{color:var(--blue);border-color:var(--blue)}
/* Routing Overview - Propagation Bars */
.routing-stats-row{display:flex;gap:1rem;flex-wrap:wrap;margin-bottom:1.25rem}
@@ -532,6 +534,44 @@ function copyToClipboard(text, btn) {
});
}
+function renderAuditList(containerId, list, sortBy, type) {
+ var sorted = list.slice();
+ if (sortBy === 'asn') sorted.sort(function(a, b) { return a.asn - b.asn; });
+ else if (sortBy === 'frequency') sorted.sort(function(a, b) { return (b.frequency || 0) - (a.frequency || 0); });
+ else if (sortBy === 'name') sorted.sort(function(a, b) { return (a.name || '').localeCompare(b.name || ''); });
+
+ var labelId = type === 'missing' ? 'missingSortLabel' : 'extraSortLabel';
+ var labelEl = document.getElementById(labelId);
+ if (labelEl) labelEl.textContent = sortBy === 'asn' ? 'by ASN' : sortBy === 'frequency' ? 'by frequency' : 'by name';
+
+ var LIMIT = 5;
+ var h = '';
+ sorted.slice(0, LIMIT).forEach(function(item) {
+ h += '
';
+ h += '' + asnLink(item.asn) + '';
+ if (item.name && item.name !== 'AS' + item.asn) h += ' ' + escHtml(item.name) + '';
+ if (type === 'missing') h += ' seen in ' + (item.frequency_pct || 0) + '% of paths (' + (item.frequency || 0) + ')';
+ else h += ' not seen in any path';
+ h += '
';
+ });
+ if (sorted.length > LIMIT) {
+ var moreId = containerId + 'More';
+ h += '';
+ sorted.slice(LIMIT).forEach(function(item) {
+ h += '
';
+ h += '' + asnLink(item.asn) + '';
+ if (item.name && item.name !== 'AS' + item.asn) h += ' ' + escHtml(item.name) + '';
+ if (type === 'missing') h += ' seen in ' + (item.frequency_pct || 0) + '% of paths (' + (item.frequency || 0) + ')';
+ else h += ' not seen in any path';
+ h += '
';
+ });
+ h += '
';
+ var remaining = sorted.length - LIMIT;
+ h += 'Show ' + remaining + ' more...
';
+ }
+ document.getElementById(containerId).innerHTML = h;
+}
+
async function doLookup() {
const raw = $('asnInput').value.trim().replace(/[^0-9]/g, '');
if (!raw) return;
@@ -1197,57 +1237,26 @@ function renderAspaDeep(d) {
h += 'Completeness' + (audit.completeness_pct || 0) + '%
';
h += '';
- // Missing from ASPA (collapsible after 5)
+ // Missing from ASPA (collapsible, sortable)
if (audit.missing_from_aspa && audit.missing_from_aspa.length > 0) {
- var missingList = audit.missing_from_aspa;
- var LIMIT = 5;
- h += 'Missing from ASPA Declaration (' + missingList.length + ')
';
- missingList.slice(0, LIMIT).forEach(function(m) {
- h += '';
- h += '' + asnLink(m.asn) + '';
- if (m.name && m.name !== 'AS' + m.asn) h += ' ' + escHtml(m.name) + '';
- h += ' seen in ' + (m.frequency_pct || 0) + '% of paths (' + (m.frequency || 0) + ')';
- h += '
';
- });
- if (missingList.length > LIMIT) {
- var moreId = 'missingMore' + Date.now();
- h += '';
- missingList.slice(LIMIT).forEach(function(m) {
- h += '
';
- h += '' + asnLink(m.asn) + '';
- if (m.name && m.name !== 'AS' + m.asn) h += ' ' + escHtml(m.name) + '';
- h += ' seen in ' + (m.frequency_pct || 0) + '% of paths (' + (m.frequency || 0) + ')';
- h += '
';
- });
- h += '
';
- h += 'Show ' + (missingList.length - LIMIT) + ' more...
';
- }
+ window._auditMissing = audit.missing_from_aspa;
+ window._auditMissingSort = 'frequency';
+ h += '';
+ h += 'Missing from ASPA Declaration (' + audit.missing_from_aspa.length + ')';
+ h += 'Sort: by frequency ↕';
+ h += '
';
+ h += '';
}
- // Extra in ASPA (collapsible after 5)
+ // Extra in ASPA (collapsible, sortable)
if (audit.extra_in_aspa && audit.extra_in_aspa.length > 0) {
- var extraList = audit.extra_in_aspa;
- h += 'Extra in ASPA (not seen in paths) (' + extraList.length + ')
';
- extraList.slice(0, LIMIT).forEach(function(e) {
- h += '';
- });
- if (extraList.length > LIMIT) {
- var extraMoreId = 'extraMore' + Date.now();
- h += '';
- h += 'Show ' + (extraList.length - LIMIT) + ' more...
';
- }
+ window._auditExtra = audit.extra_in_aspa;
+ window._auditExtraSort = 'asn';
+ h += '';
+ h += 'Extra in ASPA (not seen in paths) (' + audit.extra_in_aspa.length + ')';
+ h += 'Sort: ↕';
+ h += '
';
+ h += '';
}
if ((!audit.missing_from_aspa || audit.missing_from_aspa.length === 0) && (!audit.extra_in_aspa || audit.extra_in_aspa.length === 0)) {
@@ -1316,6 +1325,14 @@ function renderAspaDeep(d) {
h += 'ASPA verification completed in ' + (d.meta ? d.meta.duration_ms : '?') + 'ms | ' + (d.meta ? d.meta.paths_analyzed : '?') + ' of ' + (d.meta ? d.meta.total_paths_seen : '?') + ' paths analyzed
';
$('aspaDeepContent').innerHTML = h;
+
+ // Initial render of sortable audit lists
+ if (window._auditMissing && window._auditMissing.length > 0) {
+ renderAuditList('missingListEl', window._auditMissing, 'frequency', 'missing');
+ }
+ if (window._auditExtra && window._auditExtra.length > 0) {
+ renderAuditList('extraListEl', window._auditExtra, 'asn', 'extra');
+ }
}