From 3eecd318687caddbcfae97012e7d4e22e237bf7f Mon Sep 17 00:00:00 2001 From: ryan Date: Wed, 3 Jun 2026 22:48:04 +0800 Subject: [PATCH] =?UTF-8?q?[=E6=96=B0=E5=A2=9E]=20=E6=B7=BB=E5=8A=A0=20API?= =?UTF-8?q?=20=E5=8F=8D=E5=90=91=E4=BB=A3=E7=90=86=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../goose_202606030004_add_pages_api_proxy.go | 35 ++++++ openflare_server/model/goose/migrations.go | 1 + openflare_server/model/pages.go | 4 + openflare_server/service/config_version.go | 8 ++ openflare_server/service/pages.go | 42 ++++++++ openflare_server/service/pages_test.go | 54 ++++++++++ .../utils/render/openresty/render.go | 46 +++++++- .../utils/render/openresty/render_test.go | 100 ++++++++++++++++++ .../utils/render/openresty/types.go | 4 + .../features/pages/components/pages-page.tsx | 58 ++++++++++ openflare_server/web/features/pages/types.ts | 8 ++ 11 files changed, 358 insertions(+), 2 deletions(-) create mode 100644 openflare_server/model/goose/goose_202606030004_add_pages_api_proxy.go create mode 100644 openflare_server/utils/render/openresty/render_test.go diff --git a/openflare_server/model/goose/goose_202606030004_add_pages_api_proxy.go b/openflare_server/model/goose/goose_202606030004_add_pages_api_proxy.go new file mode 100644 index 00000000..fc0eb9ee --- /dev/null +++ b/openflare_server/model/goose/goose_202606030004_add_pages_api_proxy.go @@ -0,0 +1,35 @@ +package goose + +import ( + "fmt" + + presslygoose "github.com/pressly/goose/v3" + "gorm.io/gorm" +) + +const versionPagesAPIProxy int64 = 202606030004 + +// migration202606030004 adds API proxying fields (enabled, path, pass, rewrite) +// to Pages projects. +func migration202606030004(backend string, ctx Context) *presslygoose.Migration { + return newGORMMigration( + versionPagesAPIProxy, + "202606030004_add_pages_api_proxy.go", + backend, + ctx, + migratePagesAPIProxy, + ) +} + +func migratePagesAPIProxy(ctx Context, db *gorm.DB, backend string) error { + if err := ctx.ApplyCurrentSchema(db, backend); err != nil { + return err + } + // Verify that the columns exist + for _, col := range []string{"api_proxy_enabled", "api_proxy_path", "api_proxy_pass", "api_proxy_rewrite"} { + if !db.Migrator().HasColumn("pages_projects", col) { + return fmt.Errorf("column pages_projects.%s is missing", col) + } + } + return nil +} diff --git a/openflare_server/model/goose/migrations.go b/openflare_server/model/goose/migrations.go index afa6437a..ea20ef00 100644 --- a/openflare_server/model/goose/migrations.go +++ b/openflare_server/model/goose/migrations.go @@ -44,6 +44,7 @@ func registeredMigrations(backend string, ctx Context) []*presslygoose.Migration migration202606030001(backend, ctx), migration202606030002(backend, ctx), migration202606030003(backend, ctx), + migration202606030004(backend, ctx), } } diff --git a/openflare_server/model/pages.go b/openflare_server/model/pages.go index eea88464..58e9a770 100644 --- a/openflare_server/model/pages.go +++ b/openflare_server/model/pages.go @@ -15,6 +15,10 @@ type PagesProject struct { Enabled bool `json:"enabled" gorm:"not null;default:true"` SPAFallbackEnabled bool `json:"spa_fallback_enabled" gorm:"not null;default:false"` SPAFallbackPath string `json:"spa_fallback_path" gorm:"size:512;not null;default:'/index.html'"` + APIProxyEnabled bool `json:"api_proxy_enabled" gorm:"not null;default:false"` + APIProxyPath string `json:"api_proxy_path" gorm:"size:255;not null;default:''"` + APIProxyPass string `json:"api_proxy_pass" gorm:"size:2048;not null;default:''"` + APIProxyRewrite string `json:"api_proxy_rewrite" gorm:"size:255;not null;default:''"` ActiveDeploymentID *uint `json:"active_deployment_id" gorm:"index"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` diff --git a/openflare_server/service/config_version.go b/openflare_server/service/config_version.go index 5fb5a615..17f3a7e4 100644 --- a/openflare_server/service/config_version.go +++ b/openflare_server/service/config_version.go @@ -104,6 +104,10 @@ type snapshotPagesDeployment struct { EntryFile string `json:"entry_file"` SPAFallbackEnabled bool `json:"spa_fallback_enabled"` SPAFallbackPath string `json:"spa_fallback_path"` + APIProxyEnabled bool `json:"api_proxy_enabled"` + APIProxyPath string `json:"api_proxy_path"` + APIProxyPass string `json:"api_proxy_pass"` + APIProxyRewrite string `json:"api_proxy_rewrite"` LocalRoot string `json:"local_root"` } @@ -615,6 +619,10 @@ func buildSnapshotPagesDeployment(projectID *uint) (*snapshotPagesDeployment, er EntryFile: deployment.EntryFile, SPAFallbackEnabled: project.SPAFallbackEnabled, SPAFallbackPath: normalizeStoredPagesFallbackPath(project.SPAFallbackPath), + APIProxyEnabled: project.APIProxyEnabled, + APIProxyPath: project.APIProxyPath, + APIProxyPass: project.APIProxyPass, + APIProxyRewrite: project.APIProxyRewrite, LocalRoot: fmt.Sprintf("%s/deployments/%d/current", openrestyrender.PagesDirPlaceholder, deployment.ID), }, nil } diff --git a/openflare_server/service/pages.go b/openflare_server/service/pages.go index ba147291..c842dec4 100644 --- a/openflare_server/service/pages.go +++ b/openflare_server/service/pages.go @@ -8,6 +8,7 @@ import ( "fmt" "io" "mime/multipart" + "net/url" "openflare/common" "openflare/model" "os" @@ -36,6 +37,10 @@ type PagesProjectInput struct { Enabled bool `json:"enabled"` SPAFallbackEnabled bool `json:"spa_fallback_enabled"` SPAFallbackPath string `json:"spa_fallback_path"` + APIProxyEnabled bool `json:"api_proxy_enabled"` + APIProxyPath string `json:"api_proxy_path"` + APIProxyPass string `json:"api_proxy_pass"` + APIProxyRewrite string `json:"api_proxy_rewrite"` } type PagesProjectView struct { @@ -46,6 +51,10 @@ type PagesProjectView struct { Enabled bool `json:"enabled"` SPAFallbackEnabled bool `json:"spa_fallback_enabled"` SPAFallbackPath string `json:"spa_fallback_path"` + APIProxyEnabled bool `json:"api_proxy_enabled"` + APIProxyPath string `json:"api_proxy_path"` + APIProxyPass string `json:"api_proxy_pass"` + APIProxyRewrite string `json:"api_proxy_rewrite"` ActiveDeploymentID *uint `json:"active_deployment_id"` ActiveDeployment *PagesDeploymentView `json:"active_deployment,omitempty"` DeploymentCount int64 `json:"deployment_count"` @@ -137,6 +146,10 @@ func UpdatePagesProject(id uint, input PagesProjectInput) (*PagesProjectView, er "enabled": project.Enabled, "spa_fallback_enabled": project.SPAFallbackEnabled, "spa_fallback_path": project.SPAFallbackPath, + "api_proxy_enabled": project.APIProxyEnabled, + "api_proxy_path": project.APIProxyPath, + "api_proxy_pass": project.APIProxyPass, + "api_proxy_rewrite": project.APIProxyRewrite, }).Error; err != nil { if model.IsUniqueConstraintError(err) { return nil, errors.New("Pages 项目标识已存在") @@ -413,6 +426,31 @@ func buildPagesProject(project *model.PagesProject, input PagesProjectInput) (*m return nil, err } project.SPAFallbackPath = fallbackPath + + project.APIProxyEnabled = input.APIProxyEnabled + apiProxyPath := strings.TrimSpace(input.APIProxyPath) + apiProxyPass := strings.TrimSpace(input.APIProxyPass) + apiProxyRewrite := strings.TrimSpace(input.APIProxyRewrite) + + if project.APIProxyEnabled { + if apiProxyPath == "" { + return nil, errors.New("启用 API 反代时,匹配路径不能为空") + } + if !strings.HasPrefix(apiProxyPath, "/") { + return nil, errors.New("API 反代匹配路径必须以 '/' 开头") + } + if apiProxyPass == "" { + return nil, errors.New("启用 API 反代时,后端服务地址不能为空") + } + parsedURL, err := url.Parse(apiProxyPass) + if err != nil || (parsedURL.Scheme != "http" && parsedURL.Scheme != "https") || parsedURL.Host == "" { + return nil, errors.New("API 反代后端服务地址必须是有效的 HTTP/HTTPS URL") + } + } + project.APIProxyPath = apiProxyPath + project.APIProxyPass = apiProxyPass + project.APIProxyRewrite = apiProxyRewrite + return project, nil } @@ -428,6 +466,10 @@ func buildPagesProjectView(project *model.PagesProject) (*PagesProjectView, erro Enabled: project.Enabled, SPAFallbackEnabled: project.SPAFallbackEnabled, SPAFallbackPath: normalizeStoredPagesFallbackPath(project.SPAFallbackPath), + APIProxyEnabled: project.APIProxyEnabled, + APIProxyPath: project.APIProxyPath, + APIProxyPass: project.APIProxyPass, + APIProxyRewrite: project.APIProxyRewrite, ActiveDeploymentID: project.ActiveDeploymentID, CreatedAt: project.CreatedAt, UpdatedAt: project.UpdatedAt, diff --git a/openflare_server/service/pages_test.go b/openflare_server/service/pages_test.go index f1a4ff01..1b3bfd51 100644 --- a/openflare_server/service/pages_test.go +++ b/openflare_server/service/pages_test.go @@ -271,3 +271,57 @@ func TestUploadPagesDeploymentWithTopLevelFolder(t *testing.T) { t.Fatalf("expected EntryFile to be index.html, got %q", deployment.EntryFile) } } + +func TestPagesProjectAPIProxyValidation(t *testing.T) { + setupServiceTestDB(t) + + // 1. Invalid configuration: enabled but empty fields + _, err := CreatePagesProject(PagesProjectInput{ + Name: "API Proxy 1", + Enabled: true, + APIProxyEnabled: true, + }) + if err == nil || !strings.Contains(err.Error(), "匹配路径不能为空") { + t.Fatalf("expected error for empty match path, got: %v", err) + } + + // 2. Invalid path: must start with '/' + _, err = CreatePagesProject(PagesProjectInput{ + Name: "API Proxy 2", + Enabled: true, + APIProxyEnabled: true, + APIProxyPath: "api", + APIProxyPass: "http://127.0.0.1:8080", + }) + if err == nil || !strings.Contains(err.Error(), "必须以 '/' 开头") { + t.Fatalf("expected error for path not starting with /, got: %v", err) + } + + // 3. Invalid target URL + _, err = CreatePagesProject(PagesProjectInput{ + Name: "API Proxy 3", + Enabled: true, + APIProxyEnabled: true, + APIProxyPath: "/api", + APIProxyPass: "127.0.0.1:8080", + }) + if err == nil || !strings.Contains(err.Error(), "有效的 HTTP/HTTPS URL") { + t.Fatalf("expected error for invalid pass URL, got: %v", err) + } + + // 4. Valid configuration + project, err := CreatePagesProject(PagesProjectInput{ + Name: "API Proxy Valid", + Enabled: true, + APIProxyEnabled: true, + APIProxyPath: "/api", + APIProxyPass: "http://127.0.0.1:8080", + APIProxyRewrite: "/", + }) + if err != nil { + t.Fatalf("unexpected error creating valid project: %v", err) + } + if !project.APIProxyEnabled || project.APIProxyPath != "/api" || project.APIProxyPass != "http://127.0.0.1:8080" || project.APIProxyRewrite != "/" { + t.Fatalf("unexpected project state: %+v", project) + } +} diff --git a/openflare_server/utils/render/openresty/render.go b/openflare_server/utils/render/openresty/render.go index 3e2c1d24..0b83d687 100644 --- a/openflare_server/utils/render/openresty/render.go +++ b/openflare_server/utils/render/openresty/render.go @@ -421,8 +421,50 @@ func renderHTTPProxyServer(serverNames string, siteName string, originURL string return fmt.Sprintf("server {\n listen 80;\n server_name %s;\n%s%s location / {\n%s%s%s%s%s }\n%s}\n\n", serverNames, renderAccessBlock(siteName, powEnabled), renderPowLocationBlocks(powEnabled), renderBasicAuthBlock(basicAuthEnabled, basicAuthUsername, basicAuthPassword), renderProxyHeaderBlock(originURL, originHost, customHeaders, upstreamConfig, cfg), renderRouteLimitBlock(limitConfig), renderRouteCacheBlock(cacheConfig, cfg), renderProxyPassBlock(originURL, upstreamConfig), renderPowStaticLocationBlock(powEnabled)) } +func renderPagesAPIProxyLocationBlock(deployment *PagesDeployment) string { + if deployment == nil || !deployment.APIProxyEnabled { + return "" + } + path := strings.TrimSpace(deployment.APIProxyPath) + pass := strings.TrimSpace(deployment.APIProxyPass) + rewrite := strings.TrimSpace(deployment.APIProxyRewrite) + if path == "" || pass == "" { + return "" + } + if !strings.HasPrefix(path, "/") { + path = "/" + path + } + cleanPath := strings.TrimSuffix(path, "/") + + var builder strings.Builder + builder.WriteString(fmt.Sprintf("\n location %s {\n", cleanPath)) + if rewrite != "" { + if !strings.HasPrefix(rewrite, "/") { + rewrite = "/" + rewrite + } + cleanRewrite := strings.TrimSuffix(rewrite, "/") + if cleanRewrite == "" { + builder.WriteString(fmt.Sprintf(" rewrite ^%s/(.*)$ /$1 break;\n", regexp.QuoteMeta(cleanPath))) + builder.WriteString(fmt.Sprintf(" rewrite ^%s$ / break;\n", regexp.QuoteMeta(cleanPath))) + } else { + builder.WriteString(fmt.Sprintf(" rewrite ^%s/(.*)$ %s/$1 break;\n", regexp.QuoteMeta(cleanPath), cleanRewrite)) + builder.WriteString(fmt.Sprintf(" rewrite ^%s$ %s break;\n", regexp.QuoteMeta(cleanPath), cleanRewrite)) + } + } + builder.WriteString(fmt.Sprintf(" proxy_pass %s;\n", pass)) + builder.WriteString(" proxy_http_version 1.1;\n") + builder.WriteString(" proxy_set_header Host $http_host;\n") + builder.WriteString(" proxy_set_header X-Real-IP $remote_addr;\n") + builder.WriteString(" proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n") + builder.WriteString(" proxy_set_header X-Forwarded-Proto $scheme;\n") + builder.WriteString(" proxy_set_header Upgrade $http_upgrade;\n") + builder.WriteString(" proxy_set_header Connection $connection_upgrade;\n") + builder.WriteString(" }\n") + return builder.String() +} + func renderHTTPPagesServer(serverNames string, siteName string, deployment *PagesDeployment, limitConfig routeLimitConfig, powEnabled bool, basicAuthEnabled bool, basicAuthUsername string, basicAuthPassword string) string { - return fmt.Sprintf("server {\n listen 80;\n server_name %s;\n%s%s root %s;\n index %s;\n\n location / {\n%s%s }\n%s}\n\n", serverNames, renderAccessBlock(siteName, powEnabled), renderPowLocationBlocks(powEnabled), quoteNginxStringLiteral(pagesDeploymentRoot(deployment)), quoteNginxStringLiteral(pagesEntryFile(deployment)), renderBasicAuthBlock(basicAuthEnabled, basicAuthUsername, basicAuthPassword), renderPagesLocationBlock(deployment, limitConfig), renderPowStaticLocationBlock(powEnabled)) + return fmt.Sprintf("server {\n listen 80;\n server_name %s;\n%s%s root %s;\n index %s;%s\n\n location / {\n%s%s }\n%s}\n\n", serverNames, renderAccessBlock(siteName, powEnabled), renderPowLocationBlocks(powEnabled), quoteNginxStringLiteral(pagesDeploymentRoot(deployment)), quoteNginxStringLiteral(pagesEntryFile(deployment)), renderPagesAPIProxyLocationBlock(deployment), renderBasicAuthBlock(basicAuthEnabled, basicAuthUsername, basicAuthPassword), renderPagesLocationBlock(deployment, limitConfig), renderPowStaticLocationBlock(powEnabled)) } func renderHTTPRedirectServer(serverNames string) string { @@ -450,7 +492,7 @@ func renderHTTPSPagesServer(serverNames string, siteName string, certificateID u h3Listen = " listen 443 quic;\n" h3Header = " add_header Alt-Svc 'h3=\":443\"; ma=86400';\n" } - return fmt.Sprintf("server {\n listen 443 ssl;\n%s http2 on;\n server_name %s;\n ssl_certificate %s;\n ssl_certificate_key %s;\n%s%s%s root %s;\n index %s;\n\n location / {\n%s%s }\n%s}\n\n", h3Listen, serverNames, certPath, keyPath, h3Header, renderAccessBlock(siteName, powEnabled), renderPowLocationBlocks(powEnabled), quoteNginxStringLiteral(pagesDeploymentRoot(deployment)), quoteNginxStringLiteral(pagesEntryFile(deployment)), renderBasicAuthBlock(basicAuthEnabled, basicAuthUsername, basicAuthPassword), renderPagesLocationBlock(deployment, limitConfig), renderPowStaticLocationBlock(powEnabled)) + return fmt.Sprintf("server {\n listen 443 ssl;\n%s http2 on;\n server_name %s;\n ssl_certificate %s;\n ssl_certificate_key %s;\n%s%s%s root %s;\n index %s;%s\n\n location / {\n%s%s }\n%s}\n\n", h3Listen, serverNames, certPath, keyPath, h3Header, renderAccessBlock(siteName, powEnabled), renderPowLocationBlocks(powEnabled), quoteNginxStringLiteral(pagesDeploymentRoot(deployment)), quoteNginxStringLiteral(pagesEntryFile(deployment)), renderPagesAPIProxyLocationBlock(deployment), renderBasicAuthBlock(basicAuthEnabled, basicAuthUsername, basicAuthPassword), renderPagesLocationBlock(deployment, limitConfig), renderPowStaticLocationBlock(powEnabled)) } func renderPagesLocationBlock(deployment *PagesDeployment, limitConfig routeLimitConfig) string { diff --git a/openflare_server/utils/render/openresty/render_test.go b/openflare_server/utils/render/openresty/render_test.go new file mode 100644 index 00000000..8083f2c6 --- /dev/null +++ b/openflare_server/utils/render/openresty/render_test.go @@ -0,0 +1,100 @@ +package openresty + +import ( + "strings" + "testing" +) + +func TestRenderPagesAPIProxyLocationBlock(t *testing.T) { + tests := []struct { + name string + deployment *PagesDeployment + expected []string + unexpected []string + }{ + { + name: "nil deployment", + deployment: nil, + expected: []string{""}, + }, + { + name: "disabled proxy", + deployment: &PagesDeployment{ + APIProxyEnabled: false, + APIProxyPath: "/api", + APIProxyPass: "http://127.0.0.1:8080", + }, + expected: []string{""}, + }, + { + name: "enabled proxy without rewrite", + deployment: &PagesDeployment{ + APIProxyEnabled: true, + APIProxyPath: "/api", + APIProxyPass: "http://127.0.0.1:8080", + APIProxyRewrite: "", + }, + expected: []string{ + "location /api {", + "proxy_pass http://127.0.0.1:8080;", + "proxy_http_version 1.1;", + "proxy_set_header Host $http_host;", + }, + unexpected: []string{ + "rewrite", + }, + }, + { + name: "enabled proxy with rewrite to root", + deployment: &PagesDeployment{ + APIProxyEnabled: true, + APIProxyPath: "/api", + APIProxyPass: "http://127.0.0.1:8080", + APIProxyRewrite: "/", + }, + expected: []string{ + "location /api {", + "rewrite ^/api/(.*)$ /$1 break;", + "rewrite ^/api$ / break;", + "proxy_pass http://127.0.0.1:8080;", + }, + }, + { + name: "enabled proxy with rewrite to subpath", + deployment: &PagesDeployment{ + APIProxyEnabled: true, + APIProxyPath: "/api", + APIProxyPass: "http://127.0.0.1:8080", + APIProxyRewrite: "/v2", + }, + expected: []string{ + "location /api {", + "rewrite ^/api/(.*)$ /v2/$1 break;", + "rewrite ^/api$ /v2 break;", + "proxy_pass http://127.0.0.1:8080;", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := renderPagesAPIProxyLocationBlock(tt.deployment) + if len(tt.expected) == 1 && tt.expected[0] == "" { + if got != "" { + t.Fatalf("expected empty output, got: %q", got) + } + return + } + for _, exp := range tt.expected { + if !strings.Contains(got, exp) { + t.Errorf("expected output to contain %q, but got:\n%s", exp, got) + } + } + for _, unexp := range tt.unexpected { + if strings.Contains(got, unexp) { + t.Errorf("expected output NOT to contain %q, but got:\n%s", unexp, got) + } + } + }) + } +} diff --git a/openflare_server/utils/render/openresty/types.go b/openflare_server/utils/render/openresty/types.go index b803fff5..9beb4734 100644 --- a/openflare_server/utils/render/openresty/types.go +++ b/openflare_server/utils/render/openresty/types.go @@ -130,6 +130,10 @@ type PagesDeployment struct { EntryFile string `json:"entry_file"` SPAFallbackEnabled bool `json:"spa_fallback_enabled"` SPAFallbackPath string `json:"spa_fallback_path"` + APIProxyEnabled bool `json:"api_proxy_enabled"` + APIProxyPath string `json:"api_proxy_path"` + APIProxyPass string `json:"api_proxy_pass"` + APIProxyRewrite string `json:"api_proxy_rewrite"` LocalRoot string `json:"local_root"` } diff --git a/openflare_server/web/features/pages/components/pages-page.tsx b/openflare_server/web/features/pages/components/pages-page.tsx index 702dcfe5..e0fd4a5c 100644 --- a/openflare_server/web/features/pages/components/pages-page.tsx +++ b/openflare_server/web/features/pages/components/pages-page.tsx @@ -431,6 +431,10 @@ function PagesProjectEditModal({ const [description, setDescription] = useState(project.description || ''); const [spaFallbackEnabled, setSpaFallbackEnabled] = useState(project.spa_fallback_enabled); const [spaFallbackPath, setSpaFallbackPath] = useState(project.spa_fallback_path); + const [apiProxyEnabled, setApiProxyEnabled] = useState(project.api_proxy_enabled || false); + const [apiProxyPath, setApiProxyPath] = useState(project.api_proxy_path || ''); + const [apiProxyPass, setApiProxyPass] = useState(project.api_proxy_pass || ''); + const [apiProxyRewrite, setApiProxyRewrite] = useState(project.api_proxy_rewrite || ''); useEffect(() => { setName(project.name); @@ -438,6 +442,10 @@ function PagesProjectEditModal({ setDescription(project.description || ''); setSpaFallbackEnabled(project.spa_fallback_enabled); setSpaFallbackPath(project.spa_fallback_path); + setApiProxyEnabled(project.api_proxy_enabled || false); + setApiProxyPath(project.api_proxy_path || ''); + setApiProxyPass(project.api_proxy_pass || ''); + setApiProxyRewrite(project.api_proxy_rewrite || ''); }, [project]); const updateMutation = useMutation({ @@ -449,6 +457,10 @@ function PagesProjectEditModal({ enabled: project.enabled, spa_fallback_enabled: spaFallbackEnabled, spa_fallback_path: spaFallbackPath, + api_proxy_enabled: apiProxyEnabled, + api_proxy_path: apiProxyPath, + api_proxy_pass: apiProxyPass, + api_proxy_rewrite: apiProxyRewrite, }), onSuccess: () => { onClose(); @@ -527,6 +539,48 @@ function PagesProjectEditModal({ onChange={(event) => setSpaFallbackPath(event.target.value)} /> + + {apiProxyEnabled && ( + <> + + setApiProxyPath(event.target.value)} + required + /> + + + setApiProxyPass(event.target.value)} + required + /> + + + setApiProxyRewrite(event.target.value)} + /> + + + )} {updateMutation.error ? (

{updateMutation.error.message} @@ -573,6 +627,10 @@ function PagesProjectCreateModal({ enabled: true, spa_fallback_enabled: spaFallbackEnabled, spa_fallback_path: spaFallbackPath, + api_proxy_enabled: false, + api_proxy_path: '', + api_proxy_pass: '', + api_proxy_rewrite: '', }), onSuccess: () => { resetForm(); diff --git a/openflare_server/web/features/pages/types.ts b/openflare_server/web/features/pages/types.ts index 4e324b33..4ed08135 100644 --- a/openflare_server/web/features/pages/types.ts +++ b/openflare_server/web/features/pages/types.ts @@ -20,6 +20,10 @@ export interface PagesProject { enabled: boolean; spa_fallback_enabled: boolean; spa_fallback_path: string; + api_proxy_enabled: boolean; + api_proxy_path: string; + api_proxy_pass: string; + api_proxy_rewrite: string; active_deployment_id?: number | null; active_deployment?: PagesDeployment | null; deployment_count: number; @@ -34,4 +38,8 @@ export interface PagesProjectPayload { enabled: boolean; spa_fallback_enabled: boolean; spa_fallback_path: string; + api_proxy_enabled: boolean; + api_proxy_path: string; + api_proxy_pass: string; + api_proxy_rewrite: string; }