feat(ui): Fleet Pair / Revoke spire UI (#9/#12)
Some checks failed
ci.yml / feat(ui): Fleet Pair / Revoke spire UI (#9/#12) (push) Failing after 0s
Some checks failed
ci.yml / feat(ui): Fleet Pair / Revoke spire UI (#9/#12) (push) Failing after 0s
Operator-facing front for POST /machines/{id}/pair + /revoke (#21/#23):
- Pairing chip per machine row (paired / not-paired + paired-at tooltip).
- 'Pair' (qr_code_2) opens a dialog -> relays + optional duration_hours
-> POST /pair -> renders the seed_url as <lnbits-qrcode> + copy, shows
the bunker-minted spire npub. Re-pair relabels.
- 'Revoke' (link_off, shown when paired) -> confirm -> POST /revoke ->
updates the row, reports revoked_count (>=1 cut / 0 never-bound).
- Row reflects the bunker-minted identity immediately (machine_npub <-
spire_pubkey_hex, paired_at).
Quasar-UMD conventions: explicit close tags, ${ } delimiters, :style.
JS syntax-checked, conforms to .prettierrc; 210 backend tests unaffected.
Needs a manual browser smoke (superuser-gated page).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
846d5d9d0f
commit
a18f653ca7
2 changed files with 208 additions and 0 deletions
|
|
@ -191,6 +191,14 @@ window.app = Vue.createApp({
|
|||
saving: false,
|
||||
data: {}
|
||||
},
|
||||
pairDialog: {
|
||||
show: false,
|
||||
saving: false,
|
||||
machine: null,
|
||||
relays: '',
|
||||
durationHours: null,
|
||||
result: null
|
||||
},
|
||||
machineDetail: {
|
||||
show: false,
|
||||
loading: false,
|
||||
|
|
@ -781,6 +789,93 @@ window.app = Vue.createApp({
|
|||
})
|
||||
},
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Pair / revoke spire (S0 / #9, #12)
|
||||
// -----------------------------------------------------------------
|
||||
openPairDialog(machine) {
|
||||
this.pairDialog.machine = machine
|
||||
this.pairDialog.relays = ''
|
||||
this.pairDialog.durationHours = null
|
||||
this.pairDialog.result = null
|
||||
this.pairDialog.show = true
|
||||
},
|
||||
|
||||
async submitPair() {
|
||||
const relays = (this.pairDialog.relays || '')
|
||||
.split(/[\s,]+/)
|
||||
.map(s => s.trim())
|
||||
.filter(Boolean)
|
||||
if (!relays.length) {
|
||||
Quasar.Notify.create({
|
||||
type: 'negative',
|
||||
message: 'At least one relay is required'
|
||||
})
|
||||
return
|
||||
}
|
||||
const body = {relays}
|
||||
if (this.pairDialog.durationHours) {
|
||||
body.duration_hours = Number(this.pairDialog.durationHours)
|
||||
}
|
||||
this.pairDialog.saving = true
|
||||
try {
|
||||
const {data} = await LNbits.api.request(
|
||||
'POST',
|
||||
`${MACHINES_PATH}/${this.pairDialog.machine.id}/pair`,
|
||||
null,
|
||||
body
|
||||
)
|
||||
this.pairDialog.result = data
|
||||
// The bunker-minted key becomes the machine identity; reflect it +
|
||||
// the paired state in the row immediately.
|
||||
const m = this.machines.find(x => x.id === this.pairDialog.machine.id)
|
||||
if (m) {
|
||||
m.machine_npub = data.spire_pubkey_hex
|
||||
m.bunker_spire_key_name = data.bunker_key_name
|
||||
m.paired_at = new Date().toISOString()
|
||||
}
|
||||
Quasar.Notify.create({
|
||||
type: 'positive',
|
||||
message: 'Spire paired — hand the seed URL to the device'
|
||||
})
|
||||
} catch (e) {
|
||||
this._notifyError(e, 'Pairing failed')
|
||||
} finally {
|
||||
this.pairDialog.saving = false
|
||||
}
|
||||
},
|
||||
|
||||
confirmRevokeMachine(machine) {
|
||||
Quasar.Dialog.create({
|
||||
title: 'Revoke spire access?',
|
||||
message:
|
||||
`This cuts <b>${machine.name || machine.machine_npub.slice(0, 12)}</b>'s` +
|
||||
' signing access at the bunker — the spire can no longer submit' +
|
||||
' cash-outs until you re-pair it. Continue?',
|
||||
html: true,
|
||||
cancel: true,
|
||||
persistent: true
|
||||
}).onOk(async () => {
|
||||
try {
|
||||
const {data} = await LNbits.api.request(
|
||||
'POST',
|
||||
`${MACHINES_PATH}/${machine.id}/revoke`,
|
||||
null
|
||||
)
|
||||
const m = this.machines.find(x => x.id === machine.id)
|
||||
if (m) m.paired_at = null
|
||||
Quasar.Notify.create({
|
||||
type: data.revoked_count >= 1 ? 'positive' : 'warning',
|
||||
message:
|
||||
data.revoked_count >= 1
|
||||
? 'Spire access revoked'
|
||||
: 'Nothing was bound (the spire never connected)'
|
||||
})
|
||||
} catch (e) {
|
||||
this._notifyError(e, 'Revoke failed')
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Machine detail dialog (P9b)
|
||||
// -----------------------------------------------------------------
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue