diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4152350..94e4036 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -26,8 +26,35 @@ jobs: image: goreleaser/goreleaser-cross:v1.23 steps: - run: git config --global --add safe.directory /__w/nezha/nezha + - uses: actions/checkout@v4 + - uses: robinraju/release-downloader@v1 + with: + repository: nezhahq/admin-frontend + tag: v1.0.4 + fileName: dist.zip + latest: true + extract: true + + - name: prepare admin-frontend dists + run: | + rm -rf cmd/dashboard/admin-dist + mv dist cmd/dashboard/admin-dist + + - uses: robinraju/release-downloader@v1 + with: + repository: nezhahq/user-frontend + tag: v1.0.3 + fileName: dist.zip + latest: true + extract: true + + - name: prepare admin-frontend dists + run: | + rm -rf cmd/dashboard/user-dist + mv dist cmd/dashboard/user-dist + - name: Fetch IPInfo GeoIP Database env: IPINFO_TOKEN: ${{ secrets.IPINFO_TOKEN }} @@ -130,30 +157,6 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: robinraju/release-downloader@v1 - with: - repository: nezhahq/admin-frontend - tag: v1.0.3 - fileName: dist.zip - latest: true - extract: true - - - name: prepare admin-frontend dists - run: | - mv dist admin-dist - - - uses: robinraju/release-downloader@v1 - with: - repository: nezhahq/user-frontend - tag: v1.0.2 - fileName: dist.zip - latest: true - extract: true - - - name: prepare admin-frontend dists - run: | - mv dist user-dist - - name: Download artifacts uses: actions/download-artifact@v4 with: diff --git a/.gitignore b/.gitignore index 782cf4b..c3b8b90 100644 --- a/.gitignore +++ b/.gitignore @@ -17,8 +17,10 @@ .DS_Store /cmd/dashboard/data /cmd/dashboard/main -/cmd/dashboard/admin-dist -/cmd/dashboard/user-dist +/cmd/dashboard/admin-dist/* +/cmd/dashboard/user-dist/* +!/cmd/dashboard/admin-dist/.gitkeep +!/cmd/dashboard/user-dist/.gitkeep /config.yml /resource/template/theme-custom /resource/static/custom diff --git a/Dockerfile b/Dockerfile index edc2e3e..636fecc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,8 +7,6 @@ ARG TARGETOS ARG TARGETARCH COPY --from=certs /etc/ssl/certs /etc/ssl/certs -COPY ./admin-dist /dashboard/admin-dist -COPY ./user-dist /dashboard/user-dist COPY ./script/entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh diff --git a/cmd/dashboard/admin-dist/.gitkeep b/cmd/dashboard/admin-dist/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/cmd/dashboard/controller/controller.go b/cmd/dashboard/controller/controller.go index 48f555f..d354723 100644 --- a/cmd/dashboard/controller/controller.go +++ b/cmd/dashboard/controller/controller.go @@ -3,6 +3,8 @@ package controller import ( "errors" "fmt" + "io" + "io/fs" "log" "net/http" "os" @@ -21,7 +23,7 @@ import ( "github.com/nezhahq/nezha/service/singleton" ) -func ServeWeb() http.Handler { +func ServeWeb(adminFrontend, userFrontend fs.FS) http.Handler { gin.SetMode(gin.ReleaseMode) r := gin.Default() @@ -37,12 +39,13 @@ func ServeWeb() http.Handler { r.Use(waf.RealIp) r.Use(waf.Waf) r.Use(recordPath) - routers(r) + + routers(r, adminFrontend, userFrontend) return r } -func routers(r *gin.Engine) { +func routers(r *gin.Engine, adminFrontend, userFrontend fs.FS) { authMiddleware, err := jwt.New(initParams()) if err != nil { log.Fatal("JWT Error:" + err.Error()) @@ -129,7 +132,7 @@ func routers(r *gin.Engine) { auth.PATCH("/setting", commonHandler(updateConfig)) - r.NoRoute(fallbackToFrontend) + r.NoRoute(fallbackToFrontend(adminFrontend, userFrontend)) } func recordPath(c *gin.Context) { @@ -208,25 +211,46 @@ func commonHandler[T any](handler handlerFunc[T]) func(*gin.Context) { } } -func fallbackToFrontend(c *gin.Context) { - if strings.HasPrefix(c.Request.URL.Path, "/api") { - c.JSON(http.StatusOK, newErrorResponse(errors.New("404 Not Found"))) - return +func fallbackToFrontend(adminFrontend, userFrontend fs.FS) func(*gin.Context) { + checkLocalFileOrFs := func(c *gin.Context, fs fs.FS, path string) bool { + if _, err := os.Stat(path); err == nil { + c.File(path) + return true + } + f, err := fs.Open(path) + if err != nil { + return false + } + defer f.Close() + fileStat, err := f.Stat() + if err != nil { + return false + } + http.ServeContent(c.Writer, c.Request, path, fileStat.ModTime(), f.(io.ReadSeeker)) + return true } - if strings.HasPrefix(c.Request.URL.Path, "/dashboard") { - stripPath := strings.TrimPrefix(c.Request.URL.Path, "/dashboard") - localFilePath := filepath.Join("./admin-dist", stripPath) - if _, err := os.Stat(localFilePath); err == nil { - c.File(localFilePath) + return func(c *gin.Context) { + if strings.HasPrefix(c.Request.URL.Path, "/api") { + c.JSON(http.StatusOK, newErrorResponse(errors.New("404 Not Found"))) return } - c.File("admin-dist/index.html") - return + if strings.HasPrefix(c.Request.URL.Path, "/dashboard") { + stripPath := strings.TrimPrefix(c.Request.URL.Path, "/dashboard") + localFilePath := filepath.Join("admin-dist", stripPath) + if checkLocalFileOrFs(c, adminFrontend, localFilePath) { + return + } + if !checkLocalFileOrFs(c, adminFrontend, "admin-dist/index.html") { + c.JSON(http.StatusOK, newErrorResponse(errors.New("404 Not Found"))) + } + return + } + localFilePath := filepath.Join("user-dist", c.Request.URL.Path) + if checkLocalFileOrFs(c, userFrontend, localFilePath) { + return + } + if !checkLocalFileOrFs(c, userFrontend, "user-dist/index.html") { + c.JSON(http.StatusOK, newErrorResponse(errors.New("404 Not Found"))) + } } - localFilePath := filepath.Join("user-dist", c.Request.URL.Path) - if _, err := os.Stat(localFilePath); err == nil { - c.File(localFilePath) - return - } - c.File("user-dist/index.html") } diff --git a/cmd/dashboard/main.go b/cmd/dashboard/main.go index 47b43bf..673592c 100644 --- a/cmd/dashboard/main.go +++ b/cmd/dashboard/main.go @@ -2,6 +2,8 @@ package main import ( "context" + "embed" + _ "embed" "flag" "fmt" "log" @@ -32,6 +34,10 @@ type DashboardCliParam struct { var ( dashboardCliParam DashboardCliParam + //go:embed admin-dist + adminFrontend embed.FS + //go:embed user-dist + userFrontend embed.FS ) func initSystem() { @@ -119,7 +125,7 @@ func main() { singleton.NewServiceSentinel(serviceSentinelDispatchBus) grpcHandler := rpc.ServeRPC() - httpHandler := controller.ServeWeb() + httpHandler := controller.ServeWeb(adminFrontend, userFrontend) controller.InitUpgrader() muxHandler := newHTTPandGRPCMux(httpHandler, grpcHandler) diff --git a/cmd/dashboard/user-dist/.gitkeep b/cmd/dashboard/user-dist/.gitkeep new file mode 100644 index 0000000..e69de29