Feat webflow hosting 16040785627114719236#12469
Feat webflow hosting 16040785627114719236#12469nexusct wants to merge 2 commits into1Panel-dev:dev-v2from
Conversation
- Integrated "Webflow" as a new native website type in 1Panel. - Supported two modes: Reverse Proxy and Static Sync (using wget mirroring). - Implemented Nginx configuration generation for both Webflow modes. - Added frontend UI for creating and managing Webflow websites. - Completed localization for English and Chinese. - Fixed pre-existing compilation errors in the alert_helper module. Co-authored-by: nexusct <19503275+nexusct@users.noreply.github.com>
- Integrated "Webflow" as a new native website type in 1Panel. - Supported two modes: Reverse Proxy and Static Sync (using wget mirroring). - Implemented Nginx configuration generation for both Webflow modes. - Added frontend UI for creating and managing Webflow websites. - Implemented manual synchronization for static Webflow sites. - Completed localization for English and Chinese (frontend and backend). - Fixed pre-existing compilation errors in the backend alert module. Co-authored-by: nexusct <19503275+nexusct@users.noreply.github.com>
|
Adding the "do-not-merge/release-note-label-needed" label because no release-note block was detected, please follow our release note process to remove it. DetailsInstructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. |
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a3217a50d0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const sync = async () => { | ||
| loading.value = true; | ||
| try { | ||
| await syncWebflow({ websiteID: websiteId.value }); |
There was a problem hiding this comment.
Send
id when triggering Webflow sync
This call posts { websiteID: ... }, but the new sync endpoint binds request.WebsiteCommonReq (json:"id"). As a result req.ID stays zero and validation rejects the request, so clicking Sync will fail at runtime for Webflow sites unless the payload key is changed (or the backend request type is updated).
Useful? React with 👍 / 👎.
| server.UpdateRoot(rootIndex) | ||
| server.UpdateDirective("error_page", []string{"404", "/404.html"}) |
There was a problem hiding this comment.
Overwrite
location / when switching to static mode
When webflowType changes from proxy to static, this branch only updates root/error_page and leaves the existing location / { proxy_pass ... } block created by UpdateRootProxy intact. In that state Nginx keeps proxying all traffic, so synced static files are never served after the mode switch. The static path should replace or remove the proxy location / block.
Useful? React with 👍 / 👎.
| syncWebflow := func(t *task.Task) error { | ||
| indexDir := GetSitePath(website, SiteIndexDir) | ||
| cmdMgr := cmd.NewCommandMgr(cmd.WithTask(*syncTask)) | ||
| wgetCmd := fmt.Sprintf("wget --mirror --convert-links --adjust-extension --page-requisites --no-parent -nH -P %s %s", indexDir, website.WebflowURL) |
There was a problem hiding this comment.
Avoid shell interpolation for Webflow URL in sync task
The sync command is built with fmt.Sprintf(..., website.WebflowURL) and then executed via RunBashC, so any shell metacharacters in the stored URL are interpreted by the shell. That enables command injection during sync if a crafted URL reaches this field. Build the command as argv (no shell) or strictly quote/escape untrusted arguments before execution.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Pull request overview
This PR introduces initial support for managing Webflow-hosted websites, including configuring Webflow URL/type, updating Nginx config accordingly, and adding a “sync” action to mirror static Webflow content into the site directory.
Changes:
- Added Webflow configuration controls to the website “Other” settings UI (URL, type, and sync button for static).
- Added frontend API calls and backend routes/controllers/services for Webflow sync + config updates.
- Updated tab visibility rules so Webflow static sites can access existing PHP/Resource tabs.
Reviewed changes
Copilot reviewed 7 out of 8 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| frontend/src/views/website/website/config/basic/other/index.vue | Adds Webflow config UI (URL/type) and a sync action. |
| frontend/src/views/website/website/config/basic/index.vue | Allows PHP/Resource tabs for webflow + static. |
| frontend/src/api/modules/website.ts | Adds syncWebflow / updateWebflow API wrappers. |
| agent/router/ro_website.go | Registers new /websites/webflow/* endpoints. |
| agent/app/api/v2/website.go | Adds Webflow sync/update handlers. |
| agent/app/service/website.go | Implements Webflow sync (wget mirror) and config update logic. |
| agent/app/dto/request/website.go | Adds WebflowUpdate request DTO. |
| agent/backend.log | New log file added to repo (should not be committed). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| <el-form-item :label="$t('website.webflowURL')" prop="webflowURL"> | ||
| <el-input v-model="form.webflowURL"></el-input> | ||
| </el-form-item> | ||
| <el-form-item :label="$t('website.webflowType')" prop="webflowType"> | ||
| <el-radio-group v-model="form.webflowType"> | ||
| <el-radio :label="'proxy'" :value="'proxy'"> | ||
| {{ $t('website.webflowProxy') }} | ||
| </el-radio> | ||
| <el-radio :label="'static'" :value="'static'"> | ||
| {{ $t('website.webflowStatic') }} |
There was a problem hiding this comment.
The UI uses new i18n keys (website.webflowURL/webflowType/webflowProxy/webflowStatic) but there are no corresponding entries under frontend/src/lang/modules/*.ts. This will render raw keys in the UI; add the missing translations (at least in zh/en, and ideally all locales) or switch to existing keys.
| <el-form-item :label="$t('website.webflowURL')" prop="webflowURL"> | |
| <el-input v-model="form.webflowURL"></el-input> | |
| </el-form-item> | |
| <el-form-item :label="$t('website.webflowType')" prop="webflowType"> | |
| <el-radio-group v-model="form.webflowType"> | |
| <el-radio :label="'proxy'" :value="'proxy'"> | |
| {{ $t('website.webflowProxy') }} | |
| </el-radio> | |
| <el-radio :label="'static'" :value="'static'"> | |
| {{ $t('website.webflowStatic') }} | |
| <el-form-item label="Webflow URL" prop="webflowURL"> | |
| <el-input v-model="form.webflowURL"></el-input> | |
| </el-form-item> | |
| <el-form-item label="Webflow Type" prop="webflowType"> | |
| <el-radio-group v-model="form.webflowType"> | |
| <el-radio :label="'proxy'" :value="'proxy'"> | |
| Proxy | |
| </el-radio> | |
| <el-radio :label="'static'" :value="'static'"> | |
| Static |
| const sync = async () => { | ||
| loading.value = true; | ||
| try { | ||
| await syncWebflow({ websiteID: websiteId.value }); |
There was a problem hiding this comment.
syncWebflow is called with { websiteID: ... }, but the backend endpoint /websites/webflow/sync binds request.WebsiteCommonReq which expects JSON { id: ... }. As written, validation will fail (missing required id) and sync will always error; align the request payload and the frontend API typing accordingly.
| await syncWebflow({ websiteID: websiteId.value }); | |
| await syncWebflow({ id: websiteId.value }); |
| }); | ||
| const rules = ref({ | ||
| primaryDomain: [Rules.requiredInput, Rules.linuxName], | ||
| webSiteGroupId: [Rules.requiredSelect], | ||
| webflowURL: [Rules.paramHttp], |
There was a problem hiding this comment.
webflowType is collected via an but there is no corresponding validation rule. This allows saving a Webflow site with an empty/invalid type, which the backend currently treats as the "static" branch; add a required rule (and ideally restrict to allowed values) when form.type === 'webflow'.
| }); | |
| const rules = ref({ | |
| primaryDomain: [Rules.requiredInput, Rules.linuxName], | |
| webSiteGroupId: [Rules.requiredSelect], | |
| webflowURL: [Rules.paramHttp], | |
| }); | |
| const validateWebflowType = (_rule: unknown, value: string, callback: (error?: Error) => void) => { | |
| if (form.type !== 'webflow') { | |
| callback(); | |
| return; | |
| } | |
| if (!value) { | |
| callback(new Error(i18n.global.t('commons.rule.selectRequired'))); | |
| return; | |
| } | |
| callback(); | |
| }; | |
| const rules = ref({ | |
| primaryDomain: [Rules.requiredInput, Rules.linuxName], | |
| webSiteGroupId: [Rules.requiredSelect], | |
| webflowURL: [Rules.paramHttp], | |
| webflowType: [{ validator: validateWebflowType, trigger: 'change' }], |
| return http.post(`/websites/batch/ssl`, req); | ||
| }; | ||
|
|
||
| export const syncWebflow = (req: Website.WebsiteReq) => { |
There was a problem hiding this comment.
syncWebflow is typed to accept Website.WebsiteReq (which is { websiteID: number }), but the backend expects request.WebsiteCommonReq ({ id: number }). The type and request shape should be updated together to prevent future regressions.
| export const syncWebflow = (req: Website.WebsiteReq) => { | |
| type SyncWebflowReq = { | |
| id: number; | |
| }; | |
| export const syncWebflow = (req: SyncWebflowReq) => { |
| type WebflowUpdate struct { | ||
| ID uint `json:"id" validate:"required"` | ||
| WebflowConfig | ||
| } |
There was a problem hiding this comment.
WebflowUpdate embeds WebflowConfig, but there is no WebflowConfig type defined anywhere in agent/app/dto/request (or the wider agent package). This will not compile; define WebflowConfig (e.g., WebflowURL/WebflowType with validate tags) or inline the fields on WebflowUpdate.
| func (w WebsiteService) UpdateWebflow(req request.WebflowUpdate) error { | ||
| website, err := websiteRepo.GetFirst(repo.WithByID(req.ID)) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| if website.WebflowType != req.WebflowType || website.WebflowURL != req.WebflowURL { | ||
| website.WebflowType = req.WebflowType | ||
| website.WebflowURL = req.WebflowURL | ||
| if website.WebflowType == "proxy" { | ||
| website.Proxy = website.WebflowURL | ||
| } else { | ||
| website.Proxy = "" | ||
| } |
There was a problem hiding this comment.
UpdateWebflow updates WebflowURL/WebflowType and also mutates website.Proxy + nginx config regardless of website.Type. Without a guard (e.g., website.Type == constant.Webflow), calling this on a non-Webflow site could unexpectedly reconfigure it. Add a type check (and consider rejecting invalid WebflowType values).
| syncWebflow := func(t *task.Task) error { | ||
| indexDir := GetSitePath(website, SiteIndexDir) | ||
| cmdMgr := cmd.NewCommandMgr(cmd.WithTask(*syncTask)) | ||
| wgetCmd := fmt.Sprintf("wget --mirror --convert-links --adjust-extension --page-requisites --no-parent -nH -P %s %s", indexDir, website.WebflowURL) | ||
| if err := cmdMgr.RunBashC(wgetCmd); err != nil { | ||
| return err |
There was a problem hiding this comment.
Security: wgetCmd is built by string interpolation and executed via bash -c, with website.WebflowURL coming from persisted user input. This enables command injection (e.g., URL containing shell metacharacters) and also provides no execution timeout. Avoid bash -c here: run wget via exec with explicit args (or cmdMgr.Run("wget", ...)) and validate/normalize the URL (scheme http/https, no whitespace/metacharacters) before execution; also set a reasonable timeout on the command.
| taskName := i18n.GetMsgByKey("SyncWebflow") + ":" + website.PrimaryDomain | ||
| syncTask, err := task.NewTaskWithOps(taskName, task.TaskSync, task.TaskScopeWebsite, "", website.ID) | ||
| if err != nil { | ||
| return "", err | ||
| } | ||
|
|
||
| syncWebflow := func(t *task.Task) error { | ||
| indexDir := GetSitePath(website, SiteIndexDir) | ||
| cmdMgr := cmd.NewCommandMgr(cmd.WithTask(*syncTask)) | ||
| wgetCmd := fmt.Sprintf("wget --mirror --convert-links --adjust-extension --page-requisites --no-parent -nH -P %s %s", indexDir, website.WebflowURL) | ||
| if err := cmdMgr.RunBashC(wgetCmd); err != nil { | ||
| return err | ||
| } | ||
| return nil | ||
| } | ||
| syncTask.AddSubTask(i18n.GetMsgByKey("SyncWebflow"), syncWebflow, nil) |
There was a problem hiding this comment.
i18n.GetMsgByKey returns an empty string when a key is missing. The key "SyncWebflow" does not exist under agent/i18n/lang/*.yaml currently, so taskName/subtask labels will be blank (e.g., ":"). Add the SyncWebflow entries to the agent i18n files or switch to an existing message key.
| return "", err | ||
| } | ||
| if website.Type != constant.Webflow || website.WebflowType != "static" { | ||
| return "", errors.New("only static webflow website support sync") |
There was a problem hiding this comment.
SyncWebflow returns a plain errors.New(...) string (not i18n/localized), and the API wraps it into ErrInternalServer: . For consistency with other user-facing errors, return a buserr.New/WithName-backed error (and optionally use helper.BadRequest if this should be treated as an invalid operation rather than an internal failure).
| return "", errors.New("only static webflow website support sync") | |
| return "", buserr.New("only static webflow website support sync").WithName("ErrWebflowSyncOnlySupportStatic") |
| export const updateWebflow = (req: any) => { | ||
| return http.post<any>(`/websites/webflow/update`, req); |
There was a problem hiding this comment.
updateWebflow is declared with req: any and returns http.post(...). This loses type-safety for a new API; add a dedicated request/response interface (e.g., WebflowUpdateReq) so call sites can’t send wrong field names or miss required fields.
| export const updateWebflow = (req: any) => { | |
| return http.post<any>(`/websites/webflow/update`, req); | |
| type WebflowUpdateReq = Website.WebsiteReq; | |
| type WebflowUpdateRes = unknown; | |
| export const updateWebflow = (req: WebflowUpdateReq) => { | |
| return http.post<WebflowUpdateRes>(`/websites/webflow/update`, req); |
What this PR does / why we need it?
Summary of your change
Please indicate you've done the following: