feat: Add button deletion and GPIO pin modification to webapp

- Add DELETE endpoint for buttons with confirmation dialog
- Add editable GPIO pin input fields in button list
- Update PUT endpoint to handle both recipe and GPIO pin changes
- Enhanced button management UI with delete buttons and inline editing
- All changes use HTMX for seamless UI updates

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Marc Boivin 2025-06-25 08:01:52 -04:00
parent 21405819f0
commit fb82972e98
2 changed files with 79 additions and 8 deletions

View File

@ -260,26 +260,76 @@ async def create_button(
logger.error(f"Full traceback: {traceback.format_exc()}") logger.error(f"Full traceback: {traceback.format_exc()}")
raise HTTPException(status_code=400, detail="Failed to create button") raise HTTPException(status_code=400, detail="Failed to create button")
@app.put("/buttons/{button_id}") @app.put("/buttons/{button_id}", response_class=HTMLResponse)
async def update_button( async def update_button(
request: Request,
button_id: int, button_id: int,
recipe_id: str = Form(""), recipe_id: str = Form(""),
gpio_pin: Optional[int] = Form(None),
db: DatabaseManager = Depends(get_db) db: DatabaseManager = Depends(get_db)
): ):
"""Update button recipe mapping""" """Update button recipe mapping and/or GPIO pin"""
try: try:
# Convert empty string to None, otherwise convert to int # Convert empty string to None, otherwise convert to int
recipe_id_value = None if recipe_id == "" else int(recipe_id) recipe_id_value = None if recipe_id == "" else int(recipe_id)
if gpio_pin is not None:
# Update both recipe and GPIO pin
db.execute_update(
"UPDATE buttons SET recipe_id = ?, gpio_pin = ? WHERE id = ?",
(recipe_id_value, gpio_pin, button_id)
)
else:
# Update only recipe
db.execute_update( db.execute_update(
"UPDATE buttons SET recipe_id = ? WHERE id = ?", "UPDATE buttons SET recipe_id = ? WHERE id = ?",
(recipe_id_value, button_id) (recipe_id_value, button_id)
) )
return {"status": "success"}
# Return updated button list
buttons = db.execute_query("""
SELECT b.*, r.name as recipe_name
FROM buttons b
LEFT JOIN recipes r ON b.recipe_id = r.id
ORDER BY b.name
""")
recipes = db.execute_query("SELECT * FROM recipes ORDER BY name")
return templates.TemplateResponse("partials/button_list.html", {
"request": request,
"buttons": buttons,
"recipes": recipes
})
except Exception as e: except Exception as e:
logger.error(f"Error updating button: {e}") logger.error(f"Error updating button: {e}")
raise HTTPException(status_code=400, detail="Failed to update button") raise HTTPException(status_code=400, detail="Failed to update button")
@app.delete("/buttons/{button_id}", response_class=HTMLResponse)
async def delete_button(
request: Request,
button_id: int,
db: DatabaseManager = Depends(get_db)
):
"""Delete a button"""
try:
db.execute_update("DELETE FROM buttons WHERE id = ?", (button_id,))
# Return updated button list
buttons = db.execute_query("""
SELECT b.*, r.name as recipe_name
FROM buttons b
LEFT JOIN recipes r ON b.recipe_id = r.id
ORDER BY b.name
""")
recipes = db.execute_query("SELECT * FROM recipes ORDER BY name")
return templates.TemplateResponse("partials/button_list.html", {
"request": request,
"buttons": buttons,
"recipes": recipes
})
except Exception as e:
logger.error(f"Error deleting button: {e}")
raise HTTPException(status_code=400, detail="Failed to delete button")
# Shot history routes # Shot history routes
@app.get("/shots", response_class=HTMLResponse) @app.get("/shots", response_class=HTMLResponse)
async def list_shots(request: Request, db: DatabaseManager = Depends(get_db)): async def list_shots(request: Request, db: DatabaseManager = Depends(get_db)):

View File

@ -5,6 +5,7 @@
<th>Name</th> <th>Name</th>
<th>GPIO Pin</th> <th>GPIO Pin</th>
<th>Recipe</th> <th>Recipe</th>
<th>Status</th>
<th>Actions</th> <th>Actions</th>
</tr> </tr>
</thead> </thead>
@ -12,7 +13,17 @@
{% for button in buttons %} {% for button in buttons %}
<tr> <tr>
<td>{{ button.name }}</td> <td>{{ button.name }}</td>
<td>GPIO {{ button.gpio_pin }}</td> <td>
<div class="input-group input-group-sm" style="width: 120px;">
<span class="input-group-text">GPIO</span>
<input type="number" class="form-control"
value="{{ button.gpio_pin }}"
min="0" max="40"
hx-put="/buttons/{{ button.id }}"
hx-trigger="change"
name="gpio_pin">
</div>
</td>
<td> <td>
<select class="form-select form-select-sm" <select class="form-select form-select-sm"
hx-put="/buttons/{{ button.id }}" hx-put="/buttons/{{ button.id }}"
@ -32,6 +43,16 @@
{{ 'Configured' if button.recipe_name else 'Not Configured' }} {{ 'Configured' if button.recipe_name else 'Not Configured' }}
</span> </span>
</td> </td>
<td>
<button class="btn btn-danger btn-sm"
hx-delete="/buttons/{{ button.id }}"
hx-target="#button-list"
hx-swap="innerHTML"
hx-confirm="Are you sure you want to delete this button?"
title="Delete Button">
<i class="bi bi-trash"></i> Delete
</button>
</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>