migrate frontend

This commit is contained in:
ryan
2026-06-18 18:12:10 +08:00
parent e7b8993181
commit 15fa943d41
15 changed files with 57 additions and 24 deletions
@@ -16,7 +16,12 @@ export class LegacyOpenFlareBaseService {
protected static readonly basePath: string = '';
protected static getFullPath(path: string): string {
return `${this.basePath}${path}`;
const full = `${this.basePath}${path}`;
// Avoid Next.js 308 (strip slash) <-> Gin 301 (add slash) redirect loops in dev proxy.
if (full.length > 1 && full.endsWith('/')) {
return full.slice(0, -1);
}
return full;
}
protected static parseLegacyResponse<T>(body: LegacyApiResponse<T>): T {
+2
View File
@@ -4,6 +4,8 @@ const isExport = process.env.NEXT_STANDALONE_EXPORT === 'true';
const nextConfig: NextConfig = {
reactCompiler: true,
// Prevent 308 redirects on /api/* trailing slashes; dev rewrites proxy legacy APIs as-is.
skipTrailingSlashRedirect: true,
experimental: {
},
...(isExport ? { output: 'export' } : {
@@ -0,0 +1,23 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package compat
import (
"strings"
"github.com/gin-gonic/gin"
)
// RegisterCollection registers a collection endpoint on both "" and "/" so requests
// work with or without a trailing slash. This avoids 301/308 redirect loops between
// Next.js dev proxy and Gin when paths disagree on trailing slashes.
//
// When the group base already ends with "/" (e.g. userGroup.Group("/")), only "/" is
// registered to avoid duplicate route panic.
func RegisterCollection(route *gin.RouterGroup, method string, handlers ...gin.HandlerFunc) {
route.Handle(method, "/", handlers...)
if !strings.HasSuffix(route.BasePath(), "/") {
route.Handle(method, "", handlers...)
}
}
@@ -13,7 +13,7 @@ func registerApplyLogRoutes(apiGroup *gin.RouterGroup) {
applyLogRoute := apiGroup.Group("/apply-logs")
applyLogRoute.Use(compat.AdminAuth())
{
applyLogRoute.GET("/", apply_log.GetApplyLogs)
compat.RegisterCollection(applyLogRoute, "GET", apply_log.GetApplyLogs)
applyLogRoute.POST("/cleanup", apply_log.CleanupApplyLogs)
}
}
@@ -27,7 +27,7 @@ func registerAuthRoutes(apiGroup *gin.RouterGroup) {
externalAccounts := oauthGroup.Group("/external-accounts")
externalAccounts.Use(compat.UserAuth())
{
externalAccounts.GET("/", ListExternalAccounts)
compat.RegisterCollection(externalAccounts, "GET", ListExternalAccounts)
externalAccounts.POST("/:id/delete", DeleteExternalAccount)
}
}
@@ -56,10 +56,10 @@ func registerAuthRoutes(apiGroup *gin.RouterGroup) {
adminGroup := userGroup.Group("/")
adminGroup.Use(compat.AdminAuth())
{
adminGroup.GET("/", GetAllUsers)
compat.RegisterCollection(adminGroup, "GET", GetAllUsers)
adminGroup.GET("/search", SearchUsers)
adminGroup.GET("/:id", GetUser)
adminGroup.POST("/", CreateUser)
compat.RegisterCollection(adminGroup, "POST", CreateUser)
adminGroup.POST("/manage", ManageUser)
adminGroup.POST("/update", UpdateUser)
adminGroup.POST("/:id/delete", DeleteUser)
@@ -69,8 +69,8 @@ func registerAuthRoutes(apiGroup *gin.RouterGroup) {
authSourceGroup := apiGroup.Group("/auth-sources")
authSourceGroup.Use(compat.RootAuth())
{
authSourceGroup.GET("/", ListAuthSources)
authSourceGroup.POST("/", CreateAuthSource)
compat.RegisterCollection(authSourceGroup, "GET", ListAuthSources)
compat.RegisterCollection(authSourceGroup, "POST", CreateAuthSource)
authSourceGroup.POST("/:id/update", UpdateAuthSource)
authSourceGroup.POST("/:id/delete", DeleteAuthSource)
authSourceGroup.POST("/:id/toggle", ToggleAuthSource)
@@ -13,7 +13,7 @@ func registerConfigVersionRoutes(apiGroup *gin.RouterGroup) {
configVersionGroup := apiGroup.Group("/config-versions")
configVersionGroup.Use(compat.AdminAuth())
{
configVersionGroup.GET("/", config_version.ListConfigVersionsHandler)
compat.RegisterCollection(configVersionGroup, "GET", config_version.ListConfigVersionsHandler)
configVersionGroup.GET("/active", config_version.GetActiveConfigVersionHandler)
configVersionGroup.GET("/preview", config_version.PreviewConfigVersionHandler)
configVersionGroup.GET("/diff", config_version.DiffConfigVersionHandler)
@@ -15,8 +15,8 @@ func registerNodeRoutes(apiGroup *gin.RouterGroup) {
{
nodeRoute.GET("/bootstrap-token", node.GetBootstrapTokenHandler)
nodeRoute.POST("/bootstrap-token/rotate", node.RotateBootstrapTokenHandler)
nodeRoute.GET("/", node.ListNodesHandler)
nodeRoute.POST("/", node.CreateNodeHandler)
compat.RegisterCollection(nodeRoute, "GET", node.ListNodesHandler)
compat.RegisterCollection(nodeRoute, "POST", node.CreateNodeHandler)
nodeRoute.GET("/:id/agent-release", node.GetAgentReleaseHandler)
nodeRoute.POST("/:id/update", node.UpdateNodeHandler)
nodeRoute.POST("/:id/delete", node.DeleteNodeHandler)
@@ -13,9 +13,9 @@ func registerOriginRoutes(apiGroup *gin.RouterGroup) {
originRoute := apiGroup.Group("/origins")
originRoute.Use(compat.AdminAuth())
{
originRoute.GET("/", origin.GetOrigins)
compat.RegisterCollection(originRoute, "GET", origin.GetOrigins)
originRoute.GET("/:id", origin.GetOrigin)
originRoute.POST("/", origin.CreateOriginHandler)
compat.RegisterCollection(originRoute, "POST", origin.CreateOriginHandler)
originRoute.POST("/:id/update", origin.UpdateOriginHandler)
originRoute.POST("/:id/delete", origin.DeleteOriginHandler)
}
@@ -13,9 +13,9 @@ func registerPagesRoutes(apiGroup *gin.RouterGroup) {
pagesRoute := apiGroup.Group("/pages")
pagesRoute.Use(compat.AdminAuth())
{
pagesRoute.GET("/", pages.ListProjectsHandler)
compat.RegisterCollection(pagesRoute, "GET", pages.ListProjectsHandler)
pagesRoute.GET("/:id", pages.GetProjectHandler)
pagesRoute.POST("/", pages.CreateProjectHandler)
compat.RegisterCollection(pagesRoute, "POST", pages.CreateProjectHandler)
pagesRoute.POST("/:id/update", pages.UpdateProjectHandler)
pagesRoute.POST("/:id/delete", pages.DeleteProjectHandler)
pagesRoute.GET("/:id/deployments", pages.ListDeploymentsHandler)
@@ -13,9 +13,9 @@ func registerProxyRouteRoutes(apiGroup *gin.RouterGroup) {
proxyRouteGroup := apiGroup.Group("/proxy-routes")
proxyRouteGroup.Use(compat.AdminAuth())
{
proxyRouteGroup.GET("/", proxy_route.GetProxyRoutes)
compat.RegisterCollection(proxyRouteGroup, "GET", proxy_route.GetProxyRoutes)
proxyRouteGroup.GET("/:id", proxy_route.GetProxyRouteHandler)
proxyRouteGroup.POST("/", proxy_route.CreateProxyRouteHandler)
compat.RegisterCollection(proxyRouteGroup, "POST", proxy_route.CreateProxyRouteHandler)
proxyRouteGroup.POST("/:id/update", proxy_route.UpdateProxyRouteHandler)
proxyRouteGroup.POST("/:id/delete", proxy_route.DeleteProxyRouteHandler)
}
@@ -13,9 +13,9 @@ func registerTLSRoutes(apiGroup *gin.RouterGroup) {
managedDomainRoute := apiGroup.Group("/managed-domains")
managedDomainRoute.Use(compat.AdminAuth())
{
managedDomainRoute.GET("/", tls.GetManagedDomains)
compat.RegisterCollection(managedDomainRoute, "GET", tls.GetManagedDomains)
managedDomainRoute.GET("/match", tls.MatchManagedDomainCertificateHandler)
managedDomainRoute.POST("/", tls.CreateManagedDomainHandler)
compat.RegisterCollection(managedDomainRoute, "POST", tls.CreateManagedDomainHandler)
managedDomainRoute.POST("/:id/update", tls.UpdateManagedDomainHandler)
managedDomainRoute.POST("/:id/delete", tls.DeleteManagedDomainHandler)
}
@@ -23,10 +23,10 @@ func registerTLSRoutes(apiGroup *gin.RouterGroup) {
tlsCertificateRoute := apiGroup.Group("/tls-certificates")
tlsCertificateRoute.Use(compat.AdminAuth())
{
tlsCertificateRoute.GET("/", tls.GetCertificates)
compat.RegisterCollection(tlsCertificateRoute, "GET", tls.GetCertificates)
tlsCertificateRoute.GET("/:id", tls.GetCertificateDetail)
tlsCertificateRoute.GET("/:id/content", tls.GetCertificateContentHandler)
tlsCertificateRoute.POST("/", tls.CreateCertificateHandler)
compat.RegisterCollection(tlsCertificateRoute, "POST", tls.CreateCertificateHandler)
tlsCertificateRoute.POST("/:id/update", tls.UpdateCertificateHandler)
tlsCertificateRoute.POST("/:id/update-acme", tls.UpdateACMECertificateHandler)
tlsCertificateRoute.POST("/:id/convert-acme", tls.ConvertCertificateToACMEHandler)
@@ -45,8 +45,8 @@ func registerTLSRoutes(apiGroup *gin.RouterGroup) {
dnsAccountRoute := apiGroup.Group("/dns-accounts")
dnsAccountRoute.Use(compat.AdminAuth())
{
dnsAccountRoute.GET("/", tls.GetDNSAccounts)
dnsAccountRoute.POST("/", tls.CreateDNSAccountHandler)
compat.RegisterCollection(dnsAccountRoute, "GET", tls.GetDNSAccounts)
compat.RegisterCollection(dnsAccountRoute, "POST", tls.CreateDNSAccountHandler)
dnsAccountRoute.POST("/:id/update", tls.UpdateDNSAccountHandler)
dnsAccountRoute.POST("/:id/delete", tls.DeleteDNSAccountHandler)
}
@@ -15,7 +15,7 @@ func RegisterRoutes(apiGroup *gin.RouterGroup) {
accessLogRoute := apiGroup.Group("/access-logs")
accessLogRoute.Use(compat.AdminAuth())
{
accessLogRoute.GET("/", getAccessLogsHandler)
compat.RegisterCollection(accessLogRoute, "GET", getAccessLogsHandler)
accessLogRoute.GET("/folds", getFoldedAccessLogsHandler)
accessLogRoute.GET("/folds/ip-summary", getFoldedAccessLogIPsHandler)
accessLogRoute.GET("/ip-summary", getAccessLogIPSummariesHandler)
@@ -22,7 +22,7 @@ func RegisterRoutes(apiGroup *gin.RouterGroup) {
optionRoute := apiGroup.Group("/option")
optionRoute.Use(compat.RootAuth())
{
optionRoute.GET("/", listOptionsHandler)
compat.RegisterCollection(optionRoute, "GET", listOptionsHandler)
optionRoute.POST("/update", updateOptionHandler)
optionRoute.POST("/update-batch", updateOptionsBatchHandler)
optionRoute.POST("/geoip/lookup", lookupGeoIPHandler)
+2
View File
@@ -38,6 +38,8 @@ func Serve() {
// 初始化路由
r := gin.New()
// Legacy OpenFlare list endpoints register both /resource and /resource/; disable auto slash redirects.
r.RedirectTrailingSlash = false
r.Use(gin.Recovery())
r.Use(corsMiddleware())
+1
View File
@@ -43,6 +43,7 @@ sidebar: false
- 修复静态导出构建中 `useSearchParams` 未包裹 Suspense 的问题;`pnpm build:embed` 全量 46 页通过。
- 补全前端迁移收尾:新增 `/openflare/about` 关于页与 `AboutService`;补全 `UpdateService` 升级流程(在线升级、手动上传、WebSocket 日志流)及顶栏版本升级入口。
- 前端 UI 打磨:节点/WAF/DNS/配置清理等 Dialog 补 RHF+Zod;代理规则详情与访问日志 Tab 补 error 态;`pnpm build:embed` 增至 47 静态页。
- 修复开发模式下 `/api/*` 尾斜杠引发 Next.js 308 与 Gin 301 循环重定向:前端 Service 规范化路径、Next `skipTrailingSlashRedirect`、后端双路径注册。
## [v2.3.4] - 2026-06-17