mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-22 01:13:00 +02:00
webui: only flag unsaved changes when running differs from startup
The "unsaved changes" banner was set unconditionally at every site that touches config, so it could appear when running-config actually equals startup — contradicting the "Show diff" modal, which correctly showed nothing. A shared updateCfgUnsaved helper now compares running against startup (the same datastore pair the diff uses) and sets or clears the banner cookie to match: - Apply and restore-to-running clear it when an Apply/restore reverts an out-of-band (e.g. CLI) change back to match startup, instead of always showing it. - The advanced-tree presence toggle writes only the candidate datastore, so it no longer sets the running-vs-startup banner at all (matching the other candidate-only tree handlers). Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
@@ -109,19 +109,49 @@ func applyError(w http.ResponseWriter, op string, err error) {
|
||||
http.Error(w, "Could not reach the device: "+err.Error(), http.StatusBadGateway)
|
||||
}
|
||||
|
||||
// Apply copies candidate → running, activating all staged changes atomically.
|
||||
// Sets the cfg-unsaved cookie so the persistent banner appears until startup is saved.
|
||||
// Apply copies candidate → running, activating all staged changes atomically,
|
||||
// then updates the unsaved-changes banner to match the running-vs-startup state.
|
||||
// POST /configure/apply
|
||||
func (h *ConfigureHandler) Apply(w http.ResponseWriter, r *http.Request) {
|
||||
if err := h.RC.CopyDatastore(r.Context(), "candidate", "running"); err != nil {
|
||||
applyError(w, "apply", err)
|
||||
return
|
||||
}
|
||||
setCfgUnsaved(w)
|
||||
updateCfgUnsaved(r.Context(), h.RC, w)
|
||||
w.Header().Set("HX-Refresh", "true")
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// configPair fetches the startup and running datastores — the shared basis for
|
||||
// the unsaved-state check (updateCfgUnsaved) and the config diff (configDiff).
|
||||
func configPair(ctx context.Context, rc restconf.Fetcher) (startup, running json.RawMessage, err error) {
|
||||
if startup, err = rc.GetDatastore(ctx, "startup"); err != nil {
|
||||
return nil, nil, fmt.Errorf("read startup-config: %w", err)
|
||||
}
|
||||
if running, err = rc.GetDatastore(ctx, "running"); err != nil {
|
||||
return nil, nil, fmt.Errorf("read running-config: %w", err)
|
||||
}
|
||||
return startup, running, nil
|
||||
}
|
||||
|
||||
// updateCfgUnsaved sets or clears the cfg-unsaved banner cookie to match the
|
||||
// actual state: set when running-config differs from startup, cleared when they
|
||||
// are byte-identical (an Apply or restore can revert an out-of-band change so
|
||||
// nothing is unsaved). Byte comparison is reliable given the shared serializer
|
||||
// (the basis the config diff uses); a read error fails open and shows the
|
||||
// banner. Call after any operation that writes running-config.
|
||||
func updateCfgUnsaved(ctx context.Context, rc restconf.Fetcher, w http.ResponseWriter) {
|
||||
startup, running, err := configPair(ctx, rc)
|
||||
if err != nil {
|
||||
log.Printf("configure: unsaved-state check: %v", err)
|
||||
}
|
||||
if err == nil && bytes.Equal(running, startup) {
|
||||
clearCfgUnsaved(w)
|
||||
} else {
|
||||
setCfgUnsaved(w)
|
||||
}
|
||||
}
|
||||
|
||||
// Abort copies running → candidate, discarding all staged changes.
|
||||
// POST /configure/abort
|
||||
func (h *ConfigureHandler) Abort(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -188,13 +218,9 @@ func (h *ConfigureHandler) ConfigDiff(w http.ResponseWriter, r *http.Request) {
|
||||
// configDiff fetches startup and running config, writes each to a temp file,
|
||||
// and returns the `diff -u` output (empty when the two are identical).
|
||||
func (h *ConfigureHandler) configDiff(ctx context.Context) (string, error) {
|
||||
startup, err := h.RC.GetDatastore(ctx, "startup")
|
||||
startup, running, err := configPair(ctx, h.RC)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("read startup-config: %w", err)
|
||||
}
|
||||
running, err := h.RC.GetDatastore(ctx, "running")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("read running-config: %w", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
startupFile, err := writeTempConfig("cfgdiff-startup-*.json", startup)
|
||||
|
||||
@@ -395,7 +395,9 @@ func (h *SystemHandler) RestoreConfig(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if target == "running" {
|
||||
setCfgUnsaved(w)
|
||||
// Restoring the same config that's already in startup leaves nothing
|
||||
// unsaved, so reflect the actual running-vs-startup state.
|
||||
updateCfgUnsaved(r.Context(), h.RC, w)
|
||||
w.Header().Set("HX-Refresh", "true")
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return
|
||||
|
||||
@@ -1728,7 +1728,9 @@ func (h *TreeHandler) TogglePresence(w http.ResponseWriter, r *http.Request) {
|
||||
exists = true
|
||||
}
|
||||
|
||||
setCfgUnsaved(w)
|
||||
// No setCfgUnsaved here — this only modifies candidate. The cfg-unsaved
|
||||
// banner is specifically about "running differs from startup," set after
|
||||
// Apply / RestoreConfig-to-running (matching the other tree-edit handlers).
|
||||
|
||||
// Re-render the right-pane leaf group with the updated presence state.
|
||||
gd := h.buildLeafGroup(r, mgr, path, node.Name, "container")
|
||||
|
||||
Reference in New Issue
Block a user