Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions api/cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ func main() {
batchHandler := handlers.NewBatchHandler(database)
monitoringHandler := handlers.NewMonitoringHandler(database)
quotasHandler := handlers.NewQuotasHandler(database)
nodeHandler := handlers.NewNodeHandler(database, k8sClient)
// NOTE: WebSocket routes now use wsManager directly (see ws.GET routes below)
consoleHandler := handlers.NewConsoleHandler(database)
collaborationHandler := handlers.NewCollaborationHandler(database)
Expand All @@ -273,7 +274,7 @@ func main() {
}

// Setup routes
setupRoutes(router, apiHandler, userHandler, groupHandler, authHandler, activityHandler, catalogHandler, sharingHandler, pluginHandler, dashboardHandler, sessionActivityHandler, apiKeyHandler, teamHandler, preferencesHandler, notificationsHandler, searchHandler, sessionTemplatesHandler, batchHandler, monitoringHandler, quotasHandler, wsManager, consoleHandler, collaborationHandler, integrationsHandler, loadBalancingHandler, schedulingHandler, securityHandler, templateVersioningHandler, setupHandler, jwtManager, userDB, redisCache, webhookSecret)
setupRoutes(router, apiHandler, userHandler, groupHandler, authHandler, activityHandler, catalogHandler, sharingHandler, pluginHandler, dashboardHandler, sessionActivityHandler, apiKeyHandler, teamHandler, preferencesHandler, notificationsHandler, searchHandler, sessionTemplatesHandler, batchHandler, monitoringHandler, quotasHandler, nodeHandler, wsManager, consoleHandler, collaborationHandler, integrationsHandler, loadBalancingHandler, schedulingHandler, securityHandler, templateVersioningHandler, setupHandler, jwtManager, userDB, redisCache, webhookSecret)

// Create HTTP server with security timeouts
srv := &http.Server{
Expand Down Expand Up @@ -354,7 +355,7 @@ func main() {
log.Println("Graceful shutdown completed")
}

func setupRoutes(router *gin.Engine, h *api.Handler, userHandler *handlers.UserHandler, groupHandler *handlers.GroupHandler, authHandler *auth.AuthHandler, activityHandler *handlers.ActivityHandler, catalogHandler *handlers.CatalogHandler, sharingHandler *handlers.SharingHandler, pluginHandler *handlers.PluginHandler, dashboardHandler *handlers.DashboardHandler, sessionActivityHandler *handlers.SessionActivityHandler, apiKeyHandler *handlers.APIKeyHandler, teamHandler *handlers.TeamHandler, preferencesHandler *handlers.PreferencesHandler, notificationsHandler *handlers.NotificationsHandler, searchHandler *handlers.SearchHandler, sessionTemplatesHandler *handlers.SessionTemplatesHandler, batchHandler *handlers.BatchHandler, monitoringHandler *handlers.MonitoringHandler, quotasHandler *handlers.QuotasHandler, wsManager *internalWebsocket.Manager, consoleHandler *handlers.ConsoleHandler, collaborationHandler *handlers.CollaborationHandler, integrationsHandler *handlers.IntegrationsHandler, loadBalancingHandler *handlers.LoadBalancingHandler, schedulingHandler *handlers.SchedulingHandler, securityHandler *handlers.SecurityHandler, templateVersioningHandler *handlers.TemplateVersioningHandler, setupHandler *handlers.SetupHandler, jwtManager *auth.JWTManager, userDB *db.UserDB, redisCache *cache.Cache, webhookSecret string) {
func setupRoutes(router *gin.Engine, h *api.Handler, userHandler *handlers.UserHandler, groupHandler *handlers.GroupHandler, authHandler *auth.AuthHandler, activityHandler *handlers.ActivityHandler, catalogHandler *handlers.CatalogHandler, sharingHandler *handlers.SharingHandler, pluginHandler *handlers.PluginHandler, dashboardHandler *handlers.DashboardHandler, sessionActivityHandler *handlers.SessionActivityHandler, apiKeyHandler *handlers.APIKeyHandler, teamHandler *handlers.TeamHandler, preferencesHandler *handlers.PreferencesHandler, notificationsHandler *handlers.NotificationsHandler, searchHandler *handlers.SearchHandler, sessionTemplatesHandler *handlers.SessionTemplatesHandler, batchHandler *handlers.BatchHandler, monitoringHandler *handlers.MonitoringHandler, quotasHandler *handlers.QuotasHandler, nodeHandler *handlers.NodeHandler, wsManager *internalWebsocket.Manager, consoleHandler *handlers.ConsoleHandler, collaborationHandler *handlers.CollaborationHandler, integrationsHandler *handlers.IntegrationsHandler, loadBalancingHandler *handlers.LoadBalancingHandler, schedulingHandler *handlers.SchedulingHandler, securityHandler *handlers.SecurityHandler, templateVersioningHandler *handlers.TemplateVersioningHandler, setupHandler *handlers.SetupHandler, jwtManager *auth.JWTManager, userDB *db.UserDB, redisCache *cache.Cache, webhookSecret string) {
// SECURITY: Create authentication middleware
authMiddleware := auth.Middleware(jwtManager, userDB)
adminMiddleware := auth.RequireRole("admin")
Expand Down Expand Up @@ -783,6 +784,22 @@ func setupRoutes(router *gin.Engine, h *api.Handler, userHandler *handlers.UserH
// Resource quotas and limits enforcement - using dedicated handler (operators/admins only)
quotasHandler.RegisterRoutes(protected.Group("", operatorMiddleware))

// Node Management (admin only)
admin := protected.Group("/admin")
admin.Use(adminMiddleware)
{
admin.GET("/nodes", nodeHandler.ListNodes)
admin.GET("/nodes/stats", nodeHandler.GetClusterStats)
admin.GET("/nodes/:name", nodeHandler.GetNode)
admin.PUT("/nodes/:name/labels", nodeHandler.AddNodeLabel)
admin.DELETE("/nodes/:name/labels/:key", nodeHandler.RemoveNodeLabel)
admin.POST("/nodes/:name/taints", nodeHandler.AddNodeTaint)
admin.DELETE("/nodes/:name/taints/:key", nodeHandler.RemoveNodeTaint)
admin.POST("/nodes/:name/cordon", nodeHandler.CordonNode)
admin.POST("/nodes/:name/uncordon", nodeHandler.UncordonNode)
admin.POST("/nodes/:name/drain", nodeHandler.DrainNode)
}

// NOTE: Billing is now handled by the streamspace-billing plugin
// Install it via: Admin → Plugins → streamspace-billing

Expand Down
159 changes: 151 additions & 8 deletions api/internal/api/stubs.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import (
"bufio"
"context"
"fmt"
"io"
"log"
"net/http"
Expand Down Expand Up @@ -64,7 +65,7 @@
// Health returns health status
func (h *Handler) Health(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"status": "healthy",
"status": "healthy",
"service": "streamspace-api",
})
}
Expand All @@ -73,8 +74,8 @@
func (h *Handler) Version(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"version": "v0.1.0",
"api": "v1",
"phase": "2.2",
"api": "v1",
"phase": "2.2",
})
}

Expand Down Expand Up @@ -373,7 +374,7 @@

// DeleteResource deletes a K8s resource
func (h *Handler) DeleteResource(c *gin.Context) {
resourceType := c.Param("type") // e.g., "deployment", "service"
resourceType := c.Param("type") // e.g., "deployment", "service"
resourceName := c.Param("name")
apiVersion := c.Query("apiVersion") // e.g., "apps/v1"
kind := c.Query("kind") // e.g., "Deployment"
Expand Down Expand Up @@ -529,7 +530,7 @@
"namespace": h.namespace,
"ingressDomain": os.Getenv("INGRESS_DOMAIN"),
"hibernation": gin.H{
"enabled": true,
"enabled": true,
"defaultIdleTimeout": "30m",
},
"resources": gin.H{
Expand Down Expand Up @@ -601,10 +602,152 @@
// are fully implemented in api/internal/handlers/users.go by UserHandler.
// Those should be used instead of stub implementations.

// GetMetrics returns metrics
// GetMetrics returns cluster metrics including nodes, sessions, resources, and users
func (h *Handler) GetMetrics(c *gin.Context) {
stats := h.connTracker.GetStats()
c.JSON(http.StatusOK, stats)
ctx := c.Request.Context()

// Get cluster nodes
nodes, err := h.k8sClient.GetNodes(ctx)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get cluster nodes"})
return
}

// Count ready nodes
readyNodes := 0
totalCPU := int64(0)
totalMemory := int64(0)
usedPods := 0
totalPods := 0

for _, node := range nodes {

Check failure on line 623 in api/internal/api/stubs.go

View workflow job for this annotation

GitHub Actions / Go Dependency Vulnerability Scan (api)

cannot range over nodes (variable of type *"k8s.io/api/core/v1".NodeList)
// Check if node is ready
for _, condition := range node.Status.Conditions {
if condition.Type == corev1.NodeReady && condition.Status == corev1.ConditionTrue {
readyNodes++
break
}
}

// Sum up allocatable resources
if cpu, ok := node.Status.Allocatable[corev1.ResourceCPU]; ok {
totalCPU += cpu.MilliValue()
}
if memory, ok := node.Status.Allocatable[corev1.ResourceMemory]; ok {
totalMemory += memory.Value()
}
if pods, ok := node.Status.Allocatable[corev1.ResourcePods]; ok {
totalPods += int(pods.Value())
}
}

// Get all pods to calculate resource usage
pods, err := h.k8sClient.GetPods(ctx, h.namespace)
if err == nil {
usedPods = len(pods)

Check failure on line 647 in api/internal/api/stubs.go

View workflow job for this annotation

GitHub Actions / Go Dependency Vulnerability Scan (api)

invalid argument: pods (variable of type *"k8s.io/api/core/v1".PodList) for built-in len
}

// Get session counts from database
var sessionCounts struct {
Total int
Running int
Hibernated int
Terminated int
}

err = h.db.DB().QueryRowContext(ctx, `
SELECT
COUNT(*) as total,
COUNT(*) FILTER (WHERE state = 'running') as running,
COUNT(*) FILTER (WHERE state = 'hibernated') as hibernated,
COUNT(*) FILTER (WHERE state = 'terminated') as terminated
FROM sessions
`).Scan(&sessionCounts.Total, &sessionCounts.Running, &sessionCounts.Hibernated, &sessionCounts.Terminated)

if err != nil {
log.Printf("Failed to get session counts: %v", err)
// Use zeros if query fails
sessionCounts = struct {
Total, Running, Hibernated, Terminated int
}{0, 0, 0, 0}
}

// Get user counts from database
var userCounts struct {
Total int
Active int
}

err = h.db.DB().QueryRowContext(ctx, `
SELECT
COUNT(*) as total,
COUNT(*) FILTER (WHERE last_login > NOW() - INTERVAL '24 hours') as active
FROM users
`).Scan(&userCounts.Total, &userCounts.Active)

if err != nil {
log.Printf("Failed to get user counts: %v", err)
// Use zeros if query fails
userCounts = struct{ Total, Active int }{0, 0}
}

// Calculate resource usage (simplified - in production you'd query metrics-server)
// For now, estimate based on running sessions
usedCPU := int64(sessionCounts.Running * 1000) // 1000m per session estimate
usedMemory := int64(sessionCounts.Running * 2 * 1024 * 1024 * 1024) // 2GiB per session estimate

cpuPercent := float64(0)
if totalCPU > 0 {
cpuPercent = float64(usedCPU) / float64(totalCPU) * 100
}

memoryPercent := float64(0)
if totalMemory > 0 {
memoryPercent = float64(usedMemory) / float64(totalMemory) * 100
}

podsPercent := float64(0)
if totalPods > 0 {
podsPercent = float64(usedPods) / float64(totalPods) * 100
}

// Return cluster metrics in the format expected by AdminDashboard
c.JSON(http.StatusOK, gin.H{
"cluster": gin.H{
"nodes": gin.H{
"total": len(nodes),

Check failure on line 718 in api/internal/api/stubs.go

View workflow job for this annotation

GitHub Actions / Go Dependency Vulnerability Scan (api)

invalid argument: nodes (variable of type *"k8s.io/api/core/v1".NodeList) for built-in len
"ready": readyNodes,
"notReady": len(nodes) - readyNodes,

Check failure on line 720 in api/internal/api/stubs.go

View workflow job for this annotation

GitHub Actions / Go Dependency Vulnerability Scan (api)

invalid argument: nodes (variable of type *"k8s.io/api/core/v1".NodeList) for built-in len
},
"sessions": gin.H{
"total": sessionCounts.Total,
"running": sessionCounts.Running,
"hibernated": sessionCounts.Hibernated,
"terminated": sessionCounts.Terminated,
},
"resources": gin.H{
"cpu": gin.H{
"total": fmt.Sprintf("%dm", totalCPU),
"used": fmt.Sprintf("%dm", usedCPU),
"percent": cpuPercent,
},
"memory": gin.H{
"total": fmt.Sprintf("%d", totalMemory),
"used": fmt.Sprintf("%d", usedMemory),
"percent": memoryPercent,
},
"pods": gin.H{
"total": totalPods,
"used": usedPods,
"percent": podsPercent,
},
},
"users": gin.H{
"total": userCounts.Total,
"active": userCounts.Active,
},
},
})
}

// ============================================================================
Expand Down
Loading
Loading