Adds app endpoints for configuration and status.
This commit is contained in:
@@ -4,11 +4,16 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/wild-cloud/wild-central/daemon/internal/apps"
|
||||
"github.com/wild-cloud/wild-central/daemon/internal/operations"
|
||||
"github.com/wild-cloud/wild-central/daemon/internal/tools"
|
||||
)
|
||||
|
||||
// AppsListAvailable lists all available apps
|
||||
@@ -186,3 +191,190 @@ func (api *API) AppsGetStatus(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
respondJSON(w, http.StatusOK, status)
|
||||
}
|
||||
|
||||
// AppsGetEnhanced returns enhanced app details with runtime status
|
||||
func (api *API) AppsGetEnhanced(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
instanceName := vars["name"]
|
||||
appName := vars["app"]
|
||||
|
||||
// Validate instance exists
|
||||
if err := api.instance.ValidateInstance(instanceName); err != nil {
|
||||
respondError(w, http.StatusNotFound, fmt.Sprintf("Instance not found: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Get enhanced app details
|
||||
appsMgr := apps.NewManager(api.dataDir, api.appsDir)
|
||||
enhanced, err := appsMgr.GetEnhanced(instanceName, appName)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to get app details: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
respondJSON(w, http.StatusOK, enhanced)
|
||||
}
|
||||
|
||||
// AppsGetEnhancedStatus returns just runtime status for an app
|
||||
func (api *API) AppsGetEnhancedStatus(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
instanceName := vars["name"]
|
||||
appName := vars["app"]
|
||||
|
||||
// Validate instance exists
|
||||
if err := api.instance.ValidateInstance(instanceName); err != nil {
|
||||
respondError(w, http.StatusNotFound, fmt.Sprintf("Instance not found: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Get runtime status
|
||||
appsMgr := apps.NewManager(api.dataDir, api.appsDir)
|
||||
status, err := appsMgr.GetEnhancedStatus(instanceName, appName)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusNotFound, fmt.Sprintf("Failed to get runtime status: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
respondJSON(w, http.StatusOK, status)
|
||||
}
|
||||
|
||||
// AppsGetLogs returns logs for an app (from first pod)
|
||||
func (api *API) AppsGetLogs(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
instanceName := vars["name"]
|
||||
appName := vars["app"]
|
||||
|
||||
// Validate instance exists
|
||||
if err := api.instance.ValidateInstance(instanceName); err != nil {
|
||||
respondError(w, http.StatusNotFound, fmt.Sprintf("Instance not found: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Parse query parameters
|
||||
tailStr := r.URL.Query().Get("tail")
|
||||
sinceSecondsStr := r.URL.Query().Get("sinceSeconds")
|
||||
podName := r.URL.Query().Get("pod")
|
||||
|
||||
tail := 100 // default
|
||||
if tailStr != "" {
|
||||
if t, err := strconv.Atoi(tailStr); err == nil && t > 0 {
|
||||
tail = t
|
||||
}
|
||||
}
|
||||
|
||||
sinceSeconds := 0
|
||||
if sinceSecondsStr != "" {
|
||||
if s, err := strconv.Atoi(sinceSecondsStr); err == nil && s > 0 {
|
||||
sinceSeconds = s
|
||||
}
|
||||
}
|
||||
|
||||
// Get logs
|
||||
kubeconfigPath := api.dataDir + "/instances/" + instanceName + "/kubeconfig"
|
||||
kubectl := tools.NewKubectl(kubeconfigPath)
|
||||
|
||||
// If no pod specified, get the first pod
|
||||
if podName == "" {
|
||||
pods, err := kubectl.GetPods(appName, true)
|
||||
if err != nil || len(pods) == 0 {
|
||||
respondError(w, http.StatusNotFound, "No pods found for app")
|
||||
return
|
||||
}
|
||||
podName = pods[0].Name
|
||||
}
|
||||
|
||||
logOpts := tools.LogOptions{
|
||||
Tail: tail,
|
||||
SinceSeconds: sinceSeconds,
|
||||
}
|
||||
logs, err := kubectl.GetLogs(appName, podName, logOpts)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to get logs: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
respondJSON(w, http.StatusOK, map[string]interface{}{
|
||||
"pod": podName,
|
||||
"logs": logs,
|
||||
})
|
||||
}
|
||||
|
||||
// AppsGetEvents returns kubernetes events for an app
|
||||
func (api *API) AppsGetEvents(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
instanceName := vars["name"]
|
||||
appName := vars["app"]
|
||||
|
||||
// Validate instance exists
|
||||
if err := api.instance.ValidateInstance(instanceName); err != nil {
|
||||
respondError(w, http.StatusNotFound, fmt.Sprintf("Instance not found: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Parse query parameters
|
||||
limitStr := r.URL.Query().Get("limit")
|
||||
limit := 20 // default
|
||||
if limitStr != "" {
|
||||
if l, err := strconv.Atoi(limitStr); err == nil && l > 0 {
|
||||
limit = l
|
||||
}
|
||||
}
|
||||
|
||||
// Get events
|
||||
kubeconfigPath := api.dataDir + "/instances/" + instanceName + "/kubeconfig"
|
||||
kubectl := tools.NewKubectl(kubeconfigPath)
|
||||
|
||||
events, err := kubectl.GetRecentEvents(appName, limit)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to get events: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
respondJSON(w, http.StatusOK, map[string]interface{}{
|
||||
"events": events,
|
||||
})
|
||||
}
|
||||
|
||||
// AppsGetReadme returns the README.md content for an app
|
||||
func (api *API) AppsGetReadme(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
instanceName := vars["name"]
|
||||
appName := vars["app"]
|
||||
|
||||
// Validate instance exists
|
||||
if err := api.instance.ValidateInstance(instanceName); err != nil {
|
||||
respondError(w, http.StatusNotFound, fmt.Sprintf("Instance not found: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Validate app name to prevent path traversal
|
||||
if appName == "" || appName == "." || appName == ".." ||
|
||||
strings.Contains(appName, "/") || strings.Contains(appName, "\\") {
|
||||
respondError(w, http.StatusBadRequest, "Invalid app name")
|
||||
return
|
||||
}
|
||||
|
||||
// Try instance-specific README first
|
||||
instancePath := filepath.Join(api.dataDir, "instances", instanceName, "apps", appName, "README.md")
|
||||
content, err := os.ReadFile(instancePath)
|
||||
if err == nil {
|
||||
w.Header().Set("Content-Type", "text/markdown; charset=utf-8")
|
||||
w.Write(content)
|
||||
return
|
||||
}
|
||||
|
||||
// Fall back to global directory
|
||||
globalPath := filepath.Join(api.appsDir, appName, "README.md")
|
||||
content, err = os.ReadFile(globalPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
respondError(w, http.StatusNotFound, fmt.Sprintf("README not found for app '%s' in instance '%s'", appName, instanceName))
|
||||
} else {
|
||||
respondError(w, http.StatusInternalServerError, "Failed to read README file")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/markdown; charset=utf-8")
|
||||
w.Write(content)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user