From 6b880d2626dfd3c6df7f19d5db94bebc889dea84 Mon Sep 17 00:00:00 2001 From: GoEdgeLab Date: Fri, 8 Apr 2022 21:24:54 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E6=8C=81=E4=BD=BF=E7=94=A8uglifyjs?= =?UTF-8?q?=E5=8E=8B=E7=BC=A9js=E7=BB=84=E4=BB=B6=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build/build.sh | 2 + build/generate.sh | 13 + internal/gen/generate.go | 4 +- web/public/js/components.js | 10116 +-------------------- web/public/js/components.src.js | 14244 ++++++++++++++++++++++++++++++ 5 files changed, 14417 insertions(+), 9962 deletions(-) mode change 100755 => 100644 web/public/js/components.js create mode 100755 web/public/js/components.src.js diff --git a/build/build.sh b/build/build.sh index 0e95e2ba..c5b492b9 100755 --- a/build/build.sh +++ b/build/build.sh @@ -65,6 +65,8 @@ function build() { cp -R $ROOT/../web $DIST/ rm -f $DIST/web/tmp/* + rm -rf $DIST/web/public/js/components + rm -f $DIST/web/public/js/components.src.js cp $ROOT/configs/server.template.yaml $DIST/configs/ # change _plus.[ext] to .[ext] diff --git a/build/generate.sh b/build/generate.sh index acd948a2..517a4336 100755 --- a/build/generate.sh +++ b/build/generate.sh @@ -1,3 +1,16 @@ #!/usr/bin/env bash +JS_ROOT=../web/public/js + +echo "generate component.src.js ..." go run -tags=community ../cmd/edge-admin/main.go generate + +if [ `which uglifyjs` ]; then + echo "compress to component.js ..." + uglifyjs --compress --mangle -- ${JS_ROOT}/components.src.js > ${JS_ROOT}/components.js +else + echo "copy to component.js ..." + cp ${JS_ROOT}/components.src.js ${JS_ROOT}/components.js +fi + +echo "ok" \ No newline at end of file diff --git a/internal/gen/generate.go b/internal/gen/generate.go index 54a5476f..f3512a6e 100644 --- a/internal/gen/generate.go +++ b/internal/gen/generate.go @@ -21,7 +21,7 @@ import ( func Generate() error { err := generateComponentsJSFile() if err != nil { - return errors.New("generate 'components.js' failed: " + err.Error()) + return errors.New("generate 'components.src.js' failed: " + err.Error()) } return nil @@ -115,7 +115,7 @@ func generateComponentsJSFile() error { buffer.Write([]byte{'\n', '\n'}) } - fp, err := os.OpenFile(filepath.Clean(Tea.PublicFile("/js/components.js")), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0777) + fp, err := os.OpenFile(filepath.Clean(Tea.PublicFile("/js/components.src.js")), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0777) if err != nil { return err } diff --git a/web/public/js/components.js b/web/public/js/components.js old mode 100755 new mode 100644 index efae8de0..8b94ae33 --- a/web/public/js/components.js +++ b/web/public/js/components.js @@ -1,200 +1,4 @@ -Vue.component("traffic-map-box", { - props: ["v-stats", "v-is-attack"], - mounted: function () { - this.render() - }, - data: function () { - let maxPercent = 0 - let isAttack = this.vIsAttack - this.vStats.forEach(function (v) { - let percent = parseFloat(v.percent) - if (percent > maxPercent) { - maxPercent = percent - } - - v.formattedCountRequests = teaweb.formatCount(v.countRequests) + "次" - v.formattedCountAttackRequests = teaweb.formatCount(v.countAttackRequests) + "次" - }) - - if (maxPercent < 100) { - maxPercent *= 1.2 // 不要让某一项100% - } - - let screenIsNarrow = window.innerWidth < 512 - - return { - isAttack: isAttack, - stats: this.vStats, - chart: null, - minOpacity: 0.2, - maxPercent: maxPercent, - selectedCountryName: "", - screenIsNarrow: screenIsNarrow - } - }, - methods: { - render: function () { - this.chart = teaweb.initChart(document.getElementById("traffic-map-box")); - let that = this - this.chart.setOption({ - backgroundColor: "white", - grid: { - top: 0, - bottom: 0, - left: 0, - right: 0 - }, - roam: false, - tooltip: { - trigger: "item" - }, - series: [{ - type: "map", - map: "world", - zoom: 1.3, - selectedMode: false, - itemStyle: { - areaColor: "#E9F0F9", - borderColor: "#DDD" - }, - label: { - show: false, - fontSize: "10px", - color: "#fff", - backgroundColor: "#8B9BD3", - padding: [2, 2, 2, 2] - }, - emphasis: { - itemStyle: { - areaColor: "#8B9BD3", - opacity: 1.0 - }, - label: { - show: true, - fontSize: "10px", - color: "#fff", - backgroundColor: "#8B9BD3", - padding: [2, 2, 2, 2] - } - }, - //select: {itemStyle:{ areaColor: "#8B9BD3", opacity: 0.8 }}, - tooltip: { - formatter: function (args) { - let name = args.name - let stat = null - that.stats.forEach(function (v) { - if (v.name == name) { - stat = v - } - }) - - if (stat != null) { - return name + "
流量:" + stat.formattedBytes + "
流量占比:" + stat.percent + "%
请求数:" + stat.formattedCountRequests + "
攻击数:" + stat.formattedCountAttackRequests - } - return name - } - }, - data: this.stats.map(function (v) { - let opacity = parseFloat(v.percent) / that.maxPercent - if (opacity < that.minOpacity) { - opacity = that.minOpacity - } - let fullOpacity = opacity * 3 - if (fullOpacity > 1) { - fullOpacity = 1 - } - let isAttack = that.vIsAttack - let bgColor = "#276AC6" - if (isAttack) { - bgColor = "#B03A5B" - } - - return { - name: v.name, - value: v.bytes, - percent: parseFloat(v.percent), - itemStyle: { - areaColor: bgColor, - opacity: opacity - }, - emphasis: { - itemStyle: { - areaColor: bgColor, - opacity: fullOpacity - }, - label: { - show: true, - formatter: function (args) { - return args.name - } - } - }, - label: { - show: false, - formatter: function (args) { - if (args.name == that.selectedCountryName) { - return args.name - } - return "" - }, - fontSize: "10px", - color: "#fff", - backgroundColor: "#8B9BD3", - padding: [2, 2, 2, 2] - } - } - }), - nameMap: window.WorldCountriesMap - }] - }) - this.chart.resize() - }, - selectCountry: function (countryName) { - if (this.chart == null) { - return - } - let option = this.chart.getOption() - let that = this - option.series[0].data.forEach(function (v) { - let opacity = v.percent / that.maxPercent - if (opacity < that.minOpacity) { - opacity = that.minOpacity - } - - if (v.name == countryName) { - if (v.isSelected) { - v.itemStyle.opacity = opacity - v.isSelected = false - v.label.show = false - that.selectedCountryName = "" - return - } - v.isSelected = true - that.selectedCountryName = countryName - opacity *= 3 - if (opacity > 1) { - opacity = 1 - } - - // 至少是0.5,让用户能够看清 - if (opacity < 0.5) { - opacity = 0.5 - } - v.itemStyle.opacity = opacity - v.label.show = true - } else { - v.itemStyle.opacity = opacity - v.isSelected = false - v.label.show = false - } - }) - this.chart.setOption(option) - }, - select: function (args) { - this.selectCountry(args.countryName) - } - }, - template: `
+Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:function(){this.render()},data:function(){let i=0;var e=this.vIsAttack,t=(this.vStats.forEach(function(e){var t=parseFloat(e.percent);t>i&&(i=t),e.formattedCountRequests=teaweb.formatCount(e.countRequests)+"次",e.formattedCountAttackRequests=teaweb.formatCount(e.countAttackRequests)+"次"}),i<100&&(i*=1.2),window.innerWidth<512);return{isAttack:e,stats:this.vStats,chart:null,minOpacity:.2,maxPercent:i,selectedCountryName:"",screenIsNarrow:t}},methods:{render:function(){this.chart=teaweb.initChart(document.getElementById("traffic-map-box"));let n=this;this.chart.setOption({backgroundColor:"white",grid:{top:0,bottom:0,left:0,right:0},roam:!1,tooltip:{trigger:"item"},series:[{type:"map",map:"world",zoom:1.3,selectedMode:!1,itemStyle:{areaColor:"#E9F0F9",borderColor:"#DDD"},label:{show:!1,fontSize:"10px",color:"#fff",backgroundColor:"#8B9BD3",padding:[2,2,2,2]},emphasis:{itemStyle:{areaColor:"#8B9BD3",opacity:1},label:{show:!0,fontSize:"10px",color:"#fff",backgroundColor:"#8B9BD3",padding:[2,2,2,2]}},tooltip:{formatter:function(e){let t=e.name,i=null;return n.stats.forEach(function(e){e.name==t&&(i=e)}),null!=i?t+"
流量:"+i.formattedBytes+"
流量占比:"+i.percent+"%
请求数:"+i.formattedCountRequests+"
攻击数:"+i.formattedCountAttackRequests:t}},data:this.stats.map(function(e){let t=parseFloat(e.percent)/n.maxPercent,i=3*(t=t @@ -214,23 +18,7 @@ Vue.component("traffic-map-box", {
-
` -}) - -Vue.component("traffic-map-box-table", { - props: ["v-stats", "v-is-attack", "v-screen-is-narrow"], - data: function () { - return { - stats: this.vStats, - isAttack: this.vIsAttack - } - }, - methods: { - select: function (countryName) { - this.$emit("select", {countryName: countryName}) - } - }, - template: `
+
`}),Vue.component("traffic-map-box-table",{props:["v-stats","v-is-attack","v-screen-is-narrow"],data:function(){return{stats:this.vStats,isAttack:this.vIsAttack}},methods:{select:function(e){this.$emit("select",{countryName:e})}},template:`
@@ -256,30 +44,7 @@ Vue.component("traffic-map-box-table", {
-
` -}) - -// 显示节点的多个集群 -Vue.component("node-clusters-labels", { - props: ["v-primary-cluster", "v-secondary-clusters", "size"], - data: function () { - let cluster = this.vPrimaryCluster - let secondaryClusters = this.vSecondaryClusters - if (secondaryClusters == null) { - secondaryClusters = [] - } - - let labelSize = this.size - if (labelSize == null) { - labelSize = "small" - } - return { - cluster: cluster, - secondaryClusters: secondaryClusters, - labelSize: labelSize - } - }, - template: `
+
`}),Vue.component("node-clusters-labels",{props:["v-primary-cluster","v-secondary-clusters","size"],data:function(){var e=this.vPrimaryCluster;let t=this.vSecondaryClusters,i=(null==t&&(t=[]),this.size);return null==i&&(i="small"),{cluster:e,secondaryClusters:t,labelSize:i}},template:`
{{cluster.name}} {{cluster.name}} @@ -288,135 +53,14 @@ Vue.component("node-clusters-labels", { {{c.name}} {{c.name}} -
` -}) - -// 单个集群选择 -Vue.component("cluster-selector", { - props: ["v-cluster-id"], - mounted: function () { - let that = this - - Tea.action("/clusters/options") - .post() - .success(function (resp) { - that.clusters = resp.data.clusters - }) - }, - data: function () { - let clusterId = this.vClusterId - if (clusterId == null) { - clusterId = 0 - } - return { - clusters: [], - clusterId: clusterId - } - }, - template: `
+
`}),Vue.component("cluster-selector",{props:["v-cluster-id"],mounted:function(){let t=this;Tea.action("/clusters/options").post().success(function(e){t.clusters=e.data.clusters})},data:function(){let e=this.vClusterId;return{clusters:[],clusterId:e=null==e?0:e}},template:`
-
` -}) - -Vue.component("node-cluster-combo-box", { - props: ["v-cluster-id"], - data: function () { - let that = this - Tea.action("/clusters/options") - .post() - .success(function (resp) { - that.clusters = resp.data.clusters - }) - return { - clusters: [] - } - }, - methods: { - change: function (item) { - if (item == null) { - this.$emit("change", 0) - } else { - this.$emit("change", item.value) - } - } - }, - template: `
+
`}),Vue.component("node-cluster-combo-box",{props:["v-cluster-id"],data:function(){let t=this;return Tea.action("/clusters/options").post().success(function(e){t.clusters=e.data.clusters}),{clusters:[]}},methods:{change:function(e){null==e?this.$emit("change",0):this.$emit("change",e.value)}},template:`
-
` -}) - -// 一个节点的多个集群选择器 -Vue.component("node-clusters-selector", { - props: ["v-primary-cluster", "v-secondary-clusters"], - data: function () { - let primaryCluster = this.vPrimaryCluster - - let secondaryClusters = this.vSecondaryClusters - if (secondaryClusters == null) { - secondaryClusters = [] - } - - return { - primaryClusterId: (primaryCluster == null) ? 0 : primaryCluster.id, - secondaryClusterIds: secondaryClusters.map(function (v) { - return v.id - }), - - primaryCluster: primaryCluster, - secondaryClusters: secondaryClusters - } - }, - methods: { - addPrimary: function () { - let that = this - let selectedClusterIds = [this.primaryClusterId].concat(this.secondaryClusterIds) - teaweb.popup("/clusters/selectPopup?selectedClusterIds=" + selectedClusterIds.join(",") + "&mode=single", { - height: "30em", - width: "50em", - callback: function (resp) { - if (resp.data.cluster != null) { - that.primaryCluster = resp.data.cluster - that.primaryClusterId = that.primaryCluster.id - that.notifyChange() - } - } - }) - }, - removePrimary: function () { - this.primaryClusterId = 0 - this.primaryCluster = null - this.notifyChange() - }, - addSecondary: function () { - let that = this - let selectedClusterIds = [this.primaryClusterId].concat(this.secondaryClusterIds) - teaweb.popup("/clusters/selectPopup?selectedClusterIds=" + selectedClusterIds.join(",") + "&mode=multiple", { - height: "30em", - width: "50em", - callback: function (resp) { - if (resp.data.cluster != null) { - that.secondaryClusterIds.push(resp.data.cluster.id) - that.secondaryClusters.push(resp.data.cluster) - that.notifyChange() - } - } - }) - }, - removeSecondary: function (index) { - this.secondaryClusterIds.$remove(index) - this.secondaryClusters.$remove(index) - this.notifyChange() - }, - notifyChange: function () { - this.$emit("change", { - clusterId: this.primaryClusterId - }) - } - }, - template: `
+
`}),Vue.component("node-clusters-selector",{props:["v-primary-cluster","v-secondary-clusters"],data:function(){var e=this.vPrimaryCluster;let t=this.vSecondaryClusters;return null==t&&(t=[]),{primaryClusterId:null==e?0:e.id,secondaryClusterIds:t.map(function(e){return e.id}),primaryCluster:e,secondaryClusters:t}},methods:{addPrimary:function(){let t=this,e=[this.primaryClusterId].concat(this.secondaryClusterIds);teaweb.popup("/clusters/selectPopup?selectedClusterIds="+e.join(",")+"&mode=single",{height:"30em",width:"50em",callback:function(e){null!=e.data.cluster&&(t.primaryCluster=e.data.cluster,t.primaryClusterId=t.primaryCluster.id,t.notifyChange())}})},removePrimary:function(){this.primaryClusterId=0,this.primaryCluster=null,this.notifyChange()},addSecondary:function(){let t=this,e=[this.primaryClusterId].concat(this.secondaryClusterIds);teaweb.popup("/clusters/selectPopup?selectedClusterIds="+e.join(",")+"&mode=multiple",{height:"30em",width:"50em",callback:function(e){null!=e.data.cluster&&(t.secondaryClusterIds.push(e.data.cluster.id),t.secondaryClusters.push(e.data.cluster),t.notifyChange())}})},removeSecondary:function(e){this.secondaryClusterIds.$remove(e),this.secondaryClusters.$remove(e),this.notifyChange()},notifyChange:function(){this.$emit("change",{clusterId:this.primaryClusterId})}},template:`
@@ -444,110 +88,13 @@ Vue.component("node-clusters-selector", {
-
` -}) - -Vue.component("message-media-selector", { - props: ["v-media-type"], - mounted: function () { - let that = this - Tea.action("/admins/recipients/mediaOptions") - .post() - .success(function (resp) { - that.medias = resp.data.medias - - // 初始化简介 - if (that.mediaType.length > 0) { - let media = that.medias.$find(function (_, media) { - return media.type == that.mediaType - }) - if (media != null) { - that.description = media.description - } - } - }) - }, - data: function () { - let mediaType = this.vMediaType - if (mediaType == null) { - mediaType = "" - } - return { - medias: [], - description: "", - mediaType: mediaType - } - }, - watch: { - mediaType: function (v) { - let media = this.medias.$find(function (_, media) { - return media.type == v - }) - if (media == null) { - this.description = "" - } else { - this.description = media.description - } - this.$emit("change", media) - }, - }, - template: `
+
`}),Vue.component("message-media-selector",{props:["v-media-type"],mounted:function(){let i=this;Tea.action("/admins/recipients/mediaOptions").post().success(function(e){i.medias=e.data.medias,0

-` -}) - -// 消息接收人设置 -Vue.component("message-receivers-box", { - props: ["v-node-cluster-id"], - mounted: function () { - let that = this - Tea.action("/clusters/cluster/settings/message/selectedReceivers") - .params({ - clusterId: this.clusterId - }) - .post() - .success(function (resp) { - that.receivers = resp.data.receivers - }) - }, - data: function () { - let clusterId = this.vNodeClusterId - if (clusterId == null) { - clusterId = 0 - } - return { - clusterId: clusterId, - receivers: [] - } - }, - methods: { - addReceiver: function () { - let that = this - let recipientIdStrings = [] - let groupIdStrings = [] - this.receivers.forEach(function (v) { - if (v.type == "recipient") { - recipientIdStrings.push(v.id.toString()) - } else if (v.type == "group") { - groupIdStrings.push(v.id.toString()) - } - }) - - teaweb.popup("/clusters/cluster/settings/message/selectReceiverPopup?recipientIds=" + recipientIdStrings.join(",") + "&groupIds=" + groupIdStrings.join(","), { - callback: function (resp) { - that.receivers.push(resp.data) - } - }) - }, - removeReceiver: function (index) { - this.receivers.$remove(index) - } - }, - template: `
+
`}),Vue.component("message-receivers-box",{props:["v-node-cluster-id"],mounted:function(){let t=this;Tea.action("/clusters/cluster/settings/message/selectedReceivers").params({clusterId:this.clusterId}).post().success(function(e){t.receivers=e.data.receivers})},data:function(){let e=this.vNodeClusterId;return{clusterId:e=null==e?0:e,receivers:[]}},methods:{addReceiver:function(){let t=this,i=[],s=[];this.receivers.forEach(function(e){"recipient"==e.type?i.push(e.id.toString()):"group"==e.type&&s.push(e.id.toString())}),teaweb.popup("/clusters/cluster/settings/message/selectReceiverPopup?recipientIds="+i.join(",")+"&groupIds="+s.join(","),{callback:function(e){t.receivers.push(e.data)}})},removeReceiver:function(e){this.receivers.$remove(e)}},template:`
@@ -556,53 +103,7 @@ Vue.component("message-receivers-box", {
-
` -}) - -Vue.component("message-recipient-group-selector", { - props: ["v-groups"], - data: function () { - let groups = this.vGroups - if (groups == null) { - groups = [] - } - let groupIds = [] - if (groups.length > 0) { - groupIds = groups.map(function (v) { - return v.id.toString() - }).join(",") - } - - return { - groups: groups, - groupIds: groupIds - } - }, - methods: { - addGroup: function () { - let that = this - teaweb.popup("/admins/recipients/groups/selectPopup?groupIds=" + this.groupIds, { - callback: function (resp) { - that.groups.push(resp.data.group) - that.update() - } - }) - }, - removeGroup: function (index) { - this.groups.$remove(index) - this.update() - }, - update: function () { - let groupIds = [] - if (this.groups.length > 0) { - this.groups.forEach(function (v) { - groupIds.push(v.id) - }) - } - this.groupIds = groupIds.join(",") - } - }, - template: `
+
`}),Vue.component("message-recipient-group-selector",{props:["v-groups"],data:function(){let e=this.vGroups,t=[];return 0<(e=null==e?[]:e).length&&(t=e.map(function(e){return e.id.toString()}).join(",")),{groups:e,groupIds:t}},methods:{addGroup:function(){let t=this;teaweb.popup("/admins/recipients/groups/selectPopup?groupIds="+this.groupIds,{callback:function(e){t.groups.push(e.data.group),t.update()}})},removeGroup:function(e){this.groups.$remove(e),this.update()},update:function(){let t=[];0
@@ -613,115 +114,13 @@ Vue.component("message-recipient-group-selector", {
-
` -}) - -Vue.component("message-media-instance-selector", { - props: ["v-instance-id"], - mounted: function () { - let that = this - Tea.action("/admins/recipients/instances/options") - .post() - .success(function (resp) { - that.instances = resp.data.instances - - // 初始化简介 - if (that.instanceId > 0) { - let instance = that.instances.$find(function (_, instance) { - return instance.id == that.instanceId - }) - if (instance != null) { - that.description = instance.description - that.update(instance.id) - } - } - }) - }, - data: function () { - let instanceId = this.vInstanceId - if (instanceId == null) { - instanceId = 0 - } - return { - instances: [], - description: "", - instanceId: instanceId - } - }, - watch: { - instanceId: function (v) { - this.update(v) - } - }, - methods: { - update: function (v) { - let instance = this.instances.$find(function (_, instance) { - return instance.id == v - }) - if (instance == null) { - this.description = "" - } else { - this.description = instance.description - } - this.$emit("change", instance) - } - }, - template: `
+
`}),Vue.component("message-media-instance-selector",{props:["v-instance-id"],mounted:function(){let i=this;Tea.action("/admins/recipients/instances/options").post().success(function(e){i.instances=e.data.instances,0

-
` -}) - -Vue.component("message-row", { - props: ["v-message", "v-can-close"], - data: function () { - let paramsJSON = this.vMessage.params - let params = null - if (paramsJSON != null && paramsJSON.length > 0) { - params = JSON.parse(paramsJSON) - } - - return { - message: this.vMessage, - params: params, - isClosing: false - } - }, - methods: { - viewCert: function (certId) { - teaweb.popup("/servers/certs/certPopup?certId=" + certId, { - height: "28em", - width: "48em" - }) - }, - readMessage: function (messageId) { - let that = this - - Tea.action("/messages/readPage") - .params({"messageIds": [messageId]}) - .post() - .success(function () { - // 刷新父级页面Badge - if (window.parent.Tea != null && window.parent.Tea.Vue != null) { - window.parent.Tea.Vue.checkMessagesOnce() - } - - // 刷新当前页面 - if (that.vCanClose && typeof (NotifyPopup) != "undefined") { - that.isClosing = true - setTimeout(function () { - NotifyPopup({}) - }, 1000) - } else { - teaweb.reload() - } - }) - } - }, - template: `
+
`}),Vue.component("message-row",{props:["v-message","v-can-close"],data:function(){var e=this.vMessage.params;let t=null;return null!=e&&0
@@ -776,73 +175,7 @@ Vue.component("message-row", {
-` -}) - -// 选择多个线路 -Vue.component("ns-routes-selector", { - props: ["v-routes"], - mounted: function () { - let that = this - Tea.action("/ns/routes/options") - .post() - .success(function (resp) { - that.routes = resp.data.routes - }) - }, - data: function () { - let selectedRoutes = this.vRoutes - if (selectedRoutes == null) { - selectedRoutes = [] - } - - return { - routeCode: "default", - routes: [], - isAdding: false, - routeType: "default", - selectedRoutes: selectedRoutes - } - }, - watch: { - routeType: function (v) { - this.routeCode = "" - let that = this - this.routes.forEach(function (route) { - if (route.type == v && that.routeCode.length == 0) { - that.routeCode = route.code - } - }) - } - }, - methods: { - add: function () { - this.isAdding = true - this.routeType = "default" - this.routeCode = "default" - }, - cancel: function () { - this.isAdding = false - }, - confirm: function () { - if (this.routeCode.length == 0) { - return - } - - let that = this - this.routes.forEach(function (v) { - if (v.code == that.routeCode) { - that.selectedRoutes.push(v) - } - }) - this.cancel() - }, - remove: function (index) { - this.selectedRoutes.$remove(index) - } - } - , - template: `
+
`}),Vue.component("ns-routes-selector",{props:["v-routes"],mounted:function(){let t=this;Tea.action("/ns/routes/options").post().success(function(e){t.routes=e.data.routes})},data:function(){let e=this.vRoutes;return{routeCode:"default",routes:[],isAdding:!1,routeType:"default",selectedRoutes:e=null==e?[]:e}},watch:{routeType:function(t){this.routeCode="";let i=this;this.routes.forEach(function(e){e.type==t&&0==i.routeCode.length&&(i.routeCode=e.code)})}},methods:{add:function(){this.isAdding=!0,this.routeType="default",this.routeCode="default"},cancel:function(){this.isAdding=!1},confirm:function(){if(0!=this.routeCode.length){let t=this;this.routes.forEach(function(e){e.code==t.routeCode&&t.selectedRoutes.push(e)}),this.cancel()}},remove:function(e){this.selectedRoutes.$remove(e)}},template:`
@@ -875,105 +208,7 @@ Vue.component("ns-routes-selector", {
-
` -}) - -// 递归DNS设置 -Vue.component("ns-recursion-config-box", { - props: ["v-recursion-config"], - data: function () { - let recursion = this.vRecursionConfig - if (recursion == null) { - recursion = { - isOn: false, - hosts: [], - allowDomains: [], - denyDomains: [], - useLocalHosts: false - } - } - if (recursion.hosts == null) { - recursion.hosts = [] - } - if (recursion.allowDomains == null) { - recursion.allowDomains = [] - } - if (recursion.denyDomains == null) { - recursion.denyDomains = [] - } - return { - config: recursion, - hostIsAdding: false, - host: "", - updatingHost: null - } - }, - methods: { - changeHosts: function (hosts) { - this.config.hosts = hosts - }, - changeAllowDomains: function (domains) { - this.config.allowDomains = domains - }, - changeDenyDomains: function (domains) { - this.config.denyDomains = domains - }, - removeHost: function (index) { - this.config.hosts.$remove(index) - }, - addHost: function () { - this.updatingHost = null - this.host = "" - this.hostIsAdding = !this.hostIsAdding - if (this.hostIsAdding) { - var that = this - setTimeout(function () { - let hostRef = that.$refs.hostRef - if (hostRef != null) { - hostRef.focus() - } - }, 200) - } - }, - updateHost: function (host) { - this.updatingHost = host - this.host = host.host - this.hostIsAdding = !this.hostIsAdding - - if (this.hostIsAdding) { - var that = this - setTimeout(function () { - let hostRef = that.$refs.hostRef - if (hostRef != null) { - hostRef.focus() - } - }, 200) - } - }, - confirmHost: function () { - if (this.host.length == 0) { - teaweb.warn("请输入DNS地址") - return - } - - // TODO 校验Host - // TODO 可以输入端口号 - // TODO 可以选择协议 - - this.hostIsAdding = false - if (this.updatingHost == null) { - this.config.hosts.push({ - host: this.host - }) - } else { - this.updatingHost.host = this.host - } - }, - cancelHost: function () { - this.hostIsAdding = false - } - }, - template: `
+
`}),Vue.component("ns-recursion-config-box",{props:["v-recursion-config"],data:function(){let e=this.vRecursionConfig;return null==(e=null==e?{isOn:!1,hosts:[],allowDomains:[],denyDomains:[],useLocalHosts:!1}:e).hosts&&(e.hosts=[]),null==e.allowDomains&&(e.allowDomains=[]),null==e.denyDomains&&(e.denyDomains=[]),{config:e,hostIsAdding:!1,host:"",updatingHost:null}},methods:{changeHosts:function(e){this.config.hosts=e},changeAllowDomains:function(e){this.config.allowDomains=e},changeDenyDomains:function(e){this.config.denyDomains=e},removeHost:function(e){this.config.hosts.$remove(e)},addHost:function(){var t;this.updatingHost=null,this.host="",this.hostIsAdding=!this.hostIsAdding,this.hostIsAdding&&(t=this,setTimeout(function(){let e=t.$refs.hostRef;null!=e&&e.focus()},200))},updateHost:function(e){var t;this.updatingHost=e,this.host=e.host,this.hostIsAdding=!this.hostIsAdding,this.hostIsAdding&&(t=this,setTimeout(function(){let e=t.$refs.hostRef;null!=e&&e.focus()},200))},confirmHost:function(){0==this.host.length?teaweb.warn("请输入DNS地址"):(this.hostIsAdding=!1,null==this.updatingHost?this.config.hosts.push({host:this.host}):this.updatingHost.host=this.host)},cancelHost:function(){this.hostIsAdding=!1}},template:`
@@ -1041,28 +276,7 @@ Vue.component("ns-recursion-config-box", {
-
` -}) - -Vue.component("ns-access-log-ref-box", { - props: ["v-access-log-ref", "v-is-parent"], - data: function () { - let config = this.vAccessLogRef - if (config == null) { - config = { - isOn: false, - isPrior: false, - logMissingDomains: false - } - } - if (typeof (config.logMissingDomains) == "undefined") { - config.logMissingDomains = false - } - return { - config: config - } - }, - template: `
+
`}),Vue.component("ns-access-log-ref-box",{props:["v-access-log-ref","v-is-parent"],data:function(){let e=this.vAccessLogRef;return void 0===(e=null==e?{isOn:!1,isPrior:!1,logMissingDomains:!1}:e).logMissingDomains&&(e.logMissingDomains=!1),{config:e}},template:`
@@ -1083,85 +297,7 @@ Vue.component("ns-access-log-ref-box", {
-
` -}) - -Vue.component("ns-route-ranges-box", { - props: ["v-ranges"], - data: function () { - let ranges = this.vRanges - if (ranges == null) { - ranges = [] - } - return { - ranges: ranges, - isAdding: false, - - // IP范围 - ipRangeFrom: "", - ipRangeTo: "" - } - }, - methods: { - add: function () { - this.isAdding = true - let that = this - setTimeout(function () { - that.$refs.ipRangeFrom.focus() - }, 100) - }, - remove: function (index) { - this.ranges.$remove(index) - }, - cancelIPRange: function () { - this.isAdding = false - this.ipRangeFrom = "" - this.ipRangeTo = "" - }, - confirmIPRange: function () { - // 校验IP - let that = this - this.ipRangeFrom = this.ipRangeFrom.trim() - if (!this.validateIP(this.ipRangeFrom)) { - teaweb.warn("开始IP填写错误", function () { - that.$refs.ipRangeFrom.focus() - }) - return - } - - this.ipRangeTo = this.ipRangeTo.trim() - if (!this.validateIP(this.ipRangeTo)) { - teaweb.warn("结束IP填写错误", function () { - that.$refs.ipRangeTo.focus() - }) - return - } - - this.ranges.push({ - type: "ipRange", - params: { - ipFrom: this.ipRangeFrom, - ipTo: this.ipRangeTo - } - }) - this.cancelIPRange() - }, - validateIP: function (ip) { - if (!ip.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/)) { - return false - } - let pieces = ip.split(".") - let isOk = true - pieces.forEach(function (v) { - let v1 = parseInt(v) - if (v1 > 255) { - isOk = false - } - }) - return isOk - } - }, - template: `
+
`}),Vue.component("ns-route-ranges-box",{props:["v-ranges"],data:function(){let e=this.vRanges;return{ranges:e=null==e?[]:e,isAdding:!1,ipRangeFrom:"",ipRangeTo:""}},methods:{add:function(){this.isAdding=!0;let e=this;setTimeout(function(){e.$refs.ipRangeFrom.focus()},100)},remove:function(e){this.ranges.$remove(e)},cancelIPRange:function(){this.isAdding=!1,this.ipRangeFrom="",this.ipRangeTo=""},confirmIPRange:function(){let e=this;this.ipRangeFrom=this.ipRangeFrom.trim(),this.validateIP(this.ipRangeFrom)?(this.ipRangeTo=this.ipRangeTo.trim(),this.validateIP(this.ipRangeTo)?(this.ranges.push({type:"ipRange",params:{ipFrom:this.ipRangeFrom,ipTo:this.ipRangeTo}}),this.cancelIPRange()):teaweb.warn("结束IP填写错误",function(){e.$refs.ipRangeTo.focus()})):teaweb.warn("开始IP填写错误",function(){e.$refs.ipRangeFrom.focus()})},validateIP:function(e){if(!e.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/))return!1;let t=e.split("."),i=!0;return t.forEach(function(e){255
@@ -1189,104 +325,19 @@ Vue.component("ns-route-ranges-box", {
-
` -}) - -// 选择单一线路 -Vue.component("ns-route-selector", { - props: ["v-route-code"], - mounted: function () { - let that = this - Tea.action("/ns/routes/options") - .post() - .success(function (resp) { - that.routes = resp.data.routes - }) - }, - data: function () { - let routeCode = this.vRouteCode - if (routeCode == null) { - routeCode = "" - } - return { - routeCode: routeCode, - routes: [] - } - }, - template: `
+
`}),Vue.component("ns-route-selector",{props:["v-route-code"],mounted:function(){let t=this;Tea.action("/ns/routes/options").post().success(function(e){t.routes=e.data.routes})},data:function(){let e=this.vRouteCode;return{routeCode:e=null==e?"":e,routes:[]}},template:`
-
` -}) - -Vue.component("ns-user-selector", { - mounted: function () { - let that = this - - Tea.action("/ns/users/options") - .post() - .success(function (resp) { - that.users = resp.data.users - }) - }, - props: ["v-user-id"], - data: function () { - let userId = this.vUserId - if (userId == null) { - userId = 0 - } - return { - users: [], - userId: userId - } - }, - template: `
+
`}),Vue.component("ns-user-selector",{mounted:function(){let t=this;Tea.action("/ns/users/options").post().success(function(e){t.users=e.data.users})},props:["v-user-id"],data:function(){let e=this.vUserId;return{users:[],userId:e=null==e?0:e}},template:`
-
` -}) - -Vue.component("ns-access-log-box", { - props: ["v-access-log", "v-keyword"], - data: function () { - let accessLog = this.vAccessLog - return { - accessLog: accessLog - } - }, - methods: { - showLog: function () { - let that = this - let requestId = this.accessLog.requestId - this.$parent.$children.forEach(function (v) { - if (v.deselect != null) { - v.deselect() - } - }) - this.select() - - teaweb.popup("/ns/clusters/accessLogs/viewPopup?requestId=" + requestId, { - width: "50em", - height: "24em", - onClose: function () { - that.deselect() - } - }) - }, - select: function () { - this.$refs.box.parentNode.style.cssText = "background: rgba(0, 0, 0, 0.1)" - }, - deselect: function () { - this.$refs.box.parentNode.style.cssText = "" - } - }, - template: `
+
`}),Vue.component("ns-access-log-box",{props:["v-access-log","v-keyword"],data:function(){return{accessLog:this.vAccessLog}},methods:{showLog:function(){let e=this;var t=this.accessLog.requestId;this.$parent.$children.forEach(function(e){null!=e.deselect&&e.deselect()}),this.select(),teaweb.popup("/ns/clusters/accessLogs/viewPopup?requestId="+t,{width:"50em",height:"24em",onClose:function(){e.deselect()}})},select:function(){this.$refs.box.parentNode.style.cssText="background: rgba(0, 0, 0, 0.1)"},deselect:function(){this.$refs.box.parentNode.style.cssText=""}},template:`
[{{accessLog.region}}] {{accessLog.remoteAddr}} [{{accessLog.timeLocal}}] [{{accessLog.networking}}] {{accessLog.questionType}} {{accessLog.questionName}} -> {{accessLog.recordType}} {{accessLog.recordValue}}
线路: {{route.name}} @@ -1295,80 +346,17 @@ Vue.component("ns-access-log-box", {
错误:[{{accessLog.error}}]
-
` -}) - -Vue.component("ns-cluster-selector", { - props: ["v-cluster-id"], - mounted: function () { - let that = this - - Tea.action("/ns/clusters/options") - .post() - .success(function (resp) { - that.clusters = resp.data.clusters - }) - }, - data: function () { - let clusterId = this.vClusterId - if (clusterId == null) { - clusterId = 0 - } - return { - clusters: [], - clusterId: clusterId - } - }, - template: `
+
`}),Vue.component("ns-cluster-selector",{props:["v-cluster-id"],mounted:function(){let t=this;Tea.action("/ns/clusters/options").post().success(function(e){t.clusters=e.data.clusters})},data:function(){let e=this.vClusterId;return{clusters:[],clusterId:e=null==e?0:e}},template:`
-
` -}) - -Vue.component("plan-user-selector", { - mounted: function () { - let that = this - - Tea.action("/plans/users/options") - .post() - .success(function (resp) { - that.users = resp.data.users - }) - }, - props: ["v-user-id"], - data: function () { - let userId = this.vUserId - if (userId == null) { - userId = 0 - } - return { - users: [], - userId: userId - } - }, - watch: { - userId: function (v) { - this.$emit("change", v) - } - }, - template: `
+
`}),Vue.component("plan-user-selector",{mounted:function(){let t=this;Tea.action("/plans/users/options").post().success(function(e){t.users=e.data.users})},props:["v-user-id"],data:function(){let e=this.vUserId;return{users:[],userId:e=null==e?0:e}},watch:{userId:function(e){this.$emit("change",e)}},template:`
-
` -}) - -Vue.component("plan-price-view", { - props: ["v-plan"], - data: function () { - return { - plan: this.vPlan - } - }, - template: `
+
`}),Vue.component("plan-price-view",{props:["v-plan"],data:function(){return{plan:this.vPlan}},template:`
按时间周期计费
@@ -1393,97 +381,7 @@ Vue.component("plan-price-view", {
-` -}) - -Vue.component("plan-bandwidth-ranges", { - props: ["v-ranges"], - data: function () { - let ranges = this.vRanges - if (ranges == null) { - ranges = [] - } - return { - ranges: ranges, - isAdding: false, - - minMB: "", - maxMB: "", - pricePerMB: "", - addingRange: { - minMB: 0, - maxMB: 0, - pricePerMB: 0, - totalPrice: 0 - } - } - }, - methods: { - add: function () { - this.isAdding = !this.isAdding - let that = this - setTimeout(function () { - that.$refs.minMB.focus() - }) - }, - cancelAdding: function () { - this.isAdding = false - }, - confirm: function () { - this.isAdding = false - this.minMB = "" - this.maxMB = "" - this.pricePerMB = "" - this.ranges.push(this.addingRange) - this.ranges.$sort(function (v1, v2) { - if (v1.minMB < v2.minMB) { - return -1 - } - if (v1.minMB == v2.minMB) { - return 0 - } - return 1 - }) - this.change() - this.addingRange = { - minMB: 0, - maxMB: 0, - pricePerMB: 0, - totalPrice: 0 - } - }, - remove: function (index) { - this.ranges.$remove(index) - this.change() - }, - change: function () { - this.$emit("change", this.ranges) - } - }, - watch: { - minMB: function (v) { - let minMB = parseInt(v.toString()) - if (isNaN(minMB) || minMB < 0) { - minMB = 0 - } - this.addingRange.minMB = minMB - }, - maxMB: function (v) { - let maxMB = parseInt(v.toString()) - if (isNaN(maxMB) || maxMB < 0) { - maxMB = 0 - } - this.addingRange.maxMB = maxMB - }, - pricePerMB: function (v) { - let pricePerMB = parseFloat(v.toString()) - if (isNaN(pricePerMB) || pricePerMB < 0) { - pricePerMB = 0 - } - this.addingRange.pricePerMB = pricePerMB - } - }, - template: `
+
`}),Vue.component("plan-bandwidth-ranges",{props:["v-ranges"],data:function(){let e=this.vRanges;return{ranges:e=null==e?[]:e,isAdding:!1,minMB:"",maxMB:"",pricePerMB:"",addingRange:{minMB:0,maxMB:0,pricePerMB:0,totalPrice:0}}},methods:{add:function(){this.isAdding=!this.isAdding;let e=this;setTimeout(function(){e.$refs.minMB.focus()})},cancelAdding:function(){this.isAdding=!1},confirm:function(){this.isAdding=!1,this.minMB="",this.maxMB="",this.pricePerMB="",this.ranges.push(this.addingRange),this.ranges.$sort(function(e,t){return e.minMB
@@ -1533,143 +431,7 @@ Vue.component("plan-bandwidth-ranges", {
-
` -}) - -// 套餐价格配置 -Vue.component("plan-price-config-box", { - props: ["v-price-type", "v-monthly-price", "v-seasonally-price", "v-yearly-price", "v-traffic-price", "v-bandwidth-price", "v-disable-period"], - data: function () { - let priceType = this.vPriceType - if (priceType == null) { - priceType = "bandwidth" - } - - // 按时间周期计费 - let monthlyPriceNumber = 0 - let monthlyPrice = this.vMonthlyPrice - if (monthlyPrice == null || monthlyPrice <= 0) { - monthlyPrice = "" - } else { - monthlyPrice = monthlyPrice.toString() - monthlyPriceNumber = parseFloat(monthlyPrice) - if (isNaN(monthlyPriceNumber)) { - monthlyPriceNumber = 0 - } - } - - let seasonallyPriceNumber = 0 - let seasonallyPrice = this.vSeasonallyPrice - if (seasonallyPrice == null || seasonallyPrice <= 0) { - seasonallyPrice = "" - } else { - seasonallyPrice = seasonallyPrice.toString() - seasonallyPriceNumber = parseFloat(seasonallyPrice) - if (isNaN(seasonallyPriceNumber)) { - seasonallyPriceNumber = 0 - } - } - - let yearlyPriceNumber = 0 - let yearlyPrice = this.vYearlyPrice - if (yearlyPrice == null || yearlyPrice <= 0) { - yearlyPrice = "" - } else { - yearlyPrice = yearlyPrice.toString() - yearlyPriceNumber = parseFloat(yearlyPrice) - if (isNaN(yearlyPriceNumber)) { - yearlyPriceNumber = 0 - } - } - - // 按流量计费 - let trafficPrice = this.vTrafficPrice - let trafficPriceBaseNumber = 0 - if (trafficPrice != null) { - trafficPriceBaseNumber = trafficPrice.base - } else { - trafficPrice = { - base: 0 - } - } - let trafficPriceBase = "" - if (trafficPriceBaseNumber > 0) { - trafficPriceBase = trafficPriceBaseNumber.toString() - } - - // 按带宽计费 - let bandwidthPrice = this.vBandwidthPrice - if (bandwidthPrice == null) { - bandwidthPrice = { - percentile: 95, - ranges: [] - } - } else if (bandwidthPrice.ranges == null) { - bandwidthPrice.ranges = [] - } - - return { - priceType: priceType, - monthlyPrice: monthlyPrice, - seasonallyPrice: seasonallyPrice, - yearlyPrice: yearlyPrice, - - monthlyPriceNumber: monthlyPriceNumber, - seasonallyPriceNumber: seasonallyPriceNumber, - yearlyPriceNumber: yearlyPriceNumber, - - trafficPriceBase: trafficPriceBase, - trafficPrice: trafficPrice, - - bandwidthPrice: bandwidthPrice, - bandwidthPercentile: bandwidthPrice.percentile - } - }, - methods: { - changeBandwidthPriceRanges: function (ranges) { - this.bandwidthPrice.ranges = ranges - } - }, - watch: { - monthlyPrice: function (v) { - let price = parseFloat(v) - if (isNaN(price)) { - price = 0 - } - this.monthlyPriceNumber = price - }, - seasonallyPrice: function (v) { - let price = parseFloat(v) - if (isNaN(price)) { - price = 0 - } - this.seasonallyPriceNumber = price - }, - yearlyPrice: function (v) { - let price = parseFloat(v) - if (isNaN(price)) { - price = 0 - } - this.yearlyPriceNumber = price - }, - trafficPriceBase: function (v) { - let price = parseFloat(v) - if (isNaN(price)) { - price = 0 - } - this.trafficPrice.base = price - }, - bandwidthPercentile: function (v) { - let percentile = parseInt(v) - if (isNaN(percentile) || percentile <= 0) { - percentile = 95 - } else if (percentile > 100) { - percentile = 100 - } - this.bandwidthPrice.percentile = percentile - } - }, - template: `
+
`}),Vue.component("plan-price-config-box",{props:["v-price-type","v-monthly-price","v-seasonally-price","v-yearly-price","v-traffic-price","v-bandwidth-price","v-disable-period"],data:function(){let e=this.vPriceType,t=(null==e&&(e="bandwidth"),0),i=this.vMonthlyPrice,s=(null==i||i<=0?i="":(i=i.toString(),t=parseFloat(i),isNaN(t)&&(t=0)),0),n=this.vSeasonallyPrice,o=(null==n||n<=0?n="":(n=n.toString(),s=parseFloat(n),isNaN(s)&&(s=0)),0),a=this.vYearlyPrice,l=(null==a||a<=0?a="":(a=a.toString(),o=parseFloat(a),isNaN(o)&&(o=0)),this.vTrafficPrice),r=0,c=(null!=l?r=l.base:l={base:0},""),d=(0 @@ -1754,24 +516,7 @@ Vue.component("plan-price-config-box", {
-` -}) - -Vue.component("http-stat-config-box", { - props: ["v-stat-config", "v-is-location", "v-is-group"], - data: function () { - let stat = this.vStatConfig - if (stat == null) { - stat = { - isPrior: false, - isOn: false - } - } - return { - stat: stat - } - }, - template: `
+
`}),Vue.component("http-stat-config-box",{props:["v-stat-config","v-is-location","v-is-group"],data:function(){let e=this.vStatConfig;return{stat:e=null==e?{isPrior:!1,isOn:!1}:e}},template:`
@@ -1788,70 +533,7 @@ Vue.component("http-stat-config-box", {
-
` -}) - -Vue.component("http-request-conds-box", { - props: ["v-conds"], - data: function () { - let conds = this.vConds - if (conds == null) { - conds = { - isOn: true, - connector: "or", - groups: [] - } - } - return { - conds: conds, - components: window.REQUEST_COND_COMPONENTS - } - }, - methods: { - change: function () { - this.$emit("change", this.conds) - }, - addGroup: function () { - window.UPDATING_COND_GROUP = null - - let that = this - teaweb.popup("/servers/server/settings/conds/addGroupPopup", { - height: "30em", - callback: function (resp) { - that.conds.groups.push(resp.data.group) - that.change() - } - }) - }, - updateGroup: function (groupIndex, group) { - window.UPDATING_COND_GROUP = group - let that = this - teaweb.popup("/servers/server/settings/conds/addGroupPopup", { - height: "30em", - callback: function (resp) { - Vue.set(that.conds.groups, groupIndex, resp.data.group) - that.change() - } - }) - }, - removeGroup: function (groupIndex) { - let that = this - teaweb.confirm("确定要删除这一组条件吗?", function () { - that.conds.groups.$remove(groupIndex) - that.change() - }) - }, - typeName: function (cond) { - let c = this.components.$find(function (k, v) { - return v.type == cond.type - }) - if (c != null) { - return c.name; - } - return cond.param + " " + cond.operator - } - }, - template: `
+
`}),Vue.component("http-request-conds-box",{props:["v-conds"],data:function(){let e=this.vConds;return{conds:e=null==e?{isOn:!0,connector:"or",groups:[]}:e,components:window.REQUEST_COND_COMPONENTS}},methods:{change:function(){this.$emit("change",this.conds)},addGroup:function(){window.UPDATING_COND_GROUP=null;let t=this;teaweb.popup("/servers/server/settings/conds/addGroupPopup",{height:"30em",callback:function(e){t.conds.groups.push(e.data.group),t.change()}})},updateGroup:function(t,e){window.UPDATING_COND_GROUP=e;let i=this;teaweb.popup("/servers/server/settings/conds/addGroupPopup",{height:"30em",callback:function(e){Vue.set(i.conds.groups,t,e.data.group),i.change()}})},removeGroup:function(e){let t=this;teaweb.confirm("确定要删除这一组条件吗?",function(){t.conds.groups.$remove(e),t.change()})},typeName:function(i){var e=this.components.$find(function(e,t){return t.type==i.type});return null!=e?e.name:i.param+" "+i.operator}},template:`
@@ -1898,348 +580,7 @@ Vue.component("http-request-conds-box", { -` -}) - -Vue.component("ssl-config-box", { - props: ["v-ssl-policy", "v-protocol", "v-server-id"], - created: function () { - let that = this - setTimeout(function () { - that.sortableCipherSuites() - }, 100) - }, - data: function () { - let policy = this.vSslPolicy - if (policy == null) { - policy = { - id: 0, - isOn: true, - certRefs: [], - certs: [], - clientCARefs: [], - clientCACerts: [], - clientAuthType: 0, - minVersion: "TLS 1.1", - hsts: null, - cipherSuitesIsOn: false, - cipherSuites: [], - http2Enabled: true, - ocspIsOn: false - } - } else { - if (policy.certRefs == null) { - policy.certRefs = [] - } - if (policy.certs == null) { - policy.certs = [] - } - if (policy.clientCARefs == null) { - policy.clientCARefs = [] - } - if (policy.clientCACerts == null) { - policy.clientCACerts = [] - } - if (policy.cipherSuites == null) { - policy.cipherSuites = [] - } - } - - let hsts = policy.hsts - if (hsts == null) { - hsts = { - isOn: false, - maxAge: 31536000, - includeSubDomains: false, - preload: false, - domains: [] - } - } - - return { - policy: policy, - - // hsts - hsts: hsts, - hstsOptionsVisible: false, - hstsDomainAdding: false, - addingHstsDomain: "", - hstsDomainEditingIndex: -1, - - // 相关数据 - allVersions: window.SSL_ALL_VERSIONS, - allCipherSuites: window.SSL_ALL_CIPHER_SUITES.$copy(), - modernCipherSuites: window.SSL_MODERN_CIPHER_SUITES, - intermediateCipherSuites: window.SSL_INTERMEDIATE_CIPHER_SUITES, - allClientAuthTypes: window.SSL_ALL_CLIENT_AUTH_TYPES, - cipherSuitesVisible: false, - - // 高级选项 - moreOptionsVisible: false - } - }, - watch: { - hsts: { - deep: true, - handler: function () { - this.policy.hsts = this.hsts - } - } - }, - methods: { - // 删除证书 - removeCert: function (index) { - let that = this - teaweb.confirm("确定删除此证书吗?证书数据仍然保留,只是当前服务不再使用此证书。", function () { - that.policy.certRefs.$remove(index) - that.policy.certs.$remove(index) - }) - }, - - // 选择证书 - selectCert: function () { - let that = this - let selectedCertIds = [] - if (this.policy != null && this.policy.certs.length > 0) { - this.policy.certs.forEach(function (cert) { - selectedCertIds.push(cert.id.toString()) - }) - } - teaweb.popup("/servers/certs/selectPopup?selectedCertIds=" + selectedCertIds, { - width: "50em", - height: "30em", - callback: function (resp) { - that.policy.certRefs.push(resp.data.certRef) - that.policy.certs.push(resp.data.cert) - } - }) - }, - - // 上传证书 - uploadCert: function () { - let that = this - teaweb.popup("/servers/certs/uploadPopup", { - height: "28em", - callback: function (resp) { - teaweb.success("上传成功", function () { - that.policy.certRefs.push(resp.data.certRef) - that.policy.certs.push(resp.data.cert) - }) - } - }) - }, - - // 申请证书 - requestCert: function () { - // 已经在证书中的域名 - let excludeServerNames = [] - if (this.policy != null && this.policy.certs.length > 0) { - this.policy.certs.forEach(function (cert) { - excludeServerNames.$pushAll(cert.dnsNames) - }) - } - - let that = this - teaweb.popup("/servers/server/settings/https/requestCertPopup?serverId=" + this.vServerId + "&excludeServerNames=" + excludeServerNames.join(","), { - callback: function () { - that.policy.certRefs.push(resp.data.certRef) - that.policy.certs.push(resp.data.cert) - } - }) - }, - - // 更多选项 - changeOptionsVisible: function () { - this.moreOptionsVisible = !this.moreOptionsVisible - }, - - // 格式化时间 - formatTime: function (timestamp) { - return new Date(timestamp * 1000).format("Y-m-d") - }, - - // 格式化加密套件 - formatCipherSuite: function (cipherSuite) { - return cipherSuite.replace(/(AES|3DES)/, "$1") - }, - - // 添加单个套件 - addCipherSuite: function (cipherSuite) { - if (!this.policy.cipherSuites.$contains(cipherSuite)) { - this.policy.cipherSuites.push(cipherSuite) - } - this.allCipherSuites.$removeValue(cipherSuite) - }, - - // 删除单个套件 - removeCipherSuite: function (cipherSuite) { - let that = this - teaweb.confirm("确定要删除此套件吗?", function () { - that.policy.cipherSuites.$removeValue(cipherSuite) - that.allCipherSuites = window.SSL_ALL_CIPHER_SUITES.$findAll(function (k, v) { - return !that.policy.cipherSuites.$contains(v) - }) - }) - }, - - // 清除所选套件 - clearCipherSuites: function () { - let that = this - teaweb.confirm("确定要清除所有已选套件吗?", function () { - that.policy.cipherSuites = [] - that.allCipherSuites = window.SSL_ALL_CIPHER_SUITES.$copy() - }) - }, - - // 批量添加套件 - addBatchCipherSuites: function (suites) { - var that = this - teaweb.confirm("确定要批量添加套件?", function () { - suites.$each(function (k, v) { - if (that.policy.cipherSuites.$contains(v)) { - return - } - that.policy.cipherSuites.push(v) - }) - }) - }, - - /** - * 套件拖动排序 - */ - sortableCipherSuites: function () { - var box = document.querySelector(".cipher-suites-box") - Sortable.create(box, { - draggable: ".label", - handle: ".icon.handle", - onStart: function () { - - }, - onUpdate: function (event) { - - } - }) - }, - - // 显示所有套件 - showAllCipherSuites: function () { - this.cipherSuitesVisible = !this.cipherSuitesVisible - }, - - // 显示HSTS更多选项 - showMoreHSTS: function () { - this.hstsOptionsVisible = !this.hstsOptionsVisible; - if (this.hstsOptionsVisible) { - this.changeHSTSMaxAge() - } - }, - - // 监控HSTS有效期修改 - changeHSTSMaxAge: function () { - var v = this.hsts.maxAge - if (isNaN(v)) { - this.hsts.days = "-" - return - } - this.hsts.days = parseInt(v / 86400) - if (isNaN(this.hsts.days)) { - this.hsts.days = "-" - } else if (this.hsts.days < 0) { - this.hsts.days = "-" - } - }, - - // 设置HSTS有效期 - setHSTSMaxAge: function (maxAge) { - this.hsts.maxAge = maxAge - this.changeHSTSMaxAge() - }, - - // 添加HSTS域名 - addHstsDomain: function () { - this.hstsDomainAdding = true - this.hstsDomainEditingIndex = -1 - let that = this - setTimeout(function () { - that.$refs.addingHstsDomain.focus() - }, 100) - }, - - // 修改HSTS域名 - editHstsDomain: function (index) { - this.hstsDomainEditingIndex = index - this.addingHstsDomain = this.hsts.domains[index] - this.hstsDomainAdding = true - let that = this - setTimeout(function () { - that.$refs.addingHstsDomain.focus() - }, 100) - }, - - // 确认HSTS域名添加 - confirmAddHstsDomain: function () { - this.addingHstsDomain = this.addingHstsDomain.trim() - if (this.addingHstsDomain.length == 0) { - return; - } - if (this.hstsDomainEditingIndex > -1) { - this.hsts.domains[this.hstsDomainEditingIndex] = this.addingHstsDomain - } else { - this.hsts.domains.push(this.addingHstsDomain) - } - this.cancelHstsDomainAdding() - }, - - // 取消HSTS域名添加 - cancelHstsDomainAdding: function () { - this.hstsDomainAdding = false - this.addingHstsDomain = "" - this.hstsDomainEditingIndex = -1 - }, - - // 删除HSTS域名 - removeHstsDomain: function (index) { - this.cancelHstsDomainAdding() - this.hsts.domains.$remove(index) - }, - - // 选择客户端CA证书 - selectClientCACert: function () { - let that = this - teaweb.popup("/servers/certs/selectPopup?isCA=1", { - width: "50em", - height: "30em", - callback: function (resp) { - that.policy.clientCARefs.push(resp.data.certRef) - that.policy.clientCACerts.push(resp.data.cert) - } - }) - }, - - // 上传CA证书 - uploadClientCACert: function () { - let that = this - teaweb.popup("/servers/certs/uploadPopup?isCA=1", { - height: "28em", - callback: function (resp) { - teaweb.success("上传成功", function () { - that.policy.clientCARefs.push(resp.data.certRef) - that.policy.clientCACerts.push(resp.data.cert) - }) - } - }) - }, - - // 删除客户端CA证书 - removeClientCACert: function (index) { - let that = this - teaweb.confirm("确定删除此证书吗?证书数据仍然保留,只是当前服务不再使用此证书。", function () { - that.policy.clientCARefs.$remove(index) - that.policy.clientCACerts.$remove(index) - }) - } - }, - template: `
+
`}),Vue.component("ssl-config-box",{props:["v-ssl-policy","v-protocol","v-server-id"],created:function(){let e=this;setTimeout(function(){e.sortableCipherSuites()},100)},data:function(){let e=this.vSslPolicy,t=(null==e?e={id:0,isOn:!0,certRefs:[],certs:[],clientCARefs:[],clientCACerts:[],clientAuthType:0,minVersion:"TLS 1.1",hsts:null,cipherSuitesIsOn:!1,cipherSuites:[],http2Enabled:!0,ocspIsOn:!1}:(null==e.certRefs&&(e.certRefs=[]),null==e.certs&&(e.certs=[]),null==e.clientCARefs&&(e.clientCARefs=[]),null==e.clientCACerts&&(e.clientCACerts=[]),null==e.cipherSuites&&(e.cipherSuites=[])),e.hsts);return null==t&&(t={isOn:!1,maxAge:31536e3,includeSubDomains:!1,preload:!1,domains:[]}),{policy:e,hsts:t,hstsOptionsVisible:!1,hstsDomainAdding:!1,addingHstsDomain:"",hstsDomainEditingIndex:-1,allVersions:window.SSL_ALL_VERSIONS,allCipherSuites:window.SSL_ALL_CIPHER_SUITES.$copy(),modernCipherSuites:window.SSL_MODERN_CIPHER_SUITES,intermediateCipherSuites:window.SSL_INTERMEDIATE_CIPHER_SUITES,allClientAuthTypes:window.SSL_ALL_CLIENT_AUTH_TYPES,cipherSuitesVisible:!1,moreOptionsVisible:!1}},watch:{hsts:{deep:!0,handler:function(){this.policy.hsts=this.hsts}}},methods:{removeCert:function(e){let t=this;teaweb.confirm("确定删除此证书吗?证书数据仍然保留,只是当前服务不再使用此证书。",function(){t.policy.certRefs.$remove(e),t.policy.certs.$remove(e)})},selectCert:function(){let t=this,i=[];null!=this.policy&&0$1')},addCipherSuite:function(e){this.policy.cipherSuites.$contains(e)||this.policy.cipherSuites.push(e),this.allCipherSuites.$removeValue(e)},removeCipherSuite:function(e){let i=this;teaweb.confirm("确定要删除此套件吗?",function(){i.policy.cipherSuites.$removeValue(e),i.allCipherSuites=window.SSL_ALL_CIPHER_SUITES.$findAll(function(e,t){return!i.policy.cipherSuites.$contains(t)})})},clearCipherSuites:function(){let e=this;teaweb.confirm("确定要清除所有已选套件吗?",function(){e.policy.cipherSuites=[],e.allCipherSuites=window.SSL_ALL_CIPHER_SUITES.$copy()})},addBatchCipherSuites:function(e){var i=this;teaweb.confirm("确定要批量添加套件?",function(){e.$each(function(e,t){i.policy.cipherSuites.$contains(t)||i.policy.cipherSuites.push(t)})})},sortableCipherSuites:function(){var e=document.querySelector(".cipher-suites-box");Sortable.create(e,{draggable:".label",handle:".icon.handle",onStart:function(){},onUpdate:function(e){}})},showAllCipherSuites:function(){this.cipherSuitesVisible=!this.cipherSuitesVisible},showMoreHSTS:function(){this.hstsOptionsVisible=!this.hstsOptionsVisible,this.hstsOptionsVisible&&this.changeHSTSMaxAge()},changeHSTSMaxAge:function(){var e=this.hsts.maxAge;isNaN(e)?this.hsts.days="-":(this.hsts.days=parseInt(e/86400),(isNaN(this.hsts.days)||this.hsts.days<0)&&(this.hsts.days="-"))},setHSTSMaxAge:function(e){this.hsts.maxAge=e,this.changeHSTSMaxAge()},addHstsDomain:function(){this.hstsDomainAdding=!0,this.hstsDomainEditingIndex=-1;let e=this;setTimeout(function(){e.$refs.addingHstsDomain.focus()},100)},editHstsDomain:function(e){this.hstsDomainEditingIndex=e,this.addingHstsDomain=this.hsts.domains[e],this.hstsDomainAdding=!0;let t=this;setTimeout(function(){t.$refs.addingHstsDomain.focus()},100)},confirmAddHstsDomain:function(){this.addingHstsDomain=this.addingHstsDomain.trim(),0!=this.addingHstsDomain.length&&(-1

SSL/TLS相关配置

@@ -2436,41 +777,11 @@ Vue.component("ssl-config-box", {
-
` -}) - -// Action列表 -Vue.component("http-firewall-actions-view", { - props: ["v-actions"], - template: `
+
`}),Vue.component("http-firewall-actions-view",{props:["v-actions"],template:`
{{action.name}} ({{action.code.toUpperCase()}})
-
` -}) - -Vue.component("http-request-scripts-config-box", { - props: ["vRequestScriptsConfig", "v-is-location"], - data: function () { - let config = this.vRequestScriptsConfig - if (config == null) { - config = {} - } - return { - config: config - } - }, - methods: { - changeInitGroup: function (group) { - this.config.initGroup = group - this.$forceUpdate() - }, - changeRequestGroup: function (group) { - this.config.requestGroup = group - this.$forceUpdate() - } - }, - template: `
+
`}),Vue.component("http-request-scripts-config-box",{props:["vRequestScriptsConfig","v-is-location"],data:function(){let e=this.vRequestScriptsConfig;return{config:e=null==e?{}:e}},methods:{changeInitGroup:function(e){this.config.initGroup=e,this.$forceUpdate()},changeRequestGroup:function(e){this.config.requestGroup=e,this.$forceUpdate()}},template:`

请求初始化

@@ -2484,25 +795,7 @@ Vue.component("http-request-scripts-config-box", {
-
` -}) - -// 显示WAF规则的标签 -Vue.component("http-firewall-rule-label", { - props: ["v-rule"], - data: function () { - return { - rule: this.vRule - } - }, - methods: { - showErr: function (err) { - - teaweb.popupTip("规则校验错误,请修正:" + teaweb.encodeHTML(err) + "") - }, - - }, - template: `
+
`}),Vue.component("http-firewall-rule-label",{props:["v-rule"],data:function(){return{rule:this.vRule}},methods:{showErr:function(e){teaweb.popupTip('规则校验错误,请修正:'+teaweb.encodeHTML(e)+"")}},template:`
{{rule.name}}[{{rule.param}}] @@ -2527,41 +820,7 @@ Vue.component("http-firewall-rule-label", { 规则错误
-
` -}) - -// 缓存条件列表 -Vue.component("http-cache-refs-box", { - props: ["v-cache-refs"], - data: function () { - let refs = this.vCacheRefs - if (refs == null) { - refs = [] - } - return { - refs: refs - } - }, - methods: { - timeUnitName: function (unit) { - switch (unit) { - case "ms": - return "毫秒" - case "second": - return "秒" - case "minute": - return "分钟" - case "hour": - return "小时" - case "day": - return "天" - case "week": - return "周 " - } - return unit - } - }, - template: `
+
`}),Vue.component("http-cache-refs-box",{props:["v-cache-refs"],data:function(){let e=this.vCacheRefs;return{refs:e=null==e?[]:e}},methods:{timeUnitName:function(e){switch(e){case"ms":return"毫秒";case"second":return"秒";case"minute":return"分钟";case"hour":return"小时";case"day":return"天";case"week":return"周 "}return e}},template:`

暂时还没有缓存条件。

@@ -2599,97 +858,7 @@ Vue.component("http-cache-refs-box", {
-` -}) - -Vue.component("ssl-certs-box", { - props: [ - "v-certs", // 证书列表 - "v-cert", // 单个证书 - "v-protocol", // 协议:https|tls - "v-view-size", // 弹窗尺寸:normal, mini - "v-single-mode", // 单证书模式 - "v-description" // 描述文字 - ], - data: function () { - let certs = this.vCerts - if (certs == null) { - certs = [] - } - if (this.vCert != null) { - certs.push(this.vCert) - } - - let description = this.vDescription - if (description == null || typeof (description) != "string") { - description = "" - } - - return { - certs: certs, - description: description - } - }, - methods: { - certIds: function () { - return this.certs.map(function (v) { - return v.id - }) - }, - // 删除证书 - removeCert: function (index) { - let that = this - teaweb.confirm("确定删除此证书吗?证书数据仍然保留,只是当前服务不再使用此证书。", function () { - that.certs.$remove(index) - }) - }, - - // 选择证书 - selectCert: function () { - let that = this - let width = "50em" - let height = "30em" - let viewSize = this.vViewSize - if (viewSize == null) { - viewSize = "normal" - } - if (viewSize == "mini") { - width = "35em" - height = "20em" - } - teaweb.popup("/servers/certs/selectPopup?viewSize=" + viewSize, { - width: width, - height: height, - callback: function (resp) { - that.certs.push(resp.data.cert) - } - }) - }, - - // 上传证书 - uploadCert: function () { - let that = this - teaweb.popup("/servers/certs/uploadPopup", { - height: "28em", - callback: function (resp) { - teaweb.success("上传成功", function () { - that.certs.push(resp.data.cert) - }) - } - }) - }, - - // 格式化时间 - formatTime: function (timestamp) { - return new Date(timestamp * 1000).format("Y-m-d") - }, - - // 判断是否显示选择|上传按钮 - buttonsVisible: function () { - return this.vSingleMode == null || !this.vSingleMode || this.certs == null || this.certs.length == 0 - } - }, - template: `
+
`}),Vue.component("ssl-certs-box",{props:["v-certs","v-cert","v-protocol","v-view-size","v-single-mode","v-description"],data:function(){let e=this.vCerts,t=(null==e&&(e=[]),null!=this.vCert&&e.push(this.vCert),this.vDescription);return null!=t&&"string"==typeof t||(t=""),{certs:e,description:t}},methods:{certIds:function(){return this.certs.map(function(e){return e.id})},removeCert:function(e){let t=this;teaweb.confirm("确定删除此证书吗?证书数据仍然保留,只是当前服务不再使用此证书。",function(){t.certs.$remove(e)})},selectCert:function(){let t=this,e="50em",i="30em",s=this.vViewSize;"mini"==(s=null==s?"normal":s)&&(e="35em",i="20em"),teaweb.popup("/servers/certs/selectPopup?viewSize="+s,{width:e,height:i,callback:function(e){t.certs.push(e.data.cert)}})},uploadCert:function(){let t=this;teaweb.popup("/servers/certs/uploadPopup",{height:"28em",callback:function(e){teaweb.success("上传成功",function(){t.certs.push(e.data.cert)})}})},formatTime:function(e){return new Date(1e3*e).format("Y-m-d")},buttonsVisible:function(){return null==this.vSingleMode||!this.vSingleMode||null==this.certs||0==this.certs.length}},template:`
@@ -2706,98 +875,7 @@ Vue.component("ssl-certs-box", {    
-
` -}) - -Vue.component("http-host-redirect-box", { - props: ["v-redirects"], - mounted: function () { - let that = this - sortTable(function (ids) { - let newRedirects = [] - ids.forEach(function (id) { - that.redirects.forEach(function (redirect) { - if (redirect.id == id) { - newRedirects.push(redirect) - } - }) - }) - that.updateRedirects(newRedirects) - }) - }, - data: function () { - let redirects = this.vRedirects - if (redirects == null) { - redirects = [] - } - - let id = 0 - redirects.forEach(function (v) { - id++ - v.id = id - }) - - return { - redirects: redirects, - statusOptions: [ - {"code": 301, "text": "Moved Permanently"}, - {"code": 308, "text": "Permanent Redirect"}, - {"code": 302, "text": "Found"}, - {"code": 303, "text": "See Other"}, - {"code": 307, "text": "Temporary Redirect"} - ], - id: id - } - }, - methods: { - add: function () { - let that = this - window.UPDATING_REDIRECT = null - - teaweb.popup("/servers/server/settings/redirects/createPopup", { - width: "50em", - height: "30em", - callback: function (resp) { - that.id++ - resp.data.redirect.id = that.id - that.redirects.push(resp.data.redirect) - that.change() - } - }) - }, - update: function (index, redirect) { - let that = this - window.UPDATING_REDIRECT = redirect - - teaweb.popup("/servers/server/settings/redirects/createPopup", { - width: "50em", - height: "30em", - callback: function (resp) { - resp.data.redirect.id = redirect.id - Vue.set(that.redirects, index, resp.data.redirect) - that.change() - } - }) - }, - remove: function (index) { - let that = this - teaweb.confirm("确定要删除这条跳转规则吗?", function () { - that.redirects.$remove(index) - that.change() - }) - }, - change: function () { - let that = this - setTimeout(function (){ - that.$emit("change", that.redirects) - }, 100) - }, - updateRedirects: function (newRedirects) { - this.redirects = newRedirects - this.change() - } - }, - template: `
+
`}),Vue.component("http-host-redirect-box",{props:["v-redirects"],mounted:function(){let s=this;sortTable(function(e){let i=[];e.forEach(function(t){s.redirects.forEach(function(e){e.id==t&&i.push(e)})}),s.updateRedirects(i)})},data:function(){let e=this.vRedirects,t=(null==e&&(e=[]),0);return e.forEach(function(e){t++,e.id=t}),{redirects:e,statusOptions:[{code:301,text:"Moved Permanently"},{code:308,text:"Permanent Redirect"},{code:302,text:"Found"},{code:303,text:"See Other"},{code:307,text:"Temporary Redirect"}],id:t}},methods:{add:function(){let t=this;window.UPDATING_REDIRECT=null,teaweb.popup("/servers/server/settings/redirects/createPopup",{width:"50em",height:"30em",callback:function(e){t.id++,e.data.redirect.id=t.id,t.redirects.push(e.data.redirect),t.change()}})},update:function(t,i){let s=this;window.UPDATING_REDIRECT=i,teaweb.popup("/servers/server/settings/redirects/createPopup",{width:"50em",height:"30em",callback:function(e){e.data.redirect.id=i.id,Vue.set(s.redirects,t,e.data.redirect),s.change()}})},remove:function(e){let t=this;teaweb.confirm("确定要删除这条跳转规则吗?",function(){t.redirects.$remove(e),t.change()})},change:function(){let e=this;setTimeout(function(){e.$emit("change",e.redirects)},100)},updateRedirects:function(e){this.redirects=e,this.change()}},template:`
@@ -2851,104 +929,7 @@ Vue.component("http-host-redirect-box", {

所有规则匹配顺序为从上到下,可以拖动左侧的排序。

-
` -}) - -// 单个缓存条件设置 -Vue.component("http-cache-ref-box", { - props: ["v-cache-ref", "v-is-reverse"], - mounted: function () { - this.$refs.variablesDescriber.update(this.ref.key) - }, - data: function () { - let ref = this.vCacheRef - if (ref == null) { - ref = { - isOn: true, - cachePolicyId: 0, - key: "${scheme}://${host}${requestPath}${isArgs}${args}", - life: {count: 2, unit: "hour"}, - status: [200], - maxSize: {count: 32, unit: "mb"}, - minSize: {count: 0, unit: "kb"}, - skipCacheControlValues: ["private", "no-cache", "no-store"], - skipSetCookie: true, - enableRequestCachePragma: false, - conds: null, - allowChunkedEncoding: true, - allowPartialContent: false, - isReverse: this.vIsReverse, - methods: [], - expiresTime: { - isPrior: false, - isOn: false, - overwrite: true, - autoCalculate: true, - duration: {count: -1, "unit": "hour"} - } - } - } - if (ref.key == null) { - ref.key = "" - } - if (ref.methods == null) { - ref.methods = [] - } - - if (ref.life == null) { - ref.life = {count: 2, unit: "hour"} - } - if (ref.maxSize == null) { - ref.maxSize = {count: 32, unit: "mb"} - } - if (ref.minSize == null) { - ref.minSize = {count: 0, unit: "kb"} - } - return { - ref: ref, - moreOptionsVisible: false - } - }, - methods: { - changeOptionsVisible: function (v) { - this.moreOptionsVisible = v - }, - changeLife: function (v) { - this.ref.life = v - }, - changeMaxSize: function (v) { - this.ref.maxSize = v - }, - changeMinSize: function (v) { - this.ref.minSize = v - }, - changeConds: function (v) { - this.ref.conds = v - }, - changeStatusList: function (list) { - let result = [] - list.forEach(function (status) { - let statusNumber = parseInt(status) - if (isNaN(statusNumber) || statusNumber < 100 || statusNumber > 999) { - return - } - result.push(statusNumber) - }) - this.ref.status = result - }, - changeMethods: function (methods) { - this.ref.methods = methods.map(function (v) { - return v.toUpperCase() - }) - }, - changeKey: function (key) { - this.$refs.variablesDescriber.update(key) - }, - changeExpiresTime: function (expiresTime) { - this.ref.expiresTime = expiresTime - } - }, - template: ` +`}),Vue.component("http-cache-ref-box",{props:["v-cache-ref","v-is-reverse"],mounted:function(){this.$refs.variablesDescriber.update(this.ref.key)},data:function(){let e=this.vCacheRef;return null==(e=null==e?{isOn:!0,cachePolicyId:0,key:"${scheme}://${host}${requestPath}${isArgs}${args}",life:{count:2,unit:"hour"},status:[200],maxSize:{count:32,unit:"mb"},minSize:{count:0,unit:"kb"},skipCacheControlValues:["private","no-cache","no-store"],skipSetCookie:!0,enableRequestCachePragma:!1,conds:null,allowChunkedEncoding:!0,allowPartialContent:!1,isReverse:this.vIsReverse,methods:[],expiresTime:{isPrior:!1,isOn:!1,overwrite:!0,autoCalculate:!0,duration:{count:-1,unit:"hour"}}}:e).key&&(e.key=""),null==e.methods&&(e.methods=[]),null==e.life&&(e.life={count:2,unit:"hour"}),null==e.maxSize&&(e.maxSize={count:32,unit:"mb"}),null==e.minSize&&(e.minSize={count:0,unit:"kb"}),{ref:e,moreOptionsVisible:!1}},methods:{changeOptionsVisible:function(e){this.moreOptionsVisible=e},changeLife:function(e){this.ref.life=e},changeMaxSize:function(e){this.ref.maxSize=e},changeMinSize:function(e){this.ref.minSize=e},changeConds:function(e){this.ref.conds=e},changeStatusList:function(e){let t=[];e.forEach(function(e){e=parseInt(e);isNaN(e)||e<100||999 匹配条件分组 * @@ -3048,68 +1029,7 @@ Vue.component("http-cache-ref-box", {

选中后,当请求的Header中含有Pragma: no-cache或Cache-Control: no-cache时,会跳过缓存直接读取源内容。

-` -}) - -// 请求限制 -Vue.component("http-request-limit-config-box", { - props: ["v-request-limit-config", "v-is-group", "v-is-location"], - data: function () { - let config = this.vRequestLimitConfig - if (config == null) { - config = { - isPrior: false, - isOn: false, - maxConns: 0, - maxConnsPerIP: 0, - maxBodySize: { - count: -1, - unit: "kb" - }, - outBandwidthPerConn: { - count: -1, - unit: "kb" - } - } - } - return { - config: config, - maxConns: config.maxConns, - maxConnsPerIP: config.maxConnsPerIP - } - }, - watch: { - maxConns: function (v) { - let conns = parseInt(v, 10) - if (isNaN(conns)) { - this.config.maxConns = 0 - return - } - if (conns < 0) { - this.config.maxConns = 0 - } else { - this.config.maxConns = conns - } - }, - maxConnsPerIP: function (v) { - let conns = parseInt(v, 10) - if (isNaN(conns)) { - this.config.maxConnsPerIP = 0 - return - } - if (conns < 0) { - this.config.maxConnsPerIP = 0 - } else { - this.config.maxConnsPerIP = conns - } - } - }, - methods: { - isOn: function () { - return ((!this.vIsLocation && !this.vIsGroup) || this.config.isPrior) && this.config.isOn - } - }, - template: `
+`}),Vue.component("http-request-limit-config-box",{props:["v-request-limit-config","v-is-group","v-is-location"],data:function(){let e=this.vRequestLimitConfig;return{config:e=null==e?{isPrior:!1,isOn:!1,maxConns:0,maxConnsPerIP:0,maxBodySize:{count:-1,unit:"kb"},outBandwidthPerConn:{count:-1,unit:"kb"}}:e,maxConns:e.maxConns,maxConnsPerIP:e.maxConnsPerIP}},watch:{maxConns:function(e){e=parseInt(e,10);isNaN(e)?this.config.maxConns=0:this.config.maxConns=e<0?0:e},maxConnsPerIP:function(e){e=parseInt(e,10);isNaN(e)?this.config.maxConnsPerIP=0:this.config.maxConnsPerIP=e<0?0:e}},methods:{isOn:function(){return(!this.vIsLocation&&!this.vIsGroup||this.config.isPrior)&&this.config.isOn}},template:`
@@ -3153,51 +1073,7 @@ Vue.component("http-request-limit-config-box", {
-
` -}) - -Vue.component("http-header-replace-values", { - props: ["v-replace-values"], - data: function () { - let values = this.vReplaceValues - if (values == null) { - values = [] - } - return { - values: values, - isAdding: false, - addingValue: {"pattern": "", "replacement": "", "isCaseInsensitive": false, "isRegexp": false} - } - }, - methods: { - add: function () { - this.isAdding = true - let that = this - setTimeout(function () { - that.$refs.pattern.focus() - }) - }, - remove: function (index) { - this.values.$remove(index) - }, - confirm: function () { - let that = this - if (this.addingValue.pattern.length == 0) { - teaweb.warn("替换前内容不能为空", function () { - that.$refs.pattern.focus() - }) - return - } - - this.values.push(this.addingValue) - this.cancel() - }, - cancel: function () { - this.isAdding = false - this.addingValue = {"pattern": "", "replacement": "", "isCaseInsensitive": false, "isRegexp": false} - } - }, - template: `
+
`}),Vue.component("http-header-replace-values",{props:["v-replace-values"],data:function(){let e=this.vReplaceValues;return{values:e=null==e?[]:e,isAdding:!1,addingValue:{pattern:"",replacement:"",isCaseInsensitive:!1,isRegexp:!1}}},methods:{add:function(){this.isAdding=!0;let e=this;setTimeout(function(){e.$refs.pattern.focus()})},remove:function(e){this.values.$remove(e)},confirm:function(){let e=this;0==this.addingValue.pattern.length?teaweb.warn("替换前内容不能为空",function(){e.$refs.pattern.focus()}):(this.values.push(this.addingValue),this.cancel())},cancel:function(){this.isAdding=!1,this.addingValue={pattern:"",replacement:"",isCaseInsensitive:!1,isRegexp:!1}}},template:`
@@ -3231,61 +1107,7 @@ Vue.component("http-header-replace-values", {
-
` -}) - -// 浏览条件列表 -Vue.component("http-request-conds-view", { - props: ["v-conds"], - data: function () { - let conds = this.vConds - if (conds == null) { - conds = { - isOn: true, - connector: "or", - groups: [] - } - } - - let that = this - conds.groups.forEach(function (group) { - group.conds.forEach(function (cond) { - cond.typeName = that.typeName(cond) - }) - }) - - return { - initConds: conds, - version: 0 // 为了让组件能及时更新加入此变量 - } - }, - computed: { - // 之所以使用computed,是因为需要动态更新 - conds: function () { - return this.initConds - } - }, - methods: { - typeName: function (cond) { - let c = window.REQUEST_COND_COMPONENTS.$find(function (k, v) { - return v.type == cond.type - }) - if (c != null) { - return c.name; - } - return cond.param + " " + cond.operator - }, - notifyChange: function () { - this.version++ - let that = this - this.initConds.groups.forEach(function (group) { - group.conds.forEach(function (cond) { - cond.typeName = that.typeName(cond) - }) - }) - } - }, - template: `
+
`}),Vue.component("http-request-conds-view",{props:["v-conds"],data:function(){let e=this.vConds,t=(null==e&&(e={isOn:!0,connector:"or",groups:[]}),this);return e.groups.forEach(function(e){e.conds.forEach(function(e){e.typeName=t.typeName(e)})}),{initConds:e,version:0}},computed:{conds:function(){return this.initConds}},methods:{typeName:function(i){var e=window.REQUEST_COND_COMPONENTS.$find(function(e,t){return t.type==i.type});return null!=e?e.name:i.param+" "+i.operator},notifyChange:function(){this.version++;let t=this;this.initConds.groups.forEach(function(e){e.conds.forEach(function(e){e.typeName=t.typeName(e)})})}},template:`
{{version}}
@@ -3306,26 +1128,7 @@ Vue.component("http-request-conds-view", {
-
` -}) - -Vue.component("http-firewall-config-box", { - props: ["v-firewall-config", "v-is-location", "v-is-group", "v-firewall-policy"], - data: function () { - let firewall = this.vFirewallConfig - if (firewall == null) { - firewall = { - isPrior: false, - isOn: false, - firewallPolicyId: 0 - } - } - - return { - firewall: firewall - } - }, - template: `
+
`}),Vue.component("http-firewall-config-box",{props:["v-firewall-config","v-is-location","v-is-group","v-firewall-policy"],data:function(){let e=this.vFirewallConfig;return{firewall:e=null==e?{isPrior:!1,isOn:!1,firewallPolicyId:0}:e}},template:`
@@ -3352,493 +1155,18 @@ Vue.component("http-firewall-config-box", {
-
` -}) - -// 指标图表 -Vue.component("metric-chart", { - props: ["v-chart", "v-stats", "v-item"], - mounted: function () { - this.load() - }, - data: function () { - let stats = this.vStats - if (stats == null) { - stats = [] - } - if (stats.length > 0) { - let sum = stats.$sum(function (k, v) { - return v.value - }) - if (sum < stats[0].total) { - if (this.vChart.type == "pie") { - stats.push({ - keys: ["其他"], - value: stats[0].total - sum, - total: stats[0].total, - time: stats[0].time - }) - } - } - } - if (this.vChart.maxItems > 0) { - stats = stats.slice(0, this.vChart.maxItems) - } else { - stats = stats.slice(0, 10) - } - - stats.$rsort(function (v1, v2) { - return v1.value - v2.value - }) - - let widthPercent = 100 - if (this.vChart.widthDiv > 0) { - widthPercent = 100 / this.vChart.widthDiv - } - - return { - chart: this.vChart, - stats: stats, - item: this.vItem, - width: widthPercent + "%", - chartId: "metric-chart-" + this.vChart.id, - valueTypeName: (this.vItem != null && this.vItem.valueTypeName != null && this.vItem.valueTypeName.length > 0) ? this.vItem.valueTypeName : "" - } - }, - methods: { - load: function () { - var el = document.getElementById(this.chartId) - if (el == null || el.offsetWidth == 0 || el.offsetHeight == 0) { - setTimeout(this.load, 100) - } else { - this.render(el) - } - }, - render: function (el) { - let chart = echarts.init(el) - window.addEventListener("resize", function () { - chart.resize() - }) - switch (this.chart.type) { - case "pie": - this.renderPie(chart) - break - case "bar": - this.renderBar(chart) - break - case "timeBar": - this.renderTimeBar(chart) - break - case "timeLine": - this.renderTimeLine(chart) - break - case "table": - this.renderTable(chart) - break - } - }, - renderPie: function (chart) { - let values = this.stats.map(function (v) { - return { - name: v.keys[0], - value: v.value - } - }) - let that = this - chart.setOption({ - tooltip: { - show: true, - trigger: "item", - formatter: function (data) { - let stat = that.stats[data.dataIndex] - let percent = 0 - if (stat.total > 0) { - percent = Math.round((stat.value * 100 / stat.total) * 100) / 100 - } - let value = stat.value - switch (that.item.valueType) { - case "byte": - value = teaweb.formatBytes(value) - break - case "count": - value = teaweb.formatNumber(value) - break - } - return stat.keys[0] + "
" + that.valueTypeName + ": " + value + "
占比:" + percent + "%" - } - }, - series: [ - { - name: name, - type: "pie", - data: values, - areaStyle: {}, - color: ["#9DD3E8", "#B2DB9E", "#F39494", "#FBD88A", "#879BD7"] - } - ] - }) - }, - renderTimeBar: function (chart) { - this.stats.$sort(function (v1, v2) { - return (v1.time < v2.time) ? -1 : 1 - }) - let values = this.stats.map(function (v) { - return v.value - }) - - let axis = {unit: "", divider: 1} - switch (this.item.valueType) { - case "count": - axis = teaweb.countAxis(values, function (v) { - return v - }) - break - case "byte": - axis = teaweb.bytesAxis(values, function (v) { - return v - }) - break - } - - let that = this - chart.setOption({ - xAxis: { - data: this.stats.map(function (v) { - return that.formatTime(v.time) - }) - }, - yAxis: { - axisLabel: { - formatter: function (value) { - return value + axis.unit - } - } - }, - tooltip: { - show: true, - trigger: "item", - formatter: function (data) { - let stat = that.stats[data.dataIndex] - let value = stat.value - switch (that.item.valueType) { - case "byte": - value = teaweb.formatBytes(value) - break - } - return that.formatTime(stat.time) + ": " + value - } - }, - grid: { - left: 50, - top: 10, - right: 20, - bottom: 25 - }, - series: [ - { - name: name, - type: "bar", - data: values.map(function (v) { - return v / axis.divider - }), - itemStyle: { - color: "#9DD3E8" - }, - areaStyle: {}, - barWidth: "20em" - } - ] - }) - }, - renderTimeLine: function (chart) { - this.stats.$sort(function (v1, v2) { - return (v1.time < v2.time) ? -1 : 1 - }) - let values = this.stats.map(function (v) { - return v.value - }) - - let axis = {unit: "", divider: 1} - switch (this.item.valueType) { - case "count": - axis = teaweb.countAxis(values, function (v) { - return v - }) - break - case "byte": - axis = teaweb.bytesAxis(values, function (v) { - return v - }) - break - } - - let that = this - chart.setOption({ - xAxis: { - data: this.stats.map(function (v) { - return that.formatTime(v.time) - }) - }, - yAxis: { - axisLabel: { - formatter: function (value) { - return value + axis.unit - } - } - }, - tooltip: { - show: true, - trigger: "item", - formatter: function (data) { - let stat = that.stats[data.dataIndex] - let value = stat.value - switch (that.item.valueType) { - case "byte": - value = teaweb.formatBytes(value) - break - } - return that.formatTime(stat.time) + ": " + value - } - }, - grid: { - left: 50, - top: 10, - right: 20, - bottom: 25 - }, - series: [ - { - name: name, - type: "line", - data: values.map(function (v) { - return v / axis.divider - }), - itemStyle: { - color: "#9DD3E8" - }, - areaStyle: {} - } - ] - }) - }, - renderBar: function (chart) { - let values = this.stats.map(function (v) { - return v.value - }) - let axis = {unit: "", divider: 1} - switch (this.item.valueType) { - case "count": - axis = teaweb.countAxis(values, function (v) { - return v - }) - break - case "byte": - axis = teaweb.bytesAxis(values, function (v) { - return v - }) - break - } - let bottom = 24 - let rotate = 0 - let result = teaweb.xRotation(chart, this.stats.map(function (v) { - return v.keys[0] - })) - if (result != null) { - bottom = result[0] - rotate = result[1] - } - let that = this - chart.setOption({ - xAxis: { - data: this.stats.map(function (v) { - return v.keys[0] - }), - axisLabel: { - interval: 0, - rotate: rotate - } - }, - tooltip: { - show: true, - trigger: "item", - formatter: function (data) { - let stat = that.stats[data.dataIndex] - let percent = 0 - if (stat.total > 0) { - percent = Math.round((stat.value * 100 / stat.total) * 100) / 100 - } - let value = stat.value - switch (that.item.valueType) { - case "byte": - value = teaweb.formatBytes(value) - break - case "count": - value = teaweb.formatNumber(value) - break - } - return stat.keys[0] + "
" + that.valueTypeName + ":" + value + "
占比:" + percent + "%" - } - }, - yAxis: { - axisLabel: { - formatter: function (value) { - return value + axis.unit - } - } - }, - grid: { - left: 40, - top: 10, - right: 20, - bottom: bottom - }, - series: [ - { - name: name, - type: "bar", - data: values.map(function (v) { - return v / axis.divider - }), - itemStyle: { - color: "#9DD3E8" - }, - areaStyle: {}, - barWidth: "20em" - } - ] - }) - - if (this.item.keys != null) { - // IP相关操作 - if (this.item.keys.$contains("${remoteAddr}")) { - let that = this - chart.on("click", function (args) { - let index = that.item.keys.$indexesOf("${remoteAddr}")[0] - let value = that.stats[args.dataIndex].keys[index] - teaweb.popup("/servers/ipbox?ip=" + value, { - width: "50em", - height: "30em" - }) - }) - } - } - }, - renderTable: function (chart) { - let table = ` +`}),Vue.component("metric-chart",{props:["v-chart","v-stats","v-item"],mounted:function(){this.load()},data:function(){let e=this.vStats;var t;0<(e=null==e?[]:e).length&&((t=e.$sum(function(e,t){return t.value}))"+s.valueTypeName+": "+i+"
占比:"+t+"%"}},series:[{name:name,type:"pie",data:t,areaStyle:{},color:["#9DD3E8","#B2DB9E","#F39494","#FBD88A","#879BD7"]}]})},renderTimeBar:function(e){this.stats.$sort(function(e,t){return e.time"+a.valueTypeName+":"+i+"
占比:"+t+"%"}},yAxis:{axisLabel:{formatter:function(e){return e+i.unit}}},grid:{left:40,top:10,right:20,bottom:s},series:[{name:name,type:"bar",data:t.map(function(e){return e/i.divider}),itemStyle:{color:"#9DD3E8"},areaStyle:{},barWidth:"20em"}]}),null!=this.item.keys&&this.item.keys.$contains("${remoteAddr}")){let i=this;e.on("click",function(e){var t=i.item.keys.$indexesOf("${remoteAddr}")[0],e=i.stats[e.dataIndex].keys[t];teaweb.popup("/servers/ipbox?ip="+e,{width:"50em",height:"30em"})})}},renderTable:function(e){let s=`
- ` - let that = this - this.stats.forEach(function (v) { - let value = v.value - switch (that.item.valueType) { - case "byte": - value = teaweb.formatBytes(value) - break - } - table += "" - let percent = 0 - if (v.total > 0) { - percent = Math.round((v.value * 100 / v.total) * 100) / 100 - } - table += "" - table += "" - }) - - table += `
对象 数值 占比
" + v.keys[0] + "" + value + "
" + percent + "%
` - document.getElementById(this.chartId).innerHTML = table - }, - formatTime: function (time) { - if (time == null) { - return "" - } - switch (this.item.periodUnit) { - case "month": - return time.substring(0, 4) + "-" + time.substring(4, 6) - case "week": - return time.substring(0, 4) + "-" + time.substring(4, 6) - case "day": - return time.substring(0, 4) + "-" + time.substring(4, 6) + "-" + time.substring(6, 8) - case "hour": - return time.substring(0, 4) + "-" + time.substring(4, 6) + "-" + time.substring(6, 8) + " " + time.substring(8, 10) - case "minute": - return time.substring(0, 4) + "-" + time.substring(4, 6) + "-" + time.substring(6, 8) + " " + time.substring(8, 10) + ":" + time.substring(10, 12) - } - return time - } - }, - template: `
+ `,n=this;this.stats.forEach(function(e){let t=e.value,i=("byte"===n.item.valueType&&(t=teaweb.formatBytes(t)),s+=""+e.keys[0]+""+t+"",0);0
'+i)+"%"}),s+="",document.getElementById(this.chartId).innerHTML=s},formatTime:function(e){if(null==e)return"";switch(this.item.periodUnit){case"month":case"week":return e.substring(0,4)+"-"+e.substring(4,6);case"day":return e.substring(0,4)+"-"+e.substring(4,6)+"-"+e.substring(6,8);case"hour":return e.substring(0,4)+"-"+e.substring(4,6)+"-"+e.substring(6,8)+" "+e.substring(8,10);case"minute":return e.substring(0,4)+"-"+e.substring(4,6)+"-"+e.substring(6,8)+" "+e.substring(8,10)+":"+e.substring(10,12)}return e}},template:`

{{chart.name}} ({{valueTypeName}})

-
` -}) - -Vue.component("metric-board", { - template: `
` -}) - -Vue.component("http-cache-config-box", { - props: ["v-cache-config", "v-is-location", "v-is-group", "v-cache-policy", "v-web-id"], - data: function () { - let cacheConfig = this.vCacheConfig - if (cacheConfig == null) { - cacheConfig = { - isPrior: false, - isOn: false, - addStatusHeader: true, - addAgeHeader: false, - enableCacheControlMaxAge: false, - cacheRefs: [], - purgeIsOn: false, - purgeKey: "", - disablePolicyRefs: false - } - } - if (cacheConfig.cacheRefs == null) { - cacheConfig.cacheRefs = [] - } - - return { - cacheConfig: cacheConfig, - moreOptionsVisible: false, - enablePolicyRefs: !cacheConfig.disablePolicyRefs - } - }, - watch: { - enablePolicyRefs: function (v) { - this.cacheConfig.disablePolicyRefs = !v - } - }, - methods: { - isOn: function () { - return ((!this.vIsLocation && !this.vIsGroup) || this.cacheConfig.isPrior) && this.cacheConfig.isOn - }, - isPlus: function () { - return Tea.Vue.teaIsPlus - }, - generatePurgeKey: function () { - let r = Math.random().toString() + Math.random().toString() - let s = r.replace(/0\./g, "") - .replace(/\./g, "") - let result = "" - for (let i = 0; i < s.length; i++) { - result += String.fromCharCode(parseInt(s.substring(i, i + 1)) + ((Math.random() < 0.5) ? "a" : "A").charCodeAt(0)) - } - this.cacheConfig.purgeKey = result - }, - showMoreOptions: function () { - this.moreOptionsVisible = !this.moreOptionsVisible - }, - changeStale: function (stale) { - this.cacheConfig.stale = stale - } - }, - template: `
+
`}),Vue.component("metric-board",{template:"
"}),Vue.component("http-cache-config-box",{props:["v-cache-config","v-is-location","v-is-group","v-cache-policy","v-web-id"],data:function(){let e=this.vCacheConfig;return null==(e=null==e?{isPrior:!1,isOn:!1,addStatusHeader:!0,addAgeHeader:!1,enableCacheControlMaxAge:!1,cacheRefs:[],purgeIsOn:!1,purgeKey:"",disablePolicyRefs:!1}:e).cacheRefs&&(e.cacheRefs=[]),{cacheConfig:e,moreOptionsVisible:!1,enablePolicyRefs:!e.disablePolicyRefs}},watch:{enablePolicyRefs:function(e){this.cacheConfig.disablePolicyRefs=!e}},methods:{isOn:function(){return(!this.vIsLocation&&!this.vIsGroup||this.cacheConfig.isPrior)&&this.cacheConfig.isOn},isPlus:function(){return Tea.Vue.teaIsPlus},generatePurgeKey:function(){let e=Math.random().toString()+Math.random().toString(),t=e.replace(/0\./g,"").replace(/\./g,""),i="";for(let e=0;e @@ -3925,74 +1253,7 @@ Vue.component("http-cache-config-box", {
-` -}) - -// 通用Header长度 -let defaultGeneralHeaders = ["Cache-Control", "Connection", "Date", "Pragma", "Trailer", "Transfer-Encoding", "Upgrade", "Via", "Warning"] -Vue.component("http-cond-general-header-length", { - props: ["v-checkpoint"], - data: function () { - let headers = null - let length = null - - if (window.parent.UPDATING_RULE != null) { - let options = window.parent.UPDATING_RULE.checkpointOptions - if (options.headers != null && Array.$isArray(options.headers)) { - headers = options.headers - } - if (options.length != null) { - length = options.length - } - } - - - if (headers == null) { - headers = defaultGeneralHeaders - } - - if (length == null) { - length = 128 - } - - let that = this - setTimeout(function () { - that.change() - }, 100) - - return { - headers: headers, - length: length - } - }, - watch: { - length: function (v) { - let len = parseInt(v) - if (isNaN(len)) { - len = 0 - } - if (len < 0) { - len = 0 - } - this.length = len - this.change() - } - }, - methods: { - change: function () { - this.vCheckpoint.options = [ - { - code: "headers", - value: this.headers - }, - { - code: "length", - value: this.length - } - ] - } - }, - template: `
+
`});let defaultGeneralHeaders=["Cache-Control","Connection","Date","Pragma","Trailer","Transfer-Encoding","Upgrade","Via","Warning"];function sortTable(n){let e=document.createElement("script");e.setAttribute("src","/js/sortable.min.js"),e.addEventListener("load",function(){let s=document.querySelector("#sortable-table");null!=s&&Sortable.create(s,{draggable:"tbody",handle:".icon.handle",onStart:function(){},onUpdate:function(e){let t=s.querySelectorAll("tbody"),i=[];t.forEach(function(e){i.push(parseInt(e.getAttribute("v-id")))}),n(i)}})}),document.head.appendChild(e)}function sortLoad(e){let t=document.createElement("script");t.setAttribute("src","/js/sortable.min.js"),t.addEventListener("load",function(){"function"==typeof e&&e()}),document.head.appendChild(t)}function emitClick(e,arguments){let t=["click"];for(let e=0;e
@@ -4012,93 +1273,7 @@ Vue.component("http-cond-general-header-length", {
通用Header列表
-
` -}) - -// CC -Vue.component("http-firewall-checkpoint-cc", { - props: ["v-checkpoint"], - data: function () { - let keys = [] - let period = 60 - let threshold = 1000 - - let options = {} - if (window.parent.UPDATING_RULE != null) { - options = window.parent.UPDATING_RULE.checkpointOptions - } - - if (options == null) { - options = {} - } - if (options.keys != null) { - keys = options.keys - } - if (keys.length == 0) { - keys = ["${remoteAddr}", "${requestPath}"] - } - if (options.period != null) { - period = options.period - } - if (options.threshold != null) { - threshold = options.threshold - } - - let that = this - setTimeout(function () { - that.change() - }, 100) - - return { - keys: keys, - period: period, - threshold: threshold, - options: {}, - value: threshold - } - }, - watch: { - period: function () { - this.change() - }, - threshold: function () { - this.change() - } - }, - methods: { - changeKeys: function (keys) { - this.keys = keys - this.change() - }, - change: function () { - let period = parseInt(this.period.toString()) - if (isNaN(period) || period <= 0) { - period = 60 - } - - let threshold = parseInt(this.threshold.toString()) - if (isNaN(threshold) || threshold <= 0) { - threshold = 1000 - } - this.value = threshold - - this.vCheckpoint.options = [ - { - code: "keys", - value: this.keys - }, - { - code: "period", - value: period, - }, - { - code: "threshold", - value: threshold - } - ] - } - }, - template: `
+
`}),Vue.component("http-firewall-checkpoint-cc",{props:["v-checkpoint"],data:function(){let e=[],t=60,i=1e3,s={},n=(null==(s=null!=window.parent.UPDATING_RULE?window.parent.UPDATING_RULE.checkpointOptions:s)&&(s={}),0==(e=null!=s.keys?s.keys:e).length&&(e=["${remoteAddr}","${requestPath}"]),null!=s.period&&(t=s.period),null!=s.threshold&&(i=s.threshold),this);return setTimeout(function(){n.change()},100),{keys:e,period:t,threshold:i,options:{},value:i}},watch:{period:function(){this.change()},threshold:function(){this.change()}},methods:{changeKeys:function(e){this.keys=e,this.change()},change:function(){let e=parseInt(this.period.toString()),t=((isNaN(e)||e<=0)&&(e=60),parseInt(this.threshold.toString()));(isNaN(t)||t<=0)&&(t=1e3),this.value=t,this.vCheckpoint.options=[{code:"keys",value:this.keys},{code:"period",value:e},{code:"threshold",value:t}]}},template:`
@@ -4124,79 +1299,7 @@ Vue.component("http-firewall-checkpoint-cc", {
-
` -}) - -// 防盗链 -Vue.component("http-firewall-checkpoint-referer-block", { - props: ["v-checkpoint"], - data: function () { - let allowEmpty = true - let allowSameDomain = true - let allowDomains = [] - - let options = {} - if (window.parent.UPDATING_RULE != null) { - options = window.parent.UPDATING_RULE.checkpointOptions - } - - if (options == null) { - options = {} - } - if (typeof (options.allowEmpty) == "boolean") { - allowEmpty = options.allowEmpty - } - if (typeof (options.allowSameDomain) == "boolean") { - allowSameDomain = options.allowSameDomain - } - if (options.allowDomains != null && typeof (options.allowDomains) == "object") { - allowDomains = options.allowDomains - } - - let that = this - setTimeout(function () { - that.change() - }, 100) - - return { - allowEmpty: allowEmpty, - allowSameDomain: allowSameDomain, - allowDomains: allowDomains, - options: {}, - value: 0 - } - }, - watch: { - allowEmpty: function () { - this.change() - }, - allowSameDomain: function () { - this.change() - } - }, - methods: { - changeAllowDomains: function (values) { - this.allowDomains = values - this.change() - }, - change: function () { - this.vCheckpoint.options = [ - { - code: "allowEmpty", - value: this.allowEmpty - }, - { - code: "allowSameDomain", - value: this.allowSameDomain, - }, - { - code: "allowDomains", - value: this.allowDomains - } - ] - } - }, - template: `
+
`}),Vue.component("http-firewall-checkpoint-referer-block",{props:["v-checkpoint"],data:function(){let e=!0,t=!0,i=[],s={},n=("boolean"==typeof(s=null==(s=null!=window.parent.UPDATING_RULE?window.parent.UPDATING_RULE.checkpointOptions:s)?{}:s).allowEmpty&&(e=s.allowEmpty),"boolean"==typeof s.allowSameDomain&&(t=s.allowSameDomain),null!=s.allowDomains&&"object"==typeof s.allowDomains&&(i=s.allowDomains),this);return setTimeout(function(){n.change()},100),{allowEmpty:e,allowSameDomain:t,allowDomains:i,options:{},value:0}},watch:{allowEmpty:function(){this.change()},allowSameDomain:function(){this.change()}},methods:{changeAllowDomains:function(e){this.allowDomains=e,this.change()},change:function(){this.vCheckpoint.options=[{code:"allowEmpty",value:this.allowEmpty},{code:"allowSameDomain",value:this.allowSameDomain},{code:"allowDomains",value:this.allowDomains}]}},template:`
@@ -4222,175 +1325,7 @@ Vue.component("http-firewall-checkpoint-referer-block", {
-
` -}) - -Vue.component("http-cache-refs-config-box", { - props: ["v-cache-refs", "v-cache-config", "v-cache-policy-id", "v-web-id"], - mounted: function () { - let that = this - sortTable(function (ids) { - let newRefs = [] - ids.forEach(function (id) { - that.refs.forEach(function (ref) { - if (ref.id == id) { - newRefs.push(ref) - } - }) - }) - that.updateRefs(newRefs) - that.change() - }) - }, - data: function () { - let refs = this.vCacheRefs - if (refs == null) { - refs = [] - } - - let id = 0 - refs.forEach(function (ref) { - id++ - ref.id = id - }) - return { - refs: refs, - id: id // 用来对条件进行排序 - } - }, - methods: { - addRef: function (isReverse) { - window.UPDATING_CACHE_REF = null - - let width = window.innerWidth - if (width > 1024) { - width = 1024 - } - let height = window.innerHeight - if (height > 500) { - height = 500 - } - let that = this - teaweb.popup("/servers/server/settings/cache/createPopup?isReverse=" + (isReverse ? 1 : 0), { - width: width + "px", - height: height + "px", - callback: function (resp) { - let newRef = resp.data.cacheRef - if (newRef.conds == null) { - return - } - - that.id++ - newRef.id = that.id - - if (newRef.isReverse) { - let newRefs = [] - let isAdded = false - that.refs.forEach(function (v) { - if (!v.isReverse && !isAdded) { - newRefs.push(newRef) - isAdded = true - } - newRefs.push(v) - }) - if (!isAdded) { - newRefs.push(newRef) - } - - that.updateRefs(newRefs) - } else { - that.refs.push(newRef) - } - - that.change() - } - }) - }, - updateRef: function (index, cacheRef) { - window.UPDATING_CACHE_REF = cacheRef - - let width = window.innerWidth - if (width > 1024) { - width = 1024 - } - let height = window.innerHeight - if (height > 500) { - height = 500 - } - let that = this - teaweb.popup("/servers/server/settings/cache/createPopup", { - width: width + "px", - height: height + "px", - callback: function (resp) { - resp.data.cacheRef.id = that.refs[index].id - Vue.set(that.refs, index, resp.data.cacheRef) - that.change() - } - }) - }, - disableRef: function (ref) { - ref.isOn = false - this.change() - }, - enableRef: function (ref) { - ref.isOn = true - this.change() - }, - removeRef: function (index) { - let that = this - teaweb.confirm("确定要删除此缓存设置吗?", function () { - that.refs.$remove(index) - that.change() - }) - }, - updateRefs: function (newRefs) { - this.refs = newRefs - if (this.vCacheConfig != null) { - this.vCacheConfig.cacheRefs = newRefs - } - }, - timeUnitName: function (unit) { - switch (unit) { - case "ms": - return "毫秒" - case "second": - return "秒" - case "minute": - return "分钟" - case "hour": - return "小时" - case "day": - return "天" - case "week": - return "周 " - } - return unit - }, - change: function () { - // 自动保存 - if (this.vCachePolicyId != null && this.vCachePolicyId > 0) { // 缓存策略 - Tea.action("/servers/components/cache/updateRefs") - .params({ - cachePolicyId: this.vCachePolicyId, - refsJSON: JSON.stringify(this.refs) - }) - .post() - } else if (this.vWebId != null && this.vWebId > 0) { // Server Web or Group Web - Tea.action("/servers/server/settings/cache/updateRefs") - .params({ - webId: this.vWebId, - refsJSON: JSON.stringify(this.refs) - }) - .success(function (resp) { - if (resp.data.isUpdated) { - teaweb.successToast("保存成功") - } - }) - .post() - } - } - }, - template: `
+
`}),Vue.component("http-cache-refs-config-box",{props:["v-cache-refs","v-cache-config","v-cache-policy-id","v-web-id"],mounted:function(){let s=this;sortTable(function(e){let i=[];e.forEach(function(t){s.refs.forEach(function(e){e.id==t&&i.push(e)})}),s.updateRefs(i),s.change()})},data:function(){let e=this.vCacheRefs,t=(null==e&&(e=[]),0);return e.forEach(function(e){t++,e.id=t}),{refs:e,id:t}},methods:{addRef:function(e){window.UPDATING_CACHE_REF=null;let t=window.innerWidth,i=(1024
@@ -4441,62 +1376,7 @@ Vue.component("http-cache-refs-config-box", {     +添加不缓存设置
-
` -}) - -Vue.component("origin-list-box", { - props: ["v-primary-origins", "v-backup-origins", "v-server-type", "v-params"], - data: function () { - return { - primaryOrigins: this.vPrimaryOrigins, - backupOrigins: this.vBackupOrigins - } - }, - methods: { - createPrimaryOrigin: function () { - teaweb.popup("/servers/server/settings/origins/addPopup?originType=primary&" + this.vParams, { - height: "27em", - callback: function (resp) { - teaweb.success("保存成功", function () { - window.location.reload() - }) - } - }) - }, - createBackupOrigin: function () { - teaweb.popup("/servers/server/settings/origins/addPopup?originType=backup&" + this.vParams, { - height: "27em", - callback: function (resp) { - teaweb.success("保存成功", function () { - window.location.reload() - }) - } - }) - }, - updateOrigin: function (originId, originType) { - teaweb.popup("/servers/server/settings/origins/updatePopup?originType=" + originType + "&" + this.vParams + "&originId=" + originId, { - height: "27em", - callback: function (resp) { - teaweb.success("保存成功", function () { - window.location.reload() - }) - } - }) - }, - deleteOrigin: function (originId, originType) { - let that = this - teaweb.confirm("确定要删除此源站吗?", function () { - Tea.action("/servers/server/settings/origins/delete?" + that.vParams + "&originId=" + originId + "&originType=" + originType) - .post() - .success(function () { - teaweb.success("删除成功", function () { - window.location.reload() - }) - }) - }) - } - }, - template: `
+
`}),Vue.component("origin-list-box",{props:["v-primary-origins","v-backup-origins","v-server-type","v-params"],data:function(){return{primaryOrigins:this.vPrimaryOrigins,backupOrigins:this.vBackupOrigins}},methods:{createPrimaryOrigin:function(){teaweb.popup("/servers/server/settings/origins/addPopup?originType=primary&"+this.vParams,{height:"27em",callback:function(e){teaweb.success("保存成功",function(){window.location.reload()})}})},createBackupOrigin:function(){teaweb.popup("/servers/server/settings/origins/addPopup?originType=backup&"+this.vParams,{height:"27em",callback:function(e){teaweb.success("保存成功",function(){window.location.reload()})}})},updateOrigin:function(e,t){teaweb.popup("/servers/server/settings/origins/updatePopup?originType="+t+"&"+this.vParams+"&originId="+e,{height:"27em",callback:function(e){teaweb.success("保存成功",function(){window.location.reload()})}})},deleteOrigin:function(e,t){let i=this;teaweb.confirm("确定要删除此源站吗?",function(){Tea.action("/servers/server/settings/origins/delete?"+i.vParams+"&originId="+e+"&originType="+t).post().success(function(){teaweb.success("删除成功",function(){window.location.reload()})})})}},template:`

主要源站 [添加主要源站]

暂时还没有主要源站。

@@ -4504,23 +1384,7 @@ Vue.component("origin-list-box", {

备用源站 [添加备用源站]

暂时还没有备用源站。

-
` -}) - -Vue.component("origin-list-table", { - props: ["v-origins", "v-origin-type"], - data: function () { - return {} - }, - methods: { - deleteOrigin: function (originId) { - this.$emit("deleteOrigin", originId, this.vOriginType) - }, - updateOrigin: function (originId) { - this.$emit("updateOrigin", originId, this.vOriginType) - } - }, - template: ` +
`}),Vue.component("origin-list-table",{props:["v-origins","v-origin-type"],data:function(){return{}},methods:{deleteOrigin:function(e){this.$emit("deleteOrigin",e,this.vOriginType)},updateOrigin:function(e){this.$emit("updateOrigin",e,this.vOriginType)}},template:` @@ -4548,49 +1412,7 @@ Vue.component("origin-list-table", { 删除 -
` -}) - -Vue.component("http-firewall-policy-selector", { - props: ["v-http-firewall-policy"], - mounted: function () { - let that = this - Tea.action("/servers/components/waf/count") - .post() - .success(function (resp) { - that.count = resp.data.count - }) - }, - data: function () { - let firewallPolicy = this.vHttpFirewallPolicy - return { - count: 0, - firewallPolicy: firewallPolicy - } - }, - methods: { - remove: function () { - this.firewallPolicy = null - }, - select: function () { - let that = this - teaweb.popup("/servers/components/waf/selectPopup", { - callback: function (resp) { - that.firewallPolicy = resp.data.firewallPolicy - } - }) - }, - create: function () { - let that = this - teaweb.popup("/servers/components/waf/createPopup", { - height: "26em", - callback: function (resp) { - that.firewallPolicy = resp.data.firewallPolicy - } - }) - } - }, - template: `
+`}),Vue.component("http-firewall-policy-selector",{props:["v-http-firewall-policy"],mounted:function(){let t=this;Tea.action("/servers/components/waf/count").post().success(function(e){t.count=e.data.count})},data:function(){return{count:0,firewallPolicy:this.vHttpFirewallPolicy}},methods:{remove:function(){this.firewallPolicy=null},select:function(){let t=this;teaweb.popup("/servers/components/waf/selectPopup",{callback:function(e){t.firewallPolicy=e.data.firewallPolicy}})},create:function(){let t=this;teaweb.popup("/servers/components/waf/createPopup",{height:"26em",callback:function(e){t.firewallPolicy=e.data.firewallPolicy}})}},template:`
{{firewallPolicy.name}}     @@ -4598,85 +1420,7 @@ Vue.component("http-firewall-policy-selector", { -
` -}) - -Vue.component("http-websocket-box", { - props: ["v-websocket-ref", "v-websocket-config", "v-is-location", "v-is-group"], - data: function () { - let websocketRef = this.vWebsocketRef - if (websocketRef == null) { - websocketRef = { - isPrior: false, - isOn: false, - websocketId: 0 - } - } - - let websocketConfig = this.vWebsocketConfig - if (websocketConfig == null) { - websocketConfig = { - id: 0, - isOn: false, - handshakeTimeout: { - count: 30, - unit: "second" - }, - allowAllOrigins: true, - allowedOrigins: [], - requestSameOrigin: true, - requestOrigin: "" - } - } else { - if (websocketConfig.handshakeTimeout == null) { - websocketConfig.handshakeTimeout = { - count: 30, - unit: "second", - } - } - if (websocketConfig.allowedOrigins == null) { - websocketConfig.allowedOrigins = [] - } - } - - return { - websocketRef: websocketRef, - websocketConfig: websocketConfig, - handshakeTimeoutCountString: websocketConfig.handshakeTimeout.count.toString(), - advancedVisible: false - } - }, - watch: { - handshakeTimeoutCountString: function (v) { - let count = parseInt(v) - if (!isNaN(count) && count >= 0) { - this.websocketConfig.handshakeTimeout.count = count - } else { - this.websocketConfig.handshakeTimeout.count = 0 - } - } - }, - methods: { - isOn: function () { - return ((!this.vIsLocation && !this.vIsGroup) || this.websocketRef.isPrior) && this.websocketRef.isOn - }, - changeAdvancedVisible: function (v) { - this.advancedVisible = v - }, - createOrigin: function () { - let that = this - teaweb.popup("/servers/server/settings/websocket/createOrigin", { - height: "12.5em", - callback: function (resp) { - that.websocketConfig.allowedOrigins.push(resp.data.origin) - } - }) - }, - removeOrigin: function (index) { - this.websocketConfig.allowedOrigins.$remove(index) - } - }, - template: `
+
`}),Vue.component("http-websocket-box",{props:["v-websocket-ref","v-websocket-config","v-is-location","v-is-group"],data:function(){let e=this.vWebsocketRef,t=(null==e&&(e={isPrior:!1,isOn:!1,websocketId:0}),this.vWebsocketConfig);return null==t?t={id:0,isOn:!1,handshakeTimeout:{count:30,unit:"second"},allowAllOrigins:!0,allowedOrigins:[],requestSameOrigin:!0,requestOrigin:""}:(null==t.handshakeTimeout&&(t.handshakeTimeout={count:30,unit:"second"}),null==t.allowedOrigins&&(t.allowedOrigins=[])),{websocketRef:e,websocketConfig:t,handshakeTimeoutCountString:t.handshakeTimeout.count.toString(),advancedVisible:!1}},watch:{handshakeTimeoutCountString:function(e){e=parseInt(e);!isNaN(e)&&0<=e?this.websocketConfig.handshakeTimeout.count=e:this.websocketConfig.handshakeTimeout.count=0}},methods:{isOn:function(){return(!this.vIsLocation&&!this.vIsGroup||this.websocketRef.isPrior)&&this.websocketRef.isOn},changeAdvancedVisible:function(e){this.advancedVisible=e},createOrigin:function(){let t=this;teaweb.popup("/servers/server/settings/websocket/createOrigin",{height:"12.5em",callback:function(e){t.websocketConfig.allowedOrigins.push(e.data.origin)}})},removeOrigin:function(e){this.websocketConfig.allowedOrigins.$remove(e)}},template:`
@@ -4760,64 +1504,7 @@ Vue.component("http-websocket-box", {
-
` -}) - -Vue.component("http-rewrite-rule-list", { - props: ["v-web-id", "v-rewrite-rules"], - mounted: function () { - setTimeout(this.sort, 1000) - }, - data: function () { - let rewriteRules = this.vRewriteRules - if (rewriteRules == null) { - rewriteRules = [] - } - return { - rewriteRules: rewriteRules - } - }, - methods: { - updateRewriteRule: function (rewriteRuleId) { - teaweb.popup("/servers/server/settings/rewrite/updatePopup?webId=" + this.vWebId + "&rewriteRuleId=" + rewriteRuleId, { - height: "26em", - callback: function () { - window.location.reload() - } - }) - }, - deleteRewriteRule: function (rewriteRuleId) { - let that = this - teaweb.confirm("确定要删除此重写规则吗?", function () { - Tea.action("/servers/server/settings/rewrite/delete") - .params({ - webId: that.vWebId, - rewriteRuleId: rewriteRuleId - }) - .post() - .refresh() - }) - }, - // 排序 - sort: function () { - if (this.rewriteRules.length == 0) { - return - } - let that = this - sortTable(function (rowIds) { - Tea.action("/servers/server/settings/rewrite/sort") - .post() - .params({ - webId: that.vWebId, - rewriteRuleIds: rowIds - }) - .success(function () { - teaweb.success("保存成功") - }) - }) - } - }, - template: `
+
`}),Vue.component("http-rewrite-rule-list",{props:["v-web-id","v-rewrite-rules"],mounted:function(){setTimeout(this.sort,1e3)},data:function(){let e=this.vRewriteRules;return{rewriteRules:e=null==e?[]:e}},methods:{updateRewriteRule:function(e){teaweb.popup("/servers/server/settings/rewrite/updatePopup?webId="+this.vWebId+"&rewriteRuleId="+e,{height:"26em",callback:function(){window.location.reload()}})},deleteRewriteRule:function(e){let t=this;teaweb.confirm("确定要删除此重写规则吗?",function(){Tea.action("/servers/server/settings/rewrite/delete").params({webId:t.vWebId,rewriteRuleId:e}).post().refresh()})},sort:function(){if(0!=this.rewriteRules.length){let t=this;sortTable(function(e){Tea.action("/servers/server/settings/rewrite/sort").post().params({webId:t.vWebId,rewriteRuleIds:e}).success(function(){teaweb.success("保存成功")})})}}},template:`

暂时还没有重写规则。

@@ -4857,89 +1544,7 @@ Vue.component("http-rewrite-rule-list", {

拖动左侧的图标可以对重写规则进行排序。

-
` -}) - -Vue.component("http-rewrite-labels-label", { - props: ["v-class"], - template: `` -}) - -Vue.component("server-name-box", { - props: ["v-server-names"], - data: function () { - let serverNames = this.vServerNames; - if (serverNames == null) { - serverNames = [] - } - return { - serverNames: serverNames, - isSearching: false, - keyword: "" - } - }, - methods: { - addServerName: function () { - window.UPDATING_SERVER_NAME = null - let that = this - teaweb.popup("/servers/addServerNamePopup", { - callback: function (resp) { - var serverName = resp.data.serverName - that.serverNames.push(serverName) - } - }); - }, - - removeServerName: function (index) { - this.serverNames.$remove(index) - }, - - updateServerName: function (index, serverName) { - window.UPDATING_SERVER_NAME = serverName - let that = this - teaweb.popup("/servers/addServerNamePopup", { - callback: function (resp) { - var serverName = resp.data.serverName - Vue.set(that.serverNames, index, serverName) - } - }); - }, - showSearchBox: function () { - this.isSearching = !this.isSearching - if (this.isSearching) { - let that = this - setTimeout(function () { - that.$refs.keywordRef.focus() - }, 200) - } else { - this.keyword = "" - } - }, - }, - watch: { - keyword: function (v) { - this.serverNames.forEach(function (serverName) { - if (v.length == 0) { - serverName.isShowing = true - return - } - if (serverName.subNames == null || serverName.subNames.length == 0) { - if (!teaweb.match(serverName.name, v)) { - serverName.isShowing = false - } - } else { - let found = false - serverName.subNames.forEach(function (subName) { - if (teaweb.match(subName, v)) { - found = true - } - }) - serverName.isShowing = found - } - }) - } - }, - template: `
+
`}),Vue.component("http-rewrite-labels-label",{props:["v-class"],template:''}),Vue.component("server-name-box",{props:["v-server-names"],data:function(){let e=this.vServerNames;return{serverNames:e=null==e?[]:e,isSearching:!1,keyword:""}},methods:{addServerName:function(){window.UPDATING_SERVER_NAME=null;let t=this;teaweb.popup("/servers/addServerNamePopup",{callback:function(e){e=e.data.serverName;t.serverNames.push(e)}})},removeServerName:function(e){this.serverNames.$remove(e)},updateServerName:function(t,e){window.UPDATING_SERVER_NAME=e;let i=this;teaweb.popup("/servers/addServerNamePopup",{callback:function(e){e=e.data.serverName;Vue.set(i.serverNames,t,e)}})},showSearchBox:function(){if(this.isSearching=!this.isSearching,this.isSearching){let e=this;setTimeout(function(){e.$refs.keywordRef.focus()},200)}else this.keyword=""}},watch:{keyword:function(i){this.serverNames.forEach(function(e){if(0==i.length)e.isShowing=!0;else if(null==e.subNames||0==e.subNames.length)teaweb.match(e.name,i)||(e.isShowing=!1);else{let t=!1;e.subNames.forEach(function(e){teaweb.match(e,i)&&(t=!0)}),e.isShowing=t}})}},template:`
@@ -4961,39 +1566,7 @@ Vue.component("server-name-box", {
-
` -}) - -Vue.component("http-cache-stale-config", { - props: ["v-cache-stale-config"], - data: function () { - let config = this.vCacheStaleConfig - if (config == null) { - config = { - isPrior: false, - isOn: false, - status: [], - supportStaleIfErrorHeader: true, - life: { - count: 1, - unit: "day" - } - } - } - return { - config: config - } - }, - watch: { - config: { - deep: true, - handler: function () { - this.$emit("change", this.config) - } - } - }, - methods: {}, - template: ` +`}),Vue.component("http-cache-stale-config",{props:["v-cache-stale-config"],data:function(){let e=this.vCacheStaleConfig;return{config:e=null==e?{isPrior:!1,isOn:!1,status:[],supportStaleIfErrorHeader:!0,life:{count:1,unit:"day"}}:e}},watch:{config:{deep:!0,handler:function(){this.$emit("change",this.config)}}},methods:{},template:`
@@ -5023,70 +1596,7 @@ Vue.component("http-cache-stale-config", { -
启用过时缓存
` -}) - -// 域名列表 -Vue.component("domains-box", { - props: ["v-domains"], - data: function () { - let domains = this.vDomains - if (domains == null) { - domains = [] - } - return { - domains: domains, - isAdding: false, - addingDomain: "" - } - }, - methods: { - add: function () { - this.isAdding = true - let that = this - setTimeout(function () { - that.$refs.addingDomain.focus() - }, 100) - }, - confirm: function () { - let that = this - - // 删除其中的空格 - this.addingDomain = this.addingDomain.replace(/\s/g, "") - - if (this.addingDomain.length == 0) { - teaweb.warn("请输入要添加的域名", function () { - that.$refs.addingDomain.focus() - }) - return - } - - - // 基本校验 - if (this.addingDomain[0] == "~") { - let expr = this.addingDomain.substring(1) - try { - new RegExp(expr) - } catch (e) { - teaweb.warn("正则表达式错误:" + e.message, function () { - that.$refs.addingDomain.focus() - }) - return - } - } - - this.domains.push(this.addingDomain) - this.cancel() - }, - remove: function (index) { - this.domains.$remove(index) - }, - cancel: function () { - this.isAdding = false - this.addingDomain = "" - } - }, - template: `
+`}),Vue.component("domains-box",{props:["v-domains"],data:function(){let e=this.vDomains;return{domains:e=null==e?[]:e,isAdding:!1,addingDomain:""}},methods:{add:function(){this.isAdding=!0;let e=this;setTimeout(function(){e.$refs.addingDomain.focus()},100)},confirm:function(){let t=this;if(this.addingDomain=this.addingDomain.replace(/\s/g,""),0==this.addingDomain.length)teaweb.warn("请输入要添加的域名",function(){t.$refs.addingDomain.focus()});else{if("~"==this.addingDomain[0]){var e=this.addingDomain.substring(1);try{new RegExp(e)}catch(e){return void teaweb.warn("正则表达式错误:"+e.message,function(){t.$refs.addingDomain.focus()})}}this.domains.push(this.addingDomain),this.cancel()}},remove:function(e){this.domains.$remove(e)},cancel:function(){this.isAdding=!1,this.addingDomain=""}},template:`
@@ -5114,71 +1624,7 @@ Vue.component("domains-box", {
-
` -}) - -Vue.component("http-redirect-to-https-box", { - props: ["v-redirect-to-https-config", "v-is-location"], - data: function () { - let redirectToHttpsConfig = this.vRedirectToHttpsConfig - if (redirectToHttpsConfig == null) { - redirectToHttpsConfig = { - isPrior: false, - isOn: false, - host: "", - port: 0, - status: 0, - onlyDomains: [], - exceptDomains: [] - } - } else { - if (redirectToHttpsConfig.onlyDomains == null) { - redirectToHttpsConfig.onlyDomains = [] - } - if (redirectToHttpsConfig.exceptDomains == null) { - redirectToHttpsConfig.exceptDomains = [] - } - } - return { - redirectToHttpsConfig: redirectToHttpsConfig, - portString: (redirectToHttpsConfig.port > 0) ? redirectToHttpsConfig.port.toString() : "", - moreOptionsVisible: false, - statusOptions: [ - {"code": 301, "text": "Moved Permanently"}, - {"code": 308, "text": "Permanent Redirect"}, - {"code": 302, "text": "Found"}, - {"code": 303, "text": "See Other"}, - {"code": 307, "text": "Temporary Redirect"} - ] - } - }, - watch: { - "redirectToHttpsConfig.status": function () { - this.redirectToHttpsConfig.status = parseInt(this.redirectToHttpsConfig.status) - }, - portString: function (v) { - let port = parseInt(v) - if (!isNaN(port)) { - this.redirectToHttpsConfig.port = port - } else { - this.redirectToHttpsConfig.port = 0 - } - } - }, - methods: { - changeMoreOptions: function (isVisible) { - this.moreOptionsVisible = isVisible - }, - changeOnlyDomains: function (values) { - this.redirectToHttpsConfig.onlyDomains = values - this.$forceUpdate() - }, - changeExceptDomains: function (values) { - this.redirectToHttpsConfig.exceptDomains = values - this.$forceUpdate() - } - }, - template: `
+
`}),Vue.component("http-redirect-to-https-box",{props:["v-redirect-to-https-config","v-is-location"],data:function(){let e=this.vRedirectToHttpsConfig;return null==e?e={isPrior:!1,isOn:!1,host:"",port:0,status:0,onlyDomains:[],exceptDomains:[]}:(null==e.onlyDomains&&(e.onlyDomains=[]),null==e.exceptDomains&&(e.exceptDomains=[])),{redirectToHttpsConfig:e,portString:0 @@ -5275,537 +1721,14 @@ Vue.component("http-redirect-to-https-box", {
-
` -}) - -// 动作选择 -Vue.component("http-firewall-actions-box", { - props: ["v-actions", "v-firewall-policy", "v-action-configs"], - mounted: function () { - let that = this - Tea.action("/servers/iplists/levelOptions") - .success(function (resp) { - that.ipListLevels = resp.data.levels - }) - .post() - - this.loadJS(function () { - let box = document.getElementById("actions-box") - Sortable.create(box, { - draggable: ".label", - handle: ".icon.handle", - onStart: function () { - that.cancel() - }, - onUpdate: function (event) { - let labels = box.getElementsByClassName("label") - let newConfigs = [] - for (let i = 0; i < labels.length; i++) { - let index = parseInt(labels[i].getAttribute("data-index")) - newConfigs.push(that.configs[index]) - } - that.configs = newConfigs - } - }) - }) - }, - data: function () { - if (this.vFirewallPolicy.inbound == null) { - this.vFirewallPolicy.inbound = {} - } - if (this.vFirewallPolicy.inbound.groups == null) { - this.vFirewallPolicy.inbound.groups = [] - } - - let id = 0 - let configs = [] - if (this.vActionConfigs != null) { - configs = this.vActionConfigs - configs.forEach(function (v) { - v.id = (id++) - }) - } - - var defaultPageBody = ` +
`}),Vue.component("http-firewall-actions-box",{props:["v-actions","v-firewall-policy","v-action-configs"],mounted:function(){let o=this;Tea.action("/servers/iplists/levelOptions").success(function(e){o.ipListLevels=e.data.levels}).post(),this.loadJS(function(){let n=document.getElementById("actions-box");Sortable.create(n,{draggable:".label",handle:".icon.handle",onStart:function(){o.cancel()},onUpdate:function(e){let t=n.getElementsByClassName("label"),i=[];for(let e=0;e 403 Forbidden

403 Forbidden

Request ID: \${requestId}.
-` - - - return { - id: id, - - actions: this.vActions, - configs: configs, - isAdding: false, - editingIndex: -1, - - action: null, - actionCode: "", - actionOptions: {}, - - // IPList相关 - ipListLevels: [], - - // 动作参数 - blockTimeout: "", - blockScope: "global", - - captchaLife: "", - captchaMaxFails: "", - captchaFailBlockTimeout: "", - get302Life: "", - post307Life: "", - recordIPType: "black", - recordIPLevel: "critical", - recordIPTimeout: "", - recordIPListId: 0, - recordIPListName: "", - - tagTags: [], - - pageStatus: 403, - pageBody: defaultPageBody, - defaultPageBody: defaultPageBody, - - goGroupName: "", - goGroupId: 0, - goGroup: null, - - goSetId: 0, - goSetName: "" - } - }, - watch: { - actionCode: function (code) { - this.action = this.actions.$find(function (k, v) { - return v.code == code - }) - this.actionOptions = {} - }, - blockTimeout: function (v) { - v = parseInt(v) - if (isNaN(v)) { - this.actionOptions["timeout"] = 0 - } else { - this.actionOptions["timeout"] = v - } - }, - blockScope: function (v) { - this.actionOptions["scope"] = v - }, - captchaLife: function (v) { - v = parseInt(v) - if (isNaN(v)) { - this.actionOptions["life"] = 0 - } else { - this.actionOptions["life"] = v - } - }, - captchaMaxFails: function (v) { - v = parseInt(v) - if (isNaN(v)) { - this.actionOptions["maxFails"] = 0 - } else { - this.actionOptions["maxFails"] = v - } - }, - captchaFailBlockTimeout: function (v) { - v = parseInt(v) - if (isNaN(v)) { - this.actionOptions["failBlockTimeout"] = 0 - } else { - this.actionOptions["failBlockTimeout"] = v - } - }, - get302Life: function (v) { - v = parseInt(v) - if (isNaN(v)) { - this.actionOptions["life"] = 0 - } else { - this.actionOptions["life"] = v - } - }, - post307Life: function (v) { - v = parseInt(v) - if (isNaN(v)) { - this.actionOptions["life"] = 0 - } else { - this.actionOptions["life"] = v - } - }, - recordIPType: function (v) { - this.recordIPListId = 0 - }, - recordIPTimeout: function (v) { - v = parseInt(v) - if (isNaN(v)) { - this.actionOptions["timeout"] = 0 - } else { - this.actionOptions["timeout"] = v - } - }, - goGroupId: function (groupId) { - let group = this.vFirewallPolicy.inbound.groups.$find(function (k, v) { - return v.id == groupId - }) - this.goGroup = group - if (group == null) { - this.goGroupName = "" - } else { - this.goGroupName = group.name - } - this.goSetId = 0 - this.goSetName = "" - }, - goSetId: function (setId) { - if (this.goGroup == null) { - return - } - let set = this.goGroup.sets.$find(function (k, v) { - return v.id == setId - }) - if (set == null) { - this.goSetId = 0 - this.goSetName = "" - } else { - this.goSetName = set.name - } - } - }, - methods: { - add: function () { - this.action = null - this.actionCode = "block" - this.isAdding = true - this.actionOptions = {} - - // 动作参数 - this.blockTimeout = "" - this.blockScope = "global" - this.captchaLife = "" - this.captchaMaxFails = "" - this.captchaFailBlockTimeout = "" - this.get302Life = "" - this.post307Life = "" - - this.recordIPLevel = "critical" - this.recordIPType = "black" - this.recordIPTimeout = "" - this.recordIPListId = 0 - this.recordIPListName = "" - - this.tagTags = [] - - this.pageStatus = 403 - this.pageBody = this.defaultPageBody - - this.goGroupName = "" - this.goGroupId = 0 - this.goGroup = null - - this.goSetId = 0 - this.goSetName = "" - - let that = this - this.action = this.vActions.$find(function (k, v) { - return v.code == that.actionCode - }) - - // 滚到界面底部 - this.scroll() - }, - remove: function (index) { - this.isAdding = false - this.editingIndex = -1 - this.configs.$remove(index) - }, - update: function (index, config) { - if (this.isAdding && this.editingIndex == index) { - this.cancel() - return - } - - this.add() - - this.isAdding = true - this.editingIndex = index - - this.actionCode = config.code - - switch (config.code) { - case "block": - this.blockTimeout = "" - if (config.options.timeout != null || config.options.timeout > 0) { - this.blockTimeout = config.options.timeout.toString() - } - if (config.options.scope != null && config.options.scope.length > 0) { - this.blockScope = config.options.scope - } else { - this.blockScope = "global" // 兼容先前版本遗留的默认值 - } - break - case "allow": - break - case "log": - break - case "captcha": - this.captchaLife = "" - if (config.options.life != null || config.options.life > 0) { - this.captchaLife = config.options.life.toString() - } - this.captchaMaxFails = "" - if (config.options.maxFails != null || config.options.maxFails > 0) { - this.captchaMaxFails = config.options.maxFails.toString() - } - this.captchaFailBlockTimeout = "" - if (config.options.failBlockTimeout != null || config.options.failBlockTimeout > 0) { - this.captchaFailBlockTimeout = config.options.failBlockTimeout.toString() - } - break - case "notify": - break - case "get_302": - this.get302Life = "" - if (config.options.life != null || config.options.life > 0) { - this.get302Life = config.options.life.toString() - } - break - case "post_307": - this.post307Life = "" - if (config.options.life != null || config.options.life > 0) { - this.post307Life = config.options.life.toString() - } - break; - case "record_ip": - if (config.options != null) { - this.recordIPLevel = config.options.level - this.recordIPType = config.options.type - if (config.options.timeout > 0) { - this.recordIPTimeout = config.options.timeout.toString() - } - let that = this - - // VUE需要在函数执行完之后才会调用watch函数,这样会导致设置的值被覆盖,所以这里使用setTimeout - setTimeout(function () { - that.recordIPListId = config.options.ipListId - that.recordIPListName = config.options.ipListName - }) - } - break - case "tag": - this.tagTags = [] - if (config.options.tags != null) { - this.tagTags = config.options.tags - } - break - case "page": - this.pageStatus = 403 - this.pageBody = this.defaultPageBody - if (config.options.status != null) { - this.pageStatus = config.options.status - } - if (config.options.body != null) { - this.pageBody = config.options.body - } - - break - case "go_group": - if (config.options != null) { - this.goGroupName = config.options.groupName - this.goGroupId = config.options.groupId - this.goGroup = this.vFirewallPolicy.inbound.groups.$find(function (k, v) { - return v.id == config.options.groupId - }) - } - break - case "go_set": - if (config.options != null) { - this.goGroupName = config.options.groupName - this.goGroupId = config.options.groupId - this.goGroup = this.vFirewallPolicy.inbound.groups.$find(function (k, v) { - return v.id == config.options.groupId - }) - - // VUE需要在函数执行完之后才会调用watch函数,这样会导致设置的值被覆盖,所以这里使用setTimeout - let that = this - setTimeout(function () { - that.goSetId = config.options.setId - if (that.goGroup != null) { - let set = that.goGroup.sets.$find(function (k, v) { - return v.id == config.options.setId - }) - if (set != null) { - that.goSetName = set.name - } - } - }) - } - break - } - - // 滚到界面底部 - this.scroll() - }, - cancel: function () { - this.isAdding = false - this.editingIndex = -1 - }, - confirm: function () { - if (this.action == null) { - return - } - - if (this.actionOptions == null) { - this.actionOptions = {} - } - - // record_ip - if (this.actionCode == "record_ip") { - let timeout = parseInt(this.recordIPTimeout) - if (isNaN(timeout)) { - timeout = 0 - } - if (this.recordIPListId <= 0) { - return - } - this.actionOptions = { - type: this.recordIPType, - level: this.recordIPLevel, - timeout: timeout, - ipListId: this.recordIPListId, - ipListName: this.recordIPListName - } - } else if (this.actionCode == "tag") { // tag - if (this.tagTags == null || this.tagTags.length == 0) { - return - } - this.actionOptions = { - tags: this.tagTags - } - } else if (this.actionCode == "page") { - let pageStatus = this.pageStatus.toString() - if (!pageStatus.match(/^\d{3}$/)) { - pageStatus = 403 - } else { - pageStatus = parseInt(pageStatus) - } - - this.actionOptions = { - status: pageStatus, - body: this.pageBody - } - } else if (this.actionCode == "go_group") { // go_group - let groupId = this.goGroupId - if (typeof (groupId) == "string") { - groupId = parseInt(groupId) - if (isNaN(groupId)) { - groupId = 0 - } - } - if (groupId <= 0) { - return - } - this.actionOptions = { - groupId: groupId.toString(), - groupName: this.goGroupName - } - } else if (this.actionCode == "go_set") { // go_set - let groupId = this.goGroupId - if (typeof (groupId) == "string") { - groupId = parseInt(groupId) - if (isNaN(groupId)) { - groupId = 0 - } - } - - let setId = this.goSetId - if (typeof (setId) == "string") { - setId = parseInt(setId) - if (isNaN(setId)) { - setId = 0 - } - } - if (setId <= 0) { - return - } - this.actionOptions = { - groupId: groupId.toString(), - groupName: this.goGroupName, - setId: setId.toString(), - setName: this.goSetName - } - } - - let options = {} - for (let k in this.actionOptions) { - if (this.actionOptions.hasOwnProperty(k)) { - options[k] = this.actionOptions[k] - } - } - if (this.editingIndex > -1) { - this.configs[this.editingIndex] = { - id: this.configs[this.editingIndex].id, - code: this.actionCode, - name: this.action.name, - options: options - } - } else { - this.configs.push({ - id: (this.id++), - code: this.actionCode, - name: this.action.name, - options: options - }) - } - - this.cancel() - }, - removeRecordIPList: function () { - this.recordIPListId = 0 - }, - selectRecordIPList: function () { - let that = this - teaweb.popup("/servers/iplists/selectPopup?type=" + this.recordIPType, { - width: "50em", - height: "30em", - callback: function (resp) { - that.recordIPListId = resp.data.list.id - that.recordIPListName = resp.data.list.name - } - }) - }, - changeTags: function (tags) { - this.tagTags = tags - }, - loadJS: function (callback) { - if (typeof Sortable != "undefined") { - callback() - return - } - - // 引入js - let jsFile = document.createElement("script") - jsFile.setAttribute("src", "/js/sortable.min.js") - jsFile.addEventListener("load", function () { - callback() - }) - document.head.appendChild(jsFile) - }, - scroll: function () { - setTimeout(function () { - let mainDiv = document.getElementsByClassName("main") - if (mainDiv.length > 0) { - mainDiv[0].scrollTo(0, 1000) - } - }, 10) - } - }, - template: `
+`;return{id:t,actions:this.vActions,configs:e,isAdding:!1,editingIndex:-1,action:null,actionCode:"",actionOptions:{},ipListLevels:[],blockTimeout:"",blockScope:"global",captchaLife:"",captchaMaxFails:"",captchaFailBlockTimeout:"",get302Life:"",post307Life:"",recordIPType:"black",recordIPLevel:"critical",recordIPTimeout:"",recordIPListId:0,recordIPListName:"",tagTags:[],pageStatus:403,pageBody:i,defaultPageBody:i,goGroupName:"",goGroupId:0,goGroup:null,goSetId:0,goSetName:""}},watch:{actionCode:function(i){this.action=this.actions.$find(function(e,t){return t.code==i}),this.actionOptions={}},blockTimeout:function(e){e=parseInt(e),isNaN(e)?this.actionOptions.timeout=0:this.actionOptions.timeout=e},blockScope:function(e){this.actionOptions.scope=e},captchaLife:function(e){e=parseInt(e),isNaN(e)?this.actionOptions.life=0:this.actionOptions.life=e},captchaMaxFails:function(e){e=parseInt(e),isNaN(e)?this.actionOptions.maxFails=0:this.actionOptions.maxFails=e},captchaFailBlockTimeout:function(e){e=parseInt(e),isNaN(e)?this.actionOptions.failBlockTimeout=0:this.actionOptions.failBlockTimeout=e},get302Life:function(e){e=parseInt(e),isNaN(e)?this.actionOptions.life=0:this.actionOptions.life=e},post307Life:function(e){e=parseInt(e),isNaN(e)?this.actionOptions.life=0:this.actionOptions.life=e},recordIPType:function(e){this.recordIPListId=0},recordIPTimeout:function(e){e=parseInt(e),isNaN(e)?this.actionOptions.timeout=0:this.actionOptions.timeout=e},goGroupId:function(i){var e=this.vFirewallPolicy.inbound.groups.$find(function(e,t){return t.id==i});this.goGroup=e,this.goGroupName=null==e?"":e.name,this.goSetId=0,this.goSetName=""},goSetId:function(i){var e;null!=this.goGroup&&(null==(e=this.goGroup.sets.$find(function(e,t){return t.id==i}))?(this.goSetId=0,this.goSetName=""):this.goSetName=e.name)}},methods:{add:function(){this.action=null,this.actionCode="block",this.isAdding=!0,this.actionOptions={},this.blockTimeout="",this.blockScope="global",this.captchaLife="",this.captchaMaxFails="",this.captchaFailBlockTimeout="",this.get302Life="",this.post307Life="",this.recordIPLevel="critical",this.recordIPType="black",this.recordIPTimeout="",this.recordIPListId=0,this.recordIPListName="",this.tagTags=[],this.pageStatus=403,this.pageBody=this.defaultPageBody,this.goGroupName="",this.goGroupId=0,this.goGroup=null,this.goSetId=0,this.goSetName="";let i=this;this.action=this.vActions.$find(function(e,t){return t.code==i.actionCode}),this.scroll()},remove:function(e){this.isAdding=!1,this.editingIndex=-1,this.configs.$remove(e)},update:function(e,i){if(this.isAdding&&this.editingIndex==e)this.cancel();else{switch(this.add(),this.isAdding=!0,this.editingIndex=e,this.actionCode=i.code,i.code){case"block":this.blockTimeout="",(null!=i.options.timeout||0
@@ -6035,65 +1958,7 @@ Vue.component("http-firewall-actions-box", {

系统总是会先执行记录日志、标签等不会修改请求的动作,再执行阻止、验证码等可能改变请求的动作。

-
` -}) - -// 认证设置 -Vue.component("http-auth-config-box", { - props: ["v-auth-config", "v-is-location"], - data: function () { - let authConfig = this.vAuthConfig - if (authConfig == null) { - authConfig = { - isPrior: false, - isOn: false - } - } - if (authConfig.policyRefs == null) { - authConfig.policyRefs = [] - } - return { - authConfig: authConfig - } - }, - methods: { - isOn: function () { - return (!this.vIsLocation || this.authConfig.isPrior) && this.authConfig.isOn - }, - add: function () { - let that = this - teaweb.popup("/servers/server/settings/access/createPopup", { - callback: function (resp) { - that.authConfig.policyRefs.push(resp.data.policyRef) - }, - height: "28em" - }) - }, - update: function (index, policyId) { - let that = this - teaweb.popup("/servers/server/settings/access/updatePopup?policyId=" + policyId, { - callback: function (resp) { - teaweb.success("保存成功", function () { - teaweb.reload() - }) - }, - height: "28em" - }) - }, - remove: function (index) { - this.authConfig.policyRefs.$remove(index) - }, - methodName: function (methodType) { - switch (methodType) { - case "basicAuth": - return "BasicAuth" - case "subRequest": - return "子请求" - } - return "" - } - }, - template: `
+
`}),Vue.component("http-auth-config-box",{props:["v-auth-config","v-is-location"],data:function(){let e=this.vAuthConfig;return null==(e=null==e?{isPrior:!1,isOn:!1}:e).policyRefs&&(e.policyRefs=[]),{authConfig:e}},methods:{isOn:function(){return(!this.vIsLocation||this.authConfig.isPrior)&&this.authConfig.isOn},add:function(){let t=this;teaweb.popup("/servers/server/settings/access/createPopup",{callback:function(e){t.authConfig.policyRefs.push(e.data.policyRef)},height:"28em"})},update:function(e,t){teaweb.popup("/servers/server/settings/access/updatePopup?policyId="+t,{callback:function(e){teaweb.success("保存成功",function(){teaweb.reload()})},height:"28em"})},remove:function(e){this.authConfig.policyRefs.$remove(e)},methodName:function(e){switch(e){case"basicAuth":return"BasicAuth";case"subRequest":return"子请求"}return""}},template:`
@@ -6149,58 +2014,12 @@ Vue.component("http-auth-config-box", {
-` -}) - -Vue.component("user-selector", { - mounted: function () { - let that = this - - Tea.action("/servers/users/options") - .post() - .success(function (resp) { - that.users = resp.data.users - }) - }, - props: ["v-user-id"], - data: function () { - let userId = this.vUserId - if (userId == null) { - userId = 0 - } - return { - users: [], - userId: userId - } - }, - watch: { - userId: function (v) { - this.$emit("change", v) - } - }, - template: `
+
`}),Vue.component("user-selector",{mounted:function(){let t=this;Tea.action("/servers/users/options").post().success(function(e){t.users=e.data.users})},props:["v-user-id"],data:function(){let e=this.vUserId;return{users:[],userId:e=null==e?0:e}},watch:{userId:function(e){this.$emit("change",e)}},template:`
-
` -}) - -// UAM模式配置 -Vue.component("uam-config-box", { - props: ["v-uam-config"], - data: function () { - let config = this.vUamConfig - if (config == null) { - config = { - isOn: false - } - } - return { - config: config - } - }, - template: `
+
`}),Vue.component("uam-config-box",{props:["v-uam-config"],data:function(){let e=this.vUamConfig;return{config:e=null==e?{isOn:!1}:e}},template:`
@@ -6212,128 +2031,7 @@ Vue.component("uam-config-box", {
-
` -}) - -Vue.component("http-header-policy-box", { - props: ["v-request-header-policy", "v-request-header-ref", "v-response-header-policy", "v-response-header-ref", "v-params", "v-is-location", "v-is-group", "v-has-group-request-config", "v-has-group-response-config", "v-group-setting-url"], - data: function () { - let type = "response" - let hash = window.location.hash - if (hash == "#request") { - type = "request" - } - - // ref - let requestHeaderRef = this.vRequestHeaderRef - if (requestHeaderRef == null) { - requestHeaderRef = { - isPrior: false, - isOn: true, - headerPolicyId: 0 - } - } - - let responseHeaderRef = this.vResponseHeaderRef - if (responseHeaderRef == null) { - responseHeaderRef = { - isPrior: false, - isOn: true, - headerPolicyId: 0 - } - } - - // 请求相关 - let requestSettingHeaders = [] - let requestDeletingHeaders = [] - - let requestPolicy = this.vRequestHeaderPolicy - if (requestPolicy != null) { - if (requestPolicy.setHeaders != null) { - requestSettingHeaders = requestPolicy.setHeaders - } - if (requestPolicy.deleteHeaders != null) { - requestDeletingHeaders = requestPolicy.deleteHeaders - } - } - - // 响应相关 - let responseSettingHeaders = [] - let responseDeletingHeaders = [] - - let responsePolicy = this.vResponseHeaderPolicy - if (responsePolicy != null) { - if (responsePolicy.setHeaders != null) { - responseSettingHeaders = responsePolicy.setHeaders - } - if (responsePolicy.deleteHeaders != null) { - responseDeletingHeaders = responsePolicy.deleteHeaders - } - } - - return { - type: type, - typeName: (type == "request") ? "请求" : "响应", - requestHeaderRef: requestHeaderRef, - responseHeaderRef: responseHeaderRef, - requestSettingHeaders: requestSettingHeaders, - requestDeletingHeaders: requestDeletingHeaders, - responseSettingHeaders: responseSettingHeaders, - responseDeletingHeaders: responseDeletingHeaders - } - }, - methods: { - selectType: function (type) { - this.type = type - window.location.hash = "#" + type - window.location.reload() - }, - addSettingHeader: function (policyId) { - teaweb.popup("/servers/server/settings/headers/createSetPopup?" + this.vParams + "&headerPolicyId=" + policyId + "&type=" + this.type, { - callback: function () { - teaweb.successRefresh("保存成功") - } - }) - }, - addDeletingHeader: function (policyId, type) { - teaweb.popup("/servers/server/settings/headers/createDeletePopup?" + this.vParams + "&headerPolicyId=" + policyId + "&type=" + type, { - callback: function () { - teaweb.successRefresh("保存成功") - } - }) - }, - updateSettingPopup: function (policyId, headerId) { - teaweb.popup("/servers/server/settings/headers/updateSetPopup?" + this.vParams + "&headerPolicyId=" + policyId + "&headerId=" + headerId+ "&type=" + this.type, { - callback: function () { - teaweb.successRefresh("保存成功") - } - }) - }, - deleteDeletingHeader: function (policyId, headerName) { - teaweb.confirm("确定要删除'" + headerName + "'吗?", function () { - Tea.action("/servers/server/settings/headers/deleteDeletingHeader") - .params({ - headerPolicyId: policyId, - headerName: headerName - }) - .post() - .refresh() - }) - }, - deleteHeader: function (policyId, type, headerId) { - teaweb.confirm("确定要删除此Header吗?", function () { - this.$post("/servers/server/settings/headers/delete") - .params({ - headerPolicyId: policyId, - type: type, - headerId: headerId - }) - .refresh() - } - ) - } - }, - template: `
+
`}),Vue.component("http-header-policy-box",{props:["v-request-header-policy","v-request-header-ref","v-response-header-policy","v-response-header-ref","v-params","v-is-location","v-is-group","v-has-group-request-config","v-has-group-response-config","v-group-setting-url"],data:function(){let e="response";"#request"==window.location.hash&&(e="request");let t=this.vRequestHeaderRef,i=(null==t&&(t={isPrior:!1,isOn:!0,headerPolicyId:0}),this.vResponseHeaderRef),s=(null==i&&(i={isPrior:!1,isOn:!0,headerPolicyId:0}),[]),n=[];var o=this.vRequestHeaderPolicy;null!=o&&(null!=o.setHeaders&&(s=o.setHeaders),null!=o.deleteHeaders&&(n=o.deleteHeaders));let a=[],l=[];o=this.vResponseHeaderPolicy;return null!=o&&(null!=o.setHeaders&&(a=o.setHeaders),null!=o.deleteHeaders&&(l=o.deleteHeaders)),{type:e,typeName:"request"==e?"请求":"响应",requestHeaderRef:t,responseHeaderRef:i,requestSettingHeaders:s,requestDeletingHeaders:n,responseSettingHeaders:a,responseDeletingHeaders:l}},methods:{selectType:function(e){this.type=e,window.location.hash="#"+e,window.location.reload()},addSettingHeader:function(e){teaweb.popup("/servers/server/settings/headers/createSetPopup?"+this.vParams+"&headerPolicyId="+e+"&type="+this.type,{callback:function(){teaweb.successRefresh("保存成功")}})},addDeletingHeader:function(e,t){teaweb.popup("/servers/server/settings/headers/createDeletePopup?"+this.vParams+"&headerPolicyId="+e+"&type="+t,{callback:function(){teaweb.successRefresh("保存成功")}})},updateSettingPopup:function(e,t){teaweb.popup("/servers/server/settings/headers/updateSetPopup?"+this.vParams+"&headerPolicyId="+e+"&headerId="+t+"&type="+this.type,{callback:function(){teaweb.successRefresh("保存成功")}})},deleteDeletingHeader:function(e,t){teaweb.confirm("确定要删除'"+t+"'吗?",function(){Tea.action("/servers/server/settings/headers/deleteDeletingHeader").params({headerPolicyId:e,headerName:t}).post().refresh()})},deleteHeader:function(e,t,i){teaweb.confirm("确定要删除此Header吗?",function(){this.$post("/servers/server/settings/headers/delete").params({headerPolicyId:e,type:t,headerId:i}).refresh()})}},template:`
-
` -}) - -// 通用设置 -Vue.component("http-common-config-box", { - props: ["v-common-config"], - data: function () { - let config = this.vCommonConfig - if (config == null) { - config = { - mergeSlashes: false - } - } - return { - config: config - } - }, - template: `
+
`}),Vue.component("http-common-config-box",{props:["v-common-config"],data:function(){let e=this.vCommonConfig;return{config:e=null==e?{mergeSlashes:!1}:e}},template:`
@@ -6491,49 +2172,7 @@ Vue.component("http-common-config-box", {
合并重复的路径分隔符
-
` -}) - -Vue.component("http-cache-policy-selector", { - props: ["v-cache-policy"], - mounted: function () { - let that = this - Tea.action("/servers/components/cache/count") - .post() - .success(function (resp) { - that.count = resp.data.count - }) - }, - data: function () { - let cachePolicy = this.vCachePolicy - return { - count: 0, - cachePolicy: cachePolicy - } - }, - methods: { - remove: function () { - this.cachePolicy = null - }, - select: function () { - let that = this - teaweb.popup("/servers/components/cache/selectPopup", { - callback: function (resp) { - that.cachePolicy = resp.data.cachePolicy - } - }) - }, - create: function () { - let that = this - teaweb.popup("/servers/components/cache/createPopup", { - height: "26em", - callback: function (resp) { - that.cachePolicy = resp.data.cachePolicy - } - }) - } - }, - template: `
+
`}),Vue.component("http-cache-policy-selector",{props:["v-cache-policy"],mounted:function(){let t=this;Tea.action("/servers/components/cache/count").post().success(function(e){t.count=e.data.count})},data:function(){return{count:0,cachePolicy:this.vCachePolicy}},methods:{remove:function(){this.cachePolicy=null},select:function(){let t=this;teaweb.popup("/servers/components/cache/selectPopup",{callback:function(e){t.cachePolicy=e.data.cachePolicy}})},create:function(){let t=this;teaweb.popup("/servers/components/cache/createPopup",{height:"26em",callback:function(e){t.cachePolicy=e.data.cachePolicy}})}},template:`
{{cachePolicy.name}}     @@ -6541,86 +2180,11 @@ Vue.component("http-cache-policy-selector", { -
` -}) - -Vue.component("http-pages-and-shutdown-box", { - props: ["v-pages", "v-shutdown-config", "v-is-location"], - data: function () { - let pages = [] - if (this.vPages != null) { - pages = this.vPages - } - let shutdownConfig = { - isPrior: false, - isOn: false, - bodyType: "url", - url: "", - body: "", - status: 0 - } - if (this.vShutdownConfig != null) { - if (this.vShutdownConfig.body == null) { - this.vShutdownConfig.body = "" - } - if (this.vShutdownConfig.bodyType == null) { - this.vShutdownConfig.bodyType = "url" - } - shutdownConfig = this.vShutdownConfig - } - - let shutdownStatus = "" - if (shutdownConfig.status > 0) { - shutdownStatus = shutdownConfig.status.toString() - } - - return { - pages: pages, - shutdownConfig: shutdownConfig, - shutdownStatus: shutdownStatus - } - }, - watch: { - shutdownStatus: function (status) { - let statusInt = parseInt(status) - if (!isNaN(statusInt) && statusInt > 0 && statusInt < 1000) { - this.shutdownConfig.status = statusInt - } else { - this.shutdownConfig.status = 0 - } - } - }, - methods: { - addPage: function () { - let that = this - teaweb.popup("/servers/server/settings/pages/createPopup", { - height: "26em", - callback: function (resp) { - that.pages.push(resp.data.page) - } - }) - }, - updatePage: function (pageIndex, pageId) { - let that = this - teaweb.popup("/servers/server/settings/pages/updatePopup?pageId=" + pageId, { - height: "26em", - callback: function (resp) { - Vue.set(that.pages, pageIndex, resp.data.page) - } - }) - }, - removePage: function (pageIndex) { - let that = this - teaweb.confirm("确定要移除此页面吗?", function () { - that.pages.$remove(pageIndex) - }) - }, - addShutdownHTMLTemplate: function () { - this.shutdownConfig.body = ` +
`}),Vue.component("http-pages-and-shutdown-box",{props:["v-pages","v-shutdown-config","v-is-location"],data:function(){let e=[],t=(null!=this.vPages&&(e=this.vPages),{isPrior:!1,isOn:!1,bodyType:"url",url:"",body:"",status:0}),i=(null!=this.vShutdownConfig&&(null==this.vShutdownConfig.body&&(this.vShutdownConfig.body=""),null==this.vShutdownConfig.bodyType&&(this.vShutdownConfig.bodyType="url"),t=this.vShutdownConfig),"");return 0 -\t升级中 -\t + 升级中 + @@ -6630,10 +2194,7 @@ Vue.component("http-pages-and-shutdown-box", {
Request ID: \${requestId}.
-` - } - }, - template: `
+`}},template:`
@@ -6705,154 +2266,7 @@ Vue.component("http-pages-and-shutdown-box", {
-
` -}) - -// 压缩配置 -Vue.component("http-compression-config-box", { - props: ["v-compression-config", "v-is-location", "v-is-group"], - mounted: function () { - let that = this - sortLoad(function () { - that.initSortableTypes() - }) - }, - data: function () { - let config = this.vCompressionConfig - if (config == null) { - config = { - isPrior: false, - isOn: false, - useDefaultTypes: true, - types: ["brotli", "gzip", "deflate"], - level: 5, - decompressData: false, - gzipRef: null, - deflateRef: null, - brotliRef: null, - minLength: {count: 0, "unit": "kb"}, - maxLength: {count: 0, "unit": "kb"}, - mimeTypes: ["text/*", "application/*", "font/*"], - extensions: [".js", ".json", ".html", ".htm", ".xml", ".css", ".woff2", ".txt"], - conds: null - } - } - - if (config.types == null) { - config.types = [] - } - if (config.mimeTypes == null) { - config.mimeTypes = [] - } - if (config.extensions == null) { - config.extensions = [] - } - - let allTypes = [ - { - name: "Gzip", - code: "gzip", - isOn: true - }, - { - name: "Deflate", - code: "deflate", - isOn: true - }, - { - name: "Brotli", - code: "brotli", - isOn: true - } - ] - - let configTypes = [] - config.types.forEach(function (typeCode) { - allTypes.forEach(function (t) { - if (typeCode == t.code) { - t.isOn = true - configTypes.push(t) - } - }) - }) - allTypes.forEach(function (t) { - if (!config.types.$contains(t.code)) { - t.isOn = false - configTypes.push(t) - } - }) - - return { - config: config, - moreOptionsVisible: false, - allTypes: configTypes - } - }, - watch: { - "config.level": function (v) { - let level = parseInt(v) - if (isNaN(level)) { - level = 1 - } else if (level < 1) { - level = 1 - } else if (level > 10) { - level = 10 - } - this.config.level = level - } - }, - methods: { - isOn: function () { - return ((!this.vIsLocation && !this.vIsGroup) || this.config.isPrior) && this.config.isOn - }, - changeExtensions: function (values) { - values.forEach(function (v, k) { - if (v.length > 0 && v[0] != ".") { - values[k] = "." + v - } - }) - this.config.extensions = values - }, - changeMimeTypes: function (values) { - this.config.mimeTypes = values - }, - changeAdvancedVisible: function () { - this.moreOptionsVisible = !this.moreOptionsVisible - }, - changeConds: function (conds) { - this.config.conds = conds - }, - changeType: function () { - this.config.types = [] - let that = this - this.allTypes.forEach(function (v) { - if (v.isOn) { - that.config.types.push(v.code) - } - }) - }, - initSortableTypes: function () { - let box = document.querySelector("#compression-types-box") - let that = this - Sortable.create(box, { - draggable: ".checkbox", - handle: ".icon.handle", - onStart: function () { - - }, - onUpdate: function (event) { - let checkboxes = box.querySelectorAll(".checkbox") - let codes = [] - checkboxes.forEach(function (checkbox) { - let code = checkbox.getAttribute("data-code") - codes.push(code) - }) - that.config.types = codes - } - }) - } - }, - template: `
+
`}),Vue.component("http-compression-config-box",{props:["v-compression-config","v-is-location","v-is-group"],mounted:function(){let e=this;sortLoad(function(){e.initSortableTypes()})},data:function(){let t=this.vCompressionConfig,e=(null==(t=null==t?{isPrior:!1,isOn:!1,useDefaultTypes:!0,types:["brotli","gzip","deflate"],level:5,decompressData:!1,gzipRef:null,deflateRef:null,brotliRef:null,minLength:{count:0,unit:"kb"},maxLength:{count:0,unit:"kb"},mimeTypes:["text/*","application/*","font/*"],extensions:[".js",".json",".html",".htm",".xml",".css",".woff2",".txt"],conds:null}:t).types&&(t.types=[]),null==t.mimeTypes&&(t.mimeTypes=[]),null==t.extensions&&(t.extensions=[]),[{name:"Gzip",code:"gzip",isOn:!0},{name:"Deflate",code:"deflate",isOn:!0},{name:"Brotli",code:"brotli",isOn:!0}]),i=[];return t.types.forEach(function(t){e.forEach(function(e){t==e.code&&(e.isOn=!0,i.push(e))})}),e.forEach(function(e){t.types.$contains(e.code)||(e.isOn=!1,i.push(e))}),{config:t,moreOptionsVisible:!1,allTypes:i}},watch:{"config.level":function(e){let t=parseInt(e);isNaN(t)||t<1?t=1:10 @@ -6945,68 +2359,12 @@ Vue.component("http-compression-config-box", {
-
` -}) - -Vue.component("firewall-event-level-options", { - props: ["v-value"], - mounted: function () { - let that = this - Tea.action("/ui/eventLevelOptions") - .post() - .success(function (resp) { - that.levels = resp.data.eventLevels - that.change() - }) - }, - data: function () { - let value = this.vValue - if (value == null || value.length == 0) { - value = "" // 不要给默认值,因为黑白名单等默认值均有不同 - } - - return { - levels: [], - description: "", - level: value - } - }, - methods: { - change: function () { - this.$emit("change") - - let that = this - let l = this.levels.$find(function (k, v) { - return v.code == that.level - }) - if (l != null) { - this.description = l.description - } else { - this.description = "" - } - } - }, - template: `
+
`}),Vue.component("firewall-event-level-options",{props:["v-value"],mounted:function(){let t=this;Tea.action("/ui/eventLevelOptions").post().success(function(e){t.levels=e.data.eventLevels,t.change()})},data:function(){let e=this.vValue;return{levels:[],description:"",level:e=null!=e&&0!=e.length?e:""}},methods:{change:function(){this.$emit("change");let i=this;var e=this.levels.$find(function(e,t){return t.code==i.level});this.description=null!=e?e.description:""}},template:`

{{description}}

-
` -}) - -Vue.component("prior-checkbox", { - props: ["v-config"], - data: function () { - return { - isPrior: this.vConfig.isPrior - } - }, - watch: { - isPrior: function (v) { - this.vConfig.isPrior = v - } - }, - template: ` +
`}),Vue.component("prior-checkbox",{props:["v-config"],data:function(){return{isPrior:this.vConfig.isPrior}},watch:{isPrior:function(e){this.vConfig.isPrior=e}},template:` 打开独立配置 @@ -7017,32 +2375,7 @@ Vue.component("prior-checkbox", {

[已打开] 打开后可以覆盖父级或子级配置。

-` -}) - -Vue.component("http-charsets-box", { - props: ["v-usual-charsets", "v-all-charsets", "v-charset-config", "v-is-location", "v-is-group"], - data: function () { - let charsetConfig = this.vCharsetConfig - if (charsetConfig == null) { - charsetConfig = { - isPrior: false, - isOn: false, - charset: "", - isUpper: false - } - } - return { - charsetConfig: charsetConfig, - advancedVisible: false - } - }, - methods: { - changeAdvancedVisible: function (v) { - this.advancedVisible = v - } - }, - template: `
+`}),Vue.component("http-charsets-box",{props:["v-usual-charsets","v-all-charsets","v-charset-config","v-is-location","v-is-group"],data:function(){let e=this.vCharsetConfig;return{charsetConfig:e=null==e?{isPrior:!1,isOn:!1,charset:"",isUpper:!1}:e,advancedVisible:!1}},methods:{changeAdvancedVisible:function(e){this.advancedVisible=e}},template:`
@@ -7085,46 +2418,7 @@ Vue.component("http-charsets-box", {
-
` -}) - -Vue.component("http-expires-time-config-box", { - props: ["v-expires-time"], - data: function () { - let expiresTime = this.vExpiresTime - if (expiresTime == null) { - expiresTime = { - isPrior: false, - isOn: false, - overwrite: true, - autoCalculate: true, - duration: {count: -1, "unit": "hour"} - } - } - return { - expiresTime: expiresTime - } - }, - watch: { - "expiresTime.isPrior": function () { - this.notifyChange() - }, - "expiresTime.isOn": function () { - this.notifyChange() - }, - "expiresTime.overwrite": function () { - this.notifyChange() - }, - "expiresTime.autoCalculate": function () { - this.notifyChange() - } - }, - methods: { - notifyChange: function () { - this.$emit("change", this.expiresTime) - } - }, - template: `
+
`}),Vue.component("http-expires-time-config-box",{props:["v-expires-time"],data:function(){let e=this.vExpiresTime;return{expiresTime:e=null==e?{isPrior:!1,isOn:!1,overwrite:!0,autoCalculate:!0,duration:{count:-1,unit:"hour"}}:e}},watch:{"expiresTime.isPrior":function(){this.notifyChange()},"expiresTime.isOn":function(){this.notifyChange()},"expiresTime.overwrite":function(){this.notifyChange()},"expiresTime.autoCalculate":function(){this.notifyChange()}},methods:{notifyChange:function(){this.$emit("change",this.expiresTime)}},template:`
@@ -7156,118 +2450,12 @@ Vue.component("http-expires-time-config-box", {
-
` -}) - -Vue.component("http-access-log-box", { - props: ["v-access-log", "v-keyword", "v-show-server-link"], - data: function () { - let accessLog = this.vAccessLog - if (accessLog.header != null && accessLog.header.Upgrade != null && accessLog.header.Upgrade.values != null && accessLog.header.Upgrade.values.$contains("websocket")) { - if (accessLog.scheme == "http") { - accessLog.scheme = "ws" - } else if (accessLog.scheme == "https") { - accessLog.scheme = "wss" - } - } - - return { - accessLog: accessLog - } - }, - methods: { - formatCost: function (seconds) { - var s = (seconds * 1000).toString(); - var pieces = s.split("."); - if (pieces.length < 2) { - return s; - } - - return pieces[0] + "." + pieces[1].substr(0, 3); - }, - showLog: function () { - let that = this - let requestId = this.accessLog.requestId - this.$parent.$children.forEach(function (v) { - if (v.deselect != null) { - v.deselect() - } - }) - this.select() - teaweb.popup("/servers/server/log/viewPopup?requestId=" + requestId, { - width: "50em", - height: "28em", - onClose: function () { - that.deselect() - } - }) - }, - select: function () { - this.$refs.box.parentNode.style.cssText = "background: rgba(0, 0, 0, 0.1)" - }, - deselect: function () { - this.$refs.box.parentNode.style.cssText = "" - } - }, - template: `
+
`}),Vue.component("http-access-log-box",{props:["v-access-log","v-keyword","v-show-server-link"],data:function(){let e=this.vAccessLog;return null!=e.header&&null!=e.header.Upgrade&&null!=e.header.Upgrade.values&&e.header.Upgrade.values.$contains("websocket")&&("http"==e.scheme?e.scheme="ws":"https"==e.scheme&&(e.scheme="wss")),{accessLog:e}},methods:{formatCost:function(e){var e=(1e3*e).toString(),t=e.split(".");return t.length<2?e:t[0]+"."+t[1].substr(0,3)},showLog:function(){let e=this;var t=this.accessLog.requestId;this.$parent.$children.forEach(function(e){null!=e.deselect&&e.deselect()}),this.select(),teaweb.popup("/servers/server/log/viewPopup?requestId="+t,{width:"50em",height:"28em",onClose:function(){e.deselect()}})},select:function(){this.$refs.box.parentNode.style.cssText="background: rgba(0, 0, 0, 0.1)"},deselect:function(){this.$refs.box.parentNode.style.cssText=""}},template:`
[{{accessLog.node.name}}节点] [服务] [{{accessLog.region}}] {{accessLog.remoteAddr}} [{{accessLog.timeLocal}}] "{{accessLog.requestMethod}} {{accessLog.scheme}}://{{accessLog.host}}{{accessLog.requestURI}} {{accessLog.proto}}" {{accessLog.status}} cache {{accessLog.attrs['cache.status'].toLowerCase()}} waf {{accessLog.firewallActions}} - {{tag}} - 耗时:{{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 that = this - - // 初始化 - setTimeout(function () { - that.changeFields() - }, 100) - - let accessLog = { - isPrior: false, - isOn: false, - fields: [1, 2, 6, 7], - status1: true, - status2: true, - status3: true, - status4: true, - status5: true, - - firewallOnly: false, - enableClientClosed: false - } - if (this.vAccessLogConfig != null) { - accessLog = this.vAccessLogConfig - } - - this.vFields.forEach(function (v) { - if (that.vAccessLogConfig == null) { // 初始化默认值 - v.isChecked = that.vDefaultFieldCodes.$contains(v.code) - } else { - v.isChecked = accessLog.fields.$contains(v.code) - } - }) - - return { - accessLog: accessLog, - hasRequestBodyField: this.vFields.$contains(8) - } - }, - methods: { - changeFields: function () { - this.accessLog.fields = this.vFields.filter(function (v) { - return v.isChecked - }).map(function (v) { - return v.code - }) - this.hasRequestBodyField = this.accessLog.fields.$contains(8) - } - }, - template: `
+
`}),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:`
@@ -7351,100 +2539,13 @@ Vue.component("http-access-log-config-box", {
-
` -}) - -// 显示流量限制说明 -Vue.component("traffic-limit-view", { - props: ["v-traffic-limit"], - data: function () { - return { - config: this.vTrafficLimit - } - }, - template: `
+
`}),Vue.component("traffic-limit-view",{props:["v-traffic-limit"],data:function(){return{config:this.vTrafficLimit}},template:`
日流量限制:{{config.dailySize.count}}{{config.dailySize.unit.toUpperCase()}}
月流量限制:{{config.monthlySize.count}}{{config.monthlySize.unit.toUpperCase()}}
没有限制。 -
` -}) - -// 基本认证用户配置 -Vue.component("http-auth-basic-auth-user-box", { - props: ["v-users"], - data: function () { - let users = this.vUsers - if (users == null) { - users = [] - } - return { - users: users, - isAdding: false, - updatingIndex: -1, - - username: "", - password: "" - } - }, - methods: { - add: function () { - this.isAdding = true - this.username = "" - this.password = "" - - let that = this - setTimeout(function () { - that.$refs.username.focus() - }, 100) - }, - cancel: function () { - this.isAdding = false - this.updatingIndex = -1 - }, - confirm: function () { - let that = this - if (this.username.length == 0) { - teaweb.warn("请输入用户名", function () { - that.$refs.username.focus() - }) - return - } - if (this.password.length == 0) { - teaweb.warn("请输入密码", function () { - that.$refs.password.focus() - }) - return - } - if (this.updatingIndex < 0) { - this.users.push({ - username: this.username, - password: this.password - }) - } else { - this.users[this.updatingIndex].username = this.username - this.users[this.updatingIndex].password = this.password - } - this.cancel() - }, - update: function (index, user) { - this.updatingIndex = index - - this.isAdding = true - this.username = user.username - this.password = user.password - - let that = this - setTimeout(function () { - that.$refs.username.focus() - }, 100) - }, - remove: function (index) { - this.users.$remove(index) - } - }, - template: `
+
`}),Vue.component("http-auth-basic-auth-user-box",{props:["v-users"],data:function(){let e=this.vUsers;return{users:e=null==e?[]:e,isAdding:!1,updatingIndex:-1,username:"",password:""}},methods:{add:function(){this.isAdding=!0,this.username="",this.password="";let e=this;setTimeout(function(){e.$refs.username.focus()},100)},cancel:function(){this.isAdding=!1,this.updatingIndex=-1},confirm:function(){let e=this;0==this.username.length?teaweb.warn("请输入用户名",function(){e.$refs.username.focus()}):0==this.password.length?teaweb.warn("请输入密码",function(){e.$refs.password.focus()}):(this.updatingIndex<0?this.users.push({username:this.username,password:this.password}):(this.users[this.updatingIndex].username=this.username,this.users[this.updatingIndex].password=this.password),this.cancel())},update:function(e,t){this.updatingIndex=e,this.isAdding=!0,this.username=t.username,this.password=t.password;let i=this;setTimeout(function(){i.$refs.username.focus()},100)},remove:function(e){this.users.$remove(e)}},template:`
@@ -7470,34 +2571,7 @@ Vue.component("http-auth-basic-auth-user-box", {
-
` -}) - -Vue.component("http-location-labels", { - props: ["v-location-config", "v-server-id"], - data: function () { - return { - location: this.vLocationConfig - } - }, - methods: { - // 判断是否已启用某配置 - configIsOn: function (config) { - return config != null && config.isPrior && config.isOn - }, - - refIsOn: function (ref, config) { - return this.configIsOn(ref) && config != null && config.isOn - }, - - len: function (arr) { - return (arr == null) ? 0 : arr.length - }, - url: function (path) { - return "/servers/server/settings/locations" + path + "?serverId=" + this.vServerId + "&locationId=" + this.location.id - } - }, - template: `
+
`}),Vue.component("http-location-labels",{props:["v-location-config","v-server-id"],data:function(){return{location:this.vLocationConfig}},methods:{configIsOn:function(e){return null!=e&&e.isPrior&&e.isOn},refIsOn:function(e,t){return this.configIsOn(e)&&null!=t&&t.isOn},len:function(e){return null==e?0:e.length},url:function(e){return"/servers/server/settings/locations"+e+"?serverId="+this.vServerId+"&locationId="+this.location.id}},template:`
{{location.name}} @@ -7560,42 +2634,7 @@ Vue.component("http-location-labels", { REWRITE {{rewriteRule.pattern}} -> {{rewriteRule.replace}}
-
` -}) - -Vue.component("http-location-labels-label", { - props: ["v-class", "v-href"], - template: `` -}) - -Vue.component("http-gzip-box", { - props: ["v-gzip-config", "v-gzip-ref", "v-is-location"], - data: function () { - let gzip = this.vGzipConfig - if (gzip == null) { - gzip = { - isOn: true, - level: 0, - minLength: null, - maxLength: null, - conds: null - } - } - - return { - gzip: gzip, - advancedVisible: false - } - }, - methods: { - isOn: function () { - return (!this.vIsLocation || this.vGzipRef.isPrior) && this.vGzipRef.isOn - }, - changeAdvancedVisible: function (v) { - this.advancedVisible = v - } - }, - template: `
+
`}),Vue.component("http-location-labels-label",{props:["v-class","v-href"],template:''}),Vue.component("http-gzip-box",{props:["v-gzip-config","v-gzip-ref","v-is-location"],data:function(){let e=this.vGzipConfig;return{gzip:e=null==e?{isOn:!0,level:0,minLength:null,maxLength:null,conds:null}:e,advancedVisible:!1}},methods:{isOn:function(){return(!this.vIsLocation||this.vGzipRef.isPrior)&&this.vGzipRef.isOn},changeAdvancedVisible:function(e){this.advancedVisible=e}},template:`
@@ -7646,44 +2685,7 @@ Vue.component("http-gzip-box", {
-
` -}) - -Vue.component("script-config-box", { - props: ["id", "v-script-config", "comment"], - data: function () { - let config = this.vScriptConfig - if (config == null) { - config = { - isPrior: false, - isOn: false, - code: "" - } - } - - if (config.code.length == 0) { - config.code = "\n\n\n\n" - } - - return { - config: config - } - }, - watch: { - "config.isOn": function () { - this.change() - } - }, - methods: { - change: function () { - this.$emit("change", this.config) - }, - changeCode: function (code) { - this.config.code = code - this.change() - } - }, - template: `
+
`}),Vue.component("script-config-box",{props:["id","v-script-config","comment"],data:function(){let e=this.vScriptConfig;return 0==(e=null==e?{isPrior:!1,isOn:!1,code:""}:e).code.length&&(e.code="\n\n\n\n"),{config:e}},watch:{"config.isOn":function(){this.change()}},methods:{change:function(){this.$emit("change",this.config)},changeCode:function(e){this.config.code=e,this.change()}},template:`
@@ -7700,197 +2702,13 @@ Vue.component("script-config-box", {
-
` -}) - -Vue.component("ssl-certs-view", { - props: ["v-certs"], - data: function () { - let certs = this.vCerts - if (certs == null) { - certs = [] - } - return { - certs: certs - } - }, - methods: { - // 格式化时间 - formatTime: function (timestamp) { - return new Date(timestamp * 1000).format("Y-m-d") - }, - - // 查看详情 - viewCert: function (certId) { - teaweb.popup("/servers/certs/certPopup?certId=" + certId, { - height: "28em", - width: "48em" - }) - } - }, - template: `
+
`}),Vue.component("ssl-certs-view",{props:["v-certs"],data:function(){let e=this.vCerts;return{certs:e=null==e?[]:e}},methods:{formatTime:function(e){return new Date(1e3*e).format("Y-m-d")},viewCert:function(e){teaweb.popup("/servers/certs/certPopup?certId="+e,{height:"28em",width:"48em"})}},template:`
{{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 reverseProxyRef = this.vReverseProxyRef - if (reverseProxyRef == null) { - reverseProxyRef = { - isPrior: false, - isOn: false, - reverseProxyId: 0 - } - } - - let reverseProxyConfig = this.vReverseProxyConfig - if (reverseProxyConfig == null) { - reverseProxyConfig = { - 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: false - } - } - if (reverseProxyConfig.addHeaders == null) { - reverseProxyConfig.addHeaders = [] - } - if (reverseProxyConfig.connTimeout == null) { - reverseProxyConfig.connTimeout = {count: 0, unit: "second"} - } - if (reverseProxyConfig.readTimeout == null) { - reverseProxyConfig.readTimeout = {count: 0, unit: "second"} - } - if (reverseProxyConfig.idleTimeout == null) { - reverseProxyConfig.idleTimeout = {count: 0, unit: "second"} - } - - if (reverseProxyConfig.proxyProtocol == null) { - // 如果直接赋值Vue将不会触发变更通知 - Vue.set(reverseProxyConfig, "proxyProtocol", { - isOn: false, - version: 1 - }) - } - - let forwardHeaders = [ - { - name: "X-Real-IP", - isChecked: false - }, - { - name: "X-Forwarded-For", - isChecked: false - }, - { - name: "X-Forwarded-By", - isChecked: false - }, - { - name: "X-Forwarded-Host", - isChecked: false - }, - { - name: "X-Forwarded-Proto", - isChecked: false - } - ] - forwardHeaders.forEach(function (v) { - v.isChecked = reverseProxyConfig.addHeaders.$contains(v.name) - }) - - return { - reverseProxyRef: reverseProxyRef, - reverseProxyConfig: reverseProxyConfig, - advancedVisible: false, - family: this.vFamily, - forwardHeaders: forwardHeaders - } - }, - watch: { - "reverseProxyConfig.requestHostType": function (v) { - let requestHostType = parseInt(v) - if (isNaN(requestHostType)) { - requestHostType = 0 - } - this.reverseProxyConfig.requestHostType = requestHostType - }, - "reverseProxyConfig.connTimeout.count": function (v) { - let count = parseInt(v) - if (isNaN(count) || count < 0) { - count = 0 - } - this.reverseProxyConfig.connTimeout.count = count - }, - "reverseProxyConfig.readTimeout.count": function (v) { - let count = parseInt(v) - if (isNaN(count) || count < 0) { - count = 0 - } - this.reverseProxyConfig.readTimeout.count = count - }, - "reverseProxyConfig.idleTimeout.count": function (v) { - let count = parseInt(v) - if (isNaN(count) || count < 0) { - count = 0 - } - this.reverseProxyConfig.idleTimeout.count = count - }, - "reverseProxyConfig.maxConns": function (v) { - let maxConns = parseInt(v) - if (isNaN(maxConns) || maxConns < 0) { - maxConns = 0 - } - this.reverseProxyConfig.maxConns = maxConns - }, - "reverseProxyConfig.maxIdleConns": function (v) { - let maxIdleConns = parseInt(v) - if (isNaN(maxIdleConns) || maxIdleConns < 0) { - maxIdleConns = 0 - } - this.reverseProxyConfig.maxIdleConns = maxIdleConns - }, - "reverseProxyConfig.proxyProtocol.version": function (v) { - let version = parseInt(v) - if (isNaN(version)) { - version = 1 - } - this.reverseProxyConfig.proxyProtocol.version = version - } - }, - methods: { - isOn: function () { - if (this.vIsLocation || this.vIsGroup) { - return this.reverseProxyRef.isPrior && this.reverseProxyRef.isOn - } - return this.reverseProxyRef.isOn - }, - changeAdvancedVisible: function (v) { - this.advancedVisible = v - }, - changeAddHeader: function () { - this.reverseProxyConfig.addHeaders = this.forwardHeaders.filter(function (v) { - return v.isChecked - }).map(function (v) { - return v.name - }) - } - }, - template: `
+
`}),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:`
@@ -8051,62 +2869,7 @@ Vue.component("reverse-proxy-box", {
-
` -}) - -Vue.component("http-firewall-param-filters-box", { - props: ["v-filters"], - data: function () { - let filters = this.vFilters - if (filters == null) { - filters = [] - } - - return { - filters: filters, - isAdding: false, - options: [ - {name: "MD5", code: "md5"}, - {name: "URLEncode", code: "urlEncode"}, - {name: "URLDecode", code: "urlDecode"}, - {name: "BASE64Encode", code: "base64Encode"}, - {name: "BASE64Decode", code: "base64Decode"}, - {name: "UNICODE编码", code: "unicodeEncode"}, - {name: "UNICODE解码", code: "unicodeDecode"}, - {name: "HTML实体编码", code: "htmlEscape"}, - {name: "HTML实体解码", code: "htmlUnescape"}, - {name: "计算长度", code: "length"}, - {name: "十六进制->十进制", "code": "hex2dec"}, - {name: "十进制->十六进制", "code": "dec2hex"}, - {name: "SHA1", "code": "sha1"}, - {name: "SHA256", "code": "sha256"} - ], - addingCode: "" - } - }, - methods: { - add: function () { - this.isAdding = true - this.addingCode = "" - }, - confirm: function () { - if (this.addingCode.length == 0) { - return - } - let that = this - this.filters.push(this.options.$find(function (k, v) { - return (v.code == that.addingCode) - })) - this.isAdding = false - }, - cancel: function () { - this.isAdding = false - }, - remove: function (index) { - this.filters.$remove(index) - } - }, - template: `
+
`}),Vue.component("http-firewall-param-filters-box",{props:["v-filters"],data:function(){let e=this.vFilters;return{filters:e=null==e?[]:e,isAdding:!1,options:[{name:"MD5",code:"md5"},{name:"URLEncode",code:"urlEncode"},{name:"URLDecode",code:"urlDecode"},{name:"BASE64Encode",code:"base64Encode"},{name:"BASE64Decode",code:"base64Decode"},{name:"UNICODE编码",code:"unicodeEncode"},{name:"UNICODE解码",code:"unicodeDecode"},{name:"HTML实体编码",code:"htmlEscape"},{name:"HTML实体解码",code:"htmlUnescape"},{name:"计算长度",code:"length"},{name:"十六进制->十进制",code:"hex2dec"},{name:"十进制->十六进制",code:"dec2hex"},{name:"SHA1",code:"sha1"},{name:"SHA256",code:"sha256"}],addingCode:""}},methods:{add:function(){this.isAdding=!0,this.addingCode=""},confirm:function(){if(0!=this.addingCode.length){let i=this;this.filters.push(this.options.$find(function(e,t){return t.code==i.addingCode})),this.isAdding=!1}},cancel:function(){this.isAdding=!1},remove:function(e){this.filters.$remove(e)}},template:`
@@ -8132,63 +2895,7 @@ Vue.component("http-firewall-param-filters-box", {

可以对参数值进行特定的编解码处理。

-
` -}) - -Vue.component("http-remote-addr-config-box", { - props: ["v-remote-addr-config", "v-is-location", "v-is-group"], - data: function () { - let config = this.vRemoteAddrConfig - if (config == null) { - config = { - isPrior: false, - isOn: false, - value: "${rawRemoteAddr}", - isCustomized: false - } - } - - let optionValue = "" - if (!config.isCustomized && (config.value == "${remoteAddr}" || config.value == "${rawRemoteAddr}")) { - optionValue = config.value - } - - return { - config: config, - options: [ - { - name: "直接获取", - description: "用户直接访问边缘节点,即 \"用户 --> 边缘节点\" 模式,这时候可以直接从连接中读取到真实的IP地址。", - value: "${rawRemoteAddr}" - }, - { - name: "从上级代理中获取", - description: "用户和边缘节点之间有别的代理服务转发,即 \"用户 --> [第三方代理服务] --> 边缘节点\",这时候只能从上级代理中获取传递的IP地址。", - value: "${remoteAddr}" - }, - { - name: "[自定义]", - description: "通过自定义变量来获取客户端真实的IP地址。", - value: "" - } - ], - optionValue: optionValue - } - }, - methods: { - isOn: function () { - return ((!this.vIsLocation && !this.vIsGroup) || this.config.isPrior) && this.config.isOn - }, - changeOptionValue: function () { - if (this.optionValue.length > 0) { - this.config.value = this.optionValue - this.config.isCustomized = false - } else { - this.config.isCustomized = true - } - } - }, - template: `
+
`}),Vue.component("http-remote-addr-config-box",{props:["v-remote-addr-config","v-is-location","v-is-group"],data:function(){let e=this.vRemoteAddrConfig,t="";return(e=null==e?{isPrior:!1,isOn:!1,value:"${rawRemoteAddr}",isCustomized:!1}:e).isCustomized||"${remoteAddr}"!=e.value&&"${rawRemoteAddr}"!=e.value||(t=e.value),{config:e,options:[{name:"直接获取",description:'用户直接访问边缘节点,即 "用户 --\x3e 边缘节点" 模式,这时候可以直接从连接中读取到真实的IP地址。',value:"${rawRemoteAddr}"},{name:"从上级代理中获取",description:'用户和边缘节点之间有别的代理服务转发,即 "用户 --\x3e [第三方代理服务] --\x3e 边缘节点",这时候只能从上级代理中获取传递的IP地址。',value:"${remoteAddr}"},{name:"[自定义]",description:"通过自定义变量来获取客户端真实的IP地址。",value:""}],optionValue:t}},methods:{isOn:function(){return(!this.vIsLocation&&!this.vIsGroup||this.config.isPrior)&&this.config.isOn},changeOptionValue:function(){0 @@ -8227,70 +2934,7 @@ Vue.component("http-remote-addr-config-box", {
-
` -}) - -// 访问日志搜索框 -Vue.component("http-access-log-search-box", { - props: ["v-ip", "v-domain", "v-keyword", "v-cluster-id", "v-node-id"], - data: function () { - let ip = this.vIp - if (ip == null) { - ip = "" - } - - let domain = this.vDomain - if (domain == null) { - domain = "" - } - - let keyword = this.vKeyword - if (keyword == null) { - keyword = "" - } - - return { - ip: ip, - domain: domain, - keyword: keyword, - clusterId: this.vClusterId - } - }, - methods: { - cleanIP: function () { - this.ip = "" - this.submit() - }, - cleanDomain: function () { - this.domain = "" - this.submit() - }, - cleanKeyword: function () { - this.keyword = "" - this.submit() - }, - submit: function () { - let parent = this.$el.parentNode - while (true) { - if (parent == null) { - break - } - if (parent.tagName == "FORM") { - break - } - parent = parent.parentNode - } - if (parent != null) { - setTimeout(function () { - parent.submit() - }, 500) - } - }, - changeCluster: function (clusterId) { - this.clusterId = clusterId - } - }, - template: `
+
`}),Vue.component("http-access-log-search-box",{props:["v-ip","v-domain","v-keyword","v-cluster-id","v-node-id"],data:function(){let e=this.vIp,t=(null==e&&(e=""),this.vDomain),i=(null==t&&(t=""),this.vKeyword);return null==i&&(i=""),{ip:e,domain:t,keyword:i,clusterId:this.vClusterId}},methods:{cleanIP:function(){this.ip="",this.submit()},cleanDomain:function(){this.domain="",this.submit()},cleanKeyword:function(){this.keyword="",this.submit()},submit:function(){let e=this.$el.parentNode;for(;;){if(null==e)break;if("FORM"==e.tagName)break;e=e.parentNode}null!=e&&setTimeout(function(){e.submit()},500)},changeCluster:function(e){this.clusterId=e}},template:`
@@ -8328,173 +2972,9 @@ Vue.component("http-access-log-search-box", {
-
` -}) - -// 显示指标对象名 -Vue.component("metric-key-label", { - props: ["v-key"], - data: function () { - return { - keyDefs: window.METRIC_HTTP_KEYS - } - }, - methods: { - keyName: function (key) { - let that = this - let subKey = "" - let def = this.keyDefs.$find(function (k, v) { - if (v.code == key) { - return true - } - if (key.startsWith("${arg.") && v.code.startsWith("${arg.")) { - subKey = that.getSubKey("arg.", key) - return true - } - if (key.startsWith("${header.") && v.code.startsWith("${header.")) { - subKey = that.getSubKey("header.", key) - return true - } - if (key.startsWith("${cookie.") && v.code.startsWith("${cookie.")) { - subKey = that.getSubKey("cookie.", key) - return true - } - return false - }) - if (def != null) { - if (subKey.length > 0) { - return def.name + ": " + subKey - } - return def.name - } - return key - }, - getSubKey: function (prefix, key) { - prefix = "${" + prefix - let index = key.indexOf(prefix) - if (index >= 0) { - key = key.substring(index + prefix.length) - key = key.substring(0, key.length - 1) - return key - } - return "" - } - }, - template: `
+
`}),Vue.component("metric-key-label",{props:["v-key"],data:function(){return{keyDefs:window.METRIC_HTTP_KEYS}},methods:{keyName:function(i){let s=this,n="";var e=this.keyDefs.$find(function(e,t){return t.code==i||(i.startsWith("${arg.")&&t.code.startsWith("${arg.")?(n=s.getSubKey("arg.",i),!0):i.startsWith("${header.")&&t.code.startsWith("${header.")?(n=s.getSubKey("header.",i),!0):!(!i.startsWith("${cookie.")||!t.code.startsWith("${cookie."))&&(n=s.getSubKey("cookie.",i),!0))});return null!=e?0 {{keyName(this.vKey)}} -` -}) - -// 指标对象 -Vue.component("metric-keys-config-box", { - props: ["v-keys"], - data: function () { - let keys = this.vKeys - if (keys == null) { - keys = [] - } - return { - keys: keys, - isAdding: false, - key: "", - subKey: "", - keyDescription: "", - - keyDefs: window.METRIC_HTTP_KEYS - } - }, - watch: { - keys: function () { - this.$emit("change", this.keys) - } - }, - methods: { - cancel: function () { - this.key = "" - this.subKey = "" - this.keyDescription = "" - this.isAdding = false - }, - confirm: function () { - if (this.key.length == 0) { - return - } - - if (this.key.indexOf(".NAME") > 0) { - if (this.subKey.length == 0) { - teaweb.warn("请输入参数值") - return - } - this.key = this.key.replace(".NAME", "." + this.subKey) - } - this.keys.push(this.key) - this.cancel() - }, - add: function () { - this.isAdding = true - let that = this - setTimeout(function () { - if (that.$refs.key != null) { - that.$refs.key.focus() - } - }, 100) - }, - remove: function (index) { - this.keys.$remove(index) - }, - changeKey: function () { - if (this.key.length == 0) { - return - } - let that = this - let def = this.keyDefs.$find(function (k, v) { - return v.code == that.key - }) - if (def != null) { - this.keyDescription = def.description - } - }, - keyName: function (key) { - let that = this - let subKey = "" - let def = this.keyDefs.$find(function (k, v) { - if (v.code == key) { - return true - } - if (key.startsWith("${arg.") && v.code.startsWith("${arg.")) { - subKey = that.getSubKey("arg.", key) - return true - } - if (key.startsWith("${header.") && v.code.startsWith("${header.")) { - subKey = that.getSubKey("header.", key) - return true - } - if (key.startsWith("${cookie.") && v.code.startsWith("${cookie.")) { - subKey = that.getSubKey("cookie.", key) - return true - } - return false - }) - if (def != null) { - if (subKey.length > 0) { - return def.name + ": " + subKey - } - return def.name - } - return key - }, - getSubKey: function (prefix, key) { - prefix = "${" + prefix - let index = key.indexOf(prefix) - if (index >= 0) { - key = key.substring(index + prefix.length) - key = key.substring(0, key.length - 1) - return key - } - return "" - } - }, - template: `
+
`}),Vue.component("metric-keys-config-box",{props:["v-keys"],data:function(){let e=this.vKeys;return{keys:e=null==e?[]:e,isAdding:!1,key:"",subKey:"",keyDescription:"",keyDefs:window.METRIC_HTTP_KEYS}},watch:{keys:function(){this.$emit("change",this.keys)}},methods:{cancel:function(){this.key="",this.subKey="",this.keyDescription="",this.isAdding=!1},confirm:function(){if(0!=this.key.length){if(0
@@ -8528,53 +3008,7 @@ Vue.component("metric-keys-config-box", {
-
` -}) - -Vue.component("http-web-root-box", { - props: ["v-root-config", "v-is-location", "v-is-group"], - data: function () { - let rootConfig = this.vRootConfig - if (rootConfig == null) { - rootConfig = { - isPrior: false, - isOn: true, - dir: "", - indexes: [], - stripPrefix: "", - decodePath: false, - isBreak: false - } - } - if (rootConfig.indexes == null) { - rootConfig.indexes = [] - } - return { - rootConfig: rootConfig, - advancedVisible: false - } - }, - methods: { - changeAdvancedVisible: function (v) { - this.advancedVisible = v - }, - addIndex: function () { - let that = this - teaweb.popup("/servers/server/settings/web/createIndex", { - height: "10em", - callback: function (resp) { - that.rootConfig.indexes.push(resp.data.index) - } - }) - }, - removeIndex: function (i) { - this.rootConfig.indexes.$remove(i) - }, - isOn: function () { - return ((!this.vIsLocation && !this.vIsGroup) || this.rootConfig.isPrior) && this.rootConfig.isOn - } - }, - template: `
+
`}),Vue.component("http-web-root-box",{props:["v-root-config","v-is-location","v-is-group"],data:function(){let e=this.vRootConfig;return null==(e=null==e?{isPrior:!1,isOn:!0,dir:"",indexes:[],stripPrefix:"",decodePath:!1,isBreak:!1}:e).indexes&&(e.indexes=[]),{rootConfig:e,advancedVisible:!1}},methods:{changeAdvancedVisible:function(e){this.advancedVisible=e},addIndex:function(){let t=this;teaweb.popup("/servers/server/settings/web/createIndex",{height:"10em",callback:function(e){t.rootConfig.indexes.push(e.data.index)}})},removeIndex:function(e){this.rootConfig.indexes.$remove(e)},isOn:function(){return(!this.vIsLocation&&!this.vIsGroup||this.rootConfig.isPrior)&&this.rootConfig.isOn}},template:`
@@ -8645,75 +3079,7 @@ Vue.component("http-web-root-box", {
-
` -}) - -Vue.component("http-webp-config-box", { - props: ["v-webp-config", "v-is-location", "v-is-group", "v-require-cache"], - data: function () { - let config = this.vWebpConfig - if (config == null) { - config = { - isPrior: false, - isOn: false, - quality: 50, - minLength: {count: 0, "unit": "kb"}, - maxLength: {count: 0, "unit": "kb"}, - mimeTypes: ["image/png", "image/jpeg", "image/bmp", "image/x-ico", "image/gif"], - extensions: [".png", ".jpeg", ".jpg", ".bmp", ".ico"], - conds: null - } - } - - if (config.mimeTypes == null) { - config.mimeTypes = [] - } - if (config.extensions == null) { - config.extensions = [] - } - - return { - config: config, - moreOptionsVisible: false, - quality: config.quality - } - }, - watch: { - quality: function (v) { - let quality = parseInt(v) - if (isNaN(quality)) { - quality = 90 - } else if (quality < 1) { - quality = 1 - } else if (quality > 100) { - quality = 100 - } - this.config.quality = quality - } - }, - methods: { - isOn: function () { - return ((!this.vIsLocation && !this.vIsGroup) || this.config.isPrior) && this.config.isOn - }, - changeExtensions: function (values) { - values.forEach(function (v, k) { - if (v.length > 0 && v[0] != ".") { - values[k] = "." + v - } - }) - this.config.extensions = values - }, - changeMimeTypes: function (values) { - this.config.mimeTypes = values - }, - changeAdvancedVisible: function () { - this.moreOptionsVisible = !this.moreOptionsVisible - }, - changeConds: function (conds) { - this.config.conds = conds - } - }, - template: `
+
`}),Vue.component("http-webp-config-box",{props:["v-webp-config","v-is-location","v-is-group","v-require-cache"],data:function(){let e=this.vWebpConfig;return null==(e=null==e?{isPrior:!1,isOn:!1,quality:50,minLength:{count:0,unit:"kb"},maxLength:{count:0,unit:"kb"},mimeTypes:["image/png","image/jpeg","image/bmp","image/x-ico","image/gif"],extensions:[".png",".jpeg",".jpg",".bmp",".ico"],conds:null}:e).mimeTypes&&(e.mimeTypes=[]),null==e.extensions&&(e.extensions=[]),{config:e,moreOptionsVisible:!1,quality:e.quality}},watch:{quality:function(e){let t=parseInt(e);isNaN(t)?t=90:t<1?t=1:100 @@ -8780,31 +3146,7 @@ Vue.component("http-webp-config-box", {
-
` -}) - -Vue.component("origin-scheduling-view-box", { - props: ["v-scheduling", "v-params"], - data: function () { - let scheduling = this.vScheduling - if (scheduling == null) { - scheduling = {} - } - return { - scheduling: scheduling - } - }, - methods: { - update: function () { - teaweb.popup("/servers/server/settings/reverseProxy/updateSchedulingPopup?" + this.vParams, { - height: "21em", - callback: function () { - window.location.reload() - }, - }) - } - }, - template: `
+
`}),Vue.component("origin-scheduling-view-box",{props:["v-scheduling","v-params"],data:function(){let e=this.vScheduling;return{scheduling:e=null==e?{}:e}},methods:{update:function(){teaweb.popup("/servers/server/settings/reverseProxy/updateSchedulingPopup?"+this.vParams,{height:"21em",callback:function(){window.location.reload()}})}},template:`
@@ -8815,43 +3157,7 @@ Vue.component("origin-scheduling-view-box", {
-
` -}) - -Vue.component("http-firewall-block-options", { - props: ["v-block-options"], - data: function () { - return { - blockOptions: this.vBlockOptions, - statusCode: this.vBlockOptions.statusCode, - timeout: this.vBlockOptions.timeout, - isEditing: false - } - }, - 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: `
+
`}),Vue.component("http-firewall-block-options",{props:["v-block-options"],data:function(){return{blockOptions:this.vBlockOptions,statusCode:this.vBlockOptions.statusCode,timeout:this.vBlockOptions.timeout,isEditing:!1}},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:` -` -}) - -Vue.component("http-firewall-rules-box", { - props: ["v-rules", "v-type"], - data: function () { - let rules = this.vRules - if (rules == null) { - rules = [] - } - return { - rules: rules - } - }, - methods: { - addRule: function () { - window.UPDATING_RULE = null - let that = this - teaweb.popup("/servers/components/waf/createRulePopup?type=" + this.vType, { - callback: function (resp) { - that.rules.push(resp.data.rule) - } - }) - }, - updateRule: function (index, rule) { - window.UPDATING_RULE = rule - let that = this - teaweb.popup("/servers/components/waf/createRulePopup?type=" + this.vType, { - callback: function (resp) { - Vue.set(that.rules, index, resp.data.rule) - } - }) - }, - removeRule: function (index) { - let that = this - teaweb.confirm("确定要删除此规则吗?", function () { - that.rules.$remove(index) - }) - } - }, - template: `
+`}),Vue.component("http-firewall-rules-box",{props:["v-rules","v-type"],data:function(){let e=this.vRules;return{rules:e=null==e?[]:e}},methods:{addRule:function(){window.UPDATING_RULE=null;let t=this;teaweb.popup("/servers/components/waf/createRulePopup?type="+this.vType,{callback:function(e){t.rules.push(e.data.rule)}})},updateRule:function(t,e){window.UPDATING_RULE=e;let i=this;teaweb.popup("/servers/components/waf/createRulePopup?type="+this.vType,{callback:function(e){Vue.set(i.rules,t,e.data.rule)}})},removeRule:function(e){let t=this;teaweb.confirm("确定要删除此规则吗?",function(){t.rules.$remove(e)})}},template:`
@@ -8948,67 +3214,7 @@ Vue.component("http-firewall-rules-box", {
-
` -}) - -Vue.component("http-fastcgi-box", { - props: ["v-fastcgi-ref", "v-fastcgi-configs", "v-is-location"], - data: function () { - let fastcgiRef = this.vFastcgiRef - if (fastcgiRef == null) { - fastcgiRef = { - isPrior: false, - isOn: false, - fastcgiIds: [] - } - } - let fastcgiConfigs = this.vFastcgiConfigs - if (fastcgiConfigs == null) { - fastcgiConfigs = [] - } else { - fastcgiRef.fastcgiIds = fastcgiConfigs.map(function (v) { - return v.id - }) - } - - return { - fastcgiRef: fastcgiRef, - fastcgiConfigs: fastcgiConfigs, - advancedVisible: false - } - }, - methods: { - isOn: function () { - return (!this.vIsLocation || this.fastcgiRef.isPrior) && this.fastcgiRef.isOn - }, - createFastcgi: function () { - let that = this - teaweb.popup("/servers/server/settings/fastcgi/createPopup", { - height: "26em", - callback: function (resp) { - teaweb.success("添加成功", function () { - that.fastcgiConfigs.push(resp.data.fastcgi) - that.fastcgiRef.fastcgiIds.push(resp.data.fastcgi.id) - }) - } - }) - }, - updateFastcgi: function (fastcgiId, index) { - let that = this - teaweb.popup("/servers/server/settings/fastcgi/updatePopup?fastcgiId=" + fastcgiId, { - callback: function (resp) { - teaweb.success("修改成功", function () { - Vue.set(that.fastcgiConfigs, index, resp.data.fastcgi) - }) - } - }) - }, - removeFastcgi: function (index) { - this.fastcgiRef.fastcgiIds.$remove(index) - this.fastcgiConfigs.$remove(index) - } - }, - template: `
+
`}),Vue.component("http-fastcgi-box",{props:["v-fastcgi-ref","v-fastcgi-configs","v-is-location"],data:function(){let e=this.vFastcgiRef,t=(null==e&&(e={isPrior:!1,isOn:!1,fastcgiIds:[]}),this.vFastcgiConfigs);return null==t?t=[]:e.fastcgiIds=t.map(function(e){return e.id}),{fastcgiRef:e,fastcgiConfigs:t,advancedVisible:!1}},methods:{isOn:function(){return(!this.vIsLocation||this.fastcgiRef.isPrior)&&this.fastcgiRef.isOn},createFastcgi:function(){let t=this;teaweb.popup("/servers/server/settings/fastcgi/createPopup",{height:"26em",callback:function(e){teaweb.success("添加成功",function(){t.fastcgiConfigs.push(e.data.fastcgi),t.fastcgiRef.fastcgiIds.push(e.data.fastcgi.id)})}})},updateFastcgi:function(e,t){let i=this;teaweb.popup("/servers/server/settings/fastcgi/updatePopup?fastcgiId="+e,{callback:function(e){teaweb.success("修改成功",function(){Vue.set(i.fastcgiConfigs,t,e.data.fastcgi)})}})},removeFastcgi:function(e){this.fastcgiRef.fastcgiIds.$remove(e),this.fastcgiConfigs.$remove(e)}},template:`
@@ -9039,64 +3245,7 @@ Vue.component("http-fastcgi-box", {
-
` -}) - -// 请求方法列表 -Vue.component("http-methods-box", { - props: ["v-methods"], - data: function () { - let methods = this.vMethods - if (methods == null) { - methods = [] - } - return { - methods: methods, - isAdding: false, - addingMethod: "" - } - }, - methods: { - add: function () { - this.isAdding = true - let that = this - setTimeout(function () { - that.$refs.addingMethod.focus() - }, 100) - }, - confirm: function () { - let that = this - - // 删除其中的空格 - this.addingMethod = this.addingMethod.replace(/\s/g, "").toUpperCase() - - if (this.addingMethod.length == 0) { - teaweb.warn("请输入要添加的请求方法", function () { - that.$refs.addingMethod.focus() - }) - return - } - - // 是否已经存在 - if (this.methods.$contains(this.addingMethod)) { - teaweb.warn("此请求方法已经存在,无需重复添加", function () { - that.$refs.addingMethod.focus() - }) - return - } - - this.methods.push(this.addingMethod) - this.cancel() - }, - remove: function (index) { - this.methods.$remove(index) - }, - cancel: function () { - this.isAdding = false - this.addingMethod = "" - } - }, - template: `
+
`}),Vue.component("http-methods-box",{props:["v-methods"],data:function(){let e=this.vMethods;return{methods:e=null==e?[]:e,isAdding:!1,addingMethod:""}},methods:{add:function(){this.isAdding=!0;let e=this;setTimeout(function(){e.$refs.addingMethod.focus()},100)},confirm:function(){let e=this;this.addingMethod=this.addingMethod.replace(/\s/g,"").toUpperCase(),0==this.addingMethod.length?teaweb.warn("请输入要添加的请求方法",function(){e.$refs.addingMethod.focus()}):this.methods.$contains(this.addingMethod)?teaweb.warn("此请求方法已经存在,无需重复添加",function(){e.$refs.addingMethod.focus()}):(this.methods.push(this.addingMethod),this.cancel())},remove:function(e){this.methods.$remove(e)},cancel:function(){this.isAdding=!1,this.addingMethod=""}},template:`
@@ -9121,79 +3270,7 @@ Vue.component("http-methods-box", {
-
` -}) - -// URL扩展名条件 -Vue.component("http-cond-url-extension", { - props: ["v-cond"], - data: function () { - let cond = { - isRequest: true, - param: "${requestPathExtension}", - operator: "in", - value: "[]" - } - if (this.vCond != null && this.vCond.param == cond.param) { - cond.value = this.vCond.value - } - - let extensions = [] - try { - extensions = JSON.parse(cond.value) - } catch (e) { - - } - - return { - cond: cond, - extensions: extensions, // TODO 可以拖动排序 - - isAdding: false, - addingExt: "" - } - }, - watch: { - extensions: function () { - this.cond.value = JSON.stringify(this.extensions) - } - }, - methods: { - addExt: function () { - this.isAdding = !this.isAdding - - if (this.isAdding) { - let that = this - setTimeout(function () { - that.$refs.addingExt.focus() - }, 100) - } - }, - cancelAdding: function () { - this.isAdding = false - this.addingExt = "" - }, - confirmAdding: function () { - // TODO 做更详细的校验 - // TODO 如果有重复的则提示之 - - if (this.addingExt.length == 0) { - return - } - if (this.addingExt[0] != ".") { - this.addingExt = "." + this.addingExt - } - this.addingExt = this.addingExt.replace(/\s+/g, "").toLowerCase() - this.extensions.push(this.addingExt) - - // 清除状态 - this.cancelAdding() - }, - removeExt: function (index) { - this.extensions.$remove(index) - } - }, - template: `
+
`}),Vue.component("http-cond-url-extension",{props:["v-cond"],data:function(){let e={isRequest:!0,param:"${requestPathExtension}",operator:"in",value:"[]"},t=(null!=this.vCond&&this.vCond.param==e.param&&(e.value=this.vCond.value),[]);try{t=JSON.parse(e.value)}catch(e){}return{cond:e,extensions:t,isAdding:!1,addingExt:""}},watch:{extensions:function(){this.cond.value=JSON.stringify(this.extensions)}},methods:{addExt:function(){if(this.isAdding=!this.isAdding,this.isAdding){let e=this;setTimeout(function(){e.$refs.addingExt.focus()},100)}},cancelAdding:function(){this.isAdding=!1,this.addingExt=""},confirmAdding:function(){0!=this.addingExt.length&&("."!=this.addingExt[0]&&(this.addingExt="."+this.addingExt),this.addingExt=this.addingExt.replace(/\s+/g,"").toLowerCase(),this.extensions.push(this.addingExt),this.cancelAdding())},removeExt:function(e){this.extensions.$remove(e)}},template:`
{{ext}}
@@ -9212,79 +3289,7 @@ Vue.component("http-cond-url-extension", {

扩展名需要包含点(.)符号,例如.jpg.png之类。

-
` -}) - -// URL扩展名条件 -Vue.component("http-cond-url-not-extension", { - props: ["v-cond"], - data: function () { - let cond = { - isRequest: true, - param: "${requestPathExtension}", - operator: "not in", - value: "[]" - } - if (this.vCond != null && this.vCond.param == cond.param) { - cond.value = this.vCond.value - } - - let extensions = [] - try { - extensions = JSON.parse(cond.value) - } catch (e) { - - } - - return { - cond: cond, - extensions: extensions, // TODO 可以拖动排序 - - isAdding: false, - addingExt: "" - } - }, - watch: { - extensions: function () { - this.cond.value = JSON.stringify(this.extensions) - } - }, - methods: { - addExt: function () { - this.isAdding = !this.isAdding - - if (this.isAdding) { - let that = this - setTimeout(function () { - that.$refs.addingExt.focus() - }, 100) - } - }, - cancelAdding: function () { - this.isAdding = false - this.addingExt = "" - }, - confirmAdding: function () { - // TODO 做更详细的校验 - // TODO 如果有重复的则提示之 - - if (this.addingExt.length == 0) { - return - } - if (this.addingExt[0] != ".") { - this.addingExt = "." + this.addingExt - } - this.addingExt = this.addingExt.replace(/\s+/g, "").toLowerCase() - this.extensions.push(this.addingExt) - - // 清除状态 - this.cancelAdding() - }, - removeExt: function (index) { - this.extensions.$remove(index) - } - }, - template: `
+
`}),Vue.component("http-cond-url-not-extension",{props:["v-cond"],data:function(){let e={isRequest:!0,param:"${requestPathExtension}",operator:"not in",value:"[]"},t=(null!=this.vCond&&this.vCond.param==e.param&&(e.value=this.vCond.value),[]);try{t=JSON.parse(e.value)}catch(e){}return{cond:e,extensions:t,isAdding:!1,addingExt:""}},watch:{extensions:function(){this.cond.value=JSON.stringify(this.extensions)}},methods:{addExt:function(){if(this.isAdding=!this.isAdding,this.isAdding){let e=this;setTimeout(function(){e.$refs.addingExt.focus()},100)}},cancelAdding:function(){this.isAdding=!1,this.addingExt=""},confirmAdding:function(){0!=this.addingExt.length&&("."!=this.addingExt[0]&&(this.addingExt="."+this.addingExt),this.addingExt=this.addingExt.replace(/\s+/g,"").toLowerCase(),this.extensions.push(this.addingExt),this.cancelAdding())},removeExt:function(e){this.extensions.$remove(e)}},template:`
{{ext}}
@@ -9303,309 +3308,39 @@ Vue.component("http-cond-url-not-extension", {

扩展名需要包含点(.)符号,例如.jpg.png之类。

-
` -}) - -// 根据URL前缀 -Vue.component("http-cond-url-prefix", { - props: ["v-cond"], - data: function () { - let cond = { - isRequest: true, - param: "${requestPath}", - operator: "prefix", - value: "", - isCaseInsensitive: false - } - if (this.vCond != null && typeof (this.vCond.value) == "string") { - cond.value = this.vCond.value - } - return { - cond: cond - } - }, - methods: { - changeCaseInsensitive: function (isCaseInsensitive) { - this.cond.isCaseInsensitive = isCaseInsensitive - } - }, - template: `
+
`}),Vue.component("http-cond-url-prefix",{props:["v-cond"],data:function(){let e={isRequest:!0,param:"${requestPath}",operator:"prefix",value:"",isCaseInsensitive:!1};return null!=this.vCond&&"string"==typeof this.vCond.value&&(e.value=this.vCond.value),{cond:e}},methods:{changeCaseInsensitive:function(e){this.cond.isCaseInsensitive=e}},template:`

URL前缀,有此前缀的URL都将会被匹配,通常以/开头,比如/static

-
` -}) - -Vue.component("http-cond-url-not-prefix", { - props: ["v-cond"], - data: function () { - let cond = { - isRequest: true, - param: "${requestPath}", - operator: "prefix", - value: "", - isReverse: true, - isCaseInsensitive: false - } - if (this.vCond != null && typeof this.vCond.value == "string") { - cond.value = this.vCond.value - } - return { - cond: cond - } - }, - methods: { - changeCaseInsensitive: function (isCaseInsensitive) { - this.cond.isCaseInsensitive = isCaseInsensitive - } - }, - template: `
+
`}),Vue.component("http-cond-url-not-prefix",{props:["v-cond"],data:function(){let e={isRequest:!0,param:"${requestPath}",operator:"prefix",value:"",isReverse:!0,isCaseInsensitive:!1};return null!=this.vCond&&"string"==typeof this.vCond.value&&(e.value=this.vCond.value),{cond:e}},methods:{changeCaseInsensitive:function(e){this.cond.isCaseInsensitive=e}},template:`

要排除的URL前缀,有此前缀的URL都将会被匹配,通常以/开头,比如/static

-
` -}) - -// URL精准匹配 -Vue.component("http-cond-url-eq", { - props: ["v-cond"], - data: function () { - let cond = { - isRequest: true, - param: "${requestPath}", - operator: "eq", - value: "", - isCaseInsensitive: false - } - if (this.vCond != null && typeof this.vCond.value == "string") { - cond.value = this.vCond.value - } - return { - cond: cond - } - }, - methods: { - changeCaseInsensitive: function (isCaseInsensitive) { - this.cond.isCaseInsensitive = isCaseInsensitive - } - }, - template: `
+
`}),Vue.component("http-cond-url-eq",{props:["v-cond"],data:function(){let e={isRequest:!0,param:"${requestPath}",operator:"eq",value:"",isCaseInsensitive:!1};return null!=this.vCond&&"string"==typeof this.vCond.value&&(e.value=this.vCond.value),{cond:e}},methods:{changeCaseInsensitive:function(e){this.cond.isCaseInsensitive=e}},template:`

完整的URL路径,通常以/开头,比如/static/ui.js

-
` -}) - -Vue.component("http-cond-url-not-eq", { - props: ["v-cond"], - data: function () { - let cond = { - isRequest: true, - param: "${requestPath}", - operator: "eq", - value: "", - isReverse: true, - isCaseInsensitive: false - } - if (this.vCond != null && typeof this.vCond.value == "string") { - cond.value = this.vCond.value - } - return { - cond: cond - } - }, - methods: { - changeCaseInsensitive: function (isCaseInsensitive) { - this.cond.isCaseInsensitive = isCaseInsensitive - } - }, - template: `
+
`}),Vue.component("http-cond-url-not-eq",{props:["v-cond"],data:function(){let e={isRequest:!0,param:"${requestPath}",operator:"eq",value:"",isReverse:!0,isCaseInsensitive:!1};return null!=this.vCond&&"string"==typeof this.vCond.value&&(e.value=this.vCond.value),{cond:e}},methods:{changeCaseInsensitive:function(e){this.cond.isCaseInsensitive=e}},template:`

要排除的完整的URL路径,通常以/开头,比如/static/ui.js

-
` -}) - -// URL正则匹配 -Vue.component("http-cond-url-regexp", { - props: ["v-cond"], - data: function () { - let cond = { - isRequest: true, - param: "${requestPath}", - operator: "regexp", - value: "", - isCaseInsensitive: false - } - if (this.vCond != null && typeof this.vCond.value == "string") { - cond.value = this.vCond.value - } - return { - cond: cond - } - }, - methods: { - changeCaseInsensitive: function (isCaseInsensitive) { - this.cond.isCaseInsensitive = isCaseInsensitive - } - }, - template: `
+
`}),Vue.component("http-cond-url-regexp",{props:["v-cond"],data:function(){let e={isRequest:!0,param:"${requestPath}",operator:"regexp",value:"",isCaseInsensitive:!1};return null!=this.vCond&&"string"==typeof this.vCond.value&&(e.value=this.vCond.value),{cond:e}},methods:{changeCaseInsensitive:function(e){this.cond.isCaseInsensitive=e}},template:`

匹配URL的正则表达式,比如^/static/(.*).js$

-
` -}) - -// 排除URL正则匹配 -Vue.component("http-cond-url-not-regexp", { - props: ["v-cond"], - data: function () { - let cond = { - isRequest: true, - param: "${requestPath}", - operator: "not regexp", - value: "", - isCaseInsensitive: false - } - if (this.vCond != null && typeof this.vCond.value == "string") { - cond.value = this.vCond.value - } - return { - cond: cond - } - }, - methods: { - changeCaseInsensitive: function (isCaseInsensitive) { - this.cond.isCaseInsensitive = isCaseInsensitive - } - }, - template: `
+
`}),Vue.component("http-cond-url-not-regexp",{props:["v-cond"],data:function(){let e={isRequest:!0,param:"${requestPath}",operator:"not regexp",value:"",isCaseInsensitive:!1};return null!=this.vCond&&"string"==typeof this.vCond.value&&(e.value=this.vCond.value),{cond:e}},methods:{changeCaseInsensitive:function(e){this.cond.isCaseInsensitive=e}},template:`

不要匹配URL的正则表达式,意即只要匹配成功则排除此条件,比如^/static/(.*).js$

-
` -}) - - -// User-Agent正则匹配 -Vue.component("http-cond-user-agent-regexp", { - props: ["v-cond"], - data: function () { - let cond = { - isRequest: true, - param: "${userAgent}", - operator: "regexp", - value: "", - isCaseInsensitive: false - } - if (this.vCond != null && typeof this.vCond.value == "string") { - cond.value = this.vCond.value - } - return { - cond: cond - } - }, - methods: { - changeCaseInsensitive: function (isCaseInsensitive) { - this.cond.isCaseInsensitive = isCaseInsensitive - } - }, - template: `
+
`}),Vue.component("http-cond-user-agent-regexp",{props:["v-cond"],data:function(){let e={isRequest:!0,param:"${userAgent}",operator:"regexp",value:"",isCaseInsensitive:!1};return null!=this.vCond&&"string"==typeof this.vCond.value&&(e.value=this.vCond.value),{cond:e}},methods:{changeCaseInsensitive:function(e){this.cond.isCaseInsensitive=e}},template:`

匹配User-Agent的正则表达式,比如Android|iPhone

-
` -}) - -// User-Agent正则不匹配 -Vue.component("http-cond-user-agent-not-regexp", { - props: ["v-cond"], - data: function () { - let cond = { - isRequest: true, - param: "${userAgent}", - operator: "not regexp", - value: "", - isCaseInsensitive: false - } - if (this.vCond != null && typeof this.vCond.value == "string") { - cond.value = this.vCond.value - } - return { - cond: cond - } - }, - methods: { - changeCaseInsensitive: function (isCaseInsensitive) { - this.cond.isCaseInsensitive = isCaseInsensitive - } - }, - template: `
+
`}),Vue.component("http-cond-user-agent-not-regexp",{props:["v-cond"],data:function(){let e={isRequest:!0,param:"${userAgent}",operator:"not regexp",value:"",isCaseInsensitive:!1};return null!=this.vCond&&"string"==typeof this.vCond.value&&(e.value=this.vCond.value),{cond:e}},methods:{changeCaseInsensitive:function(e){this.cond.isCaseInsensitive=e}},template:`

匹配User-Agent的正则表达式,比如Android|iPhone,如果匹配,则排除此条件。

-
` -}) - -// 根据MimeType -Vue.component("http-cond-mime-type", { - props: ["v-cond"], - data: function () { - let cond = { - isRequest: false, - param: "${response.contentType}", - operator: "mime type", - value: "[]" - } - if (this.vCond != null && this.vCond.param == cond.param) { - cond.value = this.vCond.value - } - return { - cond: cond, - mimeTypes: JSON.parse(cond.value), // TODO 可以拖动排序 - - isAdding: false, - addingMimeType: "" - } - }, - watch: { - mimeTypes: function () { - this.cond.value = JSON.stringify(this.mimeTypes) - } - }, - methods: { - addMimeType: function () { - this.isAdding = !this.isAdding - - if (this.isAdding) { - let that = this - setTimeout(function () { - that.$refs.addingMimeType.focus() - }, 100) - } - }, - cancelAdding: function () { - this.isAdding = false - this.addingMimeType = "" - }, - confirmAdding: function () { - // TODO 做更详细的校验 - // TODO 如果有重复的则提示之 - - if (this.addingMimeType.length == 0) { - return - } - this.addingMimeType = this.addingMimeType.replace(/\s+/g, "") - this.mimeTypes.push(this.addingMimeType) - - // 清除状态 - this.cancelAdding() - }, - removeMimeType: function (index) { - this.mimeTypes.$remove(index) - } - }, - template: `
+
`}),Vue.component("http-cond-mime-type",{props:["v-cond"],data:function(){let e={isRequest:!1,param:"${response.contentType}",operator:"mime type",value:"[]"};return null!=this.vCond&&this.vCond.param==e.param&&(e.value=this.vCond.value),{cond:e,mimeTypes:JSON.parse(e.value),isAdding:!1,addingMimeType:""}},watch:{mimeTypes:function(){this.cond.value=JSON.stringify(this.mimeTypes)}},methods:{addMimeType:function(){if(this.isAdding=!this.isAdding,this.isAdding){let e=this;setTimeout(function(){e.$refs.addingMimeType.focus()},100)}},cancelAdding:function(){this.isAdding=!1,this.addingMimeType=""},confirmAdding:function(){0!=this.addingMimeType.length&&(this.addingMimeType=this.addingMimeType.replace(/\s+/g,""),this.mimeTypes.push(this.addingMimeType),this.cancelAdding())},removeMimeType:function(e){this.mimeTypes.$remove(e)}},template:`
{{mimeType}}
@@ -9624,171 +3359,7 @@ Vue.component("http-cond-mime-type", {

服务器返回的内容的MimeType,比如text/htmlimage/*等。

-
` -}) - -// 参数匹配 -Vue.component("http-cond-params", { - props: ["v-cond"], - mounted: function () { - let cond = this.vCond - if (cond == null) { - return - } - this.operator = cond.operator - - // stringValue - if (["regexp", "not regexp", "eq", "not", "prefix", "suffix", "contains", "not contains", "eq ip", "gt ip", "gte ip", "lt ip", "lte ip", "ip range"].$contains(cond.operator)) { - this.stringValue = cond.value - return - } - - // numberValue - if (["eq int", "eq float", "gt", "gte", "lt", "lte", "mod 10", "ip mod 10", "mod 100", "ip mod 100"].$contains(cond.operator)) { - this.numberValue = cond.value - return - } - - // modValue - if (["mod", "ip mod"].$contains(cond.operator)) { - let pieces = cond.value.split(",") - this.modDivValue = pieces[0] - if (pieces.length > 1) { - this.modRemValue = pieces[1] - } - return - } - - // stringValues - let that = this - if (["in", "not in", "file ext", "mime type"].$contains(cond.operator)) { - try { - let arr = JSON.parse(cond.value) - if (arr != null && (arr instanceof Array)) { - arr.forEach(function (v) { - that.stringValues.push(v) - }) - } - } catch (e) { - - } - return - } - - // versionValue - if (["version range"].$contains(cond.operator)) { - let pieces = cond.value.split(",") - this.versionRangeMinValue = pieces[0] - if (pieces.length > 1) { - this.versionRangeMaxValue = pieces[1] - } - return - } - }, - data: function () { - let cond = { - isRequest: true, - param: "", - operator: window.REQUEST_COND_OPERATORS[0].op, - value: "", - isCaseInsensitive: false - } - if (this.vCond != null) { - cond = this.vCond - } - return { - cond: cond, - operators: window.REQUEST_COND_OPERATORS, - operator: window.REQUEST_COND_OPERATORS[0].op, - operatorDescription: window.REQUEST_COND_OPERATORS[0].description, - variables: window.REQUEST_VARIABLES, - variable: "", - - // 各种类型的值 - stringValue: "", - numberValue: "", - - modDivValue: "", - modRemValue: "", - - stringValues: [], - - versionRangeMinValue: "", - versionRangeMaxValue: "" - } - }, - methods: { - changeVariable: function () { - let v = this.cond.param - if (v == null) { - v = "" - } - this.cond.param = v + this.variable - }, - changeOperator: function () { - let that = this - this.operators.forEach(function (v) { - if (v.op == that.operator) { - that.operatorDescription = v.description - } - }) - - this.cond.operator = this.operator - - // 移动光标 - let box = document.getElementById("variables-value-box") - if (box != null) { - setTimeout(function () { - let input = box.getElementsByTagName("INPUT") - if (input.length > 0) { - input[0].focus() - } - }, 100) - } - }, - changeStringValues: function (v) { - this.stringValues = v - this.cond.value = JSON.stringify(v) - } - }, - watch: { - stringValue: function (v) { - this.cond.value = v - }, - numberValue: function (v) { - // TODO 校验数字 - this.cond.value = v - }, - modDivValue: function (v) { - if (v.length == 0) { - return - } - let div = parseInt(v) - if (isNaN(div)) { - div = 1 - } - this.modDivValue = div - this.cond.value = div + "," + this.modRemValue - }, - modRemValue: function (v) { - if (v.length == 0) { - return - } - let rem = parseInt(v) - if (isNaN(rem)) { - rem = 0 - } - this.modRemValue = rem - this.cond.value = this.modDivValue + "," + rem - }, - versionRangeMinValue: function (v) { - this.cond.value = this.versionRangeMinValue + "," + this.versionRangeMaxValue - }, - versionRangeMaxValue: function (v) { - this.cond.value = this.versionRangeMinValue + "," + this.versionRangeMaxValue - } - }, - template: ` +
`}),Vue.component("http-cond-params",{props:["v-cond"],mounted:function(){let i=this.vCond;if(null!=i)if(this.operator=i.operator,["regexp","not regexp","eq","not","prefix","suffix","contains","not contains","eq ip","gt ip","gte ip","lt ip","lte ip","ip range"].$contains(i.operator))this.stringValue=i.value;else if(["eq int","eq float","gt","gte","lt","lte","mod 10","ip mod 10","mod 100","ip mod 100"].$contains(i.operator))this.numberValue=i.value;else{var e;if(["mod","ip mod"].$contains(i.operator))return e=i.value.split(","),this.modDivValue=e[0],void(1 参数值 @@ -9907,72 +3478,7 @@ Vue.component("http-cond-params", {

选中后表示对比时忽略参数值的大小写。

-` -}) - -// 请求方法列表 -Vue.component("http-status-box", { - props: ["v-status-list"], - data: function () { - let statusList = this.vStatusList - if (statusList == null) { - statusList = [] - } - return { - statusList: statusList, - isAdding: false, - addingStatus: "" - } - }, - methods: { - add: function () { - this.isAdding = true - let that = this - setTimeout(function () { - that.$refs.addingStatus.focus() - }, 100) - }, - confirm: function () { - let that = this - - // 删除其中的空格 - this.addingStatus = this.addingStatus.replace(/\s/g, "").toUpperCase() - - if (this.addingStatus.length == 0) { - teaweb.warn("请输入要添加的状态码", function () { - that.$refs.addingStatus.focus() - }) - return - } - - // 是否已经存在 - if (this.statusList.$contains(this.addingStatus)) { - teaweb.warn("此状态码已经存在,无需重复添加", function () { - that.$refs.addingStatus.focus() - }) - return - } - - // 格式 - if (!this.addingStatus.match(/^\d{3}$/)) { - teaweb.warn("请输入正确的状态码", function () { - that.$refs.addingStatus.focus() - }) - return - } - - this.statusList.push(parseInt(this.addingStatus, 10)) - this.cancel() - }, - remove: function (index) { - this.statusList.$remove(index) - }, - cancel: function () { - this.isAdding = false - this.addingStatus = "" - } - }, - template: `
+`}),Vue.component("http-status-box",{props:["v-status-list"],data:function(){let e=this.vStatusList;return{statusList:e=null==e?[]:e,isAdding:!1,addingStatus:""}},methods:{add:function(){this.isAdding=!0;let e=this;setTimeout(function(){e.$refs.addingStatus.focus()},100)},confirm:function(){let e=this;this.addingStatus=this.addingStatus.replace(/\s/g,"").toUpperCase(),0==this.addingStatus.length?teaweb.warn("请输入要添加的状态码",function(){e.$refs.addingStatus.focus()}):this.statusList.$contains(this.addingStatus)?teaweb.warn("此状态码已经存在,无需重复添加",function(){e.$refs.addingStatus.focus()}):this.addingStatus.match(/^\d{3}$/)?(this.statusList.push(parseInt(this.addingStatus,10)),this.cancel()):teaweb.warn("请输入正确的状态码",function(){e.$refs.addingStatus.focus()})},remove:function(e){this.statusList.$remove(e)},cancel:function(){this.isAdding=!1,this.addingStatus=""}},template:`
@@ -9997,50 +3503,7 @@ Vue.component("http-status-box", {
-
` -}) - -Vue.component("server-group-selector", { - props: ["v-groups"], - data: function () { - let groups = this.vGroups - if (groups == null) { - groups = [] - } - return { - groups: groups - } - }, - methods: { - selectGroup: function () { - let that = this - let groupIds = this.groups.map(function (v) { - return v.id.toString() - }).join(",") - teaweb.popup("/servers/groups/selectPopup?selectedGroupIds=" + groupIds, { - callback: function (resp) { - that.groups.push(resp.data.group) - } - }) - }, - addGroup: function () { - let that = this - teaweb.popup("/servers/groups/createPopup", { - callback: function (resp) { - that.groups.push(resp.data.group) - } - }) - }, - removeGroup: function (index) { - this.groups.$remove(index) - }, - groupIds: function () { - return this.groups.map(function (v) { - return v.id - }) - } - }, - template: `
+
`}),Vue.component("server-group-selector",{props:["v-groups"],data:function(){let e=this.vGroups;return{groups:e=null==e?[]:e}},methods:{selectGroup:function(){let t=this;var e=this.groups.map(function(e){return e.id.toString()}).join(",");teaweb.popup("/servers/groups/selectPopup?selectedGroupIds="+e,{callback:function(e){t.groups.push(e.data.group)}})},addGroup:function(){let t=this;teaweb.popup("/servers/groups/createPopup",{callback:function(e){t.groups.push(e.data.group)}})},removeGroup:function(e){this.groups.$remove(e)},groupIds:function(){return this.groups.map(function(e){return e.id})}},template:`
@@ -10051,82 +3514,14 @@ Vue.component("server-group-selector", { -
` -}) - -Vue.component("script-group-config-box", { - props: ["v-group", "v-is-location"], - data: function () { - let group = this.vGroup - if (group == null) { - group = { - isPrior: false, - isOn: true, - scripts: [] - } - } - if (group.scripts == null) { - group.scripts = [] - } - - let script = null - if (group.scripts.length > 0) { - script = group.scripts[group.scripts.length - 1] - } - - return { - group: group, - script: script - } - }, - methods: { - changeScript: function (script) { - this.group.scripts = [script] // 目前只支持单个脚本 - this.change() - }, - change: function () { - this.$emit("change", this.group) - } - }, - template: `
+
`}),Vue.component("script-group-config-box",{props:["v-group","v-is-location"],data:function(){let e=this.vGroup,t=(null==(e=null==e?{isPrior:!1,isOn:!0,scripts:[]}:e).scripts&&(e.scripts=[]),null);return 0
-
` -}) - -// 指标周期设置 -Vue.component("metric-period-config-box", { - props: ["v-period", "v-period-unit"], - data: function () { - let period = this.vPeriod - let periodUnit = this.vPeriodUnit - if (period == null || period.toString().length == 0) { - period = 1 - } - if (periodUnit == null || periodUnit.length == 0) { - periodUnit = "day" - } - return { - periodConfig: { - period: period, - unit: periodUnit - } - } - }, - watch: { - "periodConfig.period": function (v) { - v = parseInt(v) - if (isNaN(v) || v <= 0) { - v = 1 - } - this.periodConfig.period = v - } - }, - template: `
+
`}),Vue.component("metric-period-config-box",{props:["v-period","v-period-unit"],data:function(){let e=this.vPeriod,t=this.vPeriodUnit;return null!=e&&0!=e.toString().length||(e=1),null!=t&&0!=t.length||(t="day"),{periodConfig:{period:e,unit:t}}},watch:{"periodConfig.period":function(e){e=parseInt(e),(isNaN(e)||e<=0)&&(e=1),this.periodConfig.period=e}},template:`
@@ -10143,56 +3538,7 @@ Vue.component("metric-period-config-box", {

在此周期内同一对象累积为同一数据。

-
` -}) - -Vue.component("traffic-limit-config-box", { - props: ["v-traffic-limit"], - data: function () { - let config = this.vTrafficLimit - if (config == null) { - config = { - isOn: false, - dailySize: { - count: -1, - unit: "gb" - }, - monthlySize: { - count: -1, - unit: "gb" - }, - totalSize: { - count: -1, - unit: "gb" - }, - noticePageBody: "" - } - } - if (config.dailySize == null) { - config.dailySize = { - count: -1, - unit: "gb" - } - } - if (config.monthlySize == null) { - config.monthlySize = { - count: -1, - unit: "gb" - } - } - if (config.totalSize == null) { - config.totalSize = { - count: -1, - unit: "gb" - } - } - return { - config: config - } - }, - methods: { - showBodyTemplate: function () { - this.config.noticePageBody = ` +
`}),Vue.component("traffic-limit-config-box",{props:["v-traffic-limit"],data:function(){let e=this.vTrafficLimit;return null==(e=null==e?{isOn:!1,dailySize:{count:-1,unit:"gb"},monthlySize:{count:-1,unit:"gb"},totalSize:{count:-1,unit:"gb"},noticePageBody:""}:e).dailySize&&(e.dailySize={count:-1,unit:"gb"}),null==e.monthlySize&&(e.monthlySize={count:-1,unit:"gb"}),null==e.totalSize&&(e.totalSize={count:-1,unit:"gb"}),{config:e}},methods:{showBodyTemplate:function(){this.config.noticePageBody=` Traffic Limit Exceeded Warning @@ -10203,10 +3549,7 @@ Vue.component("traffic-limit-config-box", {
Request ID: \${requestId}.
-` - } - }, - template: `
+`}},template:`
@@ -10248,56 +3591,7 @@ Vue.component("traffic-limit-config-box", {
-
` -}) - -Vue.component("firewall-syn-flood-config-box", { - props: ["v-syn-flood-config"], - data: function () { - let config = this.vSynFloodConfig - if (config == null) { - config = { - isOn: false, - minAttempts: 10, - timeoutSeconds: 600, - ignoreLocal: true - } - } - return { - config: config, - isEditing: false, - minAttempts: config.minAttempts, - timeoutSeconds: config.timeoutSeconds - } - }, - methods: { - edit: function () { - this.isEditing = !this.isEditing - } - }, - watch: { - minAttempts: function (v) { - let count = parseInt(v) - if (isNaN(count)) { - count = 10 - } - if (count < 5) { - count = 5 - } - this.config.minAttempts = count - }, - timeoutSeconds: function (v) { - let seconds = parseInt(v) - if (isNaN(seconds)) { - seconds = 10 - } - if (seconds < 60) { - seconds = 60 - } - this.config.timeoutSeconds = seconds - } - }, - template: `
+
`}),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:`` -}) - -// TODO 支持关键词搜索 -// TODO 改成弹窗选择 -Vue.component("admin-selector", { - props: ["v-admin-id"], - mounted: function () { - let that = this - Tea.action("/admins/options") - .post() - .success(function (resp) { - that.admins = resp.data.admins - }) - }, - data: function () { - let adminId = this.vAdminId - if (adminId == null) { - adminId = 0 - } - return { - admins: [], - adminId: adminId - } - }, - template: `
+
`}),Vue.component("admin-selector",{props:["v-admin-id"],mounted:function(){let t=this;Tea.action("/admins/options").post().success(function(e){t.admins=e.data.admins})},data:function(){let e=this.vAdminId;return{admins:[],adminId:e=null==e?0:e}},template:`
-
` -}) - -// 绑定IP列表 -Vue.component("ip-list-bind-box", { - props: ["v-http-firewall-policy-id", "v-type"], - mounted: function () { - this.refresh() - }, - data: function () { - return { - policyId: this.vHttpFirewallPolicyId, - type: this.vType, - lists: [] - } - }, - methods: { - bind: function () { - let that = this - teaweb.popup("/servers/iplists/bindHTTPFirewallPopup?httpFirewallPolicyId=" + this.policyId + "&type=" + this.type, { - width: "50em", - height: "34em", - callback: function () { - - }, - onClose: function () { - that.refresh() - } - }) - }, - remove: function (index, listId) { - let that = this - teaweb.confirm("确定要删除这个绑定的IP名单吗?", function () { - Tea.action("/servers/iplists/unbindHTTPFirewall") - .params({ - httpFirewallPolicyId: that.policyId, - listId: listId - }) - .post() - .success(function (resp) { - that.lists.$remove(index) - }) - }) - }, - refresh: function () { - let that = this - Tea.action("/servers/iplists/httpFirewall") - .params({ - httpFirewallPolicyId: this.policyId, - type: this.vType - }) - .post() - .success(function (resp) { - that.lists = resp.data.lists - }) - } - }, - template: `
+
`}),Vue.component("ip-list-bind-box",{props:["v-http-firewall-policy-id","v-type"],mounted:function(){this.refresh()},data:function(){return{policyId:this.vHttpFirewallPolicyId,type:this.vType,lists:[]}},methods:{bind:function(){let e=this;teaweb.popup("/servers/iplists/bindHTTPFirewallPopup?httpFirewallPolicyId="+this.policyId+"&type="+this.type,{width:"50em",height:"34em",callback:function(){},onClose:function(){e.refresh()}})},remove:function(t,e){let i=this;teaweb.confirm("确定要删除这个绑定的IP名单吗?",function(){Tea.action("/servers/iplists/unbindHTTPFirewall").params({httpFirewallPolicyId:i.policyId,listId:e}).post().success(function(e){i.lists.$remove(t)})})},refresh:function(){let t=this;Tea.action("/servers/iplists/httpFirewall").params({httpFirewallPolicyId:this.policyId,type:this.vType}).post().success(function(e){t.lists=e.data.lists})}},template:`
绑定+   已绑定: -
` -}) - -Vue.component("ip-list-table", { - props: ["v-items", "v-keyword", "v-show-search-button"], - data: function () { - return { - items: this.vItems, - keyword: (this.vKeyword != null) ? this.vKeyword : "", - selectedAll: false, - hasSelectedItems: false - } - }, - methods: { - updateItem: function (itemId) { - this.$emit("update-item", itemId) - }, - deleteItem: function (itemId) { - this.$emit("delete-item", itemId) - }, - viewLogs: function (itemId) { - teaweb.popup("/servers/iplists/accessLogsPopup?itemId=" + itemId, { - width: "50em", - height: "30em" - }) - }, - changeSelectedAll: function () { - let boxes = this.$refs.itemCheckBox - if (boxes == null) { - return - } - - let that = this - boxes.forEach(function (box) { - box.checked = that.selectedAll - }) - - this.hasSelectedItems = this.selectedAll - }, - changeSelected: function (e) { - let that = this - that.hasSelectedItems = false - let boxes = that.$refs.itemCheckBox - if (boxes == null) { - return - } - boxes.forEach(function (box) { - if (box.checked) { - that.hasSelectedItems = true - } - }) - }, - deleteAll: function () { - let boxes = this.$refs.itemCheckBox - if (boxes == null) { - return - } - let itemIds = [] - boxes.forEach(function (box) { - if (box.checked) { - itemIds.push(box.value) - } - }) - if (itemIds.length == 0) { - return - } - - Tea.action("/servers/iplists/deleteItems") - .post() - .params({ - itemIds: itemIds - }) - .success(function () { - teaweb.successToast("批量删除成功", 1200, teaweb.reload) - }) - }, - formatSeconds: function (seconds) { - if (seconds < 60) { - return seconds + "秒" - } - if (seconds < 3600) { - return Math.ceil(seconds / 60) + "分钟" - } - if (seconds < 86400) { - return Math.ceil(seconds / 3600) + "小时" - } - return Math.ceil(seconds / 86400) + "天" - } - }, - template: `
+
`}),Vue.component("ip-list-table",{props:["v-items","v-keyword","v-show-search-button"],data:function(){return{items:this.vItems,keyword:null!=this.vKeyword?this.vKeyword:"",selectedAll:!1,hasSelectedItems:!1}},methods:{updateItem:function(e){this.$emit("update-item",e)},deleteItem:function(e){this.$emit("delete-item",e)},viewLogs:function(e){teaweb.popup("/servers/iplists/accessLogsPopup?itemId="+e,{width:"50em",height:"30em"})},changeSelectedAll:function(){let e=this.$refs.itemCheckBox;if(null!=e){let t=this;e.forEach(function(e){e.checked=t.selectedAll}),this.hasSelectedItems=this.selectedAll}},changeSelected:function(e){let t=this,i=(t.hasSelectedItems=!1,t.$refs.itemCheckBox);null!=i&&i.forEach(function(e){e.checked&&(t.hasSelectedItems=!0)})},deleteAll:function(){let e=this.$refs.itemCheckBox;if(null!=e){let t=[];e.forEach(function(e){e.checked&&t.push(e.value)}),0!=t.length&&Tea.action("/servers/iplists/deleteItems").post().params({itemIds:t}).success(function(){teaweb.successToast("批量删除成功",1200,teaweb.reload)})}},formatSeconds:function(e){return e<60?e+"秒":e<3600?Math.ceil(e/60)+"分钟":e<86400?Math.ceil(e/3600)+"小时":Math.ceil(e/86400)+"天"}},template:`
@@ -10623,12 +3746,7 @@ Vue.component("ip-list-table", { -
` -}) - -Vue.component("ip-item-text", { - props: ["v-item"], - template: ` +
`}),Vue.component("ip-item-text",{props:["v-item"],template:` * {{vItem.ipFrom}} @@ -10636,81 +3754,9 @@ Vue.component("ip-item-text", { {{vItem.ipFrom}}   级别:{{vItem.eventLevelName}} -` -}) - -Vue.component("ip-box", { - props: ["v-ip"], - methods: { - popup: function () { - let ip = this.vIp - if (ip == null || ip.length == 0) { - let e = this.$refs.container - ip = e.innerText - if (ip == null) { - ip = e.textContent - } - } - - teaweb.popup("/servers/ipbox?ip=" + ip, { - width: "50em", - height: "30em" - }) - } - }, - template: `` -}) - -Vue.component("api-node-selector", { - props: [], - data: function () { - return {} - }, - template: `
+`}),Vue.component("ip-box",{props:["v-ip"],methods:{popup:function(){let e=this.vIp;var t;null!=e&&0!=e.length||(t=this.$refs.container,null==(e=t.innerText)&&(e=t.textContent)),teaweb.popup("/servers/ipbox?ip="+e,{width:"50em",height:"30em"})}},template:''}),Vue.component("api-node-selector",{props:[],data:function(){return{}},template:`
暂未实现 -
` -}) - -Vue.component("api-node-addresses-box", { - props: ["v-addrs", "v-name"], - data: function () { - let addrs = this.vAddrs - if (addrs == null) { - addrs = [] - } - return { - addrs: addrs - } - }, - methods: { - // 添加IP地址 - addAddr: function () { - let that = this; - teaweb.popup("/api/node/createAddrPopup", { - height: "16em", - callback: function (resp) { - that.addrs.push(resp.data.addr); - } - }) - }, - - // 修改地址 - updateAddr: function (index, addr) { - let that = this; - window.UPDATING_ADDR = addr - teaweb.popup("/api/node/updateAddrPopup?addressId=", { - callback: function (resp) { - Vue.set(that.addrs, index, resp.data.addr); - } - }) - }, - - // 删除IP地址 - removeAddr: function (index) { - this.addrs.$remove(index); - } - }, - template: `
+
`}),Vue.component("api-node-addresses-box",{props:["v-addrs","v-name"],data:function(){let e=this.vAddrs;return{addrs:e=null==e?[]:e}},methods:{addAddr:function(){let t=this;teaweb.popup("/api/node/createAddrPopup",{height:"16em",callback:function(e){t.addrs.push(e.data.addr)}})},updateAddr:function(t,e){let i=this;window.UPDATING_ADDR=e,teaweb.popup("/api/node/updateAddrPopup?addressId=",{callback:function(e){Vue.set(i.addrs,t,e.data.addr)}})},removeAddr:function(e){this.addrs.$remove(e)}},template:`
@@ -10725,166 +3771,9 @@ Vue.component("api-node-addresses-box", {
-
` -}) - -// 给Table增加排序功能 -function sortTable(callback) { - // 引入js - let jsFile = document.createElement("script") - jsFile.setAttribute("src", "/js/sortable.min.js") - jsFile.addEventListener("load", function () { - // 初始化 - let box = document.querySelector("#sortable-table") - if (box == null) { - return - } - Sortable.create(box, { - draggable: "tbody", - handle: ".icon.handle", - onStart: function () { - }, - onUpdate: function (event) { - let rows = box.querySelectorAll("tbody") - let rowIds = [] - rows.forEach(function (row) { - rowIds.push(parseInt(row.getAttribute("v-id"))) - }) - callback(rowIds) - } - }) - }) - document.head.appendChild(jsFile) -} - -function sortLoad(callback) { - let jsFile = document.createElement("script") - jsFile.setAttribute("src", "/js/sortable.min.js") - jsFile.addEventListener("load", function () { - if (typeof (callback) == "function") { - callback() - } - }) - document.head.appendChild(jsFile) -} - - -Vue.component("page-box", { - data: function () { - return { - page: "" - } - }, - created: function () { - let that = this; - setTimeout(function () { - that.page = Tea.Vue.page; - }) - }, - template: `
+
`}),Vue.component("page-box",{data:function(){return{page:""}},created:function(){let e=this;setTimeout(function(){e.page=Tea.Vue.page})},template:`
-
` -}) - -Vue.component("network-addresses-box", { - props: ["v-server-type", "v-addresses", "v-protocol", "v-name", "v-from", "v-support-range"], - data: function () { - let addresses = this.vAddresses - if (addresses == null) { - addresses = [] - } - let protocol = this.vProtocol - if (protocol == null) { - protocol = "" - } - - let name = this.vName - if (name == null) { - name = "addresses" - } - - let from = this.vFrom - if (from == null) { - from = "" - } - - return { - addresses: addresses, - protocol: protocol, - name: name, - from: from - } - }, - watch: { - "vServerType": function () { - this.addresses = [] - }, - "vAddresses": function () { - if (this.vAddresses != null) { - this.addresses = this.vAddresses - } - } - }, - methods: { - addAddr: function () { - let that = this - window.UPDATING_ADDR = null - teaweb.popup("/servers/addPortPopup?serverType=" + this.vServerType + "&protocol=" + this.protocol + "&from=" + this.from + "&supportRange=" + (this.supportRange() ? 1 : 0), { - height: "18em", - callback: function (resp) { - var addr = resp.data.address - if (that.addresses.$find(function (k, v) { - return addr.host == v.host && addr.portRange == v.portRange && addr.protocol == v.protocol - }) != null) { - teaweb.warn("要添加的网络地址已经存在") - return - } - that.addresses.push(addr) - if (["https", "https4", "https6"].$contains(addr.protocol)) { - this.tlsProtocolName = "HTTPS" - } else if (["tls", "tls4", "tls6"].$contains(addr.protocol)) { - this.tlsProtocolName = "TLS" - } - - // 发送事件 - that.$emit("change", that.addresses) - } - }) - }, - removeAddr: function (index) { - this.addresses.$remove(index); - - // 发送事件 - this.$emit("change", this.addresses) - }, - updateAddr: function (index, addr) { - let that = this - window.UPDATING_ADDR = addr - teaweb.popup("/servers/addPortPopup?serverType=" + this.vServerType + "&protocol=" + this.protocol + "&from=" + this.from + "&supportRange=" + (this.supportRange() ? 1 : 0), { - height: "18em", - callback: function (resp) { - var addr = resp.data.address - Vue.set(that.addresses, index, addr) - - if (["https", "https4", "https6"].$contains(addr.protocol)) { - this.tlsProtocolName = "HTTPS" - } else if (["tls", "tls4", "tls6"].$contains(addr.protocol)) { - this.tlsProtocolName = "TLS" - } - - // 发送事件 - that.$emit("change", that.addresses) - } - }) - - // 发送事件 - this.$emit("change", this.addresses) - }, - supportRange: function () { - return this.vSupportRange || (this.vServerType == "tcpProxy" || this.vServerType == "udpProxy") - } - }, - template: `
+
`}),Vue.component("network-addresses-box",{props:["v-server-type","v-addresses","v-protocol","v-name","v-from","v-support-range"],data:function(){let e=this.vAddresses,t=(null==e&&(e=[]),this.vProtocol),i=(null==t&&(t=""),this.vName),s=(null==i&&(i="addresses"),this.vFrom);return null==s&&(s=""),{addresses:e,protocol:t,name:i,from:s}},watch:{vServerType:function(){this.addresses=[]},vAddresses:function(){null!=this.vAddresses&&(this.addresses=this.vAddresses)}},methods:{addAddr:function(){let t=this;window.UPDATING_ADDR=null,teaweb.popup("/servers/addPortPopup?serverType="+this.vServerType+"&protocol="+this.protocol+"&from="+this.from+"&supportRange="+(this.supportRange()?1:0),{height:"18em",callback:function(e){var i=e.data.address;null!=t.addresses.$find(function(e,t){return i.host==t.host&&i.portRange==t.portRange&&i.protocol==t.protocol})?teaweb.warn("要添加的网络地址已经存在"):(t.addresses.push(i),["https","https4","https6"].$contains(i.protocol)?this.tlsProtocolName="HTTPS":["tls","tls4","tls6"].$contains(i.protocol)&&(this.tlsProtocolName="TLS"),t.$emit("change",t.addresses))}})},removeAddr:function(e){this.addresses.$remove(e),this.$emit("change",this.addresses)},updateAddr:function(t,e){let i=this;window.UPDATING_ADDR=e,teaweb.popup("/servers/addPortPopup?serverType="+this.vServerType+"&protocol="+this.protocol+"&from="+this.from+"&supportRange="+(this.supportRange()?1:0),{height:"18em",callback:function(e){e=e.data.address;Vue.set(i.addresses,t,e),["https","https4","https6"].$contains(e.protocol)?this.tlsProtocolName="HTTPS":["tls","tls4","tls6"].$contains(e.protocol)&&(this.tlsProtocolName="TLS"),i.$emit("change",i.addresses)}}),this.$emit("change",this.addresses)},supportRange:function(){return this.vSupportRange||"tcpProxy"==this.vServerType||"udpProxy"==this.vServerType}},template:`
@@ -10894,264 +3783,7 @@ Vue.component("network-addresses-box", {
[添加端口绑定] -
` -}) - -/** - * 保存按钮 - */ -Vue.component("submit-btn", { - template: '' -}); - -// 可以展示更多条目的角图表 -Vue.component("more-items-angle", { - props: ["v-data-url", "v-url"], - data: function () { - return { - visible: false - } - }, - methods: { - show: function () { - this.visible = !this.visible - if (this.visible) { - this.showBox() - } else { - this.hideBox() - } - }, - showBox: function () { - let that = this - - this.visible = true - - Tea.action(this.vDataUrl) - .params({ - url: this.vUrl - }) - .post() - .success(function (resp) { - let groups = resp.data.groups - - let boxLeft = that.$el.offsetLeft + 120; - let boxTop = that.$el.offsetTop + 70; - - let box = document.createElement("div") - box.setAttribute("id", "more-items-box") - box.style.cssText = "z-index: 100; position: absolute; left: " + boxLeft + "px; top: " + boxTop + "px; max-height: 30em; overflow: auto; border-bottom: 1px solid rgba(34,36,38,.15)" - document.body.append(box) - - let menuHTML = "
    " - groups.forEach(function (group) { - menuHTML += "
    " + teaweb.encodeHTML(group.name) + "
    " - group.items.forEach(function (item) { - menuHTML += "" + teaweb.encodeHTML(item.name) + "" - }) - }) - menuHTML += "
" - box.innerHTML = menuHTML - - let listener = function (e) { - if (e.target.tagName == "I") { - return - } - - if (!that.isInBox(box, e.target)) { - document.removeEventListener("click", listener) - that.hideBox() - } - } - document.addEventListener("click", listener) - }) - }, - hideBox: function () { - let box = document.getElementById("more-items-box") - if (box != null) { - box.parentNode.removeChild(box) - } - this.visible = false - }, - isInBox: function (parent, child) { - while (true) { - if (child == null) { - break - } - if (child.parentNode == parent) { - return true - } - child = child.parentNode - } - return false - } - }, - template: `` -}) - -/** - * 菜单项 - */ -Vue.component("menu-item", { - props: ["href", "active", "code"], - data: function () { - let active = this.active - if (typeof (active) == "undefined") { - var itemCode = "" - if (typeof (window.TEA.ACTION.data.firstMenuItem) != "undefined") { - itemCode = window.TEA.ACTION.data.firstMenuItem - } - if (itemCode != null && itemCode.length > 0 && this.code != null && this.code.length > 0) { - if (itemCode.indexOf(",") > 0) { - active = itemCode.split(",").$contains(this.code) - } else { - active = (itemCode == this.code) - } - } - } - - let href = (this.href == null) ? "" : this.href - if (typeof (href) == "string" && href.length > 0 && href.startsWith(".")) { - let qIndex = href.indexOf("?") - if (qIndex >= 0) { - href = Tea.url(href.substring(0, qIndex)) + href.substring(qIndex) - } else { - href = Tea.url(href) - } - } - - return { - vHref: href, - vActive: active - } - }, - methods: { - click: function (e) { - this.$emit("click", e) - } - }, - template: '\ - \ - ' -}); - -// 使用Icon的链接方式 -Vue.component("link-icon", { - props: ["href", "title", "target"], - data: function () { - return { - vTitle: (this.title == null) ? "打开链接" : this.title - } - }, - template: ` ` -}) - -// 带有下划虚线的连接 -Vue.component("link-red", { - props: ["href", "title"], - data: function () { - let href = this.href - if (href == null) { - href = "" - } - return { - vHref: href - } - }, - methods: { - clickPrevent: function () { - emitClick(this, arguments) - } - }, - template: `` -}) - -// 会弹出窗口的链接 -Vue.component("link-popup", { - props: ["title"], - methods: { - clickPrevent: function () { - emitClick(this, arguments) - } - }, - template: `` -}) - -Vue.component("popup-icon", { - props: ["title", "href", "height"], - methods: { - clickPrevent: function () { - if (this.href != null && this.href.length > 0) { - teaweb.popup(this.href, { - height: this.height - }) - } - } - }, - template: ` ` -}) - -// 小提示 -Vue.component("tip-icon", { - props: ["content"], - methods: { - showTip: function () { - teaweb.popupTip(this.content) - } - }, - template: `` -}) - -// 提交点击事件 -function emitClick(obj, arguments) { - let event = "click" - let newArgs = [event] - for (let i = 0; i < arguments.length; i++) { - newArgs.push(arguments[i]) - } - obj.$emit.apply(obj, newArgs) -} - -Vue.component("countries-selector", { - props: ["v-countries"], - data: function () { - let countries = this.vCountries - if (countries == null) { - countries = [] - } - let countryIds = countries.$map(function (k, v) { - return v.id - }) - return { - countries: countries, - countryIds: countryIds - } - }, - methods: { - add: function () { - let countryStringIds = this.countryIds.map(function (v) { - return v.toString() - }) - let that = this - teaweb.popup("/ui/selectCountriesPopup?countryIds=" + countryStringIds.join(","), { - width: "48em", - height: "23em", - callback: function (resp) { - that.countries = resp.data.countries - that.change() - } - }) - }, - remove: function (index) { - this.countries.$remove(index) - this.change() - }, - change: function () { - this.countryIds = this.countries.$map(function (k, v) { - return v.id - }) - } - }, - template: `
+
`}),Vue.component("submit-btn",{template:''}),Vue.component("more-items-angle",{props:["v-data-url","v-url"],data:function(){return{visible:!1}},methods:{show:function(){this.visible=!this.visible,this.visible?this.showBox():this.hideBox()},showBox:function(){let a=this;this.visible=!0,Tea.action(this.vDataUrl).params({url:this.vUrl}).post().success(function(e){let t=e.data.groups;var e=a.$el.offsetLeft+120,i=a.$el.offsetTop+70;function s(e){"I"==e.target.tagName||a.isInBox(n,e.target)||(document.removeEventListener("click",s),a.hideBox())}let n=document.createElement("div"),o=(n.setAttribute("id","more-items-box"),n.style.cssText="z-index: 100; position: absolute; left: "+e+"px; top: "+i+"px; max-height: 30em; overflow: auto; border-bottom: 1px solid rgba(34,36,38,.15)",document.body.append(n),'",n.innerHTML=o;document.addEventListener("click",s)})},hideBox:function(){let e=document.getElementById("more-items-box");null!=e&&e.parentNode.removeChild(e),this.visible=!1},isInBox:function(e,t){for(;;){if(null==t)break;if(t.parentNode==e)return!0;t=t.parentNode}return!1}},template:''}),Vue.component("menu-item",{props:["href","active","code"],data:function(){let e=this.active;var t;void 0===e&&(t="",null!=(t=void 0!==window.TEA.ACTION.data.firstMenuItem?window.TEA.ACTION.data.firstMenuItem:t)&&0 \t\t'}),Vue.component("link-icon",{props:["href","title","target"],data:function(){return{vTitle:null==this.title?"打开链接":this.title}},template:' '}),Vue.component("link-red",{props:["href","title"],data:function(){let e=this.href;return{vHref:e=null==e?"":e}},methods:{clickPrevent:function(){emitClick(this,arguments)}},template:''}),Vue.component("link-popup",{props:["title"],methods:{clickPrevent:function(){emitClick(this,arguments)}},template:''}),Vue.component("popup-icon",{props:["title","href","height"],methods:{clickPrevent:function(){null!=this.href&&0 '}),Vue.component("tip-icon",{props:["content"],methods:{showTip:function(){teaweb.popupTip(this.content)}},template:''}),Vue.component("countries-selector",{props:["v-countries"],data:function(){let e=this.vCountries;var t=(e=null==e?[]:e).$map(function(e,t){return t.id});return{countries:e,countryIds:t}},methods:{add:function(){let e=this.countryIds.map(function(e){return e.toString()}),t=this;teaweb.popup("/ui/selectCountriesPopup?countryIds="+e.join(","),{width:"48em",height:"23em",callback:function(e){t.countries=e.data.countries,t.change()}})},remove:function(e){this.countries.$remove(e),this.change()},change:function(){this.countryIds=this.countries.$map(function(e,t){return t.id})}},template:`
{{country.name}}
@@ -11160,143 +3792,11 @@ Vue.component("countries-selector", {
-
` -}) - -Vue.component("raquo-item", { - template: `»` -}) - -Vue.component("more-options-tbody", { - data: function () { - return { - isVisible: false - } - }, - methods: { - show: function () { - this.isVisible = !this.isVisible - this.$emit("change", this.isVisible) - } - }, - template: ` +
`}),Vue.component("raquo-item",{template:'»'}),Vue.component("more-options-tbody",{data:function(){return{isVisible:!1}},methods:{show:function(){this.isVisible=!this.isVisible,this.$emit("change",this.isVisible)}},template:` 更多选项收起选项 -` -}) - -Vue.component("download-link", { - props: ["v-element", "v-file", "v-value"], - created: function () { - let that = this - setTimeout(function () { - that.url = that.composeURL() - }, 1000) - }, - data: function () { - let filename = this.vFile - if (filename == null || filename.length == 0) { - filename = "unknown-file" - } - return { - file: filename, - url: this.composeURL() - } - }, - methods: { - composeURL: function () { - let text = "" - if (this.vValue != null) { - text = this.vValue - } else { - let e = document.getElementById(this.vElement) - if (e == null) { - teaweb.warn("找不到要下载的内容") - return - } - text = e.innerText - if (text == null) { - text = e.textContent - } - } - return Tea.url("/ui/download", { - file: this.file, - text: text - }) - } - }, - template: ``, -}) - -Vue.component("values-box", { - props: ["values", "size", "maxlength", "name", "placeholder"], - data: function () { - let values = this.values; - if (values == null) { - values = []; - } - return { - "vValues": values, - "isUpdating": false, - "isAdding": false, - "index": 0, - "value": "", - isEditing: false - } - }, - methods: { - create: function () { - this.isAdding = true; - var that = this; - setTimeout(function () { - that.$refs.value.focus(); - }, 200); - }, - update: function (index) { - this.cancel() - this.isUpdating = true; - this.index = index; - this.value = this.vValues[index]; - var that = this; - setTimeout(function () { - that.$refs.value.focus(); - }, 200); - }, - confirm: function () { - if (this.value.length == 0) { - return - } - - if (this.isUpdating) { - Vue.set(this.vValues, this.index, this.value); - } else { - this.vValues.push(this.value); - } - this.cancel() - this.$emit("change", this.vValues) - }, - remove: function (index) { - this.vValues.$remove(index) - this.$emit("change", this.vValues) - }, - cancel: function () { - this.isUpdating = false; - this.isAdding = false; - this.value = ""; - }, - updateAll: function (values) { - this.vValeus = values - }, - addValue: function (v) { - this.vValues.push(v) - }, - - startEditing: function () { - this.isEditing = !this.isEditing - } - }, - template: `
+`}),Vue.component("download-link",{props:["v-element","v-file","v-value"],created:function(){let e=this;setTimeout(function(){e.url=e.composeURL()},1e3)},data:function(){let e=this.vFile;return{file:e=null!=e&&0!=e.length?e:"unknown-file",url:this.composeURL()}},methods:{composeURL:function(){let e="";if(null!=this.vValue)e=this.vValue;else{var t=document.getElementById(this.vElement);if(null==t)return void teaweb.warn("找不到要下载的内容");null==(e=t.innerText)&&(e=t.textContent)}return Tea.url("/ui/download",{file:this.file,text:e})}},template:''}),Vue.component("values-box",{props:["values","size","maxlength","name","placeholder"],data:function(){let e=this.values;return{vValues:e=null==e?[]:e,isUpdating:!1,isAdding:!1,index:0,value:"",isEditing:!1}},methods:{create:function(){this.isAdding=!0;var e=this;setTimeout(function(){e.$refs.value.focus()},200)},update:function(e){this.cancel(),this.isUpdating=!0,this.index=e,this.value=this.vValues[e];var t=this;setTimeout(function(){t.$refs.value.focus()},200)},confirm:function(){0!=this.value.length&&(this.isUpdating?Vue.set(this.vValues,this.index,this.value):this.vValues.push(this.value),this.cancel(),this.$emit("change",this.vValues))},remove:function(e){this.vValues.$remove(e),this.$emit("change",this.vValues)},cancel:function(){this.isUpdating=!1,this.isAdding=!1,this.value=""},updateAll:function(e){this.vValeus=e},addValue:function(e){this.vValues.push(e)},startEditing:function(){this.isEditing=!this.isEditing}},template:`
{{value}}
[修改] @@ -11328,169 +3828,7 @@ Vue.component("values-box", {
-
` -}); - -Vue.component("datetime-input", { - props: ["v-name", "v-timestamp"], - mounted: function () { - let that = this - teaweb.datepicker(this.$refs.dayInput, function (v) { - that.day = v - that.hour = "23" - that.minute = "59" - that.second = "59" - that.change() - }) - }, - data: function () { - let timestamp = this.vTimestamp - if (timestamp != null) { - timestamp = parseInt(timestamp) - if (isNaN(timestamp)) { - timestamp = 0 - } - } else { - timestamp = 0 - } - - let day = "" - let hour = "" - let minute = "" - let second = "" - - if (timestamp > 0) { - let date = new Date() - date.setTime(timestamp * 1000) - - let year = date.getFullYear().toString() - let month = this.leadingZero((date.getMonth() + 1).toString(), 2) - day = year + "-" + month + "-" + this.leadingZero(date.getDate().toString(), 2) - - hour = this.leadingZero(date.getHours().toString(), 2) - minute = this.leadingZero(date.getMinutes().toString(), 2) - second = this.leadingZero(date.getSeconds().toString(), 2) - } - - return { - timestamp: timestamp, - day: day, - hour: hour, - minute: minute, - second: second, - - hasDayError: false, - hasHourError: false, - hasMinuteError: false, - hasSecondError: false - } - }, - methods: { - change: function () { - let date = new Date() - - // day - if (!/^\d{4}-\d{1,2}-\d{1,2}$/.test(this.day)) { - this.hasDayError = true - return - } - let pieces = this.day.split("-") - let year = parseInt(pieces[0]) - date.setFullYear(year) - - let month = parseInt(pieces[1]) - if (month < 1 || month > 12) { - this.hasDayError = true - return - } - date.setMonth(month - 1) - - let day = parseInt(pieces[2]) - if (day < 1 || day > 32) { - this.hasDayError = true - return - } - date.setDate(day) - - this.hasDayError = false - - // hour - if (!/^\d+$/.test(this.hour)) { - this.hasHourError = true - return - } - let hour = parseInt(this.hour) - if (isNaN(hour)) { - this.hasHourError = true - return - } - if (hour < 0 || hour >= 24) { - this.hasHourError = true - return - } - this.hasHourError = false - date.setHours(hour) - - // minute - if (!/^\d+$/.test(this.minute)) { - this.hasMinuteError = true - return - } - let minute = parseInt(this.minute) - if (isNaN(minute)) { - this.hasMinuteError = true - return - } - if (minute < 0 || minute >= 60) { - this.hasMinuteError = true - return - } - this.hasMinuteError = false - date.setMinutes(minute) - - // second - if (!/^\d+$/.test(this.second)) { - this.hasSecondError = true - return - } - let second = parseInt(this.second) - if (isNaN(second)) { - this.hasSecondError = true - return - } - if (second < 0 || second >= 60) { - this.hasSecondError = true - return - } - this.hasSecondError = false - date.setSeconds(second) - - this.timestamp = Math.floor(date.getTime() / 1000) - }, - leadingZero: function (s, l) { - s = s.toString() - if (l <= s.length) { - return s - } - for (let i = 0; i < l - s.length; i++) { - s = "0" + s - } - return s - }, - resultTimestamp: function () { - return this.timestamp - }, - nextDays: function (days) { - let date = new Date() - date.setTime(date.getTime() + days * 86400 * 1000) - this.day = date.getFullYear() + "-" + this.leadingZero(date.getMonth() + 1, 2) + "-" + this.leadingZero(date.getDate(), 2) - this.hour = this.leadingZero(date.getHours(), 2) - this.minute = this.leadingZero(date.getMinutes(), 2) - this.second = this.leadingZero(date.getSeconds(), 2) - this.change() - } - }, - template: `
+
`}),Vue.component("datetime-input",{props:["v-name","v-timestamp"],mounted:function(){let t=this;teaweb.datepicker(this.$refs.dayInput,function(e){t.day=e,t.hour="23",t.minute="59",t.second="59",t.change()})},data:function(){let t=this.vTimestamp,i=(null!=t?(t=parseInt(t),isNaN(t)&&(t=0)):t=0,""),s="",n="",o="";if(0
@@ -11503,345 +3841,11 @@ Vue.component("datetime-input", {

常用时间:  1天  |  3天  |  一周  |  30天 

-
` -}) - -// 启用状态标签 -Vue.component("label-on", { - props: ["v-is-on"], - template: '
已启用已停用
' -}) - -// 文字代码标签 -Vue.component("code-label", { - methods: { - click: function (args) { - this.$emit("click", args) - } - }, - template: `` -}) - -// tiny标签 -Vue.component("tiny-label", { - template: `` -}) - -Vue.component("tiny-basic-label", { - template: `` -}) - -// 更小的标签 -Vue.component("micro-basic-label", { - template: `` -}) - - -// 灰色的Label -Vue.component("grey-label", { - props: ["color"], - data: function () { - let color = "grey" - if (this.color != null && this.color.length > 0) { - color = "red" - } - return { - labelColor: color - } - }, - template: `` -}) - -// 可选标签 -Vue.component("optional-label", { - template: `(可选)` -}) - -// Plus专属 -Vue.component("plus-label", { - template: `Plus专属功能。` -}) - -/** - * 一级菜单 - */ -Vue.component("first-menu", { - props: [], - template: ' \ -
\ - \ -
\ -
' -}); - -/** - * 更多选项 - */ -Vue.component("more-options-indicator", { - data: function () { - return { - visible: false - } - }, - methods: { - changeVisible: function () { - this.visible = !this.visible - if (Tea.Vue != null) { - Tea.Vue.moreOptionsVisible = this.visible - } - this.$emit("change", this.visible) - } - }, - template: '更多选项收起选项 ' -}); - -Vue.component("page-size-selector", { - data: function () { - let query = window.location.search - let pageSize = 10 - if (query.length > 0) { - query = query.substr(1) - let params = query.split("&") - params.forEach(function (v) { - let pieces = v.split("=") - if (pieces.length == 2 && pieces[0] == "pageSize") { - let pageSizeString = pieces[1] - if (pageSizeString.match(/^\d+$/)) { - pageSize = parseInt(pageSizeString, 10) - if (isNaN(pageSize) || pageSize < 1) { - pageSize = 10 - } - } - } - }) - } - return { - pageSize: pageSize - } - }, - watch: { - pageSize: function () { - window.ChangePageSize(this.pageSize) - } - }, - template: `` -}) - -/** - * 二级菜单 - */ -Vue.component("second-menu", { - template: ' \ -
\ - \ -
\ -
' -}); - -Vue.component("loading-message", { - template: `
+
`}),Vue.component("label-on",{props:["v-is-on"],template:'
已启用已停用
'}),Vue.component("code-label",{methods:{click:function(e){this.$emit("click",e)}},template:''}),Vue.component("tiny-label",{template:''}),Vue.component("tiny-basic-label",{template:''}),Vue.component("micro-basic-label",{template:''}),Vue.component("grey-label",{props:["color"],data:function(){let e="grey";return{labelColor:e=null!=this.color&&0'}),Vue.component("optional-label",{template:'(可选)'}),Vue.component("plus-label",{template:'Plus专属功能。'}),Vue.component("first-menu",{props:[],template:' \t\t
\t\t\t \t\t\t
\t\t
'}),Vue.component("more-options-indicator",{data:function(){return{visible:!1}},methods:{changeVisible:function(){this.visible=!this.visible,null!=Tea.Vue&&(Tea.Vue.moreOptionsVisible=this.visible),this.$emit("change",this.visible)}},template:'更多选项收起选项 '}),Vue.component("page-size-selector",{data:function(){let t=window.location.search,i=10;if(0 + +`}),Vue.component("second-menu",{template:' \t\t
\t\t\t \t\t\t
\t\t
'}),Vue.component("loading-message",{template:`
  -
` -}) - -Vue.component("more-options-angle", { - data: function () { - return { - isVisible: false - } - }, - methods: { - show: function () { - this.isVisible = !this.isVisible - this.$emit("change", this.isVisible) - } - }, - template: `更多选项收起选项` -}) - -/** - * 菜单项 - */ -Vue.component("inner-menu-item", { - props: ["href", "active", "code"], - data: function () { - var active = this.active; - if (typeof(active) =="undefined") { - var itemCode = ""; - if (typeof (window.TEA.ACTION.data.firstMenuItem) != "undefined") { - itemCode = window.TEA.ACTION.data.firstMenuItem; - } - active = (itemCode == this.code); - } - return { - vHref: (this.href == null) ? "" : this.href, - vActive: active - }; - }, - template: '\ - [] \ - ' -}); - -Vue.component("health-check-config-box", { - props: ["v-health-check-config"], - data: function () { - let healthCheckConfig = this.vHealthCheckConfig - let urlProtocol = "http" - let urlPort = "" - let urlRequestURI = "/" - let urlHost = "" - - if (healthCheckConfig == null) { - healthCheckConfig = { - isOn: false, - url: "", - interval: {count: 60, unit: "second"}, - statusCodes: [200], - timeout: {count: 10, unit: "second"}, - countTries: 3, - tryDelay: {count: 100, unit: "ms"}, - autoDown: true, - countUp: 1, - countDown: 3, - userAgent: "", - onlyBasicRequest: false - } - let that = this - setTimeout(function () { - that.changeURL() - }, 500) - } else { - try { - let url = new URL(healthCheckConfig.url) - urlProtocol = url.protocol.substring(0, url.protocol.length - 1) - - // 域名 - urlHost = url.host - if (urlHost == "%24%7Bhost%7D") { - urlHost = "${host}" - } - let colonIndex = urlHost.indexOf(":") - if (colonIndex > 0) { - urlHost = urlHost.substring(0, colonIndex) - } - - urlPort = url.port - urlRequestURI = url.pathname - if (url.search.length > 0) { - urlRequestURI += url.search - } - } catch (e) { - } - - if (healthCheckConfig.statusCodes == null) { - healthCheckConfig.statusCodes = [200] - } - if (healthCheckConfig.interval == null) { - healthCheckConfig.interval = {count: 60, unit: "second"} - } - if (healthCheckConfig.timeout == null) { - healthCheckConfig.timeout = {count: 10, unit: "second"} - } - if (healthCheckConfig.tryDelay == null) { - healthCheckConfig.tryDelay = {count: 100, unit: "ms"} - } - if (healthCheckConfig.countUp == null || healthCheckConfig.countUp < 1) { - healthCheckConfig.countUp = 1 - } - if (healthCheckConfig.countDown == null || healthCheckConfig.countDown < 1) { - healthCheckConfig.countDown = 3 - } - } - return { - healthCheck: healthCheckConfig, - advancedVisible: false, - urlProtocol: urlProtocol, - urlHost: urlHost, - urlPort: urlPort, - urlRequestURI: urlRequestURI, - urlIsEditing: healthCheckConfig.url.length == 0 - } - }, - watch: { - urlRequestURI: function () { - if (this.urlRequestURI.length > 0 && this.urlRequestURI[0] != "/") { - this.urlRequestURI = "/" + this.urlRequestURI - } - this.changeURL() - }, - urlPort: function (v) { - let port = parseInt(v) - if (!isNaN(port)) { - this.urlPort = port.toString() - } else { - this.urlPort = "" - } - this.changeURL() - }, - urlProtocol: function () { - this.changeURL() - }, - urlHost: function () { - this.changeURL() - }, - "healthCheck.countTries": function (v) { - let count = parseInt(v) - if (!isNaN(count)) { - this.healthCheck.countTries = count - } else { - this.healthCheck.countTries = 0 - } - }, - "healthCheck.countUp": function (v) { - let count = parseInt(v) - if (!isNaN(count)) { - this.healthCheck.countUp = count - } else { - this.healthCheck.countUp = 0 - } - }, - "healthCheck.countDown": function (v) { - let count = parseInt(v) - if (!isNaN(count)) { - this.healthCheck.countDown = count - } else { - this.healthCheck.countDown = 0 - } - } - }, - methods: { - showAdvanced: function () { - this.advancedVisible = !this.advancedVisible - }, - changeURL: function () { - let urlHost = this.urlHost - if (urlHost.length == 0) { - urlHost = "${host}" - } - this.healthCheck.url = this.urlProtocol + "://" + urlHost + ((this.urlPort.length > 0) ? ":" + this.urlPort : "") + this.urlRequestURI - }, - changeStatus: function (values) { - this.healthCheck.statusCodes = values.$map(function (k, v) { - let status = parseInt(v) - if (isNaN(status)) { - return 0 - } else { - return status - } - }) - }, - editURL: function () { - this.urlIsEditing = !this.urlIsEditing - } - }, - template: `
+
`}),Vue.component("more-options-angle",{data:function(){return{isVisible:!1}},methods:{show:function(){this.isVisible=!this.isVisible,this.$emit("change",this.isVisible)}},template:'更多选项收起选项'}),Vue.component("inner-menu-item",{props:["href","active","code"],data:function(){var e,t=this.active;return void 0===t&&(e="",t=(e=void 0!==window.TEA.ACTION.data.firstMenuItem?window.TEA.ACTION.data.firstMenuItem:e)==this.code),{vHref:null==this.href?"":this.href,vActive:t}},template:'\t\t[] \t\t'}),Vue.component("health-check-config-box",{props:["v-health-check-config"],data:function(){let t=this.vHealthCheckConfig,i="http",s="",n="/",o="";if(null==t){t={isOn:!1,url:"",interval:{count:60,unit:"second"},statusCodes:[200],timeout:{count:10,unit:"second"},countTries:3,tryDelay:{count:100,unit:"ms"},autoDown:!0,countUp:1,countDown:3,userAgent:"",onlyBasicRequest:!1};let e=this;setTimeout(function(){e.changeURL()},500)}else{try{let e=new URL(t.url);i=e.protocol.substring(0,e.protocol.length-1);var a=(o="%24%7Bhost%7D"==(o=e.host)?"${host}":o).indexOf(":");0 @@ -11972,182 +3976,9 @@ Vue.component("health-check-config-box", {
-
` -}) - -// 将变量转换为中文 -Vue.component("request-variables-describer", { - data: function () { - return { - vars:[] - } - }, - methods: { - update: function (variablesString) { - this.vars = [] - let that = this - variablesString.replace(/\${.+?}/g, function (v) { - let def = that.findVar(v) - if (def == null) { - return v - } - that.vars.push(def) - }) - }, - findVar: function (name) { - let def = null - window.REQUEST_VARIABLES.forEach(function (v) { - if (v.code == name) { - def = v - } - }) - return def - } - }, - template: ` +
`}),Vue.component("request-variables-describer",{data:function(){return{vars:[]}},methods:{update:function(e){this.vars=[];let i=this;e.replace(/\${.+?}/g,function(e){var t=i.findVar(e);if(null==t)return e;i.vars.push(t)})},findVar:function(t){let i=null;return window.REQUEST_VARIABLES.forEach(function(e){e.code==t&&(i=e)}),i}},template:` {{v.code}} - {{v.name}} -` -}) - - -Vue.component("combo-box", { - props: ["name", "title", "placeholder", "size", "v-items", "v-value"], - data: function () { - let items = this.vItems - if (items == null || !(items instanceof Array)) { - items = [] - } - - // 自动使用ID作为值 - items.forEach(function (v) { - if (v.value == null) { - v.value = v.id - } - }) - - // 当前选中项 - let selectedItem = null - if (this.vValue != null) { - let that = this - items.forEach(function (v) { - if (v.value == that.vValue) { - selectedItem = v - } - }) - } - - return { - allItems: items, - items: items.$copy(), - selectedItem: selectedItem, - keyword: "", - visible: false, - hideTimer: null, - hoverIndex: 0 - } - }, - methods: { - reset: function () { - this.selectedItem = null - this.change() - this.hoverIndex = 0 - - let that = this - setTimeout(function () { - if (that.$refs.searchBox) { - that.$refs.searchBox.focus() - } - }) - }, - changeKeyword: function () { - this.hoverIndex = 0 - let keyword = this.keyword - if (keyword.length == 0) { - this.items = this.allItems.$copy() - return - } - this.items = this.allItems.$copy().filter(function (v) { - return teaweb.match(v.name, keyword) - }) - }, - selectItem: function (item) { - this.selectedItem = item - this.change() - this.hoverIndex = 0 - this.keyword = "" - this.changeKeyword() - }, - confirm: function () { - if (this.items.length > this.hoverIndex) { - this.selectItem(this.items[this.hoverIndex]) - } - }, - show: function () { - this.visible = true - - // 不要重置hoverIndex,以便焦点可以在输入框和可选项之间切换 - }, - hide: function () { - let that = this - this.hideTimer = setTimeout(function () { - that.visible = false - }, 500) - }, - downItem: function () { - this.hoverIndex++ - if (this.hoverIndex > this.items.length - 1) { - this.hoverIndex = 0 - } - this.focusItem() - }, - upItem: function () { - this.hoverIndex-- - if (this.hoverIndex < 0) { - this.hoverIndex = 0 - } - this.focusItem() - }, - focusItem: function () { - if (this.hoverIndex < this.items.length) { - this.$refs.itemRef[this.hoverIndex].focus() - let that = this - setTimeout(function () { - that.$refs.searchBox.focus() - if (that.hideTimer != null) { - clearTimeout(that.hideTimer) - that.hideTimer = null - } - }) - } - }, - change: function () { - this.$emit("change", this.selectedItem) - - let that = this - setTimeout(function () { - if (that.$refs.selectedLabel != null) { - that.$refs.selectedLabel.focus() - } - }) - }, - submitForm: function (event) { - if (event.target.tagName != "A") { - return - } - let parentBox = this.$refs.selectedLabel.parentNode - while (true) { - parentBox = parentBox.parentNode - if (parentBox == null || parentBox.tagName == "BODY") { - return - } - if (parentBox.tagName == "FORM") { - parentBox.submit() - break - } - } - } - }, - template: `
+`}),Vue.component("combo-box",{props:["name","title","placeholder","size","v-items","v-value"],data:function(){let e=this.vItems,i=((e=null!=e&&e instanceof Array?e:[]).forEach(function(e){null==e.value&&(e.value=e.id)}),null);if(null!=this.vValue){let t=this;e.forEach(function(e){e.value==t.vValue&&(i=e)})}return{allItems:e,items:e.$copy(),selectedItem:i,keyword:"",visible:!1,hideTimer:null,hoverIndex:0}},methods:{reset:function(){this.selectedItem=null,this.change(),this.hoverIndex=0;let e=this;setTimeout(function(){e.$refs.searchBox&&e.$refs.searchBox.focus()})},changeKeyword:function(){this.hoverIndex=0;let t=this.keyword;0==t.length?this.items=this.allItems.$copy():this.items=this.allItems.$copy().filter(function(e){return teaweb.match(e.name,t)})},selectItem:function(e){this.selectedItem=e,this.change(),this.hoverIndex=0,this.keyword="",this.changeKeyword()},confirm:function(){this.items.length>this.hoverIndex&&this.selectItem(this.items[this.hoverIndex])},show:function(){this.visible=!0},hide:function(){let e=this;this.hideTimer=setTimeout(function(){e.visible=!1},500)},downItem:function(){this.hoverIndex++,this.hoverIndex>this.items.length-1&&(this.hoverIndex=0),this.focusItem()},upItem:function(){this.hoverIndex--,this.hoverIndex<0&&(this.hoverIndex=0),this.focusItem()},focusItem:function(){if(this.hoverIndex
@@ -12167,50 +3998,7 @@ Vue.component("combo-box", { {{item.name}}
-
` -}) - -Vue.component("time-duration-box", { - props: ["v-name", "v-value", "v-count", "v-unit"], - mounted: function () { - this.change() - }, - data: function () { - let v = this.vValue - if (v == null) { - v = { - count: this.vCount, - unit: this.vUnit - } - } - if (typeof (v["count"]) != "number") { - v["count"] = -1 - } - return { - duration: v, - countString: (v.count >= 0) ? v.count.toString() : "" - } - }, - watch: { - "countString": function (newValue) { - let value = newValue.trim() - if (value.length == 0) { - this.duration.count = -1 - return - } - let count = parseInt(value) - if (!isNaN(count)) { - this.duration.count = count - } - this.change() - } - }, - methods: { - change: function () { - this.$emit("change", this.duration) - } - }, - template: `
+
`}),Vue.component("time-duration-box",{props:["v-name","v-value","v-count","v-unit"],mounted:function(){this.change()},data:function(){let e=this.vValue;return"number"!=typeof(e=null==e?{count:this.vCount,unit:this.vUnit}:e).count&&(e.count=-1),{duration:e,countString:0<=e.count?e.count.toString():""}},watch:{countString:function(e){var e=e.trim();0==e.length?this.duration.count=-1:(e=parseInt(e),isNaN(e)||(this.duration.count=e),this.change())}},methods:{change:function(){this.$emit("change",this.duration)}},template:`
@@ -12225,243 +4013,28 @@ Vue.component("time-duration-box", {
-
` -}) - -Vue.component("not-found-box", { - props: ["message"], - template: `
+
`}),Vue.component("not-found-box",{props:["message"],template:`

{{message}}

-
` -}) - -// 警告消息 -Vue.component("warning-message", { - template: `
` -}) - -let checkboxId = 0 -Vue.component("checkbox", { - props: ["name", "value", "v-value", "id", "checked"], - data: function () { - checkboxId++ - let elementId = this.id - if (elementId == null) { - elementId = "checkbox" + checkboxId - } - - let elementValue = this.vValue - if (elementValue == null) { - elementValue = "1" - } - - let checkedValue = this.value - if (checkedValue == null && this.checked == "checked") { - checkedValue = elementValue - } - - return { - elementId: elementId, - elementValue: elementValue, - newValue: checkedValue - } - }, - methods: { - change: function () { - this.$emit("input", this.newValue) - }, - check: function () { - this.newValue = this.elementValue - }, - uncheck: function () { - this.newValue = "" - }, - isChecked: function () { - return this.newValue == this.elementValue - } - }, - watch: { - value: function (v) { - if (typeof v == "boolean") { - this.newValue = v - } - } - }, - template: `
+
`}),Vue.component("warning-message",{template:'
'});let checkboxId=0,radioId=(Vue.component("checkbox",{props:["name","value","v-value","id","checked"],data:function(){checkboxId++;let e=this.id,t=(null==e&&(e="checkbox"+checkboxId),this.vValue),i=(null==t&&(t="1"),this.value);return null==i&&"checked"==this.checked&&(i=t),{elementId:e,elementValue:t,newValue:i}},methods:{change:function(){this.$emit("input",this.newValue)},check:function(){this.newValue=this.elementValue},uncheck:function(){this.newValue=""},isChecked:function(){return this.newValue==this.elementValue}},watch:{value:function(e){"boolean"==typeof e&&(this.newValue=e)}},template:`
-
` -}) - -Vue.component("network-addresses-view", { - props: ["v-addresses"], - template: `
+
`}),Vue.component("network-addresses-view",{props:["v-addresses"],template:`
{{addr.protocol}}://{{addr.host.quoteIP()}}*:{{addr.portRange}}
-
` -}) - -Vue.component("size-capacity-view", { - props:["v-default-text", "v-value"], - template: `
+
`}),Vue.component("size-capacity-view",{props:["v-default-text","v-value"],template:`
{{vValue.count}}{{vValue.unit.toUpperCase()}} {{vDefaultText}} -
` -}) - -// 信息提示窗口 -Vue.component("tip-message-box", { - props: ["code"], - mounted: function () { - let that = this - Tea.action("/ui/showTip") - .params({ - code: this.code - }) - .success(function (resp) { - that.visible = resp.data.visible - }) - .post() - }, - data: function () { - return { - visible: false - } - }, - methods: { - close: function () { - this.visible = false - Tea.action("/ui/hideTip") - .params({ - code: this.code - }) - .post() - } - }, - template: `
+
`}),Vue.component("tip-message-box",{props:["code"],mounted:function(){let t=this;Tea.action("/ui/showTip").params({code:this.code}).success(function(e){t.visible=e.data.visible}).post()},data:function(){return{visible:!1}},methods:{close:function(){this.visible=!1,Tea.action("/ui/hideTip").params({code:this.code}).post()}},template:`
-
` -}) - -Vue.component("keyword", { - props: ["v-word"], - data: function () { - let word = this.vWord - if (word == null) { - word = "" - } else { - word = word.replace(/\)/g, "\\)") - word = word.replace(/\(/g, "\\(") - word = word.replace(/\+/g, "\\+") - word = word.replace(/\^/g, "\\^") - word = word.replace(/\$/g, "\\$") - word = word.replace(/\?/, "\\?") - word = word.replace(/\*/, "\\*") - word = word.replace(/\[/, "\\[") - word = word.replace(/{/, "\\{") - word = word.replace(/\./, "\\.") - } - - let slot = this.$slots["default"][0] - let text = slot.text - if (word.length > 0) { - let that = this - let m = [] // replacement => tmp - let tmpIndex = 0 - text = text.replaceAll(new RegExp("(" + word + ")", "ig"), function (replacement) { - tmpIndex++ - let s = "" + that.encodeHTML(replacement) + "" - let tmpKey = "$TMP__KEY__" + tmpIndex.toString() + "$" - m.push([tmpKey, s]) - return tmpKey - }) - text = this.encodeHTML(text) - - m.forEach(function (r) { - text = text.replace(r[0], r[1]) - }) - - } else { - text = this.encodeHTML(text) - } - - return { - word: word, - text: text - } - }, - methods: { - encodeHTML: function (s) { - s = s.replace(/&/g, "&") - s = s.replace(//g, ">") - s = s.replace(/"/g, """) - return s - } - }, - template: `` -}) - -Vue.component("node-log-row", { - props: ["v-log", "v-keyword"], - data: function () { - return { - log: this.vLog, - keyword: this.vKeyword - } - }, - template: `
+
`}),Vue.component("keyword",{props:["v-word"],data:function(){let e=this.vWord;e=null==e?"":(e=(e=(e=(e=(e=(e=(e=(e=(e=e.replace(/\)/g,"\\)")).replace(/\(/g,"\\(")).replace(/\+/g,"\\+")).replace(/\^/g,"\\^")).replace(/\$/g,"\\$")).replace(/\?/,"\\?")).replace(/\*/,"\\*")).replace(/\[/,"\\[")).replace(/{/,"\\{")).replace(/\./,"\\.");let t=this.$slots.default[0].text;if(0",t="$TMP__KEY__"+n.toString()+"$";return s.push([t,e]),t}),t=this.encodeHTML(t),s.forEach(function(e){t=t.replace(e[0],e[1])})}else t=this.encodeHTML(t);return{word:e,text:t}},methods:{encodeHTML:function(e){return e=(e=(e=(e=e.replace(/&/g,"&")).replace(//g,">")).replace(/"/g,""")}},template:''}),Vue.component("node-log-row",{props:["v-log","v-keyword"],data:function(){return{log:this.vLog,keyword:this.vKeyword}},template:`
[{{log.createdTime}}][{{log.createdTime}}][{{log.tag}}]{{log.description}}   共{{log.count}}条 {{log.server.name}}
-
` -}) - -Vue.component("provinces-selector", { - props: ["v-provinces"], - data: function () { - let provinces = this.vProvinces - if (provinces == null) { - provinces = [] - } - let provinceIds = provinces.$map(function (k, v) { - return v.id - }) - return { - provinces: provinces, - provinceIds: provinceIds - } - }, - methods: { - add: function () { - let provinceStringIds = this.provinceIds.map(function (v) { - return v.toString() - }) - let that = this - teaweb.popup("/ui/selectProvincesPopup?provinceIds=" + provinceStringIds.join(","), { - width: "48em", - height: "23em", - callback: function (resp) { - that.provinces = resp.data.provinces - that.change() - } - }) - }, - remove: function (index) { - this.provinces.$remove(index) - this.change() - }, - change: function () { - this.provinceIds = this.provinces.$map(function (k, v) { - return v.id - }) - } - }, - template: `
+
`}),Vue.component("provinces-selector",{props:["v-provinces"],data:function(){let e=this.vProvinces;var t=(e=null==e?[]:e).$map(function(e,t){return t.id});return{provinces:e,provinceIds:t}},methods:{add:function(){let e=this.provinceIds.map(function(e){return e.toString()}),t=this;teaweb.popup("/ui/selectProvincesPopup?provinceIds="+e.join(","),{width:"48em",height:"23em",callback:function(e){t.provinces=e.data.provinces,t.change()}})},remove:function(e){this.provinces.$remove(e),this.change()},change:function(){this.provinceIds=this.provinces.$map(function(e,t){return t.id})}},template:`
{{province.name}}
@@ -12470,283 +4043,13 @@ Vue.component("provinces-selector", {
-
` -}) - -Vue.component("csrf-token", { - created: function () { - this.refreshToken() - }, - mounted: function () { - let that = this - this.$refs.token.form.addEventListener("submit", function () { - that.refreshToken() - }) - - // 自动刷新 - setInterval(function () { - that.refreshToken() - }, 10 * 60 * 1000) - }, - data: function () { - return { - token: "" - } - }, - methods: { - refreshToken: function () { - let that = this - Tea.action("/csrf/token") - .get() - .success(function (resp) { - that.token = resp.data.token - }) - } - }, - template: `` -}) - - -Vue.component("labeled-input", { - props: ["name", "size", "maxlength", "label", "value"], - template: '
\ - \ - {{label}}\ -
' -}); - -let radioId = 0 -Vue.component("radio", { - props: ["name", "value", "v-value", "id"], - data: function () { - radioId++ - let elementId = this.id - if (elementId == null) { - elementId = "radio" + radioId - } - return { - "elementId": elementId - } - }, - methods: { - change: function () { - this.$emit("input", this.vValue) - } - }, - template: `
+
`}),Vue.component("csrf-token",{created:function(){this.refreshToken()},mounted:function(){let e=this;this.$refs.token.form.addEventListener("submit",function(){e.refreshToken()}),setInterval(function(){e.refreshToken()},6e5)},data:function(){return{token:""}},methods:{refreshToken:function(){let t=this;Tea.action("/csrf/token").get().success(function(e){t.token=e.data.token})}},template:''}),Vue.component("labeled-input",{props:["name","size","maxlength","label","value"],template:'
\t\t{{label}}
'}),0),sourceCodeBoxIndex=(Vue.component("radio",{props:["name","value","v-value","id"],data:function(){radioId++;let e=this.id;return{elementId:e=null==e?"radio"+radioId:e}},methods:{change:function(){this.$emit("input",this.vValue)}},template:`
-
` -}) - -Vue.component("copy-to-clipboard", { - props: ["v-target"], - created: function () { - if (typeof ClipboardJS == "undefined") { - let jsFile = document.createElement("script") - jsFile.setAttribute("src", "/js/clipboard.min.js") - document.head.appendChild(jsFile) - } - }, - methods: { - copy: function () { - new ClipboardJS('[data-clipboard-target]'); - teaweb.successToast("已复制到剪切板") - } - }, - template: `` -}) - -// 节点角色名称 -Vue.component("node-role-name", { - props: ["v-role"], - data: function () { - let roleName = "" - switch (this.vRole) { - case "node": - roleName = "边缘节点" - break - case "monitor": - roleName = "监控节点" - break - case "api": - roleName = "API节点" - break - case "user": - roleName = "用户平台" - break - case "admin": - roleName = "管理平台" - break - case "database": - roleName = "数据库节点" - break - case "dns": - roleName = "DNS节点" - break - case "report": - roleName = "区域监控终端" - break - } - return { - roleName: roleName - } - }, - template: `{{roleName}}` -}) - -let sourceCodeBoxIndex = 0 - -Vue.component("source-code-box", { - props: ["name", "type", "id", "read-only", "width", "height", "focus"], - mounted: function () { - let readOnly = this.readOnly - if (typeof readOnly != "boolean") { - readOnly = true - } - let box = document.getElementById("source-code-box-" + this.index) - let valueBox = document.getElementById(this.valueBoxId) - let value = "" - if (valueBox.textContent != null) { - value = valueBox.textContent - } else if (valueBox.innerText != null) { - value = valueBox.innerText - } - - this.createEditor(box, value, readOnly) - }, - data: function () { - let index = sourceCodeBoxIndex++ - - let valueBoxId = 'source-code-box-value-' + sourceCodeBoxIndex - if (this.id != null) { - valueBoxId = this.id - } - - return { - index: index, - valueBoxId: valueBoxId - } - }, - methods: { - createEditor: function (box, value, readOnly) { - let boxEditor = CodeMirror.fromTextArea(box, { - theme: "idea", - lineNumbers: true, - value: "", - readOnly: readOnly, - showCursorWhenSelecting: true, - height: "auto", - //scrollbarStyle: null, - viewportMargin: Infinity, - lineWrapping: true, - highlightFormatting: false, - indentUnit: 4, - indentWithTabs: true, - }) - let that = this - boxEditor.on("change", function () { - that.change(boxEditor.getValue()) - }) - boxEditor.setValue(value) - - if (this.focus) { - boxEditor.focus() - } - - let width = this.width - let height = this.height - if (width != null && height != null) { - width = parseInt(width) - height = parseInt(height) - if (!isNaN(width) && !isNaN(height)) { - if (width <= 0) { - width = box.parentNode.offsetWidth - } - boxEditor.setSize(width, height) - } - } else if (height != null) { - height = parseInt(height) - if (!isNaN(height)) { - boxEditor.setSize("100%", height) - } - } - - let info = CodeMirror.findModeByMIME(this.type) - if (info != null) { - boxEditor.setOption("mode", info.mode) - CodeMirror.modeURL = "/codemirror/mode/%N/%N.js" - CodeMirror.autoLoadMode(boxEditor, info.mode) - } - }, - change: function (code) { - this.$emit("change", code) - } - }, - template: `
+
`}),Vue.component("copy-to-clipboard",{props:["v-target"],created:function(){if("undefined"==typeof ClipboardJS){let e=document.createElement("script");e.setAttribute("src","/js/clipboard.min.js"),document.head.appendChild(e)}},methods:{copy:function(){new ClipboardJS("[data-clipboard-target]"),teaweb.successToast("已复制到剪切板")}},template:``}),Vue.component("node-role-name",{props:["v-role"],data:function(){let e="";switch(this.vRole){case"node":e="边缘节点";break;case"monitor":e="监控节点";break;case"api":e="API节点";break;case"user":e="用户平台";break;case"admin":e="管理平台";break;case"database":e="数据库节点";break;case"dns":e="DNS节点";break;case"report":e="区域监控终端"}return{roleName:e}},template:"{{roleName}}"}),0);Vue.component("source-code-box",{props:["name","type","id","read-only","width","height","focus"],mounted:function(){let e=this.readOnly;"boolean"!=typeof e&&(e=!0);var t=document.getElementById("source-code-box-"+this.index),i=document.getElementById(this.valueBoxId);let s="";null!=i.textContent?s=i.textContent:null!=i.innerText&&(s=i.innerText),this.createEditor(t,s,e)},data:function(){var e=sourceCodeBoxIndex++;let t="source-code-box-value-"+sourceCodeBoxIndex;return{index:e,valueBoxId:t=null!=this.id?this.id:t}},methods:{createEditor:function(e,t,i){let s=CodeMirror.fromTextArea(e,{theme:"idea",lineNumbers:!0,value:"",readOnly:i,showCursorWhenSelecting:!0,height:"auto",viewportMargin:1/0,lineWrapping:!0,highlightFormatting:!1,indentUnit:4,indentWithTabs:!0}),n=this,o=(s.on("change",function(){n.change(s.getValue())}),s.setValue(t),this.focus&&s.focus(),this.width),a=this.height;null!=o&&null!=a?(o=parseInt(o),a=parseInt(a),isNaN(o)||isNaN(a)||(o<=0&&(o=e.parentNode.offsetWidth),s.setSize(o,a))):null!=a&&(a=parseInt(a),isNaN(a)||s.setSize("100%",a));i=CodeMirror.findModeByMIME(this.type);null!=i&&(s.setOption("mode",i.mode),CodeMirror.modeURL="/codemirror/mode/%N/%N.js",CodeMirror.autoLoadMode(s,i.mode))},change:function(e){this.$emit("change",e)}},template:`
-
` -}) - -Vue.component("size-capacity-box", { - props: ["v-name", "v-value", "v-count", "v-unit", "size", "maxlength", "v-supported-units"], - data: function () { - let v = this.vValue - if (v == null) { - v = { - count: this.vCount, - unit: this.vUnit - } - } - if (typeof (v["count"]) != "number") { - v["count"] = -1 - } - - let vSize = this.size - if (vSize == null) { - vSize = 6 - } - - let vMaxlength = this.maxlength - if (vMaxlength == null) { - vMaxlength = 10 - } - - let supportedUnits = this.vSupportedUnits - if (supportedUnits == null) { - supportedUnits = [] - } - - return { - capacity: v, - countString: (v.count >= 0) ? v.count.toString() : "", - vSize: vSize, - vMaxlength: vMaxlength, - supportedUnits: supportedUnits - } - }, - watch: { - "countString": function (newValue) { - let value = newValue.trim() - if (value.length == 0) { - this.capacity.count = -1 - this.change() - return - } - let count = parseInt(value) - if (!isNaN(count)) { - this.capacity.count = count - } - this.change() - } - }, - methods: { - change: function () { - this.$emit("change", this.capacity) - } - }, - template: `
+
`}),Vue.component("size-capacity-box",{props:["v-name","v-value","v-count","v-unit","size","maxlength","v-supported-units"],data:function(){let e=this.vValue,t=("number"!=typeof(e=null==e?{count:this.vCount,unit:this.vUnit}:e).count&&(e.count=-1),this.size),i=(null==t&&(t=6),this.maxlength),s=(null==i&&(i=10),this.vSupportedUnits);return null==s&&(s=[]),{capacity:e,countString:0<=e.count?e.count.toString():"",vSize:t,vMaxlength:i,supportedUnits:s}},watch:{countString:function(e){e=e.trim();if(0==e.length)return this.capacity.count=-1,void this.change();e=parseInt(e);isNaN(e)||(this.capacity.count=e),this.change()}},methods:{change:function(){this.$emit("change",this.capacity)}},template:`
@@ -12762,193 +4065,17 @@ Vue.component("size-capacity-box", {
-
` -}) - -/** - * 二级菜单 - */ -Vue.component("inner-menu", { - template: ` +
`}),Vue.component("inner-menu",{template:`
-
` -}); - -Vue.component("datepicker", { - props: ["v-name", "v-value", "v-bottom-left"], - mounted: function () { - let that = this - teaweb.datepicker(this.$refs.dayInput, function (v) { - that.day = v - that.change() - }, !!this.vBottomLeft) - }, - data: function () { - let name = this.vName - if (name == null) { - name = "day" - } - - let day = this.vValue - if (day == null) { - day = "" - } - - return { - name: name, - day: day - } - }, - methods: { - change: function () { - this.$emit("change", this.day) - } - }, - template: `
+
`}),Vue.component("datepicker",{props:["v-name","v-value","v-bottom-left"],mounted:function(){let t=this;teaweb.datepicker(this.$refs.dayInput,function(e){t.day=e,t.change()},!!this.vBottomLeft)},data:function(){let e=this.vName,t=(null==e&&(e="day"),this.vValue);return null==t&&(t=""),{name:e,day:t}},methods:{change:function(){this.$emit("change",this.day)}},template:`
-
` -}) - -// 排序使用的箭头 -Vue.component("sort-arrow", { - props: ["name"], - data: function () { - let url = window.location.toString() - let order = "" - let newArgs = [] - if (window.location.search != null && window.location.search.length > 0) { - let queryString = window.location.search.substring(1) - let pieces = queryString.split("&") - let that = this - pieces.forEach(function (v) { - let eqIndex = v.indexOf("=") - if (eqIndex > 0) { - let argName = v.substring(0, eqIndex) - let argValue = v.substring(eqIndex + 1) - if (argName == that.name) { - order = argValue - } else if (argValue != "asc" && argValue != "desc") { - newArgs.push(v) - } - } else { - newArgs.push(v) - } - }) - } - if (order == "asc") { - newArgs.push(this.name + "=desc") - } else if (order == "desc") { - newArgs.push(this.name + "=asc") - } else { - newArgs.push(this.name + "=desc") - } - - let qIndex = url.indexOf("?") - if (qIndex > 0) { - url = url.substring(0, qIndex) + "?" + newArgs.join("&") - } else { - url = url + "?" + newArgs.join("&") - } - - return { - order: order, - url: url - } - }, - template: `  ` -}) - -Vue.component("user-link", { - props: ["v-user", "v-keyword"], - data: function () { - let user = this.vUser - if (user == null) { - user = {id: 0, "username": "", "fullname": ""} - } - return { - user: user - } - }, - template: `
+
`}),Vue.component("sort-arrow",{props:["name"],data:function(){let e=window.location.toString(),n="",o=[];if(null!=window.location.search&&0  `}),Vue.component("user-link",{props:["v-user","v-keyword"],data:function(){let e=this.vUser;return{user:e=null==e?{id:0,username:"",fullname:""}:e}},template:`
{{user.fullname}}{{user.username}} [已删除] -
` -}) - -// 监控节点分组选择 -Vue.component("report-node-groups-selector", { - props: ["v-group-ids"], - mounted: function () { - let that = this - Tea.action("/clusters/monitors/groups/options") - .post() - .success(function (resp) { - that.groups = resp.data.groups.map(function (group) { - group.isChecked = that.groupIds.$contains(group.id) - return group - }) - that.isLoaded = true - }) - }, - data: function () { - var groupIds = this.vGroupIds - if (groupIds == null) { - groupIds = [] - } - - return { - groups: [], - groupIds: groupIds, - isLoaded: false, - allGroups: groupIds.length == 0 - } - }, - methods: { - check: function (group) { - group.isChecked = !group.isChecked - this.groupIds = [] - let that = this - this.groups.forEach(function (v) { - if (v.isChecked) { - that.groupIds.push(v.id) - } - }) - this.change() - }, - change: function () { - let that = this - let groups = [] - this.groupIds.forEach(function (groupId) { - let group = that.groups.$find(function (k, v) { - return v.id == groupId - }) - if (group == null) { - return - } - groups.push({ - id: group.id, - name: group.name - }) - }) - this.$emit("change", groups) - } - }, - watch: { - allGroups: function (b) { - if (b) { - this.groupIds = [] - this.groups.forEach(function (v) { - v.isChecked = false - }) - } - - this.change() - } - }, - template: `
+
`}),Vue.component("report-node-groups-selector",{props:["v-group-ids"],mounted:function(){let t=this;Tea.action("/clusters/monitors/groups/options").post().success(function(e){t.groups=e.data.groups.map(function(e){return e.isChecked=t.groupIds.$contains(e.id),e}),t.isLoaded=!0})},data:function(){var e=this.vGroupIds;return{groups:[],groupIds:e=null==e?[]:e,isLoaded:!1,allGroups:0==e.length}},methods:{check:function(e){e.isChecked=!e.isChecked,this.groupIds=[];let t=this;this.groups.forEach(function(e){e.isChecked&&t.groupIds.push(e.id)}),this.change()},change:function(){let t=this,s=[];this.groupIds.forEach(function(i){var e=t.groups.$find(function(e,t){return t.id==i});null!=e&&s.push({id:e.id,name:e.name})}),this.$emit("change",s)}},watch:{allGroups:function(e){e&&(this.groupIds=[],this.groups.forEach(function(e){e.isChecked=!1})),this.change()}},template:`
还没有分组。
@@ -12968,91 +4095,12 @@ Vue.component("report-node-groups-selector", {
-
` -}) - -Vue.component("finance-user-selector", { - mounted: function () { - let that = this - - Tea.action("/finance/users/options") - .post() - .success(function (resp) { - that.users = resp.data.users - }) - }, - props: ["v-user-id"], - data: function () { - let userId = this.vUserId - if (userId == null) { - userId = 0 - } - return { - users: [], - userId: userId - } - }, - watch: { - userId: function (v) { - this.$emit("change", v) - } - }, - template: `
+
`}),Vue.component("finance-user-selector",{mounted:function(){let t=this;Tea.action("/finance/users/options").post().success(function(e){t.users=e.data.users})},props:["v-user-id"],data:function(){let e=this.vUserId;return{users:[],userId:e=null==e?0:e}},watch:{userId:function(e){this.$emit("change",e)}},template:`
-
` -}) - -// 节点登录推荐端口 -Vue.component("node-login-suggest-ports", { - data: function () { - return { - ports: [], - availablePorts: [], - autoSelected: false, - isLoading: false - } - }, - methods: { - reload: function (host) { - let that = this - this.autoSelected = false - this.isLoading = true - Tea.action("/clusters/cluster/suggestLoginPorts") - .params({ - host: host - }) - .success(function (resp) { - if (resp.data.availablePorts != null) { - that.availablePorts = resp.data.availablePorts - if (that.availablePorts.length > 0) { - that.autoSelectPort(that.availablePorts[0]) - that.autoSelected = true - } - } - if (resp.data.ports != null) { - that.ports = resp.data.ports - if (that.ports.length > 0 && !that.autoSelected) { - that.autoSelectPort(that.ports[0]) - that.autoSelected = true - } - } - }) - .done(function () { - that.isLoading = false - }) - .post() - }, - selectPort: function (port) { - this.$emit("select", port) - }, - autoSelectPort: function (port) { - this.$emit("auto-select", port) - } - }, - template: ` +
`}),Vue.component("node-login-suggest-ports",{data:function(){return{ports:[],availablePorts:[],autoSelected:!1,isLoading:!1}},methods:{reload:function(e){let t=this;this.autoSelected=!1,this.isLoading=!0,Tea.action("/clusters/cluster/suggestLoginPorts").params({host:e}).success(function(e){null!=e.data.availablePorts&&(t.availablePorts=e.data.availablePorts,0 正在检查端口... 可能端口:{{port}} @@ -13063,38 +4111,7 @@ Vue.component("node-login-suggest-ports", { 常用端口有22等。 (可以点击要使用的端口) -` -}) - -Vue.component("node-group-selector", { - props: ["v-cluster-id", "v-group"], - data: function () { - return { - selectedGroup: this.vGroup - } - }, - methods: { - selectGroup: function () { - let that = this - teaweb.popup("/clusters/cluster/groups/selectPopup?clusterId=" + this.vClusterId, { - callback: function (resp) { - that.selectedGroup = resp.data.group - } - }) - }, - addGroup: function () { - let that = this - teaweb.popup("/clusters/cluster/groups/createPopup?clusterId=" + this.vClusterId, { - callback: function (resp) { - that.selectedGroup = resp.data.group - } - }) - }, - removeGroup: function () { - this.selectedGroup = null - } - }, - template: `
+`}),Vue.component("node-group-selector",{props:["v-cluster-id","v-group"],data:function(){return{selectedGroup:this.vGroup}},methods:{selectGroup:function(){let t=this;teaweb.popup("/clusters/cluster/groups/selectPopup?clusterId="+this.vClusterId,{callback:function(e){t.selectedGroup=e.data.group}})},addGroup:function(){let t=this;teaweb.popup("/clusters/cluster/groups/createPopup?clusterId="+this.vClusterId,{callback:function(e){t.selectedGroup=e.data.group}})},removeGroup:function(){this.selectedGroup=null}},template:`
{{selectedGroup.name}}   @@ -13102,58 +4119,7 @@ Vue.component("node-group-selector", { -
` -}) - -// 节点IP地址管理(标签形式) -Vue.component("node-ip-addresses-box", { - props: ["v-ip-addresses", "role"], - data: function () { - return { - ipAddresses: (this.vIpAddresses == null) ? [] : this.vIpAddresses, - supportThresholds: this.role != "ns" - } - }, - methods: { - // 添加IP地址 - addIPAddress: function () { - window.UPDATING_NODE_IP_ADDRESS = null - - let that = this; - teaweb.popup("/nodes/ipAddresses/createPopup?supportThresholds=" + (this.supportThresholds ? 1 : 0), { - callback: function (resp) { - that.ipAddresses.push(resp.data.ipAddress); - }, - height: "24em", - width: "44em" - }) - }, - - // 修改地址 - updateIPAddress: function (index, address) { - window.UPDATING_NODE_IP_ADDRESS = address - - let that = this; - teaweb.popup("/nodes/ipAddresses/updatePopup?supportThresholds=" + (this.supportThresholds ? 1 : 0), { - callback: function (resp) { - Vue.set(that.ipAddresses, index, resp.data.ipAddress); - }, - height: "24em", - width: "44em" - }) - }, - - // 删除IP地址 - removeIPAddress: function (index) { - this.ipAddresses.$remove(index); - }, - - // 判断是否为IPv6 - isIPv6: function (ip) { - return ip.indexOf(":") > -1 - } - }, - template: `
+
`}),Vue.component("node-ip-addresses-box",{props:["v-ip-addresses","role"],data:function(){return{ipAddresses:null==this.vIpAddresses?[]:this.vIpAddresses,supportThresholds:"ns"!=this.role}},methods:{addIPAddress:function(){window.UPDATING_NODE_IP_ADDRESS=null;let t=this;teaweb.popup("/nodes/ipAddresses/createPopup?supportThresholds="+(this.supportThresholds?1:0),{callback:function(e){t.ipAddresses.push(e.data.ipAddress)},height:"24em",width:"44em"})},updateIPAddress:function(t,e){window.UPDATING_NODE_IP_ADDRESS=e;let i=this;teaweb.popup("/nodes/ipAddresses/updatePopup?supportThresholds="+(this.supportThresholds?1:0),{callback:function(e){Vue.set(i.ipAddresses,t,e.data.ipAddress)},height:"24em",width:"44em"})},removeIPAddress:function(e){this.ipAddresses.$remove(e)},isIPv6:function(e){return-1
@@ -13174,107 +4140,7 @@ Vue.component("node-ip-addresses-box", {
-
` -}) - -// 节点IP阈值 -Vue.component("node-ip-address-thresholds-view", { - props: ["v-thresholds"], - data: function () { - let thresholds = this.vThresholds - if (thresholds == null) { - thresholds = [] - } else { - thresholds.forEach(function (v) { - if (v.items == null) { - v.items = [] - } - if (v.actions == null) { - v.actions = [] - } - }) - } - - return { - thresholds: thresholds, - allItems: window.IP_ADDR_THRESHOLD_ITEMS, - allOperators: [ - { - "name": "小于等于", - "code": "lte" - }, - { - "name": "大于", - "code": "gt" - }, - { - "name": "不等于", - "code": "neq" - }, - { - "name": "小于", - "code": "lt" - }, - { - "name": "大于等于", - "code": "gte" - } - ], - allActions: window.IP_ADDR_THRESHOLD_ACTIONS - } - }, - methods: { - itemName: function (item) { - let result = "" - this.allItems.forEach(function (v) { - if (v.code == item) { - result = v.name - } - }) - return result - }, - itemUnitName: function (itemCode) { - let result = "" - this.allItems.forEach(function (v) { - if (v.code == itemCode) { - result = v.unit - } - }) - return result - }, - itemDurationUnitName: function (unit) { - switch (unit) { - case "minute": - return "分钟" - case "second": - return "秒" - case "hour": - return "小时" - case "day": - return "天" - } - return unit - }, - itemOperatorName: function (operator) { - let result = "" - this.allOperators.forEach(function (v) { - if (v.code == operator) { - result = v.name - } - }) - return result - }, - actionName: function (actionCode) { - let result = "" - this.allActions.forEach(function (v) { - if (v.code == actionCode) { - result = v.name - } - }) - return result - } - }, - template: `
+
`}),Vue.component("node-ip-address-thresholds-view",{props:["v-thresholds"],data:function(){let e=this.vThresholds;return null==e?e=[]:e.forEach(function(e){null==e.items&&(e.items=[]),null==e.actions&&(e.actions=[])}),{thresholds:e,allItems:window.IP_ADDR_THRESHOLD_ITEMS,allOperators:[{name:"小于等于",code:"lte"},{name:"大于",code:"gt"},{name:"不等于",code:"neq"},{name:"小于",code:"lt"},{name:"大于等于",code:"gte"}],allActions:window.IP_ADDR_THRESHOLD_ACTIONS}},methods:{itemName:function(t){let i="";return this.allItems.forEach(function(e){e.code==t&&(i=e.name)}),i},itemUnitName:function(t){let i="";return this.allItems.forEach(function(e){e.code==t&&(i=e.unit)}),i},itemDurationUnitName:function(e){switch(e){case"minute":return"分钟";case"second":return"秒";case"hour":return"小时";case"day":return"天"}return e},itemOperatorName:function(t){let i="";return this.allOperators.forEach(function(e){e.code==t&&(i=e.name)}),i},actionName:function(t){let i="";return this.allActions.forEach(function(e){e.code==t&&(i=e.name)}),i}},template:`
@@ -13307,370 +4173,7 @@ Vue.component("node-ip-address-thresholds-view", {
-
` -}) - -// 节点IP阈值 -Vue.component("node-ip-address-thresholds-box", { - props: ["v-thresholds"], - data: function () { - let thresholds = this.vThresholds - if (thresholds == null) { - thresholds = [] - } else { - thresholds.forEach(function (v) { - if (v.items == null) { - v.items = [] - } - if (v.actions == null) { - v.actions = [] - } - }) - } - - return { - editingIndex: -1, - thresholds: thresholds, - addingThreshold: { - items: [], - actions: [] - }, - isAdding: false, - isAddingItem: false, - isAddingAction: false, - - itemCode: "nodeAvgRequests", - itemReportGroups: [], - itemOperator: "lte", - itemValue: "", - itemDuration: "5", - allItems: window.IP_ADDR_THRESHOLD_ITEMS, - allOperators: [ - { - "name": "小于等于", - "code": "lte" - }, - { - "name": "大于", - "code": "gt" - }, - { - "name": "不等于", - "code": "neq" - }, - { - "name": "小于", - "code": "lt" - }, - { - "name": "大于等于", - "code": "gte" - } - ], - allActions: window.IP_ADDR_THRESHOLD_ACTIONS, - - actionCode: "up", - actionBackupIPs: "", - actionWebHookURL: "" - } - }, - methods: { - add: function () { - this.isAdding = !this.isAdding - }, - cancel: function () { - this.isAdding = false - this.editingIndex = -1 - this.addingThreshold = { - items: [], - actions: [] - } - }, - confirm: function () { - if (this.addingThreshold.items.length == 0) { - teaweb.warn("需要至少添加一个阈值") - return - } - if (this.addingThreshold.actions.length == 0) { - teaweb.warn("需要至少添加一个动作") - return - } - - if (this.editingIndex >= 0) { - this.thresholds[this.editingIndex].items = this.addingThreshold.items - this.thresholds[this.editingIndex].actions = this.addingThreshold.actions - } else { - this.thresholds.push({ - items: this.addingThreshold.items, - actions: this.addingThreshold.actions - }) - } - - // 还原 - this.cancel() - }, - remove: function (index) { - this.cancel() - this.thresholds.$remove(index) - }, - update: function (index) { - this.editingIndex = index - this.addingThreshold = { - items: this.thresholds[index].items.$copy(), - actions: this.thresholds[index].actions.$copy() - } - this.isAdding = true - }, - - addItem: function () { - this.isAddingItem = !this.isAddingItem - let that = this - setTimeout(function () { - that.$refs.itemValue.focus() - }, 100) - }, - cancelItem: function () { - this.isAddingItem = false - - this.itemCode = "nodeAvgRequests" - this.itmeOperator = "lte" - this.itemValue = "" - this.itemDuration = "5" - this.itemReportGroups = [] - }, - confirmItem: function () { - // 特殊阈值快速添加 - if (["nodeHealthCheck"].$contains(this.itemCode)) { - if (this.itemValue.toString().length == 0) { - teaweb.warn("请选择检查结果") - return - } - - let value = parseInt(this.itemValue) - if (isNaN(value)) { - value = 0 - } else if (value < 0) { - value = 0 - } else if (value > 1) { - value = 1 - } - - // 添加 - this.addingThreshold.items.push({ - item: this.itemCode, - operator: this.itemOperator, - value: value, - duration: 0, - durationUnit: "minute", - options: {} - }) - this.cancelItem() - return - } - - if (this.itemDuration.length == 0) { - let that = this - teaweb.warn("请输入统计周期", function () { - that.$refs.itemDuration.focus() - }) - return - } - let itemDuration = parseInt(this.itemDuration) - if (isNaN(itemDuration) || itemDuration <= 0) { - teaweb.warn("请输入正确的统计周期", function () { - that.$refs.itemDuration.focus() - }) - return - } - - if (this.itemValue.length == 0) { - let that = this - teaweb.warn("请输入对比值", function () { - that.$refs.itemValue.focus() - }) - return - } - let itemValue = parseFloat(this.itemValue) - if (isNaN(itemValue)) { - teaweb.warn("请输入正确的对比值", function () { - that.$refs.itemValue.focus() - }) - return - } - - - let options = {} - - switch (this.itemCode) { - case "connectivity": // 连通性校验 - if (itemValue > 100) { - let that = this - teaweb.warn("连通性对比值不能超过100", function () { - that.$refs.itemValue.focus() - }) - return - } - - options["groups"] = this.itemReportGroups - break - } - - // 添加 - this.addingThreshold.items.push({ - item: this.itemCode, - operator: this.itemOperator, - value: itemValue, - duration: itemDuration, - durationUnit: "minute", - options: options - }) - - // 还原 - this.cancelItem() - }, - removeItem: function (index) { - this.cancelItem() - this.addingThreshold.items.$remove(index) - }, - changeReportGroups: function (groups) { - this.itemReportGroups = groups - }, - itemName: function (item) { - let result = "" - this.allItems.forEach(function (v) { - if (v.code == item) { - result = v.name - } - }) - return result - }, - itemUnitName: function (itemCode) { - let result = "" - this.allItems.forEach(function (v) { - if (v.code == itemCode) { - result = v.unit - } - }) - return result - }, - itemDurationUnitName: function (unit) { - switch (unit) { - case "minute": - return "分钟" - case "second": - return "秒" - case "hour": - return "小时" - case "day": - return "天" - } - return unit - }, - itemOperatorName: function (operator) { - let result = "" - this.allOperators.forEach(function (v) { - if (v.code == operator) { - result = v.name - } - }) - return result - }, - - addAction: function () { - this.isAddingAction = !this.isAddingAction - }, - cancelAction: function () { - this.isAddingAction = false - this.actionCode = "up" - this.actionBackupIPs = "" - this.actionWebHookURL = "" - }, - confirmAction: function () { - this.doConfirmAction(false) - }, - doConfirmAction: function (validated, options) { - // 是否已存在 - let exists = false - let that = this - this.addingThreshold.actions.forEach(function (v) { - if (v.action == that.actionCode) { - exists = true - } - }) - if (exists) { - teaweb.warn("此动作已经添加过了,无需重复添加") - return - } - - if (options == null) { - options = {} - } - - switch (this.actionCode) { - case "switch": - if (!validated) { - Tea.action("/ui/validateIPs") - .params({ - "ips": this.actionBackupIPs - }) - .success(function (resp) { - if (resp.data.ips.length == 0) { - teaweb.warn("请输入备用IP", function () { - that.$refs.actionBackupIPs.focus() - }) - return - } - options["ips"] = resp.data.ips - that.doConfirmAction(true, options) - }) - .fail(function (resp) { - teaweb.warn("输入的IP '" + resp.data.failIP + "' 格式不正确,请改正后提交", function () { - that.$refs.actionBackupIPs.focus() - }) - }) - .post() - return - } - break - case "webHook": - if (this.actionWebHookURL.length == 0) { - teaweb.warn("请输入WebHook URL", function () { - that.$refs.webHookURL.focus() - }) - return - } - if (!this.actionWebHookURL.match(/^(http|https):\/\//i)) { - teaweb.warn("URL开头必须是http://或者https://", function () { - that.$refs.webHookURL.focus() - }) - return - } - options["url"] = this.actionWebHookURL - } - - this.addingThreshold.actions.push({ - action: this.actionCode, - options: options - }) - - // 还原 - this.cancelAction() - }, - removeAction: function (index) { - this.cancelAction() - this.addingThreshold.actions.$remove(index) - }, - actionName: function (actionCode) { - let result = "" - this.allActions.forEach(function (v) { - if (v.code == actionCode) { - result = v.name - } - }) - return result - } - }, - template: `
+
`}),Vue.component("node-ip-address-thresholds-box",{props:["v-thresholds"],data:function(){let e=this.vThresholds;return null==e?e=[]:e.forEach(function(e){null==e.items&&(e.items=[]),null==e.actions&&(e.actions=[])}),{editingIndex:-1,thresholds:e,addingThreshold:{items:[],actions:[]},isAdding:!1,isAddingItem:!1,isAddingAction:!1,itemCode:"nodeAvgRequests",itemReportGroups:[],itemOperator:"lte",itemValue:"",itemDuration:"5",allItems:window.IP_ADDR_THRESHOLD_ITEMS,allOperators:[{name:"小于等于",code:"lte"},{name:"大于",code:"gt"},{name:"不等于",code:"neq"},{name:"小于",code:"lt"},{name:"大于等于",code:"gte"}],allActions:window.IP_ADDR_THRESHOLD_ACTIONS,actionCode:"up",actionBackupIPs:"",actionWebHookURL:""}},methods:{add:function(){this.isAdding=!this.isAdding},cancel:function(){this.isAdding=!1,this.editingIndex=-1,this.addingThreshold={items:[],actions:[]}},confirm:function(){0==this.addingThreshold.items.length?teaweb.warn("需要至少添加一个阈值"):0==this.addingThreshold.actions.length?teaweb.warn("需要至少添加一个动作"):(0<=this.editingIndex?(this.thresholds[this.editingIndex].items=this.addingThreshold.items,this.thresholds[this.editingIndex].actions=this.addingThreshold.actions):this.thresholds.push({items:this.addingThreshold.items,actions:this.addingThreshold.actions}),this.cancel())},remove:function(e){this.cancel(),this.thresholds.$remove(e)},update:function(e){this.editingIndex=e,this.addingThreshold={items:this.thresholds[e].items.$copy(),actions:this.thresholds[e].actions.$copy()},this.isAdding=!0},addItem:function(){this.isAddingItem=!this.isAddingItem;let e=this;setTimeout(function(){e.$refs.itemValue.focus()},100)},cancelItem:function(){this.isAddingItem=!1,this.itemCode="nodeAvgRequests",this.itmeOperator="lte",this.itemValue="",this.itemDuration="5",this.itemReportGroups=[]},confirmItem:function(){if(["nodeHealthCheck"].$contains(this.itemCode)){if(0==this.itemValue.toString().length)return void teaweb.warn("请选择检查结果");let e=parseInt(this.itemValue);return isNaN(e)||e<0?e=0:1 @@ -13871,38 +4374,7 @@ Vue.component("node-ip-address-thresholds-box", {
-
` -}) - -Vue.component("node-region-selector", { - props: ["v-region"], - data: function () { - return { - selectedRegion: this.vRegion - } - }, - methods: { - selectRegion: function () { - let that = this - teaweb.popup("/clusters/regions/selectPopup?clusterId=" + this.vClusterId, { - callback: function (resp) { - that.selectedRegion = resp.data.region - } - }) - }, - addRegion: function () { - let that = this - teaweb.popup("/clusters/regions/createPopup?clusterId=" + this.vClusterId, { - callback: function (resp) { - that.selectedRegion = resp.data.region - } - }) - }, - removeRegion: function () { - this.selectedRegion = null - } - }, - template: `
+
`}),Vue.component("node-region-selector",{props:["v-region"],data:function(){return{selectedRegion:this.vRegion}},methods:{selectRegion:function(){let t=this;teaweb.popup("/clusters/regions/selectPopup?clusterId="+this.vClusterId,{callback:function(e){t.selectedRegion=e.data.region}})},addRegion:function(){let t=this;teaweb.popup("/clusters/regions/createPopup?clusterId="+this.vClusterId,{callback:function(e){t.selectedRegion=e.data.region}})},removeRegion:function(){this.selectedRegion=null}},template:`
{{selectedRegion.name}}   @@ -13910,155 +4382,15 @@ Vue.component("node-region-selector", { -
` -}) - -Vue.component("node-combo-box", { - props: ["v-cluster-id", "v-node-id"], - data: function () { - let that = this - Tea.action("/clusters/nodeOptions") - .params({ - clusterId: this.vClusterId - }) - .post() - .success(function (resp) { - that.nodes = resp.data.nodes - }) - return { - nodes: [] - } - }, - template: `
+
`}),Vue.component("node-combo-box",{props:["v-cluster-id","v-node-id"],data:function(){let t=this;return Tea.action("/clusters/nodeOptions").params({clusterId:this.vClusterId}).post().success(function(e){t.nodes=e.data.nodes}),{nodes:[]}},template:`
-
` -}) - -// 节点级别选择器 -Vue.component("node-level-selector", { - props: ["v-node-level"], - data: function () { - let levelCode = this.vNodeLevel - if (levelCode == null || levelCode < 1) { - levelCode = 1 - } - return { - levels: [ - { - name: "边缘节点", - code: 1, - description: "普通的边缘节点。" - }, - { - name: "L2节点", - code: 2, - description: "特殊的边缘节点,同时负责同组上一级节点的回源。" - } - ], - levelCode: levelCode - } - }, - template: `
+
`}),Vue.component("node-level-selector",{props:["v-node-level"],data:function(){let e=this.vNodeLevel;return{levels:[{name:"边缘节点",code:1,description:"普通的边缘节点。"},{name:"L2节点",code:2,description:"特殊的边缘节点,同时负责同组上一级节点的回源。"}],levelCode:e=null==e||e<1?1:e}},template:`

{{levels[levelCode - 1].description}}

-
` -}) - -Vue.component("dns-route-selector", { - props: ["v-all-routes", "v-routes"], - data: function () { - let routes = this.vRoutes - if (routes == null) { - routes = [] - } - routes.$sort(function (v1, v2) { - if (v1.domainId == v2.domainId) { - return v1.code < v2.code - } - return (v1.domainId < v2.domainId) ? 1 : -1 - }) - return { - routes: routes, - routeCodes: routes.$map(function (k, v) { - return v.code + "@" + v.domainId - }), - isAdding: false, - routeCode: "", - keyword: "", - searchingRoutes: this.vAllRoutes.$copy() - } - }, - methods: { - add: function () { - this.isAdding = true - this.keyword = "" - this.routeCode = "" - - let that = this - setTimeout(function () { - that.$refs.keywordRef.focus() - }, 200) - }, - cancel: function () { - this.isAdding = false - }, - confirm: function () { - if (this.routeCode.length == 0) { - return - } - if (this.routeCodes.$contains(this.routeCode)) { - teaweb.warn("已经添加过此线路,不能重复添加") - return - } - let that = this - let route = this.vAllRoutes.$find(function (k, v) { - return v.code + "@" + v.domainId == that.routeCode - }) - if (route == null) { - return - } - - this.routeCodes.push(this.routeCode) - this.routes.push(route) - - this.routes.$sort(function (v1, v2) { - if (v1.domainId == v2.domainId) { - return v1.code < v2.code - } - return (v1.domainId < v2.domainId) ? 1 : -1 - }) - - this.routeCode = "" - this.isAdding = false - }, - remove: function (route) { - this.routeCodes.$removeValue(route.code + "@" + route.domainId) - this.routes.$removeIf(function (k, v) { - return v.code + "@" + v.domainId == route.code + "@" + route.domainId - }) - } - }, - watch: { - keyword: function (keyword) { - if (keyword.length == 0) { - this.searchingRoutes = this.vAllRoutes.$copy() - this.routeCode = "" - return - } - this.searchingRoutes = this.vAllRoutes.filter(function (route) { - return teaweb.match(route.name, keyword) || teaweb.match(route.domainName, keyword) - }) - if (this.searchingRoutes.length > 0) { - this.routeCode = this.searchingRoutes[0].code + "@" + this.searchingRoutes[0].domainId - } else { - this.routeCode = "" - } - } - }, - template: `
+
`}),Vue.component("dns-route-selector",{props:["v-all-routes","v-routes"],data:function(){let e=this.vRoutes;return(e=null==e?[]:e).$sort(function(e,t){return e.domainId==t.domainId?e.code
@@ -14087,59 +4419,7 @@ Vue.component("dns-route-selector", {
-
` -}) - -Vue.component("dns-domain-selector", { - props: ["v-domain-id", "v-domain-name"], - data: function () { - let domainId = this.vDomainId - if (domainId == null) { - domainId = 0 - } - let domainName = this.vDomainName - if (domainName == null) { - domainName = "" - } - return { - domainId: domainId, - domainName: domainName - } - }, - methods: { - select: function () { - let that = this - teaweb.popup("/dns/domains/selectPopup", { - callback: function (resp) { - that.domainId = resp.data.domainId - that.domainName = resp.data.domainName - that.change() - } - }) - }, - remove: function() { - this.domainId = 0 - this.domainName = "" - this.change() - }, - update: function () { - let that = this - teaweb.popup("/dns/domains/selectPopup?domainId=" + this.domainId, { - callback: function (resp) { - that.domainId = resp.data.domainId - that.domainName = resp.data.domainName - that.change() - } - }) - }, - change: function () { - this.$emit("change", { - id: this.domainId, - name: this.domainName - }) - } - }, - template: `
+
`}),Vue.component("dns-domain-selector",{props:["v-domain-id","v-domain-name"],data:function(){let e=this.vDomainId,t=(null==e&&(e=0),this.vDomainName);return null==t&&(t=""),{domainId:e,domainName:t}},methods:{select:function(){let t=this;teaweb.popup("/dns/domains/selectPopup",{callback:function(e){t.domainId=e.data.domainId,t.domainName=e.data.domainName,t.change()}})},remove:function(){this.domainId=0,this.domainName="",this.change()},update:function(){let t=this;teaweb.popup("/dns/domains/selectPopup?domainId="+this.domainId,{callback:function(e){t.domainId=e.data.domainId,t.domainName=e.data.domainName,t.change()}})},change:function(){this.$emit("change",{id:this.domainId,name:this.domainName})}},template:`
@@ -14151,94 +4431,10 @@ Vue.component("dns-domain-selector", { -
` -}) - -Vue.component("grant-selector", { - props: ["v-grant", "v-node-cluster-id", "v-ns-cluster-id"], - data: function () { - return { - grantId: (this.vGrant == null) ? 0 : this.vGrant.id, - grant: this.vGrant, - nodeClusterId: (this.vNodeClusterId != null) ? this.vNodeClusterId : 0, - nsClusterId: (this.vNsClusterId != null) ? this.vNsClusterId : 0 - } - }, - methods: { - // 选择授权 - select: function () { - let that = this; - teaweb.popup("/clusters/grants/selectPopup?nodeClusterId=" + this.nodeClusterId + "&nsClusterId=" + this.nsClusterId, { - callback: (resp) => { - that.grantId = resp.data.grant.id; - if (that.grantId > 0) { - that.grant = resp.data.grant; - } - that.notifyUpdate() - }, - height: "26em" - }) - }, - - // 创建授权 - create: function () { - let that = this - teaweb.popup("/clusters/grants/createPopup", { - height: "26em", - callback: (resp) => { - that.grantId = resp.data.grant.id; - if (that.grantId > 0) { - that.grant = resp.data.grant; - } - that.notifyUpdate() - } - }) - }, - - // 修改授权 - update: function () { - if (this.grant == null) { - window.location.reload(); - return; - } - let that = this - teaweb.popup("/clusters/grants/updatePopup?grantId=" + this.grant.id, { - height: "26em", - callback: (resp) => { - that.grant = resp.data.grant - that.notifyUpdate() - } - }) - }, - - // 删除已选择授权 - remove: function () { - this.grant = null - this.grantId = 0 - this.notifyUpdate() - }, - notifyUpdate: function () { - this.$emit("change", this.grant) - } - }, - template: `
+
`}),Vue.component("grant-selector",{props:["v-grant","v-node-cluster-id","v-ns-cluster-id"],data:function(){return{grantId:null==this.vGrant?0:this.vGrant.id,grant:this.vGrant,nodeClusterId:null!=this.vNodeClusterId?this.vNodeClusterId:0,nsClusterId:null!=this.vNsClusterId?this.vNsClusterId:0}},methods:{select:function(){let t=this;teaweb.popup("/clusters/grants/selectPopup?nodeClusterId="+this.nodeClusterId+"&nsClusterId="+this.nsClusterId,{callback:e=>{t.grantId=e.data.grant.id,0{t.grantId=e.data.grant.id,0{t.grant=e.data.grant,t.notifyUpdate()}})}},remove:function(){this.grant=null,this.grantId=0,this.notifyUpdate()},notifyUpdate:function(){this.$emit("change",this.grant)}},template:`
{{grant.name}}({{grant.methodName}})({{grant.username}})
-
` -}) - -window.REQUEST_COND_COMPONENTS = [{"type":"url-extension","name":"URL扩展名","description":"根据URL中的文件路径扩展名进行过滤","component":"http-cond-url-extension","paramsTitle":"扩展名列表","isRequest":true,"caseInsensitive":false},{"type":"url-prefix","name":"URL前缀","description":"根据URL中的文件路径前缀进行过滤","component":"http-cond-url-prefix","paramsTitle":"URL前缀","isRequest":true,"caseInsensitive":true},{"type":"url-eq","name":"URL精准匹配","description":"检查URL中的文件路径是否一致","component":"http-cond-url-eq","paramsTitle":"URL完整路径","isRequest":true,"caseInsensitive":true},{"type":"url-regexp","name":"URL正则匹配","description":"使用正则表达式检查URL中的文件路径是否一致","component":"http-cond-url-regexp","paramsTitle":"正则表达式","isRequest":true,"caseInsensitive":true},{"type":"user-agent-regexp","name":"User-Agent正则匹配","description":"使用正则表达式检查User-Agent中是否含有某些浏览器和系统标识","component":"http-cond-user-agent-regexp","paramsTitle":"正则表达式","isRequest":true,"caseInsensitive":true},{"type":"params","name":"参数匹配","description":"根据参数值进行匹配","component":"http-cond-params","paramsTitle":"参数配置","isRequest":true,"caseInsensitive":false},{"type":"url-not-extension","name":"排除:URL扩展名","description":"根据URL中的文件路径扩展名进行过滤","component":"http-cond-url-not-extension","paramsTitle":"扩展名列表","isRequest":true,"caseInsensitive":false},{"type":"url-not-prefix","name":"排除:URL前缀","description":"根据URL中的文件路径前缀进行过滤","component":"http-cond-url-not-prefix","paramsTitle":"URL前缀","isRequest":true,"caseInsensitive":true},{"type":"url-not-eq","name":"排除:URL精准匹配","description":"检查URL中的文件路径是否一致","component":"http-cond-url-not-eq","paramsTitle":"URL完整路径","isRequest":true,"caseInsensitive":true},{"type":"url-not-regexp","name":"排除:URL正则匹配","description":"使用正则表达式检查URL中的文件路径是否一致,如果一致,则不匹配","component":"http-cond-url-not-regexp","paramsTitle":"正则表达式","isRequest":true,"caseInsensitive":true},{"type":"user-agent-not-regexp","name":"排除:User-Agent正则匹配","description":"使用正则表达式检查User-Agent中是否含有某些浏览器和系统标识,如果含有,则不匹配","component":"http-cond-user-agent-not-regexp","paramsTitle":"正则表达式","isRequest":true,"caseInsensitive":true},{"type":"mime-type","name":"内容MimeType","description":"根据服务器返回的内容的MimeType进行过滤。注意:当用于缓存条件时,此条件需要结合别的请求条件使用。","component":"http-cond-mime-type","paramsTitle":"MimeType列表","isRequest":false,"caseInsensitive":false}] - -window.REQUEST_COND_OPERATORS = [{"description":"判断是否正则表达式匹配","name":"正则表达式匹配","op":"regexp"},{"description":"判断是否正则表达式不匹配","name":"正则表达式不匹配","op":"not regexp"},{"description":"使用字符串对比参数值是否相等于某个值","name":"字符串等于","op":"eq"},{"description":"参数值包含某个前缀","name":"字符串前缀","op":"prefix"},{"description":"参数值包含某个后缀","name":"字符串后缀","op":"suffix"},{"description":"参数值包含另外一个字符串","name":"字符串包含","op":"contains"},{"description":"参数值不包含另外一个字符串","name":"字符串不包含","op":"not contains"},{"description":"使用字符串对比参数值是否不相等于某个值","name":"字符串不等于","op":"not"},{"description":"判断参数值在某个列表中","name":"在列表中","op":"in"},{"description":"判断参数值不在某个列表中","name":"不在列表中","op":"not in"},{"description":"判断小写的扩展名(不带点)在某个列表中","name":"扩展名","op":"file ext"},{"description":"判断MimeType在某个列表中,支持类似于image/*的语法","name":"MimeType","op":"mime type"},{"description":"判断版本号在某个范围内,格式为version1,version2","name":"版本号范围","op":"version range"},{"description":"将参数转换为整数数字后进行对比","name":"整数等于","op":"eq int"},{"description":"将参数转换为可以有小数的浮点数字进行对比","name":"浮点数等于","op":"eq float"},{"description":"将参数转换为数字进行对比","name":"数字大于","op":"gt"},{"description":"将参数转换为数字进行对比","name":"数字大于等于","op":"gte"},{"description":"将参数转换为数字进行对比","name":"数字小于","op":"lt"},{"description":"将参数转换为数字进行对比","name":"数字小于等于","op":"lte"},{"description":"对整数参数值取模,除数为10,对比值为余数","name":"整数取模10","op":"mod 10"},{"description":"对整数参数值取模,除数为100,对比值为余数","name":"整数取模100","op":"mod 100"},{"description":"对整数参数值取模,对比值格式为:除数,余数,比如10,1","name":"整数取模","op":"mod"},{"description":"将参数转换为IP进行对比","name":"IP等于","op":"eq ip"},{"description":"将参数转换为IP进行对比","name":"IP大于","op":"gt ip"},{"description":"将参数转换为IP进行对比","name":"IP大于等于","op":"gte ip"},{"description":"将参数转换为IP进行对比","name":"IP小于","op":"lt ip"},{"description":"将参数转换为IP进行对比","name":"IP小于等于","op":"lte ip"},{"description":"IP在某个范围之内,范围格式可以是英文逗号分隔的ip1,ip2,或者CIDR格式的ip/bits","name":"IP范围","op":"ip range"},{"description":"对IP参数值取模,除数为10,对比值为余数","name":"IP取模10","op":"ip mod 10"},{"description":"对IP参数值取模,除数为100,对比值为余数","name":"IP取模100","op":"ip mod 100"},{"description":"对IP参数值取模,对比值格式为:除数,余数,比如10,1","name":"IP取模","op":"ip mod"},{"description":"判断参数值解析后的文件是否存在","name":"文件存在","op":"file exist"},{"description":"判断参数值解析后的文件是否不存在","name":"文件不存在","op":"file not exist"}] - -window.REQUEST_VARIABLES = [{"code":"${edgeVersion}","description":"","name":"边缘节点版本"},{"code":"${remoteAddr}","description":"会依次根据X-Forwarded-For、X-Real-IP、RemoteAddr获取,适合前端有别的反向代理服务时使用,存在伪造的风险","name":"客户端地址(IP)"},{"code":"${rawRemoteAddr}","description":"返回直接连接服务的客户端原始IP地址","name":"客户端地址(IP)"},{"code":"${remotePort}","description":"","name":"客户端端口"},{"code":"${remoteUser}","description":"","name":"客户端用户名"},{"code":"${requestURI}","description":"比如/hello?name=lily","name":"请求URI"},{"code":"${requestPath}","description":"比如/hello","name":"请求路径(不包括参数)"},{"code":"${requestURL}","description":"比如https://example.com/hello?name=lily","name":"完整的请求URL"},{"code":"${requestLength}","description":"","name":"请求内容长度"},{"code":"${requestMethod}","description":"比如GET、POST","name":"请求方法"},{"code":"${requestFilename}","description":"","name":"请求文件路径"},{"code":"${scheme}","description":"","name":"请求协议,http或https"},{"code":"${proto}","description:":"类似于HTTP/1.0","name":"包含版本的HTTP请求协议"},{"code":"${timeISO8601}","description":"比如2018-07-16T23:52:24.839+08:00","name":"ISO 8601格式的时间"},{"code":"${timeLocal}","description":"比如17/Jul/2018:09:52:24 +0800","name":"本地时间"},{"code":"${msec}","description":"比如1531756823.054","name":"带有毫秒的时间"},{"code":"${timestamp}","description":"","name":"unix时间戳,单位为秒"},{"code":"${host}","description":"","name":"主机名"},{"code":"${serverName}","description":"","name":"接收请求的服务器名"},{"code":"${serverPort}","description":"","name":"接收请求的服务器端口"},{"code":"${referer}","description":"","name":"请求来源URL"},{"code":"${referer.host}","description":"","name":"请求来源URL域名"},{"code":"${userAgent}","description":"","name":"客户端信息"},{"code":"${contentType}","description":"","name":"请求头部的Content-Type"},{"code":"${cookies}","description":"","name":"所有cookie组合字符串"},{"code":"${cookie.NAME}","description":"","name":"单个cookie值"},{"code":"${isArgs}","description":"如果URL有参数,则值为`?`;否则,则值为空","name":"问号(?)标记"},{"code":"${args}","description":"","name":"所有参数组合字符串"},{"code":"${arg.NAME}","description":"","name":"单个参数值"},{"code":"${headers}","description":"","name":"所有Header信息组合字符串"},{"code":"${header.NAME}","description":"","name":"单个Header值"},{"code":"${geo.country.name}","description":"","name":"国家/地区名称"},{"code":"${geo.country.id}","description":"","name":"国家/地区ID"},{"code":"${geo.province.name}","description":"目前只包含中国省份","name":"省份名称"},{"code":"${geo.province.id}","description":"目前只包含中国省份","name":"省份ID"},{"code":"${geo.city.name}","description":"目前只包含中国城市","name":"城市名称"},{"code":"${geo.city.id}","description":"目前只包含中国城市","name":"城市名称"},{"code":"${isp.name}","description":"","name":"ISP服务商名称"},{"code":"${isp.id}","description":"","name":"ISP服务商ID"},{"code":"${browser.os.name}","description":"客户端所在操作系统名称","name":"操作系统名称"},{"code":"${browser.os.version}","description":"客户端所在操作系统版本","name":"操作系统版本"},{"code":"${browser.name}","description":"客户端浏览器名称","name":"浏览器名称"},{"code":"${browser.version}","description":"客户端浏览器版本","name":"浏览器版本"},{"code":"${browser.isMobile}","description":"如果客户端是手机,则值为1,否则为0","name":"手机标识"}] - -window.METRIC_HTTP_KEYS = [{"name":"客户端地址(IP)","code":"${remoteAddr}","description":"会依次根据X-Forwarded-For、X-Real-IP、RemoteAddr获取,适用于前端可能有别的反向代理的情形,存在被伪造的可能","icon":""},{"name":"直接客户端地址(IP)","code":"${rawRemoteAddr}","description":"返回直接连接服务的客户端原始IP地址","icon":""},{"name":"客户端用户名","code":"${remoteUser}","description":"通过基本认证填入的用户名","icon":""},{"name":"请求URI","code":"${requestURI}","description":"包含参数,比如/hello?name=lily","icon":""},{"name":"请求路径","code":"${requestPath}","description":"不包含参数,比如/hello","icon":""},{"name":"完整URL","code":"${requestURL}","description":"比如https://example.com/hello?name=lily","icon":""},{"name":"请求方法","code":"${requestMethod}","description":"比如GET、POST等","icon":""},{"name":"请求协议Scheme","code":"${scheme}","description":"http或https","icon":""},{"name":"文件扩展名","code":"${requestPathExtension}","description":"请求路径中的文件扩展名,包括点符号,比如.html、.png","icon":""},{"name":"主机名","code":"${host}","description":"通常是请求的域名","icon":""},{"name":"请求协议Proto","code":"${proto}","description":"包含版本的HTTP请求协议,类似于HTTP/1.0","icon":""},{"name":"HTTP协议","code":"${proto}","description":"包含版本的HTTP请求协议,类似于HTTP/1.0","icon":""},{"name":"URL参数值","code":"${arg.NAME}","description":"单个URL参数值","icon":""},{"name":"请求来源URL","code":"${referer}","description":"请求来源Referer URL","icon":""},{"name":"请求来源URL域名","code":"${referer.host}","description":"请求来源Referer URL域名","icon":""},{"name":"Header值","code":"${header.NAME}","description":"单个Header值,比如${header.User-Agent}","icon":""},{"name":"Cookie值","code":"${cookie.NAME}","description":"单个cookie值,比如${cookie.sid}","icon":""},{"name":"状态码","code":"${status}","description":"","icon":""},{"name":"响应的Content-Type值","code":"${response.contentType}","description":"","icon":""}] - -window.IP_ADDR_THRESHOLD_ITEMS = [{"code":"nodeAvgRequests","description":"当前节点在单位时间内接收到的平均请求数。","name":"节点平均请求数","unit":"个"},{"code":"nodeAvgTrafficOut","description":"当前节点在单位时间内发送的下行流量。","name":"节点平均下行流量","unit":"M"},{"code":"nodeAvgTrafficIn","description":"当前节点在单位时间内接收的上行流量。","name":"节点平均上行流量","unit":"M"},{"code":"nodeHealthCheck","description":"当前节点健康检查结果。","name":"节点健康检查结果","unit":""},{"code":"connectivity","description":"通过区域监控得到的当前IP地址的连通性数值,取值在0和100之间。","name":"IP连通性","unit":"%"},{"code":"groupAvgRequests","description":"当前节点所在分组在单位时间内接收到的平均请求数。","name":"分组平均请求数","unit":"个"},{"code":"groupAvgTrafficOut","description":"当前节点所在分组在单位时间内发送的下行流量。","name":"分组平均下行流量","unit":"M"},{"code":"groupAvgTrafficIn","description":"当前节点所在分组在单位时间内接收的上行流量。","name":"分组平均上行流量","unit":"M"},{"code":"clusterAvgRequests","description":"当前节点所在集群在单位时间内接收到的平均请求数。","name":"集群平均请求数","unit":"个"},{"code":"clusterAvgTrafficOut","description":"当前节点所在集群在单位时间内发送的下行流量。","name":"集群平均下行流量","unit":"M"},{"code":"clusterAvgTrafficIn","description":"当前节点所在集群在单位时间内接收的上行流量。","name":"集群平均上行流量","unit":"M"}] - -window.IP_ADDR_THRESHOLD_ACTIONS = [{"code":"up","description":"上线当前IP。","name":"上线"},{"code":"down","description":"下线当前IP。","name":"下线"},{"code":"notify","description":"发送已达到阈值通知。","name":"通知"},{"code":"switch","description":"在DNS中记录中将IP切换到指定的备用IP。","name":"切换"},{"code":"webHook","description":"调用外部的WebHook。","name":"WebHook"}] - +
`}),window.REQUEST_COND_COMPONENTS=[{type:"url-extension",name:"URL扩展名",description:"根据URL中的文件路径扩展名进行过滤",component:"http-cond-url-extension",paramsTitle:"扩展名列表",isRequest:!0,caseInsensitive:!1},{type:"url-prefix",name:"URL前缀",description:"根据URL中的文件路径前缀进行过滤",component:"http-cond-url-prefix",paramsTitle:"URL前缀",isRequest:!0,caseInsensitive:!0},{type:"url-eq",name:"URL精准匹配",description:"检查URL中的文件路径是否一致",component:"http-cond-url-eq",paramsTitle:"URL完整路径",isRequest:!0,caseInsensitive:!0},{type:"url-regexp",name:"URL正则匹配",description:"使用正则表达式检查URL中的文件路径是否一致",component:"http-cond-url-regexp",paramsTitle:"正则表达式",isRequest:!0,caseInsensitive:!0},{type:"user-agent-regexp",name:"User-Agent正则匹配",description:"使用正则表达式检查User-Agent中是否含有某些浏览器和系统标识",component:"http-cond-user-agent-regexp",paramsTitle:"正则表达式",isRequest:!0,caseInsensitive:!0},{type:"params",name:"参数匹配",description:"根据参数值进行匹配",component:"http-cond-params",paramsTitle:"参数配置",isRequest:!0,caseInsensitive:!1},{type:"url-not-extension",name:"排除:URL扩展名",description:"根据URL中的文件路径扩展名进行过滤",component:"http-cond-url-not-extension",paramsTitle:"扩展名列表",isRequest:!0,caseInsensitive:!1},{type:"url-not-prefix",name:"排除:URL前缀",description:"根据URL中的文件路径前缀进行过滤",component:"http-cond-url-not-prefix",paramsTitle:"URL前缀",isRequest:!0,caseInsensitive:!0},{type:"url-not-eq",name:"排除:URL精准匹配",description:"检查URL中的文件路径是否一致",component:"http-cond-url-not-eq",paramsTitle:"URL完整路径",isRequest:!0,caseInsensitive:!0},{type:"url-not-regexp",name:"排除:URL正则匹配",description:"使用正则表达式检查URL中的文件路径是否一致,如果一致,则不匹配",component:"http-cond-url-not-regexp",paramsTitle:"正则表达式",isRequest:!0,caseInsensitive:!0},{type:"user-agent-not-regexp",name:"排除:User-Agent正则匹配",description:"使用正则表达式检查User-Agent中是否含有某些浏览器和系统标识,如果含有,则不匹配",component:"http-cond-user-agent-not-regexp",paramsTitle:"正则表达式",isRequest:!0,caseInsensitive:!0},{type:"mime-type",name:"内容MimeType",description:"根据服务器返回的内容的MimeType进行过滤。注意:当用于缓存条件时,此条件需要结合别的请求条件使用。",component:"http-cond-mime-type",paramsTitle:"MimeType列表",isRequest:!1,caseInsensitive:!1}],window.REQUEST_COND_OPERATORS=[{description:"判断是否正则表达式匹配",name:"正则表达式匹配",op:"regexp"},{description:"判断是否正则表达式不匹配",name:"正则表达式不匹配",op:"not regexp"},{description:"使用字符串对比参数值是否相等于某个值",name:"字符串等于",op:"eq"},{description:"参数值包含某个前缀",name:"字符串前缀",op:"prefix"},{description:"参数值包含某个后缀",name:"字符串后缀",op:"suffix"},{description:"参数值包含另外一个字符串",name:"字符串包含",op:"contains"},{description:"参数值不包含另外一个字符串",name:"字符串不包含",op:"not contains"},{description:"使用字符串对比参数值是否不相等于某个值",name:"字符串不等于",op:"not"},{description:"判断参数值在某个列表中",name:"在列表中",op:"in"},{description:"判断参数值不在某个列表中",name:"不在列表中",op:"not in"},{description:"判断小写的扩展名(不带点)在某个列表中",name:"扩展名",op:"file ext"},{description:"判断MimeType在某个列表中,支持类似于image/*的语法",name:"MimeType",op:"mime type"},{description:"判断版本号在某个范围内,格式为version1,version2",name:"版本号范围",op:"version range"},{description:"将参数转换为整数数字后进行对比",name:"整数等于",op:"eq int"},{description:"将参数转换为可以有小数的浮点数字进行对比",name:"浮点数等于",op:"eq float"},{description:"将参数转换为数字进行对比",name:"数字大于",op:"gt"},{description:"将参数转换为数字进行对比",name:"数字大于等于",op:"gte"},{description:"将参数转换为数字进行对比",name:"数字小于",op:"lt"},{description:"将参数转换为数字进行对比",name:"数字小于等于",op:"lte"},{description:"对整数参数值取模,除数为10,对比值为余数",name:"整数取模10",op:"mod 10"},{description:"对整数参数值取模,除数为100,对比值为余数",name:"整数取模100",op:"mod 100"},{description:"对整数参数值取模,对比值格式为:除数,余数,比如10,1",name:"整数取模",op:"mod"},{description:"将参数转换为IP进行对比",name:"IP等于",op:"eq ip"},{description:"将参数转换为IP进行对比",name:"IP大于",op:"gt ip"},{description:"将参数转换为IP进行对比",name:"IP大于等于",op:"gte ip"},{description:"将参数转换为IP进行对比",name:"IP小于",op:"lt ip"},{description:"将参数转换为IP进行对比",name:"IP小于等于",op:"lte ip"},{description:"IP在某个范围之内,范围格式可以是英文逗号分隔的ip1,ip2,或者CIDR格式的ip/bits",name:"IP范围",op:"ip range"},{description:"对IP参数值取模,除数为10,对比值为余数",name:"IP取模10",op:"ip mod 10"},{description:"对IP参数值取模,除数为100,对比值为余数",name:"IP取模100",op:"ip mod 100"},{description:"对IP参数值取模,对比值格式为:除数,余数,比如10,1",name:"IP取模",op:"ip mod"},{description:"判断参数值解析后的文件是否存在",name:"文件存在",op:"file exist"},{description:"判断参数值解析后的文件是否不存在",name:"文件不存在",op:"file not exist"}],window.REQUEST_VARIABLES=[{code:"${edgeVersion}",description:"",name:"边缘节点版本"},{code:"${remoteAddr}",description:"会依次根据X-Forwarded-For、X-Real-IP、RemoteAddr获取,适合前端有别的反向代理服务时使用,存在伪造的风险",name:"客户端地址(IP)"},{code:"${rawRemoteAddr}",description:"返回直接连接服务的客户端原始IP地址",name:"客户端地址(IP)"},{code:"${remotePort}",description:"",name:"客户端端口"},{code:"${remoteUser}",description:"",name:"客户端用户名"},{code:"${requestURI}",description:"比如/hello?name=lily",name:"请求URI"},{code:"${requestPath}",description:"比如/hello",name:"请求路径(不包括参数)"},{code:"${requestURL}",description:"比如https://example.com/hello?name=lily",name:"完整的请求URL"},{code:"${requestLength}",description:"",name:"请求内容长度"},{code:"${requestMethod}",description:"比如GET、POST",name:"请求方法"},{code:"${requestFilename}",description:"",name:"请求文件路径"},{code:"${scheme}",description:"",name:"请求协议,http或https"},{code:"${proto}","description:":"类似于HTTP/1.0",name:"包含版本的HTTP请求协议"},{code:"${timeISO8601}",description:"比如2018-07-16T23:52:24.839+08:00",name:"ISO 8601格式的时间"},{code:"${timeLocal}",description:"比如17/Jul/2018:09:52:24 +0800",name:"本地时间"},{code:"${msec}",description:"比如1531756823.054",name:"带有毫秒的时间"},{code:"${timestamp}",description:"",name:"unix时间戳,单位为秒"},{code:"${host}",description:"",name:"主机名"},{code:"${serverName}",description:"",name:"接收请求的服务器名"},{code:"${serverPort}",description:"",name:"接收请求的服务器端口"},{code:"${referer}",description:"",name:"请求来源URL"},{code:"${referer.host}",description:"",name:"请求来源URL域名"},{code:"${userAgent}",description:"",name:"客户端信息"},{code:"${contentType}",description:"",name:"请求头部的Content-Type"},{code:"${cookies}",description:"",name:"所有cookie组合字符串"},{code:"${cookie.NAME}",description:"",name:"单个cookie值"},{code:"${isArgs}",description:"如果URL有参数,则值为`?`;否则,则值为空",name:"问号(?)标记"},{code:"${args}",description:"",name:"所有参数组合字符串"},{code:"${arg.NAME}",description:"",name:"单个参数值"},{code:"${headers}",description:"",name:"所有Header信息组合字符串"},{code:"${header.NAME}",description:"",name:"单个Header值"},{code:"${geo.country.name}",description:"",name:"国家/地区名称"},{code:"${geo.country.id}",description:"",name:"国家/地区ID"},{code:"${geo.province.name}",description:"目前只包含中国省份",name:"省份名称"},{code:"${geo.province.id}",description:"目前只包含中国省份",name:"省份ID"},{code:"${geo.city.name}",description:"目前只包含中国城市",name:"城市名称"},{code:"${geo.city.id}",description:"目前只包含中国城市",name:"城市名称"},{code:"${isp.name}",description:"",name:"ISP服务商名称"},{code:"${isp.id}",description:"",name:"ISP服务商ID"},{code:"${browser.os.name}",description:"客户端所在操作系统名称",name:"操作系统名称"},{code:"${browser.os.version}",description:"客户端所在操作系统版本",name:"操作系统版本"},{code:"${browser.name}",description:"客户端浏览器名称",name:"浏览器名称"},{code:"${browser.version}",description:"客户端浏览器版本",name:"浏览器版本"},{code:"${browser.isMobile}",description:"如果客户端是手机,则值为1,否则为0",name:"手机标识"}],window.METRIC_HTTP_KEYS=[{name:"客户端地址(IP)",code:"${remoteAddr}",description:"会依次根据X-Forwarded-For、X-Real-IP、RemoteAddr获取,适用于前端可能有别的反向代理的情形,存在被伪造的可能",icon:""},{name:"直接客户端地址(IP)",code:"${rawRemoteAddr}",description:"返回直接连接服务的客户端原始IP地址",icon:""},{name:"客户端用户名",code:"${remoteUser}",description:"通过基本认证填入的用户名",icon:""},{name:"请求URI",code:"${requestURI}",description:"包含参数,比如/hello?name=lily",icon:""},{name:"请求路径",code:"${requestPath}",description:"不包含参数,比如/hello",icon:""},{name:"完整URL",code:"${requestURL}",description:"比如https://example.com/hello?name=lily",icon:""},{name:"请求方法",code:"${requestMethod}",description:"比如GET、POST等",icon:""},{name:"请求协议Scheme",code:"${scheme}",description:"http或https",icon:""},{name:"文件扩展名",code:"${requestPathExtension}",description:"请求路径中的文件扩展名,包括点符号,比如.html、.png",icon:""},{name:"主机名",code:"${host}",description:"通常是请求的域名",icon:""},{name:"请求协议Proto",code:"${proto}",description:"包含版本的HTTP请求协议,类似于HTTP/1.0",icon:""},{name:"HTTP协议",code:"${proto}",description:"包含版本的HTTP请求协议,类似于HTTP/1.0",icon:""},{name:"URL参数值",code:"${arg.NAME}",description:"单个URL参数值",icon:""},{name:"请求来源URL",code:"${referer}",description:"请求来源Referer URL",icon:""},{name:"请求来源URL域名",code:"${referer.host}",description:"请求来源Referer URL域名",icon:""},{name:"Header值",code:"${header.NAME}",description:"单个Header值,比如${header.User-Agent}",icon:""},{name:"Cookie值",code:"${cookie.NAME}",description:"单个cookie值,比如${cookie.sid}",icon:""},{name:"状态码",code:"${status}",description:"",icon:""},{name:"响应的Content-Type值",code:"${response.contentType}",description:"",icon:""}],window.IP_ADDR_THRESHOLD_ITEMS=[{code:"nodeAvgRequests",description:"当前节点在单位时间内接收到的平均请求数。",name:"节点平均请求数",unit:"个"},{code:"nodeAvgTrafficOut",description:"当前节点在单位时间内发送的下行流量。",name:"节点平均下行流量",unit:"M"},{code:"nodeAvgTrafficIn",description:"当前节点在单位时间内接收的上行流量。",name:"节点平均上行流量",unit:"M"},{code:"nodeHealthCheck",description:"当前节点健康检查结果。",name:"节点健康检查结果",unit:""},{code:"connectivity",description:"通过区域监控得到的当前IP地址的连通性数值,取值在0和100之间。",name:"IP连通性",unit:"%"},{code:"groupAvgRequests",description:"当前节点所在分组在单位时间内接收到的平均请求数。",name:"分组平均请求数",unit:"个"},{code:"groupAvgTrafficOut",description:"当前节点所在分组在单位时间内发送的下行流量。",name:"分组平均下行流量",unit:"M"},{code:"groupAvgTrafficIn",description:"当前节点所在分组在单位时间内接收的上行流量。",name:"分组平均上行流量",unit:"M"},{code:"clusterAvgRequests",description:"当前节点所在集群在单位时间内接收到的平均请求数。",name:"集群平均请求数",unit:"个"},{code:"clusterAvgTrafficOut",description:"当前节点所在集群在单位时间内发送的下行流量。",name:"集群平均下行流量",unit:"M"},{code:"clusterAvgTrafficIn",description:"当前节点所在集群在单位时间内接收的上行流量。",name:"集群平均上行流量",unit:"M"}],window.IP_ADDR_THRESHOLD_ACTIONS=[{code:"up",description:"上线当前IP。",name:"上线"},{code:"down",description:"下线当前IP。",name:"下线"},{code:"notify",description:"发送已达到阈值通知。",name:"通知"},{code:"switch",description:"在DNS中记录中将IP切换到指定的备用IP。",name:"切换"},{code:"webHook",description:"调用外部的WebHook。",name:"WebHook"}]; diff --git a/web/public/js/components.src.js b/web/public/js/components.src.js new file mode 100755 index 00000000..efae8de0 --- /dev/null +++ b/web/public/js/components.src.js @@ -0,0 +1,14244 @@ +Vue.component("traffic-map-box", { + props: ["v-stats", "v-is-attack"], + mounted: function () { + this.render() + }, + data: function () { + let maxPercent = 0 + let isAttack = this.vIsAttack + this.vStats.forEach(function (v) { + let percent = parseFloat(v.percent) + if (percent > maxPercent) { + maxPercent = percent + } + + v.formattedCountRequests = teaweb.formatCount(v.countRequests) + "次" + v.formattedCountAttackRequests = teaweb.formatCount(v.countAttackRequests) + "次" + }) + + if (maxPercent < 100) { + maxPercent *= 1.2 // 不要让某一项100% + } + + let screenIsNarrow = window.innerWidth < 512 + + return { + isAttack: isAttack, + stats: this.vStats, + chart: null, + minOpacity: 0.2, + maxPercent: maxPercent, + selectedCountryName: "", + screenIsNarrow: screenIsNarrow + } + }, + methods: { + render: function () { + this.chart = teaweb.initChart(document.getElementById("traffic-map-box")); + let that = this + this.chart.setOption({ + backgroundColor: "white", + grid: { + top: 0, + bottom: 0, + left: 0, + right: 0 + }, + roam: false, + tooltip: { + trigger: "item" + }, + series: [{ + type: "map", + map: "world", + zoom: 1.3, + selectedMode: false, + itemStyle: { + areaColor: "#E9F0F9", + borderColor: "#DDD" + }, + label: { + show: false, + fontSize: "10px", + color: "#fff", + backgroundColor: "#8B9BD3", + padding: [2, 2, 2, 2] + }, + emphasis: { + itemStyle: { + areaColor: "#8B9BD3", + opacity: 1.0 + }, + label: { + show: true, + fontSize: "10px", + color: "#fff", + backgroundColor: "#8B9BD3", + padding: [2, 2, 2, 2] + } + }, + //select: {itemStyle:{ areaColor: "#8B9BD3", opacity: 0.8 }}, + tooltip: { + formatter: function (args) { + let name = args.name + let stat = null + that.stats.forEach(function (v) { + if (v.name == name) { + stat = v + } + }) + + if (stat != null) { + return name + "
流量:" + stat.formattedBytes + "
流量占比:" + stat.percent + "%
请求数:" + stat.formattedCountRequests + "
攻击数:" + stat.formattedCountAttackRequests + } + return name + } + }, + data: this.stats.map(function (v) { + let opacity = parseFloat(v.percent) / that.maxPercent + if (opacity < that.minOpacity) { + opacity = that.minOpacity + } + let fullOpacity = opacity * 3 + if (fullOpacity > 1) { + fullOpacity = 1 + } + let isAttack = that.vIsAttack + let bgColor = "#276AC6" + if (isAttack) { + bgColor = "#B03A5B" + } + + return { + name: v.name, + value: v.bytes, + percent: parseFloat(v.percent), + itemStyle: { + areaColor: bgColor, + opacity: opacity + }, + emphasis: { + itemStyle: { + areaColor: bgColor, + opacity: fullOpacity + }, + label: { + show: true, + formatter: function (args) { + return args.name + } + } + }, + label: { + show: false, + formatter: function (args) { + if (args.name == that.selectedCountryName) { + return args.name + } + return "" + }, + fontSize: "10px", + color: "#fff", + backgroundColor: "#8B9BD3", + padding: [2, 2, 2, 2] + } + } + }), + nameMap: window.WorldCountriesMap + }] + }) + this.chart.resize() + }, + selectCountry: function (countryName) { + if (this.chart == null) { + return + } + let option = this.chart.getOption() + let that = this + option.series[0].data.forEach(function (v) { + let opacity = v.percent / that.maxPercent + if (opacity < that.minOpacity) { + opacity = that.minOpacity + } + + if (v.name == countryName) { + if (v.isSelected) { + v.itemStyle.opacity = opacity + v.isSelected = false + v.label.show = false + that.selectedCountryName = "" + return + } + v.isSelected = true + that.selectedCountryName = countryName + opacity *= 3 + if (opacity > 1) { + opacity = 1 + } + + // 至少是0.5,让用户能够看清 + if (opacity < 0.5) { + opacity = 0.5 + } + v.itemStyle.opacity = opacity + v.label.show = true + } else { + v.itemStyle.opacity = opacity + v.isSelected = false + v.label.show = false + } + }) + this.chart.setOption(option) + }, + select: function (args) { + this.selectCountry(args.countryName) + } + }, + template: `
+ + + + + + + + + + + + +
+
+
+ +
+ +
+
` +}) + +Vue.component("traffic-map-box-table", { + props: ["v-stats", "v-is-attack", "v-screen-is-narrow"], + data: function () { + return { + stats: this.vStats, + isAttack: this.vIsAttack + } + }, + methods: { + select: function (countryName) { + this.$emit("select", {countryName: countryName}) + } + }, + template: `
+ + + + + + + + + + + + + + + + +
国家/地区排行 
暂无数据
+
+
+
+
{{stat.name}}
+
{{stat.percent}}% + {{stat.formattedCountAttackRequests}} + ({{stat.formattedBytes}})
+
+
` +}) + +// 显示节点的多个集群 +Vue.component("node-clusters-labels", { + props: ["v-primary-cluster", "v-secondary-clusters", "size"], + data: function () { + let cluster = this.vPrimaryCluster + let secondaryClusters = this.vSecondaryClusters + if (secondaryClusters == null) { + secondaryClusters = [] + } + + let labelSize = this.size + if (labelSize == null) { + labelSize = "small" + } + return { + cluster: cluster, + secondaryClusters: secondaryClusters, + labelSize: labelSize + } + }, + template: `` +}) + +// 单个集群选择 +Vue.component("cluster-selector", { + props: ["v-cluster-id"], + mounted: function () { + let that = this + + Tea.action("/clusters/options") + .post() + .success(function (resp) { + that.clusters = resp.data.clusters + }) + }, + data: function () { + let clusterId = this.vClusterId + if (clusterId == null) { + clusterId = 0 + } + return { + clusters: [], + clusterId: clusterId + } + }, + template: `
+ +
` +}) + +Vue.component("node-cluster-combo-box", { + props: ["v-cluster-id"], + data: function () { + let that = this + Tea.action("/clusters/options") + .post() + .success(function (resp) { + that.clusters = resp.data.clusters + }) + return { + clusters: [] + } + }, + methods: { + change: function (item) { + if (item == null) { + this.$emit("change", 0) + } else { + this.$emit("change", item.value) + } + } + }, + template: `
+ +
` +}) + +// 一个节点的多个集群选择器 +Vue.component("node-clusters-selector", { + props: ["v-primary-cluster", "v-secondary-clusters"], + data: function () { + let primaryCluster = this.vPrimaryCluster + + let secondaryClusters = this.vSecondaryClusters + if (secondaryClusters == null) { + secondaryClusters = [] + } + + return { + primaryClusterId: (primaryCluster == null) ? 0 : primaryCluster.id, + secondaryClusterIds: secondaryClusters.map(function (v) { + return v.id + }), + + primaryCluster: primaryCluster, + secondaryClusters: secondaryClusters + } + }, + methods: { + addPrimary: function () { + let that = this + let selectedClusterIds = [this.primaryClusterId].concat(this.secondaryClusterIds) + teaweb.popup("/clusters/selectPopup?selectedClusterIds=" + selectedClusterIds.join(",") + "&mode=single", { + height: "30em", + width: "50em", + callback: function (resp) { + if (resp.data.cluster != null) { + that.primaryCluster = resp.data.cluster + that.primaryClusterId = that.primaryCluster.id + that.notifyChange() + } + } + }) + }, + removePrimary: function () { + this.primaryClusterId = 0 + this.primaryCluster = null + this.notifyChange() + }, + addSecondary: function () { + let that = this + let selectedClusterIds = [this.primaryClusterId].concat(this.secondaryClusterIds) + teaweb.popup("/clusters/selectPopup?selectedClusterIds=" + selectedClusterIds.join(",") + "&mode=multiple", { + height: "30em", + width: "50em", + callback: function (resp) { + if (resp.data.cluster != null) { + that.secondaryClusterIds.push(resp.data.cluster.id) + that.secondaryClusters.push(resp.data.cluster) + that.notifyChange() + } + } + }) + }, + removeSecondary: function (index) { + this.secondaryClusterIds.$remove(index) + this.secondaryClusters.$remove(index) + this.notifyChange() + }, + notifyChange: function () { + this.$emit("change", { + clusterId: this.primaryClusterId + }) + } + }, + template: `
+ + + + + + + + + + + +
主集群 +
+
{{primaryCluster.name}}  
+
+
+ +
+

多个集群配置有冲突时,优先使用主集群配置。

+
从集群 +
+
{{cluster.name}}  
+
+
+ +
+
+
` +}) + +Vue.component("message-media-selector", { + props: ["v-media-type"], + mounted: function () { + let that = this + Tea.action("/admins/recipients/mediaOptions") + .post() + .success(function (resp) { + that.medias = resp.data.medias + + // 初始化简介 + if (that.mediaType.length > 0) { + let media = that.medias.$find(function (_, media) { + return media.type == that.mediaType + }) + if (media != null) { + that.description = media.description + } + } + }) + }, + data: function () { + let mediaType = this.vMediaType + if (mediaType == null) { + mediaType = "" + } + return { + medias: [], + description: "", + mediaType: mediaType + } + }, + watch: { + mediaType: function (v) { + let media = this.medias.$find(function (_, media) { + return media.type == v + }) + if (media == null) { + this.description = "" + } else { + this.description = media.description + } + this.$emit("change", media) + }, + }, + template: `
+ +

+
` +}) + +// 消息接收人设置 +Vue.component("message-receivers-box", { + props: ["v-node-cluster-id"], + mounted: function () { + let that = this + Tea.action("/clusters/cluster/settings/message/selectedReceivers") + .params({ + clusterId: this.clusterId + }) + .post() + .success(function (resp) { + that.receivers = resp.data.receivers + }) + }, + data: function () { + let clusterId = this.vNodeClusterId + if (clusterId == null) { + clusterId = 0 + } + return { + clusterId: clusterId, + receivers: [] + } + }, + methods: { + addReceiver: function () { + let that = this + let recipientIdStrings = [] + let groupIdStrings = [] + this.receivers.forEach(function (v) { + if (v.type == "recipient") { + recipientIdStrings.push(v.id.toString()) + } else if (v.type == "group") { + groupIdStrings.push(v.id.toString()) + } + }) + + teaweb.popup("/clusters/cluster/settings/message/selectReceiverPopup?recipientIds=" + recipientIdStrings.join(",") + "&groupIds=" + groupIdStrings.join(","), { + callback: function (resp) { + that.receivers.push(resp.data) + } + }) + }, + removeReceiver: function (index) { + this.receivers.$remove(index) + } + }, + template: `
+ +
+
+ 分组:{{receiver.name}} ({{receiver.subName}})   +
+
+
+ +
` +}) + +Vue.component("message-recipient-group-selector", { + props: ["v-groups"], + data: function () { + let groups = this.vGroups + if (groups == null) { + groups = [] + } + let groupIds = [] + if (groups.length > 0) { + groupIds = groups.map(function (v) { + return v.id.toString() + }).join(",") + } + + return { + groups: groups, + groupIds: groupIds + } + }, + methods: { + addGroup: function () { + let that = this + teaweb.popup("/admins/recipients/groups/selectPopup?groupIds=" + this.groupIds, { + callback: function (resp) { + that.groups.push(resp.data.group) + that.update() + } + }) + }, + removeGroup: function (index) { + this.groups.$remove(index) + this.update() + }, + update: function () { + let groupIds = [] + if (this.groups.length > 0) { + this.groups.forEach(function (v) { + groupIds.push(v.id) + }) + } + this.groupIds = groupIds.join(",") + } + }, + template: `
+ +
+
+
+ {{group.name}}   +
+
+
+
+ +
` +}) + +Vue.component("message-media-instance-selector", { + props: ["v-instance-id"], + mounted: function () { + let that = this + Tea.action("/admins/recipients/instances/options") + .post() + .success(function (resp) { + that.instances = resp.data.instances + + // 初始化简介 + if (that.instanceId > 0) { + let instance = that.instances.$find(function (_, instance) { + return instance.id == that.instanceId + }) + if (instance != null) { + that.description = instance.description + that.update(instance.id) + } + } + }) + }, + data: function () { + let instanceId = this.vInstanceId + if (instanceId == null) { + instanceId = 0 + } + return { + instances: [], + description: "", + instanceId: instanceId + } + }, + watch: { + instanceId: function (v) { + this.update(v) + } + }, + methods: { + update: function (v) { + let instance = this.instances.$find(function (_, instance) { + return instance.id == v + }) + if (instance == null) { + this.description = "" + } else { + this.description = instance.description + } + this.$emit("change", instance) + } + }, + template: `
+ +

+
` +}) + +Vue.component("message-row", { + props: ["v-message", "v-can-close"], + data: function () { + let paramsJSON = this.vMessage.params + let params = null + if (paramsJSON != null && paramsJSON.length > 0) { + params = JSON.parse(paramsJSON) + } + + return { + message: this.vMessage, + params: params, + isClosing: false + } + }, + methods: { + viewCert: function (certId) { + teaweb.popup("/servers/certs/certPopup?certId=" + certId, { + height: "28em", + width: "48em" + }) + }, + readMessage: function (messageId) { + let that = this + + Tea.action("/messages/readPage") + .params({"messageIds": [messageId]}) + .post() + .success(function () { + // 刷新父级页面Badge + if (window.parent.Tea != null && window.parent.Tea.Vue != null) { + window.parent.Tea.Vue.checkMessagesOnce() + } + + // 刷新当前页面 + if (that.vCanClose && typeof (NotifyPopup) != "undefined") { + that.isClosing = true + setTimeout(function () { + NotifyPopup({}) + }, 1000) + } else { + teaweb.reload() + } + }) + } + }, + template: `
+ + + + + + + +
+ {{message.datetime}} + + | + 集群:{{message.cluster.name}} + DNS集群:{{message.cluster.name}} + + + | + 节点:{{message.node.name}} + DNS节点:{{message.node.name}} + + +
+ {{message.body}} + + + + + + + + + + + + + + + + + +
+ 去审核 +
+
+
+
` +}) + +// 选择多个线路 +Vue.component("ns-routes-selector", { + props: ["v-routes"], + mounted: function () { + let that = this + Tea.action("/ns/routes/options") + .post() + .success(function (resp) { + that.routes = resp.data.routes + }) + }, + data: function () { + let selectedRoutes = this.vRoutes + if (selectedRoutes == null) { + selectedRoutes = [] + } + + return { + routeCode: "default", + routes: [], + isAdding: false, + routeType: "default", + selectedRoutes: selectedRoutes + } + }, + watch: { + routeType: function (v) { + this.routeCode = "" + let that = this + this.routes.forEach(function (route) { + if (route.type == v && that.routeCode.length == 0) { + that.routeCode = route.code + } + }) + } + }, + methods: { + add: function () { + this.isAdding = true + this.routeType = "default" + this.routeCode = "default" + }, + cancel: function () { + this.isAdding = false + }, + confirm: function () { + if (this.routeCode.length == 0) { + return + } + + let that = this + this.routes.forEach(function (v) { + if (v.code == that.routeCode) { + that.selectedRoutes.push(v) + } + }) + this.cancel() + }, + remove: function (index) { + this.selectedRoutes.$remove(index) + } + } + , + template: `
+
+
+ + {{route.name}}   +
+
+
+
+
+
+ +
+ +
+ +
+ +
+ +   +
+
+
+ +
` +}) + +// 递归DNS设置 +Vue.component("ns-recursion-config-box", { + props: ["v-recursion-config"], + data: function () { + let recursion = this.vRecursionConfig + if (recursion == null) { + recursion = { + isOn: false, + hosts: [], + allowDomains: [], + denyDomains: [], + useLocalHosts: false + } + } + if (recursion.hosts == null) { + recursion.hosts = [] + } + if (recursion.allowDomains == null) { + recursion.allowDomains = [] + } + if (recursion.denyDomains == null) { + recursion.denyDomains = [] + } + return { + config: recursion, + hostIsAdding: false, + host: "", + updatingHost: null + } + }, + methods: { + changeHosts: function (hosts) { + this.config.hosts = hosts + }, + changeAllowDomains: function (domains) { + this.config.allowDomains = domains + }, + changeDenyDomains: function (domains) { + this.config.denyDomains = domains + }, + removeHost: function (index) { + this.config.hosts.$remove(index) + }, + addHost: function () { + this.updatingHost = null + this.host = "" + this.hostIsAdding = !this.hostIsAdding + if (this.hostIsAdding) { + var that = this + setTimeout(function () { + let hostRef = that.$refs.hostRef + if (hostRef != null) { + hostRef.focus() + } + }, 200) + } + }, + updateHost: function (host) { + this.updatingHost = host + this.host = host.host + this.hostIsAdding = !this.hostIsAdding + + if (this.hostIsAdding) { + var that = this + setTimeout(function () { + let hostRef = that.$refs.hostRef + if (hostRef != null) { + hostRef.focus() + } + }, 200) + } + }, + confirmHost: function () { + if (this.host.length == 0) { + teaweb.warn("请输入DNS地址") + return + } + + // TODO 校验Host + // TODO 可以输入端口号 + // TODO 可以选择协议 + + this.hostIsAdding = false + if (this.updatingHost == null) { + this.config.hosts.push({ + host: this.host + }) + } else { + this.updatingHost.host = this.host + } + }, + cancelHost: function () { + this.hostIsAdding = false + } + }, + template: `
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
是否启用 +
+ + +
+

启用后,如果找不到某个域名的解析记录,则向上一级DNS查找。

+
从节点本机读取
上级DNS主机
+
+ + +
+

选中后,节点会试图从/etc/resolv.conf文件中读取DNS配置。

+
上级DNS主机地址 * +
+
+ {{host.host}}   + + +
+
+
+
+
+
+ +
+
+   +
+
+
+
+ +
+
允许的域名 +

支持星号通配符,比如*.example.org

+
不允许的域名 + +

支持星号通配符,比如*.example.org。优先级比允许的域名高。

+
+
+
` +}) + +Vue.component("ns-access-log-ref-box", { + props: ["v-access-log-ref", "v-is-parent"], + data: function () { + let config = this.vAccessLogRef + if (config == null) { + config = { + isOn: false, + isPrior: false, + logMissingDomains: false + } + } + if (typeof (config.logMissingDomains) == "undefined") { + config.logMissingDomains = false + } + return { + config: config + } + }, + template: `
+ + + + + + + + + + + + + +
是否启用 + +
记录所有访问 + +

包括对没有在系统里创建的域名访问。

+
+
+
` +}) + +Vue.component("ns-route-ranges-box", { + props: ["v-ranges"], + data: function () { + let ranges = this.vRanges + if (ranges == null) { + ranges = [] + } + return { + ranges: ranges, + isAdding: false, + + // IP范围 + ipRangeFrom: "", + ipRangeTo: "" + } + }, + methods: { + add: function () { + this.isAdding = true + let that = this + setTimeout(function () { + that.$refs.ipRangeFrom.focus() + }, 100) + }, + remove: function (index) { + this.ranges.$remove(index) + }, + cancelIPRange: function () { + this.isAdding = false + this.ipRangeFrom = "" + this.ipRangeTo = "" + }, + confirmIPRange: function () { + // 校验IP + let that = this + this.ipRangeFrom = this.ipRangeFrom.trim() + if (!this.validateIP(this.ipRangeFrom)) { + teaweb.warn("开始IP填写错误", function () { + that.$refs.ipRangeFrom.focus() + }) + return + } + + this.ipRangeTo = this.ipRangeTo.trim() + if (!this.validateIP(this.ipRangeTo)) { + teaweb.warn("结束IP填写错误", function () { + that.$refs.ipRangeTo.focus() + }) + return + } + + this.ranges.push({ + type: "ipRange", + params: { + ipFrom: this.ipRangeFrom, + ipTo: this.ipRangeTo + } + }) + this.cancelIPRange() + }, + validateIP: function (ip) { + if (!ip.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/)) { + return false + } + let pieces = ip.split(".") + let isOk = true + pieces.forEach(function (v) { + let v1 = parseInt(v) + if (v1 > 255) { + isOk = false + } + }) + return isOk + } + }, + template: `
+ +
+
+ IP范围: + {{range.params.ipFrom}} - {{range.params.ipTo}}   +
+
+
+ + +
+
+
+ +
+
-
+
+ +
+
+   + +
+
+
+ + +
` +}) + +// 选择单一线路 +Vue.component("ns-route-selector", { + props: ["v-route-code"], + mounted: function () { + let that = this + Tea.action("/ns/routes/options") + .post() + .success(function (resp) { + that.routes = resp.data.routes + }) + }, + data: function () { + let routeCode = this.vRouteCode + if (routeCode == null) { + routeCode = "" + } + return { + routeCode: routeCode, + routes: [] + } + }, + template: `
+
+ +
+
` +}) + +Vue.component("ns-user-selector", { + mounted: function () { + let that = this + + Tea.action("/ns/users/options") + .post() + .success(function (resp) { + that.users = resp.data.users + }) + }, + props: ["v-user-id"], + data: function () { + let userId = this.vUserId + if (userId == null) { + userId = 0 + } + return { + users: [], + userId: userId + } + }, + template: `
+ +
` +}) + +Vue.component("ns-access-log-box", { + props: ["v-access-log", "v-keyword"], + data: function () { + let accessLog = this.vAccessLog + return { + accessLog: accessLog + } + }, + methods: { + showLog: function () { + let that = this + let requestId = this.accessLog.requestId + this.$parent.$children.forEach(function (v) { + if (v.deselect != null) { + v.deselect() + } + }) + this.select() + + teaweb.popup("/ns/clusters/accessLogs/viewPopup?requestId=" + requestId, { + width: "50em", + height: "24em", + onClose: function () { + that.deselect() + } + }) + }, + select: function () { + this.$refs.box.parentNode.style.cssText = "background: rgba(0, 0, 0, 0.1)" + }, + deselect: function () { + this.$refs.box.parentNode.style.cssText = "" + } + }, + template: `
+ [{{accessLog.region}}] {{accessLog.remoteAddr}} [{{accessLog.timeLocal}}] [{{accessLog.networking}}] {{accessLog.questionType}} {{accessLog.questionName}} -> {{accessLog.recordType}} {{accessLog.recordValue}} +
+ 线路: {{route.name}} + 递归DNS +
+
+ 错误:[{{accessLog.error}}] +
+
` +}) + +Vue.component("ns-cluster-selector", { + props: ["v-cluster-id"], + mounted: function () { + let that = this + + Tea.action("/ns/clusters/options") + .post() + .success(function (resp) { + that.clusters = resp.data.clusters + }) + }, + data: function () { + let clusterId = this.vClusterId + if (clusterId == null) { + clusterId = 0 + } + return { + clusters: [], + clusterId: clusterId + } + }, + template: `
+ +
` +}) + +Vue.component("plan-user-selector", { + mounted: function () { + let that = this + + Tea.action("/plans/users/options") + .post() + .success(function (resp) { + that.users = resp.data.users + }) + }, + props: ["v-user-id"], + data: function () { + let userId = this.vUserId + if (userId == null) { + userId = 0 + } + return { + users: [], + userId: userId + } + }, + watch: { + userId: function (v) { + this.$emit("change", v) + } + }, + template: `
+ +
` +}) + +Vue.component("plan-price-view", { + props: ["v-plan"], + data: function () { + return { + plan: this.vPlan + } + }, + template: `
+ + 按时间周期计费 +
+ + 月度:¥{{plan.monthlyPrice}}元
+ 季度:¥{{plan.seasonallyPrice}}元
+ 年度:¥{{plan.yearlyPrice}}元 +
+
+
+ + 按流量计费 +
+ 基础价格:¥{{plan.trafficPrice.base}}元/GB +
+
+
+ 按{{plan.bandwidthPrice.percentile}}th带宽计费 +
+
+ {{range.minMB}} - {{range.maxMB}}MB: {{range.pricePerMB}}元/MB +
+
+
+
` +}) + +Vue.component("plan-bandwidth-ranges", { + props: ["v-ranges"], + data: function () { + let ranges = this.vRanges + if (ranges == null) { + ranges = [] + } + return { + ranges: ranges, + isAdding: false, + + minMB: "", + maxMB: "", + pricePerMB: "", + addingRange: { + minMB: 0, + maxMB: 0, + pricePerMB: 0, + totalPrice: 0 + } + } + }, + methods: { + add: function () { + this.isAdding = !this.isAdding + let that = this + setTimeout(function () { + that.$refs.minMB.focus() + }) + }, + cancelAdding: function () { + this.isAdding = false + }, + confirm: function () { + this.isAdding = false + this.minMB = "" + this.maxMB = "" + this.pricePerMB = "" + this.ranges.push(this.addingRange) + this.ranges.$sort(function (v1, v2) { + if (v1.minMB < v2.minMB) { + return -1 + } + if (v1.minMB == v2.minMB) { + return 0 + } + return 1 + }) + this.change() + this.addingRange = { + minMB: 0, + maxMB: 0, + pricePerMB: 0, + totalPrice: 0 + } + }, + remove: function (index) { + this.ranges.$remove(index) + this.change() + }, + change: function () { + this.$emit("change", this.ranges) + } + }, + watch: { + minMB: function (v) { + let minMB = parseInt(v.toString()) + if (isNaN(minMB) || minMB < 0) { + minMB = 0 + } + this.addingRange.minMB = minMB + }, + maxMB: function (v) { + let maxMB = parseInt(v.toString()) + if (isNaN(maxMB) || maxMB < 0) { + maxMB = 0 + } + this.addingRange.maxMB = maxMB + }, + pricePerMB: function (v) { + let pricePerMB = parseFloat(v.toString()) + if (isNaN(pricePerMB) || pricePerMB < 0) { + pricePerMB = 0 + } + this.addingRange.pricePerMB = pricePerMB + } + }, + template: `
+ +
+
+ {{range.minMB}}MB - {{range.maxMB}}MB   价格:{{range.pricePerMB}}元/MB +   +
+
+
+ + +
+ + + + + + + + + + + + + +
带宽下限 +
+ + MB +
+
带宽上限 +
+ + MB +
+

如果填0,表示上不封顶。

+
单位价格 +
+ + 元/MB +
+
+   + +
+ + +
+ +
+
` +}) + +// 套餐价格配置 +Vue.component("plan-price-config-box", { + props: ["v-price-type", "v-monthly-price", "v-seasonally-price", "v-yearly-price", "v-traffic-price", "v-bandwidth-price", "v-disable-period"], + data: function () { + let priceType = this.vPriceType + if (priceType == null) { + priceType = "bandwidth" + } + + // 按时间周期计费 + let monthlyPriceNumber = 0 + let monthlyPrice = this.vMonthlyPrice + if (monthlyPrice == null || monthlyPrice <= 0) { + monthlyPrice = "" + } else { + monthlyPrice = monthlyPrice.toString() + monthlyPriceNumber = parseFloat(monthlyPrice) + if (isNaN(monthlyPriceNumber)) { + monthlyPriceNumber = 0 + } + } + + let seasonallyPriceNumber = 0 + let seasonallyPrice = this.vSeasonallyPrice + if (seasonallyPrice == null || seasonallyPrice <= 0) { + seasonallyPrice = "" + } else { + seasonallyPrice = seasonallyPrice.toString() + seasonallyPriceNumber = parseFloat(seasonallyPrice) + if (isNaN(seasonallyPriceNumber)) { + seasonallyPriceNumber = 0 + } + } + + let yearlyPriceNumber = 0 + let yearlyPrice = this.vYearlyPrice + if (yearlyPrice == null || yearlyPrice <= 0) { + yearlyPrice = "" + } else { + yearlyPrice = yearlyPrice.toString() + yearlyPriceNumber = parseFloat(yearlyPrice) + if (isNaN(yearlyPriceNumber)) { + yearlyPriceNumber = 0 + } + } + + // 按流量计费 + let trafficPrice = this.vTrafficPrice + let trafficPriceBaseNumber = 0 + if (trafficPrice != null) { + trafficPriceBaseNumber = trafficPrice.base + } else { + trafficPrice = { + base: 0 + } + } + let trafficPriceBase = "" + if (trafficPriceBaseNumber > 0) { + trafficPriceBase = trafficPriceBaseNumber.toString() + } + + // 按带宽计费 + let bandwidthPrice = this.vBandwidthPrice + if (bandwidthPrice == null) { + bandwidthPrice = { + percentile: 95, + ranges: [] + } + } else if (bandwidthPrice.ranges == null) { + bandwidthPrice.ranges = [] + } + + return { + priceType: priceType, + monthlyPrice: monthlyPrice, + seasonallyPrice: seasonallyPrice, + yearlyPrice: yearlyPrice, + + monthlyPriceNumber: monthlyPriceNumber, + seasonallyPriceNumber: seasonallyPriceNumber, + yearlyPriceNumber: yearlyPriceNumber, + + trafficPriceBase: trafficPriceBase, + trafficPrice: trafficPrice, + + bandwidthPrice: bandwidthPrice, + bandwidthPercentile: bandwidthPrice.percentile + } + }, + methods: { + changeBandwidthPriceRanges: function (ranges) { + this.bandwidthPrice.ranges = ranges + } + }, + watch: { + monthlyPrice: function (v) { + let price = parseFloat(v) + if (isNaN(price)) { + price = 0 + } + this.monthlyPriceNumber = price + }, + seasonallyPrice: function (v) { + let price = parseFloat(v) + if (isNaN(price)) { + price = 0 + } + this.seasonallyPriceNumber = price + }, + yearlyPrice: function (v) { + let price = parseFloat(v) + if (isNaN(price)) { + price = 0 + } + this.yearlyPriceNumber = price + }, + trafficPriceBase: function (v) { + let price = parseFloat(v) + if (isNaN(price)) { + price = 0 + } + this.trafficPrice.base = price + }, + bandwidthPercentile: function (v) { + let percentile = parseInt(v) + if (isNaN(percentile) || percentile <= 0) { + percentile = 95 + } else if (percentile > 100) { + percentile = 100 + } + this.bandwidthPrice.percentile = percentile + } + }, + template: `
+ + + + + + + +
+  按带宽   +  按流量   +  按时间周期 +
+ + +
+
+ + + + + + + + + + + + + +
月度价格 +
+ + +
+
季度价格 +
+ + +
+
年度价格 +
+ + +
+
+
+ + +
+
+ + + + + +
基础流量费用 * +
+ + 元/GB +
+
+
+ + +
+
+ + + + + + + + + +
带宽百分位 * +
+ + th +
+
带宽价格 + +
+
+
` +}) + +Vue.component("http-stat-config-box", { + props: ["v-stat-config", "v-is-location", "v-is-group"], + data: function () { + let stat = this.vStatConfig + if (stat == null) { + stat = { + isPrior: false, + isOn: false + } + } + return { + stat: stat + } + }, + template: `
+ + + + + + + + + +
是否开启统计 +
+ + +
+
+
+
` +}) + +Vue.component("http-request-conds-box", { + props: ["v-conds"], + data: function () { + let conds = this.vConds + if (conds == null) { + conds = { + isOn: true, + connector: "or", + groups: [] + } + } + return { + conds: conds, + components: window.REQUEST_COND_COMPONENTS + } + }, + methods: { + change: function () { + this.$emit("change", this.conds) + }, + addGroup: function () { + window.UPDATING_COND_GROUP = null + + let that = this + teaweb.popup("/servers/server/settings/conds/addGroupPopup", { + height: "30em", + callback: function (resp) { + that.conds.groups.push(resp.data.group) + that.change() + } + }) + }, + updateGroup: function (groupIndex, group) { + window.UPDATING_COND_GROUP = group + let that = this + teaweb.popup("/servers/server/settings/conds/addGroupPopup", { + height: "30em", + callback: function (resp) { + Vue.set(that.conds.groups, groupIndex, resp.data.group) + that.change() + } + }) + }, + removeGroup: function (groupIndex) { + let that = this + teaweb.confirm("确定要删除这一组条件吗?", function () { + that.conds.groups.$remove(groupIndex) + that.change() + }) + }, + typeName: function (cond) { + let c = this.components.$find(function (k, v) { + return v.type == cond.type + }) + if (c != null) { + return c.name; + } + return cond.param + " " + cond.operator + } + }, + template: `
+ +
+ + + + + + +
分组{{groupIndex+1}} + + + {{cond.param}} {{cond.operator}} + {{typeName(cond)}}: + {{cond.value}} + + + + {{group.connector}}   + + + +
+
+
+ + + + + + + +
分组之间关系 + +

+ 只要满足其中一个条件分组即可。 + 需要满足所有条件分组。 +

+
+ +
+ +
+
+
` +}) + +Vue.component("ssl-config-box", { + props: ["v-ssl-policy", "v-protocol", "v-server-id"], + created: function () { + let that = this + setTimeout(function () { + that.sortableCipherSuites() + }, 100) + }, + data: function () { + let policy = this.vSslPolicy + if (policy == null) { + policy = { + id: 0, + isOn: true, + certRefs: [], + certs: [], + clientCARefs: [], + clientCACerts: [], + clientAuthType: 0, + minVersion: "TLS 1.1", + hsts: null, + cipherSuitesIsOn: false, + cipherSuites: [], + http2Enabled: true, + ocspIsOn: false + } + } else { + if (policy.certRefs == null) { + policy.certRefs = [] + } + if (policy.certs == null) { + policy.certs = [] + } + if (policy.clientCARefs == null) { + policy.clientCARefs = [] + } + if (policy.clientCACerts == null) { + policy.clientCACerts = [] + } + if (policy.cipherSuites == null) { + policy.cipherSuites = [] + } + } + + let hsts = policy.hsts + if (hsts == null) { + hsts = { + isOn: false, + maxAge: 31536000, + includeSubDomains: false, + preload: false, + domains: [] + } + } + + return { + policy: policy, + + // hsts + hsts: hsts, + hstsOptionsVisible: false, + hstsDomainAdding: false, + addingHstsDomain: "", + hstsDomainEditingIndex: -1, + + // 相关数据 + allVersions: window.SSL_ALL_VERSIONS, + allCipherSuites: window.SSL_ALL_CIPHER_SUITES.$copy(), + modernCipherSuites: window.SSL_MODERN_CIPHER_SUITES, + intermediateCipherSuites: window.SSL_INTERMEDIATE_CIPHER_SUITES, + allClientAuthTypes: window.SSL_ALL_CLIENT_AUTH_TYPES, + cipherSuitesVisible: false, + + // 高级选项 + moreOptionsVisible: false + } + }, + watch: { + hsts: { + deep: true, + handler: function () { + this.policy.hsts = this.hsts + } + } + }, + methods: { + // 删除证书 + removeCert: function (index) { + let that = this + teaweb.confirm("确定删除此证书吗?证书数据仍然保留,只是当前服务不再使用此证书。", function () { + that.policy.certRefs.$remove(index) + that.policy.certs.$remove(index) + }) + }, + + // 选择证书 + selectCert: function () { + let that = this + let selectedCertIds = [] + if (this.policy != null && this.policy.certs.length > 0) { + this.policy.certs.forEach(function (cert) { + selectedCertIds.push(cert.id.toString()) + }) + } + teaweb.popup("/servers/certs/selectPopup?selectedCertIds=" + selectedCertIds, { + width: "50em", + height: "30em", + callback: function (resp) { + that.policy.certRefs.push(resp.data.certRef) + that.policy.certs.push(resp.data.cert) + } + }) + }, + + // 上传证书 + uploadCert: function () { + let that = this + teaweb.popup("/servers/certs/uploadPopup", { + height: "28em", + callback: function (resp) { + teaweb.success("上传成功", function () { + that.policy.certRefs.push(resp.data.certRef) + that.policy.certs.push(resp.data.cert) + }) + } + }) + }, + + // 申请证书 + requestCert: function () { + // 已经在证书中的域名 + let excludeServerNames = [] + if (this.policy != null && this.policy.certs.length > 0) { + this.policy.certs.forEach(function (cert) { + excludeServerNames.$pushAll(cert.dnsNames) + }) + } + + let that = this + teaweb.popup("/servers/server/settings/https/requestCertPopup?serverId=" + this.vServerId + "&excludeServerNames=" + excludeServerNames.join(","), { + callback: function () { + that.policy.certRefs.push(resp.data.certRef) + that.policy.certs.push(resp.data.cert) + } + }) + }, + + // 更多选项 + changeOptionsVisible: function () { + this.moreOptionsVisible = !this.moreOptionsVisible + }, + + // 格式化时间 + formatTime: function (timestamp) { + return new Date(timestamp * 1000).format("Y-m-d") + }, + + // 格式化加密套件 + formatCipherSuite: function (cipherSuite) { + return cipherSuite.replace(/(AES|3DES)/, "$1") + }, + + // 添加单个套件 + addCipherSuite: function (cipherSuite) { + if (!this.policy.cipherSuites.$contains(cipherSuite)) { + this.policy.cipherSuites.push(cipherSuite) + } + this.allCipherSuites.$removeValue(cipherSuite) + }, + + // 删除单个套件 + removeCipherSuite: function (cipherSuite) { + let that = this + teaweb.confirm("确定要删除此套件吗?", function () { + that.policy.cipherSuites.$removeValue(cipherSuite) + that.allCipherSuites = window.SSL_ALL_CIPHER_SUITES.$findAll(function (k, v) { + return !that.policy.cipherSuites.$contains(v) + }) + }) + }, + + // 清除所选套件 + clearCipherSuites: function () { + let that = this + teaweb.confirm("确定要清除所有已选套件吗?", function () { + that.policy.cipherSuites = [] + that.allCipherSuites = window.SSL_ALL_CIPHER_SUITES.$copy() + }) + }, + + // 批量添加套件 + addBatchCipherSuites: function (suites) { + var that = this + teaweb.confirm("确定要批量添加套件?", function () { + suites.$each(function (k, v) { + if (that.policy.cipherSuites.$contains(v)) { + return + } + that.policy.cipherSuites.push(v) + }) + }) + }, + + /** + * 套件拖动排序 + */ + sortableCipherSuites: function () { + var box = document.querySelector(".cipher-suites-box") + Sortable.create(box, { + draggable: ".label", + handle: ".icon.handle", + onStart: function () { + + }, + onUpdate: function (event) { + + } + }) + }, + + // 显示所有套件 + showAllCipherSuites: function () { + this.cipherSuitesVisible = !this.cipherSuitesVisible + }, + + // 显示HSTS更多选项 + showMoreHSTS: function () { + this.hstsOptionsVisible = !this.hstsOptionsVisible; + if (this.hstsOptionsVisible) { + this.changeHSTSMaxAge() + } + }, + + // 监控HSTS有效期修改 + changeHSTSMaxAge: function () { + var v = this.hsts.maxAge + if (isNaN(v)) { + this.hsts.days = "-" + return + } + this.hsts.days = parseInt(v / 86400) + if (isNaN(this.hsts.days)) { + this.hsts.days = "-" + } else if (this.hsts.days < 0) { + this.hsts.days = "-" + } + }, + + // 设置HSTS有效期 + setHSTSMaxAge: function (maxAge) { + this.hsts.maxAge = maxAge + this.changeHSTSMaxAge() + }, + + // 添加HSTS域名 + addHstsDomain: function () { + this.hstsDomainAdding = true + this.hstsDomainEditingIndex = -1 + let that = this + setTimeout(function () { + that.$refs.addingHstsDomain.focus() + }, 100) + }, + + // 修改HSTS域名 + editHstsDomain: function (index) { + this.hstsDomainEditingIndex = index + this.addingHstsDomain = this.hsts.domains[index] + this.hstsDomainAdding = true + let that = this + setTimeout(function () { + that.$refs.addingHstsDomain.focus() + }, 100) + }, + + // 确认HSTS域名添加 + confirmAddHstsDomain: function () { + this.addingHstsDomain = this.addingHstsDomain.trim() + if (this.addingHstsDomain.length == 0) { + return; + } + if (this.hstsDomainEditingIndex > -1) { + this.hsts.domains[this.hstsDomainEditingIndex] = this.addingHstsDomain + } else { + this.hsts.domains.push(this.addingHstsDomain) + } + this.cancelHstsDomainAdding() + }, + + // 取消HSTS域名添加 + cancelHstsDomainAdding: function () { + this.hstsDomainAdding = false + this.addingHstsDomain = "" + this.hstsDomainEditingIndex = -1 + }, + + // 删除HSTS域名 + removeHstsDomain: function (index) { + this.cancelHstsDomainAdding() + this.hsts.domains.$remove(index) + }, + + // 选择客户端CA证书 + selectClientCACert: function () { + let that = this + teaweb.popup("/servers/certs/selectPopup?isCA=1", { + width: "50em", + height: "30em", + callback: function (resp) { + that.policy.clientCARefs.push(resp.data.certRef) + that.policy.clientCACerts.push(resp.data.cert) + } + }) + }, + + // 上传CA证书 + uploadClientCACert: function () { + let that = this + teaweb.popup("/servers/certs/uploadPopup?isCA=1", { + height: "28em", + callback: function (resp) { + teaweb.success("上传成功", function () { + that.policy.clientCARefs.push(resp.data.certRef) + that.policy.clientCACerts.push(resp.data.cert) + }) + } + }) + }, + + // 删除客户端CA证书 + removeClientCACert: function (index) { + let that = this + teaweb.confirm("确定删除此证书吗?证书数据仍然保留,只是当前服务不再使用此证书。", function () { + that.policy.clientCARefs.$remove(index) + that.policy.clientCACerts.$remove(index) + }) + } + }, + template: `
+

SSL/TLS相关配置

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
启用HTTP/2 +
+ + +
+
选择证书 +
+
+ {{cert.name}} / {{cert.dnsNames}} / 有效至{{formatTime(cert.timeEndAt)}}   +
+
+
+
+ 选择或上传证书后HTTPSTLS服务才能生效。 +
+
+   +   + +
TLS最低版本 + +
加密算法套件(CipherSuites) +
+ + +
+
+
+
+ 已添加套件({{policy.cipherSuites.length}}): +
+ +   + +
+
+ + + +

点击可选套件添加。

+
+
是否开启HSTS +
+ + +
+

+ 开启后,会自动在响应Header中加入 + Strict-Transport-Security: + ... + max-age={{hsts.maxAge}} + ; includeSubDomains + ; preload + + + 修改 + +

+
HSTS有效时间(max-age) +
+
+ +
+
+ 秒 +
+
{{hsts.days}}天
+
+

+ [1年/365天]     + [6个月/182.5天]     + [1个月/30天] +

+
HSTS包含子域名(includeSubDomains) +
+ + +
+
HSTS预加载(preload) +
+ + +
+
HSTS生效的域名 +
+ {{domain}} +   + + + +
+
+
+ +
+
+ +   取消 +
+
+
+ +
+

如果没有设置域名的话,则默认支持所有的域名。

+
OCSP Stapling +

选中表示启用OCSP Stapling。

+
客户端认证方式 + +
客户端认证CA证书 +
+
+ {{cert.name}} / {{cert.dnsNames}} / 有效至{{formatTime(cert.timeEndAt)}}   +
+
+
+   + +

用来校验客户端证书以增强安全性,通常不需要设置。

+
+
+
` +}) + +// Action列表 +Vue.component("http-firewall-actions-view", { + props: ["v-actions"], + template: `
+
+ {{action.name}} ({{action.code.toUpperCase()}}) +
+
` +}) + +Vue.component("http-request-scripts-config-box", { + props: ["vRequestScriptsConfig", "v-is-location"], + data: function () { + let config = this.vRequestScriptsConfig + if (config == null) { + config = {} + } + return { + config: config + } + }, + methods: { + changeInitGroup: function (group) { + this.config.initGroup = group + this.$forceUpdate() + }, + changeRequestGroup: function (group) { + this.config.requestGroup = group + this.$forceUpdate() + } + }, + template: `
+ +
+

请求初始化

+

在请求刚初始化时调用,此时自定义Header等尚未生效。

+
+ +
+

准备发送请求

+

在准备执行请求或者转发请求之前调用,此时自定义Header、源站等已准备好。

+
+ +
+
+
` +}) + +// 显示WAF规则的标签 +Vue.component("http-firewall-rule-label", { + props: ["v-rule"], + data: function () { + return { + rule: this.vRule + } + }, + methods: { + showErr: function (err) { + + teaweb.popupTip("规则校验错误,请修正:" + teaweb.encodeHTML(err) + "") + }, + + }, + template: `
+
+ {{rule.name}}[{{rule.param}}] + + + + {{rule.checkpointOptions.period}}秒/{{rule.checkpointOptions.threshold}}请求 + + + + + {{rule.checkpointOptions.allowDomains}} + + + + | {{paramFilter.code}} + {{rule.operator}} + {{rule.value}} + + + + ({{rule.description}}) + + 规则错误 +
+
` +}) + +// 缓存条件列表 +Vue.component("http-cache-refs-box", { + props: ["v-cache-refs"], + data: function () { + let refs = this.vCacheRefs + if (refs == null) { + refs = [] + } + return { + refs: refs + } + }, + methods: { + timeUnitName: function (unit) { + switch (unit) { + case "ms": + return "毫秒" + case "second": + return "秒" + case "minute": + return "分钟" + case "hour": + return "小时" + case "day": + return "天" + case "week": + return "周 " + } + return unit + } + }, + template: `
+ + +

暂时还没有缓存条件。

+
+ + + + + + + + + + + + + +
缓存条件分组关系缓存时间
+ + + {{cacheRef.minSize.count}}{{cacheRef.minSize.unit}} + - {{cacheRef.maxSize.count}}{{cacheRef.maxSize.unit}} + + 0 - {{cacheRef.maxSize.count}}{{cacheRef.maxSize.unit}} + {{cacheRef.methods.join(", ")}} + Expires + 状态码:{{cacheRef.status.map(function(v) {return v.toString()}).join(", ")}} + 区间缓存 + + + + + {{cacheRef.life.count}} {{timeUnitName(cacheRef.life.unit)}} + 不缓存 +
+
+
+
` +}) + +Vue.component("ssl-certs-box", { + props: [ + "v-certs", // 证书列表 + "v-cert", // 单个证书 + "v-protocol", // 协议:https|tls + "v-view-size", // 弹窗尺寸:normal, mini + "v-single-mode", // 单证书模式 + "v-description" // 描述文字 + ], + data: function () { + let certs = this.vCerts + if (certs == null) { + certs = [] + } + if (this.vCert != null) { + certs.push(this.vCert) + } + + let description = this.vDescription + if (description == null || typeof (description) != "string") { + description = "" + } + + return { + certs: certs, + description: description + } + }, + methods: { + certIds: function () { + return this.certs.map(function (v) { + return v.id + }) + }, + // 删除证书 + removeCert: function (index) { + let that = this + teaweb.confirm("确定删除此证书吗?证书数据仍然保留,只是当前服务不再使用此证书。", function () { + that.certs.$remove(index) + }) + }, + + // 选择证书 + selectCert: function () { + let that = this + let width = "50em" + let height = "30em" + let viewSize = this.vViewSize + if (viewSize == null) { + viewSize = "normal" + } + if (viewSize == "mini") { + width = "35em" + height = "20em" + } + teaweb.popup("/servers/certs/selectPopup?viewSize=" + viewSize, { + width: width, + height: height, + callback: function (resp) { + that.certs.push(resp.data.cert) + } + }) + }, + + // 上传证书 + uploadCert: function () { + let that = this + teaweb.popup("/servers/certs/uploadPopup", { + height: "28em", + callback: function (resp) { + teaweb.success("上传成功", function () { + that.certs.push(resp.data.cert) + }) + } + }) + }, + + // 格式化时间 + formatTime: function (timestamp) { + return new Date(timestamp * 1000).format("Y-m-d") + }, + + // 判断是否显示选择|上传按钮 + buttonsVisible: function () { + return this.vSingleMode == null || !this.vSingleMode || this.certs == null || this.certs.length == 0 + } + }, + template: `
+ +
+
+ {{cert.name}} / {{cert.dnsNames}} / 有效至{{formatTime(cert.timeEndAt)}}   +
+
+
+
+ 选择或上传证书后HTTPSTLS服务才能生效。 + {{description}} +
+
+
+   +   +
+
` +}) + +Vue.component("http-host-redirect-box", { + props: ["v-redirects"], + mounted: function () { + let that = this + sortTable(function (ids) { + let newRedirects = [] + ids.forEach(function (id) { + that.redirects.forEach(function (redirect) { + if (redirect.id == id) { + newRedirects.push(redirect) + } + }) + }) + that.updateRedirects(newRedirects) + }) + }, + data: function () { + let redirects = this.vRedirects + if (redirects == null) { + redirects = [] + } + + let id = 0 + redirects.forEach(function (v) { + id++ + v.id = id + }) + + return { + redirects: redirects, + statusOptions: [ + {"code": 301, "text": "Moved Permanently"}, + {"code": 308, "text": "Permanent Redirect"}, + {"code": 302, "text": "Found"}, + {"code": 303, "text": "See Other"}, + {"code": 307, "text": "Temporary Redirect"} + ], + id: id + } + }, + methods: { + add: function () { + let that = this + window.UPDATING_REDIRECT = null + + teaweb.popup("/servers/server/settings/redirects/createPopup", { + width: "50em", + height: "30em", + callback: function (resp) { + that.id++ + resp.data.redirect.id = that.id + that.redirects.push(resp.data.redirect) + that.change() + } + }) + }, + update: function (index, redirect) { + let that = this + window.UPDATING_REDIRECT = redirect + + teaweb.popup("/servers/server/settings/redirects/createPopup", { + width: "50em", + height: "30em", + callback: function (resp) { + resp.data.redirect.id = redirect.id + Vue.set(that.redirects, index, resp.data.redirect) + that.change() + } + }) + }, + remove: function (index) { + let that = this + teaweb.confirm("确定要删除这条跳转规则吗?", function () { + that.redirects.$remove(index) + that.change() + }) + }, + change: function () { + let that = this + setTimeout(function (){ + that.$emit("change", that.redirects) + }, 100) + }, + updateRedirects: function (newRedirects) { + this.redirects = newRedirects + this.change() + } + }, + template: `
+ + + + [创建] + +
+ +

暂时还没有URL跳转规则。

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
跳转前URL跳转后URL匹配模式HTTP状态码状态操作
+ {{redirect.beforeURL}} +
+ 匹配条件 +
+
->{{redirect.afterURL}} + 匹配前缀 + 正则匹配 + 精准匹配 + + {{redirect.status}} + 默认 + + 修改   + 删除 +
+

所有规则匹配顺序为从上到下,可以拖动左侧的排序。

+
+
+
` +}) + +// 单个缓存条件设置 +Vue.component("http-cache-ref-box", { + props: ["v-cache-ref", "v-is-reverse"], + mounted: function () { + this.$refs.variablesDescriber.update(this.ref.key) + }, + data: function () { + let ref = this.vCacheRef + if (ref == null) { + ref = { + isOn: true, + cachePolicyId: 0, + key: "${scheme}://${host}${requestPath}${isArgs}${args}", + life: {count: 2, unit: "hour"}, + status: [200], + maxSize: {count: 32, unit: "mb"}, + minSize: {count: 0, unit: "kb"}, + skipCacheControlValues: ["private", "no-cache", "no-store"], + skipSetCookie: true, + enableRequestCachePragma: false, + conds: null, + allowChunkedEncoding: true, + allowPartialContent: false, + isReverse: this.vIsReverse, + methods: [], + expiresTime: { + isPrior: false, + isOn: false, + overwrite: true, + autoCalculate: true, + duration: {count: -1, "unit": "hour"} + } + } + } + if (ref.key == null) { + ref.key = "" + } + if (ref.methods == null) { + ref.methods = [] + } + + if (ref.life == null) { + ref.life = {count: 2, unit: "hour"} + } + if (ref.maxSize == null) { + ref.maxSize = {count: 32, unit: "mb"} + } + if (ref.minSize == null) { + ref.minSize = {count: 0, unit: "kb"} + } + return { + ref: ref, + moreOptionsVisible: false + } + }, + methods: { + changeOptionsVisible: function (v) { + this.moreOptionsVisible = v + }, + changeLife: function (v) { + this.ref.life = v + }, + changeMaxSize: function (v) { + this.ref.maxSize = v + }, + changeMinSize: function (v) { + this.ref.minSize = v + }, + changeConds: function (v) { + this.ref.conds = v + }, + changeStatusList: function (list) { + let result = [] + list.forEach(function (status) { + let statusNumber = parseInt(status) + if (isNaN(statusNumber) || statusNumber < 100 || statusNumber > 999) { + return + } + result.push(statusNumber) + }) + this.ref.status = result + }, + changeMethods: function (methods) { + this.ref.methods = methods.map(function (v) { + return v.toUpperCase() + }) + }, + changeKey: function (key) { + this.$refs.variablesDescriber.update(key) + }, + changeExpiresTime: function (expiresTime) { + this.ref.expiresTime = expiresTime + } + }, + template: ` + + 匹配条件分组 * + + + + + + + + 缓存有效期 * + + + + + + 缓存Key * + + +

用来区分不同缓存内容的唯一Key。

+ + + + + + + 请求方法限制 + + +

允许请求的缓存方法,默认支持所有的请求方法。

+ + + + 客户端过期时间(Expires) + + + + + + 可缓存的最大内容尺寸 + + +

内容尺寸如果高于此值则不缓存。

+ + + + 可缓存的最小内容尺寸 + + +

内容尺寸如果低于此值则不缓存。

+ + + + 支持分片内容 + + +

选中后,Gzip等压缩后的Chunked内容可以直接缓存,无需检查内容长度。

+ + + + 支持缓存区间内容 + + +

选中后,支持缓存源站返回的某个区间的内容,该内容通过206 Partial Content状态码返回。此功能目前为试验性质

+ + + + 状态码列表 + + +

允许缓存的HTTP状态码列表。

+ + + + 跳过的Cache-Control值 + + +

当响应的Cache-Control为这些值时不缓存响应内容,而且不区分大小写。

+ + + + 跳过Set-Cookie + +
+ + +
+

选中后,当响应的Header中有Set-Cookie时不缓存响应内容。

+ + + + 支持请求no-cache刷新 + +
+ + +
+

选中后,当请求的Header中含有Pragma: no-cache或Cache-Control: no-cache时,会跳过缓存直接读取源内容。

+ + +` +}) + +// 请求限制 +Vue.component("http-request-limit-config-box", { + props: ["v-request-limit-config", "v-is-group", "v-is-location"], + data: function () { + let config = this.vRequestLimitConfig + if (config == null) { + config = { + isPrior: false, + isOn: false, + maxConns: 0, + maxConnsPerIP: 0, + maxBodySize: { + count: -1, + unit: "kb" + }, + outBandwidthPerConn: { + count: -1, + unit: "kb" + } + } + } + return { + config: config, + maxConns: config.maxConns, + maxConnsPerIP: config.maxConnsPerIP + } + }, + watch: { + maxConns: function (v) { + let conns = parseInt(v, 10) + if (isNaN(conns)) { + this.config.maxConns = 0 + return + } + if (conns < 0) { + this.config.maxConns = 0 + } else { + this.config.maxConns = conns + } + }, + maxConnsPerIP: function (v) { + let conns = parseInt(v, 10) + if (isNaN(conns)) { + this.config.maxConnsPerIP = 0 + return + } + if (conns < 0) { + this.config.maxConnsPerIP = 0 + } else { + this.config.maxConnsPerIP = conns + } + } + }, + methods: { + isOn: function () { + return ((!this.vIsLocation && !this.vIsGroup) || this.config.isPrior) && this.config.isOn + } + }, + template: `
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
是否启用 + +
最大并发连接数 + +

当前服务最大并发连接数。为0表示不限制。

+
单IP最大并发连接数 + +

单IP最大连接数,统计单个IP总连接数时不区分服务。为0表示不限制。

+
单连接带宽限制 + +

客户端单个请求每秒可以读取的下行流量。

+
单请求最大尺寸 + +

单个请求能发送的最大内容尺寸。

+
+
+
` +}) + +Vue.component("http-header-replace-values", { + props: ["v-replace-values"], + data: function () { + let values = this.vReplaceValues + if (values == null) { + values = [] + } + return { + values: values, + isAdding: false, + addingValue: {"pattern": "", "replacement": "", "isCaseInsensitive": false, "isRegexp": false} + } + }, + methods: { + add: function () { + this.isAdding = true + let that = this + setTimeout(function () { + that.$refs.pattern.focus() + }) + }, + remove: function (index) { + this.values.$remove(index) + }, + confirm: function () { + let that = this + if (this.addingValue.pattern.length == 0) { + teaweb.warn("替换前内容不能为空", function () { + that.$refs.pattern.focus() + }) + return + } + + this.values.push(this.addingValue) + this.cancel() + }, + cancel: function () { + this.isAdding = false + this.addingValue = {"pattern": "", "replacement": "", "isCaseInsensitive": false, "isRegexp": false} + } + }, + template: `
+ +
+
+ {{value.pattern}} => {{value.replacement}}[空] + +
+
+
+ + + + + + + + + + + + + +
替换前内容 *
替换后内容
是否忽略大小写 + +
+ +
+   + +
+
+
+ +
+
` +}) + +// 浏览条件列表 +Vue.component("http-request-conds-view", { + props: ["v-conds"], + data: function () { + let conds = this.vConds + if (conds == null) { + conds = { + isOn: true, + connector: "or", + groups: [] + } + } + + let that = this + conds.groups.forEach(function (group) { + group.conds.forEach(function (cond) { + cond.typeName = that.typeName(cond) + }) + }) + + return { + initConds: conds, + version: 0 // 为了让组件能及时更新加入此变量 + } + }, + computed: { + // 之所以使用computed,是因为需要动态更新 + conds: function () { + return this.initConds + } + }, + methods: { + typeName: function (cond) { + let c = window.REQUEST_COND_COMPONENTS.$find(function (k, v) { + return v.type == cond.type + }) + if (c != null) { + return c.name; + } + return cond.param + " " + cond.operator + }, + notifyChange: function () { + this.version++ + let that = this + this.initConds.groups.forEach(function (group) { + group.conds.forEach(function (cond) { + cond.typeName = that.typeName(cond) + }) + }) + } + }, + template: `
+ {{version}} +
+
+ + + {{cond.param}} {{cond.operator}} + {{cond.typeName}}: + {{cond.value}} + + + + {{group.connector}}   + +
+
+ {{group.description}} +
+
+
+
+
` +}) + +Vue.component("http-firewall-config-box", { + props: ["v-firewall-config", "v-is-location", "v-is-group", "v-firewall-policy"], + data: function () { + let firewall = this.vFirewallConfig + if (firewall == null) { + firewall = { + isPrior: false, + isOn: false, + firewallPolicyId: 0 + } + } + + return { + firewall: firewall + } + }, + template: `
+ + + + + + + + + + + + + +
WAF策略 +
{{vFirewallPolicy.name}}   [{{vFirewallPolicy.modeInfo.name}}]  +

使用当前服务所在集群的设置。

+
+ 当前集群没有设置WAF策略,当前配置无法生效。 +
启用WAF +
+ + +
+

启用WAF之后,各项WAF设置才会生效。

+
+
+
` +}) + +// 指标图表 +Vue.component("metric-chart", { + props: ["v-chart", "v-stats", "v-item"], + mounted: function () { + this.load() + }, + data: function () { + let stats = this.vStats + if (stats == null) { + stats = [] + } + if (stats.length > 0) { + let sum = stats.$sum(function (k, v) { + return v.value + }) + if (sum < stats[0].total) { + if (this.vChart.type == "pie") { + stats.push({ + keys: ["其他"], + value: stats[0].total - sum, + total: stats[0].total, + time: stats[0].time + }) + } + } + } + if (this.vChart.maxItems > 0) { + stats = stats.slice(0, this.vChart.maxItems) + } else { + stats = stats.slice(0, 10) + } + + stats.$rsort(function (v1, v2) { + return v1.value - v2.value + }) + + let widthPercent = 100 + if (this.vChart.widthDiv > 0) { + widthPercent = 100 / this.vChart.widthDiv + } + + return { + chart: this.vChart, + stats: stats, + item: this.vItem, + width: widthPercent + "%", + chartId: "metric-chart-" + this.vChart.id, + valueTypeName: (this.vItem != null && this.vItem.valueTypeName != null && this.vItem.valueTypeName.length > 0) ? this.vItem.valueTypeName : "" + } + }, + methods: { + load: function () { + var el = document.getElementById(this.chartId) + if (el == null || el.offsetWidth == 0 || el.offsetHeight == 0) { + setTimeout(this.load, 100) + } else { + this.render(el) + } + }, + render: function (el) { + let chart = echarts.init(el) + window.addEventListener("resize", function () { + chart.resize() + }) + switch (this.chart.type) { + case "pie": + this.renderPie(chart) + break + case "bar": + this.renderBar(chart) + break + case "timeBar": + this.renderTimeBar(chart) + break + case "timeLine": + this.renderTimeLine(chart) + break + case "table": + this.renderTable(chart) + break + } + }, + renderPie: function (chart) { + let values = this.stats.map(function (v) { + return { + name: v.keys[0], + value: v.value + } + }) + let that = this + chart.setOption({ + tooltip: { + show: true, + trigger: "item", + formatter: function (data) { + let stat = that.stats[data.dataIndex] + let percent = 0 + if (stat.total > 0) { + percent = Math.round((stat.value * 100 / stat.total) * 100) / 100 + } + let value = stat.value + switch (that.item.valueType) { + case "byte": + value = teaweb.formatBytes(value) + break + case "count": + value = teaweb.formatNumber(value) + break + } + return stat.keys[0] + "
" + that.valueTypeName + ": " + value + "
占比:" + percent + "%" + } + }, + series: [ + { + name: name, + type: "pie", + data: values, + areaStyle: {}, + color: ["#9DD3E8", "#B2DB9E", "#F39494", "#FBD88A", "#879BD7"] + } + ] + }) + }, + renderTimeBar: function (chart) { + this.stats.$sort(function (v1, v2) { + return (v1.time < v2.time) ? -1 : 1 + }) + let values = this.stats.map(function (v) { + return v.value + }) + + let axis = {unit: "", divider: 1} + switch (this.item.valueType) { + case "count": + axis = teaweb.countAxis(values, function (v) { + return v + }) + break + case "byte": + axis = teaweb.bytesAxis(values, function (v) { + return v + }) + break + } + + let that = this + chart.setOption({ + xAxis: { + data: this.stats.map(function (v) { + return that.formatTime(v.time) + }) + }, + yAxis: { + axisLabel: { + formatter: function (value) { + return value + axis.unit + } + } + }, + tooltip: { + show: true, + trigger: "item", + formatter: function (data) { + let stat = that.stats[data.dataIndex] + let value = stat.value + switch (that.item.valueType) { + case "byte": + value = teaweb.formatBytes(value) + break + } + return that.formatTime(stat.time) + ": " + value + } + }, + grid: { + left: 50, + top: 10, + right: 20, + bottom: 25 + }, + series: [ + { + name: name, + type: "bar", + data: values.map(function (v) { + return v / axis.divider + }), + itemStyle: { + color: "#9DD3E8" + }, + areaStyle: {}, + barWidth: "20em" + } + ] + }) + }, + renderTimeLine: function (chart) { + this.stats.$sort(function (v1, v2) { + return (v1.time < v2.time) ? -1 : 1 + }) + let values = this.stats.map(function (v) { + return v.value + }) + + let axis = {unit: "", divider: 1} + switch (this.item.valueType) { + case "count": + axis = teaweb.countAxis(values, function (v) { + return v + }) + break + case "byte": + axis = teaweb.bytesAxis(values, function (v) { + return v + }) + break + } + + let that = this + chart.setOption({ + xAxis: { + data: this.stats.map(function (v) { + return that.formatTime(v.time) + }) + }, + yAxis: { + axisLabel: { + formatter: function (value) { + return value + axis.unit + } + } + }, + tooltip: { + show: true, + trigger: "item", + formatter: function (data) { + let stat = that.stats[data.dataIndex] + let value = stat.value + switch (that.item.valueType) { + case "byte": + value = teaweb.formatBytes(value) + break + } + return that.formatTime(stat.time) + ": " + value + } + }, + grid: { + left: 50, + top: 10, + right: 20, + bottom: 25 + }, + series: [ + { + name: name, + type: "line", + data: values.map(function (v) { + return v / axis.divider + }), + itemStyle: { + color: "#9DD3E8" + }, + areaStyle: {} + } + ] + }) + }, + renderBar: function (chart) { + let values = this.stats.map(function (v) { + return v.value + }) + let axis = {unit: "", divider: 1} + switch (this.item.valueType) { + case "count": + axis = teaweb.countAxis(values, function (v) { + return v + }) + break + case "byte": + axis = teaweb.bytesAxis(values, function (v) { + return v + }) + break + } + let bottom = 24 + let rotate = 0 + let result = teaweb.xRotation(chart, this.stats.map(function (v) { + return v.keys[0] + })) + if (result != null) { + bottom = result[0] + rotate = result[1] + } + let that = this + chart.setOption({ + xAxis: { + data: this.stats.map(function (v) { + return v.keys[0] + }), + axisLabel: { + interval: 0, + rotate: rotate + } + }, + tooltip: { + show: true, + trigger: "item", + formatter: function (data) { + let stat = that.stats[data.dataIndex] + let percent = 0 + if (stat.total > 0) { + percent = Math.round((stat.value * 100 / stat.total) * 100) / 100 + } + let value = stat.value + switch (that.item.valueType) { + case "byte": + value = teaweb.formatBytes(value) + break + case "count": + value = teaweb.formatNumber(value) + break + } + return stat.keys[0] + "
" + that.valueTypeName + ":" + value + "
占比:" + percent + "%" + } + }, + yAxis: { + axisLabel: { + formatter: function (value) { + return value + axis.unit + } + } + }, + grid: { + left: 40, + top: 10, + right: 20, + bottom: bottom + }, + series: [ + { + name: name, + type: "bar", + data: values.map(function (v) { + return v / axis.divider + }), + itemStyle: { + color: "#9DD3E8" + }, + areaStyle: {}, + barWidth: "20em" + } + ] + }) + + if (this.item.keys != null) { + // IP相关操作 + if (this.item.keys.$contains("${remoteAddr}")) { + let that = this + chart.on("click", function (args) { + let index = that.item.keys.$indexesOf("${remoteAddr}")[0] + let value = that.stats[args.dataIndex].keys[index] + teaweb.popup("/servers/ipbox?ip=" + value, { + width: "50em", + height: "30em" + }) + }) + } + } + }, + renderTable: function (chart) { + let table = ` + + + + + + + ` + let that = this + this.stats.forEach(function (v) { + let value = v.value + switch (that.item.valueType) { + case "byte": + value = teaweb.formatBytes(value) + break + } + table += "" + let percent = 0 + if (v.total > 0) { + percent = Math.round((v.value * 100 / v.total) * 100) / 100 + } + table += "" + table += "" + }) + + table += `
对象数值占比
" + v.keys[0] + "" + value + "
" + percent + "%
` + document.getElementById(this.chartId).innerHTML = table + }, + formatTime: function (time) { + if (time == null) { + return "" + } + switch (this.item.periodUnit) { + case "month": + return time.substring(0, 4) + "-" + time.substring(4, 6) + case "week": + return time.substring(0, 4) + "-" + time.substring(4, 6) + case "day": + return time.substring(0, 4) + "-" + time.substring(4, 6) + "-" + time.substring(6, 8) + case "hour": + return time.substring(0, 4) + "-" + time.substring(4, 6) + "-" + time.substring(6, 8) + " " + time.substring(8, 10) + case "minute": + return time.substring(0, 4) + "-" + time.substring(4, 6) + "-" + time.substring(6, 8) + " " + time.substring(8, 10) + ":" + time.substring(10, 12) + } + return time + } + }, + template: `
+

{{chart.name}} ({{valueTypeName}})

+
+
+
` +}) + +Vue.component("metric-board", { + template: `
` +}) + +Vue.component("http-cache-config-box", { + props: ["v-cache-config", "v-is-location", "v-is-group", "v-cache-policy", "v-web-id"], + data: function () { + let cacheConfig = this.vCacheConfig + if (cacheConfig == null) { + cacheConfig = { + isPrior: false, + isOn: false, + addStatusHeader: true, + addAgeHeader: false, + enableCacheControlMaxAge: false, + cacheRefs: [], + purgeIsOn: false, + purgeKey: "", + disablePolicyRefs: false + } + } + if (cacheConfig.cacheRefs == null) { + cacheConfig.cacheRefs = [] + } + + return { + cacheConfig: cacheConfig, + moreOptionsVisible: false, + enablePolicyRefs: !cacheConfig.disablePolicyRefs + } + }, + watch: { + enablePolicyRefs: function (v) { + this.cacheConfig.disablePolicyRefs = !v + } + }, + methods: { + isOn: function () { + return ((!this.vIsLocation && !this.vIsGroup) || this.cacheConfig.isPrior) && this.cacheConfig.isOn + }, + isPlus: function () { + return Tea.Vue.teaIsPlus + }, + generatePurgeKey: function () { + let r = Math.random().toString() + Math.random().toString() + let s = r.replace(/0\./g, "") + .replace(/\./g, "") + let result = "" + for (let i = 0; i < s.length; i++) { + result += String.fromCharCode(parseInt(s.substring(i, i + 1)) + ((Math.random() < 0.5) ? "a" : "A").charCodeAt(0)) + } + this.cacheConfig.purgeKey = result + }, + showMoreOptions: function () { + this.moreOptionsVisible = !this.moreOptionsVisible + }, + changeStale: function (stale) { + this.cacheConfig.stale = stale + } + }, + template: `
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
缓存策略 +
{{vCachePolicy.name}} +

使用当前服务所在集群的设置。

+
+ 当前集群没有设置缓存策略,当前配置无法生效。 +
启用缓存 +
+ + +
+
+ 收起选项更多选项 +
使用默认缓存条件 + +

选中后使用系统中已经定义的默认缓存条件。

+
添加X-Cache Header + +

选中后自动在响应Header中增加X-Cache: BYPASS|MISS|HIT|PURGE

+
添加Age Header + +

选中后自动在响应Header中增加Age: [存活时间秒数]

+
支持源站控制有效时间 + +

选中后表示支持源站在Header中设置的Cache-Control: max-age=[有效时间秒数]

+
允许PURGE + +

允许使用PURGE方法清除某个URL缓存。

+
PURGE Key * + +

[随机生成]。需要在PURGE方法调用时加入Edge-Purge-Key: {{cacheConfig.purgeKey}} Header。只能包含字符、数字、下划线。

+
+ +
+

过时缓存策略

+ +
+ +
+

缓存条件

+ +
+
+
` +}) + +// 通用Header长度 +let defaultGeneralHeaders = ["Cache-Control", "Connection", "Date", "Pragma", "Trailer", "Transfer-Encoding", "Upgrade", "Via", "Warning"] +Vue.component("http-cond-general-header-length", { + props: ["v-checkpoint"], + data: function () { + let headers = null + let length = null + + if (window.parent.UPDATING_RULE != null) { + let options = window.parent.UPDATING_RULE.checkpointOptions + if (options.headers != null && Array.$isArray(options.headers)) { + headers = options.headers + } + if (options.length != null) { + length = options.length + } + } + + + if (headers == null) { + headers = defaultGeneralHeaders + } + + if (length == null) { + length = 128 + } + + let that = this + setTimeout(function () { + that.change() + }, 100) + + return { + headers: headers, + length: length + } + }, + watch: { + length: function (v) { + let len = parseInt(v) + if (isNaN(len)) { + len = 0 + } + if (len < 0) { + len = 0 + } + this.length = len + this.change() + } + }, + methods: { + change: function () { + this.vCheckpoint.options = [ + { + code: "headers", + value: this.headers + }, + { + code: "length", + value: this.length + } + ] + } + }, + template: `
+ + + + + + + + + +
通用Header列表 + +

需要检查的Header列表。

+
Header值超出长度 +
+ + 字节 +
+

超出此长度认为匹配成功,0表示不限制。

+
+
` +}) + +// CC +Vue.component("http-firewall-checkpoint-cc", { + props: ["v-checkpoint"], + data: function () { + let keys = [] + let period = 60 + let threshold = 1000 + + let options = {} + if (window.parent.UPDATING_RULE != null) { + options = window.parent.UPDATING_RULE.checkpointOptions + } + + if (options == null) { + options = {} + } + if (options.keys != null) { + keys = options.keys + } + if (keys.length == 0) { + keys = ["${remoteAddr}", "${requestPath}"] + } + if (options.period != null) { + period = options.period + } + if (options.threshold != null) { + threshold = options.threshold + } + + let that = this + setTimeout(function () { + that.change() + }, 100) + + return { + keys: keys, + period: period, + threshold: threshold, + options: {}, + value: threshold + } + }, + watch: { + period: function () { + this.change() + }, + threshold: function () { + this.change() + } + }, + methods: { + changeKeys: function (keys) { + this.keys = keys + this.change() + }, + change: function () { + let period = parseInt(this.period.toString()) + if (isNaN(period) || period <= 0) { + period = 60 + } + + let threshold = parseInt(this.threshold.toString()) + if (isNaN(threshold) || threshold <= 0) { + threshold = 1000 + } + this.value = threshold + + this.vCheckpoint.options = [ + { + code: "keys", + value: this.keys + }, + { + code: "period", + value: period, + }, + { + code: "threshold", + value: threshold + } + ] + } + }, + template: `
+ + + + + + + + + + + + + + + +
统计对象组合 * + +
统计周期 * +
+ + +
+
阈值 * + +
+
` +}) + +// 防盗链 +Vue.component("http-firewall-checkpoint-referer-block", { + props: ["v-checkpoint"], + data: function () { + let allowEmpty = true + let allowSameDomain = true + let allowDomains = [] + + let options = {} + if (window.parent.UPDATING_RULE != null) { + options = window.parent.UPDATING_RULE.checkpointOptions + } + + if (options == null) { + options = {} + } + if (typeof (options.allowEmpty) == "boolean") { + allowEmpty = options.allowEmpty + } + if (typeof (options.allowSameDomain) == "boolean") { + allowSameDomain = options.allowSameDomain + } + if (options.allowDomains != null && typeof (options.allowDomains) == "object") { + allowDomains = options.allowDomains + } + + let that = this + setTimeout(function () { + that.change() + }, 100) + + return { + allowEmpty: allowEmpty, + allowSameDomain: allowSameDomain, + allowDomains: allowDomains, + options: {}, + value: 0 + } + }, + watch: { + allowEmpty: function () { + this.change() + }, + allowSameDomain: function () { + this.change() + } + }, + methods: { + changeAllowDomains: function (values) { + this.allowDomains = values + this.change() + }, + change: function () { + this.vCheckpoint.options = [ + { + code: "allowEmpty", + value: this.allowEmpty + }, + { + code: "allowSameDomain", + value: this.allowSameDomain, + }, + { + code: "allowDomains", + value: this.allowDomains + } + ] + } + }, + template: `
+ + + + + + + + + + + + + + + +
来源域名允许为空 + +

允许不带来源的访问。

+
来源域名允许一致 + +

允许来源域名和当前访问的域名一致,相当于在站内访问。

+
允许的来源域名 + +

允许的来源域名列表,比如example.com*.example.com。单个星号*表示允许所有域名。

+
+
` +}) + +Vue.component("http-cache-refs-config-box", { + props: ["v-cache-refs", "v-cache-config", "v-cache-policy-id", "v-web-id"], + mounted: function () { + let that = this + sortTable(function (ids) { + let newRefs = [] + ids.forEach(function (id) { + that.refs.forEach(function (ref) { + if (ref.id == id) { + newRefs.push(ref) + } + }) + }) + that.updateRefs(newRefs) + that.change() + }) + }, + data: function () { + let refs = this.vCacheRefs + if (refs == null) { + refs = [] + } + + let id = 0 + refs.forEach(function (ref) { + id++ + ref.id = id + }) + return { + refs: refs, + id: id // 用来对条件进行排序 + } + }, + methods: { + addRef: function (isReverse) { + window.UPDATING_CACHE_REF = null + + let width = window.innerWidth + if (width > 1024) { + width = 1024 + } + let height = window.innerHeight + if (height > 500) { + height = 500 + } + let that = this + teaweb.popup("/servers/server/settings/cache/createPopup?isReverse=" + (isReverse ? 1 : 0), { + width: width + "px", + height: height + "px", + callback: function (resp) { + let newRef = resp.data.cacheRef + if (newRef.conds == null) { + return + } + + that.id++ + newRef.id = that.id + + if (newRef.isReverse) { + let newRefs = [] + let isAdded = false + that.refs.forEach(function (v) { + if (!v.isReverse && !isAdded) { + newRefs.push(newRef) + isAdded = true + } + newRefs.push(v) + }) + if (!isAdded) { + newRefs.push(newRef) + } + + that.updateRefs(newRefs) + } else { + that.refs.push(newRef) + } + + that.change() + } + }) + }, + updateRef: function (index, cacheRef) { + window.UPDATING_CACHE_REF = cacheRef + + let width = window.innerWidth + if (width > 1024) { + width = 1024 + } + let height = window.innerHeight + if (height > 500) { + height = 500 + } + let that = this + teaweb.popup("/servers/server/settings/cache/createPopup", { + width: width + "px", + height: height + "px", + callback: function (resp) { + resp.data.cacheRef.id = that.refs[index].id + Vue.set(that.refs, index, resp.data.cacheRef) + that.change() + } + }) + }, + disableRef: function (ref) { + ref.isOn = false + this.change() + }, + enableRef: function (ref) { + ref.isOn = true + this.change() + }, + removeRef: function (index) { + let that = this + teaweb.confirm("确定要删除此缓存设置吗?", function () { + that.refs.$remove(index) + that.change() + }) + }, + updateRefs: function (newRefs) { + this.refs = newRefs + if (this.vCacheConfig != null) { + this.vCacheConfig.cacheRefs = newRefs + } + }, + timeUnitName: function (unit) { + switch (unit) { + case "ms": + return "毫秒" + case "second": + return "秒" + case "minute": + return "分钟" + case "hour": + return "小时" + case "day": + return "天" + case "week": + return "周 " + } + return unit + }, + change: function () { + // 自动保存 + if (this.vCachePolicyId != null && this.vCachePolicyId > 0) { // 缓存策略 + Tea.action("/servers/components/cache/updateRefs") + .params({ + cachePolicyId: this.vCachePolicyId, + refsJSON: JSON.stringify(this.refs) + }) + .post() + } else if (this.vWebId != null && this.vWebId > 0) { // Server Web or Group Web + Tea.action("/servers/server/settings/cache/updateRefs") + .params({ + webId: this.vWebId, + refsJSON: JSON.stringify(this.refs) + }) + .success(function (resp) { + if (resp.data.isUpdated) { + teaweb.successToast("保存成功") + } + }) + .post() + } + } + }, + template: `
+ + +
+

暂时还没有缓存条件。

+ + + + + + + + + + + + + + + + + + + +
缓存条件分组关系缓存时间操作
+ + + {{cacheRef.minSize.count}}{{cacheRef.minSize.unit}} + - {{cacheRef.maxSize.count}}{{cacheRef.maxSize.unit}} + + 0 - {{cacheRef.maxSize.count}}{{cacheRef.maxSize.unit}} + {{cacheRef.methods.join(", ")}} + Expires + 状态码:{{cacheRef.status.map(function(v) {return v.toString()}).join(", ")}} + 区间缓存 + + + + + {{cacheRef.life.count}} {{timeUnitName(cacheRef.life.unit)}} + 不缓存 + + 修改   + 暂停恢复   + 删除 +
+

所有条件匹配顺序为从上到下,可以拖动左侧的排序。服务设置的优先级比全局缓存策略设置的优先级要高。

+ +     +添加不缓存设置 +
+
+
` +}) + +Vue.component("origin-list-box", { + props: ["v-primary-origins", "v-backup-origins", "v-server-type", "v-params"], + data: function () { + return { + primaryOrigins: this.vPrimaryOrigins, + backupOrigins: this.vBackupOrigins + } + }, + methods: { + createPrimaryOrigin: function () { + teaweb.popup("/servers/server/settings/origins/addPopup?originType=primary&" + this.vParams, { + height: "27em", + callback: function (resp) { + teaweb.success("保存成功", function () { + window.location.reload() + }) + } + }) + }, + createBackupOrigin: function () { + teaweb.popup("/servers/server/settings/origins/addPopup?originType=backup&" + this.vParams, { + height: "27em", + callback: function (resp) { + teaweb.success("保存成功", function () { + window.location.reload() + }) + } + }) + }, + updateOrigin: function (originId, originType) { + teaweb.popup("/servers/server/settings/origins/updatePopup?originType=" + originType + "&" + this.vParams + "&originId=" + originId, { + height: "27em", + callback: function (resp) { + teaweb.success("保存成功", function () { + window.location.reload() + }) + } + }) + }, + deleteOrigin: function (originId, originType) { + let that = this + teaweb.confirm("确定要删除此源站吗?", function () { + Tea.action("/servers/server/settings/origins/delete?" + that.vParams + "&originId=" + originId + "&originType=" + originType) + .post() + .success(function () { + teaweb.success("删除成功", function () { + window.location.reload() + }) + }) + }) + } + }, + template: `
+

主要源站 [添加主要源站]

+

暂时还没有主要源站。

+ + +

备用源站 [添加备用源站]

+

暂时还没有备用源站。

+ +
` +}) + +Vue.component("origin-list-table", { + props: ["v-origins", "v-origin-type"], + data: function () { + return {} + }, + methods: { + deleteOrigin: function (originId) { + this.$emit("deleteOrigin", originId, this.vOriginType) + }, + updateOrigin: function (originId) { + this.$emit("updateOrigin", originId, this.vOriginType) + } + }, + template: ` + + + + + + + + + + + + + + + +
源站地址权重状态操作
{{origin.addr}}   +
+ {{origin.name}} + 证书 + 主机名: {{origin.host}} + 匹配: {{domain}} +
+
{{origin.weight}} + + + 修改   + 删除 +
` +}) + +Vue.component("http-firewall-policy-selector", { + props: ["v-http-firewall-policy"], + mounted: function () { + let that = this + Tea.action("/servers/components/waf/count") + .post() + .success(function (resp) { + that.count = resp.data.count + }) + }, + data: function () { + let firewallPolicy = this.vHttpFirewallPolicy + return { + count: 0, + firewallPolicy: firewallPolicy + } + }, + methods: { + remove: function () { + this.firewallPolicy = null + }, + select: function () { + let that = this + teaweb.popup("/servers/components/waf/selectPopup", { + callback: function (resp) { + that.firewallPolicy = resp.data.firewallPolicy + } + }) + }, + create: function () { + let that = this + teaweb.popup("/servers/components/waf/createPopup", { + height: "26em", + callback: function (resp) { + that.firewallPolicy = resp.data.firewallPolicy + } + }) + } + }, + template: `
+
+ + {{firewallPolicy.name}}     +
+ +
` +}) + +Vue.component("http-websocket-box", { + props: ["v-websocket-ref", "v-websocket-config", "v-is-location", "v-is-group"], + data: function () { + let websocketRef = this.vWebsocketRef + if (websocketRef == null) { + websocketRef = { + isPrior: false, + isOn: false, + websocketId: 0 + } + } + + let websocketConfig = this.vWebsocketConfig + if (websocketConfig == null) { + websocketConfig = { + id: 0, + isOn: false, + handshakeTimeout: { + count: 30, + unit: "second" + }, + allowAllOrigins: true, + allowedOrigins: [], + requestSameOrigin: true, + requestOrigin: "" + } + } else { + if (websocketConfig.handshakeTimeout == null) { + websocketConfig.handshakeTimeout = { + count: 30, + unit: "second", + } + } + if (websocketConfig.allowedOrigins == null) { + websocketConfig.allowedOrigins = [] + } + } + + return { + websocketRef: websocketRef, + websocketConfig: websocketConfig, + handshakeTimeoutCountString: websocketConfig.handshakeTimeout.count.toString(), + advancedVisible: false + } + }, + watch: { + handshakeTimeoutCountString: function (v) { + let count = parseInt(v) + if (!isNaN(count) && count >= 0) { + this.websocketConfig.handshakeTimeout.count = count + } else { + this.websocketConfig.handshakeTimeout.count = 0 + } + } + }, + methods: { + isOn: function () { + return ((!this.vIsLocation && !this.vIsGroup) || this.websocketRef.isPrior) && this.websocketRef.isOn + }, + changeAdvancedVisible: function (v) { + this.advancedVisible = v + }, + createOrigin: function () { + let that = this + teaweb.popup("/servers/server/settings/websocket/createOrigin", { + height: "12.5em", + callback: function (resp) { + that.websocketConfig.allowedOrigins.push(resp.data.origin) + } + }) + }, + removeOrigin: function (index) { + this.websocketConfig.allowedOrigins.$remove(index) + } + }, + template: `
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
是否启用配置 +
+ + +
+
允许所有来源域(Origin) +
+ + +
+

选中表示允许所有的来源域。

+
允许的来源域列表(Origin) +
+
+ {{origin}} +
+
+
+ +

只允许在列表中的来源域名访问Websocket服务。

+
是否传递请求来源域 +
+ + +
+

选中表示把接收到的请求中的Origin字段传递到源站。

+
指定传递的来源域 + +

指定向源站传递的Origin字段值。

+
握手超时时间(Handshake) +
+
+ +
+
+ 秒 +
+
+

0表示使用默认的时间设置。

+
+
+
` +}) + +Vue.component("http-rewrite-rule-list", { + props: ["v-web-id", "v-rewrite-rules"], + mounted: function () { + setTimeout(this.sort, 1000) + }, + data: function () { + let rewriteRules = this.vRewriteRules + if (rewriteRules == null) { + rewriteRules = [] + } + return { + rewriteRules: rewriteRules + } + }, + methods: { + updateRewriteRule: function (rewriteRuleId) { + teaweb.popup("/servers/server/settings/rewrite/updatePopup?webId=" + this.vWebId + "&rewriteRuleId=" + rewriteRuleId, { + height: "26em", + callback: function () { + window.location.reload() + } + }) + }, + deleteRewriteRule: function (rewriteRuleId) { + let that = this + teaweb.confirm("确定要删除此重写规则吗?", function () { + Tea.action("/servers/server/settings/rewrite/delete") + .params({ + webId: that.vWebId, + rewriteRuleId: rewriteRuleId + }) + .post() + .refresh() + }) + }, + // 排序 + sort: function () { + if (this.rewriteRules.length == 0) { + return + } + let that = this + sortTable(function (rowIds) { + Tea.action("/servers/server/settings/rewrite/sort") + .post() + .params({ + webId: that.vWebId, + rewriteRuleIds: rowIds + }) + .success(function () { + teaweb.success("保存成功") + }) + }) + } + }, + template: `
+
+

暂时还没有重写规则。

+ + + + + + + + + + + + + + + + + + + + + +
匹配规则转发目标转发方式状态操作
{{rule.pattern}} +
+ BREAK + {{rule.redirectStatus}} + Host: {{rule.proxyHost}} +
{{rule.replace}} + 隐式 + 显示 + + + + 修改   + 删除 +
+

拖动左侧的图标可以对重写规则进行排序。

+ +
` +}) + +Vue.component("http-rewrite-labels-label", { + props: ["v-class"], + template: `` +}) + +Vue.component("server-name-box", { + props: ["v-server-names"], + data: function () { + let serverNames = this.vServerNames; + if (serverNames == null) { + serverNames = [] + } + return { + serverNames: serverNames, + isSearching: false, + keyword: "" + } + }, + methods: { + addServerName: function () { + window.UPDATING_SERVER_NAME = null + let that = this + teaweb.popup("/servers/addServerNamePopup", { + callback: function (resp) { + var serverName = resp.data.serverName + that.serverNames.push(serverName) + } + }); + }, + + removeServerName: function (index) { + this.serverNames.$remove(index) + }, + + updateServerName: function (index, serverName) { + window.UPDATING_SERVER_NAME = serverName + let that = this + teaweb.popup("/servers/addServerNamePopup", { + callback: function (resp) { + var serverName = resp.data.serverName + Vue.set(that.serverNames, index, serverName) + } + }); + }, + showSearchBox: function () { + this.isSearching = !this.isSearching + if (this.isSearching) { + let that = this + setTimeout(function () { + that.$refs.keywordRef.focus() + }, 200) + } else { + this.keyword = "" + } + }, + }, + watch: { + keyword: function (v) { + this.serverNames.forEach(function (serverName) { + if (v.length == 0) { + serverName.isShowing = true + return + } + if (serverName.subNames == null || serverName.subNames.length == 0) { + if (!teaweb.match(serverName.name, v)) { + serverName.isShowing = false + } + } else { + let found = false + serverName.subNames.forEach(function (subName) { + if (teaweb.match(subName, v)) { + found = true + } + }) + serverName.isShowing = found + } + }) + } + }, + template: `
+ +
+
+ {{serverName.type}} + {{serverName.name}} + {{serverName.subNames[0]}}等{{serverName.subNames.length}}个域名 + +
+
+
+
+ +
|
+
+ + +
+
+ +
+
+
` +}) + +Vue.component("http-cache-stale-config", { + props: ["v-cache-stale-config"], + data: function () { + let config = this.vCacheStaleConfig + if (config == null) { + config = { + isPrior: false, + isOn: false, + status: [], + supportStaleIfErrorHeader: true, + life: { + count: 1, + unit: "day" + } + } + } + return { + config: config + } + }, + watch: { + config: { + deep: true, + handler: function () { + this.$emit("change", this.config) + } + } + }, + methods: {}, + template: ` + + + + + + + + + + + + + + + + + + +
启用过时缓存 + +

选中后,在更新缓存失败后会尝试读取过时的缓存。

+
有效期 + +

缓存在过期之后,仍然保留的时间。

+
状态码 +

在这些状态码出现时使用过时缓存,默认支持50x状态码。

+
支持stale-if-error + +

选中后,支持在Cache-Control中通过stale-if-error指定过时缓存有效期。

+
` +}) + +// 域名列表 +Vue.component("domains-box", { + props: ["v-domains"], + data: function () { + let domains = this.vDomains + if (domains == null) { + domains = [] + } + return { + domains: domains, + isAdding: false, + addingDomain: "" + } + }, + methods: { + add: function () { + this.isAdding = true + let that = this + setTimeout(function () { + that.$refs.addingDomain.focus() + }, 100) + }, + confirm: function () { + let that = this + + // 删除其中的空格 + this.addingDomain = this.addingDomain.replace(/\s/g, "") + + if (this.addingDomain.length == 0) { + teaweb.warn("请输入要添加的域名", function () { + that.$refs.addingDomain.focus() + }) + return + } + + + // 基本校验 + if (this.addingDomain[0] == "~") { + let expr = this.addingDomain.substring(1) + try { + new RegExp(expr) + } catch (e) { + teaweb.warn("正则表达式错误:" + e.message, function () { + that.$refs.addingDomain.focus() + }) + return + } + } + + this.domains.push(this.addingDomain) + this.cancel() + }, + remove: function (index) { + this.domains.$remove(index) + }, + cancel: function () { + this.isAdding = false + this.addingDomain = "" + } + }, + template: `
+ +
+ + [正则] + [后缀] + [泛域名] + {{domain}} +   + +
+
+
+
+
+ +
+
+ +   +
+
+

支持普通域名(example.com)、泛域名(*.example.com)、域名后缀(以点号开头,如.example.com)和正则表达式(以波浪号开头,如~.*.example.com)。

+
+
+
+ +
+
` +}) + +Vue.component("http-redirect-to-https-box", { + props: ["v-redirect-to-https-config", "v-is-location"], + data: function () { + let redirectToHttpsConfig = this.vRedirectToHttpsConfig + if (redirectToHttpsConfig == null) { + redirectToHttpsConfig = { + isPrior: false, + isOn: false, + host: "", + port: 0, + status: 0, + onlyDomains: [], + exceptDomains: [] + } + } else { + if (redirectToHttpsConfig.onlyDomains == null) { + redirectToHttpsConfig.onlyDomains = [] + } + if (redirectToHttpsConfig.exceptDomains == null) { + redirectToHttpsConfig.exceptDomains = [] + } + } + return { + redirectToHttpsConfig: redirectToHttpsConfig, + portString: (redirectToHttpsConfig.port > 0) ? redirectToHttpsConfig.port.toString() : "", + moreOptionsVisible: false, + statusOptions: [ + {"code": 301, "text": "Moved Permanently"}, + {"code": 308, "text": "Permanent Redirect"}, + {"code": 302, "text": "Found"}, + {"code": 303, "text": "See Other"}, + {"code": 307, "text": "Temporary Redirect"} + ] + } + }, + watch: { + "redirectToHttpsConfig.status": function () { + this.redirectToHttpsConfig.status = parseInt(this.redirectToHttpsConfig.status) + }, + portString: function (v) { + let port = parseInt(v) + if (!isNaN(port)) { + this.redirectToHttpsConfig.port = port + } else { + this.redirectToHttpsConfig.port = 0 + } + } + }, + methods: { + changeMoreOptions: function (isVisible) { + this.moreOptionsVisible = isVisible + }, + changeOnlyDomains: function (values) { + this.redirectToHttpsConfig.onlyDomains = values + this.$forceUpdate() + }, + changeExceptDomains: function (values) { + this.redirectToHttpsConfig.exceptDomains = values + this.$forceUpdate() + } + }, + template: `
+ + + + + + + + + + + +
自动跳转到HTTPS +
+ + +
+

开启后,所有HTTP的请求都会自动跳转到对应的HTTPS URL上,

+ + + + + + + + + + + + + + + +
状态码 + +
域名或IP地址 + +

默认和用户正在访问的域名或IP地址一致。

+
端口 + +

默认端口为443。

+
+
+ + +
+
+ + +
+

开启后,所有HTTP的请求都会自动跳转到对应的HTTPS URL上,

+ + + + + + + + + + + + + + + + + + + + + + + +
状态码 + +
跳转后域名或IP地址 + +

默认和用户正在访问的域名或IP地址一致,不填写就表示使用当前的域名。

+
端口 + +

默认端口为443。

+
允许的域名 + +

如果填写了允许的域名,那么只有这些域名可以自动跳转。

+
排除的域名 + +

如果填写了排除的域名,那么这些域名将不跳转。

+
+
+
+
` +}) + +// 动作选择 +Vue.component("http-firewall-actions-box", { + props: ["v-actions", "v-firewall-policy", "v-action-configs"], + mounted: function () { + let that = this + Tea.action("/servers/iplists/levelOptions") + .success(function (resp) { + that.ipListLevels = resp.data.levels + }) + .post() + + this.loadJS(function () { + let box = document.getElementById("actions-box") + Sortable.create(box, { + draggable: ".label", + handle: ".icon.handle", + onStart: function () { + that.cancel() + }, + onUpdate: function (event) { + let labels = box.getElementsByClassName("label") + let newConfigs = [] + for (let i = 0; i < labels.length; i++) { + let index = parseInt(labels[i].getAttribute("data-index")) + newConfigs.push(that.configs[index]) + } + that.configs = newConfigs + } + }) + }) + }, + data: function () { + if (this.vFirewallPolicy.inbound == null) { + this.vFirewallPolicy.inbound = {} + } + if (this.vFirewallPolicy.inbound.groups == null) { + this.vFirewallPolicy.inbound.groups = [] + } + + let id = 0 + let configs = [] + if (this.vActionConfigs != null) { + configs = this.vActionConfigs + configs.forEach(function (v) { + v.id = (id++) + }) + } + + var defaultPageBody = ` + +403 Forbidden + +

403 Forbidden

+
Request ID: \${requestId}.
+ +` + + + return { + id: id, + + actions: this.vActions, + configs: configs, + isAdding: false, + editingIndex: -1, + + action: null, + actionCode: "", + actionOptions: {}, + + // IPList相关 + ipListLevels: [], + + // 动作参数 + blockTimeout: "", + blockScope: "global", + + captchaLife: "", + captchaMaxFails: "", + captchaFailBlockTimeout: "", + get302Life: "", + post307Life: "", + recordIPType: "black", + recordIPLevel: "critical", + recordIPTimeout: "", + recordIPListId: 0, + recordIPListName: "", + + tagTags: [], + + pageStatus: 403, + pageBody: defaultPageBody, + defaultPageBody: defaultPageBody, + + goGroupName: "", + goGroupId: 0, + goGroup: null, + + goSetId: 0, + goSetName: "" + } + }, + watch: { + actionCode: function (code) { + this.action = this.actions.$find(function (k, v) { + return v.code == code + }) + this.actionOptions = {} + }, + blockTimeout: function (v) { + v = parseInt(v) + if (isNaN(v)) { + this.actionOptions["timeout"] = 0 + } else { + this.actionOptions["timeout"] = v + } + }, + blockScope: function (v) { + this.actionOptions["scope"] = v + }, + captchaLife: function (v) { + v = parseInt(v) + if (isNaN(v)) { + this.actionOptions["life"] = 0 + } else { + this.actionOptions["life"] = v + } + }, + captchaMaxFails: function (v) { + v = parseInt(v) + if (isNaN(v)) { + this.actionOptions["maxFails"] = 0 + } else { + this.actionOptions["maxFails"] = v + } + }, + captchaFailBlockTimeout: function (v) { + v = parseInt(v) + if (isNaN(v)) { + this.actionOptions["failBlockTimeout"] = 0 + } else { + this.actionOptions["failBlockTimeout"] = v + } + }, + get302Life: function (v) { + v = parseInt(v) + if (isNaN(v)) { + this.actionOptions["life"] = 0 + } else { + this.actionOptions["life"] = v + } + }, + post307Life: function (v) { + v = parseInt(v) + if (isNaN(v)) { + this.actionOptions["life"] = 0 + } else { + this.actionOptions["life"] = v + } + }, + recordIPType: function (v) { + this.recordIPListId = 0 + }, + recordIPTimeout: function (v) { + v = parseInt(v) + if (isNaN(v)) { + this.actionOptions["timeout"] = 0 + } else { + this.actionOptions["timeout"] = v + } + }, + goGroupId: function (groupId) { + let group = this.vFirewallPolicy.inbound.groups.$find(function (k, v) { + return v.id == groupId + }) + this.goGroup = group + if (group == null) { + this.goGroupName = "" + } else { + this.goGroupName = group.name + } + this.goSetId = 0 + this.goSetName = "" + }, + goSetId: function (setId) { + if (this.goGroup == null) { + return + } + let set = this.goGroup.sets.$find(function (k, v) { + return v.id == setId + }) + if (set == null) { + this.goSetId = 0 + this.goSetName = "" + } else { + this.goSetName = set.name + } + } + }, + methods: { + add: function () { + this.action = null + this.actionCode = "block" + this.isAdding = true + this.actionOptions = {} + + // 动作参数 + this.blockTimeout = "" + this.blockScope = "global" + this.captchaLife = "" + this.captchaMaxFails = "" + this.captchaFailBlockTimeout = "" + this.get302Life = "" + this.post307Life = "" + + this.recordIPLevel = "critical" + this.recordIPType = "black" + this.recordIPTimeout = "" + this.recordIPListId = 0 + this.recordIPListName = "" + + this.tagTags = [] + + this.pageStatus = 403 + this.pageBody = this.defaultPageBody + + this.goGroupName = "" + this.goGroupId = 0 + this.goGroup = null + + this.goSetId = 0 + this.goSetName = "" + + let that = this + this.action = this.vActions.$find(function (k, v) { + return v.code == that.actionCode + }) + + // 滚到界面底部 + this.scroll() + }, + remove: function (index) { + this.isAdding = false + this.editingIndex = -1 + this.configs.$remove(index) + }, + update: function (index, config) { + if (this.isAdding && this.editingIndex == index) { + this.cancel() + return + } + + this.add() + + this.isAdding = true + this.editingIndex = index + + this.actionCode = config.code + + switch (config.code) { + case "block": + this.blockTimeout = "" + if (config.options.timeout != null || config.options.timeout > 0) { + this.blockTimeout = config.options.timeout.toString() + } + if (config.options.scope != null && config.options.scope.length > 0) { + this.blockScope = config.options.scope + } else { + this.blockScope = "global" // 兼容先前版本遗留的默认值 + } + break + case "allow": + break + case "log": + break + case "captcha": + this.captchaLife = "" + if (config.options.life != null || config.options.life > 0) { + this.captchaLife = config.options.life.toString() + } + this.captchaMaxFails = "" + if (config.options.maxFails != null || config.options.maxFails > 0) { + this.captchaMaxFails = config.options.maxFails.toString() + } + this.captchaFailBlockTimeout = "" + if (config.options.failBlockTimeout != null || config.options.failBlockTimeout > 0) { + this.captchaFailBlockTimeout = config.options.failBlockTimeout.toString() + } + break + case "notify": + break + case "get_302": + this.get302Life = "" + if (config.options.life != null || config.options.life > 0) { + this.get302Life = config.options.life.toString() + } + break + case "post_307": + this.post307Life = "" + if (config.options.life != null || config.options.life > 0) { + this.post307Life = config.options.life.toString() + } + break; + case "record_ip": + if (config.options != null) { + this.recordIPLevel = config.options.level + this.recordIPType = config.options.type + if (config.options.timeout > 0) { + this.recordIPTimeout = config.options.timeout.toString() + } + let that = this + + // VUE需要在函数执行完之后才会调用watch函数,这样会导致设置的值被覆盖,所以这里使用setTimeout + setTimeout(function () { + that.recordIPListId = config.options.ipListId + that.recordIPListName = config.options.ipListName + }) + } + break + case "tag": + this.tagTags = [] + if (config.options.tags != null) { + this.tagTags = config.options.tags + } + break + case "page": + this.pageStatus = 403 + this.pageBody = this.defaultPageBody + if (config.options.status != null) { + this.pageStatus = config.options.status + } + if (config.options.body != null) { + this.pageBody = config.options.body + } + + break + case "go_group": + if (config.options != null) { + this.goGroupName = config.options.groupName + this.goGroupId = config.options.groupId + this.goGroup = this.vFirewallPolicy.inbound.groups.$find(function (k, v) { + return v.id == config.options.groupId + }) + } + break + case "go_set": + if (config.options != null) { + this.goGroupName = config.options.groupName + this.goGroupId = config.options.groupId + this.goGroup = this.vFirewallPolicy.inbound.groups.$find(function (k, v) { + return v.id == config.options.groupId + }) + + // VUE需要在函数执行完之后才会调用watch函数,这样会导致设置的值被覆盖,所以这里使用setTimeout + let that = this + setTimeout(function () { + that.goSetId = config.options.setId + if (that.goGroup != null) { + let set = that.goGroup.sets.$find(function (k, v) { + return v.id == config.options.setId + }) + if (set != null) { + that.goSetName = set.name + } + } + }) + } + break + } + + // 滚到界面底部 + this.scroll() + }, + cancel: function () { + this.isAdding = false + this.editingIndex = -1 + }, + confirm: function () { + if (this.action == null) { + return + } + + if (this.actionOptions == null) { + this.actionOptions = {} + } + + // record_ip + if (this.actionCode == "record_ip") { + let timeout = parseInt(this.recordIPTimeout) + if (isNaN(timeout)) { + timeout = 0 + } + if (this.recordIPListId <= 0) { + return + } + this.actionOptions = { + type: this.recordIPType, + level: this.recordIPLevel, + timeout: timeout, + ipListId: this.recordIPListId, + ipListName: this.recordIPListName + } + } else if (this.actionCode == "tag") { // tag + if (this.tagTags == null || this.tagTags.length == 0) { + return + } + this.actionOptions = { + tags: this.tagTags + } + } else if (this.actionCode == "page") { + let pageStatus = this.pageStatus.toString() + if (!pageStatus.match(/^\d{3}$/)) { + pageStatus = 403 + } else { + pageStatus = parseInt(pageStatus) + } + + this.actionOptions = { + status: pageStatus, + body: this.pageBody + } + } else if (this.actionCode == "go_group") { // go_group + let groupId = this.goGroupId + if (typeof (groupId) == "string") { + groupId = parseInt(groupId) + if (isNaN(groupId)) { + groupId = 0 + } + } + if (groupId <= 0) { + return + } + this.actionOptions = { + groupId: groupId.toString(), + groupName: this.goGroupName + } + } else if (this.actionCode == "go_set") { // go_set + let groupId = this.goGroupId + if (typeof (groupId) == "string") { + groupId = parseInt(groupId) + if (isNaN(groupId)) { + groupId = 0 + } + } + + let setId = this.goSetId + if (typeof (setId) == "string") { + setId = parseInt(setId) + if (isNaN(setId)) { + setId = 0 + } + } + if (setId <= 0) { + return + } + this.actionOptions = { + groupId: groupId.toString(), + groupName: this.goGroupName, + setId: setId.toString(), + setName: this.goSetName + } + } + + let options = {} + for (let k in this.actionOptions) { + if (this.actionOptions.hasOwnProperty(k)) { + options[k] = this.actionOptions[k] + } + } + if (this.editingIndex > -1) { + this.configs[this.editingIndex] = { + id: this.configs[this.editingIndex].id, + code: this.actionCode, + name: this.action.name, + options: options + } + } else { + this.configs.push({ + id: (this.id++), + code: this.actionCode, + name: this.action.name, + options: options + }) + } + + this.cancel() + }, + removeRecordIPList: function () { + this.recordIPListId = 0 + }, + selectRecordIPList: function () { + let that = this + teaweb.popup("/servers/iplists/selectPopup?type=" + this.recordIPType, { + width: "50em", + height: "30em", + callback: function (resp) { + that.recordIPListId = resp.data.list.id + that.recordIPListName = resp.data.list.name + } + }) + }, + changeTags: function (tags) { + this.tagTags = tags + }, + loadJS: function (callback) { + if (typeof Sortable != "undefined") { + callback() + return + } + + // 引入js + let jsFile = document.createElement("script") + jsFile.setAttribute("src", "/js/sortable.min.js") + jsFile.addEventListener("load", function () { + callback() + }) + document.head.appendChild(jsFile) + }, + scroll: function () { + setTimeout(function () { + let mainDiv = document.getElementsByClassName("main") + if (mainDiv.length > 0) { + mainDiv[0].scrollTo(0, 1000) + } + }, 10) + } + }, + template: `
+ +
+
+ {{config.name}} ({{config.code.toUpperCase()}}) + + + :有效期{{config.options.timeout}}秒 + + + :有效期{{config.options.life}}秒 + + + :有效期{{config.options.life}}秒 + + + :有效期{{config.options.life}}秒 + + + :{{config.options.ipListName}} + + + :{{config.options.tags.join(", ")}} + + + :[{{config.options.status}}] + + + :{{config.options.groupName}} + + + :{{config.options.groupName}} / {{config.options.setName}} + + + +   + [所有服务] + [当前服务] + + + +       +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
动作类型 * + +

{{action.description}}

+
封锁时间 +
+ + +
+
封锁范围 + +

只封锁用户对当前网站服务的访问,其他服务不受影响。

+

封锁用户对所有网站服务的访问。

+
有效时间 +
+ + +
+

验证通过后在这个时间内不再验证,默认600秒。

+
最多失败次数 +
+ + +
+

如果为空或者为0,表示不限制。

+
失败拦截时间 +
+ + +
+

在达到最多失败次数(大于0)时,自动拦截的时间;如果为0表示不自动拦截。

+
有效时间 +
+ + +
+

验证通过后在这个时间内不再验证。

+
有效时间 +
+ + +
+

验证通过后在这个时间内不再验证。

+
IP名单类型 * + +
选择IP名单 * +
{{recordIPListName}}
+ +

如不选择,则自动添加到当前策略的IP名单中。

+
级别 + +
超时时间 +
+ + +
+

0表示不超时。

+
标签 * + +
状态码 *
网页内容 + +
下一个分组 * + +
下一个分组 * + +
下一个规则集 * + +
+   + +
+
+ +
+

系统总是会先执行记录日志、标签等不会修改请求的动作,再执行阻止、验证码等可能改变请求的动作。

+
` +}) + +// 认证设置 +Vue.component("http-auth-config-box", { + props: ["v-auth-config", "v-is-location"], + data: function () { + let authConfig = this.vAuthConfig + if (authConfig == null) { + authConfig = { + isPrior: false, + isOn: false + } + } + if (authConfig.policyRefs == null) { + authConfig.policyRefs = [] + } + return { + authConfig: authConfig + } + }, + methods: { + isOn: function () { + return (!this.vIsLocation || this.authConfig.isPrior) && this.authConfig.isOn + }, + add: function () { + let that = this + teaweb.popup("/servers/server/settings/access/createPopup", { + callback: function (resp) { + that.authConfig.policyRefs.push(resp.data.policyRef) + }, + height: "28em" + }) + }, + update: function (index, policyId) { + let that = this + teaweb.popup("/servers/server/settings/access/updatePopup?policyId=" + policyId, { + callback: function (resp) { + teaweb.success("保存成功", function () { + teaweb.reload() + }) + }, + height: "28em" + }) + }, + remove: function (index) { + this.authConfig.policyRefs.$remove(index) + }, + methodName: function (methodType) { + switch (methodType) { + case "basicAuth": + return "BasicAuth" + case "subRequest": + return "子请求" + } + return "" + } + }, + template: `
+ + + + + + + + + +
启用认证 +
+ + +
+
+
+ +
+

认证方式

+ + + + + + + + + + + + + + + + + + + +
名称认证方法参数状态操作
{{ref.authPolicy.name}} + {{methodName(ref.authPolicy.type)}} + + {{ref.authPolicy.params.users.length}}个用户 + + [{{ref.authPolicy.params.method}}] + {{ref.authPolicy.params.url}} + + + + + 修改   + 删除 +
+ +
+
+
` +}) + +Vue.component("user-selector", { + mounted: function () { + let that = this + + Tea.action("/servers/users/options") + .post() + .success(function (resp) { + that.users = resp.data.users + }) + }, + props: ["v-user-id"], + data: function () { + let userId = this.vUserId + if (userId == null) { + userId = 0 + } + return { + users: [], + userId: userId + } + }, + watch: { + userId: function (v) { + this.$emit("change", v) + } + }, + template: `
+ +
` +}) + +// UAM模式配置 +Vue.component("uam-config-box", { + props: ["v-uam-config"], + data: function () { + let config = this.vUamConfig + if (config == null) { + config = { + isOn: false + } + } + return { + config: config + } + }, + template: `
+ + + + + + +
启用5秒盾 + +

启用后,访问网站时,自动检查浏览器环境,阻止非正常访问。

+
+
+
` +}) + +Vue.component("http-header-policy-box", { + props: ["v-request-header-policy", "v-request-header-ref", "v-response-header-policy", "v-response-header-ref", "v-params", "v-is-location", "v-is-group", "v-has-group-request-config", "v-has-group-response-config", "v-group-setting-url"], + data: function () { + let type = "response" + let hash = window.location.hash + if (hash == "#request") { + type = "request" + } + + // ref + let requestHeaderRef = this.vRequestHeaderRef + if (requestHeaderRef == null) { + requestHeaderRef = { + isPrior: false, + isOn: true, + headerPolicyId: 0 + } + } + + let responseHeaderRef = this.vResponseHeaderRef + if (responseHeaderRef == null) { + responseHeaderRef = { + isPrior: false, + isOn: true, + headerPolicyId: 0 + } + } + + // 请求相关 + let requestSettingHeaders = [] + let requestDeletingHeaders = [] + + let requestPolicy = this.vRequestHeaderPolicy + if (requestPolicy != null) { + if (requestPolicy.setHeaders != null) { + requestSettingHeaders = requestPolicy.setHeaders + } + if (requestPolicy.deleteHeaders != null) { + requestDeletingHeaders = requestPolicy.deleteHeaders + } + } + + // 响应相关 + let responseSettingHeaders = [] + let responseDeletingHeaders = [] + + let responsePolicy = this.vResponseHeaderPolicy + if (responsePolicy != null) { + if (responsePolicy.setHeaders != null) { + responseSettingHeaders = responsePolicy.setHeaders + } + if (responsePolicy.deleteHeaders != null) { + responseDeletingHeaders = responsePolicy.deleteHeaders + } + } + + return { + type: type, + typeName: (type == "request") ? "请求" : "响应", + requestHeaderRef: requestHeaderRef, + responseHeaderRef: responseHeaderRef, + requestSettingHeaders: requestSettingHeaders, + requestDeletingHeaders: requestDeletingHeaders, + responseSettingHeaders: responseSettingHeaders, + responseDeletingHeaders: responseDeletingHeaders + } + }, + methods: { + selectType: function (type) { + this.type = type + window.location.hash = "#" + type + window.location.reload() + }, + addSettingHeader: function (policyId) { + teaweb.popup("/servers/server/settings/headers/createSetPopup?" + this.vParams + "&headerPolicyId=" + policyId + "&type=" + this.type, { + callback: function () { + teaweb.successRefresh("保存成功") + } + }) + }, + addDeletingHeader: function (policyId, type) { + teaweb.popup("/servers/server/settings/headers/createDeletePopup?" + this.vParams + "&headerPolicyId=" + policyId + "&type=" + type, { + callback: function () { + teaweb.successRefresh("保存成功") + } + }) + }, + updateSettingPopup: function (policyId, headerId) { + teaweb.popup("/servers/server/settings/headers/updateSetPopup?" + this.vParams + "&headerPolicyId=" + policyId + "&headerId=" + headerId+ "&type=" + this.type, { + callback: function () { + teaweb.successRefresh("保存成功") + } + }) + }, + deleteDeletingHeader: function (policyId, headerName) { + teaweb.confirm("确定要删除'" + headerName + "'吗?", function () { + Tea.action("/servers/server/settings/headers/deleteDeletingHeader") + .params({ + headerPolicyId: policyId, + headerName: headerName + }) + .post() + .refresh() + }) + }, + deleteHeader: function (policyId, type, headerId) { + teaweb.confirm("确定要删除此Header吗?", function () { + this.$post("/servers/server/settings/headers/delete") + .params({ + headerPolicyId: policyId, + type: type, + headerId: headerId + }) + .refresh() + } + ) + } + }, + template: `
+ + +
+ + + + +
+ + + +
+ +
+ +
+
+
+ 由于已经在当前服务分组中进行了对应的配置,在这里的配置将不会生效。 +
+
+

设置请求Header [添加新Header]

+

暂时还没有Header。

+ + + + + + + + + + + + + +
名称操作
+ {{header.name}} +
+ {{code}} + {{method}} + {{domain}} + 附加 + 跳转禁用 + 替换 +
+
{{header.value}}修改   删除
+ +

删除请求Header

+

这里可以设置需要从请求中删除的Header。

+ + + + +
需要删除的Header +
+
{{headerName}}
+
+
+ +
+
+
+ + +
+ + + +
+ +
+ +
+
+
+ 由于已经在当前服务分组中进行了对应的配置,在这里的配置将不会生效。 +
+
+

设置响应Header [添加新Header]

+

将会覆盖已有的同名Header。

+

暂时还没有Header。

+ + + + + + + + + + + + + +
名称操作
+ {{header.name}} +
+ {{code}} + {{method}} + {{domain}} + 附加 + 跳转禁用 + 替换 +
+
{{header.value}}修改   删除
+ +

删除响应Header

+

这里可以设置需要从响应中删除的Header。

+ + + + +
需要删除的Header +
+
{{headerName}}
+
+
+ +
+
+
+
+
` +}) + +// 通用设置 +Vue.component("http-common-config-box", { + props: ["v-common-config"], + data: function () { + let config = this.vCommonConfig + if (config == null) { + config = { + mergeSlashes: false + } + } + return { + config: config + } + }, + template: `
+ + + + + +
合并重复的路径分隔符 +
+ + +
+

合并URL中重复的路径分隔符为一个,比如//hello/world中的//

+
+
+
` +}) + +Vue.component("http-cache-policy-selector", { + props: ["v-cache-policy"], + mounted: function () { + let that = this + Tea.action("/servers/components/cache/count") + .post() + .success(function (resp) { + that.count = resp.data.count + }) + }, + data: function () { + let cachePolicy = this.vCachePolicy + return { + count: 0, + cachePolicy: cachePolicy + } + }, + methods: { + remove: function () { + this.cachePolicy = null + }, + select: function () { + let that = this + teaweb.popup("/servers/components/cache/selectPopup", { + callback: function (resp) { + that.cachePolicy = resp.data.cachePolicy + } + }) + }, + create: function () { + let that = this + teaweb.popup("/servers/components/cache/createPopup", { + height: "26em", + callback: function (resp) { + that.cachePolicy = resp.data.cachePolicy + } + }) + } + }, + template: `
+
+ + {{cachePolicy.name}}     +
+ +
` +}) + +Vue.component("http-pages-and-shutdown-box", { + props: ["v-pages", "v-shutdown-config", "v-is-location"], + data: function () { + let pages = [] + if (this.vPages != null) { + pages = this.vPages + } + let shutdownConfig = { + isPrior: false, + isOn: false, + bodyType: "url", + url: "", + body: "", + status: 0 + } + if (this.vShutdownConfig != null) { + if (this.vShutdownConfig.body == null) { + this.vShutdownConfig.body = "" + } + if (this.vShutdownConfig.bodyType == null) { + this.vShutdownConfig.bodyType = "url" + } + shutdownConfig = this.vShutdownConfig + } + + let shutdownStatus = "" + if (shutdownConfig.status > 0) { + shutdownStatus = shutdownConfig.status.toString() + } + + return { + pages: pages, + shutdownConfig: shutdownConfig, + shutdownStatus: shutdownStatus + } + }, + watch: { + shutdownStatus: function (status) { + let statusInt = parseInt(status) + if (!isNaN(statusInt) && statusInt > 0 && statusInt < 1000) { + this.shutdownConfig.status = statusInt + } else { + this.shutdownConfig.status = 0 + } + } + }, + methods: { + addPage: function () { + let that = this + teaweb.popup("/servers/server/settings/pages/createPopup", { + height: "26em", + callback: function (resp) { + that.pages.push(resp.data.page) + } + }) + }, + updatePage: function (pageIndex, pageId) { + let that = this + teaweb.popup("/servers/server/settings/pages/updatePopup?pageId=" + pageId, { + height: "26em", + callback: function (resp) { + Vue.set(that.pages, pageIndex, resp.data.page) + } + }) + }, + removePage: function (pageIndex) { + let that = this + teaweb.confirm("确定要移除此页面吗?", function () { + that.pages.$remove(pageIndex) + }) + }, + addShutdownHTMLTemplate: function () { + this.shutdownConfig.body = ` + + +\t升级中 +\t + + + +

网站升级中

+

为了给您提供更好的服务,我们正在升级网站,请稍后重新访问。

+ +
Request ID: \${requestId}.
+ + +` + } + }, + template: `
+ + + + + + + + + + + +
自定义页面 +
+
+ {{page.status}} -> {{page.url}}[HTML内容] +
+
+
+
+ +
+

根据响应状态码返回一些自定义页面,比如404,500等错误页面。

+
临时关闭页面 +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
是否开启 +
+ + +
+
内容类型 * + +
页面URL * + +

页面文件是相对于节点安装目录的页面文件比如pages/40x.html,或者一个完整的URL。

+
HTML * + +

[使用模板]。填写页面的HTML内容,支持请求变量。

+
状态码
+

开启临时关闭页面时,所有请求都会直接显示此页面。可用于临时升级网站或者禁止用户访问某个网页。

+
+
+
+
` +}) + +// 压缩配置 +Vue.component("http-compression-config-box", { + props: ["v-compression-config", "v-is-location", "v-is-group"], + mounted: function () { + let that = this + sortLoad(function () { + that.initSortableTypes() + }) + }, + data: function () { + let config = this.vCompressionConfig + if (config == null) { + config = { + isPrior: false, + isOn: false, + useDefaultTypes: true, + types: ["brotli", "gzip", "deflate"], + level: 5, + decompressData: false, + gzipRef: null, + deflateRef: null, + brotliRef: null, + minLength: {count: 0, "unit": "kb"}, + maxLength: {count: 0, "unit": "kb"}, + mimeTypes: ["text/*", "application/*", "font/*"], + extensions: [".js", ".json", ".html", ".htm", ".xml", ".css", ".woff2", ".txt"], + conds: null + } + } + + if (config.types == null) { + config.types = [] + } + if (config.mimeTypes == null) { + config.mimeTypes = [] + } + if (config.extensions == null) { + config.extensions = [] + } + + let allTypes = [ + { + name: "Gzip", + code: "gzip", + isOn: true + }, + { + name: "Deflate", + code: "deflate", + isOn: true + }, + { + name: "Brotli", + code: "brotli", + isOn: true + } + ] + + let configTypes = [] + config.types.forEach(function (typeCode) { + allTypes.forEach(function (t) { + if (typeCode == t.code) { + t.isOn = true + configTypes.push(t) + } + }) + }) + allTypes.forEach(function (t) { + if (!config.types.$contains(t.code)) { + t.isOn = false + configTypes.push(t) + } + }) + + return { + config: config, + moreOptionsVisible: false, + allTypes: configTypes + } + }, + watch: { + "config.level": function (v) { + let level = parseInt(v) + if (isNaN(level)) { + level = 1 + } else if (level < 1) { + level = 1 + } else if (level > 10) { + level = 10 + } + this.config.level = level + } + }, + methods: { + isOn: function () { + return ((!this.vIsLocation && !this.vIsGroup) || this.config.isPrior) && this.config.isOn + }, + changeExtensions: function (values) { + values.forEach(function (v, k) { + if (v.length > 0 && v[0] != ".") { + values[k] = "." + v + } + }) + this.config.extensions = values + }, + changeMimeTypes: function (values) { + this.config.mimeTypes = values + }, + changeAdvancedVisible: function () { + this.moreOptionsVisible = !this.moreOptionsVisible + }, + changeConds: function (conds) { + this.config.conds = conds + }, + changeType: function () { + this.config.types = [] + let that = this + this.allTypes.forEach(function (v) { + if (v.isOn) { + that.config.types.push(v.code) + } + }) + }, + initSortableTypes: function () { + let box = document.querySelector("#compression-types-box") + let that = this + Sortable.create(box, { + draggable: ".checkbox", + handle: ".icon.handle", + onStart: function () { + + }, + onUpdate: function (event) { + let checkboxes = box.querySelectorAll(".checkbox") + let codes = [] + checkboxes.forEach(function (checkbox) { + let code = checkbox.getAttribute("data-code") + codes.push(code) + }) + that.config.types = codes + } + }) + } + }, + template: `
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
是否启用 +
+ + +
+
压缩级别 + +

级别越高,压缩比例越大。

+
支持的扩展名 + +

含有这些扩展名的URL将会被压缩,不区分大小写。

+
支持的MimeType + +

响应的Content-Type里包含这些MimeType的内容将会被压缩。

+
压缩算法 +
+ + + +
+
+
+
+
+ + +
+
+
+ +

选择支持的压缩算法和优先顺序,拖动图表排序。

+
支持已压缩内容 + +

支持对已压缩内容尝试重新使用新的算法压缩;不选中表示保留当前的压缩格式。

+
内容最小长度 + +

0表示不限制,内容长度从文件尺寸或Content-Length中获取。

+
内容最大长度 + +

0表示不限制,内容长度从文件尺寸或Content-Length中获取。

+
匹配条件 + +
+
+
` +}) + +Vue.component("firewall-event-level-options", { + props: ["v-value"], + mounted: function () { + let that = this + Tea.action("/ui/eventLevelOptions") + .post() + .success(function (resp) { + that.levels = resp.data.eventLevels + that.change() + }) + }, + data: function () { + let value = this.vValue + if (value == null || value.length == 0) { + value = "" // 不要给默认值,因为黑白名单等默认值均有不同 + } + + return { + levels: [], + description: "", + level: value + } + }, + methods: { + change: function () { + this.$emit("change") + + let that = this + let l = this.levels.$find(function (k, v) { + return v.code == that.level + }) + if (l != null) { + this.description = l.description + } else { + this.description = "" + } + } + }, + template: `
+ +

{{description}}

+
` +}) + +Vue.component("prior-checkbox", { + props: ["v-config"], + data: function () { + return { + isPrior: this.vConfig.isPrior + } + }, + watch: { + isPrior: function (v) { + this.vConfig.isPrior = v + } + }, + template: ` + + 打开独立配置 + +
+ + +
+

[已打开] 打开后可以覆盖父级或子级配置。

+ + +` +}) + +Vue.component("http-charsets-box", { + props: ["v-usual-charsets", "v-all-charsets", "v-charset-config", "v-is-location", "v-is-group"], + data: function () { + let charsetConfig = this.vCharsetConfig + if (charsetConfig == null) { + charsetConfig = { + isPrior: false, + isOn: false, + charset: "", + isUpper: false + } + } + return { + charsetConfig: charsetConfig, + advancedVisible: false + } + }, + methods: { + changeAdvancedVisible: function (v) { + this.advancedVisible = v + } + }, + template: `
+ + + + + + + + + + + + + + + + + + + + + + +
是否启用 +
+ + +
+
选择字符编码 +
字符编码是否大写 +
+ + +
+

选中后将指定的字符编码转换为大写,比如默认为utf-8,选中后将改为UTF-8

+
+
+
` +}) + +Vue.component("http-expires-time-config-box", { + props: ["v-expires-time"], + data: function () { + let expiresTime = this.vExpiresTime + if (expiresTime == null) { + expiresTime = { + isPrior: false, + isOn: false, + overwrite: true, + autoCalculate: true, + duration: {count: -1, "unit": "hour"} + } + } + return { + expiresTime: expiresTime + } + }, + watch: { + "expiresTime.isPrior": function () { + this.notifyChange() + }, + "expiresTime.isOn": function () { + this.notifyChange() + }, + "expiresTime.overwrite": function () { + this.notifyChange() + }, + "expiresTime.autoCalculate": function () { + this.notifyChange() + } + }, + methods: { + notifyChange: function () { + this.$emit("change", this.expiresTime) + } + }, + template: `
+ + + + + + + + + + + + + + + + + + + + +
是否启用 +

启用后,将会在响应的Header中添加Expires字段,浏览器据此会将内容缓存在客户端;同时,在管理后台执行清理缓存时,也将无法清理客户端已有的缓存。

+
覆盖源站设置 + +

选中后,会覆盖源站Header中已有的Expires字段。

+
自动计算时间 +

根据已设置的缓存有效期进行计算。

+
强制缓存时间 + +

从客户端访问的时间开始要缓存的时长。

+
+
` +}) + +Vue.component("http-access-log-box", { + props: ["v-access-log", "v-keyword", "v-show-server-link"], + data: function () { + let accessLog = this.vAccessLog + if (accessLog.header != null && accessLog.header.Upgrade != null && accessLog.header.Upgrade.values != null && accessLog.header.Upgrade.values.$contains("websocket")) { + if (accessLog.scheme == "http") { + accessLog.scheme = "ws" + } else if (accessLog.scheme == "https") { + accessLog.scheme = "wss" + } + } + + return { + accessLog: accessLog + } + }, + methods: { + formatCost: function (seconds) { + var s = (seconds * 1000).toString(); + var pieces = s.split("."); + if (pieces.length < 2) { + return s; + } + + return pieces[0] + "." + pieces[1].substr(0, 3); + }, + showLog: function () { + let that = this + let requestId = this.accessLog.requestId + this.$parent.$children.forEach(function (v) { + if (v.deselect != null) { + v.deselect() + } + }) + this.select() + teaweb.popup("/servers/server/log/viewPopup?requestId=" + requestId, { + width: "50em", + height: "28em", + onClose: function () { + that.deselect() + } + }) + }, + select: function () { + this.$refs.box.parentNode.style.cssText = "background: rgba(0, 0, 0, 0.1)" + }, + deselect: function () { + this.$refs.box.parentNode.style.cssText = "" + } + }, + template: `
+ [{{accessLog.node.name}}节点] + [服务] + [{{accessLog.region}}] {{accessLog.remoteAddr}} [{{accessLog.timeLocal}}] "{{accessLog.requestMethod}} {{accessLog.scheme}}://{{accessLog.host}}{{accessLog.requestURI}} {{accessLog.proto}}" {{accessLog.status}} cache {{accessLog.attrs['cache.status'].toLowerCase()}} waf {{accessLog.firewallActions}} - {{tag}} - 耗时:{{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 that = this + + // 初始化 + setTimeout(function () { + that.changeFields() + }, 100) + + let accessLog = { + isPrior: false, + isOn: false, + fields: [1, 2, 6, 7], + status1: true, + status2: true, + status3: true, + status4: true, + status5: true, + + firewallOnly: false, + enableClientClosed: false + } + if (this.vAccessLogConfig != null) { + accessLog = this.vAccessLogConfig + } + + this.vFields.forEach(function (v) { + if (that.vAccessLogConfig == null) { // 初始化默认值 + v.isChecked = that.vDefaultFieldCodes.$contains(v.code) + } else { + v.isChecked = accessLog.fields.$contains(v.code) + } + }) + + return { + accessLog: accessLog, + hasRequestBodyField: this.vFields.$contains(8) + } + }, + methods: { + changeFields: function () { + this.accessLog.fields = this.vFields.filter(function (v) { + return v.isChecked + }).map(function (v) { + return v.code + }) + this.hasRequestBodyField = this.accessLog.fields.$contains(8) + } + }, + template: `
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
是否开启访问日志存储 +
+ + +
+

关闭访问日志,并不影响统计的运行。

+
基础信息

默认记录客户端IP、请求URL等基础信息。

高级信息 +
+ + +
+

在基础信息之外要存储的信息。 + 记录"请求Body"将会显著消耗更多的系统资源,建议仅在调试时启用,最大记录尺寸为2MB。 +

+
要存储的访问日志状态码 +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
记录客户端中断日志 +
+ + +
+

499的状态码记录客户端主动中断日志。

+
+ +
+

WAF相关

+ + + + + +
是否只记录WAF相关日志 + +

选中后只记录WAF相关的日志。通过此选项可有效减少访问日志数量,降低网络带宽和存储压力。

+
+
+
+
` +}) + +// 显示流量限制说明 +Vue.component("traffic-limit-view", { + props: ["v-traffic-limit"], + data: function () { + return { + config: this.vTrafficLimit + } + }, + template: `
+
+ 日流量限制:{{config.dailySize.count}}{{config.dailySize.unit.toUpperCase()}}
+ 月流量限制:{{config.monthlySize.count}}{{config.monthlySize.unit.toUpperCase()}}
+
+ 没有限制。 +
` +}) + +// 基本认证用户配置 +Vue.component("http-auth-basic-auth-user-box", { + props: ["v-users"], + data: function () { + let users = this.vUsers + if (users == null) { + users = [] + } + return { + users: users, + isAdding: false, + updatingIndex: -1, + + username: "", + password: "" + } + }, + methods: { + add: function () { + this.isAdding = true + this.username = "" + this.password = "" + + let that = this + setTimeout(function () { + that.$refs.username.focus() + }, 100) + }, + cancel: function () { + this.isAdding = false + this.updatingIndex = -1 + }, + confirm: function () { + let that = this + if (this.username.length == 0) { + teaweb.warn("请输入用户名", function () { + that.$refs.username.focus() + }) + return + } + if (this.password.length == 0) { + teaweb.warn("请输入密码", function () { + that.$refs.password.focus() + }) + return + } + if (this.updatingIndex < 0) { + this.users.push({ + username: this.username, + password: this.password + }) + } else { + this.users[this.updatingIndex].username = this.username + this.users[this.updatingIndex].password = this.password + } + this.cancel() + }, + update: function (index, user) { + this.updatingIndex = index + + this.isAdding = true + this.username = user.username + this.password = user.password + + let that = this + setTimeout(function () { + that.$refs.username.focus() + }, 100) + }, + remove: function (index) { + this.users.$remove(index) + } + }, + template: `
+ +
+
+ {{user.username}} + +
+
+
+
+
+
+ +
+
+ +
+
+   + +
+
+
+
+ +
+
` +}) + +Vue.component("http-location-labels", { + props: ["v-location-config", "v-server-id"], + data: function () { + return { + location: this.vLocationConfig + } + }, + methods: { + // 判断是否已启用某配置 + configIsOn: function (config) { + return config != null && config.isPrior && config.isOn + }, + + refIsOn: function (ref, config) { + return this.configIsOn(ref) && config != null && config.isOn + }, + + len: function (arr) { + return (arr == null) ? 0 : arr.length + }, + url: function (path) { + return "/servers/server/settings/locations" + path + "?serverId=" + this.vServerId + "&locationId=" + this.location.id + } + }, + template: `
+ + {{location.name}} + + +
+ {{domain}} +
+ + + BREAK + + + 自动跳转HTTPS + + + 文档根目录 + + + 反向代理 + + + + + + CACHE + + + {{location.web.charset.charset}} + + + + + + + + + Gzip:{{location.web.gzip.level}} + + + 请求Header + 响应Header + + + Websocket + + + 请求脚本 + + +
+
PAGE [状态码{{page.status[0]}}] -> {{page.url}}
+
+
+ 临时关闭 +
+ + +
+
+ REWRITE {{rewriteRule.pattern}} -> {{rewriteRule.replace}} +
+
+
` +}) + +Vue.component("http-location-labels-label", { + props: ["v-class", "v-href"], + template: `` +}) + +Vue.component("http-gzip-box", { + props: ["v-gzip-config", "v-gzip-ref", "v-is-location"], + data: function () { + let gzip = this.vGzipConfig + if (gzip == null) { + gzip = { + isOn: true, + level: 0, + minLength: null, + maxLength: null, + conds: null + } + } + + return { + gzip: gzip, + advancedVisible: false + } + }, + methods: { + isOn: function () { + return (!this.vIsLocation || this.vGzipRef.isPrior) && this.vGzipRef.isOn + }, + changeAdvancedVisible: function (v) { + this.advancedVisible = v + } + }, + template: `
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
启用Gzip压缩 +
+ + +
+
压缩级别 + +

级别越高,压缩比例越大。

+
Gzip内容最小长度 + +

0表示不限制,内容长度从文件尺寸或Content-Length中获取。

+
Gzip内容最大长度 + +

0表示不限制,内容长度从文件尺寸或Content-Length中获取。

+
匹配条件 + +
+
` +}) + +Vue.component("script-config-box", { + props: ["id", "v-script-config", "comment"], + data: function () { + let config = this.vScriptConfig + if (config == null) { + config = { + isPrior: false, + isOn: false, + code: "" + } + } + + if (config.code.length == 0) { + config.code = "\n\n\n\n" + } + + return { + config: config + } + }, + watch: { + "config.isOn": function () { + this.change() + } + }, + methods: { + change: function () { + this.$emit("change", this.config) + }, + changeCode: function (code) { + this.config.code = code + this.change() + } + }, + template: `
+ + + + + + + + + + + + + +
是否启用
脚本代码{{config.code}} +

{{comment}}

+
+
` +}) + +Vue.component("ssl-certs-view", { + props: ["v-certs"], + data: function () { + let certs = this.vCerts + if (certs == null) { + certs = [] + } + return { + certs: certs + } + }, + methods: { + // 格式化时间 + formatTime: function (timestamp) { + return new Date(timestamp * 1000).format("Y-m-d") + }, + + // 查看详情 + viewCert: function (certId) { + teaweb.popup("/servers/certs/certPopup?certId=" + certId, { + height: "28em", + width: "48em" + }) + } + }, + template: `
+
+
+ {{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 reverseProxyRef = this.vReverseProxyRef + if (reverseProxyRef == null) { + reverseProxyRef = { + isPrior: false, + isOn: false, + reverseProxyId: 0 + } + } + + let reverseProxyConfig = this.vReverseProxyConfig + if (reverseProxyConfig == null) { + reverseProxyConfig = { + 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: false + } + } + if (reverseProxyConfig.addHeaders == null) { + reverseProxyConfig.addHeaders = [] + } + if (reverseProxyConfig.connTimeout == null) { + reverseProxyConfig.connTimeout = {count: 0, unit: "second"} + } + if (reverseProxyConfig.readTimeout == null) { + reverseProxyConfig.readTimeout = {count: 0, unit: "second"} + } + if (reverseProxyConfig.idleTimeout == null) { + reverseProxyConfig.idleTimeout = {count: 0, unit: "second"} + } + + if (reverseProxyConfig.proxyProtocol == null) { + // 如果直接赋值Vue将不会触发变更通知 + Vue.set(reverseProxyConfig, "proxyProtocol", { + isOn: false, + version: 1 + }) + } + + let forwardHeaders = [ + { + name: "X-Real-IP", + isChecked: false + }, + { + name: "X-Forwarded-For", + isChecked: false + }, + { + name: "X-Forwarded-By", + isChecked: false + }, + { + name: "X-Forwarded-Host", + isChecked: false + }, + { + name: "X-Forwarded-Proto", + isChecked: false + } + ] + forwardHeaders.forEach(function (v) { + v.isChecked = reverseProxyConfig.addHeaders.$contains(v.name) + }) + + return { + reverseProxyRef: reverseProxyRef, + reverseProxyConfig: reverseProxyConfig, + advancedVisible: false, + family: this.vFamily, + forwardHeaders: forwardHeaders + } + }, + watch: { + "reverseProxyConfig.requestHostType": function (v) { + let requestHostType = parseInt(v) + if (isNaN(requestHostType)) { + requestHostType = 0 + } + this.reverseProxyConfig.requestHostType = requestHostType + }, + "reverseProxyConfig.connTimeout.count": function (v) { + let count = parseInt(v) + if (isNaN(count) || count < 0) { + count = 0 + } + this.reverseProxyConfig.connTimeout.count = count + }, + "reverseProxyConfig.readTimeout.count": function (v) { + let count = parseInt(v) + if (isNaN(count) || count < 0) { + count = 0 + } + this.reverseProxyConfig.readTimeout.count = count + }, + "reverseProxyConfig.idleTimeout.count": function (v) { + let count = parseInt(v) + if (isNaN(count) || count < 0) { + count = 0 + } + this.reverseProxyConfig.idleTimeout.count = count + }, + "reverseProxyConfig.maxConns": function (v) { + let maxConns = parseInt(v) + if (isNaN(maxConns) || maxConns < 0) { + maxConns = 0 + } + this.reverseProxyConfig.maxConns = maxConns + }, + "reverseProxyConfig.maxIdleConns": function (v) { + let maxIdleConns = parseInt(v) + if (isNaN(maxIdleConns) || maxIdleConns < 0) { + maxIdleConns = 0 + } + this.reverseProxyConfig.maxIdleConns = maxIdleConns + }, + "reverseProxyConfig.proxyProtocol.version": function (v) { + let version = parseInt(v) + if (isNaN(version)) { + version = 1 + } + this.reverseProxyConfig.proxyProtocol.version = version + } + }, + methods: { + isOn: function () { + if (this.vIsLocation || this.vIsGroup) { + return this.reverseProxyRef.isPrior && this.reverseProxyRef.isOn + } + return this.reverseProxyRef.isOn + }, + changeAdvancedVisible: function (v) { + this.advancedVisible = v + }, + changeAddHeader: function () { + this.reverseProxyConfig.addHeaders = this.forwardHeaders.filter(function (v) { + return v.isChecked + }).map(function (v) { + return v.name + }) + } + }, + template: `
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
是否启用反向代理 +
+ + +
+
回源主机名(Host) + 跟随代理服务   + 跟随源站   + 自定义 +
+ +
+

请求源站时的Host,用于修改源站接收到的域名 + ,"跟随代理服务"是指源站接收到的域名和当前代理服务保持一致 + ,"跟随源站"是指源站接收到的域名仍然是填写的源站地址中的信息,不随代理服务域名改变而改变 + ,自定义Host内容中支持请求变量

+
回源跟随 + +

选中后,自动读取源站跳转后的网页内容。

+
自动添加的Header +
+
+ {{header.name}} +
+
+
+

选中后,会自动向源站请求添加这些Header。

+
请求URI(RequestURI) + +

\${requestURI}为完整的请求URI,可以使用类似于"\${requestURI}?arg1=value1&arg2=value2"的形式添加你的参数。

+
去除URL前缀(StripPrefix) + +

可以把请求的路径部分前缀去除后再查找文件,比如把 /web/app/index.html 去除前缀 /web 后就变成 /app/index.html

+
是否自动刷新缓存区(AutoFlush) +
+ + +
+

开启后将自动刷新缓冲区数据到客户端,在类似于SSE(server-sent events)等场景下很有用。

+
源站连接失败超时时间 +
+
+ +
+
+ 秒 +
+
+

连接源站失败的最大超时时间,0表示不限制。

+
源站读取超时时间 +
+
+ +
+
+ 秒 +
+
+

读取内容时的最大超时时间,0表示不限制。

+
源站最大并发连接数 +
+
+ +
+
+

源站可以接受到的最大并发连接数,0表示使用系统默认。

+
源站最大空闲连接数 +
+
+ +
+
+

当没有请求时,源站保持等待的最大空闲连接数量,0表示使用系统默认。

+
源站最大空闲超时时间 +
+
+ +
+
+ 秒 +
+
+

源站保持等待的空闲超时时间,0表示使用默认时间。

+
PROXY Protocol + +

选中后表示启用PROXY Protocol,每次连接源站时都会在头部写入客户端地址信息。

+
PROXY Protocol版本 + +

发送类似于PROXY TCP4 192.168.1.1 192.168.1.10 32567 443的头部信息。

+

发送二进制格式的头部信息。

+
+
+
` +}) + +Vue.component("http-firewall-param-filters-box", { + props: ["v-filters"], + data: function () { + let filters = this.vFilters + if (filters == null) { + filters = [] + } + + return { + filters: filters, + isAdding: false, + options: [ + {name: "MD5", code: "md5"}, + {name: "URLEncode", code: "urlEncode"}, + {name: "URLDecode", code: "urlDecode"}, + {name: "BASE64Encode", code: "base64Encode"}, + {name: "BASE64Decode", code: "base64Decode"}, + {name: "UNICODE编码", code: "unicodeEncode"}, + {name: "UNICODE解码", code: "unicodeDecode"}, + {name: "HTML实体编码", code: "htmlEscape"}, + {name: "HTML实体解码", code: "htmlUnescape"}, + {name: "计算长度", code: "length"}, + {name: "十六进制->十进制", "code": "hex2dec"}, + {name: "十进制->十六进制", "code": "dec2hex"}, + {name: "SHA1", "code": "sha1"}, + {name: "SHA256", "code": "sha256"} + ], + addingCode: "" + } + }, + methods: { + add: function () { + this.isAdding = true + this.addingCode = "" + }, + confirm: function () { + if (this.addingCode.length == 0) { + return + } + let that = this + this.filters.push(this.options.$find(function (k, v) { + return (v.code == that.addingCode) + })) + this.isAdding = false + }, + cancel: function () { + this.isAdding = false + }, + remove: function (index) { + this.filters.$remove(index) + } + }, + template: `
+ +
+
+ {{filter.name}} +
+
+
+
+
+
+ +
+
+ +   +
+
+
+
+ +
+

可以对参数值进行特定的编解码处理。

+
` +}) + +Vue.component("http-remote-addr-config-box", { + props: ["v-remote-addr-config", "v-is-location", "v-is-group"], + data: function () { + let config = this.vRemoteAddrConfig + if (config == null) { + config = { + isPrior: false, + isOn: false, + value: "${rawRemoteAddr}", + isCustomized: false + } + } + + let optionValue = "" + if (!config.isCustomized && (config.value == "${remoteAddr}" || config.value == "${rawRemoteAddr}")) { + optionValue = config.value + } + + return { + config: config, + options: [ + { + name: "直接获取", + description: "用户直接访问边缘节点,即 \"用户 --> 边缘节点\" 模式,这时候可以直接从连接中读取到真实的IP地址。", + value: "${rawRemoteAddr}" + }, + { + name: "从上级代理中获取", + description: "用户和边缘节点之间有别的代理服务转发,即 \"用户 --> [第三方代理服务] --> 边缘节点\",这时候只能从上级代理中获取传递的IP地址。", + value: "${remoteAddr}" + }, + { + name: "[自定义]", + description: "通过自定义变量来获取客户端真实的IP地址。", + value: "" + } + ], + optionValue: optionValue + } + }, + methods: { + isOn: function () { + return ((!this.vIsLocation && !this.vIsGroup) || this.config.isPrior) && this.config.isOn + }, + changeOptionValue: function () { + if (this.optionValue.length > 0) { + this.config.value = this.optionValue + this.config.isCustomized = false + } else { + this.config.isCustomized = true + } + } + }, + template: `
+ + + + + + + + + + + + + + + + + + + +
是否启用 +
+ + +
+

选中后表示使用自定义的请求变量获取客户端IP。

+
获取IP方式 * + +

{{option.description}}

+
读取IP变量值 * + +
+ +

通过此变量获取用户的IP地址。具体可用的请求变量列表可参考官方网站文档。

+
+
+
+
` +}) + +// 访问日志搜索框 +Vue.component("http-access-log-search-box", { + props: ["v-ip", "v-domain", "v-keyword", "v-cluster-id", "v-node-id"], + data: function () { + let ip = this.vIp + if (ip == null) { + ip = "" + } + + let domain = this.vDomain + if (domain == null) { + domain = "" + } + + let keyword = this.vKeyword + if (keyword == null) { + keyword = "" + } + + return { + ip: ip, + domain: domain, + keyword: keyword, + clusterId: this.vClusterId + } + }, + methods: { + cleanIP: function () { + this.ip = "" + this.submit() + }, + cleanDomain: function () { + this.domain = "" + this.submit() + }, + cleanKeyword: function () { + this.keyword = "" + this.submit() + }, + submit: function () { + let parent = this.$el.parentNode + while (true) { + if (parent == null) { + break + } + if (parent.tagName == "FORM") { + break + } + parent = parent.parentNode + } + if (parent != null) { + setTimeout(function () { + parent.submit() + }, 500) + } + }, + changeCluster: function (clusterId) { + this.clusterId = clusterId + } + }, + template: `
+
+
+
+
+ IP + + +
+
+
+
+ 域名 + + +
+
+
+
+ 关键词 + + +
+
+
+
+
+
+ +
+
+ +
+ +
+ +
+
+
` +}) + +// 显示指标对象名 +Vue.component("metric-key-label", { + props: ["v-key"], + data: function () { + return { + keyDefs: window.METRIC_HTTP_KEYS + } + }, + methods: { + keyName: function (key) { + let that = this + let subKey = "" + let def = this.keyDefs.$find(function (k, v) { + if (v.code == key) { + return true + } + if (key.startsWith("${arg.") && v.code.startsWith("${arg.")) { + subKey = that.getSubKey("arg.", key) + return true + } + if (key.startsWith("${header.") && v.code.startsWith("${header.")) { + subKey = that.getSubKey("header.", key) + return true + } + if (key.startsWith("${cookie.") && v.code.startsWith("${cookie.")) { + subKey = that.getSubKey("cookie.", key) + return true + } + return false + }) + if (def != null) { + if (subKey.length > 0) { + return def.name + ": " + subKey + } + return def.name + } + return key + }, + getSubKey: function (prefix, key) { + prefix = "${" + prefix + let index = key.indexOf(prefix) + if (index >= 0) { + key = key.substring(index + prefix.length) + key = key.substring(0, key.length - 1) + return key + } + return "" + } + }, + template: `
+ {{keyName(this.vKey)}} +
` +}) + +// 指标对象 +Vue.component("metric-keys-config-box", { + props: ["v-keys"], + data: function () { + let keys = this.vKeys + if (keys == null) { + keys = [] + } + return { + keys: keys, + isAdding: false, + key: "", + subKey: "", + keyDescription: "", + + keyDefs: window.METRIC_HTTP_KEYS + } + }, + watch: { + keys: function () { + this.$emit("change", this.keys) + } + }, + methods: { + cancel: function () { + this.key = "" + this.subKey = "" + this.keyDescription = "" + this.isAdding = false + }, + confirm: function () { + if (this.key.length == 0) { + return + } + + if (this.key.indexOf(".NAME") > 0) { + if (this.subKey.length == 0) { + teaweb.warn("请输入参数值") + return + } + this.key = this.key.replace(".NAME", "." + this.subKey) + } + this.keys.push(this.key) + this.cancel() + }, + add: function () { + this.isAdding = true + let that = this + setTimeout(function () { + if (that.$refs.key != null) { + that.$refs.key.focus() + } + }, 100) + }, + remove: function (index) { + this.keys.$remove(index) + }, + changeKey: function () { + if (this.key.length == 0) { + return + } + let that = this + let def = this.keyDefs.$find(function (k, v) { + return v.code == that.key + }) + if (def != null) { + this.keyDescription = def.description + } + }, + keyName: function (key) { + let that = this + let subKey = "" + let def = this.keyDefs.$find(function (k, v) { + if (v.code == key) { + return true + } + if (key.startsWith("${arg.") && v.code.startsWith("${arg.")) { + subKey = that.getSubKey("arg.", key) + return true + } + if (key.startsWith("${header.") && v.code.startsWith("${header.")) { + subKey = that.getSubKey("header.", key) + return true + } + if (key.startsWith("${cookie.") && v.code.startsWith("${cookie.")) { + subKey = that.getSubKey("cookie.", key) + return true + } + return false + }) + if (def != null) { + if (subKey.length > 0) { + return def.name + ": " + subKey + } + return def.name + } + return key + }, + getSubKey: function (prefix, key) { + prefix = "${" + prefix + let index = key.indexOf(prefix) + if (index >= 0) { + key = key.substring(index + prefix.length) + key = key.substring(0, key.length - 1) + return key + } + return "" + } + }, + template: `
+ +
+
+ {{keyName(key)}}   +
+
+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ + +
+
+

{{keyDescription}}

+
+
+ +
+
` +}) + +Vue.component("http-web-root-box", { + props: ["v-root-config", "v-is-location", "v-is-group"], + data: function () { + let rootConfig = this.vRootConfig + if (rootConfig == null) { + rootConfig = { + isPrior: false, + isOn: true, + dir: "", + indexes: [], + stripPrefix: "", + decodePath: false, + isBreak: false + } + } + if (rootConfig.indexes == null) { + rootConfig.indexes = [] + } + return { + rootConfig: rootConfig, + advancedVisible: false + } + }, + methods: { + changeAdvancedVisible: function (v) { + this.advancedVisible = v + }, + addIndex: function () { + let that = this + teaweb.popup("/servers/server/settings/web/createIndex", { + height: "10em", + callback: function (resp) { + that.rootConfig.indexes.push(resp.data.index) + } + }) + }, + removeIndex: function (i) { + this.rootConfig.indexes.$remove(i) + }, + isOn: function () { + return ((!this.vIsLocation && !this.vIsGroup) || this.rootConfig.isPrior) && this.rootConfig.isOn + } + }, + template: `
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
是否开启静态资源分发 +
+ + +
+
静态资源根目录 + +

可以访问此根目录下的静态资源。

+
首页文件 + +
+
+ {{index}} +
+
+
+ +

在URL中只有目录没有文件名时默认查找的首页文件。

+
去除URL前缀 + +

可以把请求的路径部分前缀去除后再查找文件,比如把 /web/app/index.html 去除前缀 /web 后就变成 /app/index.html

+
路径解码 +
+ + +
+

是否对请求路径进行URL解码,比如把 /Web+App+Browser.html 解码成 /Web App Browser.html 再查找文件。

+
是否终止请求 +
+ + +
+

在找不到要访问的文件的情况下是否终止请求并返回404,如果选择终止请求,则不再尝试反向代理等设置。

+
+
+
` +}) + +Vue.component("http-webp-config-box", { + props: ["v-webp-config", "v-is-location", "v-is-group", "v-require-cache"], + data: function () { + let config = this.vWebpConfig + if (config == null) { + config = { + isPrior: false, + isOn: false, + quality: 50, + minLength: {count: 0, "unit": "kb"}, + maxLength: {count: 0, "unit": "kb"}, + mimeTypes: ["image/png", "image/jpeg", "image/bmp", "image/x-ico", "image/gif"], + extensions: [".png", ".jpeg", ".jpg", ".bmp", ".ico"], + conds: null + } + } + + if (config.mimeTypes == null) { + config.mimeTypes = [] + } + if (config.extensions == null) { + config.extensions = [] + } + + return { + config: config, + moreOptionsVisible: false, + quality: config.quality + } + }, + watch: { + quality: function (v) { + let quality = parseInt(v) + if (isNaN(quality)) { + quality = 90 + } else if (quality < 1) { + quality = 1 + } else if (quality > 100) { + quality = 100 + } + this.config.quality = quality + } + }, + methods: { + isOn: function () { + return ((!this.vIsLocation && !this.vIsGroup) || this.config.isPrior) && this.config.isOn + }, + changeExtensions: function (values) { + values.forEach(function (v, k) { + if (v.length > 0 && v[0] != ".") { + values[k] = "." + v + } + }) + this.config.extensions = values + }, + changeMimeTypes: function (values) { + this.config.mimeTypes = values + }, + changeAdvancedVisible: function () { + this.moreOptionsVisible = !this.moreOptionsVisible + }, + changeConds: function (conds) { + this.config.conds = conds + } + }, + template: `
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
启用 +
+ + +
+

选中后表示开启自动WebP压缩;只有满足缓存条件的图片内容才会被转换

+
图片质量 +
+ + % +
+

取值在0到100之间,数值越大生成的图像越清晰,同时文件尺寸也会越大。

+
支持的扩展名 + +

含有这些扩展名的URL将会被转成WebP,不区分大小写。

+
支持的MimeType + +

响应的Content-Type里包含这些MimeType的内容将会被转成WebP。

+
内容最小长度 + +

0表示不限制,内容长度从文件尺寸或Content-Length中获取。

+
内容最大长度 + +

0表示不限制,内容长度从文件尺寸或Content-Length中获取。

+
匹配条件 + +
+
+
` +}) + +Vue.component("origin-scheduling-view-box", { + props: ["v-scheduling", "v-params"], + data: function () { + let scheduling = this.vScheduling + if (scheduling == null) { + scheduling = {} + } + return { + scheduling: scheduling + } + }, + methods: { + update: function () { + teaweb.popup("/servers/server/settings/reverseProxy/updateSchedulingPopup?" + this.vParams, { + height: "21em", + callback: function () { + window.location.reload() + }, + }) + } + }, + template: `
+
+ + + + + +
当前正在使用的算法 + {{scheduling.name}}   [修改] +

{{scheduling.description}}

+
+
` +}) + +Vue.component("http-firewall-block-options", { + props: ["v-block-options"], + data: function () { + return { + blockOptions: this.vBlockOptions, + statusCode: this.vBlockOptions.statusCode, + timeout: this.vBlockOptions.timeout, + isEditing: false + } + }, + 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}}秒 + + + + + + + + + + + + + +
状态码 + +
提示内容 + +
超时时间 +
+ + +
+

触发阻止动作时,封锁客户端IP的时间。

+
+
+` +}) + +Vue.component("http-firewall-rules-box", { + props: ["v-rules", "v-type"], + data: function () { + let rules = this.vRules + if (rules == null) { + rules = [] + } + return { + rules: rules + } + }, + methods: { + addRule: function () { + window.UPDATING_RULE = null + let that = this + teaweb.popup("/servers/components/waf/createRulePopup?type=" + this.vType, { + callback: function (resp) { + that.rules.push(resp.data.rule) + } + }) + }, + updateRule: function (index, rule) { + window.UPDATING_RULE = rule + let that = this + teaweb.popup("/servers/components/waf/createRulePopup?type=" + this.vType, { + callback: function (resp) { + Vue.set(that.rules, index, resp.data.rule) + } + }) + }, + removeRule: function (index) { + let that = this + teaweb.confirm("确定要删除此规则吗?", function () { + that.rules.$remove(index) + }) + } + }, + template: `
+ +
+
+ {{rule.name}}[{{rule.param}}] + + + + {{rule.checkpointOptions.period}}秒/{{rule.checkpointOptions.threshold}}请求 + + + + + {{rule.checkpointOptions.allowDomains}} + + + + | {{paramFilter.code}} {{rule.operator}} {{rule.value}} + + + + ({{rule.description}}) + + + +
+
+
+ +
` +}) + +Vue.component("http-fastcgi-box", { + props: ["v-fastcgi-ref", "v-fastcgi-configs", "v-is-location"], + data: function () { + let fastcgiRef = this.vFastcgiRef + if (fastcgiRef == null) { + fastcgiRef = { + isPrior: false, + isOn: false, + fastcgiIds: [] + } + } + let fastcgiConfigs = this.vFastcgiConfigs + if (fastcgiConfigs == null) { + fastcgiConfigs = [] + } else { + fastcgiRef.fastcgiIds = fastcgiConfigs.map(function (v) { + return v.id + }) + } + + return { + fastcgiRef: fastcgiRef, + fastcgiConfigs: fastcgiConfigs, + advancedVisible: false + } + }, + methods: { + isOn: function () { + return (!this.vIsLocation || this.fastcgiRef.isPrior) && this.fastcgiRef.isOn + }, + createFastcgi: function () { + let that = this + teaweb.popup("/servers/server/settings/fastcgi/createPopup", { + height: "26em", + callback: function (resp) { + teaweb.success("添加成功", function () { + that.fastcgiConfigs.push(resp.data.fastcgi) + that.fastcgiRef.fastcgiIds.push(resp.data.fastcgi.id) + }) + } + }) + }, + updateFastcgi: function (fastcgiId, index) { + let that = this + teaweb.popup("/servers/server/settings/fastcgi/updatePopup?fastcgiId=" + fastcgiId, { + callback: function (resp) { + teaweb.success("修改成功", function () { + Vue.set(that.fastcgiConfigs, index, resp.data.fastcgi) + }) + } + }) + }, + removeFastcgi: function (index) { + this.fastcgiRef.fastcgiIds.$remove(index) + this.fastcgiConfigs.$remove(index) + } + }, + template: `
+ + + + + + + + + + + + + + + +
是否启用配置 +
+ + +
+
Fastcgi服务 +
+
+ {{fastcgi.address}}     +
+
+
+ +
+
+
` +}) + +// 请求方法列表 +Vue.component("http-methods-box", { + props: ["v-methods"], + data: function () { + let methods = this.vMethods + if (methods == null) { + methods = [] + } + return { + methods: methods, + isAdding: false, + addingMethod: "" + } + }, + methods: { + add: function () { + this.isAdding = true + let that = this + setTimeout(function () { + that.$refs.addingMethod.focus() + }, 100) + }, + confirm: function () { + let that = this + + // 删除其中的空格 + this.addingMethod = this.addingMethod.replace(/\s/g, "").toUpperCase() + + if (this.addingMethod.length == 0) { + teaweb.warn("请输入要添加的请求方法", function () { + that.$refs.addingMethod.focus() + }) + return + } + + // 是否已经存在 + if (this.methods.$contains(this.addingMethod)) { + teaweb.warn("此请求方法已经存在,无需重复添加", function () { + that.$refs.addingMethod.focus() + }) + return + } + + this.methods.push(this.addingMethod) + this.cancel() + }, + remove: function (index) { + this.methods.$remove(index) + }, + cancel: function () { + this.isAdding = false + this.addingMethod = "" + } + }, + template: `
+ +
+ + {{method}} +   + +
+
+
+
+
+ +
+
+ +   +
+
+

格式为大写,比如GETPOST等。

+
+
+
+ +
+
` +}) + +// URL扩展名条件 +Vue.component("http-cond-url-extension", { + props: ["v-cond"], + data: function () { + let cond = { + isRequest: true, + param: "${requestPathExtension}", + operator: "in", + value: "[]" + } + if (this.vCond != null && this.vCond.param == cond.param) { + cond.value = this.vCond.value + } + + let extensions = [] + try { + extensions = JSON.parse(cond.value) + } catch (e) { + + } + + return { + cond: cond, + extensions: extensions, // TODO 可以拖动排序 + + isAdding: false, + addingExt: "" + } + }, + watch: { + extensions: function () { + this.cond.value = JSON.stringify(this.extensions) + } + }, + methods: { + addExt: function () { + this.isAdding = !this.isAdding + + if (this.isAdding) { + let that = this + setTimeout(function () { + that.$refs.addingExt.focus() + }, 100) + } + }, + cancelAdding: function () { + this.isAdding = false + this.addingExt = "" + }, + confirmAdding: function () { + // TODO 做更详细的校验 + // TODO 如果有重复的则提示之 + + if (this.addingExt.length == 0) { + return + } + if (this.addingExt[0] != ".") { + this.addingExt = "." + this.addingExt + } + this.addingExt = this.addingExt.replace(/\s+/g, "").toLowerCase() + this.extensions.push(this.addingExt) + + // 清除状态 + this.cancelAdding() + }, + removeExt: function (index) { + this.extensions.$remove(index) + } + }, + template: `
+ +
+
{{ext}}
+
+
+
+
+ +
+
+ + +
+
+
+ +
+

扩展名需要包含点(.)符号,例如.jpg.png之类。

+
` +}) + +// URL扩展名条件 +Vue.component("http-cond-url-not-extension", { + props: ["v-cond"], + data: function () { + let cond = { + isRequest: true, + param: "${requestPathExtension}", + operator: "not in", + value: "[]" + } + if (this.vCond != null && this.vCond.param == cond.param) { + cond.value = this.vCond.value + } + + let extensions = [] + try { + extensions = JSON.parse(cond.value) + } catch (e) { + + } + + return { + cond: cond, + extensions: extensions, // TODO 可以拖动排序 + + isAdding: false, + addingExt: "" + } + }, + watch: { + extensions: function () { + this.cond.value = JSON.stringify(this.extensions) + } + }, + methods: { + addExt: function () { + this.isAdding = !this.isAdding + + if (this.isAdding) { + let that = this + setTimeout(function () { + that.$refs.addingExt.focus() + }, 100) + } + }, + cancelAdding: function () { + this.isAdding = false + this.addingExt = "" + }, + confirmAdding: function () { + // TODO 做更详细的校验 + // TODO 如果有重复的则提示之 + + if (this.addingExt.length == 0) { + return + } + if (this.addingExt[0] != ".") { + this.addingExt = "." + this.addingExt + } + this.addingExt = this.addingExt.replace(/\s+/g, "").toLowerCase() + this.extensions.push(this.addingExt) + + // 清除状态 + this.cancelAdding() + }, + removeExt: function (index) { + this.extensions.$remove(index) + } + }, + template: `
+ +
+
{{ext}}
+
+
+
+
+ +
+
+ + +
+
+
+ +
+

扩展名需要包含点(.)符号,例如.jpg.png之类。

+
` +}) + +// 根据URL前缀 +Vue.component("http-cond-url-prefix", { + props: ["v-cond"], + data: function () { + let cond = { + isRequest: true, + param: "${requestPath}", + operator: "prefix", + value: "", + isCaseInsensitive: false + } + if (this.vCond != null && typeof (this.vCond.value) == "string") { + cond.value = this.vCond.value + } + return { + cond: cond + } + }, + methods: { + changeCaseInsensitive: function (isCaseInsensitive) { + this.cond.isCaseInsensitive = isCaseInsensitive + } + }, + template: `
+ + +

URL前缀,有此前缀的URL都将会被匹配,通常以/开头,比如/static

+
` +}) + +Vue.component("http-cond-url-not-prefix", { + props: ["v-cond"], + data: function () { + let cond = { + isRequest: true, + param: "${requestPath}", + operator: "prefix", + value: "", + isReverse: true, + isCaseInsensitive: false + } + if (this.vCond != null && typeof this.vCond.value == "string") { + cond.value = this.vCond.value + } + return { + cond: cond + } + }, + methods: { + changeCaseInsensitive: function (isCaseInsensitive) { + this.cond.isCaseInsensitive = isCaseInsensitive + } + }, + template: `
+ + +

要排除的URL前缀,有此前缀的URL都将会被匹配,通常以/开头,比如/static

+
` +}) + +// URL精准匹配 +Vue.component("http-cond-url-eq", { + props: ["v-cond"], + data: function () { + let cond = { + isRequest: true, + param: "${requestPath}", + operator: "eq", + value: "", + isCaseInsensitive: false + } + if (this.vCond != null && typeof this.vCond.value == "string") { + cond.value = this.vCond.value + } + return { + cond: cond + } + }, + methods: { + changeCaseInsensitive: function (isCaseInsensitive) { + this.cond.isCaseInsensitive = isCaseInsensitive + } + }, + template: `
+ + +

完整的URL路径,通常以/开头,比如/static/ui.js

+
` +}) + +Vue.component("http-cond-url-not-eq", { + props: ["v-cond"], + data: function () { + let cond = { + isRequest: true, + param: "${requestPath}", + operator: "eq", + value: "", + isReverse: true, + isCaseInsensitive: false + } + if (this.vCond != null && typeof this.vCond.value == "string") { + cond.value = this.vCond.value + } + return { + cond: cond + } + }, + methods: { + changeCaseInsensitive: function (isCaseInsensitive) { + this.cond.isCaseInsensitive = isCaseInsensitive + } + }, + template: `
+ + +

要排除的完整的URL路径,通常以/开头,比如/static/ui.js

+
` +}) + +// URL正则匹配 +Vue.component("http-cond-url-regexp", { + props: ["v-cond"], + data: function () { + let cond = { + isRequest: true, + param: "${requestPath}", + operator: "regexp", + value: "", + isCaseInsensitive: false + } + if (this.vCond != null && typeof this.vCond.value == "string") { + cond.value = this.vCond.value + } + return { + cond: cond + } + }, + methods: { + changeCaseInsensitive: function (isCaseInsensitive) { + this.cond.isCaseInsensitive = isCaseInsensitive + } + }, + template: `
+ + +

匹配URL的正则表达式,比如^/static/(.*).js$

+
` +}) + +// 排除URL正则匹配 +Vue.component("http-cond-url-not-regexp", { + props: ["v-cond"], + data: function () { + let cond = { + isRequest: true, + param: "${requestPath}", + operator: "not regexp", + value: "", + isCaseInsensitive: false + } + if (this.vCond != null && typeof this.vCond.value == "string") { + cond.value = this.vCond.value + } + return { + cond: cond + } + }, + methods: { + changeCaseInsensitive: function (isCaseInsensitive) { + this.cond.isCaseInsensitive = isCaseInsensitive + } + }, + template: `
+ + +

不要匹配URL的正则表达式,意即只要匹配成功则排除此条件,比如^/static/(.*).js$

+
` +}) + + +// User-Agent正则匹配 +Vue.component("http-cond-user-agent-regexp", { + props: ["v-cond"], + data: function () { + let cond = { + isRequest: true, + param: "${userAgent}", + operator: "regexp", + value: "", + isCaseInsensitive: false + } + if (this.vCond != null && typeof this.vCond.value == "string") { + cond.value = this.vCond.value + } + return { + cond: cond + } + }, + methods: { + changeCaseInsensitive: function (isCaseInsensitive) { + this.cond.isCaseInsensitive = isCaseInsensitive + } + }, + template: `
+ + +

匹配User-Agent的正则表达式,比如Android|iPhone

+
` +}) + +// User-Agent正则不匹配 +Vue.component("http-cond-user-agent-not-regexp", { + props: ["v-cond"], + data: function () { + let cond = { + isRequest: true, + param: "${userAgent}", + operator: "not regexp", + value: "", + isCaseInsensitive: false + } + if (this.vCond != null && typeof this.vCond.value == "string") { + cond.value = this.vCond.value + } + return { + cond: cond + } + }, + methods: { + changeCaseInsensitive: function (isCaseInsensitive) { + this.cond.isCaseInsensitive = isCaseInsensitive + } + }, + template: `
+ + +

匹配User-Agent的正则表达式,比如Android|iPhone,如果匹配,则排除此条件。

+
` +}) + +// 根据MimeType +Vue.component("http-cond-mime-type", { + props: ["v-cond"], + data: function () { + let cond = { + isRequest: false, + param: "${response.contentType}", + operator: "mime type", + value: "[]" + } + if (this.vCond != null && this.vCond.param == cond.param) { + cond.value = this.vCond.value + } + return { + cond: cond, + mimeTypes: JSON.parse(cond.value), // TODO 可以拖动排序 + + isAdding: false, + addingMimeType: "" + } + }, + watch: { + mimeTypes: function () { + this.cond.value = JSON.stringify(this.mimeTypes) + } + }, + methods: { + addMimeType: function () { + this.isAdding = !this.isAdding + + if (this.isAdding) { + let that = this + setTimeout(function () { + that.$refs.addingMimeType.focus() + }, 100) + } + }, + cancelAdding: function () { + this.isAdding = false + this.addingMimeType = "" + }, + confirmAdding: function () { + // TODO 做更详细的校验 + // TODO 如果有重复的则提示之 + + if (this.addingMimeType.length == 0) { + return + } + this.addingMimeType = this.addingMimeType.replace(/\s+/g, "") + this.mimeTypes.push(this.addingMimeType) + + // 清除状态 + this.cancelAdding() + }, + removeMimeType: function (index) { + this.mimeTypes.$remove(index) + } + }, + template: `
+ +
+
{{mimeType}}
+
+
+
+
+ +
+
+ + +
+
+
+ +
+

服务器返回的内容的MimeType,比如text/htmlimage/*等。

+
` +}) + +// 参数匹配 +Vue.component("http-cond-params", { + props: ["v-cond"], + mounted: function () { + let cond = this.vCond + if (cond == null) { + return + } + this.operator = cond.operator + + // stringValue + if (["regexp", "not regexp", "eq", "not", "prefix", "suffix", "contains", "not contains", "eq ip", "gt ip", "gte ip", "lt ip", "lte ip", "ip range"].$contains(cond.operator)) { + this.stringValue = cond.value + return + } + + // numberValue + if (["eq int", "eq float", "gt", "gte", "lt", "lte", "mod 10", "ip mod 10", "mod 100", "ip mod 100"].$contains(cond.operator)) { + this.numberValue = cond.value + return + } + + // modValue + if (["mod", "ip mod"].$contains(cond.operator)) { + let pieces = cond.value.split(",") + this.modDivValue = pieces[0] + if (pieces.length > 1) { + this.modRemValue = pieces[1] + } + return + } + + // stringValues + let that = this + if (["in", "not in", "file ext", "mime type"].$contains(cond.operator)) { + try { + let arr = JSON.parse(cond.value) + if (arr != null && (arr instanceof Array)) { + arr.forEach(function (v) { + that.stringValues.push(v) + }) + } + } catch (e) { + + } + return + } + + // versionValue + if (["version range"].$contains(cond.operator)) { + let pieces = cond.value.split(",") + this.versionRangeMinValue = pieces[0] + if (pieces.length > 1) { + this.versionRangeMaxValue = pieces[1] + } + return + } + }, + data: function () { + let cond = { + isRequest: true, + param: "", + operator: window.REQUEST_COND_OPERATORS[0].op, + value: "", + isCaseInsensitive: false + } + if (this.vCond != null) { + cond = this.vCond + } + return { + cond: cond, + operators: window.REQUEST_COND_OPERATORS, + operator: window.REQUEST_COND_OPERATORS[0].op, + operatorDescription: window.REQUEST_COND_OPERATORS[0].description, + variables: window.REQUEST_VARIABLES, + variable: "", + + // 各种类型的值 + stringValue: "", + numberValue: "", + + modDivValue: "", + modRemValue: "", + + stringValues: [], + + versionRangeMinValue: "", + versionRangeMaxValue: "" + } + }, + methods: { + changeVariable: function () { + let v = this.cond.param + if (v == null) { + v = "" + } + this.cond.param = v + this.variable + }, + changeOperator: function () { + let that = this + this.operators.forEach(function (v) { + if (v.op == that.operator) { + that.operatorDescription = v.description + } + }) + + this.cond.operator = this.operator + + // 移动光标 + let box = document.getElementById("variables-value-box") + if (box != null) { + setTimeout(function () { + let input = box.getElementsByTagName("INPUT") + if (input.length > 0) { + input[0].focus() + } + }, 100) + } + }, + changeStringValues: function (v) { + this.stringValues = v + this.cond.value = JSON.stringify(v) + } + }, + watch: { + stringValue: function (v) { + this.cond.value = v + }, + numberValue: function (v) { + // TODO 校验数字 + this.cond.value = v + }, + modDivValue: function (v) { + if (v.length == 0) { + return + } + let div = parseInt(v) + if (isNaN(div)) { + div = 1 + } + this.modDivValue = div + this.cond.value = div + "," + this.modRemValue + }, + modRemValue: function (v) { + if (v.length == 0) { + return + } + let rem = parseInt(v) + if (isNaN(rem)) { + rem = 0 + } + this.modRemValue = rem + this.cond.value = this.modDivValue + "," + rem + }, + versionRangeMinValue: function (v) { + this.cond.value = this.versionRangeMinValue + "," + this.versionRangeMaxValue + }, + versionRangeMaxValue: function (v) { + this.cond.value = this.versionRangeMinValue + "," + this.versionRangeMaxValue + } + }, + template: ` + + 参数值 + + +
+
+
+ +
+
+ +
+
+
+

其中可以使用变量,类似于\${requestPath},也可以是多个变量的组合。

+ + + + 操作符 + +
+ +

{{operatorDescription}}

+
+ + + + 对比值 + + +
+ +

要匹配的正则表达式,比如^/static/(.+).js

+
+ + +
+ +

要对比的数字。

+
+ + +
+ +

参数值除以10的余数,在0-9之间。

+
+
+ +

参数值除以100的余数,在0-99之间。

+
+
+
+
除:
+
+ +
+
余:
+
+ +
+
+
+ + +
+ +

和参数值一致的字符串。

+

和参数值不一致的字符串。

+

参数值的前缀。

+

参数值的后缀为此字符串。

+

参数值包含此字符串。

+

参数值不包含此字符串。

+
+
+ +

添加参数值列表。

+

添加参数值列表。

+

添加扩展名列表,比如pnghtml,不包括点。

+

添加MimeType列表,类似于text/htmlimage/*

+
+
+
+
+
-
+
+
+
+ + +
+ +

要对比的IP。

+
+
+ +

参数中IP转换成整数后除以10的余数,在0-9之间。

+
+
+ +

参数中IP转换成整数后除以100的余数,在0-99之间。

+
+ + + + 不区分大小写 + +
+ + +
+

选中后表示对比时忽略参数值的大小写。

+ + +` +}) + +// 请求方法列表 +Vue.component("http-status-box", { + props: ["v-status-list"], + data: function () { + let statusList = this.vStatusList + if (statusList == null) { + statusList = [] + } + return { + statusList: statusList, + isAdding: false, + addingStatus: "" + } + }, + methods: { + add: function () { + this.isAdding = true + let that = this + setTimeout(function () { + that.$refs.addingStatus.focus() + }, 100) + }, + confirm: function () { + let that = this + + // 删除其中的空格 + this.addingStatus = this.addingStatus.replace(/\s/g, "").toUpperCase() + + if (this.addingStatus.length == 0) { + teaweb.warn("请输入要添加的状态码", function () { + that.$refs.addingStatus.focus() + }) + return + } + + // 是否已经存在 + if (this.statusList.$contains(this.addingStatus)) { + teaweb.warn("此状态码已经存在,无需重复添加", function () { + that.$refs.addingStatus.focus() + }) + return + } + + // 格式 + if (!this.addingStatus.match(/^\d{3}$/)) { + teaweb.warn("请输入正确的状态码", function () { + that.$refs.addingStatus.focus() + }) + return + } + + this.statusList.push(parseInt(this.addingStatus, 10)) + this.cancel() + }, + remove: function (index) { + this.statusList.$remove(index) + }, + cancel: function () { + this.isAdding = false + this.addingStatus = "" + } + }, + template: `
+ +
+ + {{status}} +   + +
+
+
+
+
+ +
+
+ +   +
+
+

格式为三位数字,比如200404等。

+
+
+
+ +
+
` +}) + +Vue.component("server-group-selector", { + props: ["v-groups"], + data: function () { + let groups = this.vGroups + if (groups == null) { + groups = [] + } + return { + groups: groups + } + }, + methods: { + selectGroup: function () { + let that = this + let groupIds = this.groups.map(function (v) { + return v.id.toString() + }).join(",") + teaweb.popup("/servers/groups/selectPopup?selectedGroupIds=" + groupIds, { + callback: function (resp) { + that.groups.push(resp.data.group) + } + }) + }, + addGroup: function () { + let that = this + teaweb.popup("/servers/groups/createPopup", { + callback: function (resp) { + that.groups.push(resp.data.group) + } + }) + }, + removeGroup: function (index) { + this.groups.$remove(index) + }, + groupIds: function () { + return this.groups.map(function (v) { + return v.id + }) + } + }, + template: `
+
+
+ + {{group.name}}   +
+
+
+ +
` +}) + +Vue.component("script-group-config-box", { + props: ["v-group", "v-is-location"], + data: function () { + let group = this.vGroup + if (group == null) { + group = { + isPrior: false, + isOn: true, + scripts: [] + } + } + if (group.scripts == null) { + group.scripts = [] + } + + let script = null + if (group.scripts.length > 0) { + script = group.scripts[group.scripts.length - 1] + } + + return { + group: group, + script: script + } + }, + methods: { + changeScript: function (script) { + this.group.scripts = [script] // 目前只支持单个脚本 + this.change() + }, + change: function () { + this.$emit("change", this.group) + } + }, + template: `
+ + +
+
+ +
+
` +}) + +// 指标周期设置 +Vue.component("metric-period-config-box", { + props: ["v-period", "v-period-unit"], + data: function () { + let period = this.vPeriod + let periodUnit = this.vPeriodUnit + if (period == null || period.toString().length == 0) { + period = 1 + } + if (periodUnit == null || periodUnit.length == 0) { + periodUnit = "day" + } + return { + periodConfig: { + period: period, + unit: periodUnit + } + } + }, + watch: { + "periodConfig.period": function (v) { + v = parseInt(v) + if (isNaN(v) || v <= 0) { + v = 1 + } + this.periodConfig.period = v + } + }, + template: `
+ +
+
+ +
+
+ +
+
+

在此周期内同一对象累积为同一数据。

+
` +}) + +Vue.component("traffic-limit-config-box", { + props: ["v-traffic-limit"], + data: function () { + let config = this.vTrafficLimit + if (config == null) { + config = { + isOn: false, + dailySize: { + count: -1, + unit: "gb" + }, + monthlySize: { + count: -1, + unit: "gb" + }, + totalSize: { + count: -1, + unit: "gb" + }, + noticePageBody: "" + } + } + if (config.dailySize == null) { + config.dailySize = { + count: -1, + unit: "gb" + } + } + if (config.monthlySize == null) { + config.monthlySize = { + count: -1, + unit: "gb" + } + } + if (config.totalSize == null) { + config.totalSize = { + count: -1, + unit: "gb" + } + } + return { + config: config + } + }, + methods: { + showBodyTemplate: function () { + this.config.noticePageBody = ` + + +Traffic Limit Exceeded Warning + + +

Traffic Limit Exceeded Warning

+

The site traffic has exceeded the limit. Please contact with the site administrator.

+
Request ID: \${requestId}.
+ + +` + } + }, + template: `
+ + + + + + + + + + + + + + + + + + + + + + + +
是否启用 + +

注意:由于流量统计是每5分钟统计一次,所以超出流量限制后,对用户的提醒也会有所延迟。

+
日流量限制 + +
月流量限制 + +
网页提示内容 + +

[使用模板]。当达到流量限制时网页显示的HTML内容,不填写则显示默认的提示内容。

+
+
+
` +}) + +Vue.component("firewall-syn-flood-config-box", { + props: ["v-syn-flood-config"], + data: function () { + let config = this.vSynFloodConfig + if (config == null) { + config = { + isOn: false, + minAttempts: 10, + timeoutSeconds: 600, + ignoreLocal: true + } + } + return { + config: config, + isEditing: false, + minAttempts: config.minAttempts, + timeoutSeconds: config.timeoutSeconds + } + }, + methods: { + edit: function () { + this.isEditing = !this.isEditing + } + }, + watch: { + minAttempts: function (v) { + let count = parseInt(v) + if (isNaN(count)) { + count = 10 + } + if (count < 5) { + count = 5 + } + this.config.minAttempts = count + }, + timeoutSeconds: function (v) { + let seconds = parseInt(v) + if (isNaN(seconds)) { + seconds = 10 + } + if (seconds < 60) { + seconds = 60 + } + this.config.timeoutSeconds = seconds + } + }, + template: `
+ + + + 已启用 / 空连接次数:{{config.minAttempts}}次/分钟 / 封禁时间:{{config.timeoutSeconds}}秒 / 忽略局域网访问 + + 未启用 + + + + + + + + + + + + + + + + + + + + +
是否启用 + +

启用后,WAF将会尝试自动检测并阻止SYN Flood攻击。此功能需要节点已安装并启用Firewalld。

+
空连接次数 +
+ + 次/分钟 +
+

超过此数字的"空连接"将被视为SYN Flood攻击,为了防止误判,此数值默认不小于5。

+
封禁时间 +
+ + +
+
忽略局域网访问 + +
+
` +}) + +// TODO 支持关键词搜索 +// TODO 改成弹窗选择 +Vue.component("admin-selector", { + props: ["v-admin-id"], + mounted: function () { + let that = this + Tea.action("/admins/options") + .post() + .success(function (resp) { + that.admins = resp.data.admins + }) + }, + data: function () { + let adminId = this.vAdminId + if (adminId == null) { + adminId = 0 + } + return { + admins: [], + adminId: adminId + } + }, + template: `
+ +
` +}) + +// 绑定IP列表 +Vue.component("ip-list-bind-box", { + props: ["v-http-firewall-policy-id", "v-type"], + mounted: function () { + this.refresh() + }, + data: function () { + return { + policyId: this.vHttpFirewallPolicyId, + type: this.vType, + lists: [] + } + }, + methods: { + bind: function () { + let that = this + teaweb.popup("/servers/iplists/bindHTTPFirewallPopup?httpFirewallPolicyId=" + this.policyId + "&type=" + this.type, { + width: "50em", + height: "34em", + callback: function () { + + }, + onClose: function () { + that.refresh() + } + }) + }, + remove: function (index, listId) { + let that = this + teaweb.confirm("确定要删除这个绑定的IP名单吗?", function () { + Tea.action("/servers/iplists/unbindHTTPFirewall") + .params({ + httpFirewallPolicyId: that.policyId, + listId: listId + }) + .post() + .success(function (resp) { + that.lists.$remove(index) + }) + }) + }, + refresh: function () { + let that = this + Tea.action("/servers/iplists/httpFirewall") + .params({ + httpFirewallPolicyId: this.policyId, + type: this.vType + }) + .post() + .success(function (resp) { + that.lists = resp.data.lists + }) + } + }, + template: `
+ 绑定+   已绑定: + +
` +}) + +Vue.component("ip-list-table", { + props: ["v-items", "v-keyword", "v-show-search-button"], + data: function () { + return { + items: this.vItems, + keyword: (this.vKeyword != null) ? this.vKeyword : "", + selectedAll: false, + hasSelectedItems: false + } + }, + methods: { + updateItem: function (itemId) { + this.$emit("update-item", itemId) + }, + deleteItem: function (itemId) { + this.$emit("delete-item", itemId) + }, + viewLogs: function (itemId) { + teaweb.popup("/servers/iplists/accessLogsPopup?itemId=" + itemId, { + width: "50em", + height: "30em" + }) + }, + changeSelectedAll: function () { + let boxes = this.$refs.itemCheckBox + if (boxes == null) { + return + } + + let that = this + boxes.forEach(function (box) { + box.checked = that.selectedAll + }) + + this.hasSelectedItems = this.selectedAll + }, + changeSelected: function (e) { + let that = this + that.hasSelectedItems = false + let boxes = that.$refs.itemCheckBox + if (boxes == null) { + return + } + boxes.forEach(function (box) { + if (box.checked) { + that.hasSelectedItems = true + } + }) + }, + deleteAll: function () { + let boxes = this.$refs.itemCheckBox + if (boxes == null) { + return + } + let itemIds = [] + boxes.forEach(function (box) { + if (box.checked) { + itemIds.push(box.value) + } + }) + if (itemIds.length == 0) { + return + } + + Tea.action("/servers/iplists/deleteItems") + .post() + .params({ + itemIds: itemIds + }) + .success(function () { + teaweb.successToast("批量删除成功", 1200, teaweb.reload) + }) + }, + formatSeconds: function (seconds) { + if (seconds < 60) { + return seconds + "秒" + } + if (seconds < 3600) { + return Math.ceil(seconds / 60) + "分钟" + } + if (seconds < 86400) { + return Math.ceil(seconds / 3600) + "小时" + } + return Math.ceil(seconds / 86400) + "天" + } + }, + template: `
+ + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+
IP类型级别过期时间备注操作
+
+ + +
+
+ + {{item.ipFrom}}  New   + - {{item.ipTo}} + * +
+ 添加于 {{item.createdTime}} + + @ + + [名单:{{item.list.name}}] + [名单:{{item.list.name}} + + + + [服务:{{item.policy.server.name}}] + [服务:{{item.policy.server.name}}] + + + [策略:{{item.policy.name}}] + + + + +
+
+ IPv4 + IPv4 + IPv6 + 所有IP + + {{item.eventLevelName}} + - + +
+ {{item.expiredTime}} +
+ 已过期 +
+
+ {{formatSeconds(item.lifeSeconds)}} +
+
+ 不过期 +
+ {{item.reason}} + - + + + + + + 日志   + 修改   + 删除 +
+
` +}) + +Vue.component("ip-item-text", { + props: ["v-item"], + template: ` + * + + {{vItem.ipFrom}} + - {{vItem.ipTo}} + + {{vItem.ipFrom}} +   级别:{{vItem.eventLevelName}} +` +}) + +Vue.component("ip-box", { + props: ["v-ip"], + methods: { + popup: function () { + let ip = this.vIp + if (ip == null || ip.length == 0) { + let e = this.$refs.container + ip = e.innerText + if (ip == null) { + ip = e.textContent + } + } + + teaweb.popup("/servers/ipbox?ip=" + ip, { + width: "50em", + height: "30em" + }) + } + }, + template: `` +}) + +Vue.component("api-node-selector", { + props: [], + data: function () { + return {} + }, + template: `
+ 暂未实现 +
` +}) + +Vue.component("api-node-addresses-box", { + props: ["v-addrs", "v-name"], + data: function () { + let addrs = this.vAddrs + if (addrs == null) { + addrs = [] + } + return { + addrs: addrs + } + }, + methods: { + // 添加IP地址 + addAddr: function () { + let that = this; + teaweb.popup("/api/node/createAddrPopup", { + height: "16em", + callback: function (resp) { + that.addrs.push(resp.data.addr); + } + }) + }, + + // 修改地址 + updateAddr: function (index, addr) { + let that = this; + window.UPDATING_ADDR = addr + teaweb.popup("/api/node/updateAddrPopup?addressId=", { + callback: function (resp) { + Vue.set(that.addrs, index, resp.data.addr); + } + }) + }, + + // 删除IP地址 + removeAddr: function (index) { + this.addrs.$remove(index); + } + }, + template: `
+ +
+
+
+ {{addr.protocol}}://{{addr.host.quoteIP()}}:{{addr.portRange}} + + +
+
+
+
+
+ +
+
` +}) + +// 给Table增加排序功能 +function sortTable(callback) { + // 引入js + let jsFile = document.createElement("script") + jsFile.setAttribute("src", "/js/sortable.min.js") + jsFile.addEventListener("load", function () { + // 初始化 + let box = document.querySelector("#sortable-table") + if (box == null) { + return + } + Sortable.create(box, { + draggable: "tbody", + handle: ".icon.handle", + onStart: function () { + }, + onUpdate: function (event) { + let rows = box.querySelectorAll("tbody") + let rowIds = [] + rows.forEach(function (row) { + rowIds.push(parseInt(row.getAttribute("v-id"))) + }) + callback(rowIds) + } + }) + }) + document.head.appendChild(jsFile) +} + +function sortLoad(callback) { + let jsFile = document.createElement("script") + jsFile.setAttribute("src", "/js/sortable.min.js") + jsFile.addEventListener("load", function () { + if (typeof (callback) == "function") { + callback() + } + }) + document.head.appendChild(jsFile) +} + + +Vue.component("page-box", { + data: function () { + return { + page: "" + } + }, + created: function () { + let that = this; + setTimeout(function () { + that.page = Tea.Vue.page; + }) + }, + template: `
+
+
` +}) + +Vue.component("network-addresses-box", { + props: ["v-server-type", "v-addresses", "v-protocol", "v-name", "v-from", "v-support-range"], + data: function () { + let addresses = this.vAddresses + if (addresses == null) { + addresses = [] + } + let protocol = this.vProtocol + if (protocol == null) { + protocol = "" + } + + let name = this.vName + if (name == null) { + name = "addresses" + } + + let from = this.vFrom + if (from == null) { + from = "" + } + + return { + addresses: addresses, + protocol: protocol, + name: name, + from: from + } + }, + watch: { + "vServerType": function () { + this.addresses = [] + }, + "vAddresses": function () { + if (this.vAddresses != null) { + this.addresses = this.vAddresses + } + } + }, + methods: { + addAddr: function () { + let that = this + window.UPDATING_ADDR = null + teaweb.popup("/servers/addPortPopup?serverType=" + this.vServerType + "&protocol=" + this.protocol + "&from=" + this.from + "&supportRange=" + (this.supportRange() ? 1 : 0), { + height: "18em", + callback: function (resp) { + var addr = resp.data.address + if (that.addresses.$find(function (k, v) { + return addr.host == v.host && addr.portRange == v.portRange && addr.protocol == v.protocol + }) != null) { + teaweb.warn("要添加的网络地址已经存在") + return + } + that.addresses.push(addr) + if (["https", "https4", "https6"].$contains(addr.protocol)) { + this.tlsProtocolName = "HTTPS" + } else if (["tls", "tls4", "tls6"].$contains(addr.protocol)) { + this.tlsProtocolName = "TLS" + } + + // 发送事件 + that.$emit("change", that.addresses) + } + }) + }, + removeAddr: function (index) { + this.addresses.$remove(index); + + // 发送事件 + this.$emit("change", this.addresses) + }, + updateAddr: function (index, addr) { + let that = this + window.UPDATING_ADDR = addr + teaweb.popup("/servers/addPortPopup?serverType=" + this.vServerType + "&protocol=" + this.protocol + "&from=" + this.from + "&supportRange=" + (this.supportRange() ? 1 : 0), { + height: "18em", + callback: function (resp) { + var addr = resp.data.address + Vue.set(that.addresses, index, addr) + + if (["https", "https4", "https6"].$contains(addr.protocol)) { + this.tlsProtocolName = "HTTPS" + } else if (["tls", "tls4", "tls6"].$contains(addr.protocol)) { + this.tlsProtocolName = "TLS" + } + + // 发送事件 + that.$emit("change", that.addresses) + } + }) + + // 发送事件 + this.$emit("change", this.addresses) + }, + supportRange: function () { + return this.vSupportRange || (this.vServerType == "tcpProxy" || this.vServerType == "udpProxy") + } + }, + template: `
+ +
+
+ {{addr.protocol}}://{{addr.host.quoteIP()}}*:{{addr.portRange}}{{addr.portRange}} + +
+
+
+ [添加端口绑定] +
` +}) + +/** + * 保存按钮 + */ +Vue.component("submit-btn", { + template: '' +}); + +// 可以展示更多条目的角图表 +Vue.component("more-items-angle", { + props: ["v-data-url", "v-url"], + data: function () { + return { + visible: false + } + }, + methods: { + show: function () { + this.visible = !this.visible + if (this.visible) { + this.showBox() + } else { + this.hideBox() + } + }, + showBox: function () { + let that = this + + this.visible = true + + Tea.action(this.vDataUrl) + .params({ + url: this.vUrl + }) + .post() + .success(function (resp) { + let groups = resp.data.groups + + let boxLeft = that.$el.offsetLeft + 120; + let boxTop = that.$el.offsetTop + 70; + + let box = document.createElement("div") + box.setAttribute("id", "more-items-box") + box.style.cssText = "z-index: 100; position: absolute; left: " + boxLeft + "px; top: " + boxTop + "px; max-height: 30em; overflow: auto; border-bottom: 1px solid rgba(34,36,38,.15)" + document.body.append(box) + + let menuHTML = "
    " + groups.forEach(function (group) { + menuHTML += "
    " + teaweb.encodeHTML(group.name) + "
    " + group.items.forEach(function (item) { + menuHTML += "" + teaweb.encodeHTML(item.name) + "" + }) + }) + menuHTML += "
" + box.innerHTML = menuHTML + + let listener = function (e) { + if (e.target.tagName == "I") { + return + } + + if (!that.isInBox(box, e.target)) { + document.removeEventListener("click", listener) + that.hideBox() + } + } + document.addEventListener("click", listener) + }) + }, + hideBox: function () { + let box = document.getElementById("more-items-box") + if (box != null) { + box.parentNode.removeChild(box) + } + this.visible = false + }, + isInBox: function (parent, child) { + while (true) { + if (child == null) { + break + } + if (child.parentNode == parent) { + return true + } + child = child.parentNode + } + return false + } + }, + template: `` +}) + +/** + * 菜单项 + */ +Vue.component("menu-item", { + props: ["href", "active", "code"], + data: function () { + let active = this.active + if (typeof (active) == "undefined") { + var itemCode = "" + if (typeof (window.TEA.ACTION.data.firstMenuItem) != "undefined") { + itemCode = window.TEA.ACTION.data.firstMenuItem + } + if (itemCode != null && itemCode.length > 0 && this.code != null && this.code.length > 0) { + if (itemCode.indexOf(",") > 0) { + active = itemCode.split(",").$contains(this.code) + } else { + active = (itemCode == this.code) + } + } + } + + let href = (this.href == null) ? "" : this.href + if (typeof (href) == "string" && href.length > 0 && href.startsWith(".")) { + let qIndex = href.indexOf("?") + if (qIndex >= 0) { + href = Tea.url(href.substring(0, qIndex)) + href.substring(qIndex) + } else { + href = Tea.url(href) + } + } + + return { + vHref: href, + vActive: active + } + }, + methods: { + click: function (e) { + this.$emit("click", e) + } + }, + template: '\ + \ + ' +}); + +// 使用Icon的链接方式 +Vue.component("link-icon", { + props: ["href", "title", "target"], + data: function () { + return { + vTitle: (this.title == null) ? "打开链接" : this.title + } + }, + template: ` ` +}) + +// 带有下划虚线的连接 +Vue.component("link-red", { + props: ["href", "title"], + data: function () { + let href = this.href + if (href == null) { + href = "" + } + return { + vHref: href + } + }, + methods: { + clickPrevent: function () { + emitClick(this, arguments) + } + }, + template: `` +}) + +// 会弹出窗口的链接 +Vue.component("link-popup", { + props: ["title"], + methods: { + clickPrevent: function () { + emitClick(this, arguments) + } + }, + template: `` +}) + +Vue.component("popup-icon", { + props: ["title", "href", "height"], + methods: { + clickPrevent: function () { + if (this.href != null && this.href.length > 0) { + teaweb.popup(this.href, { + height: this.height + }) + } + } + }, + template: ` ` +}) + +// 小提示 +Vue.component("tip-icon", { + props: ["content"], + methods: { + showTip: function () { + teaweb.popupTip(this.content) + } + }, + template: `` +}) + +// 提交点击事件 +function emitClick(obj, arguments) { + let event = "click" + let newArgs = [event] + for (let i = 0; i < arguments.length; i++) { + newArgs.push(arguments[i]) + } + obj.$emit.apply(obj, newArgs) +} + +Vue.component("countries-selector", { + props: ["v-countries"], + data: function () { + let countries = this.vCountries + if (countries == null) { + countries = [] + } + let countryIds = countries.$map(function (k, v) { + return v.id + }) + return { + countries: countries, + countryIds: countryIds + } + }, + methods: { + add: function () { + let countryStringIds = this.countryIds.map(function (v) { + return v.toString() + }) + let that = this + teaweb.popup("/ui/selectCountriesPopup?countryIds=" + countryStringIds.join(","), { + width: "48em", + height: "23em", + callback: function (resp) { + that.countries = resp.data.countries + that.change() + } + }) + }, + remove: function (index) { + this.countries.$remove(index) + this.change() + }, + change: function () { + this.countryIds = this.countries.$map(function (k, v) { + return v.id + }) + } + }, + template: `
+ +
+
{{country.name}}
+
+
+
+ +
+
` +}) + +Vue.component("raquo-item", { + template: `»` +}) + +Vue.component("more-options-tbody", { + data: function () { + return { + isVisible: false + } + }, + methods: { + show: function () { + this.isVisible = !this.isVisible + this.$emit("change", this.isVisible) + } + }, + template: ` + + 更多选项收起选项 + +` +}) + +Vue.component("download-link", { + props: ["v-element", "v-file", "v-value"], + created: function () { + let that = this + setTimeout(function () { + that.url = that.composeURL() + }, 1000) + }, + data: function () { + let filename = this.vFile + if (filename == null || filename.length == 0) { + filename = "unknown-file" + } + return { + file: filename, + url: this.composeURL() + } + }, + methods: { + composeURL: function () { + let text = "" + if (this.vValue != null) { + text = this.vValue + } else { + let e = document.getElementById(this.vElement) + if (e == null) { + teaweb.warn("找不到要下载的内容") + return + } + text = e.innerText + if (text == null) { + text = e.textContent + } + } + return Tea.url("/ui/download", { + file: this.file, + text: text + }) + } + }, + template: ``, +}) + +Vue.component("values-box", { + props: ["values", "size", "maxlength", "name", "placeholder"], + data: function () { + let values = this.values; + if (values == null) { + values = []; + } + return { + "vValues": values, + "isUpdating": false, + "isAdding": false, + "index": 0, + "value": "", + isEditing: false + } + }, + methods: { + create: function () { + this.isAdding = true; + var that = this; + setTimeout(function () { + that.$refs.value.focus(); + }, 200); + }, + update: function (index) { + this.cancel() + this.isUpdating = true; + this.index = index; + this.value = this.vValues[index]; + var that = this; + setTimeout(function () { + that.$refs.value.focus(); + }, 200); + }, + confirm: function () { + if (this.value.length == 0) { + return + } + + if (this.isUpdating) { + Vue.set(this.vValues, this.index, this.value); + } else { + this.vValues.push(this.value); + } + this.cancel() + this.$emit("change", this.vValues) + }, + remove: function (index) { + this.vValues.$remove(index) + this.$emit("change", this.vValues) + }, + cancel: function () { + this.isUpdating = false; + this.isAdding = false; + this.value = ""; + }, + updateAll: function (values) { + this.vValeus = values + }, + addValue: function (v) { + this.vValues.push(v) + }, + + startEditing: function () { + this.isEditing = !this.isEditing + } + }, + template: `
+
+
{{value}}
+ [修改] +
+
+
+
{{value}} + +   + +
+
+
+ +
+
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+
+
` +}); + +Vue.component("datetime-input", { + props: ["v-name", "v-timestamp"], + mounted: function () { + let that = this + teaweb.datepicker(this.$refs.dayInput, function (v) { + that.day = v + that.hour = "23" + that.minute = "59" + that.second = "59" + that.change() + }) + }, + data: function () { + let timestamp = this.vTimestamp + if (timestamp != null) { + timestamp = parseInt(timestamp) + if (isNaN(timestamp)) { + timestamp = 0 + } + } else { + timestamp = 0 + } + + let day = "" + let hour = "" + let minute = "" + let second = "" + + if (timestamp > 0) { + let date = new Date() + date.setTime(timestamp * 1000) + + let year = date.getFullYear().toString() + let month = this.leadingZero((date.getMonth() + 1).toString(), 2) + day = year + "-" + month + "-" + this.leadingZero(date.getDate().toString(), 2) + + hour = this.leadingZero(date.getHours().toString(), 2) + minute = this.leadingZero(date.getMinutes().toString(), 2) + second = this.leadingZero(date.getSeconds().toString(), 2) + } + + return { + timestamp: timestamp, + day: day, + hour: hour, + minute: minute, + second: second, + + hasDayError: false, + hasHourError: false, + hasMinuteError: false, + hasSecondError: false + } + }, + methods: { + change: function () { + let date = new Date() + + // day + if (!/^\d{4}-\d{1,2}-\d{1,2}$/.test(this.day)) { + this.hasDayError = true + return + } + let pieces = this.day.split("-") + let year = parseInt(pieces[0]) + date.setFullYear(year) + + let month = parseInt(pieces[1]) + if (month < 1 || month > 12) { + this.hasDayError = true + return + } + date.setMonth(month - 1) + + let day = parseInt(pieces[2]) + if (day < 1 || day > 32) { + this.hasDayError = true + return + } + date.setDate(day) + + this.hasDayError = false + + // hour + if (!/^\d+$/.test(this.hour)) { + this.hasHourError = true + return + } + let hour = parseInt(this.hour) + if (isNaN(hour)) { + this.hasHourError = true + return + } + if (hour < 0 || hour >= 24) { + this.hasHourError = true + return + } + this.hasHourError = false + date.setHours(hour) + + // minute + if (!/^\d+$/.test(this.minute)) { + this.hasMinuteError = true + return + } + let minute = parseInt(this.minute) + if (isNaN(minute)) { + this.hasMinuteError = true + return + } + if (minute < 0 || minute >= 60) { + this.hasMinuteError = true + return + } + this.hasMinuteError = false + date.setMinutes(minute) + + // second + if (!/^\d+$/.test(this.second)) { + this.hasSecondError = true + return + } + let second = parseInt(this.second) + if (isNaN(second)) { + this.hasSecondError = true + return + } + if (second < 0 || second >= 60) { + this.hasSecondError = true + return + } + this.hasSecondError = false + date.setSeconds(second) + + this.timestamp = Math.floor(date.getTime() / 1000) + }, + leadingZero: function (s, l) { + s = s.toString() + if (l <= s.length) { + return s + } + for (let i = 0; i < l - s.length; i++) { + s = "0" + s + } + return s + }, + resultTimestamp: function () { + return this.timestamp + }, + nextDays: function (days) { + let date = new Date() + date.setTime(date.getTime() + days * 86400 * 1000) + this.day = date.getFullYear() + "-" + this.leadingZero(date.getMonth() + 1, 2) + "-" + this.leadingZero(date.getDate(), 2) + this.hour = this.leadingZero(date.getHours(), 2) + this.minute = this.leadingZero(date.getMinutes(), 2) + this.second = this.leadingZero(date.getSeconds(), 2) + this.change() + } + }, + template: `
+ +
+
+ +
+
+
:
+
+
:
+
+
+

常用时间:  1天  |  3天  |  一周  |  30天 

+
` +}) + +// 启用状态标签 +Vue.component("label-on", { + props: ["v-is-on"], + template: '
已启用已停用
' +}) + +// 文字代码标签 +Vue.component("code-label", { + methods: { + click: function (args) { + this.$emit("click", args) + } + }, + template: `` +}) + +// tiny标签 +Vue.component("tiny-label", { + template: `` +}) + +Vue.component("tiny-basic-label", { + template: `` +}) + +// 更小的标签 +Vue.component("micro-basic-label", { + template: `` +}) + + +// 灰色的Label +Vue.component("grey-label", { + props: ["color"], + data: function () { + let color = "grey" + if (this.color != null && this.color.length > 0) { + color = "red" + } + return { + labelColor: color + } + }, + template: `` +}) + +// 可选标签 +Vue.component("optional-label", { + template: `(可选)` +}) + +// Plus专属 +Vue.component("plus-label", { + template: `Plus专属功能。` +}) + +/** + * 一级菜单 + */ +Vue.component("first-menu", { + props: [], + template: ' \ +
\ + \ +
\ +
' +}); + +/** + * 更多选项 + */ +Vue.component("more-options-indicator", { + data: function () { + return { + visible: false + } + }, + methods: { + changeVisible: function () { + this.visible = !this.visible + if (Tea.Vue != null) { + Tea.Vue.moreOptionsVisible = this.visible + } + this.$emit("change", this.visible) + } + }, + template: '更多选项收起选项 ' +}); + +Vue.component("page-size-selector", { + data: function () { + let query = window.location.search + let pageSize = 10 + if (query.length > 0) { + query = query.substr(1) + let params = query.split("&") + params.forEach(function (v) { + let pieces = v.split("=") + if (pieces.length == 2 && pieces[0] == "pageSize") { + let pageSizeString = pieces[1] + if (pageSizeString.match(/^\d+$/)) { + pageSize = parseInt(pageSizeString, 10) + if (isNaN(pageSize) || pageSize < 1) { + pageSize = 10 + } + } + } + }) + } + return { + pageSize: pageSize + } + }, + watch: { + pageSize: function () { + window.ChangePageSize(this.pageSize) + } + }, + template: `` +}) + +/** + * 二级菜单 + */ +Vue.component("second-menu", { + template: ' \ +
\ + \ +
\ +
' +}); + +Vue.component("loading-message", { + template: `
+
  +
` +}) + +Vue.component("more-options-angle", { + data: function () { + return { + isVisible: false + } + }, + methods: { + show: function () { + this.isVisible = !this.isVisible + this.$emit("change", this.isVisible) + } + }, + template: `更多选项收起选项` +}) + +/** + * 菜单项 + */ +Vue.component("inner-menu-item", { + props: ["href", "active", "code"], + data: function () { + var active = this.active; + if (typeof(active) =="undefined") { + var itemCode = ""; + if (typeof (window.TEA.ACTION.data.firstMenuItem) != "undefined") { + itemCode = window.TEA.ACTION.data.firstMenuItem; + } + active = (itemCode == this.code); + } + return { + vHref: (this.href == null) ? "" : this.href, + vActive: active + }; + }, + template: '\ + [] \ + ' +}); + +Vue.component("health-check-config-box", { + props: ["v-health-check-config"], + data: function () { + let healthCheckConfig = this.vHealthCheckConfig + let urlProtocol = "http" + let urlPort = "" + let urlRequestURI = "/" + let urlHost = "" + + if (healthCheckConfig == null) { + healthCheckConfig = { + isOn: false, + url: "", + interval: {count: 60, unit: "second"}, + statusCodes: [200], + timeout: {count: 10, unit: "second"}, + countTries: 3, + tryDelay: {count: 100, unit: "ms"}, + autoDown: true, + countUp: 1, + countDown: 3, + userAgent: "", + onlyBasicRequest: false + } + let that = this + setTimeout(function () { + that.changeURL() + }, 500) + } else { + try { + let url = new URL(healthCheckConfig.url) + urlProtocol = url.protocol.substring(0, url.protocol.length - 1) + + // 域名 + urlHost = url.host + if (urlHost == "%24%7Bhost%7D") { + urlHost = "${host}" + } + let colonIndex = urlHost.indexOf(":") + if (colonIndex > 0) { + urlHost = urlHost.substring(0, colonIndex) + } + + urlPort = url.port + urlRequestURI = url.pathname + if (url.search.length > 0) { + urlRequestURI += url.search + } + } catch (e) { + } + + if (healthCheckConfig.statusCodes == null) { + healthCheckConfig.statusCodes = [200] + } + if (healthCheckConfig.interval == null) { + healthCheckConfig.interval = {count: 60, unit: "second"} + } + if (healthCheckConfig.timeout == null) { + healthCheckConfig.timeout = {count: 10, unit: "second"} + } + if (healthCheckConfig.tryDelay == null) { + healthCheckConfig.tryDelay = {count: 100, unit: "ms"} + } + if (healthCheckConfig.countUp == null || healthCheckConfig.countUp < 1) { + healthCheckConfig.countUp = 1 + } + if (healthCheckConfig.countDown == null || healthCheckConfig.countDown < 1) { + healthCheckConfig.countDown = 3 + } + } + return { + healthCheck: healthCheckConfig, + advancedVisible: false, + urlProtocol: urlProtocol, + urlHost: urlHost, + urlPort: urlPort, + urlRequestURI: urlRequestURI, + urlIsEditing: healthCheckConfig.url.length == 0 + } + }, + watch: { + urlRequestURI: function () { + if (this.urlRequestURI.length > 0 && this.urlRequestURI[0] != "/") { + this.urlRequestURI = "/" + this.urlRequestURI + } + this.changeURL() + }, + urlPort: function (v) { + let port = parseInt(v) + if (!isNaN(port)) { + this.urlPort = port.toString() + } else { + this.urlPort = "" + } + this.changeURL() + }, + urlProtocol: function () { + this.changeURL() + }, + urlHost: function () { + this.changeURL() + }, + "healthCheck.countTries": function (v) { + let count = parseInt(v) + if (!isNaN(count)) { + this.healthCheck.countTries = count + } else { + this.healthCheck.countTries = 0 + } + }, + "healthCheck.countUp": function (v) { + let count = parseInt(v) + if (!isNaN(count)) { + this.healthCheck.countUp = count + } else { + this.healthCheck.countUp = 0 + } + }, + "healthCheck.countDown": function (v) { + let count = parseInt(v) + if (!isNaN(count)) { + this.healthCheck.countDown = count + } else { + this.healthCheck.countDown = 0 + } + } + }, + methods: { + showAdvanced: function () { + this.advancedVisible = !this.advancedVisible + }, + changeURL: function () { + let urlHost = this.urlHost + if (urlHost.length == 0) { + urlHost = "${host}" + } + this.healthCheck.url = this.urlProtocol + "://" + urlHost + ((this.urlPort.length > 0) ? ":" + this.urlPort : "") + this.urlRequestURI + }, + changeStatus: function (values) { + this.healthCheck.statusCodes = values.$map(function (k, v) { + let status = parseInt(v) + if (isNaN(status)) { + return 0 + } else { + return status + } + }) + }, + editURL: function () { + this.urlIsEditing = !this.urlIsEditing + } + }, + template: `
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
是否启用 +
+ + +
+
URL * +
{{healthCheck.url}}   修改
+
+ + + + + + + + + + + + + + + + + +
协议 + +
域名 + +

在此集群上可以访问到的一个域名。

+
端口 + +
RequestURI
+
+

拼接后的URL:{{healthCheck.url}},其中\${host}指的是域名。

+
+
检测时间间隔 + +
是否自动下线 +
+ + +
+

选中后系统会根据健康检查的结果自动标记节点的上线/下线状态,并可能自动同步DNS设置。

+
连续上线次数 + +

连续N次检查成功后自动恢复上线。

+
连续下线次数 + +

连续N次检查失败后自动下线。

+
允许的状态码 + +
超时时间 + +
连续尝试次数 + +
每次尝试间隔 + +
终端信息(User-Agent) + +

发送到服务器的User-Agent值,不填写表示使用默认值。

+
只基础请求 + +

只做基础的请求,不处理反向代理(不检查源站)、WAF等。

+
+
+
` +}) + +// 将变量转换为中文 +Vue.component("request-variables-describer", { + data: function () { + return { + vars:[] + } + }, + methods: { + update: function (variablesString) { + this.vars = [] + let that = this + variablesString.replace(/\${.+?}/g, function (v) { + let def = that.findVar(v) + if (def == null) { + return v + } + that.vars.push(def) + }) + }, + findVar: function (name) { + let def = null + window.REQUEST_VARIABLES.forEach(function (v) { + if (v.code == name) { + def = v + } + }) + return def + } + }, + template: ` + {{v.code}} - {{v.name}} +` +}) + + +Vue.component("combo-box", { + props: ["name", "title", "placeholder", "size", "v-items", "v-value"], + data: function () { + let items = this.vItems + if (items == null || !(items instanceof Array)) { + items = [] + } + + // 自动使用ID作为值 + items.forEach(function (v) { + if (v.value == null) { + v.value = v.id + } + }) + + // 当前选中项 + let selectedItem = null + if (this.vValue != null) { + let that = this + items.forEach(function (v) { + if (v.value == that.vValue) { + selectedItem = v + } + }) + } + + return { + allItems: items, + items: items.$copy(), + selectedItem: selectedItem, + keyword: "", + visible: false, + hideTimer: null, + hoverIndex: 0 + } + }, + methods: { + reset: function () { + this.selectedItem = null + this.change() + this.hoverIndex = 0 + + let that = this + setTimeout(function () { + if (that.$refs.searchBox) { + that.$refs.searchBox.focus() + } + }) + }, + changeKeyword: function () { + this.hoverIndex = 0 + let keyword = this.keyword + if (keyword.length == 0) { + this.items = this.allItems.$copy() + return + } + this.items = this.allItems.$copy().filter(function (v) { + return teaweb.match(v.name, keyword) + }) + }, + selectItem: function (item) { + this.selectedItem = item + this.change() + this.hoverIndex = 0 + this.keyword = "" + this.changeKeyword() + }, + confirm: function () { + if (this.items.length > this.hoverIndex) { + this.selectItem(this.items[this.hoverIndex]) + } + }, + show: function () { + this.visible = true + + // 不要重置hoverIndex,以便焦点可以在输入框和可选项之间切换 + }, + hide: function () { + let that = this + this.hideTimer = setTimeout(function () { + that.visible = false + }, 500) + }, + downItem: function () { + this.hoverIndex++ + if (this.hoverIndex > this.items.length - 1) { + this.hoverIndex = 0 + } + this.focusItem() + }, + upItem: function () { + this.hoverIndex-- + if (this.hoverIndex < 0) { + this.hoverIndex = 0 + } + this.focusItem() + }, + focusItem: function () { + if (this.hoverIndex < this.items.length) { + this.$refs.itemRef[this.hoverIndex].focus() + let that = this + setTimeout(function () { + that.$refs.searchBox.focus() + if (that.hideTimer != null) { + clearTimeout(that.hideTimer) + that.hideTimer = null + } + }) + } + }, + change: function () { + this.$emit("change", this.selectedItem) + + let that = this + setTimeout(function () { + if (that.$refs.selectedLabel != null) { + that.$refs.selectedLabel.focus() + } + }) + }, + submitForm: function (event) { + if (event.target.tagName != "A") { + return + } + let parentBox = this.$refs.selectedLabel.parentNode + while (true) { + parentBox = parentBox.parentNode + if (parentBox == null || parentBox.tagName == "BODY") { + return + } + if (parentBox.tagName == "FORM") { + parentBox.submit() + break + } + } + } + }, + template: `
+ +
+ +
+ + + + + +
+ +
+
` +}) + +Vue.component("time-duration-box", { + props: ["v-name", "v-value", "v-count", "v-unit"], + mounted: function () { + this.change() + }, + data: function () { + let v = this.vValue + if (v == null) { + v = { + count: this.vCount, + unit: this.vUnit + } + } + if (typeof (v["count"]) != "number") { + v["count"] = -1 + } + return { + duration: v, + countString: (v.count >= 0) ? v.count.toString() : "" + } + }, + watch: { + "countString": function (newValue) { + let value = newValue.trim() + if (value.length == 0) { + this.duration.count = -1 + return + } + let count = parseInt(value) + if (!isNaN(count)) { + this.duration.count = count + } + this.change() + } + }, + methods: { + change: function () { + this.$emit("change", this.duration) + } + }, + template: `
+ +
+ +
+
+ +
+
` +}) + +Vue.component("not-found-box", { + props: ["message"], + template: `
+
+

{{message}}

+
` +}) + +// 警告消息 +Vue.component("warning-message", { + template: `
` +}) + +let checkboxId = 0 +Vue.component("checkbox", { + props: ["name", "value", "v-value", "id", "checked"], + data: function () { + checkboxId++ + let elementId = this.id + if (elementId == null) { + elementId = "checkbox" + checkboxId + } + + let elementValue = this.vValue + if (elementValue == null) { + elementValue = "1" + } + + let checkedValue = this.value + if (checkedValue == null && this.checked == "checked") { + checkedValue = elementValue + } + + return { + elementId: elementId, + elementValue: elementValue, + newValue: checkedValue + } + }, + methods: { + change: function () { + this.$emit("input", this.newValue) + }, + check: function () { + this.newValue = this.elementValue + }, + uncheck: function () { + this.newValue = "" + }, + isChecked: function () { + return this.newValue == this.elementValue + } + }, + watch: { + value: function (v) { + if (typeof v == "boolean") { + this.newValue = v + } + } + }, + template: `
+ + +
` +}) + +Vue.component("network-addresses-view", { + props: ["v-addresses"], + template: `
+
+ {{addr.protocol}}://{{addr.host.quoteIP()}}*:{{addr.portRange}} +
+
` +}) + +Vue.component("size-capacity-view", { + props:["v-default-text", "v-value"], + template: `
+ {{vValue.count}}{{vValue.unit.toUpperCase()}} + {{vDefaultText}} +
` +}) + +// 信息提示窗口 +Vue.component("tip-message-box", { + props: ["code"], + mounted: function () { + let that = this + Tea.action("/ui/showTip") + .params({ + code: this.code + }) + .success(function (resp) { + that.visible = resp.data.visible + }) + .post() + }, + data: function () { + return { + visible: false + } + }, + methods: { + close: function () { + this.visible = false + Tea.action("/ui/hideTip") + .params({ + code: this.code + }) + .post() + } + }, + template: `
+ + +
+ +
+
` +}) + +Vue.component("keyword", { + props: ["v-word"], + data: function () { + let word = this.vWord + if (word == null) { + word = "" + } else { + word = word.replace(/\)/g, "\\)") + word = word.replace(/\(/g, "\\(") + word = word.replace(/\+/g, "\\+") + word = word.replace(/\^/g, "\\^") + word = word.replace(/\$/g, "\\$") + word = word.replace(/\?/, "\\?") + word = word.replace(/\*/, "\\*") + word = word.replace(/\[/, "\\[") + word = word.replace(/{/, "\\{") + word = word.replace(/\./, "\\.") + } + + let slot = this.$slots["default"][0] + let text = slot.text + if (word.length > 0) { + let that = this + let m = [] // replacement => tmp + let tmpIndex = 0 + text = text.replaceAll(new RegExp("(" + word + ")", "ig"), function (replacement) { + tmpIndex++ + let s = "" + that.encodeHTML(replacement) + "" + let tmpKey = "$TMP__KEY__" + tmpIndex.toString() + "$" + m.push([tmpKey, s]) + return tmpKey + }) + text = this.encodeHTML(text) + + m.forEach(function (r) { + text = text.replace(r[0], r[1]) + }) + + } else { + text = this.encodeHTML(text) + } + + return { + word: word, + text: text + } + }, + methods: { + encodeHTML: function (s) { + s = s.replace(/&/g, "&") + s = s.replace(//g, ">") + s = s.replace(/"/g, """) + return s + } + }, + template: `` +}) + +Vue.component("node-log-row", { + props: ["v-log", "v-keyword"], + data: function () { + return { + log: this.vLog, + keyword: this.vKeyword + } + }, + template: `
+
[{{log.createdTime}}][{{log.createdTime}}][{{log.tag}}]{{log.description}}   共{{log.count}}条 {{log.server.name}}
+
` +}) + +Vue.component("provinces-selector", { + props: ["v-provinces"], + data: function () { + let provinces = this.vProvinces + if (provinces == null) { + provinces = [] + } + let provinceIds = provinces.$map(function (k, v) { + return v.id + }) + return { + provinces: provinces, + provinceIds: provinceIds + } + }, + methods: { + add: function () { + let provinceStringIds = this.provinceIds.map(function (v) { + return v.toString() + }) + let that = this + teaweb.popup("/ui/selectProvincesPopup?provinceIds=" + provinceStringIds.join(","), { + width: "48em", + height: "23em", + callback: function (resp) { + that.provinces = resp.data.provinces + that.change() + } + }) + }, + remove: function (index) { + this.provinces.$remove(index) + this.change() + }, + change: function () { + this.provinceIds = this.provinces.$map(function (k, v) { + return v.id + }) + } + }, + template: `
+ +
+
{{province.name}}
+
+
+
+ +
+
` +}) + +Vue.component("csrf-token", { + created: function () { + this.refreshToken() + }, + mounted: function () { + let that = this + this.$refs.token.form.addEventListener("submit", function () { + that.refreshToken() + }) + + // 自动刷新 + setInterval(function () { + that.refreshToken() + }, 10 * 60 * 1000) + }, + data: function () { + return { + token: "" + } + }, + methods: { + refreshToken: function () { + let that = this + Tea.action("/csrf/token") + .get() + .success(function (resp) { + that.token = resp.data.token + }) + } + }, + template: `` +}) + + +Vue.component("labeled-input", { + props: ["name", "size", "maxlength", "label", "value"], + template: '
\ + \ + {{label}}\ +
' +}); + +let radioId = 0 +Vue.component("radio", { + props: ["name", "value", "v-value", "id"], + data: function () { + radioId++ + let elementId = this.id + if (elementId == null) { + elementId = "radio" + radioId + } + return { + "elementId": elementId + } + }, + methods: { + change: function () { + this.$emit("input", this.vValue) + } + }, + template: `
+ + +
` +}) + +Vue.component("copy-to-clipboard", { + props: ["v-target"], + created: function () { + if (typeof ClipboardJS == "undefined") { + let jsFile = document.createElement("script") + jsFile.setAttribute("src", "/js/clipboard.min.js") + document.head.appendChild(jsFile) + } + }, + methods: { + copy: function () { + new ClipboardJS('[data-clipboard-target]'); + teaweb.successToast("已复制到剪切板") + } + }, + template: `` +}) + +// 节点角色名称 +Vue.component("node-role-name", { + props: ["v-role"], + data: function () { + let roleName = "" + switch (this.vRole) { + case "node": + roleName = "边缘节点" + break + case "monitor": + roleName = "监控节点" + break + case "api": + roleName = "API节点" + break + case "user": + roleName = "用户平台" + break + case "admin": + roleName = "管理平台" + break + case "database": + roleName = "数据库节点" + break + case "dns": + roleName = "DNS节点" + break + case "report": + roleName = "区域监控终端" + break + } + return { + roleName: roleName + } + }, + template: `{{roleName}}` +}) + +let sourceCodeBoxIndex = 0 + +Vue.component("source-code-box", { + props: ["name", "type", "id", "read-only", "width", "height", "focus"], + mounted: function () { + let readOnly = this.readOnly + if (typeof readOnly != "boolean") { + readOnly = true + } + let box = document.getElementById("source-code-box-" + this.index) + let valueBox = document.getElementById(this.valueBoxId) + let value = "" + if (valueBox.textContent != null) { + value = valueBox.textContent + } else if (valueBox.innerText != null) { + value = valueBox.innerText + } + + this.createEditor(box, value, readOnly) + }, + data: function () { + let index = sourceCodeBoxIndex++ + + let valueBoxId = 'source-code-box-value-' + sourceCodeBoxIndex + if (this.id != null) { + valueBoxId = this.id + } + + return { + index: index, + valueBoxId: valueBoxId + } + }, + methods: { + createEditor: function (box, value, readOnly) { + let boxEditor = CodeMirror.fromTextArea(box, { + theme: "idea", + lineNumbers: true, + value: "", + readOnly: readOnly, + showCursorWhenSelecting: true, + height: "auto", + //scrollbarStyle: null, + viewportMargin: Infinity, + lineWrapping: true, + highlightFormatting: false, + indentUnit: 4, + indentWithTabs: true, + }) + let that = this + boxEditor.on("change", function () { + that.change(boxEditor.getValue()) + }) + boxEditor.setValue(value) + + if (this.focus) { + boxEditor.focus() + } + + let width = this.width + let height = this.height + if (width != null && height != null) { + width = parseInt(width) + height = parseInt(height) + if (!isNaN(width) && !isNaN(height)) { + if (width <= 0) { + width = box.parentNode.offsetWidth + } + boxEditor.setSize(width, height) + } + } else if (height != null) { + height = parseInt(height) + if (!isNaN(height)) { + boxEditor.setSize("100%", height) + } + } + + let info = CodeMirror.findModeByMIME(this.type) + if (info != null) { + boxEditor.setOption("mode", info.mode) + CodeMirror.modeURL = "/codemirror/mode/%N/%N.js" + CodeMirror.autoLoadMode(boxEditor, info.mode) + } + }, + change: function (code) { + this.$emit("change", code) + } + }, + template: `
+
+ +
` +}) + +Vue.component("size-capacity-box", { + props: ["v-name", "v-value", "v-count", "v-unit", "size", "maxlength", "v-supported-units"], + data: function () { + let v = this.vValue + if (v == null) { + v = { + count: this.vCount, + unit: this.vUnit + } + } + if (typeof (v["count"]) != "number") { + v["count"] = -1 + } + + let vSize = this.size + if (vSize == null) { + vSize = 6 + } + + let vMaxlength = this.maxlength + if (vMaxlength == null) { + vMaxlength = 10 + } + + let supportedUnits = this.vSupportedUnits + if (supportedUnits == null) { + supportedUnits = [] + } + + return { + capacity: v, + countString: (v.count >= 0) ? v.count.toString() : "", + vSize: vSize, + vMaxlength: vMaxlength, + supportedUnits: supportedUnits + } + }, + watch: { + "countString": function (newValue) { + let value = newValue.trim() + if (value.length == 0) { + this.capacity.count = -1 + this.change() + return + } + let count = parseInt(value) + if (!isNaN(count)) { + this.capacity.count = count + } + this.change() + } + }, + methods: { + change: function () { + this.$emit("change", this.capacity) + } + }, + template: `
+ +
+ +
+
+ +
+
` +}) + +/** + * 二级菜单 + */ +Vue.component("inner-menu", { + template: ` +
+ +
` +}); + +Vue.component("datepicker", { + props: ["v-name", "v-value", "v-bottom-left"], + mounted: function () { + let that = this + teaweb.datepicker(this.$refs.dayInput, function (v) { + that.day = v + that.change() + }, !!this.vBottomLeft) + }, + data: function () { + let name = this.vName + if (name == null) { + name = "day" + } + + let day = this.vValue + if (day == null) { + day = "" + } + + return { + name: name, + day: day + } + }, + methods: { + change: function () { + this.$emit("change", this.day) + } + }, + template: `
+ +
` +}) + +// 排序使用的箭头 +Vue.component("sort-arrow", { + props: ["name"], + data: function () { + let url = window.location.toString() + let order = "" + let newArgs = [] + if (window.location.search != null && window.location.search.length > 0) { + let queryString = window.location.search.substring(1) + let pieces = queryString.split("&") + let that = this + pieces.forEach(function (v) { + let eqIndex = v.indexOf("=") + if (eqIndex > 0) { + let argName = v.substring(0, eqIndex) + let argValue = v.substring(eqIndex + 1) + if (argName == that.name) { + order = argValue + } else if (argValue != "asc" && argValue != "desc") { + newArgs.push(v) + } + } else { + newArgs.push(v) + } + }) + } + if (order == "asc") { + newArgs.push(this.name + "=desc") + } else if (order == "desc") { + newArgs.push(this.name + "=asc") + } else { + newArgs.push(this.name + "=desc") + } + + let qIndex = url.indexOf("?") + if (qIndex > 0) { + url = url.substring(0, qIndex) + "?" + newArgs.join("&") + } else { + url = url + "?" + newArgs.join("&") + } + + return { + order: order, + url: url + } + }, + template: `  ` +}) + +Vue.component("user-link", { + props: ["v-user", "v-keyword"], + data: function () { + let user = this.vUser + if (user == null) { + user = {id: 0, "username": "", "fullname": ""} + } + return { + user: user + } + }, + template: `
+ {{user.fullname}}{{user.username}} + [已删除] +
` +}) + +// 监控节点分组选择 +Vue.component("report-node-groups-selector", { + props: ["v-group-ids"], + mounted: function () { + let that = this + Tea.action("/clusters/monitors/groups/options") + .post() + .success(function (resp) { + that.groups = resp.data.groups.map(function (group) { + group.isChecked = that.groupIds.$contains(group.id) + return group + }) + that.isLoaded = true + }) + }, + data: function () { + var groupIds = this.vGroupIds + if (groupIds == null) { + groupIds = [] + } + + return { + groups: [], + groupIds: groupIds, + isLoaded: false, + allGroups: groupIds.length == 0 + } + }, + methods: { + check: function (group) { + group.isChecked = !group.isChecked + this.groupIds = [] + let that = this + this.groups.forEach(function (v) { + if (v.isChecked) { + that.groupIds.push(v.id) + } + }) + this.change() + }, + change: function () { + let that = this + let groups = [] + this.groupIds.forEach(function (groupId) { + let group = that.groups.$find(function (k, v) { + return v.id == groupId + }) + if (group == null) { + return + } + groups.push({ + id: group.id, + name: group.name + }) + }) + this.$emit("change", groups) + } + }, + watch: { + allGroups: function (b) { + if (b) { + this.groupIds = [] + this.groups.forEach(function (v) { + v.isChecked = false + }) + } + + this.change() + } + }, + template: `
+ + 还没有分组。 +
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
` +}) + +Vue.component("finance-user-selector", { + mounted: function () { + let that = this + + Tea.action("/finance/users/options") + .post() + .success(function (resp) { + that.users = resp.data.users + }) + }, + props: ["v-user-id"], + data: function () { + let userId = this.vUserId + if (userId == null) { + userId = 0 + } + return { + users: [], + userId: userId + } + }, + watch: { + userId: function (v) { + this.$emit("change", v) + } + }, + template: `
+ +
` +}) + +// 节点登录推荐端口 +Vue.component("node-login-suggest-ports", { + data: function () { + return { + ports: [], + availablePorts: [], + autoSelected: false, + isLoading: false + } + }, + methods: { + reload: function (host) { + let that = this + this.autoSelected = false + this.isLoading = true + Tea.action("/clusters/cluster/suggestLoginPorts") + .params({ + host: host + }) + .success(function (resp) { + if (resp.data.availablePorts != null) { + that.availablePorts = resp.data.availablePorts + if (that.availablePorts.length > 0) { + that.autoSelectPort(that.availablePorts[0]) + that.autoSelected = true + } + } + if (resp.data.ports != null) { + that.ports = resp.data.ports + if (that.ports.length > 0 && !that.autoSelected) { + that.autoSelectPort(that.ports[0]) + that.autoSelected = true + } + } + }) + .done(function () { + that.isLoading = false + }) + .post() + }, + selectPort: function (port) { + this.$emit("select", port) + }, + autoSelectPort: function (port) { + this.$emit("auto-select", port) + } + }, + template: ` + 正在检查端口... + + 可能端口:{{port}} +     + + + 常用端口:{{port}} + + 常用端口有22等。 + (可以点击要使用的端口) +` +}) + +Vue.component("node-group-selector", { + props: ["v-cluster-id", "v-group"], + data: function () { + return { + selectedGroup: this.vGroup + } + }, + methods: { + selectGroup: function () { + let that = this + teaweb.popup("/clusters/cluster/groups/selectPopup?clusterId=" + this.vClusterId, { + callback: function (resp) { + that.selectedGroup = resp.data.group + } + }) + }, + addGroup: function () { + let that = this + teaweb.popup("/clusters/cluster/groups/createPopup?clusterId=" + this.vClusterId, { + callback: function (resp) { + that.selectedGroup = resp.data.group + } + }) + }, + removeGroup: function () { + this.selectedGroup = null + } + }, + template: `
+
+ + {{selectedGroup.name}}   +
+ +
` +}) + +// 节点IP地址管理(标签形式) +Vue.component("node-ip-addresses-box", { + props: ["v-ip-addresses", "role"], + data: function () { + return { + ipAddresses: (this.vIpAddresses == null) ? [] : this.vIpAddresses, + supportThresholds: this.role != "ns" + } + }, + methods: { + // 添加IP地址 + addIPAddress: function () { + window.UPDATING_NODE_IP_ADDRESS = null + + let that = this; + teaweb.popup("/nodes/ipAddresses/createPopup?supportThresholds=" + (this.supportThresholds ? 1 : 0), { + callback: function (resp) { + that.ipAddresses.push(resp.data.ipAddress); + }, + height: "24em", + width: "44em" + }) + }, + + // 修改地址 + updateIPAddress: function (index, address) { + window.UPDATING_NODE_IP_ADDRESS = address + + let that = this; + teaweb.popup("/nodes/ipAddresses/updatePopup?supportThresholds=" + (this.supportThresholds ? 1 : 0), { + callback: function (resp) { + Vue.set(that.ipAddresses, index, resp.data.ipAddress); + }, + height: "24em", + width: "44em" + }) + }, + + // 删除IP地址 + removeIPAddress: function (index) { + this.ipAddresses.$remove(index); + }, + + // 判断是否为IPv6 + isIPv6: function (ip) { + return ip.indexOf(":") > -1 + } + }, + template: `
+ +
+
+
+ [IPv6] {{address.ip}} + ({{address.name}},不可访问 + (不可访问) + [off] + [down] + [{{address.thresholds.length}}个阈值] +   + + +
+
+
+
+
+ +
+
` +}) + +// 节点IP阈值 +Vue.component("node-ip-address-thresholds-view", { + props: ["v-thresholds"], + data: function () { + let thresholds = this.vThresholds + if (thresholds == null) { + thresholds = [] + } else { + thresholds.forEach(function (v) { + if (v.items == null) { + v.items = [] + } + if (v.actions == null) { + v.actions = [] + } + }) + } + + return { + thresholds: thresholds, + allItems: window.IP_ADDR_THRESHOLD_ITEMS, + allOperators: [ + { + "name": "小于等于", + "code": "lte" + }, + { + "name": "大于", + "code": "gt" + }, + { + "name": "不等于", + "code": "neq" + }, + { + "name": "小于", + "code": "lt" + }, + { + "name": "大于等于", + "code": "gte" + } + ], + allActions: window.IP_ADDR_THRESHOLD_ACTIONS + } + }, + methods: { + itemName: function (item) { + let result = "" + this.allItems.forEach(function (v) { + if (v.code == item) { + result = v.name + } + }) + return result + }, + itemUnitName: function (itemCode) { + let result = "" + this.allItems.forEach(function (v) { + if (v.code == itemCode) { + result = v.unit + } + }) + return result + }, + itemDurationUnitName: function (unit) { + switch (unit) { + case "minute": + return "分钟" + case "second": + return "秒" + case "hour": + return "小时" + case "day": + return "天" + } + return unit + }, + itemOperatorName: function (operator) { + let result = "" + this.allOperators.forEach(function (v) { + if (v.code == operator) { + result = v.name + } + }) + return result + }, + actionName: function (actionCode) { + let result = "" + this.allActions.forEach(function (v) { + if (v.code == actionCode) { + result = v.name + } + }) + return result + } + }, + template: `
+ +
+
+ + + + [{{item.duration}}{{itemDurationUnitName(item.durationUnit)}}] + + {{itemName(item.item)}} + + + + 成功 + 失败 + + + + [{{group.name}}   ] + + [{{itemOperatorName(item.operator)}}] {{item.value}}{{itemUnitName(item.item)}}   + + + AND   + -> + {{actionName(action.action)}} + 到{{action.options.ips.join(", ")}} + ({{action.options.url}}) +   + AND   + +
+
+
` +}) + +// 节点IP阈值 +Vue.component("node-ip-address-thresholds-box", { + props: ["v-thresholds"], + data: function () { + let thresholds = this.vThresholds + if (thresholds == null) { + thresholds = [] + } else { + thresholds.forEach(function (v) { + if (v.items == null) { + v.items = [] + } + if (v.actions == null) { + v.actions = [] + } + }) + } + + return { + editingIndex: -1, + thresholds: thresholds, + addingThreshold: { + items: [], + actions: [] + }, + isAdding: false, + isAddingItem: false, + isAddingAction: false, + + itemCode: "nodeAvgRequests", + itemReportGroups: [], + itemOperator: "lte", + itemValue: "", + itemDuration: "5", + allItems: window.IP_ADDR_THRESHOLD_ITEMS, + allOperators: [ + { + "name": "小于等于", + "code": "lte" + }, + { + "name": "大于", + "code": "gt" + }, + { + "name": "不等于", + "code": "neq" + }, + { + "name": "小于", + "code": "lt" + }, + { + "name": "大于等于", + "code": "gte" + } + ], + allActions: window.IP_ADDR_THRESHOLD_ACTIONS, + + actionCode: "up", + actionBackupIPs: "", + actionWebHookURL: "" + } + }, + methods: { + add: function () { + this.isAdding = !this.isAdding + }, + cancel: function () { + this.isAdding = false + this.editingIndex = -1 + this.addingThreshold = { + items: [], + actions: [] + } + }, + confirm: function () { + if (this.addingThreshold.items.length == 0) { + teaweb.warn("需要至少添加一个阈值") + return + } + if (this.addingThreshold.actions.length == 0) { + teaweb.warn("需要至少添加一个动作") + return + } + + if (this.editingIndex >= 0) { + this.thresholds[this.editingIndex].items = this.addingThreshold.items + this.thresholds[this.editingIndex].actions = this.addingThreshold.actions + } else { + this.thresholds.push({ + items: this.addingThreshold.items, + actions: this.addingThreshold.actions + }) + } + + // 还原 + this.cancel() + }, + remove: function (index) { + this.cancel() + this.thresholds.$remove(index) + }, + update: function (index) { + this.editingIndex = index + this.addingThreshold = { + items: this.thresholds[index].items.$copy(), + actions: this.thresholds[index].actions.$copy() + } + this.isAdding = true + }, + + addItem: function () { + this.isAddingItem = !this.isAddingItem + let that = this + setTimeout(function () { + that.$refs.itemValue.focus() + }, 100) + }, + cancelItem: function () { + this.isAddingItem = false + + this.itemCode = "nodeAvgRequests" + this.itmeOperator = "lte" + this.itemValue = "" + this.itemDuration = "5" + this.itemReportGroups = [] + }, + confirmItem: function () { + // 特殊阈值快速添加 + if (["nodeHealthCheck"].$contains(this.itemCode)) { + if (this.itemValue.toString().length == 0) { + teaweb.warn("请选择检查结果") + return + } + + let value = parseInt(this.itemValue) + if (isNaN(value)) { + value = 0 + } else if (value < 0) { + value = 0 + } else if (value > 1) { + value = 1 + } + + // 添加 + this.addingThreshold.items.push({ + item: this.itemCode, + operator: this.itemOperator, + value: value, + duration: 0, + durationUnit: "minute", + options: {} + }) + this.cancelItem() + return + } + + if (this.itemDuration.length == 0) { + let that = this + teaweb.warn("请输入统计周期", function () { + that.$refs.itemDuration.focus() + }) + return + } + let itemDuration = parseInt(this.itemDuration) + if (isNaN(itemDuration) || itemDuration <= 0) { + teaweb.warn("请输入正确的统计周期", function () { + that.$refs.itemDuration.focus() + }) + return + } + + if (this.itemValue.length == 0) { + let that = this + teaweb.warn("请输入对比值", function () { + that.$refs.itemValue.focus() + }) + return + } + let itemValue = parseFloat(this.itemValue) + if (isNaN(itemValue)) { + teaweb.warn("请输入正确的对比值", function () { + that.$refs.itemValue.focus() + }) + return + } + + + let options = {} + + switch (this.itemCode) { + case "connectivity": // 连通性校验 + if (itemValue > 100) { + let that = this + teaweb.warn("连通性对比值不能超过100", function () { + that.$refs.itemValue.focus() + }) + return + } + + options["groups"] = this.itemReportGroups + break + } + + // 添加 + this.addingThreshold.items.push({ + item: this.itemCode, + operator: this.itemOperator, + value: itemValue, + duration: itemDuration, + durationUnit: "minute", + options: options + }) + + // 还原 + this.cancelItem() + }, + removeItem: function (index) { + this.cancelItem() + this.addingThreshold.items.$remove(index) + }, + changeReportGroups: function (groups) { + this.itemReportGroups = groups + }, + itemName: function (item) { + let result = "" + this.allItems.forEach(function (v) { + if (v.code == item) { + result = v.name + } + }) + return result + }, + itemUnitName: function (itemCode) { + let result = "" + this.allItems.forEach(function (v) { + if (v.code == itemCode) { + result = v.unit + } + }) + return result + }, + itemDurationUnitName: function (unit) { + switch (unit) { + case "minute": + return "分钟" + case "second": + return "秒" + case "hour": + return "小时" + case "day": + return "天" + } + return unit + }, + itemOperatorName: function (operator) { + let result = "" + this.allOperators.forEach(function (v) { + if (v.code == operator) { + result = v.name + } + }) + return result + }, + + addAction: function () { + this.isAddingAction = !this.isAddingAction + }, + cancelAction: function () { + this.isAddingAction = false + this.actionCode = "up" + this.actionBackupIPs = "" + this.actionWebHookURL = "" + }, + confirmAction: function () { + this.doConfirmAction(false) + }, + doConfirmAction: function (validated, options) { + // 是否已存在 + let exists = false + let that = this + this.addingThreshold.actions.forEach(function (v) { + if (v.action == that.actionCode) { + exists = true + } + }) + if (exists) { + teaweb.warn("此动作已经添加过了,无需重复添加") + return + } + + if (options == null) { + options = {} + } + + switch (this.actionCode) { + case "switch": + if (!validated) { + Tea.action("/ui/validateIPs") + .params({ + "ips": this.actionBackupIPs + }) + .success(function (resp) { + if (resp.data.ips.length == 0) { + teaweb.warn("请输入备用IP", function () { + that.$refs.actionBackupIPs.focus() + }) + return + } + options["ips"] = resp.data.ips + that.doConfirmAction(true, options) + }) + .fail(function (resp) { + teaweb.warn("输入的IP '" + resp.data.failIP + "' 格式不正确,请改正后提交", function () { + that.$refs.actionBackupIPs.focus() + }) + }) + .post() + return + } + break + case "webHook": + if (this.actionWebHookURL.length == 0) { + teaweb.warn("请输入WebHook URL", function () { + that.$refs.webHookURL.focus() + }) + return + } + if (!this.actionWebHookURL.match(/^(http|https):\/\//i)) { + teaweb.warn("URL开头必须是http://或者https://", function () { + that.$refs.webHookURL.focus() + }) + return + } + options["url"] = this.actionWebHookURL + } + + this.addingThreshold.actions.push({ + action: this.actionCode, + options: options + }) + + // 还原 + this.cancelAction() + }, + removeAction: function (index) { + this.cancelAction() + this.addingThreshold.actions.$remove(index) + }, + actionName: function (actionCode) { + let result = "" + this.allActions.forEach(function (v) { + if (v.code == actionCode) { + result = v.name + } + }) + return result + } + }, + template: `
+ + + +
+
+ + + [{{item.duration}}{{itemDurationUnitName(item.durationUnit)}}] + + {{itemName(item.item)}} + + + + 成功 + 失败 + + + + [{{group.name}}   ] + + [{{itemOperatorName(item.operator)}}]  {{item.value}}{{itemUnitName(item.item)}} + +  AND   + + -> + {{actionName(action.action)}} + 到{{action.options.ips.join(", ")}} + ({{action.options.url}}) +  AND   +   + + +
+
+ + +
+ + + + + + + + + + + +
阈值动作
+ +
+
+ + [{{item.duration}}{{itemDurationUnitName(item.durationUnit)}}] + + {{itemName(item.item)}} + + + + 成功 + 失败 + + + + [{{group.name}}   ] + [{{itemOperatorName(item.operator)}}] {{item.value}}{{itemUnitName(item.item)}} + +   + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
统计项目 + +

{{item.description}}

+
统计周期 +
+ + 分钟 +
+
操作符 + +
对比值 +
+ + {{item.unit}} +
+
检查结果 + +

只有状态发生改变的时候才会触发。

+
终端分组 +
+
+
+   + +
+
+
+ +
+
+ +
+
+ {{actionName(action.action)}}   + 到{{action.options.ips.join(", ")}} + ({{action.options.url}}) + +
+
+ + +
+ + + + + + + + + + + + + + + + + +
动作类型 + +

{{action.description}}

+
备用IP * + +

每行一个备用IP。

+
URL * + +

完整的URL,比如https://example.com/webhook/api,系统会在触发阈值的时候通过GET调用此URL。

+
+
+   + +
+
+ +
+ +
+
+ + +
+   + +
+
+ +
+ +
+
` +}) + +Vue.component("node-region-selector", { + props: ["v-region"], + data: function () { + return { + selectedRegion: this.vRegion + } + }, + methods: { + selectRegion: function () { + let that = this + teaweb.popup("/clusters/regions/selectPopup?clusterId=" + this.vClusterId, { + callback: function (resp) { + that.selectedRegion = resp.data.region + } + }) + }, + addRegion: function () { + let that = this + teaweb.popup("/clusters/regions/createPopup?clusterId=" + this.vClusterId, { + callback: function (resp) { + that.selectedRegion = resp.data.region + } + }) + }, + removeRegion: function () { + this.selectedRegion = null + } + }, + template: `
+
+ + {{selectedRegion.name}}   +
+ +
` +}) + +Vue.component("node-combo-box", { + props: ["v-cluster-id", "v-node-id"], + data: function () { + let that = this + Tea.action("/clusters/nodeOptions") + .params({ + clusterId: this.vClusterId + }) + .post() + .success(function (resp) { + that.nodes = resp.data.nodes + }) + return { + nodes: [] + } + }, + template: `
+ +
` +}) + +// 节点级别选择器 +Vue.component("node-level-selector", { + props: ["v-node-level"], + data: function () { + let levelCode = this.vNodeLevel + if (levelCode == null || levelCode < 1) { + levelCode = 1 + } + return { + levels: [ + { + name: "边缘节点", + code: 1, + description: "普通的边缘节点。" + }, + { + name: "L2节点", + code: 2, + description: "特殊的边缘节点,同时负责同组上一级节点的回源。" + } + ], + levelCode: levelCode + } + }, + template: `
+ +

{{levels[levelCode - 1].description}}

+
` +}) + +Vue.component("dns-route-selector", { + props: ["v-all-routes", "v-routes"], + data: function () { + let routes = this.vRoutes + if (routes == null) { + routes = [] + } + routes.$sort(function (v1, v2) { + if (v1.domainId == v2.domainId) { + return v1.code < v2.code + } + return (v1.domainId < v2.domainId) ? 1 : -1 + }) + return { + routes: routes, + routeCodes: routes.$map(function (k, v) { + return v.code + "@" + v.domainId + }), + isAdding: false, + routeCode: "", + keyword: "", + searchingRoutes: this.vAllRoutes.$copy() + } + }, + methods: { + add: function () { + this.isAdding = true + this.keyword = "" + this.routeCode = "" + + let that = this + setTimeout(function () { + that.$refs.keywordRef.focus() + }, 200) + }, + cancel: function () { + this.isAdding = false + }, + confirm: function () { + if (this.routeCode.length == 0) { + return + } + if (this.routeCodes.$contains(this.routeCode)) { + teaweb.warn("已经添加过此线路,不能重复添加") + return + } + let that = this + let route = this.vAllRoutes.$find(function (k, v) { + return v.code + "@" + v.domainId == that.routeCode + }) + if (route == null) { + return + } + + this.routeCodes.push(this.routeCode) + this.routes.push(route) + + this.routes.$sort(function (v1, v2) { + if (v1.domainId == v2.domainId) { + return v1.code < v2.code + } + return (v1.domainId < v2.domainId) ? 1 : -1 + }) + + this.routeCode = "" + this.isAdding = false + }, + remove: function (route) { + this.routeCodes.$removeValue(route.code + "@" + route.domainId) + this.routes.$removeIf(function (k, v) { + return v.code + "@" + v.domainId == route.code + "@" + route.domainId + }) + } + }, + watch: { + keyword: function (keyword) { + if (keyword.length == 0) { + this.searchingRoutes = this.vAllRoutes.$copy() + this.routeCode = "" + return + } + this.searchingRoutes = this.vAllRoutes.filter(function (route) { + return teaweb.match(route.name, keyword) || teaweb.match(route.domainName, keyword) + }) + if (this.searchingRoutes.length > 0) { + this.routeCode = this.searchingRoutes[0].code + "@" + this.searchingRoutes[0].domainId + } else { + this.routeCode = "" + } + } + }, + template: `
+ +
+ + {{route.name}} ({{route.domainName}}) + +
+
+ +
+
+
+ +
+
+ +
+ +
+ +
+
+ +
+
+
+
` +}) + +Vue.component("dns-domain-selector", { + props: ["v-domain-id", "v-domain-name"], + data: function () { + let domainId = this.vDomainId + if (domainId == null) { + domainId = 0 + } + let domainName = this.vDomainName + if (domainName == null) { + domainName = "" + } + return { + domainId: domainId, + domainName: domainName + } + }, + methods: { + select: function () { + let that = this + teaweb.popup("/dns/domains/selectPopup", { + callback: function (resp) { + that.domainId = resp.data.domainId + that.domainName = resp.data.domainName + that.change() + } + }) + }, + remove: function() { + this.domainId = 0 + this.domainName = "" + this.change() + }, + update: function () { + let that = this + teaweb.popup("/dns/domains/selectPopup?domainId=" + this.domainId, { + callback: function (resp) { + that.domainId = resp.data.domainId + that.domainName = resp.data.domainName + that.change() + } + }) + }, + change: function () { + this.$emit("change", { + id: this.domainId, + name: this.domainName + }) + } + }, + template: `
+ +
+ + {{domainName}} + + + +
+ +
` +}) + +Vue.component("grant-selector", { + props: ["v-grant", "v-node-cluster-id", "v-ns-cluster-id"], + data: function () { + return { + grantId: (this.vGrant == null) ? 0 : this.vGrant.id, + grant: this.vGrant, + nodeClusterId: (this.vNodeClusterId != null) ? this.vNodeClusterId : 0, + nsClusterId: (this.vNsClusterId != null) ? this.vNsClusterId : 0 + } + }, + methods: { + // 选择授权 + select: function () { + let that = this; + teaweb.popup("/clusters/grants/selectPopup?nodeClusterId=" + this.nodeClusterId + "&nsClusterId=" + this.nsClusterId, { + callback: (resp) => { + that.grantId = resp.data.grant.id; + if (that.grantId > 0) { + that.grant = resp.data.grant; + } + that.notifyUpdate() + }, + height: "26em" + }) + }, + + // 创建授权 + create: function () { + let that = this + teaweb.popup("/clusters/grants/createPopup", { + height: "26em", + callback: (resp) => { + that.grantId = resp.data.grant.id; + if (that.grantId > 0) { + that.grant = resp.data.grant; + } + that.notifyUpdate() + } + }) + }, + + // 修改授权 + update: function () { + if (this.grant == null) { + window.location.reload(); + return; + } + let that = this + teaweb.popup("/clusters/grants/updatePopup?grantId=" + this.grant.id, { + height: "26em", + callback: (resp) => { + that.grant = resp.data.grant + that.notifyUpdate() + } + }) + }, + + // 删除已选择授权 + remove: function () { + this.grant = null + this.grantId = 0 + this.notifyUpdate() + }, + notifyUpdate: function () { + this.$emit("change", this.grant) + } + }, + template: `
+ +
{{grant.name}}({{grant.methodName}})({{grant.username}})
+ +
` +}) + +window.REQUEST_COND_COMPONENTS = [{"type":"url-extension","name":"URL扩展名","description":"根据URL中的文件路径扩展名进行过滤","component":"http-cond-url-extension","paramsTitle":"扩展名列表","isRequest":true,"caseInsensitive":false},{"type":"url-prefix","name":"URL前缀","description":"根据URL中的文件路径前缀进行过滤","component":"http-cond-url-prefix","paramsTitle":"URL前缀","isRequest":true,"caseInsensitive":true},{"type":"url-eq","name":"URL精准匹配","description":"检查URL中的文件路径是否一致","component":"http-cond-url-eq","paramsTitle":"URL完整路径","isRequest":true,"caseInsensitive":true},{"type":"url-regexp","name":"URL正则匹配","description":"使用正则表达式检查URL中的文件路径是否一致","component":"http-cond-url-regexp","paramsTitle":"正则表达式","isRequest":true,"caseInsensitive":true},{"type":"user-agent-regexp","name":"User-Agent正则匹配","description":"使用正则表达式检查User-Agent中是否含有某些浏览器和系统标识","component":"http-cond-user-agent-regexp","paramsTitle":"正则表达式","isRequest":true,"caseInsensitive":true},{"type":"params","name":"参数匹配","description":"根据参数值进行匹配","component":"http-cond-params","paramsTitle":"参数配置","isRequest":true,"caseInsensitive":false},{"type":"url-not-extension","name":"排除:URL扩展名","description":"根据URL中的文件路径扩展名进行过滤","component":"http-cond-url-not-extension","paramsTitle":"扩展名列表","isRequest":true,"caseInsensitive":false},{"type":"url-not-prefix","name":"排除:URL前缀","description":"根据URL中的文件路径前缀进行过滤","component":"http-cond-url-not-prefix","paramsTitle":"URL前缀","isRequest":true,"caseInsensitive":true},{"type":"url-not-eq","name":"排除:URL精准匹配","description":"检查URL中的文件路径是否一致","component":"http-cond-url-not-eq","paramsTitle":"URL完整路径","isRequest":true,"caseInsensitive":true},{"type":"url-not-regexp","name":"排除:URL正则匹配","description":"使用正则表达式检查URL中的文件路径是否一致,如果一致,则不匹配","component":"http-cond-url-not-regexp","paramsTitle":"正则表达式","isRequest":true,"caseInsensitive":true},{"type":"user-agent-not-regexp","name":"排除:User-Agent正则匹配","description":"使用正则表达式检查User-Agent中是否含有某些浏览器和系统标识,如果含有,则不匹配","component":"http-cond-user-agent-not-regexp","paramsTitle":"正则表达式","isRequest":true,"caseInsensitive":true},{"type":"mime-type","name":"内容MimeType","description":"根据服务器返回的内容的MimeType进行过滤。注意:当用于缓存条件时,此条件需要结合别的请求条件使用。","component":"http-cond-mime-type","paramsTitle":"MimeType列表","isRequest":false,"caseInsensitive":false}] + +window.REQUEST_COND_OPERATORS = [{"description":"判断是否正则表达式匹配","name":"正则表达式匹配","op":"regexp"},{"description":"判断是否正则表达式不匹配","name":"正则表达式不匹配","op":"not regexp"},{"description":"使用字符串对比参数值是否相等于某个值","name":"字符串等于","op":"eq"},{"description":"参数值包含某个前缀","name":"字符串前缀","op":"prefix"},{"description":"参数值包含某个后缀","name":"字符串后缀","op":"suffix"},{"description":"参数值包含另外一个字符串","name":"字符串包含","op":"contains"},{"description":"参数值不包含另外一个字符串","name":"字符串不包含","op":"not contains"},{"description":"使用字符串对比参数值是否不相等于某个值","name":"字符串不等于","op":"not"},{"description":"判断参数值在某个列表中","name":"在列表中","op":"in"},{"description":"判断参数值不在某个列表中","name":"不在列表中","op":"not in"},{"description":"判断小写的扩展名(不带点)在某个列表中","name":"扩展名","op":"file ext"},{"description":"判断MimeType在某个列表中,支持类似于image/*的语法","name":"MimeType","op":"mime type"},{"description":"判断版本号在某个范围内,格式为version1,version2","name":"版本号范围","op":"version range"},{"description":"将参数转换为整数数字后进行对比","name":"整数等于","op":"eq int"},{"description":"将参数转换为可以有小数的浮点数字进行对比","name":"浮点数等于","op":"eq float"},{"description":"将参数转换为数字进行对比","name":"数字大于","op":"gt"},{"description":"将参数转换为数字进行对比","name":"数字大于等于","op":"gte"},{"description":"将参数转换为数字进行对比","name":"数字小于","op":"lt"},{"description":"将参数转换为数字进行对比","name":"数字小于等于","op":"lte"},{"description":"对整数参数值取模,除数为10,对比值为余数","name":"整数取模10","op":"mod 10"},{"description":"对整数参数值取模,除数为100,对比值为余数","name":"整数取模100","op":"mod 100"},{"description":"对整数参数值取模,对比值格式为:除数,余数,比如10,1","name":"整数取模","op":"mod"},{"description":"将参数转换为IP进行对比","name":"IP等于","op":"eq ip"},{"description":"将参数转换为IP进行对比","name":"IP大于","op":"gt ip"},{"description":"将参数转换为IP进行对比","name":"IP大于等于","op":"gte ip"},{"description":"将参数转换为IP进行对比","name":"IP小于","op":"lt ip"},{"description":"将参数转换为IP进行对比","name":"IP小于等于","op":"lte ip"},{"description":"IP在某个范围之内,范围格式可以是英文逗号分隔的ip1,ip2,或者CIDR格式的ip/bits","name":"IP范围","op":"ip range"},{"description":"对IP参数值取模,除数为10,对比值为余数","name":"IP取模10","op":"ip mod 10"},{"description":"对IP参数值取模,除数为100,对比值为余数","name":"IP取模100","op":"ip mod 100"},{"description":"对IP参数值取模,对比值格式为:除数,余数,比如10,1","name":"IP取模","op":"ip mod"},{"description":"判断参数值解析后的文件是否存在","name":"文件存在","op":"file exist"},{"description":"判断参数值解析后的文件是否不存在","name":"文件不存在","op":"file not exist"}] + +window.REQUEST_VARIABLES = [{"code":"${edgeVersion}","description":"","name":"边缘节点版本"},{"code":"${remoteAddr}","description":"会依次根据X-Forwarded-For、X-Real-IP、RemoteAddr获取,适合前端有别的反向代理服务时使用,存在伪造的风险","name":"客户端地址(IP)"},{"code":"${rawRemoteAddr}","description":"返回直接连接服务的客户端原始IP地址","name":"客户端地址(IP)"},{"code":"${remotePort}","description":"","name":"客户端端口"},{"code":"${remoteUser}","description":"","name":"客户端用户名"},{"code":"${requestURI}","description":"比如/hello?name=lily","name":"请求URI"},{"code":"${requestPath}","description":"比如/hello","name":"请求路径(不包括参数)"},{"code":"${requestURL}","description":"比如https://example.com/hello?name=lily","name":"完整的请求URL"},{"code":"${requestLength}","description":"","name":"请求内容长度"},{"code":"${requestMethod}","description":"比如GET、POST","name":"请求方法"},{"code":"${requestFilename}","description":"","name":"请求文件路径"},{"code":"${scheme}","description":"","name":"请求协议,http或https"},{"code":"${proto}","description:":"类似于HTTP/1.0","name":"包含版本的HTTP请求协议"},{"code":"${timeISO8601}","description":"比如2018-07-16T23:52:24.839+08:00","name":"ISO 8601格式的时间"},{"code":"${timeLocal}","description":"比如17/Jul/2018:09:52:24 +0800","name":"本地时间"},{"code":"${msec}","description":"比如1531756823.054","name":"带有毫秒的时间"},{"code":"${timestamp}","description":"","name":"unix时间戳,单位为秒"},{"code":"${host}","description":"","name":"主机名"},{"code":"${serverName}","description":"","name":"接收请求的服务器名"},{"code":"${serverPort}","description":"","name":"接收请求的服务器端口"},{"code":"${referer}","description":"","name":"请求来源URL"},{"code":"${referer.host}","description":"","name":"请求来源URL域名"},{"code":"${userAgent}","description":"","name":"客户端信息"},{"code":"${contentType}","description":"","name":"请求头部的Content-Type"},{"code":"${cookies}","description":"","name":"所有cookie组合字符串"},{"code":"${cookie.NAME}","description":"","name":"单个cookie值"},{"code":"${isArgs}","description":"如果URL有参数,则值为`?`;否则,则值为空","name":"问号(?)标记"},{"code":"${args}","description":"","name":"所有参数组合字符串"},{"code":"${arg.NAME}","description":"","name":"单个参数值"},{"code":"${headers}","description":"","name":"所有Header信息组合字符串"},{"code":"${header.NAME}","description":"","name":"单个Header值"},{"code":"${geo.country.name}","description":"","name":"国家/地区名称"},{"code":"${geo.country.id}","description":"","name":"国家/地区ID"},{"code":"${geo.province.name}","description":"目前只包含中国省份","name":"省份名称"},{"code":"${geo.province.id}","description":"目前只包含中国省份","name":"省份ID"},{"code":"${geo.city.name}","description":"目前只包含中国城市","name":"城市名称"},{"code":"${geo.city.id}","description":"目前只包含中国城市","name":"城市名称"},{"code":"${isp.name}","description":"","name":"ISP服务商名称"},{"code":"${isp.id}","description":"","name":"ISP服务商ID"},{"code":"${browser.os.name}","description":"客户端所在操作系统名称","name":"操作系统名称"},{"code":"${browser.os.version}","description":"客户端所在操作系统版本","name":"操作系统版本"},{"code":"${browser.name}","description":"客户端浏览器名称","name":"浏览器名称"},{"code":"${browser.version}","description":"客户端浏览器版本","name":"浏览器版本"},{"code":"${browser.isMobile}","description":"如果客户端是手机,则值为1,否则为0","name":"手机标识"}] + +window.METRIC_HTTP_KEYS = [{"name":"客户端地址(IP)","code":"${remoteAddr}","description":"会依次根据X-Forwarded-For、X-Real-IP、RemoteAddr获取,适用于前端可能有别的反向代理的情形,存在被伪造的可能","icon":""},{"name":"直接客户端地址(IP)","code":"${rawRemoteAddr}","description":"返回直接连接服务的客户端原始IP地址","icon":""},{"name":"客户端用户名","code":"${remoteUser}","description":"通过基本认证填入的用户名","icon":""},{"name":"请求URI","code":"${requestURI}","description":"包含参数,比如/hello?name=lily","icon":""},{"name":"请求路径","code":"${requestPath}","description":"不包含参数,比如/hello","icon":""},{"name":"完整URL","code":"${requestURL}","description":"比如https://example.com/hello?name=lily","icon":""},{"name":"请求方法","code":"${requestMethod}","description":"比如GET、POST等","icon":""},{"name":"请求协议Scheme","code":"${scheme}","description":"http或https","icon":""},{"name":"文件扩展名","code":"${requestPathExtension}","description":"请求路径中的文件扩展名,包括点符号,比如.html、.png","icon":""},{"name":"主机名","code":"${host}","description":"通常是请求的域名","icon":""},{"name":"请求协议Proto","code":"${proto}","description":"包含版本的HTTP请求协议,类似于HTTP/1.0","icon":""},{"name":"HTTP协议","code":"${proto}","description":"包含版本的HTTP请求协议,类似于HTTP/1.0","icon":""},{"name":"URL参数值","code":"${arg.NAME}","description":"单个URL参数值","icon":""},{"name":"请求来源URL","code":"${referer}","description":"请求来源Referer URL","icon":""},{"name":"请求来源URL域名","code":"${referer.host}","description":"请求来源Referer URL域名","icon":""},{"name":"Header值","code":"${header.NAME}","description":"单个Header值,比如${header.User-Agent}","icon":""},{"name":"Cookie值","code":"${cookie.NAME}","description":"单个cookie值,比如${cookie.sid}","icon":""},{"name":"状态码","code":"${status}","description":"","icon":""},{"name":"响应的Content-Type值","code":"${response.contentType}","description":"","icon":""}] + +window.IP_ADDR_THRESHOLD_ITEMS = [{"code":"nodeAvgRequests","description":"当前节点在单位时间内接收到的平均请求数。","name":"节点平均请求数","unit":"个"},{"code":"nodeAvgTrafficOut","description":"当前节点在单位时间内发送的下行流量。","name":"节点平均下行流量","unit":"M"},{"code":"nodeAvgTrafficIn","description":"当前节点在单位时间内接收的上行流量。","name":"节点平均上行流量","unit":"M"},{"code":"nodeHealthCheck","description":"当前节点健康检查结果。","name":"节点健康检查结果","unit":""},{"code":"connectivity","description":"通过区域监控得到的当前IP地址的连通性数值,取值在0和100之间。","name":"IP连通性","unit":"%"},{"code":"groupAvgRequests","description":"当前节点所在分组在单位时间内接收到的平均请求数。","name":"分组平均请求数","unit":"个"},{"code":"groupAvgTrafficOut","description":"当前节点所在分组在单位时间内发送的下行流量。","name":"分组平均下行流量","unit":"M"},{"code":"groupAvgTrafficIn","description":"当前节点所在分组在单位时间内接收的上行流量。","name":"分组平均上行流量","unit":"M"},{"code":"clusterAvgRequests","description":"当前节点所在集群在单位时间内接收到的平均请求数。","name":"集群平均请求数","unit":"个"},{"code":"clusterAvgTrafficOut","description":"当前节点所在集群在单位时间内发送的下行流量。","name":"集群平均下行流量","unit":"M"},{"code":"clusterAvgTrafficIn","description":"当前节点所在集群在单位时间内接收的上行流量。","name":"集群平均上行流量","unit":"M"}] + +window.IP_ADDR_THRESHOLD_ACTIONS = [{"code":"up","description":"上线当前IP。","name":"上线"},{"code":"down","description":"下线当前IP。","name":"下线"},{"code":"notify","description":"发送已达到阈值通知。","name":"通知"},{"code":"switch","description":"在DNS中记录中将IP切换到指定的备用IP。","name":"切换"},{"code":"webHook","description":"调用外部的WebHook。","name":"WebHook"}] +