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: ``
})
Vue.component("ddos-protection-ports-config-box", {
	props: ["v-ports"],
	data: function () {
		let ports = this.vPorts
		if (ports == null) {
			ports = []
		}
		return {
			ports: ports,
			isAdding: false,
			addingPort: {
				port: "",
				description: ""
			}
		}
	},
	methods: {
		add: function () {
			this.isAdding = true
			let that = this
			setTimeout(function () {
				that.$refs.addingPortInput.focus()
			})
		},
		confirm: function () {
			let portString = this.addingPort.port
			if (portString.length == 0) {
				this.warn("请输入端口号")
				return
			}
			if (!/^\d+$/.test(portString)) {
				this.warn("请输入正确的端口号")
				return
			}
			let port = parseInt(portString, 10)
			if (port <= 0) {
				this.warn("请输入正确的端口号")
				return
			}
			if (port > 65535) {
				this.warn("请输入正确的端口号")
				return
			}
			let exists = false
			this.ports.forEach(function (v) {
				if (v.port == port) {
					exists = true
				}
			})
			if (exists) {
				this.warn("端口号已经存在")
				return
			}
			this.ports.push({
				port: port,
				description: this.addingPort.description
			})
			this.notifyChange()
			this.cancel()
		},
		cancel: function () {
			this.isAdding = false
			this.addingPort = {
				port: "",
				description: ""
			}
		},
		remove: function (index) {
			this.ports.$remove(index)
			this.notifyChange()
		},
		warn: function (message) {
			let that = this
			teaweb.warn(message, function () {
				that.$refs.addingPortInput.focus()
			})
		},
		notifyChange: function () {
			this.$emit("change", this.ports)
		}
	},
	template: `
	
		
			{{portConfig.port}} 
({{portConfig.description}})   
		 
		
	 
	
	
		+ 
	
 `
})
// 显示节点的多个集群
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: `
	
		[选择集群] 
		{{cluster.name}} 
	 
`
})
Vue.component("node-ddos-protection-config-box", {
	props: ["v-ddos-protection-config", "v-default-configs", "v-is-node", "v-cluster-is-on"],
	data: function () {
		let config = this.vDdosProtectionConfig
		if (config == null) {
			config = {
				tcp: {
					isPrior: false,
					isOn: false,
					maxConnections: 0,
					maxConnectionsPerIP: 0,
					newConnectionsRate: 0,
					newConnectionsRateBlockTimeout: 0,
					newConnectionsSecondlyRate: 0,
					newConnectionSecondlyRateBlockTimeout: 0,
					allowIPList: [],
					ports: []
				}
			}
		}
		// initialize
		if (config.tcp == null) {
			config.tcp = {
				isPrior: false,
				isOn: false,
				maxConnections: 0,
				maxConnectionsPerIP: 0,
				newConnectionsRate: 0,
				newConnectionsRateBlockTimeout: 0,
				newConnectionsSecondlyRate: 0,
				newConnectionSecondlyRateBlockTimeout: 0,
				allowIPList: [],
				ports: []
			}
		}
		return {
			config: config,
			defaultConfigs: this.vDefaultConfigs,
			isNode: this.vIsNode,
			isAddingPort: false
		}
	},
	methods: {
		changeTCPPorts: function (ports) {
			this.config.tcp.ports = ports
		},
		changeTCPAllowIPList: function (ipList) {
			this.config.tcp.allowIPList = ipList
		}
	},
	template: `
  
 
 当前节点所在集群已设置DDoS防护。
 TCP设置 
 
 	 
 	
		
			启用DDoS防护 
			
				 
			 
		 
	 
	
		
			单节点TCP最大连接数 
			
				 
				
			 
		 
		
			单IP TCP最大连接数 
			
				 
				
			 
		 
		
			单IP TCP新连接速率(分钟)  
			
				
				
				
			 
		 
		
			单IP TCP新连接速率(秒钟)  
			
				
				
				
			 
		 
		
			TCP端口列表 
			
				 
				
			 
		 
		
			IP白名单 
			
				 
				
			 
		 
	 
 `
})
Vue.component("ddos-protection-ip-list-config-box", {
	props: ["v-ip-list"],
	data: function () {
		let list = this.vIpList
		if (list == null) {
			list = []
		}
		return {
			list: list,
			isAdding: false,
			addingIP: {
				ip: "",
				description: ""
			}
		}
	},
	methods: {
		add: function () {
			this.isAdding = true
			let that = this
			setTimeout(function () {
				that.$refs.addingIPInput.focus()
			})
		},
		confirm: function () {
			let ip = this.addingIP.ip
			if (ip.length == 0) {
				this.warn("请输入IP")
				return
			}
			let exists = false
			this.list.forEach(function (v) {
				if (v.ip == ip) {
					exists = true
				}
			})
			if (exists) {
				this.warn("IP '" + ip + "'已经存在")
				return
			}
			let that = this
			Tea.Vue.$post("/ui/validateIPs")
				.params({
					ips: [ip]
				})
				.success(function () {
					that.list.push({
						ip: ip,
						description: that.addingIP.description
					})
					that.notifyChange()
					that.cancel()
				})
				.fail(function () {
					that.warn("请输入正确的IP")
				})
		},
		cancel: function () {
			this.isAdding = false
			this.addingIP = {
				ip: "",
				description: ""
			}
		},
		remove: function (index) {
			this.list.$remove(index)
			this.notifyChange()
		},
		warn: function (message) {
			let that = this
			teaweb.warn(message, function () {
				that.$refs.addingIPInput.focus()
			})
		},
		notifyChange: function () {
			this.$emit("change", this.list)
		}
	},
	template: `
	
		
			{{ipConfig.ip}} 
({{ipConfig.description}})   
		 
		
	 
	
	
		+ 
	
 `
})
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: ``
})
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: `
    
        [选择媒介类型] 
        {{media.name}} 
     
    
`
})
// 消息接收人设置
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: ``
})
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: `
    
        [选择媒介] 
        {{instance.name}} ({{instance.media.name}}) 
     
    
`
})
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("ns-domain-group-selector", {
	props: ["v-domain-group-id"],
	data: function () {
		let groupId = this.vDomainGroupId
		if (groupId == null) {
			groupId = 0
		}
		return {
			userId: 0,
			groupId: groupId
		}
	},
	methods: {
		change: function (group) {
			if (group != null) {
				this.$emit("change", group.id)
			} else {
				this.$emit("change", 0)
			}
		},
		reload: function (userId) {
			this.userId = userId
			this.$refs.comboBox.clear()
			this.$refs.comboBox.setDataURL("/ns/domains/groups/options?userId=" + userId)
			this.$refs.comboBox.reloadData()
		}
	},
	template: `
		
	 
`
})
// 选择多个线路
Vue.component("ns-routes-selector", {
	props: ["v-routes", "name"],
	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 = []
		}
		let inputName = this.name
		if (typeof inputName != "string" || inputName.length == 0) {
			inputName = "routeCodes"
		}
		return {
			routeCode: "default",
			inputName: inputName,
			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"
			this.$emit("add")
		},
		cancel: function () {
			this.isAdding = false
			this.$emit("cancel")
		},
		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.$emit("change", this.selectedRoutes)
			this.cancel()
		},
		remove: function (index) {
			this.selectedRoutes.$remove(index)
			this.$emit("change", this.selectedRoutes)
		}
	}
	,
	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: ``
})
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-records-health-check-config-box", {
	props:["value"],
	data: function () {
		let config = this.value
		if (config == null) {
			config = {
				isOn: false,
				port: 80,
				timeoutSeconds: 5,
				countUp: 1,
				countDown: 3
			}
		}
		return {
			config: config,
			portString: config.port.toString(),
			timeoutSecondsString: config.timeoutSeconds.toString(),
			countUpString: config.countUp.toString(),
			countDownString: config.countDown.toString()
		}
	},
	watch: {
		portString: function (value) {
			let port = parseInt(value.toString())
			if (isNaN(port) || port > 65535 || port < 1) {
				this.config.port = 80
			} else {
				this.config.port = port
			}
		},
		timeoutSecondsString: function (value) {
			let timeoutSeconds = parseInt(value.toString())
			if (isNaN(timeoutSeconds) || timeoutSeconds > 1000 || timeoutSeconds < 1) {
				this.config.timeoutSeconds = 5
			} else {
				this.config.timeoutSeconds = timeoutSeconds
			}
		},
		countUpString: function (value) {
			let countUp = parseInt(value.toString())
			if (isNaN(countUp) || countUp > 1000 || countUp < 1) {
				this.config.countUp = 1
			} else {
				this.config.countUp = countUp
			}
		},
		countDownString: function (value) {
			let countDown = parseInt(value.toString())
			if (isNaN(countDown) || countDown > 1000 || countDown < 1) {
				this.config.countDown = 3
			} else {
				this.config.countDown = countDown
			}
		}
	},
	template: ``
})
Vue.component("ns-node-ddos-protection-config-box", {
	props: ["v-ddos-protection-config", "v-default-configs", "v-is-node", "v-cluster-is-on"],
	data: function () {
		let config = this.vDdosProtectionConfig
		if (config == null) {
			config = {
				tcp: {
					isPrior: false,
					isOn: false,
					maxConnections: 0,
					maxConnectionsPerIP: 0,
					newConnectionsRate: 0,
					newConnectionsRateBlockTimeout: 0,
					newConnectionsSecondlyRate: 0,
					newConnectionSecondlyRateBlockTimeout: 0,
					allowIPList: [],
					ports: []
				}
			}
		}
		// initialize
		if (config.tcp == null) {
			config.tcp = {
				isPrior: false,
				isOn: false,
				maxConnections: 0,
				maxConnectionsPerIP: 0,
				newConnectionsRate: 0,
				newConnectionsRateBlockTimeout: 0,
				newConnectionsSecondlyRate: 0,
				newConnectionSecondlyRateBlockTimeout: 0,
				allowIPList: [],
				ports: []
			}
		}
		return {
			config: config,
			defaultConfigs: this.vDefaultConfigs,
			isNode: this.vIsNode,
			isAddingPort: false
		}
	},
	methods: {
		changeTCPPorts: function (ports) {
			this.config.tcp.ports = ports
		},
		changeTCPAllowIPList: function (ipList) {
			this.config.tcp.allowIPList = ipList
		}
	},
	template: `
  
 
 当前节点所在集群已设置DDoS防护。
 TCP设置 
 
 	 
 	
		
			启用 
			
				 
			 
		 
	 
	
		
			单节点TCP最大连接数 
			
				 
				
			 
		 
		
			单IP TCP最大连接数 
			
				 
				
			 
		 
		
			单IP TCP新连接速率(分钟)  
			
				
				
				
			 
		 
		
			单IP TCP新连接速率(秒钟)  
			
				
				
				
			 
		 
		
			TCP端口列表 
			
				 
				
			 
		 
		
			IP白名单 
			
				 
				
			 
		 
	 
 `
})
Vue.component("ns-route-ranges-box", {
	props: ["v-ranges"],
	data: function () {
		let ranges = this.vRanges
		if (ranges == null) {
			ranges = []
		}
		return {
			ranges: ranges,
			isAdding: false,
			isAddingBatch: false,
			// 类型
			rangeType: "ipRange",
			isReverse: false,
			// IP范围
			ipRangeFrom: "",
			ipRangeTo: "",
			batchIPRange: "",
			// CIDR
			ipCIDR: "",
			batchIPCIDR: "",
			// region
			regions: [],
			regionType: "country",
			regionConnector: "OR"
		}
	},
	methods: {
		addIPRange: function () {
			this.isAdding = true
			let that = this
			setTimeout(function () {
				that.$refs.ipRangeFrom.focus()
			}, 100)
		},
		addCIDR: function () {
			this.isAdding = true
			let that = this
			setTimeout(function () {
				that.$refs.ipCIDR.focus()
			}, 100)
		},
		addRegions: function () {
			this.isAdding = true
		},
		addRegion: function (regionType) {
			this.regionType = regionType
		},
		remove: function (index) {
			this.ranges.$remove(index)
		},
		cancelIPRange: function () {
			this.isAdding = false
			this.ipRangeFrom = ""
			this.ipRangeTo = ""
			this.isReverse = false
		},
		cancelIPCIDR: function () {
			this.isAdding = false
			this.ipCIDR = ""
			this.isReverse = false
		},
		cancelRegions: function () {
			this.isAdding = false
			this.regions = []
			this.regionType = "country"
			this.regionConnector = "OR"
			this.isReverse = false
		},
		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,
					isReverse: this.isReverse
				}
			})
			this.cancelIPRange()
		},
		confirmIPCIDR: function () {
			let that = this
			if (this.ipCIDR.length == 0) {
				teaweb.warn("请填写CIDR", function () {
					that.$refs.ipCIDR.focus()
				})
				return
			}
			if (!this.validateCIDR(this.ipCIDR)) {
				teaweb.warn("请输入正确的CIDR", function () {
					that.$refs.ipCIDR.focus()
				})
				return
			}
			this.ranges.push({
				type: "cidr",
				params: {
					cidr: this.ipCIDR,
					isReverse: this.isReverse
				}
			})
			this.cancelIPCIDR()
		},
		confirmRegions: function () {
			if (this.regions.length == 0) {
				this.cancelRegions()
				return
			}
			this.ranges.push({
				type: "region",
				connector: this.regionConnector,
				params: {
					regions: this.regions,
					isReverse: this.isReverse
				}
			})
			this.cancelRegions()
		},
		addBatchIPRange: function () {
			this.isAddingBatch = true
			let that = this
			setTimeout(function () {
				that.$refs.batchIPRange.focus()
			}, 100)
		},
		addBatchCIDR: function () {
			this.isAddingBatch = true
			let that = this
			setTimeout(function () {
				that.$refs.batchIPCIDR.focus()
			}, 100)
		},
		cancelBatchIPRange: function () {
			this.isAddingBatch = false
			this.batchIPRange = ""
			this.isReverse = false
		},
		cancelBatchIPCIDR: function () {
			this.isAddingBatch = false
			this.batchIPCIDR = ""
			this.isReverse = false
		},
		confirmBatchIPRange: function () {
			let that = this
			let rangesText = this.batchIPRange
			if (rangesText.length == 0) {
				teaweb.warn("请填写要加入的IP范围", function () {
					that.$refs.batchIPRange.focus()
				})
				return
			}
			let validRanges = []
			let invalidLine = ""
			rangesText.split("\n").forEach(function (line) {
				line = line.trim()
				if (line.length == 0) {
					return
				}
				line = line.replace(",", ",")
				let pieces = line.split(",")
				if (pieces.length != 2) {
					invalidLine = line
					return
				}
				let ipFrom = pieces[0].trim()
				let ipTo = pieces[1].trim()
				if (!that.validateIP(ipFrom) || !that.validateIP(ipTo)) {
					invalidLine = line
					return
				}
				validRanges.push({
					type: "ipRange",
					params: {
						ipFrom: ipFrom,
						ipTo: ipTo,
						isReverse: that.isReverse
					}
				})
			})
			if (invalidLine.length > 0) {
				teaweb.warn("'" + invalidLine + "'格式错误", function () {
					that.$refs.batchIPRange.focus()
				})
				return
			}
			validRanges.forEach(function (v) {
				that.ranges.push(v)
			})
			this.cancelBatchIPRange()
		},
		confirmBatchIPCIDR: function () {
			let that = this
			let rangesText = this.batchIPCIDR
			if (rangesText.length == 0) {
				teaweb.warn("请填写要加入的CIDR", function () {
					that.$refs.batchIPCIDR.focus()
				})
				return
			}
			let validRanges = []
			let invalidLine = ""
			rangesText.split("\n").forEach(function (line) {
				let cidr = line.trim()
				if (cidr.length == 0) {
					return
				}
				if (!that.validateCIDR(cidr)) {
					invalidLine = line
					return
				}
				validRanges.push({
					type: "cidr",
					params: {
						cidr: cidr,
						isReverse: that.isReverse
					}
				})
			})
			if (invalidLine.length > 0) {
				teaweb.warn("'" + invalidLine + "'格式错误", function () {
					that.$refs.batchIPCIDR.focus()
				})
				return
			}
			validRanges.forEach(function (v) {
				that.ranges.push(v)
			})
			this.cancelBatchIPCIDR()
		},
		selectRegionCountry: function (country) {
			if (country == null) {
				return
			}
			this.regions.push({
				type: "country",
				id: country.id,
				name: country.name
			})
			this.$refs.regionCountryComboBox.clear()
		},
		selectRegionProvince: function (province) {
			if (province == null) {
				return
			}
			this.regions.push({
				type: "province",
				id: province.id,
				name: province.name
			})
			this.$refs.regionProvinceComboBox.clear()
		},
		selectRegionCity: function (city) {
			if (city == null) {
				return
			}
			this.regions.push({
				type: "city",
				id: city.id,
				name: city.name
			})
			this.$refs.regionCityComboBox.clear()
		},
		selectRegionProvider: function (provider) {
			if (provider == null) {
				return
			}
			this.regions.push({
				type: "provider",
				id: provider.id,
				name: provider.name
			})
			this.$refs.regionProviderComboBox.clear()
		},
		removeRegion: function (index) {
			this.regions.$remove(index)
		},
		validateIP: function (ip) {
			if (ip.length == 0) {
				return
			}
			// IPv6
			if (ip.indexOf(":") >= 0) {
				let pieces = ip.split(":")
				if (pieces.length > 8) {
					return false
				}
				let isOk = true
				pieces.forEach(function (piece) {
					if (!/^[\da-fA-F]{0,4}$/.test(piece)) {
						isOk = false
					}
				})
				return isOk
			}
			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
		},
		validateCIDR: function (cidr) {
			let pieces = cidr.split("/")
			if (pieces.length != 2) {
				return false
			}
			let ip = pieces[0]
			if (!this.validateIP(ip)) {
				return false
			}
			let mask = pieces[1]
			if (!/^\d{1,3}$/.test(mask)) {
				return false
			}
			mask = parseInt(mask, 10)
			if (cidr.indexOf(":") >= 0) { // IPv6
				return mask <= 128
			}
			return mask <= 32
		},
		updateRangeType: function (rangeType) {
			this.rangeType = rangeType
		}
	},
	template: `
	 
	
		
			[排除] 
			IP范围: 
			CIDR: 
			 
			{{range.params.ipFrom}} - {{range.params.ipTo}} 
			{{range.params.cidr}} 
			
				
					国家/地区 
					省份 
					城市 
					ISP 
					:{{region.name}}
					
						 
						或 
						且 
						 
					 
				 
			 
			    
		 
		
	 
	
	
	
	
	
	
	
	
	
		
		
			
				
					已添加 
					
						
							
								国家/地区 
								省份 
								城市 
								ISP 
								:{{region.name}}  
							 
							
								 
								或 
								且 
								 
							 
						 
					 
				 
				
					添加新国家/地区 省份 城市 ISP 
					
					 * 
					
					 	
						
							 
						
			
						
						
							 
						
			
						
						
							 
						
			
						
						
							 
						
						
						
							添加国家/地区   
							添加省份   
							添加城市   
							ISP   
						
					 	
				 
				
					区域之间关系 
					
						
							或 
							且 
						 
						
						
					 
				 
				
					排除 
					
						 
						
					 
				 
			
			确定   
				 
		 
		
			添加区域   
		
	
	 
 `
})
Vue.component("ns-record-health-check-config-box", {
	props:["value", "v-parent-config"],
	data: function () {
		let config = this.value
		if (config == null) {
			config = {
				isOn: false,
				port: 0,
				timeoutSeconds: 0,
				countUp: 0,
				countDown: 0
			}
		}
		let parentConfig = this.vParentConfig
		return {
			config: config,
			portString: config.port.toString(),
			timeoutSecondsString: config.timeoutSeconds.toString(),
			countUpString: config.countUp.toString(),
			countDownString: config.countDown.toString(),
			portIsEditing: config.port > 0,
			timeoutSecondsIsEditing: config.timeoutSeconds > 0,
			countUpIsEditing: config.countUp > 0,
			countDownIsEditing: config.countDown > 0,
			parentConfig: parentConfig
		}
	},
	watch: {
		portString: function (value) {
			let port = parseInt(value.toString())
			if (isNaN(port) || port > 65535 || port < 1) {
				this.config.port = 0
			} else {
				this.config.port = port
			}
		},
		timeoutSecondsString: function (value) {
			let timeoutSeconds = parseInt(value.toString())
			if (isNaN(timeoutSeconds) || timeoutSeconds > 1000 || timeoutSeconds < 1) {
				this.config.timeoutSeconds = 0
			} else {
				this.config.timeoutSeconds = timeoutSeconds
			}
		},
		countUpString: function (value) {
			let countUp = parseInt(value.toString())
			if (isNaN(countUp) || countUp > 1000 || countUp < 1) {
				this.config.countUp = 0
			} else {
				this.config.countUp = countUp
			}
		},
		countDownString: function (value) {
			let countDown = parseInt(value.toString())
			if (isNaN(countDown) || countDown > 1000 || countDown < 1) {
				this.config.countDown = 0
			} else {
				this.config.countDown = countDown
			}
		}
	},
	template: `
	 
	
		
			
				启用当前记录健康检查 
				
					 
				 
			 
		 
		
			
				检测端口 
				
					
						默认{{parentConfig.port}}
						  [修改] 
					 
					
				 
			 
			
				超时时间 
				
					
						默认{{parentConfig.timeoutSeconds}}秒
						  [修改] 
					 
					
				 
			 
			
				默认连续上线次数 
				
					
						默认{{parentConfig.countUp}}次
						  [修改] 
					 
					
				 
			 
			
				默认连续下线次数 
				
					
						默认{{parentConfig.countDown}}次
						  [修改] 
					 
					
				 
			 
		 
	
	
 `
})
Vue.component("ns-create-records-table", {
	props: ["v-types"],
	data: function () {
		let types = this.vTypes
		if (types == null) {
			types = []
		}
		return {
			types: types,
			records: [
				{
					name: "",
					type: "A",
					value: "",
					routeCodes: [],
					ttl: 600,
					index: 0
				}
			],
			lastIndex: 0,
			isAddingRoutes: false // 是否正在添加线路
		}
	},
	methods: {
		add: function () {
			this.records.push({
				name: "",
				type: "A",
				value: "",
				routeCodes: [],
				ttl: 600,
				index: ++this.lastIndex
			})
			let that = this
			setTimeout(function () {
				that.$refs.nameInputs.$last().focus()
			}, 100)
		},
		remove: function (index) {
			this.records.$remove(index)
		},
		addRoutes: function () {
			this.isAddingRoutes = true
		},
		cancelRoutes: function () {
			let that = this
			setTimeout(function () {
				that.isAddingRoutes = false
			}, 1000)
		},
		changeRoutes: function (record, routes) {
			if (routes == null) {
				record.routeCodes = []
			} else {
				record.routeCodes = routes.map(function (route) {
					return route.code
				})
			}
		}
	},
	template: ``,
})
// 选择单一线路
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", {
	props: ["v-user-id"],
	data: function () {
		return {}
	},
	methods: {
		change: function (userId) {
			this.$emit("change", userId)
		}
	},
	template: `
	 
`
})
Vue.component("ns-access-log-box", {
	props: ["v-access-log", "v-keyword"],
	data: function () {
		let accessLog = this.vAccessLog
		let isFailure = false
		if (accessLog.isRecursive) {
			if (accessLog.recordValue == null || accessLog.recordValue.length == 0) {
				isFailure = true
			}
		} else {
			if (accessLog.recordType == "SOA" || accessLog.recordType == "NS") {
				if (accessLog.recordValue == null || accessLog.recordValue.length == 0) {
					isFailure = true
				}
			}
			// 没有找到记录的不需要高亮显示,防止管理员看到红色就心理恐慌
		}
		return {
			accessLog: accessLog,
			isFailure: isFailure
		}
	},
	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: `
	
		[选择集群] 
		{{cluster.name}} 
	 
`
})
Vue.component("ns-cluster-combo-box", {
	props: ["v-cluster-id", "name"],
	data: function () {
		let that = this
		Tea.action("/ns/clusters/options")
			.post()
			.success(function (resp) {
				that.clusters = resp.data.clusters
			})
		let inputName = "clusterId"
		if (this.name != null && this.name.length > 0) {
			inputName = this.name
		}
		return {
			clusters: [],
			inputName: inputName
		}
	},
	methods: {
		change: function (item) {
			if (item == null) {
				this.$emit("change", 0)
			} else {
				this.$emit("change", item.value)
			}
		}
	},
	template: `
	 
`
})
Vue.component("plan-user-selector", {
	props: ["v-user-id"],
	data: function () {
		return {}
	},
	methods: {
		change: function (userId) {
			this.$emit("change", userId)
		}
	},
	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.totalPrice}}元 {{range.pricePerMB}}元/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: ``
})
Vue.component("plan-price-traffic-config-box", {
	props: ["v-plan-price-traffic-config"],
	data: function () {
		let config = this.vPlanPriceTrafficConfig
		if (config == null) {
			config = {
				base: 0,
				ranges: [],
				supportRegions: false
			}
		}
		if (config.ranges == null) {
			config.ranges = []
		}
		return {
			config: config,
			priceBase: config.base,
			isEditing: false
		}
	},
	watch: {
		priceBase: function (v) {
			let f = parseFloat(v)
			if (isNaN(f) || f < 0) {
				this.config.base = 0
			} else {
				this.config.base = f
			}
		}
	},
	methods: {
		edit: function () {
			this.isEditing = !this.isEditing
		}
	},
	template: `
	 
	
		基础流量价格:
{{config.base}}元/GB 没有设置    |   
		阶梯价格:
{{config.ranges.length}}段 没有设置     |  支持区域流量计费 
				
	 
	
 `
})
Vue.component("plan-bandwidth-ranges", {
	props: ["value"],
	data: function () {
		let ranges = this.value
		if (ranges == null) {
			ranges = []
		}
		return {
			ranges: ranges,
			isAdding: false,
			minMB: "",
			minMBUnit: "mb",
			maxMB: "",
			maxMBUnit: "mb",
			pricePerMB: "",
			totalPrice: "",
			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 () {
			if (this.addingRange.minMB < 0) {
				teaweb.warn("带宽下限需要大于0")
				return
			}
			if (this.addingRange.maxMB < 0) {
				teaweb.warn("带宽上限需要大于0")
				return
			}
			if (this.addingRange.pricePerMB <= 0) {
				teaweb.warn("请设置单位价格或者总价格")
				return
			}
			this.isAdding = false
			this.minMB = ""
			this.maxMB = ""
			this.pricePerMB = ""
			this.totalPrice = ""
			this.ranges.push(this.addingRange)
			this.ranges.$sort(function (v1, v2) {
				if (v1.minMB < v2.minMB) {
					return -1
				}
				if (v1.minMB == v2.minMB) {
					if (v2.maxMB == 0 || v1.maxMB < v2.maxMB) {
						return -1
					}
					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)
		},
		formatMB: function (mb) {
			return teaweb.formatBits(mb * 1024 * 1024)
		},
		changeMinMB: function (v) {
			let minMB = parseFloat(v.toString())
			if (isNaN(minMB) || minMB < 0) {
				minMB = 0
			}
			switch (this.minMBUnit) {
				case "gb":
					minMB *= 1024
					break
				case "tb":
					minMB *= 1024 * 1024
					break
			}
			this.addingRange.minMB = minMB
		},
		changeMaxMB: function (v) {
			let maxMB = parseFloat(v.toString())
			if (isNaN(maxMB) || maxMB < 0) {
				maxMB = 0
			}
			switch (this.maxMBUnit) {
				case "gb":
					maxMB *= 1024
					break
				case "tb":
					maxMB *= 1024 * 1024
					break
			}
			this.addingRange.maxMB = maxMB
		}
	},
	watch: {
		minMB: function (v) {
			this.changeMinMB(v)
		},
		minMBUnit: function () {
			this.changeMinMB(this.minMB)
		},
		maxMB: function (v) {
			this.changeMaxMB(v)
		},
		maxMBUnit: function () {
			this.changeMaxMB(this.maxMB)
		},
		pricePerMB: function (v) {
			let pricePerMB = parseFloat(v.toString())
			if (isNaN(pricePerMB) || pricePerMB < 0) {
				pricePerMB = 0
			}
			this.addingRange.pricePerMB = pricePerMB
		},
		totalPrice: function (v) {
			let totalPrice = parseFloat(v.toString())
			if (isNaN(totalPrice) || totalPrice < 0) {
				totalPrice = 0
			}
			this.addingRange.totalPrice = totalPrice
		}
	},
	template: `
	
	
		
			{{formatMB(range.minMB)}} - 
{{formatMB(range.maxMB)}} ∞     价格:
{{range.totalPrice}}元 {{range.pricePerMB}}元/Mbps 
			   
		 
		
	 
	
	
	
	
	
	
		+ 
	
 `
})
Vue.component("plan-price-bandwidth-config-box", {
	props: ["v-plan-price-bandwidth-config"],
	data: function () {
		let config = this.vPlanPriceBandwidthConfig
		if (config == null) {
			config = {
				percentile: 95,
				base: 0,
				ranges: [],
				supportRegions: false
			}
		}
		if (config.ranges == null) {
			config.ranges = []
		}
		return {
			config: config,
			bandwidthPercentile: config.percentile,
			priceBase: config.base,
			isEditing: false
		}
	},
	watch: {
		priceBase: function (v) {
			let f = parseFloat(v)
			if (isNaN(f) || f < 0) {
				this.config.base = 0
			} else {
				this.config.base = f
			}
		},
		bandwidthPercentile: function (v) {
			let i = parseInt(v)
			if (isNaN(i) || i < 0) {
				this.config.percentile = 0
			} else {
				this.config.percentile = i
			}
		}
	},
	methods: {
		edit: function () {
			this.isEditing = !this.isEditing
		}
	},
	template: `
 
	带宽百分位:
{{config.percentile}}th 没有设置    |  
	基础带宽价格:
{{config.base}}元/Mbps 没有设置    |   
	阶梯价格:
{{config.ranges.length}}段 没有设置     |  支持区域带宽计费 
	  |  使用平均带宽算法 
			
 
	
 `
})
Vue.component("plan-traffic-ranges", {
	props: ["value"],
	data: function () {
		let ranges = this.value
		if (ranges == null) {
			ranges = []
		}
		return {
			ranges: ranges,
			isAdding: false,
			minGB: "",
			minGBUnit: "gb",
			maxGB: "",
			maxGBUnit: "gb",
			pricePerGB: "",
			totalPrice: "",
			addingRange: {
				minGB: 0,
				maxGB: 0,
				pricePerGB: 0,
				totalPrice: 0
			}
		}
	},
	methods: {
		add: function () {
			this.isAdding = !this.isAdding
			let that = this
			setTimeout(function () {
				that.$refs.minGB.focus()
			})
		},
		cancelAdding: function () {
			this.isAdding = false
		},
		confirm: function () {
			if (this.addingRange.minGB < 0) {
				teaweb.warn("流量下限需要大于0")
				return
			}
			if (this.addingRange.maxGB < 0) {
				teaweb.warn("流量上限需要大于0")
				return
			}
			if (this.addingRange.pricePerGB <= 0 && this.addingRange.totalPrice <= 0) {
				teaweb.warn("请设置单位价格或者总价格")
				return;
			}
			this.isAdding = false
			this.minGB = ""
			this.maxGB = ""
			this.pricePerGB = ""
			this.totalPrice = ""
			this.ranges.push(this.addingRange)
			this.ranges.$sort(function (v1, v2) {
				if (v1.minGB < v2.minGB) {
					return -1
				}
				if (v1.minGB == v2.minGB) {
					if (v2.maxGB == 0 || v1.maxGB < v2.maxGB) {
						return -1
					}
					return 0
				}
				return 1
			})
			this.change()
			this.addingRange = {
				minGB: 0,
				maxGB: 0,
				pricePerGB: 0,
				totalPrice: 0
			}
		},
		remove: function (index) {
			this.ranges.$remove(index)
			this.change()
		},
		change: function () {
			this.$emit("change", this.ranges)
		},
		formatGB: function (gb) {
			return teaweb.formatBytes(gb * 1024 * 1024 * 1024)
		},
		changeMinGB: function (v) {
			let minGB = parseFloat(v.toString())
			if (isNaN(minGB) || minGB < 0) {
				minGB = 0
			}
			switch (this.minGBUnit) {
				case "tb":
					minGB *= 1024
					break
				case "pb":
					minGB *= 1024 * 1024
					break
				case "eb":
					minGB *= 1024 * 1024 * 1024
					break
			}
			this.addingRange.minGB = minGB
		},
		changeMaxGB: function (v) {
			let maxGB = parseFloat(v.toString())
			if (isNaN(maxGB) || maxGB < 0) {
				maxGB = 0
			}
			switch (this.maxGBUnit) {
				case "tb":
					maxGB *= 1024
					break
				case "pb":
					maxGB *= 1024 * 1024
					break
				case "eb":
					maxGB *= 1024 * 1024 * 1024
					break
			}
			this.addingRange.maxGB = maxGB
		}
	},
	watch: {
		minGB: function (v) {
			this.changeMinGB(v)
		},
		minGBUnit: function (v) {
			this.changeMinGB(this.minGB)
		},
		maxGB: function (v) {
			this.changeMaxGB(v)
		},
		maxGBUnit: function (v) {
			this.changeMaxGB(this.maxGB)
		},
		pricePerGB: function (v) {
			let pricePerGB = parseFloat(v.toString())
			if (isNaN(pricePerGB) || pricePerGB < 0) {
				pricePerGB = 0
			}
			this.addingRange.pricePerGB = pricePerGB
		},
		totalPrice: function (v) {
			let totalPrice = parseFloat(v.toString())
			if (isNaN(totalPrice) || totalPrice < 0) {
				totalPrice = 0
			}
			this.addingRange.totalPrice = totalPrice
		}
	},
	template: `
	
	
		
			{{formatGB(range.minGB)}} - 
{{formatGB(range.maxGB)}} ∞     价格:
{{range.totalPrice}}元 {{range.pricePerGB}}元/GB 
			   
		 
		
	 
	
	
	
	
	
	
		+ 
	
 `
})
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: []
			}
		}
		if (conds.groups == null) {
			conds.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",
		"v-support-http3"
	],
	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,
				http3Enabled: false,
				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
		let hstsMaxAgeString = "31536000"
		if (hsts == null) {
			hsts = {
				isOn: false,
				maxAge: 31536000,
				includeSubDomains: false,
				preload: false,
				domains: []
			}
		}
		if (hsts.maxAge != null) {
			hstsMaxAgeString = hsts.maxAge.toString()
		}
		return {
			policy: policy,
			// hsts
			hsts: hsts,
			hstsOptionsVisible: false,
			hstsDomainAdding: false,
			hstsMaxAgeString: hstsMaxAgeString,
			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())
				})
			}
			let serverId = this.vServerId
			if (serverId == null) {
				serverId = 0
			}
			teaweb.popup("/servers/certs/selectPopup?selectedCertIds=" + selectedCertIds + "&serverId=" + serverId, {
				width: "50em",
				height: "30em",
				callback: function (resp) {
					if (resp.data.cert != null && resp.data.certRef != null) {
						that.policy.certRefs.push(resp.data.certRef)
						that.policy.certs.push(resp.data.cert)
					}
					if (resp.data.certs != null && resp.data.certRefs != null) {
						that.policy.certRefs.$pushAll(resp.data.certRefs)
						that.policy.certs.$pushAll(resp.data.certs)
					}
					that.$forceUpdate()
				}
			})
		},
		// 上传证书
		uploadCert: function () {
			let that = this
			let serverId = this.vServerId
			if (typeof serverId != "number" && typeof serverId != "string") {
				serverId = 0
			}
			teaweb.popup("/servers/certs/uploadPopup?serverId=" + serverId, {
				height: "30em",
				callback: function (resp) {
					teaweb.success("上传成功", function () {
						that.policy.certRefs.push(resp.data.certRef)
						that.policy.certs.push(resp.data.cert)
					})
				}
			})
		},
		// 批量上传
		uploadBatch: function () {
			let that = this
			let serverId = this.vServerId
			if (typeof serverId != "number" && typeof serverId != "string") {
				serverId = 0
			}
			teaweb.popup("/servers/certs/uploadBatchPopup?serverId=" + serverId, {
				callback: function (resp) {
					if (resp.data.cert != null) {
						that.policy.certRefs.push(resp.data.certRef)
						that.policy.certs.push(resp.data.cert)
					}
					if (resp.data.certs != null) {
						that.policy.certRefs.$pushAll(resp.data.certRefs)
						that.policy.certs.$pushAll(resp.data.certs)
					}
					that.$forceUpdate()
				}
			})
		},
		// 申请证书
		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 = parseInt(this.hstsMaxAgeString)
			if (isNaN(v) || v < 0) {
				this.hsts.maxAge = 0
				this.hsts.days = "-"
				return
			}
			this.hsts.maxAge = v
			this.hsts.days = v / 86400
			if (this.hsts.days == 0) {
				this.hsts.days = "-"
			}
		},
		// 设置HSTS有效期
		setHSTSMaxAge: function (maxAge) {
			this.hstsMaxAgeString = maxAge.toString()
			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) {
					if (resp.data.cert != null && resp.data.certRef != null) {
						that.policy.clientCARefs.push(resp.data.certRef)
						that.policy.clientCACerts.push(resp.data.cert)
					}
					if (resp.data.certs != null && resp.data.certRefs != null) {
						that.policy.clientCARefs.$pushAll(resp.data.certRefs)
						that.policy.clientCACerts.$pushAll(resp.data.certs)
					}
					that.$forceUpdate()
				}
			})
		},
		// 上传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: ``
})
// 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: ``
})
// 显示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) + " ")
		},
		operatorName: function (operatorCode) {
			var operatorName = operatorCode
			if (typeof (window.WAF_RULE_OPERATORS) != null) {
				window.WAF_RULE_OPERATORS.forEach(function (v) {
					if (v.code == operatorCode) {
						operatorName = v.name
					}
				})
			}
			return operatorName
		},
		isEmptyString: function (v) {
			return typeof v == "string" && v.length == 0
		}
	},
	template: `
	
		{{rule.name}}[{{rule.param}}] 
		
		
			{{rule.checkpointOptions.period}}秒内请求数
		 
		
		
			允许{{rule.checkpointOptions.allowDomains}} 
			禁止{{rule.checkpointOptions.denyDomains}} 
		 
		
			 | {{paramFilter.code}}  
		{{operatorName(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: `
	 
	
	
	
		
			
				
					缓存条件 
					缓存时间 
				 
				
					
						 
							 
						
						
						忽略URI参数 
						
							{{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(", ")}} 
						分片缓存 
						Range回源 
						If-None-Match 
						If-Modified-Since 
						支持异步 
					 
					
						{{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", // 描述文字
		"v-domains", // 搜索的域名列表或者函数
		"v-user-id" // 用户ID
	],
	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 = "54em"
			let height = "32em"
			let viewSize = this.vViewSize
			if (viewSize == null) {
				viewSize = "normal"
			}
			if (viewSize == "mini") {
				width = "35em"
				height = "20em"
			}
			let searchingDomains = []
			if (this.vDomains != null) {
				if (typeof this.vDomains == "function") {
					let resultDomains = this.vDomains()
					if (resultDomains != null && typeof resultDomains == "object" && (resultDomains instanceof Array)) {
						searchingDomains = resultDomains
					}
				} else if (typeof this.vDomains == "object" && (this.vDomains instanceof Array)) {
					searchingDomains = this.vDomains
				}
				if (searchingDomains.length > 10000) {
					searchingDomains = searchingDomains.slice(0, 10000)
				}
			}
			let selectedCertIds = this.certs.map(function (cert) {
				return cert.id
			})
			let userId = this.vUserId
			if (userId == null) {
				userId = 0
			}
			teaweb.popup("/servers/certs/selectPopup?viewSize=" + viewSize + "&searchingDomains=" + window.encodeURIComponent(searchingDomains.join(",")) + "&selectedCertIds=" + selectedCertIds.join(",") + "&userId=" + userId, {
				width: width,
				height: height,
				callback: function (resp) {
					if (resp.data.cert != null) {
						that.certs.push(resp.data.cert)
					}
					if (resp.data.certs != null) {
						that.certs.$pushAll(resp.data.certs)
					}
					that.$forceUpdate()
				}
			})
		},
		// 上传证书
		uploadCert: function () {
			let that = this
			let userId = this.vUserId
			if (typeof userId != "number" && typeof userId != "string") {
				userId = 0
			}
			teaweb.popup("/servers/certs/uploadPopup?userId=" + userId, {
				height: "28em",
				callback: function (resp) {
					teaweb.success("上传成功", function () {
						if (resp.data.cert != null) {
							that.certs.push(resp.data.cert)
						}
						if (resp.data.certs != null) {
							that.certs.$pushAll(resp.data.certs)
						}
						that.$forceUpdate()
					})
				}
			})
		},
		// 批量上传
		uploadBatch: function () {
			let that = this
			let userId = this.vUserId
			if (typeof userId != "number" && typeof userId != "string") {
				userId = 0
			}
			teaweb.popup("/servers/certs/uploadBatchPopup?userId=" + userId, {
				callback: function (resp) {
					if (resp.data.cert != null) {
						that.certs.push(resp.data.cert)
					}
					if (resp.data.certs != null) {
						that.certs.$pushAll(resp.data.certs)
					}
					that.$forceUpdate()
				}
			})
		},
		// 格式化时间
		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)}}   
 
		 
		
	 
	
		选择或上传证书后HTTPS TLS 服务才能生效。 
		{{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: `
	 
	
	
		[创建] 
	 
	
	
	
		
			
				
					 
					跳转前 
					 
					跳转后 
					HTTP状态码 
					状态 
					操作 
				 
			 
			
				
					   
					
						
							{{redirect.beforeURL}}
							
								URL跳转  
								匹配前缀 
								正则匹配 
								精准匹配 
							
						 
						
							所有域名 
							
								{{redirect.domainsBefore[0]}} 
								{{redirect.domainsBefore[0]}}等{{redirect.domainsBefore.length}}个域名 
							 
							
								域名跳转  
								{{redirect.domainAfterScheme}} 
								忽略端口 
							
						 
						
							所有端口 
							
								{{redirect.portsBefore.join(", ")}} 
								{{redirect.portsBefore.slice(0, 5).join(", ")}}等{{redirect.portsBefore.length}}个端口 
							 
							
								端口跳转  
								{{redirect.portAfterScheme}} 
							
						 
						
						
							匹配条件 
						
					 
					-> 
					
						{{redirect.afterURL}} 
						{{redirect.domainAfter}} 
						{{redirect.portAfter}} 
					 
					
						{{redirect.status}} 
						默认 
					 
					 
					
						修改   
						删除 	
					 
				 
			 
		
		
	 
	
 `
})
// 单个缓存条件设置
Vue.component("http-cache-ref-box", {
	props: ["v-cache-ref", "v-is-reverse"],
	mounted: function () {
		this.$refs.variablesDescriber.update(this.ref.key)
		if (this.ref.simpleCond != null) {
			this.condType = this.ref.simpleCond.type
			this.changeCondType(this.ref.simpleCond.type, true)
			this.condCategory = "simple"
		} else if (this.ref.conds != null && this.ref.conds.groups != null) {
			this.condCategory = "complex"
		}
		this.changeCondCategory(this.condCategory)
	},
	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, // 复杂条件
				simpleCond: null, // 简单条件
				allowChunkedEncoding: true,
				allowPartialContent: true,
				forcePartialContent: false,
				enableIfNoneMatch: false,
				enableIfModifiedSince: false,
				enableReadingOriginAsync: 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"}
		}
		let condType = "url-extension"
		let condComponent = window.REQUEST_COND_COMPONENTS.$find(function (k, v) {
			return v.type == "url-extension"
		})
		return {
			ref: ref,
			keyIgnoreArgs: typeof ref.key == "string" && ref.key.indexOf("${args}") < 0,
			moreOptionsVisible: false,
			condCategory: "simple", // 条件分类:simple|complex
			condType: condType,
			condComponent: condComponent,
			condIsCaseInsensitive: (ref.simpleCond != null) ? ref.simpleCond.isCaseInsensitive : true,
			components: window.REQUEST_COND_COMPONENTS
		}
	},
	watch: {
		keyIgnoreArgs: function (b) {
			if (typeof this.ref.key != "string") {
				return
			}
			if (b) {
				this.ref.key = this.ref.key.replace("${isArgs}${args}", "")
				return;
			}
			if (this.ref.key.indexOf("${isArgs}") < 0) {
				this.ref.key = this.ref.key + "${isArgs}"
			}
			if (this.ref.key.indexOf("${args}") < 0) {
				this.ref.key = this.ref.key + "${args}"
			}
		}
	},
	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
			this.ref.simpleCond = null
		},
		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
		},
		// 切换条件类型
		changeCondCategory: function (condCategory) {
			this.condCategory = condCategory
			// resize window
			let dialog = window.parent.document.querySelector("*[role='dialog']")
			if (dialog == null) {
				return
			}
			switch (condCategory) {
				case "simple":
					dialog.style.width = "45em"
					break
				case "complex":
					let width = window.parent.innerWidth
					if (width > 1024) {
						width = 1024
					}
					dialog.style.width = width + "px"
					break
			}
		},
		changeCondType: function (condType, isInit) {
			if (!isInit && this.ref.simpleCond != null) {
				this.ref.simpleCond.value = null
			}
			let def = this.components.$find(function (k, component) {
				return component.type == condType
			})
			if (def != null) {
				this.condComponent = def
			}
		}
	},
	template: `
	
		条件类型 * 
		
			
				文件扩展名 
				首页 
				全站 
				URL前缀 
				URL完整路径 
				URL通配符 
				URL正则匹配 
				参数匹配 
			 
			
		 
	 
	
		{{condComponent.paramsTitle}} * 
		
			 
			
		 
	 
	
		不区分大小写 
		
			
				 
				 
			
			
		 
	 
	
		匹配条件分组 * 
		
			 
			
		 
	 
	
		缓存有效期 * 
		
			 
		 
	 
	
		忽略URI参数 
		
			 
			
		 
	 
	
		缓存Key * 
		
			 
			
		 
	 
	
		 
	 
	
		请求方法限制 
		
			 
			
		 
	 
	
		客户端过期时间(Expires)  
		
			 		
		 
	 
	
		可缓存的最大内容尺寸 
		
			 
			
		 
	 
	
		可缓存的最小内容尺寸 
		
			 
			
		 
	 
	
		支持缓存分片内容 
		
			 
			
		 
	 
	
		强制返回分片内容 
		
			 
			
		 
	 
	
		强制Range回源 
		
			 
			
		 
	 
	
		状态码列表 
		
			 
			
		 
	 
	
		跳过的Cache-Control值 
		
			 
			
		 
	 
	
		跳过Set-Cookie 
		
			
				 
				 
			
			
		 
	 
	
		支持请求no-cache刷新 
		
			
				 
				 
			
			
		 
	 	
	
		允许If-None-Match回源 
		
			 
			
		 
	 
	
		允许If-Modified-Since回源 
		
			 
			
		 
	 
	
		允许异步读取源站 
		
			 
			
		 
	 
	
		支持分段内容 
		
			 
			
		 
	 
	
		 
	 
 `
})
// 请求限制
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-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: []
			}
		}
		if (conds.groups == null) {
			conds.groups = []
		}
		let that = this
		conds.groups.forEach(function (group) {
			group.conds.forEach(function (cond) {
				cond.typeName = that.typeName(cond)
			})
		})
		return {
			initConds: conds
		}
	},
	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
		},
		updateConds: function (conds) {
			this.initConds = conds
		},
		notifyChange: function () {
			let that = this
			if (this.initConds.groups != null) {
				this.initConds.groups.forEach(function (group) {
					group.conds.forEach(function (cond) {
						cond.typeName = that.typeName(cond)
					})
				})
				this.$forceUpdate()
			}
		}
	},
	template: `
		
			
				
					
						{{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,
				ignoreGlobalRules: false
			}
		}
		return {
			firewall: firewall,
			moreOptionsVisible: false,
			execGlobalRules: !firewall.ignoreGlobalRules
		}
	},
	watch: {
		execGlobalRules: function (v) {
			this.firewall.ignoreGlobalRules = !v
		}
	},
	methods: {
		changeOptionsVisible: function (v) {
			this.moreOptionsVisible = v
		}
	},
	template: `
	 
	
	
		
			全局WAF策略 
			
				{{vFirewallPolicy.name}}   [{{vFirewallPolicy.modeInfo.name}}]     
					
				
				当前集群没有设置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: teaweb.DefaultChartColor
						},
						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: teaweb.DefaultChartColor
						},
						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: teaweb.DefaultChartColor
						},
						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 += "" + v.keys[0] + " " + value + " "
				let percent = 0
				if (v.total > 0) {
					percent = Math.round((v.value * 100 / v.total) * 100) / 100
				}
				table += "" + percent + "% "
				table += " "
			})
			table += `
`
			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 = []
		}
		var maxBytes = null
		if (this.vCachePolicy != null && this.vCachePolicy.maxBytes != null) {
			maxBytes = this.vCachePolicy.maxBytes
		}
		return {
			cacheConfig: cacheConfig,
			moreOptionsVisible: false,
			enablePolicyRefs: !cacheConfig.disablePolicyRefs,
			maxBytes: maxBytes
		}
	},
	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}}  
					
				
				当前集群没有设置缓存策略,当前配置无法生效。 
			 
		 
	
	
	
	
	
		
过时缓存策略 
		 
	
	
	
	
 `
})
// 通用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: ``
})
// CC
Vue.component("http-firewall-checkpoint-cc", {
	props: ["v-checkpoint"],
	data: function () {
		let keys = []
		let period = 60
		let threshold = 1000
		let ignoreCommonFiles = false
		let enableFingerprint = true
		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
		}
		if (options.ignoreCommonFiles != null && typeof (options.ignoreCommonFiles) == "boolean") {
			ignoreCommonFiles = options.ignoreCommonFiles
		}
		if (options.enableFingerprint != null && typeof (options.enableFingerprint) == "boolean") {
			enableFingerprint = options.enableFingerprint
		}
		let that = this
		setTimeout(function () {
			that.change()
		}, 100)
		return {
			keys: keys,
			period: period,
			threshold: threshold,
			ignoreCommonFiles: ignoreCommonFiles,
			enableFingerprint: enableFingerprint,
			options: {},
			value: threshold
		}
	},
	watch: {
		period: function () {
			this.change()
		},
		threshold: function () {
			this.change()
		},
		ignoreCommonFiles: function () {
			this.change()
		},
		enableFingerprint: 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
			let ignoreCommonFiles = this.ignoreCommonFiles
			if (typeof ignoreCommonFiles != "boolean") {
				ignoreCommonFiles = false
			}
			let enableFingerprint = this.enableFingerprint
			if (typeof enableFingerprint != "boolean") {
				enableFingerprint = true
			}
			this.vCheckpoint.options = [
				{
					code: "keys",
					value: this.keys
				},
				{
					code: "period",
					value: period,
				},
				{
					code: "threshold",
					value: threshold
				},
				{
					code: "ignoreCommonFiles",
					value: ignoreCommonFiles
				},
				{
					code: "enableFingerprint",
					value: enableFingerprint
				}
			]
		},
		thresholdTooLow: function () {
			let threshold = parseInt(this.threshold.toString())
			if (isNaN(threshold) || threshold <= 0) {
				threshold = 1000
			}
			return threshold > 0 && threshold < 5
		}
	},
	template: ``
})
// 防盗链
Vue.component("http-firewall-checkpoint-referer-block", {
	props: ["v-checkpoint"],
	data: function () {
		let allowEmpty = true
		let allowSameDomain = true
		let allowDomains = []
		let denyDomains = []
		let checkOrigin = true
		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
		}
		if (options.denyDomains != null && typeof (options.denyDomains) == "object") {
			denyDomains = options.denyDomains
		}
		if (typeof options.checkOrigin == "boolean") {
			checkOrigin = options.checkOrigin
		}
		let that = this
		setTimeout(function () {
			that.change()
		}, 100)
		return {
			allowEmpty: allowEmpty,
			allowSameDomain: allowSameDomain,
			allowDomains: allowDomains,
			denyDomains: denyDomains,
			checkOrigin: checkOrigin,
			options: {},
			value: 0
		}
	},
	watch: {
		allowEmpty: function () {
			this.change()
		},
		allowSameDomain: function () {
			this.change()
		},
		checkOrigin: function () {
			this.change()
		}
	},
	methods: {
		changeAllowDomains: function (values) {
			this.allowDomains = values
			this.change()
		},
		changeDenyDomains: function (values) {
			this.denyDomains = values
			this.change()
		},
		change: function () {
			this.vCheckpoint.options = [
				{
					code: "allowEmpty",
					value: this.allowEmpty
				},
				{
					code: "allowSameDomain",
					value: this.allowSameDomain,
				},
				{
					code: "allowDomains",
					value: this.allowDomains
				},
				{
					code: "denyDomains",
					value: this.denyDomains
				},
				{
					code: "checkOrigin",
					value: this.checkOrigin
				}
			]
		}
	},
	template: `
	 
	 
	
		
			来源域名允许为空 
			
				 
				
			 
		 
		
			来源域名允许一致 
			
				 
				
			 
		 
		
			允许的来源域名 
			
				 
				
			 
		 
		
			禁止的来源域名 
			
				 
				
			 
		 
		
			同时检查Origin 
			
				 
				
			 
		 
	
 `
})
Vue.component("http-access-log-partitions-box", {
	props: ["v-partition", "v-day", "v-query"],
	mounted: function () {
		let that = this
		Tea.action("/servers/logs/partitionData")
			.params({
				day: this.vDay
			})
			.success(function (resp) {
				that.partitions = []
				resp.data.partitions.reverse().forEach(function (v) {
					that.partitions.push({
						code: v,
						isDisabled: false,
						hasLogs: false
					})
				})
				if (that.partitions.length > 0) {
					if (that.vPartition == null || that.vPartition < 0) {
						that.selectedPartition = that.partitions[0].code
					}
					if (that.partitions.length > 1) {
						that.checkLogs()
					}
				}
			})
			.post()
	},
	data: function () {
		return {
			partitions: [],
			selectedPartition: this.vPartition,
			checkingPartition: 0
		}
	},
	methods: {
		url: function (p) {
			let u = window.location.toString()
			u = u.replace(/\?partition=-?\d+/, "?")
			u = u.replace(/\?requestId=-?\d+/, "?")
			u = u.replace(/&partition=-?\d+/, "")
			u = u.replace(/&requestId=-?\d+/, "")
			if (u.indexOf("?") > 0) {
				u += "&partition=" + p
			} else {
				u += "?partition=" + p
			}
			return u
		},
		disable: function (partition) {
			this.partitions.forEach(function (p) {
				if (p.code == partition) {
					p.isDisabled = true
				}
			})
		},
		checkLogs: function () {
			let that = this
			let index = this.checkingPartition
			let params = {
				partition: index
			}
			let query = this.vQuery
			if (query == null || query.length == 0) {
				return
			}
			query.split("&").forEach(function (v) {
				let param = v.split("=")
				params[param[0]] = decodeURIComponent(param[1])
			})
			Tea.action("/servers/logs/hasLogs")
				.params(params)
				.post()
				.success(function (response) {
					if (response.data.hasLogs) {
						// 因为是倒序,所以这里需要使用总长度减去index
						that.partitions[that.partitions.length - 1 - index].hasLogs = true
					}
					index++
					if (index >= that.partitions.length) {
						return
					}
					that.checkingPartition = index
					that.checkLogs()
				})
		}
	},
	template: ``
})
Vue.component("http-cache-refs-config-box", {
	props: ["v-cache-refs", "v-cache-config", "v-cache-policy-id", "v-web-id", "v-max-bytes"],
	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 maxBytes = this.vMaxBytes
		let id = 0
		refs.forEach(function (ref) {
			id++
			ref.id = id
			// check max size
			if (ref.maxSize != null && maxBytes != null && teaweb.compareSizeCapacity(ref.maxSize, maxBytes) > 0) {
				ref.overMaxSize = maxBytes
			}
		})
		return {
			refs: refs,
			id: id // 用来对条件进行排序
		}
	},
	methods: {
		addRef: function (isReverse) {
			window.UPDATING_CACHE_REF = null
			let height = window.innerHeight
			if (height > 500) {
				height = 500
			}
			let that = this
			teaweb.popup("/servers/server/settings/cache/createPopup?isReverse=" + (isReverse ? 1 : 0), {
				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 = teaweb.clone(cacheRef)
			let height = window.innerHeight
			if (height > 500) {
				height = 500
			}
			let that = this
			teaweb.popup("/servers/server/settings/cache/createPopup", {
				height: height + "px",
				callback: function (resp) {
					resp.data.cacheRef.id = that.refs[index].id
					Vue.set(that.refs, index, resp.data.cacheRef)
					that.change()
					that.$refs.cacheRef[index].updateConds(resp.data.cacheRef.conds, resp.data.cacheRef.simpleCond)
					that.$refs.cacheRef[index].notifyChange()
				}
			})
		},
		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 () {
			this.$forceUpdate()
			// 自动保存
			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: `
	 
	
	
		
		
			
				
					 
					缓存条件 
					缓存时间 
					操作 
				 
			 	
			
				
					   
					
						 
						 
						
						
						忽略URI参数 
						
						
							{{cacheRef.minSize.count}}{{cacheRef.minSize.unit}}
							- {{cacheRef.maxSize.count}}{{cacheRef.maxSize.unit.toUpperCase()}} 
						 
						0 - {{cacheRef.maxSize.count}}{{cacheRef.maxSize.unit.toUpperCase()}} 
						
						系统限制{{cacheRef.overMaxSize.count}}{{cacheRef.overMaxSize.unit.toUpperCase()}}   
						
						{{cacheRef.methods.join(", ")}} 
						Expires 
						状态码:{{cacheRef.status.map(function(v) {return v.toString()}).join(", ")}} 
						分片缓存 
						Range回源 
						If-None-Match 
						If-Modified-Since 
						支持异步 
					 
					
						{{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, {
				width: "45em",
				height: "27em",
				callback: function (resp) {
					teaweb.success("保存成功", function () {
						window.location.reload()
					})
				}
			})
		},
		createBackupOrigin: function () {
			teaweb.popup("/servers/server/settings/origins/addPopup?originType=backup&" + this.vParams, {
				width: "45em",
				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, {
				width: "45em",
				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 () {
		let hasMatchedDomains = false
		let origins = this.vOrigins
		if (origins != null && origins.length > 0) {
			origins.forEach(function (origin) {
				if (origin.domains != null && origin.domains.length > 0) {
					hasMatchedDomains = true
				}
			})
		}
		return {
			hasMatchedDomains: hasMatchedDomains
		}
	},
	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}} 
					端口跟随 
					HTTP/2 
	
					匹配: {{domain}}  
					匹配: 所有域名  
				
			 
			{{origin.weight}} 
			
				 
			 
			
				修改   
				删除 
			 
		 
	 
`
})
Vue.component("http-cors-header-config-box", {
	props: ["value"],
	data: function () {
		let config = this.value
		if (config == null) {
			config = {
				isOn: false,
				allowMethods: [],
				allowOrigin: "",
				allowCredentials: true,
				exposeHeaders: [],
				maxAge: 0,
				requestHeaders: [],
				requestMethod: "",
				optionsMethodOnly: false
			}
		}
		if (config.allowMethods == null) {
			config.allowMethods = []
		}
		if (config.exposeHeaders == null) {
			config.exposeHeaders = []
		}
		let maxAgeSecondsString = config.maxAge.toString()
		if (maxAgeSecondsString == "0") {
			maxAgeSecondsString = ""
		}
		return {
			config: config,
			maxAgeSecondsString: maxAgeSecondsString,
			moreOptionsVisible: false
		}
	},
	watch: {
		maxAgeSecondsString: function (v) {
			let seconds = parseInt(v)
			if (isNaN(seconds)) {
				seconds = 0
			}
			this.config.maxAge = seconds
		}
	},
	methods: {
		changeMoreOptions: function (visible) {
			this.moreOptionsVisible = visible
		},
		addDefaultAllowMethods: function () {
			let that = this
			let defaultMethods = ["PUT", "GET", "POST", "DELETE", "HEAD", "OPTIONS", "PATCH"]
			defaultMethods.forEach(function (method) {
				if (!that.config.allowMethods.$contains(method)) {
					that.config.allowMethods.push(method)
				}
			})
		}
	},
	template: ``
})
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-optimization-config-box", {
	props: ["v-optimization-config", "v-is-location", "v-is-group"],
	data: function () {
		let config = this.vOptimizationConfig
		return {
			config: config,
			moreOptionsVisible: false
		}
	},
	methods: {
		isOn: function () {
			return ((!this.vIsLocation && !this.vIsGroup) || this.config.isPrior) && this.config.isOn
		}
	},
	template: ``
})
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-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 = teaweb.clone(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 = ""
			}
		},
		allServerNames: function () {
			if (this.serverNames == null) {
				return []
			}
			let result = []
			this.serverNames.forEach(function (serverName) {
				if (serverName.subNames != null && serverName.subNames.length > 0) {
					serverName.subNames.forEach(function (subName) {
						if (subName != null && subName.length > 0) {
							if (!result.$contains(subName)) {
								result.push(subName)
							}
						}
					})
				} else if (serverName.name != null && serverName.name.length > 0) {
					if (!result.$contains(serverName.name)) {
						result.push(serverName.name)
					}
				}
			})
			return result
		}
	},
	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}}个域名 
			   
		 
		
	 
	
 `
})
// UAM模式配置
Vue.component("uam-config-box", {
	props: ["v-uam-config", "v-is-location", "v-is-group"],
	data: function () {
		let config = this.vUamConfig
		if (config == null) {
			config = {
				isPrior: false,
				isOn: false,
				addToWhiteList: true,
				onlyURLPatterns: [],
				exceptURLPatterns: [],
				minQPSPerIP: 0
			}
		}
		if (config.onlyURLPatterns == null) {
			config.onlyURLPatterns = []
		}
		if (config.exceptURLPatterns == null) {
			config.exceptURLPatterns = []
		}
		return {
			config: config,
			moreOptionsVisible: false,
			minQPSPerIP: config.minQPSPerIP
		}
	},
	watch: {
		minQPSPerIP: function (v) {
			let qps = parseInt(v.toString())
			if (isNaN(qps) || qps < 0) {
				qps = 0
			}
			this.config.minQPSPerIP = qps
		}
	},
	methods: {
		showMoreOptions: function () {
			this.moreOptionsVisible = !this.moreOptionsVisible
		}
	},
	template: ``
})
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: `
	
		
			启用过时缓存 
			
				 
				
			 
		 
		
			有效期 
			
				 
				
			 
		 
		
			状态码 
			 
				
			 
		 
		
			支持stale-if-error 
			
				 
				
			 
		 
	 
`
})
Vue.component("firewall-syn-flood-config-viewer", {
	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
		}
	},
	template: `
	
		已启用 / 空连接次数:{{config.minAttempts}}次/分钟  / 封禁时长:{{config.timeoutSeconds}}秒 / 忽略局域网访问 
	 
	未启用 
`
})
// 域名列表
Vue.component("domains-box", {
	props: ["v-domains", "name", "v-support-wildcard"],
	data: function () {
		let domains = this.vDomains
		if (domains == null) {
			domains = []
		}
		let realName = "domainsJSON"
		if (this.name != null && typeof this.name == "string") {
			realName = this.name
		}
		let supportWildcard = true
		if (typeof this.vSupportWildcard == "boolean") {
			supportWildcard = this.vSupportWildcard
		}
		return {
			domains: domains,
			mode: "single", // single | batch
			batchDomains: "",
			isAdding: false,
			addingDomain: "",
			isEditing: false,
			editingIndex: -1,
			realName: realName,
			supportWildcard: supportWildcard
		}
	},
	watch: {
		vSupportWildcard: function (v) {
			if (typeof v == "boolean") {
				this.supportWildcard = v
			}
		},
		mode: function (mode) {
			let that = this
			setTimeout(function () {
				if (mode == "single") {
					if (that.$refs.addingDomain != null) {
						that.$refs.addingDomain.focus()
					}
				} else if (mode == "batch") {
					if (that.$refs.batchDomains != null) {
						that.$refs.batchDomains.focus()
					}
				}
			}, 100)
		}
	},
	methods: {
		add: function () {
			this.isAdding = true
			let that = this
			setTimeout(function () {
				that.$refs.addingDomain.focus()
			}, 100)
		},
		confirm: function () {
			if (this.mode == "batch") {
				this.confirmBatch()
				return
			}
			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.supportWildcard) {
				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
					}
				}
			} else {
				if (/[*~^]/.test(this.addingDomain)) {
					teaweb.warn("当前只支持添加普通域名,域名中不能含有特殊符号", function () {
						that.$refs.addingDomain.focus()
					})
					return
				}
			}
			if (this.isEditing && this.editingIndex >= 0) {
				this.domains[this.editingIndex] = this.addingDomain
			} else {
				this.domains.push(this.addingDomain)
			}
			this.cancel()
			this.change()
		},
		confirmBatch: function () {
			let domains = this.batchDomains.split("\n")
			let realDomains = []
			let that = this
			let hasProblems = false
			domains.forEach(function (domain) {
				if (hasProblems) {
					return
				}
				if (domain.length == 0) {
					return
				}
				if (that.supportWildcard) {
					if (domain == "~") {
						let expr = domain.substring(1)
						try {
							new RegExp(expr)
						} catch (e) {
							hasProblems = true
							teaweb.warn("正则表达式错误:" + e.message, function () {
								that.$refs.batchDomains.focus()
							})
							return
						}
					}
				} else {
					if (/[*~^]/.test(domain)) {
						hasProblems = true
						teaweb.warn("当前只支持添加普通域名,域名中不能含有特殊符号", function () {
							that.$refs.batchDomains.focus()
						})
						return
					}
				}
				realDomains.push(domain)
			})
			if (hasProblems) {
				return
			}
			if (realDomains.length == 0) {
				teaweb.warn("请输入要添加的域名", function () {
					that.$refs.batchDomains.focus()
				})
				return
			}
			realDomains.forEach(function (domain) {
				that.domains.push(domain)
			})
			this.cancel()
			this.change()
		},
		edit: function (index) {
			this.addingDomain = this.domains[index]
			this.isEditing = true
			this.editingIndex = index
			let that = this
			setTimeout(function () {
				that.$refs.addingDomain.focus()
			}, 50)
		},
		remove: function (index) {
			this.domains.$remove(index)
			this.change()
		},
		cancel: function () {
			this.isAdding = false
			this.mode = "single"
			this.batchDomains = ""
			this.isEditing = false
			this.editingIndex = -1
			this.addingDomain = ""
		},
		change: function () {
			this.$emit("change", this.domains)
		}
	},
	template: `
	 
	
		
			[正则] 
			[后缀] 
			[泛域名] 
			{{domain}}
			
				   
				   
			 
			
				   
				   
			 
		 
		
	 
	
	
		+ 
	
 `
})
Vue.component("http-firewall-province-selector", {
	props: ["v-type", "v-provinces"],
	data: function () {
		let provinces = this.vProvinces
		if (provinces == null) {
			provinces = []
		}
		return {
			listType: this.vType,
			provinces: provinces
		}
	},
	methods: {
		addProvince: function () {
			let selectedProvinceIds = this.provinces.map(function (province) {
				return province.id
			})
			let that = this
			teaweb.popup("/servers/server/settings/waf/ipadmin/selectProvincesPopup?type=" + this.listType + "&selectedProvinceIds=" + selectedProvinceIds.join(","), {
				width: "50em",
				height: "26em",
				callback: function (resp) {
					that.provinces = resp.data.selectedProvinces
					that.$forceUpdate()
					that.notifyChange()
				}
			})
		},
		removeProvince: function (index) {
			this.provinces.$remove(index)
			this.notifyChange()
		},
		resetProvinces: function () {
			this.provinces = []
			this.notifyChange()
		},
		notifyChange: function () {
			this.$emit("change", {
				"provinces": this.provinces
			})
		}
	},
	template: ``
})
Vue.component("http-referers-config-box", {
	props: ["v-referers-config", "v-is-location", "v-is-group"],
	data: function () {
		let config = this.vReferersConfig
		if (config == null) {
			config = {
				isPrior: false,
				isOn: false,
				allowEmpty: true,
				allowSameDomain: true,
				allowDomains: [],
				denyDomains: [],
				checkOrigin: true
			}
		}
		if (config.allowDomains == null) {
			config.allowDomains = []
		}
		if (config.denyDomains == null) {
			config.denyDomains = []
		}
		return {
			config: config
		}
	},
	methods: {
		isOn: function () {
			return ((!this.vIsLocation && !this.vIsGroup) || this.config.isPrior) && this.config.isOn
		},
		changeAllowDomains: function (domains) {
			if (typeof (domains) == "object") {
				this.config.allowDomains = domains
				this.$forceUpdate()
			}
		},
		changeDenyDomains: function (domains) {
			if (typeof (domains) == "object") {
				this.config.denyDomains = domains
				this.$forceUpdate()
			}
		}
	},
	template: ``
})
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-firewall-actions-box", {
	props: ["v-actions", "v-firewall-policy", "v-action-configs", "v-group-type"],
	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 = []
		}
		if (this.vFirewallPolicy.outbound == null) {
			this.vFirewallPolicy.outbound = {}
		}
		if (this.vFirewallPolicy.outbound.groups == null) {
			this.vFirewallPolicy.outbound.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: "",
			blockTimeoutMax: "",
			blockScope: "global",
			captchaLife: "",
			captchaMaxFails: "",
			captchaFailBlockTimeout: "",
			get302Life: "",
			post307Life: "",
			recordIPType: "black",
			recordIPLevel: "critical",
			recordIPTimeout: "",
			recordIPListId: 0,
			recordIPListName: "",
			tagTags: [],
			pageStatus: 403,
			pageBody: defaultPageBody,
			defaultPageBody: defaultPageBody,
			redirectStatus: 307,
			redirectURL: "",
			goGroupName: "",
			goGroupId: 0,
			goGroup: null,
			goSetId: 0,
			goSetName: "",
			jsCookieLife: "",
			jsCookieMaxFails: "",
			jsCookieFailBlockTimeout: "",
			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: {
		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
			}
		},
		blockTimeoutMax: function (v) {
			v = parseInt(v)
			if (isNaN(v)) {
				this.actionOptions["timeoutMax"] = 0
			} else {
				this.actionOptions["timeoutMax"] = 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) {
				// search outbound groups
				group = this.vFirewallPolicy.outbound.groups.$find(function (k, v) {
					return v.id == groupId
				})
				if (group == null) {
					this.goGroupName = ""
				} else {
					this.goGroup = group
					this.goGroupName = group.name
				}
			} 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
			}
		},
		jsCookieLife: function (v) {
			v = parseInt(v)
			if (isNaN(v)) {
				this.actionOptions["life"] = 0
			} else {
				this.actionOptions["life"] = v
			}
		},
		jsCookieMaxFails: function (v) {
			v = parseInt(v)
			if (isNaN(v)) {
				this.actionOptions["maxFails"] = 0
			} else {
				this.actionOptions["maxFails"] = v
			}
		},
		jsCookieFailBlockTimeout: function (v) {
			v = parseInt(v)
			if (isNaN(v)) {
				this.actionOptions["failBlockTimeout"] = 0
			} else {
				this.actionOptions["failBlockTimeout"] = v
			}
		},
	},
	methods: {
		add: function () {
			this.action = null
			this.actionCode = "block"
			this.isAdding = true
			this.actionOptions = {}
			// 动作参数
			this.blockTimeout = ""
			this.blockTimeoutMax = ""
			this.blockScope = "global"
			this.captchaLife = ""
			this.captchaMaxFails = ""
			this.captchaFailBlockTimeout = ""
			this.jsCookieLife = ""
			this.jsCookieMaxFails = ""
			this.jsCookieFailBlockTimeout = ""
			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.redirectStatus = 307
			this.redirectURL = ""
			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 = ""
					this.blockTimeoutMax = ""
					if (config.options.timeout != null || config.options.timeout > 0) {
						this.blockTimeout = config.options.timeout.toString()
					}
					if (config.options.timeoutMax != null || config.options.timeoutMax > 0) {
						this.blockTimeoutMax = config.options.timeoutMax.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 "js_cookie":
					this.jsCookieLife = ""
					if (config.options.life != null || config.options.life > 0) {
						this.jsCookieLife = config.options.life.toString()
					}
					this.jsCookieMaxFails = ""
					if (config.options.maxFails != null || config.options.maxFails > 0) {
						this.jsCookieMaxFails = config.options.maxFails.toString()
					}
					this.jsCookieFailBlockTimeout = ""
					if (config.options.failBlockTimeout != null || config.options.failBlockTimeout > 0) {
						this.jsCookieFailBlockTimeout = 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 "redirect":
					this.redirectStatus = 307
					this.redirectURL = ""
					if (config.options.status != null) {
						this.redirectStatus = config.options.status
					}
					if (config.options.url != null) {
						this.redirectURL = config.options.url
					}
					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 == "redirect") {
				let redirectStatus = this.redirectStatus.toString()
				if (!redirectStatus.match(/^\d{3}$/)) {
					redirectStatus = 307
				} else {
					redirectStatus = parseInt(redirectStatus)
				}
				if (this.redirectURL.length == 0) {
					teaweb.warn("请输入跳转到URL")
					return
				}
				this.actionOptions = {
					status: redirectStatus,
					url: this.redirectURL
				}
			} 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.timeoutMax}} 秒 
			
			
			:有效期{{config.options.life}}秒
				 / 最多失败{{config.options.maxFails}}次 
			 
			
			
			:有效期{{config.options.life}}秒
				 / 最多失败{{config.options.maxFails}}次 
			 
			
			
			:有效期{{config.options.life}}秒 
			
			
			:有效期{{config.options.life}}秒 
			
			
			:{{config.options.ipListName}} 
			
			
			:{{config.options.tags.join(", ")}} 
			
			
			:[{{config.options.status}}] 
			
			
			:{{config.options.url}} 
			
			
			:{{config.options.groupName}} 
			
			
			:{{config.options.groupName}} / {{config.options.setName}} 
			
			
			
				  
				[所有网站] 
				[当前网站] 
			 
			
			
			            
		 
		
	 
	
	
		+ 
	
	
 `
})
// 认证设置
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)
					that.change()
				},
				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)
			this.change()
		},
		methodName: function (methodType) {
			switch (methodType) {
				case "basicAuth":
					return "BasicAuth"
				case "subRequest":
					return "子请求"
				case "typeA":
					return "URL鉴权A"
				case "typeB":
					return "URL鉴权B"
				case "typeC":
					return "URL鉴权C"
				case "typeD":
					return "URL鉴权D"
			}
			return ""
		},
		change: function () {
			let that = this
			setTimeout(function () {
				// 延时通知,是为了让表单有机会变更数据
				that.$emit("change", this.authConfig)
			}, 100)
		}
	},
	template: `
  
	鉴权方式 
	
		
			
				名称 
				鉴权方法 
				参数 
				状态 
				操作 
			 
		 
		
			
				{{ref.authPolicy.name}} 
				
					{{methodName(ref.authPolicy.type)}}
				 
				
					{{ref.authPolicy.params.users.length}}个用户 
					
						[{{ref.authPolicy.params.method}}] 
						{{ref.authPolicy.params.url}}
					 
					{{ref.authPolicy.params.signParamName}}/有效期{{ref.authPolicy.params.life}}秒 
					有效期{{ref.authPolicy.params.life}}秒 
					有效期{{ref.authPolicy.params.life}}秒 
					{{ref.authPolicy.params.signParamName}}/{{ref.authPolicy.params.timestampParamName}}/有效期{{ref.authPolicy.params.life}}秒 
					
					
						扩展名:{{ext}} 
						域名:{{domain}} 
					
				 
				
					 
				 
				
					修改   
					删除 
				 
			 
		 
	
	+添加鉴权方式 
 
 `
})
Vue.component("user-selector", {
	props: ["v-user-id", "data-url"],
	data: function () {
		let userId = this.vUserId
		if (userId == null) {
			userId = 0
		}
		let dataURL = this.dataUrl
		if (dataURL == null || dataURL.length == 0) {
			dataURL = "/servers/users/options"
		}
		return {
			users: [],
			userId: userId,
			dataURL: dataURL
		}
	},
	methods: {
		change: function(item) {
			if (item != null) {
				this.$emit("change", item.id)
			} else {
				this.$emit("change", 0)
			}
		},
		clear: function () {
			this.$refs.comboBox.clear()
		}
	},
	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 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 requestNonStandardHeaders = []
		let requestPolicy = this.vRequestHeaderPolicy
		if (requestPolicy != null) {
			if (requestPolicy.setHeaders != null) {
				requestSettingHeaders = requestPolicy.setHeaders
			}
			if (requestPolicy.deleteHeaders != null) {
				requestDeletingHeaders = requestPolicy.deleteHeaders
			}
			if (requestPolicy.nonStandardHeaders != null) {
				requestNonStandardHeaders = requestPolicy.nonStandardHeaders
			}
		}
		// 响应相关
		let responseSettingHeaders = []
		let responseDeletingHeaders = []
		let responseNonStandardHeaders = []
		let responsePolicy = this.vResponseHeaderPolicy
		if (responsePolicy != null) {
			if (responsePolicy.setHeaders != null) {
				responseSettingHeaders = responsePolicy.setHeaders
			}
			if (responsePolicy.deleteHeaders != null) {
				responseDeletingHeaders = responsePolicy.deleteHeaders
			}
			if (responsePolicy.nonStandardHeaders != null) {
				responseNonStandardHeaders = responsePolicy.nonStandardHeaders
			}
		}
		let responseCORS = {
			isOn: false
		}
		if (responsePolicy.cors != null) {
			responseCORS = responsePolicy.cors
		}
		return {
			type: type,
			typeName: (type == "request") ? "请求" : "响应",
			requestHeaderRef: requestHeaderRef,
			responseHeaderRef: responseHeaderRef,
			requestSettingHeaders: requestSettingHeaders,
			requestDeletingHeaders: requestDeletingHeaders,
			requestNonStandardHeaders: requestNonStandardHeaders,
			responseSettingHeaders: responseSettingHeaders,
			responseDeletingHeaders: responseDeletingHeaders,
			responseNonStandardHeaders: responseNonStandardHeaders,
			responseCORS: responseCORS
		}
	},
	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("保存成功")
				}
			})
		},
		addNonStandardHeader: function (policyId, type) {
			teaweb.popup("/servers/server/settings/headers/createNonStandardPopup?" + 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()
			})
		},
		deleteNonStandardHeader: function (policyId, headerName) {
			teaweb.confirm("确定要删除'" + headerName + "'吗?", function () {
				Tea.action("/servers/server/settings/headers/deleteNonStandardHeader")
					.params({
						headerPolicyId: policyId,
						headerName: headerName
					})
					.post()
					.refresh()
			})
		},
		deleteHeader: function (policyId, type, headerId) {
			teaweb.confirm("确定要删除此报头吗?", function () {
					this.$post("/servers/server/settings/headers/delete")
						.params({
							headerPolicyId: policyId,
							type: type,
							headerId: headerId
						})
						.refresh()
				}
			)
		},
		updateCORS: function (policyId) {
			teaweb.popup("/servers/server/settings/headers/updateCORSPopup?" + this.vParams + "&headerPolicyId=" + policyId + "&type=" + this.type, {
				height: "30em",
				callback: function () {
					teaweb.successRefresh("保存成功")
				}
			})
		}
	},
	template: `
	
	
	
	
	 
	
	
	
	
	
		
        	
        	由于已经在当前网站分组 中进行了对应的配置,在这里的配置将不会生效。 
    	 
    				
	 
	
	
	
	
	
		
        	
        	由于已经在当前网站分组 中进行了对应的配置,在这里的配置将不会生效。 
    	 
    	
			
			
			
			
				
					
						名称 
						值 
						操作 
					 
				 
				
					
						
							{{header.name}}  
							
								{{code}}  
								{{method}}  
								{{domain}}  
								附加 
								跳转禁用 
								替换 
							
							
							
							
								建议使用当前页面下方的"CORS自适应跨域"功能代替Access-Control-*-*相关报头。 
							
						 
						{{header.value}} 
						修改    删除   
					 
				 
			
			
			其他设置 
			
			
				
					
						删除报头  
						
							
							+ 
						 
					 
					
						非标报头  
						
							
							+ 
						 
					 
					
						CORS自适应跨域 
						
							已启用 未启用    [修改] 
							
						 
					 
				 
			
		 		
	 
	
 `
})
// 通用设置
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-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", {
				width: "42em",
				height: "26em",
				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-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内容]     
				 
				
			 
			
				+ 
			
			
		 
	 	
	
		临时关闭页面 
		
			
		 
	 
 `
})
// 压缩配置
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", "zstd", "deflate"],
				level: 5,
				decompressData: false,
				gzipRef: null,
				deflateRef: null,
				brotliRef: null,
				minLength: {count: 1, "unit": "kb"},
				maxLength: {count: 32, "unit": "mb"},
				mimeTypes: ["text/*", "application/javascript", "application/json", "application/atom+xml", "application/rss+xml", "application/xhtml+xml", "font/*", "image/svg+xml"],
				extensions: [".js", ".json", ".html", ".htm", ".xml", ".css", ".woff2", ".txt"],
				exceptExtensions: [".apk", ".ipa"],
				conds: null,
				enablePartialContent: false
			}
		}
		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
			},
			{
				name: "ZSTD",
				code: "zstd",
				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
		},
		changeExceptExtensions: function (values) {
			values.forEach(function (v, k) {
				if (v.length > 0 && v[0] != ".") {
					values[k] = "." + v
				}
			})
			this.config.exceptExtensions = 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: ``
})
// HTTP CC防护配置
Vue.component("http-cc-config-box", {
	props: ["v-cc-config", "v-is-location", "v-is-group"],
	data: function () {
		let config = this.vCcConfig
		if (config == null) {
			config = {
				isPrior: false,
				isOn: false,
				enableFingerprint: true,
				enableGET302: true,
				onlyURLPatterns: [],
				exceptURLPatterns: [],
				useDefaultThresholds: true
			}
		}
		if (config.thresholds == null || config.thresholds.length == 0) {
			config.thresholds = [
				{
					maxRequests: 0
				},
				{
					maxRequests: 0
				},
				{
					maxRequests: 0
				}
			]
		}
		if (typeof config.enableFingerprint != "boolean") {
			config.enableFingerprint = true
		}
		if (typeof config.enableGET302 != "boolean") {
			config.enableGET302 = true
		}
		if (config.onlyURLPatterns == null) {
			config.onlyURLPatterns = []
		}
		if (config.exceptURLPatterns == null) {
			config.exceptURLPatterns = []
		}
		return {
			config: config,
			moreOptionsVisible: false,
			minQPSPerIP: config.minQPSPerIP,
			useCustomThresholds: !config.useDefaultThresholds,
			thresholdMaxRequests0: this.maxRequestsStringAtThresholdIndex(config, 0),
			thresholdMaxRequests1: this.maxRequestsStringAtThresholdIndex(config, 1),
			thresholdMaxRequests2: this.maxRequestsStringAtThresholdIndex(config, 2)
		}
	},
	watch: {
		minQPSPerIP: function (v) {
			let qps = parseInt(v.toString())
			if (isNaN(qps) || qps < 0) {
				qps = 0
			}
			this.config.minQPSPerIP = qps
		},
		thresholdMaxRequests0: function (v) {
			this.setThresholdMaxRequests(0, v)
		},
		thresholdMaxRequests1: function (v) {
			this.setThresholdMaxRequests(1, v)
		},
		thresholdMaxRequests2: function (v) {
			this.setThresholdMaxRequests(2, v)
		},
		useCustomThresholds: function (b) {
			this.config.useDefaultThresholds = !b
		}
	},
	methods: {
		maxRequestsStringAtThresholdIndex: function (config, index) {
			if (config.thresholds == null) {
				return ""
			}
			if (index < config.thresholds.length) {
				let s = config.thresholds[index].maxRequests.toString()
				if (s == "0") {
					s = ""
				}
				return s
			}
			return ""
		},
		setThresholdMaxRequests: function (index, v) {
			let maxRequests = parseInt(v)
			if (isNaN(maxRequests) || maxRequests < 0) {
				maxRequests = 0
			}
			if (index < this.config.thresholds.length) {
				this.config.thresholds[index].maxRequests = maxRequests
			}
		},
		showMoreOptions: function () {
			this.moreOptionsVisible = !this.moreOptionsVisible
		}
	},
	template: ``
})
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: `
    
        {{level.name}} 
     
    
`
})
Vue.component("prior-checkbox", {
	props: ["v-config", "description"],
	data: function () {
		let description = this.description
		if (description == null) {
			description = "打开后可以覆盖父级或子级配置"
		}
		return {
			isPrior: this.vConfig.isPrior,
			realDescription: description
		}
	},
	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: ``
})
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-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"
			}
		}
		// 对TAG去重
		if (accessLog.tags != null && accessLog.tags.length > 0) {
			let tagMap = {}
			accessLog.tags = accessLog.tags.$filter(function (k, tag) {
				let b = (typeof (tagMap[tag]) == "undefined")
				tagMap[tag] = true
				return b
			})
		}
		// 域名
		accessLog.unicodeHost = ""
		if (accessLog.host != null && accessLog.host.startsWith("xn--")) {
			// port
			let portIndex = accessLog.host.indexOf(":")
			if (portIndex > 0) {
				accessLog.unicodeHost = punycode.ToUnicode(accessLog.host.substring(0, portIndex))
			} else {
				accessLog.unicodeHost = punycode.ToUnicode(accessLog.host)
			}
		}
		return {
			accessLog: accessLog
		}
	},
	methods: {
		formatCost: function (seconds) {
			if (seconds == null) {
				return "0"
			}
			let s = (seconds * 1000).toString();
			let pieces = s.split(".");
			if (pieces.length < 2) {
				return s;
			}
			return pieces[0] + "." + pieces[1].substring(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 = ""
		},
		mismatch: function () {
			teaweb.warn("当前访问没有匹配到任何网站")
		}
	},
	template: `
	
		[{{accessLog.node.name}}节点 ]  
		
		
		[服务]  
		[服务]  
		
		[{{accessLog.region}}]   
		{{accessLog.remoteAddr}}  [{{accessLog.timeLocal}}] 
"{{accessLog.requestMethod}}  {{accessLog.scheme}}://{{accessLog.host}} {{accessLog.requestURI}}      {{accessLog.proto}}"   {{accessLog.status}}  
		
		{{accessLog.unicodeHost}} 
		
		
		cache {{accessLog.attrs['cache.status'].toLowerCase()}}  
		
		waf {{accessLog.firewallActions}}  
		
		
		- {{tag}} 
		 
		
			
				
					
						WAF -
						{{accessLog.wafInfo.group.name}} - 
						{{accessLog.wafInfo.set.name}} 
					 
				 
			 
		 
			
		 - 耗时:{{formatCost(accessLog.requestTime)}} ms    ({{accessLog.humanTime}}) 
		   
	 
 `
})
// Javascript Punycode converter derived from example in RFC3492.
// This implementation is created by some@domain.name and released into public domain
// 代码来自:https://stackoverflow.com/questions/183485/converting-punycode-with-dash-character-to-unicode
var punycode = new function Punycode() {
	// This object converts to and from puny-code used in IDN
	//
	// punycode.ToASCII ( domain )
	//
	// Returns a puny coded representation of "domain".
	// It only converts the part of the domain name that
	// has non ASCII characters. I.e. it dosent matter if
	// you call it with a domain that already is in ASCII.
	//
	// punycode.ToUnicode (domain)
	//
	// Converts a puny-coded domain name to unicode.
	// It only converts the puny-coded parts of the domain name.
	// I.e. it dosent matter if you call it on a string
	// that already has been converted to unicode.
	//
	//
	this.utf16 = {
		// The utf16-class is necessary to convert from javascripts internal character representation to unicode and back.
		decode: function (input) {
			var output = [], i = 0, len = input.length, value, extra;
			while (i < len) {
				value = input.charCodeAt(i++);
				if ((value & 0xF800) === 0xD800) {
					extra = input.charCodeAt(i++);
					if (((value & 0xFC00) !== 0xD800) || ((extra & 0xFC00) !== 0xDC00)) {
						throw new RangeError("UTF-16(decode): Illegal UTF-16 sequence");
					}
					value = ((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000;
				}
				output.push(value);
			}
			return output;
		},
		encode: function (input) {
			var output = [], i = 0, len = input.length, value;
			while (i < len) {
				value = input[i++];
				if ((value & 0xF800) === 0xD800) {
					throw new RangeError("UTF-16(encode): Illegal UTF-16 value");
				}
				if (value > 0xFFFF) {
					value -= 0x10000;
					output.push(String.fromCharCode(((value >>> 10) & 0x3FF) | 0xD800));
					value = 0xDC00 | (value & 0x3FF);
				}
				output.push(String.fromCharCode(value));
			}
			return output.join("");
		}
	}
	//Default parameters
	var initial_n = 0x80;
	var initial_bias = 72;
	var delimiter = "\x2D";
	var base = 36;
	var damp = 700;
	var tmin = 1;
	var tmax = 26;
	var skew = 38;
	var maxint = 0x7FFFFFFF;
	// decode_digit(cp) returns the numeric value of a basic code
	// point (for use in representing integers) in the range 0 to
	// base-1, or base if cp is does not represent a value.
	function decode_digit(cp) {
		return cp - 48 < 10 ? cp - 22 : cp - 65 < 26 ? cp - 65 : cp - 97 < 26 ? cp - 97 : base;
	}
	// encode_digit(d,flag) returns the basic code point whose value
	// (when used for representing integers) is d, which needs to be in
	// the range 0 to base-1. The lowercase form is used unless flag is
	// nonzero, in which case the uppercase form is used. The behavior
	// is undefined if flag is nonzero and digit d has no uppercase form.
	function encode_digit(d, flag) {
		return d + 22 + 75 * (d < 26) - ((flag != 0) << 5);
		//  0..25 map to ASCII a..z or A..Z
		// 26..35 map to ASCII 0..9
	}
	//** Bias adaptation function **
	function adapt(delta, numpoints, firsttime) {
		var k;
		delta = firsttime ? Math.floor(delta / damp) : (delta >> 1);
		delta += Math.floor(delta / numpoints);
		for (k = 0; delta > (((base - tmin) * tmax) >> 1); k += base) {
			delta = Math.floor(delta / (base - tmin));
		}
		return Math.floor(k + (base - tmin + 1) * delta / (delta + skew));
	}
	// encode_basic(bcp,flag) forces a basic code point to lowercase if flag is zero,
	// uppercase if flag is nonzero, and returns the resulting code point.
	// The code point is unchanged if it is caseless.
	// The behavior is undefined if bcp is not a basic code point.
	function encode_basic(bcp, flag) {
		bcp -= (bcp - 97 < 26) << 5;
		return bcp + ((!flag && (bcp - 65 < 26)) << 5);
	}
	// Main decode
	this.decode = function (input, preserveCase) {
		// Dont use utf16
		var output = [];
		var case_flags = [];
		var input_length = input.length;
		var n, out, i, bias, basic, j, ic, oldi, w, k, digit, t, len;
		// Initialize the state:
		n = initial_n;
		i = 0;
		bias = initial_bias;
		// Handle the basic code points: Let basic be the number of input code
		// points before the last delimiter, or 0 if there is none, then
		// copy the first basic code points to the output.
		basic = input.lastIndexOf(delimiter);
		if (basic < 0) basic = 0;
		for (j = 0; j < basic; ++j) {
			if (preserveCase) case_flags[output.length] = (input.charCodeAt(j) - 65 < 26);
			if (input.charCodeAt(j) >= 0x80) {
				throw new RangeError("Illegal input >= 0x80");
			}
			output.push(input.charCodeAt(j));
		}
		// Main decoding loop: Start just after the last delimiter if any
		// basic code points were copied; start at the beginning otherwise.
		for (ic = basic > 0 ? basic + 1 : 0; ic < input_length;) {
			// ic is the index of the next character to be consumed,
			// Decode a generalized variable-length integer into delta,
			// which gets added to i. The overflow checking is easier
			// if we increase i as we go, then subtract off its starting
			// value at the end to obtain delta.
			for (oldi = i, w = 1, k = base; ; k += base) {
				if (ic >= input_length) {
					throw RangeError("punycode_bad_input(1)");
				}
				digit = decode_digit(input.charCodeAt(ic++));
				if (digit >= base) {
					throw RangeError("punycode_bad_input(2)");
				}
				if (digit > Math.floor((maxint - i) / w)) {
					throw RangeError("punycode_overflow(1)");
				}
				i += digit * w;
				t = k <= bias ? tmin : k >= bias + tmax ? tmax : k - bias;
				if (digit < t) {
					break;
				}
				if (w > Math.floor(maxint / (base - t))) {
					throw RangeError("punycode_overflow(2)");
				}
				w *= (base - t);
			}
			out = output.length + 1;
			bias = adapt(i - oldi, out, oldi === 0);
			// i was supposed to wrap around from out to 0,
			// incrementing n each time, so we'll fix that now:
			if (Math.floor(i / out) > maxint - n) {
				throw RangeError("punycode_overflow(3)");
			}
			n += Math.floor(i / out);
			i %= out;
			// Insert n at position i of the output:
			// Case of last character determines uppercase flag:
			if (preserveCase) {
				case_flags.splice(i, 0, input.charCodeAt(ic - 1) - 65 < 26);
			}
			output.splice(i, 0, n);
			i++;
		}
		if (preserveCase) {
			for (i = 0, len = output.length; i < len; i++) {
				if (case_flags[i]) {
					output[i] = (String.fromCharCode(output[i]).toUpperCase()).charCodeAt(0);
				}
			}
		}
		return this.utf16.encode(output);
	};
	//** Main encode function **
	this.encode = function (input, preserveCase) {
		//** Bias adaptation function **
		var n, delta, h, b, bias, j, m, q, k, t, ijv, case_flags;
		if (preserveCase) {
			// Preserve case, step1 of 2: Get a list of the unaltered string
			case_flags = this.utf16.decode(input);
		}
		// Converts the input in UTF-16 to Unicode
		input = this.utf16.decode(input.toLowerCase());
		var input_length = input.length; // Cache the length
		if (preserveCase) {
			// Preserve case, step2 of 2: Modify the list to true/false
			for (j = 0; j < input_length; j++) {
				case_flags[j] = input[j] != case_flags[j];
			}
		}
		var output = [];
		// Initialize the state:
		n = initial_n;
		delta = 0;
		bias = initial_bias;
		// Handle the basic code points:
		for (j = 0; j < input_length; ++j) {
			if (input[j] < 0x80) {
				output.push(
					String.fromCharCode(
						case_flags ? encode_basic(input[j], case_flags[j]) : input[j]
					)
				);
			}
		}
		h = b = output.length;
		// h is the number of code points that have been handled, b is the
		// number of basic code points
		if (b > 0) output.push(delimiter);
		// Main encoding loop:
		//
		while (h < input_length) {
			// All non-basic code points < n have been
			// handled already. Find the next larger one:
			for (m = maxint, j = 0; j < input_length; ++j) {
				ijv = input[j];
				if (ijv >= n && ijv < m) m = ijv;
			}
			// Increase delta enough to advance the decoder's
			//  state to , but guard against overflow:
			if (m - n > Math.floor((maxint - delta) / (h + 1))) {
				throw RangeError("punycode_overflow (1)");
			}
			delta += (m - n) * (h + 1);
			n = m;
			for (j = 0; j < input_length; ++j) {
				ijv = input[j];
				if (ijv < n) {
					if (++delta > maxint) return Error("punycode_overflow(2)");
				}
				if (ijv == n) {
					// Represent delta as a generalized variable-length integer:
					for (q = delta, k = base; ; k += base) {
						t = k <= bias ? tmin : k >= bias + tmax ? tmax : k - bias;
						if (q < t) break;
						output.push(String.fromCharCode(encode_digit(t + (q - t) % (base - t), 0)));
						q = Math.floor((q - t) / (base - t));
					}
					output.push(String.fromCharCode(encode_digit(q, preserveCase && case_flags[j] ? 1 : 0)));
					bias = adapt(delta, h + 1, h == b);
					delta = 0;
					++h;
				}
			}
			++delta, ++n;
		}
		return output.join("");
	}
	this.ToASCII = function (domain) {
		var domain_array = domain.split(".");
		var out = [];
		for (var i = 0; i < domain_array.length; ++i) {
			var s = domain_array[i];
			out.push(
				s.match(/[^A-Za-z0-9-]/) ?
					"xn--" + punycode.encode(s) :
					s
			);
		}
		return out.join(".");
	}
	this.ToUnicode = function (domain) {
		var domain_array = domain.split(".");
		var out = [];
		for (var i = 0; i < domain_array.length; ++i) {
			var s = domain_array[i];
			out.push(
				s.match(/^xn--/) ?
					punycode.decode(s.slice(4)) :
					s
			);
		}
		return out.join(".");
	}
}();
Vue.component("http-firewall-block-options-viewer", {
	props: ["v-block-options"],
	data: function () {
		return {
			options: this.vBlockOptions
		}
	},
	template: `
	默认设置 
	
		状态码:{{options.statusCode}} / 提示内容:[{{options.body.length}}字符] [无]   / 超时时间:{{options.timeout}}秒 / 最大封禁时长:{{options.timeoutMax}}秒 
	
 	
`
})
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),
			showAdvancedOptions: false
		}
	},
	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)
		},
		changeAdvanced: function (v) {
			this.showAdvancedOptions = v
		}
	},
	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-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 
	
	
	文档根目录 
	
	
	源站 
	
	
	5秒盾  
	
	
	CC防护  
	
	
	
	
	
	CACHE 
	
	
	{{location.web.charset.charset}} 
	
	
	
	
	
	
	
	
	Gzip:{{location.web.gzip.level}} 
	
	
	请求Header 
	响应Header 
	
	
	Websocket 
	
	
	请求脚本 
	
	
	访客IP地址 
	
	
	请求限制 
		
	
	
		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: ``
})
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}} 
					
				 
			 
		 
	
 `
})
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("http-firewall-captcha-options-viewer", {
	props: ["v-captcha-options"],
	mounted: function () {
		this.updateSummary()
	},
	data: function () {
		let options = this.vCaptchaOptions
		if (options == null) {
			options = {
				life: 0,
				maxFails: 0,
				failBlockTimeout: 0,
				failBlockScopeAll: false,
				uiIsOn: false,
				uiTitle: "",
				uiPrompt: "",
				uiButtonTitle: "",
				uiShowRequestId: false,
				uiCss: "",
				uiFooter: "",
				uiBody: "",
				cookieId: "",
				lang: ""
			}
		}
		return {
			options: options,
			summary: ""
		}
	},
	methods: {
		updateSummary: function () {
			let summaryList = []
			if (this.options.life > 0) {
				summaryList.push("有效时间" + this.options.life + "秒")
			}
			if (this.options.maxFails > 0) {
				summaryList.push("最多失败" + this.options.maxFails + "次")
			}
			if (this.options.failBlockTimeout > 0) {
				summaryList.push("失败拦截" + this.options.failBlockTimeout + "秒")
			}
			if (this.options.failBlockScopeAll) {
				summaryList.push("全局封禁")
			}
			if (this.options.uiIsOn) {
				summaryList.push("定制UI")
			}
			if (summaryList.length == 0) {
				this.summary = "默认配置"
			} else {
				this.summary = summaryList.join(" / ")
			}
		}
	},
	template: `{{summary}}
`
})
Vue.component("reverse-proxy-box", {
	props: ["v-reverse-proxy-ref", "v-reverse-proxy-config", "v-is-location", "v-is-group", "v-family"],
	data: function () {
		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,
				requestHostExcludingPort: false,
				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("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-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-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("server-config-copy-link", {
	props: ["v-server-id", "v-config-code"],
	data: function () {
		return {
			serverId: this.vServerId,
			configCode: this.vConfigCode
		}
	},
	methods: {
		copy: function () {
			teaweb.popup("/servers/server/settings/copy?serverId=" + this.serverId + "&configCode=" + this.configCode, {
				height: "25em",
				callback: function () {
					teaweb.success("批量复制成功")
				}
			})
		}
	},
	template: `批量   `
})
// 显示指标对象名
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: ``
})
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: false,
				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-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("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}}   [修改]  
				
			 
		 
	
 `
})
Vue.component("http-firewall-block-options", {
	props: ["v-block-options"],
	data: function () {
		return {
			blockOptions: this.vBlockOptions,
			statusCode: this.vBlockOptions.statusCode,
			timeout: this.vBlockOptions.timeout,
			timeoutMax: this.vBlockOptions.timeoutMax,
			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
			}
		},
		timeoutMax: function (v) {
			let timeoutMax = parseInt(v)
			if (isNaN(timeoutMax)) {
				this.blockOptions.timeoutMax = 0
			} else {
				this.blockOptions.timeoutMax = timeoutMax
			}
		}
	},
	methods: {
		edit: function () {
			this.isEditing = !this.isEditing
		}
	},
	template: `	
`
})
Vue.component("http-oss-bucket-params", {
	props: ["v-oss-config", "v-params", "name"],
	data: function () {
		let params = this.vParams
		if (params == null) {
			params = []
		}
		let ossConfig = this.vOssConfig
		if (ossConfig == null) {
			ossConfig = {
				bucketParam: "input",
				bucketName: "",
				bucketArgName: ""
			}
		} else {
			// 兼容以往
			if (ossConfig.bucketParam != null && ossConfig.bucketParam.length == 0) {
				ossConfig.bucketParam = "input"
			}
			if (ossConfig.options != null && ossConfig.options.bucketName != null && ossConfig.options.bucketName.length > 0) {
				ossConfig.bucketName = ossConfig.options.bucketName
			}
		}
		return {
			params: params,
			ossConfig: ossConfig
		}
	},
	template: `
	
		{{name}}名称获取方式 * 
		
			
				{{param.name.replace("\${optionName}", name)}} 
				{{param.name}} - {{param.example}} 
			 
			
		 
	 
    
        {{name}}名称 * 
        
             
            
         
     
    
    	{{name}}参数名称 * 
        
             
            
         
	 
 `
})
Vue.component("http-request-cond-view", {
	props: ["v-cond"],
	data: function () {
		return {
			cond: this.vCond,
			components: window.REQUEST_COND_COMPONENTS
		}
	},
	methods: {
		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
		},
		updateConds: function (conds, simpleCond) {
			for (let k in simpleCond) {
				if (simpleCond.hasOwnProperty(k)) {
					this.cond[k] = simpleCond[k]
				}
			}
		},
		notifyChange: function () {
		}
	},
	template: `
	
		{{cond.param}} {{cond.operator}}  
		{{typeName(cond)}}:  
		{{cond.value}}
		 
	 
`
})
Vue.component("http-header-assistant", {
	props: ["v-type", "v-value"],
	mounted: function () {
		let that = this
		Tea.action("/servers/headers/options?type=" + this.vType)
			.post()
			.success(function (resp) {
				that.allHeaders = resp.data.headers
			})
	},
	data: function () {
		return {
			allHeaders: [],
			matchedHeaders: [],
			selectedHeaderName: ""
		}
	},
	watch: {
		vValue: function (v) {
			if (v != this.selectedHeaderName) {
				this.selectedHeaderName = ""
			}
			if (v.length == 0) {
				this.matchedHeaders = []
				return
			}
			this.matchedHeaders = this.allHeaders.filter(function (header) {
				return teaweb.match(header, v)
			}).slice(0, 10)
		}
	},
	methods: {
		select: function (header) {
			this.$emit("select", header)
			this.selectedHeaderName = header
		}
	},
	template: `
	{{header}} 
	    
 `
})
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, {
				height: "30em",
				callback: function (resp) {
					that.rules.push(resp.data.rule)
				}
			})
		},
		updateRule: function (index, rule) {
			window.UPDATING_RULE = teaweb.clone(rule)
			let that = this
			teaweb.popup("/servers/components/waf/createRulePopup?type=" + this.vType, {
				height: "30em",
				callback: function (resp) {
					Vue.set(that.rules, index, resp.data.rule)
				}
			})
		},
		removeRule: function (index) {
			let that = this
			teaweb.confirm("确定要删除此规则吗?", function () {
				that.rules.$remove(index)
			})
		},
		operatorName: function (operatorCode) {
			var operatorName = operatorCode
			if (typeof (window.WAF_RULE_OPERATORS) != null) {
				window.WAF_RULE_OPERATORS.forEach(function (v) {
					if (v.code == operatorCode) {
						operatorName = v.name
					}
				})
			}
			return operatorName
		},
		isEmptyString: function (v) {
			return typeof v == "string" && v.length == 0
		}
	},
	template: `
		 
		
			
				{{rule.name}}[{{rule.param}}] 
				
				
				
					{{rule.checkpointOptions.period}}秒内请求数
				 	
				
				
				
					允许{{rule.checkpointOptions.allowDomains}} 
					禁止{{rule.checkpointOptions.denyDomains}} 
				 
				
				
					 | {{paramFilter.code}}  {{operatorName(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: ``
})
// 请求方法列表
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: ``
})
// URL扩展名条件
Vue.component("http-cond-url-extension", {
	props: ["v-cond"],
	data: function () {
		let cond = {
			isRequest: true,
			param: "${requestPathLowerExtension}",
			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
			}
			let that = this
			this.addingExt.split(/[,;,;|]/).forEach(function (ext) {
				ext = ext.trim()
				if (ext.length > 0) {
					if (ext[0] != ".") {
						ext = "." + ext
					}
					ext = ext.replace(/\s+/g, "").toLowerCase()
					that.extensions.push(ext)
				}
			})
			// 清除状态
			this.cancelAdding()
		},
		removeExt: function (index) {
			this.extensions.$remove(index)
		}
	},
	template: ``
})
// 排除URL扩展名条件
Vue.component("http-cond-url-not-extension", {
	props: ["v-cond"],
	data: function () {
		let cond = {
			isRequest: true,
			param: "${requestPathLowerExtension}",
			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: ``
})
// 根据URL前缀
Vue.component("http-cond-url-prefix", {
	props: ["v-cond"],
	mounted: function () {
		this.$refs.valueInput.focus()
	},
	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-not-prefix", {
	props: ["v-cond"],
	mounted: function () {
		this.$refs.valueInput.focus()
	},
	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-eq-index", {
	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-all", {
	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精准匹配
Vue.component("http-cond-url-eq", {
	props: ["v-cond"],
	mounted: function () {
		this.$refs.valueInput.focus()
	},
	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-not-eq", {
	props: ["v-cond"],
	mounted: function () {
		this.$refs.valueInput.focus()
	},
	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正则匹配
Vue.component("http-cond-url-regexp", {
	props: ["v-cond"],
	mounted: function () {
		this.$refs.valueInput.focus()
	},
	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正则匹配
Vue.component("http-cond-url-not-regexp", {
	props: ["v-cond"],
	mounted: function () {
		this.$refs.valueInput.focus()
	},
	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通配符
Vue.component("http-cond-url-wildcard-match", {
	props: ["v-cond"],
	mounted: function () {
		this.$refs.valueInput.focus()
	},
	data: function () {
		let cond = {
			isRequest: true,
			param: "${requestPath}",
			operator: "wildcard match",
			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正则匹配
Vue.component("http-cond-user-agent-regexp", {
	props: ["v-cond"],
	mounted: function () {
		this.$refs.valueInput.focus()
	},
	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正则不匹配
Vue.component("http-cond-user-agent-not-regexp", {
	props: ["v-cond"],
	mounted: function () {
		this.$refs.valueInput.focus()
	},
	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: `
	 
	 
	
`
})
// 根据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-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: `
	
		参数值 
		
			 
			
			
		 
	 
	
		操作符 
		
			
				
					{{operator.name}} 
				 
				
			
		 
	 
	
		对比值 
		
			
			
				 
				
			
			
			
			
				 
				
			
			
			
			
				 
				
			
			
				 
				
			
			
			
			
			
				 
				
				
				
				
				
				
			
			
				 
				
				
				
				
			
			
			
			
			
				 
				
			
			
				 
				
			
			
				 
				
			
		 
	 
	
		不区分大小写 
		
		   
				 
				 
			
			
		 
	 
 
`
})
// 请求方法列表
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("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("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: `
	 
	
		
			
				启用流量限制 
				
					 
					
				 
			 
		 
		
			
				日流量限制 
				
					 
				 
			 
			
				月流量限制 
				
					 
				 
			 
			
			
				网页提示内容 
				
					
					
				 
			 
		 
	
	
 `
})
Vue.component("http-firewall-captcha-options", {
	props: ["v-captcha-options"],
	mounted: function () {
		this.updateSummary()
	},
	data: function () {
		let options = this.vCaptchaOptions
		if (options == null) {
			options = {
				countLetters: 0,
				life: 0,
				maxFails: 0,
				failBlockTimeout: 0,
				failBlockScopeAll: false,
				uiIsOn: false,
				uiTitle: "",
				uiPrompt: "",
				uiButtonTitle: "",
				uiShowRequestId: true,
				uiCss: "",
				uiFooter: "",
				uiBody: "",
				cookieId: "",
				lang: ""
			}
		}
		if (options.countLetters <= 0) {
			options.countLetters = 6
		}
		return {
			options: options,
			isEditing: false,
			summary: ""
		}
	},
	watch: {
		"options.countLetters": function (v) {
			let i = parseInt(v, 10)
			if (isNaN(i)) {
				i = 0
			} else if (i < 0) {
				i = 0
			} else if (i > 10) {
				i = 10
			}
			this.options.countLetters = i
		},
		"options.life": function (v) {
			let i = parseInt(v, 10)
			if (isNaN(i)) {
				i = 0
			}
			this.options.life = i
			this.updateSummary()
		},
		"options.maxFails": function (v) {
			let i = parseInt(v, 10)
			if (isNaN(i)) {
				i = 0
			}
			this.options.maxFails = i
			this.updateSummary()
		},
		"options.failBlockTimeout": function (v) {
			let i = parseInt(v, 10)
			if (isNaN(i)) {
				i = 0
			}
			this.options.failBlockTimeout = i
			this.updateSummary()
		},
		"options.failBlockScopeAll": function (v) {
			this.updateSummary()
		},
		"options.uiIsOn": function (v) {
			this.updateSummary()
		}
	},
	methods: {
		edit: function () {
			this.isEditing = !this.isEditing
		},
		updateSummary: function () {
			let summaryList = []
			if (this.options.life > 0) {
				summaryList.push("有效时间" + this.options.life + "秒")
			}
			if (this.options.maxFails > 0) {
				summaryList.push("最多失败" + this.options.maxFails + "次")
			}
			if (this.options.failBlockTimeout > 0) {
				summaryList.push("失败拦截" + this.options.failBlockTimeout + "秒")
			}
			if (this.options.failBlockScopeAll) {
				summaryList.push("全局封禁")
			}
			if (this.options.uiIsOn) {
				summaryList.push("定制UI")
			}
			if (summaryList.length == 0) {
				this.summary = "默认配置"
			} else {
				this.summary = summaryList.join(" / ")
			}
		},
		confirm: function () {
			this.isEditing = false
		}
	},
	template: `
`
})
Vue.component("user-agent-config-box", {
	props: ["v-is-location", "v-is-group", "value"],
	data: function () {
		let config = this.value
		if (config == null) {
			config = {
				isPrior: false,
				isOn: false,
				filters: []
			}
		}
		if (config.filters == null) {
			config.filters = []
		}
		return {
			config: config,
			isAdding: false,
			addingFilter: {
				keywords: [],
				action: "deny"
			}
		}
	},
	methods: {
		isOn: function () {
			return ((!this.vIsLocation && !this.vIsGroup) || this.config.isPrior) && this.config.isOn
		},
		remove: function (index) {
			let that = this
			teaweb.confirm("确定要删除此名单吗?", function () {
				that.config.filters.$remove(index)
			})
		},
		add: function () {
			this.isAdding = true
		},
		confirm: function () {
			if (this.addingFilter.action == "deny") {
				this.config.filters.push(this.addingFilter)
			} else {
				let index = -1
				this.config.filters.forEach(function (filter, filterIndex) {
					if (filter.action == "allow") {
						index = filterIndex
					}
				})
				if (index < 0) {
					this.config.filters.unshift(this.addingFilter)
				} else {
					this.config.filters.$insert(index + 1, this.addingFilter)
				}
			}
			this.cancel()
		},
		cancel: function () {
			this.isAdding = false
			this.addingFilter = {
				keywords: [],
				action: "deny"
			}
		},
		changeKeywords: function (keywords) {
			this.addingFilter.keywords = keywords
		}
	},
	template: ``
})
Vue.component("http-pages-box", {
	props: ["v-pages"],
	data: function () {
		let pages = []
		if (this.vPages != null) {
			pages = this.vPages
		}
		return {
			pages: pages
		}
	},
	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)
			})
		}
	},
	template: `
 
	
		自定义页面 
		
			
				
					{{page.status}} -> 
{{page.url}} [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: ``
})
Vue.component("http-firewall-region-selector", {
	props: ["v-type", "v-countries"],
	data: function () {
		let countries = this.vCountries
		if (countries == null) {
			countries = []
		}
		return {
			listType: this.vType,
			countries: countries
		}
	},
	methods: {
		addCountry: function () {
			let selectedCountryIds = this.countries.map(function (country) {
				return country.id
			})
			let that = this
			teaweb.popup("/servers/server/settings/waf/ipadmin/selectCountriesPopup?type=" + this.listType + "&selectedCountryIds=" + selectedCountryIds.join(","), {
				width: "52em",
				height: "30em",
				callback: function (resp) {
					that.countries = resp.data.selectedCountries
					that.$forceUpdate()
					that.notifyChange()
				}
			})
		},
		removeCountry: function (index) {
			this.countries.$remove(index)
			this.notifyChange()
		},
		resetCountries: function () {
			this.countries = []
			this.notifyChange()
		},
		notifyChange: function () {
			this.$emit("change", {
				"countries": this.countries
			})
		}
	},
	template: `
	暂时没有选择允许 封禁 的区域。 
	
		
			
			({{country.letter}}){{country.name}} 
 
		 
	 
	
	修改    清空 
 `
})
// 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: `
    
        [选择系统用户] 
        {{admin.name}}({{admin.username}}) 
     
`
})
// 绑定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.region}} 
						|  {{item.isp}} 
					
					
				 
				
					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("email-sender", {
	props: ["value", "name"],
	data: function () {
		let value = this.value
		if (value == null) {
			value = {
				isOn: false,
				smtpHost: "",
				smtpPort: 0,
				username: "",
				password: "",
				fromEmail: "",
				fromName: ""
			}
		}
		let smtpPortString = value.smtpPort.toString()
		if (smtpPortString == "0") {
			smtpPortString = ""
		}
		return {
			config: value,
			smtpPortString: smtpPortString
		}
	},
	watch: {
		smtpPortString: function (v) {
			let port = parseInt(v)
			if (!isNaN(port)) {
				this.config.smtpPort = port
			}
		}
	},
	methods: {
		test: function () {
			window.TESTING_EMAIL_CONFIG = this.config
			teaweb.popup("/users/setting/emailTest", {
				height: "36em"
			})
		}
	},
	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("/settings/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("/settings/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", "v-url"],
	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,
			isEditing: false
		}
	},
	watch: {
		"vServerType": function () {
			this.addresses = []
		},
		"vAddresses": function () {
			if (this.vAddresses != null) {
				this.addresses = this.vAddresses
			}
		}
	},
	methods: {
		addAddr: function () {
			this.isEditing = true
			let that = this
			window.UPDATING_ADDR = null
			let url = this.vUrl
			if (url == null) {
				url = "/servers/addPortPopup"
			}
			teaweb.popup(url + "?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
			let url = this.vUrl
			if (url == null) {
				url = "/servers/addPortPopup"
			}
			teaweb.popup(url + "?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")
		},
		edit: function () {
			this.isEditing = true
		}
	},
	template: `
	 
	
		
			
				{{addr.protocol}}://{{addr.host.quoteIP()}} * :{{addr.portRange}} {{addr.portRange}} 
			
			    [修改] 
		 	
	 
	
		
			
				{{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 = ""
					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)
			if (this.vHref.length > 0) {
				window.location = this.vHref
			}
		}
	},
	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("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) {
					// 不提示错误,因为此时可能页面未加载完整
					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", "v-values", "size", "maxlength", "name", "placeholder", "v-allow-empty", "validator"],
	data: function () {
		let values = this.values;
		if (values == null) {
			values = [];
		}
		if (this.vValues != null && typeof this.vValues == "object") {
			values = this.vValues
		}
		return {
			"realValues": 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.realValues[index];
			var that = this;
			setTimeout(function () {
				that.$refs.value.focus();
			}, 200);
		},
		confirm: function () {
			if (this.value.length == 0) {
				if (typeof(this.vAllowEmpty) != "boolean" || !this.vAllowEmpty) {
					return
				}
			}
			// validate
			if (typeof(this.validator) == "function") {
				let resp = this.validator.call(this, this.value)
				if (typeof resp == "object") {
					if (typeof resp.isOk == "boolean" && !resp.isOk) {
						if (typeof resp.message == "string") {
							let that = this
							teaweb.warn(resp.message, function () {
								that.$refs.value.focus();
							})
						}
						return
					}
				}
			}
			if (this.isUpdating) {
				Vue.set(this.realValues, this.index, this.value);
			} else {
				this.realValues.push(this.value);
			}
			this.cancel()
			this.$emit("change", this.realValues)
		},
		remove: function (index) {
			this.realValues.$remove(index)
			this.$emit("change", this.realValues)
		},
		cancel: function () {
			this.isUpdating = false;
			this.isAdding = false;
			this.value = "";
		},
		updateAll: function (values) {
			this.realValues = values
		},
		addValue: function (v) {
			this.realValues.push(v)
		},
		startEditing: function () {
			this.isEditing = !this.isEditing
		},
		allValues: function () {
			return this.realValues
		}
	},
	template: ``
});
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 () {
			// 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])
			let month = parseInt(pieces[1])
			if (month < 1 || month > 12) {
				this.hasDayError = true
				return
			}
			let day = parseInt(pieces[2])
			if (day < 1 || day > 32) {
				this.hasDayError = true
				return
			}
			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
			// 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
			// 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
			let date = new Date(year, month - 1, day, hour, minute, 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()
		},
		nextHours: function (hours) {
			let date = new Date()
			date.setTime(date.getTime() + hours * 3600 * 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("label-on", {
	props: ["v-is-on"],
	template: '已启用 已停用 
'
})
// 文字代码标签
Vue.component("code-label", {
	methods: {
		click: function (args) {
			this.$emit("click", args)
		}
	},
	template: ` `
})
Vue.component("code-label-plain", {
	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("pro-warning-label", {
	template: ` 注意:通常不需要修改;如要修改,请在专家指导下进行。 `
})
Vue.component("js-page", {
	props: ["v-max"],
	data: function () {
		let max = this.vMax
		if (max == null) {
			max = 0
		}
		return {
			max: max,
			page: 1
		}
	},
	methods: {
		updateMax: function (max) {
			this.max = max
		},
		selectPage: function(page) {
			this.page = page
			this.$emit("change", page)
		}
	},
	template:``
})
/**
 * 一级菜单
 */
Vue.component("first-menu", {
	props: [],
	template: ' \
		'
});
/**
 * 更多选项
 */
Vue.component("more-options-indicator", {
	props:[],
	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)
			this.$emit("input", 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: `
\t[每页] 10条 20条 30条 40条 50条 60条 70条 80条 90条 100条 
 `
})
/**
 * 二级菜单
 */
Vue.component("second-menu", {
	template: ' \
		'
});
Vue.component("loading-message", {
	template: ``
})
Vue.component("file-textarea", {
	props: ["value"],
	data: function () {
		let value = this.value
		if (typeof value != "string") {
			value = ""
		}
		return {
			realValue: value
		}
	},
	mounted: function () {
	},
	methods: {
		dragover: function () {},
		drop: function (e) {
			let that = this
			e.dataTransfer.items[0].getAsFile().text().then(function (data) {
				that.setValue(data)
			})
		},
		setValue: function (value) {
			this.realValue = value
		},
		focus: function () {
			this.$refs.textarea.focus()
		}
	},
	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("columns-grid", {
	props: [],
	mounted: function () {
		this.columns = this.calculateColumns()
		let that = this
		window.addEventListener("resize", function () {
			that.columns = that.calculateColumns()
		})
	},
	data: function () {
		return {
			columns: "four"
		}
	},
	methods: {
		calculateColumns: function () {
			let w = window.innerWidth
			let columns = Math.floor(w / 250)
			if (columns == 0) {
				columns = 1
			}
			let columnElements = this.$el.getElementsByClassName("column")
			if (columnElements.length == 0) {
				return
			}
			let maxColumns = columnElements.length
			if (columns > maxColumns) {
				columns = maxColumns
			}
			// 添加右侧边框
			for (let index = 0; index < columnElements.length; index++) {
				let el = columnElements[index]
				el.className = el.className.replace("with-border", "")
				if (index % columns == columns - 1 || index == columnElements.length - 1 /** 最后一个 **/) {
					el.className += " with-border"
				}
			}
			switch (columns) {
				case 1:
					return "one"
				case 2:
					return "two"
				case 3:
					return "three"
				case 4:
					return "four"
				case 5:
					return "five"
				case 6:
					return "six"
				case 7:
					return "seven"
				case 8:
					return "eight"
				case 9:
					return "nine"
				case 10:
					return "ten"
				default:
					return "ten"
			}
		}
	},
	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", "v-check-domain-url", "v-is-plus"],
	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: true,
				accessLogIsOn: true
			}
			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,
			hostErr: ""
		}
	},
	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()
			this.hostErr = ""
		},
		"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
				}
			})
		},
		onChangeURLHost: function () {
			let checkDomainURL = this.vCheckDomainUrl
			if (checkDomainURL == null || checkDomainURL.length == 0) {
				return
			}
			let that = this
			Tea.action(checkDomainURL)
				.params({host: this.urlHost})
				.success(function (resp) {
					if (!resp.data.isOk) {
						that.hostErr = "在当前集群中找不到此域名,可能会影响健康检查结果。"
					} else {
						that.hostErr = ""
					}
				})
				.post()
		},
		editURL: function () {
			this.urlIsEditing = !this.urlIsEditing
		}
	},
	template: ``
})
// 将变量转换为中文
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", {
	// data-url 和 data-key 成对出现
	props: [
		"name", "title", "placeholder", "size", "v-items", "v-value",
		"data-url", // 数据源URL
		"data-key", // 数据源中数据的键名
		"data-search", // 是否启用动态搜索,如果值为on或true,则表示启用
		"width"
	],
	mounted: function () {
		if (this.dataURL.length > 0) {
			this.search("")
		}
		// 设定菜单宽度
		let searchBox = this.$refs.searchBox
		if (searchBox != null) {
			let inputWidth = searchBox.offsetWidth
			if (inputWidth != null && inputWidth > 0) {
				this.$refs.menu.style.width = inputWidth + "px"
			} else if (this.styleWidth.length > 0) {
				this.$refs.menu.style.width = this.styleWidth
			}
		}
	},
	data: function () {
		let items = this.vItems
		if (items == null || !(items instanceof Array)) {
			items = []
		}
		items = this.formatItems(items)
		// 当前选中项
		let selectedItem = null
		if (this.vValue != null) {
			let that = this
			items.forEach(function (v) {
				if (v.value == that.vValue) {
					selectedItem = v
				}
			})
		}
		let width = this.width
		if (width == null || width.length == 0) {
			width = "11em"
		} else {
			if (/\d+$/.test(width)) {
				width += "em"
			}
		}
		// data url
		let dataURL = ""
		if (typeof this.dataUrl == "string" && this.dataUrl.length > 0) {
			dataURL = this.dataUrl
		}
		return {
			allItems: items, // 原始的所有的items
			items: items.$copy(), // 候选的items
			selectedItem: selectedItem, // 选中的item
			keyword: "",
			visible: false,
			hideTimer: null,
			hoverIndex: 0,
			styleWidth: width,
			isInitial: true,
			dataURL: dataURL,
			urlRequestId: 0 // 记录URL请求ID,防止并行冲突
		}
	},
	methods: {
		search: function (keyword) {
			// 从URL中获取选项数据
			let dataUrl = this.dataURL
			let dataKey = this.dataKey
			let that = this
			let requestId = Math.random()
			this.urlRequestId = requestId
			Tea.action(dataUrl)
				.params({
					keyword: (keyword == null) ? "" : keyword
				})
				.post()
				.success(function (resp) {
					if (requestId != that.urlRequestId) {
						return
					}
					if (resp.data != null) {
						if (typeof (resp.data[dataKey]) == "object") {
							let items = that.formatItems(resp.data[dataKey])
							that.allItems = items
							that.items = items.$copy()
							if (that.isInitial) {
								that.isInitial = false
								if (that.vValue != null) {
									items.forEach(function (v) {
										if (v.value == that.vValue) {
											that.selectedItem = v
										}
									})
								}
							}
						}
					}
				})
		},
		formatItems: function (items) {
			items.forEach(function (v) {
				if (v.value == null) {
					v.value = v.id
				}
			})
			return items
		},
		reset: function () {
			this.selectedItem = null
			this.change()
			this.hoverIndex = 0
			let that = this
			setTimeout(function () {
				if (that.$refs.searchBox) {
					that.$refs.searchBox.focus()
				}
			})
		},
		clear: function () {
			this.selectedItem = null
			this.change()
			this.hoverIndex = 0
		},
		changeKeyword: function () {
			let shouldSearch = this.dataURL.length > 0 && (this.dataSearch == "on" || this.dataSearch == "true")
			this.hoverIndex = 0
			let keyword = this.keyword
			if (keyword.length == 0) {
				if (shouldSearch) {
					this.search(keyword)
				} else {
					this.items = this.allItems.$copy()
				}
				return
			}
			if (shouldSearch) {
				this.search(keyword)
			} else {
				this.items = this.allItems.$copy().filter(function (v) {
					if (v.fullname != null && v.fullname.length > 0 && teaweb.match(v.fullname, keyword)) {
						return true
					}
					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
				}
			}
		},
		setDataURL: function (dataURL) {
			this.dataURL = dataURL
		},
		reloadData: function () {
			this.search("")
		}
	},
	template: ``
})
Vue.component("search-box", {
	props: ["placeholder", "width"],
	data: function () {
		let width = this.width
		if (width == null) {
			width = "10em"
		}
		return {
			realWidth: width,
			realValue: ""
		}
	},
	methods: {
		onInput: function () {
			this.$emit("input", { value: this.realValue})
			this.$emit("change", { value: this.realValue})
		},
		clearValue: function () {
			this.realValue = ""
			this.focus()
			this.onInput()
		},
		focus: function () {
			this.$refs.valueRef.focus()
		}
	},
	template: ``
})
Vue.component("dot", {
	template: ' '
})
Vue.component("time-duration-box", {
	props: ["name", "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
		}
		let realName = ""
		if (typeof this.name == "string" && this.name.length > 0) {
			realName = this.name
		} else if (typeof this.vName == "string" && this.vName.length > 0) {
			realName = this.vName
		}
		return {
			duration: v,
			countString: (v.count >= 0) ? v.count.toString() : "",
			realName: realName
		}
	},
	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: ``
})
// 警告消息
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 (typeof (this.newValue) == "boolean" && this.newValue) || 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("url-patterns-box", {
	props: ["value"],
	data: function () {
		let patterns = []
		if (this.value != null) {
			patterns = this.value
		}
		return {
			patterns: patterns,
			isAdding: false,
			addingPattern: {"type": "wildcard", "pattern": ""},
			editingIndex: -1
		}
	},
	methods: {
		add: function () {
			this.isAdding = true
			let that = this
			setTimeout(function () {
				that.$refs.patternInput.focus()
			})
		},
		edit: function (index) {
			this.isAdding = true
			this.editingIndex = index
			this.addingPattern = {
				type: this.patterns[index].type,
				pattern: this.patterns[index].pattern
			}
		},
		confirm: function () {
			let pattern = this.addingPattern.pattern.trim()
			if (pattern.length == 0) {
				let that = this
				teaweb.warn("请输入URL", function () {
					that.$refs.patternInput.focus()
				})
				return
			}
			if (this.editingIndex < 0) {
				this.patterns.push({
					type: this.addingPattern.type,
					pattern: this.addingPattern.pattern
				})
			} else {
				this.patterns[this.editingIndex].type = this.addingPattern.type
				this.patterns[this.editingIndex].pattern = this.addingPattern.pattern
			}
			this.notifyChange()
			this.cancel()
		},
		remove: function (index) {
			this.patterns.$remove(index)
			this.cancel()
			this.notifyChange()
		},
		cancel: function () {
			this.isAdding = false
			this.addingPattern = {"type": "wildcard", "pattern": ""}
			this.editingIndex = -1
		},
		patternTypeName: function (patternType) {
			switch (patternType) {
				case "wildcard":
					return "通配符"
				case "regexp":
					return "正则"
			}
			return ""
		},
		notifyChange: function () {
			this.$emit("input", this.patterns)
		}
	},
	template: `
	
		
			[{{patternTypeName(pattern.type)}}]  {{pattern.pattern}}    
			  
			 
		 
	 
	
	
		+ 
	
 `
})
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("digit-input", {
	props: ["value", "maxlength", "size", "min", "max", "required", "placeholder"],
	mounted: function () {
		let that = this
		setTimeout(function () {
			that.check()
		})
	},
	data: function () {
		let realMaxLength = this.maxlength
		if (realMaxLength == null) {
			realMaxLength = 20
		}
		let realSize = this.size
		if (realSize == null) {
			realSize = 6
		}
		return {
			realValue: this.value,
			realMaxLength: realMaxLength,
			realSize: realSize,
			isValid: true
		}
	},
	watch: {
		realValue: function (v) {
			this.notifyChange()
		}
	},
	methods: {
		notifyChange: function () {
			let v = parseInt(this.realValue.toString(), 10)
			if (isNaN(v)) {
				v = 0
			}
			this.check()
			this.$emit("input", v)
		},
		check: function () {
			if (this.realValue == null) {
				return
			}
			let s = this.realValue.toString()
			if (!/^\d+$/.test(s)) {
				this.isValid = false
				return
			}
			let v = parseInt(s, 10)
			if (isNaN(v)) {
				this.isValid = false
			} else {
				if (this.required) {
					this.isValid = (this.min == null || this.min <= v) && (this.max == null || this.max >= v)
				} else {
					this.isValid = (v == 0 || (this.min == null || this.min <= v) && (this.max == null || this.max >= v))
				}
			}
		}
	},
	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(/\?/g, "\\?")
			word = word.replace(/\*/g, "\\*")
			word = word.replace(/\[/g, "\\[")
			word = word.replace(/{/g, "\\{")
			word = word.replace(/\./g, "\\.")
		}
		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("bits-var", {
	props: ["v-bits"],
	data: function () {
		let bits = this.vBits
		if (typeof bits != "number") {
			bits = 0
		}
		let format = teaweb.splitFormat(teaweb.formatBits(bits))
		return {
			format: format
		}
	},
	template:`
	{{format[0]}} {{format[1]}}
 `
})
Vue.component("bytes-var", {
	props: ["v-bytes"],
	data: function () {
		let bytes = this.vBytes
		if (typeof bytes != "number") {
			bytes = 0
		}
		let format = teaweb.splitFormat(teaweb.formatBytes(bytes))
		return {
			format: format
		}
	},
	template:`
	{{format[0]}} {{format[1]}}
 `
})
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("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: ["value", "v-name", "name", "v-value", "v-bottom-left", "placeholder"],
	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 = this.name
		}
		if (name == null) {
			name = "day"
		}
		let day = this.vValue
		if (day == null) {
			day = this.value
			if (day == null) {
				day = ""
			}
		}
		let placeholder = "YYYY-MM-DD"
		if (this.placeholder != null) {
			placeholder = this.placeholder
		}
		return {
			realName: name,
			realPlaceholder: placeholder,
			day: day
		}
	},
	watch: {
		value: function (v) {
			this.day = v
			let picker = this.$refs.dayInput.picker
			if (picker != null) {
				if (v != null && /^\d+-\d+-\d+$/.test(v)) {
					picker.setDate(v)
				}
			}
		}
	},
	methods: {
		change: function () {
			this.$emit("input", this.day) // support v-model,事件触发需要在 change 之前
			this.$emit("change", this.day)
		}
	},
	template: `
	 
`
})
// 排序使用的箭头
Vue.component("sort-arrow", {
	props: ["name"],
	data: function () {
		let url = window.location.toString()
		let order = ""
		let iconTitle = ""
		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 (argName != "page" && argValue != "asc" && argValue != "desc") {
						newArgs.push(v)
					}
				} else {
					newArgs.push(v)
				}
			})
		}
		if (order == "asc") {
			newArgs.push(this.name + "=desc")
			iconTitle = "当前正序排列"
		} else if (order == "desc") {
			newArgs.push(this.name + "=asc")
			iconTitle = "当前倒序排列"
		} else {
			newArgs.push(this.name + "=desc")
			iconTitle = "当前正序排列"
		}
		let qIndex = url.indexOf("?")
		if (qIndex > 0) {
			url = url.substring(0, qIndex) + "?" + newArgs.join("&")
		} else {
			url = url + "?" + newArgs.join("&")
		}
		return {
			order: order,
			url: url,
			iconTitle: iconTitle
		}
	},
	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", {
	props: ["v-user-id"],
	data: function () {
		return {}
	},
	methods: {
		change: function (userId) {
			this.$emit("change", userId)
		}
	},
	template: `
	 
`
})
Vue.component("node-cache-disk-dirs-box", {
	props: ["value", "name"],
	data: function () {
		let dirs = this.value
		if (dirs == null) {
			dirs = []
		}
		return {
			dirs: dirs,
			isEditing: false,
			isAdding: false,
			addingPath: ""
		}
	},
	methods: {
		add: function () {
			this.isAdding = true
			let that = this
			setTimeout(function () {
				that.$refs.addingPath.focus()
			}, 100)
		},
		confirm: function () {
			let addingPath = this.addingPath.trim()
			if (addingPath.length == 0) {
				let that = this
				teaweb.warn("请输入要添加的缓存目录", function () {
					that.$refs.addingPath.focus()
				})
				return
			}
			if (addingPath[0] != "/") {
				addingPath = "/" + addingPath
			}
			this.dirs.push({
				path: addingPath
			})
			this.cancel()
		},
		cancel: function () {
			this.addingPath = ""
			this.isAdding = false
			this.isEditing = false
		},
		remove: function (index) {
			let that = this
			teaweb.confirm("确定要删除此目录吗?", function () {
				that.dirs.$remove(index)
			})
		}
	},
	template: ``
})
Vue.component("node-ip-address-clusters-selector", {
	props: ["vClusters"],
	mounted: function () {
		this.checkClusters()
	},
	data: function () {
		let clusters = this.vClusters
		if (clusters == null) {
			clusters = []
		}
		return {
			clusters: clusters,
			hasCheckedCluster: false,
			clustersVisible: false
		}
	},
	methods: {
		checkClusters: function () {
			let that = this
			let b = false
			this.clusters.forEach(function (cluster) {
				if (cluster.isChecked) {
					b = true
				}
			})
			this.hasCheckedCluster = b
			return b
		},
		changeCluster: function (cluster) {
			cluster.isChecked = !cluster.isChecked
			this.checkClusters()
		},
		showClusters: function () {
			this.clustersVisible = !this.clustersVisible
		}
	},
	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: ``
})
// 节点IP地址管理(标签形式)
Vue.component("node-ip-addresses-box", {
	props: ["v-ip-addresses", "role", "v-node-id"],
	data: function () {
		let nodeId = this.vNodeId
		if (nodeId == null) {
			nodeId = 0
		}
		return {
			ipAddresses: (this.vIpAddresses == null) ? [] : this.vIpAddresses,
			supportThresholds: this.role != "ns",
			nodeId: nodeId
		}
	},
	methods: {
		// 添加IP地址
		addIPAddress: function () {
			window.UPDATING_NODE_IP_ADDRESS = null
			let that = this;
			teaweb.popup("/nodes/ipAddresses/createPopup?nodeId=" + this.nodeId + "&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 = teaweb.clone(address)
			let that = this;
			teaweb.popup("/nodes/ipAddresses/updatePopup?nodeId=" + this.nodeId + "&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}}个阈值] 
				 
				 
					  专属集群:[ {{cluster.name}},  ] 
					 
				 
				
				 
				 
			 
		 
		
	 
	
		+ 
	
 `
})
Vue.component("node-schedule-conds-box", {
	props: ["value", "v-params", "v-operators"],
	mounted: function () {
		this.formatConds(this.condsConfig.conds)
		this.$forceUpdate()
	},
	data: function () {
		let condsConfig = this.value
		if (condsConfig == null) {
			condsConfig = {
				conds: [],
				connector: "and"
			}
		}
		if (condsConfig.conds == null) {
			condsConfig.conds = []
		}
		let paramMap = {}
		this.vParams.forEach(function (param) {
			paramMap[param.code] = param
		})
		let operatorMap = {}
		this.vOperators.forEach(function (operator) {
			operatorMap[operator.code] = operator.name
		})
		return {
			condsConfig: condsConfig,
			params: this.vParams,
			paramMap: paramMap,
			operatorMap: operatorMap,
			operator: "",
			isAdding: false,
			paramCode: "",
			param: null,
			valueBandwidth: {
				count: 100,
				unit: "mb"
			},
			valueTraffic: {
				count: 1,
				unit: "gb"
			},
			valueCPU: 80,
			valueMemory: 90,
			valueLoad: 20,
			valueRate: 0
		}
	},
	watch: {
		paramCode: function (code) {
			if (code.length == 0) {
				this.param = null
			} else {
				this.param = this.params.$find(function (k, v) {
					return v.code == code
				})
			}
			this.$emit("changeparam", this.param)
		}
	},
	methods: {
		add: function () {
			this.isAdding = true
		},
		confirm: function () {
			if (this.param == null) {
				teaweb.warn("请选择参数")
				return
			}
			if (this.param.operators != null && this.param.operators.length > 0 && this.operator.length == 0) {
				teaweb.warn("请选择操作符")
				return
			}
			if (this.param.operators == null || this.param.operators.length == 0) {
				this.operator = ""
			}
			let value = null
			switch (this.param.valueType) {
				case "bandwidth": {
					if (this.valueBandwidth.unit.length == 0) {
						teaweb.warn("请选择带宽单位")
						return
					}
					let count = parseInt(this.valueBandwidth.count.toString())
					if (isNaN(count)) {
						count = 0
					}
					if (count < 0) {
						count = 0
					}
					value = {
						count: count,
						unit: this.valueBandwidth.unit
					}
				}
					break
				case "traffic": {
					if (this.valueTraffic.unit.length == 0) {
						teaweb.warn("请选择带宽单位")
						return
					}
					let count = parseInt(this.valueTraffic.count.toString())
					if (isNaN(count)) {
						count = 0
					}
					if (count < 0) {
						count = 0
					}
					value = {
						count: count,
						unit: this.valueTraffic.unit
					}
				}
					break
				case "cpu":
					let cpu = parseInt(this.valueCPU.toString())
					if (isNaN(cpu)) {
						cpu = 0
					}
					if (cpu < 0) {
						cpu = 0
					}
					if (cpu > 100) {
						cpu = 100
					}
					value = cpu
					break
				case "memory":
					let memory = parseInt(this.valueMemory.toString())
					if (isNaN(memory)) {
						memory = 0
					}
					if (memory < 0) {
						memory = 0
					}
					if (memory > 100) {
						memory = 100
					}
					value = memory
					break
				case "load":
					let load = parseInt(this.valueLoad.toString())
					if (isNaN(load)) {
						load = 0
					}
					if (load < 0) {
						load = 0
					}
					value = load
					break
				case "rate":
					let rate = parseInt(this.valueRate.toString())
					if (isNaN(rate)) {
						rate = 0
					}
					if (rate < 0) {
						rate = 0
					}
					value = rate
					break
			}
			this.condsConfig.conds.push({
				param: this.param.code,
				operator: this.operator,
				value: value
			})
			this.formatConds(this.condsConfig.conds)
			this.cancel()
		},
		cancel: function () {
			this.isAdding = false
			this.paramCode = ""
			this.param = null
		},
		remove: function (index) {
			this.condsConfig.conds.$remove(index)
		},
		formatConds: function (conds) {
			let that = this
			conds.forEach(function (cond) {
				switch (that.paramMap[cond.param].valueType) {
					case "bandwidth":
						cond.valueFormat = cond.value.count + cond.value.unit[0].toUpperCase() + cond.value.unit.substring(1) + "ps"
						return
					case "traffic":
						cond.valueFormat = cond.value.count + cond.value.unit.toUpperCase()
						return
					case "cpu":
						cond.valueFormat = cond.value + "%"
						return
					case "memory":
						cond.valueFormat = cond.value + "%"
						return
					case "load":
						cond.valueFormat = cond.value
						return
					case "rate":
						cond.valueFormat = cond.value + "/秒"
						return
				}
			})
		}
	},
	template: `
	 
	
	
	
		
			
				{{paramMap[cond.param].name}} 
					{{operatorMap[cond.operator]}}  {{cond.valueFormat}}  
					   
				 
			 
			  且 或    
		 
	 
	
	
	
	
		+ 
	
 `
})
Vue.component("node-schedule-action-box", {
	props: ["value", "v-actions"],
	data: function () {
		let actionConfig = this.value
		if (actionConfig == null) {
			actionConfig = {
				code: "",
				params: {}
			}
		}
		return {
			actions: this.vActions,
			currentAction: null,
			actionConfig: actionConfig
		}
	},
	watch: {
		"actionConfig.code": function (actionCode) {
			if (actionCode.length == 0) {
				this.currentAction = null
			} else {
				this.currentAction = this.actions.$find(function (k, v) {
					return v.code == actionCode
				})
			}
			this.actionConfig.params = {}
		}
	},
	template: ``
})
// 节点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)}}
							   
							  
							 
						 
					 
					
					
					
					
						+ 
					
				 
				
					
					
						
							{{actionName(action.action)}}  
							
到{{action.options.ips.join(", ")}} 
							({{action.options.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: ``
})
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
		}
	},
	watch: {
		levelCode: function (code) {
			this.$emit("change", code)
		}
	},
	template: `
	
	{{level.name}} 
 
`
})
Vue.component("node-schedule-conds-viewer", {
	props: ["value", "v-params", "v-operators"],
	mounted: function () {
		this.formatConds(this.condsConfig.conds)
		this.$forceUpdate()
	},
	data: function () {
		let paramMap = {}
		this.vParams.forEach(function (param) {
			paramMap[param.code] = param
		})
		let operatorMap = {}
		this.vOperators.forEach(function (operator) {
			operatorMap[operator.code] = operator.name
		})
		return {
			condsConfig: this.value,
			paramMap: paramMap,
			operatorMap: operatorMap
		}
	},
	methods: {
		formatConds: function (conds) {
			let that = this
			conds.forEach(function (cond) {
				switch (that.paramMap[cond.param].valueType) {
					case "bandwidth":
						cond.valueFormat = cond.value.count + cond.value.unit[0].toUpperCase() + cond.value.unit.substring(1) + "ps"
						return
					case "traffic":
						cond.valueFormat = cond.value.count + cond.value.unit.toUpperCase()
						return
					case "cpu":
						cond.valueFormat = cond.value + "%"
						return
					case "memory":
						cond.valueFormat = cond.value + "%"
						return
					case "load":
						cond.valueFormat = cond.value
						return
					case "rate":
						cond.valueFormat = cond.value + "/秒"
						return
				}
			})
		}
	},
	template: `
	
		
			{{paramMap[cond.param].name}} 
				{{operatorMap[cond.operator]}}  {{cond.valueFormat}}  
			 
		 
		  且 或    
	 
`
})
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.code, 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", "v-provider-name"],
	data: function () {
		let domainId = this.vDomainId
		if (domainId == null) {
			domainId = 0
		}
		let domainName = this.vDomainName
		if (domainName == null) {
			domainName = ""
		}
		let providerName = this.vProviderName
		if (providerName == null) {
			providerName = ""
		}
		return {
			domainId: domainId,
			domainName: domainName,
			providerName: providerName
		}
	},
	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.providerName = resp.data.providerName
					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.providerName = resp.data.providerName
					that.change()
				}
			})
		},
		change: function () {
			this.$emit("change", {
				id: this.domainId,
				name: this.domainName
			})
		}
	},
	template: `
	 
	
		
			{{providerName}} »   {{domainName}}
			 
			 
		 
	 
	
 `
})
Vue.component("dns-resolver-config-box", {
	props:["v-dns-resolver-config"],
	data: function () {
		let config = this.vDnsResolverConfig
		if (config == null) {
			config = {
				type: "default"
			}
		}
		return {
			config: config,
			types: [
				{
					name: "默认",
					code: "default"
				},
				{
					name: "CGO",
					code: "cgo"
				},
				{
					name: "Go原生",
					code: "goNative"
				},
			]
		}
	},
	template: ``
})
Vue.component("dns-resolvers-config-box", {
	props: ["value", "name"],
	data: function () {
		let resolvers = this.value
		if (resolvers == null) {
			resolvers = []
		}
		let name = this.name
		if (name == null || name.length == 0) {
			name = "dnsResolversJSON"
		}
		return {
			formName: name,
			resolvers: resolvers,
			host: "",
			isAdding: false
		}
	},
	methods: {
		add: function () {
			this.isAdding = true
			let that = this
			setTimeout(function () {
				that.$refs.hostRef.focus()
			})
		},
		confirm: function () {
			let host = this.host.trim()
			if (host.length == 0) {
				let that = this
				setTimeout(function () {
					that.$refs.hostRef.focus()
				})
				return
			}
			this.resolvers.push({
				host: host,
				port: 0, // TODO
				protocol: "" // TODO
			})
			this.cancel()
		},
		cancel: function () {
			this.isAdding = false
			this.host = ""
			this.port = 0
			this.protocol = ""
		},
		remove: function (index) {
			this.resolvers.$remove(index)
		}
	},
	template: `
	 
	
		
			{{resolver.protocol}} {{resolver.host}}
:{{resolver.port}} 
			 
			 
		 
	 
	
	
	
	
		+ 
	
 `
})
Vue.component("ad-instance-objects-box", {
	props: ["v-objects", "v-user-id"],
	mounted: function () {
		this.getUserServers(1)
	},
	data: function () {
		let objects = this.vObjects
		if (objects == null) {
			objects = []
		}
		let objectCodes = []
		objects.forEach(function (v) {
			objectCodes.push(v.code)
		})
		return {
			userId: this.vUserId,
			objects: objects,
			objectCodes: objectCodes,
			isAdding: true,
			servers: [],
			serversIsLoading: false
		}
	},
	methods: {
		add: function () {
			this.isAdding = true
		},
		cancel: function () {
			this.isAdding = false
		},
		remove: function (index) {
			let that = this
			teaweb.confirm("确定要删除此防护对象吗?", function () {
				that.objects.$remove(index)
				that.notifyChange()
			})
		},
		removeObjectCode: function (objectCode) {
			let index = -1
			this.objectCodes.forEach(function (v, k) {
				if (objectCode == v) {
					index = k
				}
			})
			if (index >= 0) {
				this.objects.$remove(index)
				this.notifyChange()
			}
		},
		getUserServers: function (page) {
			if (Tea.Vue == null) {
				let that = this
				setTimeout(function () {
					that.getUserServers(page)
				}, 100)
				return
			}
			let that = this
			this.serversIsLoading = true
			Tea.Vue.$post(".userServers")
				.params({
					userId: this.userId,
					page: page,
					pageSize: 5
				})
				.success(function (resp) {
					that.servers = resp.data.servers
					that.$refs.serverPage.updateMax(resp.data.page.max)
					that.serversIsLoading = false
				})
				.error(function () {
					that.serversIsLoading = false
				})
		},
		changeServerPage: function (page) {
			this.getUserServers(page)
		},
		selectServerObject: function (server) {
			if (this.existObjectCode("server:" + server.id)) {
				return
			}
			this.objects.push({
				"type": "server",
				"code": "server:" + server.id,
				"id": server.id,
				"name": server.name
			})
			this.notifyChange()
		},
		notifyChange: function () {
			let objectCodes = []
			this.objects.forEach(function (v) {
				objectCodes.push(v.code)
			})
			this.objectCodes = objectCodes
		},
		existObjectCode: function (objectCode) {
			let found = false
			this.objects.forEach(function (v) {
				if (v.code == objectCode) {
					found = true
				}
			})
			return found
		}
	},
	template: `
	 
	
	
	
	
	
	
	
		
			
				对象类型 
				网站 
			 
			
			
				网站列表 
				
					加载中... 
					暂时还没有可选的网站。
					
						
							
								网站名称 
								操作 
							 	
						 
						
							{{server.name}} 
							
								选中 
								取消  
							 
						 
					
					
					 
				 
			 
		
	 
	
	
	
		+ 
	
 `
})
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":"文件扩展名","description":"根据URL中的文件路径扩展名进行过滤","component":"http-cond-url-extension","paramsTitle":"扩展名列表","isRequest":true,"caseInsensitive":false},{"type":"url-eq-index","name":"首页","description":"检查URL路径是为\"/\"","component":"http-cond-url-eq-index","paramsTitle":"URL完整路径","isRequest":true,"caseInsensitive":false},{"type":"url-all","name":"全站","description":"全站所有URL","component":"http-cond-url-all","paramsTitle":"URL完整路径","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":"url-wildcard-match","name":"URL通配符","description":"使用通配符检查URL中的文件路径是否一致","component":"http-cond-url-wildcard-match","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":"wildcard match"},{"description":"判断是否和指定的通配符不匹配","name":"通配符不匹配","op":"wildcard not match"},{"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在某个范围之内,范围格式可以是英文逗号分隔的\u003ccode-label\u003e开始IP,结束IP\u003c/code-label\u003e,比如\u003ccode-label\u003e192.168.1.100,192.168.2.200\u003c/code-label\u003e,或者CIDR格式的ip/bits,比如\u003ccode-label\u003e192.168.2.1/24\u003c/code-label\u003e","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"}]
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":"${requestPathExtension}","description":"请求路径中的文件扩展名,包括点符号,比如.html、.png","name":"请求文件扩展名"},{"code":"${requestPathLowerExtension}","description":"请求路径中的文件扩展名,其中大写字母会被自动转换为小写,包括点符号,比如.html、.png","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":"${cname}","description":"比如38b48e4f.goedge.cn","name":"当前网站的CNAME"},{"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":"${requestPathLowerExtension}","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.WAF_RULE_OPERATORS = [{"name":"数值大于","code":"gt","description":"使用数值对比大于,对比值需要是一个数字","caseInsensitive":"none"},{"name":"数值大于等于","code":"gte","description":"使用数值对比大于等于,对比值需要是一个数字","caseInsensitive":"none"},{"name":"数值小于","code":"lt","description":"使用数值对比小于,对比值需要是一个数字","caseInsensitive":"none"},{"name":"数值小于等于","code":"lte","description":"使用数值对比小于等于,对比值需要是一个数字","caseInsensitive":"none"},{"name":"数值等于","code":"eq","description":"使用数值对比等于,对比值需要是一个数字","caseInsensitive":"none"},{"name":"数值不等于","code":"neq","description":"使用数值对比不等于,对比值需要是一个数字","caseInsensitive":"none"},{"name":"字符串等于","code":"eq string","description":"使用字符串对比等于","caseInsensitive":"no"},{"name":"字符串不等于","code":"neq string","description":"使用字符串对比不等于","caseInsensitive":"no"},{"name":"正则匹配","code":"match","description":"使用正则表达式匹配,在头部使用(?i)表示不区分大小写,\u003ca href=\"https://goedge.cn/docs/Appendix/Regexp/Index.md\" target=\"_blank\"\u003e正则表达式语法 \u0026raquo;\u003c/a\u003e","caseInsensitive":"yes"},{"name":"正则不匹配","code":"not match","description":"使用正则表达式不匹配,在头部使用(?i)表示不区分大小写,\u003ca href=\"https://goedge.cn/docs/Appendix/Regexp/Index.md\" target=\"_blank\"\u003e正则表达式语法 \u0026raquo;\u003c/a\u003e","caseInsensitive":"yes"},{"name":"包含字符串","code":"contains","description":"包含某个字符串,比如Hello World包含了World","caseInsensitive":"no"},{"name":"不包含字符串","code":"not contains","description":"不包含某个字符串,比如Hello字符串中不包含Hi","caseInsensitive":"no"},{"name":"包含任一字符串","code":"contains any","description":"包含字符串列表中的任意一个,比如/hello/world包含/hello和/hi中的/hello,每行一个字符串","caseInsensitive":"no"},{"name":"包含所有字符串","code":"contains all","description":"包含字符串列表中的所有字符串,比如/hello/world必须包含/hello和/world,每行一个字符串","caseInsensitive":"no"},{"name":"包含前缀","code":"prefix","description":"包含字符串前缀部分,比如/hello前缀会匹配/hello, /hello/world等","caseInsensitive":"no"},{"name":"包含后缀","code":"suffix","description":"包含字符串后缀部分,比如/hello后缀会匹配/hello, /hi/hello等","caseInsensitive":"no"},{"name":"包含二进制数据","code":"contains binary","description":"包含一组二进制数据","caseInsensitive":"no"},{"name":"不包含二进制数据","code":"not contains binary","description":"不包含一组二进制数据","caseInsensitive":"no"},{"name":"包含索引","code":"has key","description":"对于一组数据拥有某个键值或者索引","caseInsensitive":"no"},{"name":"版本号大于","code":"version gt","description":"对比版本号大于","caseInsensitive":"no"},{"name":"版本号小于","code":"version lt","description":"对比版本号小于","caseInsensitive":"no"},{"name":"版本号范围","code":"version range","description":"判断版本号在某个范围内,格式为version1,version2","caseInsensitive":"no"},{"name":"IP等于","code":"eq ip","description":"将参数转换为IP进行对比,只能对比单个IP","caseInsensitive":"none"},{"name":"在一组IP中","code":"in ip list","description":"判断参数IP在一组IP内,每行一个IP","caseInsensitive":"none"},{"name":"IP大于","code":"gt ip","description":"将参数转换为IP进行对比","caseInsensitive":"none"},{"name":"IP大于等于","code":"gte ip","description":"将参数转换为IP进行对比","caseInsensitive":"none"},{"name":"IP小于","code":"lt ip","description":"将参数转换为IP进行对比","caseInsensitive":"none"},{"name":"IP小于等于","code":"lte ip","description":"将参数转换为IP进行对比","caseInsensitive":"none"},{"name":"IP范围","code":"ip range","description":"IP在某个范围之内,范围格式可以是英文逗号分隔的\u003ccode-label\u003e开始IP,结束IP\u003c/code-label\u003e,比如\u003ccode-label\u003e192.168.1.100,192.168.2.200\u003c/code-label\u003e;或者CIDR格式的ip/bits,比如\u003ccode-label\u003e192.168.2.1/24\u003c/code-label\u003e;或者单个IP。可以填写多行,每行一个IP范围。","caseInsensitive":"none"},{"name":"不在IP范围","code":"not ip range","description":"IP不在某个范围之内,范围格式可以是英文逗号分隔的\u003ccode-label\u003e开始IP,结束IP\u003c/code-label\u003e,比如\u003ccode-label\u003e192.168.1.100,192.168.2.200\u003c/code-label\u003e;或者CIDR格式的ip/bits,比如\u003ccode-label\u003e192.168.2.1/24\u003c/code-label\u003e;或者单个IP。可以填写多行,每行一个IP范围。","caseInsensitive":"none"},{"name":"IP取模10","code":"ip mod 10","description":"对IP参数值取模,除数为10,对比值为余数","caseInsensitive":"none"},{"name":"IP取模100","code":"ip mod 100","description":"对IP参数值取模,除数为100,对比值为余数","caseInsensitive":"none"},{"name":"IP取模","code":"ip mod","description":"对IP参数值取模,对比值格式为:除数,余数,比如10,1","caseInsensitive":"none"}]