controller v0.92.0: NAS network storage Part A2 (registry + UI + per-share health)
Controller-side of NAS network storage, proxying to agent A1 /netstorage/*. Distinct 'network' storage kind (no drive lifecycle); add/list/remove + per-share health UI; unreachable NAS is a recoverable warning, never the drive missing/stop cascade; SMB creds pass through to the agent, never persisted. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HxLA1mZurFq9kt8hneFeCs
This commit is contained in:
@@ -217,6 +217,19 @@ func (s *Server) registerStoragePath(where, label string, setDefault bool) error
|
||||
return nil
|
||||
}
|
||||
|
||||
// refuseNetworkLifecycle blocks a drive-lifecycle action (eject/decommission/migrate/wipe) on a NAS
|
||||
// network-storage path. A network share is a DISTINCT class with no device lifecycle — its only
|
||||
// lifecycle action is /api/netstorage/remove. Returns true (and writes the refusal) when the path is a
|
||||
// network path; the caller must then return. This is the server-side Kind-gate that backs the UI gating.
|
||||
func (s *Server) refuseNetworkLifecycle(w http.ResponseWriter, where string) bool {
|
||||
if s.settings != nil && s.settings.IsNetworkStoragePath(where) {
|
||||
writeDiskJSON(w, http.StatusBadRequest, false,
|
||||
"ez hálózati tárhely (NAS) — a meghajtó-műveletek (leválasztás/leszerelés/áthelyezés/törlés) nem alkalmazhatók rá; használd az „Eltávolítás” gombot", nil)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ---- HTTP handlers (behind RequireAuth + CsrfProtect) -----------------------------------------
|
||||
|
||||
// storageWizardPageHandler renders the init/attach wizard page (the disk list + actions are driven
|
||||
@@ -253,6 +266,13 @@ func (s *Server) ServeStorageAPI(w http.ResponseWriter, r *http.Request) {
|
||||
s.handleStorageMigrateStatus(w, r)
|
||||
case r.URL.Path == "/api/storage/decommission" && r.Method == http.MethodPost:
|
||||
s.handleStorageDecommission(w, r)
|
||||
// NAS network storage (Part A2) — distinct from the drive lifecycle above (proxy to agent /netstorage/*).
|
||||
case r.URL.Path == "/api/storage/netstorage/add" && r.Method == http.MethodPost:
|
||||
s.handleNetStorageAdd(w, r)
|
||||
case r.URL.Path == "/api/storage/netstorage" && r.Method == http.MethodGet:
|
||||
s.handleNetStorageList(w, r)
|
||||
case r.URL.Path == "/api/storage/netstorage/remove" && r.Method == http.MethodPost:
|
||||
s.handleNetStorageRemove(w, r)
|
||||
case r.URL.Path == "/api/storage/disconnect" && r.Method == http.MethodPost:
|
||||
s.handleStorageDisconnect(w, r)
|
||||
case r.URL.Path == "/api/storage/reconnect" && r.Method == http.MethodPost:
|
||||
@@ -276,6 +296,9 @@ func (s *Server) handleStorageMigrate(w http.ResponseWriter, r *http.Request) {
|
||||
writeDiskJSON(w, http.StatusBadRequest, false, "érvénytelen kérés", nil)
|
||||
return
|
||||
}
|
||||
if s.refuseNetworkLifecycle(w, strings.TrimSpace(req.Source)) || s.refuseNetworkLifecycle(w, strings.TrimSpace(req.Target)) {
|
||||
return
|
||||
}
|
||||
id, err := s.stackMgr.MigrateAll(r.Context(), strings.TrimSpace(req.Source), strings.TrimSpace(req.Target))
|
||||
if err != nil {
|
||||
writeDiskJSON(w, http.StatusConflict, false, err.Error(), nil)
|
||||
@@ -328,6 +351,9 @@ func (s *Server) handleStorageDecommission(w http.ResponseWriter, r *http.Reques
|
||||
writeDiskJSON(w, http.StatusBadRequest, false, "érvénytelen csatlakoztatási pont", nil)
|
||||
return
|
||||
}
|
||||
if s.refuseNetworkLifecycle(w, req.Where) {
|
||||
return
|
||||
}
|
||||
|
||||
switch req.Mode {
|
||||
case "migrate":
|
||||
@@ -554,6 +580,9 @@ func (s *Server) handleStorageWipe(w http.ResponseWriter, r *http.Request) {
|
||||
writeDiskJSON(w, http.StatusBadRequest, false, "érvénytelen csatlakoztatási pont", nil)
|
||||
return
|
||||
}
|
||||
if s.refuseNetworkLifecycle(w, req.Where) {
|
||||
return
|
||||
}
|
||||
// Server-side type-to-confirm: the typed name must match the mount's basename exactly.
|
||||
if strings.TrimSpace(req.MountName) != path.Base(req.Where) {
|
||||
writeDiskJSON(w, http.StatusBadRequest, false, "a beírt név nem egyezik a csatlakoztatási névvel", nil)
|
||||
@@ -716,6 +745,9 @@ func (s *Server) handleStorageEject(w http.ResponseWriter, r *http.Request) {
|
||||
writeDiskJSON(w, http.StatusBadRequest, false, "érvénytelen csatlakoztatási pont", nil)
|
||||
return
|
||||
}
|
||||
if s.refuseNetworkLifecycle(w, req.Where) {
|
||||
return
|
||||
}
|
||||
agent, err := s.agentClient()
|
||||
if err != nil {
|
||||
writeDiskJSON(w, http.StatusServiceUnavailable, false, err.Error(), nil)
|
||||
|
||||
Reference in New Issue
Block a user