ui: P2R-12 hook editor — source-group form + host-default Repo section
Source-group edit form gains pre/post hook textareas with a service-
user warning banner; bodies AEAD-encrypted on save (per-group AD).
Repo page adds a 'Host-default hooks' panel above the danger zone
with the same shape; saved via POST /hosts/{id}/repo/hooks.
This commit is contained in:
@@ -56,6 +56,8 @@ type sourceFormData struct {
|
||||
RetryMax int
|
||||
RetryBackoffSeconds int
|
||||
ConflictDimension string
|
||||
PreHook string // plaintext; encrypted on save
|
||||
PostHook string
|
||||
}
|
||||
|
||||
// sourceGroupEditPage backs both the new and edit form views.
|
||||
@@ -173,11 +175,14 @@ func (s *Server) handleUISourceGroupEditGet(w stdhttp.ResponseWriter, r *stdhttp
|
||||
}
|
||||
view := s.baseView(u)
|
||||
view.Title = g.Name + " · " + host.Name + " · restic-manager"
|
||||
form := formFromGroup(*g)
|
||||
form.PreHook = s.decryptHookOrFallback(g.PreHook, "", host.ID, "pre")
|
||||
form.PostHook = s.decryptHookOrFallback(g.PostHook, "", host.ID, "post")
|
||||
view.Page = sourceGroupEditPage{
|
||||
hostChromeData: s.loadHostChrome(r, *host, "sources", g.Name),
|
||||
IsNew: false,
|
||||
GroupID: gid,
|
||||
Form: formFromGroup(*g),
|
||||
Form: form,
|
||||
SaveAction: "/hosts/" + host.ID + "/sources/" + gid + "/edit",
|
||||
}
|
||||
if err := s.deps.UI.Render(w, "source_group_edit", view); err != nil {
|
||||
@@ -253,6 +258,20 @@ func (s *Server) handleUISourceGroupSave(w stdhttp.ResponseWriter, r *stdhttp.Re
|
||||
return
|
||||
}
|
||||
|
||||
// Encrypt hook bodies (empty → empty stored, clearing the column).
|
||||
preEnc, err := s.EncryptHookForGroup(host.ID, "pre", form.PreHook)
|
||||
if err != nil {
|
||||
slog.Error("ui sources: encrypt pre_hook", "err", err)
|
||||
s.renderSourceFormError(w, r, u, host, gid, isNew, form, "Couldn't encrypt pre-hook — see the server log.")
|
||||
return
|
||||
}
|
||||
postEnc, err := s.EncryptHookForGroup(host.ID, "post", form.PostHook)
|
||||
if err != nil {
|
||||
slog.Error("ui sources: encrypt post_hook", "err", err)
|
||||
s.renderSourceFormError(w, r, u, host, gid, isNew, form, "Couldn't encrypt post-hook — see the server log.")
|
||||
return
|
||||
}
|
||||
|
||||
g := store.SourceGroup{
|
||||
ID: gid,
|
||||
HostID: host.ID,
|
||||
@@ -265,6 +284,8 @@ func (s *Server) handleUISourceGroupSave(w stdhttp.ResponseWriter, r *stdhttp.Re
|
||||
},
|
||||
RetryMax: form.RetryMax,
|
||||
RetryBackoffSeconds: form.RetryBackoffSeconds,
|
||||
PreHook: preEnc,
|
||||
PostHook: postEnc,
|
||||
}
|
||||
|
||||
if isNew {
|
||||
@@ -381,6 +402,8 @@ func parseSourceForm(v map[string][]string) sourceFormData {
|
||||
KeepYearly: get("keep_yearly"),
|
||||
RetryMax: rmax,
|
||||
RetryBackoffSeconds: rback,
|
||||
PreHook: firstVal(v, "pre_hook"),
|
||||
PostHook: firstVal(v, "post_hook"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -435,5 +458,7 @@ func formFromGroup(g store.SourceGroup) sourceFormData {
|
||||
RetryMax: g.RetryMax,
|
||||
RetryBackoffSeconds: g.RetryBackoffSeconds,
|
||||
ConflictDimension: g.ConflictDimension,
|
||||
// PreHook/PostHook are decrypted on render (handler-side, not
|
||||
// here) since formFromGroup has no AEAD reference.
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user