feat: deployed app removal + missing field injection (v0.19.0)
Add "Eltávolítás" to remove deployed (non-orphaned) stacks — reverts them to "Nincs telepítve" while preserving templates for redeploy. Modal offers HDD data and backup data cleanup choices. Auto-inject missing deploy fields (secrets, domains) into existing app.yaml when templates are updated via sync or on controller startup. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -111,6 +111,14 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
case hasSuffix(path, "/hdd-data") && req.Method == http.MethodGet:
|
||||
r.getStackHDDData(w, req, extractName(path, "/hdd-data"))
|
||||
|
||||
// GET /api/stacks/{name}/backup-data
|
||||
case hasSuffix(path, "/backup-data") && req.Method == http.MethodGet:
|
||||
r.getStackBackupData(w, req, extractName(path, "/backup-data"))
|
||||
|
||||
// POST /api/stacks/{name}/remove — remove a deployed (non-orphaned) stack
|
||||
case hasSuffix(path, "/remove") && req.Method == http.MethodPost:
|
||||
r.removeStack(w, req, extractName(path, "/remove"))
|
||||
|
||||
// DELETE /api/stacks/{name}
|
||||
case strings.HasPrefix(path, "/stacks/") && req.Method == http.MethodDelete && !hasSubpath(path, "/stacks/"):
|
||||
r.deleteStack(w, req, trimSegment(path, "/stacks/"))
|
||||
@@ -344,6 +352,82 @@ func (r *Router) getStackHDDData(w http.ResponseWriter, _ *http.Request, name st
|
||||
writeJSON(w, http.StatusOK, apiResponse{OK: true, Data: resp})
|
||||
}
|
||||
|
||||
func (r *Router) getStackBackupData(w http.ResponseWriter, _ *http.Request, name string) {
|
||||
if name == "" {
|
||||
writeJSON(w, http.StatusBadRequest, apiResponse{OK: false, Error: "invalid stack name"})
|
||||
return
|
||||
}
|
||||
|
||||
// Compute the drive path for this stack (HDD or system data path)
|
||||
var drivePath string
|
||||
if r.crossDriveRunner != nil {
|
||||
drivePath = r.crossDriveRunner.GetAppDrivePath(name)
|
||||
}
|
||||
|
||||
resp, err := r.stackMgr.GetStackBackupData(name, drivePath)
|
||||
if err != nil {
|
||||
writeJSON(w, http.StatusNotFound, apiResponse{OK: false, Error: err.Error()})
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, apiResponse{OK: true, Data: resp})
|
||||
}
|
||||
|
||||
func (r *Router) removeStack(w http.ResponseWriter, req *http.Request, name string) {
|
||||
if name == "" {
|
||||
writeJSON(w, http.StatusBadRequest, apiResponse{OK: false, Error: "invalid stack name"})
|
||||
return
|
||||
}
|
||||
limitBody(w, req)
|
||||
r.logger.Printf("[API] Remove requested for stack: %s", name)
|
||||
|
||||
var body struct {
|
||||
RemoveHDDData bool `json:"remove_hdd_data"`
|
||||
RemoveBackups bool `json:"remove_backups"`
|
||||
}
|
||||
if err := json.NewDecoder(req.Body).Decode(&body); err != nil {
|
||||
body.RemoveHDDData = false
|
||||
body.RemoveBackups = false
|
||||
}
|
||||
|
||||
// Compute backup paths to remove if requested
|
||||
var backupPaths []string
|
||||
if body.RemoveBackups && r.crossDriveRunner != nil {
|
||||
drivePath := r.crossDriveRunner.GetAppDrivePath(name)
|
||||
if drivePath != "" {
|
||||
backupPaths = append(backupPaths,
|
||||
backup.AppDBDumpPath(drivePath, name),
|
||||
backup.AppSecondaryRsyncPath(drivePath, name),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := r.stackMgr.RemoveStack(name, body.RemoveHDDData, backupPaths)
|
||||
if err != nil {
|
||||
r.logger.Printf("[API] Remove failed for %s: %v", name, err)
|
||||
status := http.StatusInternalServerError
|
||||
if strings.Contains(err.Error(), "protected") {
|
||||
status = http.StatusForbidden
|
||||
}
|
||||
if strings.Contains(err.Error(), "not found") {
|
||||
status = http.StatusNotFound
|
||||
}
|
||||
if strings.Contains(err.Error(), "not deployed") || strings.Contains(err.Error(), "still running") {
|
||||
status = http.StatusConflict
|
||||
}
|
||||
writeJSON(w, status, apiResponse{OK: false, Error: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Clean up cross-drive backup config for this stack
|
||||
if r.sett != nil {
|
||||
if err := r.sett.SetCrossDriveConfig(name, nil); err != nil {
|
||||
r.logger.Printf("[WARN] Failed to clean cross-drive config for %s: %v", name, err)
|
||||
}
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, apiResponse{OK: true, Data: resp, Message: "Stack " + name + " removed"})
|
||||
}
|
||||
|
||||
func (r *Router) deleteStack(w http.ResponseWriter, req *http.Request, name string) {
|
||||
limitBody(w, req)
|
||||
r.logger.Printf("[API] Delete requested for stack: %s", name)
|
||||
|
||||
Reference in New Issue
Block a user