P2R-02 slice 4: Repo tab — connection / bandwidth / maintenance
Three independent forms on /hosts/{id}/repo so saving one section
doesn't disturb the others:
* Connection: edits repo URL, username, password (pre-filled from
the redacted GET /api/hosts/{id}/repo-credentials view; password
field shows masked stored-creds placeholder; blank password = keep
existing). On save, encrypts and pushes config.update to a
connected agent.
* Bandwidth: host-wide upload/download caps (KB/s; blank = no cap)
written via store.SetHostBandwidth. New REST endpoint
PUT /api/hosts/{id}/bandwidth for JSON callers.
* Maintenance: forget/prune/check cadences + check subset %, with
per-row enabled toggles. Reuses cronParser for validation;
auto-seeds the row if a host pre-dates the migration.
Right-rail surfaces repo size, snapshot count, snapshots-by-tag
breakdown (counted from existing snapshot tag rows), and an
'untagged snapshots are left alone' note.
Danger-zone re-init button is rendered but disabled with a hint
pointing at P2R-09 (real implementation lands there).
Validation re-renders the page with the relevant form's banner and
all other section state intact. Successful saves redirect with a
?saved=<section> query param so the page surfaces a small ✓ saved
indicator on the relevant form.
ci.yml: bump golangci-lint-action v6→v7 (separate change picked up
in this commit).
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
// host_bandwidth.go — REST API for /api/hosts/{id}/bandwidth.
|
||||
//
|
||||
// Host-wide upload/download caps (KB/s). Applied to every restic
|
||||
// invocation as --limit-upload / --limit-download. Pass null /
|
||||
// omit a field to clear that cap.
|
||||
package http
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
stdhttp "net/http"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"gitea.dcglab.co.uk/steve/restic-manager/internal/store"
|
||||
)
|
||||
|
||||
type hostBandwidthRequest struct {
|
||||
BandwidthUpKBps *int `json:"bandwidth_up_kbps"`
|
||||
BandwidthDownKBps *int `json:"bandwidth_down_kbps"`
|
||||
}
|
||||
|
||||
type hostBandwidthView struct {
|
||||
BandwidthUpKBps *int `json:"bandwidth_up_kbps"`
|
||||
BandwidthDownKBps *int `json:"bandwidth_down_kbps"`
|
||||
}
|
||||
|
||||
func (s *Server) handleUpdateHostBandwidth(w stdhttp.ResponseWriter, r *stdhttp.Request) {
|
||||
if !s.authedUser(r) {
|
||||
writeJSONError(w, stdhttp.StatusUnauthorized, "unauthorized", "")
|
||||
return
|
||||
}
|
||||
hostID := chi.URLParam(r, "id")
|
||||
if _, err := s.deps.Store.GetHost(r.Context(), hostID); err != nil {
|
||||
if errors.Is(err, store.ErrNotFound) {
|
||||
writeJSONError(w, stdhttp.StatusNotFound, "host_not_found", "")
|
||||
return
|
||||
}
|
||||
writeJSONError(w, stdhttp.StatusInternalServerError, "internal", "")
|
||||
return
|
||||
}
|
||||
var req hostBandwidthRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeJSONError(w, stdhttp.StatusBadRequest, "invalid_json", err.Error())
|
||||
return
|
||||
}
|
||||
if req.BandwidthUpKBps != nil && *req.BandwidthUpKBps < 0 {
|
||||
writeJSONError(w, stdhttp.StatusBadRequest, "invalid_value",
|
||||
"bandwidth_up_kbps must be non-negative")
|
||||
return
|
||||
}
|
||||
if req.BandwidthDownKBps != nil && *req.BandwidthDownKBps < 0 {
|
||||
writeJSONError(w, stdhttp.StatusBadRequest, "invalid_value",
|
||||
"bandwidth_down_kbps must be non-negative")
|
||||
return
|
||||
}
|
||||
if err := s.deps.Store.SetHostBandwidth(r.Context(), hostID, req.BandwidthUpKBps, req.BandwidthDownKBps); err != nil {
|
||||
writeJSONError(w, stdhttp.StatusInternalServerError, "internal", err.Error())
|
||||
return
|
||||
}
|
||||
writeJSON(w, stdhttp.StatusOK, hostBandwidthView{
|
||||
BandwidthUpKBps: req.BandwidthUpKBps,
|
||||
BandwidthDownKBps: req.BandwidthDownKBps,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user