@@ -2,9 +2,7 @@ package main
22
33import (
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-
23020func 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