feat: orphan stack detection/deletion, filebrowser infra, setup scripts

- Add orphan detection: stacks not in catalog marked as "Elavult"
- Add DELETE /api/stacks/{name} endpoint with HDD data handling
- Add GET /api/stacks/{name}/hdd-data endpoint
- Add delete confirmation modal with HDD data checkbox (Hungarian UI)
- Add filebrowser to protected stacks list
- Add scripts/hdd-setup.sh and scripts/docker-setup.sh for node setup
- Hide "Frissítés" and "Részletek" buttons for orphaned stacks

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-15 10:03:10 +01:00
parent a5c6899e2c
commit 59ed4bd1c2
7 changed files with 3367 additions and 3 deletions
+42
View File
@@ -29,6 +29,7 @@ const (
StatePaused ContainerState = "paused"
StateUnknown ContainerState = "unknown"
StateNotDeployed ContainerState = "not_deployed"
StateOrphaned ContainerState = "orphaned"
)
// ContainerInfo holds status info about a single container within a stack.
@@ -47,6 +48,7 @@ type Stack struct {
State ContainerState `json:"state"`
Deployed bool `json:"deployed"` // Has app.yaml with deployed=true
Protected bool `json:"protected"`
Orphaned bool `json:"orphaned"` // Deployed but no catalog template
Containers []ContainerInfo `json:"containers"`
AppConfig *AppConfig `json:"app_config,omitempty"`
LastUpdated time.Time `json:"last_updated"`
@@ -166,6 +168,25 @@ func (m *Manager) ScanStacks() error {
}
}
// Detect orphaned stacks (deployed but no longer in catalog)
catalogTemplates := m.getCatalogTemplateSlugs()
if catalogTemplates != nil {
orphanCount := 0
for _, stack := range m.stacks {
if stack.Protected || !stack.Deployed {
stack.Orphaned = false
continue
}
stack.Orphaned = !catalogTemplates[stack.Name]
if stack.Orphaned {
orphanCount++
}
}
if orphanCount > 0 {
m.logger.Printf("[INFO] Detected %d orphaned stack(s)", orphanCount)
}
}
deployedCount := 0
for _, s := range m.stacks {
if s.Deployed {
@@ -733,4 +754,25 @@ func (m *Manager) CommittedMemory() (requestMB int, limitMB int) {
limitMB += ParseMemoryMB(s.Meta.Resources.MemLimit)
}
return
}
// getCatalogTemplateSlugs reads the synced catalog cache and returns a set of
// template slugs (directory names) that have a docker-compose.yml.
func (m *Manager) getCatalogTemplateSlugs() map[string]bool {
cacheDir := filepath.Join(m.cfg.Paths.DataDir, "catalog-cache", "templates")
entries, err := os.ReadDir(cacheDir)
if err != nil {
m.logger.Printf("[WARN] Cannot read catalog cache for orphan detection: %v", err)
return nil
}
slugs := make(map[string]bool, len(entries))
for _, e := range entries {
if e.IsDir() {
composePath := filepath.Join(cacheDir, e.Name(), "docker-compose.yml")
if _, err := os.Stat(composePath); err == nil {
slugs[e.Name()] = true
}
}
}
return slugs
}