/**
* GET /peers — swarm peers + sync remotes.
*
* Orchestrator-level view: combines state from all services that share
* the daemon's Hyperswarm. Shows:
*
* - Swarm summary (topic + peer count)
* - Live connections (remote public keys from the shared swarm)
* - Sync remotes — named peer serverKeys for pulling other servers' repos
* - Form to add a new sync remote by serverKey
*
* "Remotes" in sync-speak are server identities this daemon knows how to
* pull from; they're not the same as live connections.
*/
import b4a from 'b4a'
import { layout, escapeHtml, identicon } from '../helpers.js'
import { isAdmin } from '../../auth/check.js'
const PEERS_STYLES = `
<style>
.peer-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 10px; }
.peer-card { display: flex; align-items: center; gap: 0.6rem; background: #0d1117; border: 1px solid #30363d; border-radius: 6px; padding: 0.6rem 0.8rem; }
.peer-key { font-family: monospace; font-size: 0.75rem; color: #c9d1d9; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.peer-sub { font-size: 0.7rem; color: #8b949e; margin-top: 2px; }
</style>`
export async function renderPeersIndex (svc, ctx) {
const prefix = (ctx && ctx.prefix) || ''
const hasSwarm = !!svc.swarm
const swarm = hasSwarm ? svc.swarm.swarm : null
const canAdmin = await isAdmin(ctx, svc.identityRegistry)
const topicHex = hasSwarm ? b4a.toString(svc.swarm.topic, 'hex') : null
const connectionCount = hasSwarm ? svc.swarm.connections : 0
// Try to extract live peer pubkeys from the underlying hyperswarm's
// connection set. Hyperswarm exposes .connections as a Set of Noise
// streams; each stream has a `.remotePublicKey` buffer. Where available
// we also pull the underlying UDX stream's remote address so we can show
// something human-sized beyond the noise pubkey.
const livePeers = []
if (swarm && swarm.connections) {
try {
for (const conn of swarm.connections) {
if (!conn.remotePublicKey) continue
const rawStream = conn.rawStream || null
const remoteAddr = rawStream && rawStream.remoteHost
? rawStream.remoteHost + ':' + rawStream.remotePort
: null
livePeers.push({
pubkey: b4a.toString(conn.remotePublicKey, 'hex'),
remoteAddr
})
}
} catch {}
}
// Sync remotes (server keys we know how to pull from)
const syncRemotes = []
if (svc._sync && svc._sync.remotes) {
for (const [name, info] of svc._sync.remotes) {
syncRemotes.push({
name,
serverKey: typeof info === 'string' ? info : (info.serverKey || info)
})
}
}
// Repos currently served — read hyper-sync's topic map so we can render
// per-repo topics + a link back to each repo's own Peers tab. Each
// _topics entry is { topic: Buffer, discovery, name }.
const servedRepos = []
if (svc._sync && svc._sync._topics) {
for (const [name, entry] of svc._sync._topics) {
servedRepos.push({
name,
topic: entry.topic ? Buffer.from(entry.topic).toString('hex') : null
})
}
}
// Daemon's own serverKey + identity — handy for giving peers our address.
const serverKey = svc._sync && svc._sync.serverKey ? svc._sync.serverKey : null
const identityHex = svc.identity ? svc.identity.publicKeyHex : null
const body = `
${PEERS_STYLES}
<h1>peers</h1>
<div class="card">
<h2>This daemon</h2>
${identityHex ? `<div class="row"><div class="label">identity</div><div class="meta mono" style="font-size:0.7rem">${escapeHtml(identityHex)}</div></div>` : ''}
${serverKey ? `<div class="row"><div class="label">server key</div><div class="meta mono" style="font-size:0.7rem">${escapeHtml(serverKey)}</div></div>` : ''}
</div>
<div class="card">
<h2>Swarm</h2>
${hasSwarm ? `
<div class="row"><div class="label">status</div><div><span class="badge badge-open">connected</span></div></div>
<div class="row"><div class="label">topic</div><div class="meta mono" style="font-size:0.7rem">${escapeHtml(topicHex)}</div></div>
<div class="row"><div class="label">connections</div><div>${connectionCount}</div></div>
` : `
<div class="empty">Swarm is not enabled. Start the daemon with a swarm config to peer with others.</div>`}
</div>
${hasSwarm && livePeers.length > 0 ? `
<div class="card">
<h2>Live connections (${livePeers.length})</h2>
<div class="peer-grid">
${livePeers.map(p => `
<div class="peer-card">
${identicon(p.pubkey, 32)}
<div style="flex:1;min-width:0">
<div class="peer-key">${escapeHtml(p.pubkey.slice(0, 20))}…</div>
<div class="peer-sub">${p.remoteAddr ? escapeHtml(p.remoteAddr) : 'remote pubkey'}</div>
</div>
</div>`).join('')}
</div>
</div>` : hasSwarm ? `
<div class="card">
<h2>Live connections (0)</h2>
<div class="empty">No active connections. Run the topic on another daemon to peer up.</div>
</div>` : ''}
${svc._sync ? `
<div class="card">
<h2>Repos served (${servedRepos.length})</h2>
<div class="meta" style="margin-bottom:0.5rem">Repos this daemon is actively serving on the swarm. Each has its own per-repo topic.</div>
${servedRepos.length === 0 ? '<div class="empty">No repos in the repos directory yet. Push one to start serving.</div>' : `
<div class="peer-grid">
${servedRepos.map(r => `
<div class="peer-card">
<div style="flex:1;min-width:0">
<div class="peer-key"><a href="${prefix}/repos/${encodeURIComponent(r.name)}/peers">${escapeHtml(r.name)}</a></div>
<div class="peer-sub">${r.topic ? escapeHtml(r.topic.slice(0, 16)) + '…' : '(no topic)'}</div>
</div>
</div>`).join('')}
</div>`}
</div>` : ''}
${svc._sync ? `
<div class="card">
<h2>Sync remotes (${syncRemotes.length})</h2>
<div class="meta" style="margin-bottom:0.5rem">Known servers you can <code>git pull</code> from via punch://.</div>
${syncRemotes.length === 0
? `<div class="empty">${canAdmin ? '(no remotes — add one below)' : 'No sync remotes — only owners can add them.'}</div>`
: `
<div class="peer-grid">
${syncRemotes.map(r => `
<div class="peer-card">
${identicon(r.serverKey, 32)}
<div style="flex:1;min-width:0">
<div class="peer-key">${escapeHtml(r.name)}</div>
<div class="peer-sub">${escapeHtml(r.serverKey.slice(0, 20))}…</div>
</div>
${canAdmin ? `
<form action="${prefix}/peers/remote-remove" method="POST" style="margin:0">
<input type="hidden" name="name" value="${escapeHtml(r.name)}">
<button class="btn-secondary" type="submit" style="font-size:0.7rem;padding:0.2rem 0.5rem" onclick="return confirm('Remove remote ${escapeHtml(r.name)}?')">remove</button>
</form>` : ''}
</div>`).join('')}
</div>`}
${canAdmin ? `
<form action="${prefix}/peers/remote-add" method="POST" style="margin-top:1rem">
<h2 style="margin-top:0">Add a sync remote</h2>
<div class="field"><label for="add-remote-name">Name (alias)</label><input id="add-remote-name" name="name" placeholder="my-mirror" required></div>
<div class="field"><label for="add-remote-serverkey">Server key (hex)</label><input id="add-remote-serverkey" name="serverKey" placeholder="64-char hex pubkey" required></div>
<button type="submit">Add</button>
</form>` : ''}
</div>` : `
<div class="card">
<h2>Sync remotes</h2>
<div class="empty">Sync service is not running.</div>
</div>`}
`
return layout('peers', '/peers', body, ctx)
}