compares plc.directory with other mirrors
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

better mobile, show links to audit log on missing ops and check if the ops are present currently in the log on modal open

dawn ee5f9192 c7916722

+87 -9
+87 -9
index.html
··· 393 393 .miss-did:first-child { margin-top: 0; border-top: none; padding-top: 0; } 394 394 .miss-did-label { 395 395 color: #c8c8c8; 396 + font-family: ui-monospace, 'SF Mono', Menlo, monospace; 396 397 font-size: 0.74rem; 397 398 margin-bottom: 0.15rem; 398 399 word-break: break-all; 399 400 } 400 - .miss-cids { 401 - display: flex; 402 - flex-wrap: wrap; 403 - gap: 4px 6px; 401 + .miss-audit-link { 402 + font-size: 0.67rem; 403 + color: #555; 404 + text-decoration: none; 405 + margin-left: 0.4rem; 406 + white-space: nowrap; 404 407 } 408 + .miss-audit-link:hover { color: #e8c840; text-decoration: underline; } 409 + .miss-cids { display: flex; flex-direction: column; gap: 3px; } 410 + .miss-cid-row { display: flex; align-items: center; gap: 6px; } 405 411 .miss-cid { 406 412 font-family: ui-monospace, 'SF Mono', Menlo, monospace; 407 413 font-size: 0.69rem; ··· 409 415 background: #1a1a1a; 410 416 border: 1px solid #282828; 411 417 border-radius: 3px; 412 - padding: 1px 5px; 418 + padding: 2px 6px; 413 419 cursor: default; 414 420 user-select: all; 421 + word-break: break-all; 422 + } 423 + .miss-cid-status { font-size: 0.67rem; white-space: nowrap; } 424 + .miss-status-checking { color: #383838; } 425 + .miss-status-present { color: #5ab05e; } 426 + .miss-status-missing { color: #b05a5e; } 427 + .miss-status-error { color: #555; } 428 + 429 + /* ── mobile ── */ 430 + @media (max-width: 600px) { 431 + body { padding: 1.25rem 0.85rem 3rem; } 432 + 433 + .section-head { flex-wrap: wrap; gap: 0.35rem 0.6rem; } 434 + 435 + /* hide total-events (col 2) and bar (col 7) on small screens */ 436 + thead tr th:nth-child(2), 437 + tbody tr td:nth-child(2), 438 + thead tr th:nth-child(7), 439 + tbody tr td:nth-child(7) { display: none; } 440 + 441 + /* let lag column break more gracefully */ 442 + tbody tr td:nth-child(6) { font-size: 0.72rem; line-height: 1.4; } 443 + 444 + .hist-label { width: 76px; font-size: 0.67rem; } 445 + 446 + /* single-column compare panels */ 447 + #panels { grid-template-columns: 1fr; gap: 1.5rem; } 448 + 449 + /* full-width url input */ 450 + input[type=text] { max-width: none; } 451 + 452 + /* modal fills more of the screen */ 453 + #missBox { width: min(560px, 98vw); max-height: 88vh; } 454 + #missHead { flex-wrap: wrap; gap: 0.4rem; } 455 + #missTime { order: 3; flex-basis: 100%; color: #444; } 415 456 } 416 457 </style> 417 458 </head> ··· 1048 1089 1049 1090 // ── missed ops modal ────────────────────────────────────────────────────────── 1050 1091 let missModalData = null; // { mirrorName, snap, url } 1092 + let missModalGen = 0; 1051 1093 1052 1094 function openMissModal(mirrorName, snap, url) { 1095 + const gen = ++missModalGen; 1053 1096 missModalData = { mirrorName, snap, url }; 1054 1097 const sm = snap.mirrors[url] ?? {}; 1055 1098 const missed = sm.missedCids ?? {}; ··· 1065 1108 if (!dids.length) { 1066 1109 body.innerHTML = '<div class="miss-empty">no missed ops recorded for this interval</div>'; 1067 1110 } else { 1111 + const mirrorBase = url.replace(/^wss?:\/\//, 'https://').replace(/\/$/, ''); 1068 1112 body.innerHTML = dids.sort().map(did => { 1069 1113 const cids = missed[did]; 1070 - const cidBadges = cids.map(c => 1071 - `<span class="miss-cid" title="${c}">${c.slice(0, 7)}</span>` 1114 + const cidRows = cids.map(c => 1115 + `<div class="miss-cid-row"><span class="miss-cid" data-cid="${c}">${c}</span><span class="miss-cid-status miss-status-checking">·</span></div>` 1072 1116 ).join(''); 1073 1117 return `<div class="miss-did"> 1074 - <div class="miss-did-label">${did} <span style="color:#555;font-size:0.68rem">(${cids.length} op${cids.length !== 1 ? 's' : ''})</span></div> 1075 - <div class="miss-cids">${cidBadges}</div> 1118 + <div class="miss-did-label">${did} <span style="color:#555;font-size:0.68rem">(${cids.length} op${cids.length !== 1 ? 's' : ''})</span> 1119 + <a class="miss-audit-link" href="https://plc.directory/${did}/log/audit/" target="_blank" rel="noopener">upstream ↗</a> 1120 + <a class="miss-audit-link" href="${mirrorBase}/${did}/log/audit/" target="_blank" rel="noopener">mirror ↗</a> 1121 + </div> 1122 + <div class="miss-cids">${cidRows}</div> 1076 1123 </div>`; 1077 1124 }).join(''); 1125 + 1126 + for (const did of dids) checkMirrorAudit(mirrorBase, did, missed[did], gen); 1078 1127 } 1079 1128 1080 1129 document.getElementById('missModal').classList.add('show'); 1130 + } 1131 + 1132 + async function checkMirrorAudit(mirrorBase, did, cids, gen) { 1133 + const setStatus = (cid, text, cls) => { 1134 + if (missModalGen !== gen) return; 1135 + const row = document.querySelector(`.miss-cid[data-cid="${CSS.escape(cid)}"]`); 1136 + if (!row) return; 1137 + const s = row.nextElementSibling; 1138 + if (!s) return; 1139 + s.textContent = text; 1140 + s.className = `miss-cid-status ${cls}`; 1141 + }; 1142 + try { 1143 + const res = await fetch(`${mirrorBase}/${did}/log/audit/`); 1144 + if (missModalGen !== gen) return; 1145 + if (res.status === 404) { 1146 + for (const cid of cids) setStatus(cid, '✗ still missing', 'miss-status-missing'); 1147 + return; 1148 + } 1149 + if (!res.ok) throw new Error(); 1150 + const log = await res.json(); 1151 + if (missModalGen !== gen) return; 1152 + const present = new Set(log.map(e => e.cid)); 1153 + for (const cid of cids) 1154 + setStatus(cid, present.has(cid) ? '✓ now present' : '✗ still missing', 1155 + present.has(cid) ? 'miss-status-present' : 'miss-status-missing'); 1156 + } catch { 1157 + for (const cid of cids) setStatus(cid, 'fetch error', 'miss-status-error'); 1158 + } 1081 1159 } 1082 1160 1083 1161 function closeMissModal() {