Skip to content

Feat webflow hosting 16040785627114719236#12469

Open
nexusct wants to merge 2 commits into1Panel-dev:dev-v2from
nexusct:feat-webflow-hosting-16040785627114719236
Open

Feat webflow hosting 16040785627114719236#12469
nexusct wants to merge 2 commits into1Panel-dev:dev-v2from
nexusct:feat-webflow-hosting-16040785627114719236

Conversation

@nexusct
Copy link
Copy Markdown

@nexusct nexusct commented Apr 11, 2026

What this PR does / why we need it?

Summary of your change

Please indicate you've done the following:

  • Made sure tests are passing and test coverage is added if needed.
  • Made sure commit message follow the rule of Conventional Commits specification.
  • Considered the docs impact and opened a new docs issue or PR with docs changes if needed.

google-labs-jules bot and others added 2 commits March 17, 2026 08:27
- 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>
Copilot AI review requested due to automatic review settings April 11, 2026 13:19
@f2c-ci-robot
Copy link
Copy Markdown

f2c-ci-robot bot commented Apr 11, 2026

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.

Details

Instructions 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.

@f2c-ci-robot
Copy link
Copy Markdown

f2c-ci-robot bot commented Apr 11, 2026

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign ssongliu for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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 });
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Comment on lines +2371 to +2372
server.UpdateRoot(rootIndex)
server.UpdateDirective("error_page", []string{"404", "/404.html"})
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +24 to +33
<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') }}
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
<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

Copilot uses AI. Check for mistakes.
const sync = async () => {
loading.value = true;
try {
await syncWebflow({ websiteID: websiteId.value });
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
await syncWebflow({ websiteID: websiteId.value });
await syncWebflow({ id: websiteId.value });

Copilot uses AI. Check for mistakes.
Comment on lines 86 to +90
});
const rules = ref({
primaryDomain: [Rules.requiredInput, Rules.linuxName],
webSiteGroupId: [Rules.requiredSelect],
webflowURL: [Rules.paramHttp],
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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'.

Suggested change
});
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' }],

Copilot uses AI. Check for mistakes.
return http.post(`/websites/batch/ssl`, req);
};

export const syncWebflow = (req: Website.WebsiteReq) => {
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
export const syncWebflow = (req: Website.WebsiteReq) => {
type SyncWebflowReq = {
id: number;
};
export const syncWebflow = (req: SyncWebflowReq) => {

Copilot uses AI. Check for mistakes.
Comment on lines +376 to +379
type WebflowUpdate struct {
ID uint `json:"id" validate:"required"`
WebflowConfig
}
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +2352 to +2364
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 = ""
}
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot uses AI. Check for mistakes.
Comment on lines +2398 to +2403
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
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +2392 to +2407
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)
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
return "", err
}
if website.Type != constant.Webflow || website.WebflowType != "static" {
return "", errors.New("only static webflow website support sync")
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
return "", errors.New("only static webflow website support sync")
return "", buserr.New("only static webflow website support sync").WithName("ErrWebflowSyncOnlySupportStatic")

Copilot uses AI. Check for mistakes.
Comment on lines +389 to +390
export const updateWebflow = (req: any) => {
return http.post<any>(`/websites/webflow/update`, req);
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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);

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants