feat(ui): edit doors from the admin page (add Edit action)

The PUT /api/v1/doors/{id} endpoint already existed and preserves the
controller token when it isn't resent, but the admin UI only exposed
create / reveal-token / delete. Add a per-row Edit (pencil) button that
reuses the door dialog as create-or-update (saveDoor: POST when new, PUT
when editing), so a door's Home Assistant webhook URL / boltcards base /
timeout can be changed without deleting + recreating (which would churn
the controller token the reader uses). Bump to 0.0.3.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Patrick Mulligan 2026-09-08 23:56:52 +02:00
commit 2bc4a01edc
3 changed files with 39 additions and 10 deletions

View file

@ -2,7 +2,7 @@
"name": "Access", "name": "Access",
"short_description": "NFC door access — authorize Bolt Card taps and unlock via Home Assistant", "short_description": "NFC door access — authorize Bolt Card taps and unlock via Home Assistant",
"tile": "/access/static/image/access.png", "tile": "/access/static/image/access.png",
"version": "0.0.2", "version": "0.0.3",
"min_lnbits_version": "1.3.0", "min_lnbits_version": "1.3.0",
"contributors": [ "contributors": [
{ {

View file

@ -75,14 +75,31 @@ window.app = Vue.createApp({
) )
this.logs = data this.logs = data
}, },
async createDoor() { editDoor(door) {
// Prefill the dialog for an update. Drop controller_token so it's left
// untouched server-side (api_door_update keeps it when not resent).
const { controller_token, ...data } = door
this.doorDialog = { show: true, data }
},
async saveDoor() {
// POST to create, PUT to update an existing door (data.id present).
const d = this.doorDialog.data
try { try {
if (d.id) {
await LNbits.api.request(
'PUT',
`/access/api/v1/doors/${d.id}`,
this.adminkey,
d
)
} else {
await LNbits.api.request( await LNbits.api.request(
'POST', 'POST',
'/access/api/v1/doors', '/access/api/v1/doors',
this.adminkey, this.adminkey,
this.doorDialog.data d
) )
}
this.doorDialog = { show: false, data: { unlock_timeout_ms: 2500 } } this.doorDialog = { show: false, data: { unlock_timeout_ms: 2500 } }
await this.loadDoors() await this.loadDoors()
} catch (e) { } catch (e) {

View file

@ -34,6 +34,14 @@
> >
</q-td> </q-td>
<q-td> <q-td>
<q-btn
flat
dense
size="sm"
icon="edit"
@click="editDoor(props.row)"
><q-tooltip>Edit door</q-tooltip></q-btn
>
<q-btn <q-btn
flat flat
dense dense
@ -136,7 +144,9 @@
<q-dialog v-model="doorDialog.show"> <q-dialog v-model="doorDialog.show">
<q-card class="q-pa-lg" style="min-width: 420px"> <q-card class="q-pa-lg" style="min-width: 420px">
<h6 class="q-mt-none">New door</h6> <h6 class="q-mt-none">
{% raw %}{{ doorDialog.data.id ? 'Edit door' : 'New door' }}{% endraw %}
</h6>
<q-input <q-input
v-model="doorDialog.data.name" v-model="doorDialog.data.name"
label="Name" label="Name"
@ -167,7 +177,9 @@
></q-input> ></q-input>
<div class="row q-mt-md q-gutter-sm justify-end"> <div class="row q-mt-md q-gutter-sm justify-end">
<q-btn flat v-close-popup>Cancel</q-btn> <q-btn flat v-close-popup>Cancel</q-btn>
<q-btn unelevated color="primary" @click="createDoor">Create</q-btn> <q-btn unelevated color="primary" @click="saveDoor"
>{% raw %}{{ doorDialog.data.id ? 'Save' : 'Create' }}{% endraw %}</q-btn
>
</div> </div>
</q-card> </q-card>
</q-dialog> </q-dialog>