|
385 | 385 | networkHealthSync.innerHTML = `<span class="prompt-char">></span> probes: ${message}` |
386 | 386 | } |
387 | 387 |
|
388 | | - const probeCheckStatusClass = (status) => { |
| 388 | + const probeCheckStatusClass = (status, options = {}) => { |
389 | 389 | if (status === 'ok') { |
| 390 | + if (typeof options.tlsDaysUntilExpiry === 'number' && options.tlsDaysUntilExpiry < 14) { |
| 391 | + return 'status-degraded' |
| 392 | + } |
| 393 | + |
390 | 394 | return 'status-ok' |
391 | 395 | } |
392 | 396 | if (status === 'error') { |
|
396 | 400 | return 'status-no-data' |
397 | 401 | } |
398 | 402 |
|
399 | | - const formatProbeCheckDetail = (check, formatter) => { |
| 403 | + const formatProbeCheckDetail = (check, formatter, options = {}) => { |
400 | 404 | const label = statusLabels.probeCheck[check?.status] ?? statusLabels.probeCheck.skipped |
401 | 405 | const detail = typeof formatter === 'function' && check?.status === 'ok' ? formatter(check.data) : check?.error |
| 406 | + const className = probeCheckStatusClass(check?.status, options) |
402 | 407 |
|
403 | 408 | return { |
404 | 409 | label, |
405 | | - className: probeCheckStatusClass(check?.status), |
| 410 | + className, |
406 | 411 | detail: detail ? String(detail) : '', |
407 | 412 | } |
408 | 413 | } |
|
458 | 463 | const card = document.createElement('article') |
459 | 464 | card.className = 'network-health-target' |
460 | 465 |
|
| 466 | + const header = document.createElement('div') |
| 467 | + header.className = 'network-health-target-header' |
| 468 | + |
461 | 469 | const title = document.createElement('p') |
462 | 470 | title.className = 'network-health-target-url mb-0' |
463 | 471 | title.textContent = result?.target?.relayUrl ?? result?.target?.wsUrl ?? 'Unknown target' |
464 | | - card.appendChild(title) |
| 472 | + header.appendChild(title) |
| 473 | + |
| 474 | + const networkType = result?.target?.networkType |
| 475 | + if (networkType) { |
| 476 | + const badge = document.createElement('span') |
| 477 | + badge.className = 'network-health-network-type' |
| 478 | + badge.textContent = networkType |
| 479 | + header.appendChild(badge) |
| 480 | + } |
| 481 | + |
| 482 | + card.appendChild(header) |
465 | 483 |
|
466 | 484 | const checks = document.createElement('div') |
467 | 485 | checks.className = 'network-health-checks' |
468 | 486 |
|
469 | 487 | const dns = formatProbeCheckDetail(result.dns, (data) => { |
470 | | - const count = Array.isArray(data?.records) ? data.records.length : 0 |
471 | | - return `${count} record${count === 1 ? '' : 's'}` |
472 | | - }) |
473 | | - const tls = formatProbeCheckDetail(result.tls, (data) => { |
474 | | - if (typeof data?.daysUntilExpiry === 'number') { |
475 | | - return `${data.daysUntilExpiry}d remaining` |
| 488 | + const records = Array.isArray(data?.records) ? data.records : [] |
| 489 | + |
| 490 | + if (records.length === 0) { |
| 491 | + return 'no records' |
476 | 492 | } |
477 | 493 |
|
478 | | - return data?.issuer ?? 'valid' |
| 494 | + const preview = records.slice(0, 3).map((record) => { |
| 495 | + const ttl = typeof record?.ttl === 'number' ? ` TTL ${record.ttl}` : '' |
| 496 | + return `${record.type} ${record.value}${ttl}` |
| 497 | + }) |
| 498 | + |
| 499 | + if (records.length > 3) { |
| 500 | + preview.push(`+${records.length - 3} more`) |
| 501 | + } |
| 502 | + |
| 503 | + return preview.join('; ') |
479 | 504 | }) |
| 505 | + const tlsDaysUntilExpiry = |
| 506 | + result.tls?.status === 'ok' && typeof result.tls?.data?.daysUntilExpiry === 'number' |
| 507 | + ? result.tls.data.daysUntilExpiry |
| 508 | + : undefined |
| 509 | + const tls = formatProbeCheckDetail( |
| 510 | + result.tls, |
| 511 | + (data) => { |
| 512 | + if (typeof data?.daysUntilExpiry === 'number') { |
| 513 | + return `${data.daysUntilExpiry}d remaining` |
| 514 | + } |
| 515 | + |
| 516 | + return data?.issuer ?? 'valid' |
| 517 | + }, |
| 518 | + { tlsDaysUntilExpiry }, |
| 519 | + ) |
480 | 520 | const wsRtt = formatProbeCheckDetail(result.wsRtt, (data) => `${data.rttOpenMs} ms`) |
481 | 521 | const nip11 = formatProbeCheckDetail(result.nip11, (data) => { |
482 | 522 | const name = data?.name ? ` ${data.name}` : '' |
483 | | - return `HTTP ${data.statusCode}${name}` |
| 523 | + const supportedNips = Array.isArray(data?.supportedNips) ? data.supportedNips : null |
| 524 | + const nip66Warning = |
| 525 | + supportedNips && !supportedNips.includes(66) ? ' · NIP-66 not in supported_nips' : '' |
| 526 | + |
| 527 | + return `HTTP ${data.statusCode}${name}${nip66Warning}` |
484 | 528 | }) |
485 | 529 |
|
| 530 | + if ( |
| 531 | + result.nip11?.status === 'ok' && |
| 532 | + Array.isArray(result.nip11?.data?.supportedNips) && |
| 533 | + !result.nip11.data.supportedNips.includes(66) |
| 534 | + ) { |
| 535 | + nip11.className = 'status-degraded' |
| 536 | + } |
| 537 | + |
486 | 538 | ;[ |
487 | 539 | ['DNS', dns], |
488 | 540 | ['TLS', tls], |
|
0 commit comments