refactor(firewall): queue rule operations and simplify synchronization (#13786)

This commit is contained in:
ssongliu
2026-09-11 15:16:44 +08:00
committed by GitHub
parent b7ec17b3e3
commit 9300bf4141
30 changed files with 1093 additions and 608 deletions

View File

@@ -460,6 +460,8 @@ func handleFirewallRuleError(c *gin.Context, err error) {
helper.ErrorWithBusinessCode(c, http.StatusConflict, "FW_RULE_STALE", "ErrInvalidParams", err)
case errors.Is(err, repo.ErrFirewallRuleRevisionConflict):
helper.ErrorWithBusinessCode(c, http.StatusConflict, "FW_RULE_REVISION_CONFLICT", "ErrInvalidParams", err)
case errors.Is(err, filter.ErrManagedScopeChange):
helper.ErrorWithBusinessCode(c, http.StatusBadRequest, "FW_SCOPE_UNSUPPORTED", "ErrFirewallRuleScopeChange", err)
case errors.Is(err, filter.ErrUnsupportedScope), errors.Is(err, filter.ErrInvalidScope),
errors.Is(err, filter.ErrProviderUnavailable), errors.Is(err, filter.ErrAdapterUnavailable):
helper.ErrorWithBusinessCode(c, http.StatusBadRequest, "FW_SCOPE_UNSUPPORTED", "ErrInvalidParams", err)
@@ -488,6 +490,29 @@ func (b *BaseApi) LoadFirewallSettings(c *gin.Context) {
helper.SuccessWithData(c, data)
}
// @Tags Firewall
// @Summary Queue firewall port whitelist update
// @Description Returns a taskID; configuration save and per-rule results are recorded in the task log.
// @Accept json
// @Param request body dto.FirewallPortWhitelistUpdate true "request"
// @Success 200 {object} dto.FilterChainOperationResponse
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /hosts/firewall/settings/whitelist [post]
// @x-panel-log {"bodyKeys":["value"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新防火墙端口白名单 [value]","formatEN":"update firewall port whitelist [value]"}
func (b *BaseApi) UpdateFirewallPortWhitelist(c *gin.Context) {
var request dto.FirewallPortWhitelistUpdate
if err := helper.CheckBindAndValidate(&request, c); err != nil {
return
}
result, err := firewallSettingService.QueuePortWhitelist(request.Value)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, result)
}
// @Tags Firewall
// @Summary Operate firewall backend
// @Accept json

View File

@@ -62,6 +62,10 @@ type FirewallSettings struct {
PortWhitelist string `json:"portWhiteList"`
}
type FirewallPortWhitelistUpdate struct {
Value string `json:"value" validate:"required"`
}
type FirewallBackendOperation struct {
Subsystem string `json:"subsystem" validate:"required,oneof=system forwarding docker"`
Backend string `json:"backend" validate:"required,oneof=firewalld ufw iptables nftables"`

View File

@@ -35,7 +35,7 @@ type SettingUpdate struct {
}
type AgentSettingUpdate struct {
Key string `json:"key" validate:"required,oneof=SystemIP DockerSockPath FileRecycleBin FirewallPortWhiteList"`
Key string `json:"key" validate:"required,oneof=SystemIP DockerSockPath FileRecycleBin"`
Value string `json:"value"`
}

View File

@@ -1825,7 +1825,7 @@ func (s *FirewallService) prepareManagedUpdate(
return preparedManagedUpdate{}, err
}
if after.Scope.Key() != before.Rule.Scope.Key() {
return preparedManagedUpdate{}, fmt.Errorf("%w: managed rule scope cannot be changed", filter.ErrUnsupportedScope)
return preparedManagedUpdate{}, filter.ErrManagedScopeChange
}
if !supportsManagedNativeKindTransition(before.Rule, after) {
return preparedManagedUpdate{}, fmt.Errorf("%w: native rule conversion requires an explicit workflow", filter.ErrUnsupportedScope)
@@ -2471,6 +2471,47 @@ func isFirewallPolicyIncompatible(err error) bool {
errors.Is(err, filter.ErrInvalidScope) || errors.Is(err, filter.ErrCompositeRule)
}
func (s *FirewallService) compileRestorableFirewallRules(
ctx context.Context,
stored model.FirewallRule,
provider filter.Provider,
) (restorable, preserved []filter.DesiredRule, err error) {
compiled, err := s.compileStoredFirewallRules(ctx, stored, provider)
if err != nil {
return nil, nil, err
}
if !supportsManagedFilterChains(string(provider)) || !isProtectedSystemFirewallRule(stored) {
return compiled, nil, nil
}
loadRequired := s.requiredPorts
if loadRequired == nil {
loadRequired = LoadRequiredFirewallPortWhiteList
}
required, err := loadRequired()
if err != nil {
return nil, nil, err
}
requiredPorts := systemPorts(required)
for _, desired := range compiled {
covered := false
for _, port := range requiredPorts {
covered, err = filter.SameRuleContent(desired.Rule, systemPortRule(provider, port))
if err != nil {
return nil, nil, err
}
if covered {
break
}
}
if covered {
preserved = append(preserved, desired)
} else {
restorable = append(restorable, desired)
}
}
return restorable, preserved, nil
}
func (s *FirewallService) desiredFirewallRulesByScope(
ctx context.Context,
stored []model.FirewallRule,
@@ -2480,7 +2521,7 @@ func (s *FirewallService) desiredFirewallRulesByScope(
desired := make(map[string][]filter.DesiredRule)
var failures []filter.InventoryItem
for _, record := range stored {
compiled, err := s.compileStoredFirewallRules(ctx, record, provider)
compiled, _, err := s.compileRestorableFirewallRules(ctx, record, provider)
if err != nil {
rule := filter.FirewallRule{
UUID: record.UUID,
@@ -2648,47 +2689,6 @@ func loadRequiredFirewallPorts(panelPort string) ([]firewall.PortWhitelist, erro
return firewall.NormalizeRequiredPorts(ports)
}
func (s *FirewallService) updatePortWhitelist(ctx context.Context, value string) error {
firewallRuleMutationMu.Lock()
defer firewallRuleMutationMu.Unlock()
ports, err := firewall.ParsePortWhitelist(value)
if err != nil {
return err
}
oldValue, err := settingRepo.GetValueByKey(constant.FirewallPortWhiteList)
if errors.Is(err, gorm.ErrRecordNotFound) {
oldValue = constant.FirewallPortWhiteListValue
} else if err != nil {
return err
}
oldPorts, err := firewall.ParsePortWhitelist(oldValue)
if err != nil {
return err
}
required, err := s.requiredPorts()
if err != nil {
return err
}
ctx = context.WithValue(ctx, panelPortWhitelistKey{}, firewall.NormalizePortWhitelist(append(oldPorts, required...)))
added := excludeFirewallPorts(excludeFirewallPorts(ports, oldPorts), required)
for _, port := range systemPorts(added) {
if err := s.ensureSystemPortLocked(ctx, port); err != nil {
return err
}
}
removed := excludeFirewallPorts(excludeFirewallPorts(oldPorts, ports), required)
return global.DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
txCtx := context.WithValue(ctx, constant.DB, tx)
if err := s.releaseSystemPorts(txCtx, systemPorts(removed)); err != nil {
return err
}
return tx.Where("key = ?", constant.FirewallPortWhiteList).
Assign(map[string]interface{}{"value": value}).
FirstOrCreate(&model.Setting{Key: constant.FirewallPortWhiteList}).Error
})
}
func (s *FirewallService) releaseSystemPorts(ctx context.Context, ports []dto.FirewallSystemPort) error {
portSet, err := normalizeSystemPorts(ports)
if err != nil || len(portSet) == 0 {

View File

@@ -2,11 +2,18 @@ package service
import (
"context"
"encoding/json"
"errors"
"fmt"
"sync"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/app/repo"
"github.com/1Panel-dev/1Panel/agent/app/task"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/i18n"
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
"github.com/1Panel-dev/1Panel/agent/utils/firewall"
"github.com/1Panel-dev/1Panel/agent/utils/firewall/docker_guard"
@@ -15,15 +22,19 @@ import (
"github.com/1Panel-dev/1Panel/agent/utils/firewall/lifecycle"
"github.com/1Panel-dev/1Panel/agent/utils/firewall/nftables_helper"
"github.com/1Panel-dev/1Panel/agent/utils/firewall/ping"
"gorm.io/gorm"
)
type IFirewallSettingService interface {
QueuePortWhitelist(value string) (dto.FilterChainOperationResponse, error)
Load(context.Context) (dto.FirewallSettings, error)
Operate(context.Context, dto.FirewallBackendOperation) error
}
type FirewallSettingService struct{}
var firewallWhitelistTaskMu sync.Mutex
var ErrFirewallBackendCleanupRequired = errors.New("firewall backend cleanup required")
func firewallBackendCleanupRequired(current, target string) error {
@@ -39,6 +50,180 @@ func NewIFirewallSettingService() IFirewallSettingService {
return &FirewallSettingService{}
}
func (s *FirewallSettingService) QueuePortWhitelist(value string) (dto.FilterChainOperationResponse, error) {
return s.queuePortWhitelist(value, newFirewallService())
}
func (s *FirewallSettingService) queuePortWhitelist(value string, firewallService *FirewallService) (dto.FilterChainOperationResponse, error) {
firewallWhitelistTaskMu.Lock()
defer firewallWhitelistTaskMu.Unlock()
if err := task.CheckScopeTaskIsExecuting(task.TaskScopeFirewall, 0); err != nil {
return dto.FilterChainOperationResponse{}, err
}
taskItem, err := task.NewTask(i18n.GetMsgByKey("FirewallWhitelistTask"), task.TaskUpdate, task.TaskScopeFirewall, "", 0)
if err != nil {
return dto.FilterChainOperationResponse{}, err
}
taskItem.AddSubTaskWithOps(taskItem.Name, func(t *task.Task) error {
succeeded, failed := 0, 0
err := s.applyPortWhitelist(t.TaskCtx, value, firewallService, func(status, label string, err error) {
switch status {
case "applied":
succeeded++
t.LogSuccess(label)
case "failed":
failed++
t.LogFailedWithErr(label, err)
default:
t.Log(i18n.GetWithName(status, label))
}
})
t.Log(i18n.GetMsgWithMap("FirewallRuleOperationResult", map[string]interface{}{
"succeeded": succeeded, "failed": failed,
}))
return err
}, nil, 0, 0)
if err := repo.NewITaskRepo().Save(context.Background(), taskItem.Task); err != nil {
closeUnstartedFirewallTask(taskItem)
return dto.FilterChainOperationResponse{}, fmt.Errorf("save firewall whitelist task: %w", err)
}
go func() { _ = taskItem.Execute() }()
return dto.FilterChainOperationResponse{TaskID: taskItem.TaskID, Queued: true}, nil
}
type whitelistReporter func(status, label string, err error)
func (s *FirewallSettingService) applyPortWhitelist(ctx context.Context, value string, firewallService *FirewallService, report whitelistReporter) error {
firewallRuleMutationMu.Lock()
defer firewallRuleMutationMu.Unlock()
if err := ctx.Err(); err != nil {
return err
}
ports, err := firewall.ParsePortWhitelist(value)
if err != nil {
return err
}
required, err := firewallService.requiredPorts()
if err != nil {
return err
}
removed, err := s.savePortWhitelist(ctx, ports, required, firewallService)
if err != nil {
return err
}
report("FirewallWhitelistSaved", "", nil)
for _, port := range systemPorts(removed) {
report("FirewallWhitelistReleased", whitelistPortLabel(port), nil)
}
ctx = context.WithValue(ctx, panelPortWhitelistKey{}, required)
provider, providerErr := firewallService.selectedProvider(ctx)
ready := s.portWhitelistReadiness(provider, providerErr, firewallService)
return syncPortWhitelist(ctx, ports, required, ready, firewallService.ensureSystemPortLocked, report)
}
func (s *FirewallSettingService) savePortWhitelist(ctx context.Context, ports, required []firewall.PortWhitelist, firewallService *FirewallService) ([]firewall.PortWhitelist, error) {
var removed []firewall.PortWhitelist
err := global.DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
var setting model.Setting
err := tx.Where("key = ?", constant.FirewallPortWhiteList).First(&setting).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
setting.Value = constant.FirewallPortWhiteListValue
} else if err != nil {
return err
}
previous, err := firewall.ParsePortWhitelist(setting.Value)
if err != nil {
return err
}
removed = excludeFirewallPorts(excludeFirewallPorts(previous, ports), required)
txCtx := context.WithValue(ctx, constant.DB, tx)
if err := firewallService.releaseSystemPorts(txCtx, systemPorts(removed)); err != nil {
return err
}
value, err := json.Marshal(ports)
if err != nil {
return err
}
return tx.Where("key = ?", constant.FirewallPortWhiteList).
Assign(map[string]interface{}{"value": string(value)}).
FirstOrCreate(&model.Setting{Key: constant.FirewallPortWhiteList}).Error
})
return removed, err
}
func (s *FirewallSettingService) portWhitelistReadiness(provider filter.Provider, providerErr error, firewallService *FirewallService) func(dto.FirewallSystemPort) (bool, error) {
type state struct {
ready bool
err error
}
states := make(map[string]state)
return func(port dto.FirewallSystemPort) (bool, error) {
if providerErr != nil {
return false, providerErr
}
key := "service"
if isDirectFirewallProvider(provider) {
key = port.Family
}
if cached, ok := states[key]; ok {
return cached.ready, cached.err
}
var result state
if isDirectFirewallProvider(provider) {
initialized, bound, err := loadSystemFirewallFamilyStatus(string(provider), port.Family)
result = state{ready: initialized && bound, err: err}
} else {
client, err := firewallService.baseClient()
result.err = err
if err == nil {
result.ready, result.err = client.Status()
}
}
states[key] = result
return result.ready, result.err
}
}
func syncPortWhitelist(
ctx context.Context,
ports, required []firewall.PortWhitelist,
ready func(dto.FirewallSystemPort) (bool, error),
ensure func(context.Context, dto.FirewallSystemPort) error,
report whitelistReporter,
) error {
var failures []error
for _, port := range systemPorts(ports) {
if err := ctx.Err(); err != nil {
return errors.Join(append(failures, err)...)
}
label := whitelistPortLabel(port)
if containsFirewallPort(required, firewall.PortWhitelist{Family: port.Family, Port: port.Port, Protocol: port.Protocol}) {
report("FirewallWhitelistRequired", label, nil)
continue
}
active, err := ready(port)
if err == nil && !active {
report("FirewallWhitelistDeferred", label, nil)
continue
}
if err == nil {
err = ensure(ctx, port)
}
if err != nil {
report("failed", label, err)
failures = append(failures, fmt.Errorf("%s: %w", label, err))
continue
}
report("applied", label, nil)
}
return errors.Join(failures...)
}
func whitelistPortLabel(port dto.FirewallSystemPort) string {
return fmt.Sprintf("%s %s/%s", port.Family, port.Port, port.Protocol)
}
func (s *FirewallSettingService) Load(ctx context.Context) (dto.FirewallSettings, error) {
result := dto.FirewallSettings{PingStatus: ping.LoadStatus()}
if ports, err := settingRepo.GetValueByKey(constant.FirewallPortWhiteList); err == nil {

View File

@@ -83,14 +83,18 @@ func (s *FirewallService) loadFirewallSyncRules(ctx context.Context, request dto
}
model.SortFirewallRules(stored, request.TargetProvider)
rules := make([]*firewallSyncRule, 0, len(stored))
preservedMarkers := make(map[string]bool)
compileFailed := false
for _, record := range stored {
desired, err := s.compileStoredFirewallRules(ctx, record, request.TargetProvider)
desired, preserved, err := s.compileRestorableFirewallRules(ctx, record, request.TargetProvider)
if err != nil {
compileFailed = true
rules = append(rules, &firewallSyncRule{FirewallRuleSyncItem: dto.FirewallRuleSyncItem{SourceUUID: record.UUID, Status: firewallsync.StatusBlocked, Reason: err.Error()}})
continue
}
for _, rule := range preserved {
preservedMarkers[rule.Marker] = true
}
for _, native := range desired {
rule := native.Rule
rules = append(rules, &firewallSyncRule{desired: native, FirewallRuleSyncItem: dto.FirewallRuleSyncItem{SourceUUID: record.UUID, Rule: &rule}})
@@ -129,7 +133,7 @@ func (s *FirewallService) loadFirewallSyncRules(ctx context.Context, request dto
matched := make(map[string]filter.InventoryItem)
for _, item := range inventory {
if item.Desired == nil {
if compileFailed || item.Observed == nil || !strings.HasPrefix(item.Observed.Marker, "1panel-rule:") {
if compileFailed || item.Observed == nil || !strings.HasPrefix(item.Observed.Marker, "1panel-rule:") || preservedMarkers[item.Observed.Marker] {
continue
}
observed := item.Observed

View File

@@ -1,7 +1,6 @@
package service
import (
"context"
"encoding/base64"
"encoding/json"
"errors"
@@ -125,9 +124,6 @@ func (u *SettingService) GetWebsiteDir() string {
}
func (u *SettingService) Update(key, value string) error {
if key == constant.FirewallPortWhiteList {
return newFirewallService().updatePortWhitelist(context.Background(), value)
}
return settingRepo.UpdateOrCreate(key, value)
}

View File

@@ -116,6 +116,16 @@
"formatZH": "删除告警配置 [id]",
"formatEN": "delete alert config [id]"
},
"/alert/config/status": {
"bodyKeys": [
"id",
"status"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "更新告警配置状态 [id][status]",
"formatEN": "update alert config status [id][status]"
},
"/alert/config/update": {
"bodyKeys": [
"id",
@@ -1745,262 +1755,6 @@
"formatZH": "更新 [name]",
"formatEN": "update user [name]"
},
"/core/xpack/vms": {
"bodyKeys": [
"name"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "创建虚拟机 [name]",
"formatEN": "create VM [name]"
},
"/core/xpack/vms/del": {
"bodyKeys": [
"name"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "删除虚拟机 [name]",
"formatEN": "delete VM [name]"
},
"/core/xpack/vms/environment/enable": {
"bodyKeys": [],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "启用虚拟机运行环境",
"formatEN": "enable VM runtime environment"
},
"/core/xpack/vms/iso": {
"bodyKeys": [
"name",
"type"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "创建虚拟机镜像 [name] [type]",
"formatEN": "create VM ISO [name] [type]"
},
"/core/xpack/vms/iso/del": {
"bodyKeys": [
"id"
],
"paramKeys": [],
"beforeFunctions": [
{
"input_column": "id",
"input_value": "id",
"isList": false,
"db": "vm_isos",
"output_column": "name",
"output_value": "name"
}
],
"formatZH": "删除虚拟机镜像 [name]",
"formatEN": "delete VM ISO [name]"
},
"/core/xpack/vms/iso/update": {
"bodyKeys": [
"name",
"type"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "更新虚拟机镜像 [name] [type]",
"formatEN": "update VM ISO [name] [type]"
},
"/core/xpack/vms/networks": {
"bodyKeys": [
"name",
"type"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "创建虚拟机网络 [name] [type]",
"formatEN": "create VM network [name] [type]"
},
"/core/xpack/vms/networks/del": {
"bodyKeys": [
"id"
],
"paramKeys": [],
"beforeFunctions": [
{
"input_column": "id",
"input_value": "id",
"isList": false,
"db": "vm_networks",
"output_column": "name",
"output_value": "name"
}
],
"formatZH": "删除虚拟机网络 [name]",
"formatEN": "delete VM network [name]"
},
"/core/xpack/vms/networks/update": {
"bodyKeys": [
"id"
],
"paramKeys": [],
"beforeFunctions": [
{
"input_column": "id",
"input_value": "id",
"isList": false,
"db": "vm_networks",
"output_column": "name",
"output_value": "name"
}
],
"formatZH": "更新虚拟机网络 [name]",
"formatEN": "update VM network [name]"
},
"/core/xpack/vms/operate": {
"bodyKeys": [
"name",
"operate"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "操作虚拟机 [name] [operate]",
"formatEN": "operate VM [name] [operate]"
},
"/core/xpack/vms/orphans/clean": {
"bodyKeys": [],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "清理虚拟机孤立数据",
"formatEN": "clean VM orphaned data"
},
"/core/xpack/vms/rename": {
"bodyKeys": [
"name",
"newName"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "重命名虚拟机 [name] 为 [newName]",
"formatEN": "rename VM [name] to [newName]"
},
"/core/xpack/vms/snapshots": {
"bodyKeys": [
"name",
"snapshotName"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "创建虚拟机 [name] 快照 [snapshotName]",
"formatEN": "create VM [name] snapshot [snapshotName]"
},
"/core/xpack/vms/snapshots/del": {
"bodyKeys": [
"name",
"snapshotName"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "删除虚拟机 [name] 快照 [snapshotName]",
"formatEN": "delete VM [name] snapshot [snapshotName]"
},
"/core/xpack/vms/snapshots/recover": {
"bodyKeys": [
"name",
"snapshotName"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "恢复虚拟机 [name] 快照 [snapshotName]",
"formatEN": "recover VM [name] snapshot [snapshotName]"
},
"/core/xpack/vms/storages": {
"bodyKeys": [
"name",
"type"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "创建虚拟机存储 [name] [type]",
"formatEN": "create VM storage [name] [type]"
},
"/core/xpack/vms/storages/del": {
"bodyKeys": [
"id"
],
"paramKeys": [],
"beforeFunctions": [
{
"input_column": "id",
"input_value": "id",
"isList": false,
"db": "vm_storages",
"output_column": "name",
"output_value": "name"
}
],
"formatZH": "删除虚拟机存储 [name]",
"formatEN": "delete VM storage [name]"
},
"/core/xpack/vms/storages/update": {
"bodyKeys": [
"id"
],
"paramKeys": [],
"beforeFunctions": [
{
"input_column": "id",
"input_value": "id",
"isList": false,
"db": "vm_storages",
"output_column": "name",
"output_value": "name"
}
],
"formatZH": "更新虚拟机存储 [name]",
"formatEN": "update VM storage [name]"
},
"/core/xpack/vms/sync": {
"bodyKeys": [],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "同步虚拟机",
"formatEN": "sync virtual machines"
},
"/core/xpack/vms/templates": {
"bodyKeys": [
"name",
"templateName"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "创建虚拟机 [name] 模板 [templateName]",
"formatEN": "create VM [name] template [templateName]"
},
"/core/xpack/vms/templates/del": {
"bodyKeys": [
"id"
],
"paramKeys": [],
"beforeFunctions": [
{
"input_column": "id",
"input_value": "id",
"isList": false,
"db": "vm_templates",
"output_column": "name",
"output_value": "name"
}
],
"formatZH": "删除虚拟机模板 [name]",
"formatEN": "delete VM template [name]"
},
"/core/xpack/vms/update": {
"bodyKeys": [
"name"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "更新虚拟机 [name]",
"formatEN": "update VM [name]"
},
"/core/groups": {
"bodyKeys": [
"name",
@@ -2705,6 +2459,262 @@
"formatZH": "同步 SSL 证书 [primaryDomain]",
"formatEN": "sync SSL certificate [primaryDomain]"
},
"/core/xpack/vms": {
"bodyKeys": [
"name"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "创建虚拟机 [name]",
"formatEN": "create VM [name]"
},
"/core/xpack/vms/del": {
"bodyKeys": [
"name"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "删除虚拟机 [name]",
"formatEN": "delete VM [name]"
},
"/core/xpack/vms/environment/enable": {
"bodyKeys": [],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "启用虚拟机运行环境",
"formatEN": "enable VM runtime environment"
},
"/core/xpack/vms/iso": {
"bodyKeys": [
"name",
"type"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "创建虚拟机镜像 [name] [type]",
"formatEN": "create VM ISO [name] [type]"
},
"/core/xpack/vms/iso/del": {
"bodyKeys": [
"id"
],
"paramKeys": [],
"beforeFunctions": [
{
"input_column": "id",
"input_value": "id",
"isList": false,
"db": "vm_isos",
"output_column": "name",
"output_value": "name"
}
],
"formatZH": "删除虚拟机镜像 [name]",
"formatEN": "delete VM ISO [name]"
},
"/core/xpack/vms/iso/update": {
"bodyKeys": [
"name",
"type"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "更新虚拟机镜像 [name] [type]",
"formatEN": "update VM ISO [name] [type]"
},
"/core/xpack/vms/networks": {
"bodyKeys": [
"name",
"type"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "创建虚拟机网络 [name] [type]",
"formatEN": "create VM network [name] [type]"
},
"/core/xpack/vms/networks/del": {
"bodyKeys": [
"id"
],
"paramKeys": [],
"beforeFunctions": [
{
"input_column": "id",
"input_value": "id",
"isList": false,
"db": "vm_networks",
"output_column": "name",
"output_value": "name"
}
],
"formatZH": "删除虚拟机网络 [name]",
"formatEN": "delete VM network [name]"
},
"/core/xpack/vms/networks/update": {
"bodyKeys": [
"id"
],
"paramKeys": [],
"beforeFunctions": [
{
"input_column": "id",
"input_value": "id",
"isList": false,
"db": "vm_networks",
"output_column": "name",
"output_value": "name"
}
],
"formatZH": "更新虚拟机网络 [name]",
"formatEN": "update VM network [name]"
},
"/core/xpack/vms/operate": {
"bodyKeys": [
"name",
"operate"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "操作虚拟机 [name] [operate]",
"formatEN": "operate VM [name] [operate]"
},
"/core/xpack/vms/orphans/clean": {
"bodyKeys": [],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "清理虚拟机孤立数据",
"formatEN": "clean VM orphaned data"
},
"/core/xpack/vms/rename": {
"bodyKeys": [
"name",
"newName"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "重命名虚拟机 [name] 为 [newName]",
"formatEN": "rename VM [name] to [newName]"
},
"/core/xpack/vms/snapshots": {
"bodyKeys": [
"name",
"snapshotName"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "创建虚拟机 [name] 快照 [snapshotName]",
"formatEN": "create VM [name] snapshot [snapshotName]"
},
"/core/xpack/vms/snapshots/del": {
"bodyKeys": [
"name",
"snapshotName"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "删除虚拟机 [name] 快照 [snapshotName]",
"formatEN": "delete VM [name] snapshot [snapshotName]"
},
"/core/xpack/vms/snapshots/recover": {
"bodyKeys": [
"name",
"snapshotName"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "恢复虚拟机 [name] 快照 [snapshotName]",
"formatEN": "recover VM [name] snapshot [snapshotName]"
},
"/core/xpack/vms/storages": {
"bodyKeys": [
"name",
"type"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "创建虚拟机存储 [name] [type]",
"formatEN": "create VM storage [name] [type]"
},
"/core/xpack/vms/storages/del": {
"bodyKeys": [
"id"
],
"paramKeys": [],
"beforeFunctions": [
{
"input_column": "id",
"input_value": "id",
"isList": false,
"db": "vm_storages",
"output_column": "name",
"output_value": "name"
}
],
"formatZH": "删除虚拟机存储 [name]",
"formatEN": "delete VM storage [name]"
},
"/core/xpack/vms/storages/update": {
"bodyKeys": [
"id"
],
"paramKeys": [],
"beforeFunctions": [
{
"input_column": "id",
"input_value": "id",
"isList": false,
"db": "vm_storages",
"output_column": "name",
"output_value": "name"
}
],
"formatZH": "更新虚拟机存储 [name]",
"formatEN": "update VM storage [name]"
},
"/core/xpack/vms/sync": {
"bodyKeys": [],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "同步虚拟机",
"formatEN": "sync virtual machines"
},
"/core/xpack/vms/templates": {
"bodyKeys": [
"name",
"templateName"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "创建虚拟机 [name] 模板 [templateName]",
"formatEN": "create VM [name] template [templateName]"
},
"/core/xpack/vms/templates/del": {
"bodyKeys": [
"id"
],
"paramKeys": [],
"beforeFunctions": [
{
"input_column": "id",
"input_value": "id",
"isList": false,
"db": "vm_templates",
"output_column": "name",
"output_value": "name"
}
],
"formatZH": "删除虚拟机模板 [name]",
"formatEN": "delete VM template [name]"
},
"/core/xpack/vms/update": {
"bodyKeys": [
"name"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "更新虚拟机 [name]",
"formatEN": "update VM [name]"
},
"/cronjobs": {
"bodyKeys": [
"type",
@@ -3504,7 +3514,9 @@
"formatEN": "Download url =\u003e [path]/[name]"
},
"/files/wget/process/remove": {
"bodyKeys": ["keys"],
"bodyKeys": [
"keys"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "移除已结束下载记录 [keys]",
@@ -3634,13 +3646,11 @@
"formatEN": "[operation] Docker port guard"
},
"/hosts/firewall/docker/policies/batch": {
"bodyKeys": [
"mode"
],
"bodyKeys": [],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "批量更新 Docker 端口防护策略 [mode]",
"formatEN": "batch update Docker port guard policies [mode]"
"formatZH": "批量更新 Docker 端口防护策略",
"formatEN": "batch update Docker port guard policies"
},
"/hosts/firewall/docker/policies/delete/batch": {
"bodyKeys": [
@@ -3697,6 +3707,13 @@
"formatZH": "添加防火墙规则",
"formatEN": "create firewall rules"
},
"/hosts/firewall/rules/adopt": {
"bodyKeys": [],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "纳管防火墙规则",
"formatEN": "adopt firewall rule"
},
"/hosts/firewall/rules/delete": {
"bodyKeys": [],
"paramKeys": [],
@@ -3751,6 +3768,15 @@
"formatZH": "防火墙子系统 [subsystem] 后端 [operation] [backend]",
"formatEN": "[operation] firewall [subsystem] backend [backend]"
},
"/hosts/firewall/settings/whitelist": {
"bodyKeys": [
"value"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "更新防火墙端口白名单 [value]",
"formatEN": "update firewall port whitelist [value]"
},
"/hosts/monitor/clean": {
"bodyKeys": [],
"paramKeys": [],
@@ -5794,4 +5820,4 @@
"formatZH": "从主节点同步设置",
"formatEN": "sync settings from master"
}
}
}

View File

@@ -705,3 +705,11 @@ AIProviderBailian: 'Alibaba Cloud Model Studio'
AIProviderBailianCodingPlan: 'Alibaba Cloud Model Studio Coding Plan'
AIProviderArk: 'Volcengine Ark'
AIProviderArkCodingPlan: 'Volcengine Ark Coding Plan'
# Firewall whitelist
ErrFirewallRuleScopeChange: "The current firewall does not support changing a rule's scope (such as its IPv4/IPv6 address family). Please create a new rule."
FirewallWhitelistTask: "Update firewall port whitelist"
FirewallWhitelistSaved: "Whitelist configuration saved"
FirewallWhitelistDeferred: "{{ .name }}: configuration saved; pending firewall activation"
FirewallWhitelistReleased: "{{ .name }}: whitelist protection released; allow rule retained. To close the port, delete the rule manually from the rule list"
FirewallWhitelistRequired: "{{ .name }}: protected by mandatory system port rules"

View File

@@ -705,3 +705,11 @@ AIProviderBailian: 'Alibaba Cloud Model Studio'
AIProviderBailianCodingPlan: 'Alibaba Cloud Model Studio Coding Plan'
AIProviderArk: 'Volcengine Ark'
AIProviderArkCodingPlan: 'Volcengine Ark Coding Plan'
# Firewall whitelist
ErrFirewallRuleScopeChange: "El cortafuegos actual no permite cambiar el ámbito de una regla (como su familia de direcciones IPv4/IPv6). Cree una regla nueva."
FirewallWhitelistTask: "Actualizar la lista de puertos permitidos del cortafuegos"
FirewallWhitelistSaved: "Configuración de la lista de permitidos guardada"
FirewallWhitelistDeferred: "{{ .name }}: configuración guardada; pendiente de activar el cortafuegos"
FirewallWhitelistReleased: "{{ .name }}: protección de la lista de permitidos retirada; se conserva la regla de permiso. Para cerrar el puerto, elimine la regla manualmente de la lista"
FirewallWhitelistRequired: "{{ .name }}: protegido por las reglas de puertos obligatorios del sistema"

View File

@@ -705,3 +705,11 @@ AIProviderBailian: 'Alibaba Cloud Model Studio'
AIProviderBailianCodingPlan: 'Alibaba Cloud Model Studio Coding Plan'
AIProviderArk: 'Volcengine Ark'
AIProviderArkCodingPlan: 'Volcengine Ark Coding Plan'
# Firewall whitelist
ErrFirewallRuleScopeChange: "فایروال فعلی از تغییر محدودهٔ قانون (مانند خانوادهٔ آدرس IPv4/IPv6) پشتیبانی نمی‌کند. لطفاً یک قانون جدید ایجاد کنید."
FirewallWhitelistTask: "به‌روزرسانی فهرست پورت‌های مجاز فایروال"
FirewallWhitelistSaved: "پیکربندی فهرست مجاز ذخیره شد"
FirewallWhitelistDeferred: "{{ .name }}: پیکربندی ذخیره شد؛ در انتظار فعال‌سازی فایروال"
FirewallWhitelistReleased: "{{ .name }}: حفاظت فهرست مجاز برداشته شد؛ قانون اجازه حفظ می‌شود. برای بستن پورت، قانون را به‌صورت دستی از فهرست قوانین حذف کنید"
FirewallWhitelistRequired: "{{ .name }}: توسط قوانین پورت‌های ضروری سیستم محافظت می‌شود"

View File

@@ -705,3 +705,11 @@ AIProviderBailian: 'Alibaba Cloud Model Studio'
AIProviderBailianCodingPlan: 'Alibaba Cloud Model Studio Coding Plan'
AIProviderArk: 'Volcengine Ark'
AIProviderArkCodingPlan: 'Volcengine Ark Coding Plan'
# Firewall whitelist
ErrFirewallRuleScopeChange: "現在のファイアウォールでは、ルールの適用範囲IPv4/IPv6 アドレスファミリーなど)を変更できません。新しいルールを作成してください。"
FirewallWhitelistTask: "ファイアウォールのポート許可リストを更新"
FirewallWhitelistSaved: "許可リストの設定を保存しました"
FirewallWhitelistDeferred: "{{ .name }}:設定を保存しました。ファイアウォールの有効化後に適用します"
FirewallWhitelistReleased: "{{ .name }}:許可リストの保護を解除しました。許可ルールは保持されます。ポートを閉じるには、ルール一覧から手動で削除してください"
FirewallWhitelistRequired: "{{ .name }}:システム必須ポートのルールで保護されています"

View File

@@ -705,3 +705,11 @@ AIProviderBailian: 'Alibaba Cloud Model Studio'
AIProviderBailianCodingPlan: 'Alibaba Cloud Model Studio Coding Plan'
AIProviderArk: 'Volcengine Ark'
AIProviderArkCodingPlan: 'Volcengine Ark Coding Plan'
# Firewall whitelist
ErrFirewallRuleScopeChange: "현재 방화벽에서는 규칙의 적용 범위(예: IPv4/IPv6 주소 패밀리)를 변경할 수 없습니다. 새 규칙을 생성하세요."
FirewallWhitelistTask: "방화벽 포트 허용 목록 업데이트"
FirewallWhitelistSaved: "허용 목록 설정이 저장되었습니다"
FirewallWhitelistDeferred: "{{ .name }}: 설정이 저장되었으며 방화벽 활성화 후 적용됩니다"
FirewallWhitelistReleased: "{{ .name }}: 허용 목록 보호가 해제되었으며 허용 규칙은 유지됩니다. 포트를 닫으려면 규칙 목록에서 수동으로 삭제하세요"
FirewallWhitelistRequired: "{{ .name }}: 시스템 필수 포트 규칙으로 보호됩니다"

View File

@@ -696,3 +696,11 @@ AIProviderBailian: 'Alibaba Cloud Model Studio'
AIProviderBailianCodingPlan: 'Alibaba Cloud Model Studio Coding Plan'
AIProviderArk: 'Volcengine Ark'
AIProviderArkCodingPlan: 'Volcengine Ark Coding Plan'
# Firewall whitelist
ErrFirewallRuleScopeChange: "ໄຟວໍປັດຈຸບັນບໍ່ຮອງຮັບການປ່ຽນຂອບເຂດຂອງກົດ (ເຊັ່ນ ຕະກູນທີ່ຢູ່ IPv4/IPv6). ກະລຸນາສ້າງກົດໃໝ່."
FirewallWhitelistTask: "ອັບເດດລາຍຊື່ພອດທີ່ອະນຸຍາດຂອງໄຟວໍ"
FirewallWhitelistSaved: "ບັນທຶກການຕັ້ງຄ່າລາຍຊື່ທີ່ອະນຸຍາດແລ້ວ"
FirewallWhitelistDeferred: "{{ .name }}: ບັນທຶກການຕັ້ງຄ່າແລ້ວ; ລໍຖ້າເປີດໃຊ້ໄຟວໍ"
FirewallWhitelistReleased: "{{ .name }}: ຍົກເລີກການປ້ອງກັນລາຍຊື່ທີ່ອະນຸຍາດແລ້ວ; ຍັງຄົງກົດອະນຸຍາດໄວ້. ຫາກຕ້ອງການປິດພອດ ໃຫ້ລຶບກົດດ້ວຍຕົນເອງຈາກລາຍການກົດ"
FirewallWhitelistRequired: "{{ .name }}: ປ້ອງກັນໂດຍກົດພອດທີ່ຈຳເປັນຂອງລະບົບ"

View File

@@ -705,3 +705,11 @@ AIProviderBailian: 'Alibaba Cloud Model Studio'
AIProviderBailianCodingPlan: 'Alibaba Cloud Model Studio Coding Plan'
AIProviderArk: 'Volcengine Ark'
AIProviderArkCodingPlan: 'Volcengine Ark Coding Plan'
# Firewall whitelist
ErrFirewallRuleScopeChange: "Tembok api semasa tidak menyokong perubahan skop peraturan (seperti keluarga alamat IPv4/IPv6). Sila cipta peraturan baharu."
FirewallWhitelistTask: "Kemas kini senarai port dibenarkan tembok api"
FirewallWhitelistSaved: "Konfigurasi senarai dibenarkan telah disimpan"
FirewallWhitelistDeferred: "{{ .name }}: konfigurasi disimpan; menunggu pengaktifan tembok api"
FirewallWhitelistReleased: "{{ .name }}: perlindungan senarai dibenarkan telah dilepaskan; peraturan izin dikekalkan. Untuk menutup port, padamkan peraturan secara manual daripada senarai peraturan"
FirewallWhitelistRequired: "{{ .name }}: dilindungi oleh peraturan port wajib sistem"

View File

@@ -705,3 +705,11 @@ AIProviderBailian: 'Alibaba Cloud Model Studio'
AIProviderBailianCodingPlan: 'Alibaba Cloud Model Studio Coding Plan'
AIProviderArk: 'Volcengine Ark'
AIProviderArkCodingPlan: 'Volcengine Ark Coding Plan'
# Firewall whitelist
ErrFirewallRuleScopeChange: "O firewall atual não permite alterar o escopo de uma regra (como a família de endereços IPv4/IPv6). Crie uma nova regra."
FirewallWhitelistTask: "Atualizar a lista de portas permitidas do firewall"
FirewallWhitelistSaved: "Configuração da lista de permissões salva"
FirewallWhitelistDeferred: "{{ .name }}: configuração salva; aguardando a ativação do firewall"
FirewallWhitelistReleased: "{{ .name }}: proteção da lista de permissões removida; regra de permissão mantida. Para fechar a porta, exclua a regra manualmente da lista"
FirewallWhitelistRequired: "{{ .name }}: protegido pelas regras de portas obrigatórias do sistema"

View File

@@ -705,3 +705,11 @@ AIProviderBailian: 'Alibaba Cloud Model Studio'
AIProviderBailianCodingPlan: 'Alibaba Cloud Model Studio Coding Plan'
AIProviderArk: 'Volcengine Ark'
AIProviderArkCodingPlan: 'Volcengine Ark Coding Plan'
# Firewall whitelist
ErrFirewallRuleScopeChange: "Текущий межсетевой экран не поддерживает изменение области действия правила (например, семейства адресов IPv4/IPv6). Создайте новое правило."
FirewallWhitelistTask: "Обновить список разрешённых портов межсетевого экрана"
FirewallWhitelistSaved: "Настройки списка разрешённых портов сохранены"
FirewallWhitelistDeferred: "{{ .name }}: настройки сохранены; ожидается включение межсетевого экрана"
FirewallWhitelistReleased: "{{ .name }}: защита списка разрешённых портов снята; разрешающее правило сохранено. Чтобы закрыть порт, удалите правило вручную из списка правил"
FirewallWhitelistRequired: "{{ .name }}: защищён обязательными правилами системных портов"

View File

@@ -705,3 +705,11 @@ AIProviderBailian: 'Alibaba Cloud Model Studio'
AIProviderBailianCodingPlan: 'Alibaba Cloud Model Studio Coding Plan'
AIProviderArk: 'Volcengine Ark'
AIProviderArkCodingPlan: 'Volcengine Ark Coding Plan'
# Firewall whitelist
ErrFirewallRuleScopeChange: "Mevcut güvenlik duvarı, kuralın kapsamını (IPv4/IPv6 adres ailesi gibi) değiştirmeyi desteklemiyor. Lütfen yeni bir kural oluşturun."
FirewallWhitelistTask: "Güvenlik duvarı izin verilen port listesini güncelle"
FirewallWhitelistSaved: "İzin verilenler listesi yapılandırması kaydedildi"
FirewallWhitelistDeferred: "{{ .name }}: yapılandırma kaydedildi; güvenlik duvarının etkinleştirilmesi bekleniyor"
FirewallWhitelistReleased: "{{ .name }}: izin listesi koruması kaldırıldı; izin kuralı korundu. Portu kapatmak için kuralı kural listesinden elle silin"
FirewallWhitelistRequired: "{{ .name }}: zorunlu sistem portu kuralları tarafından korunuyor"

View File

@@ -705,3 +705,11 @@ AIProviderBailian: '阿里雲百煉'
AIProviderBailianCodingPlan: '阿里雲百煉 Coding Plan'
AIProviderArk: '火山方舟'
AIProviderArkCodingPlan: '火山方舟 Coding Plan'
# Firewall whitelist
ErrFirewallRuleScopeChange: "目前的防火牆不支援修改規則的作用範圍(如 IPv4/IPv6 位址族),請建立新規則。"
FirewallWhitelistTask: "更新防火牆連接埠白名單"
FirewallWhitelistSaved: "白名單設定已儲存"
FirewallWhitelistDeferred: "{{ .name }}:設定已儲存,待防火牆啟用後套用"
FirewallWhitelistReleased: "{{ .name }}:已解除白名單保護,放行規則保留;如需關閉連接埠,請在規則清單手動刪除"
FirewallWhitelistRequired: "{{ .name }}:由系統必要連接埠規則保護"

View File

@@ -705,3 +705,11 @@ AIProviderBailian: "阿里云百炼"
AIProviderBailianCodingPlan: "阿里云百炼 Coding Plan"
AIProviderArk: "火山方舟"
AIProviderArkCodingPlan: "火山方舟 Coding Plan"
# Firewall whitelist
ErrFirewallRuleScopeChange: "当前防火墙不支持修改规则的作用范围(如 IPv4/IPv6 地址族),请新建规则。"
FirewallWhitelistTask: "更新防火墙端口白名单"
FirewallWhitelistSaved: "白名单配置已保存"
FirewallWhitelistDeferred: "{{ .name }}:配置已保存,待防火墙启用后下发"
FirewallWhitelistReleased: "{{ .name }}:已解除白名单保护,放行规则保留;如需关闭端口,请在规则列表手动删除"
FirewallWhitelistRequired: "{{ .name }}:由系统必需端口规则保护"

View File

@@ -27,6 +27,7 @@ func (s *HostRouter) InitRouter(Router *gin.RouterGroup) {
hostRouter.POST("/firewall/port", baseApi.UpdatePanelFirewallPort)
hostRouter.GET("/firewall/settings", baseApi.LoadFirewallSettings)
hostRouter.POST("/firewall/settings/operate", baseApi.OperateFirewallBackend)
hostRouter.POST("/firewall/settings/whitelist", baseApi.UpdateFirewallPortWhitelist)
hostRouter.POST("/firewall/forward/base", baseApi.LoadForwardingBaseInfo)
hostRouter.POST("/firewall/forward/search", baseApi.SearchForwardingRules)
hostRouter.POST("/firewall/forward/operate", baseApi.OperateForwardingRules)

View File

@@ -108,12 +108,13 @@ type ScopeNotice struct {
}
var (
ErrInvalidScope = errors.New("invalid firewall scope")
ErrUnsupportedScope = errors.New("unsupported firewall scope")
ErrInvalidRule = errors.New("invalid firewall rule")
ErrProtectedRule = errors.New("protected firewall rule cannot be modified")
ErrCompositeRule = errors.New("firewall rule must be atomic")
ErrExpansionLimit = errors.New("firewall rule expansion limit exceeded")
ErrInvalidScope = errors.New("invalid firewall scope")
ErrUnsupportedScope = errors.New("unsupported firewall scope")
ErrManagedScopeChange = fmt.Errorf("%w: managed rule scope cannot be changed", ErrUnsupportedScope)
ErrInvalidRule = errors.New("invalid firewall rule")
ErrProtectedRule = errors.New("protected firewall rule cannot be modified")
ErrCompositeRule = errors.New("firewall rule must be atomic")
ErrExpansionLimit = errors.New("firewall rule expansion limit exceeded")
)
type Scope struct {

View File

@@ -336,7 +336,7 @@ func compileChange(snapshot filter.Snapshot, change filter.DesiredChange) (filte
return filter.NativeRulePlan{}, fmt.Errorf("%w: create target is out of range", filter.ErrInvalidRule)
}
command := insertCommand(position, normalized, marker)
if change.Append {
if change.Append || position == maximumObservedPosition(snapshot)+1 {
command = commentCommand(normalized, marker)
plan.Expected.Locator.NativeID = ""
plan.Expected.Locator.Position = nil

View File

@@ -19413,6 +19413,54 @@ const docTemplate = `{
}
}
},
"/hosts/firewall/settings/whitelist": {
"post": {
"consumes": [
"application/json"
],
"description": "Returns a taskID; configuration save and per-rule results are recorded in the task log.",
"parameters": [
{
"description": "request",
"in": "body",
"name": "request",
"required": true,
"schema": {
"$ref": "#/definitions/dto.FirewallPortWhitelistUpdate"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/dto.FilterChainOperationResponse"
}
}
},
"security": [
{
"ApiKeyAuth": []
},
{
"Timestamp": []
}
],
"summary": "Queue firewall port whitelist update",
"tags": [
"Firewall"
],
"x-panel-log": {
"BeforeFunctions": [],
"bodyKeys": [
"value"
],
"formatEN": "update firewall port whitelist [value]",
"formatZH": "更新防火墙端口白名单 [value]",
"paramKeys": []
}
}
},
"/hosts/info": {
"post": {
"consumes": [
@@ -31901,8 +31949,7 @@ const docTemplate = `{
"enum": [
"SystemIP",
"DockerSockPath",
"FileRecycleBin",
"FirewallPortWhiteList"
"FileRecycleBin"
],
"type": "string"
},
@@ -36405,6 +36452,17 @@ const docTemplate = `{
],
"type": "object"
},
"dto.FirewallPortWhitelistUpdate": {
"properties": {
"value": {
"type": "string"
}
},
"required": [
"value"
],
"type": "object"
},
"dto.FirewallSettings": {
"properties": {
"docker": {

View File

@@ -19409,6 +19409,54 @@
}
}
},
"/hosts/firewall/settings/whitelist": {
"post": {
"consumes": [
"application/json"
],
"description": "Returns a taskID; configuration save and per-rule results are recorded in the task log.",
"parameters": [
{
"description": "request",
"in": "body",
"name": "request",
"required": true,
"schema": {
"$ref": "#/definitions/dto.FirewallPortWhitelistUpdate"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/dto.FilterChainOperationResponse"
}
}
},
"security": [
{
"ApiKeyAuth": []
},
{
"Timestamp": []
}
],
"summary": "Queue firewall port whitelist update",
"tags": [
"Firewall"
],
"x-panel-log": {
"BeforeFunctions": [],
"bodyKeys": [
"value"
],
"formatEN": "update firewall port whitelist [value]",
"formatZH": "更新防火墙端口白名单 [value]",
"paramKeys": []
}
}
},
"/hosts/info": {
"post": {
"consumes": [
@@ -31897,8 +31945,7 @@
"enum": [
"SystemIP",
"DockerSockPath",
"FileRecycleBin",
"FirewallPortWhiteList"
"FileRecycleBin"
],
"type": "string"
},
@@ -36401,6 +36448,17 @@
],
"type": "object"
},
"dto.FirewallPortWhitelistUpdate": {
"properties": {
"value": {
"type": "string"
}
},
"required": [
"value"
],
"type": "object"
},
"dto.FirewallSettings": {
"properties": {
"docker": {

View File

@@ -116,6 +116,16 @@
"formatZH": "删除告警配置 [id]",
"formatEN": "delete alert config [id]"
},
"/alert/config/status": {
"bodyKeys": [
"id",
"status"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "更新告警配置状态 [id][status]",
"formatEN": "update alert config status [id][status]"
},
"/alert/config/update": {
"bodyKeys": [
"id",
@@ -1745,262 +1755,6 @@
"formatZH": "更新 [name]",
"formatEN": "update user [name]"
},
"/core/xpack/vms": {
"bodyKeys": [
"name"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "创建虚拟机 [name]",
"formatEN": "create VM [name]"
},
"/core/xpack/vms/del": {
"bodyKeys": [
"name"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "删除虚拟机 [name]",
"formatEN": "delete VM [name]"
},
"/core/xpack/vms/environment/enable": {
"bodyKeys": [],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "启用虚拟机运行环境",
"formatEN": "enable VM runtime environment"
},
"/core/xpack/vms/iso": {
"bodyKeys": [
"name",
"type"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "创建虚拟机镜像 [name] [type]",
"formatEN": "create VM ISO [name] [type]"
},
"/core/xpack/vms/iso/del": {
"bodyKeys": [
"id"
],
"paramKeys": [],
"beforeFunctions": [
{
"input_column": "id",
"input_value": "id",
"isList": false,
"db": "vm_isos",
"output_column": "name",
"output_value": "name"
}
],
"formatZH": "删除虚拟机镜像 [name]",
"formatEN": "delete VM ISO [name]"
},
"/core/xpack/vms/iso/update": {
"bodyKeys": [
"name",
"type"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "更新虚拟机镜像 [name] [type]",
"formatEN": "update VM ISO [name] [type]"
},
"/core/xpack/vms/networks": {
"bodyKeys": [
"name",
"type"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "创建虚拟机网络 [name] [type]",
"formatEN": "create VM network [name] [type]"
},
"/core/xpack/vms/networks/del": {
"bodyKeys": [
"id"
],
"paramKeys": [],
"beforeFunctions": [
{
"input_column": "id",
"input_value": "id",
"isList": false,
"db": "vm_networks",
"output_column": "name",
"output_value": "name"
}
],
"formatZH": "删除虚拟机网络 [name]",
"formatEN": "delete VM network [name]"
},
"/core/xpack/vms/networks/update": {
"bodyKeys": [
"id"
],
"paramKeys": [],
"beforeFunctions": [
{
"input_column": "id",
"input_value": "id",
"isList": false,
"db": "vm_networks",
"output_column": "name",
"output_value": "name"
}
],
"formatZH": "更新虚拟机网络 [name]",
"formatEN": "update VM network [name]"
},
"/core/xpack/vms/operate": {
"bodyKeys": [
"name",
"operate"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "操作虚拟机 [name] [operate]",
"formatEN": "operate VM [name] [operate]"
},
"/core/xpack/vms/orphans/clean": {
"bodyKeys": [],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "清理虚拟机孤立数据",
"formatEN": "clean VM orphaned data"
},
"/core/xpack/vms/rename": {
"bodyKeys": [
"name",
"newName"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "重命名虚拟机 [name] 为 [newName]",
"formatEN": "rename VM [name] to [newName]"
},
"/core/xpack/vms/snapshots": {
"bodyKeys": [
"name",
"snapshotName"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "创建虚拟机 [name] 快照 [snapshotName]",
"formatEN": "create VM [name] snapshot [snapshotName]"
},
"/core/xpack/vms/snapshots/del": {
"bodyKeys": [
"name",
"snapshotName"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "删除虚拟机 [name] 快照 [snapshotName]",
"formatEN": "delete VM [name] snapshot [snapshotName]"
},
"/core/xpack/vms/snapshots/recover": {
"bodyKeys": [
"name",
"snapshotName"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "恢复虚拟机 [name] 快照 [snapshotName]",
"formatEN": "recover VM [name] snapshot [snapshotName]"
},
"/core/xpack/vms/storages": {
"bodyKeys": [
"name",
"type"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "创建虚拟机存储 [name] [type]",
"formatEN": "create VM storage [name] [type]"
},
"/core/xpack/vms/storages/del": {
"bodyKeys": [
"id"
],
"paramKeys": [],
"beforeFunctions": [
{
"input_column": "id",
"input_value": "id",
"isList": false,
"db": "vm_storages",
"output_column": "name",
"output_value": "name"
}
],
"formatZH": "删除虚拟机存储 [name]",
"formatEN": "delete VM storage [name]"
},
"/core/xpack/vms/storages/update": {
"bodyKeys": [
"id"
],
"paramKeys": [],
"beforeFunctions": [
{
"input_column": "id",
"input_value": "id",
"isList": false,
"db": "vm_storages",
"output_column": "name",
"output_value": "name"
}
],
"formatZH": "更新虚拟机存储 [name]",
"formatEN": "update VM storage [name]"
},
"/core/xpack/vms/sync": {
"bodyKeys": [],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "同步虚拟机",
"formatEN": "sync virtual machines"
},
"/core/xpack/vms/templates": {
"bodyKeys": [
"name",
"templateName"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "创建虚拟机 [name] 模板 [templateName]",
"formatEN": "create VM [name] template [templateName]"
},
"/core/xpack/vms/templates/del": {
"bodyKeys": [
"id"
],
"paramKeys": [],
"beforeFunctions": [
{
"input_column": "id",
"input_value": "id",
"isList": false,
"db": "vm_templates",
"output_column": "name",
"output_value": "name"
}
],
"formatZH": "删除虚拟机模板 [name]",
"formatEN": "delete VM template [name]"
},
"/core/xpack/vms/update": {
"bodyKeys": [
"name"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "更新虚拟机 [name]",
"formatEN": "update VM [name]"
},
"/core/groups": {
"bodyKeys": [
"name",
@@ -2705,6 +2459,262 @@
"formatZH": "同步 SSL 证书 [primaryDomain]",
"formatEN": "sync SSL certificate [primaryDomain]"
},
"/core/xpack/vms": {
"bodyKeys": [
"name"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "创建虚拟机 [name]",
"formatEN": "create VM [name]"
},
"/core/xpack/vms/del": {
"bodyKeys": [
"name"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "删除虚拟机 [name]",
"formatEN": "delete VM [name]"
},
"/core/xpack/vms/environment/enable": {
"bodyKeys": [],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "启用虚拟机运行环境",
"formatEN": "enable VM runtime environment"
},
"/core/xpack/vms/iso": {
"bodyKeys": [
"name",
"type"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "创建虚拟机镜像 [name] [type]",
"formatEN": "create VM ISO [name] [type]"
},
"/core/xpack/vms/iso/del": {
"bodyKeys": [
"id"
],
"paramKeys": [],
"beforeFunctions": [
{
"input_column": "id",
"input_value": "id",
"isList": false,
"db": "vm_isos",
"output_column": "name",
"output_value": "name"
}
],
"formatZH": "删除虚拟机镜像 [name]",
"formatEN": "delete VM ISO [name]"
},
"/core/xpack/vms/iso/update": {
"bodyKeys": [
"name",
"type"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "更新虚拟机镜像 [name] [type]",
"formatEN": "update VM ISO [name] [type]"
},
"/core/xpack/vms/networks": {
"bodyKeys": [
"name",
"type"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "创建虚拟机网络 [name] [type]",
"formatEN": "create VM network [name] [type]"
},
"/core/xpack/vms/networks/del": {
"bodyKeys": [
"id"
],
"paramKeys": [],
"beforeFunctions": [
{
"input_column": "id",
"input_value": "id",
"isList": false,
"db": "vm_networks",
"output_column": "name",
"output_value": "name"
}
],
"formatZH": "删除虚拟机网络 [name]",
"formatEN": "delete VM network [name]"
},
"/core/xpack/vms/networks/update": {
"bodyKeys": [
"id"
],
"paramKeys": [],
"beforeFunctions": [
{
"input_column": "id",
"input_value": "id",
"isList": false,
"db": "vm_networks",
"output_column": "name",
"output_value": "name"
}
],
"formatZH": "更新虚拟机网络 [name]",
"formatEN": "update VM network [name]"
},
"/core/xpack/vms/operate": {
"bodyKeys": [
"name",
"operate"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "操作虚拟机 [name] [operate]",
"formatEN": "operate VM [name] [operate]"
},
"/core/xpack/vms/orphans/clean": {
"bodyKeys": [],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "清理虚拟机孤立数据",
"formatEN": "clean VM orphaned data"
},
"/core/xpack/vms/rename": {
"bodyKeys": [
"name",
"newName"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "重命名虚拟机 [name] 为 [newName]",
"formatEN": "rename VM [name] to [newName]"
},
"/core/xpack/vms/snapshots": {
"bodyKeys": [
"name",
"snapshotName"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "创建虚拟机 [name] 快照 [snapshotName]",
"formatEN": "create VM [name] snapshot [snapshotName]"
},
"/core/xpack/vms/snapshots/del": {
"bodyKeys": [
"name",
"snapshotName"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "删除虚拟机 [name] 快照 [snapshotName]",
"formatEN": "delete VM [name] snapshot [snapshotName]"
},
"/core/xpack/vms/snapshots/recover": {
"bodyKeys": [
"name",
"snapshotName"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "恢复虚拟机 [name] 快照 [snapshotName]",
"formatEN": "recover VM [name] snapshot [snapshotName]"
},
"/core/xpack/vms/storages": {
"bodyKeys": [
"name",
"type"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "创建虚拟机存储 [name] [type]",
"formatEN": "create VM storage [name] [type]"
},
"/core/xpack/vms/storages/del": {
"bodyKeys": [
"id"
],
"paramKeys": [],
"beforeFunctions": [
{
"input_column": "id",
"input_value": "id",
"isList": false,
"db": "vm_storages",
"output_column": "name",
"output_value": "name"
}
],
"formatZH": "删除虚拟机存储 [name]",
"formatEN": "delete VM storage [name]"
},
"/core/xpack/vms/storages/update": {
"bodyKeys": [
"id"
],
"paramKeys": [],
"beforeFunctions": [
{
"input_column": "id",
"input_value": "id",
"isList": false,
"db": "vm_storages",
"output_column": "name",
"output_value": "name"
}
],
"formatZH": "更新虚拟机存储 [name]",
"formatEN": "update VM storage [name]"
},
"/core/xpack/vms/sync": {
"bodyKeys": [],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "同步虚拟机",
"formatEN": "sync virtual machines"
},
"/core/xpack/vms/templates": {
"bodyKeys": [
"name",
"templateName"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "创建虚拟机 [name] 模板 [templateName]",
"formatEN": "create VM [name] template [templateName]"
},
"/core/xpack/vms/templates/del": {
"bodyKeys": [
"id"
],
"paramKeys": [],
"beforeFunctions": [
{
"input_column": "id",
"input_value": "id",
"isList": false,
"db": "vm_templates",
"output_column": "name",
"output_value": "name"
}
],
"formatZH": "删除虚拟机模板 [name]",
"formatEN": "delete VM template [name]"
},
"/core/xpack/vms/update": {
"bodyKeys": [
"name"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "更新虚拟机 [name]",
"formatEN": "update VM [name]"
},
"/cronjobs": {
"bodyKeys": [
"type",
@@ -3504,7 +3514,9 @@
"formatEN": "Download url =\u003e [path]/[name]"
},
"/files/wget/process/remove": {
"bodyKeys": ["keys"],
"bodyKeys": [
"keys"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "移除已结束下载记录 [keys]",
@@ -3634,13 +3646,11 @@
"formatEN": "[operation] Docker port guard"
},
"/hosts/firewall/docker/policies/batch": {
"bodyKeys": [
"mode"
],
"bodyKeys": [],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "批量更新 Docker 端口防护策略 [mode]",
"formatEN": "batch update Docker port guard policies [mode]"
"formatZH": "批量更新 Docker 端口防护策略",
"formatEN": "batch update Docker port guard policies"
},
"/hosts/firewall/docker/policies/delete/batch": {
"bodyKeys": [
@@ -3697,6 +3707,13 @@
"formatZH": "添加防火墙规则",
"formatEN": "create firewall rules"
},
"/hosts/firewall/rules/adopt": {
"bodyKeys": [],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "纳管防火墙规则",
"formatEN": "adopt firewall rule"
},
"/hosts/firewall/rules/delete": {
"bodyKeys": [],
"paramKeys": [],
@@ -3751,6 +3768,15 @@
"formatZH": "防火墙子系统 [subsystem] 后端 [operation] [backend]",
"formatEN": "[operation] firewall [subsystem] backend [backend]"
},
"/hosts/firewall/settings/whitelist": {
"bodyKeys": [
"value"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "更新防火墙端口白名单 [value]",
"formatEN": "update firewall port whitelist [value]"
},
"/hosts/monitor/clean": {
"bodyKeys": [],
"paramKeys": [],
@@ -5794,4 +5820,4 @@
"formatZH": "从主节点同步设置",
"formatEN": "sync settings from master"
}
}
}

View File

@@ -3,6 +3,9 @@ import { ResPage } from '@/api/interface';
import { Firewall } from '@/api/interface/firewall';
import { TimeoutEnum } from '@/enums/http-enum';
export const updateFirewallPortWhitelist = (value: string) =>
http.post<Firewall.FilterChainOperationResult>('/hosts/firewall/settings/whitelist', { value });
export const loadFireBaseInfo = (tab: string) =>
http.post<Firewall.FirewallBase>('/hosts/firewall/base', { name: tab }, TimeoutEnum.T_40S);

View File

@@ -208,7 +208,10 @@ const showPriorityField = computed(() => {
);
});
const selectedPositionRanges = computed(() => {
const families = [...new Set(form.sourceAddresses.map((item) => item.family))];
const families =
mode.value === 'edit' && editingRule.value
? [editingRule.value.scope.family]
: [...new Set(form.sourceAddresses.map((item) => item.family))];
if (families.length === 0) return [{ min: 1, max: 1 }];
return families.map((family) => positionRanges.value[family] || { min: 1, max: 1 });
});

View File

@@ -104,7 +104,8 @@
</el-form>
</template>
</LayoutContent>
<WhiteList ref="whiteListRef" @search="load" />
<WhiteList ref="whiteListRef" @created="openWhitelistTask" />
<TaskLog ref="whitelistTaskRef" @close="load" />
</div>
</template>
@@ -114,6 +115,7 @@ import { Firewall } from '@/api/interface/firewall';
import { loadFirewallSettings, operateFire, operateFirewallBackend } from '@/api/modules/firewall';
import FireRouter from '@/views/host/firewall/index.vue';
import WhiteList from '@/views/host/firewall/setting/white-list/index.vue';
import TaskLog from '@/components/log/task/index.vue';
import { whiteListRuleCount } from '@/views/host/firewall/setting/white-list/model';
import { useGlobalStore } from '@/composables/useGlobalStore';
import i18n from '@/lang';
@@ -132,6 +134,8 @@ const savedBackends = ref<Record<Firewall.BackendSubsystem, string>>({
const pingStatus = ref('Disable');
const oldPingStatus = ref('Disable');
const whiteListRef = ref();
const whitelistTaskRef = ref<InstanceType<typeof TaskLog>>();
const openWhitelistTask = (taskID: string) => whitelistTaskRef.value?.openWithTaskID(taskID, true);
const providerOrder: Record<Firewall.Provider, number> = {
iptables: 0,

View File

@@ -1,9 +1,7 @@
<template>
<DrawerPro v-model="drawerVisible" :header="$t('firewall.portWhiteList')" @close="handleClose" size="large">
<template #content>
<el-alert type="info" :closable="false" :title="$t('firewall.portWhiteListAlter')" />
<el-button class="mt-5" type="primary" @click="openCreate">
<el-button type="primary" @click="openCreate">
{{ $t('commons.button.add') }}
</el-button>
<ComplexTable :data="data" v-loading="loading">
@@ -61,9 +59,10 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { getAgentSettingInfo, updateAgentSetting } from '@/api/modules/setting';
import { getAgentSettingInfo } from '@/api/modules/setting';
import { updateFirewallPortWhitelist } from '@/api/modules/firewall';
import i18n from '@/lang';
import { MsgError, MsgSuccess } from '@/utils/message';
import { MsgError } from '@/utils/message';
import {
normalizeWhiteListRule,
parseWhiteList,
@@ -79,7 +78,7 @@ interface WhiteListItem extends WhiteListRule {
edit: boolean;
isNew: boolean;
}
const emit = defineEmits<{ (e: 'search'): void }>();
const emit = defineEmits<{ (e: 'created', taskID: string): void }>();
const drawerVisible = ref(false);
const loading = ref(false);
@@ -186,20 +185,16 @@ const validateRules = (): WhiteListRule[] | undefined => {
const onSubmit = async () => {
const rules = validateRules();
if (!rules) return;
if (!rules || loading.value) return;
loading.value = true;
await updateAgentSetting({
key: 'FirewallPortWhiteList',
value: serializeWhiteList(rules),
})
.then(() => {
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
emit('search');
drawerVisible.value = false;
})
.finally(() => {
loading.value = false;
});
try {
const { data: result } = await updateFirewallPortWhitelist(serializeWhiteList(rules));
if (!result.taskID || !result.queued) return;
drawerVisible.value = false;
emit('created', result.taskID);
} finally {
loading.value = false;
}
};
const handleClose = () => {