feat(webui): add state force unlock
All checks were successful
main / main (push) Successful in 2m49s
main / deploy (push) Has been skipped
main / publish (push) Has been skipped

Closes #13
This commit is contained in:
Julien Dessaux 2025-05-02 00:42:50 +02:00
parent 8f17a3661e
commit 20bc9fe17a
Signed by: adyxax
GPG key ID: F92E51B86E07177E
3 changed files with 31 additions and 11 deletions

View file

@ -6,6 +6,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"git.adyxax.org/adyxax/tfstated/pkg/model"
"go.n16f.net/uuid" "go.n16f.net/uuid"
) )
@ -83,4 +84,15 @@ func (db *DB) Unlock(path string, lock any) (bool, error) {
} }
return n == 1, nil return n == 1, nil
} }
func (db *DB) ForceUnlock(state *model.State) error {
_, err := db.Exec(
`UPDATE states
SET lock = NULL
WHERE id = ?;`,
state.Id)
if err != nil {
return fmt.Errorf("failed to update state: %w", err)
}
return nil
} }

View file

@ -70,7 +70,8 @@
<input name="csrf_token" type="hidden" value="{{ .Page.Session.Data.CsrfToken }}"> <input name="csrf_token" type="hidden" value="{{ .Page.Session.Data.CsrfToken }}">
<fieldset> <fieldset>
<legend>Danger Zone</legend> <legend>Danger Zone</legend>
<button action="delete" type="submit" value="delete">Delete State</button> <button name="action" type="submit" value="delete">Delete State</button>
<button {{ if eq .State.Lock nil }}disabled{{ end }} name="action" type="submit" value="unlock">Unlock State</button>
</fieldset> </fieldset>
</form> </form>
</div> </div>

View file

@ -66,7 +66,6 @@ func handleStatesIdPOST(db *database.DB) http.Handler {
if !verifyCSRFToken(w, r) { if !verifyCSRFToken(w, r) {
return return
} }
action := r.FormValue("action")
var stateId uuid.UUID var stateId uuid.UUID
if err := stateId.Parse(r.PathValue("id")); err != nil { if err := stateId.Parse(r.PathValue("id")); err != nil {
errorResponse(w, r, http.StatusBadRequest, err) errorResponse(w, r, http.StatusBadRequest, err)
@ -87,6 +86,7 @@ func handleStatesIdPOST(db *database.DB) http.Handler {
errorResponse(w, r, http.StatusInternalServerError, err) errorResponse(w, r, http.StatusInternalServerError, err)
return return
} }
action := r.FormValue("action")
switch action { switch action {
case "delete": case "delete":
errorResponse(w, r, http.StatusNotImplemented, err) errorResponse(w, r, http.StatusNotImplemented, err)
@ -121,17 +121,24 @@ func handleStatesIdPOST(db *database.DB) http.Handler {
}) })
return return
} }
render(w, statesIdTemplate, http.StatusOK, StatesIdPage{ case "unlock":
Page: makePage(r, &Page{ if err := db.ForceUnlock(state); err != nil {
Section: "states", errorResponse(w, r, http.StatusInternalServerError, err)
Title: state.Path, return
}), }
State: state, state.Lock = nil
Usernames: usernames,
Versions: versions,
})
default: default:
errorResponse(w, r, http.StatusBadRequest, err) errorResponse(w, r, http.StatusBadRequest, err)
return
} }
render(w, statesIdTemplate, http.StatusOK, StatesIdPage{
Page: makePage(r, &Page{
Section: "states",
Title: state.Path,
}),
State: state,
Usernames: usernames,
Versions: versions,
})
}) })
} }