Skip to content

Commit edaf9a7

Browse files
authored
Refactor release-critical modules and parallelize cluster sync (#452)
* Refactor release-critical module structure * Parallelize cluster sync client rebuilds
1 parent 1ae5ccf commit edaf9a7

25 files changed

+4701
-4573
lines changed

app.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package main
2+
3+
import (
4+
"github.com/gin-contrib/gzip"
5+
"github.com/gin-gonic/gin"
6+
"github.com/zxh326/kite/internal"
7+
"github.com/zxh326/kite/pkg/cluster"
8+
"github.com/zxh326/kite/pkg/common"
9+
"github.com/zxh326/kite/pkg/handlers"
10+
"github.com/zxh326/kite/pkg/middleware"
11+
"github.com/zxh326/kite/pkg/model"
12+
"github.com/zxh326/kite/pkg/rbac"
13+
"k8s.io/klog/v2"
14+
)
15+
16+
func initializeApp() (*cluster.ClusterManager, error) {
17+
common.LoadEnvs()
18+
if klog.V(1).Enabled() {
19+
gin.SetMode(gin.DebugMode)
20+
} else {
21+
gin.SetMode(gin.ReleaseMode)
22+
}
23+
24+
model.InitDB()
25+
if _, err := model.GetGeneralSetting(); err != nil {
26+
klog.Warningf("Failed to load general setting: %v", err)
27+
}
28+
29+
rbac.InitRBAC()
30+
handlers.InitTemplates()
31+
internal.LoadConfigFromEnv()
32+
33+
return cluster.NewClusterManager()
34+
}
35+
36+
func buildEngine(cm *cluster.ClusterManager) *gin.Engine {
37+
r := gin.New()
38+
r.Use(middleware.Metrics())
39+
if !common.DisableGZIP {
40+
klog.Info("GZIP compression is enabled")
41+
r.Use(gzip.Gzip(gzip.DefaultCompression, gzip.WithExcludedPaths([]string{"/metrics"})))
42+
}
43+
r.Use(gin.Recovery())
44+
r.Use(middleware.Logger())
45+
r.Use(middleware.DevCORS(common.CORSAllowedOrigins))
46+
47+
base := r.Group(common.Base)
48+
setupAPIRouter(base, cm)
49+
setupStatic(r)
50+
51+
return r
52+
}

main.go

Lines changed: 3 additions & 240 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@ package main
22

33
import (
44
"context"
5-
"embed"
65
"flag"
7-
"io/fs"
86
"log"
97
"net/http"
108
"os"
@@ -14,261 +12,26 @@ import (
1412

1513
_ "net/http/pprof"
1614

17-
"github.com/gin-contrib/gzip"
18-
"github.com/gin-gonic/gin"
19-
"github.com/prometheus/client_golang/prometheus"
20-
"github.com/prometheus/client_golang/prometheus/promhttp"
21-
"github.com/zxh326/kite/internal"
22-
"github.com/zxh326/kite/pkg/ai"
23-
"github.com/zxh326/kite/pkg/auth"
24-
"github.com/zxh326/kite/pkg/cluster"
2515
"github.com/zxh326/kite/pkg/common"
26-
"github.com/zxh326/kite/pkg/handlers"
27-
"github.com/zxh326/kite/pkg/handlers/resources"
28-
"github.com/zxh326/kite/pkg/middleware"
29-
"github.com/zxh326/kite/pkg/model"
30-
"github.com/zxh326/kite/pkg/rbac"
31-
"github.com/zxh326/kite/pkg/utils"
3216
"github.com/zxh326/kite/pkg/version"
3317
"k8s.io/klog/v2"
34-
ctrlmetrics "sigs.k8s.io/controller-runtime/pkg/metrics"
3518
)
3619

37-
//go:embed static
38-
var static embed.FS
39-
40-
func setupStatic(r *gin.Engine) {
41-
base := common.Base
42-
if base != "" && base != "/" {
43-
r.GET("/", func(c *gin.Context) {
44-
c.Redirect(http.StatusFound, base+"/")
45-
})
46-
}
47-
assertsFS, err := fs.Sub(static, "static/assets")
48-
if err != nil {
49-
panic(err)
50-
}
51-
// Apply cache control middleware for static assets
52-
assetsGroup := r.Group(base + "/assets")
53-
assetsGroup.Use(middleware.StaticCache())
54-
assetsGroup.StaticFS("/", http.FS(assertsFS))
55-
r.NoRoute(func(c *gin.Context) {
56-
path := c.Request.URL.Path
57-
if len(path) >= len(base)+5 && path[len(base):len(base)+5] == "/api/" {
58-
c.JSON(http.StatusNotFound, gin.H{"error": "API endpoint not found"})
59-
return
60-
}
61-
62-
content, err := static.ReadFile("static/index.html")
63-
if err != nil {
64-
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to read index.html"})
65-
return
66-
}
67-
68-
htmlContent := string(content)
69-
htmlContent = utils.InjectKiteBase(htmlContent, base)
70-
if common.EnableAnalytics {
71-
htmlContent = utils.InjectAnalytics(htmlContent)
72-
}
73-
74-
c.Header("Content-Type", "text/html; charset=utf-8")
75-
c.String(http.StatusOK, htmlContent)
76-
})
77-
}
78-
79-
func setupAPIRouter(r *gin.RouterGroup, cm *cluster.ClusterManager) {
80-
r.GET("/metrics", gin.WrapH(promhttp.HandlerFor(prometheus.Gatherers{
81-
prometheus.DefaultGatherer,
82-
ctrlmetrics.Registry,
83-
}, promhttp.HandlerOpts{})))
84-
r.GET("/healthz", func(c *gin.Context) {
85-
c.JSON(http.StatusOK, gin.H{
86-
"status": "ok",
87-
})
88-
})
89-
r.GET("/api/v1/init_check", handlers.InitCheck)
90-
r.GET("/api/v1/version", version.GetVersion)
91-
// Auth routes (no auth required)
92-
authHandler := auth.NewAuthHandler()
93-
authGroup := r.Group("/api/auth")
94-
{
95-
authGroup.GET("/providers", authHandler.GetProviders)
96-
authGroup.POST("/login/password", authHandler.PasswordLogin)
97-
authGroup.POST("/login/ldap", authHandler.LDAPLogin)
98-
authGroup.GET("/login", authHandler.Login)
99-
authGroup.GET("/callback", authHandler.Callback)
100-
authGroup.POST("/logout", authHandler.Logout)
101-
authGroup.POST("/refresh", authHandler.RefreshToken)
102-
authGroup.GET("/user", authHandler.RequireAuth(), authHandler.GetUser)
103-
}
104-
105-
userGroup := r.Group("/api/users")
106-
{
107-
userGroup.POST("/sidebar_preference", authHandler.RequireAuth(), handlers.UpdateSidebarPreference)
108-
}
109-
110-
// admin apis
111-
adminAPI := r.Group("/api/v1/admin")
112-
// Initialize the setup API without authentication.
113-
// Once users are configured, this API cannot be used.
114-
adminAPI.POST("/users/create_super_user", handlers.CreateSuperUser)
115-
adminAPI.POST("/clusters/import", cm.ImportClustersFromKubeconfig)
116-
adminAPI.Use(authHandler.RequireAuth(), authHandler.RequireAdmin())
117-
{
118-
adminAPI.GET("/audit-logs", handlers.ListAuditLogs)
119-
oauthProviderAPI := adminAPI.Group("/oauth-providers")
120-
{
121-
oauthProviderAPI.GET("/", authHandler.ListOAuthProviders)
122-
oauthProviderAPI.POST("/", authHandler.CreateOAuthProvider)
123-
oauthProviderAPI.GET("/:id", authHandler.GetOAuthProvider)
124-
oauthProviderAPI.PUT("/:id", authHandler.UpdateOAuthProvider)
125-
oauthProviderAPI.DELETE("/:id", authHandler.DeleteOAuthProvider)
126-
}
127-
128-
ldapSettingAPI := adminAPI.Group("/ldap-setting")
129-
{
130-
ldapSettingAPI.GET("/", authHandler.GetLDAPSetting)
131-
ldapSettingAPI.PUT("/", authHandler.UpdateLDAPSetting)
132-
}
133-
134-
clusterAPI := adminAPI.Group("/clusters")
135-
{
136-
clusterAPI.GET("/", cm.GetClusterList)
137-
clusterAPI.POST("/", cm.CreateCluster)
138-
clusterAPI.PUT("/:id", cm.UpdateCluster)
139-
clusterAPI.DELETE("/:id", cm.DeleteCluster)
140-
}
141-
142-
rbacAPI := adminAPI.Group("/roles")
143-
{
144-
rbacAPI.GET("/", rbac.ListRoles)
145-
rbacAPI.POST("/", rbac.CreateRole)
146-
rbacAPI.GET("/:id", rbac.GetRole)
147-
rbacAPI.PUT("/:id", rbac.UpdateRole)
148-
rbacAPI.DELETE("/:id", rbac.DeleteRole)
149-
150-
rbacAPI.POST("/:id/assign", rbac.AssignRole)
151-
rbacAPI.DELETE("/:id/assign", rbac.UnassignRole)
152-
}
153-
154-
userAPI := adminAPI.Group("/users")
155-
{
156-
userAPI.GET("/", handlers.ListUsers)
157-
userAPI.POST("/", handlers.CreatePasswordUser)
158-
userAPI.PUT(":id", handlers.UpdateUser)
159-
userAPI.DELETE(":id", handlers.DeleteUser)
160-
userAPI.POST(":id/reset_password", handlers.ResetPassword)
161-
userAPI.POST(":id/enable", handlers.SetUserEnabled)
162-
}
163-
164-
apiKeyAPI := adminAPI.Group("/apikeys")
165-
{
166-
apiKeyAPI.GET("/", handlers.ListAPIKeys)
167-
apiKeyAPI.POST("/", handlers.CreateAPIKey)
168-
apiKeyAPI.DELETE("/:id", handlers.DeleteAPIKey)
169-
}
170-
171-
generalSettingAPI := adminAPI.Group("/general-setting")
172-
{
173-
generalSettingAPI.GET("/", ai.HandleGetGeneralSetting)
174-
generalSettingAPI.PUT("/", ai.HandleUpdateGeneralSetting)
175-
}
176-
177-
templateAPI := adminAPI.Group("/templates")
178-
{
179-
templateAPI.POST("/", handlers.CreateTemplate)
180-
templateAPI.PUT("/:id", handlers.UpdateTemplate)
181-
templateAPI.DELETE("/:id", handlers.DeleteTemplate)
182-
}
183-
}
184-
185-
// API routes group (protected)
186-
api := r.Group("/api/v1")
187-
api.GET("/clusters", authHandler.RequireAuth(), cm.GetClusters)
188-
api.Use(authHandler.RequireAuth(), middleware.ClusterMiddleware(cm))
189-
{
190-
api.GET("/overview", handlers.GetOverview)
191-
192-
promHandler := handlers.NewPromHandler()
193-
api.GET("/prometheus/resource-usage-history", promHandler.GetResourceUsageHistory)
194-
api.GET("/prometheus/pods/:namespace/:podName/metrics", promHandler.GetPodMetrics)
195-
196-
logsHandler := handlers.NewLogsHandler()
197-
api.GET("/logs/:namespace/:podName/ws", logsHandler.HandleLogsWebSocket)
198-
199-
terminalHandler := handlers.NewTerminalHandler()
200-
api.GET("/terminal/:namespace/:podName/ws", terminalHandler.HandleTerminalWebSocket)
201-
202-
nodeTerminalHandler := handlers.NewNodeTerminalHandler()
203-
api.GET("/node-terminal/:nodeName/ws", nodeTerminalHandler.HandleNodeTerminalWebSocket)
204-
205-
kubectlTerminalHandler := handlers.NewKubectlTerminalHandler()
206-
api.GET("/kubectl-terminal/ws", kubectlTerminalHandler.HandleKubectlTerminalWebSocket)
207-
208-
searchHandler := handlers.NewSearchHandler()
209-
api.GET("/search", searchHandler.GlobalSearch)
210-
211-
resourceApplyHandler := handlers.NewResourceApplyHandler()
212-
api.POST("/resources/apply", resourceApplyHandler.ApplyResource)
213-
214-
api.GET("/image/tags", handlers.GetImageTags)
215-
api.GET("/templates", handlers.ListTemplates)
216-
217-
proxyHandler := handlers.NewProxyHandler()
218-
proxyHandler.RegisterRoutes(api)
219-
220-
// AI chat routes
221-
api.GET("/ai/status", ai.HandleAIStatus)
222-
api.POST("/ai/chat", ai.HandleChat)
223-
api.POST("/ai/execute/continue", ai.HandleExecuteContinue)
224-
225-
api.Use(middleware.RBACMiddleware())
226-
resources.RegisterRoutes(api)
227-
}
228-
}
229-
23020
func main() {
23121
klog.InitFlags(nil)
23222
flag.Parse()
23323
go func() {
23424
log.Println(http.ListenAndServe("localhost:6060", nil))
23525
}()
236-
common.LoadEnvs()
237-
if klog.V(1).Enabled() {
238-
gin.SetMode(gin.DebugMode)
239-
} else {
240-
gin.SetMode(gin.ReleaseMode)
241-
}
242-
r := gin.New()
243-
r.Use(middleware.Metrics())
244-
if !common.DisableGZIP {
245-
klog.Info("GZIP compression is enabled")
246-
r.Use(gzip.Gzip(gzip.DefaultCompression, gzip.WithExcludedPaths([]string{"/metrics"})))
247-
}
248-
r.Use(gin.Recovery())
249-
r.Use(middleware.Logger())
250-
r.Use(middleware.DevCORS(common.CORSAllowedOrigins))
251-
model.InitDB()
252-
if _, err := model.GetGeneralSetting(); err != nil {
253-
klog.Warningf("Failed to load general setting: %v", err)
254-
}
255-
rbac.InitRBAC()
256-
handlers.InitTemplates()
257-
internal.LoadConfigFromEnv()
25826

259-
cm, err := cluster.NewClusterManager()
27+
cm, err := initializeApp()
26028
if err != nil {
261-
log.Fatalf("Failed to create ClusterManager: %v", err)
29+
log.Fatalf("Failed to initialize app: %v", err)
26230
}
26331

264-
base := r.Group(common.Base)
265-
// Setup router
266-
setupAPIRouter(base, cm)
267-
setupStatic(r)
268-
26932
srv := &http.Server{
27033
Addr: ":" + common.Port,
271-
Handler: r.Handler(),
34+
Handler: buildEngine(cm).Handler(),
27235
}
27336
go func() {
27437
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {

0 commit comments

Comments
 (0)