diff --git a/internal/web/actions/default/servers/components/waf/policy.go b/internal/web/actions/default/servers/components/waf/policy.go
index ef4fecc0..937dfc9f 100644
--- a/internal/web/actions/default/servers/components/waf/policy.go
+++ b/internal/web/actions/default/servers/components/waf/policy.go
@@ -94,6 +94,7 @@ func (this *PolicyAction) RunGet(params struct {
"modeInfo": firewallconfigs.FindFirewallMode(firewallPolicy.Mode),
"groups": internalGroups,
"blockOptions": firewallPolicy.BlockOptions,
+ "captchaOptions": firewallPolicy.CaptchaOptions,
"useLocalFirewall": firewallPolicy.UseLocalFirewall,
"synFlood": firewallPolicy.SYNFlood,
"log": firewallPolicy.Log,
diff --git a/internal/web/actions/default/servers/components/waf/update.go b/internal/web/actions/default/servers/components/waf/update.go
index 435b334c..aec363a3 100644
--- a/internal/web/actions/default/servers/components/waf/update.go
+++ b/internal/web/actions/default/servers/components/waf/update.go
@@ -70,6 +70,7 @@ func (this *UpdateAction) RunGet(params struct {
"isOn": firewallPolicy.IsOn,
"mode": firewallPolicy.Mode,
"blockOptions": firewallPolicy.BlockOptions,
+ "captchaOptions": firewallPolicy.CaptchaOptions,
"useLocalFirewall": firewallPolicy.UseLocalFirewall,
"synFloodConfig": firewallPolicy.SYNFlood,
"log": firewallPolicy.Log,
@@ -98,16 +99,17 @@ func (this *UpdateAction) RunGet(params struct {
}
func (this *UpdateAction) RunPost(params struct {
- FirewallPolicyId int64
- Name string
- GroupCodes []string
- BlockOptionsJSON []byte
- Description string
- IsOn bool
- Mode string
- UseLocalFirewall bool
- SynFloodJSON []byte
- LogJSON []byte
+ FirewallPolicyId int64
+ Name string
+ GroupCodes []string
+ BlockOptionsJSON []byte
+ CaptchaOptionsJSON []byte
+ Description string
+ IsOn bool
+ Mode string
+ UseLocalFirewall bool
+ SynFloodJSON []byte
+ LogJSON []byte
Must *actions.Must
}) {
@@ -118,13 +120,20 @@ func (this *UpdateAction) RunPost(params struct {
Field("name", params.Name).
Require("请输入策略名称")
- // 校验JSON
+ // 校验拦截选项JSON
var blockOptions = &firewallconfigs.HTTPFirewallBlockAction{}
err := json.Unmarshal(params.BlockOptionsJSON, blockOptions)
if err != nil {
this.Fail("拦截动作参数校验失败:" + err.Error())
}
+ // 校验验证码选项JSON
+ var captchaOptions = &firewallconfigs.HTTPFirewallCaptchaAction{}
+ err = json.Unmarshal(params.CaptchaOptionsJSON, captchaOptions)
+ if err != nil {
+ this.Fail("验证码动作参数校验失败:" + err.Error())
+ }
+
_, err = this.RPC().HTTPFirewallPolicyRPC().UpdateHTTPFirewallPolicy(this.AdminContext(), &pb.UpdateHTTPFirewallPolicyRequest{
HttpFirewallPolicyId: params.FirewallPolicyId,
IsOn: params.IsOn,
@@ -132,6 +141,7 @@ func (this *UpdateAction) RunPost(params struct {
Description: params.Description,
FirewallGroupCodes: params.GroupCodes,
BlockOptionsJSON: params.BlockOptionsJSON,
+ CaptchaOptionsJSON: params.CaptchaOptionsJSON,
Mode: params.Mode,
UseLocalFirewall: params.UseLocalFirewall,
SynFloodJSON: params.SynFloodJSON,
diff --git a/web/public/js/components.js b/web/public/js/components.js
index 73ade29a..6ec94f99 100644
--- a/web/public/js/components.js
+++ b/web/public/js/components.js
@@ -1863,7 +1863,9 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
:有效期{{config.options.timeout}}秒
- :有效期{{config.options.life}}秒
+ :有效期{{config.options.life}}秒
+ / 最多失败{{config.options.maxFails}}次
+
:有效期{{config.options.life}}秒
@@ -2599,7 +2601,10 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
- 耗时:{{formatCost(accessLog.requestTime)}} ms ({{accessLog.humanTime}})
-`}),Vue.component("http-access-log-config-box",{props:["v-access-log-config","v-fields","v-default-field-codes","v-is-location","v-is-group"],data:function(){let t=this,i=(setTimeout(function(){t.changeFields()},100),{isPrior:!1,isOn:!1,fields:[1,2,6,7],status1:!0,status2:!0,status3:!0,status4:!0,status5:!0,firewallOnly:!1,enableClientClosed:!1});return null!=this.vAccessLogConfig&&(i=this.vAccessLogConfig),this.vFields.forEach(function(e){null==t.vAccessLogConfig?e.isChecked=t.vDefaultFieldCodes.$contains(e.code):e.isChecked=i.fields.$contains(e.code)}),{accessLog:i,hasRequestBodyField:this.vFields.$contains(8)}},methods:{changeFields:function(){this.accessLog.fields=this.vFields.filter(function(e){return e.isChecked}).map(function(e){return e.code}),this.hasRequestBodyField=this.accessLog.fields.$contains(8)}},template:`
+
`}),Vue.component("http-firewall-block-options-viewer",{props:["v-block-options"],data:function(){return{blockOptions:this.vBlockOptions,statusCode:this.vBlockOptions.statusCode,timeout:this.vBlockOptions.timeout}},watch:{statusCode:function(e){e=parseInt(e);isNaN(e)?this.blockOptions.statusCode=403:this.blockOptions.statusCode=e},timeout:function(e){e=parseInt(e);isNaN(e)?this.blockOptions.timeout=0:this.blockOptions.timeout=e}},methods:{edit:function(){this.isEditing=!this.isEditing}},template:`
+ 状态码:{{statusCode}} / 提示内容:[{{blockOptions.body.length}}字符] [无] / 超时时间:{{timeout}}秒
+
+`}),Vue.component("http-access-log-config-box",{props:["v-access-log-config","v-fields","v-default-field-codes","v-is-location","v-is-group"],data:function(){let t=this,i=(setTimeout(function(){t.changeFields()},100),{isPrior:!1,isOn:!1,fields:[1,2,6,7],status1:!0,status2:!0,status3:!0,status4:!0,status5:!0,firewallOnly:!1,enableClientClosed:!1});return null!=this.vAccessLogConfig&&(i=this.vAccessLogConfig),this.vFields.forEach(function(e){null==t.vAccessLogConfig?e.isChecked=t.vDefaultFieldCodes.$contains(e.code):e.isChecked=i.fields.$contains(e.code)}),{accessLog:i,hasRequestBodyField:this.vFields.$contains(8)}},methods:{changeFields:function(){this.accessLog.fields=this.vFields.filter(function(e){return e.isChecked}).map(function(e){return e.code}),this.hasRequestBodyField=this.accessLog.fields.$contains(8)}},template:`
@@ -2851,7 +2856,8 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
{{cert.name}} / {{cert.dnsNames}} / 有效至{{formatTime(cert.timeEndAt)}}
-`}),Vue.component("reverse-proxy-box",{props:["v-reverse-proxy-ref","v-reverse-proxy-config","v-is-location","v-is-group","v-family"],data:function(){let e=this.vReverseProxyRef,t=(null==e&&(e={isPrior:!1,isOn:!1,reverseProxyId:0}),this.vReverseProxyConfig),i=(null==(t=null==t?{requestPath:"",stripPrefix:"",requestURI:"",requestHost:"",requestHostType:0,addHeaders:[],connTimeout:{count:0,unit:"second"},readTimeout:{count:0,unit:"second"},idleTimeout:{count:0,unit:"second"},maxConns:0,maxIdleConns:0,followRedirects:!1}:t).addHeaders&&(t.addHeaders=[]),null==t.connTimeout&&(t.connTimeout={count:0,unit:"second"}),null==t.readTimeout&&(t.readTimeout={count:0,unit:"second"}),null==t.idleTimeout&&(t.idleTimeout={count:0,unit:"second"}),null==t.proxyProtocol&&Vue.set(t,"proxyProtocol",{isOn:!1,version:1}),[{name:"X-Real-IP",isChecked:!1},{name:"X-Forwarded-For",isChecked:!1},{name:"X-Forwarded-By",isChecked:!1},{name:"X-Forwarded-Host",isChecked:!1},{name:"X-Forwarded-Proto",isChecked:!1}]);return i.forEach(function(e){e.isChecked=t.addHeaders.$contains(e.name)}),{reverseProxyRef:e,reverseProxyConfig:t,advancedVisible:!1,family:this.vFamily,forwardHeaders:i}},watch:{"reverseProxyConfig.requestHostType":function(e){let t=parseInt(e);isNaN(t)&&(t=0),this.reverseProxyConfig.requestHostType=t},"reverseProxyConfig.connTimeout.count":function(e){let t=parseInt(e);(isNaN(t)||t<0)&&(t=0),this.reverseProxyConfig.connTimeout.count=t},"reverseProxyConfig.readTimeout.count":function(e){let t=parseInt(e);(isNaN(t)||t<0)&&(t=0),this.reverseProxyConfig.readTimeout.count=t},"reverseProxyConfig.idleTimeout.count":function(e){let t=parseInt(e);(isNaN(t)||t<0)&&(t=0),this.reverseProxyConfig.idleTimeout.count=t},"reverseProxyConfig.maxConns":function(e){let t=parseInt(e);(isNaN(t)||t<0)&&(t=0),this.reverseProxyConfig.maxConns=t},"reverseProxyConfig.maxIdleConns":function(e){let t=parseInt(e);(isNaN(t)||t<0)&&(t=0),this.reverseProxyConfig.maxIdleConns=t},"reverseProxyConfig.proxyProtocol.version":function(e){let t=parseInt(e);isNaN(t)&&(t=1),this.reverseProxyConfig.proxyProtocol.version=t}},methods:{isOn:function(){return(!this.vIsLocation&&!this.vIsGroup||this.reverseProxyRef.isPrior)&&this.reverseProxyRef.isOn},changeAdvancedVisible:function(e){this.advancedVisible=e},changeAddHeader:function(){this.reverseProxyConfig.addHeaders=this.forwardHeaders.filter(function(e){return e.isChecked}).map(function(e){return e.name})}},template:`
+
`}),Vue.component("http-firewall-captcha-options-viewer",{props:["v-captcha-options"],mounted:function(){this.updateSummary()},data:function(){let e=this.vCaptchaOptions;return{options:e=null==e?{life:0,maxFails:0,failBlockTimeout:0,failBlockScopeAll:!1,uiIsOn:!1,uiTitle:"",uiPrompt:"",uiButtonTitle:"",uiShowRequestId:!1,uiCss:"",uiFooter:"",uiBody:"",cookieId:"",lang:""}:e,summary:""}},methods:{updateSummary:function(){let e=[];0{{summary}}
+`}),Vue.component("reverse-proxy-box",{props:["v-reverse-proxy-ref","v-reverse-proxy-config","v-is-location","v-is-group","v-family"],data:function(){let e=this.vReverseProxyRef,t=(null==e&&(e={isPrior:!1,isOn:!1,reverseProxyId:0}),this.vReverseProxyConfig),i=(null==(t=null==t?{requestPath:"",stripPrefix:"",requestURI:"",requestHost:"",requestHostType:0,addHeaders:[],connTimeout:{count:0,unit:"second"},readTimeout:{count:0,unit:"second"},idleTimeout:{count:0,unit:"second"},maxConns:0,maxIdleConns:0,followRedirects:!1}:t).addHeaders&&(t.addHeaders=[]),null==t.connTimeout&&(t.connTimeout={count:0,unit:"second"}),null==t.readTimeout&&(t.readTimeout={count:0,unit:"second"}),null==t.idleTimeout&&(t.idleTimeout={count:0,unit:"second"}),null==t.proxyProtocol&&Vue.set(t,"proxyProtocol",{isOn:!1,version:1}),[{name:"X-Real-IP",isChecked:!1},{name:"X-Forwarded-For",isChecked:!1},{name:"X-Forwarded-By",isChecked:!1},{name:"X-Forwarded-Host",isChecked:!1},{name:"X-Forwarded-Proto",isChecked:!1}]);return i.forEach(function(e){e.isChecked=t.addHeaders.$contains(e.name)}),{reverseProxyRef:e,reverseProxyConfig:t,advancedVisible:!1,family:this.vFamily,forwardHeaders:i}},watch:{"reverseProxyConfig.requestHostType":function(e){let t=parseInt(e);isNaN(t)&&(t=0),this.reverseProxyConfig.requestHostType=t},"reverseProxyConfig.connTimeout.count":function(e){let t=parseInt(e);(isNaN(t)||t<0)&&(t=0),this.reverseProxyConfig.connTimeout.count=t},"reverseProxyConfig.readTimeout.count":function(e){let t=parseInt(e);(isNaN(t)||t<0)&&(t=0),this.reverseProxyConfig.readTimeout.count=t},"reverseProxyConfig.idleTimeout.count":function(e){let t=parseInt(e);(isNaN(t)||t<0)&&(t=0),this.reverseProxyConfig.idleTimeout.count=t},"reverseProxyConfig.maxConns":function(e){let t=parseInt(e);(isNaN(t)||t<0)&&(t=0),this.reverseProxyConfig.maxConns=t},"reverseProxyConfig.maxIdleConns":function(e){let t=parseInt(e);(isNaN(t)||t<0)&&(t=0),this.reverseProxyConfig.maxIdleConns=t},"reverseProxyConfig.proxyProtocol.version":function(e){let t=parseInt(e);isNaN(t)&&(t=1),this.reverseProxyConfig.proxyProtocol.version=t}},methods:{isOn:function(){return(!this.vIsLocation&&!this.vIsGroup||this.reverseProxyRef.isPrior)&&this.reverseProxyRef.isOn},changeAdvancedVisible:function(e){this.advancedVisible=e},changeAddHeader:function(){this.reverseProxyConfig.addHeaders=this.forwardHeaders.filter(function(e){return e.isChecked}).map(function(e){return e.name})}},template:`
@@ -3734,7 +3740,115 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
-
`}),Vue.component("firewall-syn-flood-config-box",{props:["v-syn-flood-config"],data:function(){let e=this.vSynFloodConfig;return{config:e=null==e?{isOn:!1,minAttempts:10,timeoutSeconds:600,ignoreLocal:!0}:e,isEditing:!1,minAttempts:e.minAttempts,timeoutSeconds:e.timeoutSeconds}},methods:{edit:function(){this.isEditing=!this.isEditing}},watch:{minAttempts:function(e){let t=parseInt(e);(t=isNaN(t)?10:t)<5&&(t=5),this.config.minAttempts=t},timeoutSeconds:function(e){let t=parseInt(e);(t=isNaN(t)?10:t)<60&&(t=60),this.config.timeoutSeconds=t}},template:`
+
`}),Vue.component("http-firewall-captcha-options",{props:["v-captcha-options"],mounted:function(){this.updateSummary()},data:function(){let e=this.vCaptchaOptions;return(e=null==e?{countLetters:0,life:0,maxFails:0,failBlockTimeout:0,failBlockScopeAll:!1,uiIsOn:!1,uiTitle:"",uiPrompt:"",uiButtonTitle:"",uiShowRequestId:!1,uiCss:"",uiFooter:"",uiBody:"",cookieId:"",lang:""}:e).countLetters<=0&&(e.countLetters=6),{options:e,isEditing:!1,summary:""}},watch:{"options.countLetters":function(e){let t=parseInt(e,10);isNaN(t)||t<0?t=0:10
+
+ {{summary}}
+
+
+`}),Vue.component("firewall-syn-flood-config-box",{props:["v-syn-flood-config"],data:function(){let e=this.vSynFloodConfig;return{config:e=null==e?{isOn:!1,minAttempts:10,timeoutSeconds:600,ignoreLocal:!0}:e,isEditing:!1,minAttempts:e.minAttempts,timeoutSeconds:e.timeoutSeconds}},methods:{edit:function(){this.isEditing=!this.isEditing}},watch:{minAttempts:function(e){let t=parseInt(e);(t=isNaN(t)?10:t)<5&&(t=5),this.config.minAttempts=t},timeoutSeconds:function(e){let t=parseInt(e);(t=isNaN(t)?10:t)<60&&(t=60),this.config.timeoutSeconds=t}},template:``
})
+Vue.component("http-firewall-block-options-viewer", {
+ props: ["v-block-options"],
+ data: function () {
+ return {
+ blockOptions: this.vBlockOptions,
+ statusCode: this.vBlockOptions.statusCode,
+ timeout: this.vBlockOptions.timeout
+ }
+ },
+ watch: {
+ statusCode: function (v) {
+ let statusCode = parseInt(v)
+ if (isNaN(statusCode)) {
+ this.blockOptions.statusCode = 403
+ } else {
+ this.blockOptions.statusCode = statusCode
+ }
+ },
+ timeout: function (v) {
+ let timeout = parseInt(v)
+ if (isNaN(timeout)) {
+ this.blockOptions.timeout = 0
+ } else {
+ this.blockOptions.timeout = timeout
+ }
+ }
+ },
+ methods: {
+ edit: function () {
+ this.isEditing = !this.isEditing
+ }
+ },
+ template: `
+ 状态码:{{statusCode}} / 提示内容:[{{blockOptions.body.length}}字符] [无] / 超时时间:{{timeout}}秒
+
+`
+})
+
Vue.component("http-access-log-config-box", {
props: ["v-access-log-config", "v-fields", "v-default-field-codes", "v-is-location", "v-is-group"],
data: function () {
@@ -8170,6 +8210,65 @@ Vue.component("ssl-certs-view", {
`
})
+Vue.component("http-firewall-captcha-options-viewer", {
+ props: ["v-captcha-options"],
+ mounted: function () {
+ this.updateSummary()
+ },
+ data: function () {
+ let options = this.vCaptchaOptions
+ if (options == null) {
+ options = {
+ life: 0,
+ maxFails: 0,
+ failBlockTimeout: 0,
+ failBlockScopeAll: false,
+ uiIsOn: false,
+ uiTitle: "",
+ uiPrompt: "",
+ uiButtonTitle: "",
+ uiShowRequestId: false,
+ uiCss: "",
+ uiFooter: "",
+ uiBody: "",
+ cookieId: "",
+ lang: ""
+ }
+ }
+ return {
+ options: options,
+ summary: ""
+ }
+ },
+ methods: {
+ updateSummary: function () {
+ let summaryList = []
+ if (this.options.life > 0) {
+ summaryList.push("有效时间" + this.options.life + "秒")
+ }
+ if (this.options.maxFails > 0) {
+ summaryList.push("最多失败" + this.options.maxFails + "次")
+ }
+ if (this.options.failBlockTimeout > 0) {
+ summaryList.push("失败拦截" + this.options.failBlockTimeout + "秒")
+ }
+ if (this.options.failBlockScopeAll) {
+ summaryList.push("全局封禁")
+ }
+ if (this.options.uiIsOn) {
+ summaryList.push("定制UI")
+ }
+ if (summaryList.length == 0) {
+ this.summary = "默认配置"
+ } else {
+ this.summary = summaryList.join(" / ")
+ }
+ }
+ },
+ template: `{{summary}}
+`
+})
+
Vue.component("reverse-proxy-box", {
props: ["v-reverse-proxy-ref", "v-reverse-proxy-config", "v-is-location", "v-is-group", "v-family"],
data: function () {
@@ -10684,6 +10783,226 @@ Vue.component("traffic-limit-config-box", {
`
})
+Vue.component("http-firewall-captcha-options", {
+ props: ["v-captcha-options"],
+ mounted: function () {
+ this.updateSummary()
+ },
+ data: function () {
+ let options = this.vCaptchaOptions
+ if (options == null) {
+ options = {
+ countLetters: 0,
+ life: 0,
+ maxFails: 0,
+ failBlockTimeout: 0,
+ failBlockScopeAll: false,
+ uiIsOn: false,
+ uiTitle: "",
+ uiPrompt: "",
+ uiButtonTitle: "",
+ uiShowRequestId: false,
+ uiCss: "",
+ uiFooter: "",
+ uiBody: "",
+ cookieId: "",
+ lang: ""
+ }
+ }
+ if (options.countLetters <= 0) {
+ options.countLetters = 6
+ }
+ return {
+ options: options,
+ isEditing: false,
+ summary: ""
+ }
+ },
+ watch: {
+ "options.countLetters": function (v) {
+ let i = parseInt(v, 10)
+ if (isNaN(i)) {
+ i = 0
+ } else if (i < 0) {
+ i = 0
+ } else if (i > 10) {
+ i = 10
+ }
+ this.options.countLetters = i
+ },
+ "options.life": function (v) {
+ let i = parseInt(v, 10)
+ if (isNaN(i)) {
+ i = 0
+ }
+ this.options.life = i
+ this.updateSummary()
+ },
+ "options.maxFails": function (v) {
+ let i = parseInt(v, 10)
+ if (isNaN(i)) {
+ i = 0
+ }
+ this.options.maxFails = i
+ this.updateSummary()
+ },
+ "options.failBlockTimeout": function (v) {
+ let i = parseInt(v, 10)
+ if (isNaN(i)) {
+ i = 0
+ }
+ this.options.failBlockTimeout = i
+ this.updateSummary()
+ },
+ "options.failBlockScopeAll": function (v) {
+ this.updateSummary()
+ },
+ "options.uiIsOn": function (v) {
+ this.updateSummary()
+ }
+ },
+ methods: {
+ edit: function () {
+ this.isEditing = !this.isEditing
+ },
+ updateSummary: function () {
+ let summaryList = []
+ if (this.options.life > 0) {
+ summaryList.push("有效时间" + this.options.life + "秒")
+ }
+ if (this.options.maxFails > 0) {
+ summaryList.push("最多失败" + this.options.maxFails + "次")
+ }
+ if (this.options.failBlockTimeout > 0) {
+ summaryList.push("失败拦截" + this.options.failBlockTimeout + "秒")
+ }
+ if (this.options.failBlockScopeAll) {
+ summaryList.push("全局封禁")
+ }
+ if (this.options.uiIsOn) {
+ summaryList.push("定制UI")
+ }
+ if (summaryList.length == 0) {
+ this.summary = "默认配置"
+ } else {
+ this.summary = summaryList.join(" / ")
+ }
+ },
+ confirm: function () {
+ this.isEditing = false
+ }
+ },
+ template: `
+`
+})
+
Vue.component("firewall-syn-flood-config-box", {
props: ["v-syn-flood-config"],
data: function () {
diff --git a/web/public/js/components/server/http-firewall-actions-box.js b/web/public/js/components/server/http-firewall-actions-box.js
index c4c4541a..879663cf 100644
--- a/web/public/js/components/server/http-firewall-actions-box.js
+++ b/web/public/js/components/server/http-firewall-actions-box.js
@@ -535,7 +535,9 @@ Vue.component("http-firewall-actions-box", {
:有效期{{config.options.timeout}}秒
- :有效期{{config.options.life}}秒
+ :有效期{{config.options.life}}秒
+ / 最多失败{{config.options.maxFails}}次
+
:有效期{{config.options.life}}秒
diff --git a/web/public/js/components/server/http-firewall-block-options-viewer.js b/web/public/js/components/server/http-firewall-block-options-viewer.js
new file mode 100644
index 00000000..6ce56cd8
--- /dev/null
+++ b/web/public/js/components/server/http-firewall-block-options-viewer.js
@@ -0,0 +1,37 @@
+Vue.component("http-firewall-block-options-viewer", {
+ props: ["v-block-options"],
+ data: function () {
+ return {
+ blockOptions: this.vBlockOptions,
+ statusCode: this.vBlockOptions.statusCode,
+ timeout: this.vBlockOptions.timeout
+ }
+ },
+ watch: {
+ statusCode: function (v) {
+ let statusCode = parseInt(v)
+ if (isNaN(statusCode)) {
+ this.blockOptions.statusCode = 403
+ } else {
+ this.blockOptions.statusCode = statusCode
+ }
+ },
+ timeout: function (v) {
+ let timeout = parseInt(v)
+ if (isNaN(timeout)) {
+ this.blockOptions.timeout = 0
+ } else {
+ this.blockOptions.timeout = timeout
+ }
+ }
+ },
+ methods: {
+ edit: function () {
+ this.isEditing = !this.isEditing
+ }
+ },
+ template: `
+ 状态码:{{statusCode}} / 提示内容:[{{blockOptions.body.length}}字符] [无] / 超时时间:{{timeout}}秒
+
+`
+})
\ No newline at end of file
diff --git a/web/public/js/components/server/http-firewall-captcha-options-viewer.js b/web/public/js/components/server/http-firewall-captcha-options-viewer.js
new file mode 100644
index 00000000..e6550f4a
--- /dev/null
+++ b/web/public/js/components/server/http-firewall-captcha-options-viewer.js
@@ -0,0 +1,58 @@
+Vue.component("http-firewall-captcha-options-viewer", {
+ props: ["v-captcha-options"],
+ mounted: function () {
+ this.updateSummary()
+ },
+ data: function () {
+ let options = this.vCaptchaOptions
+ if (options == null) {
+ options = {
+ life: 0,
+ maxFails: 0,
+ failBlockTimeout: 0,
+ failBlockScopeAll: false,
+ uiIsOn: false,
+ uiTitle: "",
+ uiPrompt: "",
+ uiButtonTitle: "",
+ uiShowRequestId: false,
+ uiCss: "",
+ uiFooter: "",
+ uiBody: "",
+ cookieId: "",
+ lang: ""
+ }
+ }
+ return {
+ options: options,
+ summary: ""
+ }
+ },
+ methods: {
+ updateSummary: function () {
+ let summaryList = []
+ if (this.options.life > 0) {
+ summaryList.push("有效时间" + this.options.life + "秒")
+ }
+ if (this.options.maxFails > 0) {
+ summaryList.push("最多失败" + this.options.maxFails + "次")
+ }
+ if (this.options.failBlockTimeout > 0) {
+ summaryList.push("失败拦截" + this.options.failBlockTimeout + "秒")
+ }
+ if (this.options.failBlockScopeAll) {
+ summaryList.push("全局封禁")
+ }
+ if (this.options.uiIsOn) {
+ summaryList.push("定制UI")
+ }
+ if (summaryList.length == 0) {
+ this.summary = "默认配置"
+ } else {
+ this.summary = summaryList.join(" / ")
+ }
+ }
+ },
+ template: `{{summary}}
+`
+})
\ No newline at end of file
diff --git a/web/public/js/components/server/http-firewall-captcha-options.js b/web/public/js/components/server/http-firewall-captcha-options.js
new file mode 100644
index 00000000..703af040
--- /dev/null
+++ b/web/public/js/components/server/http-firewall-captcha-options.js
@@ -0,0 +1,219 @@
+Vue.component("http-firewall-captcha-options", {
+ props: ["v-captcha-options"],
+ mounted: function () {
+ this.updateSummary()
+ },
+ data: function () {
+ let options = this.vCaptchaOptions
+ if (options == null) {
+ options = {
+ countLetters: 0,
+ life: 0,
+ maxFails: 0,
+ failBlockTimeout: 0,
+ failBlockScopeAll: false,
+ uiIsOn: false,
+ uiTitle: "",
+ uiPrompt: "",
+ uiButtonTitle: "",
+ uiShowRequestId: false,
+ uiCss: "",
+ uiFooter: "",
+ uiBody: "",
+ cookieId: "",
+ lang: ""
+ }
+ }
+ if (options.countLetters <= 0) {
+ options.countLetters = 6
+ }
+ return {
+ options: options,
+ isEditing: false,
+ summary: ""
+ }
+ },
+ watch: {
+ "options.countLetters": function (v) {
+ let i = parseInt(v, 10)
+ if (isNaN(i)) {
+ i = 0
+ } else if (i < 0) {
+ i = 0
+ } else if (i > 10) {
+ i = 10
+ }
+ this.options.countLetters = i
+ },
+ "options.life": function (v) {
+ let i = parseInt(v, 10)
+ if (isNaN(i)) {
+ i = 0
+ }
+ this.options.life = i
+ this.updateSummary()
+ },
+ "options.maxFails": function (v) {
+ let i = parseInt(v, 10)
+ if (isNaN(i)) {
+ i = 0
+ }
+ this.options.maxFails = i
+ this.updateSummary()
+ },
+ "options.failBlockTimeout": function (v) {
+ let i = parseInt(v, 10)
+ if (isNaN(i)) {
+ i = 0
+ }
+ this.options.failBlockTimeout = i
+ this.updateSummary()
+ },
+ "options.failBlockScopeAll": function (v) {
+ this.updateSummary()
+ },
+ "options.uiIsOn": function (v) {
+ this.updateSummary()
+ }
+ },
+ methods: {
+ edit: function () {
+ this.isEditing = !this.isEditing
+ },
+ updateSummary: function () {
+ let summaryList = []
+ if (this.options.life > 0) {
+ summaryList.push("有效时间" + this.options.life + "秒")
+ }
+ if (this.options.maxFails > 0) {
+ summaryList.push("最多失败" + this.options.maxFails + "次")
+ }
+ if (this.options.failBlockTimeout > 0) {
+ summaryList.push("失败拦截" + this.options.failBlockTimeout + "秒")
+ }
+ if (this.options.failBlockScopeAll) {
+ summaryList.push("全局封禁")
+ }
+ if (this.options.uiIsOn) {
+ summaryList.push("定制UI")
+ }
+ if (summaryList.length == 0) {
+ this.summary = "默认配置"
+ } else {
+ this.summary = summaryList.join(" / ")
+ }
+ },
+ confirm: function () {
+ this.isEditing = false
+ }
+ },
+ template: `
+`
+})
\ No newline at end of file
diff --git a/web/views/@default/servers/components/waf/policy.html b/web/views/@default/servers/components/waf/policy.html
index fcaf873f..a4bdf45a 100644
--- a/web/views/@default/servers/components/waf/policy.html
+++ b/web/views/@default/servers/components/waf/policy.html
@@ -29,26 +29,13 @@
阻止动作设置
- 还没有设置。
-
-
-
- 状态码
- {{firewallPolicy.blockOptions.statusCode}}
-
-
- 提示内容
- {{firewallPolicy.blockOptions.body}}
-
-
- 超时时间
-
- 使用默认时间。
- {{firewallPolicy.blockOptions.timeout}}秒
-
-
-
-
+
+
+
+
+ 验证码动作设置
+
+
diff --git a/web/views/@default/servers/components/waf/update.html b/web/views/@default/servers/components/waf/update.html
index 994cba9a..e4f12582 100644
--- a/web/views/@default/servers/components/waf/update.html
+++ b/web/views/@default/servers/components/waf/update.html
@@ -37,6 +37,12 @@
+
+ 验证码动作配置
+
+
+
+
使用系统防火墙