OverviewFilesCommitsIssuesDocsReleasesPeers

pear-git-v3 / lib / web / pages / status.js

102 lines · javascript ref:
/**
 * GET /status — daemon status page (formerly /).
 *
 * Scannable cards (Identity, Service, Data, Bootstrap keys) so the operator
 * can see what's running at a glance instead of scanning walls of text.
 * Bootstrap keys are collapsed by default since the operator only needs
 * them when joining a peer.
 *
 * Linked from the /admin page; not in the main nav.
 */

import b4a from 'b4a'

import { layout, escapeHtml } from '../helpers.js'

export async function renderStatus (svc, ctx) {
  const prefix = (ctx && ctx.prefix) || ''
  const orgs = await svc.modules.orgs.list()
  // Aggregate across every per-repo and per-org issue tracker.
  const counts = await svc.issueRegistry.counts()
  const users = await svc.identityRegistry.listUsers().catch(() => [])
  const handlers = svc.listHandlers()

  const swarmTopic = svc.swarm ? b4a.toString(svc.swarm.topic, 'hex') : null
  const swarmPeers = svc.swarm ? svc.swarm.connections : 0

  const id = svc.identity
  const idShort = id ? id.publicKeyHex.slice(0, 16) + '…' + id.publicKeyHex.slice(-8) : '(none)'

  const sessionId = ctx && ctx.identity && ctx.identity.publicKey
    ? ctx.identity.publicKey.slice(0, 16) + '…' + ctx.identity.publicKey.slice(-8)
    : '(unauthenticated)'
  const sessionTransport = ctx ? ctx.transport : '?'

  const bk = svc.bootstrapKeys || {}
  const orgsKey = bk.orgs || svc.modules.orgs.keyHex
  const identityKey = bk.identity || null
  const repoEventLogKey = bk.repoEventLog || null
  // Per-scope issue bootstrap keys — built on demand from the registry.
  // Displayed under the "Bootstrap keys" disclosure only when >0 scopes exist.
  const issueKeys = await svc.issueRegistry.keys()
  const issueKeyRows = [
    ...Object.entries(issueKeys.repos || {}).map(([name, key]) => ({ label: 'issues.repo.' + name, key })),
    ...Object.entries(issueKeys.orgs  || {}).map(([name, key]) => ({ label: 'issues.org.'  + name, key }))
  ]

  const transports = [
    { name: 'uds', enabled: !!svc.uds, info: svc.uds ? svc.uds.path : '—' },
    { name: 'http', enabled: !!svc.http, info: svc.http ? '127.0.0.1:' + (svc.http.port || '?') : '—' },
    {
      name: 'swarm',
      enabled: !!svc.swarm,
      info: svc.swarm ? `topic ${swarmTopic.slice(0, 16)}… · ${swarmPeers} peer${swarmPeers === 1 ? '' : 's'}` : '—'
    }
  ]

  const body = `
<h1>status</h1>

<div class="card">
  <h2>Identity</h2>
  <div class="row"><div class="label">daemon</div><div title="${id ? escapeHtml(id.publicKeyHex) : ''}" class="mono" style="font-size:0.8rem">${escapeHtml(idShort)}</div></div>
  <div class="row"><div class="label">session</div><div class="mono" style="font-size:0.8rem">${escapeHtml(sessionId)}</div></div>
  <div class="row"><div class="label">transport</div><div>${escapeHtml(sessionTransport)}</div></div>
</div>

<div class="card">
  <h2>Service</h2>
  <div class="row"><div class="label">name</div><div>${escapeHtml(svc.name)}</div></div>
  <div class="row"><div class="label">handlers</div><div>${handlers.length}</div></div>
  <div class="row"><div class="label">mounts</div><div>${svc.listMounts().join(', ')}</div></div>
  ${transports.map(tr => `
  <div class="row">
    <div class="label">${escapeHtml(tr.name)}</div>
    <div class="meta">${tr.enabled ? 'on · ' + escapeHtml(tr.info) : 'off'}</div>
  </div>`).join('')}
</div>

<div class="card">
  <h2>Data</h2>
  <div class="row"><div class="label">orgs</div><div>${orgs.length} (${orgs.filter(o => o.type === 'group').length} group${orgs.filter(o => o.type === 'group').length === 1 ? '' : 's'}, ${orgs.filter(o => o.type === 'user').length} user${orgs.filter(o => o.type === 'user').length === 1 ? '' : 's'})</div></div>
  <div class="row"><div class="label">issues</div><div>${counts.total} (${counts.open} open, ${counts.closed} closed)</div></div>
  <div class="row"><div class="label">users</div><div>${Array.isArray(users) ? users.length : '?'}</div></div>
</div>

<div class="card">
  <details>
    <summary style="cursor:pointer;color:#8b949e;font-size:0.75rem;text-transform:uppercase;letter-spacing:0.5px;">Bootstrap keys</summary>
    <div class="meta" style="margin:0.5rem 0">Copy these into a peer's <code>opts.mounts</code> to join this deployment.</div>
    <div class="row"><div class="label">orgs.bootstrapKey</div><div class="mono" style="font-size:0.7rem">${escapeHtml(orgsKey || '(none)')}</div></div>
    <div class="row"><div class="label">identity.bootstrapKey</div><div class="mono" style="font-size:0.7rem">${escapeHtml(identityKey || '(none)')}</div></div>
    <div class="row"><div class="label">repoEventLog.coreKey</div><div class="mono" style="font-size:0.7rem">${escapeHtml(repoEventLogKey || '(none)')}</div></div>
    ${issueKeyRows.length > 0 ? issueKeyRows.map(r =>
      `<div class="row"><div class="label">${escapeHtml(r.label)}</div><div class="mono" style="font-size:0.7rem">${escapeHtml(r.key || '(none)')}</div></div>`
    ).join('') : '<div class="row"><div class="label">issues</div><div class="meta">(no issue trackers created yet — they instantiate on first access per repo/org)</div></div>'}
    ${swarmTopic ? `<div class="row"><div class="label">swarm.topic</div><div class="mono" style="font-size:0.7rem">${escapeHtml(swarmTopic)}</div></div>` : ''}
  </details>
</div>
`
  return layout('status', '/status', body, ctx)
}