/**
* Device management — `/devices`.
*
* Web UI for the device-attestation surface that was previously CLI-only
* (`pgh device invite/list/revoke/label`). Lists the devices linked to
* your identity, mints link invites, and revokes/labels devices.
*
* The `join` side (accepting an invite on a NEW device) stays a CLI
* ceremony (`pgh device join <code>`) — it's an interactive blind-pairing
* over the swarm, not a web form.
*/
import { layout, escapeHtml } from '../helpers.js'
export async function renderDevices (svc, ctx) {
const prefix = (ctx && ctx.prefix) || ''
let devices = []
try {
const r = await svc.dispatch('list-my-devices', {}, ctx)
devices = (r && r.devices) || []
} catch (err) {
if (err && (err.status === 401 || err.status === 403)) {
return layout('devices', '/devices', `
<div class="card"><h2>Devices</h2>
<div class="meta">Sign in to manage the devices linked to your identity.</div>
</div>`, ctx)
}
}
const fmt = (t) => t ? new Date(t).toISOString().slice(0, 10) : '—'
const body = `
<div class="card">
<h2>Your devices</h2>
<div class="meta" style="margin-bottom:0.5rem;font-size:0.8rem">
Devices attested to your identity chain. Each can act as you on this forge. Revoke any you no longer control.
</div>
${devices.length === 0 ? '<div class="empty">(no devices linked yet)</div>' : `
<div class="member-list">
${devices.map(d => {
const pk = String(d.publicKey || '')
const short = pk.slice(0, 24)
const badge = d.revoked
? '<span class="badge badge-closed">revoked</span>'
: '<span class="badge badge-open">active</span>'
return `
<div class="member-row" style="display:flex;align-items:flex-start;gap:0.75rem;padding:0.5rem 0;border-bottom:1px solid var(--border,#222)">
<div style="flex:1">
<div>${badge} ${d.label ? '<strong>' + escapeHtml(d.label) + '</strong>' : '<span class="meta">(unlabeled)</span>'}</div>
<div class="mono" style="font-size:0.7rem">${escapeHtml(short)}${pk.length > 24 ? '…' : ''}</div>
<div class="meta" style="font-size:0.7rem">method: ${escapeHtml(String(d.method || 'unknown'))} · linked: ${fmt(d.firstLinkedAt)} · last seen: ${fmt(d.lastSeenAt)}</div>
</div>
${d.revoked ? '' : `
<div style="display:flex;flex-direction:column;gap:0.3rem;align-items:flex-end">
<form action="${prefix}/devices/label" method="POST" style="margin:0;display:flex;gap:0.3rem">
<input type="hidden" name="devicePubkey" value="${escapeHtml(pk)}">
<input type="text" name="label" placeholder="label" value="${escapeHtml(String(d.label || ''))}" style="font-size:0.7rem;width:8rem">
<button class="btn-secondary" type="submit" style="font-size:0.7rem;padding:0.2rem 0.5rem">rename</button>
</form>
<form action="${prefix}/devices/revoke" method="POST" style="margin:0" onsubmit="return confirm('Revoke device ${escapeHtml(short)}…? It will no longer be able to act as you.')">
<input type="hidden" name="devicePubkey" value="${escapeHtml(pk)}">
<button class="btn-secondary" type="submit" style="font-size:0.7rem;padding:0.2rem 0.5rem">revoke</button>
</form>
</div>`}
</div>`
}).join('')}
</div>`}
</div>
<div class="card">
<h2>Link a new device</h2>
<div class="meta" style="margin-bottom:0.5rem;font-size:0.8rem">
Generates a one-time invite code. On the new device run <code>pgh device join <code></code> — it pairs over the swarm and is attested to your identity chain. A <em>mirror</em> invite also carries the deployment bundle so a sister daemon joins the same autobases (HA).
</div>
<form action="${prefix}/devices/invite" method="POST">
<label style="font-size:0.8rem"><input type="checkbox" name="mirror" value="1"> mirror invite (sister daemon / HA)</label>
<div style="margin-top:0.5rem"><button type="submit">Generate device invite</button></div>
</form>
</div>`
return layout('devices', '/devices', body, ctx)
}
export function renderDeviceInviteResult (inviteHex, mirror, ctx) {
const prefix = (ctx && ctx.prefix) || ''
const body = `
<h2><a href="${prefix}/devices">← devices</a> / invite</h2>
<div class="card">
<h2 style="margin-top:0">Device invite created${mirror ? ' (mirror)' : ''}</h2>
<div class="meta" style="margin-bottom:0.5rem">On the new device, run <code>pgh device join <code></code>. The code is one-time and expires in 10 minutes.</div>
<textarea readonly rows="4" style="width:100%;font-family:monospace;font-size:0.8rem" onclick="this.select()">${escapeHtml(inviteHex)}</textarea>
</div>`
return layout('device invite', '/devices', body, ctx)
}