API and TUI Updates

This commit is contained in:
2026-02-04 17:10:09 +00:00
parent e902e5f320
commit 255fce5807
30 changed files with 3234 additions and 185 deletions

View File

@@ -1,15 +1,17 @@
package main
import (
"log"
"time"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
"clickploy/internal/api"
"clickploy/internal/builder"
"clickploy/internal/db"
"clickploy/internal/deployer"
"clickploy/internal/ports"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
"log"
"time"
)
func main() {
db.Init(".")
pm := ports.NewManager(2000, 60000)
@@ -36,6 +38,7 @@ func main() {
handler.RegisterSystemRoutes(r)
handler.RegisterStorageRoutes(r)
handler.RegisterAdminRoutes(r)
handler.RegisterFrontendRoutes(r)
log.Println("Starting Clickploy Backend on :8080")
if err := r.Run(":8080"); err != nil {
log.Fatalf("Server failed: %v", err)

View File

@@ -77,6 +77,29 @@ func (h *Handler) handleDeploy(c *gin.Context) {
func (h *Handler) RegisterSystemRoutes(r *gin.Engine) {
r.GET("/api/system/status", h.handleSystemStatus)
}
func (h *Handler) RegisterFrontendRoutes(r *gin.Engine) {
// Serve static files from the build directory
r.Static("/_app", "./dist/_app")
r.StaticFile("/robots.txt", "./dist/robots.txt")
// Serve index.html for root
r.GET("/", func(c *gin.Context) {
c.File("./dist/index.html")
})
// Handle SPA client-side routing: if no other route matches, serve index.html
// But ensure we don't serve HTML for API 404s
r.NoRoute(func(c *gin.Context) {
path := c.Request.URL.Path
if len(path) >= 4 && path[:4] == "/api" {
c.JSON(http.StatusNotFound, gin.H{"error": "API route not found"})
return
}
c.File("./dist/index.html")
})
}
func (h *Handler) handleSystemStatus(c *gin.Context) {
localIP := GetLocalIP()
publicIP := GetPublicIP()

View File

@@ -83,7 +83,7 @@ func (h *Handler) login(c *gin.Context) {
func (h *Handler) RegisterUserRoutes(r *gin.Engine) {
userGroup := r.Group("/api/user", AuthMiddleware())
{
userGroup.GET("/", h.getMe)
userGroup.GET("", h.getMe)
userGroup.PUT("/profile", h.updateProfile)
userGroup.PUT("/password", h.updatePassword)
userGroup.POST("/key", h.regenerateAPIKey)

View File

@@ -26,6 +26,7 @@ func (h *Handler) RegisterProjectRoutes(r *gin.Engine) {
protected.PUT("/projects/:id", h.updateProject)
protected.PUT("/projects/:id/env", h.updateProjectEnv)
protected.POST("/projects/:id/redeploy", h.redeployProject)
protected.POST("/projects/:id/stop", h.stopProject)
protected.GET("/activity", h.getActivity)
}
}
@@ -310,7 +311,9 @@ func (h *Handler) resolveEnvVars(ctx context.Context, userID string, envVars map
func (h *Handler) listProjects(c *gin.Context) {
userID, _ := c.Get("userID")
var projects []models.Project
if result := db.DB.Preload("Deployments").Where("owner_id = ?", userID).Find(&projects); result.Error != nil {
if result := db.DB.Preload("Deployments", func(db *gorm.DB) *gorm.DB {
return db.Order("deployments.created_at desc")
}).Where("owner_id = ?", userID).Find(&projects); result.Error != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch projects"})
return
}
@@ -328,3 +331,30 @@ func (h *Handler) getProject(c *gin.Context) {
}
c.JSON(http.StatusOK, project)
}
func (h *Handler) stopProject(c *gin.Context) {
userID, _ := c.Get("userID")
projectID := c.Param("id")
var project models.Project
if result := db.DB.Where("id = ? AND owner_id = ?", projectID, userID).First(&project); result.Error != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Project not found"})
return
}
// Stop the container using the project name
err := h.deployer.StopContainer(c.Request.Context(), project.Name)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to stop container: %v", err)})
return
}
// Update the latest deployment status to stopped
var deployment models.Deployment
if result := db.DB.Where("project_id = ?", project.ID).Order("created_at desc").First(&deployment); result.Error == nil {
deployment.Status = "stopped"
db.DB.Save(&deployment)
}
c.JSON(http.StatusOK, gin.H{"status": "stopped", "message": "Container stopped successfully"})
}

View File

@@ -229,13 +229,18 @@ func (h *Handler) handleGetDatabaseCredentials(c *gin.Context) {
return
}
var username, password string
fmt.Printf("Debug: Container env vars count: %d\n", len(envVars))
for _, env := range envVars {
if len(env) > 25 && env[:25] == "MONGO_INITDB_ROOT_USERNAME=" {
username = env[25:]
} else if len(env) > 25 && env[:25] == "MONGO_INITDB_ROOT_PASSWORD=" {
password = env[25:]
fmt.Printf("Debug: env var: %s\n", env)
if len(env) > 26 && env[:26] == "MONGO_INITDB_ROOT_USERNAME=" {
username = env[26:]
fmt.Printf("Debug: Found username: %s\n", username)
} else if len(env) > 26 && env[:26] == "MONGO_INITDB_ROOT_PASSWORD=" {
password = env[26:]
fmt.Printf("Debug: Found password: %s\n", password)
}
}
fmt.Printf("Debug: Final username=%s, password=%s\n", username, password)
uri := fmt.Sprintf("mongodb://%s:%s@localhost:%d/?authSource=admin", username, password, database.Port)
publicUri := fmt.Sprintf("mongodb://%s:%s@<HOST>:%d/?authSource=admin", username, password, database.Port)
c.JSON(http.StatusOK, gin.H{
@@ -273,10 +278,10 @@ func (h *Handler) handleUpdateDatabaseCredentials(c *gin.Context) {
var currentUser, currentPass string
var otherEnv []string
for _, env := range envVars {
if len(env) > 25 && env[:25] == "MONGO_INITDB_ROOT_USERNAME=" {
currentUser = env[25:]
} else if len(env) > 25 && env[:25] == "MONGO_INITDB_ROOT_PASSWORD=" {
currentPass = env[25:]
if len(env) > 26 && env[:26] == "MONGO_INITDB_ROOT_USERNAME=" {
currentUser = env[26:]
} else if len(env) > 26 && env[:26] == "MONGO_INITDB_ROOT_PASSWORD=" {
currentPass = env[26:]
} else {
otherEnv = append(otherEnv, env)
}