R-280: attach list from mounted-but-unregistered filesystems; two-clicks promise made conditional
gates / gates (push) Successful in 17s
gates / gates (push) Successful in 17s
After a reinstall the data drive could not be re-attached through any dashboard route: both candidate lists came from the agent's unclaimed-disk scan, and the rebuilt box's drives are claimed. The restore page said it was two clicks while pointing at an empty picker. The attach list now also carries the controller's own mounted-but-unregistered filesystems. initialize is untouched, so the format wizard's system/backup protection is unchanged. The 'two clicks' sentence is conditional on the picker being non-empty, and says something true and actionable when it is not.
This commit is contained in:
@@ -373,6 +373,8 @@ func (s *Server) ServeStorageAPI(w http.ResponseWriter, r *http.Request) {
|
||||
s.handleStorageImpact(w, r)
|
||||
case r.URL.Path == "/api/storage/register" && r.Method == http.MethodPost:
|
||||
s.handleStorageRegister(w, r)
|
||||
case r.URL.Path == "/api/storage/register-mounted" && r.Method == http.MethodPost:
|
||||
s.handleStorageRegisterMounted(w, r)
|
||||
// E-2 Parts 3+4. State drives the degraded banner and the OFFER; assign is the offer's
|
||||
// ACCEPTANCE and the ONLY writer of the role — registration above deliberately does not set it.
|
||||
case r.URL.Path == "/api/storage/backup-target" && r.Method == http.MethodGet:
|
||||
@@ -865,6 +867,53 @@ func (s *Server) handleStorageRegister(w http.ResponseWriter, r *http.Request) {
|
||||
writeDiskJSON(w, http.StatusOK, true, "", map[string]any{"registered": true, "where": stable, "raw": req.Where})
|
||||
}
|
||||
|
||||
// handleStorageRegisterMounted registers an ALREADY-mounted, unregistered filesystem verbatim
|
||||
// (R-280). It is the action behind an `already_mounted` attach candidate, and it is the route the
|
||||
// rebuilt demo-hp needed: the escape hatch that unblocked that box registered `/mnt/sys_drive`, an
|
||||
// in-guest path, and nothing in the dashboard offered it.
|
||||
//
|
||||
// It differs from handleStorageRegister deliberately: that one takes the agent's RAW /mnt/<name> host
|
||||
// mount and registers the STABLE /mnt/felhom-drives/<name> the intermediary model binds it to. These
|
||||
// candidates are not agent drives and have no stable twin — the mountpoint IS the path to register,
|
||||
// so translating it would register a directory that does not exist.
|
||||
//
|
||||
// The posted path is NOT trusted: it is matched against the freshly re-derived set of mounted,
|
||||
// unregistered filesystems. A path that is not currently offered is refused, so this cannot be used
|
||||
// to register an arbitrary directory.
|
||||
func (s *Server) handleStorageRegisterMounted(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
Path string `json:"path"`
|
||||
Label string `json:"label"`
|
||||
SetDefault bool `json:"set_default"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeDiskJSON(w, http.StatusBadRequest, false, "érvénytelen kérés", nil)
|
||||
return
|
||||
}
|
||||
want := path.Clean(strings.TrimSpace(req.Path))
|
||||
var match *mountedStore
|
||||
for _, m := range s.attachableStores() {
|
||||
if m.Path == want {
|
||||
found := m
|
||||
match = &found
|
||||
break
|
||||
}
|
||||
}
|
||||
if match == nil {
|
||||
// Names a reason the customer can act on, and a route — never a bare refusal.
|
||||
writeDiskJSON(w, http.StatusBadRequest, false,
|
||||
"Ez a meghajtó most nem csatolható — lehet, hogy már regisztrálva van, vagy időközben lecsatolódott. Frissítsd az oldalt, és nézd meg a Tárhely → Meghajtók listát.", nil)
|
||||
return
|
||||
}
|
||||
if err := s.registerStoragePath(match.Path, req.Label, req.SetDefault); err != nil {
|
||||
s.logger.Printf("[WARN] [web] mounted-store register %s failed: %v", match.Path, err)
|
||||
writeDiskJSON(w, http.StatusBadGateway, false, err.Error(), nil)
|
||||
return
|
||||
}
|
||||
s.logger.Printf("[INFO] [web] storage path registered (already-mounted store): %s", match.Path)
|
||||
writeDiskJSON(w, http.StatusOK, true, "", map[string]any{"registered": true, "where": match.Path})
|
||||
}
|
||||
|
||||
func (s *Server) handleStorageAttach(w http.ResponseWriter, r *http.Request) {
|
||||
var req storageProvReq
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user