From a6902cd769c4ec531a4e220d29e3d0adef1cb213 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 18 Nov 2025 00:28:53 +0000 Subject: [PATCH 1/3] fix(api): add missing time package import in database.go The database.go file was using time.Minute on lines 188-189 but the time package was not imported, causing build failures. This fixes the Docker build error by adding the time import to the import block. --- api/internal/db/database.go | 1 + 1 file changed, 1 insertion(+) diff --git a/api/internal/db/database.go b/api/internal/db/database.go index 093f406a..7c3bf16a 100644 --- a/api/internal/db/database.go +++ b/api/internal/db/database.go @@ -75,6 +75,7 @@ import ( "regexp" "strconv" "strings" + "time" "golang.org/x/crypto/bcrypt" _ "github.com/lib/pq" From 4aced65c9de5cbc42596cc7deadcddfc395a4b60 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 18 Nov 2025 00:34:02 +0000 Subject: [PATCH 2/3] fix(api): resolve build errors in handlers - Add missing log package import in notifications.go (line 645) - Remove unused ctx variable in handlers.go SyncRepository function (line 1505 - not needed since goroutine uses context.Background()) --- api/internal/api/handlers.go | 2 -- api/internal/handlers/notifications.go | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/api/internal/api/handlers.go b/api/internal/api/handlers.go index 44ae071b..be99e170 100644 --- a/api/internal/api/handlers.go +++ b/api/internal/api/handlers.go @@ -1501,8 +1501,6 @@ func (h *Handler) AddRepository(c *gin.Context) { // SyncRepository triggers a sync for a repository func (h *Handler) SyncRepository(c *gin.Context) { - // SECURITY FIX: Use request context for proper cancellation and timeout handling - ctx := c.Request.Context() repoIDStr := c.Param("id") // Convert repo ID to int diff --git a/api/internal/handlers/notifications.go b/api/internal/handlers/notifications.go index b209b738..942fa965 100644 --- a/api/internal/handlers/notifications.go +++ b/api/internal/handlers/notifications.go @@ -90,6 +90,7 @@ import ( "encoding/json" "fmt" "html/template" + "log" "net/http" "net/smtp" "os" From 1dde51c05aaefa18429d51b32049c3c0109584f2 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 18 Nov 2025 00:49:17 +0000 Subject: [PATCH 3/3] fix(ui/api): resolve runtime errors in admin UI UI fixes: - Add optional chaining for user.role and user.provider in UserDetail.tsx to prevent TypeError when fields are undefined - Fallback to 'N/A' label when values are missing API fixes: - Handle nil k8sClient gracefully in GetMetrics endpoint - Return metrics with default values instead of error when k8s unavailable - Log warnings but continue serving metrics from database - Prevents frontend 'Failed to get cluster nodes' error --- api/internal/api/stubs.go | 72 +++++++++++++++++-------------- ui/src/pages/admin/UserDetail.tsx | 4 +- 2 files changed, 42 insertions(+), 34 deletions(-) diff --git a/api/internal/api/stubs.go b/api/internal/api/stubs.go index fbd3ca5b..3820e77b 100644 --- a/api/internal/api/stubs.go +++ b/api/internal/api/stubs.go @@ -675,45 +675,53 @@ func (h *Handler) UpdateConfig(c *gin.Context) { func (h *Handler) GetMetrics(c *gin.Context) { 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 + // Initialize default values + totalNodes := 0 readyNodes := 0 totalCPU := int64(0) totalMemory := int64(0) usedPods := 0 totalPods := 0 - for _, node := range nodes.Items { - // Check if node is ready - for _, condition := range node.Status.Conditions { - if condition.Type == corev1.NodeReady && condition.Status == corev1.ConditionTrue { - readyNodes++ - break + // Get cluster nodes (handle nil k8sClient gracefully) + if h.k8sClient != nil { + nodes, err := h.k8sClient.GetNodes(ctx) + if err != nil { + log.Printf("Failed to get cluster nodes: %v", err) + // Continue with default values instead of failing + } else { + totalNodes = len(nodes.Items) + + // Count ready nodes and sum resources + for _, node := range nodes.Items { + // 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()) + } } - } - // 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.Items) + } } - } - - // Get all pods to calculate resource usage - pods, err := h.k8sClient.GetPods(ctx, h.namespace) - if err == nil { - usedPods = len(pods.Items) + } else { + log.Printf("Warning: k8sClient is nil, returning metrics without cluster data") } // Get session counts from database @@ -784,9 +792,9 @@ func (h *Handler) GetMetrics(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "cluster": gin.H{ "nodes": gin.H{ - "total": len(nodes.Items), + "total": totalNodes, "ready": readyNodes, - "notReady": len(nodes.Items) - readyNodes, + "notReady": totalNodes - readyNodes, }, "sessions": gin.H{ "total": sessionCounts.Total, diff --git a/ui/src/pages/admin/UserDetail.tsx b/ui/src/pages/admin/UserDetail.tsx index a91ea4ae..5b48a2eb 100644 --- a/ui/src/pages/admin/UserDetail.tsx +++ b/ui/src/pages/admin/UserDetail.tsx @@ -284,7 +284,7 @@ export default function UserDetail() { Role - + @@ -292,7 +292,7 @@ export default function UserDetail() { Provider - +