Add app detail/info pages with optional config support

- New route: GET /apps/{slug} renders info page with use cases, setup guide, prerequisites
- New API: POST /api/stacks/{name}/optional-config for updating optional env vars
- New structs: AppInfo, OptionalConfigGroup, OptionalConfigField in metadata.go
- UpdateOptionalConfig saves to app.yaml and restarts deployed stacks with new env vars
- Info page template with hero section, screenshots, info cards, optional config form
- Navigation: stack cards now link to /apps/{slug}, deploy page has "Részletek" link

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-14 20:12:18 +01:00
parent 528f64cab7
commit 6080170367
5 changed files with 544 additions and 19 deletions
+37 -6
View File
@@ -165,6 +165,16 @@ func (s *Server) loadTemplates() {
}
return r
},
"screenshotURL": func(slug string, index int) string {
return s.cfg.AppScreenshotURL(slug, index)
},
"seq": func(n int) []int {
result := make([]int, n)
for i := range result {
result[i] = i + 1
}
return result
},
}
s.tmpl = template.Must(template.New("").Funcs(funcMap).Parse(allTemplates))
@@ -475,16 +485,37 @@ func (s *Server) serveAsset(w http.ResponseWriter, r *http.Request, filename str
http.ServeFile(w, r, path)
}
func (s *Server) appDetailHandler(w http.ResponseWriter, r *http.Request, slug string) {
func (s *Server) appDetailHandler(w http.ResponseWriter, _ *http.Request, slug string) {
var found *stacks.Stack
for _, stack := range s.stackMgr.GetStacks() {
if stack.Meta.Slug == slug {
// Always redirect to deploy page — it has a read-only mode for
// deployed apps showing current settings, plus the external link
http.Redirect(w, r, "/stacks/"+stack.Name+"/deploy", http.StatusFound)
return
found = &stack
break
}
}
http.NotFound(w, r)
if found == nil {
http.NotFound(w, nil)
return
}
// Load current optional config values from app.yaml
currentValues := make(map[string]string)
if appCfg := s.stackMgr.LoadAppConfigByName(found.Name); appCfg != nil {
for k, v := range appCfg.Env {
currentValues[k] = v
}
}
data := s.baseData("stacks", found.Meta.DisplayName)
data["Stack"] = found
data["Meta"] = found.Meta
data["AppInfo"] = found.Meta.AppInfo
data["OptionalConfig"] = found.Meta.OptionalConfig
data["CurrentValues"] = currentValues
data["HasAppInfo"] = found.Meta.HasAppInfo()
data["HasOptionalConfig"] = found.Meta.HasOptionalConfig()
s.render(w, "app_info", data)
}
func (s *Server) renderLogin(w http.ResponseWriter, errorMsg string) {